Skip to content

Commit 3ec0d92

Browse files
committed
fixes
1 parent 40deb93 commit 3ec0d92

6 files changed

Lines changed: 98 additions & 213 deletions

File tree

artist/core/kinematics_reconstructor.py

Lines changed: 67 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -266,70 +266,79 @@ def _reconstruct_kinematics_parameters_with_raytracing(
266266
initial_actuator_params = (
267267
heliostat_group.kinematics.actuators.optimizable_parameters.detach()
268268
)
269-
270-
angle_mean = initial_actuator_params[
271-
:, index_mapping.actuator_params_initial_angle
272-
].mean()
273-
angle_std = (
274-
initial_actuator_params[
269+
if initial_actuator_params is not None:
270+
angle_mean = initial_actuator_params[
275271
:, index_mapping.actuator_params_initial_angle
276-
]
277-
.std()
278-
.clamp(min=1e-3)
279-
)
272+
].mean()
273+
angle_std = (
274+
initial_actuator_params[
275+
:, index_mapping.actuator_params_initial_angle
276+
]
277+
.std()
278+
.clamp(min=1e-3)
279+
)
280280

281-
stroke_mean = initial_actuator_params[
282-
:, index_mapping.actuator_params_initial_stroke_length
283-
].mean()
284-
stroke_std = (
285-
initial_actuator_params[
281+
stroke_mean = initial_actuator_params[
286282
:, index_mapping.actuator_params_initial_stroke_length
287-
]
288-
.std()
289-
.clamp(min=1e-3)
290-
)
291-
292-
angle_normalized = (
293-
initial_actuator_params[
294-
:, index_mapping.actuator_params_initial_angle
295-
]
296-
- angle_mean
297-
) / angle_std
298-
stroke_length_normalized = (
299-
initial_actuator_params[
300-
:, index_mapping.actuator_params_initial_stroke_length
301-
]
302-
- stroke_mean
303-
) / stroke_std
283+
].mean()
284+
stroke_std = (
285+
initial_actuator_params[
286+
:, index_mapping.actuator_params_initial_stroke_length
287+
]
288+
.std()
289+
.clamp(min=1e-3)
290+
)
304291

305-
delta_angle = torch.zeros_like(angle_normalized, requires_grad=True)
306-
delta_stroke = torch.zeros_like(
307-
stroke_length_normalized, requires_grad=True
308-
)
292+
angle_normalized = (
293+
initial_actuator_params[
294+
:, index_mapping.actuator_params_initial_angle
295+
]
296+
- angle_mean
297+
) / angle_std
298+
stroke_length_normalized = (
299+
initial_actuator_params[
300+
:, index_mapping.actuator_params_initial_stroke_length
301+
]
302+
- stroke_mean
303+
) / stroke_std
304+
305+
delta_angle = torch.zeros_like(angle_normalized, requires_grad=True)
306+
delta_stroke = torch.zeros_like(
307+
stroke_length_normalized, requires_grad=True
308+
)
309+
else:
310+
delta_angle = None
311+
delta_stroke = None
309312

310313
# Set up optimizer, scheduler, and early stopping.
311-
optimizer = torch.optim.Adam(
312-
[
313-
{
314-
"params": heliostat_group.kinematics.rotation_deviation_parameters.requires_grad_(),
315-
"lr": self.optimizer_dict[
316-
config_dictionary.initial_learning_rate_rotation_deviation
317-
],
318-
},
319-
{
320-
"params": delta_angle,
321-
"lr": self.optimizer_dict[
322-
config_dictionary.initial_learning_rate_initial_angles
323-
],
324-
},
325-
{
326-
"params": delta_stroke,
327-
"lr": self.optimizer_dict[
328-
config_dictionary.initial_learning_rate_initial_stroke_length
329-
],
330-
},
331-
]
332-
)
314+
optimizer_params = [
315+
{
316+
"params": heliostat_group.kinematics.rotation_deviation_parameters.requires_grad_(),
317+
"lr": self.optimizer_dict[
318+
config_dictionary.initial_learning_rate_rotation_deviation
319+
],
320+
}
321+
]
322+
323+
if initial_actuator_params is not None:
324+
optimizer_params.extend(
325+
[
326+
{
327+
"params": delta_angle,
328+
"lr": self.optimizer_dict[
329+
config_dictionary.initial_learning_rate_initial_angles
330+
],
331+
},
332+
{
333+
"params": delta_stroke,
334+
"lr": self.optimizer_dict[
335+
config_dictionary.initial_learning_rate_initial_stroke_length
336+
],
337+
},
338+
]
339+
)
340+
341+
optimizer = torch.optim.Adam(optimizer_params)
333342

334343
# Create a learning rate scheduler.
335344
scheduler_fn = getattr(

artist/util/utils.py

Lines changed: 0 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -720,146 +720,6 @@ def bitmap_coordinates_to_target_coordinates(
720720
return target_coordinates
721721

722722

723-
def target_coordinates_to_bitmap_coordinates(
724-
world_coordinates: torch.Tensor,
725-
bitmap_resolution: torch.Tensor,
726-
solar_tower: SolarTower,
727-
target_area_indices: torch.Tensor,
728-
device: torch.device | None = None,
729-
) -> torch.Tensor:
730-
"""
731-
Map 3D world coordinates on tower targets into 2D bitmap pixel coordinates.
732-
733-
Bitmaps and the resolution are conceptually defined as: [W, H] # width, height
734-
Tensor memory layout follows PyTorch convention: [H, W] # height, width
735-
736-
The bitmap is treated as a discrete image grid with resolution:
737-
- bitmap_resolution = [width, height]
738-
Pixel coordinates follow image indexing conventions:
739-
- bitmap_coordinates[..., e] ∈ [0, W-1]
740-
- bitmap_coordinates[..., u] ∈ [0, H-1]
741-
They are interpreted as centered pixels:
742-
- (e + 0.5) / W
743-
- (u + 0.5) / H
744-
This ensures each pixel represents its spatial cell center rather than its corner.
745-
746-
The e-axis is intentionally flipped (0.5 - e_norm) to match the desired bitmap orientation.
747-
This means: increasing bitmap e → decreases world e.
748-
749-
Parameters
750-
----------
751-
world_coordinates : torch.Tensor
752-
Coordinates in world space.
753-
Shape is ``[number_of_active_heliostats, 4]``.
754-
bitmap_resolution : torch.Tensor
755-
Resolution of the bitmap (width, height) in pixels.
756-
Shape is ``[2]``.
757-
solar_tower : SolarTower
758-
Solar tower containing all target area definitions (planar and cylindrical).
759-
target_area_indices : torch.Tensor
760-
Global target area index for each heliostat (planar indices first, cylindrical second).
761-
Shape is ``[number_of_active_heliostats]``.
762-
device : torch.device | None
763-
The device on which to perform computations or load tensors and models (default is None).
764-
If None, ``ARTIST`` will automatically select the most appropriate
765-
device (CUDA or CPU) based on availability and OS.
766-
767-
Returns
768-
-------
769-
torch.Tensor
770-
Bitmap coordinates in homogeneous format.
771-
Shape is ``[number_of_active_heliostats, 4]``.
772-
"""
773-
device = get_device(device=device)
774-
775-
world_enu = world_coordinates[:, :3]
776-
number_of_coordinates = world_enu.shape[0]
777-
778-
bitmap_coords = torch.zeros((number_of_coordinates, 2), device=device)
779-
780-
bitmap_width = bitmap_resolution[index_mapping.unbatched_bitmap_e]
781-
bitmap_height = bitmap_resolution[index_mapping.unbatched_bitmap_u]
782-
783-
planar_mask = (
784-
target_area_indices
785-
< solar_tower.number_of_target_areas_per_type[index_mapping.planar_target_areas]
786-
)
787-
788-
if planar_mask.any():
789-
planar_indices = target_area_indices[planar_mask]
790-
791-
planar = cast(
792-
TowerTargetAreasPlanar,
793-
solar_tower.target_areas[index_mapping.planar_target_areas],
794-
)
795-
796-
centers = planar.centers[planar_indices][:, :3]
797-
dims = planar.dimensions[planar_indices]
798-
799-
e_axis = torch.tensor([1.0, 0.0, 0.0], device=device).expand(
800-
planar_indices.numel(), 3
801-
)
802-
u_axis = torch.tensor([0.0, 0.0, 1.0], device=device).expand(
803-
planar_indices.numel(), 3
804-
)
805-
806-
rel = world_enu[planar_mask] - centers
807-
808-
e_local = -torch.sum(rel * e_axis, dim=-1)
809-
u_local = -torch.sum(rel * u_axis, dim=-1)
810-
811-
e_norm = e_local / dims[:, index_mapping.target_dimensions_width] + 0.5
812-
u_norm = u_local / dims[:, index_mapping.target_dimensions_height] + 0.5
813-
814-
bitmap_coords[planar_mask, index_mapping.unbatched_bitmap_e] = (
815-
e_norm * bitmap_width - 0.5
816-
)
817-
bitmap_coords[planar_mask, index_mapping.unbatched_bitmap_u] = (
818-
u_norm * bitmap_height - 0.5
819-
)
820-
821-
if (~planar_mask).any():
822-
cylinder_indices = (
823-
target_area_indices[~planar_mask]
824-
- solar_tower.number_of_target_areas_per_type[
825-
index_mapping.planar_target_areas
826-
]
827-
)
828-
cylindrical = cast(
829-
TowerTargetAreasCylindrical,
830-
solar_tower.target_areas[index_mapping.cylindrical_target_areas],
831-
)
832-
833-
centers = cylindrical.centers[cylinder_indices][:, :3]
834-
axes = cylindrical.axes[cylinder_indices][:, :3]
835-
normals = cylindrical.normals[cylinder_indices][:, :3]
836-
heights = cylindrical.heights[cylinder_indices].flatten()
837-
opening_angles = cylindrical.opening_angles[cylinder_indices].flatten()
838-
839-
v = torch.cross(axes, normals, dim=-1)
840-
841-
rel = world_enu[~planar_mask] - centers
842-
843-
z = torch.sum(rel * axes, dim=-1)
844-
845-
x_n = torch.sum(rel * normals, dim=-1)
846-
x_v = torch.sum(rel * v, dim=-1)
847-
848-
theta = torch.atan2(x_v, x_n)
849-
850-
e_norm = theta / opening_angles + 0.5
851-
u_norm = -z / heights + 0.5
852-
853-
bitmap_coords[~planar_mask, index_mapping.unbatched_bitmap_e] = (
854-
e_norm * bitmap_width - 0.5
855-
)
856-
bitmap_coords[~planar_mask, index_mapping.unbatched_bitmap_u] = (
857-
u_norm * bitmap_height - 0.5
858-
)
859-
860-
return bitmap_coords
861-
862-
863723
def create_nurbs_evaluation_grid(
864724
number_of_evaluation_points: torch.Tensor,
865725
epsilon: float = 1e-7,
Binary file not shown.
Binary file not shown.

tests/data_parser/test_paint_calibration_parser.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@
2323
],
2424
)
2525
],
26-
torch.tensor([50.91342112259258, 6.387824755874856, 87.0]),
26+
torch.tensor(
27+
[50.91342112259258, 6.387824755874856, 87.0], dtype=torch.float64
28+
),
2729
1,
2830
paint_mappings.UTIS_KEY,
2931
[
3032
torch.tensor(
3133
[
3234
[
33-
-17.623041152954,
34-
-3.039341926575,
35-
50.708953857422,
35+
-17.639921188354,
36+
-2.744207382202,
37+
50.708946228027,
3638
1.000000000000,
3739
]
3840
]
@@ -57,22 +59,24 @@
5759
],
5860
)
5961
],
60-
torch.tensor([50.91342112259258, 6.387824755874856, 87.0]),
62+
torch.tensor(
63+
[50.91342112259258, 6.387824755874856, 87.0], dtype=torch.float64
64+
),
6165
3,
6266
paint_mappings.HELIOS_KEY,
6367
[
6468
torch.tensor(
6569
[
6670
[
67-
-17.593019485474,
68-
-3.039341926575,
69-
50.708175659180,
71+
-17.614156723022,
72+
-2.744694471359,
73+
50.708179473877,
7074
1.000000000000,
7175
],
7276
[
73-
-17.202730178833,
74-
-3.039341926575,
75-
51.339904785156,
77+
-17.210176467896,
78+
-2.746782302856,
79+
51.339900970459,
7680
1.000000000000,
7781
],
7882
]
@@ -110,7 +114,9 @@
110114
],
111115
)
112116
],
113-
torch.tensor([50.91342112259258, 6.387824755874856, 87.0]),
117+
torch.tensor(
118+
[50.91342112259258, 6.387824755874856, 87.0], dtype=torch.float64
119+
),
114120
2,
115121
"invalid",
116122
[

tests/util/test_utils.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -983,22 +983,32 @@ def test_rotation_angle_and_axis(
983983
torch.tensor([256, 256]),
984984
torch.tensor([0, 0, 1]),
985985
torch.tensor(
986-
[[0.0, 0.0, 0.0, 1.0], [1.5, 0.0, -3.0, 1.0], [2.0, 0.0, 4.0, 1.0]]
986+
[
987+
[0.0000, 0.0000, 0.0000, 1.0000],
988+
[1.4941, 0.0000, -2.9883, 1.0000],
989+
[1.9961, 0.0000, 3.9922, 1.0000],
990+
]
987991
),
988992
),
989993
(
990994
torch.tensor([[127.5, 127.5], [127.5, 255.0], [0.0, 63.75]]),
991995
torch.tensor([256, 256]),
992996
torch.tensor([2, 2, 2]),
993997
torch.tensor(
994-
[[0.0, 2.0, 0.0, 1.0], [0.0, 2.0, -3.0, 1.0], [2.0, 0.0, 1.5, 1.0]]
998+
[
999+
[0.0000, 2.0000, 0.0000, 1.0000],
1000+
[0.0000, 2.0000, -2.9883, 1.0000],
1001+
[2.0000, 0.0123, 1.4941, 1.0000],
1002+
]
9951003
),
9961004
),
9971005
(
9981006
torch.tensor([[255.0, 191.25], [255.0, 255.0]]),
9991007
torch.tensor([256, 256]),
10001008
torch.tensor([2, 0]),
1001-
torch.tensor([[-2.0, 0.0, -1.5, 1.0], [-3.0, 0.0, -3.0, 1.0]]),
1009+
torch.tensor(
1010+
[[-2.0000, 0.0123, -1.4941, 1.0000], [-2.9883, 0.0000, -2.9883, 1.0000]]
1011+
),
10021012
),
10031013
],
10041014
)

0 commit comments

Comments
 (0)