Skip to main content

具身智能多模态数据集工具库:统一的读写、校验和格式转换(含完整工作空间)

Project description

modelbest_robo_dataset

具身智能多模态数据集工具库。提供统一的读写、校验、格式转换与一整套数据工程脚本。

安装

pip install modelbest-robo-dataset

PyPI 主页:https://pypi.org/project/modelbest-robo-dataset/

自 v0.3.0 起,pip install 即可获得完整工作空间——包内同时包含 SDK、命令行脚本、URDF/标注资源和所有 SOP 文档,不再需要 git clone 就能上手。

可选 extra:

pip install modelbest-robo-dataset[torch]      # 训练时需要 EmbodiedDataset (PyTorch)
pip install modelbest-robo-dataset[robomind]   # 转换 RoboMIND 时需要 h5py
pip install modelbest-robo-dataset[all]        # 全装

5 行入门

from modelbest_robo_dataset import EmbodiedReader, EmbodiedWriter, Episode

reader = EmbodiedReader("/path/to/mb_dataset")
for episode in reader:
    print(episode.duration, len(episode.messages))

更多用法见下文 快速开始

包内提供的资源

pip install 后,除 SDK 外还附带:

类别 路径(包内) 用途
CLI 脚本 modelbest_robo_dataset.scripts.* 数据转换、校验、可视化
SOP 文档 modelbest_robo_dataset/docs/*.md 给 Cursor / AI 协作的标准 Runbook
数据集配置 modelbest_robo_dataset/configs/datasets/*.json load_dataset_config() 默认读取的内置配置
配置/标注 modelbest_robo_dataset/annotations/ URDF、mesh、meta_annotations.json 模板

直接调用 CLI 脚本:

python -m modelbest_robo_dataset.scripts.convert --help
python -m modelbest_robo_dataset.scripts.verify_e2e --help
python -m modelbest_robo_dataset.scripts.expand_skeleton --help

访问包内文档/资源:

from importlib.resources import files

sop_dir = files("modelbest_robo_dataset") / "docs"
print((sop_dir / "sop_droid_adapter.md").read_text())

franka_urdf = files("modelbest_robo_dataset") / "annotations" / "custom_urdfs" / "franka" / "franka.urdf"

SOP 索引(适配新数据集 / 发版 / 校验)

场景 SOP
适配 LeRobot 类数据集 sop_lerobot_to_mb_dataset.md
适配 RLDS/TFRecord 数据集(如 DROID) sop_droid_adapter.md
通用数据集适配方法论 sop_dataset_adapter.md
数据生产 / 校验 / 可视化 sop_data_production.md · sop_data_verification.md · sop_visualization.md
发布到 PyPI sop_pypi_release.md

每个 SOP 都是给 Cursor / AI 下指令的标准 Runbook——直接把 请按 sop_droid_adapter.md 适配 droid 数据集 丢给 AI 即可执行。

想贡献代码?

git clone git@modelbest.codeup.aliyun.com:modelbest/embody/datapipe/mb-robo-dataset.git
cd mb-robo-dataset
pip install -e ".[dev,all]"
pytest tests/

设计理念

与 LeRobot 的区别

LeRobot 是优秀的机器人学习框架,本库在数据层面与其互补而非替代:

LeRobot modelbest_robo_dataset
定位 端到端训练框架(数据+策略+部署) 纯数据工具库(格式转换+存储+读取)
骨架存储 Parquet 表 (每行一帧) SSTable partition (每条一个 episode)
数据模型 扁平表:每帧一行,所有 feature 列铺平 嵌套结构:Episode → Message → Content,env/user/ai 三轨道
元信息 info.json + tasks.jsonl MetaContent (嵌在骨架内,Pydantic 序列化)
多模态 视频 + 状态 视频 + 状态 + 音频 + 力 + IMU + 语言指令
标注 task description task_id, user_id, scene_id, quality_rating, dim_names
视频 原始 MP4,按 chunk 分文件 per-episode MP4(默认)或逐帧 PNG(可选),H264 CRF23 + 720p 上限
扩展性 围绕 HuggingFace Hub 生态 围绕 modelbest_sdk SSTable 生态

为什么不直接用 LeRobot 格式

  1. 三轨道模型:env/user/ai 的消息结构天然支持人机交互场景(人类语音纠正、机器人语音回复),LeRobot 的扁平表不适合这种嵌套关系
  2. 多源异构:RH20T 有力传感器+音频,fuse 有 IMU+触觉麦克风,RoboMind 有 3 种机器人变体——需要一个足够灵活的骨架来容纳这些差异
  3. 生产级存储:SSTable partition 支持大规模分布式训练的随机读取,比单个 Parquet 文件更适合 10 万+ episode 的场景
  4. TimeseriesName 自描述ai.action.delta_cartesian_position 本身就说明了控制空间和绝对/增量语义,不需要额外的 type 字段

与 LeRobot 的兼容

  • LeRobotSource 读取 LeRobot v3.0 目录布局(meta/episodes/*.parquet + data/chunk-*/file-*.parquet)。若 meta/episodes 目录缺失(如 RoboMIND LeRobot),自动从 data/ parquet 的 episode_index 列推导可用 episode 列表
  • TimeseriesName 的命名风格(env.obs.* / ai.action.*)兼容 LeRobot 社区惯例
  • dim_names 的设计参考了 LeRobot info.json 中的 features.*.names.motors
  • 对损坏的单路视频会记录 warning 并跳过该相机,不会因为单个坏视频中断整条 episode 的转换
  • 大体积 v3 布局(如 DROID 95K episodes):设置 lazy=True 启用懒加载模式,初始化时不读取 meta/episodesdata 的 parquet 文件,按需加载单个 episode 的元数据和数据。DROID 的 config 已默认启用 "lazy": true

Task 解析

LeRobotSource._resolve_task 按以下优先级解析自然语言任务字符串:

  1. Episode meta tasks 列表 — 首个非空字符串,或按整数 task_indextasks_df
  2. Episode meta task_indextasks_df
  3. Data parquet task_indextasks_df(v3.0 Libero 只在 data parquet 中存 task_index,不在 episode meta 中)
  4. Data parquet 的 task_fallback_column 列(仅当 config 中显式设置了 task_fallback_column 时,如 DROID 的 language_instruction
  5. tasks_df 首行(全局回退,仅适用于单任务数据集)

懒加载(Lazy Loading)

DROID 等超大数据集(95K+ episodes)在默认模式下会一次性加载所有 meta/episodes/*.parquetdata/*.parquet 到内存,导致 OOM。设置 lazy=True(config 或构造函数参数)启用懒加载:

默认模式 懒加载模式
初始化 读取所有 meta/episodes + data parquet 仅读取 meta/info.json + tasks.parquet
list_episodes() 从 episodes_df 提取 range(total_episodes)
load_episode(id) 从内存 DataFrame 查找 按需读取对应 chunk 文件,LRU 缓存最近 8 个文件
内存占用 全量(DROID ~数 GB) 仅当前 episode 所在文件(~数十 MB)
初始化耗时 DROID OOM DROID 0.04s

首次 load_episode 时会扫描所有 data/chunk-*/ 下 parquet 文件的 episode_index 列(仅读该列,非常轻量),构建 episode→file 映射索引。后续调用直接查索引并读取对应文件。

v3.0 共享数据文件(file-{file_index}.parquet,如 DROID/Libero)在非懒加载模式下也自动使用相同的按需加载策略。

视频处理

LeRobotSource.load_episode 返回 RawEpisode 时使用 video_files(视频文件路径字典)而非 video_frames(解码后的帧列表)。视频不在 source 层解码,由 EmbodiedWriter 在转换时按需复制或重编码为 per-episode MP4。

视频路径解析:按 episode parquet 中的 per-camera chunk_index / file_index + info.jsonvideo_path 模板拼路径。

元数据流水线(新模型)

modelbest_robo_dataset 的元数据由三个制品共同表达,职责互不重叠。本节给出总览,详细 schema 与字段归属规则见 docs/lerobot_meta_mapping.md

flowchart LR
    LR["LeRobot info.json + parquet"]
    Src["configs/sources/dataset.json<br/>HOW: state_key, camera_keys, lazy"]
    Ann["annotations/dataset.meta.json<br/>WHAT: timeseries spec, urdf, robot_type"]
    Boot["bootstrap_annotation.py (future)"]
    Conv["convert.py (future)"]
    Meta["MetaContent (derived from annotation)"]
    Verify["vla_verify (annotation as truth)"]
    StaticViz["generate_viz.py (static HTML)"]

    LR -->|"auto-infer"| Boot
    Src --> Boot
    Boot --> Ann
    Ann -->|"human review"| Ann
    Ann --> Conv
    Src --> Conv
    LR --> Conv
    Conv --> Meta
    Meta --> Verify
    Meta --> StaticViz
    Ann --> Verify
制品 文件 维护者 主消费者 角色
annotation annotations/<dataset>.meta.json 人工 converter / vla_verify WHAT —— 数据集应该是什么;converter 输入契约
source 配置 configs/sources/<dataset>.json dev converter HOW —— 怎么读 LeRobot 源 + 运行时开关
runtime metadata skeleton 内 MetaContent / TimeseriesRef / VideoRef converter 自动写 reader / 静态可视化 / vla_verify 派生输出

单一真值原则:

  • 数据集 metadata(robot_type / urdf_path / dim_names / timeseries_name / ...)→ annotation 为真值
  • 列名映射(state_key / camera_keys / task_key / lazy)→ configs/sources/ 为真值
  • 派生量(fps / total_frames / shape / dtype)→ source 数据本身为真值,不写入 annotation

迁移现状:当前仓库使用包内 modelbest_robo_dataset/configs/datasets/*.json(同时混了 WHAT 与 HOW),尚未迁到上面的两文件模型。迁移路线见 docs/lerobot_meta_mapping.md §9。

架构

modelbest_robo_dataset/
├── data_types.py              # Episode/Message/Content 类型定义
├── dataset_config.py          # 数据集配置注册表 (DatasetConfig + load/list)
├── lerobot_state_semantics.py # LeRobot observation.state 语义推断(关节 vs 末端)
├── writer.py                  # RawEpisode → 统一格式
├── reader.py                  # 统一格式 → 训练采样
├── validator.py               # State/Action 一致性校验
├── sources/
│   ├── base.py        # RawEpisode + EpisodeSource 接口
│   ├── lerobot.py     # LeRobot v3.0 读取
│   ├── rh20t.py       # RH20T (上海交大)
│   ├── fuse.py        # fuse/DIGIT (TFRecord/RLDS)
│   └── robomind.py    # RoboMIND (HDF5)
├── configs/datasets/  # 每个数据集的转换配置(pip 包内默认配置)
│   ├── libero.json
│   ├── droid.json
│   ├── agibotworld.json
│   ├── robomind_franka_*.json
│   ├── robotwin2_*.json
│   └── ...
├── annotations/       # URDF / mesh / meta annotation 模板
├── docs/              # SOP / 设计文档(随 wheel 分发)
└── scripts/           # 多源转换、校验、可视化工具
    ├── convert.py
    ├── expand_skeleton.py
    ├── convertion/
    ├── preprocess/
    ├── sim_comparison/
    └── vla_verify/

tests/
├── test_lerobot_source.py  # LeRobotSource 单元测试
└── test_sstable_record.py  # SSTable 序列化测试

数据集配置注册表

过渡期说明:本节描述当前包内 modelbest_robo_dataset/configs/datasets/*.json 用法。该文件同时混了「数据集 WHAT」与「源解析 HOW」,会在 docs/lerobot_meta_mapping.md §9 描述的迁移路线 Step 2–5 完成后,被 annotations/<dataset>.meta.json(WHAT)+ configs/sources/<dataset>.json(HOW)取代。本节内容当前与现状保持一致,便于过渡期使用。

modelbest_robo_dataset/configs/datasets/<name>.json 为每个 LeRobot 数据集定义转换参数,消除命令行逐一传参的需要。load_dataset_config("<name>") 默认读取这些包内配置;如果你需要实验性覆盖,可传入 configs_dir= 指向自己的目录。

配置文件格式

{
  "state_key": "observation.state",
  "action_key": "action",
  "state_type": "joint_position",
  "action_type": "joint_position",
  "camera_keys": ["observation.images.image"],
  "task_key": "index",
  "task_fallback_column": null,
  "lazy": false,
  "description": "..."
}

对于双臂等多列拼接的数据集,state_key / action_key 支持 列表 形式,读取时按顺序沿最后一维拼接:

{
  "state_key": ["observation.states.joint_position_left", "observation.states.joint_position_right"],
  "action_key": ["actions.joint_position_left", "actions.joint_position_right"],
  "camera_keys": ["observation.images.camera_front", "observation.images.camera_left_wrist", "observation.images.camera_right_wrist"]
}

上例将 left[7] + right[7] 拼接为 14 维 state/action。单字符串与列表均可,接口与 camera_keys 一致。

字段 说明
state_key 必填。data parquet 中 state 列名。单列用字符串,多列拼接用列表(如双臂 left+right)
action_key 必填。data parquet 中 action 列名。格式同 state_key
state_type 必填。映射到 TimeseriesName(joint_position / cartesian_position / ...)
action_type 必填。映射到 TimeseriesName(joint_position / delta_cartesian_position / ...)
camera_keys 必填。使用的相机 feature key 列表(如 ["observation.images.image", "observation.images.wrist_image"]
task_key tasks.parquet 中任务文本的来源。"index" = DataFrame Index(多数 v3.0 数据集包括 DROID/Libero/AgiBotWorld/RoboTwin2),其他字符串 = 重命名该列,null = 假定已有 task
task_fallback_column tasks.parquet 中找不到任务时,从 data parquet 的该列回退(如 DROID 的 "language_instruction")。null = 不回退
lazy 懒加载模式。true 时初始化不加载 meta/episodesdata parquet,按需读取单个 episode。适用于 DROID 等超大数据集(95K+ episodes),避免 OOM。默认 false
其余字段 convert.py CLI 参数对应,作为默认值(CLI 显式传入时覆盖配置)

已有配置

配置名 state_key action_key cameras lazy task_key 说明
libero observation.state action 2 - index LIBERO(long/spatial/object/goal 共用)
droid observation.state action 3 true index DROID 1.0.1(95K eps,懒加载)
agibotworld observation.states.joint.position actions.joint.position 8 - index AgiBotWorld(全 8 相机)
agibotworld_3rgb observation.states.joint.position actions.joint.position 3 - index AgiBotWorld(head + hand_left/right,快速转换)
robomind_franka_1rgb observation.states.joint_position actions.joint_position 1 - index RoboMIND Franka 单臂 8d,1 相机
robomind_franka_2rgb observation.states.joint_position actions.joint_position 2 - index RoboMIND Franka 单臂 8d,2 相机
robomind_franka_3rgb observation.states.joint_position actions.joint_position 3 - index RoboMIND Franka 单臂 8d,3 相机
robomind_ur_1rgb observation.states.joint_position actions.joint_position 1 - index RoboMIND UR 单臂 7d,1 相机
robomind_agilex_3rgb [joint_position_left, _right] [joint_position_left, _right] 3 - index RoboMIND agilex 双臂 7+7=14d,3 相机(列表拼接)
robomind_franka_fr3_dual observation.states.joint_position actions.joint_position 3 - index RoboMIND FR3 双臂 16d,3 相机
robotwin2_franka observation.state action 4 - index RoboTwin2 Franka
robotwin2_aloha_agilex observation.state action 4 - index RoboTwin2 ALOHA
robotwin2_ur5 observation.state action 4 - index RoboTwin2 UR5
pusht observation.state action 1 - - PushT
xarm_push_medium observation.state action 1 - - xArm
aloha_sim_insertion observation.state action 1 - - ALOHA sim

使用方式

Python API

from modelbest_robo_dataset.dataset_config import load_dataset_config
from modelbest_robo_dataset.sources.lerobot import LeRobotSource

# 所有参数由 config 驱动,无自动检测
cfg = load_dataset_config("libero")
src = LeRobotSource(
    "/path/to/libero_long",   # or libero_spatial, libero_object, libero_goal
    state_type=cfg.state_type,
    action_type=cfg.action_type,
    state_key=cfg.state_key,
    action_key=cfg.action_key,
    camera_keys=cfg.camera_keys,
    task_key=cfg.task_key,
)

# 大数据集使用懒加载(如 DROID,config 中 lazy=true)
cfg = load_dataset_config("droid")   # lazy=True already set
src = LeRobotSource(
    "/path/to/droid_1.0.1",
    state_type=cfg.state_type,
    action_type=cfg.action_type,
    state_key=cfg.state_key,
    action_key=cfg.action_key,
    camera_keys=cfg.camera_keys,
    task_key=cfg.task_key,
    task_fallback_column=cfg.task_fallback_column,
    lazy=cfg.lazy,               # True — init 0.04s instead of OOM
)
eps = src.list_episodes()            # 95617 episodes
ep = src.load_episode(eps[0])        # on-demand: reads only the needed parquet file

命令行

# 使用配置文件(无需手动传 --state-type / --action-type)
python -m modelbest_robo_dataset.scripts.convert --source lerobot \
  --input /path/to/agibotworld/task_327 --output /path/to/output \
  --config agibotworld

# CLI 参数优先于配置文件
python -m modelbest_robo_dataset.scripts.convert --source lerobot \
  --input /path/to/dataset --output /path/to/output \
  --config libero --max-episodes 100

批量转换

batch_convert_lerobot.py 支持 --configs-dir,自动按数据集名查找配置:

python -m modelbest_robo_dataset.scripts.convertion.batch_convert_lerobot \
  --source-root /path/to/lerobot_datasets \
  --output-root /path/to/output \
  --configs-dir /path/to/custom/configs \
  ...

添加新数据集

  1. 查看数据集的 meta/info.json,确认 features 中 state/action 列名和 dtype: "video" 的相机列名
  2. 查看 meta/tasks.parquet 结构,确认任务文本在 DataFrame Index 还是某个列中
  3. 创建自定义 <name>.json,或把配置加入包内 modelbest_robo_dataset/configs/datasets/
  4. 运行 python -m modelbest_robo_dataset.scripts.convert --config <name> 验证

LeRobot observation.state 语义推断

LeRobot v2/v3 里 observation.state 没有统一 schema:同一向量可能是关节角,也可能是末端位姿(位置 + 旋转 + 夹爪)。转换到本库时,writer.STATE_TYPE_MAP 需要区分 joint_position(映射到 env.obs.joint_position)与 ee_pose(映射到 env.obs.cartesian_position)。本库提供 元数据优先、Parquet 抽样数值为辅 的推断,不能单靠维度数判断

实现与返回结果

  • 模块lerobot_state_semantics.py
  • APIinfer_observation_state_semantics(root, max_sample_rows=5000) -> StateSemanticsResult
  • 字段labeljoint_position / ee_pose / unknown)、confidence(0–1)、reasons(命中规则说明)、state_keyshapesample_dim_names

规则优先级(概要)

  1. 特征键名(不区分大小写):observation.state* 中含 eeftcpcartesianposeworld_poseee_poseee_ 等子串 → 判为 ee_pose(高置信度)。
  2. 维度名(与 LeRobotSource._extract_dim_names 一致:支持 names.motorsnames.axes,或顶层 names 为列表;键存在但值为 JSON null 时视为缺失,避免异常):
    • 倾向末端:维度名集合中同时含 xyz,或名称文本中含 quataxis_angleeulerrpyrotationorient 等;
    • 倾向关节:名称中含 jointshoulderelbowwristfinger 等;
    • 冲突:若末端与关节信号并存,且存在 x/y/z 三元组 → 偏向 ee_pose;否则继续走数值启发。
    • 弱信号:仅 motor_0motor_1… 等形式 不单独下结论,需结合 Parquet 抽样。
  3. 数值启发(读取 data/**/*.parquet 中 state 列,总行数上限由 max_sample_rows 控制,默认 5000):
    • 维度 ≥ 7:对「最后 4 维」与「第 4–7 列」两种四元数候选块分别算均值 |‖q‖ - 1| ,取更优者;若小于 0.05ee_pose
    • 维度 = 6:若前三维幅度接近米级、后三维接近弧度量级 → 弱信号 ee_pose(置信度较低);
    • motor_*无名,且抽样不满足单位四元数块 → joint_position
    • 缺少 meta/info.json、无 observation.state* 特征、无可用 Parquet 等 → unknown,并在 reasons 中写明原因。

局限

无法保证 100% 自动正确(例如元数据全写成 motor_* 但实际存的是末端位姿)。请以 confidencereasons 为准做抽检;必要时在流水线侧人工指定或修正。

说明:单数据集 / 批量脚本通过 importlib 直接加载 lerobot_state_semantics.py不经过包根 __init__.py,可在未安装 PyAV 的环境下运行。若在已安装全量依赖的环境中使用 Python API,可正常 from modelbest_robo_dataset.lerobot_state_semantics import infer_observation_state_semantics

命令行

# 单个 LeRobot 数据集根目录(需含 meta/info.json)
python -m modelbest_robo_dataset.scripts.convertion.infer_lerobot_state /path/to/lerobot_dataset
python -m modelbest_robo_dataset.scripts.convertion.infer_lerobot_state /path/to/lerobot_dataset --json --max-rows 2000

# 父目录下:每个直接子目录若含 meta/info.json 则推断一次
python -m modelbest_robo_dataset.scripts.convertion.batch_infer_lerobot_state /path/to/parent \
  --output-format csv -o lerobot_state_summary.csv

# 递归查找所有 meta/info.json(数据集根 = meta 的父目录)
python -m modelbest_robo_dataset.scripts.convertion.batch_infer_lerobot_state /path/to/parent --recursive --output-format jsonl

batch_infer_lerobot_state.py 还支持 --output-format table|json-o 输出到文件。

Python 调用示例

from pathlib import Path
from modelbest_robo_dataset.lerobot_state_semantics import infer_observation_state_semantics

r = infer_observation_state_semantics(Path("/path/to/lerobot_dataset"))
print(r.label, r.confidence, r.reasons)

数据格式

输出结构:

output_dir/
├── skeleton_episode/{name}/part-XXXXX       # SSTable partition 骨架
├── data/{name}/state/chunk-000/file-000.parquet
├── data/{name}/action/chunk-000/file-000.parquet
├── videos/{name}/{cam}/episode_000000.mp4   # 默认:整段 MP4,H264 CRF23,上限 720p
│   或 episode_000000.png                    # 逐帧模式:每 timestep 一张 PNG
├── meta/{name}/info.json                     # 轻量 summary(非 LeRobot 原始 info.json 全量镜像)
├── meta/{name}/stats.json                    # action/state 全量统计:mean/std/min/max/q01/q99/count
└── meta/{name}/norm_stats.json               # 训练 norm_stats_path 直接消费的 {"norm_stats": {...}} 包装

说明:

  • meta/{name}/info.json 当前只保存轻量 summary(如 dataset_nametotal_episodestotal_frames、accumulator 行数)。
  • 更细的结构化信息(例如 robot_typetaskdim_namesurdf_pathfps、video key、时序 ref 的 from_ts/to_ts)保存在 skeleton 的 MetaContent / TimeseriesRef / VideoRef 中。
  • 时序 Parquet 的每行也会保存自身的 fps 列,便于下游核对。
  • meta/{name}/stats.jsonmeta/{name}/norm_stats.json 同时包含 q01/q99min/max
    • openpi(默认)preprocessor 走 q01/q99
    • starvla preprocessor 走 min/max(缺失才会回落 q01/q99 并 warning)
    • 直接把 meta/{name}/norm_stats.json 配给训练 config 的 norm_stats_path 即可同时支持两类

Episode 骨架

每个 Episode 包含三类消息:

角色 内容 说明
env MetaContent, VideoContent, TimeseriesContent(state), AudioContent 环境感知
user TextContent, AudioContent 人类干预
assistant TimeseriesContent(action), TextContent 机器人输出

TimeseriesName 命名规范

采用 env.obs.* / ai.action.* 的 dot-separated 命名:

key 说明
env.obs.joint_position 关节角度 (绝对)
env.obs.cartesian_position 末端位姿 (绝对)
env.obs.gripper_position 夹爪开度
env.obs.force_torque 力/力矩
env.obs.imu IMU
ai.action.joint_position 绝对目标关节角
ai.action.cartesian_position 绝对目标末端位姿
ai.action.delta_joint_position 关节角增量
ai.action.delta_cartesian_position 末端位姿增量

TimeseriesName 本身区分绝对/增量,不需要额外的 action_type 字段。

MB 原生预处理脚本

modelbest_robo_dataset.scripts.preprocess 下的脚本支持直接处理 modelbest_robo_dataset 原生格式(目录下包含 data/meta/skeleton_episode/videos/)。

输入与输出约定

  • --input_dir 可以传原生数据根目录,也可以直接传某个数据集目录,例如 data/<dataset>meta/<dataset>skeleton_episode/<dataset>
  • 原生 MB 数据 执行预处理时,脚本现在会先复制该数据集的完整内容,再在副本上修改;不会复用源数据文件,也不会把新数据集内部引用继续指向原数据集。
  • 复制时会同步带走并改写同名数据集下的 data/meta/videos/skeleton_episode/,以及已有的 skeleton_single_frame/ / skeleton_ctx*
  • 输出参数 --output_dir 对原生 MB 数据来说表示“新的 MB 根目录”,不是单个数据集目录;真正的新数据集会出现在 output_dir/<collection>/<new_dataset_name>/... 下。

新数据集命名规则

原生 MB 预处理脚本会自动给新数据集名追加操作后缀:

  • abs2rel.py -> <dataset>_abs2rel
  • downsampling.py -> <dataset>_downsample_<source>hz_to_<target>hz
  • normalization.py -> <dataset>_q99_norm
  • clear_statistic.py -> <dataset>_clean_xyz
  • native_single_frame_clean.py -> <dataset>_clean_single_frame

例如对 libero_goal_no_noops_1.0.0_lerobot 运行 native_single_frame_clean.py,输出数据集名会变成 libero_goal_no_noops_1.0.0_lerobot_clean_single_frame

派生 skeleton 的处理

  • 如果预处理会改变帧数、时长或 fps(例如 downsampling.pyclear_statistic.pynative_single_frame_clean.py),脚本会自动删除复制副本中旧的 skeleton_single_frame/skeleton_ctx*,避免它们继续引用过期的时间范围。
  • native_single_frame_clean.py 会在清洗完成后重新生成新的 skeleton_single_frame
  • 对不改变帧数的脚本(如 abs2rel.pynormalization.py),已有 skeleton 会保留,并同步改写数据集名与内部引用路径。

dry-run

  • clear_statistic.py --dry_runnative_single_frame_clean.py --dry_run 只做统计,不写出新数据。
  • dry-run 会复用源数据集名做分析;只有实际写出时才会生成带操作后缀的新数据集副本。

示例

# 先做 dry-run,看清洗后会删掉多少帧
python -m modelbest_robo_dataset.scripts.preprocess.native_single_frame_clean \
  --input_dir /path/to/mb_root \
  --dataset_name libero_goal_no_noops_1.0.0_lerobot \
  --output_dir /path/to/mb_preprocessed \
  --threshold 1e-4 \
  --dry_run

# 实际转换:会复制整套数据,并生成一个新的数据集
python -m modelbest_robo_dataset.scripts.preprocess.native_single_frame_clean \
  --input_dir /path/to/mb_root \
  --dataset_name libero_goal_no_noops_1.0.0_lerobot \
  --output_dir /path/to/mb_preprocessed \
  --threshold 1e-4 \
  --keep_endpoints

实际运行后,输出中会保留原始数据不变,并在:

/path/to/mb_preprocessed/data/libero_goal_no_noops_1.0.0_lerobot_clean_single_frame/...
/path/to/mb_preprocessed/meta/libero_goal_no_noops_1.0.0_lerobot_clean_single_frame/...
/path/to/mb_preprocessed/skeleton_episode/libero_goal_no_noops_1.0.0_lerobot_clean_single_frame/...
/path/to/mb_preprocessed/skeleton_single_frame/libero_goal_no_noops_1.0.0_lerobot_clean_single_frame/...
/path/to/mb_preprocessed/videos/libero_goal_no_noops_1.0.0_lerobot_clean_single_frame/...

MetaContent 结构化字段

字段 类型 说明
urdf_path str 机器人 URDF 路径,可由 python -m modelbest_robo_dataset.scripts.convert --urdf-path 写入
joint_names list[str] 关节名称列表(如源数据可提供)
task_id str 任务 ID (如 "task_0001",从目录名解析)
quality_rating int 质量评分: 0=机器人失败, 1=任务失败, 2-9=完成质量, -1=未标注
user_id str 操作者 ID (如 "user_0001")
scene_id str 场景 ID (如 "scene_0001")
dim_names dict key=TimeseriesName, value=维度名列表

快速开始

转换数据集

from modelbest_robo_dataset import EmbodiedWriter
from modelbest_robo_dataset.dataset_config import load_dataset_config
from modelbest_robo_dataset.sources.lerobot import LeRobotSource

# 使用配置注册表(推荐,所有参数由 config 显式定义)
cfg = load_dataset_config("libero")
source = LeRobotSource(
    root="/path/to/libero_long",  # or any LIBERO variant
    state_type=cfg.state_type,
    action_type=cfg.action_type,
    state_key=cfg.state_key,
    action_key=cfg.action_key,
    camera_keys=cfg.camera_keys,
    task_key=cfg.task_key,
)

writer = EmbodiedWriter(output_dir="/path/to/output", dataset_name="pusht")
for ep_id in source.list_episodes():
    raw = source.load_episode(ep_id)
    writer.write_episode(raw)
writer.finalize()

逐帧 episode(每 timestep 一条骨架 + 单帧 PNG)

默认下,每个源 trajectory 对应一条 Episode,视频为整段 episode_XXXXXX.mp4。若需要 每个时间步单独一条 Episode,且相机保存为 单张 PNG(而非 MP4),使用 EmbodiedWriter(..., one_frame_per_episode=True)

可选:在写入前调用 set_shuffled_episode_indices(total_frames, seed),为每条帧级 Episode 分配 打乱后的 episode_index(与 Parquet 行、PNG 文件名一致)。LeRobot 下可用 LeRobotSource.episode_frame_counts() 先求总帧数(需与 list_episodes 的截断方式一致,例如同样应用 max_episodes)。

from modelbest_robo_dataset import EmbodiedWriter
from modelbest_robo_dataset.dataset_config import load_dataset_config
from modelbest_robo_dataset.sources.lerobot import LeRobotSource

cfg = load_dataset_config("my_ds")
source = LeRobotSource(
    root="/path/to/lerobot_ds",
    name="my_ds",
    state_type=cfg.state_type,
    action_type=cfg.action_type,
    state_key=cfg.state_key,
    action_key=cfg.action_key,
    camera_keys=cfg.camera_keys,
    task_key=cfg.task_key,
)
episodes = source.list_episodes()
counts = source.episode_frame_counts()
total_frames = sum(counts)

writer = EmbodiedWriter(
    output_dir="/path/to/output",
    dataset_name="my_ds",
    one_frame_per_episode=True,
)
writer.set_shuffled_episode_indices(total_frames, seed=42)

for ep_id in episodes:
    writer.write_episode(source.load_episode(ep_id))
writer.finalize()

说明:

  • 状态/动作/各相机帧数不一致时,按 最短长度 对齐并打日志警告。
  • video_files、无 video_frames 时,本库 不会 自动逐帧解码 MP4;可能只有时序无图像。
  • 多帧 trajectory 下 不写入 整段 audio_env(仅单帧 trajectory 保留原逻辑)。

读取数据

from modelbest_robo_dataset import EmbodiedReader

reader = EmbodiedReader("/path/to/output", "pusht")
reader.summary()

sample = reader.load_sample(episode_idx=0, timestamp=1.0)
print(sample.keys())

命令行转换

# LeRobot: 使用配置注册表(推荐)
python -m modelbest_robo_dataset.scripts.convert --source lerobot \
  --input /path/to/libero_long --output /path/to/output --config libero

# LeRobot: 非标准 feature key(如 AgiBotWorld,config 中已定义)
python -m modelbest_robo_dataset.scripts.convert --source lerobot \
  --input /path/to/agibotworld/task_327 --output /path/to/output --config agibotworld

# RH20T 全部
python -m modelbest_robo_dataset.scripts.convert --source rh20t --all --output /path/to/output

# fuse
python -m modelbest_robo_dataset.scripts.convert --source fuse --output /path/to/output

# RoboMind (支持 puppet/franka/tiangong 三种变体,自动检测)
python -m modelbest_robo_dataset.scripts.convert --source robomind \
  --input /path/to/failure_data --output /path/to/output --name robomind_failure

# RoboMind 全量转换 (约4小时)
python -m modelbest_robo_dataset.scripts.convert --source robomind \
  --input /backup/.../robomind/failure_data --output /path/to/output \
  --name robomind_failure --max-episodes 1678

LeRobot:逐帧 PNG + 打乱 episode_index

适用于希望 每条样本对应一帧图像videos/.../episode_XXXXXX.png),并对全数据集的 episode_index 随机打乱(可复现)的场景。

python -m modelbest_robo_dataset.scripts.convert --source lerobot \
  --input /path/to/lerobot_dataset --output /path/to/output --name my_ds \
  --one-frame-per-episode --shuffle-seed 42

(v0.3.1 起推荐始终使用 python -m modelbest_robo_dataset.scripts.convert,无论是 pip 安装还是 git clone 开发环境。)

参数 说明
--one-frame-per-episode 每个 timestep 写一条 Episode;相机输出为 PNG,不再为整段 MP4。
--shuffle-seed N 必须与上一参数同时使用。在写入前根据数据表统计总帧数,对 0..N-1episode_index 做固定种子的随机排列。

注意--shuffle-seed 依赖数据源的 episode_frame_counts();当前 LeRobot 已实现。其他 --source 若未实现该方法,请勿对该源使用 --shuffle-seed

骨架记录在 SSTable 中的 追加顺序 仍为按源 Episode 依次写入;打乱的是每条记录内的 episode_index 及对应的 Parquet/图像路径,而非磁盘上的记录物理顺序。

已有 episode 骨架转 single_frame

适用于你已经有 modelbest_robo_dataset 标准输出目录,且目录下已有 skeleton_episode/{dataset},现在只想继续生成 skeleton_single_frame/{dataset} 的场景。

# 转换指定数据集
python -m modelbest_robo_dataset.scripts.expand_skeleton \
  --input /path/to/output \
  --dataset my_dataset \
  --mode single_frame \
  --action-chunk 50

# 一次转换 skeleton_episode/ 下全部数据集
python -m modelbest_robo_dataset.scripts.expand_skeleton \
  --input /path/to/output \
  --all \
  --shuffle-seed 42
参数 说明
--input modelbest_robo_dataset 数据根目录,内部应包含 skeleton_episode/
--datasets 指定一个或多个数据集名称。
--all 转换 skeleton_episode/ 下所有数据集。
--action-chunk 每条 single_frame 样本中 action 窗口长度,默认 50
--shuffle-seed 写入前打乱全部帧级记录,传固定值可复现。
--include-terminal-frame 保留末尾不足一个完整 action chunk 的终止帧。

说明:

  • 输出目录为 skeleton_single_frame/{dataset}
  • 脚本会读取 skeleton_episode 中的 SSTable 记录,并保持原有 data/videos/meta/ 目录不变。
  • 若目标目录已存在旧的 part-* 文件,脚本会先清理再重写,避免重复运行后混入旧分片。

校验与诊断

LeRobot / MB 一致性校验

modelbest_robo_dataset.scripts.verify_lerobot_to_mb_dataset 用于校验:

  • LeRobot 数据集与 modelbest_robo_datasetsingle_frame 输出是否一致
  • 两个 LeRobot 数据集之间的 state / action / task / video 是否一致
  • single_frame 记录顺序被 shuffle 的情况

示例:

# 校验 LeRobot 与 MB single_frame
python -m modelbest_robo_dataset.scripts.verify_lerobot_to_mb_dataset \
  --lerobot-root /path/to/lerobot_ds \
  --mb-root /path/to/mb_root \
  --dataset-name my_dataset

# 严格模式:额外逐帧比较视频
python -m modelbest_robo_dataset.scripts.verify_lerobot_to_mb_dataset \
  --lerobot-root /path/to/lerobot_ds \
  --mb-root /path/to/mb_root \
  --dataset-name my_dataset \
  --check-video

# 对比两个 LeRobot 数据集
python -m modelbest_robo_dataset.scripts.verify_lerobot_to_mb_dataset \
  --lerobot-root /path/to/lerobot_a \
  --compare-lerobot-root /path/to/lerobot_b \
  --dataset-name my_dataset \
  --check-video

打印骨架中的机器人 meta / video key / fps

modelbest_robo_dataset.scripts.convertion.print_robot_meta_info 用于直接读取 skeleton_episodeskeleton_single_frame,打印:

  • MetaContent 中的机器人相关字段
  • dim_names
  • video key
  • skeleton 中推导出的 timeseries / fps 信息

示例:

python -m modelbest_robo_dataset.scripts.convertion.print_robot_meta_info /path/to/mb_root --dataset-name my_dataset
python -m modelbest_robo_dataset.scripts.convertion.print_robot_meta_info /path/to/mb_root/skeleton_single_frame --dataset-name my_dataset
python -m modelbest_robo_dataset.scripts.convertion.print_robot_meta_info /path/to/mb_root --dataset-name my_dataset --json

构建 LeRobot 采样子集(大数据集测试用)

modelbest_robo_dataset.scripts.convertion.build_lerobot_sample_subset 从大型 LeRobot v3.0 数据集(如 DROID 95K episodes)中快速创建最小可测试子集。通过软链接(symlink)避免复制大文件:

source ~/minicpmo/bin/activate
python -m modelbest_robo_dataset.scripts.convertion.build_lerobot_sample_subset \
  --source /path/to/droid_1.0.1 \
  --output /tmp/droid_sample \
  --max-data-files 1

输出目录结构:

  • meta/info.json:调整 total_episodes 为子集大小
  • meta/episodes/chunk-000/file-000.parquet:按 data shard 中出现的 episode_index 过滤
  • data/chunk-000/file-000.parquet:软链接到源文件
  • meta/tasks.parquet:软链接到源文件
  • videos/:软链接到源目录

端到端 skeleton 验证

modelbest_robo_dataset.scripts.verify_skeleton_identity 用于验证对 LeRobotSource 的修改不会破坏已有的 skeleton_episode / skeleton_single_frame 输出:

  • Phase 1:用当前 LeRobotSource 加载每个 episode,与已有 skeleton_episode SSTable 中的 task、state/action 维度、视频数等进行比较
  • Phase 2:从已有 skeleton_episode 重新运行 expand_skeleton,MD5 比较生成的 skeleton_single_frame 每个 part-* 文件
source ~/minicpmo/bin/activate
cd /path/to/modelbest-robo-dataset
python -m modelbest_robo_dataset.scripts.verify_skeleton_identity \
  --lerobot-root /path/to/raw/libero_long \
  --unified-root /path/to/unified \
  --dataset libero_long \
  --state-type joint_position \
  --action-type delta_ee \
  --action-chunk 10 \
  --shuffle-seed 42

检查 single_frame 是否被 shuffle

modelbest_robo_dataset.scripts.convertion.check_single_frame_shuffle 用于判断 single_frame 数据是否经过 shuffle,支持:

  • python -m modelbest_robo_dataset.scripts.convert --one-frame-per-episode
  • python -m modelbest_robo_dataset.scripts.expand_skeleton --mode single_frame

示例:

python -m modelbest_robo_dataset.scripts.convertion.check_single_frame_shuffle \
  --input /path/to/mb_root --dataset-name my_dataset

多数据集共享 action 归一化并批量转换

modelbest_robo_dataset.scripts.convertion.convert_multi_lerobot_normalized 支持:

  1. 对多个 LeRobot 数据集计算共享 action 统计量
  2. 输出归一化后的 LeRobot 副本
  3. 转换为 modelbest episode 格式
  4. 再展开为 single_frame

示例:

python -m modelbest_robo_dataset.scripts.convertion.convert_multi_lerobot_normalized \
  --root /path/to/lerobot_root \
  --datasets task_a task_b \
  --output /path/to/modelbest_out \
  --state-type joint_position \
  --action-type delta_ee

已接入数据集

数据集 源格式 机器人 State (env.obs) Action (ai.action) 模态 结构化标注
pusht LeRobot - cartesian_position cartesian_position 视频+时序 -
xarm_push_medium LeRobot xarm joint_position delta_joint_position 视频+时序 -
aloha_sim_insertion LeRobot aloha joint_position joint_position 视频+时序 -
fuse TFRecord DIGIT cartesian_position, imu delta_cartesian_position 视频+音频+IMU -
rh20t_cfg1~7 RH20T 多种 cartesian_position, force_torque cartesian_position 视频+音频+力 task_id, user_id, scene_id, quality_rating
droid_1.0.1 LeRobot v3.0 多种 joint_position joint_position 3视频+时序 language_instruction (via tasks.parquet index)
libero LeRobot v3.0 Franka joint_position delta_cartesian_position 2视频+时序 task (long/spatial/object/goal)
agibotworld LeRobot v3.0 a2d joint_position (14d) joint_position (14d) 8视频+时序 task (需 state_key/action_key 覆盖)
agibotworld_3rgb LeRobot v3.0 a2d joint_position (14d) joint_position (14d) 3视频+时序 同上,仅 head+hand_left/right(快速转换)
robomind_franka (LeRobot) LeRobot v3.0 Franka joint_position (8d) joint_position (8d) 1–3 视频+时序 task(1/2/3 相机变体配置)
robomind_ur (LeRobot) LeRobot v3.0 UR joint_position (7d) joint_position (7d) 1 视频+时序 task
robomind_agilex (LeRobot) LeRobot v3.0 agilex 双臂 joint_position (7+7=14d) joint_position (7+7=14d) 3 视频+时序 task(state/action 多列拼接)
robomind_franka_fr3_dual (LeRobot) LeRobot v3.0 FR3 双臂 joint_position (16d) joint_position (16d) 3 视频+时序 task
robotwin2_franka LeRobot v3.0 Franka 双臂 joint_position (16d) joint_position (16d) 4视频+时序 task (per-episode)
robotwin2_aloha_agilex LeRobot v3.0 ALOHA-Agilex 双臂 joint_position (14d) joint_position (14d) 4视频+时序 task (per-episode)
robotwin2_ur5 LeRobot v3.0 UR5 双臂 joint_position (14d) joint_position (14d) 4视频+时序 task (per-episode)
robomind_failure HDF5 tiangong/puppet/franka joint_position joint_position 多视频+时序 task_id, quality_rating=0 (失败数据)
robomind_puppet HDF5 puppet (双臂) joint_position joint_position 多视频+时序 task_id
robomind_franka HDF5 Franka joint_position joint_position 多视频+时序 task_id

依赖

运行本仓库的 Python 脚本或 pytest 前,请先激活你的 Python 环境(例如 minicpmo):

source ~/minicpmo/bin/activate
numpy
pandas
pyarrow
pydantic>=2.0
av (PyAV)          # EmbodiedWriter 视频重编码;LeRobotSource 不再依赖
Pillow
h5py              # RoboMind
tensorflow        # fuse (可选)
modelbest_sdk     # SSTable 骨架存储
thriftpy2         # modelbest_sdk 依赖

测试

source ~/minicpmo/bin/activate
pytest tests/ -v
测试文件 覆盖范围
tests/test_lerobot_source.py LeRobotSource 初始化、list_episodesload_episode、task 解析(标准/DROID index 风格/language_instruction 回退)、_normalize_tasks_dfbuild_lerobot_sample_subset
tests/test_sstable_record.py SSTable 记录序列化/反序列化

State/Action 规范

详见 docs/sop_state_action.md

多源数据集接入与适配说明详见 docs/sop_dataset_adapter.md

核心原则:

  1. Action 必须使用原始数据,禁止用 np.diff 等方式构造
  2. 没有原始 action 的遥操作数据,用 action[t] = state[t+1] (shifted state)
  3. TimeseriesName 自描述delta_ 前缀表示增量,无前缀表示绝对目标

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modelbest_robo_dataset-0.3.1.tar.gz (4.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

modelbest_robo_dataset-0.3.1-py3-none-any.whl (4.3 MB view details)

Uploaded Python 3

File details

Details for the file modelbest_robo_dataset-0.3.1.tar.gz.

File metadata

  • Download URL: modelbest_robo_dataset-0.3.1.tar.gz
  • Upload date:
  • Size: 4.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for modelbest_robo_dataset-0.3.1.tar.gz
Algorithm Hash digest
SHA256 7c0d48335f53c52a1907dd94a6aaa8d8d152e5e39b3292e68fb5b4ae1340ebdc
MD5 0551c2dde8160803e564a0d10b2bfcf8
BLAKE2b-256 9716207adcdfce3c55fc305e1ea0ee169cb390dcbf305a328a96c439a6c4062b

See more details on using hashes here.

File details

Details for the file modelbest_robo_dataset-0.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for modelbest_robo_dataset-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 58f2595e07e5e83296a6b481c98a1b10653d003a9c790904fed96b4a2b94ee44
MD5 dc1858f4be81f171b09348a438f88ce7
BLAKE2b-256 a33a5f08bf4ef0978e0e913bfb21b329259275bc20015fcb3de8d0a55fa4c0ab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page