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.
- lsurf/__init__.py +471 -0
- lsurf/analysis/__init__.py +107 -0
- lsurf/analysis/healpix_utils.py +418 -0
- lsurf/analysis/sphere_viz.py +1280 -0
- lsurf/cli/__init__.py +48 -0
- lsurf/cli/build.py +398 -0
- lsurf/cli/config_schema.py +318 -0
- lsurf/cli/gui_cmd.py +76 -0
- lsurf/cli/interactive.py +850 -0
- lsurf/cli/main.py +81 -0
- lsurf/cli/run.py +806 -0
- lsurf/detectors/__init__.py +266 -0
- lsurf/detectors/analysis.py +289 -0
- lsurf/detectors/base.py +284 -0
- lsurf/detectors/constant_size_rings.py +485 -0
- lsurf/detectors/directional.py +45 -0
- lsurf/detectors/extended/__init__.py +73 -0
- lsurf/detectors/extended/local_sphere.py +353 -0
- lsurf/detectors/extended/recording_sphere.py +368 -0
- lsurf/detectors/planar.py +45 -0
- lsurf/detectors/protocol.py +187 -0
- lsurf/detectors/recording_spheres.py +63 -0
- lsurf/detectors/results.py +1140 -0
- lsurf/detectors/small/__init__.py +79 -0
- lsurf/detectors/small/directional.py +330 -0
- lsurf/detectors/small/planar.py +401 -0
- lsurf/detectors/small/spherical.py +450 -0
- lsurf/detectors/spherical.py +45 -0
- lsurf/geometry/__init__.py +199 -0
- lsurf/geometry/builder.py +478 -0
- lsurf/geometry/cell.py +228 -0
- lsurf/geometry/cell_geometry.py +247 -0
- lsurf/geometry/detector_arrays.py +1785 -0
- lsurf/geometry/geometry.py +222 -0
- lsurf/geometry/surface_analysis.py +375 -0
- lsurf/geometry/validation.py +91 -0
- lsurf/gui/__init__.py +51 -0
- lsurf/gui/app.py +903 -0
- lsurf/gui/core/__init__.py +39 -0
- lsurf/gui/core/scene.py +343 -0
- lsurf/gui/core/simulation.py +264 -0
- lsurf/gui/renderers/__init__.py +40 -0
- lsurf/gui/renderers/ray_renderer.py +353 -0
- lsurf/gui/renderers/source_renderer.py +505 -0
- lsurf/gui/renderers/surface_renderer.py +477 -0
- lsurf/gui/views/__init__.py +48 -0
- lsurf/gui/views/config_editor.py +3199 -0
- lsurf/gui/views/properties.py +257 -0
- lsurf/gui/views/results.py +291 -0
- lsurf/gui/views/scene_tree.py +180 -0
- lsurf/gui/views/viewport_3d.py +555 -0
- lsurf/gui/views/visualizations.py +712 -0
- lsurf/materials/__init__.py +169 -0
- lsurf/materials/base/__init__.py +64 -0
- lsurf/materials/base/full_inhomogeneous.py +208 -0
- lsurf/materials/base/grid_inhomogeneous.py +319 -0
- lsurf/materials/base/homogeneous.py +342 -0
- lsurf/materials/base/material_field.py +527 -0
- lsurf/materials/base/simple_inhomogeneous.py +418 -0
- lsurf/materials/base/spectral_inhomogeneous.py +497 -0
- lsurf/materials/implementations/__init__.py +120 -0
- lsurf/materials/implementations/data/alpha_values_typical_atmosphere_updated.txt +24 -0
- lsurf/materials/implementations/duct_atmosphere.py +390 -0
- lsurf/materials/implementations/exponential_atmosphere.py +435 -0
- lsurf/materials/implementations/gaussian_lens.py +120 -0
- lsurf/materials/implementations/interpolated_data.py +123 -0
- lsurf/materials/implementations/layered_atmosphere.py +134 -0
- lsurf/materials/implementations/linear_gradient.py +109 -0
- lsurf/materials/implementations/linsley_atmosphere.py +764 -0
- lsurf/materials/implementations/standard_materials.py +126 -0
- lsurf/materials/implementations/turbulent_atmosphere.py +135 -0
- lsurf/materials/implementations/us_standard_atmosphere.py +149 -0
- lsurf/materials/utils/__init__.py +77 -0
- lsurf/materials/utils/constants.py +45 -0
- lsurf/materials/utils/device_functions.py +117 -0
- lsurf/materials/utils/dispersion.py +160 -0
- lsurf/materials/utils/factories.py +142 -0
- lsurf/propagation/__init__.py +91 -0
- lsurf/propagation/detector_gpu.py +67 -0
- lsurf/propagation/gpu_device_rays.py +294 -0
- lsurf/propagation/kernels/__init__.py +175 -0
- lsurf/propagation/kernels/absorption/__init__.py +61 -0
- lsurf/propagation/kernels/absorption/grid.py +240 -0
- lsurf/propagation/kernels/absorption/simple.py +232 -0
- lsurf/propagation/kernels/absorption/spectral.py +410 -0
- lsurf/propagation/kernels/detection/__init__.py +64 -0
- lsurf/propagation/kernels/detection/protocol.py +102 -0
- lsurf/propagation/kernels/detection/spherical.py +255 -0
- lsurf/propagation/kernels/device_functions.py +790 -0
- lsurf/propagation/kernels/fresnel/__init__.py +64 -0
- lsurf/propagation/kernels/fresnel/protocol.py +97 -0
- lsurf/propagation/kernels/fresnel/standard.py +258 -0
- lsurf/propagation/kernels/intersection/__init__.py +79 -0
- lsurf/propagation/kernels/intersection/annular_plane.py +207 -0
- lsurf/propagation/kernels/intersection/bounded_plane.py +205 -0
- lsurf/propagation/kernels/intersection/plane.py +166 -0
- lsurf/propagation/kernels/intersection/protocol.py +95 -0
- lsurf/propagation/kernels/intersection/signed_distance.py +742 -0
- lsurf/propagation/kernels/intersection/sphere.py +190 -0
- lsurf/propagation/kernels/propagation/__init__.py +85 -0
- lsurf/propagation/kernels/propagation/grid.py +527 -0
- lsurf/propagation/kernels/propagation/protocol.py +105 -0
- lsurf/propagation/kernels/propagation/simple.py +460 -0
- lsurf/propagation/kernels/propagation/spectral.py +875 -0
- lsurf/propagation/kernels/registry.py +331 -0
- lsurf/propagation/kernels/surface/__init__.py +72 -0
- lsurf/propagation/kernels/surface/bisection.py +232 -0
- lsurf/propagation/kernels/surface/detection.py +402 -0
- lsurf/propagation/kernels/surface/reduction.py +166 -0
- lsurf/propagation/propagator_protocol.py +222 -0
- lsurf/propagation/propagators/__init__.py +101 -0
- lsurf/propagation/propagators/detector_handler.py +354 -0
- lsurf/propagation/propagators/factory.py +200 -0
- lsurf/propagation/propagators/fresnel_handler.py +305 -0
- lsurf/propagation/propagators/gpu_gradient.py +566 -0
- lsurf/propagation/propagators/gpu_surface_propagator.py +707 -0
- lsurf/propagation/propagators/gradient.py +429 -0
- lsurf/propagation/propagators/intersection_handler.py +327 -0
- lsurf/propagation/propagators/material_propagator.py +398 -0
- lsurf/propagation/propagators/signed_distance_handler.py +522 -0
- lsurf/propagation/propagators/spectral_gpu_gradient.py +553 -0
- lsurf/propagation/propagators/surface_interaction.py +616 -0
- lsurf/propagation/propagators/surface_propagator.py +719 -0
- lsurf/py.typed +1 -0
- lsurf/simulation/__init__.py +70 -0
- lsurf/simulation/config.py +164 -0
- lsurf/simulation/orchestrator.py +462 -0
- lsurf/simulation/result.py +299 -0
- lsurf/simulation/simulation.py +262 -0
- lsurf/sources/__init__.py +128 -0
- lsurf/sources/base.py +264 -0
- lsurf/sources/collimated.py +252 -0
- lsurf/sources/custom.py +409 -0
- lsurf/sources/diverging.py +228 -0
- lsurf/sources/gaussian.py +272 -0
- lsurf/sources/parallel_from_positions.py +197 -0
- lsurf/sources/point.py +172 -0
- lsurf/sources/uniform_diverging.py +258 -0
- lsurf/surfaces/__init__.py +184 -0
- lsurf/surfaces/cpu/__init__.py +50 -0
- lsurf/surfaces/cpu/curved_wave.py +463 -0
- lsurf/surfaces/cpu/gerstner_wave.py +381 -0
- lsurf/surfaces/cpu/wave_params.py +118 -0
- lsurf/surfaces/gpu/__init__.py +72 -0
- lsurf/surfaces/gpu/annular_plane.py +453 -0
- lsurf/surfaces/gpu/bounded_plane.py +390 -0
- lsurf/surfaces/gpu/curved_wave.py +483 -0
- lsurf/surfaces/gpu/gerstner_wave.py +377 -0
- lsurf/surfaces/gpu/multi_curved_wave.py +520 -0
- lsurf/surfaces/gpu/plane.py +299 -0
- lsurf/surfaces/gpu/recording_sphere.py +587 -0
- lsurf/surfaces/gpu/sphere.py +311 -0
- lsurf/surfaces/protocol.py +336 -0
- lsurf/surfaces/registry.py +373 -0
- lsurf/utilities/__init__.py +175 -0
- lsurf/utilities/detector_analysis.py +814 -0
- lsurf/utilities/fresnel.py +628 -0
- lsurf/utilities/interactions.py +1215 -0
- lsurf/utilities/propagation.py +602 -0
- lsurf/utilities/ray_data.py +532 -0
- lsurf/utilities/recording_sphere.py +745 -0
- lsurf/utilities/time_spread.py +463 -0
- lsurf/visualization/__init__.py +329 -0
- lsurf/visualization/absorption_plots.py +334 -0
- lsurf/visualization/atmospheric_plots.py +754 -0
- lsurf/visualization/common.py +348 -0
- lsurf/visualization/detector_plots.py +1350 -0
- lsurf/visualization/detector_sphere_plots.py +1173 -0
- lsurf/visualization/fresnel_plots.py +1061 -0
- lsurf/visualization/ocean_simulation_plots.py +999 -0
- lsurf/visualization/polarization_plots.py +916 -0
- lsurf/visualization/raytracing_plots.py +1521 -0
- lsurf/visualization/ring_detector_plots.py +1867 -0
- lsurf/visualization/time_spread_plots.py +531 -0
- lsurf-1.0.0.dist-info/METADATA +381 -0
- lsurf-1.0.0.dist-info/RECORD +180 -0
- lsurf-1.0.0.dist-info/WHEEL +5 -0
- lsurf-1.0.0.dist-info/entry_points.txt +2 -0
- lsurf-1.0.0.dist-info/licenses/LICENSE +32 -0
- 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
|
+
"""Ray renderer - visualizes ray paths and detection points.
|
|
35
|
+
|
|
36
|
+
Generates 3D representations of ray trajectories and detection scatter plots.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from typing import TYPE_CHECKING, Any
|
|
40
|
+
|
|
41
|
+
import numpy as np
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from lsurf.detectors import DetectorResult
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RayRenderer:
|
|
48
|
+
"""Renders ray paths and detection points for Dear PyGui."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
max_rays: int = 1000,
|
|
53
|
+
point_size: float = 0.1,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the renderer.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
max_rays: Maximum number of rays to render (for performance)
|
|
59
|
+
point_size: Size of detection point markers
|
|
60
|
+
"""
|
|
61
|
+
self.max_rays = max_rays
|
|
62
|
+
self.point_size = point_size
|
|
63
|
+
|
|
64
|
+
def render_detections(self, detected: "DetectorResult") -> dict[str, Any]:
|
|
65
|
+
"""Render detection points as a point cloud.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
detected: Detection results from simulation
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dict with 'vertices', 'indices', and 'wireframe' keys
|
|
72
|
+
"""
|
|
73
|
+
if detected is None or len(detected.positions) == 0:
|
|
74
|
+
return {
|
|
75
|
+
"vertices": np.zeros((0, 3), dtype=np.float32),
|
|
76
|
+
"indices": np.zeros(0, dtype=np.uint32),
|
|
77
|
+
"wireframe": False,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
positions = np.array(detected.positions, dtype=np.float32)
|
|
81
|
+
|
|
82
|
+
# Subsample if too many points
|
|
83
|
+
if len(positions) > self.max_rays:
|
|
84
|
+
indices_sample = np.random.choice(
|
|
85
|
+
len(positions), self.max_rays, replace=False
|
|
86
|
+
)
|
|
87
|
+
positions = positions[indices_sample]
|
|
88
|
+
|
|
89
|
+
# Generate small markers for each detection point
|
|
90
|
+
all_vertices = []
|
|
91
|
+
all_indices = []
|
|
92
|
+
vertex_offset = 0
|
|
93
|
+
|
|
94
|
+
for pos in positions:
|
|
95
|
+
marker = self._generate_point_marker(pos)
|
|
96
|
+
all_vertices.append(marker["vertices"])
|
|
97
|
+
all_indices.append(marker["indices"] + vertex_offset)
|
|
98
|
+
vertex_offset += len(marker["vertices"])
|
|
99
|
+
|
|
100
|
+
if all_vertices:
|
|
101
|
+
vertices = np.vstack(all_vertices)
|
|
102
|
+
indices = np.concatenate(all_indices)
|
|
103
|
+
else:
|
|
104
|
+
vertices = np.zeros((0, 3), dtype=np.float32)
|
|
105
|
+
indices = np.zeros(0, dtype=np.uint32)
|
|
106
|
+
|
|
107
|
+
return {"vertices": vertices, "indices": indices, "wireframe": False}
|
|
108
|
+
|
|
109
|
+
def render_ray_paths(self, surface_hits: dict[str, list]) -> dict[str, Any]:
|
|
110
|
+
"""Render ray paths from surface hit records.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
surface_hits: Dict mapping surface names to lists of hit records
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dict with 'vertices', 'indices', and 'wireframe' keys
|
|
117
|
+
"""
|
|
118
|
+
if not surface_hits:
|
|
119
|
+
return {
|
|
120
|
+
"vertices": np.zeros((0, 3), dtype=np.float32),
|
|
121
|
+
"indices": np.zeros(0, dtype=np.uint32),
|
|
122
|
+
"wireframe": True,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Collect all hit positions
|
|
126
|
+
all_positions = []
|
|
127
|
+
for surface_name, hits in surface_hits.items():
|
|
128
|
+
for hit in hits:
|
|
129
|
+
if hasattr(hit, "position"):
|
|
130
|
+
all_positions.append(np.array(hit.position, dtype=np.float32))
|
|
131
|
+
elif hasattr(hit, "positions"):
|
|
132
|
+
all_positions.extend(
|
|
133
|
+
[np.array(p, dtype=np.float32) for p in hit.positions]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if not all_positions:
|
|
137
|
+
return {
|
|
138
|
+
"vertices": np.zeros((0, 3), dtype=np.float32),
|
|
139
|
+
"indices": np.zeros(0, dtype=np.uint32),
|
|
140
|
+
"wireframe": True,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Subsample if too many
|
|
144
|
+
if len(all_positions) > self.max_rays:
|
|
145
|
+
indices_sample = np.random.choice(
|
|
146
|
+
len(all_positions), self.max_rays, replace=False
|
|
147
|
+
)
|
|
148
|
+
all_positions = [all_positions[i] for i in indices_sample]
|
|
149
|
+
|
|
150
|
+
vertices = np.array(all_positions, dtype=np.float32)
|
|
151
|
+
|
|
152
|
+
# For now, just render as points (connecting rays would require
|
|
153
|
+
# tracking individual ray paths through bounces)
|
|
154
|
+
all_indices = []
|
|
155
|
+
vertex_offset = 0
|
|
156
|
+
|
|
157
|
+
markers_vertices = []
|
|
158
|
+
for pos in vertices:
|
|
159
|
+
marker = self._generate_point_marker(pos, size=self.point_size * 0.5)
|
|
160
|
+
markers_vertices.append(marker["vertices"])
|
|
161
|
+
all_indices.append(marker["indices"] + vertex_offset)
|
|
162
|
+
vertex_offset += len(marker["vertices"])
|
|
163
|
+
|
|
164
|
+
if markers_vertices:
|
|
165
|
+
final_vertices = np.vstack(markers_vertices)
|
|
166
|
+
final_indices = np.concatenate(all_indices)
|
|
167
|
+
else:
|
|
168
|
+
final_vertices = np.zeros((0, 3), dtype=np.float32)
|
|
169
|
+
final_indices = np.zeros(0, dtype=np.uint32)
|
|
170
|
+
|
|
171
|
+
return {"vertices": final_vertices, "indices": final_indices, "wireframe": True}
|
|
172
|
+
|
|
173
|
+
def render_rays_batch(
|
|
174
|
+
self,
|
|
175
|
+
origins: np.ndarray,
|
|
176
|
+
directions: np.ndarray,
|
|
177
|
+
lengths: np.ndarray | float = 1.0,
|
|
178
|
+
) -> dict[str, Any]:
|
|
179
|
+
"""Render a batch of rays as lines.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
origins: Ray origin positions (N, 3)
|
|
183
|
+
directions: Ray direction vectors (N, 3)
|
|
184
|
+
lengths: Ray lengths, scalar or array (N,)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict with 'vertices', 'indices', and 'wireframe' keys
|
|
188
|
+
"""
|
|
189
|
+
origins = np.array(origins, dtype=np.float32)
|
|
190
|
+
directions = np.array(directions, dtype=np.float32)
|
|
191
|
+
|
|
192
|
+
if np.isscalar(lengths):
|
|
193
|
+
lengths = np.full(len(origins), lengths, dtype=np.float32)
|
|
194
|
+
else:
|
|
195
|
+
lengths = np.array(lengths, dtype=np.float32)
|
|
196
|
+
|
|
197
|
+
# Subsample if too many
|
|
198
|
+
if len(origins) > self.max_rays:
|
|
199
|
+
indices_sample = np.random.choice(
|
|
200
|
+
len(origins), self.max_rays, replace=False
|
|
201
|
+
)
|
|
202
|
+
origins = origins[indices_sample]
|
|
203
|
+
directions = directions[indices_sample]
|
|
204
|
+
lengths = lengths[indices_sample]
|
|
205
|
+
|
|
206
|
+
# Normalize directions
|
|
207
|
+
norms = np.linalg.norm(directions, axis=1, keepdims=True)
|
|
208
|
+
norms[norms == 0] = 1 # Avoid division by zero
|
|
209
|
+
directions = directions / norms
|
|
210
|
+
|
|
211
|
+
# Compute end points
|
|
212
|
+
ends = origins + directions * lengths[:, np.newaxis]
|
|
213
|
+
|
|
214
|
+
# Interleave origins and ends for line segments
|
|
215
|
+
n_rays = len(origins)
|
|
216
|
+
vertices = np.zeros((n_rays * 2, 3), dtype=np.float32)
|
|
217
|
+
vertices[0::2] = origins
|
|
218
|
+
vertices[1::2] = ends
|
|
219
|
+
|
|
220
|
+
# Line segment indices
|
|
221
|
+
indices = np.arange(n_rays * 2, dtype=np.uint32)
|
|
222
|
+
|
|
223
|
+
return {"vertices": vertices, "indices": indices, "wireframe": True}
|
|
224
|
+
|
|
225
|
+
def _generate_point_marker(
|
|
226
|
+
self, position: np.ndarray, size: float | None = None
|
|
227
|
+
) -> dict[str, Any]:
|
|
228
|
+
"""Generate a small marker for a single point.
|
|
229
|
+
|
|
230
|
+
Uses a small octahedron shape for visibility.
|
|
231
|
+
"""
|
|
232
|
+
if size is None:
|
|
233
|
+
size = self.point_size
|
|
234
|
+
|
|
235
|
+
pos = np.array(position, dtype=np.float32)
|
|
236
|
+
|
|
237
|
+
# Octahedron vertices
|
|
238
|
+
vertices = np.array(
|
|
239
|
+
[
|
|
240
|
+
pos + [size, 0, 0],
|
|
241
|
+
pos + [-size, 0, 0],
|
|
242
|
+
pos + [0, size, 0],
|
|
243
|
+
pos + [0, -size, 0],
|
|
244
|
+
pos + [0, 0, size],
|
|
245
|
+
pos + [0, 0, -size],
|
|
246
|
+
],
|
|
247
|
+
dtype=np.float32,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Triangle faces
|
|
251
|
+
indices = np.array(
|
|
252
|
+
[
|
|
253
|
+
0,
|
|
254
|
+
2,
|
|
255
|
+
4,
|
|
256
|
+
2,
|
|
257
|
+
1,
|
|
258
|
+
4,
|
|
259
|
+
1,
|
|
260
|
+
3,
|
|
261
|
+
4,
|
|
262
|
+
3,
|
|
263
|
+
0,
|
|
264
|
+
4,
|
|
265
|
+
0,
|
|
266
|
+
5,
|
|
267
|
+
2,
|
|
268
|
+
2,
|
|
269
|
+
5,
|
|
270
|
+
1,
|
|
271
|
+
1,
|
|
272
|
+
5,
|
|
273
|
+
3,
|
|
274
|
+
3,
|
|
275
|
+
5,
|
|
276
|
+
0,
|
|
277
|
+
],
|
|
278
|
+
dtype=np.uint32,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return {"vertices": vertices, "indices": indices, "wireframe": False}
|
|
282
|
+
|
|
283
|
+
def color_by_intensity(self, intensities: np.ndarray) -> np.ndarray:
|
|
284
|
+
"""Generate colors based on ray intensities.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
intensities: Ray intensity values
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
RGBA colors array (N, 4)
|
|
291
|
+
"""
|
|
292
|
+
# Normalize to 0-1
|
|
293
|
+
if len(intensities) == 0:
|
|
294
|
+
return np.zeros((0, 4), dtype=np.float32)
|
|
295
|
+
|
|
296
|
+
i_min, i_max = intensities.min(), intensities.max()
|
|
297
|
+
if i_max > i_min:
|
|
298
|
+
normalized = (intensities - i_min) / (i_max - i_min)
|
|
299
|
+
else:
|
|
300
|
+
normalized = np.ones_like(intensities)
|
|
301
|
+
|
|
302
|
+
# Use a simple colormap (blue -> red)
|
|
303
|
+
colors = np.zeros((len(intensities), 4), dtype=np.float32)
|
|
304
|
+
colors[:, 0] = normalized # Red
|
|
305
|
+
colors[:, 2] = 1 - normalized # Blue
|
|
306
|
+
colors[:, 3] = 1.0 # Alpha
|
|
307
|
+
|
|
308
|
+
return colors
|
|
309
|
+
|
|
310
|
+
def color_by_wavelength(self, wavelengths: np.ndarray) -> np.ndarray:
|
|
311
|
+
"""Generate colors based on wavelengths (approximate visible spectrum).
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
wavelengths: Wavelength values in meters
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
RGBA colors array (N, 4)
|
|
318
|
+
"""
|
|
319
|
+
if len(wavelengths) == 0:
|
|
320
|
+
return np.zeros((0, 4), dtype=np.float32)
|
|
321
|
+
|
|
322
|
+
# Convert to nanometers
|
|
323
|
+
wl_nm = wavelengths * 1e9
|
|
324
|
+
|
|
325
|
+
colors = np.zeros((len(wavelengths), 4), dtype=np.float32)
|
|
326
|
+
|
|
327
|
+
for i, wl in enumerate(wl_nm):
|
|
328
|
+
if wl < 380:
|
|
329
|
+
# UV - purple
|
|
330
|
+
colors[i] = [0.5, 0.0, 0.5, 1.0]
|
|
331
|
+
elif wl < 440:
|
|
332
|
+
# Violet
|
|
333
|
+
colors[i] = [0.5 - 0.5 * (wl - 380) / 60, 0, 1, 1]
|
|
334
|
+
elif wl < 490:
|
|
335
|
+
# Blue -> Cyan
|
|
336
|
+
colors[i] = [0, (wl - 440) / 50, 1, 1]
|
|
337
|
+
elif wl < 510:
|
|
338
|
+
# Cyan -> Green
|
|
339
|
+
colors[i] = [0, 1, 1 - (wl - 490) / 20, 1]
|
|
340
|
+
elif wl < 580:
|
|
341
|
+
# Green -> Yellow
|
|
342
|
+
colors[i] = [(wl - 510) / 70, 1, 0, 1]
|
|
343
|
+
elif wl < 645:
|
|
344
|
+
# Yellow -> Red
|
|
345
|
+
colors[i] = [1, 1 - (wl - 580) / 65, 0, 1]
|
|
346
|
+
elif wl < 780:
|
|
347
|
+
# Red
|
|
348
|
+
colors[i] = [1, 0, 0, 1]
|
|
349
|
+
else:
|
|
350
|
+
# IR - dark red
|
|
351
|
+
colors[i] = [0.5, 0, 0, 1]
|
|
352
|
+
|
|
353
|
+
return colors.astype(np.float32)
|