From d75612e1fbb4a65cb8c9b97c0355b46ad27a81a8 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 10 Oct 2026 03:53:42 -0400 Subject: [PATCH 1/2] Delete the flat viewer placement-mode enum; bump to 0.4.2 ViewerComponentMode mixed napari (stack, layer) and Fiji (window, channel, slice, frame) placement slots in one roster, restated by both OpenHCS viewer configs. Each viewer now owns its slot family (OpenHCS G6); the display payload carries modes as wire strings. ViewerComponentModeGroups, viewer_component_mode_value and ViewerBatchDisplayPayload.components_for_mode / component_mode_groups go with it. Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 2 +- src/zmqruntime/__init__.py | 4 -- src/zmqruntime/viewer_protocol.py | 82 ------------------------------- tests/test_viewer_protocol.py | 7 +-- 4 files changed, 2 insertions(+), 93 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aaceea4..4029b75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "zmqruntime" -version = "0.4.1" +version = "0.4.2" description = "Generic ZMQ-based distributed execution framework" authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}] license = {text = "MIT"} diff --git a/src/zmqruntime/__init__.py b/src/zmqruntime/__init__.py index 19453a8..f1db076 100644 --- a/src/zmqruntime/__init__.py +++ b/src/zmqruntime/__init__.py @@ -124,8 +124,6 @@ "ViewerBatchMessageWirePayload": "zmqruntime.viewer_protocol", "ViewerBatchWireField": "zmqruntime.viewer_protocol", "ViewerComponentMetadataPayload": "zmqruntime.viewer_protocol", - "ViewerComponentMode": "zmqruntime.viewer_protocol", - "ViewerComponentModeGroups": "zmqruntime.viewer_protocol", "ViewerControlReplyHeader": "zmqruntime.viewer_protocol", "ViewerControlReplyPayload": "zmqruntime.viewer_protocol", "ViewerControlResponseField": "zmqruntime.viewer_protocol", @@ -241,8 +239,6 @@ "ViewerControlReplyHeader", "ViewerControlReplyPayload", "ViewerControlResponseField", - "ViewerComponentMode", - "ViewerComponentModeGroups", "ViewerComponentMetadataPayload", "ViewerProtocolStatus", "ViewerTransportEndpoint", diff --git a/src/zmqruntime/viewer_protocol.py b/src/zmqruntime/viewer_protocol.py index d7922ba..fe271af 100644 --- a/src/zmqruntime/viewer_protocol.py +++ b/src/zmqruntime/viewer_protocol.py @@ -282,41 +282,6 @@ class ViewerSourceSpatialWireField(str, Enum): SOURCE_SPATIAL_SHAPE_YX = "source_spatial_shape_yx" -class ViewerComponentMode(str, Enum): - """Viewer component placement modes shared by stream receivers.""" - - STACK = "stack" - LAYER = "layer" - SLICE = "slice" - WINDOW = "window" - CHANNEL = "channel" - FRAME = "frame" - - -@dataclass(frozen=True) -class ViewerComponentModeGroups: - """Components grouped by viewer placement mode for a display payload.""" - - components_by_mode: Mapping[str, tuple[str, ...]] - unsupported_component_modes: Mapping[str, str] - - def components_for_mode( - self, - mode: ViewerComponentMode | str | Enum, - ) -> tuple[str, ...]: - mode_value = viewer_component_mode_value(mode) - if mode_value not in self.components_by_mode: - raise ValueError(f"Viewer component mode groups missing mode {mode_value!r}.") - return self.components_by_mode[mode_value] - - def require_all_supported(self, context: str) -> None: - if self.unsupported_component_modes: - raise ValueError( - f"Unsupported viewer component modes for {context}: " - f"{dict(self.unsupported_component_modes)!r}." - ) - - ViewerBatchWireField: TypeAlias = ViewerWireField ViewerBatchItemWireField: TypeAlias = ViewerWireField ViewerDisplayConfigWireField: TypeAlias = ViewerWireField @@ -448,45 +413,6 @@ class ViewerBatchDisplayPayload: default_factory=dict ) - def components_for_mode( - self, - mode: ViewerComponentMode | str | Enum, - ) -> tuple[str, ...]: - mode_value = viewer_component_mode_value(mode) - components: list[str] = [] - for component in self.component_order: - if component not in self.component_modes: - raise ValueError( - f"Viewer display payload missing mode for component {component!r}." - ) - if viewer_component_mode_value(self.component_modes[component]) == mode_value: - components.append(component) - return tuple(components) - - def component_mode_groups( - self, - supported_modes: Sequence[ViewerComponentMode | str | Enum], - ) -> ViewerComponentModeGroups: - supported_mode_values = tuple(viewer_component_mode_value(mode) for mode in supported_modes) - components_by_mode: dict[str, list[str]] = {mode: [] for mode in supported_mode_values} - unsupported_component_modes: dict[str, str] = {} - for component in self.component_order: - if component not in self.component_modes: - raise ValueError( - f"Viewer display payload missing mode for component {component!r}." - ) - mode_value = viewer_component_mode_value(self.component_modes[component]) - if mode_value in components_by_mode: - components_by_mode[mode_value].append(component) - else: - unsupported_component_modes[component] = mode_value - return ViewerComponentModeGroups( - components_by_mode={ - mode: tuple(components) for mode, components in components_by_mode.items() - }, - unsupported_component_modes=unsupported_component_modes, - ) - def to_wire_mapping(self) -> dict[str, ViewerWireValue]: payload: dict[str, ViewerWireValue] = { ViewerDisplayConfigWireField.COMPONENT_MODES.value: ViewerWirePayload.mapping( @@ -507,14 +433,6 @@ def to_wire_mapping(self) -> dict[str, ViewerWireValue]: return payload -def viewer_component_mode_value(mode: ViewerComponentMode | str | Enum) -> str: - """Return the canonical wire value for a viewer component mode.""" - - if isinstance(mode, Enum): - return str(mode.value) - return str(mode) - - @dataclass(frozen=True) class ViewerBatchItemPayload: """One item inside a shared viewer batch message.""" diff --git a/tests/test_viewer_protocol.py b/tests/test_viewer_protocol.py index 4a6685d..976f377 100644 --- a/tests/test_viewer_protocol.py +++ b/tests/test_viewer_protocol.py @@ -13,7 +13,6 @@ ViewerBatchMessagePayload, ViewerBatchWireField, ViewerComponentMetadataPayload, - ViewerComponentMode, ViewerDisplayConfigWireField, ViewerImageIntensityControlOptions, ViewerNativeImageIntensityPresentation, @@ -190,10 +189,6 @@ def display_image(self, image_data, metadata) -> None: del image_data, metadata -def test_viewer_component_modes_distinguish_napari_layers_from_plane_slices(): - assert ViewerComponentMode.LAYER.value == "layer" - assert ViewerComponentMode.SLICE.value == "slice" - def test_streaming_server_inheritance_owns_viewer_process_usage(monkeypatch): usage = ProcessResourceUsage(memory_mb=12.5, cpu_percent=3.0) @@ -240,7 +235,7 @@ def test_viewer_batch_message_normalizes_nested_mapping_proxy_to_json_wire(): message = ViewerBatchMessagePayload.from_parts( images=[item], display_payload=ViewerBatchDisplayPayload( - component_modes={"channel": ViewerComponentMode.CHANNEL}, + component_modes={"channel": "channel"}, component_order=("channel",), extra={ ViewerDisplayConfigWireField.IMAGES_DIR: "/tmp/openhcs", From 8bd1798af7119405dba5ac75cfacae36fb39f9e6 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 10 Oct 2026 04:02:44 -0400 Subject: [PATCH 2/2] Bump to 0.5.0: removing the viewer mode names is a breaking change Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4029b75..a5aaa5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "zmqruntime" -version = "0.4.2" +version = "0.5.0" description = "Generic ZMQ-based distributed execution framework" authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}] license = {text = "MIT"}