Skip to content

Commit 3366833

Browse files
authored
Merge pull request #323 from alectimison-maker/fix/zed-calibration-dependency
fix(zed): remove hidden realsense dependency
2 parents 110690b + 55a406e commit 3366833

6 files changed

Lines changed: 81 additions & 15 deletions

File tree

‎docs/extensions/rcs_zed.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ pip install -ve . --no-build-isolation
1919
pip install -ve extensions/rcs_zed
2020
```
2121

22+
## Calibration
23+
24+
The `default_zed(...)` helper uses the identity
25+
`DummyCalibrationStrategy` unless a `calibration_strategy` mapping is supplied.
26+
Mapping keys must match the logical camera names, and values must implement
27+
`rcs.camera.hw.CalibrationStrategy`. Calibration is injected explicitly so the
28+
ZED extension remains independent of other hardware-camera extensions.
29+
2230
## CLI
2331

2432
```shell

‎extensions/rcs_zed/Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PYSRC = src/rcs_zed
22
TESTSRC = tests
33
TESTFILE = tests/test_zed_extension.py
4-
PYTHONPATH_LOCAL = ../../python:src:../rcs_realsense/src
4+
PYTHONPATH_LOCAL = ../../python:src
55

66
pycheckformat:
77
isort --check-only ${PYSRC} ${TESTSRC}

‎extensions/rcs_zed/README.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ pip install -ve . --no-build-isolation
3232
pip install -ve extensions/rcs_zed
3333
```
3434

35+
## Calibration
36+
37+
`default_zed(...)` is standalone and uses RCS's identity
38+
`DummyCalibrationStrategy` by default. To use measured extrinsics, pass one
39+
calibration strategy per logical camera:
40+
41+
```python
42+
from rcs_zed.utils import default_zed
43+
44+
calibration = {
45+
"wrist": my_wrist_calibration,
46+
"scene": my_scene_calibration,
47+
}
48+
cameras = default_zed(
49+
{"wrist": "12345678", "scene": "87654321"},
50+
calibration_strategy=calibration,
51+
)
52+
```
53+
54+
Each value must implement the `rcs.camera.hw.CalibrationStrategy` protocol.
55+
This keeps ZED installation independent of other camera extensions and lets
56+
applications choose the calibration method that matches their robot setup.
57+
3558
## CLI
3659

3760
```shell

‎extensions/rcs_zed/pyproject.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ license = "AGPL-3.0-or-later"
1111
dependencies = [
1212
"rcs-core>=0.7.2",
1313
"opencv-python~=4.10.0",
14-
"pupil_apriltags",
15-
"diskcache",
1614
"typer~=0.9",
1715
]
1816
maintainers = [{ name = "Tobias Juelg", email = "tobias.juelg@utn.de" }]
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
1-
import typing
2-
31
from rcs.camera.hw import CalibrationStrategy
4-
from rcs_realsense.calibration import FR3BaseArucoCalibration
52
from rcs_zed.camera import ZEDCameraSet
63

74
from rcs import common
85

96

10-
def default_zed(name2id: dict[str, str] | None) -> ZEDCameraSet | None:
7+
def default_zed(
8+
name2id: dict[str, str] | None,
9+
calibration_strategy: dict[str, CalibrationStrategy] | None = None,
10+
) -> ZEDCameraSet | None:
11+
"""Create the default ZED camera set.
12+
13+
Args:
14+
name2id: Mapping from logical camera names to ZED serial numbers.
15+
calibration_strategy: Optional calibration strategy for each logical
16+
camera. When omitted, ``ZEDCameraSet`` uses
17+
``DummyCalibrationStrategy``.
18+
"""
1119
if name2id is None:
1220
return None
1321
cameras = {
1422
name: common.BaseCameraConfig(identifier=id, resolution_width=1280, resolution_height=720, frame_rate=30)
1523
for name, id in name2id.items()
1624
}
17-
calibration_strategy = {name: typing.cast(CalibrationStrategy, FR3BaseArucoCalibration(name)) for name in name2id}
1825
return ZEDCameraSet(cameras=cameras, calibration_strategy=calibration_strategy)
1926

2027

2128
def default_zed_dummy_calibration(name2id: dict[str, str] | None) -> ZEDCameraSet | None:
22-
if name2id is None:
23-
return None
24-
cameras = {
25-
name: common.BaseCameraConfig(identifier=id, resolution_width=1280, resolution_height=720, frame_rate=30)
26-
for name, id in name2id.items()
27-
}
28-
return ZEDCameraSet(cameras=cameras)
29+
"""Create the default ZED camera set with dummy calibration."""
30+
return default_zed(name2id)

‎extensions/rcs_zed/tests/test_zed_extension.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
sys.path.insert(0, str(REPO_ROOT / "python"))
1010
sys.path.insert(0, str(REPO_ROOT / "extensions/rcs_zed/src"))
1111

12+
from rcs.camera.hw import DummyCalibrationStrategy # noqa: E402
1213
from rcs_zed.camera import ZEDCameraSet, ZEDDeviceInfo, ZEDFrameBundle # noqa: E402
14+
from rcs_zed.utils import default_zed, default_zed_dummy_calibration # noqa: E402
1315

1416
from rcs import common # noqa: E402
1517

@@ -36,6 +38,14 @@ class PatchZedState(TypedDict):
3638
open_calls: list[tuple[str, bool, bool, bool]]
3739

3840

41+
class FakeCalibrationStrategy:
42+
def calibrate(self, samples, intrinsics, lock):
43+
return True
44+
45+
def get_extrinsics(self):
46+
return np.eye(4)
47+
48+
3949
@pytest.fixture()
4050
def patch_zed(monkeypatch) -> PatchZedState:
4151
state: PatchZedState = {"devices": {}, "opened": {}, "open_calls": []}
@@ -163,3 +173,28 @@ def test_zed_include_right_adds_logical_right_camera_without_double_grab(patch_z
163173
assert left_frame.avg_timestamp == right_frame.avg_timestamp == 12.5
164174
assert left_frame.camera.depth is None
165175
assert right_frame.camera.depth is None
176+
177+
178+
def test_default_zed_uses_builtin_dummy_calibration():
179+
camera_set = default_zed({"wrist": "123"})
180+
181+
assert camera_set is not None
182+
assert isinstance(camera_set.calibration_strategy["wrist"], DummyCalibrationStrategy)
183+
184+
185+
def test_default_zed_accepts_explicit_calibration_strategy():
186+
calibration = FakeCalibrationStrategy()
187+
camera_set = default_zed(
188+
{"wrist": "123"},
189+
calibration_strategy={"wrist": calibration},
190+
)
191+
192+
assert camera_set is not None
193+
assert camera_set.calibration_strategy == {"wrist": calibration}
194+
195+
196+
def test_default_zed_dummy_calibration_remains_compatible():
197+
camera_set = default_zed_dummy_calibration({"wrist": "123"})
198+
199+
assert camera_set is not None
200+
assert isinstance(camera_set.calibration_strategy["wrist"], DummyCalibrationStrategy)

0 commit comments

Comments
 (0)