pyvale 2025.5.3__cp311-cp311-win_amd64.whl → 2025.7.0__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 (93) hide show
  1. pyvale/__init__.py +12 -0
  2. pyvale/blendercalibrationdata.py +3 -1
  3. pyvale/blenderscene.py +7 -5
  4. pyvale/blendertools.py +27 -5
  5. pyvale/camera.py +1 -0
  6. pyvale/cameradata.py +3 -0
  7. pyvale/camerasensor.py +147 -0
  8. pyvale/camerastereo.py +4 -4
  9. pyvale/cameratools.py +23 -61
  10. pyvale/cython/rastercyth.c +1657 -1352
  11. pyvale/cython/rastercyth.cp311-win_amd64.pyd +0 -0
  12. pyvale/cython/rastercyth.py +71 -26
  13. pyvale/data/plate_hole_def0000.tiff +0 -0
  14. pyvale/data/plate_hole_def0001.tiff +0 -0
  15. pyvale/data/plate_hole_ref0000.tiff +0 -0
  16. pyvale/data/plate_rigid_def0000.tiff +0 -0
  17. pyvale/data/plate_rigid_def0001.tiff +0 -0
  18. pyvale/data/plate_rigid_ref0000.tiff +0 -0
  19. pyvale/dataset.py +96 -6
  20. pyvale/dic/cpp/dicbruteforce.cpp +370 -0
  21. pyvale/dic/cpp/dicfourier.cpp +648 -0
  22. pyvale/dic/cpp/dicinterpolator.cpp +559 -0
  23. pyvale/dic/cpp/dicmain.cpp +215 -0
  24. pyvale/dic/cpp/dicoptimizer.cpp +675 -0
  25. pyvale/dic/cpp/dicrg.cpp +137 -0
  26. pyvale/dic/cpp/dicscanmethod.cpp +677 -0
  27. pyvale/dic/cpp/dicsmooth.cpp +138 -0
  28. pyvale/dic/cpp/dicstrain.cpp +383 -0
  29. pyvale/dic/cpp/dicutil.cpp +563 -0
  30. pyvale/dic2d.py +164 -0
  31. pyvale/dic2dcpp.cp311-win_amd64.pyd +0 -0
  32. pyvale/dicchecks.py +476 -0
  33. pyvale/dicdataimport.py +247 -0
  34. pyvale/dicregionofinterest.py +887 -0
  35. pyvale/dicresults.py +55 -0
  36. pyvale/dicspecklegenerator.py +238 -0
  37. pyvale/dicspecklequality.py +305 -0
  38. pyvale/dicstrain.py +387 -0
  39. pyvale/dicstrainresults.py +37 -0
  40. pyvale/errorintegrator.py +10 -8
  41. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +124 -113
  42. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +124 -132
  43. pyvale/examples/basics/ex1_3_customsens_therm3d.py +199 -195
  44. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +125 -121
  45. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +145 -141
  46. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +96 -101
  47. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +109 -105
  48. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +92 -91
  49. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +96 -90
  50. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +88 -89
  51. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +172 -171
  52. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +88 -86
  53. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +90 -90
  54. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +93 -91
  55. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +172 -160
  56. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +154 -148
  57. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +249 -231
  58. pyvale/examples/dic/ex1_region_of_interest.py +98 -0
  59. pyvale/examples/dic/ex2_plate_with_hole.py +149 -0
  60. pyvale/examples/dic/ex3_plate_with_hole_strain.py +93 -0
  61. pyvale/examples/dic/ex4_dic_blender.py +95 -0
  62. pyvale/examples/dic/ex5_dic_challenge.py +102 -0
  63. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +4 -2
  64. pyvale/examples/renderblender/ex1_1_blenderscene.py +152 -105
  65. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +151 -100
  66. pyvale/examples/renderblender/ex2_1_stereoscene.py +183 -116
  67. pyvale/examples/renderblender/ex2_2_stereodeformed.py +185 -112
  68. pyvale/examples/renderblender/ex3_1_blendercalibration.py +164 -109
  69. pyvale/examples/renderrasterisation/ex_rastenp.py +74 -35
  70. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +6 -13
  71. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +2 -2
  72. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +2 -4
  73. pyvale/imagedef2d.py +3 -2
  74. pyvale/imagetools.py +137 -0
  75. pyvale/rastercy.py +34 -4
  76. pyvale/rasternp.py +300 -276
  77. pyvale/rasteropts.py +58 -0
  78. pyvale/renderer.py +47 -0
  79. pyvale/rendermesh.py +52 -62
  80. pyvale/renderscene.py +51 -0
  81. pyvale/sensorarrayfactory.py +2 -2
  82. pyvale/sensortools.py +19 -35
  83. pyvale/simcases/case21.i +1 -1
  84. pyvale/simcases/run_1case.py +8 -0
  85. pyvale/simtools.py +2 -2
  86. pyvale/visualsimplotter.py +180 -0
  87. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/METADATA +11 -57
  88. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/RECORD +91 -56
  89. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/WHEEL +1 -1
  90. pyvale/examples/visualisation/ex1_1_plot_traces.py +0 -102
  91. pyvale/examples/visualisation/ex2_1_animate_sim.py +0 -89
  92. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/licenses/LICENSE +0 -0
  93. {pyvale-2025.5.3.dist-info → pyvale-2025.7.0.dist-info}/top_level.txt +0 -0
@@ -4,13 +4,14 @@
4
4
  # Copyright (C) 2025 The Computer Aided Validation Team
5
5
  # ==============================================================================
6
6
 
7
- """Pyvale example: Multi-physics experiment simulation in 3D
8
- --------------------------------------------------------------------------------
7
+ """Basics: Multi-physics experiment simulation in 3D
8
+ ================================================================================
9
+
9
10
  In the previous example we performed a series of simulated experiments on a set
10
11
  of 2D multi-physics simulations. Here we use a 3D thermo-mechanical analysis of
11
12
  a divertor armour heatsink to show how we can run simulated experiments in 3D.
12
13
 
13
- Note that this tutorial assumes you are familiar with the use of pyvale for
14
+ Note that this tutorial assumes you are familiar with the use of `pyvale` for
14
15
  scalar and tensor fields as described in the previous examples.
15
16
 
16
17
  Test case: thermo-mechanical analysis of a divertor heatsink in 3D
@@ -22,231 +23,248 @@ import matplotlib.pyplot as plt
22
23
  import mooseherder as mh
23
24
  import pyvale as pyv
24
25
 
25
- def main() -> None:
26
- # First we get the path to simulation output file and then read the
27
- # simulation into a `SimData` object. In this case our simulation is a
28
- # thermomechanical model of a divertor heatsink.
29
- sim_path = pyv.DataSet.thermomechanical_3d_path()
30
- sim_data = mh.ExodusReader(sim_path).read_all_sim_data()
31
- elem_dims: int = 3
32
- # We scale our length and displacement units to mm to help with
33
- # visualisation.
34
- disp_comps = ("disp_x","disp_y","disp_z")
35
- sim_data = pyv.scale_length_units(scale=1000.0,
36
- sim_data=sim_data,
37
- disp_comps=disp_comps)
38
-
39
- # If we are going to save figures showing where our sensors are and their
40
- # simulated traces we need to create a directory. Set the flag below to
41
- # save the figures when you run the script
42
- save_figs = False
43
- save_tag = "thermomech3d"
44
- fig_save_path = Path.cwd()/"images"
45
- if not fig_save_path.is_dir():
46
- fig_save_path.mkdir(parents=True, exist_ok=True)
47
-
48
- # We specify manual sensor sampling times but we could also set this to None
49
- # for the sensors to sample at the simulation time steps.
50
- sample_times = np.linspace(0.0,np.max(sim_data.time),50)
51
-
52
-
53
- x_lims = (12.5,12.5)
54
- y_lims = (0.0,33.0)
55
- z_lims = (0.0,12.0)
56
- n_sens = (1,4,1)
57
- tc_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
58
-
59
- tc_sens_data = pyv.SensorData(positions=tc_sens_pos,
60
- sample_times=sample_times)
61
-
62
- # We use the sensor array factory to create our thermocouple array with no
63
- # errors.
64
- tc_field_name = "temperature"
65
- tc_array = pyv.SensorArrayFactory \
66
- .thermocouples_no_errs(sim_data,
67
- tc_sens_data,
68
- elem_dims=elem_dims,
69
- field_name=tc_field_name)
70
-
71
- # Now we build our error chain starting with some basic errors on the order
72
- # of 1 degree.
73
- tc_err_chain = []
74
- tc_err_chain.append(pyv.ErrSysUnif(low=1.0,high=1.0))
75
- tc_err_chain.append(pyv.ErrRandNorm(std=1.0))
76
-
77
- # Now we add positioning error for our thermocouples.
78
- tc_pos_uncert = 0.1 # units = mm
79
- tc_pos_rand = (pyv.GenNormal(std=tc_pos_uncert),
80
- pyv.GenNormal(std=tc_pos_uncert),
81
- pyv.GenNormal(std=tc_pos_uncert))
82
-
83
- # We block translation in x so the thermocouples stay attached.
84
- tc_pos_lock = np.full(tc_sens_pos.shape,False,dtype=bool)
85
- tc_pos_lock[:,0] = True
86
-
87
- tc_field_err_data = pyv.ErrFieldData(pos_rand_xyz=tc_pos_rand,
88
- pos_lock_xyz=tc_pos_lock)
89
- tc_err_chain.append(pyv.ErrSysField(tc_array.get_field(),
90
- tc_field_err_data))
91
- # We have finished our error chain so we can build our error integrator and
92
- # attach it to our thermocouple array.
93
- tc_error_int = pyv.ErrIntegrator(tc_err_chain,
94
- tc_sens_data,
95
- tc_array.get_measurement_shape())
96
- tc_array.set_error_integrator(tc_error_int)
97
-
98
- # We visualise our thermcouple locations on our mesh to make sure they are
99
- # in the correct positions.
100
- pv_plot = pyv.plot_point_sensors_on_sim(tc_array,"temperature")
101
- pv_plot.camera_position = [(59.354, 43.428, 69.946),
102
- (-2.858, 13.189, 4.523),
103
- (-0.215, 0.948, -0.233)]
104
- if save_figs:
105
- pv_plot.save_graphic(fig_save_path/(save_tag+"_tc_vis.svg"))
106
- pv_plot.screenshot(fig_save_path/(save_tag+"_tc_vis.png"))
107
-
108
- pv_plot.show()
109
-
110
- # Now we have finished with our thermocouple array we can move on to our
111
- # strain gauge array.
112
-
113
- # We use the same sampling time but we are going to place the strain gauges
114
- # down the side of the monoblock where the pipe passes through.
115
- x_lims = (9.4,9.4)
116
- y_lims = (0.0,33.0)
117
- z_lims = (12.0,12.0)
118
- n_sens = (1,4,1)
119
- sg_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
120
-
121
- sg_sens_data = pyv.SensorData(positions=sg_sens_pos,
122
- sample_times=sample_times)
123
-
124
- # We use the sensor array factory to give us a strain gauge array with no
125
- # errors.
126
- sg_field_name = "strain"
127
- sg_norm_comps = ("strain_xx","strain_yy","strain_zz")
128
- sg_dev_comps = ("strain_xy","strain_yz","strain_xz")
129
- sg_array = pyv.SensorArrayFactory \
130
- .strain_gauges_no_errs(sim_data,
131
- sg_sens_data,
132
- elem_dims=elem_dims,
133
- field_name=sg_field_name,
134
- norm_comps=sg_norm_comps,
135
- dev_comps=sg_dev_comps)
136
-
137
- # Now we build our error chain starting with some basic errors on the order
138
- # of 1 percent.
139
- sg_err_chain = []
140
- sg_err_chain.append(pyv.ErrSysUnifPercent(low_percent=1.0,high_percent=1.0))
141
- sg_err_chain.append(pyv.ErrRandNormPercent(std_percent=1.0))
142
-
143
- # We are going to add +/-2 degree rotation uncertainty to our strain gauges.
144
- angle_uncert = 2.0
145
- angle_rand_zyx = (pyv.GenUniform(low=-angle_uncert,high=angle_uncert), # units = deg
146
- pyv.GenUniform(low=-angle_uncert,high=angle_uncert),
147
- pyv.GenUniform(low=-angle_uncert,high=angle_uncert))
148
-
149
- # We only allow rotation on the face the strain gauges are on
150
- angle_lock = np.full(sg_sens_pos.shape,True,dtype=bool)
151
- angle_lock[:,0] = False # Allow rotation about z
152
-
153
- sg_field_err_data = pyv.ErrFieldData(ang_rand_zyx=angle_rand_zyx,
154
- ang_lock_zyx=angle_lock)
155
- sg_err_chain.append(pyv.ErrSysField(sg_array.get_field(),
156
- sg_field_err_data))
157
-
158
- # We have finished our error chain so we can build our error integrator and
159
- # attach it to our thermocouple array.
160
- sg_error_int = pyv.ErrIntegrator(sg_err_chain,
161
- sg_sens_data,
162
- sg_array.get_measurement_shape())
163
- sg_array.set_error_integrator(sg_error_int)
164
-
165
- # Now we visualise the strain gauge locations to make sure they are where
166
- # we expect them to be.
167
- pv_plot = pyv.plot_point_sensors_on_sim(sg_array,"strain_yy")
168
- pv_plot.camera_position = [(59.354, 43.428, 69.946),
169
- (-2.858, 13.189, 4.523),
170
- (-0.215, 0.948, -0.233)]
171
- if save_figs:
172
- pv_plot.save_graphic(fig_save_path/(save_tag+"_sg_vis.svg"))
173
- pv_plot.screenshot(fig_save_path/(save_tag+"_sg_vis.png"))
174
-
175
- pv_plot.show()
176
-
177
- # We have both our sensor arrays so we will create and run our experiment.
178
- # Here we only have a single input simulation in our list and we only run
179
- # 100 simulated experiments as we are going to plot all simulated data
180
- # points on our traces. Note that if you are running more than 100
181
- # experiments here you will need to set the trace plots below to not show
182
- # all points on the graph.
183
- sim_list = [sim_data,]
184
- sensor_arrays = [tc_array,sg_array]
185
- exp_sim = pyv.ExperimentSimulator(sim_list,
186
- sensor_arrays,
187
- num_exp_per_sim=100)
188
-
189
- # We run our experiments and calculate summary statistics as in the previous
190
- # example
191
- exp_data = exp_sim.run_experiments()
192
- exp_stats = exp_sim.calc_stats()
193
-
194
- # We print the lengths of our exp_data and exp_stats lists along with the
195
- # shape of the numpy arrays they contain so we can index into them easily.
196
- print(80*"=")
197
- print("exp_data and exp_stats are lists where the index is the sensor array")
198
- print("position in the list as field components are not consistent dims:")
199
- print(f"{len(exp_data)=}")
200
- print(f"{len(exp_stats)=}")
201
- print()
202
- print(80*"-")
203
- print("Thermal sensor array @ exp_data[0]")
204
- print(80*"-")
205
- print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)")
206
- print(f"{exp_data[0].shape=}")
207
- print()
208
- print("Stats are calculated over all experiments (axis=1)")
209
- print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)")
210
- print(f"{exp_stats[0].max.shape=}")
211
- print()
212
- print(80*"-")
213
- print("Mechanical sensor array @ exp_data[1]")
214
- print(80*"-")
215
- print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)")
216
- print(f"{exp_data[1].shape=}")
217
- print()
218
- print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)")
219
- print(f"{exp_stats[1].max.shape=}")
220
- print(80*"=")
221
-
222
- # Finally, we are going to plot the simulated sensor traces but we are going
223
- # to control some of the plotting options using the options data class here.
224
- # We set the plot to show all simulated experiment data points and to plot
225
- # the median as the centre line and to fill between the min and max values.
226
- # Note that the default here is to plot the mean and fill between 3 times
227
- # the standard deviation.
228
- trace_opts = pyv.TraceOptsExperiment(plot_all_exp_points=True,
229
- centre=pyv.EExpVisCentre.MEDIAN,
230
- fill_between=pyv.EExpVisBounds.MINMAX)
231
-
232
- (fig,ax) = pyv.plot_exp_traces(exp_sim,
233
- component="temperature",
234
- sens_array_num=0,
235
- sim_num=0,
236
- trace_opts=trace_opts)
237
- if save_figs:
238
- fig.savefig(fig_save_path/(save_tag+"_tc_traces.png"),
239
- dpi=300, format='png', bbox_inches='tight')
240
-
241
- (fig,ax) = pyv.plot_exp_traces(exp_sim,
242
- component="strain_yy",
243
- sens_array_num=1,
244
- sim_num=0,
245
- trace_opts=trace_opts)
246
- if save_figs:
247
- fig.savefig(fig_save_path/(save_tag+"_sg_traces.png"),
248
- dpi=300, format='png', bbox_inches='tight')
249
- plt.show()
250
-
251
- if __name__ == "__main__":
252
- main()
26
+ #%%
27
+ # First we get the path to simulation output file and then read the
28
+ # simulation into a `SimData` object. In this case our simulation is a
29
+ # thermomechanical model of a divertor heatsink.
30
+ sim_path = pyv.DataSet.thermomechanical_3d_path()
31
+ sim_data = mh.ExodusReader(sim_path).read_all_sim_data()
32
+ elem_dims: int = 3
33
+
34
+ #%%
35
+ # We scale our length and displacement units to mm to help with
36
+ # visualisation.
37
+ disp_comps = ("disp_x","disp_y","disp_z")
38
+ sim_data = pyv.scale_length_units(scale=1000.0,
39
+ sim_data=sim_data,
40
+ disp_comps=disp_comps)
41
+
42
+ #%%
43
+ # If we are going to save figures showing where our sensors are and their
44
+ # simulated traces we need to create a directory. Set the flag below to
45
+ # save the figures when you run the script
46
+ save_figs = False
47
+ save_tag = "thermomech3d"
48
+ fig_save_path = Path.cwd()/"images"
49
+ if not fig_save_path.is_dir():
50
+ fig_save_path.mkdir(parents=True, exist_ok=True)
51
+
52
+ #%%
53
+ # We specify manual sensor sampling times but we could also set this to None
54
+ # for the sensors to sample at the simulation time steps.
55
+ sample_times = np.linspace(0.0,np.max(sim_data.time),50)
56
+
57
+
58
+ x_lims = (12.5,12.5)
59
+ y_lims = (0.0,33.0)
60
+ z_lims = (0.0,12.0)
61
+ n_sens = (1,4,1)
62
+ tc_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
63
+
64
+ tc_sens_data = pyv.SensorData(positions=tc_sens_pos,
65
+ sample_times=sample_times)
66
+ #%%
67
+ # We use the sensor array factory to create our thermocouple array with no
68
+ # errors.
69
+ tc_field_name = "temperature"
70
+ tc_array = pyv.SensorArrayFactory \
71
+ .thermocouples_no_errs(sim_data,
72
+ tc_sens_data,
73
+ elem_dims=elem_dims,
74
+ field_name=tc_field_name)
75
+ #%%
76
+ # Now we build our error chain starting with some basic errors on the order
77
+ # of 1 degree.
78
+ tc_err_chain = []
79
+ tc_err_chain.append(pyv.ErrSysUnif(low=1.0,high=1.0))
80
+ tc_err_chain.append(pyv.ErrRandNorm(std=1.0))
81
+
82
+ #%%
83
+ # Now we add positioning error for our thermocouples.
84
+ tc_pos_uncert = 0.1 # units = mm
85
+ tc_pos_rand = (pyv.GenNormal(std=tc_pos_uncert),
86
+ pyv.GenNormal(std=tc_pos_uncert),
87
+ pyv.GenNormal(std=tc_pos_uncert))
88
+
89
+ #%%
90
+ # We block translation in x so the thermocouples stay attached.
91
+ tc_pos_lock = np.full(tc_sens_pos.shape,False,dtype=bool)
92
+ tc_pos_lock[:,0] = True
93
+
94
+ tc_field_err_data = pyv.ErrFieldData(pos_rand_xyz=tc_pos_rand,
95
+ pos_lock_xyz=tc_pos_lock)
96
+ tc_err_chain.append(pyv.ErrSysField(tc_array.get_field(),
97
+
98
+ tc_field_err_data))
99
+ #%%
100
+ # We have finished our error chain so we can build our error integrator and
101
+ # attach it to our thermocouple array.
102
+ tc_error_int = pyv.ErrIntegrator(tc_err_chain,
103
+ tc_sens_data,
104
+ tc_array.get_measurement_shape())
105
+ tc_array.set_error_integrator(tc_error_int)
106
+
107
+ #%%
108
+ # We visualise our thermcouple locations on our mesh to make sure they are
109
+ # in the correct positions.
110
+ pv_plot = pyv.plot_point_sensors_on_sim(tc_array,"temperature")
111
+ pv_plot.camera_position = [(59.354, 43.428, 69.946),
112
+ (-2.858, 13.189, 4.523),
113
+ (-0.215, 0.948, -0.233)]
114
+ if save_figs:
115
+ pv_plot.save_graphic(fig_save_path/(save_tag+"_tc_vis.svg"))
116
+ pv_plot.screenshot(fig_save_path/(save_tag+"_tc_vis.png"))
117
+
118
+ pv_plot.show()
119
+
120
+ #%%
121
+ # Now we have finished with our thermocouple array we can move on to our
122
+ # strain gauge array.
123
+ #
124
+ # We use the same sampling time but we are going to place the strain gauges
125
+ # down the side of the monoblock where the pipe passes through.
126
+ x_lims = (9.4,9.4)
127
+ y_lims = (0.0,33.0)
128
+ z_lims = (12.0,12.0)
129
+ n_sens = (1,4,1)
130
+ sg_sens_pos = pyv.create_sensor_pos_array(n_sens,x_lims,y_lims,z_lims)
131
+
132
+ sg_sens_data = pyv.SensorData(positions=sg_sens_pos,
133
+ sample_times=sample_times)
134
+
135
+ #%%
136
+ # We use the sensor array factory to give us a strain gauge array with no
137
+ # errors.
138
+ sg_field_name = "strain"
139
+ sg_norm_comps = ("strain_xx","strain_yy","strain_zz")
140
+ sg_dev_comps = ("strain_xy","strain_yz","strain_xz")
141
+ sg_array = pyv.SensorArrayFactory \
142
+ .strain_gauges_no_errs(sim_data,
143
+ sg_sens_data,
144
+ elem_dims=elem_dims,
145
+ field_name=sg_field_name,
146
+ norm_comps=sg_norm_comps,
147
+ dev_comps=sg_dev_comps)
148
+
149
+ #%%
150
+ # Now we build our error chain starting with some basic errors on the order
151
+ # of 1 percent.
152
+ sg_err_chain = []
153
+ sg_err_chain.append(pyv.ErrSysUnifPercent(low_percent=1.0,high_percent=1.0))
154
+ sg_err_chain.append(pyv.ErrRandNormPercent(std_percent=1.0))
155
+
156
+ #%%
157
+ # We are going to add +/-2 degree rotation uncertainty to our strain gauges.
158
+ angle_uncert = 2.0
159
+ angle_rand_zyx = (pyv.GenUniform(low=-angle_uncert,high=angle_uncert), # units = deg
160
+ pyv.GenUniform(low=-angle_uncert,high=angle_uncert),
161
+ pyv.GenUniform(low=-angle_uncert,high=angle_uncert))
162
+
163
+ #%%
164
+ # We only allow rotation on the face the strain gauges are on
165
+ angle_lock = np.full(sg_sens_pos.shape,True,dtype=bool)
166
+ angle_lock[:,0] = False # Allow rotation about z
167
+
168
+ sg_field_err_data = pyv.ErrFieldData(ang_rand_zyx=angle_rand_zyx,
169
+ ang_lock_zyx=angle_lock)
170
+ sg_err_chain.append(pyv.ErrSysField(sg_array.get_field(),
171
+ sg_field_err_data))
172
+
173
+ #%%
174
+ # We have finished our error chain so we can build our error integrator and
175
+ # attach it to our thermocouple array.
176
+ sg_error_int = pyv.ErrIntegrator(sg_err_chain,
177
+ sg_sens_data,
178
+ sg_array.get_measurement_shape())
179
+ sg_array.set_error_integrator(sg_error_int)
180
+
181
+ #%%
182
+ # Now we visualise the strain gauge locations to make sure they are where
183
+ # we expect them to be.
184
+ pv_plot = pyv.plot_point_sensors_on_sim(sg_array,"strain_yy")
185
+ pv_plot.camera_position = [(59.354, 43.428, 69.946),
186
+ (-2.858, 13.189, 4.523),
187
+ (-0.215, 0.948, -0.233)]
188
+ if save_figs:
189
+ pv_plot.save_graphic(fig_save_path/(save_tag+"_sg_vis.svg"))
190
+ pv_plot.screenshot(fig_save_path/(save_tag+"_sg_vis.png"))
191
+
192
+ pv_plot.show()
193
+
194
+ #%%
195
+ # We have both our sensor arrays so we will create and run our experiment.
196
+ # Here we only have a single input simulation in our list and we only run
197
+ # 100 simulated experiments as we are going to plot all simulated data
198
+ # points on our traces. Note that if you are running more than 100
199
+ # experiments here you will need to set the trace plots below to not show
200
+ # all points on the graph.
201
+ sim_list = [sim_data,]
202
+ sensor_arrays = [tc_array,sg_array]
203
+ exp_sim = pyv.ExperimentSimulator(sim_list,
204
+ sensor_arrays,
205
+ num_exp_per_sim=100)
206
+
207
+ #%%
208
+ # We run our experiments and calculate summary statistics as in the previous
209
+ # example
210
+ exp_data = exp_sim.run_experiments()
211
+ exp_stats = exp_sim.calc_stats()
212
+
213
+ #%%
214
+ # We print the lengths of our exp_data and exp_stats lists along with the
215
+ # shape of the numpy arrays they contain so we can index into them easily.
216
+ print(80*"=")
217
+ print("exp_data and exp_stats are lists where the index is the sensor array")
218
+ print("position in the list as field components are not consistent dims:")
219
+ print(f"{len(exp_data)=}")
220
+ print(f"{len(exp_stats)=}")
221
+ print()
222
+ print(80*"-")
223
+ print("Thermal sensor array @ exp_data[0]")
224
+ print(80*"-")
225
+ print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)")
226
+ print(f"{exp_data[0].shape=}")
227
+ print()
228
+ print("Stats are calculated over all experiments (axis=1)")
229
+ print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)")
230
+ print(f"{exp_stats[0].max.shape=}")
231
+ print()
232
+ print(80*"-")
233
+ print("Mechanical sensor array @ exp_data[1]")
234
+ print(80*"-")
235
+ print("shape=(n_sims,n_exps,n_sensors,n_field_comps,n_time_steps)")
236
+ print(f"{exp_data[1].shape=}")
237
+ print()
238
+ print("shape=(n_sims,n_sensors,n_field_comps,n_time_steps)")
239
+ print(f"{exp_stats[1].max.shape=}")
240
+ print(80*"=")
241
+
242
+ #%%
243
+ # Finally, we are going to plot the simulated sensor traces but we are going
244
+ # to control some of the plotting options using the options data class here.
245
+ # We set the plot to show all simulated experiment data points and to plot
246
+ # the median as the centre line and to fill between the min and max values.
247
+ # Note that the default here is to plot the mean and fill between 3 times
248
+ # the standard deviation.
249
+ trace_opts = pyv.TraceOptsExperiment(plot_all_exp_points=True,
250
+ centre=pyv.EExpVisCentre.MEDIAN,
251
+ fill_between=pyv.EExpVisBounds.MINMAX)
252
+
253
+ (fig,ax) = pyv.plot_exp_traces(exp_sim,
254
+ component="temperature",
255
+ sens_array_num=0,
256
+ sim_num=0,
257
+ trace_opts=trace_opts)
258
+ if save_figs:
259
+ fig.savefig(fig_save_path/(save_tag+"_tc_traces.png"),
260
+ dpi=300, format='png', bbox_inches='tight')
261
+
262
+ (fig,ax) = pyv.plot_exp_traces(exp_sim,
263
+ component="strain_yy",
264
+ sens_array_num=1,
265
+ sim_num=0,
266
+ trace_opts=trace_opts)
267
+ if save_figs:
268
+ fig.savefig(fig_save_path/(save_tag+"_sg_traces.png"),
269
+ dpi=300, format='png', bbox_inches='tight')
270
+ plt.show()
@@ -0,0 +1,98 @@
1
+ #================================================================================
2
+ #Example: thermocouples on a 2d plate
3
+ #
4
+ #pyvale: the python validation engine
5
+ #License: MIT
6
+ #Copyright (C) 2024 The Computer Aided Validation Team
7
+ #================================================================================
8
+
9
+ """
10
+ Selecting a Region of Interest (ROI)
11
+ ---------------------------------------------
12
+
13
+ This example looks at the current core functionality of the Region of Interest
14
+ (ROI) Selection Firsly we'll need to import `pyvale` itself.
15
+ """
16
+
17
+ from pathlib import Path
18
+ import pyvale
19
+
20
+ # %%
21
+ # We'll begin by selecting our Region of Interest (ROI) using the interactive selection tool.
22
+ # First, we create an instance of the ROI class. We pass a reference image to it, which is
23
+ # displayed as the underlay during ROI selection.
24
+ ref_img = pyvale.DataSet.dic_plate_with_hole_ref()
25
+ roi = pyvale.DICRegionOfInterest(ref_image=ref_img)
26
+ roi.interactive_selection(subset_size=31)
27
+
28
+ # create a directory for the the different outputs
29
+ output_path = Path.cwd() / "pyvale-output"
30
+ if not output_path.is_dir():
31
+ output_path.mkdir(parents=True, exist_ok=True)
32
+
33
+ # %%
34
+ # .. image:: ../../../../_static/roi_tool.gif
35
+ # :alt: ROI selection GUI (animated)
36
+ # :width: 600px
37
+ # :align: center
38
+
39
+ # %%
40
+ # After closing the interactive tool, a mask and a set of seed coordinates will be generated.
41
+ # These can be used directly in the DIC engine. If you plan to reuse the ROI, it’s a good idea
42
+ # to save it. For very large images, set `binary=True` to reduce file size and speed up saving.
43
+ roi_file = output_path / "roi.dat"
44
+ roi.save_array(filename=roi_file,binary=False)
45
+
46
+ # %%
47
+ # To reuse the saved ROI mask in the future, load it using:
48
+ roi.read_array(filename=roi_file,binary=False)
49
+
50
+ # %%
51
+ # If you are loading a previously saved ROI, you may want to visualize it
52
+ # overlaid on the reference image to verify it before proceeding with correlation.
53
+ roi.show_image()
54
+
55
+ # %%
56
+ # There are also programmatic ways to define an ROI.
57
+ # For example, to exclude a boundary region and keep only the central part:
58
+ roi.reset_mask()
59
+ roi.rect_boundary(left=50,right=50,bottom=50,top=50)
60
+ boundary_img = output_path / "rect_boundary.tiff"
61
+ roi.save_image(boundary_img)
62
+
63
+ # %%
64
+ # This excludes 50 pixels along each edge of the image from the ROI.
65
+ # Alternatively, to define a specific rectangular region:
66
+ roi.reset_mask()
67
+ roi.rect_region(x=200,y=200,size_x=200,size_y=200)
68
+ region_img = output_path / "rect_region.tiff"
69
+ roi.save_image(region_img)
70
+
71
+ # %%
72
+ # .. list-table::
73
+ # :widths: 50 50
74
+ # :align: center
75
+ # :header-rows: 0
76
+ #
77
+ # * - .. figure:: ../../../../_static/rect_boundary.png
78
+ # :width: 300px
79
+ # :align: center
80
+ #
81
+ # ``roi.rect_boundary(left=200, right=200, bottom=200, top=200)``
82
+ #
83
+ # - .. figure:: ../../../../_static/rect_region.png
84
+ # :width: 300px
85
+ # :align: center
86
+ #
87
+ # ``roi.rect_region(x=200, y=200, size_x=200, size_y=200)``
88
+
89
+ # %%
90
+ # The `rect_region` example above creates an ROI starting at pixel coordinates (200, 200)
91
+ # with a size of 200×200 pixels.
92
+ #
93
+ # You can also manually modify the ROI mask. A good starting point is:
94
+ # `roi.rect_boundary(0, 0, 0, 0)` — this sets the ROI to include the full image.
95
+ # From there, you can manipulate `roi.mask` as you would any other 2D NumPy array.
96
+
97
+
98
+