Resume LeRobot Recording
SUMMARY
Open an existing LeRobot dataset with mode="resume", append episodes that match its schema, and finalize all writers after collection.
Resume Workflow
Resuming preserves the dataset's feature schema, frame rate, metadata, and existing episodes. New episodes are appended after those already stored.
1. Identify the Existing Dataset
Use the same repository ID and local path used when the dataset was created:
from pathlib import Path
repo_id = "user/my_record_example"
local_path = (
Path(__file__).resolve().parent.parent.parent.parent.parent
/ "results"
/ repo_id
)repo_ididentifies the existing dataset.local_pathpoints to its local storage directory. When omitted, the default~/.cache/telekinesis/lerobot/<repo_id>directory is used.- Do not delete or replace the directory before resuming.
Writer Configuration
Writer and encoder behavior can be configured for the resumed session:
from telekinesis.dataengine import datasets
config = datasets.LeRobotDatasetWriterConfig(
tolerance_s=1e-4,
)Only values that differ from the defaults need to be specified. See the configuration reference for all available options.
2. Resume the Logger
Use mode="resume" without supplying fps or features:
from telekinesis.dataengine import data_loggers
lerobot_logger = data_loggers.LeRobotDatasetLogger(
repo_id=repo_id,
local_path=local_path,
mode="resume",
config=config,
)
dataset_fps = lerobot_logger.dataset.fpsThe existing frame rate and feature schema are loaded from dataset metadata. Set force_cache_sync=True when cached metadata must be refreshed before resuming.
SCHEMA COMPATIBILITY
Every new frame must match the existing feature keys, shapes, and data types. Resume mode does not redefine or migrate the schema. To verify the existing schema before recording, see Inspect Dataset Metadata.
3. Record Additional Episodes
Set the number of new episodes to append:
num_new_episodes = 5Use the same episode lifecycle as a newly created dataset:
try:
for episode_index in range(num_new_episodes):
lerobot_logger.start_episode()
frame_index = 0
try:
while not task_complete(frame_index):
frame_start = time.perf_counter()
task = "Pick up the blue cube"
frame = make_frame(task=task)
lerobot_logger.log(frame)
frame_index += 1
wait_for_next_frame(frame_start, dataset_fps)
except (Exception, KeyboardInterrupt):
lerobot_logger.discard_episode()
logger.exception(
"Episode recording failed. Current episode discarded."
)
raise
else:
lerobot_logger.stop_episode()
logger.info(f"Additional episode {episode_index} saved.")
except KeyboardInterrupt:
logger.info("Stopping data collection.")stop_episode() appends the completed episode. discard_episode() clears only the active episode buffer and does not modify previously saved episodes.
4. Finalize Recording
Clean up an active episode first, then close the logger as the final operation:
finally:
logger.info("Cleaning up active episode if any.")
if lerobot_logger.episode_active:
try:
lerobot_logger.discard_episode()
except Exception:
logger.exception(
"Failed to discard the active episode during cleanup."
)
lerobot_logger.close()
logger.info("Logging complete.")Handling Interrupted Recording
If acquisition or saving fails, discard the active episode before calling close(). Existing saved episodes remain unchanged.
What Is Preserved When Resuming
| Dataset state | Resume behavior |
|---|---|
| Feature schema | Loaded from existing metadata; cannot be redefined. |
| Frame rate | Loaded from existing metadata. |
| Saved episodes | Preserved. |
| Dataset metadata | Preserved, or refreshed with force_cache_sync=True. |
| New episodes | Appended after existing episodes when stop_episode() succeeds. |
| Discarded episode | Removed from the active buffer without affecting saved episodes. |
Class Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
repo_id | str | required | Dataset repository identifier, typically "{hf_user}/{dataset_name}". |
mode | Literal["create", "overwrite", "resume"] | "create" | Set to "resume" to append episodes to an existing dataset. |
local_path | str | Path | None | None | Local dataset directory; otherwise uses the default LeRobot cache path. |
fps | int | None | None | Collection frame rate. Omit in "resume" mode because it is loaded from metadata. |
features | dict | None | None | Dataset feature schema. Omit in "resume" mode because it is loaded from metadata. |
robot_type | str | None | None | Optional robot type for creation modes; existing metadata is preserved when resuming. |
use_videos | bool | True | Controls visual storage in creation modes; existing storage is preserved when resuming. |
config | LeRobotDatasetWriterConfig | None | None | Recording and encoding configuration; uses the default configuration when omitted. |
force_cache_sync | bool | False | Whether to refresh existing metadata before resuming. |
Method Reference
| Method | Purpose |
|---|---|
start_episode() | Start a new episode. |
log(frame) | Add one schema-complete frame to the active episode. |
stop_episode(parallel_encoding=True) | Persist the active episode, optionally encoding multiple camera streams in parallel. |
discard_episode() | Clear the active episode without saving it. |
close() | Finalize the dataset when no episode is active. |
Attribute Reference
| Attribute | Type | Access | Description |
|---|---|---|---|
episode_active | bool | Read-only | Whether an episode is currently being recorded. |
Example
This script resumes a LeRobot dataset, records synchronized frames across additional episodes at its existing frame rate, and finalizes all writers. Replace the generated observations, actions, and termination condition with application-specific interfaces.
"""Example script demonstrating how to resume a LeRobot dataset."""
import time
from pathlib import Path
import numpy as np
from loguru import logger
from telekinesis.dataengine import data_loggers, datasets
def resume_lerobot_dataset_example():
"""Resume an existing dataset and append episodes."""
# Step 1: Identify the existing dataset
repo_id = "user/my_record_example"
local_path = (
Path(__file__).resolve().parent.parent.parent.parent.parent
/ "results"
/ repo_id
)
config = datasets.LeRobotDatasetWriterConfig(tolerance_s=1e-4)
# Step 2: Resume the logger
lerobot_logger = data_loggers.LeRobotDatasetLogger(
repo_id=repo_id,
local_path=local_path,
mode="resume",
config=config,
)
dataset_fps = lerobot_logger.dataset.fps
# Step 3: Record additional episodes
num_new_episodes = 5
try:
for episode_index in range(num_new_episodes):
lerobot_logger.start_episode()
frame_index = 0
try:
while not task_complete(frame_index):
frame_start = time.perf_counter()
frame = make_frame(task="Pick up the blue cube")
lerobot_logger.log(frame)
frame_index += 1
wait_for_next_frame(frame_start, dataset_fps)
except (Exception, KeyboardInterrupt):
lerobot_logger.discard_episode()
logger.exception(
"Episode recording failed. Current episode discarded."
)
raise
else:
lerobot_logger.stop_episode()
logger.info(f"Additional episode {episode_index} saved.")
except KeyboardInterrupt:
logger.info("Stopping data collection.")
# Step 4: Finalize recording
finally:
logger.info("Cleaning up active episode if any.")
if lerobot_logger.episode_active:
try:
lerobot_logger.discard_episode()
except Exception:
logger.exception(
"Failed to discard the active episode during cleanup."
)
lerobot_logger.close()
logger.info("Logging complete.")
def read_observation_camera() -> np.ndarray:
"""Return the latest RGB camera observation."""
return np.random.rand(3, 480, 640).astype("float32")
def read_observation_robot_state() -> np.ndarray:
"""Return the current robot state."""
return np.random.rand(7).astype("float32")
def get_robot_action() -> np.ndarray:
"""Return the current action applied to the robot."""
return np.random.rand(7).astype("float32")
def make_frame(task: str) -> dict:
"""Assemble a dataset frame from observations and an action."""
return {
"observation.camera_rgb": read_observation_camera(),
"observation.state": read_observation_robot_state(),
"action": get_robot_action(),
"task": task,
}
def task_complete(frame_index: int) -> bool:
"""Replace this with the application's task termination condition."""
max_frames = 5
return frame_index >= max_frames
def wait_for_next_frame(start_time: float, fps: int) -> None:
"""Wait until the next dataset frame should be recorded."""
frame_period = 1.0 / fps
elapsed = time.perf_counter() - start_time
remaining = frame_period - elapsed
if remaining > 0:
time.sleep(remaining)
if __name__ == "__main__":
resume_lerobot_dataset_example()Next Steps
Visualize the Dataset
Open the resumed dataset in Rerun and inspect its appended episodes.
Visualize dataset →Inspect Dataset Metadata
Review the feature schema, frame rate, episodes, statistics, and storage metadata.
Inspect metadata →Load a LeRobot Dataset
Open an existing local or remote dataset and access its recorded frames.
Load dataset →Record a New Dataset
Create a new writable dataset and record demonstrations from the beginning.
Start recording →