Skip to content

refactor(autoware_pointcloud_preprocessor): make the concatenation core ROS-runtime-free - #12873

Merged
vividf merged 54 commits into
autowarefoundation:mainfrom
vividf:feat/concat_ros_free_core
Oct 15, 2026
Merged

vividf merged 54 commits into
autowarefoundation:mainfrom
vividf:feat/concat_ros_free_core

Conversation

@vividf

@vividf vividf commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Description

Make the point cloud concatenation core (CombineCloudHandler, CombineCloudHandlerBase,
the concatenation diagnostics) independent of the ROS runtime, so it can be driven from a
non-node context (the offline/Python pipeline in #13113 / #12874) and unit-tested without
spinning a node. The node keeps exactly the same topics, parameters and outputs.

This PR is the merged result of a 4-part stack that was reviewed step by step on the fork
(links under Related links); each step is still a separate commit here.

  1. Type the matching strategy — MatchingStrategyType enum pinned to the
    ConcatenatedPointCloudInfo STRATEGY_* constants; the string is parsed once, at the
    parameter boundary (parse_matching_strategy()); matching_strategy_name_map is gone.
  2. Key the handler on the message type — CombineCloudHandler<sensor_msgs::msg::PointCloud2>
    / CombineCloudHandler<cuda_blackboard::CudaPointCloud2> instead of the *Traits tag types.
  3. Drop pcl and rclcpp time from the core — stamps are builtin_interfaces::msg::Time
    with a small time_utils.hpp (is_earlier, add, subtract, to_seconds);
    pcl_ros::transformPointCloud / pcl::concatenatePointCloud are replaced by an Eigen
    transform loop and a byte append on the shared XYZIRC layout.
  4. Inject transforms instead of holding a node — the handler no longer owns a
    ManagedTransformBuffer; the node looks up sensor→output extrinsics and calls
    set_transform(). A source whose frame has no transform is dropped and reported
    (dropped_sources_missing_transform, STATUS_INVALID) instead of being placed with an
    identity transform. Motion-compensation warnings became a MotionCompensationStatus the
    node logs.
  5. Build the diagnostics outside the node — build_diagnostic_status() in
    concatenation_diagnostics.{hpp,cpp} produces the DiagnosticStatus from a plain summary
    struct; the node only feeds it and publishes. A source without a transform now counts as
    not concatenated in the diagnostics, consistent with ConcatenatedPointCloudInfo.

A new concatenate_core library target contains only the ROS-runtime-free sources and links
message targets only, to keep it that way.

Related links

How was this PR tested?

  • colcon build/test --packages-select autoware_pointcloud_preprocessor autoware_cuda_pointcloud_preprocessor
    — 519 tests, 0 failures (incl. lint).
    • test_concatenation_info: typed strategy.
    • test_concatenation_diagnostics (new, 11 cases): key order, level/message rules, missing
      topic, missing transform, late drop, advanced window.
    • test_cuda_combine_cloud_handler (new, 2 cases, runs on the GPU and self-skips without one):
      concatenation on device, drop of a source without an extrinsic.
    • test_concatenate_node_unit: transforms injected via set_transform() instead of a TF broadcaster.
  • On-hardware A/B against main (same base commit, x86 + RTX, 3 lidars, motion compensation on,
    ~120 frames): output clouds equivalent (max |Δxyz| 23 µm, float32 rounding; identity-rotation
    sensor byte-identical); ConcatenatedPointCloudInfo identical; /diagnostics level identical on
    every frame; processing time −8 % median.

Notes for reviewers

  • Review commit by commit if the whole diff is too much; the five steps above map to the commits.
  • Unknown matching_strategy.type now throws std::invalid_argument from parse_matching_strategy()
    instead of the node's std::runtime_error. Both abort startup.
  • transform_pointcloud() applies Matrix4f * Vector4f where pcl used linear()*v + translation();
    results differ only by float32 rounding (≤ 1–8 ULP, zero-mean).

Interface changes

None. No topic, parameter or message changes. Internal C++ API of the handler changed
(constructor no longer takes a rclcpp::Node; set_transform() added).

Effects on system behavior

  • Nominal operation: none observable (verified as above).
  • Missing TF for a source: previously the source was concatenated with an identity transform
    and source_info.status stayed OK. Now its points are dropped, source_info.status = INVALID,
    concatenation_success = false, and /diagnostics reports ERROR with
    Concatenated: <topic> = False plus a new key Frames without a transform to the output frame.
  • One diagnostics message string changed: "dropped due to missing topics and its timestamp is
    earlier" → "... and because its timestamp is earlier". Exact-match monitoring rules need
    updating.
  • Diagnostics timestamps are rendered from sec + nanosec*1e-9 (single rounding) instead of
    rclcpp::Time::seconds(); values may differ in the last digits.
  • Misconfigured matching_strategy.type: different exception type/message, still aborts startup.

@github-actions

Copy link
Copy Markdown

Thank you for contributing to the Autoware project!

🚧 If your pull request is in progress, switch it to draft mode.

Please ensure:

@vividf
vividf requested a review from jacoblambert June 25, 2026 02:42
@vividf vividf self-assigned this Jun 25, 2026
@vividf
vividf force-pushed the feat/concat_ros_free_core branch 3 times, most recently from 953a3ed to 4e65b3e Compare July 8, 2026 07:19
@vividf
vividf force-pushed the feat/concat_ros_free_core branch 2 times, most recently from 35736d8 to 3821246 Compare July 28, 2026 09:45
@vividf
vividf marked this pull request as ready for review July 28, 2026 10:22
@vividf
vividf marked this pull request as draft July 29, 2026 05:10
… of passing strings

Introduce MatchingStrategyType, an enum whose enumerator values are pinned to
the ConcatenatedPointCloudInfo STRATEGY_* constants (the wire format is the
source of truth), and parse the strategy name exactly once per string
boundary -- for the node, its "matching_strategy.type" parameter.

- ConcatenationInfoManager takes MatchingStrategyType, so recording the
  strategy into the info message becomes a static_cast: the string-to-constant
  lookup table (matching_strategy_name_map) is gone.
- The unknown-strategy error now comes from parse_matching_strategy(), the
  only place that knows the names.

No behavioral change.

Signed-off-by: vividf <yihsiang.fang@tier4.jp>
sasakisasaki and others added 5 commits October 2, 2026 14:20
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
@github-actions github-actions Bot added the type:documentation Creating or refining documentation. (auto-assigned) label Oct 2, 2026
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>

@sasakisasaki sasakisasaki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick Comment

I agree with merging this PR as the reasons below 👍

Reasons for Approval

  • Characterization test passed: this indicates no regression for both core logic and ROS node logic
  • I could tested ROS node free concatenation by using this example

I believe this PR certainly separates core logic from ROS node 🎉

CC: @vividf @mojomex

@drwnz drwnz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. Some changes suggested. I did not test CUDA or Agnocast as both aren't working in my environment right now.

@sasakisasaki

sasakisasaki commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

@drwnz Thank you for your review! That are make sense. Let me apply the fix based on your comments, respectively 👍

  Commit:
    - autowarefoundation@95162b3
  PR Review comment:
    - autowarefoundation#12873 (comment)

Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
  - Apply the following review comment:
      - autowarefoundation#12873 (comment)

Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
  - Apply the following review comment:
      - autowarefoundation#12873 (comment)

Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
  - Apply the following review comment:
      - autowarefoundation#12873 (comment)

Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
Signed-off-by: Junya Sasaki <junya.sasaki@tier4.jp>
@sasakisasaki

Copy link
Copy Markdown
Contributor

Now let me do double check if all the review comment is discussed tomorrow again.

@sasakisasaki

Copy link
Copy Markdown
Contributor

@drwnz I applied fixes 👍

Let's wait review by @mojomex and @vividf 👍

sasakisasaki and others added 4 commits October 13, 2026 19:14
…t the cloud stamp

The sensor-to-output transform was looked up at this->now() rather than at
the time the point cloud was captured. On main the CPU combine handler used
the cloud stamp, while the CUDA handler used node_.now(); unifying both
paths in the node callback carried over the latter.

With static extrinsics ManagedTransformBuffer ignores the time, so nothing
changes there. With a dynamic link in the chain, now() asks for a transform
in the future and blocks the callback until one arrives or the 1 s timeout
expires. Both the CPU and the CUDA paths now use the cloud stamp.

Signed-off-by: vividf <yihsiang.fang@tier4.jp>
…ged when transforming

transform_pointcloud() replaced pcl_ros::transformPointCloud() to keep the
concatenation core free of the ROS runtime. Unlike pcl_ros it multiplied every
point by the matrix, so a point with a single non-finite coordinate, e.g.
(NaN, 1, 2), came out as (NaN, NaN, NaN), even under an identity transform.
Skip such points so they are copied through unchanged, as pcl_ros does, and
document the FLOAT32 x/y/z precondition. Finite points are unaffected. The
CUDA kernel, which already transformed every point on main, is unchanged.

Add a unit test that sends NaN and Inf points through the sensor-to-output
transform, motion compensation and the transform back to the sensor frame.

Signed-off-by: vividf <yihsiang.fang@tier4.jp>
@vividf
vividf requested a review from drwnz October 15, 2026 11:27
@vividf

vividf commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@drwnz @mojomex @sasakisasaki
Thanks for the reviews, and @sasakisasaki for implementing the fixes!
@drwnz, could you take another look when you have time :)

@drwnz drwnz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, LGTM!

@vividf
vividf merged commit e82aded into autowarefoundation:main Oct 15, 2026
29 of 30 checks passed
@github-project-automation github-project-automation Bot moved this from To Triage to Done in Software Working Group Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component:sensing Data acquisition from sensors, drivers, preprocessing. (auto-assigned) run:build-and-test-differential Mark to enable build-and-test-differential workflow. (used-by-ci) type:documentation Creating or refining documentation. (auto-assigned)

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants