pyvale 2025.4.0__py3-none-any.whl → 2025.5.1__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.

Files changed (153) hide show
  1. pyvale/__init__.py +78 -64
  2. pyvale/analyticmeshgen.py +102 -0
  3. pyvale/{core/analyticsimdatafactory.py → analyticsimdatafactory.py} +44 -16
  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/{core/camera.py → camera.py} +15 -15
  12. pyvale/{core/cameradata.py → cameradata.py} +27 -22
  13. pyvale/{core/cameradata2d.py → cameradata2d.py} +8 -6
  14. pyvale/camerastereo.py +217 -0
  15. pyvale/{core/cameratools.py → cameratools.py} +220 -26
  16. pyvale/{core/cython → cython}/rastercyth.py +11 -7
  17. pyvale/data/__init__.py +5 -7
  18. pyvale/data/cal_target.tiff +0 -0
  19. pyvale/data/case00_HEX20_out.e +0 -0
  20. pyvale/data/case00_HEX27_out.e +0 -0
  21. pyvale/data/case00_HEX8_out.e +0 -0
  22. pyvale/data/case00_TET10_out.e +0 -0
  23. pyvale/data/case00_TET14_out.e +0 -0
  24. pyvale/data/case00_TET4_out.e +0 -0
  25. pyvale/{core/dataset.py → dataset.py} +91 -16
  26. pyvale/{core/errorcalculator.py → errorcalculator.py} +13 -16
  27. pyvale/{core/errordriftcalc.py → errordriftcalc.py} +14 -14
  28. pyvale/{core/errorintegrator.py → errorintegrator.py} +25 -28
  29. pyvale/{core/errorrand.py → errorrand.py} +39 -46
  30. pyvale/errorsyscalib.py +134 -0
  31. pyvale/{core/errorsysdep.py → errorsysdep.py} +25 -29
  32. pyvale/{core/errorsysfield.py → errorsysfield.py} +59 -52
  33. pyvale/{core/errorsysindep.py → errorsysindep.py} +85 -182
  34. pyvale/examples/__init__.py +5 -7
  35. pyvale/examples/basics/ex1_1_basicscalars_therm2d.py +131 -0
  36. pyvale/examples/basics/ex1_2_sensormodel_therm2d.py +158 -0
  37. pyvale/examples/basics/ex1_3_customsens_therm3d.py +216 -0
  38. pyvale/examples/basics/ex1_4_basicerrors_therm3d.py +153 -0
  39. pyvale/examples/basics/ex1_5_fielderrs_therm3d.py +168 -0
  40. pyvale/examples/basics/ex1_6_caliberrs_therm2d.py +133 -0
  41. pyvale/examples/basics/ex1_7_spatavg_therm2d.py +123 -0
  42. pyvale/examples/basics/ex2_1_basicvectors_disp2d.py +112 -0
  43. pyvale/examples/basics/ex2_2_vectorsens_disp2d.py +111 -0
  44. pyvale/examples/basics/ex2_3_sensangle_disp2d.py +139 -0
  45. pyvale/examples/basics/ex2_4_chainfielderrs_disp2d.py +196 -0
  46. pyvale/examples/basics/ex2_5_vectorfields3d_disp3d.py +109 -0
  47. pyvale/examples/basics/ex3_1_basictensors_strain2d.py +114 -0
  48. pyvale/examples/basics/ex3_2_tensorsens2d_strain2d.py +111 -0
  49. pyvale/examples/basics/ex3_3_tensorsens3d_strain3d.py +182 -0
  50. pyvale/examples/basics/ex4_1_expsim2d_thermmech2d.py +171 -0
  51. pyvale/examples/basics/ex4_2_expsim3d_thermmech3d.py +252 -0
  52. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_1_scalarvisualisation.py +6 -9
  53. pyvale/examples/{analyticdatagen → genanalyticdata}/ex1_2_scalarcasebuild.py +8 -11
  54. pyvale/examples/{analyticdatagen → genanalyticdata}/ex2_1_analyticsensors.py +9 -12
  55. pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +8 -15
  56. pyvale/examples/renderblender/ex1_1_blenderscene.py +121 -0
  57. pyvale/examples/renderblender/ex1_2_blenderdeformed.py +119 -0
  58. pyvale/examples/renderblender/ex2_1_stereoscene.py +128 -0
  59. pyvale/examples/renderblender/ex2_2_stereodeformed.py +131 -0
  60. pyvale/examples/renderblender/ex3_1_blendercalibration.py +120 -0
  61. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastenp.py +6 -7
  62. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_oneframe.py +5 -7
  63. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_cypara.py +6 -13
  64. pyvale/examples/{rasterisation → renderrasterisation}/ex_rastercyth_static_pypara.py +9 -12
  65. pyvale/examples/{ex1_4_thermal2d.py → visualisation/ex1_1_plot_traces.py} +33 -20
  66. pyvale/examples/{features/ex_animation_tools_3dmonoblock.py → visualisation/ex2_1_animate_sim.py} +37 -31
  67. pyvale/experimentsimulator.py +175 -0
  68. pyvale/{core/field.py → field.py} +6 -14
  69. pyvale/fieldconverter.py +351 -0
  70. pyvale/{core/fieldsampler.py → fieldsampler.py} +9 -10
  71. pyvale/{core/fieldscalar.py → fieldscalar.py} +17 -18
  72. pyvale/{core/fieldtensor.py → fieldtensor.py} +23 -26
  73. pyvale/{core/fieldtransform.py → fieldtransform.py} +9 -5
  74. pyvale/{core/fieldvector.py → fieldvector.py} +14 -16
  75. pyvale/{core/generatorsrandom.py → generatorsrandom.py} +29 -52
  76. pyvale/{core/imagedef2d.py → imagedef2d.py} +11 -8
  77. pyvale/{core/integratorfactory.py → integratorfactory.py} +12 -13
  78. pyvale/{core/integratorquadrature.py → integratorquadrature.py} +57 -32
  79. pyvale/integratorrectangle.py +165 -0
  80. pyvale/{core/integratorspatial.py → integratorspatial.py} +9 -10
  81. pyvale/{core/integratortype.py → integratortype.py} +7 -8
  82. pyvale/output.py +17 -0
  83. pyvale/pyvaleexceptions.py +11 -0
  84. pyvale/{core/raster.py → raster.py} +8 -8
  85. pyvale/{core/rastercy.py → rastercy.py} +11 -10
  86. pyvale/{core/rasternp.py → rasternp.py} +12 -13
  87. pyvale/{core/rendermesh.py → rendermesh.py} +10 -19
  88. pyvale/{core/sensorarray.py → sensorarray.py} +7 -8
  89. pyvale/{core/sensorarrayfactory.py → sensorarrayfactory.py} +64 -78
  90. pyvale/{core/sensorarraypoint.py → sensorarraypoint.py} +39 -41
  91. pyvale/{core/sensordata.py → sensordata.py} +7 -8
  92. pyvale/sensordescriptor.py +213 -0
  93. pyvale/{core/sensortools.py → sensortools.py} +8 -9
  94. pyvale/simcases/case00_HEX20.i +5 -5
  95. pyvale/simcases/case00_HEX27.i +5 -5
  96. pyvale/simcases/case00_HEX8.i +242 -0
  97. pyvale/simcases/case00_TET10.i +2 -2
  98. pyvale/simcases/case00_TET14.i +2 -2
  99. pyvale/simcases/case00_TET4.i +242 -0
  100. pyvale/simcases/run_1case.py +1 -1
  101. pyvale/simtools.py +67 -0
  102. pyvale/visualexpplotter.py +191 -0
  103. pyvale/{core/visualimagedef.py → visualimagedef.py} +13 -10
  104. pyvale/{core/visualimages.py → visualimages.py} +10 -9
  105. pyvale/visualopts.py +493 -0
  106. pyvale/{core/visualsimanimator.py → visualsimanimator.py} +47 -19
  107. pyvale/visualsimsensors.py +318 -0
  108. pyvale/visualtools.py +136 -0
  109. pyvale/visualtraceplotter.py +142 -0
  110. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/METADATA +17 -14
  111. pyvale-2025.5.1.dist-info/RECORD +172 -0
  112. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/WHEEL +1 -1
  113. pyvale/core/__init__.py +0 -7
  114. pyvale/core/analyticmeshgen.py +0 -59
  115. pyvale/core/analyticsimdatagenerator.py +0 -160
  116. pyvale/core/cython/rastercyth.c +0 -32267
  117. pyvale/core/experimentsimulator.py +0 -99
  118. pyvale/core/fieldconverter.py +0 -154
  119. pyvale/core/integratorrectangle.py +0 -88
  120. pyvale/core/optimcheckfuncs.py +0 -153
  121. pyvale/core/sensordescriptor.py +0 -101
  122. pyvale/core/visualexpplotter.py +0 -151
  123. pyvale/core/visualopts.py +0 -180
  124. pyvale/core/visualsimplotter.py +0 -182
  125. pyvale/core/visualtools.py +0 -81
  126. pyvale/core/visualtraceplotter.py +0 -256
  127. pyvale/examples/analyticdatagen/__init__.py +0 -7
  128. pyvale/examples/ex1_1_thermal2d.py +0 -89
  129. pyvale/examples/ex1_2_thermal2d.py +0 -111
  130. pyvale/examples/ex1_3_thermal2d.py +0 -113
  131. pyvale/examples/ex1_5_thermal2d.py +0 -105
  132. pyvale/examples/ex2_1_thermal3d .py +0 -87
  133. pyvale/examples/ex2_2_thermal3d.py +0 -51
  134. pyvale/examples/ex2_3_thermal3d.py +0 -109
  135. pyvale/examples/ex3_1_displacement2d.py +0 -47
  136. pyvale/examples/ex3_2_displacement2d.py +0 -79
  137. pyvale/examples/ex3_3_displacement2d.py +0 -104
  138. pyvale/examples/ex3_4_displacement2d.py +0 -105
  139. pyvale/examples/ex4_1_strain2d.py +0 -57
  140. pyvale/examples/ex4_2_strain2d.py +0 -79
  141. pyvale/examples/ex4_3_strain2d.py +0 -100
  142. pyvale/examples/ex5_1_multiphysics2d.py +0 -78
  143. pyvale/examples/ex6_1_multiphysics2d_expsim.py +0 -118
  144. pyvale/examples/ex6_2_multiphysics3d_expsim.py +0 -158
  145. pyvale/examples/features/__init__.py +0 -7
  146. pyvale/examples/features/ex_area_avg.py +0 -89
  147. pyvale/examples/features/ex_calibration_error.py +0 -108
  148. pyvale/examples/features/ex_chain_field_errs.py +0 -141
  149. pyvale/examples/features/ex_field_errs.py +0 -78
  150. pyvale/examples/features/ex_sensor_single_angle_batch.py +0 -110
  151. pyvale-2025.4.0.dist-info/RECORD +0 -157
  152. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/licenses/LICENSE +0 -0
  153. {pyvale-2025.4.0.dist-info → pyvale-2025.5.1.dist-info}/top_level.txt +0 -0
pyvale/camerastereo.py ADDED
@@ -0,0 +1,217 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
7
+ """
8
+ NOTE: This module is a feature under developement.
9
+ """
10
+
11
+ from typing import Self
12
+ from pathlib import Path
13
+ import numpy as np
14
+ import yaml
15
+ from scipy.spatial.transform import Rotation
16
+ from pyvale.cameradata import CameraData
17
+ from pyvale.pyvaleexceptions import BlenderError
18
+
19
+
20
+ class CameraStereo:
21
+ __slots__ = ("cam_data_0","cam_data_1","stereo_dist","stereo_rotation")
22
+
23
+ def __init__(self, cam_data_0: CameraData, cam_data_1: CameraData) -> None:
24
+ self.cam_data_0 = cam_data_0
25
+ self.cam_data_1 = cam_data_1
26
+
27
+ cam0_rot_matrix = Rotation.as_matrix(self.cam_data_0.rot_world)
28
+ cam1_rot_matrix = Rotation.as_matrix(self.cam_data_1.rot_world)
29
+ (self.stereo_rotation, _) = Rotation.align_vectors(cam0_rot_matrix,
30
+ cam1_rot_matrix)
31
+ dist = self.cam_data_0.pos_world - self.cam_data_1.pos_world
32
+ dist_rot = self.cam_data_0.rot_world.apply(dist)
33
+ inverse = self.stereo_rotation.inv().as_quat()
34
+ inverse[3] *= -1
35
+ inverse = Rotation.from_quat(inverse)
36
+ self.stereo_dist = inverse.apply(dist_rot)
37
+
38
+ @classmethod
39
+ def from_calibration(cls,
40
+ calib_path: Path,
41
+ pos_world_0: np.ndarray,
42
+ rot_world_0: Rotation,
43
+ focal_length: float) -> Self:
44
+ """A method to initialise the CameraStereo using a calibration file and
45
+ some additional parameters. This creates an instance of the CameraStereo
46
+ class from the calibration parameters.
47
+
48
+ Parameters
49
+ ----------
50
+ calib_path : Path
51
+ The path to the calibration file (in yaml format).
52
+ pos_world_0 : np.ndarray
53
+ The position of camera 0 in world coordinates.
54
+ rot_world_0 : Rotation
55
+ The rotation of camera 0 in world coordinates.
56
+ focal_length : float
57
+ The focal length of camera 0.
58
+
59
+ Returns
60
+ -------
61
+ Self
62
+ An instance of the CameraStereo class, given the specified parameters.
63
+ """
64
+ calib_params = yaml.safe_load(calib_path.read_text())
65
+ pixels_num_cam0 = np.array([calib_params['Cam0_Cx [pixels]']*2,
66
+ calib_params['Cam0_Cy [pixels]']*2])
67
+ pixels_num_cam1 = np.array([calib_params['Cam1_Cx [pixels]']*2,
68
+ calib_params['Cam1_Cy [pixels]']*2])
69
+ pixels_size = focal_length / calib_params["Cam0_Fx [pixels]"]
70
+ stereo_rotation = Rotation.from_euler("xyz", ([calib_params['Theta [deg]'],
71
+ calib_params['Phi [deg]'],
72
+ calib_params['Psi [deg]']]), degrees=True)
73
+ stereo_dist = np.array([calib_params["Tx [mm]"],
74
+ calib_params["Ty [mm]"],
75
+ calib_params["Tz [mm]"]])
76
+
77
+ rot_world_1 = stereo_rotation * rot_world_0
78
+
79
+ inverse = stereo_rotation.inv().as_quat()
80
+ inverse[3] *= -1
81
+ inverse = Rotation.from_quat(inverse)
82
+
83
+ dist_rot = inverse.inv().apply(stereo_dist)
84
+ dist = rot_world_0.inv().apply(dist_rot)
85
+ pos_world_1 = pos_world_0 - dist
86
+
87
+ cam_data_0 = CameraData(pixels_num=pixels_num_cam0,
88
+ pixels_size=np.array([pixels_size, pixels_size]),
89
+ pos_world=pos_world_0,
90
+ rot_world=rot_world_0,
91
+ roi_cent_world=np.array([0, 0, 0]),
92
+ focal_length=focal_length)
93
+ cam_data_1 = CameraData(pixels_num=pixels_num_cam1,
94
+ pixels_size=np.array([pixels_size, pixels_size]),
95
+ pos_world=pos_world_1,
96
+ rot_world=rot_world_1,
97
+ roi_cent_world=np.array([0, 0, 0]),
98
+ focal_length=focal_length)
99
+ camera_stereo = cls(cam_data_0, cam_data_1)
100
+
101
+ return camera_stereo
102
+
103
+ def save_calibration(self, base_dir: Path) -> None:
104
+ """A method to save a calibration file of the stereo system as a yaml.
105
+ This is so that the file can easily be read into python, but is also
106
+ user-readable.
107
+
108
+ Parameters
109
+ ----------
110
+ base_dir : Path
111
+ The base directory to which all files should be saved. The
112
+ calibration file will be saved in a sub-directory named "calibration"
113
+ within this directory.
114
+
115
+ Raises
116
+ ------
117
+ BlenderError
118
+ "The specified save directory does not exist"
119
+ """
120
+ stereo_rotation = self.stereo_rotation.as_euler("xyz", degrees=True)
121
+ calib_params = {
122
+ "Cam0_Fx [pixels]": float(self.cam_data_0.focal_length /
123
+ self.cam_data_0.pixels_size[0]),
124
+ "Cam0_Fy [pixels]": float(self.cam_data_0.focal_length /
125
+ self.cam_data_0.pixels_size[1]),
126
+ "Cam0_Fs [pixels]": 0,
127
+ "Cam0_Kappa 1": self.cam_data_0.k1,
128
+ "Cam0_Kappa 2": self.cam_data_0.k2,
129
+ "Cam0_Kappa 3": self.cam_data_0.k3,
130
+ "Cam0_P1": self.cam_data_0.p1,
131
+ "Cam0_P2": self.cam_data_0.p2,
132
+ "Cam0_Cx [pixels]": float(self.cam_data_0.c0),
133
+ "Cam0_Cy [pixels]": float(self.cam_data_0.c1),
134
+ "Cam1_Fx [pixels]": float(self.cam_data_1.focal_length /
135
+ self.cam_data_1.pixels_size[0]),
136
+ "Cam1_Fy [pixels]": float(self.cam_data_1.focal_length /
137
+ self.cam_data_1.pixels_size[1]),
138
+ "Cam1_Fs [pixels]": 0,
139
+ "Cam1_Kappa 1": self.cam_data_1.k1,
140
+ "Cam1_Kappa 2": self.cam_data_1.k2,
141
+ "Cam1_Kappa 3": self.cam_data_1.k3,
142
+ "Cam1_P1": self.cam_data_1.p1,
143
+ "Cam1_P2": self.cam_data_1.p2,
144
+ "Cam1_Cx [pixels]": float(self.cam_data_1.c0),
145
+ "Cam1_Cy [pixels]": float(self.cam_data_1.c1),
146
+ "Tx [mm]": float(self.stereo_dist[0]),
147
+ "Ty [mm]": float(self.stereo_dist[1]),
148
+ "Tz [mm]": float(self.stereo_dist[2]),
149
+ "Theta [deg]": float(stereo_rotation[0]),
150
+ "Phi [deg]": float(stereo_rotation[1]),
151
+ "Psi [deg]": float(stereo_rotation[2])
152
+ }
153
+ if not base_dir.is_dir():
154
+ raise BlenderError("The specified save directory does not exist")
155
+
156
+ save_dir = base_dir / "calibration"
157
+ if not save_dir.is_dir():
158
+ save_dir.mkdir(parents=True, exist_ok=True)
159
+
160
+ filepath = str(save_dir / "calibration.yaml")
161
+ calib_file = open(filepath, "w")
162
+ yaml.safe_dump(calib_params, calib_file)
163
+ calib_file.close()
164
+ print("Calibration file saved to:", (save_dir / "calibration.yaml"))
165
+
166
+ def save_calibration_mid(self, base_dir: Path) -> None:
167
+ """A method to save a calibration file of the stereo system in a MatchID
168
+ accepted format.
169
+
170
+ Parameters
171
+ ----------
172
+ base_dir : Path
173
+ The base directory to which all files should be saved. The
174
+ calibration file will be saved in a sub-directory named "calibration"
175
+ within this directory.
176
+
177
+ Raises
178
+ ------
179
+ BlenderError
180
+ "The specified save directory does not exist"
181
+ """
182
+ if not base_dir.is_dir():
183
+ raise BlenderError("The specified save directory does not exist")
184
+
185
+ save_dir = base_dir / "calibration"
186
+ if not save_dir.is_dir():
187
+ save_dir.mkdir(parents=True, exist_ok=True)
188
+
189
+ filepath = str(save_dir / "calibration.caldat")
190
+ with open(filepath, "w") as file:
191
+ file.write(f'Cam0_Fx [pixels]; {self.cam_data_0.focal_length/ self.cam_data_0.pixels_size[0]}\n')
192
+ file.write(f'Cam0_Fy [pixels]; {self.cam_data_0.focal_length/ self.cam_data_0.pixels_size[1]}\n')
193
+ file.write("Cam0_Fs [pixels];0\n")
194
+ file.write(f'Cam0_Kappa 1;{self.cam_data_0.k1}\n')
195
+ file.write(f'Cam0_Kappa 2;{self.cam_data_0.k2}\n')
196
+ file.write(f'Cam0_Kappa 3;{self.cam_data_0.k3}\n')
197
+ file.write(f'Cam0_P1;{self.cam_data_0.p1}\n')
198
+ file.write(f'Cam0_P2;{self.cam_data_0.p2}\n')
199
+ file.write(f'Cam0_Cx [pixels];{self.cam_data_0.c0}\n')
200
+ file.write(f'Cam0_Cy [pixels];{self.cam_data_0.c1}\n')
201
+ file.write(f'Cam1_Fx [pixels]; {self.cam_data_1.focal_length/ self.cam_data_1.pixels_size[0]}\n')
202
+ file.write(f'Cam1_Fy [pixels]; {self.cam_data_1.focal_length/ self.cam_data_1.pixels_size[1]}\n')
203
+ file.write("Cam1_Fs [pixels];0\n")
204
+ file.write(f'Cam1_Kappa 1;{self.cam_data_1.k1}\n')
205
+ file.write(f'Cam1_Kappa 2;{self.cam_data_1.k2}\n')
206
+ file.write(f'Cam1_Kappa 3;{self.cam_data_1.k3}\n')
207
+ file.write(f'Cam1_P1;{self.cam_data_1.p1}\n')
208
+ file.write(f'Cam1_P2;{self.cam_data_1.p2}\n')
209
+ file.write(f'Cam1_Cx [pixels];{self.cam_data_1.c0}\n')
210
+ file.write(f'Cam1_Cy [pixels];{self.cam_data_1.c1}\n')
211
+ file.write(f"Tx [mm];{self.stereo_dist[0]}\n")
212
+ file.write(f"Ty [mm];{self.stereo_dist[1]}\n")
213
+ file.write(f"Tz [mm];{self.stereo_dist[2]}\n")
214
+ stereo_rotation = self.stereo_rotation.as_euler("xyz", degrees=True)
215
+ file.write(f"Theta [deg];{stereo_rotation[0]}\n")
216
+ file.write(f"Phi [deg];{stereo_rotation[1]}\n")
217
+ file.write(f"Psi [deg];{stereo_rotation[2]}")
@@ -1,24 +1,28 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
1
7
  """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
8
+ NOTE: This module is a feature under developement.
7
9
  """
10
+
8
11
  import warnings
9
12
  from pathlib import Path
10
13
  import numpy as np
11
14
  from scipy.signal import convolve2d
15
+ import copy
12
16
  from scipy.spatial.transform import Rotation
13
17
  import matplotlib.image as mplim
14
18
  from PIL import Image
15
- from pyvale.core.cameradata2d import CameraData2D
16
- from pyvale.core.sensordata import SensorData
19
+ from pyvale.cameradata2d import CameraData2D
20
+ from pyvale.sensordata import SensorData
21
+ from pyvale.cameradata import CameraData
22
+ from pyvale.camerastereo import CameraStereo
17
23
 
18
- # NOTE: This module is a feature under developement.
19
24
 
20
25
  class CameraTools:
21
- #-------------------------------------------------------------------------------
22
26
  @staticmethod
23
27
  def load_image(im_path: Path) -> np.ndarray:
24
28
 
@@ -56,7 +60,6 @@ class CameraTools:
56
60
 
57
61
  return num_str
58
62
 
59
- #-------------------------------------------------------------------------------
60
63
  @staticmethod
61
64
  def pixel_vec_px(pixels_count: np.ndarray) -> tuple[np.ndarray,np.ndarray]:
62
65
  px_vec_x = np.arange(0,pixels_count[0],1)
@@ -72,7 +75,6 @@ class CameraTools:
72
75
  (px_grid_x,px_grid_y) = CameraTools.pixel_grid_px(pixels_count)
73
76
  return (px_grid_x.flatten(),px_grid_y.flatten())
74
77
 
75
- #-------------------------------------------------------------------------------
76
78
  @staticmethod
77
79
  def subpixel_vec_px(pixels_count: np.ndarray,
78
80
  subsample: int = 2) -> tuple[np.ndarray,np.ndarray]:
@@ -92,7 +94,6 @@ class CameraTools:
92
94
  (px_grid_x,px_grid_y) = CameraTools.subpixel_grid_px(pixels_count,subsample)
93
95
  return (px_grid_x.flatten(),px_grid_y.flatten())
94
96
 
95
- #-------------------------------------------------------------------------------
96
97
  @staticmethod
97
98
  def pixel_vec_leng(field_of_view: np.ndarray,
98
99
  leng_per_px: float) -> tuple[np.ndarray,np.ndarray]:
@@ -116,7 +117,7 @@ class CameraTools:
116
117
  (px_grid_x,px_grid_y) = CameraTools.pixel_grid_leng(field_of_view,leng_per_px)
117
118
  return (px_grid_x.flatten(),px_grid_y.flatten())
118
119
 
119
- #-------------------------------------------------------------------------------
120
+
120
121
  @staticmethod
121
122
  def subpixel_vec_leng(field_of_view: np.ndarray,
122
123
  leng_per_px: float,
@@ -149,7 +150,6 @@ class CameraTools:
149
150
  subsample)
150
151
  return (px_grid_x.flatten(),px_grid_y.flatten())
151
152
 
152
- #-------------------------------------------------------------------------------
153
153
  @staticmethod
154
154
  def calc_resolution_from_sim_2d(pixels_count: np.ndarray,
155
155
  coords: np.ndarray,
@@ -211,7 +211,6 @@ class CameraTools:
211
211
  round(subsample/2)-1::subsample]
212
212
  return avg_image
213
213
 
214
- #---------------------------------------------------------------------------
215
214
  @staticmethod
216
215
  def build_sensor_data_from_camera_2d(cam_data: CameraData2D) -> SensorData:
217
216
  pixels_vectorised = CameraTools.vectorise_pixel_grid_leng(cam_data.field_of_view,
@@ -263,16 +262,16 @@ class CameraTools:
263
262
  boundbox_cam_leng = (np.max(bound_box_cam_vecs,axis=1)
264
263
  - np.min(bound_box_cam_vecs,axis=1))
265
264
 
266
- print(80*"-")
267
- print(f"{bb_min=}")
268
- print(f"{bb_max=}")
269
- print()
270
- print("Cam to world mat:")
271
- print(cam_to_world_mat)
272
- print()
273
- print("World to cam mat:")
274
- print(world_to_cam_mat)
275
- print(80*"-")
265
+ # print(80*"-")
266
+ # print(f"{bb_min=}")
267
+ # print(f"{bb_max=}")
268
+ # print()
269
+ # print("Cam to world mat:")
270
+ # print(cam_to_world_mat)
271
+ # print()
272
+ # print("World to cam mat:")
273
+ # print(world_to_cam_mat)
274
+ # print(80*"-")
276
275
 
277
276
  return np.array((boundbox_cam_leng[xx],boundbox_cam_leng[yy]))
278
277
 
@@ -325,4 +324,199 @@ class CameraTools:
325
324
  return (roi_pos_world,cam_pos_world)
326
325
 
327
326
 
328
- #-------------------------------------------------------------------------------
327
+ #---------------------------------------------------------------------------
328
+ # Blender camera tools
329
+
330
+ @staticmethod
331
+ def calculate_FOV(cam_data: CameraData) -> tuple[float, float]:
332
+ """A method to calulate the camera's field of view in mm
333
+
334
+ Parameters
335
+ ----------
336
+ cam_data : CameraData
337
+ A dataclass containing the camera parameters
338
+
339
+ Returns
340
+ -------
341
+ tuple[float, float]
342
+ A tuple containing the field of view in mm in both x and y directions
343
+ """
344
+ FOV_x = (((cam_data.image_dist - cam_data.focal_length)
345
+ / cam_data.focal_length) *
346
+ (cam_data.pixels_size) *
347
+ cam_data.pixels_num[0])[0]
348
+ FOV_y = (cam_data.pixels_num[1] / cam_data.pixels_num[0]) * FOV_x
349
+ FOV_mm = (FOV_x, FOV_y)
350
+ return FOV_mm
351
+
352
+ @staticmethod
353
+ def blender_FOV(cam_data: CameraData) -> tuple[float, float]:
354
+ """A method to calculate the camera's field of view in mm using Blender's
355
+ method. This method differs due to one simplification.
356
+
357
+ Parameters
358
+ ----------
359
+ cam_data : CameraData
360
+ A dataclass containing the camera parameters
361
+
362
+ Returns
363
+ -------
364
+ tuple[float, float]
365
+ A tuple containing the FOV in x and y directions
366
+ """
367
+ FOV_x = (cam_data.pixels_num[0] * cam_data.pixels_size[0] * cam_data.image_dist) / cam_data.focal_length
368
+ FOV_y = (cam_data.pixels_num[1] / cam_data.pixels_num[0]) * FOV_x
369
+ FOV_blender = (FOV_x, FOV_y)
370
+ return FOV_blender
371
+
372
+ @staticmethod
373
+ def calculate_mm_px_resolution(cam_data: CameraData) -> float:
374
+ """Function to calculate the mm/px resolution of a camera
375
+
376
+ Parameters
377
+ ----------
378
+ cam_data : CameraData
379
+ A dataclass containing the camera parameters
380
+
381
+ Returns
382
+ -------
383
+ float
384
+ The mm/px resolution
385
+ """
386
+ FOV_mm = CameraTools.blender_FOV(cam_data)
387
+ resolution = FOV_mm[0] / cam_data.pixels_num[0]
388
+ return resolution
389
+
390
+ @staticmethod
391
+ def focal_length_from_resolution(pixels_size: np.ndarray,
392
+ working_dist: float,
393
+ resolution: float) -> float:
394
+ """A method to calculate the required focal length to achieve a certain
395
+ resolution. This is calculated given the pixel size and working distance.
396
+ This method can be used for a 2D setup or for camera 0 for a stereo setup.
397
+
398
+ Parameters
399
+ ----------
400
+ pixels_size : np.ndarray
401
+ The camera pixel size in the x and y directions (in mm).
402
+ working_dist : float
403
+ The working distance of the camera to the sample.
404
+ resolution : float
405
+ The desired resolution in mm/px.
406
+
407
+ Returns
408
+ -------
409
+ float
410
+ The focal length required to obtain the desired image resolution.
411
+ """
412
+ focal_length = working_dist / ((resolution / pixels_size[0]))
413
+ return focal_length
414
+
415
+ @staticmethod
416
+ def blender_camera_from_resolution(pixels_num: np.ndarray,
417
+ pixels_size: np.ndarray,
418
+ working_dist: float,
419
+ resolution: float) -> CameraData:
420
+ """A convenience function to create a camera object in Blender from its pixels,
421
+ the pixel size, the working distance and desired resolution.
422
+
423
+ Parameters
424
+ ----------
425
+ pixels_num : np.ndarray
426
+ The number of pixels in the camera, in the x and y directions.
427
+ pixels_size : np.ndarray
428
+ The camera pixels size in mm, in the x and y directions.
429
+ working_dist : float
430
+ The working distance of the camera.
431
+ resolution : float
432
+ The desired mm/px resolution
433
+
434
+ Returns
435
+ -------
436
+ CameraData
437
+ A dataclass containing the created camera's parameters.
438
+ """
439
+ focal_length = CameraTools.focal_length_from_resolution(pixels_size, working_dist, resolution)
440
+
441
+ cam_data = CameraData(pixels_num=pixels_num,
442
+ pixels_size=pixels_size,
443
+ pos_world=(0, 0, working_dist),
444
+ rot_world=Rotation.from_euler("xyz", [0, 0, 0]),
445
+ roi_cent_world=(0, 0, 0),
446
+ focal_length=focal_length)
447
+ return cam_data
448
+
449
+ @staticmethod
450
+ def symmetric_stereo_cameras(cam_data_0: CameraData,
451
+ stereo_angle:float) -> CameraStereo:
452
+ """A convenience function to set up a symmetric stereo camera system, given
453
+ an initial CameraData dataclass and a stereo angle. This assumes the basic
454
+ camera parameters are the same.
455
+
456
+ Parameters
457
+ ----------
458
+ cam_data_0 : CameraData
459
+ A dataclass containing the camera parameters for a single camera, which
460
+ will be camera 0.
461
+ stereo_angle : float
462
+ The stereo angle between the two cameras.
463
+
464
+ Returns
465
+ -------
466
+ CameraStereo
467
+ An instance of the CameraStereo class. This class contains
468
+ information about each of the cameras, as well as the extrinsic
469
+ parameters between them.
470
+ """
471
+ cam_data_1 = copy.deepcopy(cam_data_0)
472
+ base = 2 * cam_data_0.pos_world[2] * np.tan(np.radians(stereo_angle) / 2)
473
+
474
+ cam_data_0.pos_world[0] -= base / 2
475
+ cam_data_1.pos_world[0] += base / 2
476
+
477
+ cam_0_rot = (0, -np.radians(stereo_angle / 2), 0)
478
+ cam_0_rot = Rotation.from_euler("xyz", cam_0_rot, degrees=False)
479
+ cam_data_0.rot_world = cam_0_rot
480
+
481
+ cam_1_rot = (0, np.radians(stereo_angle / 2), 0)
482
+ cam_1_rot = Rotation.from_euler("xyz", cam_1_rot, degrees=False)
483
+ cam_data_1.rot_world = cam_1_rot
484
+
485
+ stereo_system = CameraStereo(cam_data_0, cam_data_1)
486
+
487
+ return stereo_system
488
+
489
+ @staticmethod
490
+ def faceon_stereo_cameras(cam_data_0: CameraData,
491
+ stereo_angle: float) -> CameraStereo:
492
+ # TODO: Correct docstring
493
+ """A convenience function to set up a face-on stereo camera system, given
494
+ an initial CameraData dataclass and a stereo angle. This assumes the basic
495
+ camera parameters are the same.
496
+
497
+ Parameters
498
+ ----------
499
+ cam_data_0 : CameraData
500
+ A dataclass containing the camera parameters for a single camera, which
501
+ will be camera 0.
502
+ stereo_angle : float
503
+ The stereo angle between the two cameras.
504
+
505
+ Returns
506
+ -------
507
+ CameraStereo
508
+ An instance of the CameraStereo class. This class contains
509
+ information about each of the cameras, as well as the extrinsic
510
+ parameters between them.
511
+ """
512
+ cam_data_1 = copy.deepcopy(cam_data_0)
513
+ base = cam_data_0.pos_world[2] * np.tan(np.radians(stereo_angle))
514
+ cam_data_1.pos_world[0] += base
515
+
516
+ rotation_angle = (0, np.radians(stereo_angle), 0)
517
+ rotation_angle = Rotation.from_euler("xyz", rotation_angle, degrees=False)
518
+ cam_data_1.rot_world = rotation_angle
519
+
520
+ stereo_system = CameraStereo(cam_data_0, cam_data_1)
521
+
522
+ return stereo_system
@@ -1,18 +1,22 @@
1
+ # ==============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ # ==============================================================================
6
+
1
7
  """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
8
+ NOTE: this module is a feature under developement.
7
9
  """
10
+
8
11
  import numpy as np
9
12
  import cython
10
13
  from cython.parallel import prange, parallel, threadid
11
14
  from cython.cimports.libc.math import floor, ceil
12
15
 
13
- from pyvale.core.rendermesh import RenderMeshData
14
- from pyvale.core.cameradata import CameraData
16
+ from pyvale.rendermesh import RenderMeshData
17
+ from pyvale.cameradata import CameraData
15
18
 
19
+ # NOTE: This module is a feature under developement.
16
20
 
17
21
  @cython.nogil
18
22
  @cython.cfunc # python+C or cython.cfunc for C only
pyvale/data/__init__.py CHANGED
@@ -1,7 +1,5 @@
1
- """
2
- ================================================================================
3
- pyvale: the python validation engine
4
- License: MIT
5
- Copyright (C) 2025 The Computer Aided Validation Team
6
- ================================================================================
7
- """
1
+ #===============================================================================
2
+ # pyvale: the python validation engine
3
+ # License: MIT
4
+ # Copyright (C) 2025 The Computer Aided Validation Team
5
+ #===============================================================================
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file