This document describes the runtime processing flow of the lidar_odometry_node.
The node performs:
- LiDAR scan registration
- IMU-assisted stationary detection
- ICP-based motion estimation
- Rolling submap management
- Keyframe extraction
- TF and odometry publication
+----------------------+
| /imu/data |
+----------+-----------+
|
v
IMU Callback Thread
|
v
IMU Motion Classification
|
|
+----------------------+ |
| /points | |
+----------+-----------+ |
| |
v |
Point Cloud Callback |
| |
+-----------+-----------+
|
v
Point Cloud Cleanup
|
v
TF Validation
|
v
lidar_frame -> base_link
|
v
Startup Anchor Map
|
v
Motion Prediction
|
v
ICP Target Build
|
v
ICP Alignment
|
v
Alignment Validation
|
v
Stationary Suppression
|
v
Pose State Update
|
+-------------+-------------+
| |
v v
Submap Update Keyframe Logic
| |
+-------------+-------------+
|
v
TF / Odom Publish
|
v
Diagnostic Publishers
The node uses:
rclcpp::executors::MultiThreadedExecutorwith two worker threads.
Processes:
- IMU statistics
- Gyroscope analysis
- Accelerometer variance
- Stationary classification
Processes:
- Point cloud cleanup
- ICP alignment
- Pose updates
- Submaps
- Publishing
At startup the node does NOT immediately perform odometry.
Instead, it accumulates multiple static scans to build an anchor map.
Node Startup
↓
Initialize ROS interfaces
↓
Wait for static TFs
↓
Receive point clouds
↓
Transform lidar → base_link
↓
Accumulate static scans
↓
Build immutable anchor map
↓
Initialize active submap
↓
Begin ICP odometry
The startup anchor map is:
anchor_submap_This map:
- remains immutable
- provides stable ICP initialization
- prevents startup drift
- improves local registration stability
Each incoming point cloud executes the following pipeline.
last_msg_time_ = now();Purpose:
- confirms sensor activity
- prevents watchdog shutdown
Before processing, the node verifies:
base_link ↔ lidar
base_link ↔ imu
are valid.
If unavailable:
skip frame
retry TF acquisition
Incoming ROS cloud:
sensor_msgs::msg::PointCloud2is converted into:
pcl::PointCloud<pcl::PointXYZI>Invalid points are removed:
pcl::removeNaNFromPointCloud(...)This prevents:
- ICP instability
- numerical failures
- invalid transforms
The LiDAR scan is transformed:
lidar_frame → base_link
Result:
base_link_scan_This standardizes all registration into robot body coordinates.
Before odometry begins:
*local_map_ += *base_link_scan_;Multiple static scans accumulate into:
local_map_Once:
init_scans_total >= init_scans_the node:
- freezes the anchor map
- initializes active submap tracking
- enables ICP odometry
After initialization, every incoming scan executes ICP registration.
The node predicts robot motion using the previous ICP delta.
guess = lastAlignedTF_ * delta_pred;Assumptions:
- approximately constant velocity
- approximately constant direction
The predicted transform is bounded.
max_predict_dist_Prevents excessive jumps.
max_rotation_step_Prevents unstable rotational guesses.
The node evaluates whether the robot is stationary.
This uses:
- ICP translation statistics
- IMU motion analysis
Running statistics are computed from frame-to-frame motion:
| Variable | Purpose |
|---|---|
avgDistance_ |
Mean motion |
sigmaDistance_ |
Motion variance |
The IMU classifier produces:
| State | Meaning |
|---|---|
Moving |
Robot moving |
Stationary |
Robot stationary |
Unknown |
Insufficient confidence |
Motion suppression occurs when:
ICP indicates stationary
AND
IMU does not indicate moving
This suppresses:
- stationary drift
- ICP noise walk
- false motion accumulation
The node constructs a local registration target from rolling submaps.
Nearby submaps are selected by:
distance from current pose
within:
submap_radius_The target cloud may include:
- nearby rolling submaps
- immutable startup anchor map
The node performs ICP alignment.
| Input | Purpose |
|---|---|
| Source | Current LiDAR scan |
| Target | Local merged submaps |
| Guess | Predicted transform |
icp.align(aligned, guess);Result:
T_alignmentThe ICP solution is validated before use.
icp.hasConverged()Rejects failed solves.
icp.getFitnessScore()Rejects poor alignments.
Frame-to-frame motion is computed:
deltaMeasured_ =
lastAlignedTF_.inverse() * T_alignment;This becomes the next prediction basis.
If stationary:
freeze pose
zero motion delta
suppress drift
Otherwise:
update transform state
update prediction state
The active robot pose becomes:
T_odom_base_representing:
odom → base_link
If moving:
*active_submap_ += aligned;The aligned scan is inserted into the active rolling submap.
The current submap closes when:
| Condition | Threshold |
|---|---|
| Translation distance | submap_dist_thresh_ |
| Rotation change | submap_yaw_thresh_ |
| Point count | submap_max_points_ |
Close active submap
↓
Compute centroid
↓
Push into rolling submap list
↓
Discard oldest if necessary
↓
Start new active submap
The node evaluates whether a new keyframe should be created.
| Trigger | Description |
|---|---|
| Translation | Robot moved enough |
| Rotation | Robot rotated enough |
| Time | Too much time elapsed |
When triggered:
- full-resolution scan is used
- scan is transformed into odom frame
- pose is published
- cloud is published
The node publishes:
odom → base_link
for the ROS TF tree.
A diagnostic path message is updated and published.
Purpose:
- RViz visualization
- drift observation
- trajectory debugging
The node publishes:
nav_msgs::msg::Odometrycontaining:
- pose
- orientation
- timestamps
- frame IDs
The aligned ICP scan is published for RViz visualization.
A periodic watchdog timer validates sensor activity.
Timer tick
↓
Compute elapsed time since last message
↓
Compare against watchdog timeout
↓
If exceeded:
↓
Log error
↓
Shutdown ROS node
LiDAR Scan
↓
lidar_frame
↓
Transform
↓
base_link
↓
ICP Alignment
↓
odom
Current Scan
↓
Transform to base_link
↓
Predict pose
↓
Build ICP target
↓
Run ICP
↓
Validate result
↓
Update odometry pose
Create active submap
↓
Accumulate aligned scans
↓
Motion threshold exceeded
↓
Close submap
↓
Insert into rolling list
↓
Create new active submap
Motion detected
↓
Threshold exceeded
↓
Create keyframe
↓
Publish pose
↓
Publish cloud
| State | Description |
|---|---|
| Startup | Building anchor map |
| Active Tracking | ICP odometry running |
| Stationary | Motion suppression enabled |
| Recovery | ICP rejection / retry |
| Shutdown | Watchdog timeout |
The node rejects frames when:
- ICP fails
- fitness score too high
- TF unavailable
- cloud becomes empty
- NaN corruption detected
The architecture is optimized for:
- real-time operation
- long-duration runtime stability
- reduced stationary drift
- scalable local mapping
- future SLAM integration
- robust sensor fault handling
The lidar_odometry_node processing pipeline combines:
- LiDAR scan registration
- IMU-assisted motion classification
- rolling submap management
- ICP prediction and validation
- keyframe extraction
- ROS TF integration
to produce a robust real-time LiDAR odometry backend suitable for long-duration robotic operation.