Skip to content

abczarr.ome.base

The version-independent OME-Zarr metadata model.

OME-Zarr, the NGFF specification, is a metadata convention for bioimaging data stored in Zarr. It describes multiscale image pyramids, high-content screening plates, segmentation labels, and rendering settings, all as JSON attached to a Zarr group. abczarr models that metadata as typed classes under abczarr.ome. One package exists per NGFF version, v0_1 through v0_5, plus the 0.6 pre-release previews v0_6dev1 through v0_6dev4 and v0_6rc0.

The examples below target 0.5, the latest stable version. The Reference documents every version.

Describing a multiscale image

A multiscale image is a pyramid of resolution levels, each a Zarr array, described by Multiscale. It is built from a plain dict shaped like the JSON the spec defines:

>>> from abczarr.ome import v0_5
>>> multiscale = v0_5.Multiscale.from_json({
...     "name": "nucleus-stain",
...     "type": "gaussian",
...     "axes": [
...         {"name": "c", "type": "channel"},
...         {"name": "y", "type": "space", "unit": "micrometer"},
...         {"name": "x", "type": "space", "unit": "micrometer"},
...     ],
...     "datasets": [
...         {
...             "path": "0",
...             "coordinateTransformations": [
...                 {"type": "scale", "scale": [1.0, 0.325, 0.325]}
...             ],
...         },
...         {
...             "path": "1",
...             "coordinateTransformations": [
...                 {"type": "scale", "scale": [1.0, 0.65, 0.65]}
...             ],
...         },
...     ],
... })
>>> [axis.name for axis in multiscale.axes]
['c', 'y', 'x']
>>> [dataset.path for dataset in multiscale.datasets]
['0', '1']

Each axis in axes becomes an Axis, matched by its type field. The y and x axes in this example become SpaceAxis objects, since each carries type="space". Each entry in datasets becomes a Dataset. A dataset names an array and carries the Scale that places it in physical space, one value per axis, in that axis's unit.

A Multiscale describes the pyramid, not the whole group. Wrapping it in OMEImage produces the metadata an image group actually carries, optionally alongside Omero rendering settings:

from abczarr.ome import v0_5

image = v0_5.OMEImage(
    version="0.5",
    multiscales=[multiscale],
    omero=v0_5.Omero.from_json({
        "channels": [
            {
                "color": "00FF00",
                "window": {"start": 0, "end": 1500, "min": 0, "max": 65535},
            },
        ],
    }),
)

The same shape covers the other kinds of OME-Zarr group: ImageLabel for a segmentation, and Plate / Well for a high-content screen, wrapped in OMEImageLabel, OMEPlate and OMEWell.

Reading and writing OME metadata on a group

OME-Zarr metadata lives in a group's user attributes, the same attrs mapping any Zarr group exposes. NGFF 0.5 nests it all under one "ome" key, so an object round-trips through to_json and from_json like this:

group.attrs["ome"] = image.to_json()

loaded = v0_5.OMEImage.from_json(group.attrs["ome"])
loaded.multiscales[0].axes[0].name  # "c"

Earlier NGFF versions, 0.4 and before, write the same fields directly at the top level of attrs instead of nesting them under "ome": group.attrs.update(image.to_json()).

Converting between NGFF versions

OMEMetadata.to_version converts a piece of OME metadata, built against one NGFF version, to another. It works on the top-level container and on any nested piece of metadata alike:

>>> from abczarr.ome import v0_4
>>> old = v0_4.Multiscale.from_json({
...     "version": "0.4",
...     "axes": [
...         {"name": "y", "type": "space"},
...         {"name": "x", "type": "space"},
...     ],
...     "datasets": [
...         {
...             "path": "0",
...             "coordinateTransformations": [
...                 {"type": "scale", "scale": [1.0, 1.0]}
...             ],
...         }
...     ],
... })
>>> new = old.to_version("0.5")
>>> type(new).__module__
'abczarr.ome.v0_5.images'
>>> new.to_version("0.4") == old
True

Fields both versions share carry over unchanged. Converting forward, a field only the newer version has gets a reasonable default. Axes, for example, gained a type field in NGFF 0.4, defaulted from the axis's name. Converting back, that field is dropped. Converting to a version that would need information the source does not carry raises ValueError rather than guessing.

Example

>>> from abczarr.ome import v0_2
>>> untyped = v0_2.Multiscale.from_json({
...     "version": "0.2",
...     "name": "x",
...     "datasets": [{"path": "0"}],
... })
>>> untyped.to_version("0.3")
Traceback (most recent call last):
    ...
ValueError: cannot convert Multiscale from OME 0.2 to 0.3:
the target requires information OME 0.2 does not carry

Classes

OMEMetadata

OMEMetadata(*, extra_items: Iterable[tuple[str | bytes, Real | floating | bool | bool | int | str | bytes | Iterable[()] | None]] | Mapping[str | bytes, Real | floating | bool | bool | int | str | bytes | Iterable[()] | None] = FrozenDict({}))

Bases: FlexibleMetadata

The base of every OME-Zarr metadata class.

Every piece of OME-Zarr metadata is a subclass of this: a multiscale pyramid, an axis, a plate, a rendering setting, and so on. Each lives in the version package it belongs to, such as abczarr.ome.v0_5.images. Build one with from_json from the JSON an OME-Zarr group carries, or with keyword arguments matching its fields. to_json serializes it back to that same shape, and any key it does not recognize survives the round trip unchanged.

Use to_version to convert an object built against one NGFF version to another.

Methods:

to_version
to_version(version: str, policy: ConversionPolicy = 'warn') -> Self

Convert this OME metadata to another OME-NGFF version.

This method works on any piece of OME metadata, not only the top-level container. A Multiscale or an Omero converts just as well as an OMEImage. A field both versions carry is passed through unchanged. Converting forward, a field only the newer version has gets a reasonable default. Converting backward, that field is dropped.

Crossing the 0.5/0.6 boundary reshapes coordinate metadata. Converting forward, 0.5's per-multiscale axes and per-dataset scale and translation become 0.6's named coordinateSystems and general coordinate transformations. Converting backward, a 0.6 transformation the stable model cannot express, such as an affine or a rotation, is treated according to policy.

Between the 0.6 previews, a mapAxis or byDimension transformation switches between addressing axes by name and addressing them by index. Translating between axis names and axis indices needs the axes of the coordinate systems involved. A transformation converted on its own, apart from the multiscale that declares those coordinate systems, raises ValueError when that translation is needed. Later versions have no mapAxis counterpart for a 0.6.dev1 mapAxis that drops or repeats an input axis. Such a mapAxis becomes an equivalent affine that selects the same input coordinates. A projectAxis, which first appears in 0.6rc0, becomes an equivalent affine in earlier versions in the same way. Neither affine turns back into the original transformation when converted again.

Parameters:

Name Type Description Default
version str

The target OME-NGFF version, such as "0.4" or "0.6rc0".

required
policy ConversionPolicy

How to treat information the target version cannot hold: "lossy" drops it silently, "warn" (the default) drops it with one warning, and "strict" raises UnsupportedConversion.

'warn'

Raises:

Type Description
ValueError

If version names no known OME-NGFF version, or if converting to it would require information this object does not carry. Within the 0.6 previews, this includes the axes of a coordinate system that is not declared alongside the transformation. A byDimension whose transformations do not write every output axis exactly once also raises this error. So does a mapAxis or byDimension that refers to an axis its coordinate system does not have.

UnsupportedConversion

If policy is "strict" and a field or transformation cannot be represented in version.

OME

OME(version: str | bytes = '', *, extra_items: Iterable[tuple[str | bytes, Real | floating | bool | bool | int | str | bytes | Iterable[()] | None]] | Mapping[str | bytes, Real | floating | bool | bool | int | str | bytes | Iterable[()] | None] = FrozenDict({}))

Bases: OMEMetadata

The version-tagged, top-level OME-Zarr metadata for a group.

Every OME-Zarr group carries one of these: an image and its multiscale pyramid, a collection of labels, a plate, or a well. Each kind has its own subclass in every version's package. The version field records which NGFF version the metadata is written against, and with it what the rest of the fields mean.

Build the concrete class for the version in use instead of this one. See v0_5.OME and its siblings, including OMEImage and OMEPlate.

Parameters:

Name Type Description Default
version str

The OME-NGFF version the metadata is written against.

''

Attributes

version class-attribute instance-attribute
version: str = field(factory=False)

Methods:

from_json classmethod
from_json(data: Any) -> Self

Create an OME container from a JSON-serializable dict.

Called on a version's own class, such as v0_5.OME.from_json, this picks the right image, plate, well, or label subclass for the data, the same way any OME class does.

Called on this version-independent base class, it first reads the version field to decide which NGFF version the data belongs to, then hands the data to that version's own OME class. The base class cannot make that choice on its own, because every version's classes share it and it has no way to tell a v0.4 image from a v0.5 one. Metadata that carries no version field is therefore ambiguous and raises ValueError rather than being guessed at.

Raises:

Type Description
ValueError

If called on the version-independent base with data that has no version field, or a version that names no known OME-NGFF version.