๐๏ธ Core System Design
The system is composed of 11 ROS 2 packages and 2 standalone Python packages that provide configuration tooling and the visualization dashboard. Together, these components form a modular pipeline for LiDAR evaluation, where data flows through clearly separated stages of ingestion, processing, spatial filtering, visualization, and reporting.
The architecture diagram below illustrates the overall system design and how data moves across each submodule, from raw sensor input through to final evaluation outputs.
๐ง System Architecture Diagramโ
1. Configuration & Setup
Configuration Files (one pair per run)
Standalone Tool (not a node)
Generated Artifacts
2. System Bringup
3. Run Mode โ just start-run
Mode A โ Record new bags
just enable-bag-recording ยท hardware attached
- Cycle the vendor driver, apply each parameter under test
- Command the mount angle, let the scene settle
- Record one bag per case, straight into its case folder
- When every case is captured โ /start_evaluation
Mode B โ Replay existing bags
just disable-bag-recording ยท no hardware needed
- No driver, no automation manager, no actuators
- start-run calls /start_evaluation directly
- Evaluates bags recorded on an earlier run
4. Runtime Pipeline โ ROS 2 Core Architecture
โ Establish ground truth
the bench frames come from the generated URDF, so the expected answer is known before any data arrives
The evaluation loop โ clockwise, once per scan, then once per case
โป inner loop
โก โ โข โ โฃ repeats for every scan in the bag, each pass measured against the same baseline profiles
Alongside the loop โ live inspection, not part of the cycle
Interfaces (lidar_test_bench_interfaces)
- FilterCloud (srv)
- GetProfiles (srv)
- Visualization (srv + msg)
- ExpectedZone (msg)
- Plane3D (msg)
- NumericalPointCloud (msg)
- Point4D (msg)
5. Outputs & Reporting
Legend
๐ฆ Repository Package Structure
The framework is organized into modular ROS 2 packages, each responsible for a specific stage of the LiDAR evaluation pipeline. This separation allows configuration management, sensor processing, evaluation, reporting, and hardware integration to evolve independently while maintaining a consistent data flow across the system.
ROS 2 Core Packagesโ
lidar_test_bench_bringupโ
The primary entry point for launching the LiDAR evaluation bench. This package initializes the required ROS 2 nodes, loads global runtime parameters, and handles the launch coordination for the complete evaluation pipeline. Its launch file is regenerated by polysetup for each run.
lidar_test_bench_interfacesโ
Defines the custom ROS 2 interfaces used throughout the framework. This package contains the custom topics, services, and action definitions required for communication between the orchestrator, filtering nodes, metrics backend, and reporting components.
lidar_eval_orchestratorโ
The central orchestration package responsible for managing evaluation runs. It acts as the state machine for the bench, coordinating the lifecycle of data playback, point cloud processing, metric collection, and reporting workflows.
lidar_metrics_libraryโ
A modular, plugin-based math and analysis library that computes sensor performance metrics (such as range error and depth accuracy) from filtered point cloud populations. It exposes an extensible API, allowing developers to implement custom metrics tailored to specific evaluation criteria.
lidar_zonesโ
Owns everything about zone geometry. It consumes YAML zone definitions, resolves them into geometric bounds against the TF tree, and serves the resulting baseline profiles โ the ground truth every metric is scored against โ to the rest of the pipeline, along with zone overlays for RViz.
Each zone geometry is a plugin selected by registry, so a new shape is added by writing one plugin rather than by editing the engine or its consumers. The package also generates the bench URDF consumed by lidar_transforms; that generation is invoked by polysetup at configuration time, not at runtime.
lidar_transformsโ
Publishes the static structure of the bench. It holds the generated lidar_bench.urdf, brings up robot_state_publisher to publish it as the robot description, and runs a TF broadcaster that supplies the bench frames โ cart, mount, sensor, and zone links โ that zone resolution and metric evaluation are computed against.
lidar_automation_managerโ
Provides automation utilities for high-level evaluation workflows, including automated rosbag recording, hardware rigging controls, and batch experiment management to ensure repeatable testing scenarios.
lidar_reportingโ
Provides the reporting pipeline for aggregating and publishing evaluation results. It reads the per-case reports produced during a run, assembles the full metrics tree together with per-case visualization data and the recorded rosbags, and hands the finished result to a storage backend through lidar_eval_backends.
lidar_eval_backendsโ
The swappable persistence layer. A single interface defines both halves of the contract โ writing a completed run and reading it back for analysis โ so the reporting pipeline and the dashboard use opposite halves of the same backend.
Backends are chosen by registry rather than by import; a Google backend (Sheets + Drive) ships today, and adding another means implementing the interface and enabling it. Credential providers are pluggable on the same principle, so how credentials are obtained can change without touching the storage backend.
lidar_pointcloud_filterโ
The primary data-path processing node responsible for isolating sensor data. It consumes incoming raw point clouds (sensor_msgs/msg/PointCloud2) and uses the geometric definitions from the zones generator to apply high-frequency spatial, projective, and boundary-box filtering.
๐ Configuration Folders
environment_configsโ
Contains environment definitions used for LiDAR evaluation. Each configuration describes the geometric structure of an evaluation environment, including zones, obstacles, reference surfaces, and sensor test scenarios.
lidar_configsโ
Contains LiDAR-specific configuration files describing sensor properties, mounting parameters, TF relationships, topics, and evaluation settings required to integrate a sensor into the framework.
๐ Python Support Modules
polysetupโ
A standalone CLI tool that configures the bench for one specific run. It is the first thing you run โ before any launch โ and it takes exactly two inputs: one lidar config from lidar_configs/ and one environment config from environment_configs/. From that pair it generates every artifact the runtime needs, so launching the bench never requires hand-editing a node's parameter file.
What it writes:
- The bench URDF (
lidar_transforms/urdf/lidar_bench.urdf) โ cart geometry, mount pose, and the zone link tree, constructed throughlidar_zones. - Per-node parameter files โ the orchestrator's
eval_framework_manager.yaml(input topic, lidar and environment identity, results location),zones_orchestrator.yamlandroi.yaml(zone frames and ROI regions),pointcloud_filter.yaml(lidar frame and per-zone projective padding),lidar_reporting/config.yaml(reporting identity and resolutions), andautomation_manager.yaml(driver command, bag recording, sweep parameters, servo angles). - The bringup launch file โ regenerated with the base node set, and with the recording chain included or commented out according to the current bag-recording setting.
It also computes values you would otherwise maintain by hand. The most significant are the automation manager's servo pan angles, solved geometrically from the sensor's horizontal FOV and the zones' combined edge-to-edge boundary rather than written into the lidar config โ see the Developer Guide for that derivation and for the sweep model it feeds.
Every generated path is derived from the --src-dir argument at runtime, so polysetup behaves identically no matter which directory it is invoked from. It is not a ROS 2 node, but it imports the built lidar_zones package, so the workspace must be sourced before it will run. Configuration problems โ a missing or malformed YAML file, or a zone region too wide for the chosen sensor's field of view โ are surfaced as a single [ERROR] โฆ line rather than a Python traceback.
It installs two console scripts. polysetup-ws-sync performs the configuration itself, naming the
workspace root and both config files explicitly:
polysetup-ws-sync --src-dir /lidar_test_bench \
--lidar-file /lidar_test_bench/lidar_configs/robosense.yaml \
--env-file /lidar_test_bench/environment_configs/rocinante.yaml
An optional --backend-file accepts a backend config YAML for setups that need one.
polysetup-bag-recording --src-dir <ws> --bag-recording-status <true|false> records whether the run
should capture fresh bags. It persists the choice so the next configuration pass includes or excludes
the recording chain accordingly.
In day-to-day use you reach both through the just recipes described below rather than typing these
paths.
polyview_appโ
A visualization dashboard for inspecting LiDAR evaluation results. It provides interactive visualization of point clouds, evaluation zones, metric outputs, and sensor comparison results.
โ๏ธ Task Runner (just)
Workspace tasks are wrapped as recipes in the Justfile at the repository root, run with
just. The recipes exist so routine operations don't depend on
remembering absolute paths or flag spellings. Run just --list to see what the Justfile currently
offers; a full run uses these:
| Recipe | Purpose |
|---|---|
just setup-ws <environment> <lidar> | Configure the workspace for one environment/LiDAR pair, then build |
just launch-bench | Bring up the evaluation pipeline |
just start-run | Begin a run |
just stop-run | Stop an in-progress evaluation |
just enable-bag-recording | Record fresh bags on the next run |
just disable-bag-recording | Evaluate already-recorded bags instead |
just setup-ws <environment> <lidar>โ
The standard way to configure a run. This is a thin, convenience-focused wrapper around polysetup:
it resolves two friendly names into config file paths, fills in the workspace root, and hands off.
just setup-ws rocinante robosense
Note the argument order โ environment first, lidar second โ which is the reverse of how the
underlying polysetup flags are usually written.
Each name is resolved against its own config tree: the first argument is searched under
environment_configs/, the second under lidar_configs/. Resolution is deliberately forgiving about
how you type the name and deliberately strict about what it accepts:
- The
.yaml/.ymlextension is optional โrocinante,rocinante.yaml, androcinante.ymlall work. - Matching is case-insensitive and searches the config tree recursively, so configs may live in subdirectories.
- If no config matches, the recipe fails and prints every available config under that tree, so you can see the valid names.
- If more than one config matches, the recipe refuses to guess and lists the candidates for you to disambiguate.
Both failure modes exit before polysetup is invoked, so an unresolvable name never partially
configures the workspace. On success the recipe echoes the two resolved paths, then calls polysetup
with --src-dir set to the repository root automatically. Because that root comes from the Justfile's
own location rather than the current directory, just setup-ws behaves identically from anywhere in
the repository.
Once configuration succeeds the recipe builds the workspace, then reminds you to source
install/setup.bash โ sourcing inside a recipe would only affect that recipe's own subshell.
The recipe inherits polysetup's prerequisite: the ROS 2 workspace must be sourced first, since
polysetup imports the built lidar_zones package. On a clean checkout this is a chicken-and-egg
situation, so build once by hand before the first setup-ws.
Recording mode: just start-runโ
The bench runs in one of two modes, and start-run behaves differently in each. The mode is set by
just enable-bag-recording / just disable-bag-recording, which persist the choice so it survives
across configuration passes.
Recording enabled โ the automation manager owns the run end to end. start-run triggers it once,
and it then drives the whole sweep itself: cycling the sensor driver, commanding mount angles,
applying each parameter under test, capturing a bag per case, and starting evaluation when every case
has been recorded.
Recording disabled โ the recording chain is left out of the launch file entirely, and start-run
begins evaluation directly against bags recorded earlier. This is the fast path for iterating on
zones or metrics, since it needs no sensor and no hardware attached.
๐ How Everything Works Togetherโ
1. Configuration Loadingโ
The system begins by loading structured YAML configuration files defining:
- LiDAR sensor setup
- Environment zones
- Evaluation parameters
These are parsed into a unified runtime configuration model.
2. System Initializationโ
polysetup builds the evaluation environment before anything launches:
- URDF construction of the test bench, generated through
lidar_zones - TF tree definition for sensor and zone alignment
- Regeneration of the bringup launch file and every node's parameter file
At launch, lidar_transforms publishes that URDF and broadcasts the bench frames, and
lidar_zones resolves each zone against them into the baseline profiles the run is scored against.
3. Processing Loopโ
Once running, the system executes a continuous evaluation pipeline:
- Raw point clouds are ingested (live sensor or rosbag playback)
lidar_pointcloud_filterisolates each zone, producing both a 3D-region view and an angular-frustum viewlidar_eval_orchestratorfeeds those per-zone clouds into the metrics library, scan by scan- Metrics accumulate state across the run rather than emitting a result per frame
4. Evaluation & Reportingโ
When a case completes, each metric reduces its accumulated state to a result and the orchestrator
writes a structured per-case report. lidar_reporting then aggregates those reports with per-case
visualization data and the recorded bags, and publishes the result through a storage backend for the
dashboard and any external analytics to read.
๐งฉ Core Conceptsโ
๐ Zone-Based Evaluation Modelโ
The environment is divided into configurable spatial zones defined via YAML. These zones enable:
- Region-specific evaluation
- Noise filtering outside areas of interest
- Consistent benchmarking across sensors
๐ Plugin-Based Metrics Systemโ
Metrics are not hardcoded. Instead, they are:
- Implemented as plugins
- Dynamically loaded at runtime
- Applied per-zone to filtered point clouds
This allows domain-specific evaluation without modifying core infrastructure.
๐ Reproducible Evaluation Pipelineโ
Every evaluation run is fully reproducible through:
- YAML-based configuration
- Fixed TF tree definitions
- Deterministic processing pipeline
๐ Multi-Layer Output Systemโ
Evaluation results are surfaced through multiple channels:
- Structured report files (YAML/JSON)
- Visualization dashboards
- External analytics tools (Grafana, Sheets, etc.)
๐งญ Summaryโ
This architecture enables a fully modular LiDAR evaluation pipeline where:
- Configuration is externalized (YAML-driven)
- Zones are extensible for different objects and shapes as long as the boundaries are defined correctly
- Processing is deterministic and reproducible
- Metrics are extensible via plugins
- Visualization and reporting are decoupled from core computation
- database backend are configurable via plugins
Together, these components form a scalable benchmarking framework for evaluating LiDAR performance across diverse robotics environments.