Skip to content

Add OME-NGFF version selector and omero rendering metadata - #240

Open
aliddell wants to merge 6 commits into
acquire-project:mainfrom
aliddell:ome-version-selector-and-omero
Open

aliddell wants to merge 6 commits into
acquire-project:mainfrom
aliddell:ome-version-selector-and-omero

Conversation

@aliddell

Copy link
Copy Markdown
Member

Summary

First PR in a series adding support for newer OME-NGFF features. This one adds:

  • OME-NGFF version selector — ZarrOMEVersion (0.5 default, 0.6 opt-in) on ZarrStreamSettings, driving the emitted ome.version. 0.5 output is unchanged by default.
  • omero rendering metadata — per-image ZarrOMERenderingSettings (channels: label/color/window/active/family/coefficient/inverted, plus rdefs and id/name) on ZarrArraySettings, emitted as ome.omero so viewers (napari, vizarr, neuroglancer) render channels correctly.

Details

  • C API (zarr.types.h, acquire.zarr.h, acquire.zarr.cpp): the enum, omero structs, and ZarrOMERenderingSettings_create/destroy_channel_array helpers.
  • Core (array.base.*, zarr.stream.*, multiscale.array.cpp): version threaded through ArrayConfig; omero copied to an internal representation and emitted in group metadata. Presence of omero promotes an otherwise-flat array to an OME image group (single-level multiscales + omero), so the metadata isn't silently dropped; existing flat-array output is unchanged. HCS plate/well sub-spec versions are intentionally left at 0.5 (independently versioned).
  • Config files (settings.io.cpp): YAML/JSON load, dump, and free round-trip for both new fields.
  • Python (acquire-zarr-py.cpp, __init__.pyi): OMEVersion enum and OMEWindow/OMEChannel/OMERenderingDefs/OMERenderingSettings classes. Nested objects are mutable in place (opaque channels vector; shared_ptr-held nullable objects), so array.omero.channels[0].label = "x", .channels.append(...), and .window.max = ... all persist.

Test plan

  • C++ integration: stream-omero-and-ome-version (omero emission + 0.5/0.6 selection) and extended stream-from-config-file (omero + ome_version round-trip through YAML/JSON dump→reload).
  • Python: test_settings.py (config round-trip, in-place mutation, optional/identity) and test_stream.py (omero metadata + version selector).
just test-cpp
uv run pytest python/tests/test_settings.py python/tests/test_stream.py

Relying on CI to confirm the full build across platforms.

Follow-ups (series)

Phase 1b (labels/image-label), 1c (translation transforms), then Phase 2 (RFC-5 coordinate systems & transformations, 0.6).

🤖 Generated with Claude Code

aliddell and others added 5 commits July 30, 2026 15:22
Introduce ZarrOMEVersion (0.5 default, 0.6 opt-in) on ZarrStreamSettings
and per-image "omero" rendering metadata (ZarrOMERenderingSettings) on
ZarrArraySettings. Presence of omero promotes an array to an OME image
group, so it gains a multiscales block plus the omero block.

- C API: ZarrOMEVersion enum, omero structs, channel-array alloc/free
  helpers.
- Core: ome.version driven by the selector; omero emitted in group
  metadata. HCS plate/well sub-spec versions left at 0.5 (independent).
- Config files: YAML/JSON load, dump, and free round-trip for both.
- Python: OMEVersion enum and OMEWindow/OMEChannel/OMERenderingDefs/
  OMERenderingSettings classes with in-place mutation (opaque channels
  vector, shared_ptr-held nullable objects).
- Tests: C++ integration (omero + version, config round-trip) and
  Python settings/stream coverage.

First PR in a series adding newer OME-NGFF feature support.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The omero id is an image identifier that every other producer writes as a
JSON integer, so model it as uint32_t + has_id rather than a string.

Reject settings that would emit unusable rendering metadata: an omero block
with no channels, a channel count that disagrees with the Channel dimension,
and a zero-initialized window (which would write a blank display range).
Loaded arrays were allocated with malloc/calloc but the public
ZarrXxx_create_*_array helpers use new[], so a caller replacing one with the
other crossed allocators. Use new[]/delete[] on both paths.

An unquoted YAML scalar like 'ome_version: 0.6' decodes as a double, which
as_string rejected; accept floats via dump() for the shortest round-trip form.
RFC-2 moved the OME-Zarr metadata version up to a single ome.version key; the
0.5 prose that still required a version inside the plate and well dicts was a
spec bug, removed upstream in ome/ngff-spec#84 (see ome/ngff#309). The 0.5
schemas and normative examples carry no inner version key.

The group-level ome.version must be consistent within a hierarchy, so plate
and well groups now emit the stream's selected version instead of a hardcoded
0.5.
The HCS integration tests assert the exact number of fields in the plate dict,
which is now 6 rather than 7.
@aliddell
aliddell requested a review from jeskesen August 28, 2026 12:12

This branch has not been deployed

No deployments
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.

1 participant