This is the operational reference for ERASOR2: installation, running the pipeline on SemanticKITTI / HeLiPR / HeLiMOS, visualization, YAML schema, and the practical notes collected during the ROS+catkin to plain-CMake refactor.
The headline numbers and the short reproduction path live in the
top-level README.md. This page keeps the deeper reference
for users who want to inspect or modify each step.
ERASOR2 builds with a standard CMake flow on Linux or macOS when PCL, Eigen, OpenCV, OpenMP, Boost, and yaml-cpp are available. No catkin workspace or ROS environment is required.
# Ubuntu 20.04 / 22.04
sudo apt-get install -y \
build-essential cmake \
libpcl-dev libeigen3-dev libopencv-dev libomp-dev \
libboost-system-dev libboost-filesystem-dev \
libyaml-cpp-devThe rerun_sdk 0.32 dependency is fetched by CMake at configure time;
no system install is needed.
git clone https://github.com/LimHyungTae/ERASOR2.git
cd ERASOR2
cmake -B build -S .
cmake --build build -jThe same source tree has been verified on Ubuntu 20.04
(GCC 9.4 + PCL 1.10) and Ubuntu 22.04 (GCC 11.4 + PCL 1.12). The build
produces these binaries under build/:
| Binary | Purpose |
|---|---|
mapgen |
Accumulate raw scans into a labelled ground-truth map |
run_erasor2 |
Remove dynamic objects → static map + per-frame MOS labels |
compare_map |
Compare multiple estimates against GT (TP/FP/FN/TN) |
accum_4dmos |
Accumulate 4D-MOS predictions into a static map |
fill_removert_labels |
Fill REMOVERT label files (two positional PCD paths) |
helipr_to_kitti, merge_heliclouds |
HeLiPR / HeLiMOS preprocessing |
Q. I don't want to install the system layer myself.
A pre-built image is published at
shapelim/opengl-ubuntu20.04-erasor2:latest. Mount the repository and
build inside the container:
docker run --rm -v "$PWD":/work -w /work \
shapelim/opengl-ubuntu20.04-erasor2:latest \
bash -c "cmake -B build -S . && cmake --build build -j"Q. Build fails on Ubuntu 24.04 with target "VTK::mpi" was not found.
Ubuntu 24.04 can expose a stale VTK::mpi import in the PCL config. Add
find_package(MPI QUIET) before find_package(PCL) in
CMakeLists.txt, or build inside the 20.04 docker.
Q. Where are the ROS launch files / RViz configs?
roslaunch erasor2 run_erasor2.launch target_seq:=seq_05 was removed
in v1.0. Use the CMake-built binary directly:
./build/run_erasor2 ./config/erasor2/seq_05.yamlThe launch/ and rviz/ directories remain for historical reference,
but they are no longer wired into the build. The 2D grid abstraction
that previously came from ros-noetic-grid-map-* now lives in
include/erasor2/grid_map.hpp: a small subset of the upstream
grid_map_core API, matched against the seq-05 parity check.
Visualization now uses rerun.io instead of RViz /
tf2.
The C++ binaries consume per-frame instance labels, ground labels, and PCD outputs generated by the Python helpers (open3d, pypatchworkpp, and HDBSCAN). The conda recipe is shipped in the repository:
conda env create -f scripts/environment.yml # creates env "erasor2"
conda activate erasor2Once the environment is active, scripts/run_pipeline.py can chain
preprocessing → mapgen → run_erasor2 → evaluate in one command (see
How to run).
Q. open3d import crashes with CXXABI_1.3.15 not found.
Ubuntu 20.04's system libstdc++ only goes up to CXXABI_1.3.14;
open3d ≥ 0.18 needs 1.3.15, which lives in the conda env's own
libstdc++. Preload it:
export LD_PRELOAD="$CONDA_PREFIX/lib/libstdc++.so.6"scripts/run_pipeline.py --conda-env <path> sets this automatically.
Q. Why not pure pip?
pypatchworkpp and open3d ship native wheels that need a recent glibc
/ libstdc++. Mixing them with a system Python on Ubuntu 20.04 often
causes ABI mismatches. Conda keeps the Python toolchain isolated, which
is what run_pipeline.py expects.
ERASOR2 needs three inputs per sequence: raw scans, per-frame instance labels from HDBSCAN, and per-frame ground labels from Patchwork. The Python helper generates these labels, and the Python driver chains the C++ binaries end-to-end.
# 1. Per-frame instance + ground labels (conda env with open3d + pypatchworkpp).
# --kitti_dir is the directory ABOVE 'dataset/', so that
# <kitti_dir>/dataset/sequences/05/velodyne/ exists.
python scripts/kitti_clustering.py \
--kitti_dir /home/url/datasets/kitti \
--seq 05 --init_stamp 2350 --end_stamp 2670 \
--save-instance-labels --save-ground-labels
# 2. Full pipeline: mapgen → run_erasor2 → evaluate.
# Before running, edit `config/erasor2/seq_05.yaml` and set `abs_data_dir`
# (= <kitti_dir>/dataset/sequences) and `abs_save_dir` (where mapgen
# and run_erasor2 write the GT/estimated PCDs) for your machine.
python scripts/run_pipeline.py \
--config config/erasor2/seq_05.yaml \
--conda-env ~/.miniconda3/envs/erasor2-3.10Important
Each config/*.yaml ships with example paths from the maintainer's
machine. Before running anything that takes a --config, open the
file and update dataloader.abs_data_dir + dataloader.abs_save_dir
(and, for HeLiPR, the per-sensor paths) to point at your own dataset
and output locations. run_pipeline.py derives --kitti_dir for the
preprocessing step from abs_data_dir automatically (two parents up).
SemanticKITTI evaluation uses SuMa poses. Place
poses_suma_optim.txt inside each sequence directory, e.g.
<kitti_dir>/dataset/sequences/05/poses_suma_optim.txt.
Warning
Always inspect the HDBSCAN instance segmentation before running the full pipeline. The clustering quality varies sequence-to-sequence and frame-to-frame; bad clusters (over-segmented dynamic objects, merged car-plus-ground blobs, missed pedestrians) will silently degrade ERASOR2's PR/RR/F1. Use the visualizer below to scrub the labels frame-by-frame and confirm distinct objects get distinct colors before trusting the downstream numbers.
scripts/visualize_clustering.py loads the per-frame Velodyne scan,
Patchwork ground labels, and HDBSCAN instance labels, then streams them
into a rerun viewer on a frame timeline:
world/ground— Patchwork++ ground points (brown)world/instances— non-ground points colored by HDBSCAN cluster ID
# Spawns the rerun viewer. Use the same args you passed to kitti_clustering.py.
python scripts/visualize_clustering.py \
--kitti_dir /home/url/datasets/kitti \
--seq 05 --init_stamp 2350 --end_stamp 2670
# Or save a .rrd you can open later with `rerun /path/to/file.rrd`:
python scripts/visualize_clustering.py \
--kitti_dir /home/url/datasets/kitti \
--seq 05 --init_stamp 2350 --end_stamp 2670 \
--save cluster_viz.rrdDrag the timeline slider at the bottom of the viewer to step through
frames. --show-raw adds the uncolored raw scan as a third overlay.
The wrapper orchestrates the C++ binaries via subprocess. You can
also invoke each binary directly:
./build/mapgen config/erasor2/seq_05.yaml
./build/run_erasor2 config/erasor2/seq_05.yaml
python scripts/evaluate.py \
--gt <abs_save_dir>/<gt>.pcd \
--est <abs_save_dir>/<est>.pcdYAML schema (highlights)
Each binary takes one positional argument: the path to a YAML. Any
key omitted falls back to the default in include/erasor2/Config.hpp.
start_frame: 2350
end_frame: 2670
is_large_scale: true
dataloader:
dataset_name: "SemanticKITTI" # or "HeLiPR"
abs_data_dir: "/path/to/sequences"
sequence: "05"
abs_save_dir: "/path/to/output"
instance_seg_method: "hdbscan" # or "cais"
accum_interval: 2
voxel_size: 0.2
map_voxel_size: 0.2
erasor2:
grid_resolution: 2.0
range_of_interest: 60.0
min_z_voi: -3.0
max_z_voi: 1.5
scan_ratio_threshold: 0.2
log_odds: { increment_gain: 2.0, increment: 0.15 }
region_proposal_thr: 0.8
moving_object_detection:
obj_score_soft_thr: 0.8
obj_score_hard_thr: 14.0
hard_thr_radius: 10.0
save_map: true
extrinsic:
robot_body_size: 2.7
sensor_height: 1.73
rotation: [1, 0, 0, 0, 1, 0, 0, 0, 1]
translation: [0, 0, 0]
rerun:
enabled: true # false ⇒ every log call is a no-op
spawn: true # launch the rerun viewer subprocess
save_path: "" # if non-empty, dump a .rrd instead of spawningPer-sequence configs live under config/ (seq_05.yaml, seq_07.yaml,
...).
Headless / batch operation
For CI, batch evaluation, or paper-figure generation, point
rerun.save_path at a .rrd file and set spawn: false:
rerun:
enabled: true
spawn: false
save_path: /tmp/erasor2_run.rrdInspect later with rerun /tmp/erasor2_run.rrd. Setting
rerun.enabled: false makes every visualization call a no-op
(slightly faster end-to-end).
Warning
This path is experimental and has not been fully validated yet.
The same binaries handle HeLiPR-style data: set
dataloader.dataset_name to "HeLiPR" and point abs_data_dir at
the directory that contains the per-sensor trees:
<abs_data_dir>/
└── <sensor>/ # Avia, Aeva, VLP16, or Merged
├── velodyne/ # .bin scans
├── poses.txt # KITTI-style 3x4 row-major
├── patchwork/ # ground labels (from pypatchworkpp)
└── hdbscan/ # instance labels (from clusters_hdbscan)
Avia, Aeva, and VLP16 poses are corrected by the per-sensor
extrinsic baked into src/dataloader/dataloader.cpp (T_OS2_*).
Merged is assumed to be in the Ouster frame and uses identity.
# Per-frame labels (same script as KITTI; just change --seq).
# For HeLiMOS, --kitti_dir is the directory directly containing the
# sequence folders (e.g. .../HeLiMOS/KAIST05/deskewed_LiDAR/).
python scripts/kitti_clustering.py \
--kitti_dir /home/url/datasets/HeLiMOS/KAIST05/deskewed_LiDAR \
--seq Merged --init_stamp 8600 --end_stamp 8649 \
--save-instance-labels --save-ground-labels
# mapgen + run_erasor2 with the HeLiPR config.
./build/mapgen config/erasor2/helipr_mapgen.yaml
./build/run_erasor2 config/erasor2/helipr_mapgen.yamlWarning
Same as the KITTI flow: visually inspect a sample of the HDBSCAN instance labels before trusting the downstream metrics. HeLiPR sensors have very different point densities (Avia/Aeva are far sparser than Ouster/Velodyne), so clustering hyperparameters that look fine on one sensor may over- or under-segment on another.
The shipped HeLiPR configs (config/erasor2/HeLiPR.yaml,
config/erasor2/HeLiPR_kitti.yaml, and
config/erasor2/helipr_mapgen.yaml) contain example paths from the
original development machines under /media/...; update
abs_data_dir and abs_save_dir before running. The HeLiPR-specific
preprocessing binaries helipr_to_kitti and merge_heliclouds
(per-sensor extraction → time-aligned merge into Merged) use their
own YAMLs; see the dataprocessor: section in
config/erasor2/HeLiPR_kitti.yaml.
dataloader.expansion_rangedefaults to 20.run_erasor2looks 20 frames before each cluster'sstart_frameto expand the trajectory submap. For partial copies of a dataset (CI / smoke testing), either copy 20 frames of padding beforestart_frame, or setexpansion_range: 0in the YAML.- The accumulation loop iterates
[start_frame, end_frame + accum_interval). Withend_frame: 2670andaccum_interval: 2, the loop reads frame 2671. Copy at least one extra frame pastend_framewhen using a trimmed dataset. mapgenprints[pcl::VoxelGrid] Leaf size too small …on HeLiMOS-sized maps. This is cosmetic: actual voxelization is handled byvoxelize_preserving_labels_by_nanoflann, which avoids the integer-index overflow that triggers the PCL warning.
config/ per-sequence YAML configs
include/ public headers (Config, GridMap, rerun logger, utils)
src/ C++ sources for the seven binaries + shared utils
scripts/ Python helpers (preprocessing, evaluation, pipeline driver)
tests/ parity-check CI (workflow disabled until self-hosted runner is registered)
launch/ [legacy] ROS1 launch files, no longer wired into the build
rviz/ [legacy] RViz configs, replaced by the rerun entity tree
ERASOR2 builds on open-source LiDAR tooling, especially PCL, Eigen, rerun.io, pypatchworkpp, and nanoflann. The v1.0/v1.1 transition removed ROS and catkin from the runtime, which was possible because these dependencies are clean to consume from a plain CMake project.