Skip to content

Commit e311bfd

Browse files
committed
Add support for Livox point cloud 'xyz' field format
- Handle both dict and structured array formats from ros2_numpy - Support Livox MID-360's combined 'xyz' field instead of separate x,y,z - Add robust error handling for various point cloud structures - Prevent KeyError when processing Livox lidar data
1 parent f3b0a39 commit e311bfd

File tree

1 file changed

+72
-11
lines changed

1 file changed

+72
-11
lines changed

elevation_mapping_cupy/scripts/elevation_mapping_node.py

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,36 @@ def pointcloud_callback(self, msg: PointCloud2, sub_key: str) -> None:
365365
channels = ["x", "y", "z"] + additional_channels
366366
try:
367367
points = rnp.numpify(msg)
368-
except:
368+
except Exception as e:
369+
self.get_logger().warn(f"Failed to numpify point cloud: {e}")
369370
return
370-
if points['x'].size == 0:
371+
372+
# Check if points is empty - handle both structured array and dict cases
373+
if points is None:
371374
return
375+
376+
# Handle different data structures from rnp.numpify
377+
if isinstance(points, dict):
378+
# If it's a dict, check if it has data
379+
if not points or (len(points) == 0):
380+
return
381+
# Check for x,y,z keys or xyz field in dict
382+
if 'xyz' in points:
383+
# Handle combined xyz field (common in Livox lidars)
384+
xyz_data = points['xyz']
385+
if len(xyz_data) == 0:
386+
return
387+
elif 'x' in points:
388+
# Handle separate x,y,z fields
389+
if len(points['x']) == 0:
390+
return
391+
else:
392+
self.get_logger().warn(f"Point cloud dict missing 'x' or 'xyz' field. Available fields: {list(points.keys())}")
393+
return
394+
else:
395+
# It's a structured numpy array
396+
if points.size == 0:
397+
return
372398
frame_sensor_id = msg.header.frame_id
373399
transform_sensor_to_map = self.safe_lookup_transform(
374400
self.map_frame,
@@ -379,15 +405,50 @@ def pointcloud_callback(self, msg: PointCloud2, sub_key: str) -> None:
379405
q = transform_sensor_to_map.transform.rotation
380406
t_np = np.array([t.x, t.y, t.z], dtype=np.float32)
381407
R = quaternion_matrix([q.x, q.y, q.z, q.w])[:3, :3].astype(np.float32)
382-
pts = rnp.point_cloud2.get_xyz_points(points)
383-
# TODO: This is probably expensive. Consider modifying rnp or input_pointcloud()
384-
# Append additional channels to pts
385-
for channel in additional_channels:
386-
if channel in points.dtype.names:
387-
data = points[channel].flatten()
388-
if data.ndim == 1:
389-
data = data[:, np.newaxis]
390-
pts = np.hstack((pts, data))
408+
409+
# Extract xyz points based on data structure
410+
if isinstance(points, dict):
411+
# If points is a dict, manually construct xyz array
412+
if 'xyz' in points:
413+
# Handle combined xyz field (common in Livox lidars)
414+
xyz_array = np.array(points['xyz'])
415+
if xyz_array.ndim == 2 and xyz_array.shape[1] == 3:
416+
pts = xyz_array
417+
elif xyz_array.ndim == 1:
418+
# Reshape if flattened
419+
pts = xyz_array.reshape(-1, 3)
420+
else:
421+
# Try to extract x, y, z from the structure
422+
pts = xyz_array[:, :3] if xyz_array.shape[1] >= 3 else xyz_array
423+
elif 'x' in points and 'y' in points and 'z' in points:
424+
# Handle separate x,y,z fields
425+
x = np.array(points['x']).flatten()
426+
y = np.array(points['y']).flatten()
427+
z = np.array(points['z']).flatten()
428+
pts = np.column_stack((x, y, z))
429+
else:
430+
# Fallback: try to use any available method
431+
self.get_logger().warn(f"Unexpected point cloud structure, attempting fallback")
432+
return
433+
434+
# Append additional channels
435+
for channel in additional_channels:
436+
if channel in points:
437+
data = np.array(points[channel]).flatten()
438+
if data.ndim == 1:
439+
data = data[:, np.newaxis]
440+
pts = np.hstack((pts, data))
441+
else:
442+
# Use standard method for structured arrays
443+
pts = rnp.point_cloud2.get_xyz_points(points)
444+
# TODO: This is probably expensive. Consider modifying rnp or input_pointcloud()
445+
# Append additional channels to pts
446+
for channel in additional_channels:
447+
if hasattr(points, 'dtype') and hasattr(points.dtype, 'names') and channel in points.dtype.names:
448+
data = points[channel].flatten()
449+
if data.ndim == 1:
450+
data = data[:, np.newaxis]
451+
pts = np.hstack((pts, data))
391452
self._map.input_pointcloud(pts, channels, R, t_np, 0, 0)
392453
self._pointcloud_process_counter += 1
393454

0 commit comments

Comments
 (0)