fix(cameras): update docstring + handle sn when starts with 0 + update timeouts to more reasonable value (#1154)
This commit is contained in:
@@ -370,11 +370,14 @@ class OpenCVCamera(Camera):
|
|||||||
|
|
||||||
def _read_loop(self):
|
def _read_loop(self):
|
||||||
"""
|
"""
|
||||||
Internal loop run by the background thread for asynchronous reading.
|
Internal loop for background thread for asynchronous reading.
|
||||||
|
|
||||||
Continuously reads frames from the camera using the synchronous `read()`
|
On each iteration:
|
||||||
method and places the latest frame into the `frame_queue`. It overwrites
|
1. Reads a color frame
|
||||||
any previous frame in the queue.
|
2. Stores result in latest_frame (thread-safe)
|
||||||
|
3. Sets new_frame_event to notify listeners
|
||||||
|
|
||||||
|
Stops on DeviceNotConnectedError, logs other errors and continues.
|
||||||
"""
|
"""
|
||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
@@ -412,18 +415,17 @@ class OpenCVCamera(Camera):
|
|||||||
self.thread = None
|
self.thread = None
|
||||||
self.stop_event = None
|
self.stop_event = None
|
||||||
|
|
||||||
def async_read(self, timeout_ms: float = 2000) -> np.ndarray:
|
def async_read(self, timeout_ms: float = 200) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Reads the latest available frame asynchronously.
|
Reads the latest available frame asynchronously.
|
||||||
|
|
||||||
This method retrieves the most recent frame captured by the background
|
This method retrieves the most recent frame captured by the background
|
||||||
read thread. It does not block waiting for the camera hardware directly,
|
read thread. It does not block waiting for the camera hardware directly,
|
||||||
only waits for a frame to appear in the internal queue up to the specified
|
but may wait up to timeout_ms for the background thread to provide a frame.
|
||||||
timeout.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
timeout_ms (float): Maximum time in milliseconds to wait for a frame
|
timeout_ms (float): Maximum time in milliseconds to wait for a frame
|
||||||
to become available in the queue. Defaults to 2000ms (2 seconds).
|
to become available. Defaults to 200ms (0.2 seconds).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray: The latest captured frame as a NumPy array in the format
|
np.ndarray: The latest captured frame as a NumPy array in the format
|
||||||
@@ -432,7 +434,7 @@ class OpenCVCamera(Camera):
|
|||||||
Raises:
|
Raises:
|
||||||
DeviceNotConnectedError: If the camera is not connected.
|
DeviceNotConnectedError: If the camera is not connected.
|
||||||
TimeoutError: If no frame becomes available within the specified timeout.
|
TimeoutError: If no frame becomes available within the specified timeout.
|
||||||
RuntimeError: If an unexpected error occurs while retrieving from the queue.
|
RuntimeError: If an unexpected error occurs.
|
||||||
"""
|
"""
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
raise DeviceNotConnectedError(f"{self} is not connected.")
|
raise DeviceNotConnectedError(f"{self} is not connected.")
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class RealSenseCamera(Camera):
|
|||||||
from lerobot.common.cameras import ColorMode, Cv2Rotation
|
from lerobot.common.cameras import ColorMode, Cv2Rotation
|
||||||
|
|
||||||
# Basic usage with serial number
|
# Basic usage with serial number
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=1234567890) # Replace with actual SN
|
config = RealSenseCameraConfig(serial_number_or_name="0123456789") # Replace with actual SN
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class RealSenseCamera(Camera):
|
|||||||
|
|
||||||
# Example with depth capture and custom settings
|
# Example with depth capture and custom settings
|
||||||
custom_config = RealSenseCameraConfig(
|
custom_config = RealSenseCameraConfig(
|
||||||
serial_number_or_name=1234567890, # Replace with actual SN
|
serial_number_or_name="0123456789", # Replace with actual SN
|
||||||
fps=30,
|
fps=30,
|
||||||
width=1280,
|
width=1280,
|
||||||
height=720,
|
height=720,
|
||||||
@@ -116,8 +116,8 @@ class RealSenseCamera(Camera):
|
|||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
if isinstance(config.serial_number_or_name, int):
|
if config.serial_number_or_name.isdigit():
|
||||||
self.serial_number = str(config.serial_number_or_name)
|
self.serial_number = config.serial_number_or_name
|
||||||
else:
|
else:
|
||||||
self.serial_number = self._find_serial_number_from_name(config.serial_number_or_name)
|
self.serial_number = self._find_serial_number_from_name(config.serial_number_or_name)
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ class RealSenseCamera(Camera):
|
|||||||
self.width, self.height = actual_width, actual_height
|
self.width, self.height = actual_width, actual_height
|
||||||
self.capture_width, self.capture_height = actual_width, actual_height
|
self.capture_width, self.capture_height = actual_width, actual_height
|
||||||
|
|
||||||
def read_depth(self, timeout_ms: int = 100) -> np.ndarray:
|
def read_depth(self, timeout_ms: int = 200) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Reads a single frame (depth) synchronously from the camera.
|
Reads a single frame (depth) synchronously from the camera.
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ class RealSenseCamera(Camera):
|
|||||||
from the camera hardware via the RealSense pipeline.
|
from the camera hardware via the RealSense pipeline.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 100ms.
|
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 200ms.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray: The depth map as a NumPy array (height, width)
|
np.ndarray: The depth map as a NumPy array (height, width)
|
||||||
@@ -353,7 +353,7 @@ class RealSenseCamera(Camera):
|
|||||||
|
|
||||||
return depth_map_processed
|
return depth_map_processed
|
||||||
|
|
||||||
def read(self, color_mode: ColorMode | None = None, timeout_ms: int = 100) -> np.ndarray:
|
def read(self, color_mode: ColorMode | None = None, timeout_ms: int = 200) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Reads a single frame (color) synchronously from the camera.
|
Reads a single frame (color) synchronously from the camera.
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ class RealSenseCamera(Camera):
|
|||||||
from the camera hardware via the RealSense pipeline.
|
from the camera hardware via the RealSense pipeline.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 100ms.
|
timeout_ms (int): Maximum time in milliseconds to wait for a frame. Defaults to 200ms.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray: The captured color frame as a NumPy array
|
np.ndarray: The captured color frame as a NumPy array
|
||||||
@@ -442,11 +442,14 @@ class RealSenseCamera(Camera):
|
|||||||
|
|
||||||
def _read_loop(self):
|
def _read_loop(self):
|
||||||
"""
|
"""
|
||||||
Internal loop run by the background thread for asynchronous reading.
|
Internal loop for background thread for asynchronous reading.
|
||||||
|
|
||||||
Continuously reads frames (color and optional depth) using `read()`
|
On each iteration:
|
||||||
and places the latest result (single image or tuple) into the `frame_queue`.
|
1. Reads a color frame with 500ms timeout
|
||||||
It overwrites any previous frame in the queue.
|
2. Stores result in latest_frame (thread-safe)
|
||||||
|
3. Sets new_frame_event to notify listeners
|
||||||
|
|
||||||
|
Stops on DeviceNotConnectedError, logs other errors and continues.
|
||||||
"""
|
"""
|
||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
@@ -485,18 +488,17 @@ class RealSenseCamera(Camera):
|
|||||||
self.stop_event = None
|
self.stop_event = None
|
||||||
|
|
||||||
# NOTE(Steven): Missing implementation for depth for now
|
# NOTE(Steven): Missing implementation for depth for now
|
||||||
def async_read(self, timeout_ms: float = 100) -> np.ndarray:
|
def async_read(self, timeout_ms: float = 200) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Reads the latest available frame data (color or color+depth) asynchronously.
|
Reads the latest available frame data (color) asynchronously.
|
||||||
|
|
||||||
This method retrieves the most recent frame captured by the background
|
This method retrieves the most recent color frame captured by the background
|
||||||
read thread. It does not block waiting for the camera hardware directly,
|
read thread. It does not block waiting for the camera hardware directly,
|
||||||
only waits for a frame to appear in the internal queue up to the specified
|
but may wait up to timeout_ms for the background thread to provide a frame.
|
||||||
timeout.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
timeout_ms (float): Maximum time in milliseconds to wait for a frame
|
timeout_ms (float): Maximum time in milliseconds to wait for a frame
|
||||||
to become available in the queue. Defaults to 100ms (0.1 seconds).
|
to become available. Defaults to 200ms (0.2 seconds).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
np.ndarray:
|
np.ndarray:
|
||||||
@@ -505,7 +507,7 @@ class RealSenseCamera(Camera):
|
|||||||
Raises:
|
Raises:
|
||||||
DeviceNotConnectedError: If the camera is not connected.
|
DeviceNotConnectedError: If the camera is not connected.
|
||||||
TimeoutError: If no frame data becomes available within the specified timeout.
|
TimeoutError: If no frame data becomes available within the specified timeout.
|
||||||
RuntimeError: If the background thread died unexpectedly or another queue error occurs.
|
RuntimeError: If the background thread died unexpectedly or another error occurs.
|
||||||
"""
|
"""
|
||||||
if not self.is_connected:
|
if not self.is_connected:
|
||||||
raise DeviceNotConnectedError(f"{self} is not connected.")
|
raise DeviceNotConnectedError(f"{self} is not connected.")
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class RealSenseCameraConfig(CameraConfig):
|
|||||||
Example configurations for Intel RealSense D405:
|
Example configurations for Intel RealSense D405:
|
||||||
```python
|
```python
|
||||||
# Basic configurations
|
# Basic configurations
|
||||||
RealSenseCameraConfig(128422271347, 30, 1280, 720) # 1280x720 @ 30FPS
|
RealSenseCameraConfig("0123456789", 30, 1280, 720) # 1280x720 @ 30FPS
|
||||||
RealSenseCameraConfig(128422271347, 60, 640, 480) # 640x480 @ 60FPS
|
RealSenseCameraConfig("0123456789", 60, 640, 480) # 640x480 @ 60FPS
|
||||||
|
|
||||||
# Advanced configurations
|
# Advanced configurations
|
||||||
RealSenseCameraConfig(128422271347, 30, 640, 480, use_depth=True) # With depth sensing
|
RealSenseCameraConfig("0123456789", 30, 640, 480, use_depth=True) # With depth sensing
|
||||||
RealSenseCameraConfig(128422271347, 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
|
RealSenseCameraConfig("0123456789", 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
|
||||||
```
|
```
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
@@ -53,7 +53,7 @@ class RealSenseCameraConfig(CameraConfig):
|
|||||||
- For `fps`, `width` and `height`, either all of them need to be set, or none of them.
|
- For `fps`, `width` and `height`, either all of them need to be set, or none of them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serial_number_or_name: int | str
|
serial_number_or_name: str
|
||||||
color_mode: ColorMode = ColorMode.RGB
|
color_mode: ColorMode = ColorMode.RGB
|
||||||
use_depth: bool = False
|
use_depth: bool = False
|
||||||
rotation: Cv2Rotation = Cv2Rotation.NO_ROTATION
|
rotation: Cv2Rotation = Cv2Rotation.NO_ROTATION
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ def fixture_patch_realsense():
|
|||||||
|
|
||||||
def test_abc_implementation():
|
def test_abc_implementation():
|
||||||
"""Instantiation should raise an error if the class doesn't implement abstract methods/properties."""
|
"""Instantiation should raise an error if the class doesn't implement abstract methods/properties."""
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
_ = RealSenseCamera(config)
|
_ = RealSenseCamera(config)
|
||||||
|
|
||||||
|
|
||||||
def test_connect():
|
def test_connect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
@@ -70,7 +70,7 @@ def test_connect():
|
|||||||
|
|
||||||
|
|
||||||
def test_connect_already_connected():
|
def test_connect_already_connected():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ def test_connect_already_connected():
|
|||||||
|
|
||||||
def test_connect_invalid_camera_path(patch_realsense):
|
def test_connect_invalid_camera_path(patch_realsense):
|
||||||
patch_realsense.side_effect = mock_rs_config_enable_device_bad_file
|
patch_realsense.side_effect = mock_rs_config_enable_device_bad_file
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
@@ -88,7 +88,7 @@ def test_connect_invalid_camera_path(patch_realsense):
|
|||||||
|
|
||||||
|
|
||||||
def test_invalid_width_connect():
|
def test_invalid_width_connect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, width=99999, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number_or_name="042", width=99999, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
@@ -96,7 +96,7 @@ def test_invalid_width_connect():
|
|||||||
|
|
||||||
|
|
||||||
def test_read():
|
def test_read():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ def test_read():
|
|||||||
|
|
||||||
|
|
||||||
def test_read_depth():
|
def test_read_depth():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30, use_depth=True)
|
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30, use_depth=True)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ def test_read_depth():
|
|||||||
|
|
||||||
|
|
||||||
def test_read_before_connect():
|
def test_read_before_connect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
@@ -122,7 +122,7 @@ def test_read_before_connect():
|
|||||||
|
|
||||||
|
|
||||||
def test_disconnect():
|
def test_disconnect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ def test_disconnect():
|
|||||||
|
|
||||||
|
|
||||||
def test_disconnect_before_connect():
|
def test_disconnect_before_connect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
@@ -140,7 +140,7 @@ def test_disconnect_before_connect():
|
|||||||
|
|
||||||
|
|
||||||
def test_async_read():
|
def test_async_read():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ def test_async_read():
|
|||||||
|
|
||||||
|
|
||||||
def test_async_read_timeout():
|
def test_async_read_timeout():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number_or_name="042", width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ def test_async_read_timeout():
|
|||||||
|
|
||||||
|
|
||||||
def test_async_read_before_connect():
|
def test_async_read_before_connect():
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42)
|
config = RealSenseCameraConfig(serial_number_or_name="042")
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
@@ -189,7 +189,7 @@ def test_async_read_before_connect():
|
|||||||
ids=["no_rot", "rot90", "rot180", "rot270"],
|
ids=["no_rot", "rot90", "rot180", "rot270"],
|
||||||
)
|
)
|
||||||
def test_rotation(rotation):
|
def test_rotation(rotation):
|
||||||
config = RealSenseCameraConfig(serial_number_or_name=42, rotation=rotation)
|
config = RealSenseCameraConfig(serial_number_or_name="042", rotation=rotation)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect(warmup=False)
|
camera.connect(warmup=False)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user