Skip to content

ingest: Aria and EVA uploads through the ingest pipeline - #746

Draft
SaveAanchal wants to merge 13 commits into
aanchal/ingest-dashboard-v2from
aanchal/ingest-formats
Draft

SaveAanchal wants to merge 13 commits into
aanchal/ingest-dashboard-v2from
aanchal/ingest-formats

Conversation

@SaveAanchal

Copy link
Copy Markdown
Contributor

Summary

Aria and EVA uploads go through the same ingest pipeline as MCAP: upload (dashboard or CLI) → watcher → convert → review → hand-off → committer. Built on aanchal/ingest-dashboard-v2.

  • Shared metadata: rows_from_metadata builds the recording, task and alias rows for every converter (MCAP unchanged), and one check covers every upload: main's required-field rules plus embodiment, rig_name, license and a valid vendor_recording_id. A missing field is rejected with a reason, never stored as a silent null.
  • Formats by file-name pattern: every file in a delivery must share one {id}; mismatched names or two recordings in one delivery are refused. stage.prepare is split into a per-format front half and a shared back half.
  • EVA: <id>.hdf5 + <id>_metadata.json. Recording id recording_id("eva", <id>), as the backfill, so a demo arriving both ways is committed once. Rig files are read as written today (task/robot_name keys, ["None"] objects as a waiver, layouts spelled with spaces). A missing wrist camera is rejected before converting (eva_to_lance: convert raw EVA demos into Lance deliveries #729).
  • Aria: <id>.vrs + <id>_metadata.json + mps_<id>_vrs/**. Metadata comes from the upload, not SQL; MPS hand tracking and trajectory are checked before GPU time; device_serial is read from the VRS when missing; aria_to_lance uses the MANO model it is given. Recording id recording_id("aria", <id>), as the backfill.
  • License: rig files have none, so each lab vendor gets a default at onboarding (onboard <vendor> --license ...).
  • Uploads: the dashboard page and CLI accept any name a format allows (with that format's size limit); folder uploads keep mps_*/ subfolders.

Read-only check against every metadata file on R2: Aria 2,523 / 2,575 pass (560 with a waiver for review), EVA 3,250 / 3,254 (269). Every rejection is a problem in the data (no robot_name, wrong layout, placeholders).

Open before deploying

  • Which license the lab vendors get.
  • One run with the real AV1 encoder and MANO on a GPU node: pytest --integration tests/integration/test_ingest_aria.py with EGOVERSE_TEST_ARIA_RAW_DIR.
  • One test upload against a real R2 bucket (presigned parts, CORS exposing ETag).
  • Expected EVA cameras: assumes front_img_1 plus a wrist camera per arm in use.

Test plan

  • pytest tests/unit: 2,013 passed, 1 failed (test_infra_port_wiring.py::test_pi_loss_is_reduced_over_the_packed_width, needs openpi)
  • End to end on two real Aria recordings (MANO off, libx264 in place of AV1) into a scratch Lance root
  • Browser check of the upload page on a local store: an Aria folder with its MPS subfolders and an EVA pair upload and queue byte-identical; a delivery with mismatched ids is refused
  • Not run: AWS Batch, R2 uploads, GPU/AV1/MANO

🤖 Generated with Claude Code

SaveAanchal and others added 13 commits October 27, 2026 01:37
egomimic/rldb/lance/catalog.py builds a recording's recordings / tasks /
aliases rows from the shared snake_case metadata (rows_from_metadata, plus
episode_task for recordings without per-segment tasks and objects_list for
comma text or lists). The MCAP converter now uses it; EVA and Aria ingest will
too, so every format fills the catalog the same way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
egomimic/ingest/metadata.py checks an upload's metadata in the shared
snake_case keys with main's rules (required_metadata: task_name, operator,
lab, scene, objects; only scene/objects waivable via missing_metadata) plus the
recording keys every upload must carry (embodiment, rig_name, device_serial,
license) and a usable vendor_recording_id. A missing or null key is a problem
with a reason, never a silent null. The catalog's objects_list now reuses
required_metadata's parser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A format's required/optional entries are now patterns: `{id}` is one path
component every file of a delivery must agree on (the recording's vendor id,
recording_key()), `*` / `**` glob within and across components. Stems that
disagree, or two recordings in one delivery, no longer match; files of another
id are unexpected. Literal names (MCAP's episode.mcap) behave as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
stage_converted() does what follows a format's own validate + convert: stage
the delivery with its labels, write the reviewer's diff, hash the files, write
the pin (format, source, settings, report, waived metadata) and swap the folder
in atomically. MCAP's prepare keeps its front half (validate, convert, MCAP
report fields) and calls it; EVA and Aria will too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
An EVA delivery is <id>.hdf5 + <id>_metadata.json (egomimic/ingest/eva.py),
<id> the demo's start in epoch ms. Its prepare checks the metadata with the
shared rules (the rig's legacy task / robot_name keys are read as task_name /
embodiment; fps from the metadata, 30 or 60, default 30), then prechecks before
converting: a plausible epoch-ms id, the numeric arrays, and the cameras the
embodiment needs (the front camera and the wrists of its arms), so a demo
missing a wrist camera is rejected with a reason instead of breaking the
loader later (#729). It converts with eva_to_lance.convert, builds the catalog
rows with rows_from_metadata, runs the committer's check_delivery before
review, and stages through stage_converted under source "eva" with
recording_id("eva", <id>), as the backfill does.

Formats now declare the settings their prepare takes; prepare_delivery passes
only those (EVA: ffmpeg). The reviewer's diff takes validity from the steps'
own *_valid columns, so it works for robots too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sample of raw_v2/eva/*_metadata.json (152 of 3477, read-only) shows the rigs
write embodiment = the robot (eva, eve), robot_name = the arm layout, and no
rig_name, device_serial or license. The EVA mapping now reads robot_name as
the embodiment and the robot as rig_name when that is missing; license
defaults to the vendor's, recorded at onboarding (onboard --license, kept in
vendors.json and passed by the conversion job). device_serial stays required
- only the rig knows which unit recorded a demo - so the rigs must add it.
Problems for these keys now say where each comes from. Tests start from a
real-shaped rig file, including one without robot_name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Checked against every raw_v2/eva/*_metadata.json on R2 (read-only):
3473 of 3477 now pass, 270 of them with a waiver for review.

- device_serial is optional for EVA: the rigs do not write it and Lance does
  not store it; when given it is kept in the review report.
- objects: ["None"] (any case, or []) is how the rigs say "no objects"; it is
  taken as a missing_metadata waiver of objects and flagged for review rather
  than rejected as a placeholder name.
- robot_name spelled with a space ("eva bimanual") is read as the layout.

The 4 files without robot_name stay rejected: their arm layout cannot be known.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
rig_metadata (task -> task_name, robot_name -> embodiment through a per-format
layout map, device as rig_name, vendor license default, objects ["None"] as a
waiver), read_metadata_file and start_time_problem move from the EVA format to
egomimic/ingest/metadata.py so the Aria format reads the rigs' files the same
way. No behaviour change for EVA.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
An Aria delivery is <id>.vrs + <id>_metadata.json + its MPS outputs in
mps_<id>_vrs/ (hand_tracking_results.csv and closed_loop_trajectory.csv
required, the rest of the folder kept as delivered), <id> the recording's start
in epoch ms (egomimic/ingest/aria.py). No SQL: the metadata comes from the
upload, read as the rigs write it (robot_name aria_bimanual / aria_left_arm /
aria_right_arm -> human_*, also the "robot name" key; the device as rig_name;
the vendor's default license; objects ["None"] as a waiver). device_serial is
optional and read from the VRS when the file has none.

Before any GPU time it checks the metadata, the epoch-ms id and that the MPS
CSVs have rows; it converts with aria_to_lance.convert (MANO settings pinned,
now honouring mano_model_dir), runs the committer's check_delivery, and stages
under source "aria" with recording_id("aria", <id>), as the backfill does.

Unit tests cover everything up to the conversion; the integration test
(EGOVERSE_TEST_ARIA_RAW_DIR) converts, stages and commits real recordings. It
passed locally on two raw_v2/aria recordings with MANO off and libx264 in place
of SVT-AV1 (this machine's ffmpeg has no SVT-AV1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Checked against every raw_v2 recording on R2 (read-only), metadata only:
Aria 2523/2575 pass (560 with a waiver for review), EVA 3250/3254 (269).

- rig_metadata takes the device a format implies: Aria files that already say
  embodiment human_bimanual (no device named) get rig_name "aria".
- "scene": 2 is the scene "2".

What still fails is the data itself: no robot_name (38 Aria, 4 EVA), an EVA or
bare "human" layout on Aria, debug / test placeholders, a person as the lab.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Starting an upload checked the file name against the formats' exact names, so
only episode.mcap / annotation.mcap could be sent through the dashboard API.
It now asks whether any format's patterns take the name (Format.accepts,
formats.accepting), with that format's size limit; the delivery as a whole is
still checked when it is marked complete (one recording id across its files).
The upload page explains {id} and ** in the accepted-files list; its folder
picker and the CLI already send paths relative to the folder, so an Aria
recording's mps_<id>_vrs/ subfolders upload as they are.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rig files name the device in "embodiment" ("eva") and the arm layout in
robot_name. Ingest also reads the layout from "robot name" and spelled with
spaces ("eva right arm"), but eva_to_lance.convert re-read the raw file and only
knew robot_name, so those uploads passed the checks and then failed converting
with KeyError: 'eva'. prepare now passes the normalized embodiment, as Aria's
prepare already does; without it, convert reads the file as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant