Benchmark Results¶
FusionCore on the NCLT dataset (University of Michigan North Campus Long-Term): twelve sequences of a Segway robot driving the same campus across all seasons, one config file, no per-sequence tuning. Every number on this page comes from the 2026-10-26 evaluation unless it is explicitly labelled as history.
Current results, 2026-10-26¶
All twelve sequences were re-run under a reporting rule committed before the run started
(tools/report_rule_20261025.md).
The dataset player was corrected first: it had been declaring the IMU's accelerometer-derived
tilt as a 0.57 degree attitude measurement. That declaration was ours, not the dataset's. It
counted the accelerometer twice and made the ROS node lock onto a wrong position for about
800 s on 2012-01-08 (178.808 m full-trajectory RMSE before the correction, 57.314 m in this run).
Scored at GPS fix times, the shipped default does not beat raw GPS: raw GPS has the lower
RMSE on 11 of 12, and the twelfth, 2012-05-11, is a tie at 9.186 m against 9.187 m. With
opt-in re-anchoring (gnss.reanchor_enabled), FusionCore is lower than raw GPS on 2 of 12,
2012-03-31 by 22.0% and 2012-10-28 by 5.6%, both held-out sequences never used for tuning.
Against the default on the same pipeline and scored over the full trajectory, re-anchoring
lowers RMSE on 10 of 12 and raises it on none. It also raises the median on 4 (by 0.3% to
3.0%) and the output-rate jitter on 3 (by up to 0.124), which is why it is off by default.
ATE RMSE (3D) at GPS fix times, every column on identical timestamps:
| Sequence | Set | Raw GPS | Default, ROS node | Default, replay | Re-anchoring on, replay | Re-anchoring on, ratio to raw GPS |
|---|---|---|---|---|---|---|
| 2012-01-08 | held out | 9.883 m | 56.957 m | 50.065 m | 10.899 m | 1.103x |
| 2012-02-04 | dev | 57.564 m | 163.830 m | 145.629 m | 59.340 m | 1.031x |
| 2012-03-31 | held out | 13.825 m | 16.818 m | 16.937 m | 10.787 m | 0.780x |
| 2012-05-11 | held out | 9.187 m | 9.186 m | 9.187 m | 9.187 m | 1.000x |
| 2012-06-15 | dev | 9.293 m | 24.363 m | 12.041 m | 12.041 m | 1.296x |
| 2012-08-20 | dev | 34.250 m | 160.359 m | 172.961 m | 39.790 m | 1.162x |
| 2012-09-28 | dev | 8.960 m | 18.883 m | 22.236 m | 10.176 m | 1.136x |
| 2012-10-28 | held out | 13.923 m | 15.999 m | 16.763 m | 13.150 m | 0.944x |
| 2012-11-04 | held out | 20.267 m | 23.626 m | 24.512 m | 20.414 m | 1.007x |
| 2012-12-01 | held out | 12.681 m | 16.160 m | 16.466 m | 12.741 m | 1.005x |
| 2013-02-23 | held out | 15.724 m | 17.294 m | 16.951 m | 15.793 m | 1.004x |
| 2013-04-05 | dev | 8.624 m | 9.994 m | 10.007 m | 8.798 m | 1.020x |
Medians, path ratios, output-rate jitter and coverage for every arm and sequence, including the
full-trajectory scores, are in
tools/eval_20261025_results.md.
What this does and does not show. Re-anchoring was run through the deterministic replay only, so compare it with the replay default column, not the node one. The node and the replay still disagree: node over replay is 0.70x to 1.14x over the full trajectory, and at fix times up to 2.0x on 2012-06-15 (24.363 m against 12.041 m). That gap is not yet explained. NCLT's ground truth covers only about 18% of the time GPS is absent, so this dataset cannot say how well any filter coasts through a long outage, in either direction.
robot_localization: re-run pending¶
There is no current FusionCore against robot_localization comparison on this page, and no claim in either direction should be drawn from older ones. In the 2026-10-26 run robot_localization's config fused IMU acceleration. Once the corrected player stopped supplying an attitude, robot_localization removed gravity using a level attitude, so real tilt leaked into the acceleration and it diverged to kilometres on nine sequences. That was our input change meeting that config, not a property of robot_localization, so those numbers are withheld. All twelve are being re-run with the config following robot_localization's own planar wheeled-robot example; the amendment in the rule file states exactly what changed and why. This page will carry those results, per sequence and per metric, once they exist.
What the measurements since May 2026 found¶
- Most of the error is recovery, not the blackout itself. On the five development
sequences, 74.8% to 96.8% of squared error sits in GPS blackouts or in the re-convergence
after them, and in that recovery window the outlier gate rejected 81% of arriving fixes. The
filter was discarding the fixes that would have corrected it.
gnss.reanchor_enabledis the fix for that part, and it stays opt-in for the costs stated above. - Two input errors in our own dataset player fed both filters wrong data: a wheel odometry yaw rate in the wrong frame (#169) and the 0.57 degree tilt declaration (found 2026-10-25). Every number measured before each fix is superseded.
- Run-to-run spread through the ROS node can reach 14.8% on a single sequence (2012-02-04, n=3), while the replay is deterministic. A single node run cannot grade a small difference.
The full narrative, with every number and its provenance, is in
tools/benchmark_regression.md.
Methodology¶
Dataset: NCLT (University of Michigan, 2012-2013). Segway RMP on a large campus over
multiple seasons, replayed from the raw CSV files by nclt_player.
Sensors fused:
- IMU: Microstrain 3DM-GX3-45 at 100 Hz (accelerometer and gyro; its orientation output is
published as invalid,
orientation_covariance[0] = -1, because it is derived from the same accelerometer) - Wheel odometry: Segway RMP at 100 Hz
- GPS: the dataset's consumer-grade receiver (
gps.csv), 3D fixes only
Ground truth: NCLT's RTK GPS (gps_rtk.csv), projected to local ENU.
Pipelines: "node" is the ROS node at 1x playback (achieved filter rate 99.8 to 103.3 Hz on
every sequence). "replay" is the deterministic offline replay (hardware/replay.cpp) of the
same config on the same input.
Scoring: tools/score_nclt_suite.py (version 803c158e99ff, printed on every row) through
hardware/ate_tum.py: Umeyama SE(3) alignment, 0.1 s association, which matches evo at the
same tolerance. "At GPS fix times" means every column, raw GPS included, is scored on
identical timestamps.
FusionCore config: fusioncore_datasets/config/nclt_fusioncore.yaml, identical across all
twelve sequences. "Held out" sequences were never used to develop or tune anything
(tools/ship_rule.md).
History: the May 2026 table (superseded)¶
This page used to lead with the table below, under the heading "12 sequences, 10 FC wins". That claim is withdrawn. Every row was measured through the dataset player with both input errors above, which fed both filters wrong data, and re-measurement flipped published results in both directions (2012-01-08 went from FC 18.6 m against RL 41.2 m to FC 185.2 m against RL 22.8 m). It is kept so the correction is visible. Nothing should be concluded from it in either direction.
| Sequence | Season | Duration | GPS fixes | Max blackout | FC ATE (3D), May 2026 | RL-EKF ATE (3D), May 2026 |
|---|---|---|---|---|---|---|
| 2012-01-08 | Winter | 92 min | 22,041 | 203s | 18.6 m | 41.2 m |
| 2012-02-04 | Winter | 77 min | 18,808 | 184s | 49.7 m | 265.5 m |
| 2012-03-31 | Spring | 87 min | 20,482 | 262s | 22.0 m | 156.5 m |
| 2012-05-11 | Spring | 84 min | 21,621 | 120s | 9.7 m | 11.5 m |
| 2012-06-15 | Summer | 55 min | 12,399 | 462s | 49.2 m | 18.2 m |
| 2012-08-20 | Summer | 83 min | 20,025 | 228s | 98.3 m | 10.6 m |
| 2012-09-28 | Fall | 77 min | 19,191 | 196s | 22.4 m | 53.8 m |
| 2012-10-28 | Fall | 85 min | 21,060 | 256s | 15.6 m | 56.4 m |
| 2012-11-04 | Fall | 79 min | 17,840 | 400s | 60.1 m | 122.0 m |
| 2012-12-01 | Winter | 75 min | 17,941 | 173s | 21.0 m | 90.7 m |
| 2013-02-23 | Winter | 78 min | 19,333 | 240s | 59.4 m | 82.2 m |
| 2013-04-05 | Spring | 68 min | 16,297 | 275s | 12.1 m | 268.9 m |
The analysis that used to follow it ("why RL-EKF fails on 10 sequences", performance tiers, and the two losses explained as heading drift) was built on these numbers and is withdrawn with them. The recovery finding above replaced it.
Corrections, in order: 2026-10-09, five rows re-measured after the #169 frame fix, FusionCore two wins and three losses. 2026-10-17, nine rows, four and five, with four of the nine at n=1. 2026-10-18, raw GPS published as the baseline the benchmark never had, and found to beat FusionCore on all five development sequences. 2026-10-26, the twelve-sequence evaluation above, with robot_localization withheld.
Reproduce¶
# Build
colcon build --packages-select fusioncore_core fusioncore_ros fusioncore_datasets
# One sequence through the ROS node at 1x (about 60 to 95 min)
bash tools/run_nclt.sh 2012-01-08 <output_dir> 1.0
# Score several trajectories of one sequence on identical points
python3 tools/score_nclt_suite.py <exported.txt> <ground_truth.tum> \
NAME=path[@pipeline] [NAME=path[@pipeline] ...] --seq 2012-01-08
One playback at a time: a starved run's numbers are meaningless, and tools/run_nclt.sh
reports the achieved filter rate so you can check.