Skip to content

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:

python
from pathlib import Path

repo_id = "user/my_record_example"
local_path = (
    Path(__file__).resolve().parent.parent.parent.parent.parent
    / "results"
    / repo_id
)
  • repo_id identifies the existing dataset.
  • local_path points 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:

python
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:

python
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.fps

The 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:

python
num_new_episodes = 5

Use the same episode lifecycle as a newly created dataset:

python
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:

python
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 stateResume behavior
Feature schemaLoaded from existing metadata; cannot be redefined.
Frame rateLoaded from existing metadata.
Saved episodesPreserved.
Dataset metadataPreserved, or refreshed with force_cache_sync=True.
New episodesAppended after existing episodes when stop_episode() succeeds.
Discarded episodeRemoved from the active buffer without affecting saved episodes.

Class Reference

ParameterTypeDefaultDescription
repo_idstrrequiredDataset repository identifier, typically "{hf_user}/{dataset_name}".
modeLiteral["create", "overwrite", "resume"]"create"Set to "resume" to append episodes to an existing dataset.
local_pathstr | Path | NoneNoneLocal dataset directory; otherwise uses the default LeRobot cache path.
fpsint | NoneNoneCollection frame rate. Omit in "resume" mode because it is loaded from metadata.
featuresdict | NoneNoneDataset feature schema. Omit in "resume" mode because it is loaded from metadata.
robot_typestr | NoneNoneOptional robot type for creation modes; existing metadata is preserved when resuming.
use_videosboolTrueControls visual storage in creation modes; existing storage is preserved when resuming.
configLeRobotDatasetWriterConfig | NoneNoneRecording and encoding configuration; uses the default configuration when omitted.
force_cache_syncboolFalseWhether to refresh existing metadata before resuming.

Method Reference

MethodPurpose
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

AttributeTypeAccessDescription
episode_activeboolRead-onlyWhether 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.

python
"""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