rerun-io--rerun
172 行
5.1 KiB
Python
172 行
5.1 KiB
Python
"""Demonstrate converting Rerun recording to LeRobot dataset."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import atexit
|
|
import pathlib
|
|
import shutil
|
|
import tempfile
|
|
|
|
TMP_DIR = pathlib.Path(tempfile.mkdtemp())
|
|
atexit.register(lambda: shutil.rmtree(TMP_DIR) if TMP_DIR.exists() else None)
|
|
|
|
# region: setup
|
|
|
|
from pathlib import Path
|
|
|
|
from lerobot.datasets.lerobot_dataset import (
|
|
LeRobotDataset, # type: ignore[import-untyped,import-not-found]
|
|
)
|
|
from rerun_lerobot.converter import convert_dataframe_to_episode
|
|
from rerun_lerobot.feature_inference import infer_features
|
|
from rerun_lerobot.types import LeRobotConversionConfig, VideoSpec
|
|
|
|
import rerun as rr
|
|
|
|
# Start a server with RRD recordings
|
|
# In practice, you would point this to your directory of RRD files
|
|
sample_5_path = (
|
|
Path(__file__).parents[4] / "tests" / "assets" / "rrd" / "sample_5"
|
|
)
|
|
|
|
server = rr.server.Server(datasets={"robot_dataset": sample_5_path})
|
|
client = server.client()
|
|
dataset = client.get_dataset(name="robot_dataset")
|
|
# endregion: setup
|
|
|
|
# region: filter_data
|
|
# Select a single recording (episode) to export
|
|
single_recording = dataset.segment_ids()[0]
|
|
|
|
# Filter the dataset to include only the data we need for training:
|
|
# - Action commands sent to the robot
|
|
# - Observed joint positions (robot state)
|
|
# - Camera feeds from multiple viewpoints
|
|
# - Task descriptions (e.g., language instructions)
|
|
training_data = (
|
|
dataset
|
|
.filter_segments(single_recording)
|
|
.filter_contents([
|
|
"/action/joint_positions",
|
|
"/observation/joint_positions",
|
|
"/camera/**",
|
|
"/language_instruction",
|
|
])
|
|
.reader(index="real_time")
|
|
)
|
|
|
|
# endregion: filter_data
|
|
|
|
# region: configure_export
|
|
# Define how to extract task instructions from the recording
|
|
# This could be from a TextDocument, static metadata, etc.
|
|
# For this example, we assume a static instruction
|
|
instructions = "/language_instruction:TextDocument:text"
|
|
|
|
# Specify video streams to include in the dataset.
|
|
# Each stream needs a key (camera identifier) and entity path where the
|
|
# VideoStream is logged
|
|
videos = [
|
|
VideoSpec(key="ext1", path="/camera/ext1", video_format="h264"),
|
|
VideoSpec(key="ext2", path="/camera/ext2", video_format="h264"),
|
|
VideoSpec(key="wrist", path="/camera/wrist", video_format="h264"),
|
|
]
|
|
|
|
# Configure the conversion parameters
|
|
# This maps Rerun's flexible data model to LeRobot's standardized format
|
|
config = LeRobotConversionConfig(
|
|
fps=15, # Target frame rate for the dataset
|
|
index_column="real_time", # Timeline to use for alignment
|
|
# Fully qualified action column
|
|
action="/action/joint_positions:Scalars:scalars",
|
|
# Fully qualified state column
|
|
state="/observation/joint_positions:Scalars:scalars",
|
|
task=instructions, # Task description column
|
|
videos=videos, # Video streams to include
|
|
)
|
|
# endregion: configure_export
|
|
|
|
# region: infer_features
|
|
# Infer the LeRobot feature schema from the data
|
|
# This automatically detects data types, shapes, and creates the appropriate
|
|
# LeRobot feature definitions
|
|
features = infer_features(
|
|
table=training_data.to_arrow_table(),
|
|
config=config,
|
|
)
|
|
# endregion: infer_features
|
|
|
|
# region: create_dataset
|
|
# Create the LeRobot dataset structure on disk
|
|
lerobot_dataset = LeRobotDataset.create(
|
|
repo_id="rerun/droid_lerobot", # Dataset identifier
|
|
fps=config.fps,
|
|
features=features,
|
|
root=TMP_DIR / "lerobot_dataset",
|
|
use_videos=config.use_videos,
|
|
)
|
|
# endregion: create_dataset
|
|
|
|
# region: export_episode
|
|
# Convert the recording to a LeRobot episode
|
|
# This aligns all time series to the target frame rate, extracts video frames,
|
|
# and writes the episode data in LeRobot's Parquet format
|
|
print("Creating episode")
|
|
|
|
convert_dataframe_to_episode(
|
|
df=training_data,
|
|
config=config,
|
|
lerobot_dataset=lerobot_dataset,
|
|
segment_id=single_recording,
|
|
features=features,
|
|
)
|
|
|
|
# Finalize the dataset (write metadata, close files, etc.)
|
|
lerobot_dataset.finalize()
|
|
# endregion: export_episode
|
|
|
|
# region: multi_episode_export
|
|
# Create a new LeRobot dataset for multiple episodes
|
|
lerobot_dataset = LeRobotDataset.create(
|
|
repo_id="rerun/droid_lerobot_full",
|
|
fps=config.fps,
|
|
features=features,
|
|
root=TMP_DIR / "lerobot_dataset_full",
|
|
use_videos=config.use_videos,
|
|
)
|
|
|
|
# To export multiple recordings, repeat the filtering and conversion steps
|
|
for segment_id in dataset.segment_ids():
|
|
print(f"Exporting segment: {segment_id}")
|
|
|
|
segment_data = (
|
|
dataset
|
|
.filter_segments(segment_id)
|
|
.filter_contents([
|
|
"/action/joint_positions",
|
|
"/observation/joint_positions",
|
|
"/camera/**",
|
|
"/language_instruction",
|
|
])
|
|
.reader(index="real_time")
|
|
)
|
|
|
|
convert_dataframe_to_episode(
|
|
df=segment_data,
|
|
config=config,
|
|
lerobot_dataset=lerobot_dataset,
|
|
segment_id=segment_id,
|
|
features=features,
|
|
)
|
|
|
|
lerobot_dataset.finalize()
|
|
# endregion: multi_episode_export
|
|
|
|
# region: use_dataset
|
|
|
|
# Access episode data
|
|
print(f"Total episodes: {lerobot_dataset.num_episodes}")
|
|
print(f"Total frames: {lerobot_dataset.num_frames}")
|
|
|
|
# endregion: use_dataset
|