From 08fbbb318f4825769f6891ff907525a3d5aa3431 Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Thu, 8 May 2025 13:15:59 +0200 Subject: [PATCH] Add replay --- lerobot/replay.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 lerobot/replay.py diff --git a/lerobot/replay.py b/lerobot/replay.py new file mode 100644 index 000000000..0de0d4539 --- /dev/null +++ b/lerobot/replay.py @@ -0,0 +1,86 @@ +# Copyright 2024 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import time +from dataclasses import asdict, dataclass +from pathlib import Path +from pprint import pformat + +import draccus + +from lerobot.common.datasets.lerobot_dataset import LeRobotDataset +from lerobot.common.robots import ( # noqa: F401 + Robot, + RobotConfig, + koch_follower, + make_robot_from_config, + so100_follower, +) +from lerobot.common.utils.robot_utils import busy_wait +from lerobot.common.utils.utils import ( + init_logging, + log_say, +) + + +@dataclass +class DatasetReplayConfig: + # Dataset identifier. By convention it should match '{hf_username}/{dataset_name}' (e.g. `lerobot/test`). + repo_id: str + # Episode to replay. + episode: int + # Root directory where the dataset will be stored (e.g. 'dataset/path'). + root: str | Path | None = None + # Limit the frames per second. By default, uses the policy fps. + fps: int = 30 + + +@dataclass +class ReplayConfig: + robot: RobotConfig + dataset: DatasetReplayConfig + # Use vocal synthesis to read events. + play_sounds: bool = True + + +@draccus.wrap() +def replay(cfg: ReplayConfig): + init_logging() + logging.info(pformat(asdict(cfg))) + + robot = make_robot_from_config(cfg.robot) + dataset = LeRobotDataset(cfg.dataset.repo_id, root=cfg.dataset.root, episodes=[cfg.dataset.episode]) + actions = dataset.hf_dataset.select_columns("action.joints") + robot.connect() + + log_say("Replaying episode", cfg.play_sounds, blocking=True) + for idx in range(dataset.num_frames): + start_episode_t = time.perf_counter() + + action_array = actions[idx]["action.joints"] + action = {} + for i, name in enumerate(dataset.features["action.joints"]["names"]): + action[name] = action_array[i] + + robot.send_action(action) + + dt_s = time.perf_counter() - start_episode_t + busy_wait(1 / dataset.fps - dt_s) + + robot.disconnect() + + +if __name__ == "__main__": + replay()