pyvale 2025.7.1__cp311-cp311-win32.whl → 2025.8.1__cp311-cp311-win32.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-win32.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-win32.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-win32.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
@@ -15,7 +15,7 @@ import yaml
15
15
  import os
16
16
  from pathlib import Path
17
17
 
18
- class DICRegionOfInterest:
18
+ class RegionOfInterest:
19
19
  """
20
20
  A class for interactively selecting and manipulating ROI of an image before passing to the DIC engine.
21
21
 
@@ -84,6 +84,7 @@ class DICRegionOfInterest:
84
84
  self.height = None
85
85
  self.width = None
86
86
  self.subset_size = None
87
+ self.coord_label = None
87
88
 
88
89
  def interactive_selection(self, subset_size):
89
90
  """
@@ -117,6 +118,8 @@ class DICRegionOfInterest:
117
118
 
118
119
  # Create graphics widget
119
120
  self.graphics_widget = pg.GraphicsLayoutWidget()
121
+
122
+
120
123
 
121
124
  main_layout.addLayout(sidebar)
122
125
  main_layout.addWidget(self.graphics_widget)
@@ -165,7 +168,27 @@ class DICRegionOfInterest:
165
168
  self.buttons['undo_prev'].setEnabled(False)
166
169
  self.buttons['redo_prev'].setEnabled(False)
167
170
 
171
+ self.coord_label = QtWidgets.QLabel("(-, -)")
172
+ self.coord_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
173
+
174
+ # Set fixed width to handle largest expected value comfortably
175
+ self.coord_label.setMinimumWidth(350)
176
+ self.coord_label.setMaximumWidth(350)
177
+
178
+ self.coord_label.setStyleSheet("""
179
+ QLabel {
180
+ background-color: rgba(0, 0, 0, 150);
181
+ color: white;
182
+ padding: 5px;
183
+ border-radius: 3px;
184
+ font-family: monospace;
185
+ font-size: 12px;
186
+ }
187
+ """)
188
+
168
189
  sidebar.addStretch()
190
+ sidebar.addWidget(self.coord_label)
191
+
169
192
  return sidebar
170
193
 
171
194
  def _setup_graphics(self):
@@ -222,6 +245,24 @@ class DICRegionOfInterest:
222
245
  self.buttons[btn_id].clicked.connect(handler)
223
246
 
224
247
  self.main_view.scene().sigMouseClicked.connect(self._mouse_clicked)
248
+ self.main_view.scene().sigMouseMoved.connect(self._mouse_moved)
249
+
250
+
251
+ def _mouse_moved(self, pos):
252
+ """Handle mouse movement to update coordinate display."""
253
+ if self.main_view.sceneBoundingRect().contains(pos):
254
+ mouse_point = self.main_view.mapSceneToView(pos)
255
+ # Convert from graphics coordinates to image coordinates
256
+ img_x = int(round(mouse_point.x()))
257
+ img_y = int(round(self.width - mouse_point.y()))
258
+
259
+ # Clamp coordinates to image bounds
260
+ img_x = max(0, min(img_x, self.height - 1))
261
+ img_y = max(0, min(img_y, self.width - 1))
262
+
263
+ self.coord_label.setText(f"({img_x}, {img_y})")
264
+ else:
265
+ self.coord_label.setText("(-, -)")
225
266
 
226
267
  def _start_drawing_mode(self, mode):
227
268
  """Start drawing mode for specified shape type."""
@@ -536,7 +577,7 @@ class DICRegionOfInterest:
536
577
  self._update_button_states()
537
578
 
538
579
  def _save_interactive_roi(self):
539
- """Save the current ROI to a YAML file."""
580
+ """Save the current ROI to a YAML file. This only works with the interactive GUI."""
540
581
  filename, _ = QtWidgets.QFileDialog.getSaveFileName(self.main_window, 'Save ROI', 'roi_interactive.yaml', filter='YAML Files (*.yaml)')
541
582
 
542
583
  if filename:
@@ -566,7 +607,7 @@ class DICRegionOfInterest:
566
607
  yaml.dump(serialized, f, sort_keys=False)
567
608
 
568
609
  def _open_interactive_roi(self):
569
- """Open ROI from a YAML file."""
610
+ """Open ROI from a YAML file. This only works with the interactive GUI."""
570
611
  filename, _ = QtWidgets.QFileDialog.getOpenFileName(
571
612
  self.main_window, 'Open ROI', filter='YAML Files (*.yaml)'
572
613
  )
@@ -586,6 +627,7 @@ class DICRegionOfInterest:
586
627
  if entry.get('type') == 'SeedROI':
587
628
  # Restore the seed ROI
588
629
  x, y = entry['pos']
630
+ y = self.width-y
589
631
  size = entry.get('size', [10, 10]) # fallback default
590
632
  self.seed_roi = pg.RectROI(
591
633
  [x, y], size,
@@ -671,13 +713,13 @@ class DICRegionOfInterest:
671
713
  def _finalize_selection(self):
672
714
  """Process the final mask and seed location."""
673
715
  self.mask = np.flipud(self.temp_mask.T)
674
-
716
+
675
717
  if hasattr(self, 'seed_roi'):
676
718
  pos = self.seed_roi.pos()
677
719
  x = int(np.floor(pos.x()))
678
720
  y = int(np.floor(self.width - pos.y()))
679
721
  self.seed = [x, y]
680
-
722
+
681
723
  if not self.mask[y, x]:
682
724
  raise ValueError(f"Seed location [{x}, {y}] is not within the mask")
683
725
  print(f"Final seed location: [{x}, {y}]")
@@ -833,6 +875,111 @@ class DICRegionOfInterest:
833
875
  self.__roi_selected = True
834
876
 
835
877
 
878
+ def save_yaml(self, filename: str | Path) -> None:
879
+ """
880
+ Save the current ROI to a YAML file. This only works with the after having run the interactive GUI.
881
+
882
+ Parameters
883
+ ----------
884
+ filename : str or pathlib.Path
885
+ Filename of the YAML file to save the ROI data.
886
+
887
+ Raises
888
+ ------
889
+ ValueError
890
+ If no ROI has been selected.
891
+ """
892
+
893
+ if filename:
894
+
895
+ # Ensure extension is added if user doesn't include it
896
+ if filename and not filename.endswith('.yaml'):
897
+ filename += '.yaml'
898
+
899
+ print("Saving to file:", filename)
900
+ serialized = [
901
+ self._get_roi_data(roi, add)
902
+ for roi, add in zip(self.roi_list, self.add_list)
903
+ ]
904
+
905
+ # add ROI to serialized data
906
+ if hasattr(self, 'seed_roi'):
907
+ self._finalize_selection()
908
+ seed_data = {
909
+ 'type': 'SeedROI',
910
+ 'pos': [self.seed[0], self.seed[1]],
911
+ 'size': [self.subset_size, self.subset_size],
912
+ 'add': True
913
+ }
914
+ serialized.append(seed_data)
915
+
916
+ with open(filename, 'w') as f:
917
+ yaml.dump(serialized, f, sort_keys=False)
918
+
919
+ def read_yaml(self, filename: str | Path) -> None:
920
+ """
921
+ Load the ROI from a YAML file and restore the state of the GUI.
922
+ This method will clear existing ROIs and restore the state from the YAML file.
923
+
924
+ Parameters
925
+ ----------
926
+ filename : str or pathlib.Path
927
+ Path to the YAML file containing the ROI data.
928
+
929
+ Raises
930
+ ------
931
+ FileNotFoundError
932
+ If the specified file does not exist.
933
+ ValueError
934
+ If the loaded data is not a valid ROI format.
935
+ """
936
+
937
+ # need to create a temp qapplication so I can import the ROI.
938
+ self.__roi_selected = True
939
+
940
+ # Initialize GUI
941
+ self._setup_gui()
942
+ self._setup_graphics()
943
+ self._connect_signals()
944
+
945
+ if filename:
946
+ with open(filename, 'r') as f:
947
+ data = yaml.safe_load(f)
948
+
949
+ self.roi_list = []
950
+ self.add_list = []
951
+
952
+ self.seed_roi = None # Clear existing seed
953
+
954
+ for entry in data:
955
+ if entry.get('type') == 'SeedROI':
956
+ # Restore the seed ROI
957
+ x, y = entry['pos']
958
+ y = self.width-y
959
+ size = entry.get('size', [10, 10]) # fallback default
960
+ self.seed_roi = pg.RectROI(
961
+ [x, y], size,
962
+ pen=pg.mkPen('b', width=3),
963
+ hoverPen=pg.mkPen('y', width=3),
964
+ handlePen='#0000',
965
+ handleHoverPen='#0000'
966
+ )
967
+ self.main_view.addItem(self.seed_roi)
968
+
969
+ else:
970
+ # Restore standard ROI
971
+ roi = self._create_roi_from_data(entry)
972
+ self.roi_list.append(roi)
973
+ self.add_list.append(entry['add'])
974
+ self.main_view.addItem(roi)
975
+ roi.sigRegionChanged.connect(self._redraw_fill_layer)
976
+
977
+ self._redraw_fill_layer()
978
+ self._update_button_states()
979
+ self._finalize_selection()
980
+
981
+
982
+
836
983
  def show_image(self) -> None:
837
984
  """
838
985
  Displays the current mask in grayscale.
@@ -842,20 +989,30 @@ class DICRegionOfInterest:
842
989
  ValueError: If no ROI is selected.
843
990
  """
844
991
 
845
-
846
- if not self.__roi_selected:
847
- raise ValueError("No ROI selected with 'interactive_selection' or 'rect_boundary'")
992
+ # Convert grayscale image to 3-channel if needed
993
+ if self.ref_image.ndim == 2:
994
+ ref_image_color = cv2.cvtColor(self.ref_image.astype(np.uint8), cv2.COLOR_GRAY2BGR)
995
+ else:
996
+ ref_image_color = self.ref_image
848
997
 
849
998
  # Create a green mask image
850
- green_mask = np.zeros_like(self.ref_image)
999
+ if self.ref_image.ndim == 3:
1000
+ green_mask = np.zeros_like(self.ref_image)
1001
+ elif self.ref_image.ndim == 2:
1002
+ h, w = self.ref_image.shape
1003
+ green_mask = np.zeros((h, w, 3), dtype=self.ref_image.dtype)
1004
+ else:
1005
+ raise ValueError(f"Unsupported image shape: {self.ref_image.shape}")
851
1006
 
852
- green_mask[self.mask,:] = [0, 255, 0]
1007
+ # Apply the green mask
1008
+ green_mask[self.mask, :] = [0, 255, 0]
853
1009
 
854
- # Blend the original image and the mask
855
- blended = self.ref_image.astype(float) * 0.7 + green_mask.astype(float) * 0.3
1010
+ # Blend the original image and the green mask
1011
+ blended = ref_image_color.astype(float) * 0.7 + green_mask.astype(float) * 0.3
856
1012
  blended = blended.astype(np.uint8)
857
1013
 
858
1014
  # Display using Matplotlib
1015
+ import matplotlib.pyplot as plt
859
1016
  plt.figure()
860
1017
  plt.imshow(blended)
861
1018
  plt.axis('off')
@@ -9,7 +9,7 @@ from dataclasses import dataclass
9
9
  import numpy as np
10
10
 
11
11
  @dataclass(slots=True)
12
- class DICResults:
12
+ class Results:
13
13
  """
14
14
  Data container for Digital Image Correlation (DIC) analysis results.
15
15
 
@@ -38,6 +38,8 @@ class DICResults:
38
38
  Final `xtol` value from the optimization routine, indicating solution tolerance.
39
39
  niter : np.ndarray
40
40
  Number of iterations taken to converge for each subset point.
41
+ shape_params : np.ndarray | None
42
+ Optional shape parameters if output during DIC calculation (e.g., affine, rigid).
41
43
  filenames : list[str]
42
44
  name of DIC result files that have been found
43
45
  """
@@ -52,4 +54,5 @@ class DICResults:
52
54
  ftol: np.ndarray
53
55
  xtol: np.ndarray
54
56
  niter: np.ndarray
57
+ shape_params: np.ndarray
55
58
  filenames: list[str]
@@ -9,13 +9,13 @@ import numpy as np
9
9
  import glob
10
10
  from pathlib import Path
11
11
 
12
- from pyvale import dic2dcpp
13
- from pyvale import dicchecks
14
- from pyvale.dicdataimport import dic_data_import
15
- from pyvale.dicresults import DICResults
16
- from pyvale.dicstrainresults import StrainResults
12
+ # pyvale
13
+ import pyvale.dic.dic2dcpp as dic2dcpp
14
+ from pyvale.dic.dicstrainresults import StrainResults
15
+ from pyvale.dic.dicchecks import check_strain_files, check_output_directory
16
+ from pyvale.dic.dicdataimport import data_import
17
17
 
18
- def strain_2d(data: str | Path,
18
+ def strain_two_dimensional(data: str | Path,
19
19
  window_size: int=5,
20
20
  window_element: int=4,
21
21
  input_binary: bool=False,
@@ -80,10 +80,10 @@ def strain_2d(data: str | Path,
80
80
  if window_size % 2 == 0:
81
81
  raise ValueError(f"Invalid strain window size: '{window_size}'. Must be an odd number.")
82
82
 
83
- filenames = dicchecks.check_strain_files(strain_files=data)
83
+ filenames = check_strain_files(strain_files=data)
84
84
 
85
85
  # Load data if a file path is given
86
- results = dic_data_import(layout="matrix", data=str(data),
86
+ results = data_import(layout="matrix", data=str(data),
87
87
  binary=input_binary, delimiter=input_delimiter)
88
88
 
89
89
  # Extract dimensions from the validated object
@@ -92,7 +92,7 @@ def strain_2d(data: str | Path,
92
92
  nimg = results.u.shape[0]
93
93
 
94
94
 
95
- dicchecks.check_output_directory(str(output_basepath), output_prefix)
95
+ check_output_directory(str(output_basepath), output_prefix)
96
96
 
97
97
  # assigning c++ struct vals for save config
98
98
  strain_save_conf = dic2dcpp.SaveConfig()
@@ -18,8 +18,11 @@ Test case: Scalar field point sensors (thermocouples) on a 2D thermal simulation
18
18
 
19
19
  from pathlib import Path
20
20
  import matplotlib.pyplot as plt
21
- import mooseherder as mh
22
- import pyvale as pyv
21
+
22
+ # Pyvale imports
23
+ import pyvale.sensorsim as sens
24
+ import pyvale.mooseherder as mh
25
+ import pyvale.dataset as dataset
23
26
 
24
27
  #%%
25
28
  # Here we load a pre-generated MOOSE finite element simulation dataset that
@@ -28,13 +31,13 @@ import pyvale as pyv
28
31
  # to your own MOOSE simulation with exodus output (*.e). Note that the
29
32
  # field_key must match the name of your variable in your MOOSE simulation.
30
33
  # We use `mooseherder` to load the exodus file into a `SimData` object.
31
- data_path = pyv.DataSet.thermal_2d_path()
34
+ data_path = dataset.thermal_2d_path()
32
35
  sim_data = mh.ExodusReader(data_path).read_all_sim_data()
33
36
 
34
37
  #%%
35
38
  # Scale to mm to make 3D visualisation scaling easier as pyvista scales
36
39
  # everything to unity
37
- sim_data = pyv.scale_length_units(scale=1000.0,
40
+ sim_data = sens.scale_length_units(scale=1000.0,
38
41
  sim_data=sim_data,
39
42
  disp_comps=None)
40
43
 
@@ -46,14 +49,14 @@ n_sens = (3,2,1)
46
49
  x_lims = (0.0,100.0)
47
50
  y_lims = (0.0,50.0)
48
51
  z_lims = (0.0,0.0)
49
- sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
52
+ sens_pos = sens.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
50
53
 
51
54
  #%%
52
55
  # This dataclass contains the parameters to build our sensor array. We can
53
56
  # also customise the output frequency, the sensor area and the sensor
54
57
  # orientation. For now we will use the defaults which assumes an ideal point
55
58
  # sensor sampling at the simulation time steps.
56
- sens_data = pyv.SensorData(positions=sens_pos)
59
+ sens_data = sens.SensorData(positions=sens_pos)
57
60
 
58
61
  #%%
59
62
  # Now that we have our sensor locations we can use the sensor factory to
@@ -63,7 +66,7 @@ sens_data = pyv.SensorData(positions=sens_pos)
63
66
  # If you want to remove the simulated errors and just interpolate at the
64
67
  # sensor locations then user `.thermocouples_no_errs()`.
65
68
  field_key: str = "temperature"
66
- tc_array = pyv.SensorArrayFactory \
69
+ tc_array = sens.SensorArrayFactory \
67
70
  .thermocouples_basic_errs(sim_data,
68
71
  sens_data,
69
72
  elem_dims=2,
@@ -93,7 +96,7 @@ if not output_path.is_dir():
93
96
  # This creates a pyvista visualisation of the sensor locations on the
94
97
  # simulation mesh. The plot will can be shown in interactive mode by calling
95
98
  # `pv_plot.show()`.
96
- pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
99
+ pv_plot = sens.plot_point_sensors_on_sim(tc_array,field_key)
97
100
 
98
101
  #%%
99
102
  # We determined manually by moving camera in interative mode and then
@@ -125,7 +128,7 @@ print(80*"-"+"\n")
125
128
  # markers shows the simulated sensor traces. In later examples we will see
126
129
  # how to configure this plot but for now we note we that we are returned a
127
130
  # matplotlib figure and axes object which allows for further customisation.
128
- (fig,ax) = pyv.plot_time_traces(tc_array,field_key)
131
+ (fig,ax) = sens.plot_time_traces(tc_array,field_key)
129
132
 
130
133
  #%%
131
134
  # We can also save the sensor trace plot as a vector and raster graphic
@@ -29,8 +29,11 @@ Test case: Scalar field point sensors (thermocouples) on a 2D thermal simulation
29
29
  """
30
30
 
31
31
  import matplotlib.pyplot as plt
32
- import mooseherder as mh
33
- import pyvale as pyv
32
+
33
+ # Pyvale imports
34
+ import pyvale.sensorsim as sens
35
+ import pyvale.mooseherder as mh
36
+ import pyvale.dataset as dataset
34
37
 
35
38
 
36
39
  #%%
@@ -40,14 +43,14 @@ import pyvale as pyv
40
43
  # Here we load a pre-generated MOOSE finite element simulation dataset that
41
44
  # comes packaged with pyvale. The simulation is a 2D rectangular plate with
42
45
  # a bi-directional temperature gradient.
43
- data_path = pyv.DataSet.thermal_2d_path()
46
+ data_path = dataset.thermal_2d_path()
44
47
  sim_data = mh.ExodusReader(data_path).read_all_sim_data()
45
48
  field_key: str = "temperature"
46
49
 
47
50
  #%%
48
51
  # Scale to mm to make 3D visualisation scaling easier as pyvista scales
49
52
  # everything to unity
50
- sim_data = pyv.scale_length_units(scale=1000.0,
53
+ sim_data = sens.scale_length_units(scale=1000.0,
51
54
  sim_data=sim_data,
52
55
  disp_comps=None)
53
56
 
@@ -59,14 +62,14 @@ n_sens = (4,1,1)
59
62
  x_lims = (0.0,100.0)
60
63
  y_lims = (0.0,50.0)
61
64
  z_lims = (0.0,0.0)
62
- sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
65
+ sens_pos = sens.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
63
66
 
64
67
  #%%
65
68
  # This dataclass contains the parameters to build our sensor array. We can
66
69
  # also customise the output frequency, the sensor area and the sensor
67
70
  # orientation. For now we will use the defaults which assumes an ideal point
68
71
  # sensor sampling at the simulation time steps.
69
- sens_data = pyv.SensorData(positions=sens_pos)
72
+ sens_data = sens.SensorData(positions=sens_pos)
70
73
 
71
74
  #%%
72
75
  # Now that we have our sensor locations we can use the sensor factory to
@@ -74,7 +77,7 @@ sens_data = pyv.SensorData(positions=sens_pos)
74
77
  # examples we will see how to customise sensor parameters and errors.
75
78
  # This basic thermocouple array includes a 5% systematic and random error -
76
79
  # We are specifically using exaggerated errors here for visualisation.
77
- tc_array = pyv.SensorArrayFactory \
80
+ tc_array = sens.SensorArrayFactory \
78
81
  .thermocouples_basic_errs(sim_data,
79
82
  sens_data,
80
83
  elem_dims=2,
@@ -111,16 +114,16 @@ print(80*"-")
111
114
  print(f"Looking at the last {time_last} virtual measurements for sensor"
112
115
  +f" {sens_print}:")
113
116
 
114
- pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
117
+ sens.print_measurements(tc_array,sens_print,comp_print,time_print)
115
118
 
116
119
  print(80*"-")
117
120
  print("If we call the `calc_measurements()` method then the errors are "
118
121
  + "re-calculated.")
119
122
  measurements = tc_array.calc_measurements()
120
123
 
121
- pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
124
+ sens.print_measurements(tc_array,sens_print,comp_print,time_print)
122
125
 
123
- (fig,ax) = pyv.plot_time_traces(tc_array,field_key)
126
+ (fig,ax) = sens.plot_time_traces(tc_array,field_key)
124
127
  ax.set_title("Exp 1: called calc_measurements()")
125
128
 
126
129
  print(80*"-")
@@ -128,9 +131,9 @@ print("If we call the `get_measurements()` method then the errors are the "
128
131
  + "same:")
129
132
  measurements = tc_array.get_measurements()
130
133
 
131
- pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
134
+ sens.print_measurements(tc_array,sens_print,comp_print,time_print)
132
135
 
133
- (fig,ax) = pyv.plot_time_traces(tc_array,field_key)
136
+ (fig,ax) = sens.plot_time_traces(tc_array,field_key)
134
137
  ax.set_title("Exp 2: called get_measurements()")
135
138
 
136
139
  print(80*"-")
@@ -138,9 +141,9 @@ print("If we call the `calc_measurements()` method again we generate / "
138
141
  "sample new errors:")
139
142
  measurements = tc_array.calc_measurements()
140
143
 
141
- pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
144
+ sens.print_measurements(tc_array,sens_print,comp_print,time_print)
142
145
 
143
- (fig,ax) = pyv.plot_time_traces(tc_array,field_key)
146
+ (fig,ax) = sens.plot_time_traces(tc_array,field_key)
144
147
  ax.set_title("Exp 3: called calc_measurements()")
145
148
 
146
149
  print(80*"-")
@@ -18,8 +18,11 @@ Test case: Scalar field point sensors (thermocouples) on a 3D thermal simulation
18
18
  from pathlib import Path
19
19
  import numpy as np
20
20
  import matplotlib.pyplot as plt
21
- import mooseherder as mh
22
- import pyvale as pyv
21
+
22
+ # Pyvale imports
23
+ import pyvale.mooseherder as mh
24
+ import pyvale.sensorsim as sens
25
+ import pyvale.dataset as dataset
23
26
 
24
27
  #%%
25
28
  # To build our custom point sensor array we need to at minimum provide a
@@ -32,9 +35,9 @@ import pyvale as pyv
32
35
  # based on the same thermal example we have used in the last two examples so
33
36
  # we start by loading our simulation data:
34
37
 
35
- data_path = pyv.DataSet.thermal_3d_path()
38
+ data_path = dataset.thermal_3d_path()
36
39
  sim_data = mh.ExodusReader(data_path).read_all_sim_data()
37
- sim_data = pyv.scale_length_units(scale=1000.0,
40
+ sim_data = sens.scale_length_units(scale=1000.0,
38
41
  sim_data=sim_data,
39
42
  disp_comps=None)
40
43
 
@@ -43,7 +46,7 @@ sim_data = pyv.scale_length_units(scale=1000.0,
43
46
  # field object to perform interpolation to the sensor locations at the
44
47
  # desired sampling times.
45
48
  field_key: str = "temperature"
46
- t_field = pyv.FieldScalar(sim_data,
49
+ t_field = sens.FieldScalar(sim_data,
47
50
  field_key=field_key,
48
51
  elem_dims=3)
49
52
 
@@ -56,7 +59,7 @@ n_sens = (1,4,1)
56
59
  x_lims = (12.5,12.5)
57
60
  y_lims = (0.0,33.0)
58
61
  z_lims = (0.0,12.0)
59
- sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
62
+ sens_pos = sens.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
60
63
 
61
64
  #%%
62
65
  # We are also going to specify the times at which we would like to simulate
@@ -64,8 +67,8 @@ sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
64
67
  # to match the simulation time steps.
65
68
  sample_times = np.linspace(0.0,np.max(sim_data.time),50)
66
69
 
67
- sensor_data = pyv.SensorData(positions=sens_pos,
68
- sample_times=sample_times)
70
+ sensor_data = sens.SensorData(positions=sens_pos,
71
+ sample_times=sample_times)
69
72
 
70
73
  #%%
71
74
  # Finally, we can create a `SensorDescriptor` which will be used to label
@@ -73,20 +76,20 @@ sensor_data = pyv.SensorData(positions=sens_pos,
73
76
  # examples.
74
77
  use_auto_descriptor: str = "blank"
75
78
  if use_auto_descriptor == "manual":
76
- descriptor = pyv.SensorDescriptor(name="Temperature",
79
+ descriptor = sens.SensorDescriptor(name="Temperature",
77
80
  symbol="T",
78
81
  units = r"^{\circ}C",
79
82
  tag = "TC")
80
83
  elif use_auto_descriptor == "factory":
81
- descriptor = pyv.SensorDescriptorFactory.temperature_descriptor()
84
+ descriptor = sens.SensorDescriptorFactory.temperature_descriptor()
82
85
  else:
83
- descriptor = pyv.SensorDescriptor()
86
+ descriptor = sens.SensorDescriptor()
84
87
 
85
88
  #%%
86
89
  # We can now build our custom point sensor array. This sensor array has no
87
90
  # errors so if we call `get_measurements()` or `calc_measurements()` we will
88
91
  # be able to extract the simulation truth values at the sensor locations.
89
- tc_array = pyv.SensorArrayPoint(sensor_data,
92
+ tc_array = sens.SensorArrayPoint(sensor_data,
90
93
  t_field,
91
94
  descriptor)
92
95
 
@@ -101,7 +104,7 @@ output_path = Path.cwd() / "pyvale-output"
101
104
  if not output_path.is_dir():
102
105
  output_path.mkdir(parents=True, exist_ok=True)
103
106
 
104
- pv_plot = pyv.plot_point_sensors_on_sim(tc_array,field_key)
107
+ pv_plot = sens.plot_point_sensors_on_sim(tc_array,field_key)
105
108
 
106
109
  pv_plot.camera_position = [(59.354, 43.428, 69.946),
107
110
  (-2.858, 13.189, 4.523),
@@ -149,8 +152,8 @@ errors_on = {"sys": True,
149
152
 
150
153
  error_chain = []
151
154
  if errors_on["sys"]:
152
- error_chain.append(pyv.ErrSysOffset(offset=-10.0))
153
- error_chain.append(pyv.ErrSysUnif(low=-10.0,
155
+ error_chain.append(sens.ErrSysOffset(offset=-10.0))
156
+ error_chain.append(sens.ErrSysUnif(low=-10.0,
154
157
  high=10.0))
155
158
  #%%
156
159
  # This random error is generated by sampling from a normal distribution
@@ -159,8 +162,8 @@ if errors_on["sys"]:
159
162
  # probability distribution
160
163
 
161
164
  if errors_on["rand"]:
162
- error_chain.append(pyv.ErrRandNorm(std=5.0))
163
- error_chain.append(pyv.ErrRandUnifPercent(low_percent=-5.0,
165
+ error_chain.append(sens.ErrRandNorm(std=5.0))
166
+ error_chain.append(sens.ErrRandUnifPercent(low_percent=-5.0,
164
167
  high_percent=5.0))
165
168
 
166
169
  #%%
@@ -171,11 +174,11 @@ if errors_on["rand"]:
171
174
  # INDEPENDENT.
172
175
 
173
176
  if len(error_chain) > 0:
174
- err_int_opts = pyv.ErrIntOpts()
175
- error_integrator = pyv.ErrIntegrator(error_chain,
176
- sensor_data,
177
- tc_array.get_measurement_shape(),
178
- err_int_opts=err_int_opts)
177
+ err_int_opts = sens.ErrIntOpts()
178
+ error_integrator = sens.ErrIntegrator(error_chain,
179
+ sensor_data,
180
+ tc_array.get_measurement_shape(),
181
+ err_int_opts=err_int_opts)
179
182
  tc_array.set_error_integrator(error_integrator)
180
183
 
181
184
  #%%
@@ -204,11 +207,11 @@ time_print = slice(measurements.shape[2]-time_last,measurements.shape[2])
204
207
  print(f"These are the last {time_last} virtual measurements of sensor "
205
208
  + f"{sens_print}:")
206
209
 
207
- pyv.print_measurements(tc_array,sens_print,comp_print,time_print)
210
+ sens.print_measurements(tc_array,sens_print,comp_print,time_print)
208
211
 
209
212
  print(80*"-")
210
213
 
211
- (fig,ax) = pyv.plot_time_traces(tc_array,field_key)
214
+ (fig,ax) = sens.plot_time_traces(tc_array,field_key)
212
215
 
213
216
  save_traces = output_path/"customsensors_ex1_3_sensortraces.png"
214
217
  fig.savefig(save_traces, dpi=300, bbox_inches="tight")