Skip to content

OME 0.6: validate spec MUST rules (error on write, warn on read) - #111

Closed
konstibob wants to merge 1 commit into
zarr-developers:mainfrom
konstibob:feat/ome-v06-validation
Closed

konstibob wants to merge 1 commit into
zarr-developers:mainfrom
konstibob:feat/ome-v06-validation

Conversation

@konstibob

Copy link
Copy Markdown
Contributor

PR 3 of 3 for OME-NGFF 0.6 spec compliance.

  • PR 1: transform input/output become {name, path} objects instead of Strings. After it merges, OmeValidator.outputName(...), the only helper that interprets output references, needs a one-line change to read getOutput().name.
  • PR 2: keeps metadata fields that are currently dropped (labels, image-label, longName, interpolation, projectAxis, series).

This PR adds dev.zarr.zarrjava.experimental.ome.v0_6.OmeValidator. It checks the MUST-level rules of the 0.6 spec and returns a list of readable violations. Nothing validated metadata before this.

Strictness policy

  • On write (MultiscaleImage.create, createScaleLevel, Plate.createPlate, Well.createWell, Scene.createScene), a ZarrException is thrown that lists every violation. Nothing is written when validation fails. In createScaleLevel, the array is now created after validation, not before.
  • On read (openMultiscaleImage, openPlate, openWell, openScene, and Plate.openWell for the plate-dependent acquisition check), violations are logged as one java.util.logging warning per node, and the node still opens.
  • MultiscaleImage.create accepts an empty datasets list, because levels are added afterwards with createScaleLevel. Opening an image requires datasets to be non-empty.

Rules

Coordinate systems (in multiscales and scene)

  • name is non-empty and unique within its coordinateSystems array.
  • Axis names are non-empty and unique within a coordinate system.

Multiscales

  • coordinateSystems is present and non-empty. If an entry uses legacy axes instead, it is reported, and the axis rules are applied to axes.
  • Every coordinate system has 2 to 5 axes: 2 or 3 of type space, at most one time, and at most one channel/custom/null-type axis.
  • Axes are ordered time, then channel/custom, then space.
  • datasets is present and non-empty. Each dataset has a path and coordinateTransformations.
  • The dataset transform list is exactly one of: a single scale, a single identity, or a single sequence of one scale followed by one translation.
  • The scale and translation vectors have the same length as the number of axes in the intrinsic (output) coordinate system.
  • All datasets have the same output, and that output names a coordinate system of the multiscales. A missing output is tolerated, because createScaleLevel currently writes transforms without input/output (PR 1 territory).
  • In createScaleLevel: the array's ndim equals the number of intrinsic axes, and all levels share one dtype and ndim. Existing levels are opened to compare against.

Transforms (checked recursively in datasets, multiscales-level and scene transforms)

  • rotation is N×N, orthonormal, and has det ≈ 1 (tolerance 1e-6).
  • All affine rows have the same length (N+1, with N ≥ 1).
  • mapAxis is a permutation of 0..N-1.
  • In byDimension, each output axis appears in exactly one child's outputAxes.
  • A sequence has a non-empty transformations list.

Plate

  • Row and column names are alphanumeric and unique.
  • Each well's path equals rows[rowIndex].name + "/" + columns[columnIndex].name, and both indices are in range.
  • Acquisition ids are unique and ≥ 0. maximumfieldcount and field_count are positive if present.

Well

  • Image paths are unique and non-empty, contain no /, are not only periods, do not start with __, and use only [A-Za-z0-9._-].
  • If the plate has more than one acquisition, each image needs an acquisition. An acquisition that is present must match a plate acquisition id. This is only checked when the plate is known (Plate.openWell, or validateWell(well, plate)).

Scene

  • coordinateTransformations is present.
  • The coordinate system and transform rules above also apply.

Out of scope: omero (it is transitional and already warns). Also out of scope: the fields PR 2 adds.

TODO: label images must use integer dtypes. This isn't checked because main has no API for creating or opening label images. It fits naturally once PR 2's labels / image-label support has landed.

Test data

No existing test data had to change, and all existing OME tests pass unchanged.

Some upstream fixtures break MUST rules. They still open, with warnings:

  • 2d|3d/axis_dependent/mapAxis.zarr, 2d/axis_dependent/byDimension.zarr, 2d|3d/nonlinear/{coordinates,displacements,invDisplacements}.zarr: a secondary coordinate system has only array-typed axes. The spec applies the axis rules to all coordinate systems inside multiscales.
  • user_stories/image_registration_3d.zarr/{FCWB,JRC2018F}: the dataset output names a coordinate system that isn't declared. The declared system is physical.

Unrelated to this PR: 3d/axis_dependent/byDimension.zarr already fails to deserialize on main. It uses inputAxes/outputAxes with axis names, while the model expects input_axes/output_axes with ints.

Tests

OmeValidatorV06Test (29 tests) has:

  • a valid case and an invalid case for each rule
  • write-rejection tests for all five create methods, which check the exception message
  • a test that opens the 2d/3d/organ-atlas images, the known-violating fixtures and the v0.6_scene fixtures, and checks that a warning is logged

mvn test -Dtest='OmeZarr*Test,OmeObjectMappersTest,*Validator*': 103 tests, 0 failures.

🤖 Generated with Claude Code

Add v0_6.OmeValidator covering coordinate systems, multiscales axes and
datasets, scale-level arrays, transformations, plate, well and scene
metadata. The v0.6 create methods throw a ZarrException listing all
violations; the open methods log them as warnings and still open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@konstibob

Copy link
Copy Markdown
Contributor Author

Closing: validation isn't needed for now.

@konstibob konstibob closed this Sep 24, 2026
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