Skip to content

Commit 37585be

Browse files
authored
Merge pull request #135 from leggedrobotics/rotation_fix
Fix 90° CCW rotation in masked_replace, load and save
2 parents d101475 + 7139528 commit 37585be

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

elevation_mapping_cupy/elevation_mapping_cupy/elevation_mapping.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -875,15 +875,60 @@ def get_map_with_name_ref(self, name, data):
875875
# Old 180° rotation (incorrect - missing transpose, caused 90° CCW error in RViz):
876876
# m = xp.flip(m, 0)
877877
# m = xp.flip(m, 1)
878-
m = m.T
879-
m = xp.flip(m, 0)
880-
m = xp.flip(m, 1)
878+
m = self._transform_to_grid_map_coordinate_convention(m)
881879
if use_stream:
882880
stream = cp.cuda.Stream(non_blocking=False)
883881
else:
884882
stream = None
885883
self.copy_to_cpu(m, data, stream=stream)
886884

885+
def _transform_to_grid_map_coordinate_convention(self, m):
886+
"""Transform the map to the grid_map coordinate convention.
887+
888+
elevation_mapping_cupy uses Row=Y, Col=X (see kernels/custom_kernels.py:35)
889+
grid_map uses Row→-X, Col→-Y (see grid_map_core/src/GridMapMath.cpp:64-67
890+
transformBufferOrderToMapFrame returns {-index[0], -index[1]})
891+
Required transformation:
892+
1. Transpose: swap axes so Row=X, Col=Y (matching grid_map's axis assignment)
893+
2. Flip axis 0: so increasing row → decreasing X (matching grid_map's -X)
894+
3. Flip axis 1: so increasing col → decreasing Y (matching grid_map's -Y)
895+
896+
This is equivalent to: rot90(m.T, k=2) or flip(flip(m.T, 0), 1)
897+
898+
Args:
899+
m (cupy._core.core.ndarray):
900+
901+
Returns:
902+
cupy._core.core.ndarray:
903+
"""
904+
m = m.T
905+
m = xp.flip(m, 0)
906+
m = xp.flip(m, 1)
907+
return m
908+
909+
def _transform_to_elevation_mapping_coordinate_convention(self, m):
910+
"""Transform the map to the grid_map coordinate convention.
911+
912+
elevation_mapping_cupy uses Row=Y, Col=X (see kernels/custom_kernels.py:35)
913+
grid_map uses Row→-X, Col→-Y (see grid_map_core/src/GridMapMath.cpp:64-67
914+
transformBufferOrderToMapFrame returns {-index[0], -index[1]})
915+
To transform back to a normal array, we need to apply the inverse transformation:
916+
Flip axis 0: so increasing row → decreasing X (matching grid_map's -X)
917+
Flip axis 1: so increasing col → decreasing Y (matching grid_map's -Y)
918+
Transpose: swap axes so Row=X, Col=Y (matching grid_map's axis assignment)
919+
This is equivalent to: flip(flip(m, 0), 1).T
920+
921+
Args:
922+
m (cupy._core.core.ndarray):
923+
924+
Returns:
925+
cupy._core.core.ndarray:
926+
"""
927+
m = xp.flip(m, 0)
928+
m = xp.flip(m, 1)
929+
m = m.T
930+
return m
931+
887932
def get_normal_maps(self):
888933
"""Get the normal maps.
889934
@@ -1062,6 +1107,12 @@ def apply_masked_replace(
10621107
if not layer_data:
10631108
raise ValueError("No layer data provided for masked replace.")
10641109

1110+
# Transform the layer data from grid_map coordinate convention to the elevation_mapping_cupy coordinate convention
1111+
for name, array in layer_data.items():
1112+
layer_data[name] = self._transform_to_elevation_mapping_coordinate_convention(array)
1113+
if mask is not None:
1114+
mask = self._transform_to_elevation_mapping_coordinate_convention(mask)
1115+
10651116
sample_shape: Optional[Tuple[int, int]] = None
10661117
for array in layer_data.values():
10671118
if sample_shape is None:
@@ -1134,6 +1185,11 @@ def set_full_map(
11341185
) -> None:
11351186
if not raw_layers:
11361187
raise ValueError("Raw layer data required to restore the map.")
1188+
1189+
# Transform the raw layer data from grid_map coordinate convention to the elevation_mapping_cupy coordinate convention
1190+
for name, array in raw_layers.items():
1191+
raw_layers[name] = self._transform_to_elevation_mapping_coordinate_convention(array)
1192+
11371193
sample_shape = next(iter(raw_layers.values())).shape
11381194
self._validate_geometry_against_shape(sample_shape, geometry)
11391195

elevation_mapping_cupy/scripts/elevation_mapping_node.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,6 @@ def handle_load_map(self, request, response):
442442

443443
fused_layers, _ = self._grid_map_to_numpy(fused_msg)
444444
raw_layers, geometry = self._grid_map_to_numpy(raw_msg)
445-
raw_layers = self._restore_internal_layer_orientation(raw_layers)
446445

447446
self._map.set_full_map(fused_layers, raw_layers, geometry)
448447

@@ -499,17 +498,6 @@ def _grid_map_to_numpy(self, grid_map_msg: GridMap):
499498
)
500499
return arrays, geometry
501500

502-
def _restore_internal_layer_orientation(self, layers: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
503-
"""Undo the double-axis flip applied when exporting layers for GridMap messages."""
504-
restored: Dict[str, np.ndarray] = {}
505-
for name, array in layers.items():
506-
if array.ndim >= 2:
507-
flipped = np.flip(np.flip(array, axis=0), axis=1)
508-
restored[name] = np.ascontiguousarray(flipped)
509-
else:
510-
restored[name] = np.ascontiguousarray(array)
511-
return restored
512-
513501
def _extract_layout_shape(self, array_msg: Float32MultiArray) -> tuple:
514502
if array_msg.layout.dim:
515503
cols = array_msg.layout.dim[0].size or 1

scripts/masked_replace_tool.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def _numpy_to_multiarray(array: np.ndarray) -> Float32MultiArray:
297297
msg = Float32MultiArray()
298298
layout = MultiArrayLayout()
299299
rows, cols = array.shape
300+
# numpy
300301
layout.dim.append(MultiArrayDimension(label="column_index", size=cols, stride=rows * cols))
301302
layout.dim.append(MultiArrayDimension(label="row_index", size=rows, stride=rows))
302303
msg.layout = layout

0 commit comments

Comments
 (0)