Skip to content
 
 

Repository files navigation

POLKA

English | 한국어 | 한국어 학습 가이드

ROS 2 Humble ROS 2 Iron ROS 2 Jazzy ROS 2 Kilted ROS 2 Lyrical
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)

Multi-LiDAR fusion node for ROS 2. Merges any mix of PointCloud2 and LaserScan sources into a unified PointCloud2 and/or LaserScan, with per-source and output filtering, IMU deskewing, and optional CUDA acceleration. One composable node replaces a relay / filter / transform / merge / downsample chain.

Features in action

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

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

per-point deskew: raw scan vs deskewed
Deskew: per-point SE(3) correction removes intra-scan motion smear (synthetic yaw, mechanism demo). This clip is synthetic, generated separately from the TIERS dataset clips above.

Performance

Polka 0.5.0 before and after performance summary

CUDA. The GPU merge engine wins on heavy pipelines by fusing transform, filter, voxel, and scan flatten into a single pass over the points. On a filterless merge the CPU stays competitive, because kernel dispatch and host to device transfer overhead dominate when there is little per-point work to hide them behind. Build with -DWITH_CUDA=ON (it falls back to CPU automatically); it is not universally faster.

Bandwidth. polka fans N sensor streams into one output topic, so every downstream node subscribes once instead of to each raw sensor. Voxel downsampling can thin that cloud further, a tradeoff the user sets through leaf_size: at the leaf size used in the demo clip it drops 69k points to 5k (about 14x), which is an example of the ratio, not a fixed figure or a 0.5.0 speedup.

See Performance notes for measurement context, the per number sources, and the CPU to CUDA crossover.

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
  • 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
  • Full runtime reconfiguration: filters, outputs, deskewing, and even the source list can be changed live via ros2 param set — no restart
  • Diagnostics, drift detection, and a terminal dashboard: per-source rate/bandwidth/lag on /diagnostics, timing/rate drift flags, and an optional polka_monitor TUI
  • Composable node: runs standalone or loaded into 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
Articulated IMUs (moving joint or turret) yes dynamic TF from joint_states; config/example_articulated_imu.yaml

Every source can carry 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 their own motion:

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

Install

Each ROS 2 distro has its own code-identical branch:

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 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

Set output_frame_id to your base frame, list sensors under source_names, and make sure TF resolves each sensor frame_id to output_frame_id. Replaying 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

A drop in clean and efficient replacement for your messy lidar pre-processing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages