Skip to content

G1: declare axes as a family; delete the component enums - #1170

Merged
trissim merged 7 commits into
mainfrom
refactor/G1-axis-family
Oct 10, 2026
Merged

trissim merged 7 commits into
mainfrom
refactor/G1-axis-family

Conversation

@trissim

@trissim trissim commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Surface G1 (receipt: docs/refactor/consolidation-20261009/G1-axis-family.md).

What changed

  • openhcs/core/axes.py: nominal Axis classes registered in an AxisFamily (AutoRegisterMeta). Roles are capability mixins with cardinality checked when a family is declared: PartitionAxis (exactly one), StackAxis (at most one), ColourAxis, TileAxis, TimeAxis, DefaultVariable, DefaultGroupBy (at most one). Value kinds are LabelValued and OrdinalValued. Ungrouped is the explicit absent-grouping declaration. A role-keyed strategy mixin replaces the strategies that were keyed by enum member.
  • The microscopy family (site, channel, z_index, timepoint, well, with their current roles and order) lives in openhcs/domains/microscopy/axes.py. The product entry point (openhcs/__init__.py) activates it once per process. No kernel module imports the domain.
  • Deleted: the five enums, _ComponentTemplate, ComponentConfiguration and its factory, the per-call get_openhcs_config, the GroupBy __eq__/__hash__ patch, convert_enum_by_value, ComponentSet.coerce*, the import-time DEFAULT_*/MULTIPROCESSING_AXIS, OpenHCSPlaneAddress.from_values, and every .value identity bridge.
  • Kernel member references are now role queries:
    • source metadata projections, Zarr NGFF axes and viewer labels use role-keyed leaves;
    • viewer display configs have one mode field per role (partition_mode, tile_mode, colour_mode, stack_mode, time_mode);
    • plane filenames derive from the declared axis tokens;
    • function requirements declare roles (required_axis_roles, allowed_group_by_roles).
  • Boundaries:
    • JSON and wire values use Axis.name, and group_by uses "none" for Ungrouped;
    • config documents spell Microscopy.Channel (new pycodify formatter);
    • form fields declare AnnotationChoices.
  • Lockstep libraries: Add AnnotationChoices for declared finite choice sets python-introspect#17 (AnnotationChoices, type[X] validation) and Render AnnotationChoices fields; provider-owned grouping labels PyQT-reactive#16 (choice widgets; the provider now owns grouping labels instead of get_groupby_enum).

Durable state (owner decision G1-Q1)

Pickled pipelines are deprecated and not converted (F1 deletes the GUI pickle path). tools/cutover/g1_axis_family.py rewrites pycodify Python sources (.py pipelines, config documents, custom functions): enum members, GroupBy.NONE, per-member viewer mode fields, required_variable_components/allowed_group_by and their imports, keeping a .pre-g1 backup. tests/unit/test_g1_cutover_tool.py converts fixtures rendered by pre-G1 main and loads and validates them on G1. Tool, test and fixtures are deleted once the owner has migrated.

Verification

  • Guards: tests/unit/test_axis_family_guards.py, 3/3 pass. The deleted names appear nowhere in openhcs/, tests/, benchmark/ or scripts/. Kernel modules neither import openhcs.domains nor name the microscopy family or its members. The product entry point activates Microscopy.
  • Witness: tests/unit/test_axis_family_witness.py, 5/5 pass. A remote-sensing family (scene, tile, band, date; no stack axis) is activated in-process with zero kernel edits. These all follow it:
    • family queries;
    • ProcessingConfig defaults and validation;
    • Fiji/Napari mode projection and its wire round trip;
    • plane filenames;
    • step validation and the compiler's group-axis resolution.
    • A real pipeline was not run end to end: the openhcsdata handler (domain, G4) still writes microscopy-named metadata.
  • Official30 parity (ZMQ): 29/30 equivalent. Only cp_tutorial_3d_monolayer fails, with the same .pkl serialization error as the baseline on main.
  • Offscreen GUI import: OK. variable_components and sequential_components render as checkbox groups, group_by as a combo.
  • Touched-module tests: every failure left is either one that also fails in the main baseline, an environment issue (pyclesperanto/torch not installed, subprocess import-root checks), or a test file that did not collect on main.

MCP schema changes (fixture regenerated)

All intended consequences of the per-role viewer mode fields:

  • openhcs_stream_plate_files_to_viewer: the Napari display-config properties site_mode, channel_mode, z_index_mode, timepoint_mode, well_mode become tile_mode, colour_mode, stack_mode, time_mode, partition_mode (same enum, same defaults).
  • openhcs_set_viewer_image_color: the description now says colour_mode=LAYER instead of channel_mode=LAYER.

No other tool, surface or resource changed.

Handoff

Recorded in the receipt:

  • domain member references left for G2/G4;
  • one(role) assumptions for G3/G4/G5;
  • 24 axis-name string literals in kernel modules for G4/G7/G8;
  • viewer slot families for G6;
  • separating the grid role from the partition role (G8);
  • merging the debug JSON switches (K6).

Production +2445/−2193. Net +252: the new kernel axes.py and role-keyed strategy leaves exceed the deleted factory and enum machinery. Tests +2656/−2403.

G1 · PR #1170 · guards 3/3 · production −2193 +2445 · tests −2403 +2656

🤖 Generated with Claude Code

trissim and others added 2 commits October 9, 2026 22:21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel's axes are nominal Axis classes registered in an AxisFamily, with
roles (PartitionAxis, StackAxis, ColourAxis, TileAxis, TimeAxis,
DefaultVariable, DefaultGroupBy) as capability mixins checked for cardinality
and value kinds (LabelValued, OrdinalValued). The microscopy family is declared
in openhcs/domains/microscopy/axes.py and activated by the product entry point.

Deleted: AllComponents, VariableComponents, SequentialComponents,
StreamingComponents, GroupBy, _ComponentTemplate, ComponentConfiguration and
its factory, get_openhcs_config and the per-call enum construction, the GroupBy
equality patch, convert_enum_by_value, the DEFAULT_*/MULTIPROCESSING_AXIS
import-time copies and every .value identity bridge. Consumers take axis
classes; GroupBy.NONE is Ungrouped; subsets are family queries; strings exist
only at boundaries via Axis.name.

Kernel member references became role queries: role-keyed strategies for
source metadata projection, Zarr NGFF axes and viewer labels; viewer display
configs carry one mode field per role; plane filenames derive from declared
axis tokens; function requirements declare roles (required_axis_roles,
allowed_group_by_roles).

Durable state: tools/cutover/g1_axis_family.py rewrites pre-G1 config
documents and pickled pipelines (owner decision G1-Q1).

Guards: tests/unit/test_axis_family_guards.py. Witness:
tests/unit/test_axis_family_witness.py (a remote-sensing family).

Lockstep: python-introspect#17 (AnnotationChoices), PyQT-reactive#16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 04:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

trissim and others added 5 commits October 10, 2026 00:15
Resolutions keep all three intents: the C3 editor derives its columns from
the dataclass (ComponentSelector.component declares AxisChoices, which the
choice cell editor renders by axis name); C5/L1 provider families and
metaclass_registry strategies/caches; G1 axis classes with no component enums.

Axis and family declarations are now validated after class creation: raising
from __init_subclass__ left a half-built ABC subclass that made unrelated axes
pass role checks (witness regression assertion added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pickled pipelines are deprecated; the pickle branch is deleted. The tool now
rewrites any pycodify-generated source (.py pipelines, config documents,
custom functions): enum members, GroupBy.NONE, per-member viewer mode fields,
required_variable_components/allowed_group_by and their imports, keeping a
.pre-g1 backup. A test converts fixtures rendered by pre-G1 main and loads and
validates them on G1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Axis declarations register their JSON encoding with python_introspect's
to_jsonable (openhcs/serialization/json.py moved there in L2). Submodules point
at python-introspect 0.2.3 (AnnotationChoices) and pyqt-reactive 0.3.28; pins
raised to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- RuntimeArtifactAddress.from_dict decodes axis names through the active
  family (live measurement previews failed with "'channel' is not an axis").
- Pattern matching rejects non-axis variable components instead of silently
  matching nothing.
- Axis declarations register under their boundary name, so python-introspect's
  to_jsonable spells them by name with no extra registration; form choice sets
  move to openhcs.core.axis_boundaries so importing openhcs (and
  openhcs.desktop_deployment) no longer loads python_introspect.
- Stale tests updated to axis classes; the MCP contract fixture is regenerated
  for the per-role viewer mode fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim merged commit 2897dca into main Oct 10, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants