robonix.service.pcld2lscan

service v0.1.0

PointCloud2 to LaserScan conversion service.

README service-pcld2lscan-rbnx@main

service-pcld2lscan-rbnx

Atlas-managed sensor_msgs/PointCloud2 to sensor_msgs/LaserScan preprocessing service. It lets mapping, localization, navigation, and safety consumers share one canonical scan without making Mapping depend on Navigation.

Processing pipeline

lidar3d PointCloud2
  -> optional motion deskew
  -> latest-first queue and input-rate limit
  -> one-pass TF + height/range + robot self-filter + angular projection
  -> in-process isolated-return filter
  -> LaserScan

The projection implementation deliberately fuses TF application, robot self-filtering, and projection into one C++ traversal. The upstream ROS node normally transforms/copies the complete cloud and then traverses it again. The service also defaults the input queue to one: old obstacle observations are dropped instead of being processed after newer clouds have arrived.

Example

- name: pcld2lscan
  path: /home/jetson/cxk/service-pcld2lscan-rbnx
  config:
    scan_frame: base_link
    scan_rate_hz: 6.0
    queue_size: 1
    max_scan_age_s: 0.5
    transform_tolerance_s: 0.05
    min_height_m: 0.05
    max_height_m: 1.0
    range_min_m: 0.03
    range_max_m: 20.0
    angle_increment_rad: 0.0087

    # Circumscribed radius of Lite3's 0.305 x 0.185 m rectangular footprint.
    self_filter_mode: circle
    self_filter_footprint:
      - [0.305, 0.185]
      - [0.305, -0.185]
      - [-0.305, -0.185]
      - [-0.305, 0.185]
    self_filter_margin_m: 0.0

    speckle_filter_enabled: true
    speckle_window_bins: 2
    speckle_max_range_delta_m: 0.20
    speckle_min_neighbors: 2

    # Enable only if the cloud contains usable per-point timing and odom TF is
    # available across the complete cloud interval.
    deskewing: false
    deskew_fixed_frame: odom
    deskew_wait_for_transform_s: 0.2

scan_rate_hz is a maximum processing/output rate, not fabricated data rate. Frames above this rate are discarded before the expensive TF and projection work. max_scan_age_s: 0 disables the age guard; physical navigation should set a finite limit. scan_time in the resulting message is derived from scan_rate_hz.

Self-filter modes are:

  • none: preserve the legacy behavior.
  • circle: compute the circumscribed radius from self_filter_footprint, or use an explicit self_filter_radius_m, then add the configured margin.
  • rectangle: remove returns inside the axis-aligned bounds of self_filter_footprint plus the margin. Coordinates are evaluated in scan_frame, so this should normally be the robot base frame.

The speckle filter matches Navigation's range-neighborhood policy but runs in the projector process, avoiding a second LaserScan serialization and Python node. It removes a finite beam when fewer than speckle_min_neighbors nearby angular bins have a range within speckle_max_range_delta_m.

The projector writes a throttled diagnostic line to rbnx-build/data/pcld2lscan.log, including cloud age, processing time, point counts, self-filtered returns, removed speckles, and dropped-frame counters.

Build and test

bash scripts/build.sh
python3 -m unittest discover -s tests -v