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 data, and uses CUDA if you build it in. One node instead of a relay, filter, transform, merge and downsample chain.
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.
Deskew: per-point SE(3) correction removes intra-scan motion smear. Synthetic yaw, generated separately from the TIERS clips above.
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.
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.
- 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
- IMU deskewing: per-point SE(3) motion correction, with 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 optionalpolka_monitorTUI - Composable node: runs standalone or in a component container
| 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 |
| Articulated IMUs (moving joint or turret) | yes | dynamic TF from joint_states; config/example_articulated_imu.yaml |
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]
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 |
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 enginecp config/example_params.yaml config/my_robot.yaml # edit topics + output_frame_id
ros2 launch polka polka.launch.py config_file:=config/my_robot.yamlPoint 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).
- Configuration: every parameter, filters, IMU deskewing, bag playback
- Pipeline and architecture: what polka replaces, the internal stages, the file layout
- Performance: the 0.5.0 numbers, the CPU/CUDA crossover, bandwidth
- Maintaining distro branches: how the five branches stay in sync
Apache-2.0. The per-point deskewing motion model is inspired by rko_lio (Malladi et al., 2025, arXiv:2509.06593).











