Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cda11dc
fix(py,ts): correct the v0.4 axis model — optional `type`, unique names
vboussot Jul 27, 2026
3287a21
feat(py): add the OME-Zarr 1.0-DEV version and its metadata model
vboussot Jul 27, 2026
900bfb0
feat(py): gate the RFC-3 axis model on OME-Zarr 1.0-DEV
vboussot Jul 27, 2026
4476752
feat(ts): mirror the 1.0-DEV version gate in the TypeScript port
vboussot Jul 27, 2026
b9c9d13
fix(py,ts): address 1.0-DEV review feedback on the axis model
vboussot Aug 4, 2026
874c0c3
fix(py): require 2 or 3 'space' axes below 1.0-DEV, matching the TS rule
vboussot Aug 4, 2026
6c9a7d3
refactor(py,ts): rename the OME-Zarr 1.0-DEV model to 0.9.dev1
vboussot Aug 20, 2026
fc34da0
fix(py,ts): let a v0.6 array coordinate system satisfy the spatial-ax…
vboussot Aug 20, 2026
ed15671
fix(py,ts): describe the write gate accurately and lock the RFC-3 ver…
vboussot Aug 20, 2026
f473718
feat(py): offer 0.9.dev1 as an `ngff-zarr upgrade` target
vboussot Aug 20, 2026
deb9068
docs: describe the opt-in 0.9.dev1 version
vboussot Aug 20, 2026
d993b9f
chore(py,ts): complete the 0.9.dev1 type hints and drop a duplicated …
vboussot Aug 20, 2026
7db1eb5
fix(ts): report the read version and gate the axes the writer serializes
vboussot Aug 20, 2026
9510791
fix(py,ts): raise on a non-canonical axis class order like every othe…
vboussot Aug 20, 2026
b61273c
test(py,ts): lock the refusal of a non-canonical axis class order
vboussot Aug 20, 2026
650d797
fix(py): restore axis-names-unique in the rule table and pin both tables
vboussot Aug 20, 2026
8cc1a64
fix(py): keep the axis type vocabulary in the 0.9.dev1 model
vboussot Aug 20, 2026
a3e8190
fix(ts): validate a store against the version it declares
vboussot Aug 20, 2026
267fc96
fix(py,ts): gate the axes each target version writes, with one message
vboussot Aug 20, 2026
0f08d17
test(py,ts): lock axis-names-unique's position in the evaluation order
vboussot Aug 20, 2026
7652f15
fix(py,ts): type the axis unit as the vocabulary or any string
vboussot Aug 24, 2026
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ The main Python package provides:
- Optional OME-Zarr data model validation during reading
- Writes OME-Zarr v0.4 to v0.6
- v0.6 adds RFC-5 coordinate systems and transformations
- Reads and writes the opt-in development version `0.9.dev1`, which adopts RFC-3
(expanded axis count, names, types and order)
- [Sharded Zarr] stores
- Optional writing via zarr-python 2, zarr-python 3, [tensorstore] or zarrita (TypeScript)
- [Anatomical orientation metadata](./docs/rfc4.md) (RFC-4)
Expand All @@ -67,7 +69,7 @@ The TypeScript package provides universal OME-Zarr support for modern JavaScript
- 🦕 **Deno-first** with first-class TypeScript support
- 📦 **Universal compatibility** - Works in Deno, Node.js, and browsers
- 🔍 **Type-safe** with Zod schema validation
- 🗂️ **OME-Zarr v0.4, v0.5, and v0.6** support (v0.6 adds RFC-5 coordinate systems and transformations)
- 🗂️ **OME-Zarr v0.4, v0.5, and v0.6** support (v0.6 adds RFC-5 coordinate systems and transformations), plus the opt-in development version `0.9.dev1` which adopts RFC-3 (expanded axis count, names, types and order)
- 🌐 **Web ready** - No filesystem dependencies, works with remote stores
- 🏗️ **Mirrors Python API** - Familiar interfaces for Python users
- 📚 **Lazy loading** - Efficient handling of large datasets
Expand Down
6 changes: 4 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,10 @@ ngff-zarr upgrade src.zarr -o dst.zarr --to 0.5
```

The target version is selected with `--to` (alias `--version`), one of `0.4`,
`0.5`, or `0.6` (default `0.6`). Add `--validate` to validate the source
metadata against the NGFF schema while reading. For the write-to-new-store mode,
`0.5`, `0.6`, or `0.9.dev1` (default `0.6`). `0.9.dev1` is the development
version that adopts RFC-3; OME publishes no JSON Schema for it yet, so
`--validate` cannot check a store at that version. Add `--validate` to validate
the source metadata against the NGFF schema while reading. For the write-to-new-store mode,
`--overwrite` (the default) replaces any pre-existing data at the output store,
while `--no-overwrite` refuses to; both flags are ignored for an in-place
upgrade, which never overwrites array data.
Expand Down
8 changes: 5 additions & 3 deletions docs/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ To read an OME-Zarr file, use [`from_ngff_zarr`], which returns the
>>> multiscales = nz.from_ngff_zarr('cthead1.ome.zarr')
```

OME-Zarr version 0.1 to 0.6 is supported. Version 0.6 adds RFC-5 coordinate systems and transformations.
OME-Zarr version 0.1 to 0.6 is supported. Version 0.6 adds RFC-5 coordinate systems and transformations. The opt-in development version `0.9.dev1` additionally adopts RFC-3, that expands supported axis counts, names, types and order.

## OME-Zarr Zip (.ozx) files

Expand Down Expand Up @@ -544,8 +544,10 @@ boundary (0.5/0.6 to 0.4) -- cannot preserve chunk keys and raises a

**Write-to-new-store.** When an `output` store distinct from `input` is given,
the source is read lazily and re-written to `output` at the requested version
through the standard write pipeline. Every supported transition (0.4, 0.5, 0.6,
in either direction) works in this mode, and the source store is never erased.
through the standard write pipeline. Every transition among 0.4, 0.5, 0.6 and
0.9.dev1 works in this mode whenever the target version can express the axis
model, and the source store is never erased. An RFC-3 axis model is refused
below 0.9.dev1, so a store using one only converts upward.

Upgrade a 0.5 store to 0.6 in place, keeping every array chunk:

Expand Down
5 changes: 5 additions & 0 deletions docs/spec_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ supported by `ngff-zarr`.
- **OME-Zarr v0.4 to v0.6**: Writes OME-Zarr versions 0.4 to 0.6, including
RFC-4 anatomical orientation. v0.6 additionally adds RFC-5 coordinate systems
and transformations.
- **OME-Zarr 0.9.dev1**: Reads and writes the development version that adopts
RFC-3, which extends support for the number, names, types and order
of axes. It is opt-in: pass `version="0.9.dev1"` explicitly. The default
target is unchanged, and OME publishes no JSON Schema for it yet, so schema
validation is unavailable at that version.

## High Content Screening (HCS)

Expand Down
14 changes: 8 additions & 6 deletions docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ NGFF-Zarr provides a TypeScript implementation for working with OME-Zarr data st
- 🦕 **Deno-first**: Built for Deno with first-class TypeScript support
- 📦 **Universal compatibility**: Works in Deno, Node.js, and browsers
- 🔍 **Type-safe**: Full TypeScript support with Zod schema validation
- 🗂️ **OME-Zarr support**: Read and write OME-Zarr v0.4, v0.5, and v0.6 (v0.6 adds RFC-5 coordinate systems and transformations)
- 🗂️ **OME-Zarr support**: Read and write OME-Zarr v0.4, v0.5, and v0.6 (v0.6 adds RFC-5 coordinate systems and transformations), plus the opt-in development version `0.9.dev1` which adopts RFC-3 (extended axis count, names, types and order)
- 🧪 **Well-tested**: Comprehensive test suite with browser validation
- 🏗️ **Mirrors Python API**: Familiar interfaces for Python users
- 📖 **Lazy loading**: Efficient handling of large datasets
Expand Down Expand Up @@ -378,7 +378,7 @@ async function fromNgffZarr(
store: string | MemoryStore | FetchStore,
options?: {
validate?: boolean;
version?: "0.4" | "0.5" | "0.6";
version?: "0.4" | "0.5" | "0.6" | "0.9.dev1";
}
): Promise<NgffMultiscales>
```
Expand Down Expand Up @@ -414,7 +414,7 @@ async function toNgffZarr(
store: string,
multiscales: NgffMultiscales,
options?: {
version?: "0.4" | "0.5" | "0.6";
version?: "0.4" | "0.5" | "0.6" | "0.9.dev1";
chunksPerShard?: number | number[] | Record<string, number>;
}
): Promise<void>
Expand Down Expand Up @@ -809,7 +809,7 @@ function upgradeOmeZarr(
input: string | MemoryStore | FetchStore | Readable,
options?: {
output?: string | MemoryStore; // FetchStore is read-only, not a destination
version?: "0.4" | "0.5" | "0.6"; // default "0.6"
version?: "0.4" | "0.5" | "0.6" | "0.9.dev1"; // default "0.6"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
validate?: boolean;
overwrite?: boolean; // write-to-new-store only; default true
},
Expand Down Expand Up @@ -864,8 +864,10 @@ await upgradeOmeZarr(store, { version: "0.6" });

**Write-to-new-store.** When `output` is a store distinct from `input`, the
source is read lazily and re-written to `output` at the requested version
through the standard write pipeline. Every supported transition (0.4, 0.5, 0.6,
in either direction) works in this mode, and the source store is never mutated.
through the standard write pipeline. Every transition among 0.4, 0.5, 0.6 and
0.9.dev1 works in this mode whenever the target version can express the axis
model, and the source store is never mutated. An RFC-3 axis model is refused
below 0.9.dev1, so a store using one only converts upward.

```typescript
import { upgradeOmeZarr, type MemoryStore } from "@fideus-labs/ngff-zarr";
Expand Down
18 changes: 12 additions & 6 deletions docs/validation/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ from ngff_zarr import (
)
```

`validate_structural(metadata, options=None)` runs the image/multiscales rules.
When `options` is `None` it uses `ValidateOptions()`, i.e.
`ValidationLevel.STRICT`. A `ValidationError` carries `.rule` (a `SpecRule`),
`validate_structural(metadata, options=None, version=None)` runs the
image/multiscales rules. When `options` is `None` it uses `ValidateOptions()`,
i.e. `ValidationLevel.STRICT`. `version` is the OME-Zarr version the metadata
declares; the axis rules are inert for the versions that adopt the RFC-3 axis
model (see [[parity]]), so omitting it holds every store to the v0.4 axis
caps. A `ValidationError` carries `.rule` (a `SpecRule`),
`.message` (str), and `.location` (`str | None`); `str(exc)` is
`Spec rule [<rule>] violated: <message>`.

Expand Down Expand Up @@ -97,9 +100,12 @@ import {
} from "@fideus-labs/ngff-zarr";
```

`validateStructural(metadata, options?)` runs the image/multiscales rules. When
`options` (or its `level`) is omitted, the level resolves to
`ValidationLevel.Strict`. A `ValidationError` carries a readonly `rule`
`validateStructural(metadata, options?, version?)` runs the image/multiscales
rules. When `options` (or its `level`) is omitted, the level resolves to
`ValidationLevel.Strict`. `version` is the OME-Zarr version the metadata
declares; the axis rules are inert for the versions that adopt the RFC-3 axis
model (see [[parity]]), so omitting it holds every store to the v0.4 axis
caps. A `ValidationError` carries a readonly `rule`
(`SpecRule`) and an optional `location` (`string`); its `message` is
`Spec rule [<rule>] violated: <message>`.

Expand Down
79 changes: 49 additions & 30 deletions docs/validation/parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ for usage, see [[api]].

## The contract

Both ports must agree on four observable dimensions:
Both ports must agree on five observable dimensions:

1. **Rule identifiers** — the same `SpecRule` string values, in the same
canonical declaration/iteration order.
Expand All @@ -38,6 +38,10 @@ Both ports must agree on four observable dimensions:
(`validate_structural` / `validateStructural`) evaluates rules in the same
canonical order, so the same metadata yields the same first violation in
both languages.
5. **The RFC-3 version set** — the four axis rules take a `version` and are
inert for the versions that adopt the RFC-3 free-form axis model. Both ports
must treat exactly the same version strings as RFC-3, or the same metadata
validates in one language and not the other.

Because both test suites assert these facts against the **same literal
identifier list**, adding, removing, renaming, or reordering a rule — or
Expand All @@ -53,23 +57,34 @@ The canonical `SpecRule` set, in evaluation order:
1. `axis-count`
2. `axis-type`
3. `axis-order`
4. `scale-length-mismatch`
5. `global-coord-transform-after-per-level`
6. `dataset-order-highest-to-lowest`
7. `omero-channel-color-format`
8. `axis-orientation-anatomical-type`
9. `axis-orientation-on-non-space`
10. `axis-orientation-unique-axis`
11. `zarr-format`
12. `ome-namespace`
13. `plate-row-index-consistency`
14. `well-acquisition-missing`

Entries 11–12 are the v0.5 namespacing rules; they fire only for v0.5 metadata
4. `axis-names-unique`
5. `scale-length-mismatch`
6. `global-coord-transform-after-per-level`
7. `dataset-order-highest-to-lowest`
8. `omero-channel-color-format`
9. `axis-orientation-anatomical-type`
10. `axis-orientation-on-non-space`
11. `axis-orientation-unique-axis`
12. `zarr-format`
13. `ome-namespace`
14. `plate-row-index-consistency`
15. `well-acquisition-missing`

Entries 12–13 are the v0.5 namespacing rules; they fire only for v0.5 metadata
and are inert for v0.4. Each suite pins this list as a `CANONICAL_SPEC_RULE_IDS`
literal — byte-identical between the two languages so the tests are
line-for-line comparable.

The versions that adopt the RFC-3 axis model are pinned the same way, as a
`CANONICAL_RFC3_VERSIONS` literal:

1. `0.9.dev1`

Rules 1–3 (and the spatial arm of 3) are inert at those versions and enforced
at every other. Rule 4, `axis-names-unique`, is never inert: RFC-3 *adds* it,
and ngff-zarr applies it at all versions as a strictness choice (see
[[rule-reference]]).

## The parity tests

| Language | Test file |
Expand All @@ -93,17 +108,21 @@ Each suite independently locks:
`strict`, and `strict` is the default.
- **Fail-fast evaluation order** — driving the orchestrator with v0.4 metadata
that violates the earliest rule plus every later rule, then repairing exactly
one rule per stage across ten stages, the rule raised at each stage builds a
sequence equal to a shared `EXPECTED_EVALUATION_ORDER`. This proves every rule
is evaluated strictly before all rules after it; a final, fully-repaired
one rule per stage across eleven stages, the rule raised at each stage builds
a sequence equal to a shared `EXPECTED_EVALUATION_ORDER`. This proves every
rule is evaluated strictly before all rules after it; a final, fully-repaired
metadata is accepted with no violation.

The two v0.5 namespacing rules (`zarr-format`, `ome-namespace`) run last in the
image orchestrator but are inert for the v0.4 metadata the order test exercises,
so they never appear in `EXPECTED_EVALUATION_ORDER`. The two HCS rules
(`plate-row-index-consistency`, `well-acquisition-missing`) are deliberately
absent from the image orchestrator's evaluation order; they are dispatched by
the separate plate/well orchestrators (see [[rule-reference]]).
- **RFC-3 version set** — the axis rules are inert at exactly the versions in
`CANONICAL_RFC3_VERSIONS` and enforced at every other supported version and
when no version is given, asserted through the public orchestrator rather
than through the internal predicate.

Four of the fifteen rules never appear in `EXPECTED_EVALUATION_ORDER`, each for
its own reason. The two v0.5 namespacing rules (`zarr-format`, `ome-namespace`)
run last in the image orchestrator but are inert for the v0.4 metadata the order
test exercises. The two HCS rules (`plate-row-index-consistency`,
`well-acquisition-missing`) are deliberately absent; they are dispatched by the
separate plate/well orchestrators (see [[rule-reference]]).

## Sanctioned TypeScript-only adaptations

Expand All @@ -115,12 +134,12 @@ departures in the TypeScript suite:
Python an `UPPER_SNAKE` `str, Enum` (`SpecRule.AXIS_COUNT`,
`ValidationLevel.STRICT`). The `.value` strings are identical, so the
cross-language assertions hold.
- **Axis-name labels and the default check** — TypeScript's ordering fixtures
use valid `SupportedDims` members in place of the Python helper's free-form
axis names, and assert the `strict` default *observably* (because the
TypeScript options type is a bare interface with no constructor) rather than
through a constructable options object. Neither alters the observed rule set
or evaluation order.
- **The default check** — TypeScript asserts the `strict` default *observably*,
because its options type is a bare interface with no constructor, rather than
through a constructable options object. This alters neither the observed rule
set nor the evaluation order. The ordering fixtures themselves are identical
in both ports: `Axis.name` is `AxisName`, so TypeScript uses the same
free-form axis names as Python.

Internal message-fidelity helpers in the TypeScript port (rendering whole-number
scales with a trailing `.0`, Python-style quoted name lists, and `repr`-style
Expand Down
Loading