Skip to content
cocel-postechPublic

Latest commit

 

History

63 Commits

Folders and files

Repository files navigation

GenZ-LIO

Generalizable LiDAR-Inertial Odometry Beyond Confined–Open Boundaries

C++ Python ROS 1 ROS 2 License

Youtube   •   Bilibili   •   Paper   •   Dataset   •   Install   •   Python   •   ROS

GenZ-LIO on NarrowWide Handheld-A-01

(October 7, 2026) pip installation is now live:
$ pip install genz-lio

GenZ-LIO is designed for robust and computationally efficient LiDAR-inertial odometry across confined spaces, open environments, and transitions between them.

Python support

Process recorded data with an optional visualizer; no ROS installation is needed.

Install and run (Linux x86-64, Python 3.8–3.12)

1. Prepare the environment

Use Linux x86-64 with Python 3.8–3.12. Ubuntu 20.04, 22.04, and 24.04 provide Python 3.8, 3.10, and 3.12, respectively.

sudo apt-get update
sudo apt-get install -y python3-venv libgl1
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip

2. Install

Install all optional readers (rosbag and Ouster PCAP) and the visualizer:

python -m pip install 'genz-lio[all]'

The published wheels include the compiled C++ extension; no local C++ build or ROS installation is required. Plain pip install genz-lio installs the core and CLI.

3. Prepare configurations

The experiments/ YAMLs provide the sequence-specific configurations for reproducing the benchmark experiments in the paper. The default/ YAMLs are sensor templates for your own recordings.

Export all bundled YAMLs once, preserving their default/ and experiments/ subdirectories under configs/:

genz_lio_pipeline list-configs | while IFS= read -r config; do
    mkdir -p "configs/$(dirname "$config")"
    genz_lio_pipeline export-config "$config" "configs/$config"
done

Run this in the directory where you will run the examples below. This copies all 36 YAMLs unchanged and works with both source and wheel installations. Keep this folder for subsequent runs; there is no need to export again per sequence. Exported YAMLs are editable. Re-running the export will not overwrite existing files or your changes.

4. Run

1) For reproducing benchmark experiments (e.g., NarrowWide Handheld-A-01)

Use the experiment YAML prepared above with the NarrowWide Handheld-A-01 recording:

genz_lio_pipeline run "{path_to_bag}/{NW_Handheld-A-01}.bag" \
    --config configs/experiments/narrowwide/vlp16_handheld_a_01.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/narrowwide_handheld_a_01

The experiment YAML already contains the benchmark sensor topics and calibration. Obtain the dataset separately. The optional --image-topic displays the camera images recorded in this bag; omit it to run without the camera panel.

Press SPACE to start. For rosbag2, pass the recording directory instead of a .bag file. The exported YAMLs also work outside the source checkout.

▶️ All benchmark sequences: downloads and Python commands (42 sequences)

The commands use configs/experiments/ exported in the preparation step and write each sequence's trajectory to a separate output directory. All enable --visualize; --image-topic is included where the recording contains a camera stream. Omit that option to disable the preview. Camera streams are display-only. Download the full recordings with images when using the camera options below.

Bag filename labels in braces identify the sequence, not the official download filename. Substitute the actual downloaded or prepared filename; no file renaming is required. Labels ending in _pointcloud or _merged refer to the prepared inputs described below.

GEODE (7 sequences)

Dataset · Download.

Use the Alpha (Velodyne) recordings for these YAMLs. The examples select the left camera.

Stairs

genz_lio_pipeline run "{path_to_bag}/{GD_Stairs}.bag" \
    --config configs/experiments/geode/vlp16_stairs.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_stairs

Waterways-Short

genz_lio_pipeline run "{path_to_bag}/{GD_Waterways-Short}.bag" \
    --config configs/experiments/geode/vlp16_waterways_short.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_waterways_short

Waterways-Medium

genz_lio_pipeline run "{path_to_bag}/{GD_Waterways-Medium}.bag" \
    --config configs/experiments/geode/vlp16_waterways_medium.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_waterways_medium

Waterways-Long

genz_lio_pipeline run "{path_to_bag}/{GD_Waterways-Long}.bag" \
    --config configs/experiments/geode/vlp16_waterways_long.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_waterways_long

Offroad-02

genz_lio_pipeline run "{path_to_bag}/{GD_Offroad-02}.bag" \
    --config configs/experiments/geode/vlp16_offroad.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_offroad_02

Offroad-04

genz_lio_pipeline run "{path_to_bag}/{GD_Offroad-04}.bag" \
    --config configs/experiments/geode/vlp16_offroad.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_offroad_04

Offroad-07

genz_lio_pipeline run "{path_to_bag}/{GD_Offroad-07}.bag" \
    --config configs/experiments/geode/vlp16_offroad.yaml \
    --visualize --image-topic /left_camera/compressed \
    --output results/gd_offroad_07
ENWIDE (4 sequences)

Dataset · Download.

These four recordings contain no Image/CompressedImage topic, so the commands omit --image-topic.

Katzensee-S

genz_lio_pipeline run "{path_to_bag}/{EW_Katzensee-S}.bag" \
    --config configs/experiments/enwide/os128_enwide.yaml \
    --visualize \
    --output results/ew_katzensee_s

Katzensee-D

genz_lio_pipeline run "{path_to_bag}/{EW_Katzensee-D}.bag" \
    --config configs/experiments/enwide/os128_enwide.yaml \
    --visualize \
    --output results/ew_katzensee_d

Intersection-S

genz_lio_pipeline run "{path_to_bag}/{EW_Intersection-S}.bag" \
    --config configs/experiments/enwide/os128_enwide.yaml \
    --visualize \
    --output results/ew_intersection_s

Intersection-D

genz_lio_pipeline run "{path_to_bag}/{EW_Intersection-D}.bag" \
    --config configs/experiments/enwide/os128_enwide.yaml \
    --visualize \
    --output results/ew_intersection_d
NTU VIRAL (3 sequences)

Dataset and downloads.

SPMS-01

genz_lio_pipeline run "{path_to_bag}/{NV_SPMS-01}.bag" \
    --config configs/experiments/ntu_viral/os16_spms_01.yaml \
    --visualize --image-topic /left/image_raw \
    --output results/nv_spms_01

SPMS-02

genz_lio_pipeline run "{path_to_bag}/{NV_SPMS-02}.bag" \
    --config configs/experiments/ntu_viral/os16_spms_02.yaml \
    --visualize --image-topic /left/image_raw \
    --output results/nv_spms_02

SPMS-03

genz_lio_pipeline run "{path_to_bag}/{NV_SPMS-03}.bag" \
    --config configs/experiments/ntu_viral/os16_spms_03.yaml \
    --visualize --image-topic /left/image_raw \
    --output results/nv_spms_03
SuperLoc (4 sequences)

Dataset and downloads.

Download Cave01, Cave02, Cave04, and Corridor02 from the SuperLoc dataset table.

Cave-01

genz_lio_pipeline run "{path_to_bag}/{SL_Cave-01}.bag" \
    --config configs/experiments/superloc/vlp16_cave.yaml \
    --visualize --image-topic /camera_1/image_raw \
    --output results/sl_cave_01

Cave-02

genz_lio_pipeline run "{path_to_bag}/{SL_Cave-02}.bag" \
    --config configs/experiments/superloc/vlp16_cave.yaml \
    --visualize --image-topic /camera_1/image_raw \
    --output results/sl_cave_02

Cave-04

genz_lio_pipeline run "{path_to_bag}/{SL_Cave-04}.bag" \
    --config configs/experiments/superloc/vlp16_cave.yaml \
    --visualize --image-topic /camera_1/image_raw \
    --output results/sl_cave_04

Corridor-02

genz_lio_pipeline run "{path_to_bag}/{SL_Corridor-02}.bag" \
    --config configs/experiments/superloc/vlp16_corridor_02.yaml \
    --visualize --image-topic /camera_1/image_raw \
    --output results/sl_corridor_02
NarrowWide (6 sequences)

Dataset and downloads.

Dataset information and download instructions are maintained in the NarrowWide repository.

Tracked-01

genz_lio_pipeline run "{path_to_bag}/{NW_Tracked-01}.bag" \
    --config configs/experiments/narrowwide/mid70_tracked_01.yaml \
    --visualize --image-topic /camera/image_color/compressed \
    --output results/nw_tracked_01

Tracked-02

genz_lio_pipeline run "{path_to_bag}/{NW_Tracked-02}.bag" \
    --config configs/experiments/narrowwide/mid70_tracked_02.yaml \
    --visualize --image-topic /camera/image_color/compressed \
    --output results/nw_tracked_02

Handheld-A-01

genz_lio_pipeline run "{path_to_bag}/{NW_Handheld-A-01}.bag" \
    --config configs/experiments/narrowwide/vlp16_handheld_a_01.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/nw_handheld_a_01

Handheld-A-02

genz_lio_pipeline run "{path_to_bag}/{NW_Handheld-A-02}.bag" \
    --config configs/experiments/narrowwide/vlp16_handheld_a_02.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/nw_handheld_a_02

Handheld-B-01

genz_lio_pipeline run "{path_to_bag}/{NW_Handheld-B-01}.bag" \
    --config configs/experiments/narrowwide/avia_handheld_b_01.yaml \
    --visualize --image-topic /camera/image_color/compressed \
    --output results/nw_handheld_b_01

Handheld-B-02

genz_lio_pipeline run "{path_to_bag}/{NW_Handheld-B-02}.bag" \
    --config configs/experiments/narrowwide/avia_handheld_b_02.yaml \
    --visualize --image-topic /camera/image_color/compressed \
    --output results/nw_handheld_b_02
SubT-MRS (3 sequences)

Dataset and downloads.

Prepare PointCloud2 input first. The downloaded bags contain /velodyne_packets, which the Python pipeline cannot decode directly. Convert those packets with a Velodyne VLP-16 driver to /velodyne_points (sensor_msgs/PointCloud2) with per-point timing, while preserving /imu/data, original timestamps, and the image topics shown below. Record the prepared sequence as one .bag; use its filename in the corresponding command below.

Multi-Floor spans 0.bag–2.bag; Laurel-Cavern spans 0.bag–10.bag. Process all parts in timestamp order into one continuous prepared recording. The CLI accepts one .bag or one rosbag2 recording, not a directory of ROS 1 bag parts. Laurel-Cavern has no image topic; the other two commands require retaining their camera streams during preparation.

Long-Corridor

genz_lio_pipeline run "{path_to_bag}/{SM_Long-Corridor_pointcloud}.bag" \
    --config configs/experiments/subt_mrs/vlp16_long_corridor.yaml \
    --visualize --image-topic /camera_1/image_raw \
    --output results/sm_long_corridor

Multi-Floor

genz_lio_pipeline run "{path_to_bag}/{SM_Multi-Floor_pointcloud}.bag" \
    --config configs/experiments/subt_mrs/vlp16_multi_floor.yaml \
    --visualize --image-topic /cmu_sp1/camera_1/image_raw \
    --output results/sm_multi_floor

Laurel-Cavern

genz_lio_pipeline run "{path_to_bag}/{SM_Laurel-Cavern_pointcloud}.bag" \
    --config configs/experiments/subt_mrs/vlp16_laurel_cavern.yaml \
    --visualize \
    --output results/sm_laurel_cavern
HILTI 2021 (2 sequences)

Dataset and downloads.

The examples use camera 0 and the MID-70 experiment configurations.

Basement-04

genz_lio_pipeline run "{path_to_bag}/{H21_Basement-04}.bag" \
    --config configs/experiments/hilti21/mid70_hilti21_basement04.yaml \
    --visualize --image-topic /alphasense/cam0/image_raw \
    --output results/h21_basement_04

Drone-Arena

genz_lio_pipeline run "{path_to_bag}/{H21_Drone-Arena}.bag" \
    --config configs/experiments/hilti21/mid70_hilti21_drone_arena.yaml \
    --visualize --image-topic /alphasense/cam0/image_raw \
    --output results/h21_drone_arena
HILTI 2022 (3 sequences)

Dataset and downloads.

The examples use camera 0 and the Pandar32 experiment configurations.

Exp-10

genz_lio_pipeline run "{path_to_bag}/{H22_Exp-10}.bag" \
    --config configs/experiments/hilti22/pandar32_hilti22_exp10.yaml \
    --visualize --image-topic /alphasense/cam0/image_raw \
    --output results/h22_exp_10

Exp-16

genz_lio_pipeline run "{path_to_bag}/{H22_Exp-16}.bag" \
    --config configs/experiments/hilti22/pandar32_hilti22_exp16.yaml \
    --visualize --image-topic /alphasense/cam0/image_raw \
    --output results/h22_exp_16

Exp-18

genz_lio_pipeline run "{path_to_bag}/{H22_Exp-18}.bag" \
    --config configs/experiments/hilti22/pandar32_hilti22_exp18.yaml \
    --visualize --image-topic /alphasense/cam0/image_raw \
    --output results/h22_exp_18
M3DGR (4 sequences)

Dataset and downloads.

The examples select the RGB color camera rather than the compressed depth stream.

Corridor-01

genz_lio_pipeline run "{path_to_bag}/{M3D_Corridor-01}.bag" \
    --config configs/experiments/m3dgr/avia_corridor_01.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/m3d_corridor_01

Corridor-02

genz_lio_pipeline run "{path_to_bag}/{M3D_Corridor-02}.bag" \
    --config configs/experiments/m3dgr/avia_corridor_02.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/m3d_corridor_02

GNSS-denial-01

genz_lio_pipeline run "{path_to_bag}/{M3D_GNSS-denial-01}.bag" \
    --config configs/experiments/m3dgr/avia_gnss_denial.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/m3d_gnss_denial_01

GNSS-denial-02

genz_lio_pipeline run "{path_to_bag}/{M3D_GNSS-denial-02}.bag" \
    --config configs/experiments/m3dgr/avia_gnss_denial.yaml \
    --visualize --image-topic /camera/color/image_raw/compressed \
    --output results/m3d_gnss_denial_02
Oxford Spires (6 sequences)

Dataset · Download.

Download the ROS bags including the camera streams. The examples select cam0/debayered/image/compressed.

Split recordings: christ_church-01 and christ_church-02 each contain two bag parts (..._0.bag and ..._1.bag). Merge both parts in timestamp order, without changing topics, message contents, or timestamps, into one merged .bag per sequence. The _merged placeholders below refer to those files. The commands below use those prepared files; passing only the first part would run an incomplete sequence. The other four commands use the original single bags.

christ_church-01

genz_lio_pipeline run "{path_to_bag}/{OS_christ_church-01_merged}.bag" \
    --config configs/experiments/oxford_spires/hesai64_christ_church.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_christ_church_01

christ_church-02

genz_lio_pipeline run "{path_to_bag}/{OS_christ_church-02_merged}.bag" \
    --config configs/experiments/oxford_spires/hesai64_christ_church.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_christ_church_02

christ_church-05

genz_lio_pipeline run "{path_to_bag}/{OS_christ_church-05}.bag" \
    --config configs/experiments/oxford_spires/hesai64_christ_church.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_christ_church_05

blenheim_palace-01

genz_lio_pipeline run "{path_to_bag}/{OS_blenheim_palace-01}.bag" \
    --config configs/experiments/oxford_spires/hesai64_blenheim_palace.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_blenheim_palace_01

blenheim_palace-02

genz_lio_pipeline run "{path_to_bag}/{OS_blenheim_palace-02}.bag" \
    --config configs/experiments/oxford_spires/hesai64_blenheim_palace.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_blenheim_palace_02

blenheim_palace-05

genz_lio_pipeline run "{path_to_bag}/{OS_blenheim_palace-05}.bag" \
    --config configs/experiments/oxford_spires/hesai64_blenheim_palace.yaml \
    --visualize --image-topic /alphasense_driver_ros/cam0/debayered/image/compressed \
    --output results/os_blenheim_palace_05

2) For your own sensor, copy a template, calibrate it, then open the visualizer:

If you use a Velodyne LiDAR, start with the following template:

cp configs/default/velodyne.yaml my_robot.yaml
# Edit my_robot.yaml for your sensor topics and LiDAR-to-IMU calibration.
genz_lio_pipeline run /data/sequence.bag \
    --config my_robot.yaml --visualize --output results/my_run

To show camera images from your own recording, add --image-topic followed by an Image or CompressedImage topic present in that bag.

Parameter tuning guidance is available in the parameter guide.

See python/README.md for rebuilding after C++ changes, saving odometry, input options, camera input requirements, the Python API, and Ouster PCAP input.

ROS 1 support

Install, build, and run with RViz (Noetic)

1. Install dependencies

Start with ROS 1 Noetic installed on Ubuntu 20.04:

source /opt/ros/noetic/setup.bash
sudo apt-get update
sudo apt-get install -y git build-essential cmake libeigen3-dev libboost-dev \
    libyaml-cpp-dev ros-noetic-roscpp ros-noetic-rospy ros-noetic-roslib \
    ros-noetic-pcl-ros ros-noetic-pcl-conversions ros-noetic-tf \
    ros-noetic-tf2-ros ros-noetic-visualization-msgs ros-noetic-rviz \
    ros-noetic-rosbag

For Livox CustomMsg input, complete the optional driver build below before building GenZ-LIO. PointCloud2 input does not require this step.

Livox ROS 1: build CustomMsg support (optional)

For livox_ros_driver/CustomMsg, install Livox-SDK following its build instructions, then build livox_ros_driver in a separate workspace:

source /opt/ros/noetic/setup.bash
git clone https://github.com/Livox-SDK/livox_ros_driver.git ~/livox_ws/src
cd ~/livox_ws
catkin_make -j2 -DCMAKE_BUILD_TYPE=Release
source devel/setup.bash

Keep this shell for the next step. GenZ-LIO's ROS 1 wrapper expects livox_ros_driver, not the ROS 1 variant of livox_ros_driver2.

2. Build GenZ-LIO

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
git clone https://github.com/cocel-postech/genz-lio.git
cd ~/catkin_ws
catkin_make -j2 -DCMAKE_BUILD_TYPE=Release
source devel/setup.bash

The build commands limit compilation to two parallel jobs to reduce peak memory use. This does not change runtime.max_threads during odometry.

If using Livox CustomMsg, check that CMake reports Livox CustomMsg support enabled. If the driver was added after GenZ-LIO was built, source its workspace and rerun catkin_make -j2 --force-cmake -DCMAKE_BUILD_TYPE=Release from your GenZ-LIO workspace.

3. Run

Use the package's config/experiments/ YAMLs for the paper's benchmark sequences, or a calibrated config/default/ template for your own sensor. config:= accepts a path relative to the package's config/ directory, or an absolute YAML path.

1) For reproducing benchmark experiments (e.g., NarrowWide Handheld-A-01)

Start GenZ-LIO with RViz in the first terminal:

source ~/catkin_ws/devel/setup.bash
roslaunch genz_lio odometry.launch \
    config:=experiments/narrowwide/vlp16_handheld_a_01.yaml

Wait for the node to start, then play the recording in a second terminal:

source ~/catkin_ws/devel/setup.bash
rosbag play --delay 1 "{path_to_bag}/{NW_Handheld-A-01}.bag"

--delay 1 gives subscribers time to connect after each topic is advertised, helping preserve the first IMU samples used for initialization. Increase it if startup connections are slow.

Replace the bag placeholders with the actual downloaded or prepared file. GenZ-LIO consumes PointCloud2 or supported Livox CustomMsg plus IMU messages; raw Velodyne packets must first be decoded to PointCloud2 with point timing.

▶️ All benchmark sequences: downloads and ROS 1 configurations (42 sequences)

Use the YAML path below as the config:= argument in the launch command above. Paths are relative to the package's config/ directory.

GEODE (7 sequences)

Dataset · Download.

Use the Alpha (Velodyne) recordings for these YAMLs.

Sequence YAML (config:=)
Stairs experiments/geode/vlp16_stairs.yaml
Waterways-Short experiments/geode/vlp16_waterways_short.yaml
Waterways-Medium experiments/geode/vlp16_waterways_medium.yaml
Waterways-Long experiments/geode/vlp16_waterways_long.yaml
Offroad-02 experiments/geode/vlp16_offroad.yaml
Offroad-04 experiments/geode/vlp16_offroad.yaml
Offroad-07 experiments/geode/vlp16_offroad.yaml
ENWIDE (4 sequences)

Dataset · Download.

Sequence YAML (config:=)
Katzensee-S experiments/enwide/os128_enwide.yaml
Katzensee-D experiments/enwide/os128_enwide.yaml
Intersection-S experiments/enwide/os128_enwide.yaml
Intersection-D experiments/enwide/os128_enwide.yaml
NTU VIRAL (3 sequences)

Dataset and downloads.

Sequence YAML (config:=)
SPMS-01 experiments/ntu_viral/os16_spms_01.yaml
SPMS-02 experiments/ntu_viral/os16_spms_02.yaml
SPMS-03 experiments/ntu_viral/os16_spms_03.yaml
SuperLoc (4 sequences)

Dataset and downloads.

Download Cave01, Cave02, Cave04, and Corridor02 from the SuperLoc dataset table.

Sequence YAML (config:=)
Cave-01 experiments/superloc/vlp16_cave.yaml
Cave-02 experiments/superloc/vlp16_cave.yaml
Cave-04 experiments/superloc/vlp16_cave.yaml
Corridor-02 experiments/superloc/vlp16_corridor_02.yaml
NarrowWide (6 sequences)

Dataset and downloads.

Dataset information and download instructions are maintained in the NarrowWide repository.

Sequence YAML (config:=)
Tracked-01 experiments/narrowwide/mid70_tracked_01.yaml
Tracked-02 experiments/narrowwide/mid70_tracked_02.yaml
Handheld-A-01 experiments/narrowwide/vlp16_handheld_a_01.yaml
Handheld-A-02 experiments/narrowwide/vlp16_handheld_a_02.yaml
Handheld-B-01 experiments/narrowwide/avia_handheld_b_01.yaml
Handheld-B-02 experiments/narrowwide/avia_handheld_b_02.yaml
SubT-MRS (3 sequences)

Dataset and downloads.

Prepare PointCloud2 input first. Decode /velodyne_packets with a Velodyne VLP-16 driver to /velodyne_points (sensor_msgs/PointCloud2) with per-point timing, preserving /imu/data and original timestamps. GenZ-LIO does not consume raw packets directly.

Multi-Floor spans 0.bag–2.bag; Laurel-Cavern spans 0.bag–10.bag. Process all parts in timestamp order into one continuous prepared recording. Use one prepared .bag per sequence for the playback example above.

Sequence YAML (config:=)
Long-Corridor experiments/subt_mrs/vlp16_long_corridor.yaml
Multi-Floor experiments/subt_mrs/vlp16_multi_floor.yaml
Laurel-Cavern experiments/subt_mrs/vlp16_laurel_cavern.yaml
HILTI 2021 (2 sequences)

Dataset and downloads.

Use the MID-70 recordings with these experiment configurations.

Sequence YAML (config:=)
Basement-04 experiments/hilti21/mid70_hilti21_basement04.yaml
Drone-Arena experiments/hilti21/mid70_hilti21_drone_arena.yaml
HILTI 2022 (3 sequences)

Dataset and downloads.

Use the Pandar32 recordings with these experiment configurations.

Sequence YAML (config:=)
Exp-10 experiments/hilti22/pandar32_hilti22_exp10.yaml
Exp-16 experiments/hilti22/pandar32_hilti22_exp16.yaml
Exp-18 experiments/hilti22/pandar32_hilti22_exp18.yaml
M3DGR (4 sequences)

Dataset and downloads.

Sequence YAML (config:=)
Corridor-01 experiments/m3dgr/avia_corridor_01.yaml
Corridor-02 experiments/m3dgr/avia_corridor_02.yaml
GNSS-denial-01 experiments/m3dgr/avia_gnss_denial.yaml
GNSS-denial-02 experiments/m3dgr/avia_gnss_denial.yaml
Oxford Spires (6 sequences)

Dataset · Download.

Split recordings: christ_church-01 and christ_church-02 each contain two bag parts (..._0.bag and ..._1.bag). Include both in timestamp order, preserving topics, message contents, and timestamps. Using only the first part runs an incomplete sequence. The other four sequences have one source bag each. ROS 1 can play both parts together without merging. Start GenZ-LIO with the matching YAML, then run this in the playback terminal:

rosbag play --delay 1 "{path_to_bag}/{sequence}_0.bag" "{path_to_bag}/{sequence}_1.bag"

Replace the placeholders with the actual filenames; both parts are replayed in timestamp order.

Sequence YAML (config:=)
christ_church-01 experiments/oxford_spires/hesai64_christ_church.yaml
christ_church-02 experiments/oxford_spires/hesai64_christ_church.yaml
christ_church-05 experiments/oxford_spires/hesai64_christ_church.yaml
blenheim_palace-01 experiments/oxford_spires/hesai64_blenheim_palace.yaml
blenheim_palace-02 experiments/oxford_spires/hesai64_blenheim_palace.yaml
blenheim_palace-05 experiments/oxford_spires/hesai64_blenheim_palace.yaml

2) For your own sensor, copy a template, calibrate it, then open RViz:

If you use a Velodyne LiDAR, start with the following template:

cp ~/catkin_ws/src/genz-lio/ros/config/default/velodyne.yaml ~/catkin_ws/src/genz-lio/ros/config/my_robot.yaml

Edit my_robot.yaml for your topics, sensor type, per-point timing, noise, and LiDAR-to-IMU calibration. The default extrinsics are placeholders. Then launch:

roslaunch genz_lio odometry.launch config:=my_robot.yaml

In the second terminal, play your own recording instead of the benchmark above. For live input, start your LiDAR/IMU drivers instead of the bag player. The ROS node publishes /Odometry; it does not automatically save a trajectory.

Parameter tuning guidance is available in the parameter guide.

See ros/README.md for TUM/KITTI odometry saving, ROS 1-to-ROS 2 bag conversion, published topics, and troubleshooting.

ROS 2 support

Install, build, and run with RViz (Humble / Jazzy)

1. Install dependencies

Start with ROS 2 Humble on Ubuntu 22.04 or Jazzy on Ubuntu 24.04. Use a separate shell and workspace for each distribution.

# ROS 2: use jazzy instead of humble on Ubuntu 24.04.
source /opt/ros/humble/setup.bash
sudo apt-get update
sudo apt-get install -y git build-essential cmake libeigen3-dev libboost-dev \
    libyaml-cpp-dev libpcl-dev python3-colcon-common-extensions \
    ros-${ROS_DISTRO}-rclcpp ros-${ROS_DISTRO}-rclpy ros-${ROS_DISTRO}-pcl-conversions \
    ros-${ROS_DISTRO}-tf2-ros ros-${ROS_DISTRO}-visualization-msgs \
    ros-${ROS_DISTRO}-launch-ros ros-${ROS_DISTRO}-rosbag2 ros-${ROS_DISTRO}-rviz2

For Livox CustomMsg input, complete the optional driver build below before building GenZ-LIO. PointCloud2 input does not require this step.

Livox ROS 2: build CustomMsg support (optional)

For livox_ros_driver2/msg/CustomMsg, install Livox-SDK2 following its build instructions, then build livox_ros_driver2 in a dedicated workspace. Its build script clears that workspace's build/install directories, so keep it separate from GenZ-LIO and use a different workspace per ROS distribution.

In the same Humble or Jazzy shell used above:

mkdir -p ~/livox_ros2_ws/src
git clone https://github.com/Livox-SDK/livox_ros_driver2.git ~/livox_ros2_ws/src/livox_ros_driver2
cd ~/livox_ros2_ws/src/livox_ros_driver2
MAKEFLAGS="-j2" bash build.sh "$ROS_DISTRO"
source ~/livox_ros2_ws/install/setup.bash

The driver build accepts humble or jazzy. Keep this shell for the next step. For bag replay, the recorded CustomMsg type must match the ROS 2 driver's type; renaming a topic does not convert ROS 1 messages into ROS 2 messages.

2. Build GenZ-LIO

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/cocel-postech/genz-lio.git
cd ~/ros2_ws
MAKEFLAGS="-j2" colcon build --packages-select genz_lio --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash

MAKEFLAGS="-j2" limits compilation to two parallel jobs to reduce peak memory use. This does not change runtime.max_threads during odometry.

If using Livox CustomMsg, check log/latest_build/genz_lio/stdout.log for Livox CustomMsg support enabled. If the driver was added after GenZ-LIO was built, source its workspace and force CMake to detect it:

cd ~/ros2_ws
MAKEFLAGS="-j2" colcon build --packages-select genz_lio --cmake-force-configure \
    --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash

3. Run

Use the package's config/experiments/ YAMLs for the paper's benchmark sequences, or a calibrated config/default/ template for your own sensor. config:= accepts a path relative to the package's config/ directory, or an absolute YAML path.

1) For reproducing benchmark experiments (e.g., NarrowWide Handheld-A-01)

Start GenZ-LIO with RViz in the first terminal:

source ~/ros2_ws/install/setup.bash
ros2 launch genz_lio odometry.launch.py \
    config:=experiments/narrowwide/vlp16_handheld_a_01.yaml

Wait for the node to start, then play the recording in a second terminal:

source ~/ros2_ws/install/setup.bash
ros2 bag play --delay 1 "{path_to_rosbag2_recording}"

--delay 1 allows discovery before playback starts, helping preserve the initial sensor messages. Increase it if discovery is slow.

Use a rosbag2 recording of Handheld-A-01 with the original sensor topics, timestamps, and message fields. The command above expects a rosbag2 directory, not a ROS 1 .bag; follow the bag conversion guide if needed. Source install/setup.bash in every terminal, including the bag player and drivers. With Fast DDS, this also applies the package's default transport profile unless you have already selected your own profile. GenZ-LIO consumes PointCloud2 or supported Livox CustomMsg plus IMU messages; raw Velodyne packets must first be decoded to PointCloud2 with point timing.

▶️ All benchmark sequences: downloads and ROS 2 configurations (42 sequences)

Use the YAML path below as the config:= argument in the launch command above. Paths are relative to the package's config/ directory.

Downloads may contain ROS 1 bags. Prepare rosbag2 recordings for ROS 2 playback, preserving sensor topics, timestamps, per-point timing, and IMU data. Livox CustomMsg recordings must use livox_ros_driver2/msg/CustomMsg; renaming the topic alone does not convert the message type.

GEODE (7 sequences)

Dataset · Download.

Use the Alpha (Velodyne) recordings for these YAMLs.

Sequence YAML (config:=)
Stairs experiments/geode/vlp16_stairs.yaml
Waterways-Short experiments/geode/vlp16_waterways_short.yaml
Waterways-Medium experiments/geode/vlp16_waterways_medium.yaml
Waterways-Long experiments/geode/vlp16_waterways_long.yaml
Offroad-02 experiments/geode/vlp16_offroad.yaml
Offroad-04 experiments/geode/vlp16_offroad.yaml
Offroad-07 experiments/geode/vlp16_offroad.yaml
ENWIDE (4 sequences)

Dataset · Download.

Sequence YAML (config:=)
Katzensee-S experiments/enwide/os128_enwide.yaml
Katzensee-D experiments/enwide/os128_enwide.yaml
Intersection-S experiments/enwide/os128_enwide.yaml
Intersection-D experiments/enwide/os128_enwide.yaml
NTU VIRAL (3 sequences)

Dataset and downloads.

Sequence YAML (config:=)
SPMS-01 experiments/ntu_viral/os16_spms_01.yaml
SPMS-02 experiments/ntu_viral/os16_spms_02.yaml
SPMS-03 experiments/ntu_viral/os16_spms_03.yaml
SuperLoc (4 sequences)

Dataset and downloads.

Download Cave01, Cave02, Cave04, and Corridor02 from the SuperLoc dataset table.

Sequence YAML (config:=)
Cave-01 experiments/superloc/vlp16_cave.yaml
Cave-02 experiments/superloc/vlp16_cave.yaml
Cave-04 experiments/superloc/vlp16_cave.yaml
Corridor-02 experiments/superloc/vlp16_corridor_02.yaml
NarrowWide (6 sequences)

Dataset and downloads.

Dataset information and download instructions are maintained in the NarrowWide repository.

Sequence YAML (config:=)
Tracked-01 experiments/narrowwide/mid70_tracked_01.yaml
Tracked-02 experiments/narrowwide/mid70_tracked_02.yaml
Handheld-A-01 experiments/narrowwide/vlp16_handheld_a_01.yaml
Handheld-A-02 experiments/narrowwide/vlp16_handheld_a_02.yaml
Handheld-B-01 experiments/narrowwide/avia_handheld_b_01.yaml
Handheld-B-02 experiments/narrowwide/avia_handheld_b_02.yaml
SubT-MRS (3 sequences)

Dataset and downloads.

Prepare PointCloud2 input first. Decode /velodyne_packets with a Velodyne VLP-16 driver to /velodyne_points (sensor_msgs/PointCloud2) with per-point timing, preserving /imu/data and original timestamps. GenZ-LIO does not consume raw packets directly.

Multi-Floor spans 0.bag–2.bag; Laurel-Cavern spans 0.bag–10.bag. Process all parts in timestamp order into one continuous prepared recording. Record or convert the prepared data to one rosbag2 recording per sequence.

Sequence YAML (config:=)
Long-Corridor experiments/subt_mrs/vlp16_long_corridor.yaml
Multi-Floor experiments/subt_mrs/vlp16_multi_floor.yaml
Laurel-Cavern experiments/subt_mrs/vlp16_laurel_cavern.yaml
HILTI 2021 (2 sequences)

Dataset and downloads.

Use the MID-70 recordings with these experiment configurations.

Sequence YAML (config:=)
Basement-04 experiments/hilti21/mid70_hilti21_basement04.yaml
Drone-Arena experiments/hilti21/mid70_hilti21_drone_arena.yaml
HILTI 2022 (3 sequences)

Dataset and downloads.

Use the Pandar32 recordings with these experiment configurations.

Sequence YAML (config:=)
Exp-10 experiments/hilti22/pandar32_hilti22_exp10.yaml
Exp-16 experiments/hilti22/pandar32_hilti22_exp16.yaml
Exp-18 experiments/hilti22/pandar32_hilti22_exp18.yaml
M3DGR (4 sequences)

Dataset and downloads.

Sequence YAML (config:=)
Corridor-01 experiments/m3dgr/avia_corridor_01.yaml
Corridor-02 experiments/m3dgr/avia_corridor_02.yaml
GNSS-denial-01 experiments/m3dgr/avia_gnss_denial.yaml
GNSS-denial-02 experiments/m3dgr/avia_gnss_denial.yaml
Oxford Spires (6 sequences)

Dataset · Download.

Split recordings: christ_church-01 and christ_church-02 each contain two bag parts (..._0.bag and ..._1.bag). Include both in timestamp order, preserving topics, message contents, and timestamps. Using only the first part runs an incomplete sequence. The other four sequences have one source bag each. Prepare one continuous rosbag2 recording containing both parts per sequence.

Sequence YAML (config:=)
christ_church-01 experiments/oxford_spires/hesai64_christ_church.yaml
christ_church-02 experiments/oxford_spires/hesai64_christ_church.yaml
christ_church-05 experiments/oxford_spires/hesai64_christ_church.yaml
blenheim_palace-01 experiments/oxford_spires/hesai64_blenheim_palace.yaml
blenheim_palace-02 experiments/oxford_spires/hesai64_blenheim_palace.yaml
blenheim_palace-05 experiments/oxford_spires/hesai64_blenheim_palace.yaml

2) For your own sensor, copy a template, calibrate it, then open RViz:

If you use a Velodyne LiDAR, start with the following template:

cp ~/ros2_ws/src/genz-lio/ros/config/default/velodyne.yaml ~/my_robot.yaml

Edit my_robot.yaml for your topics, sensor type, per-point timing, noise, and LiDAR-to-IMU calibration. The default extrinsics are placeholders. Then launch:

ros2 launch genz_lio odometry.launch.py config:="$HOME/my_robot.yaml"

In the second terminal, play your own recording instead of the benchmark above. For live input, start your LiDAR/IMU drivers instead of the bag player. The ROS node publishes /Odometry; it does not automatically save a trajectory.

Parameter tuning guidance is available in the parameter guide.

See ros/README.md for TUM/KITTI odometry saving, ROS 1-to-ROS 2 bag conversion, troubleshooting, and DDS settings.

📝 Citation

If you use GenZ-LIO, please cite our paper.

@article{lee2026genzlio,
  title={{GenZ-LIO: Generalizable LiDAR-Inertial Odometry Beyond Confined--Open Boundaries}},
  author={Lee, Daehan and Lim, Hyungtae and Kim, Seongjun and Rho, Soonbin and Lee, Changhyeon and Park, Sanghyun and Hong, Junwoo and Choi, Eunseon and Jo, Hyunyoung and Han, Soohee},
  journal={arXiv preprint arXiv:2603.16273},
  year={2026}
}

For LiDAR-only odometry, see GenZ-ICP (arXiv, IEEE Xplore).

@article{lee2024genzicp,
  author={Lee, Daehan and Lim, Hyungtae and Han, Soohee},
  title={{GenZ-ICP: Generalizable and Degeneracy-Robust LiDAR Odometry Using an Adaptive Weighting}},
  journal={IEEE Robotics and Automation Letters (RA-L)},
  year={2025},
  volume={10},
  number={1},
  pages={152--159},
  doi={10.1109/LRA.2024.3498779}
}

✨ Contributors

Bug reports, documentation improvements, and pull requests are always welcome.

🙏 Acknowledgments and license

Many thanks to the HKU-MARS Lab and PRBonn for their open-source contributions to the robotics community.

GenZ-LIO builds on PV-LIO, VoxelMap, FAST-LIO, and IKFoM. We also thank the KISS-ICP project.

GenZ-LIO is distributed under GPL-2.0; dependency modifications are listed in PATCHES.md.

📫 Contact

For questions or issues, open an issue or contact Daehan Lee ( ✉️ daehanlee at postech dot ac dot kr)

Releases

Packages

Contributors

Languages