lsurf 1.0.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.
Files changed (180) hide show
  1. lsurf/__init__.py +471 -0
  2. lsurf/analysis/__init__.py +107 -0
  3. lsurf/analysis/healpix_utils.py +418 -0
  4. lsurf/analysis/sphere_viz.py +1280 -0
  5. lsurf/cli/__init__.py +48 -0
  6. lsurf/cli/build.py +398 -0
  7. lsurf/cli/config_schema.py +318 -0
  8. lsurf/cli/gui_cmd.py +76 -0
  9. lsurf/cli/interactive.py +850 -0
  10. lsurf/cli/main.py +81 -0
  11. lsurf/cli/run.py +806 -0
  12. lsurf/detectors/__init__.py +266 -0
  13. lsurf/detectors/analysis.py +289 -0
  14. lsurf/detectors/base.py +284 -0
  15. lsurf/detectors/constant_size_rings.py +485 -0
  16. lsurf/detectors/directional.py +45 -0
  17. lsurf/detectors/extended/__init__.py +73 -0
  18. lsurf/detectors/extended/local_sphere.py +353 -0
  19. lsurf/detectors/extended/recording_sphere.py +368 -0
  20. lsurf/detectors/planar.py +45 -0
  21. lsurf/detectors/protocol.py +187 -0
  22. lsurf/detectors/recording_spheres.py +63 -0
  23. lsurf/detectors/results.py +1140 -0
  24. lsurf/detectors/small/__init__.py +79 -0
  25. lsurf/detectors/small/directional.py +330 -0
  26. lsurf/detectors/small/planar.py +401 -0
  27. lsurf/detectors/small/spherical.py +450 -0
  28. lsurf/detectors/spherical.py +45 -0
  29. lsurf/geometry/__init__.py +199 -0
  30. lsurf/geometry/builder.py +478 -0
  31. lsurf/geometry/cell.py +228 -0
  32. lsurf/geometry/cell_geometry.py +247 -0
  33. lsurf/geometry/detector_arrays.py +1785 -0
  34. lsurf/geometry/geometry.py +222 -0
  35. lsurf/geometry/surface_analysis.py +375 -0
  36. lsurf/geometry/validation.py +91 -0
  37. lsurf/gui/__init__.py +51 -0
  38. lsurf/gui/app.py +903 -0
  39. lsurf/gui/core/__init__.py +39 -0
  40. lsurf/gui/core/scene.py +343 -0
  41. lsurf/gui/core/simulation.py +264 -0
  42. lsurf/gui/renderers/__init__.py +40 -0
  43. lsurf/gui/renderers/ray_renderer.py +353 -0
  44. lsurf/gui/renderers/source_renderer.py +505 -0
  45. lsurf/gui/renderers/surface_renderer.py +477 -0
  46. lsurf/gui/views/__init__.py +48 -0
  47. lsurf/gui/views/config_editor.py +3199 -0
  48. lsurf/gui/views/properties.py +257 -0
  49. lsurf/gui/views/results.py +291 -0
  50. lsurf/gui/views/scene_tree.py +180 -0
  51. lsurf/gui/views/viewport_3d.py +555 -0
  52. lsurf/gui/views/visualizations.py +712 -0
  53. lsurf/materials/__init__.py +169 -0
  54. lsurf/materials/base/__init__.py +64 -0
  55. lsurf/materials/base/full_inhomogeneous.py +208 -0
  56. lsurf/materials/base/grid_inhomogeneous.py +319 -0
  57. lsurf/materials/base/homogeneous.py +342 -0
  58. lsurf/materials/base/material_field.py +527 -0
  59. lsurf/materials/base/simple_inhomogeneous.py +418 -0
  60. lsurf/materials/base/spectral_inhomogeneous.py +497 -0
  61. lsurf/materials/implementations/__init__.py +120 -0
  62. lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
  63. lsurf/materials/implementations/duct_atmosphere.py +390 -0
  64. lsurf/materials/implementations/exponential_atmosphere.py +435 -0
  65. lsurf/materials/implementations/gaussian_lens.py +120 -0
  66. lsurf/materials/implementations/interpolated_data.py +123 -0
  67. lsurf/materials/implementations/layered_atmosphere.py +134 -0
  68. lsurf/materials/implementations/linear_gradient.py +109 -0
  69. lsurf/materials/implementations/linsley_atmosphere.py +764 -0
  70. lsurf/materials/implementations/standard_materials.py +126 -0
  71. lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
  72. lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
  73. lsurf/materials/utils/__init__.py +77 -0
  74. lsurf/materials/utils/constants.py +45 -0
  75. lsurf/materials/utils/device_functions.py +117 -0
  76. lsurf/materials/utils/dispersion.py +160 -0
  77. lsurf/materials/utils/factories.py +142 -0
  78. lsurf/propagation/__init__.py +91 -0
  79. lsurf/propagation/detector_gpu.py +67 -0
  80. lsurf/propagation/gpu_device_rays.py +294 -0
  81. lsurf/propagation/kernels/__init__.py +175 -0
  82. lsurf/propagation/kernels/absorption/__init__.py +61 -0
  83. lsurf/propagation/kernels/absorption/grid.py +240 -0
  84. lsurf/propagation/kernels/absorption/simple.py +232 -0
  85. lsurf/propagation/kernels/absorption/spectral.py +410 -0
  86. lsurf/propagation/kernels/detection/__init__.py +64 -0
  87. lsurf/propagation/kernels/detection/protocol.py +102 -0
  88. lsurf/propagation/kernels/detection/spherical.py +255 -0
  89. lsurf/propagation/kernels/device_functions.py +790 -0
  90. lsurf/propagation/kernels/fresnel/__init__.py +64 -0
  91. lsurf/propagation/kernels/fresnel/protocol.py +97 -0
  92. lsurf/propagation/kernels/fresnel/standard.py +258 -0
  93. lsurf/propagation/kernels/intersection/__init__.py +79 -0
  94. lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
  95. lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
  96. lsurf/propagation/kernels/intersection/plane.py +166 -0
  97. lsurf/propagation/kernels/intersection/protocol.py +95 -0
  98. lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
  99. lsurf/propagation/kernels/intersection/sphere.py +190 -0
  100. lsurf/propagation/kernels/propagation/__init__.py +85 -0
  101. lsurf/propagation/kernels/propagation/grid.py +527 -0
  102. lsurf/propagation/kernels/propagation/protocol.py +105 -0
  103. lsurf/propagation/kernels/propagation/simple.py +460 -0
  104. lsurf/propagation/kernels/propagation/spectral.py +875 -0
  105. lsurf/propagation/kernels/registry.py +331 -0
  106. lsurf/propagation/kernels/surface/__init__.py +72 -0
  107. lsurf/propagation/kernels/surface/bisection.py +232 -0
  108. lsurf/propagation/kernels/surface/detection.py +402 -0
  109. lsurf/propagation/kernels/surface/reduction.py +166 -0
  110. lsurf/propagation/propagator_protocol.py +222 -0
  111. lsurf/propagation/propagators/__init__.py +101 -0
  112. lsurf/propagation/propagators/detector_handler.py +354 -0
  113. lsurf/propagation/propagators/factory.py +200 -0
  114. lsurf/propagation/propagators/fresnel_handler.py +305 -0
  115. lsurf/propagation/propagators/gpu_gradient.py +566 -0
  116. lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
  117. lsurf/propagation/propagators/gradient.py +429 -0
  118. lsurf/propagation/propagators/intersection_handler.py +327 -0
  119. lsurf/propagation/propagators/material_propagator.py +398 -0
  120. lsurf/propagation/propagators/signed_distance_handler.py +522 -0
  121. lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
  122. lsurf/propagation/propagators/surface_interaction.py +616 -0
  123. lsurf/propagation/propagators/surface_propagator.py +719 -0
  124. lsurf/py.typed +1 -0
  125. lsurf/simulation/__init__.py +70 -0
  126. lsurf/simulation/config.py +164 -0
  127. lsurf/simulation/orchestrator.py +462 -0
  128. lsurf/simulation/result.py +299 -0
  129. lsurf/simulation/simulation.py +262 -0
  130. lsurf/sources/__init__.py +128 -0
  131. lsurf/sources/base.py +264 -0
  132. lsurf/sources/collimated.py +252 -0
  133. lsurf/sources/custom.py +409 -0
  134. lsurf/sources/diverging.py +228 -0
  135. lsurf/sources/gaussian.py +272 -0
  136. lsurf/sources/parallel_from_positions.py +197 -0
  137. lsurf/sources/point.py +172 -0
  138. lsurf/sources/uniform_diverging.py +258 -0
  139. lsurf/surfaces/__init__.py +184 -0
  140. lsurf/surfaces/cpu/__init__.py +50 -0
  141. lsurf/surfaces/cpu/curved_wave.py +463 -0
  142. lsurf/surfaces/cpu/gerstner_wave.py +381 -0
  143. lsurf/surfaces/cpu/wave_params.py +118 -0
  144. lsurf/surfaces/gpu/__init__.py +72 -0
  145. lsurf/surfaces/gpu/annular_plane.py +453 -0
  146. lsurf/surfaces/gpu/bounded_plane.py +390 -0
  147. lsurf/surfaces/gpu/curved_wave.py +483 -0
  148. lsurf/surfaces/gpu/gerstner_wave.py +377 -0
  149. lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
  150. lsurf/surfaces/gpu/plane.py +299 -0
  151. lsurf/surfaces/gpu/recording_sphere.py +587 -0
  152. lsurf/surfaces/gpu/sphere.py +311 -0
  153. lsurf/surfaces/protocol.py +336 -0
  154. lsurf/surfaces/registry.py +373 -0
  155. lsurf/utilities/__init__.py +175 -0
  156. lsurf/utilities/detector_analysis.py +814 -0
  157. lsurf/utilities/fresnel.py +628 -0
  158. lsurf/utilities/interactions.py +1215 -0
  159. lsurf/utilities/propagation.py +602 -0
  160. lsurf/utilities/ray_data.py +532 -0
  161. lsurf/utilities/recording_sphere.py +745 -0
  162. lsurf/utilities/time_spread.py +463 -0
  163. lsurf/visualization/__init__.py +329 -0
  164. lsurf/visualization/absorption_plots.py +334 -0
  165. lsurf/visualization/atmospheric_plots.py +754 -0
  166. lsurf/visualization/common.py +348 -0
  167. lsurf/visualization/detector_plots.py +1350 -0
  168. lsurf/visualization/detector_sphere_plots.py +1173 -0
  169. lsurf/visualization/fresnel_plots.py +1061 -0
  170. lsurf/visualization/ocean_simulation_plots.py +999 -0
  171. lsurf/visualization/polarization_plots.py +916 -0
  172. lsurf/visualization/raytracing_plots.py +1521 -0
  173. lsurf/visualization/ring_detector_plots.py +1867 -0
  174. lsurf/visualization/time_spread_plots.py +531 -0
  175. lsurf-1.0.0.dist-info/METADATA +381 -0
  176. lsurf-1.0.0.dist-info/RECORD +180 -0
  177. lsurf-1.0.0.dist-info/WHEEL +5 -0
  178. lsurf-1.0.0.dist-info/entry_points.txt +2 -0
  179. lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
  180. lsurf-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,353 @@
1
+ # The Clear BSD License
2
+ #
3
+ # Copyright (c) 2026 Tobias Heibges
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted (subject to the limitations in the disclaimer
8
+ # below) provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ #
13
+ # * Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # * Neither the name of the copyright holder nor the names of its
18
+ # contributors may be used to endorse or promote products derived from this
19
+ # software without specific prior written permission.
20
+ #
21
+ # NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22
+ # THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30
+ # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+
34
+ """
35
+ Local Recording Sphere Detector for Local-Scale Simulations
36
+
37
+ Provides a spherical detection surface centered at a specified position
38
+ for local-scale simulations without Earth curvature effects.
39
+
40
+ Examples
41
+ --------
42
+ >>> from lsurf.detectors.extended import LocalRecordingSphereDetector
43
+ >>>
44
+ >>> # Create detector with 33 km radius at origin
45
+ >>> detector = LocalRecordingSphereDetector(radius=33000.0)
46
+ >>> result = detector.detect(rays)
47
+ >>> print(f"Detected {result.num_rays} rays")
48
+ """
49
+
50
+ import numpy as np
51
+ from numpy.typing import NDArray
52
+
53
+ from ...utilities.ray_data import RayBatch
54
+ from ..base import DetectionEvent
55
+ from ..results import DetectorResult
56
+
57
+
58
+ class LocalRecordingSphereDetector:
59
+ """
60
+ Spherical detection surface centered at a specified position.
61
+
62
+ Records all rays that intersect the sphere, useful for local-scale
63
+ simulations without Earth curvature effects.
64
+
65
+ Parameters
66
+ ----------
67
+ radius : float
68
+ Sphere radius in meters (default 33 km)
69
+ center : tuple of float
70
+ Center position (default (0, 0, 0))
71
+ name : str
72
+ Detector name for identification
73
+
74
+ Attributes
75
+ ----------
76
+ sphere_radius : float
77
+ Radius of detection sphere in meters.
78
+ center : ndarray, shape (3,)
79
+ Center position of the sphere.
80
+ name : str
81
+ Detector name.
82
+ accumulated_result : DetectorResult
83
+ All accumulated detections since last clear().
84
+
85
+ Examples
86
+ --------
87
+ >>> # Local detector at origin with 33 km radius
88
+ >>> detector = LocalRecordingSphereDetector(radius=33000.0)
89
+ >>> result = detector.detect(rays)
90
+ >>>
91
+ >>> # Detector at specific location
92
+ >>> detector = LocalRecordingSphereDetector(
93
+ ... radius=10000.0,
94
+ ... center=(1000.0, 0.0, 500.0)
95
+ ... )
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ radius: float = 33000.0,
101
+ center: tuple[float, float, float] = (0, 0, 0),
102
+ name: str = "Local Recording Sphere",
103
+ ):
104
+ """
105
+ Initialize local recording sphere detector.
106
+
107
+ Parameters
108
+ ----------
109
+ radius : float
110
+ Sphere radius in meters.
111
+ center : tuple of float
112
+ Center position.
113
+ name : str
114
+ Detector name.
115
+ """
116
+ self.name = name
117
+ self._radius = radius
118
+ self._center = np.array(center, dtype=np.float64)
119
+
120
+ self._accumulated_result = DetectorResult.empty(name)
121
+ self._events: list[DetectionEvent] = []
122
+
123
+ @property
124
+ def sphere_radius(self) -> float:
125
+ """Radius of the detection sphere in meters."""
126
+ return self._radius
127
+
128
+ @property
129
+ def center(self) -> NDArray[np.float64]:
130
+ """Center position of the sphere."""
131
+ return self._center
132
+
133
+ @property
134
+ def accumulated_result(self) -> DetectorResult:
135
+ """All accumulated detections since last clear()."""
136
+ return self._accumulated_result
137
+
138
+ @property
139
+ def events(self) -> list[DetectionEvent]:
140
+ """
141
+ Backward compatibility: list of DetectionEvent objects.
142
+
143
+ For new code, use accumulated_result instead.
144
+ """
145
+ return self._events
146
+
147
+ def detect(
148
+ self,
149
+ rays: RayBatch,
150
+ current_time: float = 0.0,
151
+ accumulate: bool = True,
152
+ compute_travel_time: bool = True,
153
+ speed_of_light: float = 299792458.0,
154
+ ) -> DetectorResult:
155
+ """
156
+ Detect rays intersecting the recording sphere.
157
+
158
+ Parameters
159
+ ----------
160
+ rays : RayBatch
161
+ Rays to detect
162
+ current_time : float
163
+ Current simulation time (unused, for interface compatibility)
164
+ accumulate : bool
165
+ Whether to accumulate results. Default is True.
166
+ compute_travel_time : bool
167
+ If True, add travel time to intersection to ray's accumulated time
168
+ speed_of_light : float
169
+ Speed of light for time computation
170
+
171
+ Returns
172
+ -------
173
+ DetectorResult
174
+ Detection results for all intersecting rays
175
+ """
176
+ active_mask = rays.active
177
+ if not np.any(active_mask):
178
+ return DetectorResult.empty(self.name)
179
+
180
+ origins = rays.positions[active_mask].astype(np.float64)
181
+ directions = rays.directions[active_mask].astype(np.float64)
182
+ active_indices = np.where(active_mask)[0]
183
+
184
+ # Ray-sphere intersection
185
+ oc = origins - self._center
186
+ a = np.sum(directions**2, axis=1)
187
+ b = 2 * np.sum(oc * directions, axis=1)
188
+ c = np.sum(oc**2, axis=1) - self._radius**2
189
+
190
+ discriminant = b**2 - 4 * a * c
191
+ hit_mask = discriminant >= 0
192
+
193
+ if not np.any(hit_mask):
194
+ return DetectorResult.empty(self.name)
195
+
196
+ sqrt_disc = np.sqrt(discriminant[hit_mask])
197
+ t1 = (-b[hit_mask] - sqrt_disc) / (2 * a[hit_mask])
198
+ t2 = (-b[hit_mask] + sqrt_disc) / (2 * a[hit_mask])
199
+
200
+ # Take the positive intersection (forward along ray)
201
+ # For rays inside the sphere, take t2 (exit point)
202
+ t = np.where(t1 > 0, t1, t2)
203
+ valid = t > 1e-6
204
+
205
+ if not np.any(valid):
206
+ return DetectorResult.empty(self.name)
207
+
208
+ # Get indices into original rays
209
+ hit_indices = active_indices[hit_mask]
210
+ valid_indices = hit_indices[valid]
211
+
212
+ # Compute intersection positions
213
+ t_valid = t[valid]
214
+ hit_origins = origins[hit_mask][valid]
215
+ hit_directions = directions[hit_mask][valid]
216
+ intersection_positions = hit_origins + t_valid[:, np.newaxis] * hit_directions
217
+
218
+ # Compute times
219
+ if compute_travel_time:
220
+ times = rays.accumulated_time[valid_indices] + t_valid / speed_of_light
221
+ else:
222
+ times = rays.accumulated_time[valid_indices].copy()
223
+
224
+ # Get polarization vectors if available
225
+ polarization_vectors = None
226
+ if rays.polarization_vector is not None:
227
+ polarization_vectors = rays.polarization_vector[valid_indices].astype(
228
+ np.float32
229
+ )
230
+
231
+ result = DetectorResult(
232
+ positions=intersection_positions.astype(np.float32),
233
+ directions=hit_directions.astype(np.float32),
234
+ times=times.astype(np.float32),
235
+ intensities=rays.intensities[valid_indices].astype(np.float32),
236
+ wavelengths=rays.wavelengths[valid_indices].astype(np.float32),
237
+ ray_indices=valid_indices.astype(np.int32),
238
+ generations=rays.generations[valid_indices].astype(np.int32),
239
+ polarization_vectors=polarization_vectors,
240
+ detector_name=self.name,
241
+ )
242
+
243
+ if accumulate:
244
+ self._accumulated_result = DetectorResult.merge(
245
+ [self._accumulated_result, result]
246
+ )
247
+ self._events.extend(result.to_detection_events())
248
+
249
+ return result
250
+
251
+ def detect_rays(
252
+ self,
253
+ rays: RayBatch,
254
+ compute_travel_time: bool = True,
255
+ speed_of_light: float = 299792458.0,
256
+ ) -> DetectorResult:
257
+ """
258
+ Detect rays (alias for detect with accumulate=False).
259
+
260
+ This method is provided for backward compatibility with code
261
+ that used the detect_rays() method.
262
+
263
+ Parameters
264
+ ----------
265
+ rays : RayBatch
266
+ Rays to detect
267
+ compute_travel_time : bool
268
+ If True, add travel time to intersection
269
+ speed_of_light : float
270
+ Speed of light for time computation
271
+
272
+ Returns
273
+ -------
274
+ DetectorResult
275
+ Detection results
276
+ """
277
+ return self.detect(
278
+ rays,
279
+ accumulate=False,
280
+ compute_travel_time=compute_travel_time,
281
+ speed_of_light=speed_of_light,
282
+ )
283
+
284
+ def record_rays(self, rays: RayBatch) -> DetectorResult:
285
+ """
286
+ Record rays (backward compatibility alias for detect).
287
+
288
+ Parameters
289
+ ----------
290
+ rays : RayBatch
291
+ Rays to record
292
+
293
+ Returns
294
+ -------
295
+ DetectorResult
296
+ Detection results
297
+ """
298
+ return self.detect(rays, accumulate=False)
299
+
300
+ def clear(self) -> None:
301
+ """
302
+ Clear all recorded detections.
303
+
304
+ Resets the detector to its initial state with no recorded events.
305
+ """
306
+ self._accumulated_result = DetectorResult.empty(self.name)
307
+ self._events = []
308
+
309
+ def __repr__(self) -> str:
310
+ """Return string representation."""
311
+ return (
312
+ f"LocalRecordingSphereDetector(radius={self._radius/1000:.1f}km, "
313
+ f"center={self._center.tolist()}, rays={self._accumulated_result.num_rays})"
314
+ )
315
+
316
+ def __len__(self) -> int:
317
+ """Return number of detected rays."""
318
+ return self._accumulated_result.num_rays
319
+
320
+ # Backward compatibility methods from old Detector base class
321
+ def get_arrival_times(self) -> NDArray[np.float64]:
322
+ """Get array of all arrival times."""
323
+ return self._accumulated_result.times.astype(np.float64)
324
+
325
+ def get_arrival_angles(
326
+ self, reference_direction: NDArray[np.float32]
327
+ ) -> NDArray[np.float64]:
328
+ """Get angles between ray directions and reference direction."""
329
+ if self._accumulated_result.is_empty:
330
+ return np.array([], dtype=np.float64)
331
+ ref = reference_direction / np.linalg.norm(reference_direction)
332
+ dir_norms = self._accumulated_result.directions / np.linalg.norm(
333
+ self._accumulated_result.directions, axis=1, keepdims=True
334
+ )
335
+ cos_angles = np.dot(dir_norms, ref)
336
+ cos_angles = np.clip(cos_angles, -1.0, 1.0)
337
+ return np.arccos(cos_angles).astype(np.float64)
338
+
339
+ def get_intensities(self) -> NDArray[np.float64]:
340
+ """Get array of all detected intensities."""
341
+ return self._accumulated_result.intensities.astype(np.float64)
342
+
343
+ def get_wavelengths(self) -> NDArray[np.float64]:
344
+ """Get array of all detected wavelengths."""
345
+ return self._accumulated_result.wavelengths.astype(np.float64)
346
+
347
+ def get_positions(self) -> NDArray[np.float32]:
348
+ """Get array of all detection positions."""
349
+ return self._accumulated_result.positions.astype(np.float32)
350
+
351
+ def get_total_intensity(self) -> float:
352
+ """Get sum of all detected intensities."""
353
+ return self._accumulated_result.total_intensity