From ee0b014aa16200536624762b27aae8741800993e Mon Sep 17 00:00:00 2001 From: alectimison-maker Date: Fri, 24 Jul 2026 17:16:11 +0800 Subject: [PATCH 1/2] fix(zed): remove hidden realsense dependency --- extensions/rcs_zed/Makefile | 2 +- extensions/rcs_zed/pyproject.toml | 2 - extensions/rcs_zed/src/rcs_zed/utils.py | 21 ++++------ .../rcs_zed/tests/test_zed_extension.py | 39 ++++++++++++++++++- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/extensions/rcs_zed/Makefile b/extensions/rcs_zed/Makefile index 940b17db..841c909d 100644 --- a/extensions/rcs_zed/Makefile +++ b/extensions/rcs_zed/Makefile @@ -1,7 +1,7 @@ PYSRC = src/rcs_zed TESTSRC = tests TESTFILE = tests/test_zed_extension.py -PYTHONPATH_LOCAL = ../../python:src:../rcs_realsense/src +PYTHONPATH_LOCAL = ../../python:src pycheckformat: isort --check-only ${PYSRC} ${TESTSRC} diff --git a/extensions/rcs_zed/pyproject.toml b/extensions/rcs_zed/pyproject.toml index 5289ec2e..2ed6d4b9 100644 --- a/extensions/rcs_zed/pyproject.toml +++ b/extensions/rcs_zed/pyproject.toml @@ -11,8 +11,6 @@ license = "AGPL-3.0-or-later" dependencies = [ "rcs-core>=0.7.2", "opencv-python~=4.10.0", - "pupil_apriltags", - "diskcache", "typer~=0.9", ] maintainers = [{ name = "Tobias Juelg", email = "tobias.juelg@utn.de" }] diff --git a/extensions/rcs_zed/src/rcs_zed/utils.py b/extensions/rcs_zed/src/rcs_zed/utils.py index 0573076a..a49366b6 100644 --- a/extensions/rcs_zed/src/rcs_zed/utils.py +++ b/extensions/rcs_zed/src/rcs_zed/utils.py @@ -1,28 +1,21 @@ -import typing - +from rcs import common from rcs.camera.hw import CalibrationStrategy -from rcs_realsense.calibration import FR3BaseArucoCalibration -from rcs_zed.camera import ZEDCameraSet -from rcs import common +from rcs_zed.camera import ZEDCameraSet -def default_zed(name2id: dict[str, str] | None) -> ZEDCameraSet | None: +def default_zed( + name2id: dict[str, str] | None, + calibration_strategy: dict[str, CalibrationStrategy] | None = None, +) -> ZEDCameraSet | None: if name2id is None: return None cameras = { name: common.BaseCameraConfig(identifier=id, resolution_width=1280, resolution_height=720, frame_rate=30) for name, id in name2id.items() } - calibration_strategy = {name: typing.cast(CalibrationStrategy, FR3BaseArucoCalibration(name)) for name in name2id} return ZEDCameraSet(cameras=cameras, calibration_strategy=calibration_strategy) def default_zed_dummy_calibration(name2id: dict[str, str] | None) -> ZEDCameraSet | None: - if name2id is None: - return None - cameras = { - name: common.BaseCameraConfig(identifier=id, resolution_width=1280, resolution_height=720, frame_rate=30) - for name, id in name2id.items() - } - return ZEDCameraSet(cameras=cameras) + return default_zed(name2id) diff --git a/extensions/rcs_zed/tests/test_zed_extension.py b/extensions/rcs_zed/tests/test_zed_extension.py index 5ab7c457..74736e5a 100644 --- a/extensions/rcs_zed/tests/test_zed_extension.py +++ b/extensions/rcs_zed/tests/test_zed_extension.py @@ -9,9 +9,11 @@ sys.path.insert(0, str(REPO_ROOT / "python")) sys.path.insert(0, str(REPO_ROOT / "extensions/rcs_zed/src")) -from rcs_zed.camera import ZEDCameraSet, ZEDDeviceInfo, ZEDFrameBundle # noqa: E402 - from rcs import common # noqa: E402 +from rcs.camera.hw import DummyCalibrationStrategy # noqa: E402 + +from rcs_zed.camera import ZEDCameraSet, ZEDDeviceInfo, ZEDFrameBundle # noqa: E402 +from rcs_zed.utils import default_zed, default_zed_dummy_calibration # noqa: E402 class FakeOpenedZEDCamera: @@ -36,6 +38,14 @@ class PatchZedState(TypedDict): open_calls: list[tuple[str, bool, bool, bool]] +class FakeCalibrationStrategy: + def calibrate(self, samples, intrinsics, lock): + return True + + def get_extrinsics(self): + return np.eye(4) + + @pytest.fixture() def patch_zed(monkeypatch) -> PatchZedState: state: PatchZedState = {"devices": {}, "opened": {}, "open_calls": []} @@ -163,3 +173,28 @@ def test_zed_include_right_adds_logical_right_camera_without_double_grab(patch_z assert left_frame.avg_timestamp == right_frame.avg_timestamp == 12.5 assert left_frame.camera.depth is None assert right_frame.camera.depth is None + + +def test_default_zed_uses_builtin_dummy_calibration(): + camera_set = default_zed({"wrist": "123"}) + + assert camera_set is not None + assert isinstance(camera_set.calibration_strategy["wrist"], DummyCalibrationStrategy) + + +def test_default_zed_accepts_explicit_calibration_strategy(): + calibration = FakeCalibrationStrategy() + camera_set = default_zed( + {"wrist": "123"}, + calibration_strategy={"wrist": calibration}, + ) + + assert camera_set is not None + assert camera_set.calibration_strategy == {"wrist": calibration} + + +def test_default_zed_dummy_calibration_remains_compatible(): + camera_set = default_zed_dummy_calibration({"wrist": "123"}) + + assert camera_set is not None + assert isinstance(camera_set.calibration_strategy["wrist"], DummyCalibrationStrategy) From b56a259a51423c99a9b8bad45c7a94e5a1cc4a79 Mon Sep 17 00:00:00 2001 From: alectimison-maker Date: Fri, 24 Jul 2026 17:17:20 +0800 Subject: [PATCH 2/2] docs(zed): explain calibration injection --- docs/extensions/rcs_zed.md | 8 ++++++++ extensions/rcs_zed/README.md | 23 +++++++++++++++++++++++ extensions/rcs_zed/src/rcs_zed/utils.py | 9 +++++++++ 3 files changed, 40 insertions(+) diff --git a/docs/extensions/rcs_zed.md b/docs/extensions/rcs_zed.md index fcf7a5ee..6f82cfc1 100644 --- a/docs/extensions/rcs_zed.md +++ b/docs/extensions/rcs_zed.md @@ -19,6 +19,14 @@ pip install -ve . --no-build-isolation pip install -ve extensions/rcs_zed ``` +## Calibration + +The `default_zed(...)` helper uses the identity +`DummyCalibrationStrategy` unless a `calibration_strategy` mapping is supplied. +Mapping keys must match the logical camera names, and values must implement +`rcs.camera.hw.CalibrationStrategy`. Calibration is injected explicitly so the +ZED extension remains independent of other hardware-camera extensions. + ## CLI ```shell diff --git a/extensions/rcs_zed/README.md b/extensions/rcs_zed/README.md index 495c02c8..a612e8f4 100644 --- a/extensions/rcs_zed/README.md +++ b/extensions/rcs_zed/README.md @@ -32,6 +32,29 @@ pip install -ve . --no-build-isolation pip install -ve extensions/rcs_zed ``` +## Calibration + +`default_zed(...)` is standalone and uses RCS's identity +`DummyCalibrationStrategy` by default. To use measured extrinsics, pass one +calibration strategy per logical camera: + +```python +from rcs_zed.utils import default_zed + +calibration = { + "wrist": my_wrist_calibration, + "scene": my_scene_calibration, +} +cameras = default_zed( + {"wrist": "12345678", "scene": "87654321"}, + calibration_strategy=calibration, +) +``` + +Each value must implement the `rcs.camera.hw.CalibrationStrategy` protocol. +This keeps ZED installation independent of other camera extensions and lets +applications choose the calibration method that matches their robot setup. + ## CLI ```shell diff --git a/extensions/rcs_zed/src/rcs_zed/utils.py b/extensions/rcs_zed/src/rcs_zed/utils.py index a49366b6..d534f309 100644 --- a/extensions/rcs_zed/src/rcs_zed/utils.py +++ b/extensions/rcs_zed/src/rcs_zed/utils.py @@ -8,6 +8,14 @@ def default_zed( name2id: dict[str, str] | None, calibration_strategy: dict[str, CalibrationStrategy] | None = None, ) -> ZEDCameraSet | None: + """Create the default ZED camera set. + + Args: + name2id: Mapping from logical camera names to ZED serial numbers. + calibration_strategy: Optional calibration strategy for each logical + camera. When omitted, ``ZEDCameraSet`` uses + ``DummyCalibrationStrategy``. + """ if name2id is None: return None cameras = { @@ -18,4 +26,5 @@ def default_zed( def default_zed_dummy_calibration(name2id: dict[str, str] | None) -> ZEDCameraSet | None: + """Create the default ZED camera set with dummy calibration.""" return default_zed(name2id)