Pipeline and architecture¶
Running several LiDARs in ROS 2 normally takes a chain of nodes: one to concatenate, one to filter, one per scan projection, another to merge those. Every hop costs latency and CPU, and every hop is one more thing to configure and one more thing that can die. polka does the whole chain in a single composable node.
What polka replaces¶
polka (1 node)¶
graph LR
subgraph Drivers
D1[lidar driver · front]
D2[odom / cmd_vel]
D3[lidar driver · back]
end
P[<strong>polka</strong>]
subgraph Consumers
C1[mapping / reconstruction<br/>~/merged_cloud]
C2[localization / navigation<br/>~/merged_scan]
end
D1 --> P
D2 -.-> P
D3 --> P
P --> C1
P --> C2
pcl_ros chain (7+ nodes)¶
Cloud path:
graph LR
subgraph Drivers
D1[lidar driver · front]
D2[lidar driver · back]
end
CAT[pcl_ros::<br/>ConcatenatePointCloud<br/>+ ApproxTimeSynchronizer]
CF[custom node<br/>cloud filters]
MAP[mapping node]
D1 --> CAT
D2 --> CAT
CAT --> CF -->|merged_cloud| MAP
Scan path:
graph LR
subgraph Drivers
D1[lidar driver · front]
D2[lidar driver · back]
end
P2L1[pointcloud_to_laserscan<br/>· front]
P2L2[pointcloud_to_laserscan<br/>· back]
IRA[ira_laser_tools::<br/>LaserscanMerger]
SF[custom node<br/>scan filters]
NAV[localization / navigation]
D1 --> P2L1
D2 --> P2L2
P2L1 --> IRA
P2L2 --> IRA
IRA --> SF -->|merged_scan| NAV
Internal stages¶
graph TB
subgraph Sources
PC[PointCloud2<br/>/front/points]
LS[LaserScan<br/>/rear/scan]
end
subgraph Per-Source Filters
PF1[Range / Angular /<br/>Box Filter]
PF2[Range / Angular /<br/>Box Filter]
end
subgraph Merge Engine
ME[CPU or CUDA<br/>Merge]
end
subgraph Output Pipeline
OF[Range / Angular /<br/>Box Filter]
FF[Footprint Filter]
HF[Height Filter]
VX[Voxel Downsample]
end
PC --> PF1 --> ME
LS --> PF2 --> ME
ME --> OF --> FF --> HF --> VX
VX --> OUT_PC[PointCloud2]
VX --> OUT_LS[LaserScan]
Each source is filtered in its own frame before the merge, so points you don't want are gone before they cost anything. The merge engine, CPU or CUDA, transforms every source into output_frame_id and concatenates. The output stage then runs the shared filters, height cap, footprint exclusion and voxel downsample in that fixed order, and publishes the cloud, the flattened scan, or both.
File structure¶
polka/
├── config/
│ ├── example_params.yaml # Minimal starting point
│ ├── detailed_params.yaml # Annotated reference for every parameter
│ └── example_articulated_imu.yaml # Per-source IMU deskewing example
├── launch/polka.launch.py # Launch file
├── include/polka/
│ ├── polka_node.hpp # Main composable node (orchestration only)
│ ├── types.hpp # Config structs and type definitions
│ ├── config/config_loader.hpp # Parameter loading and hot-reload
│ ├── input/
│ │ ├── source_adapter.hpp # Subscribes and converts incoming sensor data
│ │ ├── imu_buffer.hpp # IMU ring buffer with atomic snapshot
│ │ ├── gyro_bias.hpp # Gyro bias learned at standstill
│ │ ├── odom_buffer.hpp # Odometry twist buffer for translation deskew
│ │ └── point_time_decoder.hpp # Per-point time field decoding
│ ├── filters/
│ │ ├── i_filter.hpp # Filter interface
│ │ ├── filter_chain.hpp # Builds a filter chain from FilterParams
│ │ ├── range_filter.hpp # Min/max distance filter
│ │ ├── angular_filter.hpp # Angular sector filter
│ │ └── box_filter.hpp # Axis-aligned box filter (invert for self filter)
│ ├── merge_engine/
│ │ ├── i_merge_engine.hpp # Merge engine interface
│ │ ├── cpu_merge_engine.hpp # CPU merge implementation
│ │ ├── cuda_merge_engine.hpp # CUDA GPU merge implementation
│ │ └── cuda_types.cuh # GPU type definitions
│ ├── output/
│ │ ├── output_pipeline.hpp # Post-merge work (filter, height cap, voxel)
│ │ └── scan_builder.hpp # Builds a LaserScan from a cloud or a range vector
│ └── util/
│ ├── qos_builder.hpp # build_qos() for output publishers
│ ├── se3_exp.hpp # SE(3) exponential map for motion compensation
│ └── log_format.hpp # Log throttle constants
└── src/
├── main.cpp # Entry point
├── polka_node.cpp # Node implementation
├── config_loader.cpp
├── source_adapter.cpp
├── imu_buffer.cpp
├── gyro_bias.cpp
├── odom_buffer.cpp
├── filters/ # Filter implementations
├── merge_engine/ # Merge engine implementations
└── output/ # OutputPipeline and ScanBuilder implementations
This site, its figures and the demo GIF toolchain live in polka-ros/polka-ros.github.io.