diff --git a/positronic/cfg/hardware/camera.py b/positronic/cfg/hardware/camera.py index 07acc4b5a..c36cb6e08 100644 --- a/positronic/cfg/hardware/camera.py +++ b/positronic/cfg/hardware/camera.py @@ -35,11 +35,23 @@ def zed(**kwargs): zed_2i = zed.override(serial_number=39567055) zed_2i_second = zed.override(serial_number=13785037) +# The station's two sideviews, by side. UNVERIFIED against the rig: correct these two lines and +# every sided config below follows them. +sideview_left = zed_2i +sideview_right = zed_2i_second + _DROID_STREAM = {'view': 'left', 'resolution': 'hd720', 'fps': 30, 'image_enhancement': False} -droid = {keys.WRIST_IMAGE: zed_m.override(**_DROID_STREAM), keys.EXTERIOR_IMAGE: zed_2i.override(**_DROID_STREAM)} +droid = { + keys.WRIST_IMAGE: zed_m.override(**_DROID_STREAM), + keys.EXTERIOR_IMAGE: sideview_left.override(**_DROID_STREAM), +} + +droid_3cam = {**droid, keys.EXTERIOR_IMAGE_2: sideview_right.override(**_DROID_STREAM)} -droid_3cam = {**droid, keys.EXTERIOR_IMAGE_2: zed_2i_second.override(**_DROID_STREAM)} +# One exterior each, by side. Same keys as `droid`, so only the vantage moves. +droid_left = droid # the unsided default already binds the left sideview +droid_right = {**droid, keys.EXTERIOR_IMAGE: sideview_right.override(**_DROID_STREAM)} # YAM station (brunello): ZED X overhead + two ZED X One wrist cameras on the ZED Link Duo. zed_x_top = zed.override(serial_number=48953814) diff --git a/positronic/cfg/tests/test_camera.py b/positronic/cfg/tests/test_camera.py new file mode 100644 index 000000000..4563f73bc --- /dev/null +++ b/positronic/cfg/tests/test_camera.py @@ -0,0 +1,49 @@ +import configuronic as cfn + +from positronic import keys +from positronic.cfg.hardware import camera + + +def _serials(cameras: dict) -> dict[str, int]: + return {name: cfg.kwargs['serial_number'] for name, cfg in cameras.items()} + + +def test_each_sided_droid_keeps_the_observation_keys_of_the_unsided_one(): + assert set(camera.droid_left) == set(camera.droid) == {keys.WRIST_IMAGE, keys.EXTERIOR_IMAGE} + assert set(camera.droid_right) == set(camera.droid) + + +def test_the_two_sides_bind_different_sideviews_and_share_the_wrist(): + left, right = _serials(camera.droid_left), _serials(camera.droid_right) + + assert left[keys.EXTERIOR_IMAGE] != right[keys.EXTERIOR_IMAGE] + assert left[keys.WRIST_IMAGE] == right[keys.WRIST_IMAGE] + + +def test_each_side_binds_the_sideview_the_station_declares_for_it(): + assert _serials(camera.droid_left)[keys.EXTERIOR_IMAGE] == camera.sideview_left.kwargs['serial_number'] + assert _serials(camera.droid_right)[keys.EXTERIOR_IMAGE] == camera.sideview_right.kwargs['serial_number'] + + +def test_an_at_reference_overrides_cameras_and_leaves_a_sibling_override_standing(): + """An `@` reference resolves to a plain dict, and overriding `cameras` alone leaves a sibling + override on the same config standing.""" + + @cfn.config() + def arm(brake_after_idle_s: float = 0.0): + return brake_after_idle_s + + @cfn.config(robot_arm=arm, cameras=camera.droid) + def embodiment(robot_arm, cameras): + return robot_arm, cameras + + # Written out rather than derived from `camera.__name__`: the dotted path is this module's + # public name, so a derived reference would follow a rename instead of failing on one. + right_sideview_ref = '@positronic.cfg.hardware.camera.droid_right' + + pinned = embodiment.override(robot_arm=arm.override(brake_after_idle_s=600.0)) + bound = cfn.Config(lambda e: e, e=pinned).override(**{'e.cameras': right_sideview_ref}).kwargs['e'] + + assert _serials(bound.kwargs['cameras']) == _serials(camera.droid_right) + assert bound.kwargs['robot_arm'].kwargs == {'brake_after_idle_s': 600.0} + assert _serials(camera.droid) != _serials(camera.droid_right) # the module dict is not mutated