pyvale 2025.5.3__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 (174) hide show
  1. pyvale/__init__.py +89 -0
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/analyticsimdatafactory.py +91 -0
  4. pyvale/analyticsimdatagenerator.py +323 -0
  5. pyvale/blendercalibrationdata.py +15 -0
  6. pyvale/blenderlightdata.py +26 -0
  7. pyvale/blendermaterialdata.py +15 -0
  8. pyvale/blenderrenderdata.py +30 -0
  9. pyvale/blenderscene.py +488 -0
  10. pyvale/blendertools.py +420 -0
  11. pyvale/camera.py +146 -0
  12. pyvale/cameradata.py +69 -0
  13. pyvale/cameradata2d.py +84 -0
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/cameratools.py +522 -0
  16. pyvale/cython/rastercyth.c +32211 -0
  17. pyvale/cython/rastercyth.cp311-win_amd64.pyd +0 -0
  18. pyvale/cython/rastercyth.py +640 -0
  19. pyvale/data/__init__.py +5 -0
  20. pyvale/data/cal_target.tiff +0 -0
  21. pyvale/data/case00_HEX20_out.e +0 -0
  22. pyvale/data/case00_HEX27_out.e +0 -0
  23. pyvale/data/case00_HEX8_out.e +0 -0
  24. pyvale/data/case00_TET10_out.e +0 -0
  25. pyvale/data/case00_TET14_out.e +0 -0
  26. pyvale/data/case00_TET4_out.e +0 -0
  27. pyvale/data/case13_out.e +0 -0
  28. pyvale/data/case16_out.e +0 -0
  29. pyvale/data/case17_out.e +0 -0
  30. pyvale/data/case18_1_out.e +0 -0
  31. pyvale/data/case18_2_out.e +0 -0
  32. pyvale/data/case18_3_out.e +0 -0
  33. pyvale/data/case25_out.e +0 -0
  34. pyvale/data/case26_out.e +0 -0
  35. pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
  36. pyvale/dataset.py +325 -0
  37. pyvale/errorcalculator.py +109 -0
  38. pyvale/errordriftcalc.py +146 -0
  39. pyvale/errorintegrator.py +336 -0
  40. pyvale/errorrand.py +607 -0
  41. pyvale/errorsyscalib.py +134 -0
  42. pyvale/errorsysdep.py +327 -0
  43. pyvale/errorsysfield.py +414 -0
  44. pyvale/errorsysindep.py +808 -0
  45. pyvale/examples/__init__.py +5 -0
  46. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  47. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  48. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  49. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  50. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  51. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  52. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  53. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  54. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  55. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  56. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  57. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  58. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  59. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  60. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  61. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  62. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  63. pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +35 -0
  64. pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +43 -0
  65. pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +80 -0
  66. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +79 -0
  67. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  68. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  69. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  70. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  71. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  72. pyvale/examples/renderrasterisation/ex_rastenp.py +153 -0
  73. pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +218 -0
  74. pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +187 -0
  75. pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +190 -0
  76. pyvale/examples/visualisation/ex1_1_plot_traces.py +102 -0
  77. pyvale/examples/visualisation/ex2_1_animate_sim.py +89 -0
  78. pyvale/experimentsimulator.py +175 -0
  79. pyvale/field.py +128 -0
  80. pyvale/fieldconverter.py +351 -0
  81. pyvale/fieldsampler.py +111 -0
  82. pyvale/fieldscalar.py +166 -0
  83. pyvale/fieldtensor.py +218 -0
  84. pyvale/fieldtransform.py +388 -0
  85. pyvale/fieldvector.py +213 -0
  86. pyvale/generatorsrandom.py +505 -0
  87. pyvale/imagedef2d.py +569 -0
  88. pyvale/integratorfactory.py +240 -0
  89. pyvale/integratorquadrature.py +217 -0
  90. pyvale/integratorrectangle.py +165 -0
  91. pyvale/integratorspatial.py +89 -0
  92. pyvale/integratortype.py +43 -0
  93. pyvale/output.py +17 -0
  94. pyvale/pyvaleexceptions.py +11 -0
  95. pyvale/raster.py +31 -0
  96. pyvale/rastercy.py +77 -0
  97. pyvale/rasternp.py +603 -0
  98. pyvale/rendermesh.py +147 -0
  99. pyvale/sensorarray.py +178 -0
  100. pyvale/sensorarrayfactory.py +196 -0
  101. pyvale/sensorarraypoint.py +278 -0
  102. pyvale/sensordata.py +71 -0
  103. pyvale/sensordescriptor.py +213 -0
  104. pyvale/sensortools.py +142 -0
  105. pyvale/simcases/case00_HEX20.i +242 -0
  106. pyvale/simcases/case00_HEX27.i +242 -0
  107. pyvale/simcases/case00_HEX8.i +242 -0
  108. pyvale/simcases/case00_TET10.i +242 -0
  109. pyvale/simcases/case00_TET14.i +242 -0
  110. pyvale/simcases/case00_TET4.i +242 -0
  111. pyvale/simcases/case01.i +101 -0
  112. pyvale/simcases/case02.i +156 -0
  113. pyvale/simcases/case03.i +136 -0
  114. pyvale/simcases/case04.i +181 -0
  115. pyvale/simcases/case05.i +234 -0
  116. pyvale/simcases/case06.i +305 -0
  117. pyvale/simcases/case07.geo +135 -0
  118. pyvale/simcases/case07.i +87 -0
  119. pyvale/simcases/case08.geo +144 -0
  120. pyvale/simcases/case08.i +153 -0
  121. pyvale/simcases/case09.geo +204 -0
  122. pyvale/simcases/case09.i +87 -0
  123. pyvale/simcases/case10.geo +204 -0
  124. pyvale/simcases/case10.i +257 -0
  125. pyvale/simcases/case11.geo +337 -0
  126. pyvale/simcases/case11.i +147 -0
  127. pyvale/simcases/case12.geo +388 -0
  128. pyvale/simcases/case12.i +329 -0
  129. pyvale/simcases/case13.i +140 -0
  130. pyvale/simcases/case14.i +159 -0
  131. pyvale/simcases/case15.geo +337 -0
  132. pyvale/simcases/case15.i +150 -0
  133. pyvale/simcases/case16.geo +391 -0
  134. pyvale/simcases/case16.i +357 -0
  135. pyvale/simcases/case17.geo +135 -0
  136. pyvale/simcases/case17.i +144 -0
  137. pyvale/simcases/case18.i +254 -0
  138. pyvale/simcases/case18_1.i +254 -0
  139. pyvale/simcases/case18_2.i +254 -0
  140. pyvale/simcases/case18_3.i +254 -0
  141. pyvale/simcases/case19.geo +252 -0
  142. pyvale/simcases/case19.i +99 -0
  143. pyvale/simcases/case20.geo +252 -0
  144. pyvale/simcases/case20.i +250 -0
  145. pyvale/simcases/case21.geo +74 -0
  146. pyvale/simcases/case21.i +155 -0
  147. pyvale/simcases/case22.geo +82 -0
  148. pyvale/simcases/case22.i +140 -0
  149. pyvale/simcases/case23.geo +164 -0
  150. pyvale/simcases/case23.i +140 -0
  151. pyvale/simcases/case24.geo +79 -0
  152. pyvale/simcases/case24.i +123 -0
  153. pyvale/simcases/case25.geo +82 -0
  154. pyvale/simcases/case25.i +140 -0
  155. pyvale/simcases/case26.geo +166 -0
  156. pyvale/simcases/case26.i +140 -0
  157. pyvale/simcases/run_1case.py +61 -0
  158. pyvale/simcases/run_all_cases.py +69 -0
  159. pyvale/simcases/run_build_case.py +64 -0
  160. pyvale/simcases/run_example_cases.py +69 -0
  161. pyvale/simtools.py +67 -0
  162. pyvale/visualexpplotter.py +191 -0
  163. pyvale/visualimagedef.py +74 -0
  164. pyvale/visualimages.py +76 -0
  165. pyvale/visualopts.py +493 -0
  166. pyvale/visualsimanimator.py +111 -0
  167. pyvale/visualsimsensors.py +318 -0
  168. pyvale/visualtools.py +136 -0
  169. pyvale/visualtraceplotter.py +142 -0
  170. pyvale-2025.5.3.dist-info/METADATA +144 -0
  171. pyvale-2025.5.3.dist-info/RECORD +174 -0
  172. pyvale-2025.5.3.dist-info/WHEEL +5 -0
  173. pyvale-2025.5.3.dist-info/licenses/LICENSE +21 -0
  174. pyvale-2025.5.3.dist-info/top_level.txt +1 -0
pyvale/visualopts.py ADDED
@@ -0,0 +1,493 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ This module contains options dataclasses for controlling the appearance of
9
+ visualisations in pyvale.
10
+ """
11
+ import platform
12
+ from pathlib import Path
13
+ import enum
14
+ from dataclasses import dataclass, field
15
+ import numpy as np
16
+ import matplotlib as plt
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class PlotOptsGeneral:
21
+ """Dataclass for controlling the properties of figures and graphs such as
22
+ figure size, resolution, font sizes, marker sizes, line widths and
23
+ colormaps. This dataclass is used to interact with matplotlib and pyvista
24
+ so units conform to these packages. The defaults set in this dataclass are
25
+ selected based on producing print quality figures for journal articles.
26
+ """
27
+
28
+ aspect_ratio: float = 1.62
29
+ """Aspect ratio of the figure canvas.
30
+ """
31
+
32
+ single_fig_scale: float = 0.5
33
+ """Scaling for a single column figure, defaults to a half (0.5) page width.
34
+ """
35
+
36
+ resolution: float = 300.0
37
+ """Figure resolution in dpi, defaults to 300dpi for print quality.
38
+ """
39
+
40
+ font_def_weight: str = "normal"
41
+ """Default weight for fonts on plots.
42
+ """
43
+
44
+ font_def_size: float = 8.0
45
+ """Default font size for plots.
46
+ """
47
+
48
+ font_tick_size: float = 8.0
49
+ """Default font tick label size
50
+ """
51
+
52
+ font_head_size: float = 9.0
53
+ """Default font size for headings/titles on plots.
54
+ """
55
+
56
+ font_ax_size: float = 8.0
57
+ """Default axis label font size.
58
+ """
59
+
60
+ font_leg_size: float = 8.0
61
+ """Default font size for legends.
62
+ """
63
+
64
+ ms: float = 3.2
65
+ """Marker size for points on plots
66
+ """
67
+
68
+ lw: float = 0.8
69
+ """Line width for traces on plots.
70
+ """
71
+
72
+ cmap_seq: str = "cividis"
73
+ """The colormap to use for monotonic fields
74
+ """
75
+
76
+ cmap_div: str = "RdBu"
77
+ """The colormap to use for diverging fields, defaults to Red-Blue.
78
+ """
79
+
80
+ colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
81
+ """Color cycle for lines on plots.
82
+ """
83
+
84
+ # These are in inches because of matplotlib
85
+ a4_width: float = 8.25
86
+ """Width of an A4 page in inches.
87
+ """
88
+
89
+ a4_height: float = 11.75
90
+ """Height of an A4 page in inches.
91
+ """
92
+
93
+ a4_margin_width: float = 0.5
94
+ """Margin width on an A4 page in inches.
95
+ """
96
+
97
+ a4_margin_height: float = 0.5
98
+ """Margin heigh on an A4 page in inches.
99
+ """
100
+
101
+ font_name: str = field(init=False)
102
+ """Does not need to be initialised, calculated from other inputs. Name of
103
+ the font to use. Defaults to Arial on Windows/Mac and Liberation Sans on
104
+ Linux.
105
+ """
106
+
107
+ a4_print_width: float = field(init=False)
108
+ """Does not need to be initialised, calculated from other inputs. Printable
109
+ width of an A4 page in inches based on subtracting twice the margin width.
110
+ """
111
+
112
+ a4_print_height: float = field(init=False)
113
+ """Does not need to be initialised, calculated from other inputs. Printable
114
+ height of an A4 page in inches based on subtracting twice the margin width.
115
+ """
116
+
117
+ single_fig_size_square: tuple[float,float] = field(init=False)
118
+ """Does not need to be initialised, calculated from other inputs. Uses the
119
+ printable A4 width and the single figure scaling to create a square canvas
120
+ that fits in a single column of a two column journal article.
121
+ """
122
+
123
+ single_fig_size_portrait: tuple[float,float] = field(init=False)
124
+ """Does not need to be initialised, calculated from other inputs. Uses the
125
+ printable A4 width and the single figure scaling to create a potrait canvas
126
+ that fits in a single column of a two column journal article.
127
+ """
128
+
129
+ single_fig_size_landscape: tuple[float,float] = field(init=False)
130
+ """Does not need to be initialised, calculated from other inputs. Uses the
131
+ printable A4 width and the single figure scaling to create a landscape
132
+ canvas that fits in a single column of a two column journal article.
133
+ """
134
+
135
+ colors_num: int = field(init=False)
136
+ """Does not need to be initialised, calculated from other inputs. The number
137
+ of colors in the line color cycle.
138
+ """
139
+
140
+
141
+ def __post_init__(self) -> None:
142
+
143
+ self.font_name = "Arial"
144
+ if platform.system() == "Linux":
145
+ self.font_name = "Liberation Sans"
146
+
147
+ self.a4_print_width = self.a4_width-2*self.a4_margin_width
148
+ self.a4_print_height = self.a4_height-2*self.a4_margin_height
149
+
150
+ self.single_fig_size_square = (
151
+ self.a4_print_width*self.single_fig_scale,
152
+ self.a4_print_width*self.single_fig_scale
153
+ )
154
+
155
+ self.single_fig_size_portrait = (
156
+ self.a4_print_width*self.single_fig_scale/self.aspect_ratio,
157
+ self.a4_print_width*self.single_fig_scale
158
+ )
159
+ self.single_fig_size_landscape = (
160
+ self.a4_print_width*self.single_fig_scale,
161
+ self.a4_print_width*self.single_fig_scale/self.aspect_ratio
162
+ )
163
+
164
+ self.colors_num = len(self.colors)
165
+
166
+ plt.rc("font", size=self.font_def_size)
167
+ plt.rc("axes", titlesize=self.font_def_size)
168
+
169
+
170
+ @dataclass(slots=True)
171
+ class TraceOptsSensor:
172
+ """Dataclass for controlling the appearance of sensor trace plots including
173
+ axis labels, line styles and time over which to plot the sensor traces. Note
174
+ that latex symbols can be used in label strings by using a python raw string
175
+ . For example: r"strain, $\epsilon$ [-]".
176
+ """
177
+
178
+ legend_loc: str | None = "best"
179
+ """Set the legend location based on matplotlib legend location string. If
180
+ None then no legend is added. The legend lists the sensors by tag
181
+ """
182
+
183
+ x_label: str = r"x [$mm$]"
184
+ """Label for the x axis defaults to: r"x [$mm$]".
185
+ """
186
+
187
+ y_label: str = r"y [$mm$]"
188
+ """Label for the y axis defaults to: r"y [$mm$]".
189
+ """
190
+
191
+ z_label: str = r"z [$mm$]"
192
+ """Label for the z axis defaults to: r"z [$mm$]".
193
+ """
194
+
195
+ time_label: str = r"Time, $t$ [$s$]"
196
+ """Label for the time axis for traces pots which is assumed to be the
197
+ horizontal axis.
198
+ """
199
+
200
+ truth_line: str | None = "-"
201
+ """Matplotlib line style string for the ground truth virtual sensor values.
202
+ If None then the truth line is not plotted for all virtual sensors.
203
+ """
204
+
205
+ sim_line: str | None = None
206
+ """Matplotlib line style for the simulation output at the virtual sensor
207
+ locations. If None then the line is not plotted for all virtual sensors.
208
+ """
209
+
210
+ meas_line: str = "--+"
211
+ """Matplotlib line style for the virtual sensor measurement traces.
212
+ """
213
+
214
+ sensors_to_plot: np.ndarray | None = None
215
+ """Array (1D) of indices for the sensors to plot. If None then all sensors
216
+ are plotted. Defaults to None.
217
+ """
218
+
219
+ time_min_max: tuple[float,float] | None = None
220
+ """Time range over which to plot the sensor traces. If None then the full
221
+ time range is plotted. Defaults to None.
222
+ """
223
+
224
+
225
+ class EExpVisCentre(enum.Enum):
226
+ """Enumeration for plotting the center of the distribution of a series of
227
+ virtual sensor experiment traces.
228
+ """
229
+
230
+ MEAN = enum.auto()
231
+ """Mean over all virtual experiments for plotting the center of the virtual
232
+ experiment traces.
233
+ """
234
+
235
+ MEDIAN = enum.auto()
236
+ """Median over all virtual experiments for plotting the center of the
237
+ virtual experiment traces.
238
+ """
239
+
240
+
241
+ class EExpVisBounds(enum.Enum):
242
+ """Enumeration for plotting the uncertainty bounds of a series of virtual
243
+ sensor experiment traces. The uncertainty bounds are shown by filling
244
+ between the given upper and lower bounds. See the experient trace opts
245
+ dataclass which also allows for a scaling factor to be set to allow for
246
+ plotting a given multiple of the standard deviation.
247
+ """
248
+
249
+ MINMAX = enum.auto()
250
+ """Minimum and maximum over all virtual experiments for each sampling point.
251
+ """
252
+
253
+ QUARTILE = enum.auto()
254
+ """Lower 25% and upper 75% quartiles over all virtual experiments for each
255
+ sampling point.
256
+ """
257
+
258
+ MAD = enum.auto()
259
+ """Median absolute deviation over all virtual experiments for each sampling
260
+ point.
261
+ """
262
+
263
+ STD = enum.auto()
264
+ """Standard deviation over all virtual sensor experiments for each sampling
265
+ point.
266
+ """
267
+
268
+
269
+
270
+ @dataclass(slots=True)
271
+ class TraceOptsExperiment:
272
+ """Dataclass for controlling the properties of sensor trace plots from
273
+ batches of simulated experiments.
274
+ """
275
+
276
+ legend_loc: str | None = "best"
277
+ """Set the legend location based on matplotlib legend location string. If
278
+ None then no legend is added. The legend lists the sensors by tag
279
+ """
280
+
281
+ x_label: str = r"x [$mm$]"
282
+ """Label for the x axis defaults to: r"x [$mm$]".
283
+ """
284
+
285
+ y_label: str = r"y [$mm$]"
286
+ """Label for the y axis defaults to: r"y [$mm$]".
287
+ """
288
+
289
+ z_label: str = r"z [$mm$]"
290
+ """Label for the z axis defaults to: r"z [$mm$]".
291
+ """
292
+
293
+ time_label: str = r"Time, $t$ [$s$]"
294
+ """Label for the time axis for traces pots which is assumed to be the
295
+ horizontal axis.
296
+ """
297
+
298
+ truth_line: str | None = "-"
299
+ """Matplotlib line style string for the ground truth virtual sensor values.
300
+ If None then the truth line is not plotted for all virtual sensors.
301
+ """
302
+
303
+ sim_line: str | None = None
304
+ """Matplotlib line style for the simulation output at the virtual sensor
305
+ locations. If None then the line is not plotted for all virtual sensors.
306
+ """
307
+
308
+ exp_centre_line: str = "-"
309
+ """Matplotlib line style string for the experiment centre line.
310
+ """
311
+
312
+ exp_marker_line: str = "+"
313
+ """Maplotlib line style string use for plotting all experiments.
314
+ """
315
+
316
+ sensors_to_plot: np.ndarray | None = None
317
+ """Array (1D) of indices for the sensors to plot. If None then all sensors
318
+ are plotted. Defaults to None.
319
+ """
320
+
321
+ time_min_max: tuple[float,float] | None = None
322
+ """Time range over which to plot the sensor traces. If None then the full
323
+ time range is plotted. Defaults to None.
324
+ """
325
+
326
+ centre: EExpVisCentre = EExpVisCentre.MEAN
327
+ """Specifies the summary statistic to use for the center line of the sensor
328
+ trace distribution. Defaults to EExpVisCentre.MEAN.
329
+ """
330
+
331
+ fill_between: EExpVisBounds | None = EExpVisBounds.MINMAX
332
+ """Specifies the summary statistic to use for plotting the uncertainty
333
+ bounds for the virtual sensor traces. Defaults to EExpVisBounds.MINMAX.
334
+ Note that this statistic will be multipled by the fill_scale parameter.
335
+ """
336
+
337
+ fill_scale: float = 1.0
338
+ """Scaling factor multiplied by the uncertainty bound summary statistic for
339
+ showing filled uncertainty bounds on sensor traces plots. Defaults to 1.0.
340
+ A common setting would be 2.0 or 3.0 while setting fill_between =
341
+ EExpVisBounds.STD (standard deviation).
342
+ """
343
+
344
+ plot_all_exp_points: bool = False
345
+ """Allows all experiment points to be plotted. Note that for more than 100
346
+ experiments for a given sensor array this will be slow. Defaults to False.
347
+ """
348
+
349
+
350
+ @dataclass(slots=True)
351
+ class VisOptsSimSensors:
352
+ """Dataclass for controlling displays of the simulation mesh and sensor
353
+ locations using pyvista.
354
+ """
355
+ # pyvista ops
356
+ window_size_px: tuple[int,int] = (1280,800)
357
+ """Window size for pyvista canvas in pixels: (horizontal_px,vertical_px).
358
+ """
359
+
360
+ camera_position: np.ndarray | str = "xy"
361
+ """Camera position for the pyvista view either as a string of axis labels
362
+ or as a 3x3 rotation matrix. Defaults to viewing the x-y plane with "xy".
363
+ """
364
+
365
+ show_edges: bool = True
366
+ """Flag to show the element edges in visualisations. Defaults to True.
367
+ """
368
+
369
+ interactive: bool = True
370
+ """Flag to allow interactive viewing of the plot. Defaults to True.
371
+ """
372
+
373
+ font_colour: str = "black"
374
+ """Font colour string. Useful for creating "dark mode" style plots with
375
+ "white" font and a "black background". Defaults to "light mode" with "black"
376
+ font.
377
+ """
378
+
379
+ background_colour: str = "white"
380
+ """Background colour string. Useful for creating "dark mode" style plots
381
+ with "white" font and a "black background". Defaults to "light mode" with
382
+ "black" font.
383
+ """
384
+
385
+ time_label_pos: str | None = "upper_left"
386
+ """Position of the simulation time step label. If None then the simulation
387
+ time step label is not shown. Defaults to "upper_left".
388
+ """
389
+
390
+ time_label_font_size: int = 12
391
+ """Font size for the simulation time step label on the canvas. Defaults to
392
+ 12.
393
+ """
394
+
395
+ colour_bar_show: bool = True
396
+ """Flag to show the colourbar for the simulation field. Defaults to True.
397
+ """
398
+
399
+ colour_bar_font_size: int = 18
400
+ """Font size for the colourbar. Defaults to 18.
401
+ """
402
+
403
+ colour_bar_lims: tuple[float,float] | None = None
404
+ """Max and min limits for the colour bar. If None the default limits are
405
+ used.
406
+ """
407
+
408
+ colour_bar_vertical: bool = True
409
+ """Flag to set the colourbar to vertical instead of horizontal. Defaults to
410
+ True.
411
+ """
412
+
413
+ # pyvale ops
414
+ show_perturbed_pos: bool = True
415
+ """Flag to show the perturbed sensor positions if field errors are used.
416
+ Defaults to True.
417
+ """
418
+
419
+ sens_colour_nom: str = "red"
420
+ """Colour for the markers showing the nominal sensor locations.
421
+ """
422
+
423
+ sens_colour_pert: str = "blue"
424
+ """Colour for the markers showing the perturbed sensor locations.
425
+ """
426
+
427
+ sens_point_size: float = 20.0
428
+ """Size for the markers used to show the sensor locations on the mesh.
429
+ """
430
+
431
+ sens_label_font_size: int = 30
432
+ """Font size for the sensor marker labels.
433
+ """
434
+
435
+ sens_label_colour: str = "grey"
436
+ """Colour for the sensor labels. Note that this needs to provide reasonable
437
+ contrast with the selected font colour so "grey" is the default.
438
+ """
439
+
440
+
441
+ class EImageType(enum.Enum):
442
+ """NOTE: This is a feature under developement.
443
+
444
+ Enumeration for specifying the format for saving images.
445
+ """
446
+ PNG = enum.auto()
447
+ SVG = enum.auto()
448
+
449
+
450
+ @dataclass(slots=True)
451
+ class VisOptsImageSave:
452
+ """NOTE: This is a feature under developement.
453
+
454
+ Dataclass for image saving options.
455
+ """
456
+
457
+ path: Path | None = None
458
+ image_type: EImageType = EImageType.PNG
459
+ transparent_background: bool = False
460
+
461
+
462
+
463
+ class EAnimationType(enum.Enum):
464
+ """NOTE: This is a feature under developement.
465
+
466
+ Enumeration for specifying the save file type for animations.
467
+ """
468
+ MP4 = enum.auto()
469
+ GIF = enum.auto()
470
+
471
+
472
+ @dataclass(slots=True)
473
+ class VisOptsAnimation:
474
+ """NOTE: This is a feature under developement.
475
+
476
+ Dataclass for animation save options.
477
+ """
478
+
479
+ frames_per_second: float = 10.0
480
+ off_screen: bool = False
481
+ save_animation: EAnimationType | None = None
482
+ save_path: Path | None = None
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+
@@ -0,0 +1,111 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ This module contains functions for animating simulation fields including
9
+ visualisation of virtual sensor locations on the simulation mesh using pyvista.
10
+ """
11
+
12
+ import numpy as np
13
+ #import vtk #NOTE: has to be here to fix latex bug in pyvista/vtk
14
+ # See: https://github.com/pyvista/pyvista/discussions/2928
15
+ #NOTE: causes output to console to be suppressed unfortunately
16
+ import pyvista as pv
17
+ from pyvale.sensorarraypoint import SensorArrayPoint
18
+ from pyvale.visualopts import VisOptsSimSensors, VisOptsAnimation
19
+ from pyvale.visualtools import (create_pv_plotter,
20
+ get_colour_lims,
21
+ set_animation_writer)
22
+ from pyvale.visualsimsensors import (add_sensor_points_nom,
23
+ add_sensor_points_pert,
24
+ add_sim_field)
25
+
26
+
27
+ def animate_sim_with_sensors(sensor_array: SensorArrayPoint,
28
+ component: str,
29
+ time_steps: np.ndarray | None = None,
30
+ vis_opts: VisOptsSimSensors | None = None,
31
+ anim_opts: VisOptsAnimation | None = None,
32
+ ) -> pv.Plotter:
33
+ """Creates an animation of the simulation fields using pyvista showing the
34
+ virtual sensor locations during the animation.
35
+
36
+ Parameters
37
+ ----------
38
+ sensor_array : SensorArrayPoint
39
+ Sensor array that will be displayed on the simulation while the
40
+ simulation results are animated.
41
+ component : str
42
+ String key for the field component to animate.
43
+ time_steps : np.ndarray | None, optional
44
+ Time steps over which to creatre the animation, by default None. If None
45
+ then the animation is performed over all time steps.
46
+ vis_opts : VisOptsSimSensors | None, optional
47
+ Dataclass containing options for controlling the appearance of the
48
+ virtual sensors, by default None. If None a default options dataclass is
49
+ created.
50
+ anim_opts : VisOptsAnimation | None, optional
51
+ Dataclass containing options for controlling the animation output, by
52
+ default None. If None then a default options dataclass is created.
53
+
54
+ Returns
55
+ -------
56
+ pv.Plotter
57
+ Handle to the pyvista plotter object used to create the animation.
58
+ """
59
+ if vis_opts is None:
60
+ vis_opts = VisOptsSimSensors()
61
+
62
+ if anim_opts is None:
63
+ anim_opts = VisOptsAnimation()
64
+
65
+ if time_steps is None:
66
+ time_steps = np.arange(0,sensor_array.get_sample_times().shape[0])
67
+
68
+ sim_data = sensor_array._field.get_sim_data()
69
+ vis_opts.colour_bar_lims = get_colour_lims(
70
+ sim_data.node_vars[component][:,time_steps],
71
+ vis_opts.colour_bar_lims)
72
+
73
+ #---------------------------------------------------------------------------
74
+ pv_plot = create_pv_plotter(vis_opts)
75
+
76
+ pv_plot = add_sensor_points_pert(pv_plot,sensor_array,vis_opts)
77
+ pv_plot = add_sensor_points_nom(pv_plot,sensor_array,vis_opts)
78
+ (pv_plot,sim_vis) = add_sim_field(pv_plot,
79
+ sensor_array,
80
+ component,
81
+ time_step = 0,
82
+ vis_opts = vis_opts)
83
+
84
+ pv_plot.camera_position = vis_opts.camera_position
85
+ pv_plot.show(auto_close=False,interactive=False)
86
+
87
+ pv_plot = set_animation_writer(pv_plot,anim_opts)
88
+
89
+ #---------------------------------------------------------------------------
90
+ for tt in time_steps:
91
+ # Updates the field plotted on the mesh
92
+ sim_vis[component] = sim_data.node_vars[component][:,tt]
93
+
94
+ if vis_opts.time_label_pos is not None:
95
+ pv_plot.add_text(f"Time: {sim_data.time[tt]} " + \
96
+ f"{sensor_array._descriptor.time_units}",
97
+ position=vis_opts.time_label_pos,
98
+ font_size=vis_opts.time_label_font_size,
99
+ name='time-label')
100
+
101
+ if anim_opts.save_animation is not None:
102
+ pv_plot.write_frame()
103
+
104
+ pv_plot.show(auto_close=False,interactive=vis_opts.interactive)
105
+ return pv_plot
106
+
107
+
108
+
109
+
110
+
111
+