Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ A lean and kind
- Zarr I/O backed by [zarrista] / Rust [zarrs] (Python) or zarrita
(TypeScript) -- no zarr-python dependency; OME-Zarr 0.4 (Zarr v2) outputs
stay readable by zarr-python 2 and 3, and 0.5+ (Zarr v3) by zarr-python 3
- [Arbitrary axis dimensions, names, types, and ordering](./rfc3.md) (RFC-3)
- [Anatomical orientation metadata](./rfc4.md) (RFC-4)
- [Coordinate systems and transformations](./rfc5.md) (RFC-5)
- **OME-Zarr Zip (.ozx) file support** for single-file OME-Zarr datasets (RFC-9)
Expand All @@ -50,6 +51,7 @@ python.md
typescript.md
cli.md
mcp.md
rfc3.md
rfc4.md
rfc5.md
hcs.md
Expand Down
127 changes: 127 additions & 0 deletions docs/rfc3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!-- SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC -->
<!-- SPDX-License-Identifier: MIT -->
# RFC-3: More Dimensions for Thee

[RFC-3] removes the historical OME-Zarr restrictions on the number, names,
types, and ordering of axes. An OME-Zarr image is no longer required to be a
2-to-5-dimensional `TCZYX`-style array: it may have any number of axes, in any
order, with arbitrary names and types.

## What changed

RFC-3 removes these restrictions:

- axis count must be between 2 and 5;
- at most 5 dimensions;
- exactly 2 or 3 `space` axes;
- at most one `time` axis;
- at most one `channel` (or custom) axis;
- axes ordered `time`, then `channel`/custom, then `space`;
- three spatial axes ordered `z, y, x`.

The following rules still apply (MUST):

- the number of axis names matches the array's dimensionality;
- axis names are unique within a dataset;
- the axis order matches the data order (preserved on read and write);
- every array in a multiscale image has the same dimensionality and datatype;
- each `scale`/`translation` vector has one entry per axis.

Naming conventions become recommendations (SHOULD), not requirements: two
spatial axes should still be named `y, x`; three should be `z, y, x`; a single
time axis should be `t`.

## Support in ngff-zarr

RFC-3 is scoped to **OME-Zarr `0.9.dev1`**. Versions 0.4, 0.5 and 0.6 keep the
existing restrictions: their bundled `axes` schemas all cap the axis count at 5
and require 2-3 `space` axes, so v0.6 is RFC-5, not RFC-3.

The in-memory model is unrestricted; only serialization is gated:

- the **in-memory model** is free-form at every version;
- **writing** is version-gated -- serializing an RFC-3 axis model to 0.4, 0.5 or
0.6 raises, naming the offending axes and pointing at `0.9.dev1`;
- **reading** stays permissive, so a non-conformant store can still be loaded
and inspected.

```python
import ngff_zarr

ngff_zarr.to_ngff_zarr("out.ome.zarr", six_axis_multiscales, version="0.5")
# ValueError: Cannot write OME-Zarr version="0.5": this axis model violates
# that version's [axis-count] rule ... Pass version="0.9.dev1" to write it.

ngff_zarr.to_ngff_zarr("out.ome.zarr", six_axis_multiscales, version="0.9.dev1")
```

`0.9.dev1` is opt-in: `NgffVersion.LATEST` remains `0.6.dev4`, so no caller's
default target changes. OME publishes no `0.9.dev1` JSON Schema, so none is
bundled and `validate=True` reports that explicitly; `validate_structural()`
carries the structural rules.

Reading and structural validation accept RFC-3 datasets. The exact axis order,
names, and types are preserved on read:

```python
import ngff_zarr

# A 6-dimensional image, a 1-D signal, or a non-TCZYX ordering all read back
# with their axes intact -- nothing is squeezed, transposed, or renamed.
multiscales = ngff_zarr.from_ngff_zarr("ramp_6d.ome.zarr")
print(multiscales.images[0].dims) # e.g. ('a', 'b', 'c', 'd', 'e', 'f')
```

Structural validation still enforces the surviving MUSTs, so duplicate axis
names are rejected:

```python
from ngff_zarr import validate_structural, SpecRule
from ngff_zarr.structural_validation import ValidationError

try:
validate_structural(metadata_with_repeated_axis_name)
except ValidationError as error:
assert error.rule == SpecRule.AXIS_NAMES_UNIQUE
```

The TypeScript package mirrors this behaviour: `validateStructural` applies the
same rules, and `SpecRule.AxisNamesUnique` is the axis-level rule that survives.

## Operation-level limitations

RFC-3 relaxes what a *valid dataset* may look like; it does not require every
operation to support every dimensionality. Building a multiscale pyramid
(`to_multiscales`) still downsamples spatial axes and therefore needs to
recognise them by the conventional `x`/`y`/`z` names. When an operation cannot
process a given set of axes it fails with an explicit error describing the axes
it received; the metadata reader and validator never reject a valid RFC-3
dataset merely because a downstream algorithm cannot process it.

## Reference data and known gaps

The official RFC-3 sample datasets are generated by
[`clbarnes/ome-zarr-rfc3-data`](https://github.com/clbarnes/ome-zarr-rfc3-data)
(`ecg_1d`, `astronaut_xcy`, `ramp_6d`). There is no upstream conformance suite or
validator; an opt-in driver, `py/test/rfc3_conformance.py`, checks these datasets
against an authored manifest (`--data-dir`; never run by the default test suite,
never downloads).

On a copy retagged `0.9.dev1`, `ecg_1d` (1-D), `astronaut_xcy` (non-TCZYX
order), and `ramp_6d` (6-D, type-less axes) all read at full dimensionality,
validate, and preserve axis order. The raw datasets do not read at all: they
declare `0.5+rfc3`, a **version string** no specification defines and which
ngff-zarr rejects.

The driver reports a separate pass over a copy retagged `0.9.dev1`. At `0.9.dev1`
those axis models are conformant, whereas 0.5 rejects a 6-D image outright, so
declaring `0.5` would claim conformance to a version the data violates.

## Axis type

RFC-3 states no requirement that an axis carry a `type` field, and the official
`ramp_6d` reference dataset uses type-less axes, so a name-only axis is read with
`type` `None`. Custom, non-standard `type` strings (anything other than
`space`/`time`/`channel`) are also accepted.

[RFC-3]: https://ngff.openmicroscopy.org/rfc/3/index.html
Loading
Loading