数据格式与质量导出

实时录制只写 LeRobot v2.1 原始布局;LeRobot v3.0、HDF5 和 MCAP 都是质量审查之后的导出产物。

先划清录制与导出的边界

EVA 的实时采集链路和离线格式转换是两个阶段。COLLECT 在 START 与 STOP 之间接收遥操作数据,保存 worker 对原始快照做对齐、校验和打包,然后写入 LeRobot v2.1 episode。录制过程中不会根据下拉框分支去写 v3.0、HDF5 或 MCAP;这些格式只在已有 v2.1 数据集上运行质量拆分和导出。这样可以让采集时序、回放方式和原始证据保持稳定,也让同一批数据能够在审查后重新选择产物格式。

阶段输入 / 输出可见入口
实时采集机器人状态、下达动作、相机帧 → LeRobot v2.1 原始数据集COLLECT 的 START、STOP、后台保存队列
质量审查自动质量标记,加上可选的 qc_verdict 与备注QUALITY CHECK / REPLAY 的 PASS、FAIL
质量导出v2.1 原始数据集 → accepted 与 rejected 两个子集,按所选格式写出COLLECT 的 QUALITY EXPORT 面板
发布 / 上传只将当前格式的 accepted 导出目录递归上传UPLOAD ACCEPTED(需配置上传后端)

转换模块注册的格式共有四种:原生 lerobot_v21lerobot_v3hdf5mcap。其中本页的新增重点是后三种。选择原生 v2.1 也会执行质量拆分并复制成 accepted/rejected,而不是把未审查的原始目录直接当作可发布目录。

没有通用公开命令行入口。当前用户路径是控制台 COLLECT 的 QUALITY EXPORT 面板。源码中的 export_dataset_by_quality 是供程序调用的 Python API,不是已声明的通用 CLI 命令;请勿据此编造 eva export 等命令。

在 COLLECT 中完成一次数据生命周期

启动带有 collection 配置的 EVA 后,COLLECT 面板会把数据集名称和任务提示词分开显示。配置中的 collection.tasks 提供 DATASET NAME 与 TASK / PROMPT 的选择项;选中的数据集实际位于 <log_dir>/<数据集名>/raw/。空的 log_dir 会先按配置规则落到工作目录,再由同一规则解析路径。

顺序操作发生的事情
1选择 DATASET NAME 与 TASK / PROMPT任务字符串写入 episode 元数据;不同数据集名称对应不同的 raw 目录。
2打开 MOTION,按 START RECORD开始接收本次 episode。尚未完成遥操作跟踪的预热帧会跳过,不会被写成空动作。
3按 END / SAVE(STOP)停止采集,把快照交给后台保存 worker;状态从 COLLECTING 变为 SAVING,保存结束后回到 IDLE
4QUALITY CHECK选中已保存的 episode 回放,标记 PASS 或 FAIL,并可保存备注。自动检查结果也会写入元数据。
5QUALITY EXPORT选格式并按 EXPORT DATASET。面板轮询异步 job,显示 episode 进度与 accepted/rejected 数量。
6UPLOAD ACCEPTED只有当前所选格式已有完成的 accepted 导出,并且上传配置可用时才会启用;上传也异步报告文件和字节进度。

START 后采集与保存、图像编码分离运行;主机跟不上时队列会增长并记录告警。STOP 会校验、打包并排队保存,零帧 episode 不写入。重新启动同一配置会继续追加 episode,不覆盖原始数据。按 CANCEL 则清除当前尚未结束的快照;它不是一次失败的质量导出,也不会产生 accepted 或 rejected 目录。

先等保存完成再审查。只有保存完成、出现在 episode 历史中的项目才可回放或标记;QUEUE_FULL 时应等待 IDLE,必要时调整配置的 fpssave_queue_max

如何拆分 accepted 与 rejected

原始 v2.1 的 meta/episodes.jsonl 每行代表一个 episode。采集保存时,所有自动检查通过的行写入 quality: "green",任一检查失败则写入 quality: "red",并带有 quality_issues。QUALITY CHECK 的人工决定会以 qc_verdict: "pass"qc_verdict: "fail" 写回同一行;只保存备注时可以没有 verdict。导出时,源码使用如下优先级:

元数据状态导出归类说明
qc_verdict == "pass"accepted人工 PASS 优先,即使自动 quality 曾为 red 也接受;这表示操作者明确确认了该 episode。
qc_verdict == "fail"rejected人工 FAIL 一定拒绝。
没有上述 verdict,且 quality == "red"rejected自动质量问题未被人工 PASS 覆盖。
没有上述 verdict,且质量不是 red(通常为 green)accepted导出器按源码的默认规则接受;若需要人工闭环,应在导出前完成回放与标记。

accepted 和 rejected 都是完整的数据集子目录:各自包含重新编号后的 episode、相应的视频或内嵌图像、任务表、统计、原始来源字段以及质量标记。拆分时不会把原始 episode 的质量原因抹掉;同一子集内部的 episode 会按该子集重新编号,Parquet 中的 episode_index 与全局 index 也会重写为连续值,以便下游读取。

阅读 quality_split.json

每个输出目录的 meta/quality_split.json 是来源和门控标记,不是可忽略的装饰文件。它至少包含以下四个关键字段:

字段含义使用方式
source_dir被拆分的 v2.1 原始数据集绝对路径确认导出确实来自当前 COLLECT 数据集,而不是另一个目录。
subsetacceptedrejected标明目录可以代表哪一类质量结果;上传端只允许 accepted。
dataset_format实际写出的格式:lerobot_v21lerobot_v3hdf5mcap阻止把一个格式的目录冒充为另一个格式。
source_episode_indices列表位置对应输出 episode,值是原始 v2.1 的 episode_index把重编号后的文件追溯回原始录制,并核对 accepted/rejected 覆盖范围。

例如,若 accepted 的列表是 [0, 3],那么 accepted 中的新 episode_index 0 与 1 分别来自原始 episode 0 与 3;它们不是原始编号 0 与 1 的简单别名。该列表也解释了为什么转换后仍能追溯原始 episode,即使每个子集都从零开始编号。

暂存、成对发布与回滚

质量导出不是边转换边把半成品暴露为最终目录。导出器先为 accepted 和 rejected 各建立临时 staging 根;非 v2.1 格式先在临时位置完成 v2.1 拆分,再分别写出目标格式。两个子集和各自的元数据都完成后,发布器才尝试把它们移动到目标路径。目标目录由当前原始目录和格式共同决定。

text
<log_dir>/<dataset-name>/raw/
└── ...                                  # 实时采集的 v2.1 原始数据

<log_dir>/<dataset-name>/export/<format>/
├── accepted/                            # 当前格式、可发布子集
└── rejected/                            # 当前格式、被拒绝子集

上面是目录名恰为 raw 时的路径。如果后端得到的源目录名称不是 raw,源码会采用 <source-name>_export/<format>/accepted.../rejected;不要把这一规则硬编码成不存在的固定路径。Console 为当前数据集和格式计算路径,并把实际路径放入导出 job。

发布是 accepted/rejected 的一对逻辑结果:任一转换、路径检查或第二个目录发布失败,临时目录会清理,最终 accepted 与 rejected 不会留下半成品。若替换已有输出,发布器会先暂存旧目录;后续发布失败时删除已经发布的新目录并恢复旧目录。因此重新导出不会把一边换成新版本、另一边却保留旧版本。这个“原子发布”描述的是一对结果的一致性,不代表单个文件写入期间可以被外部读取。

帧数不一致会让整对发布失败。例如某相机视频只有 2 帧而 Parquet 有 3 行,v3、HDF5 或 MCAP writer 会报错;accepted 和 rejected 都不发布,也不会留下可误上传的半成品。修复原始数据或重新录制后,再从头导出。

原始 LeRobot v2.1 布局

理解三种导出格式前,先确认源数据的证据形态。一个选定数据集的实时录制目录以 meta/info.jsoncodebase_version: "v2.1" 为准;源读取器会拒绝 codebase_version 不是 v2.1,或显式 dataset_format 不是 lerobot_v21 的质量拆分输入(未写入该字段时按 v2.1 处理)。

text
<log_dir>/<dataset-name>/raw/
├── data/
│   └── chunk-000/
│       ├── episode_000000.parquet       # 每帧一行的状态、动作和时间列
│       └── episode_000001.parquet
├── videos/
│   └── chunk-000/
│       └── <video-key>/
│           └── episode_000000.mp4       # 每个相机、每个 episode 一个 MP4
└── meta/
    ├── info.json                         # v2.1、fps、列与视频特征
    ├── episodes.jsonl                    # episode、任务、长度、质量与 QC
    ├── episodes_stats.jsonl              # 可用时的逐 episode 统计
    ├── tasks.jsonl                       # task_index 与任务文本
    └── stats.json                        # 数据集统计

原始 Parquet 的 timestamp 使用配置的目标 fps 生成等间隔时间;capture_time 保留采集时刻用于诊断。相机视频与数据表应逐帧对应。原始写入只会把采集得到的向量、图像和质量信息保存下来,格式转换不会重新推断机器人的动作或替换质量判定。

LeRobot v3.0:分片表与视频

选择 LeRobot v3 时,导出器读取每个 accepted 或 rejected 的 v2.1 episode,把数据表写成 v3 风格的 Parquet 文件,把各相机帧写成与分片对应的 MP4 文件。它仍然是离线导出,不改变原始 v2.1 目录,也不在采集时实时产生 v3 文件。

text
<export-root>/lerobot_v3/accepted/
├── data/
│   └── chunk-000/
│       ├── file-000.parquet             # 一个数据分片可含多个 episode
│       └── file-001.parquet              # 达到边界或 schema 改变时滚动
├── videos/
│   └── <video-key>/
│       └── chunk-000/
│           └── file-000.mp4              # 该分片内该相机的连续帧
└── meta/
    ├── episodes/
    │   └── chunk-000/file-000.parquet    # episode → 数据/视频分片索引与时间范围
    ├── tasks.parquet                     # 任务表的 Parquet 版本
    ├── info.json                          # dataset_format=v3、路径与特征
    ├── episodes.jsonl、tasks.jsonl       # 保留的通用元数据
    ├── stats.json
    └── quality_split.json

数据 writer 以 episode 为写入单位:在当前表大小加上本 episode 的 Arrow 表大小将超过约 100 MiB,或 schema 发生变化时关闭当前 Parquet/视频分片并滚动到下一个 file-NNNmeta/info.json 记录数据与视频文件的大小约定,以及 data_pathvideo_path 模板。一个 episode 的元数据会记录 data/chunk_indexdata/file_indexdataset_from_indexdataset_to_index;有视频时还记录每个视频的分片索引、文件索引、from_timestampto_timestamp

因此,使用 v3 时不要按 v2.1 的“每个 episode 一个 Parquet 与一个 MP4”假设读取;应先读取 meta/episodes/chunk-000/file-000.parquet 的定位列,再打开对应的 Parquet 与相机 MP4 分片。任务同时有 tasks.jsonl 和 writer 生成的 tasks.parquet,便于按任务索引恢复语义。任何视频帧数不等于该 episode 表的行数都会中止 writer,整对 accepted/rejected 都不会发布。

HDF5:每个 episode 一个文件

HDF5 导出把一个 episode 的非图像列、图像数组和 episode 元数据放入同一个文件。它不保留独立 MP4;源 v2.1 的视频会被解码为帧,并以内嵌的可追加 HDF5 数据集写入 images 组。导出后的 meta/info.json 将这些特征标为 dtype: "image",设置 embedded_images: truetotal_videos: 0,并移除 video_path

text
<export-root>/hdf5/accepted/
├── data/
│   └── chunk-000/
│       ├── episode_000000.hdf5
│       └── episode_000001.hdf5
└── meta/
    ├── info.json、episodes.jsonl、tasks.jsonl、stats.json
    └── quality_split.json

episode_000000.hdf5
├── columns/                               # 状态、动作等非图像列
│   └── item_000000/{value, attrs[key,kind]}
├── images/                                # 每个图像键一个可追加 value
│   └── item_000000/{value, attrs[key,kind]}
└── metadata/                              # episode 行 + fps
    └── item_000000/{value, attrs[key,kind]}

三个顶层组的键值都用带 key 属性的 item_NNNNNN 表示。数值数组通常存为 kind: "array";字符串、bytes、None 和无法直接映射的容器会记录相应的 kind,容器可使用 MessagePack 字节保存,从而不丢失元数据结构。metadata 还写入该 episode 的原始元数据和源 fps

每个图像 value 的第 0 轴是帧轴,数据集可追加;第一帧确定后续帧的形状。若同一图像键后续帧改变 H×W×C 形状,或最终观察帧数不是 Parquet 行数,HDF5 writer 报错。文件会停留在临时 staging,而不会作为最终 accepted 或 rejected 目录发布。

MCAP:EVA 消息与内嵌图像

MCAP 导出按 episode 生成一个 .mcap 文件。它适合已经有 MCAP 读取链路、并希望按消息主题和时间顺序消费数据的系统。当前实现不是 ROS bag,也没有使用 ROS message schema;不要把 topic 名称解释成 ROS 话题类型或把 payload 当成 ROS 消息。

text
<export-root>/mcap/accepted/
├── data/
│   └── chunk-000/
│       ├── episode_000000.mcap
│       └── episode_000001.mcap
└── meta/
    ├── info.json、episodes.jsonl、tasks.jsonl、stats.json
    └── quality_split.json

MCAP records
├── topic: episode                         # 一个 episode 元消息
│   └── MessagePack({columns, images, metadata})
└── topic: episode/image/<video-key>       # 每相机、每帧一个 MessagePack 图像
    └── sequence=frame_index; shared timestamp per frame

writer 使用 MCAP profile eva-client 与 library eva-client。它注册 eva_client.episode 描述,随后以 message_encoding: "messagepack" 写入名为 episode 的元消息;该消息的内容包括非图像列、表中已有的内嵌图像和 {...episode row, fps} 元数据。这里的注册描述是当前实现的 MCAP 元数据,不是 ROS schema;实际 payload 编码是 MessagePack。

源视频不会以独立 MP4 搬到 MCAP 目录。每个相机注册 episode/image/<key> 主题,图像帧直接 MessagePack 编码。对 frame index 0、1、2……,所有相机都使用同一个按目标 fps 计算的时间戳(第一个时间戳为一个帧周期),并将该 index 作为 sequence;这样多相机消息可以按帧对齐。writer 还会检查每个相机没有提前结束,也没有多余尾帧;不一致即失败并阻止整对发布。meta/info.json 同样标记内嵌图像、total_videos: 0,并移除 video_path

四种格式的差异

下表只比较源码实际写出的目录与内部结构,不把任何格式宣传成训练框架的通用标准,也不声称某种格式普遍优于其他格式。原生 v2.1 是录制的边界;后三种是从同一份源数据导出的不同消费形态。

格式主数据布局图像形式元数据 / 适合检查的入口
lerobot_v21data/chunk-NNN/episode_NNNNNN.parquet,每 episode 一个表videos/chunk-NNN/<key>/episode_NNNNNN.mp4,每相机独立文件meta/info.jsonepisodes.jsonl、任务与统计;也是实时录制源
lerobot_v3data/chunk-000/file-NNN.parquet 分片;episode 通过 metadata 定位按相机与分片写 MP4;与数据分片关联meta/episodes/chunk-000/file-000.parquet 记录索引和时间范围,另有 tasks.parquet
hdf5data/chunk-NNN/episode_NNNNNN.hdf5,每 episode 一个文件内嵌在 images 组的帧数组,无独立 MP4一个 HDF5 文件内含 columnsimagesmetadata 三组
mcapdata/chunk-NNN/episode_NNNNNN.mcap,每 episode 一个消息文件元消息内嵌图像列,视频图像作为 episode/image/<key> MessagePack 消息,无独立 MP4episode 元消息 + 按帧共享时间戳和 sequence;profile 为 eva-client

所有导出结果都保留通用的 meta/info.json、任务和统计(源文件存在时复制),并写入 quality_split.json。对于 HDF5 与 MCAP,不能只检查一个文件名就假定有视频目录;应检查 embedded_imagestotal_videosvideo_path 是否符合内嵌图像布局。对于 v3,应以 episode 定位表解释 Parquet/MP4 分片,而不是套用 v2.1 的单 episode 文件路径。

共同校验与核验办法

导出器首先把输入当作 v2.1 数据集验证:必须存在 meta/episodes.jsonlmeta/info.json,版本和格式必须匹配,episode 索引必须唯一;每个源 Parquet 与每个列出的相机文件都必须存在。源表需要包含 episode_index 和全局 index,任务索引必须能在 tasks.jsonl 中找到,统计列也必须能从逐 episode 统计或表列中得到。空 episode 会被 writer 拒绝。

校验层检查内容失败结果
采集保存episode_too_short、非单调时间戳、missing_camerainvalid_image_shape、配置字段缺失或维度错误、non_finite_valueframe_count_mismatchepisode 标红,原因持久化;缺失或非法向量可按固定长度补零,数据仍保持规整。
质量拆分v2.1 版本、文件路径、唯一索引、任务索引、统计与 accepted/rejected 规则不开始发布;临时 staging 清理。
LeRobot v3非空表、每个视频解码帧数与表行数一致、分片 schema 一致转换 job failed,成对输出不发布。
HDF5非空 episode、图像形状稳定、每个图像数据集帧数与表行数一致转换 job failed,成对输出不发布。
MCAP非空 episode、每个图像主题没有缺帧或尾帧,所有相机按相同 frame index 写入转换 job failed,成对输出不发布。

核验一份导出时,先分别打开 accepted 与 rejected 的 meta/quality_split.json,检查 source_dir 是当前 raw 目录、subset 正确、dataset_format 与选择一致,再检查两个 source_episode_indices 是否按预期覆盖原始索引。接着将 info.json 的总 episode/帧数与实际文件对照:v2.1 对照每个 Parquet 行数和 MP4 帧数,v3 对照 episode 定位表、分片范围和视频帧数,HDF5 对照每个 images/*/value 第 0 轴,MCAP 对照每个图像 topic 的消息数量、sequence 与共享时间戳。

最后做一次真正的读取测试:用下游的 Parquet、HDF5 或 MCAP reader 读取一条状态、一条动作和一帧图像;不要只凭文件扩展名判断成功。MCAP reader 应看到一个 episode 元消息,以及每个相机的 episode/image/<key> 消息;它们的数据是 MessagePack,不是 ROS bag/schema。核验通过后,再执行上传门控。

为什么只能上传当前 accepted 导出

UPLOAD ACCEPTED 的按钮由当前选中的格式和已完成的导出 job 共同门控。前端切换格式时会清空已记录的 acceptedDir、导出与上传状态,并显示“需要先导出”;因此刚完成的 HDF5 accepted 不能直接当作 MCAP 上传。格式不同意味着目录布局、图像承载方式和 info.json 语义不同,重新导出是得到匹配产物的必要步骤,不是重复复制。

后端收到上传请求时还会检查:当前源目录与格式是否有最新的 completed export job;accepted 路径是否与该 job 一致;meta/quality_split.json 是否可读;subset 必须是 accepteddataset_format 必须等于请求格式;source_dir 必须解析到当前源目录。任何 rejected 目录、旧格式、来源不符或未导出的目录都会被拒绝。没有配置上传后端时,接口也不会启动上传。

当前配置解析的公开上传后端是 collection.storage.sftp。它需要主机、端口、可选用户与 identity file,以及绝对、规范化的远端目录;Console 会递归统计文件和字节并异步上传。SFTP 先将文件写入同一父目录下的临时 staging 目录,再用远端移动发布;目标已经存在时不会覆盖旧目标,而会选择带时间戳的副本名称。上传失败会清理远端 staging,新目标不会被发布;已存在的旧目标不受这次失败影响。

上传的是发布目录,不是 raw 目录,也不是 rejected。在远端核验时应记录 job 的格式、accepted 路径、远端目的地和文件/字节数;若要发布另一种格式,先回到面板重新选择并完成该格式的 QUALITY EXPORT。

故障处理

现象原因与处理
QUALITY EXPORT 立即失败检查当前 raw 的 info.json 是否 v2.1、episodes.jsonl 是否存在且非空,以及数据表、相机文件、任务表和统计是否齐全。修复来源后重新点击 EXPORT DATASET。
某 episode 报视频帧数不一致对照该 episode 的 Parquet 行数和每个相机的解码帧数。v3、HDF5、MCAP 都将整次成对发布判为失败;不要上传残留临时目录,应修复或重录再导出。
accepted 有旧文件,rejected 也不确定不要手动拼目录。Console 重新导出时会使用同一格式路径和 replace_existing=True,发布器负责替换与回滚;导出完成后重新检查两边 marker。
UPLOAD ACCEPTED 是灰的或返回“先导出当前数据集”确认选择器格式等于最近完成的导出格式,accepted marker 的 subset、format、source_dir 均匹配,且 SFTP 配置存在。格式切换、导出失败或只有 rejected 都不能绕过门控。
SFTP 上传失败检查 OpenSSH sftp/ssh、身份文件、主机、端口和远端权限。源码会清除新 staging 并保留旧目标;网络或凭据恢复后可从 accepted 目录重试。
MCAP 下游读不到 ROS 消息这是消费假设错误:当前文件使用 eva-client profile 和 MessagePack,且不是 ROS bag/schema。使用能读取 MCAP、解 MessagePack 并理解 EVA topic 的适配器。

状态面板会显示 queued、running、completed 或 failed,以及 episode、文件和字节进度。failed job 的错误文本用于定位问题;转换失败时不要把“已创建临时文件”当成成功信号,最终可见目录才是发布结果。若人工 PASS 覆盖自动 red,应把该决定和备注一并保留,便于之后理解为什么它进入 accepted。

按消费方式选择格式

选择原则是下游真正需要的目录和读取方式,而不是格式名称听起来是否“标准”。同一 raw 数据可按不同需求多次导出;每次导出都应独立核验,并只上传当次 accepted。

不存在源码支持的“通用训练标准”“最优格式”或“格式越新越好”的保证。若下游同时能读取多种格式,比较其已实现的解码器、并发读取、图像内存和元数据需求;若下游只支持一种格式,就以该读取器的契约为准。无论选哪种格式,都把 raw 目录和两个质量子集的 marker 一并保留,避免日后无法回溯来源。

交付前清单

  1. 确认实时录制已回到 IDLE,raw 目录包含 v2.1 的 info.json、episode 表、相机文件与质量元数据。
  2. 在 QUALITY CHECK 中回放并处理需要处理的 episode,理解 green、red、PASS、FAIL 的优先级。
  3. 选择一个格式并点击 EXPORT DATASET,等待 job completed;同时检查 accepted 与 rejected 的 episode/frame 汇总。
  4. 检查两个 quality_split.json 的来源、subset、format 和 source episode 索引,检查目标格式的图像承载方式与目录。
  5. 用真实下游 reader 做最小读取和帧数核验。v3 检查分片定位,HDF5 检查三组和图像第 0 轴,MCAP 检查 MessagePack topic、sequence 与时间戳。
  6. 只有在格式未切换、当前 accepted marker 匹配、SFTP 已配置且导出 job 是 completed 时,才点击 UPLOAD ACCEPTED。
保留可追溯性。raw 是录制阶段的唯一格式边界;accepted/rejected 是质量决策的成对发布;具体 v3/HDF5/MCAP 文件是面向消费者的导出。三者不要互相替代或混称。