noteflowai/robot-reel · 0.18.1 · 2026-09-30

fix: name the first bad frame in LeRobot timeline errors

robot-reel lerobot now names the first frame that breaks an episode's timeline. It still stops with exit status 2 and writes no output. Each message gives the dataset-relative data file, the episode and the first bad frame_index. There are five cases: a missing frame (with the next recorded frame_index), a repeated frame, an unexpected value (negative, null, NaN or non-numeric), a frame with no finite timestamp, and a timestamp that is not after the previous frame's (both values shown). Frame indices are now checked before the metadata length check, so a frame dropped mid-episode is reported as a gap instead of a count mismatch. A missing final frame keeps the old 'Episode metadata lists N frames but ... holds N-1' message. Every timeline message ends with 'Robot Reel does not reorder, fill or interpolate frames; repair or re-export the episode.' The accept/reject rule is unchanged, so float or bool frame_index columns equal to 0..length-1 are still accepted. docs/lerobot.md has a new 'Timeline errors' section. Controller results for commit f8e0c1e: before the fix, the new test file ran 10 tests on the base code with 5 failures (the old generic messages) and 5 skips. After the fix, one gated run passed the new test file, the full Python unittest suite, npm run check:js, node --test (52 of 52) and robot_reel.pages --write (0 files updated). I wrote this delivery text only; I did not run any of these checks myself.

Use it

Prerequisites: robot-reel with the lerobot extra (python -m pip install 'robot-reel[lerobot]'), which provides pyarrow. You also need a local LeRobot v2.1 dataset that you own, with per-episode parquet files, where episode 3 has at least 59 frames. This is a manual damage reproduction on a COPY of that dataset. The copy is a synthetic defect, not real collection damage.

cp -r ~/datasets/my_so101_run /tmp/rr_damaged
python - <<'EOF'
# Manual synthetic damage: drop frame_index 57 and leave meta/episodes.jsonl unchanged
import pyarrow.parquet as pq, pyarrow.compute as pc
p = '/tmp/rr_damaged/data/chunk-000/episode_000003.parquet'
t = pq.read_table(p)
pq.write_table(t.filter(pc.not_equal(t['frame_index'], 57)), p)
EOF
robot-reel lerobot /tmp/rr_damaged --episode 3 --output /tmp/rr_out; echo "exit $?"
# Expected: exit 2, no /tmp/rr_out files, and stderr:
# robot-reel lerobot: error: Episode 3 in data/chunk-000/episode_000003.parquet is missing frame_index 57 (next recorded frame_index is 58); frames must be 0..length-1 without gaps. Robot Reel does not reorder, fill or interpolate frames; repair or re-export the episode.

For a real damaged episode, filter it for the named frame_index, repair or re-export it, then rerun the same command. The repository's synthetic fixtures build their own temporary datasets, so no input file is needed to run them:
python -m unittest discover -s tests -p test_feature_4a9fd62e667c.py

Scope

Only the frame_index and timestamp checks in read_episode changed. Only the first defect is reported. The numbers are frame_index values, not file row numbers. Nothing is repaired, reordered or interpolated. Timestamp columns with more than one value per frame keep the generic 'Timestamps are missing or not increasing' message. One order change: a missing timestamp or frame_index column is now reported before a metadata length mismatch. All test fixtures are synthetic local v2.1 episodes with no cameras. The cases that ran use FakeReaderTimelineTest, a labeled stand-in for pyarrow.parquet that stores columns as JSON. That class exercises the error cases and the float64 frame_index export, --verify and --check-source path. It does not prove real parquet behavior. In the controller's pre-fix run, the 5 RealReaderTimelineTest cases (real parquet files) were skipped. The passing run used the same container image, and its log excerpt shows no skip counts, so no claim is made that the real-parquet cases ran. v2.0, v3.0 and Hub sources share the code path but have no dedicated fixtures. Passing tests show the specified messages for these fixtures; they do not show coverage of every real-world corruption. The controller's node --test run passed 52 existing browser regression checks, including the desktop and mobile LeRobot replay checks. This feature changes no UI, and no visual review of it was performed. The RR-01 first-use observation with three independent users remains open.

Upgrade and rollback

Patch-level upgrade. There are no schema, file-format or CLI flag changes, and the same episodes are accepted and rejected as before. Previously valid episodes, including float frame_index columns equal to 0..length-1, export with the same trace content. As in every release, the exporter.robot_reel version string changes, so the episode.json and index.html bytes and their manifest.json hashes differ from exports made by earlier versions. Existing exports still pass --verify, and --check-source against their dataset still passes. Rejected episodes still exit 2 and write nothing. Scripts that match the old exact strings 'Frame indices are not 0..length-1' or 'Timestamps are missing or not increasing' must switch to the new phrases, for example 'missing frame_index', 'repeats frame_index', 'no finite timestamp at frame_index' or 'does not reorder, fill or interpolate'. To roll back, reinstall the previous robot-reel version. No data migration is needed, and existing exported replays are unaffected.

Source commit: 7f71f584c6c4acdbe0ee7003be19ef8d09d43295