pyvale 2025.7.1__cp311-cp311-win_amd64.whl → 2025.8.1__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyvale might be problematic. Click here for more details.

Files changed (186) hide show
  1. pyvale/__init__.py +12 -92
  2. pyvale/blender/__init__.py +23 -0
  3. pyvale/{pyvaleexceptions.py → blender/blenderexceptions.py} +0 -3
  4. pyvale/{blenderlightdata.py → blender/blenderlightdata.py} +3 -3
  5. pyvale/{blendermaterialdata.py → blender/blendermaterialdata.py} +1 -1
  6. pyvale/{blenderrenderdata.py → blender/blenderrenderdata.py} +5 -3
  7. pyvale/{blenderscene.py → blender/blenderscene.py} +33 -30
  8. pyvale/{blendertools.py → blender/blendertools.py} +14 -10
  9. pyvale/dataset/__init__.py +7 -0
  10. pyvale/dataset/dataset.py +443 -0
  11. pyvale/dic/__init__.py +20 -0
  12. pyvale/dic/cpp/dicfourier.cpp +36 -4
  13. pyvale/dic/cpp/dicinterpolator.cpp +56 -1
  14. pyvale/dic/cpp/dicmain.cpp +24 -19
  15. pyvale/dic/cpp/dicoptimizer.cpp +6 -1
  16. pyvale/dic/cpp/dicscanmethod.cpp +32 -32
  17. pyvale/dic/cpp/dicsignalhandler.cpp +16 -0
  18. pyvale/dic/cpp/dicstrain.cpp +7 -3
  19. pyvale/dic/cpp/dicutil.cpp +79 -23
  20. pyvale/{dic2d.py → dic/dic2d.py} +51 -29
  21. pyvale/dic/dic2dconv.py +6 -0
  22. pyvale/dic/dic2dcpp.cp311-win_amd64.pyd +0 -0
  23. pyvale/{dicchecks.py → dic/dicchecks.py} +28 -16
  24. pyvale/dic/dicdataimport.py +370 -0
  25. pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +169 -12
  26. pyvale/{dicresults.py → dic/dicresults.py} +4 -1
  27. pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
  28. pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
  29. pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
  30. pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
  31. pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
  32. pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
  33. pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
  34. pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
  35. pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
  36. pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
  37. pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
  38. pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
  39. pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
  40. pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
  41. pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
  42. pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
  43. pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
  44. pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
  45. pyvale/examples/basics/ex5_nomesh.py +24 -0
  46. pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
  47. pyvale/examples/dic/ex1_region_of_interest.py +6 -3
  48. pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
  49. pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
  50. pyvale/examples/dic/ex4_dic_blender.py +17 -15
  51. pyvale/examples/dic/ex5_dic_challenge.py +19 -14
  52. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
  53. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
  54. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
  55. pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
  56. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
  57. pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
  58. pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
  59. pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
  60. pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
  61. pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
  62. pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
  63. pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
  64. pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
  65. pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
  66. pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
  67. pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
  68. pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
  69. pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
  70. pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
  71. pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
  72. pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
  73. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
  74. pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
  75. pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
  76. pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
  77. pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
  78. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
  79. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
  80. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
  81. pyvale/mooseherder/__init__.py +32 -0
  82. pyvale/mooseherder/directorymanager.py +416 -0
  83. pyvale/mooseherder/exodusreader.py +763 -0
  84. pyvale/mooseherder/gmshrunner.py +163 -0
  85. pyvale/mooseherder/inputmodifier.py +236 -0
  86. pyvale/mooseherder/mooseconfig.py +226 -0
  87. pyvale/mooseherder/mooseherd.py +527 -0
  88. pyvale/mooseherder/mooserunner.py +303 -0
  89. pyvale/mooseherder/outputreader.py +22 -0
  90. pyvale/mooseherder/simdata.py +92 -0
  91. pyvale/mooseherder/simrunner.py +31 -0
  92. pyvale/mooseherder/sweepreader.py +356 -0
  93. pyvale/mooseherder/sweeptools.py +76 -0
  94. pyvale/sensorsim/__init__.py +82 -0
  95. pyvale/{camera.py → sensorsim/camera.py} +7 -7
  96. pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
  97. pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
  98. pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
  99. pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
  100. pyvale/{cython → sensorsim/cython}/rastercyth.cp311-win_amd64.pyd +0 -0
  101. pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
  102. pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
  103. pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
  104. pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
  105. pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
  106. pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
  107. pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
  108. pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
  109. pyvale/sensorsim/exceptions.py +8 -0
  110. pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
  111. pyvale/{field.py → sensorsim/field.py} +1 -1
  112. pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
  113. pyvale/sensorsim/fieldinterp.py +37 -0
  114. pyvale/sensorsim/fieldinterpmesh.py +124 -0
  115. pyvale/sensorsim/fieldinterppoints.py +55 -0
  116. pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
  117. pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
  118. pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
  119. pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
  120. pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
  121. pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
  122. pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
  123. pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
  124. pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
  125. pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
  126. pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
  127. pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
  128. pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
  129. pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
  130. pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
  131. pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
  132. pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
  133. pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
  134. pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
  135. pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
  136. pyvale/sensorsim/simtools.py +174 -0
  137. pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
  138. pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
  139. pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
  140. pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
  141. pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
  142. pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
  143. pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
  144. pyvale/simcases/case17.geo +3 -0
  145. pyvale/simcases/case17.i +4 -4
  146. pyvale/simcases/run_1case.py +1 -9
  147. pyvale/simcases/run_all_cases.py +1 -1
  148. pyvale/simcases/run_build_case.py +1 -1
  149. pyvale/simcases/run_example_cases.py +1 -1
  150. pyvale/verif/__init__.py +12 -0
  151. pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
  152. pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
  153. pyvale/verif/psens.py +125 -0
  154. pyvale/verif/psensconst.py +18 -0
  155. pyvale/verif/psensmech.py +227 -0
  156. pyvale/verif/psensmultiphys.py +187 -0
  157. pyvale/verif/psensscalar.py +347 -0
  158. pyvale/verif/psenstensor.py +123 -0
  159. pyvale/verif/psensvector.py +116 -0
  160. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
  161. pyvale-2025.8.1.dist-info/RECORD +260 -0
  162. pyvale/dataset.py +0 -415
  163. pyvale/dic2dcpp.cp311-win_amd64.pyd +0 -0
  164. pyvale/dicdataimport.py +0 -247
  165. pyvale/simtools.py +0 -67
  166. pyvale-2025.7.1.dist-info/RECORD +0 -211
  167. /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
  168. /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
  169. /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
  170. /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
  171. /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
  172. /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
  173. /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
  174. /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
  175. /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
  176. /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
  177. /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
  178. /pyvale/{output.py → sensorsim/output.py} +0 -0
  179. /pyvale/{raster.py → sensorsim/raster.py} +0 -0
  180. /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
  181. /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
  182. /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
  183. /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
  184. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
  185. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
  186. {pyvale-2025.7.1.dist-info → pyvale-2025.8.1.dist-info}/top_level.txt +0 -0
@@ -9,33 +9,38 @@
9
9
  import numpy as np
10
10
  from pathlib import Path
11
11
 
12
- # import cython module
13
- import pyvale.dic2dcpp as dic2dcpp
14
- import pyvale.dicchecks as dicchecks
15
-
16
-
17
- def dic_2d(reference: np.ndarray | str | Path,
18
- deformed: np.ndarray | str | Path,
19
- roi_mask: np.ndarray,
20
- seed: list[int],
21
- subset_size: int = 21,
22
- subset_step: int = 10,
23
- correlation_criteria: str="ZNSSD",
24
- shape_function: str="AFFINE",
25
- interpolation_routine: str="BICUBIC",
26
- max_iterations: int=40,
27
- opt_precision: float=0.001,
28
- opt_threshold: float=0.9,
29
- bf_threshold: float=0.6,
30
- max_displacement: int=128,
31
- scanning_method: str="RG",
32
- fft_mad: bool=False,
33
- fft_mad_scale: float=3.0,
34
- output_at_end: bool=False,
35
- output_basepath: Path | str = "./",
36
- output_binary: bool=False,
37
- output_prefix: str="dic_results_",
38
- output_delimiter: str=",") -> None:
12
+ # pyvale
13
+ import pyvale.dic.dic2dcpp as dic2dcpp
14
+ import pyvale.dic.dicchecks as dicchecks
15
+
16
+
17
+ def two_dimensional(reference: np.ndarray | str | Path,
18
+ deformed: np.ndarray | str | Path,
19
+ roi_mask: np.ndarray,
20
+ seed: list[int] | list[np.int32] | np.ndarray,
21
+ subset_size: int = 21,
22
+ subset_step: int = 10,
23
+ correlation_criteria: str="ZNSSD",
24
+ shape_function: str="AFFINE",
25
+ interpolation_routine: str="BICUBIC",
26
+ max_iterations: int=40,
27
+ opt_precision: float=0.001,
28
+ opt_threshold: float=0.9,
29
+ bf_threshold: float=0.6,
30
+ num_threads: int | None = None,
31
+ max_displacement: int=128,
32
+ scanning_method: str="RG",
33
+ fft_mad: bool=False,
34
+ fft_mad_scale: float=3.0,
35
+ output_at_end: bool=False,
36
+ output_basepath: Path | str = "./",
37
+ output_binary: bool=False,
38
+ output_prefix: str="dic_results_",
39
+ output_delimiter: str=",",
40
+ output_unconverged: bool=False,
41
+ output_shape_params: bool=False,
42
+ debug_level: int=0) -> None:
43
+
39
44
  """
40
45
  Perform 2D Digital Image Correlation (DIC) between a reference image and one or more deformed images.
41
46
 
@@ -51,7 +56,7 @@ def dic_2d(reference: np.ndarray | str | Path,
51
56
  The deformed image(s) (3D array for multiple images) or path/pattern to image files.
52
57
  roi_mask : np.ndarray
53
58
  A binary mask indicating the Region of Interest (ROI) for analysis (same size as image).
54
- seed : list of int, optional
59
+ seed : list[int], list[np.int32] or np.ndarray
55
60
  Coordinates `[x, y]` of the seed point for Reliability-Guided (RG) scanning, default is empty.
56
61
  subset_size : int, optional
57
62
  Size of the square subset window in pixels (default: 21).
@@ -70,6 +75,8 @@ def dic_2d(reference: np.ndarray | str | Path,
70
75
  Precision threshold for iterative optimization convergence (default: 0.001).
71
76
  opt_threshold : float, optional
72
77
  Minimum correlation improvement threshold to continue iterations (default: 0.9).
78
+ num_threads : int, optional
79
+ Number of threads to use for parallel computation (default: None, uses all available).
73
80
  bf_threshold : float, optional
74
81
  Correlation threshold used in rigid bruteforce check for a subset to be considered a
75
82
  good match(default: 0.6).
@@ -103,6 +110,12 @@ def dic_2d(reference: np.ndarray | str | Path,
103
110
  changed to ".csv" or ".dic2d" depending on whether outputting as a binary.
104
111
  output_delimiter : str, optional
105
112
  Delimiter used in text output files (default: ",").
113
+ output_unconverged : bool, optional
114
+ If True, subset results as they were for the final iteration of the optimization
115
+ that did not converge will be saved (default: False).
116
+ output_shape_params : bool, optional
117
+ If True, all shape parameters will be saved in the output files (default: False).
118
+ debug_level:
106
119
 
107
120
  Returns
108
121
  -------
@@ -151,6 +164,7 @@ def dic_2d(reference: np.ndarray | str | Path,
151
164
  config.filenames = filenames
152
165
  config.fft_mad = fft_mad
153
166
  config.fft_mad_scale = fft_mad_scale
167
+ config.debug_level = debug_level
154
168
 
155
169
  # assigning c++ struct vals for save config
156
170
  saveconf = dic2dcpp.SaveConfig()
@@ -159,6 +173,14 @@ def dic_2d(reference: np.ndarray | str | Path,
159
173
  saveconf.prefix = output_prefix
160
174
  saveconf.delimiter = output_delimiter
161
175
  saveconf.at_end = output_at_end
176
+ saveconf.output_unconverged = output_unconverged
177
+ saveconf.shape_params = output_shape_params
178
+
179
+
180
+ #set the number of OMP threads
181
+ if num_threads is not None:
182
+ dic2dcpp.set_num_threads(num_threads)
162
183
 
163
184
  # calling the c++ dic engine
164
- dic2dcpp.dic_engine(ref_arr, def_arr, roi_c, config, saveconf)
185
+ with dic2dcpp.ostream_redirect(stdout=True, stderr=True):
186
+ dic2dcpp.dic_engine(ref_arr, def_arr, roi_c, config, saveconf)
@@ -0,0 +1,6 @@
1
+ # ================================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ================================================================================
6
+
Binary file
@@ -55,16 +55,18 @@ def check_output_directory(output_basepath: str,
55
55
 
56
56
  if conflicting_files:
57
57
  conflicting_files.sort()
58
- print("The following output files already exist and may be overwritten:")
58
+ print("WARNING: The following output files already exist and may be overwritten:")
59
59
  for f in conflicting_files:
60
60
  print(f" - {os.path.join(output_basepath, f)}")
61
61
  print("")
62
62
 
63
- user_input = input("Do you want to continue? (y/n): ").strip().lower()
64
63
 
65
- if user_input not in ("y", "yes", "Y", "YES"):
66
- print("Aborting to avoid overwriting data in output directory.")
67
- exit(0)
64
+ ###### TURNING USER INPUT OFF FOR NOW ######
65
+ # user_input = input("Do you want to continue? (y/n): ").strip().lower()
66
+
67
+ # if user_input not in ("y", "yes", "Y", "YES"):
68
+ # print("Aborting to avoid overwriting data in output directory.")
69
+ # exit(0)
68
70
 
69
71
 
70
72
  def check_correlation_criteria(correlation_criteria: str) -> None:
@@ -247,7 +249,7 @@ def check_subsets(subset_size: int, subset_step: int) -> None:
247
249
 
248
250
 
249
251
 
250
- def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_method: str, px_hori: int, px_vert: int, subset_size: int, subset_step: int) -> list[int]:
252
+ def check_and_update_rg_seed(seed: list[int] | list[np.int32] | np.ndarray, roi_mask: np.ndarray, scanning_method: str, px_hori: int, px_vert: int, subset_size: int, subset_step: int) -> list[int]:
251
253
  """
252
254
  Validate and update the region-growing seed location to align with image bounds and subset spacing.
253
255
 
@@ -260,7 +262,7 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
260
262
 
261
263
  Parameters
262
264
  ----------
263
- seed : list of int
265
+ seed : list[int], list[np.int32] or np.ndarray
264
266
  The initial seed coordinates as a list of two integers: [x, y].
265
267
  roi_mask : np.ndarray
266
268
  A 2D binary mask (same size as the image) indicating the region of interest.
@@ -287,11 +289,19 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
287
289
  if scanning_method != "RG":
288
290
  return [0,0]
289
291
 
290
- if not (isinstance(seed, list) and len(seed) == 2 and all(isinstance(coord, int) for coord in seed)):
291
- raise ValueError("Reliability Guided seed is either missing or has been defined incorrectly. must be a list of two integers: seed=[x, y]")
292
+ if (len(seed) != 2):
293
+ raise ValueError(f"Reliability Guided seed does not have two elements: " \
294
+ f"seed={seed}. Seed " \
295
+ f" must be a list of two integers: seed=[x, y]")
296
+
297
+ if not isinstance(seed, (list, np.ndarray)) or not all(isinstance(coord, (int, np.int32)) for coord in seed):
298
+ raise ValueError("Reliability Guided seed must be a list of two integers: seed=[x, y]")
292
299
 
293
300
  x, y = seed
294
301
 
302
+ if x < 0 or x >= px_hori or y < 0 or y >= px_vert:
303
+ raise ValueError(f"Seed ({x}, {y}) goes outside the image bounds: ({px_hori}, {px_vert})")
304
+
295
305
  corner_x = x - subset_size//2
296
306
  corner_y = y - subset_size//2
297
307
 
@@ -302,17 +312,11 @@ def check_and_update_rg_seed(seed: list[int], roi_mask: np.ndarray, scanning_met
302
312
  new_x = round_to_step(corner_x, subset_step)
303
313
  new_y = round_to_step(corner_y, subset_step)
304
314
 
305
-
306
- # Clamp to image bounds
307
- new_x = min(max(new_x, 0), px_hori - 1)
308
- new_y = min(max(new_y, 0), px_vert - 1)
309
-
310
315
  # check if all pixel values within the seed location are within the ROI
311
316
  # seed coordinates are the central pixel to the subset
312
317
  max_x = new_x + subset_size//2+1
313
318
  max_y = new_y + subset_size//2+1
314
319
 
315
-
316
320
  # Check if all pixel values in the ROI are valid
317
321
  for i in range(corner_x, max_x):
318
322
  for j in range(corner_y, max_y):
@@ -435,9 +439,17 @@ def check_and_get_images(reference: np.ndarray | str | Path,
435
439
  ref_arr = reference
436
440
  def_arr = deformed
437
441
 
438
- if (reference.shape != deformed[0].shape or reference.shape != roi.shape):
442
+ # user might only pass a single deformed image. need to convert to 'stack'
443
+ if (reference.shape == deformed.shape):
444
+ def_arr = def_arr.reshape((1,def_arr.shape[0],def_arr.shape[1]))
445
+
446
+ elif (reference.shape != deformed[0].shape or reference.shape != roi.shape):
439
447
  raise ValueError(f"Shape mismatch: reference {reference.shape}, "
440
448
  f"deformed[0] {deformed[0].shape}, roi {roi.shape}")
449
+
450
+
451
+ # need to set some dummy filenames in the case that the user passes numpy arrays
452
+ filenames = [f"deformed image {i}" for i in range(def_arr.shape[0])]
441
453
 
442
454
  # it might be the case that the roi has been manipulated prior to DIC run
443
455
  # and therefore we need to to prevent the roi mask from being a 'view'
@@ -0,0 +1,370 @@
1
+ # ================================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ================================================================================
6
+
7
+
8
+
9
+ import numpy as np
10
+ import glob
11
+ import os
12
+ from pathlib import Path
13
+
14
+ # Pyvale modules
15
+ from pyvale.dic.dicresults import Results
16
+
17
+ """
18
+ Module responsible for handling importing of DIC results from completed
19
+ calculations.
20
+ """
21
+
22
+
23
+ def data_import(data: str | Path,
24
+ binary: bool = False,
25
+ layout: str = "matrix",
26
+ delimiter: str = ",") -> Results:
27
+ """
28
+ Import DIC result data from human readable text or binary files.
29
+
30
+ Parameters
31
+ ----------
32
+
33
+ data : str or pathlib.Path
34
+ Path pattern to the data files (can include wildcards). Default is "./".
35
+
36
+ layout : str, optional
37
+ Format of the output data layout: "column" (flat array per frame) or "matrix"
38
+ (reshaped grid per frame). Default is "column".
39
+
40
+ binary : bool, optional
41
+ If True, expects files in a specific binary format. If False, expects text data.
42
+ Default is False.
43
+
44
+ delimiter : str, optional
45
+ Delimiter used in text data files. Ignored if binary=True. Default is a single space.
46
+
47
+ Returns
48
+ -------
49
+ Results
50
+ A named container with the following fields:
51
+ - ss_x, ss_y (grid arrays if layout=="matrix"; otherwise, 1D integer arrays)
52
+ - u, v, m, converged, cost, ftol, xtol, niter (arrays with shape depending on layout)
53
+ - filenames (python list)
54
+
55
+ Raises
56
+ ------
57
+ ValueError:
58
+ If `layout` is not "column" or "matrix", or text data has insufficient columns,
59
+ or binary rows are malformed.
60
+ import cython module
61
+ FileNotFoundError:
62
+ If no matching data files are found.
63
+ """
64
+
65
+
66
+ print("")
67
+ print("Attempting DIC Data import...")
68
+ print("")
69
+
70
+ # convert to str
71
+ if isinstance(data, Path):
72
+ data = str(data)
73
+
74
+ files = sorted(glob.glob(data))
75
+ filenames = files
76
+ if not files:
77
+ raise FileNotFoundError(f"No results found in: {data}")
78
+
79
+ print(f"Found {len(files)} files containing DIC results:")
80
+ for file in files:
81
+ print(f" - {file}")
82
+ print("")
83
+
84
+
85
+ # Read first file to define reference coordinates
86
+ read_data = read_binary if binary else read_text
87
+ ss_x_ref, ss_y_ref, *fields = read_data(files[0], delimiter=delimiter)
88
+ frames = [list(fields)]
89
+
90
+ for file in files[1:]:
91
+ ss_x, ss_y, *f = read_data(file, delimiter)
92
+ if not (np.array_equal(ss_x_ref, ss_x) and np.array_equal(ss_y_ref, ss_y)):
93
+ raise ValueError("Mismatch in coordinates across frames.")
94
+ frames.append(f)
95
+
96
+ # Stack results (except ss_x and ss_y) into arrays
97
+ arrays = [np.stack([frame[i] for frame in frames]) for i in range(len(fields))]
98
+
99
+ if layout == "matrix":
100
+
101
+ # convert x and y data to meshgrid
102
+ x_unique = np.unique(ss_x_ref)
103
+ y_unique = np.unique(ss_y_ref)
104
+ X, Y = np.meshgrid(x_unique, y_unique)
105
+ shape = (len(files), len(y_unique), len(x_unique))
106
+
107
+
108
+ arrays = [to_grid(a,shape,ss_x_ref, ss_y_ref, x_unique,y_unique) for a in arrays]
109
+
110
+
111
+ # sorting out shape function parameters if they are present in the files
112
+ current_shape = arrays[0].shape # (file,x,y)
113
+ shape_params = np.zeros(())
114
+
115
+ # rigid
116
+ if len(fields) == 10:
117
+ shape_params = np.zeros(current_shape+(2,))
118
+ shape_params[:,:,:,0] = arrays[8]
119
+ shape_params[:,:,:,1] = arrays[9]
120
+ if len(fields) == 14:
121
+ shape_params = np.zeros(current_shape+(6,))
122
+ shape_params[:,:,:,0] = arrays[8]
123
+ shape_params[:,:,:,1] = arrays[9]
124
+ shape_params[:,:,:,2] = arrays[10]
125
+ shape_params[:,:,:,3] = arrays[11]
126
+ shape_params[:,:,:,4] = arrays[12]
127
+ shape_params[:,:,:,5] = arrays[13]
128
+
129
+
130
+
131
+
132
+ return Results(X, Y, arrays[0], arrays[1], arrays[2], arrays[3],
133
+ arrays[4], arrays[5], arrays[6], arrays[7],
134
+ shape_params, filenames)
135
+ # column layout
136
+ else:
137
+
138
+ shape_params = np.zeros(())
139
+ current_shape = arrays[0].shape # (file,(x,y))
140
+ # rigid
141
+ if len(fields) == 10:
142
+ shape_params = np.zeros(current_shape+(2,))
143
+ shape_params[:,:,0] = arrays[8]
144
+ shape_params[:,:,1] = arrays[9]
145
+ if len(fields) == 14:
146
+ shape_params = np.zeros(current_shape+(6,))
147
+ shape_params[:,:,0] = arrays[8]
148
+ shape_params[:,:,1] = arrays[9]
149
+ shape_params[:,:,2] = arrays[10]
150
+ shape_params[:,:,3] = arrays[11]
151
+ shape_params[:,:,4] = arrays[12]
152
+ shape_params[:,:,5] = arrays[13]
153
+
154
+ return Results(ss_x_ref, ss_y_ref, arrays[0], arrays[1], arrays[2], arrays[3],
155
+ arrays[4], arrays[5], arrays[6], arrays[7],
156
+ shape_params, filenames)
157
+
158
+
159
+
160
+
161
+
162
+ def read_binary(file: str, delimiter: str):
163
+ """
164
+ Read a binary DIC result file and extract DIC fields.
165
+
166
+ Assumes a fixed binary structure with each row containing:
167
+ - 2 × int32 (subset coordinates)
168
+ - 6 × float64 (u, v, match quality, cost, ftol, xtol)
169
+ - 1 × int32 (number of iterations)
170
+ - 1 × uint8 (convergence flag)
171
+ - 2 or 6 × float64 (shape parameters)
172
+
173
+ Parameters
174
+ ----------
175
+ file : str
176
+ Path to the binary result file.
177
+
178
+ delimiter : str
179
+ Ignored for binary data (included for API consistency).
180
+
181
+ Returns
182
+ -------
183
+ tuple of np.ndarray
184
+ Arrays corresponding to:
185
+ (ss_x, ss_y, u, v, m, cost, ftol, xtol, niter)
186
+
187
+ Raises
188
+ ------
189
+ ValueError
190
+ If the binary file size does not align with expected row size.
191
+ """
192
+
193
+ # row size can either be 3×4 + 6×8 + 1 = 61 bytes (without shape params)
194
+ # or 3×4 + 6×8 + 1 + 6×8 = 109 bytes (with shape params)
195
+ with open(file, "rb") as f:
196
+ raw = f.read()
197
+
198
+ has_shape_params = False
199
+ has_rigid_params = False
200
+ has_affine_params = False
201
+
202
+ row_size_basic = 3 * 4 + 6 * 8 + 1 # 61 bytes
203
+ row_size_with_rigid = row_size_basic + 2 * 8 # 77 bytes
204
+ row_size_with_affine = row_size_basic + 6 * 8 # 109 bytes
205
+
206
+ if len(raw) % row_size_basic == 0:
207
+ row_size = row_size_basic
208
+ has_shape_params = False
209
+ elif len(raw) % row_size_with_rigid == 0:
210
+ has_shape_params = True
211
+ row_size = row_size_with_rigid
212
+ has_rigid_params = True
213
+ has_affine_params = False
214
+ elif len(raw) % row_size_with_affine == 0:
215
+ has_shape_params = True
216
+ row_size = row_size_with_affine
217
+ has_affine_params = True
218
+ has_rigid_params = False
219
+ else:
220
+ raise ValueError(
221
+ f"Binary file has incomplete rows: {file}. "
222
+ f"Expected row size: 65 ((without shape params), "
223
+ f"81 (with rigid shape params) bytes, "
224
+ f"109 (with affine shape params). "
225
+ f"Actual size: {len(raw)} bytes."
226
+ )
227
+
228
+ rows = len(raw) // row_size
229
+ arr = np.frombuffer(raw, dtype=np.uint8).reshape(rows, row_size)
230
+
231
+ def extract(col, dtype, start):
232
+ return np.frombuffer(arr[:, start:start+col].copy(), dtype=dtype)
233
+
234
+ ss_x = extract(4, np.int32, 0)
235
+ ss_y = extract(4, np.int32, 4)
236
+ u = extract(8, np.float64, 8)
237
+ v = extract(8, np.float64, 16)
238
+ m = extract(8, np.float64, 24)
239
+ conv = extract(1, np.uint8, 32).astype(bool)
240
+ cost = extract(8, np.float64, 33)
241
+ ftol = extract(8, np.float64, 41)
242
+ xtol = extract(8, np.float64, 49)
243
+ niter = extract(4, np.int32, 57)
244
+
245
+ if has_shape_params:
246
+ if has_rigid_params:
247
+ p0 = extract(8, np.float64, 61)
248
+ p1 = extract(8, np.float64, 69)
249
+ return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, p0,p1
250
+ if has_affine_params:
251
+ p0 = extract(8, np.float64, 61)
252
+ p1 = extract(8, np.float64, 69)
253
+ p2 = extract(8, np.float64, 77)
254
+ p3 = extract(8, np.float64, 85)
255
+ p4 = extract(8, np.float64, 93)
256
+ p5 = extract(8, np.float64, 101)
257
+ return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, p0,p1,p2,p3,p4,p5
258
+ else:
259
+ return ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter
260
+
261
+
262
+
263
+
264
+ def read_text(file: str, delimiter: str):
265
+ """
266
+ Read a human-readable text DIC result file and extract DIC fields.
267
+
268
+ Expects at least 9 columns:
269
+ [ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter]
270
+ Could also include shape parameters if present.
271
+
272
+ Parameters
273
+ ----------
274
+ file : str
275
+ Path to the text result file.
276
+
277
+ delimiter : str
278
+ Delimiter used in the text file (e.g., space, tab, comma).
279
+
280
+ Returns
281
+ -------
282
+ tuple of np.ndarray
283
+ Arrays corresponding to:
284
+ (ss_x, ss_y, u, v, m, conv, cost, ftol, xtol, niter, shape_params)
285
+
286
+ Raises
287
+ ------
288
+ ValueError
289
+ If the text file has fewer than 9 columns.
290
+ """
291
+
292
+ data = np.loadtxt(file, delimiter=delimiter, skiprows=1)
293
+
294
+ if data.shape[1] < 9:
295
+ raise ValueError("Text data must have at least 9 columns.")
296
+
297
+ if data.shape[1] == 10:
298
+ return (
299
+ data[:, 0].astype(np.int32), # ss_x
300
+ data[:, 1].astype(np.int32), # ss_y
301
+ data[:, 2], data[:, 3], data[:, 4], # u, v, mag
302
+ data[:, 5].astype(np.bool_), # convergence
303
+ data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
304
+ data[:, 9].astype(np.int32) #niter
305
+ )
306
+ #rigid
307
+ elif data.shape[1]==12:
308
+ return (
309
+ data[:, 0].astype(np.int32), # ss_x
310
+ data[:, 1].astype(np.int32), # ss_y
311
+ data[:, 2], data[:, 3], data[:, 4], # u, v, mag
312
+ data[:, 5].astype(np.bool_), # convergence
313
+ data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
314
+ data[:, 9].astype(np.int32), #niter
315
+ data[:,10], data[:,11] # shape params (rigid)
316
+ )
317
+ #affine
318
+ elif data.shape[1]==16:
319
+ return (
320
+ data[:, 0].astype(np.int32), # ss_x
321
+ data[:, 1].astype(np.int32), # ss_y
322
+ data[:, 2], data[:, 3], data[:, 4], # u, v, mag
323
+ data[:, 5].astype(np.bool_), # convergence
324
+ data[:, 6], data[:, 7], data[:,8], # cost, ftol, xtol
325
+ data[:, 9].astype(np.int32), #niter
326
+ data[:,10], data[:,11], data[:,12], data[:,13], data[:,14], data[:,15] # shape params (affine)
327
+ )
328
+
329
+
330
+
331
+ def to_grid(data, shape, ss_x_ref, ss_y_ref, x_unique, y_unique):
332
+ """
333
+ Reshape a 2D DIC field from flat (column) format into grid (matrix) format.
334
+
335
+ This is used when output layout is specified as "matrix".
336
+ Maps values using reference subset coordinates (ss_x_ref, ss_y_ref).
337
+
338
+ Parameters
339
+ ol
340
+ ----------
341
+ data : np.ndarray
342
+ Array of shape (n_frames, n_points) to be reshaped into (n_frames, height, width).
343
+
344
+ shape : tuple
345
+ Target shape of output array: (n_frames, height, width).
346
+
347
+ ss_x_ref : np.ndarray
348
+ X coordinates of subset centers.
349
+
350
+ ss_y_ref : np.ndarray
351
+ Y coordinates of subset centers.
352
+
353
+ x_unique : np.ndarray
354
+ Sorted unique X coordinates in the grid.
355
+
356
+ y_unique : np.ndarray
357
+ Sorted unique Y coordinates in the grid.
358
+
359
+ Returns
360
+ -------
361
+ np.ndarray
362
+ Reshaped array with shape `shape`, filled with NaNs where no data exists.
363
+ """
364
+
365
+ grid = np.full(shape, np.nan)
366
+ for i, (x, y) in enumerate(zip(ss_x_ref, ss_y_ref)):
367
+ x_idx = np.where(x_unique == x)[0][0]
368
+ y_idx = np.where(y_unique == y)[0][0]
369
+ grid[:, y_idx, x_idx] = data[:, i]
370
+ return grid