Skip to content

feat: add LeRobot v3 dataset import support#268

Open
rikunosuke wants to merge 6 commits intomainfrom
feature/lerobot-import-export
Open

feat: add LeRobot v3 dataset import support#268
rikunosuke wants to merge 6 commits intomainfrom
feature/lerobot-import-export

Conversation

@rikunosuke
Copy link
Contributor

@rikunosuke rikunosuke commented Mar 12, 2026

Summary

  • LeRobot v3 データセットを FastLabel ロボティクスプロジェクトにインポートする import_lerobot メソッドを追加
  • pip install fastlabel[robotics] でオプション依存(pandas, pyarrow)をインストール可能に
  • v2 データセットはエラーメッセージで非対応を通知
  • flake8 の E231 誤検知(f-string format spec)を tox.ini で対応
スクリーンショット 2026-03-12 9 33 38

Test plan

  • LeRobot v3 データセットでの import_lerobot 動作確認
  • v2 データセットで適切なエラーメッセージが表示されることを確認
  • pip install fastlabel[robotics] で pandas, pyarrow がインストールされることを確認
  • robotics 依存なしで import 時に適切なエラーが出ることを確認

🤖 Generated with Claude Code

Add import_lerobot method to import LeRobot v3 datasets into FastLabel
robotics projects. Includes optional dependencies (pandas, pyarrow) via
pip install fastlabel[robotics]. v2 datasets are detected and rejected
with an error message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +25 to +27
[project.optional-dependencies]
robotics = ["pandas>=2.0.0", "pyarrow>=14.0.0"]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pip install fastlabel[robotics] でダウンロードできるようにしています。pandas, pyarrow くらいであれば分ける必要はないと思っているんですが、今後、Lerobot 用のパッケージがなどが入るので、先にこうしています。

Comment on lines +22 to +26
if version == "v2":
raise FastLabelInvalidException(
"LeRobot dataset v2 is not supported. Please convert to v3.",
422,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まずはメインで使われている v3 をサポートしています。
v2 に関しては、必要今後検討します。

@rikunosuke
Copy link
Contributor Author

こちら、サンプルのために録画した lerobot のデータになります。

sample-realman.zip

Copy link

@kamei-takuma kamei-takuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rikunosuke
拝見させていただきました!
ロボット周りがあまりわかってないので、変なこと言ってたら無視してください🙇

localhostに向けて動かして、問題なく動くことは確認しました!
スクリーンショット 2026-03-13 8 02 40

(sdk) kmtkm@193-TakumaKamei fastlabel-python-sdk % python -c "
dquote> from fastlabel.lerobot import common
from pathlib import Path

data_path = Path('sample-realman')

# 1. バージョン検出
print('Version:', common.detect_version(data_path))

# 2. カメラ一覧
for cam_dir, name in common.get_camera_dirs(data_path):
    print(f'Camera: {name}')
"
Version: v3
Camera: cam_high
Camera: cam_left_wrist
Camera: cam_right_wrist
Camera: camera_01
(sdk) kmtkm@193-TakumaKamei fastlabel-python-sdk % python -c "
from fastlabel.lerobot import v3
from pathlib import Path

data_path = Path('sample-realman')

# エピソード一覧
indices = v3.get_episode_indices(data_path)
print('Episodes:', indices)

# 最初のエピソードで ZIP 生成
zip_path = v3.create_episode_zip(data_path, indices[0])
print('ZIP:', zip_path)

# ZIP の中身を確認
import zipfile
with zipfile.ZipFile(zip_path) as zf:
    for name in zf.namelist():
        print(f'  {name}')
"

Episodes: [0]
ZIP: /var/folders/s_/yw3yzchj76vgrqzw9vg24z0h0000gn/T/tmpcuq297nn/episode_000000.zip
  cam_left_wrist.mp4
  cam_right_wrist.mp4
  cam_high.mp4
  episode_000000.json
  camera_01.mp4
(sdk) kmtkm@193-TakumaKamei fastlabel-python-sdk % python -c "
import fastlabel

client = fastlabel.Client()
results = client.import_lerobot(
    project='robotics-task-classification',
    lerobot_data_path='sample-realman',
    episode_indices=[0],
)
print(results)
"
[{'episode': 'episode_000000', 'success': True, 'result': {'id': '7c310bf6-1937-4d48-99cb-04a704317229', 'status': 'running', 'msgCode': 'none', 'userName': '', 'credentialName': 'test', 'createdAt': '2026-03-12T23:01:56.137Z', 'updatedAt': '2026-03-12T23:01:56.137Z'}}]

episode_name = lerobot.format_episode_name(episode_index)
self.create_robotics_task(project=project, name=episode_name)

zip_path = lerobot.create_episode_zip(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nits]

parquet読み込みが何度も走っているのが若干気にはなりました
create_episode_zipから内部的に呼び出している_build_episode_map
ループ毎に全 parquet をスキャンしてエピソードのインデックス情報を構築してそうで、
結果は呼び出し間で変わらなそうだなと

これがパフォーマンスにどれくらい影響があるのかちょっとわかってないのですが、ループ外で一度だけ構築して渡す設計でもいいかもと思ったりしました

Copy link
Contributor Author

@rikunosuke rikunosuke Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにその通りですね!ループ外のメインのメソッドで作成して、引数として渡す構造に変更しました!

d3303df

ret, frame = cap.read()
if not ret:
break
writer.write(frame)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ループ中に例外が起きるとreleseされなさそうなので、finally入れとくといいかもです

try:
    for _ in range(num_frames):
        ret, frame = cap.read()
        if not ret:
            break
        writer.write(frame)
finally:
    writer.release()
    cap.release()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます! finally いれました!
d3303df


Requires: pip install fastlabel[robotics]

Supports both LeRobot v2 and v3 dataset formats.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v2も対応してるぜ、ってコメントで言ってそう?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみみません、最初にv2対応してみた時の名残です🙇
削除しました!
d3303df

"""
Import a LeRobot dataset into a FastLabel robotics project.

Automatically detects LeRobot dataset version (v2 or v3).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同じくv2も対応しているぜと言ってそう

project=project, file_path=zip_path
)
results.append(
{"episode": episode_name, "success": True, "result": result}
Copy link

@kamei-takuma kamei-takuma Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[imo]
例外が発生した時のexceptがなさそうなので、例外が発生するとその時点で処理を中断して返却値なしとなるAll-or-nothin的な実装だと読み取ったのですが、
一方、resultsに各エピソードの処理結果をsuccess として格納しており、部分的にも処理した結果を返したいようにも見えてます。

一部でも失敗したら返却がいらないならsuccessってキーとしているっけ?という感じですし、
一部失敗しても全部処理をし切りたいならexceptで受けて {"success": False, ...} 等を
resultsに追加してcontinueで次に進む等の対応が必要な気がしました。


if episode_index not in episode_map:
raise FastLabelInvalidException(
f"Episode index {episode_index} not found in dataset.", # noqa: E713

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E713って何だっけと思って軽く調べたのですが、なんか関係なさそうにも見えてて、これないと通らなかったです??
https://www.flake8rules.com/rules/E713.html

"action": row["action"].tolist(),
"frame_index": int(row["frame_index"]),
"timestamp": float(row["timestamp"]),
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ロボット詳しくなくてすみません、ここのキー名って決め打ちで大丈夫でしたかね?
observation.stateとかactionとかってロボットが変わっても変わらずあるぜっていうキーなのかどうかがちょっと心配になりました。

もしキーが存在しなかったらKeyエラーが出そうだなとmm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

observation.state と action は lerobot の仕様でロボットごとの差異はないので、決め打ちで大丈夫です!
ただ、key がない可能性を考慮できていなかったので、確認をするようにしました🙇

681bb56

ZIP structure:
{episode_name}/
{content_name}.mp4 (one per camera)
{episode_name}.json (frame data)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstringでは {episode_name}/ がZIPのルートディレクトリとして存在し、
その下にファイルが配置される構造をしてそうに見えたのでsが、
v3.pyの下記実装だと shutil.make_archive(root_dir=content_dir) でファイルが直接ZIPのルートに配置されちゃうかもと思ったのですがどうですかね?

    tmp_dir = tempfile.mkdtemp()
    content_dir = Path(tmp_dir) / "content"
    content_dir.mkdir()

    ...

    # Create ZIP (files at root, ZIP name = episode name)
    zip_path = shutil.make_archive(
        base_name=str(Path(tmp_dir) / episode_name),
        format="zip",
        root_dir=str(content_dir),
    )

もし乖離がありそうなら、docstringを実態に合わせる形で良いかなと思いますmm

[docstringの想定]
episode_000000.zip
  └── episode_000000/
        ├── top.mp4
        └── episode_000000.json

[コード上の構造]
episode_000000.zip
  ├── top.mp4
  └── episode_000000.json

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここも修正忘れでした🙇

d928047

if not obs_dir.is_dir():
continue
parts = obs_dir.name.split(".")
content_name = parts[-1] if len(parts) > 1 else obs_dir.name

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下記みたいなパターンってあります?content_nameが被るとv3.pyの132行目あたりで同じ名前で衝突しちゃいそうだなと

videos/
  observation.images.top/    → content_name = "top"
  observation.depth.top/     → content_name = "top"

あり得なさそうであればスルーで大丈夫です!

Copy link
Contributor Author

@rikunosuke rikunosuke Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lerobot の仕様としては observation.* みたいなので、あり得そうですね!
修正しました!

6cddb65

rikunosuke and others added 5 commits March 19, 2026 14:20
The docstring incorrectly described files as nested under an
{episode_name}/ directory, but the implementation places files
at the ZIP root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Return an empty list when required parquet columns
(observation.state, action, frame_index, timestamp) are missing,
preventing KeyError on datasets from different robot types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use all parts after "observation." joined with "_" for content_name
(e.g. observation.images.top -> images_top) instead of only the last
part. Also replace assert with FastLabelInvalidException for
non-observation camera directories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

2 participants