# Patchwork++: ground segmentation for 3D LiDAR scans

> Patchwork++ removes the ground plane from 3D LiDAR point clouds with self-tuning thresholds, and the KAIST repository ships a C++ library, a pybind11 Python package, a ROS2 node and a SemanticKITTI evaluation harness.

**url-kaist/patchwork-plusplus** — Patchwork++: Fast and robust ground segmentation method for 3D LiDAR scans. @ IROS'22

- Repository: https://github.com/url-kaist/patchwork-plusplus
- Stars: 1,118 · Forks: 161
- Language: C++
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/url-kaist-patchwork-plusplus

## What Patchwork++ removes from a LiDAR scan

A 3D LiDAR scan arrives as a cloud of points, and the ground is the part you almost always want to discard first. It is the largest, flattest structure in the scene, it dominates the point count, and every detection or mapping module downstream works better without it. Patchwork++ is a ground segmentation algorithm that operates on the raw cloud, described by its authors as an extension of Patchwork, presented at IROS 2022 and documented in a paper available on arXiv.

The repository at url-kaist/patchwork-plusplus is the reference implementation, written in C++ under a BSD-2-Clause licence. The audience is a robotics or self driving team that needs the ground removed at sensor rate, and the code is structured for that: the core is a C++ library, the Python binding uses pybind11 so the same implementation can be called from a Python perception stack, and a ROS2 package drops it into a running robot graph.

The algorithm's selling point is that it does not need a tuned height. The default variant estimates its thresholds from the data, which matters when the sensor height is unknown or when the vehicle is on a slope, two situations where a fixed ground plane assumption fails outright.

## Installing the Python package or building the C++ library

The simplest route is PyPI, where the package is published as pypatchworkpp. A core install pulls in the library, and the demo extra adds Open3D for the visualisation examples:

```bash
pip install pypatchworkpp                # core library
pip install 'pypatchworkpp[demo]'        # + Open3D for the visual demos
```

After that, the import name is pypatchworkpp. On Linux the system dependencies are modest, and Eigen is the one that matters because the geometry code depends on it:

```bash
sudo apt-get install g++ build-essential libeigen3-dev python3-pip python3-dev cmake -y
```

On macOS the README notes that Eigen is fetched by CMake, so no extra system package is needed and the build works with the bundled AppleClang toolchain. The only extra command there is cmake itself:

```bash
brew install cmake
```

Contributors and users tracking unreleased work can build the binding from the checkout instead, using the Makefile targets, which run pip against the local python/ directory:

```bash
make pyinstall                # equivalent to `pip install ./python/`
make pyinstall_with_demo      # also installs Open3D >= 0.17.0
```

The C++ side uses the same Makefile. cppinstall configures the CMake project in cpp/build and compiles it, and the demo variant turns on the bundled C++ examples and adds Open3D, which the README pins above 0.17.0:

```bash
make cppinstall
```

The parallel job count is derived from the machine, using hw.ncpu on Darwin and nproc elsewhere, so a build on a large machine will use all cores without configuration.

## Choosing between Patchwork++ and the classic Patchwork

Since version 1.1.0 the repository ships two algorithms behind the same input and output API, which is unusual for a research code release and useful in practice. The choice is not a quality judgement, the README frames it as a question of what your data looks like.

Patchwork++ is the default. It uses adaptive elevation and flatness thresholds, and adds three named components: RNR for intensity based reflected noise removal, RVPF for vertical structure suppression, and TGR, a probability based ground revert step. The README points at sensors with reflection artefacts, and at anyone who wants thresholds that tune themselves rather than being set once by hand.

Classic Patchwork uses fixed elevation and flatness thresholds, applies an explicit cutoff at z < -sensor_height - 2.0m, rejects regions with too few points, and offers optional ATAT handling for unknown sensor heights. It is described as more aggressive on ground plane noise in cluttered scenes. The difference shows up in code as two constructors:

```python
import pypatchworkpp as p

pp_default = p.patchworkpp(p.Parameters())          # Patchwork++
pp_classic = p.patchwork(p.PatchworkParams())       # Patchwork (classic)
```

Because the two share an API, switching is a constructor change rather than a rewrite, and the same choice can be made at runtime in the ROS2 node.

## The ROS2 node and its algorithm switch

The ros/ directory contains a ROS2 package, and the README states that no extra dependency is required, only a clone and a build:

```bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/url-kaist/patchwork-plusplus.git
cd ~/ros2_ws
colcon build --packages-select patchworkpp
source ./install/setup.bash
```

The package builds under ROS2 Humble on Ubuntu, and the node is launched with a launch file:

```bash
# Default — runs Patchwork++
ros2 launch patchworkpp patchworkpp.launch.py

# Override to the classic Patchwork
ros2 launch patchworkpp patchworkpp.launch.py algorithm:=patchwork
```

The algorithm argument is the part to remember, because it is the same decision as in the Python API but exposed where a robot operator would look. There is also a separate ROS1 branch that the README links for users who have not migrated, which tells you how long the project has been carried across ROS generations.

For a deployment, the honest question is where the segmentation happens in your stack. Running it in the ROS node means the ground is already removed by the time your detection module subscribes to the cloud, which saves bandwidth and compute downstream. Running it in Python inside a perception pipeline gives you one language for the whole stack, at the cost of copying points across the binding.

## The SemanticKITTI harness and the protocol caveat

The repository includes a reproducible evaluation harness for SemanticKITTI, for both Patchwork and Patchwork++, at python/examples/evaluate_semantickitti.py. That is unusual for a paper release, and it is the most useful thing in the repository for anyone who has to justify a choice.

The README attaches a warning to it. The papers of Patchwork and Patchwork++ used different evaluation protocols, and USAGE.md section 1 explains why, with section 4 carrying per-sequence numbers. The practical meaning is that a number quoted from one paper and a number from the other are not directly comparable, and the harness exists to make the comparison repeatable on your own data.

Data files live in the data/ directory and the repository also ships scripts/ and docs/ alongside the C++ source, the Python binding and the ROS2 package. A CHANGELOG.md is present at the top level, which is where the behaviour changes between releases are recorded.

## What the 2026 releases changed

The release notes show a project that has moved from algorithm work to performance work. The most recent release, v1.4.1 on 2026-05-23, is titled to kill per-patch heap traffic in Patchwork++ and claims 14.8 percent more Hz. The release before it, v1.4.0 on 2026-05-21, introduced a shared common library and optional TBB on classic Patchwork with a claimed 1.73 times improvement in Hz. v1.3.1, also on 2026-05-21, fixed a ringwise_flatness leakage bug in TGR.

The Hz figures come from the release titles in this repository, so they are the maintainers' own measurements, and their baseline is not stated in the release notes themselves.

The bug fix is worth more attention than the percentages. A leakage fix in the flatness computation of the ground revert step changes which points TGR treats as ground, and a pipeline that validated its detections against the old behaviour may see a different point set. The last push to the repository was on 2026-05-23, the same date as v1.4.1, so the tree is at that release.

## Where ground segmentation is the wrong tool

Patchwork++ assumes a ground plane and removes it. It is not a detector and it produces no class labels, so a system that needs to tell a road from a floor from a table top is looking at the wrong library.

There is a second limitation that follows from the algorithm family. A patch based method with flatness and elevation tests will treat a large horizontal surface above the vehicle, such as an overpass or a bridge deck, as ground, and the reflective noise removal step exists precisely because such artefacts confuse the threshold estimate. The README mentions the reflected noise case as the argument for the default algorithm, which is an admission that the harder scenes are where the fixed-threshold variant and the self-tuning variant diverge most.

Sensor height matters too. Classic Patchwork takes an explicit z cutoff relative to the sensor height and offers ATAT for when that height is unknown, so a user who does not know the mounting height has fewer reliable controls. Finally, this is research code with a paper attached. The C++ API, the parameter names and the ROS2 launch arguments can change between minor versions, and the semantic content of a parameter such as the flatness threshold is tied to the algorithm version that defines it.

## Conclusion

Patchwork++ fits robotics and mapping teams that need the ground removed in real time and want to switch between a self-tuning algorithm and a fixed-threshold one without changing their code. It does not fit pipelines that need a learned detector or semantic labels, and you should verify first that the prebuilt Python wheel matches your Eigen and compiler setup before committing to it.

## FAQ

### How do I install Patchwork++ for Python?

Run pip install pypatchworkpp for the core library, or pip install 'pypatchworkpp[demo]' to add Open3D for the visual demos. Contributors can instead run make pyinstall from the checkout, which pip installs the local python/ directory.

### What is the difference between Patchwork++ and classic Patchwork?

Patchwork++ uses adaptive elevation and flatness thresholds plus RNR, RVPF and TGR, while classic Patchwork uses fixed thresholds with an explicit z cutoff, a few-points reject and optional ATAT for unknown sensor heights. Both are available from the same API in the same repository.

### How do I run Patchwork++ in ROS2?

Clone the repository into a colcon workspace, build with colcon build --packages-select patchworkpp, then launch with ros2 launch patchworkpp patchworkpp.launch.py. Passing algorithm:=patchwork switches the node to the classic variant.

### Does the repository include an evaluation harness?

Yes. python/examples/evaluate_semantickitti.py evaluates both Patchwork and Patchwork++ on SemanticKITTI. The README notes the two papers used different evaluation protocols, with the reasons in USAGE.md section 1 and per-sequence numbers in section 4.

## Sources

- [Issues](https://github.com/url-kaist/patchwork-plusplus/issues)
- [License: BSD-2-Clause](https://github.com/url-kaist/patchwork-plusplus/blob/master/LICENSE)
- [README](https://github.com/url-kaist/patchwork-plusplus/blob/master/README.md)
- [Releases](https://github.com/url-kaist/patchwork-plusplus/releases)
- [url-kaist/patchwork-plusplus on GitHub](https://github.com/url-kaist/patchwork-plusplus)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/url-kaist-patchwork-plusplus
