Conversation
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>
| [project.optional-dependencies] | ||
| robotics = ["pandas>=2.0.0", "pyarrow>=14.0.0"] | ||
|
|
There was a problem hiding this comment.
pip install fastlabel[robotics] でダウンロードできるようにしています。pandas, pyarrow くらいであれば分ける必要はないと思っているんですが、今後、Lerobot 用のパッケージがなどが入るので、先にこうしています。
| if version == "v2": | ||
| raise FastLabelInvalidException( | ||
| "LeRobot dataset v2 is not supported. Please convert to v3.", | ||
| 422, | ||
| ) |
There was a problem hiding this comment.
まずはメインで使われている v3 をサポートしています。
v2 に関しては、必要今後検討します。
|
こちら、サンプルのために録画した lerobot のデータになります。 |
There was a problem hiding this comment.
@rikunosuke
拝見させていただきました!
ロボット周りがあまりわかってないので、変なこと言ってたら無視してください🙇
localhostに向けて動かして、問題なく動くことは確認しました!

(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( |
There was a problem hiding this comment.
[nits]
parquet読み込みが何度も走っているのが若干気にはなりました
create_episode_zipから内部的に呼び出している_build_episode_mapが
ループ毎に全 parquet をスキャンしてエピソードのインデックス情報を構築してそうで、
結果は呼び出し間で変わらなそうだなと
これがパフォーマンスにどれくらい影響があるのかちょっとわかってないのですが、ループ外で一度だけ構築して渡す設計でもいいかもと思ったりしました
There was a problem hiding this comment.
確かにその通りですね!ループ外のメインのメソッドで作成して、引数として渡す構造に変更しました!
fastlabel/lerobot/v3.py
Outdated
| ret, frame = cap.read() | ||
| if not ret: | ||
| break | ||
| writer.write(frame) |
There was a problem hiding this comment.
ループ中に例外が起きるとreleseされなさそうなので、finally入れとくといいかもです
try:
for _ in range(num_frames):
ret, frame = cap.read()
if not ret:
break
writer.write(frame)
finally:
writer.release()
cap.release()
examples/import_lerobot.py
Outdated
|
|
||
| Requires: pip install fastlabel[robotics] | ||
|
|
||
| Supports both LeRobot v2 and v3 dataset formats. |
There was a problem hiding this comment.
すみみません、最初にv2対応してみた時の名残です🙇
削除しました!
d3303df
fastlabel/__init__.py
Outdated
| """ | ||
| Import a LeRobot dataset into a FastLabel robotics project. | ||
|
|
||
| Automatically detects LeRobot dataset version (v2 or v3). |
| project=project, file_path=zip_path | ||
| ) | ||
| results.append( | ||
| {"episode": episode_name, "success": True, "result": result} |
There was a problem hiding this comment.
[imo]
例外が発生した時のexceptがなさそうなので、例外が発生するとその時点で処理を中断して返却値なしとなるAll-or-nothin的な実装だと読み取ったのですが、
一方、resultsに各エピソードの処理結果をsuccess として格納しており、部分的にも処理した結果を返したいようにも見えてます。
一部でも失敗したら返却がいらないならsuccessってキーとしているっけ?という感じですし、
一部失敗しても全部処理をし切りたいならexceptで受けて {"success": False, ...} 等を
resultsに追加してcontinueで次に進む等の対応が必要な気がしました。
fastlabel/lerobot/v3.py
Outdated
|
|
||
| if episode_index not in episode_map: | ||
| raise FastLabelInvalidException( | ||
| f"Episode index {episode_index} not found in dataset.", # noqa: E713 |
There was a problem hiding this comment.
E713って何だっけと思って軽く調べたのですが、なんか関係なさそうにも見えてて、これないと通らなかったです??
https://www.flake8rules.com/rules/E713.html
| "action": row["action"].tolist(), | ||
| "frame_index": int(row["frame_index"]), | ||
| "timestamp": float(row["timestamp"]), | ||
| } |
There was a problem hiding this comment.
ロボット詳しくなくてすみません、ここのキー名って決め打ちで大丈夫でしたかね?
observation.stateとかactionとかってロボットが変わっても変わらずあるぜっていうキーなのかどうかがちょっと心配になりました。
もしキーが存在しなかったらKeyエラーが出そうだなとmm
There was a problem hiding this comment.
observation.state と action は lerobot の仕様でロボットごとの差異はないので、決め打ちで大丈夫です!
ただ、key がない可能性を考慮できていなかったので、確認をするようにしました🙇
fastlabel/lerobot/__init__.py
Outdated
| ZIP structure: | ||
| {episode_name}/ | ||
| {content_name}.mp4 (one per camera) | ||
| {episode_name}.json (frame data) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
ここも修正忘れでした🙇
fastlabel/lerobot/common.py
Outdated
| if not obs_dir.is_dir(): | ||
| continue | ||
| parts = obs_dir.name.split(".") | ||
| content_name = parts[-1] if len(parts) > 1 else obs_dir.name |
There was a problem hiding this comment.
下記みたいなパターンってあります?content_nameが被るとv3.pyの132行目あたりで同じ名前で衝突しちゃいそうだなと
videos/
observation.images.top/ → content_name = "top"
observation.depth.top/ → content_name = "top"
あり得なさそうであればスルーで大丈夫です!
There was a problem hiding this comment.
lerobot の仕様としては observation.* みたいなので、あり得そうですね!
修正しました!
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>
Summary
import_lerobotメソッドを追加pip install fastlabel[robotics]でオプション依存(pandas, pyarrow)をインストール可能にTest plan
import_lerobot動作確認pip install fastlabel[robotics]で pandas, pyarrow がインストールされることを確認🤖 Generated with Claude Code