Skip to content

Commit cb5c664

Browse files
committed
Merge remote-tracking branch 'origin/fix/record_policy_action_dict' into refactor/updated_api_docs
2 parents 6599895 + f166f13 commit cb5c664

File tree

10 files changed

+62
-53
lines changed

10 files changed

+62
-53
lines changed

lerobot/common/cameras/opencv/camera_opencv.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,12 @@ def _read_loop(self):
372372
"""
373373
Internal loop run by the background thread for asynchronous reading.
374374
375-
Continuously reads frames from the camera using the synchronous `read()`
376-
method and places the latest frame into the `frame_queue`. It overwrites
377-
any previous frame in the queue.
375+
On each iteration:
376+
1. Reads a color frame
377+
2. Stores result in latest_frame (thread-safe)
378+
3. Sets new_frame_event to notify listeners
379+
380+
Stops on DeviceNotConnectedError, logs other errors and continues.
378381
"""
379382
while not self.stop_event.is_set():
380383
try:
@@ -412,18 +415,17 @@ def _stop_read_thread(self) -> None:
412415
self.thread = None
413416
self.stop_event = None
414417

415-
def async_read(self, timeout_ms: float = 2000) -> np.ndarray:
418+
def async_read(self, timeout_ms: float = 200) -> np.ndarray:
416419
"""
417420
Reads the latest available frame asynchronously.
418421
419422
This method retrieves the most recent frame captured by the background
420423
read thread. It does not block waiting for the camera hardware directly,
421-
only waits for a frame to appear in the internal queue up to the specified
422-
timeout.
424+
but may wait up to timeout_ms for the background thread to provide a frame.
423425
424426
Args:
425427
timeout_ms (float): Maximum time in milliseconds to wait for a frame
426-
to become available in the queue. Defaults to 2000ms (2 seconds).
428+
to become available. Defaults to 200ms (0.2 seconds).
427429
428430
Returns:
429431
np.ndarray: The latest captured frame as a NumPy array in the format
@@ -432,7 +434,7 @@ def async_read(self, timeout_ms: float = 2000) -> np.ndarray:
432434
Raises:
433435
DeviceNotConnectedError: If the camera is not connected.
434436
TimeoutError: If no frame becomes available within the specified timeout.
435-
RuntimeError: If an unexpected error occurs while retrieving from the queue.
437+
RuntimeError: If an unexpected error occurs.
436438
"""
437439
if not self.is_connected:
438440
raise DeviceNotConnectedError(f"{self} is not connected.")

lerobot/common/cameras/realsense/camera_realsense.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class RealSenseCamera(Camera):
6767
from lerobot.common.cameras import ColorMode, Cv2Rotation
6868
6969
# Basic usage with serial number
70-
config = RealSenseCameraConfig(serial_number_or_name=1234567890) # Replace with actual SN
70+
config = RealSenseCameraConfig(serial_number_or_name="0123456789") # Replace with actual SN
7171
camera = RealSenseCamera(config)
7272
camera.connect()
7373
@@ -83,7 +83,7 @@ class RealSenseCamera(Camera):
8383
8484
# Example with depth capture and custom settings
8585
custom_config = RealSenseCameraConfig(
86-
serial_number_or_name=1234567890, # Replace with actual SN
86+
serial_number_or_name="0123456789", # Replace with actual SN
8787
fps=30,
8888
width=1280,
8989
height=720,
@@ -116,8 +116,8 @@ def __init__(self, config: RealSenseCameraConfig):
116116

117117
self.config = config
118118

119-
if isinstance(config.serial_number_or_name, int):
120-
self.serial_number = str(config.serial_number_or_name)
119+
if config.serial_number_or_name.isdigit():
120+
self.serial_number = config.serial_number_or_name
121121
else:
122122
self.serial_number = self._find_serial_number_from_name(config.serial_number_or_name)
123123

@@ -310,15 +310,15 @@ def _configure_capture_settings(self) -> None:
310310
self.width, self.height = actual_width, actual_height
311311
self.capture_width, self.capture_height = actual_width, actual_height
312312

313-
def read_depth(self, timeout_ms: int = 100) -> np.ndarray:
313+
def read_depth(self, timeout_ms: int = 200) -> np.ndarray:
314314
"""
315315
Reads a single frame (depth) synchronously from the camera.
316316
317317
This is a blocking call. It waits for a coherent set of frames (depth)
318318
from the camera hardware via the RealSense pipeline.
319319
320320
Args:
321-
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 100ms.
321+
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 200ms.
322322
323323
Returns:
324324
np.ndarray: The depth map as a NumPy array (height, width)
@@ -353,15 +353,15 @@ def read_depth(self, timeout_ms: int = 100) -> np.ndarray:
353353

354354
return depth_map_processed
355355

356-
def read(self, color_mode: ColorMode | None = None, timeout_ms: int = 100) -> np.ndarray:
356+
def read(self, color_mode: ColorMode | None = None, timeout_ms: int = 200) -> np.ndarray:
357357
"""
358358
Reads a single frame (color) synchronously from the camera.
359359
360360
This is a blocking call. It waits for a coherent set of frames (color)
361361
from the camera hardware via the RealSense pipeline.
362362
363363
Args:
364-
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 100ms.
364+
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 200ms.
365365
366366
Returns:
367367
np.ndarray: The captured color frame as a NumPy array
@@ -444,9 +444,12 @@ def _read_loop(self):
444444
"""
445445
Internal loop run by the background thread for asynchronous reading.
446446
447-
Continuously reads frames (color and optional depth) using `read()`
448-
and places the latest result (single image or tuple) into the `frame_queue`.
449-
It overwrites any previous frame in the queue.
447+
On each iteration:
448+
1. Reads a color frame with 500ms timeout
449+
2. Stores result in latest_frame (thread-safe)
450+
3. Sets new_frame_event to notify listeners
451+
452+
Stops on DeviceNotConnectedError, logs other errors and continues.
450453
"""
451454
while not self.stop_event.is_set():
452455
try:
@@ -485,18 +488,17 @@ def _stop_read_thread(self):
485488
self.stop_event = None
486489

487490
# NOTE(Steven): Missing implementation for depth for now
488-
def async_read(self, timeout_ms: float = 100) -> np.ndarray:
491+
def async_read(self, timeout_ms: float = 200) -> np.ndarray:
489492
"""
490-
Reads the latest available frame data (color or color+depth) asynchronously.
493+
Reads the latest available frame data (color) asynchronously.
491494
492-
This method retrieves the most recent frame captured by the background
495+
This method retrieves the most recent color frame captured by the background
493496
read thread. It does not block waiting for the camera hardware directly,
494-
only waits for a frame to appear in the internal queue up to the specified
495-
timeout.
497+
but may wait up to timeout_ms for the background thread to provide a frame.
496498
497499
Args:
498500
timeout_ms (float): Maximum time in milliseconds to wait for a frame
499-
to become available in the queue. Defaults to 100ms (0.1 seconds).
501+
to become available. Defaults to 200ms (0.2 seconds).
500502
501503
Returns:
502504
np.ndarray:
@@ -505,7 +507,7 @@ def async_read(self, timeout_ms: float = 100) -> np.ndarray:
505507
Raises:
506508
DeviceNotConnectedError: If the camera is not connected.
507509
TimeoutError: If no frame data becomes available within the specified timeout.
508-
RuntimeError: If the background thread died unexpectedly or another queue error occurs.
510+
RuntimeError: If the background thread died unexpectedly or another error occurs.
509511
"""
510512
if not self.is_connected:
511513
raise DeviceNotConnectedError(f"{self} is not connected.")

lerobot/common/cameras/realsense/configuration_realsense.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ class RealSenseCameraConfig(CameraConfig):
2828
Example configurations for Intel RealSense D405:
2929
```python
3030
# Basic configurations
31-
RealSenseCameraConfig(128422271347, 30, 1280, 720) # 1280x720 @ 30FPS
32-
RealSenseCameraConfig(128422271347, 60, 640, 480) # 640x480 @ 60FPS
31+
RealSenseCameraConfig("0123456789", 30, 1280, 720) # 1280x720 @ 30FPS
32+
RealSenseCameraConfig("0123456789", 60, 640, 480) # 640x480 @ 60FPS
3333
3434
# Advanced configurations
35-
RealSenseCameraConfig(128422271347, 30, 640, 480, use_depth=True) # With depth sensing
36-
RealSenseCameraConfig(128422271347, 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
35+
RealSenseCameraConfig("0123456789", 30, 640, 480, use_depth=True) # With depth sensing
36+
RealSenseCameraConfig("0123456789", 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
3737
```
3838
3939
Attributes:
@@ -53,7 +53,7 @@ class RealSenseCameraConfig(CameraConfig):
5353
- For `fps`, `width` and `height`, either all of them need to be set, or none of them.
5454
"""
5555

56-
serial_number_or_name: int | str
56+
serial_number_or_name: str
5757
color_mode: ColorMode = ColorMode.RGB
5858
use_depth: bool = False
5959
rotation: Cv2Rotation = Cv2Rotation.NO_ROTATION

lerobot/common/datasets/image_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def worker_process(queue: queue.Queue, num_threads: int):
106106
class AsyncImageWriter:
107107
"""
108108
This class abstract away the initialisation of processes or/and threads to
109-
save images on disk asynchrounously, which is critical to control a robot and record data
109+
save images on disk asynchronously, which is critical to control a robot and record data
110110
at a high frame rate.
111111
112112
When `num_processes=0`, it creates a threads pool of size `num_threads`.

lerobot/common/datasets/lerobot_dataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ def start_image_writer(self, num_processes: int = 0, num_threads: int = 4) -> No
932932
def stop_image_writer(self) -> None:
933933
"""
934934
Whenever wrapping this dataset inside a parallelized DataLoader, this needs to be called first to
935-
remove the image_writer in order for the LeRobotDataset object to be pickleable and parallelized.
935+
remove the image_writer in order for the LeRobotDataset object to be picklable and parallelized.
936936
"""
937937
if self.image_writer is not None:
938938
self.image_writer.stop()

lerobot/common/datasets/video_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def decode_video_frames_torchvision(
101101
keyframes_only = False
102102
torchvision.set_video_backend(backend)
103103
if backend == "pyav":
104-
keyframes_only = True # pyav doesnt support accuracte seek
104+
keyframes_only = True # pyav doesn't support accurate seek
105105

106106
# set a video stream reader
107107
# TODO(rcadene): also load audio stream at the same time

lerobot/common/policies/pi0/modeling_pi0.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def prepare_images(self, batch):
357357
if self.config.resize_imgs_with_padding is not None:
358358
img = resize_with_pad(img, *self.config.resize_imgs_with_padding, pad_value=0)
359359

360-
# Normalize from range [0,1] to [-1,1] as expacted by siglip
360+
# Normalize from range [0,1] to [-1,1] as expected by siglip
361361
img = img * 2.0 - 1.0
362362

363363
bsize = img.shape[0]

lerobot/common/policies/pi0fast/modeling_pi0fast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def prepare_images(self, batch):
516516
interpolate_like_pi=self.config.interpolate_like_pi,
517517
)
518518

519-
# Normalize from range [0,1] to [-1,1] as expacted by siglip
519+
# Normalize from range [0,1] to [-1,1] as expected by siglip
520520
img = img * 2.0 - 1.0
521521

522522
bsize = img.shape[0]

lerobot/record.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from pathlib import Path
3939
from pprint import pformat
4040

41-
import draccus
4241
import numpy as np
4342
import rerun as rr
4443

@@ -152,6 +151,11 @@ def __post_init__(self):
152151
self.policy = PreTrainedConfig.from_pretrained(policy_path, cli_overrides=cli_overrides)
153152
self.policy.pretrained_path = policy_path
154153

154+
@classmethod
155+
def __get_path_fields__(cls) -> list[str]:
156+
"""This enables the parser to load config from the policy using `--policy.path=local/dir`"""
157+
return ["policy"]
158+
155159

156160
@safe_stop_image_writer
157161
def record_loop(
@@ -183,9 +187,10 @@ def record_loop(
183187
observation_frame = build_dataset_frame(dataset.features, observation, prefix="observation")
184188

185189
if policy is not None:
186-
action = predict_action(
190+
action_values = predict_action(
187191
observation_frame, policy, get_safe_torch_device(policy.config.device), policy.config.use_amp
188192
)
193+
action = {key: action_values[i] for i, key in enumerate(robot.action_features.keys())}
189194
else:
190195
action = teleop.get_action()
191196

@@ -221,7 +226,7 @@ def record_loop(
221226
break
222227

223228

224-
@draccus.wrap()
229+
@parser.wrap()
225230
def record(cfg: RecordConfig) -> LeRobotDataset:
226231
init_logging()
227232
logging.info(pformat(asdict(cfg)))

tests/cameras/test_realsense.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,20 @@ def fixture_patch_realsense():
5757

5858
def test_abc_implementation():
5959
"""Instantiation should raise an error if the class doesn't implement abstract methods/properties."""
60-
config = RealSenseCameraConfig(serial_number_or_name=42)
60+
config = RealSenseCameraConfig(serial_number_or_name="042")
6161
_ = RealSenseCamera(config)
6262

6363

6464
def test_connect():
65-
config = RealSenseCameraConfig(serial_number_or_name=42)
65+
config = RealSenseCameraConfig(serial_number_or_name="042")
6666
camera = RealSenseCamera(config)
6767

6868
camera.connect(warmup=False)
6969
assert camera.is_connected
7070

7171

7272
def test_connect_already_connected():
73-
config = RealSenseCameraConfig(serial_number_or_name=42)
73+
config = RealSenseCameraConfig(serial_number_or_name="042")
7474
camera = RealSenseCamera(config)
7575
camera.connect(warmup=False)
7676

@@ -80,23 +80,23 @@ def test_connect_already_connected():
8080

8181
def test_connect_invalid_camera_path(patch_realsense):
8282
patch_realsense.side_effect = mock_rs_config_enable_device_bad_file
83-
config = RealSenseCameraConfig(serial_number_or_name=42)
83+
config = RealSenseCameraConfig(serial_number_or_name="042")
8484
camera = RealSenseCamera(config)
8585

8686
with pytest.raises(ConnectionError):
8787
camera.connect(warmup=False)
8888

8989

9090
def test_invalid_width_connect():
91-
config = RealSenseCameraConfig(serial_number_or_name=42, width=99999, height=480, fps=30)
91+
config = RealSenseCameraConfig(serial_number_or_name="042", width=99999, height=480, fps=30)
9292
camera = RealSenseCamera(config)
9393

9494
with pytest.raises(ConnectionError):
9595
camera.connect(warmup=False)
9696

9797

9898
def test_read():
99-
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
99+
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
100100
camera = RealSenseCamera(config)
101101
camera.connect(warmup=False)
102102

@@ -105,7 +105,7 @@ def test_read():
105105

106106

107107
def test_read_depth():
108-
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30, use_depth=True)
108+
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30, use_depth=True)
109109
camera = RealSenseCamera(config)
110110
camera.connect(warmup=False)
111111

@@ -114,15 +114,15 @@ def test_read_depth():
114114

115115

116116
def test_read_before_connect():
117-
config = RealSenseCameraConfig(serial_number_or_name=42)
117+
config = RealSenseCameraConfig(serial_number_or_name="042")
118118
camera = RealSenseCamera(config)
119119

120120
with pytest.raises(DeviceNotConnectedError):
121121
_ = camera.read()
122122

123123

124124
def test_disconnect():
125-
config = RealSenseCameraConfig(serial_number_or_name=42)
125+
config = RealSenseCameraConfig(serial_number_or_name="042")
126126
camera = RealSenseCamera(config)
127127
camera.connect(warmup=False)
128128

@@ -132,15 +132,15 @@ def test_disconnect():
132132

133133

134134
def test_disconnect_before_connect():
135-
config = RealSenseCameraConfig(serial_number_or_name=42)
135+
config = RealSenseCameraConfig(serial_number_or_name="042")
136136
camera = RealSenseCamera(config)
137137

138138
with pytest.raises(DeviceNotConnectedError):
139139
camera.disconnect()
140140

141141

142142
def test_async_read():
143-
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
143+
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
144144
camera = RealSenseCamera(config)
145145
camera.connect(warmup=False)
146146

@@ -156,7 +156,7 @@ def test_async_read():
156156

157157

158158
def test_async_read_timeout():
159-
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
159+
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
160160
camera = RealSenseCamera(config)
161161
camera.connect(warmup=False)
162162

@@ -171,7 +171,7 @@ def test_async_read_timeout():
171171

172172

173173
def test_async_read_before_connect():
174-
config = RealSenseCameraConfig(serial_number_or_name=42)
174+
config = RealSenseCameraConfig(serial_number_or_name="042")
175175
camera = RealSenseCamera(config)
176176

177177
with pytest.raises(DeviceNotConnectedError):
@@ -189,7 +189,7 @@ def test_async_read_before_connect():
189189
ids=["no_rot", "rot90", "rot180", "rot270"],
190190
)
191191
def test_rotation(rotation):
192-
config = RealSenseCameraConfig(serial_number_or_name=42, rotation=rotation)
192+
config = RealSenseCameraConfig(serial_number_or_name="042", rotation=rotation)
193193
camera = RealSenseCamera(config)
194194
camera.connect(warmup=False)
195195

0 commit comments

Comments
 (0)