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.
- 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.
| 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.
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.bashEvery 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.
ros2 launch pure_odometry_bringup odometry_container.launch.py \
use_gnss:=false \
points_input_topic:=/points_raw \
imu_input_topic:=/imuThe 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:=/imuThen 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.
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.pyThis adds /localization/precision_local_odom; it does not replace or feed back
into /localization/gyro_lidar_odom.
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:=trueFor 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.
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.shThis 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/imuSee 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.
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:=/imuReplay 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_staticKeep --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.
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° |
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.
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-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 Course 2 global yaw error
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 Course 2 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.
The normal LiDAR/IMU configuration requires:
sensor_msgs/msg/PointCloud2with a valid per-point time field;- monotonic
sensor_msgs/msg/Imusamples covering each scan; - calibrated static transforms from
base_linkto 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.
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.
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.
| 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.
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.
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.
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.
python3 tools/check_repository.py
./tools/run_reference_tests.sh
colcon test --event-handlers console_direct+
colcon test-result --verboseSee validation scope for the full release and recording gates.
- Evaluation results and published plots
- Architecture
- LiDAR/IMU odometry and scan-to-submap isolation
- NMEA position, covariance, and heading
- GNSS initialization and outage recovery
- Tuning and known limitations
- Docker-based Autoware localization-interface workflow
- Changelog and migration notes
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.