$

$ teds read --post tii-ratm-drone-racing-fiftyone-analysis

TII-RATM-Drone-Racing: Measure Odometry Drift Against Motion Capture in FiftyOne

TII-RATM pairs a racing drone's own odometry with motion-capture truth for six flights. Sort by tracking error and watch the feature tracks.

A racing drone estimates where it is from its own camera and IMU. A motion-capture system measures where it actually is. TII-RATM keeps both for every flight, plus the distance between them, so you can see exactly where visual-inertial odometry drifts.

## What is in it

The flights come from the TII-RATM drone racing dataset, published with OpenVINS odometry in alvgaona/tii-ratm-rosbag2, and converted by Voxel51 to native multimodal MCAP episodes. A quadrotor flies laps of a four-gate indoor track, three flights on an ellipse and three on a lemniscate.

The set has six flights, 10.9 minutes and 2,563 metres flown. Each episode carries these streams:

Stream What it holds
/camera Onboard fisheye view, 640x480 at about 26 Hz
/feature-tracks The same view with the points the odometry is tracking drawn on it
/imu.plot Accelerometer and gyroscope at about 490 Hz
/pose-ground-truth The motion-capture pose
/pose-vio The odometry pose, in the same frame
/tracking-error.plot The distance between the two at each capture pose
/trajectory Both flown paths as line strips
/gates, /gate-range.plot The four track gates, and the distance to each

Across the whole set that comes to 17,305 camera frames, 9,658 feature-track frames, 327,086 IMU samples, 293,219 odometry poses and 179,283 capture poses.

Which flights were hardest

The odometry error varies a lot between flights:

Flight Track Distance ATE RMSE ATE max
flight-03p-ellipse ellipse 380 m 0.612 m 2.349 m
flight-02p-ellipse ellipse 446 m 0.634 m 3.455 m
flight-09p-lemniscate lemniscate 447 m 0.642 m 1.699 m
flight-08p-lemniscate lemniscate 367 m 0.674 m 2.194 m
flight-01p-ellipse ellipse 479 m 1.072 m 3.482 m
flight-07p-lemniscate lemniscate 444 m 1.656 m 3.784 m

flight-07p-lemniscate has the largest RMSE, 1.656 m, which is more than two and a half times the best flight (derived: 1.656 / 0.612 = 2.7).

Load it and sort by error to start with the worst:

import fiftyone as fo
import fiftyone.utils.huggingface as fouh

dataset = fouh.load_from_hub(
    "Voxel51/TII-RATM-Drone-Racing",
    name="TII-RATM-Drone-Racing",
    persistent=True,
)

view = dataset.sort_by("tracking_error_rmse_m", reverse=True)
fo.launch_app(dataset, view=view)

Open the first flight and watch the feature tracks while the tracking-error plot moves, so you can compare how many points the odometry is holding with how far it is from the capture pose.

How to read the error

The odometry and the capture system report in unrelated frames, because the estimator starts at its own origin with an arbitrary yaw. A rigid transform is fitted over all time-matched poses and applied to the odometry, which is how absolute trajectory error is usually measured. Scale is not fitted. The estimate is metric because it is inertial-aided, and solving for scale would hide drift instead of measuring it.

That fit spreads the residual across the whole flight. The error plot therefore does not start at zero and does not climb steadily. On flight-08p-lemniscate it runs 1.26 m at the start, dips to 0.15 m mid-flight and reaches 2.19 m by touchdown, where the estimate places the drone 1.4 m below the floor it has landed on.

The camera uses an equidistant (Kannala-Brandt) fisheye model. A viewer that assumes a pinhole or plumb-bob model will not undistort it correctly.

Two source streams are not carried. loop_depth is typed as a 16-bit depth image, but only a few dozen of its 307,200 pixels are ever set, so it is a sparse projection of loop-closure features and not a depth map. points_aruco is empty in every message of every flight.

The source release is CC BY 4.0, and the conversion uses the same license. The data is at Voxel51/TII-RATM-Drone-Racing.

For more on checking a perception pipeline against a physical reference, see a roundup of open-source robotics repositories for 3D perception, VLA policies and sensor calibration. For more of my FiftyOne dataset notes, see my Voxel51 author page, and to inspect and curate your own multimodal data in the same tool, try Voxel51.

Frequently asked questions

What does TII-RATM-Drone-Racing contain?

Six flights of a quadrotor on a four-gate indoor track, three on an ellipse and three on a lemniscate. Each carries a fisheye camera and a 500 Hz IMU, a motion-capture reference, and the odometry estimate the drone computed from its own camera and IMU.

How big is the dataset?

Six flights, 10.9 minutes and 2,563 metres flown. In total that is 17,305 camera frames, 9,658 feature-track frames, 327,086 IMU samples, 293,219 odometry poses and 179,283 capture poses.

How is the tracking error measured?

A rigid transform is fitted over every time-matched pose in the flight and applied to the odometry, the usual way an absolute trajectory error is measured. Scale is not fitted, because the estimate is metric when inertial-aided and solving for scale would hide drift.

Why does the tracking-error plot not start at zero?

The rigid fit spreads the residual across the whole flight, so the plot shows the distance from the reference at each instant, not the distance from a shared starting point. On flight-08p-lemniscate it runs 1.26 m at the start, dips to 0.15 m mid-flight and reaches 2.19 m by touchdown.

Can I undistort the camera as a pinhole?

No. The camera uses an equidistant (Kannala-Brandt) fisheye model, and a viewer that assumes a pinhole or plumb-bob model will not undistort it correctly.