Merge branch 'main' into fix/minor_fixes_examples

This commit is contained in:
Steven Palma 2025-04-09 17:44:15 +02:00 committed by GitHub
commit 0c2ef11ce5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 186 additions and 76 deletions

View File

@ -36,8 +36,8 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/crate-ci/typos - repo: https://github.com/adhtruong/mirrors-typos
rev: v1.30.2 rev: v1.31.1
hooks: hooks:
- id: typos - id: typos
args: [--force-exclude] args: [--force-exclude]
@ -48,7 +48,7 @@ repos:
- id: pyupgrade - id: pyupgrade
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.10 rev: v0.11.4
hooks: hooks:
- id: ruff - id: ruff
args: [--fix] args: [--fix]
@ -57,12 +57,12 @@ repos:
##### Security ##### ##### Security #####
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0 rev: v8.24.2
hooks: hooks:
- id: gitleaks - id: gitleaks
- repo: https://github.com/woodruffw/zizmor-pre-commit - repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.4.1 rev: v1.5.2
hooks: hooks:
- id: zizmor - id: zizmor

View File

@ -98,14 +98,14 @@ conda create -y -n lerobot python=3.10
conda activate lerobot conda activate lerobot
``` ```
When using `miniconda`, if you don't have `ffmpeg` in your environment: When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
conda install ffmpeg conda install ffmpeg -c conda-forge
``` ```
Install 🤗 LeRobot: Install 🤗 LeRobot:
```bash ```bash
pip install --no-binary=av -e . pip install -e .
``` ```
> **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run: > **NOTE:** If you encounter build errors, you may need to install additional dependencies (`cmake`, `build-essential`, and `ffmpeg libs`). On Linux, run:
@ -118,7 +118,7 @@ For simulations, 🤗 LeRobot comes with gymnasium environments that can be inst
For instance, to install 🤗 LeRobot with aloha and pusht, use: For instance, to install 🤗 LeRobot with aloha and pusht, use:
```bash ```bash
pip install --no-binary=av -e ".[aloha, pusht]" pip install -e ".[aloha, pusht]"
``` ```
To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with To use [Weights and Biases](https://docs.wandb.ai/quickstart) for experiment tracking, log in with

View File

@ -17,12 +17,21 @@
import argparse import argparse
import datetime as dt import datetime as dt
import os
import time
from pathlib import Path from pathlib import Path
import cv2 import cv2
import rerun as rr
# see https://rerun.io/docs/howto/visualization/limit-ram
RERUN_MEMORY_LIMIT = os.getenv("LEROBOT_RERUN_MEMORY_LIMIT", "5%")
def display_and_save_video_stream(output_dir: Path, fps: int, width: int, height: int): def display_and_save_video_stream(output_dir: Path, fps: int, width: int, height: int, duration: int):
rr.init("lerobot_capture_camera_feed")
rr.spawn(memory_limit=RERUN_MEMORY_LIMIT)
now = dt.datetime.now() now = dt.datetime.now()
capture_dir = output_dir / f"{now:%Y-%m-%d}" / f"{now:%H-%M-%S}" capture_dir = output_dir / f"{now:%Y-%m-%d}" / f"{now:%H-%M-%S}"
if not capture_dir.exists(): if not capture_dir.exists():
@ -39,24 +48,21 @@ def display_and_save_video_stream(output_dir: Path, fps: int, width: int, height
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
frame_index = 0 frame_index = 0
while True: start_time = time.time()
while time.time() - start_time < duration:
ret, frame = cap.read() ret, frame = cap.read()
if not ret: if not ret:
print("Error: Could not read frame.") print("Error: Could not read frame.")
break break
rr.log("video/stream", rr.Image(frame.numpy()), static=True)
cv2.imshow("Video Stream", frame)
cv2.imwrite(str(capture_dir / f"frame_{frame_index:06d}.png"), frame) cv2.imwrite(str(capture_dir / f"frame_{frame_index:06d}.png"), frame)
frame_index += 1 frame_index += 1
# Break the loop on 'q' key press # Release the capture
if cv2.waitKey(1) & 0xFF == ord("q"):
break
# Release the capture and destroy all windows
cap.release() cap.release()
cv2.destroyAllWindows()
# TODO(Steven): Add a graceful shutdown via a close() method for the Viewer context, though not currently supported in the Rerun API.
if __name__ == "__main__": if __name__ == "__main__":
@ -86,5 +92,11 @@ if __name__ == "__main__":
default=720, default=720,
help="Height of the captured images.", help="Height of the captured images.",
) )
parser.add_argument(
"--duration",
type=int,
default=20,
help="Duration in seconds for which the video stream should be captured.",
)
args = parser.parse_args() args = parser.parse_args()
display_and_save_video_stream(**vars(args)) display_and_save_video_stream(**vars(args))

View File

@ -57,9 +57,15 @@ conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
#### 5. Install LeRobot with dependencies for the feetech motors: #### 5. Install ffmpeg in your environment:
When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[feetech]" conda install ffmpeg -c conda-forge
```
#### 6. Install LeRobot with dependencies for the feetech motors:
```bash
cd ~/lerobot && pip install -e ".[feetech]"
``` ```
Great :hugs:! You are now done installing LeRobot and we can begin assembling the SO100 arms :robot:. Great :hugs:! You are now done installing LeRobot and we can begin assembling the SO100 arms :robot:.
@ -491,6 +497,9 @@ python lerobot/scripts/control_robot.py \
#### a. Teleop with displaying cameras #### a. Teleop with displaying cameras
Follow [this guide to setup your cameras](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#c-add-your-cameras-with-opencvcamera). Then you will be able to display the cameras on your computer while you are teleoperating by running the following code. This is useful to prepare your setup before recording your first dataset. Follow [this guide to setup your cameras](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#c-add-your-cameras-with-opencvcamera). Then you will be able to display the cameras on your computer while you are teleoperating by running the following code. This is useful to prepare your setup before recording your first dataset.
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
```bash ```bash
python lerobot/scripts/control_robot.py \ python lerobot/scripts/control_robot.py \
--robot.type=so100 \ --robot.type=so100 \

View File

@ -67,9 +67,15 @@ conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
#### 5. Install LeRobot with dependencies for the feetech motors: #### 5. Install ffmpeg in your environment:
When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[feetech]" conda install ffmpeg -c conda-forge
```
#### 6. Install LeRobot with dependencies for the feetech motors:
```bash
cd ~/lerobot && pip install -e ".[feetech]"
``` ```
## C. Install LeRobot on laptop ## C. Install LeRobot on laptop
@ -108,9 +114,15 @@ conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
#### 5. Install LeRobot with dependencies for the feetech motors: #### 5. Install ffmpeg in your environment:
When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[feetech]" conda install ffmpeg -c conda-forge
```
#### 6. Install LeRobot with dependencies for the feetech motors:
```bash
cd ~/lerobot && pip install -e ".[feetech]"
``` ```
Great :hugs:! You are now done installing LeRobot and we can begin assembling the SO100 arms and Mobile base :robot:. Great :hugs:! You are now done installing LeRobot and we can begin assembling the SO100 arms and Mobile base :robot:.
@ -412,6 +424,8 @@ python lerobot/scripts/control_robot.py \
--control.fps=30 --control.fps=30
``` ```
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`. For the `--control.type=remote_robot` you will also need to set `--control.viewer_ip` and `--control.viewer_port`
You should see on your laptop something like this: ```[INFO] Connected to remote robot at tcp://172.17.133.91:5555 and video stream at tcp://172.17.133.91:5556.``` Now you can move the leader arm and use the keyboard (w,a,s,d) to drive forward, left, backwards, right. And use (z,x) to turn left or turn right. You can use (r,f) to increase and decrease the speed of the mobile robot. There are three speed modes, see the table below: You should see on your laptop something like this: ```[INFO] Connected to remote robot at tcp://172.17.133.91:5555 and video stream at tcp://172.17.133.91:5556.``` Now you can move the leader arm and use the keyboard (w,a,s,d) to drive forward, left, backwards, right. And use (z,x) to turn left or turn right. You can use (r,f) to increase and decrease the speed of the mobile robot. There are three speed modes, see the table below:
| Speed Mode | Linear Speed (m/s) | Rotation Speed (deg/s) | | Speed Mode | Linear Speed (m/s) | Rotation Speed (deg/s) |
| ---------- | ------------------ | ---------------------- | | ---------- | ------------------ | ---------------------- |

View File

@ -31,9 +31,15 @@ conda create -y -n lerobot python=3.10 && conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
5. Install LeRobot with dependencies for the feetech motors: 5. Install ffmpeg in your environment:
When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[feetech]" conda install ffmpeg -c conda-forge
```
6. Install LeRobot with dependencies for the feetech motors:
```bash
cd ~/lerobot && pip install -e ".[feetech]"
``` ```
## Configure the motors ## Configure the motors
@ -212,6 +218,9 @@ python lerobot/scripts/control_robot.py \
**Teleop with displaying cameras** **Teleop with displaying cameras**
Follow [this guide to setup your cameras](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#c-add-your-cameras-with-opencvcamera). Then you will be able to display the cameras on your computer while you are teleoperating by running the following code. This is useful to prepare your setup before recording your first dataset. Follow [this guide to setup your cameras](https://github.com/huggingface/lerobot/blob/main/examples/7_get_started_with_real_robot.md#c-add-your-cameras-with-opencvcamera). Then you will be able to display the cameras on your computer while you are teleoperating by running the following code. This is useful to prepare your setup before recording your first dataset.
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
```bash ```bash
python lerobot/scripts/control_robot.py \ python lerobot/scripts/control_robot.py \
--robot.type=moss \ --robot.type=moss \

View File

@ -119,7 +119,7 @@ print(dataset.features[camera_key]["shape"])
delta_timestamps = { delta_timestamps = {
# loads 4 images: 1 second before current frame, 500 ms before, 200 ms before, and current frame # loads 4 images: 1 second before current frame, 500 ms before, 200 ms before, and current frame
camera_key: [-1, -0.5, -0.20, 0], camera_key: [-1, -0.5, -0.20, 0],
# loads 8 state vectors: 1.5 seconds before, 1 second before, ... 200 ms, 100 ms, and current frame # loads 6 state vectors: 1.5 seconds before, 1 second before, ... 200 ms, 100 ms, and current frame
"observation.state": [-1.5, -1, -0.5, -0.20, -0.10, 0], "observation.state": [-1.5, -1, -0.5, -0.20, -0.10, 0],
# loads 64 action vectors: current frame, 1 frame in the future, 2 frames, ... 63 frames in the future # loads 64 action vectors: current frame, 1 frame in the future, 2 frames, ... 63 frames in the future
"action": [t / dataset.fps for t in range(64)], "action": [t / dataset.fps for t in range(64)],
@ -143,6 +143,6 @@ dataloader = torch.utils.data.DataLoader(
for batch in dataloader: for batch in dataloader:
print(f"{batch[camera_key].shape=}") # (32, 4, c, h, w) print(f"{batch[camera_key].shape=}") # (32, 4, c, h, w)
print(f"{batch['observation.state'].shape=}") # (32, 5, c) print(f"{batch['observation.state'].shape=}") # (32, 6, c)
print(f"{batch['action'].shape=}") # (32, 64, c) print(f"{batch['action'].shape=}") # (32, 64, c)
break break

View File

@ -18,7 +18,7 @@ training outputs directory. In the latter case, you might want to run examples/3
It requires the installation of the 'gym_pusht' simulation environment. Install it by running: It requires the installation of the 'gym_pusht' simulation environment. Install it by running:
```bash ```bash
pip install --no-binary=av -e ".[pusht]" pip install -e ".[pusht]"
``` ```
""" """

View File

@ -33,7 +33,7 @@ First, install the additional dependencies required for robots built with dynami
Using `pip`: Using `pip`:
```bash ```bash
pip install --no-binary=av -e ".[dynamixel]" pip install -e ".[dynamixel]"
``` ```
Using `poetry`: Using `poetry`:
@ -55,6 +55,9 @@ Finally, connect both arms to your computer via USB. Note that the USB doesn't p
Now you are ready to configure your motors for the first time, as detailed in the sections below. In the upcoming sections, you'll learn about our classes and functions by running some python code in an interactive session, or by copy-pasting it in a python file. Now you are ready to configure your motors for the first time, as detailed in the sections below. In the upcoming sections, you'll learn about our classes and functions by running some python code in an interactive session, or by copy-pasting it in a python file.
If you have already configured your motors the first time, you can streamline the process by directly running the teleoperate script (which is detailed further in the tutorial): If you have already configured your motors the first time, you can streamline the process by directly running the teleoperate script (which is detailed further in the tutorial):
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
```bash ```bash
python lerobot/scripts/control_robot.py \ python lerobot/scripts/control_robot.py \
--robot.type=koch \ --robot.type=koch \
@ -829,7 +832,7 @@ It contains:
Troubleshooting: Troubleshooting:
- On Linux, if you encounter any issue during video encoding with `ffmpeg: unknown encoder libsvtav1`, you can: - On Linux, if you encounter any issue during video encoding with `ffmpeg: unknown encoder libsvtav1`, you can:
- install with conda-forge by running `conda install -c conda-forge ffmpeg` (it should be compiled with `libsvtav1`), - install with conda-forge by running `conda install -c conda-forge ffmpeg` (it should be compiled with `libsvtav1`),
- or, install [Homebrew](https://brew.sh) and run `brew install ffmpeg` (it should be compiled with `libsvtav1`), > **NOTE:** This usually installs `ffmpeg 7.X` for your platform (check the version installed with `ffmpeg -encoders | grep libsvtav1`). If it isn't `ffmpeg 7.X` or lacks `libsvtav1` support, you can explicitly install `ffmpeg 7.X` using: `conda install ffmpeg=7.1.1 -c conda-forge`
- or, install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1), - or, install [ffmpeg build dependencies](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#GettheDependencies) and [compile ffmpeg from source with libsvtav1](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#libsvtav1),
- and, make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`. - and, make sure you use the corresponding ffmpeg binary to your install with `which ffmpeg`.
- On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux). - On Linux, if the left and right arrow keys and escape key don't have any effect during data recording, make sure you've set the `$DISPLAY` environment variable. See [pynput limitations](https://pynput.readthedocs.io/en/latest/limitations.html#linux).

View File

@ -43,14 +43,19 @@ conda create -y -n lerobot python=3.10 && conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
6. Install LeRobot with stretch dependencies: 6. When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[stretch]" conda install ffmpeg -c conda-forge
```
7. Install LeRobot with stretch dependencies:
```bash
cd ~/lerobot && pip install -e ".[stretch]"
``` ```
> **Note:** If you get this message, you can ignore it: `ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.` > **Note:** If you get this message, you can ignore it: `ERROR: pip's dependency resolver does not currently take into account all the packages that are installed.`
7. Run a [system check](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#system-check) to make sure your robot is ready: 8. Run a [system check](https://docs.hello-robot.com/0.3/getting_started/stretch_hardware_overview/#system-check) to make sure your robot is ready:
```bash ```bash
stretch_system_check.py stretch_system_check.py
``` ```
@ -97,6 +102,8 @@ This is equivalent to running `stretch_robot_home.py`
Before trying teleoperation, you need activate the gamepad controller by pressing the middle button. For more info, see Stretch's [doc](https://docs.hello-robot.com/0.3/getting_started/hello_robot/#gamepad-teleoperation). Before trying teleoperation, you need activate the gamepad controller by pressing the middle button. For more info, see Stretch's [doc](https://docs.hello-robot.com/0.3/getting_started/hello_robot/#gamepad-teleoperation).
Now try out teleoperation (see above documentation to learn about the gamepad controls): Now try out teleoperation (see above documentation to learn about the gamepad controls):
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
```bash ```bash
python lerobot/scripts/control_robot.py \ python lerobot/scripts/control_robot.py \
--robot.type=stretch \ --robot.type=stretch \

View File

@ -30,9 +30,14 @@ conda create -y -n lerobot python=3.10 && conda activate lerobot
git clone https://github.com/huggingface/lerobot.git ~/lerobot git clone https://github.com/huggingface/lerobot.git ~/lerobot
``` ```
5. Install LeRobot with dependencies for the Aloha motors (dynamixel) and cameras (intelrealsense): 5. When using `miniconda`, install `ffmpeg` in your environment:
```bash ```bash
cd ~/lerobot && pip install --no-binary=av -e ".[dynamixel, intelrealsense]" conda install ffmpeg -c conda-forge
```
6. Install LeRobot with dependencies for the Aloha motors (dynamixel) and cameras (intelrealsense):
```bash
cd ~/lerobot && pip install -e ".[dynamixel, intelrealsense]"
``` ```
## Teleoperate ## Teleoperate
@ -43,6 +48,9 @@ Teleoperation consists in manually operating the leader arms to move the followe
2. Our code assumes that your robot has been assembled following Trossen Robotics instructions. This allows us to skip calibration, as we use the pre-defined calibration files in `.cache/calibration/aloha_default`. If you replace a motor, make sure you follow the exact instructions from Trossen Robotics. 2. Our code assumes that your robot has been assembled following Trossen Robotics instructions. This allows us to skip calibration, as we use the pre-defined calibration files in `.cache/calibration/aloha_default`. If you replace a motor, make sure you follow the exact instructions from Trossen Robotics.
By running the following code, you can start your first **SAFE** teleoperation: By running the following code, you can start your first **SAFE** teleoperation:
> **NOTE:** To visualize the data, enable `--control.display_data=true`. This streams the data using `rerun`.
```bash ```bash
python lerobot/scripts/control_robot.py \ python lerobot/scripts/control_robot.py \
--robot.type=aloha \ --robot.type=aloha \

View File

@ -1053,7 +1053,7 @@ class MultiLeRobotDataset(torch.utils.data.Dataset):
super().__init__() super().__init__()
self.repo_ids = repo_ids self.repo_ids = repo_ids
self.root = Path(root) if root else HF_LEROBOT_HOME self.root = Path(root) if root else HF_LEROBOT_HOME
self.tolerances_s = tolerances_s if tolerances_s else {repo_id: 1e-4 for repo_id in repo_ids} self.tolerances_s = tolerances_s if tolerances_s else dict.fromkeys(repo_ids, 0.0001)
# Construct the underlying datasets passing everything but `transform` and `delta_timestamps` which # Construct the underlying datasets passing everything but `transform` and `delta_timestamps` which
# are handled by this class. # are handled by this class.
self._datasets = [ self._datasets = [

View File

@ -240,7 +240,7 @@ def load_episodes_stats(local_dir: Path) -> dict:
def backward_compatible_episodes_stats( def backward_compatible_episodes_stats(
stats: dict[str, dict[str, np.ndarray]], episodes: list[int] stats: dict[str, dict[str, np.ndarray]], episodes: list[int]
) -> dict[str, dict[str, np.ndarray]]: ) -> dict[str, dict[str, np.ndarray]]:
return {ep_idx: stats for ep_idx in episodes} return dict.fromkeys(episodes, stats)
def load_image_as_numpy( def load_image_as_numpy(

View File

@ -481,7 +481,7 @@ def convert_dataset(
# Tasks # Tasks
if single_task: if single_task:
tasks_by_episodes = {ep_idx: single_task for ep_idx in episode_indices} tasks_by_episodes = dict.fromkeys(episode_indices, single_task)
dataset, tasks = add_task_index_by_episodes(dataset, tasks_by_episodes) dataset, tasks = add_task_index_by_episodes(dataset, tasks_by_episodes)
tasks_by_episodes = {ep_idx: [task] for ep_idx, task in tasks_by_episodes.items()} tasks_by_episodes = {ep_idx: [task] for ep_idx, task in tasks_by_episodes.items()}
elif tasks_path: elif tasks_path:

View File

@ -24,7 +24,7 @@ Designed by Physical Intelligence. Ported from Jax by Hugging Face.
Install pi0 extra dependencies: Install pi0 extra dependencies:
```bash ```bash
pip install --no-binary=av -e ".[pi0]" pip install -e ".[pi0]"
``` ```
Example of finetuning the pi0 pretrained model (`pi0_base` in `openpi`): Example of finetuning the pi0 pretrained model (`pi0_base` in `openpi`):

View File

@ -41,7 +41,7 @@ class TeleoperateControlConfig(ControlConfig):
fps: int | None = None fps: int | None = None
teleop_time_s: float | None = None teleop_time_s: float | None = None
# Display all cameras on screen # Display all cameras on screen
display_cameras: bool = True display_data: bool = False
@ControlConfig.register_subclass("record") @ControlConfig.register_subclass("record")
@ -82,7 +82,7 @@ class RecordControlConfig(ControlConfig):
# Not enough threads might cause low camera fps. # Not enough threads might cause low camera fps.
num_image_writer_threads_per_camera: int = 4 num_image_writer_threads_per_camera: int = 4
# Display all cameras on screen # Display all cameras on screen
display_cameras: bool = True display_data: bool = False
# Use vocal synthesis to read events. # Use vocal synthesis to read events.
play_sounds: bool = True play_sounds: bool = True
# Resume recording on an existing dataset. # Resume recording on an existing dataset.
@ -116,6 +116,11 @@ class ReplayControlConfig(ControlConfig):
@dataclass @dataclass
class RemoteRobotConfig(ControlConfig): class RemoteRobotConfig(ControlConfig):
log_interval: int = 100 log_interval: int = 100
# Display all cameras on screen
display_data: bool = False
# Rerun configuration for remote robot (https://ref.rerun.io/docs/python/0.22.1/common/initialization_functions/#rerun.connect_tcp)
viewer_ip: str | None = None
viewer_port: str | None = None
@dataclass @dataclass

View File

@ -24,7 +24,7 @@ from contextlib import nullcontext
from copy import copy from copy import copy
from functools import cache from functools import cache
import cv2 import rerun as rr
import torch import torch
from deepdiff import DeepDiff from deepdiff import DeepDiff
from termcolor import colored from termcolor import colored
@ -174,13 +174,13 @@ def warmup_record(
events, events,
enable_teleoperation, enable_teleoperation,
warmup_time_s, warmup_time_s,
display_cameras, display_data,
fps, fps,
): ):
control_loop( control_loop(
robot=robot, robot=robot,
control_time_s=warmup_time_s, control_time_s=warmup_time_s,
display_cameras=display_cameras, display_data=display_data,
events=events, events=events,
fps=fps, fps=fps,
teleoperate=enable_teleoperation, teleoperate=enable_teleoperation,
@ -192,7 +192,7 @@ def record_episode(
dataset, dataset,
events, events,
episode_time_s, episode_time_s,
display_cameras, display_data,
policy, policy,
fps, fps,
single_task, single_task,
@ -200,7 +200,7 @@ def record_episode(
control_loop( control_loop(
robot=robot, robot=robot,
control_time_s=episode_time_s, control_time_s=episode_time_s,
display_cameras=display_cameras, display_data=display_data,
dataset=dataset, dataset=dataset,
events=events, events=events,
policy=policy, policy=policy,
@ -215,7 +215,7 @@ def control_loop(
robot, robot,
control_time_s=None, control_time_s=None,
teleoperate=False, teleoperate=False,
display_cameras=False, display_data=False,
dataset: LeRobotDataset | None = None, dataset: LeRobotDataset | None = None,
events=None, events=None,
policy: PreTrainedPolicy = None, policy: PreTrainedPolicy = None,
@ -264,11 +264,15 @@ def control_loop(
frame = {**observation, **action, "task": single_task} frame = {**observation, **action, "task": single_task}
dataset.add_frame(frame) dataset.add_frame(frame)
if display_cameras and not is_headless(): # TODO(Steven): This should be more general (for RemoteRobot instead of checking the name, but anyways it will change soon)
if (display_data and not is_headless()) or (display_data and robot.robot_type.startswith("lekiwi")):
for k, v in action.items():
for i, vv in enumerate(v):
rr.log(f"sent_{k}_{i}", rr.Scalar(vv.numpy()))
image_keys = [key for key in observation if "image" in key] image_keys = [key for key in observation if "image" in key]
for key in image_keys: for key in image_keys:
cv2.imshow(key, cv2.cvtColor(observation[key].numpy(), cv2.COLOR_RGB2BGR)) rr.log(key, rr.Image(observation[key].numpy()), static=True)
cv2.waitKey(1)
if fps is not None: if fps is not None:
dt_s = time.perf_counter() - start_loop_t dt_s = time.perf_counter() - start_loop_t
@ -297,16 +301,12 @@ def reset_environment(robot, events, reset_time_s, fps):
) )
def stop_recording(robot, listener, display_cameras): def stop_recording(robot, listener, display_data):
robot.disconnect() robot.disconnect()
if not is_headless(): if not is_headless() and listener is not None:
if listener is not None:
listener.stop() listener.stop()
if display_cameras:
cv2.destroyAllWindows()
def sanity_check_dataset_name(repo_id, policy_cfg): def sanity_check_dataset_name(repo_id, policy_cfg):
_, dataset_name = repo_id.split("/") _, dataset_name = repo_id.split("/")

View File

@ -94,7 +94,7 @@ class MetricsTracker:
metrics: dict[str, AverageMeter], metrics: dict[str, AverageMeter],
initial_step: int = 0, initial_step: int = 0,
): ):
self.__dict__.update({k: None for k in self.__keys__}) self.__dict__.update(dict.fromkeys(self.__keys__))
self._batch_size = batch_size self._batch_size = batch_size
self._num_frames = num_frames self._num_frames = num_frames
self._avg_samples_per_ep = num_frames / num_episodes self._avg_samples_per_ep = num_frames / num_episodes

View File

@ -135,15 +135,19 @@ python lerobot/scripts/control_robot.py \
""" """
import logging import logging
import os
import time import time
from dataclasses import asdict from dataclasses import asdict
from pprint import pformat from pprint import pformat
import rerun as rr
# from safetensors.torch import load_file, save_file # from safetensors.torch import load_file, save_file
from lerobot.common.datasets.lerobot_dataset import LeRobotDataset from lerobot.common.datasets.lerobot_dataset import LeRobotDataset
from lerobot.common.policies.factory import make_policy from lerobot.common.policies.factory import make_policy
from lerobot.common.robot_devices.control_configs import ( from lerobot.common.robot_devices.control_configs import (
CalibrateControlConfig, CalibrateControlConfig,
ControlConfig,
ControlPipelineConfig, ControlPipelineConfig,
RecordControlConfig, RecordControlConfig,
RemoteRobotConfig, RemoteRobotConfig,
@ -153,6 +157,7 @@ from lerobot.common.robot_devices.control_configs import (
from lerobot.common.robot_devices.control_utils import ( from lerobot.common.robot_devices.control_utils import (
control_loop, control_loop,
init_keyboard_listener, init_keyboard_listener,
is_headless,
log_control_info, log_control_info,
record_episode, record_episode,
reset_environment, reset_environment,
@ -232,7 +237,7 @@ def teleoperate(robot: Robot, cfg: TeleoperateControlConfig):
control_time_s=cfg.teleop_time_s, control_time_s=cfg.teleop_time_s,
fps=cfg.fps, fps=cfg.fps,
teleoperate=True, teleoperate=True,
display_cameras=cfg.display_cameras, display_data=cfg.display_data,
) )
@ -280,7 +285,7 @@ def record(
# 3. place the cameras windows on screen # 3. place the cameras windows on screen
enable_teleoperation = policy is None enable_teleoperation = policy is None
log_say("Warmup record", cfg.play_sounds) log_say("Warmup record", cfg.play_sounds)
warmup_record(robot, events, enable_teleoperation, cfg.warmup_time_s, cfg.display_cameras, cfg.fps) warmup_record(robot, events, enable_teleoperation, cfg.warmup_time_s, cfg.display_data, cfg.fps)
if has_method(robot, "teleop_safety_stop"): if has_method(robot, "teleop_safety_stop"):
robot.teleop_safety_stop() robot.teleop_safety_stop()
@ -296,7 +301,7 @@ def record(
dataset=dataset, dataset=dataset,
events=events, events=events,
episode_time_s=cfg.episode_time_s, episode_time_s=cfg.episode_time_s,
display_cameras=cfg.display_cameras, display_data=cfg.display_data,
policy=policy, policy=policy,
fps=cfg.fps, fps=cfg.fps,
single_task=cfg.single_task, single_task=cfg.single_task,
@ -326,7 +331,7 @@ def record(
break break
log_say("Stop recording", cfg.play_sounds, blocking=True) log_say("Stop recording", cfg.play_sounds, blocking=True)
stop_recording(robot, listener, cfg.display_cameras) stop_recording(robot, listener, cfg.display_data)
if cfg.push_to_hub: if cfg.push_to_hub:
dataset.push_to_hub(tags=cfg.tags, private=cfg.private) dataset.push_to_hub(tags=cfg.tags, private=cfg.private)
@ -363,6 +368,40 @@ def replay(
log_control_info(robot, dt_s, fps=cfg.fps) log_control_info(robot, dt_s, fps=cfg.fps)
def _init_rerun(control_config: ControlConfig, session_name: str = "lerobot_control_loop") -> None:
"""Initializes the Rerun SDK for visualizing the control loop.
Args:
control_config: Configuration determining data display and robot type.
session_name: Rerun session name. Defaults to "lerobot_control_loop".
Raises:
ValueError: If viewer IP is missing for non-remote configurations with display enabled.
"""
if (control_config.display_data and not is_headless()) or (
control_config.display_data and isinstance(control_config, RemoteRobotConfig)
):
# Configure Rerun flush batch size default to 8KB if not set
batch_size = os.getenv("RERUN_FLUSH_NUM_BYTES", "8000")
os.environ["RERUN_FLUSH_NUM_BYTES"] = batch_size
# Initialize Rerun based on configuration
rr.init(session_name)
if isinstance(control_config, RemoteRobotConfig):
viewer_ip = control_config.viewer_ip
viewer_port = control_config.viewer_port
if not viewer_ip or not viewer_port:
raise ValueError(
"Viewer IP & Port are required for remote config. Set via config file/CLI or disable control_config.display_data."
)
logging.info(f"Connecting to viewer at {viewer_ip}:{viewer_port}")
rr.connect_tcp(f"{viewer_ip}:{viewer_port}")
else:
# Get memory limit for rerun viewer parameters
memory_limit = os.getenv("LEROBOT_RERUN_MEMORY_LIMIT", "10%")
rr.spawn(memory_limit=memory_limit)
@parser.wrap() @parser.wrap()
def control_robot(cfg: ControlPipelineConfig): def control_robot(cfg: ControlPipelineConfig):
init_logging() init_logging()
@ -370,17 +409,22 @@ def control_robot(cfg: ControlPipelineConfig):
robot = make_robot_from_config(cfg.robot) robot = make_robot_from_config(cfg.robot)
# TODO(Steven): Blueprint for fixed window size
if isinstance(cfg.control, CalibrateControlConfig): if isinstance(cfg.control, CalibrateControlConfig):
calibrate(robot, cfg.control) calibrate(robot, cfg.control)
elif isinstance(cfg.control, TeleoperateControlConfig): elif isinstance(cfg.control, TeleoperateControlConfig):
_init_rerun(control_config=cfg.control, session_name="lerobot_control_loop_teleop")
teleoperate(robot, cfg.control) teleoperate(robot, cfg.control)
elif isinstance(cfg.control, RecordControlConfig): elif isinstance(cfg.control, RecordControlConfig):
_init_rerun(control_config=cfg.control, session_name="lerobot_control_loop_record")
record(robot, cfg.control) record(robot, cfg.control)
elif isinstance(cfg.control, ReplayControlConfig): elif isinstance(cfg.control, ReplayControlConfig):
replay(robot, cfg.control) replay(robot, cfg.control)
elif isinstance(cfg.control, RemoteRobotConfig): elif isinstance(cfg.control, RemoteRobotConfig):
from lerobot.common.robot_devices.robots.lekiwi_remote import run_lekiwi from lerobot.common.robot_devices.robots.lekiwi_remote import run_lekiwi
_init_rerun(control_config=cfg.control, session_name="lerobot_control_loop_remote")
run_lekiwi(cfg.robot) run_lekiwi(cfg.robot)
if robot.is_connected: if robot.is_connected:

View File

@ -60,9 +60,9 @@ dependencies = [
"jsonlines>=4.0.0", "jsonlines>=4.0.0",
"numba>=0.59.0", "numba>=0.59.0",
"omegaconf>=2.3.0", "omegaconf>=2.3.0",
"opencv-python>=4.9.0", "opencv-python-headless>=4.9.0",
"packaging>=24.2", "packaging>=24.2",
"av>=12.0.5,<13.0.0", "av>=12.0.5",
"pymunk>=6.6.0", "pymunk>=6.6.0",
"pynput>=1.7.7", "pynput>=1.7.7",
"pyzmq>=26.2.1", "pyzmq>=26.2.1",

View File

@ -172,8 +172,7 @@ def test_record_and_replay_and_policy(tmp_path, request, robot_type, mock):
push_to_hub=False, push_to_hub=False,
# TODO(rcadene, aliberts): test video=True # TODO(rcadene, aliberts): test video=True
video=False, video=False,
# TODO(rcadene): display cameras through cv2 sometimes crashes on mac display_data=False,
display_cameras=False,
play_sounds=False, play_sounds=False,
) )
dataset = record(robot, rec_cfg) dataset = record(robot, rec_cfg)
@ -226,7 +225,7 @@ def test_record_and_replay_and_policy(tmp_path, request, robot_type, mock):
num_episodes=2, num_episodes=2,
push_to_hub=False, push_to_hub=False,
video=False, video=False,
display_cameras=False, display_data=False,
play_sounds=False, play_sounds=False,
num_image_writer_processes=num_image_writer_processes, num_image_writer_processes=num_image_writer_processes,
) )
@ -273,7 +272,7 @@ def test_resume_record(tmp_path, request, robot_type, mock):
episode_time_s=1, episode_time_s=1,
push_to_hub=False, push_to_hub=False,
video=False, video=False,
display_cameras=False, display_data=False,
play_sounds=False, play_sounds=False,
num_episodes=1, num_episodes=1,
) )
@ -330,7 +329,7 @@ def test_record_with_event_rerecord_episode(tmp_path, request, robot_type, mock)
num_episodes=1, num_episodes=1,
push_to_hub=False, push_to_hub=False,
video=False, video=False,
display_cameras=False, display_data=False,
play_sounds=False, play_sounds=False,
) )
dataset = record(robot, rec_cfg) dataset = record(robot, rec_cfg)
@ -380,7 +379,7 @@ def test_record_with_event_exit_early(tmp_path, request, robot_type, mock):
num_episodes=1, num_episodes=1,
push_to_hub=False, push_to_hub=False,
video=False, video=False,
display_cameras=False, display_data=False,
play_sounds=False, play_sounds=False,
) )
@ -433,7 +432,7 @@ def test_record_with_event_stop_recording(tmp_path, request, robot_type, mock, n
num_episodes=2, num_episodes=2,
push_to_hub=False, push_to_hub=False,
video=False, video=False,
display_cameras=False, display_data=False,
play_sounds=False, play_sounds=False,
num_image_writer_processes=num_image_writer_processes, num_image_writer_processes=num_image_writer_processes,
) )