pyvale 2025.4.0__py3-none-any.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.
- pyvale/__init__.py +75 -0
- pyvale/core/__init__.py +7 -0
- pyvale/core/analyticmeshgen.py +59 -0
- pyvale/core/analyticsimdatafactory.py +63 -0
- pyvale/core/analyticsimdatagenerator.py +160 -0
- pyvale/core/camera.py +146 -0
- pyvale/core/cameradata.py +64 -0
- pyvale/core/cameradata2d.py +82 -0
- pyvale/core/cameratools.py +328 -0
- pyvale/core/cython/rastercyth.c +32267 -0
- pyvale/core/cython/rastercyth.py +636 -0
- pyvale/core/dataset.py +250 -0
- pyvale/core/errorcalculator.py +112 -0
- pyvale/core/errordriftcalc.py +146 -0
- pyvale/core/errorintegrator.py +339 -0
- pyvale/core/errorrand.py +614 -0
- pyvale/core/errorsysdep.py +331 -0
- pyvale/core/errorsysfield.py +407 -0
- pyvale/core/errorsysindep.py +905 -0
- pyvale/core/experimentsimulator.py +99 -0
- pyvale/core/field.py +136 -0
- pyvale/core/fieldconverter.py +154 -0
- pyvale/core/fieldsampler.py +112 -0
- pyvale/core/fieldscalar.py +167 -0
- pyvale/core/fieldtensor.py +221 -0
- pyvale/core/fieldtransform.py +384 -0
- pyvale/core/fieldvector.py +215 -0
- pyvale/core/generatorsrandom.py +528 -0
- pyvale/core/imagedef2d.py +566 -0
- pyvale/core/integratorfactory.py +241 -0
- pyvale/core/integratorquadrature.py +192 -0
- pyvale/core/integratorrectangle.py +88 -0
- pyvale/core/integratorspatial.py +90 -0
- pyvale/core/integratortype.py +44 -0
- pyvale/core/optimcheckfuncs.py +153 -0
- pyvale/core/raster.py +31 -0
- pyvale/core/rastercy.py +76 -0
- pyvale/core/rasternp.py +604 -0
- pyvale/core/rendermesh.py +156 -0
- pyvale/core/sensorarray.py +179 -0
- pyvale/core/sensorarrayfactory.py +210 -0
- pyvale/core/sensorarraypoint.py +280 -0
- pyvale/core/sensordata.py +72 -0
- pyvale/core/sensordescriptor.py +101 -0
- pyvale/core/sensortools.py +143 -0
- pyvale/core/visualexpplotter.py +151 -0
- pyvale/core/visualimagedef.py +71 -0
- pyvale/core/visualimages.py +75 -0
- pyvale/core/visualopts.py +180 -0
- pyvale/core/visualsimanimator.py +83 -0
- pyvale/core/visualsimplotter.py +182 -0
- pyvale/core/visualtools.py +81 -0
- pyvale/core/visualtraceplotter.py +256 -0
- pyvale/data/__init__.py +7 -0
- pyvale/data/case13_out.e +0 -0
- pyvale/data/case16_out.e +0 -0
- pyvale/data/case17_out.e +0 -0
- pyvale/data/case18_1_out.e +0 -0
- pyvale/data/case18_2_out.e +0 -0
- pyvale/data/case18_3_out.e +0 -0
- pyvale/data/case25_out.e +0 -0
- pyvale/data/case26_out.e +0 -0
- pyvale/data/optspeckle_2464x2056px_spec5px_8bit_gblur1px.tiff +0 -0
- pyvale/examples/__init__.py +7 -0
- pyvale/examples/analyticdatagen/__init__.py +7 -0
- pyvale/examples/analyticdatagen/ex1_1_scalarvisualisation.py +38 -0
- pyvale/examples/analyticdatagen/ex1_2_scalarcasebuild.py +46 -0
- pyvale/examples/analyticdatagen/ex2_1_analyticsensors.py +83 -0
- pyvale/examples/ex1_1_thermal2d.py +89 -0
- pyvale/examples/ex1_2_thermal2d.py +111 -0
- pyvale/examples/ex1_3_thermal2d.py +113 -0
- pyvale/examples/ex1_4_thermal2d.py +89 -0
- pyvale/examples/ex1_5_thermal2d.py +105 -0
- pyvale/examples/ex2_1_thermal3d .py +87 -0
- pyvale/examples/ex2_2_thermal3d.py +51 -0
- pyvale/examples/ex2_3_thermal3d.py +109 -0
- pyvale/examples/ex3_1_displacement2d.py +47 -0
- pyvale/examples/ex3_2_displacement2d.py +79 -0
- pyvale/examples/ex3_3_displacement2d.py +104 -0
- pyvale/examples/ex3_4_displacement2d.py +105 -0
- pyvale/examples/ex4_1_strain2d.py +57 -0
- pyvale/examples/ex4_2_strain2d.py +79 -0
- pyvale/examples/ex4_3_strain2d.py +100 -0
- pyvale/examples/ex5_1_multiphysics2d.py +78 -0
- pyvale/examples/ex6_1_multiphysics2d_expsim.py +118 -0
- pyvale/examples/ex6_2_multiphysics3d_expsim.py +158 -0
- pyvale/examples/features/__init__.py +7 -0
- pyvale/examples/features/ex_animation_tools_3dmonoblock.py +83 -0
- pyvale/examples/features/ex_area_avg.py +89 -0
- pyvale/examples/features/ex_calibration_error.py +108 -0
- pyvale/examples/features/ex_chain_field_errs.py +141 -0
- pyvale/examples/features/ex_field_errs.py +78 -0
- pyvale/examples/features/ex_sensor_single_angle_batch.py +110 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +86 -0
- pyvale/examples/rasterisation/ex_rastenp.py +154 -0
- pyvale/examples/rasterisation/ex_rastercyth_oneframe.py +220 -0
- pyvale/examples/rasterisation/ex_rastercyth_static_cypara.py +194 -0
- pyvale/examples/rasterisation/ex_rastercyth_static_pypara.py +193 -0
- pyvale/simcases/case00_HEX20.i +242 -0
- pyvale/simcases/case00_HEX27.i +242 -0
- pyvale/simcases/case00_TET10.i +242 -0
- pyvale/simcases/case00_TET14.i +242 -0
- pyvale/simcases/case01.i +101 -0
- pyvale/simcases/case02.i +156 -0
- pyvale/simcases/case03.i +136 -0
- pyvale/simcases/case04.i +181 -0
- pyvale/simcases/case05.i +234 -0
- pyvale/simcases/case06.i +305 -0
- pyvale/simcases/case07.geo +135 -0
- pyvale/simcases/case07.i +87 -0
- pyvale/simcases/case08.geo +144 -0
- pyvale/simcases/case08.i +153 -0
- pyvale/simcases/case09.geo +204 -0
- pyvale/simcases/case09.i +87 -0
- pyvale/simcases/case10.geo +204 -0
- pyvale/simcases/case10.i +257 -0
- pyvale/simcases/case11.geo +337 -0
- pyvale/simcases/case11.i +147 -0
- pyvale/simcases/case12.geo +388 -0
- pyvale/simcases/case12.i +329 -0
- pyvale/simcases/case13.i +140 -0
- pyvale/simcases/case14.i +159 -0
- pyvale/simcases/case15.geo +337 -0
- pyvale/simcases/case15.i +150 -0
- pyvale/simcases/case16.geo +391 -0
- pyvale/simcases/case16.i +357 -0
- pyvale/simcases/case17.geo +135 -0
- pyvale/simcases/case17.i +144 -0
- pyvale/simcases/case18.i +254 -0
- pyvale/simcases/case18_1.i +254 -0
- pyvale/simcases/case18_2.i +254 -0
- pyvale/simcases/case18_3.i +254 -0
- pyvale/simcases/case19.geo +252 -0
- pyvale/simcases/case19.i +99 -0
- pyvale/simcases/case20.geo +252 -0
- pyvale/simcases/case20.i +250 -0
- pyvale/simcases/case21.geo +74 -0
- pyvale/simcases/case21.i +155 -0
- pyvale/simcases/case22.geo +82 -0
- pyvale/simcases/case22.i +140 -0
- pyvale/simcases/case23.geo +164 -0
- pyvale/simcases/case23.i +140 -0
- pyvale/simcases/case24.geo +79 -0
- pyvale/simcases/case24.i +123 -0
- pyvale/simcases/case25.geo +82 -0
- pyvale/simcases/case25.i +140 -0
- pyvale/simcases/case26.geo +166 -0
- pyvale/simcases/case26.i +140 -0
- pyvale/simcases/run_1case.py +61 -0
- pyvale/simcases/run_all_cases.py +69 -0
- pyvale/simcases/run_build_case.py +64 -0
- pyvale/simcases/run_example_cases.py +69 -0
- pyvale-2025.4.0.dist-info/METADATA +140 -0
- pyvale-2025.4.0.dist-info/RECORD +157 -0
- pyvale-2025.4.0.dist-info/WHEEL +5 -0
- pyvale-2025.4.0.dist-info/licenses/LICENSE +21 -0
- pyvale-2025.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
pyvale: the python validation engine
|
|
4
|
+
License: MIT
|
|
5
|
+
Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
================================================================================
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
from pyvale.core.visualopts import (PlotOptsGeneral,
|
|
13
|
+
TraceOptsExperiment)
|
|
14
|
+
from pyvale.core.experimentsimulator import ExperimentSimulator
|
|
15
|
+
|
|
16
|
+
#TODO: Docstrings
|
|
17
|
+
|
|
18
|
+
def plot_exp_traces(exp_sim: ExperimentSimulator,
|
|
19
|
+
component: str,
|
|
20
|
+
sens_array_num: int,
|
|
21
|
+
sim_num: int,
|
|
22
|
+
trace_opts: TraceOptsExperiment | None = None,
|
|
23
|
+
plot_opts: PlotOptsGeneral | None = None) -> tuple[Any,Any]:
|
|
24
|
+
|
|
25
|
+
if trace_opts is None:
|
|
26
|
+
trace_opts = TraceOptsExperiment()
|
|
27
|
+
|
|
28
|
+
if plot_opts is None:
|
|
29
|
+
plot_opts = PlotOptsGeneral()
|
|
30
|
+
|
|
31
|
+
descriptor = exp_sim.sensor_arrays[sens_array_num].descriptor
|
|
32
|
+
comp_ind = exp_sim.sensor_arrays[sens_array_num].field.get_component_index(component)
|
|
33
|
+
samp_time = exp_sim.sensor_arrays[sens_array_num].get_sample_times()
|
|
34
|
+
num_sens = exp_sim.sensor_arrays[sens_array_num].get_measurement_shape()[0]
|
|
35
|
+
|
|
36
|
+
exp_data = exp_sim.get_data()
|
|
37
|
+
exp_stats = exp_sim.get_stats()
|
|
38
|
+
|
|
39
|
+
if trace_opts.sensors_to_plot is None:
|
|
40
|
+
sensors_to_plot = range(num_sens)
|
|
41
|
+
else:
|
|
42
|
+
sensors_to_plot = trace_opts.sensors_to_plot
|
|
43
|
+
|
|
44
|
+
#---------------------------------------------------------------------------
|
|
45
|
+
# Figure canvas setup
|
|
46
|
+
fig, ax = plt.subplots(figsize=plot_opts.single_fig_size_portrait,
|
|
47
|
+
layout='constrained')
|
|
48
|
+
fig.set_dpi(plot_opts.resolution)
|
|
49
|
+
|
|
50
|
+
#---------------------------------------------------------------------------
|
|
51
|
+
# Plot all simulated experimental points
|
|
52
|
+
if trace_opts.plot_all_exp_points:
|
|
53
|
+
for ss in sensors_to_plot:
|
|
54
|
+
for ee in range(exp_sim.num_exp_per_sim):
|
|
55
|
+
ax.plot(samp_time,
|
|
56
|
+
exp_data[sens_array_num][sim_num,ee,ss,comp_ind,:],
|
|
57
|
+
"+",
|
|
58
|
+
lw=plot_opts.lw,
|
|
59
|
+
ms=plot_opts.ms,
|
|
60
|
+
color=plot_opts.colors[ss % plot_opts.n_colors])
|
|
61
|
+
|
|
62
|
+
for ss in sensors_to_plot:
|
|
63
|
+
if trace_opts.centre == "median":
|
|
64
|
+
ax.plot(samp_time,
|
|
65
|
+
exp_stats[sens_array_num].median[sim_num,ss,comp_ind,:],
|
|
66
|
+
trace_opts.exp_mean_line,
|
|
67
|
+
lw=plot_opts.lw,
|
|
68
|
+
ms=plot_opts.ms,
|
|
69
|
+
color=plot_opts.colors[ss % plot_opts.n_colors])
|
|
70
|
+
else:
|
|
71
|
+
ax.plot(samp_time,
|
|
72
|
+
exp_stats[sens_array_num].mean[sim_num,ss,comp_ind,:],
|
|
73
|
+
trace_opts.exp_mean_line,
|
|
74
|
+
lw=plot_opts.lw,
|
|
75
|
+
ms=plot_opts.ms,
|
|
76
|
+
color=plot_opts.colors[ss % plot_opts.n_colors])
|
|
77
|
+
|
|
78
|
+
if trace_opts is not None:
|
|
79
|
+
upper = np.zeros_like(exp_stats[sens_array_num].min)
|
|
80
|
+
lower = np.zeros_like(exp_stats[sens_array_num].min)
|
|
81
|
+
|
|
82
|
+
if trace_opts.fill_between == 'max':
|
|
83
|
+
upper = exp_stats[sens_array_num].min
|
|
84
|
+
lower = exp_stats[sens_array_num].max
|
|
85
|
+
|
|
86
|
+
elif trace_opts.fill_between == 'quartile':
|
|
87
|
+
upper = exp_stats[sens_array_num].q25
|
|
88
|
+
lower = exp_stats[sens_array_num].q75
|
|
89
|
+
|
|
90
|
+
elif trace_opts.fill_between == '2std':
|
|
91
|
+
upper = exp_stats[sens_array_num].mean + \
|
|
92
|
+
2*exp_stats[sens_array_num].std
|
|
93
|
+
lower = exp_stats[sens_array_num].mean - \
|
|
94
|
+
2*exp_stats[sens_array_num].std
|
|
95
|
+
|
|
96
|
+
elif trace_opts.fill_between == '3std':
|
|
97
|
+
upper = exp_stats[sens_array_num].mean + \
|
|
98
|
+
3*exp_stats[sens_array_num].std
|
|
99
|
+
lower = exp_stats[sens_array_num].mean - \
|
|
100
|
+
3*exp_stats[sens_array_num].std
|
|
101
|
+
|
|
102
|
+
ax.fill_between(samp_time,
|
|
103
|
+
upper[sim_num,ss,comp_ind,:],
|
|
104
|
+
lower[sim_num,ss,comp_ind,:],
|
|
105
|
+
color=plot_opts.colors[ss % plot_opts.n_colors],
|
|
106
|
+
alpha=0.2)
|
|
107
|
+
|
|
108
|
+
#---------------------------------------------------------------------------
|
|
109
|
+
# Plot simulation and truth line
|
|
110
|
+
if trace_opts.sim_line is not None:
|
|
111
|
+
sim_time = exp_sim.sensor_arrays[sens_array_num].field.get_time_steps()
|
|
112
|
+
sim_vals = exp_sim.sensor_arrays[sens_array_num].field.sample_field(
|
|
113
|
+
exp_sim.sensor_arrays[sens_array_num].positions)
|
|
114
|
+
|
|
115
|
+
for ss in sensors_to_plot:
|
|
116
|
+
ax.plot(sim_time,
|
|
117
|
+
sim_vals[ss,comp_ind,:],
|
|
118
|
+
trace_opts.sim_line,
|
|
119
|
+
lw=plot_opts.lw,
|
|
120
|
+
ms=plot_opts.ms)
|
|
121
|
+
|
|
122
|
+
if trace_opts.truth_line is not None:
|
|
123
|
+
truth = exp_sim.sensor_arrays[sens_array_num].get_truth()
|
|
124
|
+
for ss in sensors_to_plot:
|
|
125
|
+
ax.plot(samp_time,
|
|
126
|
+
truth[ss,comp_ind,:],
|
|
127
|
+
trace_opts.truth_line,
|
|
128
|
+
lw=plot_opts.lw,
|
|
129
|
+
ms=plot_opts.ms,
|
|
130
|
+
color=plot_opts.colors[ss % plot_opts.n_colors])
|
|
131
|
+
|
|
132
|
+
#---------------------------------------------------------------------------
|
|
133
|
+
# Axis / legend labels and options
|
|
134
|
+
ax.set_xlabel(trace_opts.time_label,
|
|
135
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
136
|
+
ax.set_ylabel(descriptor.create_label(comp_ind),
|
|
137
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
138
|
+
|
|
139
|
+
if trace_opts.time_min_max is None:
|
|
140
|
+
ax.set_xlim((np.min(samp_time),np.max(samp_time))) # type: ignore
|
|
141
|
+
else:
|
|
142
|
+
ax.set_xlim(trace_opts.time_min_max)
|
|
143
|
+
|
|
144
|
+
trace_opts.legend = False
|
|
145
|
+
if trace_opts.legend:
|
|
146
|
+
ax.legend(prop={"size":plot_opts.font_leg_size},loc='best')
|
|
147
|
+
|
|
148
|
+
plt.grid(True)
|
|
149
|
+
plt.draw()
|
|
150
|
+
|
|
151
|
+
return (fig,ax)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
pyvale: the python validation engine
|
|
4
|
+
License: MIT
|
|
5
|
+
Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
================================================================================
|
|
7
|
+
"""
|
|
8
|
+
import numpy as np
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
|
|
11
|
+
class ImageDefDiags:
|
|
12
|
+
@staticmethod
|
|
13
|
+
def plot_speckle_image(image: np.ndarray,
|
|
14
|
+
title: str = "",
|
|
15
|
+
cmap: str = "gray") -> None:
|
|
16
|
+
fig, ax = plt.subplots()
|
|
17
|
+
cset = plt.imshow(image,cmap=plt.get_cmap(cmap),origin='lower')
|
|
18
|
+
ax.set_aspect('equal','box')
|
|
19
|
+
ax.set_title(title,fontsize=12)
|
|
20
|
+
fig.colorbar(cset)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def plot_vector_image(image: np.ndarray,
|
|
24
|
+
title: str = "",
|
|
25
|
+
cmap: str = "plasma") -> None:
|
|
26
|
+
fig, ax = plt.subplots()
|
|
27
|
+
cset = plt.imshow(image,cmap=plt.get_cmap(cmap),origin='lower')
|
|
28
|
+
ax.set_aspect('equal','box')
|
|
29
|
+
ax.set_title(title,fontsize=12)
|
|
30
|
+
fig.colorbar(cset)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def plot_image_xy(image: np.ndarray,
|
|
35
|
+
extent: tuple[float,float,float,float],
|
|
36
|
+
title: str = "",
|
|
37
|
+
cmap: str = "plasma",
|
|
38
|
+
ax_units: str = "mm") -> None:
|
|
39
|
+
|
|
40
|
+
fig, ax = plt.subplots()
|
|
41
|
+
cset = plt.imshow(image,
|
|
42
|
+
aspect='auto',interpolation='none',
|
|
43
|
+
origin='lower',cmap=plt.get_cmap(cmap),
|
|
44
|
+
extent=extent)
|
|
45
|
+
ax.set_aspect('equal','box')
|
|
46
|
+
ax.set_title(title,fontsize=12)
|
|
47
|
+
ax.set_xlabel(f'x [{ax_units}]',fontsize=12)
|
|
48
|
+
ax.set_ylabel(f'y [{ax_units}]',fontsize=12)
|
|
49
|
+
fig.colorbar(cset)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def plot_all_diags(def_image: np.ndarray,
|
|
53
|
+
def_mask: np.ndarray | None,
|
|
54
|
+
def_image_subpx: np.ndarray,
|
|
55
|
+
subpx_disp_x: np.ndarray,
|
|
56
|
+
subpx_disp_y: np.ndarray,
|
|
57
|
+
subpx_grid_xm: np.ndarray,
|
|
58
|
+
subpx_grid_ym: np.ndarray) -> None:
|
|
59
|
+
image_map = "gray"
|
|
60
|
+
vector_map = "plasma"
|
|
61
|
+
|
|
62
|
+
if def_mask is not None:
|
|
63
|
+
ImageDefDiags.plot_image('Def. Mask',def_mask,image_map)
|
|
64
|
+
|
|
65
|
+
ImageDefDiags.plot_image('Subpx Def. Image',def_image_subpx,image_map)
|
|
66
|
+
ImageDefDiags.plot_image('Def. Image',def_image,image_map)
|
|
67
|
+
|
|
68
|
+
ext = tuple(np.array([subpx_grid_xm.min(),subpx_grid_xm.max(),
|
|
69
|
+
subpx_grid_ym.min(),subpx_grid_ym.max()])*10**3)
|
|
70
|
+
ImageDefDiags.plot_image_xy('Sub Pixel Disp X',subpx_disp_x,ext,V_CMAP)
|
|
71
|
+
ImageDefDiags.plot_image_xy('Sub Pixel Disp Y',subpx_disp_y,ext,V_CMAP)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
pyvale: the python validation engine
|
|
4
|
+
License: MIT
|
|
5
|
+
Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
================================================================================
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any
|
|
9
|
+
import numpy as np
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from pyvale.core.camera import CameraBasic2D
|
|
12
|
+
from pyvale.core.visualopts import PlotOptsGeneral
|
|
13
|
+
|
|
14
|
+
# NOTE: This module is a feature under developement.
|
|
15
|
+
|
|
16
|
+
# TODO: this only works for a 2D camera, maybe this should be deprecated
|
|
17
|
+
def plot_measurement_image(camera: CameraBasic2D,
|
|
18
|
+
component: str,
|
|
19
|
+
time_step: int = -1,
|
|
20
|
+
plot_opts: PlotOptsGeneral | None = None
|
|
21
|
+
) -> tuple[Any,Any]:
|
|
22
|
+
|
|
23
|
+
if plot_opts is None:
|
|
24
|
+
plot_opts = PlotOptsGeneral()
|
|
25
|
+
|
|
26
|
+
comp_ind = camera.get_field().get_component_index(component)
|
|
27
|
+
meas_image = camera.get_measurement_images()[:,:,comp_ind,time_step]
|
|
28
|
+
descriptor = camera.get_descriptor()
|
|
29
|
+
|
|
30
|
+
(fig, ax) = plt.subplots(figsize=plot_opts.single_fig_size_square,
|
|
31
|
+
layout='constrained')
|
|
32
|
+
fig.set_dpi(plot_opts.resolution)
|
|
33
|
+
|
|
34
|
+
cset = plt.imshow(meas_image,
|
|
35
|
+
cmap=plt.get_cmap(plot_opts.cmap_seq),
|
|
36
|
+
origin='lower')
|
|
37
|
+
ax.set_aspect('equal','box')
|
|
38
|
+
|
|
39
|
+
fig.colorbar(cset,
|
|
40
|
+
label=descriptor.create_label_flat(comp_ind))
|
|
41
|
+
|
|
42
|
+
title = f"Time: {camera.get_sample_times()[time_step]}s"
|
|
43
|
+
ax.set_title(title,fontsize=plot_opts.font_head_size)
|
|
44
|
+
ax.set_xlabel(r"x ($px$)",
|
|
45
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
46
|
+
ax.set_ylabel(r"y ($px$)",
|
|
47
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
48
|
+
|
|
49
|
+
return (fig,ax)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def plot_field_image(image: np.ndarray,
|
|
53
|
+
title_str: str | None = None,
|
|
54
|
+
plot_opts: PlotOptsGeneral | None = None
|
|
55
|
+
) -> tuple[Any,Any]:
|
|
56
|
+
|
|
57
|
+
if plot_opts is None:
|
|
58
|
+
plot_opts = PlotOptsGeneral()
|
|
59
|
+
|
|
60
|
+
(fig, ax) = plt.subplots(figsize=plot_opts.single_fig_size_square,
|
|
61
|
+
layout='constrained')
|
|
62
|
+
fig.set_dpi(plot_opts.resolution)
|
|
63
|
+
cset = plt.imshow(image,
|
|
64
|
+
cmap=plt.get_cmap(plot_opts.cmap_seq))
|
|
65
|
+
#origin='lower')
|
|
66
|
+
ax.set_aspect('equal','box')
|
|
67
|
+
fig.colorbar(cset)
|
|
68
|
+
if title_str is not None:
|
|
69
|
+
ax.set_title(title_str,fontsize=plot_opts.font_head_size)
|
|
70
|
+
ax.set_xlabel(r"x ($px$)",
|
|
71
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
72
|
+
ax.set_ylabel(r"y ($px$)",
|
|
73
|
+
fontsize=plot_opts.font_ax_size, fontname=plot_opts.font_name)
|
|
74
|
+
|
|
75
|
+
return (fig,ax)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
pyvale: the python validation engine
|
|
4
|
+
License: MIT
|
|
5
|
+
Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
================================================================================
|
|
7
|
+
"""
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import enum
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
import numpy as np
|
|
12
|
+
import matplotlib as plt
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class PlotOptsGeneral:
|
|
17
|
+
""" Dataclass for controlling the properties of figures and graphs such as
|
|
18
|
+
figure size, resolution, font sizes, marker sizes, line widths and
|
|
19
|
+
colormaps.
|
|
20
|
+
"""
|
|
21
|
+
aspect_ratio: float = 1.62
|
|
22
|
+
# These are in inches because of matplotlib
|
|
23
|
+
a4_width: float = 8.25
|
|
24
|
+
a4_height: float = 11.75
|
|
25
|
+
a4_margin_width: float = 0.5
|
|
26
|
+
a4_margin_height: float = 0.5
|
|
27
|
+
a4_print_width: float = a4_width-2*a4_margin_width
|
|
28
|
+
a4_print_height: float = a4_height-2*a4_margin_height
|
|
29
|
+
|
|
30
|
+
single_fig_scale: float = 0.5
|
|
31
|
+
|
|
32
|
+
single_fig_size_square: tuple[float,float] = (
|
|
33
|
+
a4_print_width*single_fig_scale,
|
|
34
|
+
a4_print_width*single_fig_scale
|
|
35
|
+
)
|
|
36
|
+
single_fig_size_portrait: tuple[float,float] = (
|
|
37
|
+
a4_print_width*single_fig_scale/aspect_ratio,
|
|
38
|
+
a4_print_width*single_fig_scale
|
|
39
|
+
)
|
|
40
|
+
single_fig_size_landscape: tuple[float,float] = (
|
|
41
|
+
a4_print_width*single_fig_scale,
|
|
42
|
+
a4_print_width*single_fig_scale/aspect_ratio
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
resolution: float = 300.0
|
|
46
|
+
|
|
47
|
+
font_name: str = 'Liberation Sans'
|
|
48
|
+
font_def_weight: str = 'normal'
|
|
49
|
+
font_def_size: float = 8.0
|
|
50
|
+
font_tick_size: float = 8.0
|
|
51
|
+
font_head_size: float = 9.0
|
|
52
|
+
font_ax_size: float = 8.0
|
|
53
|
+
font_leg_size: float = 8.0
|
|
54
|
+
|
|
55
|
+
ms: float = 3.2
|
|
56
|
+
lw: float = 0.8
|
|
57
|
+
|
|
58
|
+
cmap_seq: str = "cividis"
|
|
59
|
+
cmap_div: str = "RdBu"
|
|
60
|
+
|
|
61
|
+
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
|
|
62
|
+
n_colors = len(plt.rcParams['axes.prop_cycle'].by_key()['color'])
|
|
63
|
+
|
|
64
|
+
def __post_init__(self) -> None:
|
|
65
|
+
plt.rc('font', size=self.font_def_size)
|
|
66
|
+
plt.rc('axes', titlesize=self.font_def_size)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(slots=True)
|
|
70
|
+
class TraceOptsSensor:
|
|
71
|
+
"""Dataclass for controlling the properties of sensor trace plots.
|
|
72
|
+
"""
|
|
73
|
+
legend: bool = True
|
|
74
|
+
|
|
75
|
+
x_label: str = r"x [$m$]"
|
|
76
|
+
y_label: str = r"y [$m$]"
|
|
77
|
+
z_label: str = r"z [$m$]"
|
|
78
|
+
time_label: str = r"Time, $t$ [$s$]"
|
|
79
|
+
|
|
80
|
+
truth_line: str | None = "-"
|
|
81
|
+
sim_line: str | None = "-"
|
|
82
|
+
meas_line: str = "--o"
|
|
83
|
+
|
|
84
|
+
sensors_to_plot: np.ndarray | None = None
|
|
85
|
+
time_min_max: tuple[float,float] | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class TraceOptsExperiment:
|
|
90
|
+
"""Dataclass for contorlling the properties of sensor trace plots from
|
|
91
|
+
batches of simulated experiments.
|
|
92
|
+
"""
|
|
93
|
+
legend: bool = True
|
|
94
|
+
|
|
95
|
+
x_label: str = r"x [$m$]"
|
|
96
|
+
y_label: str = r"y [$m$]"
|
|
97
|
+
z_label: str = r"z [$m$]"
|
|
98
|
+
time_label: str = r"Time, $t$ [$s$]"
|
|
99
|
+
|
|
100
|
+
truth_line: str | None = None
|
|
101
|
+
sim_line: str | None = None
|
|
102
|
+
exp_mean_line: str = "-"
|
|
103
|
+
exp_marker_line: str = "+"
|
|
104
|
+
|
|
105
|
+
sensors_to_plot: np.ndarray | None = None
|
|
106
|
+
time_min_max: tuple[float,float] | None = None
|
|
107
|
+
|
|
108
|
+
centre: str = "mean"
|
|
109
|
+
plot_all_exp_points: bool = False
|
|
110
|
+
fill_between: str | None = "3std"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(slots=True)
|
|
114
|
+
class VisOptsSimSensors:
|
|
115
|
+
"""Dataclass for controlling displays of the simulation mesh and sensor
|
|
116
|
+
locations using pyvista.
|
|
117
|
+
"""
|
|
118
|
+
# pyvista ops
|
|
119
|
+
window_size_px: tuple[int,int] = (1280,800)
|
|
120
|
+
camera_position: np.ndarray | str = "xy"
|
|
121
|
+
show_edges: bool = True
|
|
122
|
+
interactive: bool = True
|
|
123
|
+
|
|
124
|
+
font_colour: str = "black"
|
|
125
|
+
background_colour: str = "white" # "white"
|
|
126
|
+
|
|
127
|
+
time_label_font_size: float = 12
|
|
128
|
+
time_label_position: str = "upper_left"
|
|
129
|
+
time_label_show: bool = True
|
|
130
|
+
|
|
131
|
+
colour_bar_font_size: float = 18
|
|
132
|
+
colour_bar_show: bool = True
|
|
133
|
+
colour_bar_lims: tuple[float,float] | None = None
|
|
134
|
+
colour_bar_vertical: bool = True
|
|
135
|
+
|
|
136
|
+
# pyvale ops
|
|
137
|
+
show_perturbed_pos: bool = True
|
|
138
|
+
sens_colour_nom: str = "red"
|
|
139
|
+
sens_colour_pert: str = "blue"
|
|
140
|
+
sens_point_size: float = 20
|
|
141
|
+
sens_label_font_size: float = 30
|
|
142
|
+
sens_label_colour: str = "grey"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class EImageType(enum.Enum):
|
|
146
|
+
PNG = enum.auto()
|
|
147
|
+
SVG = enum.auto()
|
|
148
|
+
|
|
149
|
+
@dataclass(slots=True)
|
|
150
|
+
class VisOptsImageSave:
|
|
151
|
+
"""Dataclass for image save options.
|
|
152
|
+
"""
|
|
153
|
+
path: Path | None = None
|
|
154
|
+
image_type: EImageType = EImageType.PNG
|
|
155
|
+
transparent_background: bool = False
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class EAnimationType(enum.Enum):
|
|
159
|
+
MP4 = enum.auto()
|
|
160
|
+
GIF = enum.auto()
|
|
161
|
+
|
|
162
|
+
@dataclass(slots=True)
|
|
163
|
+
class VisOptsAnimation:
|
|
164
|
+
"""Dataclass for animation save options.
|
|
165
|
+
"""
|
|
166
|
+
frames_per_second: float = 10.0
|
|
167
|
+
off_screen: bool = False
|
|
168
|
+
|
|
169
|
+
# save options
|
|
170
|
+
save_animation: EAnimationType | None = None
|
|
171
|
+
save_path: Path | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
================================================================================
|
|
3
|
+
pyvale: the python validation engine
|
|
4
|
+
License: MIT
|
|
5
|
+
Copyright (C) 2025 The Computer Aided Validation Team
|
|
6
|
+
================================================================================
|
|
7
|
+
"""
|
|
8
|
+
import numpy as np
|
|
9
|
+
#import vtk #NOTE: has to be here to fix latex bug in pyvista/vtk
|
|
10
|
+
# See: https://github.com/pyvista/pyvista/discussions/2928
|
|
11
|
+
#NOTE: causes output to console to be suppressed unfortunately
|
|
12
|
+
import pyvista as pv
|
|
13
|
+
from pyvale.core.sensorarraypoint import SensorArrayPoint
|
|
14
|
+
from pyvale.core.visualopts import VisOptsSimSensors, VisOptsAnimation
|
|
15
|
+
from pyvale.core.visualtools import (create_pv_plotter,
|
|
16
|
+
get_colour_lims,
|
|
17
|
+
set_animation_writer)
|
|
18
|
+
from pyvale.core.visualsimplotter import (add_sensor_points_nom,
|
|
19
|
+
add_sensor_points_pert,
|
|
20
|
+
add_sim_field)
|
|
21
|
+
|
|
22
|
+
#TODO: Docstrings
|
|
23
|
+
|
|
24
|
+
def animate_sim_with_sensors(sensor_array: SensorArrayPoint,
|
|
25
|
+
component: str,
|
|
26
|
+
time_steps: np.ndarray | None = None,
|
|
27
|
+
vis_opts: VisOptsSimSensors | None = None,
|
|
28
|
+
anim_opts: VisOptsAnimation | None = None,
|
|
29
|
+
) -> pv.Plotter:
|
|
30
|
+
|
|
31
|
+
if vis_opts is None:
|
|
32
|
+
vis_opts = VisOptsSimSensors()
|
|
33
|
+
|
|
34
|
+
if anim_opts is None:
|
|
35
|
+
anim_opts = VisOptsAnimation()
|
|
36
|
+
|
|
37
|
+
if time_steps is None:
|
|
38
|
+
time_steps = np.arange(0,sensor_array.get_sample_times().shape[0])
|
|
39
|
+
|
|
40
|
+
sim_data = sensor_array.field.get_sim_data()
|
|
41
|
+
vis_opts.colour_bar_lims = get_colour_lims(
|
|
42
|
+
sim_data.node_vars[component][:,time_steps],
|
|
43
|
+
vis_opts.colour_bar_lims)
|
|
44
|
+
|
|
45
|
+
#---------------------------------------------------------------------------
|
|
46
|
+
pv_plot = create_pv_plotter(vis_opts)
|
|
47
|
+
|
|
48
|
+
pv_plot = add_sensor_points_pert(pv_plot,sensor_array,vis_opts)
|
|
49
|
+
pv_plot = add_sensor_points_nom(pv_plot,sensor_array,vis_opts)
|
|
50
|
+
(pv_plot,sim_vis) = add_sim_field(pv_plot,
|
|
51
|
+
sensor_array,
|
|
52
|
+
component,
|
|
53
|
+
time_step = 0,
|
|
54
|
+
vis_opts = vis_opts)
|
|
55
|
+
|
|
56
|
+
pv_plot.camera_position = vis_opts.camera_position
|
|
57
|
+
pv_plot.show(auto_close=False,interactive=False)
|
|
58
|
+
|
|
59
|
+
pv_plot = set_animation_writer(pv_plot,anim_opts)
|
|
60
|
+
|
|
61
|
+
#---------------------------------------------------------------------------
|
|
62
|
+
for tt in time_steps:
|
|
63
|
+
# Updates the field plotted on the mesh
|
|
64
|
+
sim_vis[component] = sim_data.node_vars[component][:,tt]
|
|
65
|
+
|
|
66
|
+
if vis_opts.time_label_show:
|
|
67
|
+
pv_plot.add_text(f"Time: {sim_data.time[tt]} " + \
|
|
68
|
+
f"{sensor_array.descriptor.time_units}",
|
|
69
|
+
position=vis_opts.time_label_position,
|
|
70
|
+
font_size=vis_opts.time_label_font_size,
|
|
71
|
+
name='time-label')
|
|
72
|
+
|
|
73
|
+
if anim_opts.save_animation is not None:
|
|
74
|
+
pv_plot.write_frame()
|
|
75
|
+
|
|
76
|
+
pv_plot.show(auto_close=False,interactive=vis_opts.interactive)
|
|
77
|
+
return pv_plot
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|