Skip to content

feat: add Patchwork (classic) algorithm alongside Patchwork++#79

Merged
LimHyungTae merged 21 commits into
masterfrom
feat/patchwork-classic
May 9, 2026
Merged

feat: add Patchwork (classic) algorithm alongside Patchwork++#79
LimHyungTae merged 21 commits into
masterfrom
feat/patchwork-classic

Conversation

@LimHyungTae
Copy link
Copy Markdown
Member

Summary

Adds the original Patchwork ground segmentation algorithm as a parallel library inside this repo so users can A/B compare against Patchwork++ from the same Python module and ROS2 node.

The maintainer suspected ground-plane noise removal is more aggressive in upstream Patchwork (z-cutoff at sensor_height + 2 m, reject-too-small-patches policy). Rather than try to merge two genuinely different algorithms, we expose both behind a runtime switch and let users pick what fits their data.

What you get

  • New static library patchworkpp::ground_seg_classic (cpp/patchwork/)
  • New Python class pypatchworkpp.patchwork(PatchworkParams) with the same I/O signature as the existing pypatchworkpp.patchworkpp
  • ROS2 launch arg: ros2 launch patchworkpp patchworkpp.launch.py algorithm:=patchwork
  • Smoke tests covering both algorithms against data/000000.bin
  • README "Choosing an algorithm" subsection
  • Version bump 1.0.4 → 1.1.0

Deliberate deviations from a literal port

The upstream /Users/fudxo/git/patchwork is ROS-coupled (PCL, TBB, Boost.Format, rclcpp). We strip all of that and adapt the I/O surface:

  1. CZM uses Patchwork++'s parametric form (num_zones, num_sectors_each_zone, num_rings_each_zone, min_ranges) instead of the upstream sensor-string-driven CZM. No sensor_configs.hpp / zone_models.hpp.
  2. tbb::parallel_for over patches becomes a sequential for loop. Patchwork++ is also sequential and fast enough for 100 k-point scans.
  3. pcl::PointCloud<PointXYZI> becomes std::vector<patchwork::PointXYZ>. The PointXYZ POD is reused from cpp/patchworkpp/include/patchwork/patchworkpp.h.
  4. boost::format + RCLCPP_INFO becomes std::cout guarded by params_.verbose.

The semantics of the algorithm (seed selection, plane fit, GLE classifier, ATAT) are byte-for-byte logically equivalent.

Bit of design context

Spec: docs/superpowers/specs/2026-05-09-add-patchwork-classic-algorithm-design.md
Plan: docs/superpowers/plans/2026-05-09-patchwork-classic.md

Plan was decomposed into 19 commits across 7 phases (skeleton → algorithm port → Python bindings → ROS wiring → docs/version). Each commit independently builds and keeps the smoke test green.

Test plan

  • Local cpp smoke test passes (patchwork_smoke)
  • Local pytest passes — 4 tests: 2 patchwork++ + 2 patchwork classic, both running on real KITTI data (data/000000.bin) producing non-empty ground/nonground partitions
  • ROS Humble Docker build clean (verified during E1)
  • CI matrix (Ubuntu 22/24, Windows 2022, macOS 14, ROS humble/jazzy)
  • Verify ground-plane noise behavior subjectively on user's own data after merge

Out of scope

  • PyPI release (cut a GitHub Release after merge to publish 1.1.0 wheels)
  • Cross-algorithm benchmark suite
  • Updating cpp/example_of_find_package/ for the classic algorithm

LimHyungTae added 21 commits May 9, 2026 10:59
Documents the planned addition of the original Patchwork algorithm
as a parallel library target alongside Patchwork++, exposed through
the same Python module and ROS2 node via a runtime switch. Captures
the API surface, the strategy for stripping PCL/TBB/ROS dependencies
from the upstream code, testing approach, and out-of-scope items.
Decomposes the spec into 7 phases (skeleton, C++ verify, algorithm
port, Python bindings, ROS wiring, docs/version, PR) with bite-sized
tasks per phase. Each task ends in a commit. Documents the deliberate
deviations from a literal upstream port (parametric CZM, sequential
patch processing, Eigen-only types).
Replace stub header with full PCAFeature, PatchStatus, PatchworkParams
(CZM, plane-fit, GLE thresholds, ATAT) and PatchWork private state
(regionwise_patches_, lazy materialize pattern). Link ground_seg_cores
so the classic target can include patchworkpp.h for PointXYZ reuse.
Also fixes stub patchwork.cpp to use renamed ground_mat_/nonground_mat_.
…lize

Implement estimateGround (CZM init on first call, point conversion,
pre-filter, ATAT stub, flush+redistribute, per-patch segmentation loop,
lazy outputs_dirty flag), materialize() with to_matrix helper, and
rewritten getters. Add no-op stubs for consensus_set_based_height_estimation
and estimate_sensor_height (filled in C9). Add <chrono> include.
Implement consensus_set_based_height_estimation and estimate_sensor_height
(Task C9). Uses per-sector lowest-z bucketing in a 5 m near-field window,
filters candidates within max_h_for_ATAT of the current sensor_height_,
and selects the largest noise_bound-coherent cluster via pairwise counting.
Replace the single PatchWorkpp unique_ptr with an ImplVariant (std::variant)
and branch on the 'algorithm' ROS parameter at node startup. Adds
loadPlusplusParamsFromROS() and loadClassicParamsFromROS() helpers so only
the selected algorithm's parameters are declared. All call-sites use
std::visit for uniform dispatch. Links ground_seg_classic into gseg_component
so patchwork/patchwork.h is visible; verified clean build on ROS Humble in Docker.
@LimHyungTae LimHyungTae merged commit 099a413 into master May 9, 2026
18 checks passed
@LimHyungTae LimHyungTae deleted the feat/patchwork-classic branch May 9, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant