Skip to content
RobotnikAutomationPublic
forked from Pana1v/polka

About

One shot solution for your Lidar data processing, supports GPU Acceleration, deskewing, and much more....

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

POLKA

ROS 2 Humble ROS 2 Iron ROS 2 Jazzy ROS 2 Kilted ROS 2 Lyrical ROS 2 Rolling
Ubuntu 22.04 Ubuntu 24.04 Ubuntu 26.04
C++17 CUDA optional PCL colcon
License: Apache-2.0 GitHub stars GitHub issues Last commit

Polka Screencastfrom2026-07-2510-38-51-ezgif com-optimize   (2) image

Multi-LiDAR fusion node for ROS 2. Takes any mix of PointCloud2 and LaserScan sources and publishes one merged PointCloud2, one merged LaserScan, or both. Filters per source and on the output, deskews with IMU and odometry, and uses CUDA if you build it in. One node instead of a relay, filter, transform, merge and downsample chain.

Features in action

Each clip is polka with a different config, run on the TIERS multi-LiDAR dataset (Ouster OS1 + Livox Avia + Mid-360) and rendered headless with Open3D. doc/media/ has the scripts to regenerate them.

range filter
Range filter: keep points within a distance shell
angular filter
Angular filter: keep a yaw sector
box filter
Box filter: crop to an axis-aligned box
height cap
Height cap: clip to a z-range
angular invert flag
Angular invert flag: keep vs. exclude a sector
self filter
Self-filter: remove the robot's own footprint
voxel downsample
Voxel downsample: 69k to 5k points
dual output
Dual output: merged cloud plus flattened 2D scan
2D LaserScan merge
2D LaserScan merge: each beam colored by the sensor with the nearest return

three lidars on an articulated vehicle: sweep, deskew, filter, merge
Simulated: three lidars on the articulated rig from #2, geometry from its bag's TF. Each sweep is deskewed with IMU, odometry and steering-joint rate, filtered per source, merged and filtered again.

Performance

deskew time per point: polka, GLIM, rko_lio, LIO-SAM

Deskew. 3.3 ns per point rotation-only, 4.4 with translation: 1.5 to 7x cheaper than the deskew step of GLIM, rko_lio and LIO-SAM on the same public clouds. Method and versions in Performance.

CUDA. The GPU merge engine does transform, filter, voxel and scan flatten in one pass over the points, which pays off on heavy pipelines. On a filterless merge the CPU stays competitive — there is not enough per-point work to hide the kernel dispatch and the host-to-device copy. Build with -DWITH_CUDA=ON and it falls back to CPU on its own. It is not faster everywhere. Any binary package installed with apt is CPU-only: the ROS build farm has no CUDA toolchain, so the GPU engine is compiled out there. Build from source to get it.

Bandwidth. polka turns N sensor streams into one topic, so downstream nodes subscribe once instead of once per sensor. Voxel downsampling thins that cloud further if you want it, by as much as you set with leaf_size — in the demo clip 69k points become 5k, but that is one leaf size, not a fixed ratio or a 0.5.0 speedup.

Performance notes covers where the numbers come from and when CUDA stops paying off.

Features

  • Heterogeneous fusion: mix 3D PointCloud2 and 2D LaserScan sources freely
  • Dual output: merged PointCloud2, LaserScan, or both at once
  • Per-source and output filtering: range, angular, box, height cap, footprint (ego-body) exclusion, voxel downsample
  • Deskewing: per-point SE(3) motion correction, cost independent of point order; translation from odometry, IMU, or none; gyro bias learned at standstill; per-point timestamp auto-detect
  • CUDA acceleration: optional GPU merge engine, falls back to CPU
  • TF2 integration: automatic lookup with last-known-good fallback
  • Runtime reconfiguration: filters, outputs, deskewing and the source list all change live via ros2 param set, no restart
  • Diagnostics and a terminal dashboard: per-source rate, bandwidth and lag on /diagnostics, drift flags, and an optional polka_monitor TUI
  • Composable node: runs standalone or in a component container

Sensor and IMU support

Capability Supported How
3D PointCloud2 yes native
2D LaserScan yes projected and merged
Single global IMU yes motion_compensation.imu_topic
Multiple IMUs (per source) yes sources.<name>.imu_topic
Decentralized IMUs (different mounts) yes TF rotates angular velocity and acceleration into each sensor frame
External IMU per lidar yes any sensor_msgs/Imu frame with a TF to the lidar
Articulated IMUs (moving joint or turret) yes dynamic TF from joint_states; config/example_articulated_imu.yaml
Velocity from odometry yes motion_compensation.translation: odometry, lever arm and joint rate from TF

Every source can have its own IMU on its own mount. polka looks up the live TF from each IMU frame to its sensor frame and rotates that IMU's angular velocity and acceleration into the sensor frame before deskewing, so a fixed chassis LiDAR and a rotating turret LiDAR each deskew against the motion they actually see:

graph LR
  gimu[global IMU] -->|TF into sensor frame| chassis[chassis LiDAR]
  timu[turret IMU] -->|TF into sensor frame| turret[turret LiDAR]
  chassis --> polka
  turret --> polka
  polka --> merged[one merged cloud]
Loading

Translation

An IMU gives rotation and changes in speed, not speed itself, so steady motion leaves skew behind. translation: odometry takes velocity from any nav_msgs/Odometry (wheels, an EKF, GNSS), moved to each lidar's origin through TF. That source must be independent of the clouds being deskewed.

RMS error against truth: no deskew, translation none, translation odometry
polka_node on a synthetic stream with exact truth.

Articulated vehicles

three lidars on a center-steered vehicle, top and side view
The rig from #2 as read from its bag's TF. Positions measured, body outlines schematic.

Odometry reports the rear body; the front lidars ride the steering joint. polka adds each lidar's rate relative to the odometry frame, sampled from TF over the scan, and places every cloud with its mount pose at its own stamp. On the real front-lidar scan in test/data that takes the lidar's speed from 0.61 to 0.46 m/s and removes 6 cm of error.

raw skew per 5 cm cell on the rear lidar during a turn
Raw skew on the rig's rear lidar at its sharpest turn, top view.

Install

One branch per ROS 2 distro, same code on each:

Distro Ubuntu Branch
Humble 22.04 humble
Iron 22.04 iron
Jazzy 24.04 jazzy
Kilted 24.04 kilted
Lyrical 26.04 lyrical
Rolling 26.04 rolling
git clone -b humble https://github.com/Pana1v/polka.git ~/ros2_ws/src/polka
cd ~/ros2_ws && colcon build --packages-select polka
# add  --cmake-args -DWITH_CUDA=ON  for the GPU merge engine

Quick start

cp config/example_params.yaml config/my_robot.yaml      # edit topics + output_frame_id
ros2 launch polka polka.launch.py config_file:=config/my_robot.yaml

Point output_frame_id at your base frame, list your sensors under source_names, and check that TF resolves every sensor frame_id to output_frame_id. Playing a bag? Pass use_sim_time:=true and play with --clock (see Configuration).

Documentation

License and credits

Apache-2.0. The per-point deskewing motion model is inspired by rko_lio (Malladi et al., 2025, arXiv:2509.06593).

About

One shot solution for your Lidar data processing, supports GPU Acceleration, deskewing, and much more....

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages