dheera / rosboard

ROS node that turns your robot into a web server to visualize ROS topics
Other
823 stars 161 forks source link

Crash on empty point_cloud2 #85

Open tim-fan opened 2 years ago

tim-fan commented 2 years ago

Hi there, thanks for the great visualisation package!

I'm trying to visualise a point_cloud2 topic which occasionally contains no points (it is the output of a filtering operation, and sometimes all points are removed by the filter).

When rosboard receives the empty message it crashes with the error:

Traceback (most recent call last):
  File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/tmp/colcon_ws/build/rosboard/rosboard/rospy2/__init__.py", line 82, in _thread_spin_target
    rclpy.spin(_node)
  File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/__init__.py", line 191, in spin
    executor.spin_once()
  File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 711, in spin_once
    raise handler.exception()
  File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/task.py", line 239, in __call__
    self._handler.send(None)
  File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 426, in handler                                               await call_coroutine(entity, arg)
  File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 351, in _execute_subscription
    await await_or_execute(sub.callback, msg)                                                                                           File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 118, in await_or_execute
    return callback(*args)
  File "/tmp/colcon_ws/build/rosboard/rosboard/rospy2/__init__.py", line 245, in _ros2_callback                                           self.callback(msg, self.callback_args)
  File "/tmp/colcon_ws/build/rosboard/rosboard/rosboard.py", line 324, in on_ros_msg
    ros_msg_dict = ros2dict(msg)                                                                                                        File "/tmp/colcon_ws/build/rosboard/rosboard/serialization.py", line 66, in ros2dict
    rosboard.compression.compress_point_cloud2(msg, output)
  File "/tmp/colcon_ws/build/rosboard/rosboard/compression.py", line 278, in compress_point_cloud2
    xmax = np.max(xpoints)                                                                                                              File "<__array_function__ internals>", line 5, in amax
  File "/tmp/colcon_ws/venv/lib/python3.8/site-packages/numpy/core/fromnumeric.py", line 2733, in amax
    return _wrapreduction(a, np.maximum, 'max', axis, None, out,                                                                        File "/tmp/colcon_ws/venv/lib/python3.8/site-packages/numpy/core/fromnumeric.py", line 87, in _wrapreduction
    return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
ValueError: zero-size array to reduction operation maximum which has no identity

Looks like the issue is with calling np.max on an empty array.

My workaround was as follows, I confirmed this works for my case:

$ git rev-parse HEAD
7ac609d8ab321cddf098bbf503ec3dee12324f1e
$ git diff
diff --git a/rosboard/compression.py b/rosboard/compression.py
index 3f36590..a6cdbd5 100644
--- a/rosboard/compression.py
+++ b/rosboard/compression.py
@@ -273,6 +273,10 @@ def compress_point_cloud2(msg, output):
         idx = np.random.randint(points.size, size=65536)
         points = points[idx]

+    if points.size == 0:
+        output["_warn"] = "Received empty PointCloud2 - ignoring"
+        return
+
     xpoints = points['x'].astype(np.float32)
     xmax = np.max(xpoints)
     xmin = np.min(xpoints)

If you're OK with that change I can put it in a pull request. Otherwise I'm not sure what the best fix would be,

dheera commented 2 years ago

Hi @tim-fan Thanks for bringing this up!

Your fix looks like it would work in your cause. But I'm thinking that for general users, perhaps a better solution would be to actually send an empty cloud to the client for visualization (instead of throwing an exception of course).

The reason I say this is that in many systems, sporadic empty point clouds could actually be a bug, and one of the purposes of a visualization tool should be to see those blips so you know they are happening. Like for example if it is because one day suddenly my LIDAR is failing, I want to be able to see that problem in a visualization tool.