Merge branch 'main' into feat/add-xvla

This commit is contained in:
Michel Aractingi
2025-11-28 10:54:42 +01:00
committed by GitHub
39 changed files with 1782 additions and 1817 deletions
-94
View File
@@ -1,94 +0,0 @@
#!/usr/bin/env python
# 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 threading
import time
from contextlib import ContextDecorator
class TimeBenchmark(ContextDecorator):
"""
Measures execution time using a context manager or decorator.
This class supports both context manager and decorator usage, and is thread-safe for multithreaded
environments.
Args:
print: If True, prints the elapsed time upon exiting the context or completing the function. Defaults
to False.
Examples:
Using as a context manager:
>>> benchmark = TimeBenchmark()
>>> with benchmark:
... time.sleep(1)
>>> print(f"Block took {benchmark.result:.4f} seconds")
Block took approximately 1.0000 seconds
Using with multithreading:
```python
import threading
benchmark = TimeBenchmark()
def context_manager_example():
with benchmark:
time.sleep(0.01)
print(f"Block took {benchmark.result_ms:.2f} milliseconds")
threads = []
for _ in range(3):
t1 = threading.Thread(target=context_manager_example)
threads.append(t1)
for t in threads:
t.start()
for t in threads:
t.join()
```
Expected output:
Block took approximately 10.00 milliseconds
Block took approximately 10.00 milliseconds
Block took approximately 10.00 milliseconds
"""
def __init__(self, print=False):
self.local = threading.local()
self.print_time = print
def __enter__(self):
self.local.start_time = time.perf_counter()
return self
def __exit__(self, *exc):
self.local.end_time = time.perf_counter()
self.local.elapsed_time = self.local.end_time - self.local.start_time
if self.print_time:
print(f"Elapsed time: {self.local.elapsed_time:.4f} seconds")
return False
@property
def result(self):
return getattr(self.local, "elapsed_time", None)
@property
def result_ms(self):
return self.result * 1e3
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python
# 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.
"""Capture video feed from a camera as raw images."""
import argparse
import datetime as dt
import os
import time
from pathlib import Path
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, duration: int):
rr.init("lerobot_capture_camera_feed")
rr.spawn(memory_limit=RERUN_MEMORY_LIMIT)
now = dt.datetime.now()
capture_dir = output_dir / f"{now:%Y-%m-%d}" / f"{now:%H-%M-%S}"
if not capture_dir.exists():
capture_dir.mkdir(parents=True, exist_ok=True)
# Opens the default webcam
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Error: Could not open video stream.")
return
cap.set(cv2.CAP_PROP_FPS, fps)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
frame_index = 0
start_time = time.time()
while time.time() - start_time < duration:
ret, frame = cap.read()
if not ret:
print("Error: Could not read frame.")
break
rr.log("video/stream", rr.Image(frame), static=True)
cv2.imwrite(str(capture_dir / f"frame_{frame_index:06d}.png"), frame)
frame_index += 1
# Release the capture
cap.release()
# 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__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--output-dir",
type=Path,
default=Path("outputs/cam_capture/"),
help="Directory where the capture images are written. A subfolder named with the current date & time will be created inside it for each capture.",
)
parser.add_argument(
"--fps",
type=int,
default=30,
help="Frames Per Second of the capture.",
)
parser.add_argument(
"--width",
type=int,
default=1280,
help="Width of the captured images.",
)
parser.add_argument(
"--height",
type=int,
default=720,
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()
display_and_save_video_stream(**vars(args))
+28 -33
View File
@@ -21,11 +21,13 @@ See the provided README.md or run `python benchmark/video/run_video_benchmark.py
import argparse import argparse
import datetime as dt import datetime as dt
import itertools
import random import random
import shutil import shutil
from collections import OrderedDict from collections import OrderedDict
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path from pathlib import Path
from threading import Lock
import einops import einops
import numpy as np import numpy as np
@@ -35,13 +37,13 @@ import torch
from skimage.metrics import mean_squared_error, peak_signal_noise_ratio, structural_similarity from skimage.metrics import mean_squared_error, peak_signal_noise_ratio, structural_similarity
from tqdm import tqdm from tqdm import tqdm
from benchmarks.video.benchmark import TimeBenchmark
from lerobot.datasets.lerobot_dataset import LeRobotDataset from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.video_utils import ( from lerobot.datasets.video_utils import (
decode_video_frames_torchvision, decode_video_frames,
encode_video_frames, encode_video_frames,
) )
from lerobot.utils.constants import OBS_IMAGE from lerobot.utils.constants import OBS_IMAGE
from lerobot.utils.utils import TimerManager
BASE_ENCODING = OrderedDict( BASE_ENCODING = OrderedDict(
[ [
@@ -86,7 +88,7 @@ def load_original_frames(imgs_dir: Path, timestamps: list[float], fps: int) -> t
frames = [] frames = []
for ts in timestamps: for ts in timestamps:
idx = int(ts * fps) idx = int(ts * fps)
frame = PIL.Image.open(imgs_dir / f"frame_{idx:06d}.png") frame = PIL.Image.open(imgs_dir / f"frame-{idx:06d}.png")
frame = torch.from_numpy(np.array(frame)) frame = torch.from_numpy(np.array(frame))
frame = frame.type(torch.float32) / 255 frame = frame.type(torch.float32) / 255
frame = einops.rearrange(frame, "h w c -> c h w") frame = einops.rearrange(frame, "h w c -> c h w")
@@ -97,21 +99,21 @@ def load_original_frames(imgs_dir: Path, timestamps: list[float], fps: int) -> t
def save_decoded_frames( def save_decoded_frames(
imgs_dir: Path, save_dir: Path, frames: torch.Tensor, timestamps: list[float], fps: int imgs_dir: Path, save_dir: Path, frames: torch.Tensor, timestamps: list[float], fps: int
) -> None: ) -> None:
if save_dir.exists() and len(list(save_dir.glob("frame_*.png"))) == len(timestamps): if save_dir.exists() and len(list(save_dir.glob("frame-*.png"))) == len(timestamps):
return return
save_dir.mkdir(parents=True, exist_ok=True) save_dir.mkdir(parents=True, exist_ok=True)
for i, ts in enumerate(timestamps): for i, ts in enumerate(timestamps):
idx = int(ts * fps) idx = int(ts * fps)
frame_hwc = (frames[i].permute((1, 2, 0)) * 255).type(torch.uint8).cpu().numpy() frame_hwc = (frames[i].permute((1, 2, 0)) * 255).type(torch.uint8).cpu().numpy()
PIL.Image.fromarray(frame_hwc).save(save_dir / f"frame_{idx:06d}_decoded.png") PIL.Image.fromarray(frame_hwc).save(save_dir / f"frame-{idx:06d}_decoded.png")
shutil.copyfile(imgs_dir / f"frame_{idx:06d}.png", save_dir / f"frame_{idx:06d}_original.png") shutil.copyfile(imgs_dir / f"frame-{idx:06d}.png", save_dir / f"frame-{idx:06d}_original.png")
def save_first_episode(imgs_dir: Path, dataset: LeRobotDataset) -> None: def save_first_episode(imgs_dir: Path, dataset: LeRobotDataset) -> None:
episode_index = 0 episode_index = 0
ep_num_images = dataset.meta.episodes["length"][episode_index] ep_num_images = dataset.meta.episodes["length"][episode_index]
if imgs_dir.exists() and len(list(imgs_dir.glob("frame_*.png"))) == ep_num_images: if imgs_dir.exists() and len(list(imgs_dir.glob("frame-*.png"))) == ep_num_images:
return return
imgs_dir.mkdir(parents=True, exist_ok=True) imgs_dir.mkdir(parents=True, exist_ok=True)
@@ -125,7 +127,7 @@ def save_first_episode(imgs_dir: Path, dataset: LeRobotDataset) -> None:
tqdm(imgs_dataset, desc=f"saving {dataset.repo_id} first episode images", leave=False) tqdm(imgs_dataset, desc=f"saving {dataset.repo_id} first episode images", leave=False)
): ):
img = item[img_keys[0]] img = item[img_keys[0]]
img.save(str(imgs_dir / f"frame_{i:06d}.png"), quality=100) img.save(str(imgs_dir / f"frame-{i:06d}.png"), quality=100)
if i >= ep_num_images - 1: if i >= ep_num_images - 1:
break break
@@ -149,18 +151,6 @@ def sample_timestamps(timestamps_mode: str, ep_num_images: int, fps: int) -> lis
return [idx / fps for idx in frame_indexes] return [idx / fps for idx in frame_indexes]
def decode_video_frames(
video_path: str,
timestamps: list[float],
tolerance_s: float,
backend: str,
) -> torch.Tensor:
if backend in ["pyav", "video_reader"]:
return decode_video_frames_torchvision(video_path, timestamps, tolerance_s, backend)
else:
raise NotImplementedError(backend)
def benchmark_decoding( def benchmark_decoding(
imgs_dir: Path, imgs_dir: Path,
video_path: Path, video_path: Path,
@@ -172,8 +162,8 @@ def benchmark_decoding(
num_workers: int = 4, num_workers: int = 4,
save_frames: bool = False, save_frames: bool = False,
) -> dict: ) -> dict:
def process_sample(sample: int): def process_sample(sample: int, lock: Lock):
time_benchmark = TimeBenchmark() time_benchmark = TimerManager(log=False)
timestamps = sample_timestamps(timestamps_mode, ep_num_images, fps) timestamps = sample_timestamps(timestamps_mode, ep_num_images, fps)
num_frames = len(timestamps) num_frames = len(timestamps)
result = { result = {
@@ -182,13 +172,13 @@ def benchmark_decoding(
"mse_values": [], "mse_values": [],
} }
with time_benchmark: with time_benchmark, lock:
frames = decode_video_frames(video_path, timestamps=timestamps, tolerance_s=5e-1, backend=backend) frames = decode_video_frames(video_path, timestamps=timestamps, tolerance_s=5e-1, backend=backend)
result["load_time_video_ms"] = time_benchmark.result_ms / num_frames result["load_time_video_ms"] = (time_benchmark.last * 1000) / num_frames
with time_benchmark: with time_benchmark:
original_frames = load_original_frames(imgs_dir, timestamps, fps) original_frames = load_original_frames(imgs_dir, timestamps, fps)
result["load_time_images_ms"] = time_benchmark.result_ms / num_frames result["load_time_images_ms"] = (time_benchmark.last * 1000) / num_frames
frames_np, original_frames_np = frames.numpy(), original_frames.numpy() frames_np, original_frames_np = frames.numpy(), original_frames.numpy()
for i in range(num_frames): for i in range(num_frames):
@@ -215,8 +205,10 @@ def benchmark_decoding(
# A sample is a single set of decoded frames specified by timestamps_mode (e.g. a single frame, 2 frames, etc.). # A sample is a single set of decoded frames specified by timestamps_mode (e.g. a single frame, 2 frames, etc.).
# For each sample, we record metrics (loading time and quality metrics) which are then averaged over all samples. # For each sample, we record metrics (loading time and quality metrics) which are then averaged over all samples.
# As these samples are independent, we run them in parallel threads to speed up the benchmark. # As these samples are independent, we run them in parallel threads to speed up the benchmark.
# Use a single shared lock for all worker threads
shared_lock = Lock()
with ThreadPoolExecutor(max_workers=num_workers) as executor: with ThreadPoolExecutor(max_workers=num_workers) as executor:
futures = [executor.submit(process_sample, i) for i in range(num_samples)] futures = [executor.submit(process_sample, i, shared_lock) for i in range(num_samples)]
for future in tqdm(as_completed(futures), total=num_samples, desc="samples", leave=False): for future in tqdm(as_completed(futures), total=num_samples, desc="samples", leave=False):
result = future.result() result = future.result()
load_times_video_ms.append(result["load_time_video_ms"]) load_times_video_ms.append(result["load_time_video_ms"])
@@ -358,11 +350,14 @@ def main(
imgs_dir = output_dir / "images" / dataset.repo_id.replace("/", "_") imgs_dir = output_dir / "images" / dataset.repo_id.replace("/", "_")
# We only use the first episode # We only use the first episode
save_first_episode(imgs_dir, dataset) save_first_episode(imgs_dir, dataset)
for key, values in tqdm(encoding_benchmarks.items(), desc="encodings (g, crf)", leave=False): for duet in [
for value in tqdm(values, desc=f"encodings ({key})", leave=False): dict(zip(encoding_benchmarks.keys(), unique_combination, strict=False))
for unique_combination in itertools.product(*encoding_benchmarks.values())
]:
encoding_cfg = BASE_ENCODING.copy() encoding_cfg = BASE_ENCODING.copy()
encoding_cfg["vcodec"] = video_codec encoding_cfg["vcodec"] = video_codec
encoding_cfg["pix_fmt"] = pixel_format encoding_cfg["pix_fmt"] = pixel_format
for key, value in duet.items():
encoding_cfg[key] = value encoding_cfg[key] = value
args_path = Path("_".join(str(value) for value in encoding_cfg.values())) args_path = Path("_".join(str(value) for value in encoding_cfg.values()))
video_path = output_dir / "videos" / args_path / f"{repo_id.replace('/', '_')}.mp4" video_path = output_dir / "videos" / args_path / f"{repo_id.replace('/', '_')}.mp4"
@@ -409,9 +404,9 @@ if __name__ == "__main__":
nargs="*", nargs="*",
default=[ default=[
"lerobot/pusht_image", "lerobot/pusht_image",
"aliberts/aloha_mobile_shrimp_image", "lerobot/aloha_mobile_shrimp_image",
"aliberts/paris_street", "lerobot/paris_street",
"aliberts/kitchen", "lerobot/kitchen",
], ],
help="Datasets repo-ids to test against. First episodes only are used. Must be images.", help="Datasets repo-ids to test against. First episodes only are used. Must be images.",
) )
@@ -419,7 +414,7 @@ if __name__ == "__main__":
"--vcodec", "--vcodec",
type=str, type=str,
nargs="*", nargs="*",
default=["libx264", "hevc", "libsvtav1"], default=["h264", "hevc", "libsvtav1"],
help="Video codecs to be tested", help="Video codecs to be tested",
) )
parser.add_argument( parser.add_argument(
@@ -468,7 +463,7 @@ if __name__ == "__main__":
"--backends", "--backends",
type=str, type=str,
nargs="*", nargs="*",
default=["pyav", "video_reader"], default=["torchcodec", "pyav"],
help="Torchvision decoding backend to be tested.", help="Torchvision decoding backend to be tested.",
) )
parser.add_argument( parser.add_argument(
+1 -1
View File
@@ -196,7 +196,7 @@ client_cfg = RobotClientConfig(
server_address="localhost:8080", server_address="localhost:8080",
policy_device="mps", policy_device="mps",
policy_type="smolvla", policy_type="smolvla",
pretrained_name_or_path="fracapuano/smolvla_async", pretrained_name_or_path="<user>/smolvla_async",
chunk_size_threshold=0.5, chunk_size_threshold=0.5,
actions_per_chunk=50, # make sure this is less than the max actions of the policy actions_per_chunk=50, # make sure this is less than the max actions of the policy
) )
+2 -2
View File
@@ -139,7 +139,7 @@ from lerobot.teleoperators import ( # noqa: F401
make_teleoperator_from_config, make_teleoperator_from_config,
so101_leader, so101_leader,
) )
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import init_logging from lerobot.utils.utils import init_logging
from lerobot.envs.factory import make_env from lerobot.envs.factory import make_env
@@ -196,7 +196,7 @@ def teleop_loop(teleop: Teleoperator, env: gym.Env, fps: int):
obs, info = env.reset() obs, info = env.reset()
dt_s = time.perf_counter() - loop_start dt_s = time.perf_counter() - loop_start
busy_wait(1 / fps - dt_s) precise_sleep(1 / fps - dt_s)
loop_s = time.perf_counter() - loop_start loop_s = time.perf_counter() - loop_start
print(f"\ntime: {loop_s * 1e3:.2f}ms ({1 / loop_s:.0f} Hz)") print(f"\ntime: {loop_s * 1e3:.2f}ms ({1 / loop_s:.0f} Hz)")
+2 -2
View File
@@ -393,7 +393,7 @@ import time
from lerobot.datasets.lerobot_dataset import LeRobotDataset from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import log_say from lerobot.utils.utils import log_say
episode_idx = 0 episode_idx = 0
@@ -415,7 +415,7 @@ for idx in range(dataset.num_frames):
} }
robot.send_action(action) robot.send_action(action)
busy_wait(1.0 / dataset.fps - (time.perf_counter() - t0)) precise_sleep(1.0 / dataset.fps - (time.perf_counter() - t0))
robot.disconnect() robot.disconnect()
``` ```
+2 -2
View File
@@ -45,7 +45,7 @@ from lerobot.robots import ( # noqa: F401
so101_follower, so101_follower,
) )
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import ( from lerobot.utils.utils import (
init_logging, init_logging,
log_say, log_say,
@@ -97,7 +97,7 @@ def replay(cfg: ReplayConfig):
robot.send_action(action) robot.send_action(action)
dt_s = time.perf_counter() - start_episode_t dt_s = time.perf_counter() - start_episode_t
busy_wait(1 / dataset.fps - dt_s) precise_sleep(1 / dataset.fps - dt_s)
robot.disconnect() robot.disconnect()
+6 -1
View File
@@ -34,6 +34,8 @@ from huggingface_hub import HfApi
import lerobot import lerobot
from lerobot.datasets.lerobot_dataset import LeRobotDataset, LeRobotDatasetMetadata from lerobot.datasets.lerobot_dataset import LeRobotDataset, LeRobotDatasetMetadata
def main():
# We ported a number of existing datasets ourselves, use this to see the list: # We ported a number of existing datasets ourselves, use this to see the list:
print("List of available datasets:") print("List of available datasets:")
pprint(lerobot.available_datasets) pprint(lerobot.available_datasets)
@@ -132,7 +134,6 @@ print(f"\n{dataset[0][camera_key].shape=}") # (4, c, h, w)
print(f"{dataset[0]['observation.state'].shape=}") # (6, c) print(f"{dataset[0]['observation.state'].shape=}") # (6, c)
print(f"{dataset[0]['action'].shape=}\n") # (64, c) print(f"{dataset[0]['action'].shape=}\n") # (64, c)
if __name__ == "__main__":
dataloader = torch.utils.data.DataLoader( dataloader = torch.utils.data.DataLoader(
dataset, dataset,
num_workers=4, num_workers=4,
@@ -144,3 +145,7 @@ if __name__ == "__main__":
print(f"{batch['observation.state'].shape=}") # (32, 6, 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
if __name__ == "__main__":
main()
+6
View File
@@ -33,6 +33,8 @@ TASK_DESCRIPTION = "My task description"
HF_MODEL_ID = "<hf_username>/<model_repo_id>" HF_MODEL_ID = "<hf_username>/<model_repo_id>"
HF_DATASET_ID = "<hf_username>/<eval_dataset_repo_id>" HF_DATASET_ID = "<hf_username>/<eval_dataset_repo_id>"
def main():
# Create the robot configuration & robot # Create the robot configuration & robot
robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi") robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi")
@@ -136,3 +138,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+6
View File
@@ -34,6 +34,8 @@ RESET_TIME_SEC = 10
TASK_DESCRIPTION = "My task description" TASK_DESCRIPTION = "My task description"
HF_REPO_ID = "<hf_username>/<dataset_repo_id>" HF_REPO_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Create the robot and teleoperator configurations # Create the robot and teleoperator configurations
robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi") robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi")
leader_arm_config = SO100LeaderConfig(port="/dev/tty.usbmodem585A0077581", id="my_awesome_leader_arm") leader_arm_config = SO100LeaderConfig(port="/dev/tty.usbmodem585A0077581", id="my_awesome_leader_arm")
@@ -133,3 +135,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+8 -2
View File
@@ -20,11 +20,13 @@ from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.robots.lekiwi.config_lekiwi import LeKiwiClientConfig from lerobot.robots.lekiwi.config_lekiwi import LeKiwiClientConfig
from lerobot.robots.lekiwi.lekiwi_client import LeKiwiClient from lerobot.robots.lekiwi.lekiwi_client import LeKiwiClient
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import log_say from lerobot.utils.utils import log_say
EPISODE_IDX = 0 EPISODE_IDX = 0
def main():
# Initialize the robot config # Initialize the robot config
robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi") robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="lekiwi")
@@ -56,6 +58,10 @@ for idx in range(len(episode_frames)):
# Send action to robot # Send action to robot
_ = robot.send_action(action) _ = robot.send_action(action)
busy_wait(max(1.0 / dataset.fps - (time.perf_counter() - t0), 0.0)) precise_sleep(max(1.0 / dataset.fps - (time.perf_counter() - t0), 0.0))
robot.disconnect() robot.disconnect()
if __name__ == "__main__":
main()
+8 -2
View File
@@ -19,11 +19,13 @@ import time
from lerobot.robots.lekiwi import LeKiwiClient, LeKiwiClientConfig from lerobot.robots.lekiwi import LeKiwiClient, LeKiwiClientConfig
from lerobot.teleoperators.keyboard.teleop_keyboard import KeyboardTeleop, KeyboardTeleopConfig from lerobot.teleoperators.keyboard.teleop_keyboard import KeyboardTeleop, KeyboardTeleopConfig
from lerobot.teleoperators.so100_leader import SO100Leader, SO100LeaderConfig from lerobot.teleoperators.so100_leader import SO100Leader, SO100LeaderConfig
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
FPS = 30 FPS = 30
def main():
# Create the robot and teleoperator configurations # Create the robot and teleoperator configurations
robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="my_lekiwi") robot_config = LeKiwiClientConfig(remote_ip="172.18.134.136", id="my_lekiwi")
teleop_arm_config = SO100LeaderConfig(port="/dev/tty.usbmodem585A0077581", id="my_awesome_leader_arm") teleop_arm_config = SO100LeaderConfig(port="/dev/tty.usbmodem585A0077581", id="my_awesome_leader_arm")
@@ -69,4 +71,8 @@ while True:
# Visualize # Visualize
log_rerun_data(observation=observation, action=action) log_rerun_data(observation=observation, action=action)
busy_wait(max(1.0 / FPS - (time.perf_counter() - t0), 0.0)) precise_sleep(max(1.0 / FPS - (time.perf_counter() - t0), 0.0))
if __name__ == "__main__":
main()
+9 -1
View File
@@ -52,6 +52,8 @@ TASK_DESCRIPTION = "My task description"
HF_MODEL_ID = "<hf_username>/<model_repo_id>" HF_MODEL_ID = "<hf_username>/<model_repo_id>"
HF_DATASET_ID = "<hf_username>/<dataset_repo_id>" HF_DATASET_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Create the robot configuration & robot # Create the robot configuration & robot
camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)} camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)}
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
@@ -89,7 +91,9 @@ robot_ee_to_joints_processor = RobotProcessorPipeline[tuple[RobotAction, RobotOb
# Build pipeline to convert joints observation to EE observation # Build pipeline to convert joints observation to EE observation
robot_joints_to_ee_pose_processor = RobotProcessorPipeline[RobotObservation, RobotObservation]( robot_joints_to_ee_pose_processor = RobotProcessorPipeline[RobotObservation, RobotObservation](
steps=[ steps=[
ForwardKinematicsJointsToEE(kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())) ForwardKinematicsJointsToEE(
kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())
)
], ],
to_transition=observation_to_transition, to_transition=observation_to_transition,
to_output=transition_to_observation, to_output=transition_to_observation,
@@ -197,3 +201,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+12 -3
View File
@@ -50,6 +50,8 @@ RESET_TIME_SEC = 30
TASK_DESCRIPTION = "My task description" TASK_DESCRIPTION = "My task description"
HF_REPO_ID = "<hf_username>/<dataset_repo_id>" HF_REPO_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Create the robot and teleoperator configurations # Create the robot and teleoperator configurations
camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)} camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)}
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
@@ -72,7 +74,9 @@ kinematics_solver = RobotKinematics(
) )
# Build pipeline to convert phone action to EE action # Build pipeline to convert phone action to EE action
phone_to_robot_ee_pose_processor = RobotProcessorPipeline[tuple[RobotAction, RobotObservation], RobotAction]( phone_to_robot_ee_pose_processor = RobotProcessorPipeline[
tuple[RobotAction, RobotObservation], RobotAction
](
steps=[ steps=[
MapPhoneActionToRobotAction(platform=teleop_config.phone_os), MapPhoneActionToRobotAction(platform=teleop_config.phone_os),
EEReferenceAndDelta( EEReferenceAndDelta(
@@ -107,7 +111,9 @@ robot_ee_to_joints_processor = RobotProcessorPipeline[tuple[RobotAction, RobotOb
# Build pipeline to convert joint observation to EE observation # Build pipeline to convert joint observation to EE observation
robot_joints_to_ee_pose = RobotProcessorPipeline[RobotObservation, RobotObservation]( robot_joints_to_ee_pose = RobotProcessorPipeline[RobotObservation, RobotObservation](
steps=[ steps=[
ForwardKinematicsJointsToEE(kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())) ForwardKinematicsJointsToEE(
kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())
)
], ],
to_transition=observation_to_transition, to_transition=observation_to_transition,
to_output=transition_to_observation, to_output=transition_to_observation,
@@ -147,7 +153,6 @@ init_rerun(session_name="phone_so100_record")
if not robot.is_connected or not phone.is_connected: if not robot.is_connected or not phone.is_connected:
raise ValueError("Robot or teleop is not connected!") raise ValueError("Robot or teleop is not connected!")
print("Starting record loop. Move your phone to teleoperate the robot...") print("Starting record loop. Move your phone to teleoperate the robot...")
episode_idx = 0 episode_idx = 0
while episode_idx < NUM_EPISODES and not events["stop_recording"]: while episode_idx < NUM_EPISODES and not events["stop_recording"]:
@@ -203,3 +208,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+8 -2
View File
@@ -29,12 +29,14 @@ from lerobot.robots.so100_follower.robot_kinematic_processor import (
) )
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import log_say from lerobot.utils.utils import log_say
EPISODE_IDX = 0 EPISODE_IDX = 0
HF_REPO_ID = "<hf_username>/<dataset_repo_id>" HF_REPO_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Initialize the robot config # Initialize the robot config
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True
@@ -94,7 +96,11 @@ for idx in range(len(episode_frames)):
# Send action to robot # Send action to robot
_ = robot.send_action(joint_action) _ = robot.send_action(joint_action)
busy_wait(1.0 / dataset.fps - (time.perf_counter() - t0)) precise_sleep(1.0 / dataset.fps - (time.perf_counter() - t0))
# Clean up # Clean up
robot.disconnect() robot.disconnect()
if __name__ == "__main__":
main()
+11 -3
View File
@@ -32,11 +32,13 @@ from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.teleoperators.phone.config_phone import PhoneConfig, PhoneOS from lerobot.teleoperators.phone.config_phone import PhoneConfig, PhoneOS
from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction from lerobot.teleoperators.phone.phone_processor import MapPhoneActionToRobotAction
from lerobot.teleoperators.phone.teleop_phone import Phone from lerobot.teleoperators.phone.teleop_phone import Phone
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
FPS = 30 FPS = 30
def main():
# Initialize the robot and teleoperator # Initialize the robot and teleoperator
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True
@@ -55,7 +57,9 @@ kinematics_solver = RobotKinematics(
) )
# Build pipeline to convert phone action to ee pose action to joint action # Build pipeline to convert phone action to ee pose action to joint action
phone_to_robot_joints_processor = RobotProcessorPipeline[tuple[RobotAction, RobotObservation], RobotAction]( phone_to_robot_joints_processor = RobotProcessorPipeline[
tuple[RobotAction, RobotObservation], RobotAction
](
steps=[ steps=[
MapPhoneActionToRobotAction(platform=teleop_config.phone_os), MapPhoneActionToRobotAction(platform=teleop_config.phone_os),
EEReferenceAndDelta( EEReferenceAndDelta(
@@ -110,4 +114,8 @@ while True:
# Visualize # Visualize
log_rerun_data(observation=phone_obs, action=joint_action) log_rerun_data(observation=phone_obs, action=joint_action)
busy_wait(max(1.0 / FPS - (time.perf_counter() - t0), 0.0)) precise_sleep(max(1.0 / FPS - (time.perf_counter() - t0), 0.0))
if __name__ == "__main__":
main()
+9 -2
View File
@@ -52,6 +52,8 @@ TASK_DESCRIPTION = "My task description"
HF_MODEL_ID = "<hf_username>/<model_repo_id>" HF_MODEL_ID = "<hf_username>/<model_repo_id>"
HF_DATASET_ID = "<hf_username>/<dataset_repo_id>" HF_DATASET_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Create the robot configuration & robot # Create the robot configuration & robot
camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)} camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)}
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
@@ -89,13 +91,14 @@ robot_ee_to_joints_processor = RobotProcessorPipeline[tuple[RobotAction, RobotOb
# Build pipeline to convert joints observation to EE observation # Build pipeline to convert joints observation to EE observation
robot_joints_to_ee_pose_processor = RobotProcessorPipeline[RobotObservation, RobotObservation]( robot_joints_to_ee_pose_processor = RobotProcessorPipeline[RobotObservation, RobotObservation](
steps=[ steps=[
ForwardKinematicsJointsToEE(kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())) ForwardKinematicsJointsToEE(
kinematics=kinematics_solver, motor_names=list(robot.bus.motors.keys())
)
], ],
to_transition=observation_to_transition, to_transition=observation_to_transition,
to_output=transition_to_observation, to_output=transition_to_observation,
) )
# Create the dataset # Create the dataset
dataset = LeRobotDataset.create( dataset = LeRobotDataset.create(
repo_id=HF_DATASET_ID, repo_id=HF_DATASET_ID,
@@ -198,3 +201,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+10 -2
View File
@@ -48,10 +48,15 @@ RESET_TIME_SEC = 30
TASK_DESCRIPTION = "My task description" TASK_DESCRIPTION = "My task description"
HF_REPO_ID = "<hf_username>/<dataset_repo_id>" HF_REPO_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Create the robot and teleoperator configurations # Create the robot and teleoperator configurations
camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)} camera_config = {"front": OpenCVCameraConfig(index_or_path=0, width=640, height=480, fps=FPS)}
follower_config = SO100FollowerConfig( follower_config = SO100FollowerConfig(
port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", cameras=camera_config, use_degrees=True port="/dev/tty.usbmodem5A460814411",
id="my_awesome_follower_arm",
cameras=camera_config,
use_degrees=True,
) )
leader_config = SO100LeaderConfig(port="/dev/tty.usbmodem5A460819811", id="my_awesome_leader_arm") leader_config = SO100LeaderConfig(port="/dev/tty.usbmodem5A460819811", id="my_awesome_leader_arm")
@@ -135,7 +140,6 @@ dataset = LeRobotDataset.create(
image_writer_threads=4, image_writer_threads=4,
) )
# Connect the robot and teleoperator # Connect the robot and teleoperator
leader.connect() leader.connect()
follower.connect() follower.connect()
@@ -202,3 +206,7 @@ listener.stop()
dataset.finalize() dataset.finalize()
dataset.push_to_hub() dataset.push_to_hub()
if __name__ == "__main__":
main()
+8 -2
View File
@@ -30,12 +30,14 @@ from lerobot.robots.so100_follower.robot_kinematic_processor import (
) )
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import log_say from lerobot.utils.utils import log_say
EPISODE_IDX = 0 EPISODE_IDX = 0
HF_REPO_ID = "<hf_username>/<dataset_repo_id>" HF_REPO_ID = "<hf_username>/<dataset_repo_id>"
def main():
# Initialize the robot config # Initialize the robot config
robot_config = SO100FollowerConfig( robot_config = SO100FollowerConfig(
port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True
@@ -95,7 +97,11 @@ for idx in range(len(episode_frames)):
# Send action to robot # Send action to robot
_ = robot.send_action(joint_action) _ = robot.send_action(joint_action)
busy_wait(1.0 / dataset.fps - (time.perf_counter() - t0)) precise_sleep(1.0 / dataset.fps - (time.perf_counter() - t0))
# Clean up # Clean up
robot.disconnect() robot.disconnect()
if __name__ == "__main__":
main()
+8 -2
View File
@@ -32,11 +32,13 @@ from lerobot.robots.so100_follower.robot_kinematic_processor import (
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
from lerobot.teleoperators.so100_leader.config_so100_leader import SO100LeaderConfig from lerobot.teleoperators.so100_leader.config_so100_leader import SO100LeaderConfig
from lerobot.teleoperators.so100_leader.so100_leader import SO100Leader from lerobot.teleoperators.so100_leader.so100_leader import SO100Leader
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
FPS = 30 FPS = 30
def main():
# Initialize the robot and teleoperator config # Initialize the robot and teleoperator config
follower_config = SO100FollowerConfig( follower_config = SO100FollowerConfig(
port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True port="/dev/tty.usbmodem5A460814411", id="my_awesome_follower_arm", use_degrees=True
@@ -118,4 +120,8 @@ while True:
# Visualize # Visualize
log_rerun_data(observation=leader_ee_act, action=follower_joints_act) log_rerun_data(observation=leader_ee_act, action=follower_joints_act)
busy_wait(max(1.0 / FPS - (time.perf_counter() - t0), 0.0)) precise_sleep(max(1.0 / FPS - (time.perf_counter() - t0), 0.0))
if __name__ == "__main__":
main()
+10 -4
View File
@@ -19,6 +19,7 @@ def make_delta_timestamps(delta_indices: list[int] | None, fps: int) -> list[flo
return [i / fps for i in delta_indices] return [i / fps for i in delta_indices]
def main():
output_directory = Path("outputs/robot_learning_tutorial/act") output_directory = Path("outputs/robot_learning_tutorial/act")
output_directory.mkdir(parents=True, exist_ok=True) output_directory.mkdir(parents=True, exist_ok=True)
@@ -48,7 +49,8 @@ delta_timestamps = {
# add image features if they are present # add image features if they are present
delta_timestamps |= { delta_timestamps |= {
k: make_delta_timestamps(cfg.observation_delta_indices, dataset_metadata.fps) for k in cfg.image_features k: make_delta_timestamps(cfg.observation_delta_indices, dataset_metadata.fps)
for k in cfg.image_features
} }
# Instantiate the dataset # Instantiate the dataset
@@ -93,6 +95,10 @@ preprocessor.save_pretrained(output_directory)
postprocessor.save_pretrained(output_directory) postprocessor.save_pretrained(output_directory)
# Save all assets to the Hub # Save all assets to the Hub
policy.push_to_hub("fracapuano/robot_learning_tutorial_act") policy.push_to_hub("<user>/robot_learning_tutorial_act")
preprocessor.push_to_hub("fracapuano/robot_learning_tutorial_act") preprocessor.push_to_hub("<user>/robot_learning_tutorial_act")
postprocessor.push_to_hub("fracapuano/robot_learning_tutorial_act") postprocessor.push_to_hub("<user>/robot_learning_tutorial_act")
if __name__ == "__main__":
main()
+10 -4
View File
@@ -8,8 +8,13 @@ from lerobot.policies.utils import build_inference_frame, make_robot_action
from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
def main():
device = torch.device("mps") # or "cuda" or "cpu" device = torch.device("mps") # or "cuda" or "cpu"
model_id = "fracapuano/robot_learning_tutorial_act" model_id = "<user>/robot_learning_tutorial_act"
model = ACTPolicy.from_pretrained(model_id) model = ACTPolicy.from_pretrained(model_id)
dataset_id = "lerobot/svla_so101_pickplace" dataset_id = "lerobot/svla_so101_pickplace"
@@ -23,9 +28,6 @@ follower_port = ... # something like "/dev/tty.usbmodem58760431631"
# # the robot ids are used the load the right calibration files # # the robot ids are used the load the right calibration files
follower_id = ... # something like "follower_so100" follower_id = ... # something like "follower_so100"
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
# Robot and environment configuration # Robot and environment configuration
# Camera keys must match the name and resolutions of the ones used for training! # Camera keys must match the name and resolutions of the ones used for training!
# You can check the camera keys expected by a model in the info.json card on the model card on the Hub # You can check the camera keys expected by a model in the info.json card on the model card on the Hub
@@ -55,3 +57,7 @@ for _ in range(MAX_EPISODES):
robot.send_action(action) robot.send_action(action)
print("Episode finished! Starting new episode...") print("Episode finished! Starting new episode...")
if __name__ == "__main__":
main()
@@ -1,6 +1,8 @@
from lerobot.async_inference.configs import PolicyServerConfig from lerobot.async_inference.configs import PolicyServerConfig
from lerobot.async_inference.policy_server import serve from lerobot.async_inference.policy_server import serve
def main():
host = ... # something like "127.0.0.1" if you're exposing to localhost host = ... # something like "127.0.0.1" if you're exposing to localhost
port = ... # something like 8080 port = ... # something like 8080
@@ -9,3 +11,7 @@ config = PolicyServerConfig(
port=port, port=port,
) )
serve(config) serve(config)
if __name__ == "__main__":
main()
+7 -1
View File
@@ -6,6 +6,8 @@ from lerobot.async_inference.robot_client import RobotClient
from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig from lerobot.cameras.opencv.configuration_opencv import OpenCVCameraConfig
from lerobot.robots.so100_follower import SO100FollowerConfig from lerobot.robots.so100_follower import SO100FollowerConfig
def main():
# these cameras must match the ones expected by the policy - find your cameras with lerobot-find-cameras # these cameras must match the ones expected by the policy - find your cameras with lerobot-find-cameras
# check the config.json on the Hub for the policy you are using to see the expected camera specs # check the config.json on the Hub for the policy you are using to see the expected camera specs
camera_cfg = { camera_cfg = {
@@ -29,7 +31,7 @@ client_cfg = RobotClientConfig(
server_address=server_address, server_address=server_address,
policy_device="mps", policy_device="mps",
policy_type="act", policy_type="act",
pretrained_name_or_path="fracapuano/robot_learning_tutorial_act", pretrained_name_or_path="<user>/robot_learning_tutorial_act",
chunk_size_threshold=0.5, # g chunk_size_threshold=0.5, # g
actions_per_chunk=50, # make sure this is less than the max actions of the policy actions_per_chunk=50, # make sure this is less than the max actions of the policy
) )
@@ -53,3 +55,7 @@ if client.start():
action_receiver_thread.join() action_receiver_thread.join()
# (Optionally) plot the action queue size # (Optionally) plot the action queue size
visualize_action_queue_size(client.action_queue_size) visualize_action_queue_size(client.action_queue_size)
if __name__ == "__main__":
main()
@@ -19,6 +19,7 @@ def make_delta_timestamps(delta_indices: list[int] | None, fps: int) -> list[flo
return [i / fps for i in delta_indices] return [i / fps for i in delta_indices]
def main():
output_directory = Path("outputs/robot_learning_tutorial/diffusion") output_directory = Path("outputs/robot_learning_tutorial/diffusion")
output_directory.mkdir(parents=True, exist_ok=True) output_directory.mkdir(parents=True, exist_ok=True)
@@ -49,7 +50,8 @@ delta_timestamps = {
# add image features if they are present # add image features if they are present
delta_timestamps |= { delta_timestamps |= {
k: make_delta_timestamps(cfg.observation_delta_indices, dataset_metadata.fps) for k in cfg.image_features k: make_delta_timestamps(cfg.observation_delta_indices, dataset_metadata.fps)
for k in cfg.image_features
} }
# Instantiate the dataset # Instantiate the dataset
@@ -94,6 +96,10 @@ preprocessor.save_pretrained(output_directory)
postprocessor.save_pretrained(output_directory) postprocessor.save_pretrained(output_directory)
# Save all assets to the Hub # Save all assets to the Hub
policy.push_to_hub("fracapuano/robot_learning_tutorial_diffusion") policy.push_to_hub("<user>/robot_learning_tutorial_diffusion")
preprocessor.push_to_hub("fracapuano/robot_learning_tutorial_diffusion") preprocessor.push_to_hub("<user>/robot_learning_tutorial_diffusion")
postprocessor.push_to_hub("fracapuano/robot_learning_tutorial_diffusion") postprocessor.push_to_hub("<user>/robot_learning_tutorial_diffusion")
if __name__ == "__main__":
main()
@@ -8,8 +8,13 @@ from lerobot.policies.utils import build_inference_frame, make_robot_action
from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig from lerobot.robots.so100_follower.config_so100_follower import SO100FollowerConfig
from lerobot.robots.so100_follower.so100_follower import SO100Follower from lerobot.robots.so100_follower.so100_follower import SO100Follower
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
def main():
device = torch.device("mps") # or "cuda" or "cpu" device = torch.device("mps") # or "cuda" or "cpu"
model_id = "fracapuano/robot_learning_tutorial_diffusion" model_id = "<user>/robot_learning_tutorial_diffusion"
model = DiffusionPolicy.from_pretrained(model_id) model = DiffusionPolicy.from_pretrained(model_id)
@@ -20,10 +25,6 @@ preprocess, postprocess = make_pre_post_processors(
model.config, model_id, dataset_stats=dataset_metadata.stats model.config, model_id, dataset_stats=dataset_metadata.stats
) )
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
# # find ports using lerobot-find-port # # find ports using lerobot-find-port
follower_port = ... # something like "/dev/tty.usbmodem58760431631" follower_port = ... # something like "/dev/tty.usbmodem58760431631"
@@ -42,7 +43,6 @@ robot_cfg = SO100FollowerConfig(port=follower_port, id=follower_id, cameras=came
robot = SO100Follower(robot_cfg) robot = SO100Follower(robot_cfg)
robot.connect() robot.connect()
for _ in range(MAX_EPISODES): for _ in range(MAX_EPISODES):
for _ in range(MAX_STEPS_PER_EPISODE): for _ in range(MAX_STEPS_PER_EPISODE):
obs = robot.get_observation() obs = robot.get_observation()
@@ -58,3 +58,7 @@ for _ in range(MAX_EPISODES):
robot.send_action(action) robot.send_action(action)
print("Episode finished! Starting new episode...") print("Episode finished! Starting new episode...")
if __name__ == "__main__":
main()
@@ -11,6 +11,8 @@ from lerobot.robots.so100_follower.so100_follower import SO100Follower
MAX_EPISODES = 5 MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20 MAX_STEPS_PER_EPISODE = 20
def main():
device = torch.device("mps") # or "cuda" or "cpu" device = torch.device("mps") # or "cuda" or "cpu"
model_id = "lerobot/pi0_base" model_id = "lerobot/pi0_base"
@@ -65,3 +67,7 @@ for _ in range(MAX_EPISODES):
robot.send_action(action) robot.send_action(action)
print("Episode finished! Starting new episode...") print("Episode finished! Starting new episode...")
if __name__ == "__main__":
main()
+8 -6
View File
@@ -20,6 +20,8 @@ from lerobot.teleoperators.utils import TeleopEvents
LOG_EVERY = 10 LOG_EVERY = 10
SEND_EVERY = 10 SEND_EVERY = 10
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
def run_learner( def run_learner(
@@ -223,6 +225,7 @@ def make_policy_obs(obs, device: torch.device = "cpu"):
} }
def main():
"""Main function - coordinates actor and learner processes.""" """Main function - coordinates actor and learner processes."""
device = "mps" # or "cuda" or "cpu" device = "mps" # or "cuda" or "cpu"
@@ -238,15 +241,12 @@ follower_id = ...
leader_id = ... leader_id = ...
# A pretrained model (to be used in-distribution!) # A pretrained model (to be used in-distribution!)
reward_classifier_id = "fracapuano/reward_classifier_hil_serl_example" reward_classifier_id = "<user>/reward_classifier_hil_serl_example"
reward_classifier = Classifier.from_pretrained(reward_classifier_id) reward_classifier = Classifier.from_pretrained(reward_classifier_id)
reward_classifier.to(device) reward_classifier.to(device)
reward_classifier.eval() reward_classifier.eval()
MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20
# Robot and environment configuration # Robot and environment configuration
robot_cfg = SO100FollowerConfig(port=follower_port, id=follower_id) robot_cfg = SO100FollowerConfig(port=follower_port, id=follower_id)
teleop_cfg = SO100LeaderConfig(port=leader_port, id=leader_id) teleop_cfg = SO100LeaderConfig(port=leader_port, id=leader_id)
@@ -285,13 +285,11 @@ transitions_queue = mp.Queue(maxsize=10)
parameters_queue = mp.Queue(maxsize=2) parameters_queue = mp.Queue(maxsize=2)
shutdown_event = mp.Event() shutdown_event = mp.Event()
# Signal handler for graceful shutdown # Signal handler for graceful shutdown
def signal_handler(sig): def signal_handler(sig):
print(f"\nSignal {sig} received, shutting down...") print(f"\nSignal {sig} received, shutting down...")
shutdown_event.set() shutdown_event.set()
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
@@ -343,3 +341,7 @@ finally:
learner_process.terminate() learner_process.terminate()
if actor_process.is_alive(): if actor_process.is_alive():
actor_process.terminate() actor_process.terminate()
if __name__ == "__main__":
main()
@@ -4,6 +4,8 @@ from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.policies.factory import make_policy, make_pre_post_processors from lerobot.policies.factory import make_policy, make_pre_post_processors
from lerobot.policies.sac.reward_model.configuration_classifier import RewardClassifierConfig from lerobot.policies.sac.reward_model.configuration_classifier import RewardClassifierConfig
def main():
# Device to use for training # Device to use for training
device = "mps" # or "cuda", or "cpu" device = "mps" # or "cuda", or "cpu"
@@ -26,8 +28,7 @@ policy = make_policy(config, ds_meta=dataset.meta)
optimizer = config.get_optimizer_preset().build(policy.parameters()) optimizer = config.get_optimizer_preset().build(policy.parameters())
preprocessor, _ = make_pre_post_processors(policy_cfg=config, dataset_stats=dataset.meta.stats) preprocessor, _ = make_pre_post_processors(policy_cfg=config, dataset_stats=dataset.meta.stats)
classifier_id = "<user>/reward_classifier_hil_serl_example"
classifier_id = "fracapuano/reward_classifier_hil_serl_example"
# Instantiate a dataloader # Instantiate a dataloader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=16, shuffle=True) dataloader = torch.utils.data.DataLoader(dataset, batch_size=16, shuffle=True)
@@ -60,3 +61,7 @@ print("Training finished!")
# You can now save the trained policy. # You can now save the trained policy.
policy.push_to_hub(classifier_id) policy.push_to_hub(classifier_id)
if __name__ == "__main__":
main()
@@ -11,6 +11,8 @@ from lerobot.robots.so100_follower.so100_follower import SO100Follower
MAX_EPISODES = 5 MAX_EPISODES = 5
MAX_STEPS_PER_EPISODE = 20 MAX_STEPS_PER_EPISODE = 20
def main():
device = torch.device("mps") # or "cuda" or "cpu" device = torch.device("mps") # or "cuda" or "cpu"
model_id = "lerobot/smolvla_base" model_id = "lerobot/smolvla_base"
@@ -64,3 +66,7 @@ for _ in range(MAX_EPISODES):
robot.send_action(action) robot.send_action(action)
print("Episode finished! Starting new episode...") print("Episode finished! Starting new episode...")
if __name__ == "__main__":
main()
+1 -1
View File
@@ -25,7 +25,7 @@ discord = "https://discord.gg/s3KuuzsPFb"
[project] [project]
name = "lerobot" name = "lerobot"
version = "0.4.2" version = "0.4.3"
description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch" description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch"
readme = "README.md" readme = "README.md"
license = { text = "Apache-2.0" } license = { text = "Apache-2.0" }
+1 -2
View File
@@ -16,7 +16,6 @@
import concurrent.futures import concurrent.futures
import contextlib import contextlib
import logging import logging
import platform
import shutil import shutil
import tempfile import tempfile
from collections.abc import Callable from collections.abc import Callable
@@ -1149,7 +1148,7 @@ class LeRobotDataset(torch.utils.data.Dataset):
def save_episode( def save_episode(
self, self,
episode_data: dict | None = None, episode_data: dict | None = None,
parallel_encoding: bool = platform.system() == "Linux", parallel_encoding: bool = True,
) -> None: ) -> None:
""" """
This will save to disk the current episode in self.episode_buffer. This will save to disk the current episode in self.episode_buffer.
+2 -2
View File
@@ -78,7 +78,7 @@ from lerobot.transport.utils import (
transitions_to_bytes, transitions_to_bytes,
) )
from lerobot.utils.random_utils import set_seed from lerobot.utils.random_utils import set_seed
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.transition import ( from lerobot.utils.transition import (
Transition, Transition,
move_state_dict_to_device, move_state_dict_to_device,
@@ -398,7 +398,7 @@ def act_with_policy(
if cfg.env.fps is not None: if cfg.env.fps is not None:
dt_time = time.perf_counter() - start_time dt_time = time.perf_counter() - start_time
busy_wait(1 / cfg.env.fps - dt_time) precise_sleep(1 / cfg.env.fps - dt_time)
# Communication Functions - Group all gRPC/messaging functions # Communication Functions - Group all gRPC/messaging functions
+5 -5
View File
@@ -74,7 +74,7 @@ from lerobot.teleoperators import (
from lerobot.teleoperators.teleoperator import Teleoperator from lerobot.teleoperators.teleoperator import Teleoperator
from lerobot.teleoperators.utils import TeleopEvents from lerobot.teleoperators.utils import TeleopEvents
from lerobot.utils.constants import ACTION, DONE, OBS_IMAGES, OBS_STATE, REWARD from lerobot.utils.constants import ACTION, DONE, OBS_IMAGES, OBS_STATE, REWARD
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import log_say from lerobot.utils.utils import log_say
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -114,7 +114,7 @@ def reset_follower_position(robot_arm: Robot, target_position: np.ndarray) -> No
for pose in trajectory: for pose in trajectory:
action_dict = dict(zip(current_position_dict, pose, strict=False)) action_dict = dict(zip(current_position_dict, pose, strict=False))
robot_arm.bus.sync_write("Goal_Position", action_dict) robot_arm.bus.sync_write("Goal_Position", action_dict)
busy_wait(0.015) precise_sleep(0.015)
class RobotEnv(gym.Env): class RobotEnv(gym.Env):
@@ -238,7 +238,7 @@ class RobotEnv(gym.Env):
reset_follower_position(self.robot, np.array(self.reset_pose)) reset_follower_position(self.robot, np.array(self.reset_pose))
log_say("Reset the environment done.", play_sounds=True) log_say("Reset the environment done.", play_sounds=True)
busy_wait(self.reset_time_s - (time.perf_counter() - start_time)) precise_sleep(self.reset_time_s - (time.perf_counter() - start_time))
super().reset(seed=seed, options=options) super().reset(seed=seed, options=options)
@@ -713,7 +713,7 @@ def control_loop(
transition = env_processor(transition) transition = env_processor(transition)
# Maintain fps timing # Maintain fps timing
busy_wait(dt - (time.perf_counter() - step_start_time)) precise_sleep(dt - (time.perf_counter() - step_start_time))
if dataset is not None and cfg.dataset.push_to_hub: if dataset is not None and cfg.dataset.push_to_hub:
logging.info("Pushing dataset to hub") logging.info("Pushing dataset to hub")
@@ -745,7 +745,7 @@ def replay_trajectory(
) )
transition = action_processor(transition) transition = action_processor(transition)
env.step(transition[TransitionKey.ACTION]) env.step(transition[TransitionKey.ACTION])
busy_wait(1 / cfg.env.fps - (time.perf_counter() - start_time)) precise_sleep(1 / cfg.env.fps - (time.perf_counter() - start_time))
@parser.wrap() @parser.wrap()
@@ -50,7 +50,7 @@ from lerobot.teleoperators import ( # noqa: F401
make_teleoperator_from_config, make_teleoperator_from_config,
so100_leader, so100_leader,
) )
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
@dataclass @dataclass
@@ -114,7 +114,7 @@ def find_joint_and_ee_bounds(cfg: FindJointLimitsConfig):
print(f"Min joint pos position {np.round(min_pos, 4).tolist()}") print(f"Min joint pos position {np.round(min_pos, 4).tolist()}")
break break
busy_wait(0.01) precise_sleep(0.01)
def main(): def main():
+2 -2
View File
@@ -119,7 +119,7 @@ from lerobot.utils.control_utils import (
sanity_check_dataset_robot_compatibility, sanity_check_dataset_robot_compatibility,
) )
from lerobot.utils.import_utils import register_third_party_devices from lerobot.utils.import_utils import register_third_party_devices
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import ( from lerobot.utils.utils import (
get_safe_torch_device, get_safe_torch_device,
init_logging, init_logging,
@@ -364,7 +364,7 @@ def record_loop(
log_rerun_data(observation=obs_processed, action=action_values) log_rerun_data(observation=obs_processed, action=action_values)
dt_s = time.perf_counter() - start_loop_t dt_s = time.perf_counter() - start_loop_t
busy_wait(1 / fps - dt_s) precise_sleep(1 / fps - dt_s)
timestamp = time.perf_counter() - start_episode_t timestamp = time.perf_counter() - start_episode_t
+2 -2
View File
@@ -62,7 +62,7 @@ from lerobot.robots import ( # noqa: F401
) )
from lerobot.utils.constants import ACTION from lerobot.utils.constants import ACTION
from lerobot.utils.import_utils import register_third_party_devices from lerobot.utils.import_utils import register_third_party_devices
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import ( from lerobot.utils.utils import (
init_logging, init_logging,
log_say, log_say,
@@ -121,7 +121,7 @@ def replay(cfg: ReplayConfig):
_ = robot.send_action(processed_action) _ = robot.send_action(processed_action)
dt_s = time.perf_counter() - start_episode_t dt_s = time.perf_counter() - start_episode_t
busy_wait(1 / dataset.fps - dt_s) precise_sleep(1 / dataset.fps - dt_s)
robot.disconnect() robot.disconnect()
+5 -4
View File
@@ -89,7 +89,7 @@ from lerobot.teleoperators import ( # noqa: F401
so101_leader, so101_leader,
) )
from lerobot.utils.import_utils import register_third_party_devices from lerobot.utils.import_utils import register_third_party_devices
from lerobot.utils.robot_utils import busy_wait from lerobot.utils.robot_utils import precise_sleep
from lerobot.utils.utils import init_logging, move_cursor_up from lerobot.utils.utils import init_logging, move_cursor_up
from lerobot.utils.visualization_utils import init_rerun, log_rerun_data from lerobot.utils.visualization_utils import init_rerun, log_rerun_data
@@ -170,12 +170,13 @@ def teleop_loop(
# Display the final robot action that was sent # Display the final robot action that was sent
for motor, value in robot_action_to_send.items(): for motor, value in robot_action_to_send.items():
print(f"{motor:<{display_len}} | {value:>7.2f}") print(f"{motor:<{display_len}} | {value:>7.2f}")
move_cursor_up(len(robot_action_to_send) + 5) move_cursor_up(len(robot_action_to_send) + 3)
dt_s = time.perf_counter() - loop_start dt_s = time.perf_counter() - loop_start
busy_wait(1 / fps - dt_s) precise_sleep(1 / fps - dt_s)
loop_s = time.perf_counter() - loop_start loop_s = time.perf_counter() - loop_start
print(f"\ntime: {loop_s * 1e3:.2f}ms ({1 / loop_s:.0f} Hz)") print(f"Teleop loop time: {loop_s * 1e3:.2f}ms ({1 / loop_s:.0f} Hz)")
move_cursor_up(1)
if duration is not None and time.perf_counter() - start >= duration: if duration is not None and time.perf_counter() - start >= duration:
return return
+33 -7
View File
@@ -16,14 +16,40 @@ import platform
import time import time
def busy_wait(seconds): def precise_sleep(seconds: float, spin_threshold: float = 0.010, sleep_margin: float = 0.003):
if platform.system() == "Darwin" or platform.system() == "Windows": """
# On Mac and Windows, `time.sleep` is not accurate and we need to use this while loop trick, Wait for `seconds` with better precision than time.sleep alone at the expense of more CPU usage.
# but it consumes CPU cycles.
Parameters:
- seconds: duration to wait
- spin_threshold: if remaining <= spin_threshold -> spin; otherwise sleep (seconds). Default 10ms
- sleep_margin: when sleeping leave this much time before deadline to avoid oversleep. Default 3ms
Note:
The default parameters are chosen to prioritize timing accuracy over CPU usage for the common 30 FPS use case.
"""
if seconds <= 0:
return
system = platform.system()
# On macOS and Windows the scheduler / sleep granularity can make
# short sleeps inaccurate. Instead of burning CPU for the whole
# duration, sleep for most of the time and spin for the final few
# milliseconds to achieve good accuracy with much lower CPU usage.
if system in ("Darwin", "Windows"):
end_time = time.perf_counter() + seconds end_time = time.perf_counter() + seconds
while time.perf_counter() < end_time: while True:
remaining = end_time - time.perf_counter()
if remaining <= 0:
break
# If there's more than a couple milliseconds left, sleep most
# of the remaining time and leave a small margin for the final spin.
if remaining > spin_threshold:
# Sleep but avoid sleeping past the end by leaving a small margin.
time.sleep(max(remaining - sleep_margin, 0))
else:
# Final short spin to hit precise timing without long sleeps.
pass pass
else: else:
# On Linux time.sleep is accurate # On Linux time.sleep is accurate enough for most uses
if seconds > 0:
time.sleep(seconds) time.sleep(seconds)