Skip to content

RingOutlierFilterComponent never sets row_step on its output PointCloud2 (always 0) #13413

Description

@JArmandoAnaya

Checklist

  • I've read the contribution guidelines.
  • I've searched other issues and no duplicate issues were found.
  • I'm convinced that this is not my fault but a bug.

Description

RingOutlierFilterComponent::set_up_pointcloud_format() in autoware_pointcloud_preprocessor never sets PointCloud2::row_step on the output cloud it constructs. Every field except row_step is set (header.frame_id, height, width, is_bigendian, is_dense, fields), so the output message is published with row_step == 0 (its default-constructed value) regardless of input.

This node is the last stage of the standard lidar preprocessing pipeline (crop_box_filter_self → crop_box_filter_mirror → distortion_corrector_node → ring_outlier_filter → e.g. pointcloud_before_sync), so every downstream consumer of its output receives a cloud with an invalid row_step. The recently-added generic PointCloud2 validation in the shared Filter base class (#11853, tracked under #11858) correctly flags this on every downstream node that happens to validate its input, but the validation warning's own text says this becomes a hard error starting July 2026 — at which point every pipeline downstream of ring_outlier_filter will start rejecting clouds outright.

#11855 already fixed the equivalent row_step/width recalculation for the concatenation node (autoware::pointcloud_preprocessor::PointCloudConcatenationComponent), but that fix only covers clouds that pass through multi-source concatenation. A single-lidar setup (common in simulation integrations, e.g. one lidar wired straight through a topic_tools::RelayNode instead of the concatenation node) never touches that fix, so ring_outlier_filter's own bug reaches consumers unmodified.

Expected behavior

ring_outlier_filter's output PointCloud2 should have row_step == width * point_step (for the unorganized, height == 1 clouds this node always produces), matching the invariant the new validator in filter.hpp enforces.

Actual behavior

row_step is always 0 on ring_outlier_filter's output, for every input. Downstream nodes (e.g. crop_box_filter instances in both perception's obstacle-segmentation pipeline and localization's NDT preprocessing pipeline) log, once per received message:

Invalid PointCloud: row_step mismatch. Expected: <width * point_step> (width <W> * point_step <P>), Got: 0. Frame: 'base_link', Stamp: <t>. Please fill in the `cloud->row_step` field accordingly. This will be an ERROR starting in 2026 July

Steps to reproduce

  1. Launch any lidar pipeline that routes through autoware::pointcloud_preprocessor::RingOutlierFilterComponent (the standard common_sensor_launch/nebula_node_container.launch.py chain used by, e.g., awsim_sensor_kit_launch/launch/lidar.launch.xml).
  2. Use a single-lidar configuration, where ring_outlier_filter's output feeds a downstream consumer directly (or via a plain relay) rather than through PointCloudConcatenationComponent (whose row_step recalculation from fix(autoware_pointcloud_preprocessor): recalculate row_step and width after concatenation #11855 would otherwise mask this).
  3. Subscribe to any node downstream of ring_outlier_filter that validates incoming PointCloud2 messages via the shared Filter base class (e.g. any crop_box_filter instance).
  4. Observe the row_step mismatch ... Got: 0 warning on every message, indefinitely.

Reproduced against ghcr.io/autowarefoundation/autoware:universe-devel-cuda-jazzy in a CARLA-based single-lidar simulation integration; traced to source against autoware_universe@cdc0792965be82577a310eb363bf877c5c2d070e (current main), so the bug is present at main as of this report, not specific to that image build.

Versions

  • Autoware: autoware_universe @ cdc0792965be82577a310eb363bf877c5c2d070e (main)
  • Docker image tested: ghcr.io/autowarefoundation/autoware:universe-devel-cuda-jazzy
  • ROS 2: Jazzy

Possible causes

sensing/autoware_pointcloud_preprocessor/src/outlier_filter/ring_outlier_filter_node.cpp, RingOutlierFilterComponent::set_up_pointcloud_format() (currently lines 381-400):

void RingOutlierFilterComponent::set_up_pointcloud_format(
  const PointCloud2ConstPtr & input, PointCloud2 & formatted_points, size_t points_size)
{
  formatted_points.data.resize(points_size);
  formatted_points.header.frame_id =
    !tf_input_frame_.empty() ? tf_input_frame_ : tf_input_orig_frame_;
  formatted_points.height = 1;
  formatted_points.width =
    static_cast<uint32_t>(formatted_points.data.size() / formatted_points.point_step);
  formatted_points.is_bigendian = input->is_bigendian;
  formatted_points.is_dense = input->is_dense;
  // row_step is never assigned here — stays at its default-constructed 0.
  ...
}

Suggested fix, right after width is computed:

  formatted_points.row_step = formatted_points.width * formatted_points.point_step;

(height is always 1 here, so row_step and data.size() coincide; the simpler equivalent formatted_points.row_step = static_cast<uint32_t>(formatted_points.data.size()); also works and matches the pattern already used in CropBoxFilterComponent's own output construction.)

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions