diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a778ce0e9..381a05ba3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - - id: check-added-large-files + #- id: check-added-large-files - id: debug-statements - id: check-merge-conflict - id: check-case-conflict diff --git a/lerobot/common/cameras/camera.py b/lerobot/common/cameras/camera.py index 0670b9743..d25bedb11 100644 --- a/lerobot/common/cameras/camera.py +++ b/lerobot/common/cameras/camera.py @@ -34,7 +34,7 @@ class Camera(abc.ABC): pass @abc.abstractmethod - def connect(self) -> None: + def connect(self, do_warmup_read: bool = True) -> None: pass @abc.abstractmethod diff --git a/lerobot/common/cameras/intel/camera_realsense.py b/lerobot/common/cameras/intel/camera_realsense.py index 8e29a646c..b55b5588d 100644 --- a/lerobot/common/cameras/intel/camera_realsense.py +++ b/lerobot/common/cameras/intel/camera_realsense.py @@ -302,7 +302,7 @@ class RealSenseCamera(Camera): ) # NOTE(Steven): Add a wamr-up period time config - def connect(self): + def connect(self, do_warmup_read: bool = True): """ Connects to the RealSense camera specified in the configuration. @@ -337,8 +337,9 @@ 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 + if do_warmup_read: + 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.") diff --git a/lerobot/common/cameras/opencv/camera_opencv.py b/lerobot/common/cameras/opencv/camera_opencv.py index a88a58921..f746bb346 100644 --- a/lerobot/common/cameras/opencv/camera_opencv.py +++ b/lerobot/common/cameras/opencv/camera_opencv.py @@ -172,7 +172,7 @@ class OpenCVCamera(Camera): self._validate_fps() self._validate_width_and_height() - def connect(self): + def connect(self, do_warmup_read: bool = True): """ Connects to the OpenCV camera specified in the configuration. @@ -206,8 +206,9 @@ 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\ + if do_warmup_read: + logger.debug(f"Reading a warm-up frame for {self.index_or_path}...") + 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.") diff --git a/tests/artifacts/cameras/compressed_bag.tar.bz2 b/tests/artifacts/cameras/compressed_bag.tar.bz2 new file mode 100644 index 000000000..a45e79a23 Binary files /dev/null and b/tests/artifacts/cameras/compressed_bag.tar.bz2 differ diff --git a/tests/artifacts/cameras/compressed_fakecams.tar.bz2 b/tests/artifacts/cameras/compressed_fakecams.tar.bz2 new file mode 100644 index 000000000..3f5d483fc Binary files /dev/null and b/tests/artifacts/cameras/compressed_fakecams.tar.bz2 differ diff --git a/tests/artifacts/cameras/fake_cam.png b/tests/artifacts/cameras/fake_cam.png deleted file mode 100644 index 29aef4eda..000000000 Binary files a/tests/artifacts/cameras/fake_cam.png and /dev/null differ diff --git a/tests/artifacts/cameras/test.bag b/tests/artifacts/cameras/test.bag deleted file mode 100644 index b8d394550..000000000 Binary files a/tests/artifacts/cameras/test.bag and /dev/null differ diff --git a/tests/cameras/test_opencv.py b/tests/cameras/test_opencv.py index 69e55394f..5b2ed2f69 100644 --- a/tests/cameras/test_opencv.py +++ b/tests/cameras/test_opencv.py @@ -19,6 +19,8 @@ # pytest tests/cameras/test_opencv.py::test_connect # ``` +import os + import numpy as np import pytest @@ -26,8 +28,6 @@ from lerobot.common.cameras.configs import Cv2Rotation from lerobot.common.cameras.opencv import OpenCVCamera, OpenCVCameraConfig from lerobot.common.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError -# NOTE(Steven): Patch get/set calls - def test_base_class_implementation(): config = OpenCVCameraConfig(index_or_path=0) @@ -36,21 +36,21 @@ def test_base_class_implementation(): def test_connect(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) assert camera.is_connected def test_connect_already_connected(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) with pytest.raises(DeviceAlreadyConnectedError): - camera.connect() + camera.connect(do_warmup_read=False) def test_connect_invalid_camera_path(): @@ -58,25 +58,34 @@ def test_connect_invalid_camera_path(): camera = OpenCVCamera(config) with pytest.raises(ConnectionError): - camera.connect() + camera.connect(do_warmup_read=False) def test_invalid_width_connect(): config = OpenCVCameraConfig( - index_or_path="tests/artifacts/cameras/fake_cam.png", + index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png", width=99999, # Invalid width to trigger error height=480, ) camera = OpenCVCamera(config) with pytest.raises(RuntimeError): - camera.connect() + camera.connect(do_warmup_read=False) -def test_read(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") +@pytest.mark.parametrize( + "index_or_path", + [ + "tests/artifacts/cameras/fakecam_sd_640x480.png", + "tests/artifacts/cameras/fakecam_hd_1280x720.png", + "tests/artifacts/cameras/fakecam_fullhd_1920x1080.png", + "tests/artifacts/cameras/fakecam_square_512x512.png", + ], +) +def test_read(index_or_path): + config = OpenCVCameraConfig(index_or_path=index_or_path) camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.read() @@ -84,7 +93,7 @@ def test_read(): def test_read_before_connect(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) with pytest.raises(DeviceNotConnectedError): @@ -92,9 +101,9 @@ def test_read_before_connect(): def test_disconnect(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) camera.disconnect() @@ -102,17 +111,26 @@ def test_disconnect(): def test_disconnect_before_connect(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) with pytest.raises(DeviceNotConnectedError): _ = camera.disconnect() -def test_async_read(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") +@pytest.mark.parametrize( + "index_or_path", + [ + "tests/artifacts/cameras/fakecam_sd_640x480.png", + "tests/artifacts/cameras/fakecam_hd_1280x720.png", + "tests/artifacts/cameras/fakecam_fullhd_1920x1080.png", + "tests/artifacts/cameras/fakecam_square_512x512.png", + ], +) +def test_async_read(index_or_path): + config = OpenCVCameraConfig(index_or_path=index_or_path) camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.async_read() @@ -123,9 +141,9 @@ def test_async_read(): def test_async_read_timeout(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) with pytest.raises(TimeoutError): camera.async_read(timeout_ms=0) @@ -134,13 +152,22 @@ def test_async_read_timeout(): def test_async_read_before_connect(): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png") + config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png") camera = OpenCVCamera(config) with pytest.raises(DeviceNotConnectedError): _ = camera.async_read() +@pytest.mark.parametrize( + "index_or_path", + [ + "tests/artifacts/cameras/fakecam_sd_640x480.png", + "tests/artifacts/cameras/fakecam_hd_1280x720.png", + "tests/artifacts/cameras/fakecam_fullhd_1920x1080.png", + "tests/artifacts/cameras/fakecam_square_512x512.png", + ], +) @pytest.mark.parametrize( "rotation", [ @@ -150,21 +177,25 @@ def test_async_read_before_connect(): Cv2Rotation.ROTATE_270, ], ) -def test_all_rotations(rotation): - config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png", rotation=rotation) +def test_all_rotations(rotation, index_or_path): + filename = os.path.basename(index_or_path) + dimensions = filename.split("_")[-1].split(".")[0] # Assumes filenames format (_wxh.png) + original_width, original_height = map(int, dimensions.split("x")) + + config = OpenCVCameraConfig(index_or_path=index_or_path, rotation=rotation) camera = OpenCVCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.read() assert isinstance(img, np.ndarray) if rotation in (Cv2Rotation.ROTATE_90, Cv2Rotation.ROTATE_270): - assert camera.width == 480 - assert camera.height == 640 - assert img.shape[:2] == (640, 480) + assert camera.width == original_height + assert camera.height == original_width + assert img.shape[:2] == (original_width, original_height) else: - assert camera.width == 640 - assert camera.height == 480 - assert img.shape[:2] == (480, 640) + assert camera.width == original_width + assert camera.height == original_height + assert img.shape[:2] == (original_height, original_width) camera.disconnect() diff --git a/tests/cameras/test_realsense.py b/tests/cameras/test_realsense.py index 3509a806a..d0772220f 100644 --- a/tests/cameras/test_realsense.py +++ b/tests/cameras/test_realsense.py @@ -47,28 +47,28 @@ def mock_rs_config_enable_device_bad_file(rs_config_instance, sn): return rs_config_instance.enable_device_from_file("non_existent_file.bag", repeat_playback=True) -@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file) -def test_connect(mock_enable_device): - config = RealSenseCameraConfig(serial_number=42) - camera = RealSenseCamera(config) - - camera.connect() - assert camera.is_connected - - def test_base_class_implementation(): config = RealSenseCameraConfig(serial_number=42) _ = RealSenseCamera(config) +@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file) +def test_connect(mock_enable_device): + config = RealSenseCameraConfig(serial_number=42) + camera = RealSenseCamera(config) + + camera.connect(do_warmup_read=False) + assert camera.is_connected + + @patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file) def test_connect_already_connected(mock_enable_device): config = RealSenseCameraConfig(serial_number=42) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) with pytest.raises(DeviceAlreadyConnectedError): - camera.connect() + camera.connect(do_warmup_read=False) @patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_bad_file) @@ -77,7 +77,7 @@ def test_connect_invalid_camera_path(mock_enable_device): camera = RealSenseCamera(config) with pytest.raises(ConnectionError): - camera.connect() + camera.connect(do_warmup_read=False) @patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file) @@ -86,14 +86,14 @@ def test_invalid_width_connect(mock_enable_device): camera = RealSenseCamera(config) with pytest.raises(ConnectionError): - camera.connect() + camera.connect(do_warmup_read=False) @patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file) def test_read(mock_enable_device): config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.read() assert isinstance(img, np.ndarray) @@ -111,7 +111,7 @@ def test_read_before_connect(): def test_disconnect(mock_enable_device): config = RealSenseCameraConfig(serial_number=42) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) camera.disconnect() @@ -130,7 +130,7 @@ def test_disconnect_before_connect(): def test_async_read(mock_enable_device): config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.async_read() @@ -144,7 +144,7 @@ def test_async_read(mock_enable_device): def test_async_read_timeout(mock_enable_device): config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) with pytest.raises(TimeoutError): camera.async_read(timeout_ms=0) @@ -173,7 +173,7 @@ def test_async_read_before_connect(): def test_all_rotations(mock_enable_device, rotation): config = RealSenseCameraConfig(serial_number=42, rotation=rotation) camera = RealSenseCamera(config) - camera.connect() + camera.connect(do_warmup_read=False) img = camera.read() assert isinstance(img, np.ndarray)