From 72248071713f64a06aac6b3f0fcd4ce8dd9e37ab Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Mon, 12 May 2025 18:04:26 +0200 Subject: [PATCH] refactor(cameras): add warm-up, fix defaul args, remove width and height from find_cameras utils --- lerobot/common/cameras/configs.py | 2 +- lerobot/common/cameras/intel/camera_realsense.py | 6 +++++- lerobot/common/cameras/opencv/camera_opencv.py | 4 ++++ lerobot/common/cameras/opencv/configuration_opencv.py | 6 ++---- lerobot/find_cameras.py | 10 ++-------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lerobot/common/cameras/configs.py b/lerobot/common/cameras/configs.py index cbbcc2585..1f8dda865 100644 --- a/lerobot/common/cameras/configs.py +++ b/lerobot/common/cameras/configs.py @@ -33,7 +33,7 @@ class Cv2Rotation(Enum): ROTATE_270 = -90 -@dataclass +@dataclass(kw_only=True) class CameraConfig(draccus.ChoiceRegistry, abc.ABC): fps: int | None = None width: int | None = None diff --git a/lerobot/common/cameras/intel/camera_realsense.py b/lerobot/common/cameras/intel/camera_realsense.py index ae5729bd5..8e29a646c 100644 --- a/lerobot/common/cameras/intel/camera_realsense.py +++ b/lerobot/common/cameras/intel/camera_realsense.py @@ -336,6 +336,10 @@ class RealSenseCamera(Camera): logger.debug(f"Validating stream configuration for {self.serial_number}...") self._validate_capture_settings() + + logger.debug(f"Reading a warm-up frame for {self.serial_number}...") + self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs + logger.info(f"Camera {self.serial_number} connected and configured successfully.") def _validate_fps(self, stream) -> None: @@ -396,7 +400,7 @@ class RealSenseCamera(Camera): def read( self, color_mode: ColorMode | None = None, timeout_ms: int = 5000 - ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: + ) -> np.ndarray | Tuple[np.ndarray, np.ndarray]: """ Reads a single frame (color and optionally depth) synchronously from the camera. diff --git a/lerobot/common/cameras/opencv/camera_opencv.py b/lerobot/common/cameras/opencv/camera_opencv.py index 6e2745801..a88a58921 100644 --- a/lerobot/common/cameras/opencv/camera_opencv.py +++ b/lerobot/common/cameras/opencv/camera_opencv.py @@ -205,6 +205,10 @@ class OpenCVCamera(Camera): logger.debug(f"Successfully opened camera {self.index_or_path}. Applying configuration...") self._configure_capture_settings() + + logger.debug(f"Reading a warm-up frame for {self.serial_number}...") + self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs\ + logger.debug(f"Camera {self.index_or_path} connected and configured successfully.") def _validate_fps(self) -> None: diff --git a/lerobot/common/cameras/opencv/configuration_opencv.py b/lerobot/common/cameras/opencv/configuration_opencv.py index 20085c3d0..ab9fc2f72 100644 --- a/lerobot/common/cameras/opencv/configuration_opencv.py +++ b/lerobot/common/cameras/opencv/configuration_opencv.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from pathlib import Path from ..configs import CameraConfig, ColorMode, Cv2Rotation @@ -18,9 +18,7 @@ class OpenCVCameraConfig(CameraConfig): ``` """ - index_or_path: int | Path = field( - default=..., - ) + index_or_path: int | Path color_mode: ColorMode = ColorMode.RGB channels: int = 3 # NOTE(Steven): Why is this a config? rotation: Cv2Rotation = Cv2Rotation.NO_ROTATION diff --git a/lerobot/find_cameras.py b/lerobot/find_cameras.py index de5c09a04..cd3195caa 100644 --- a/lerobot/find_cameras.py +++ b/lerobot/find_cameras.py @@ -157,28 +157,21 @@ def create_camera_instance(cam_meta: Dict[str, Any]) -> Optional[Dict[str, Any]] """Create and connect to a camera instance based on metadata.""" cam_type = cam_meta.get("type") cam_id = cam_meta.get("id") - default_profile = cam_meta.get("default_stream_profile") - width = default_profile.get("width") - height = default_profile.get("height") instance = None - logger.info(f"Preparing {cam_type} ID {cam_id} with profile: Width={width}, Height={height}") + logger.info(f"Preparing {cam_type} ID {cam_id} with default profile") try: if cam_type == "OpenCV": cv_config = OpenCVCameraConfig( index_or_path=cam_id, color_mode=ColorMode.RGB, - width=width, - height=height, ) instance = OpenCVCamera(cv_config) elif cam_type == "RealSense": rs_config = RealSenseCameraConfig( serial_number=str(cam_id), color_mode=ColorMode.RGB, - width=width, - height=height, ) instance = RealSenseCamera(rs_config) else: @@ -270,6 +263,7 @@ def save_images_from_all_cameras( logger.info(f"Starting image capture for {record_time_s} seconds from {len(cameras_to_use)} cameras.") start_time = time.perf_counter() + # NOTE(Steven): This seems like an overkill to me with concurrent.futures.ThreadPoolExecutor(max_workers=len(cameras_to_use) * 2) as executor: try: while time.perf_counter() - start_time < record_time_s: