Skip to content

Repository files navigation

GICP–GNSS Odom Localizer

Hesai Course 2 Autoware localization-interface visualization in RViz

gicp_gnss_odom_localizer is a ROS 2 planar LiDAR–IMU–GNSS localization stack for research and engineering evaluation. It provides LiDAR/IMU local odometry, optional GNSS-based global localization, and an isolated scan-to-submap output through standard ROS 2 messages and TF. Autoware support is an optional downstream localization-interface adapter, not a core dependency.

The stack is prior-map-free: it does not require a PCD map. Its standard profiles also do not require wheel speed or a CUDA-capable GPU.

Intended uses and key capabilities

  • Robot odometry: estimate local x, y, and yaw from LiDAR and IMU only.
  • Global vehicle or robot localization: anchor LiDAR/IMU odometry with NMEA GGA/RTK GNSS while applying the calibrated antenna lever arm.
  • No required PCD map or wheel speed: it does not require a PCD map. Its standard profiles also do not require wheel speed or a CUDA-capable GPU.
  • Optional scan-to-submap output: run rolling-submap matching in isolated processes without feeding corrections into the scan-to-scan odometer.
  • General ROS 2 integration: consume the odometry topics and TF directly in a robot, navigation, mapping, or automated-driving stack.
  • Optional Autoware example: use the separate adapter to translate the same estimator outputs into Autoware pose, kinematic-state, twist-with-covariance, acceleration, and TF interfaces. The core estimators do not depend on Autoware.
  • CPU-only operation: the estimator has no CUDA dependency; CPU-only operation has also been demonstrated.

Operating modes

Mode Processing path Intended trade-off
Scan-to-scan Strict deskew, scan-to-scan GICP, and the SE(2) smoother Default path with fewer processes; publishes /localization/gyro_lidar_odom and can optionally feed GNSS fusion. CPU and RSS are not yet quantified.
Scan-to-submap Keeps the scan-to-scan path unchanged and adds accepted-scan snapshots, an external rolling-submap matcher, and isolated local/global compositors Adds computation and latency for separate scan-to-submap outputs at /localization/precision_local_odom and, with a healthy global anchor, /localization/precision_global_odom.

The scan-to-submap mode runs alongside the primary scan-to-scan odometer. Its corrections never feed back into scan-to-scan, and the mode is selected at launch rather than switched while running.

The estimator is planar SE(2), intended for research and engineering evaluation, and is not a safety-certified localization system. CPU utilization and RSS have not yet been measured; CPU-only support is not a claim of low CPU load.

Build

ROS 2 Jazzy on Ubuntu 24.04 is the primary target.

git clone --recurse-submodules \
  https://github.com/KariControl/gicp_gnss_odom_localizer.git
cd gicp_gnss_odom_localizer

source /opt/ros/jazzy/setup.bash
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash

Quick start

Every terminal must source ROS 2 and install/setup.bash. Publish the calibrated sensor static transforms before expecting accepted localization output.

The Docker runner accepts --rqt-robot-monitor to open the aggregated /diagnostics_agg view in a graphical session. It is disabled by default.

Scan-to-scan mode: LiDAR + IMU local odometry

ros2 launch pure_odometry_bringup odometry_container.launch.py \
  use_gnss:=false \
  points_input_topic:=/points_raw \
  imu_input_topic:=/imu

The primary output is /localization/gyro_lidar_odom in the odom frame.

For rosbag replay, start the estimator with simulated time:

ros2 launch pure_odometry_bringup odometry_container.launch.py \
  use_sim_time:=true \
  use_gnss:=false \
  points_input_topic:=/points_raw \
  imu_input_topic:=/imu

Then use the replay helper in a second sourced terminal:

BAG_DIR=/path/to/recording
POINTS_TOPIC=/recorded/pointcloud
IMU_TOPIC=/recorded/imu

./script/play_localization_bag.sh \
  --bag "$BAG_DIR" --points "$POINTS_TOPIC" --imu "$IMU_TOPIC"

The helper isolates recorded dynamic localization TF by default while retaining recorded static sensor transforms.

Scan-to-submap mode: rolling-submap output

Keep the scan-to-scan odometer in scan_to_scan, enable only its accepted-scan snapshot bridge, and start the overlay in another sourced terminal:

ros2 launch pure_odometry_bringup odometry_container.launch.py \
  use_gnss:=false \
  odom_override_param:=$(ros2 pkg prefix pure_precision_bringup)/share/pure_precision_bringup/config/submap_snapshot_override.yaml

ros2 launch pure_precision_bringup precision_overlay.launch.py

This adds /localization/precision_local_odom; it does not replace or feed back into /localization/gyro_lidar_odom.

LiDAR + IMU + GNSS global localization

When the packaged projection matches the deployment's map metadata, launch with the calibrated GNSS static TF and no evaluation-origin override:

ros2 launch pure_odometry_bringup odometry_container.launch.py \
  use_gnss:=true

For a different map projection, create a deployment-owned ROS parameter override whose projector, datum, origin, and scale match that map's projector metadata; do not pass map_projector_info.yaml directly as a ROS parameter file.

The principal global output is /localization/ekf_odom, with the corresponding map -> odom TF when TF publication is enabled. See NMEA observation semantics and GNSS initialization and recovery before deployment.

Optional integration example: Autoware localization-interface integration

Autoware is one possible downstream consumer, not a dependency of the localization stack. The optional Docker workflow connects the standard fused output to Autoware localization interfaces without requiring a host Autoware workspace.

The retained autoware_lsim filenames and command names are project-local compatibility identifiers, not names of an official Autoware component or workflow.

Before replaying a recording, the version-selected Autoware 1.9.0 interface and diagnostic contract can be exercised without a bag:

./script/run_autoware_localization_contract_docker.sh

This deterministic test launches the real Autoware pose_instability_detector and localization_error_monitor, checks all adapter outputs, requires exactly one runtime /tf publisher endpoint owned by the adapter for map -> base_link, confirms that no competing dynamic base_link parent is observed, and drives both monitors through normal, injected-fault, and recovery phases. The Jazzy package tests additionally launch the container, standalone, main GNSS, NMEA-wrapper, LiDAR-IMU-only, and precision-overlay profiles in isolated ROS domains. They check the exact /tf endpoint multiset, each owner's effective publish_tf and frame parameters, and the globally emitted dynamic-edge set. Jazzy's Python API cannot attribute an individual TF sample to its endpoint GID, and a bag alone cannot identify two publishers that emit the same TF edge. This is an interface and diagnostic-response test; it does not launch planning or control, assess localization accuracy, or detect input dropout.

A minimal run is:

BAG_DIR=/path/to/recording

./script/run_autoware_lsim_docker.sh \
  --bag "$BAG_DIR" \
  --points /recorded/pointcloud \
  --imu /recorded/imu

See the Docker guide for GNSS, sensor profiles, RViz, and scan-to-submap options.

For an optional presentation view, add --rviz --rviz-sample-vehicle. The sample vehicle is visualization only; verify its frame convention and visual height offset for your deployment.

Replay the public synthetic rosbag

Complete the build steps first. Then start the LiDAR/IMU estimator from the repository root in terminal 1:

source /opt/ros/jazzy/setup.bash
source install/setup.bash

ros2 launch pure_odometry_bringup lidar_imu_only.launch.py \
  use_sim_time:=true \
  use_imu_deskew:=true \
  points_input_topic:=/points_raw \
  imu_input_topic:=/imu

Replay the included synthetic rosbag from terminal 2:

source /opt/ros/jazzy/setup.bash
source install/setup.bash

./script/play_localization_bag.sh \
  --bag data/synthetic_output_pointcloud2 \
  --points /pandar_points_ex \
  --imu /sensor/imu/data_raw \
  --clock-frequency 100 \
  --tf-policy isolate-dynamic \
  -- --disable-keyboard-controls \
  --topics /pandar_points_ex /sensor/imu/data_raw /tf_static

Keep --tf-policy isolate-dynamic; isolate-all removes the static sensor transforms required by the fail-closed deskewer and odometer. The resulting local odometry is published on /localization/gyro_lidar_odom.

Evaluation results

The evaluation uses GLIM as a correlated LiDAR/IMU pseudo-reference; it is not independent ground truth. The RMSE values compare the estimator output with that pseudo-reference. Representative results are summarized below.

The published Hesai primary accuracy result uses exact-initial-pose alignment. Its initialization timing and typed-authority checks are evaluated separately and do not contribute to the primary RMSE values or plots.

Sensor Scope XY RMSE (scan-to-scan mode) XY RMSE (submap mode) Yaw RMSE (scan-to-scan mode) Yaw RMSE (submap mode)
Velodyne 32-Line + External IMU (No GNSS) local 0.495 m 0.216 m 0.314° 0.123°
MID-360 + Internal IMU (No GNSS) local 0.630 m 0.294 m 2.906° 0.602°
Hesai + External IMU + RTK-GNSS local 1.457 m 0.389 m 0.917° 0.792°
Hesai + External IMU + RTK-GNSS global 1.406 m 0.516 m 2.099° 1.568°

Representative plots

Velodyne 32-Line + External IMU

Velodyne LiDAR and external IMU scan-to-scan and scan-to-submap trajectories

Scan-to-scan and scan-to-submap.

Assessment: Scan-to-submap met the project's predefined accuracy criteria over the accepted 45.991 s prefix, with 0.2156 m XY RMSE and 0.1227 deg yaw RMSE.

Livox MID-360 + Internal IMU

Livox MID-360 and internal IMU tuned scan-to-scan and rolling scan-to-submap trajectories

Tuned scan-to-scan and rolling scan-to-submap.

Assessment: Rolling scan-to-submap passed all predefined acceptance criteria over the complete evaluated interval, with 0.2941 m XY RMSE and 0.6024 deg yaw RMSE. This is sufficient for this recorded LiDAR/IMU-only evaluation.

Hesai 32-Line + IMU + RTK GNSS

hesai-course-2-rviz-inline.webm

The replay is a visualization-only example, not accuracy, performance, or passing-run evidence.

The global plots compare the GNSS-anchored scan-to-scan and scan-to-submap outputs.

Hesai Course 2 global XY error

Hesai 32-Line, IMU, and RTK GNSS Course 2 scan-to-scan and scan-to-submap global XY error during GNSS outage and recovery

Hesai Course 2 global yaw error

Hesai 32-Line, IMU, and RTK GNSS Course 2 scan-to-scan and scan-to-submap global yaw error during GNSS outage and recovery

The hatched span marks the 117.252446 s RTK-Q4-unavailable interval. The red marker shows Q4 returning; the green marker shows finite global localization resuming 4.147737 s later. The separately evaluated fusion-outage RMSE window is 121.500028 s because it follows the fusion-state definition rather than the raw Q4-gap boundaries.

Assessment: Across 7,805 exact common global samples, scan-to-submap reduced XY/yaw RMSE from 1.406461 m / 2.099877 deg to 0.516555 m / 1.568195 deg. During the 121.500028 s fusion-outage metric window, its XY/yaw RMSE was 0.579715 m / 0.861855 deg, and finite global localization resumed 4.147737 s after RTK-Q4 returned.

The local plots compare scan-to-scan with scan-to-submap odometry.

Hesai Course 2 local XY error

Hesai 32-Line, IMU, and RTK GNSS Course 2 scan-to-scan and scan-to-submap local XY error

Hesai Course 2 local yaw error

Hesai 32-Line, IMU, and RTK GNSS Course 2 scan-to-scan and scan-to-submap local yaw error

Assessment: Across 8,477 exact common local samples, scan-to-submap reduced XY/yaw RMSE from 1.457149 m / 0.917636 deg to 0.389683 m / 0.792790 deg.

These are dataset-scoped engineering acceptance results. They do not establish fitness for safety-critical use or accuracy across other datasets, environments, or sensor installations.

See the evaluation pages for full-size plots, error distributions, methodology, provenance, and limitations.

Required data

The normal LiDAR/IMU configuration requires:

  • sensor_msgs/msg/PointCloud2 with a valid per-point time field;
  • monotonic sensor_msgs/msg/Imu samples covering each scan;
  • calibrated static transforms from base_link to the LiDAR and IMU frames.

GNSS operation additionally requires NMEA GGA input, a projection configuration that matches the deployment's map metadata, and a calibrated base_link to GNSS-antenna transform. The packaged runtime parameters in param/param.yaml match the projector type, datum, origin, and scale in map_projector_info.yaml. The metadata file is not itself a ROS parameter file. Optional Doppler or secondary-antenna topics can provide additional heading observations.

See known limitations before selecting this stack for a new sensor, environment, or vehicle.

Public synthetic rosbag sample

The repository includes a 6.4 MiB fully procedural LiDAR/IMU rosbag for trying the localization stack without a sensor recording. It uses a Pandar-style PointCloud2 layout and nominal LiDAR/IMU rates; all points, motion, timestamps, and transforms are generated procedurally.

The current regression run deskewed 121/121 scans without fallback, corrected 1,241/1,241 IMU samples, accepted 118/120 post-initialization registrations, and ended within 0.0667 m / 0.359 deg of the analytic endpoint. These results cover one artificial scene and are not a sensor benchmark or real-world accuracy claim. Follow Replay the public synthetic rosbag for the commands. See the dataset card for provenance, schema, hashes, regeneration, and limitations.

Packages

The usual entry points are pure_odometry_bringup for scan-to-scan and pure_precision_bringup for the optional scan-to-submap overlay. The remaining packages are components, interfaces, and narrowly scoped validation support installed from the same repository.

Package Role
pure_odometry_bringup Primary launch, configuration, and RViz entry point for the LiDAR–IMU–GNSS localization stack.
pure_precision_bringup Launch and configuration entry point for the optional isolated scan-to-submap overlay.
pure_localization_contract Reusable runtime TF ownership probe and supported-profile system test.
pure_localization_evaluation_profiles Data-only package for recording-specific evaluation profiles and provenance manifests.
pure_localization_interface_adapter Converts fused odometry into configurable kinematic-state, pose, twist-with-covariance, acceleration, and map -> base_link interfaces; the optional Autoware workflow consumes these outputs.
pure_imu_undistortion Validates point timing and deskews LiDAR scans from IMU motion, with optional translation compensation.
pure_lidar_gyro_odometer Produces scan-to-scan planar LiDAR–IMU odometry with fixed-lag SE(2) smoothing.
pure_nmea_gnss_conversion Converts NMEA GGA and optional heading observations into explicit GNSS fusion inputs.
pure_gnss_map_odom_fusion Anchors local odometry in map using multi-sample GNSS initialization and bounded outage recovery.
pure_lidar_submap_matcher Computes isolated rolling-submap SE(2) corrections from accepted LiDAR scans.
pure_precision_global_localizer Composes scan-to-submap local output and guarded GNSS-anchored scan-to-submap global output without publishing TF.
pure_gnss_msgs Defines the GNSS observation messages shared by the conversion and fusion packages.
pure_lidar_msgs Defines the exact-key scan and correction messages used by the scan-to-submap branch.

small_gicp is an external MIT-licensed dependency included as a Git submodule; it is not a first-party package in this project.

Validated environments

Use case Operating system ROS 2 Autoware Status and scope
Standalone localization Ubuntu 24.04 Jazzy Not required Primary source-build and rosbag-replay target.
Optional Autoware integration Ubuntu 24.04 Jazzy 1.9.0 Localization-interface and diagnostic-monitor integration in the supplied CPU-only Docker workflow.
Other combinations Not currently claimed; they may work but have not been validated by this project.

The standalone estimator path does not require Autoware. The deterministic contract covers localization-facing topics, TF consistency, and the two Autoware monitors; the public synthetic replay covers the live localizer path and simulation time without GNSS. Neither establishes full-stack integration, closed-loop driving, planning/control readiness, or safety certification. CPU-only execution was demonstrated, but CPU utilization and memory usage were not measured.

Sensor-to-output flow

These diagrams intentionally show functional roles and major signal flow, not the exact ROS node graph or message contract. Exact interfaces and package mapping are in Architecture. Without GNSS, the LiDAR/IMU path still publishes local odometry. In the figures, GNSS "heading" is optional; a valid position-only observation may carry no heading. The exact sensor input types are nmea_msgs/msg/Sentence, sensor_msgs/msg/Imu, and sensor_msgs/msg/PointCloud2.

Scan-to-scan mode

Scan-to-scan LiDAR, IMU, and optional GNSS localization data flow

The deskew function publishes only the deskewed point cloud. The LiDAR–gyro odometry function is the source of both the base-frame, yaw-bias-corrected IMU stream and the stop state used by the single-antenna heading logic. Stop detection requires a quiet IMU and, when a causal wheel or LiDAR speed estimate exists, a speed below its threshold; with neither speed source it deliberately falls back to IMU-only detection.

Scan-to-submap mode

Isolated scan-to-submap precision localization data flow

Each SubmapScan carries an accepted filtered cloud, its unmodified scan-to-scan pose, and the immutable exact key (odom_session_id, odom_generation, sequence, header.stamp). Both precision nodes consume that snapshot: the matcher estimates a persistent full-SE(2) correction, while the local/global compositor validates the corresponding key before applying it to continuous raw odometry. The typed FusionAuthority permits global-anchor updates only while the existing fusion is fresh and fully healthy. Direct GNSS input is only an outage-yaw guard in the default profile, not an alternative global-position authority.

The scan-to-submap branch publishes separate message outputs, no TF, and no feedback into the scan-to-scan odometer or existing GNSS fusion. Full frame, component, and failure-isolation details are in Architecture; the complete stop rule is documented in LiDAR/IMU odometry.

The container, standalone, and standalone-with-NMEA launches assign odom -> base_link to the gyro odometer. The evaluation-oriented lidar_imu_only launch leaves it disabled by default. The Autoware profile also disables gyro-odometer and fusion TF publication; its launch configuration assigns the direct map -> base_link transform to the adapter.

Validation

python3 tools/check_repository.py
./tools/run_reference_tests.sh
colcon test --event-handlers console_direct+
colcon test-result --verbose

See validation scope for the full release and recording gates.

Documentation

Citation and related work

Machine-readable citation metadata is provided in CITATION.cff. The registration path builds on Generalized-ICP (Segal et al., 2009) through small_gicp (Koide, 2024). Published trajectory evaluation uses GLIM (Koide et al., 2024) only as a correlated LiDAR/IMU pseudo-reference.

LIO-SAM (Shan et al., 2020) and FAST-LIO2 (Xu et al., 2022) are representative tightly coupled LiDAR-inertial systems included as related architectural context. They have not been evaluated as head-to-head baselines in this repository.

See CONTRIBUTING.md and SECURITY.md. Licensed under Apache-2.0. Third-party attribution is in THIRD_PARTY_NOTICES.md.

About

gicp_gnss_odom_localizer is a ROS 2 planar LiDAR–IMU–GNSS localization stack for research and engineering evaluation. It provides LiDAR/IMU local odometry, optional GNSS-based global localization, and an isolated scan-to-submap output through standard ROS 2 messages and TF.

Topics

Resources

Contributing

Security policy

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages