TII-RATM Drone Racing in FiftyOne: When Onboard Odometry Never Recovers

Sep 16, 2026
7 min read
Author
Adonai Vera
Adonai Vera
Adonai Vera is a Machine Learning Engineer & DevRel at Voxel51 with over 7 years of experience building computer vision and machine learning models using TensorFlow, Docker, and OpenCV. Adonai started as a software developer, moved into AI, led teams, and served as CTO. Today, he connect code and community to build open, production-ready AI — making technology simple, accessible, and reliable. LinkedIn | GitHub
See all articles by Adonai Vera

Talk to an AI expert

FiftyOne 1.22.0 added a new Dataset Zoo benchmark of six real indoor quadrotor racing flights, each one a native MCAP episode with the drone's own onboard position estimate recorded next to what a motion capture rig measured at the same instant. Querying that pair of numbers, no model training required, shows that in four of six flights the onboard estimate's single worst disagreement with ground truth lands on the very last recorded pose: once it drifts near the end of a fast lap, it does not get the chance to correct itself before the flight is over.

Key takeaways

  • In four of six TII-RATM drone racing flights, visual-inertial odometry (VIO) records its single worst disagreement with motion capture ground truth on the very last pose of the flight, with no recovery afterward.
  • Dividing the tracking error at the final pose by the maximum error anywhere in the run, "final over max," separates a VIO system that recovers from drift from one that ends on its worst moment. RMSE alone cannot tell them apart.
  • The worst flight, flight-07p-lemniscate, reaches 1.66 meters RMSE, but the flight that actually recovers, flight-03p-ellipse, has a higher RMSE than one that never recovers.
  • Across all six flights, the onboard fisheye feature tracker produces usable matches on only 53.9% to 57.9% of camera frames, a structural cost of flying a wide fisheye lens fast indoors that tracking error alone never surfaces.
  • FiftyOne 1.22.0 ships tii-ratm-drone-racing in the Dataset Zoo as native MCAP episodes, so both trajectories render in the multimodal viewer with no conversion step and no model training.

FiftyOne 1.22.0 added a real onboard-vs-ground-truth racing benchmark

FiftyOne 1.22.0, released September 11, 2026, added the tii-ratm-drone-racing dataset to the Dataset Zoo: six flights of a quadrotor lapping a four-gate indoor track, three laps on an ellipse and three on a lemniscate (a figure eight), carrying a fisheye camera and a 500 Hz IMU while a motion capture system watches the room. Across the six flights that is 10.9 minutes and 2,563 meters flown, with tracking error ranging from 0.61 to 1.66 meters RMSE. Every episode ships as a native .mcap file, opened directly by FiftyOne's multimodal viewer, no separate conversion step.
Full dataset card, with exact scale and license: https://docs.voxel51.com/dataset_zoo/datasets/tii_ratm_drone_racing.html

Two numbers per timestamp: visual-inertial odometry versus motion capture ground truth

Most robotics datasets give you one trajectory and ask you to trust it. This one gives you two: the position the drone's own camera and IMU computed for itself (VIO, visual-inertial odometry), and the position a motion capture rig measured for it at the same instant. VIO is what a real robot has to fly on, since a mocap rig or GPS fix is not available in the field. Having both numbers, recorded by the dataset itself rather than something we computed, is what makes a tracking-error check honest instead of a heatmap you eyeball.
The rest of this post is one worked example: load all six flights, sort them by how far the onboard estimate drifted, open the worst one in FiftyOne's real multimodal MCAP viewer, and check, in code, whether the drift ever corrects itself.

Six flights, three tiles each, no model training required

Loading the dataset gives six samples, one per flight, each pointing at an episode.fo.mcap file. FiftyOne reads scalar summary fields straight off each sample (track shape, duration, speed, gate count, and four tracking error statistics already computed by the dataset itself: RMSE, median, max, and the error at the final matched pose) so you can sort and filter before ever opening a single episode.
import fiftyone as fo
import fiftyone.zoo as foz

dataset = foz.load_zoo_dataset("tii-ratm-drone-racing")
worst_first = dataset.sort_by("tracking_error_rmse_m", reverse=True)

session = fo.launch_app(worst_first)
All six flights in the FiftyOne grid. Even the thumbnail shows the fisheye lens: the gate frames and ceiling beams curve toward the edges, the same distortion the onboard feature tracker has to work with on every frame.

The worst flight, in FiftyOne's own 3D viewer, both trajectories at once

flight-07p-lemniscate has the highest RMSE of the six, 1.66 meters. Opening its episode gives three linked tiles read straight from the MCAP topics: a 3D view (points-slam) with the drone's SLAM point cloud, gates, and both trajectories; the raw fisheye camera feed; and a feature-tracks view with the onboard tracker's own feature points overlaid in green. Turning on pose-ground-truth and pose-vio under Ego Pose, and switching to a top-down view, draws both position estimates as separate lines in the same 3D space.
flight-07p-lemniscate from directly above. Teal is motion-capture ground truth tracing the figure-eight; orange is the drone's own onboard VIO estimate. They start together but the orange line runs visibly wide of the teal one, most clearly on the outer loops, before trailing off the whole pattern near the end. The camera tile (left) shows the same fisheye distortion as the grid thumbnails; the feature-tracks tile (right) shows the tracker's own matched points in green.
How FiftyOne's multimodal viewer reads MCAP topics into linked tiles like these is covered in the user guide: https://docs.voxel51.com/user_guide/multimodal.html.

What we measured: does the drift ever recover?

Tracking error for all six TII-RATM drone racing flights, sorted worst to best by RMSE. Read straight off the dataset's own fields for every flight (n=6), no sampling. "Final / max" divides the visual-inertial odometry error at the last matched pose by the largest error anywhere in the flight. A value of 1.00 means the flight's worst moment of drift was also its last, with no recovery before landing.
Tracking error for all six TII-RATM drone racing flights, sorted worst to best by RMSE. Read straight off the dataset's own fields for every flight (n=6), no sampling. "Final / max" divides the visual-inertial odometry error at the last matched pose by the largest error anywhere in the flight. A value of 1.00 means the flight's worst moment of drift was also its last, with no recovery before landing.
FlightTrackRMSE (m)Max err (m)Final err (m)Final / max
flight-07p-lemniscatelemniscate1.65633.78363.46640.92
flight-01p-ellipseellipse1.07193.4823.4821.00
flight-08p-lemniscatelemniscate0.67422.19392.19391.00
flight-09p-lemniscatelemniscate0.64171.69921.69921.00
flight-02p-ellipseellipse0.63433.45553.45551.00
flight-03p-ellipseellipse0.61232.34950.67120.29
Measured by us, read straight off the dataset's own fields for all six flights (n=6), no sampling. "Final / max" is the tracking error at the last matched pose divided by the largest error anywhere in the flight: a value of 1.00 means the flight's single worst moment of drift is also the last thing that happened before landing, with no recovery afterward.
4 of the 6 flights land exactly on 1.00 (flight-01p-ellipse, flight-08p-lemniscate, flight-09p-lemniscate, flight-02p-ellipse): the onboard estimate's worst disagreement with ground truth is the final one on record. Two flights break that pattern. flight-07p-lemniscate, the worst flight overall, comes close at 0.92: still drifting hard at the end, just not quite at its single worst moment. flight-03p-ellipse is the outlier that actually recovers, at 0.29: it hit a peak error of 2.35 m mid-flight and closed to 0.67 m by the last pose, more than three times better than where it had been.
This is not a track-shape effect. flight-01p-ellipse and flight-02p-ellipse both end exactly on their own worst moment too, same as two of the three lemniscate flights. Whatever makes flight-03p-ellipse correct itself while the other five do not is something in that specific flight, not something you can predict from knowing the track was an ellipse.

A second, independent failure: the fisheye tracker drops nearly half of every frame

Every flight also records num_camera_frames and num_feature_track_frames: how many frames the camera captured, and how many of those the onboard feature tracker actually got usable matches on. Across all six flights that ratio sits between 53.9% and 57.9%, averaging 55.8%. Every flight, regardless of track shape or how well its final position landed, loses roughly four in ten frames to failed feature tracking. That is a real, structural cost of flying a wide fisheye lens fast indoors, motion blur and radial distortion both degrade corner features toward the frame edges, and it holds steady enough across six independent flights that it looks like a property of the sensor and the track, not noise in any one run.
Neither of these two findings is a bug in how we measured them; both come straight from fields the dataset itself computed and ships, read the same way for all six flights. The near-45% frame loss did not stop these drones from finishing their laps, VIO systems are built to tolerate dropped frames, but it is exactly the kind of margin a team building on this class of hardware needs to know about before they fly it somewhere the mocap rig cannot follow.

Building your own

The transferable idea here is not specific to drones or to this dataset: any pose-estimation pipeline that has an independent ground-truth reference, motion capture, RTK GPS, surveyed markers, a second sensor you trust more, can be checked the same two ways, in a few lines, with no plot to design by hand.
  • Compute final-over-max, not just RMSE. A single summary error number hides whether a system recovers from drift or ends on its worst moment. Dividing the error at the last pose by the largest error anywhere in the run turns that into one comparable number per run.
  • Sort before you look. dataset.sort_by("tracking_error_rmse_m", reverse=True) put the flight worth opening first, out of six, without watching any video.
  • Check coverage fields, not just accuracy fields. num_feature_track_frames next to num_camera_frames caught a real sensor limitation that tracking error alone never would have shown, since the system compensated for it well enough to still finish the lap.
  • Let the viewer show what the query found. Once the query has picked the flight and the moment, FiftyOne's multimodal viewer draws both trajectories in the same 3D space for free, no custom plotting code to write.

Where does vision-only localization matter outside drone racing?

A few real, concrete situations where this exact check matters, not a demo dataset:
  • Indoor and warehouse robots with no GPS. A forklift-replacement AMR or a warehouse inspection drone loses GPS the moment it goes indoors, and adding a motion capture rig or a fleet of UWB anchors to every building it visits is not realistic. Vision-inertial odometry is often the only localization it gets, and knowing whether that estimate corrects itself after a rack aisle or a low-light dock, versus committing to its worst error right at the docking maneuver, is the difference between a near miss and a collision report.
  • Autonomous drone racing and inspection flights near obstacles. This dataset is itself drawn from that world: a racing quadrotor flying gates at speed on a single fisheye camera. The same sensor and the same odometry stack show up in infrastructure inspection drones flying close to towers, turbines, or bridge undersides, where the final approach to the structure is exactly when a lingering drift is most expensive.
  • Any vision-only SLAM stack evaluated only on RMSE. RMSE and median error are the numbers most benchmark leaderboards report, and both of those flights that never recover would look unremarkable on that alone (flight-02p-ellipse has a lower RMSE, 0.63 m, than the flight that actually corrects itself). A team picking a SLAM stack off a leaderboard number alone would miss exactly the failure mode this check is built to catch.

Try it

foz.load_zoo_dataset("tii-ratm-drone-racing") pulls all six flights directly from the Dataset Zoo, 0.58 GB total.
The full Dataset Zoo API, including sort_by and other view stages used in this post: https://docs.voxel51.com/dataset_zoo/api.html.

FAQ

Adonai Vera
Adonai Vera
Adonai Vera is a Machine Learning Engineer & DevRel at Voxel51 with over 7 years of experience building computer vision and machine learning models using TensorFlow, Docker, and OpenCV. Adonai started as a software developer, moved into AI, led teams, and served as CTO. Today, he connect code and community to build open, production-ready AI — making technology simple, accessible, and reliable.
See all articles by Adonai Vera

Talk to an AI expert

Loading related posts...