[skip ci] refactor(cameras): add warmup read + images different size testing opencv + compressed test artefacts

This commit is contained in:
Steven Palma
2025-05-13 13:43:25 +02:00
parent 7224807171
commit a66d0dc57f
10 changed files with 91 additions and 58 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.")

View File

@@ -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.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 753 KiB

Binary file not shown.

View File

@@ -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()

View File

@@ -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)