Transitional metadata is added to the specification with the intention of removing it in the future. Implementations may be expected (MUST) or encouraged (SHOULD) to support the reading of the data, but writing will usually be optional (MAY). Examples of transitional metadata include custom additions by implementations that are later submitted as a formal specification. (See [bioformats2raw](bf2raw))
Some of the JSON examples in this document include comments. However, these are only for clarity purposes and comments MUST NOT be included in JSON objects. ## Storage format OME-Zarr is implemented using the Zarr format as defined by the [version 3 of the Zarr specification](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html). All features of the Zarr format including codecs, chunk grids, chunk key encodings, data types and storage transformers MAY be used with OME-Zarr unless explicitly disallowed in this specification. An overview of the layout of an OME-Zarr fileset should make understanding the following metadata sections easier. The hierarchy is represented here as it would appear locally but could equally be stored on a web server to be accessed via HTTP or in object storage like S3 or GCS. ### Images The following layout describes the expected Zarr hierarchy for images with multiple levels of resolutions and optionally associated labels. Note that the number of dimensions is variable between 2 and 5 and that axis names are arbitrary, see [multiscales metadata](#multiscales-md) for details. ```text βββ 123.zarr # One OME-Zarr image (id=123). β ... β βββ 456.zarr # Another OME-Zarr image (id=456). β βββ zarr.json # Each image is a Zarr group of other groups and arrays. β # Group level attributes are stored in the `zarr.json` file and include β # "multiscales" and "omero" (see below). β βββ 0 # Each multiscale level is stored as a separate Zarr array, β ... # which is a folder containing chunk files which compose the array. βββ n # The name of the array is arbitrary with the ordering defined by β β # by the "multiscales" metadata, but is often a sequence starting at 0. β β β βββ zarr.json # All image arrays must be up to 5-dimensional β β # with the axis of type time before type channel, before spatial axes. β β β ββ ... # Chunks are stored conforming to the Zarr array specification and β # metadata as specified in the array's `zarr.json`. β βββ labels β βββ zarr.json # The labels group is a container which holds an array of labels to make the objects easily discoverable β # All labels will be listed in `zarr.json` e.g. `{ "labels": [ "original/0" ] }` β # Each dimension of the label should be either the same as the β # corresponding dimension of the image, or `1` if that dimension of the label β # is irrelevant. β βββ original # Intermediate folders are permitted but not necessary and currently contain no extra metadata. β βββ 0 # Multiscale, labeled image. The name is unimportant but is registered in the "labels" group above. βββ zarr.json # Zarr Group which is both a multiscaled image as well as a labeled image. β # Metadata of the related image and as well as display information under the "image-label" key. β βββ 0 # Each multiscale level is stored as a separate Zarr array, as above, but only integer values βββ ... # are supported. ``` ### High-content screening The following specification defines the hierarchy for a high-content screening dataset. Three groups MUST be defined above the images: - the group above the images defines the well and MUST implement the [well specification](#well-md). All images contained in a well are fields of view of the same well - the group above the well defines a row of wells - the group above the well row defines an entire plate i.e. a two-dimensional collection of wells organized in rows and columns. It MUST implement the [plate specification](#plate-md) A well row group SHOULD NOT be present if there are no images in the well row. A well group SHOULD NOT be present if there are no images in the well. ```text . β βββ 5966.zarr # One OME-Zarr plate (id=5966) βββ zarr.json # Implements "plate" specification βββ A # First row of the plate β βββ zarr.json β β β βββ 1 # First column of row A β β βββ zarr.json # Implements "well" specification β β β β β βββ 0 # First field of view of well A1 β β β β β β β βββ zarr.json # Implements "multiscales", "omero" β β β βββ 0 # Resolution levels β β β βββ ... β β β βββ labels # Labels (optional) β β βββ ... # Other fields of view β βββ ... # Other columns βββ ... # Other rows ``` ## OME-Zarr Metadata (metadata)= The "OME-Zarr Metadata" contains metadata keys as specified below for discovering certain types of data, especially images. The OME-Zarr Metadata is stored in the various `zarr.json` files throughout the above array hierarchy. The OME-Zarr Metadata version MUST be consistent within a hierarchy. The group `attributes` MUST contain a key `ome`. The value of the `ome` key MUST be a JSON object that MUST contain a `version` key, the value of which MUST be a string specifying the version of the OME-Zarr specification defined by [this document](ngff-spec:spec:0.6.dev3). ```jsonc { // ... "attributes": { "ome": { "version": "0.6.dev3", // ... } } } ``` ### "coordinateSystems" metadata (coordinate-systems-md)= A `coordinateSystem` is a JSON object with a `name` field and a `axes` field. Every coordinate system: - MUST contain the field `name`. The value MUST be a non-empty string that is unique among all entries under `coordinateSystems`. - MUST contain the field `axes`, whose value is an array of valid `axes` (see below). The elements of `axes` correspond to the index of each array dimension and coordinates for points in that coordinate system. For the below example, the `x` dimension is the last dimension. The "dimensionality" of a coordinate system is indicated by the length of its `axes` array. The `volume_micrometers` example coordinate system below is three dimensional (3D). :::{dropdown} Example Coordinate Systems metadata example ```json { "name" : "volume_micrometers", "axes" : [ {"name": "z", "type": "space", "unit": "micrometer"}, {"name": "y", "type": "space", "unit": "micrometer"}, {"name": "x", "type": "space", "unit": "micrometer"} ] } ``` ::: The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate system's dimensions. Axis names may contain semantically meaningful information, but can be arbitrary. As a result, two coordinate systems that have identical axes in the same order may not be "the same" in the sense that measurements at the same point refer to different physical entities and therefore should not be analyzed jointly. Tasks that require images, annotations, regions of interest, etc., SHOULD ensure that they are in the same coordinate system (same name and location within the Zarr hierarchy, with identical axes) or can be transformed to the same coordinate system before doing analysis. See the [example below](#spec:example:coordinate_transformation). #### "axes" metadata `axes` describes the dimensions of a coordinate systems and adds an interpretation to the samples along that dimension. It is an array of objects, where each object describes a dimension (axis) and: - MUST contain the field `name` that gives the name for this dimension. The values MUST be unique across all `name` fields in the same coordinate system. - SHOULD contain the field `type`. It SHOULD be one of the strings `array`, `space`, `time`, `channel`, `coordinate`, or `displacement` but MAY take other string values for custom axis types that are not part of this specification yet. - MAY contain the field `discrete`. The value MUST be a boolean, and is `true` if the axis represents a discrete dimension (see below for details). - SHOULD contain the field `unit` to specify the physical unit of this dimension. The value SHOULD be one of the following strings, which are valid units according to UDUNITS-2. - Units for `space` axes: 'angstrom', 'attometer', 'centimeter', 'decimeter', 'exameter', 'femtometer', 'foot', 'gigameter', 'hectometer', 'inch', 'kilometer', 'megameter', 'meter', 'micrometer', 'mile', 'millimeter', 'nanometer', 'parsec', 'petameter', 'picometer', 'terameter', 'yard', 'yoctometer', 'yottameter', 'zeptometer', 'zettameter' - Units for `time` axes: 'attosecond', 'centisecond', 'day', 'decisecond', 'exasecond', 'femtosecond', 'gigasecond', 'hectosecond', 'hour', 'kilosecond', 'megasecond', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'petasecond', 'picosecond', 'second', 'terasecond', 'yoctosecond', 'yottasecond', 'zeptosecond', 'zettasecond' - MAY contain the field `longName`. The value MUST be a string, and can provide a longer name or description of an axis and its properties. The length of `axes` MUST be equal to the number of dimensions of the arrays that contain the image data. Arrays are inherently discrete (see Array coordinate systems, below) but are often used to store discrete samples of a continuous variable. The continuous values "in between" discrete samples can be retrieved using an *interpolation* method. If an axis is continuous (`"discrete" : false`), it indicates that interpolation is well-defined. Axes representing `space` and `time` are usually continuous. Similarly, joint interpolation across axes is well-defined only for axes of the same `type`. In contrast, discrete axes (`"discrete" : true`) may be indexed only by integers. Axes representing a channel, coordinate, or displacement are usually discrete. ```{note} The most common methods for interpolation are "nearest neighbor", "linear", "cubic", and "windowed sinc". Here, we refer to any method that obtains values at real-valued coordinates using discrete samples as an "interpolator". As such, label images may be interpolated using "nearest neighbor" to obtain labels at points along the continuum. ``` #### Array coordinate systems The dimensions of an array do not have an interpretation until they are associated with a coordinate system via a coordinate transformation. Nevertheless, it can be useful to refer to the "raw" coordinates of the array. Some applications might prefer to define points or regions-of-interest in "pixel coordinates" rather than "physical coordinates," for example. Indicating that choice explicitly will be important for interoperability. This is possible by using **array coordinate systems**. Every array has a default coordinate system whose parameters need not be explicitly defined. The dimensionality of each array coordinate system equals the dimensionality of its corresponding Zarr array. Its name is the path to the array in the container, its axes have `"type": "array"`, are unitless, and have default names. The i-th axis has `"name": "dim_i"` (these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). As with all coordinate systems, the dimension names must be unique and non-null. :::{dropdown} Example ```json { "arrayCoordinateSystem" : { "name" : "myDataArray", "axes" : [ {"name": "dim_0", "type": "array"}, {"name": "dim_1", "type": "array"}, {"name": "dim_2", "type": "array"} ] } } ``` For example, if 0/zarr.json contains: ```jsonc { "zarr_format": 3, "node_type": "array", "shape": [4, 3, 5], //... } ``` Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5. ::: The axes and their order align with the shape of the corresponding zarr array, and whose data depends on the byte order used to store chunks. As described in the [Zarr array metadata](https://zarr.readthedocs.io/en/stable/spec/v3.html#arrays), the last dimension of an array in "C" order are stored contiguously on disk or in-memory when directly loaded. The name and axes names MAY be customized by including a `arrayCoordinateSystem` field in the user-defined attributes of the array whose value is a coordinate system object. The length of `axes` MUST be equal to the dimensionality. The value of `type` for each object in the axes array MUST equal `"array"`. #### Coordinate convention **The pixel/voxel center is the origin of the continuous coordinate system.** It is vital to consistently define relationship between the discrete/array and continuous/interpolated coordinate systems. A pixel/voxel is the continuous region (rectangle) that corresponds to a single sample in the discrete array, i.e., the area corresponding to nearest-neighbor (NN) interpolation of that sample. The center of a 2d pixel corresponding to the origin `(0,0)` in the discrete array is the origin of the continuous coordinate system `(0.0, 0.0)` (when the transformation is the identity). The continuous rectangle of the pixel is given by the half-open interval `[-0.5, 0.5) x [-0.5, 0.5)` (i.e., -0.5 is included, +0.5 is excluded). See chapter 4 and figure 4.1 of the ITK Software Guide. ### bioformats2raw.layout (bf2raw)= [=Transitional=] `"bioformats2raw.layout` metadata identifies a group which implicitly describes a series of images. The need for the collection stems from the common "multi-image file" scenario in microscopy. Parsers like Bio-Formats define a strict, stable ordering of the images in a single container that can be used to refer to them by other tools. In order to capture that information within an OME-Zarr dataset, `bioformats2raw` internally introduced a wrapping layer. The bioformats2raw layout has been added to v0.4 as a transitional specification to specify filesets that already exist in the wild. An upcoming NGFF specification will replace this layout with explicit metadata. #### Layout (bf2raw-layout)= Typical Zarr layout produced by running `bioformats2raw` on a fileset that contains more than one image (series > 1): ```text series.ome.zarr # One converted fileset from bioformats2raw βββ zarr.json # Contains "bioformats2raw.layout" metadata βββ OME # Special group for containing OME metadata β βββ zarr.json # Contains "series" metadata β βββ METADATA.ome.xml # OME-XML file stored within the Zarr fileset βββ 0 # First image in the collection βββ 1 # Second image in the collection βββ ... ``` #### bf2raw-attributes (bf2raw-attributes-md)= The OME-Zarr Metadata in the top-level `zarr.json` file must contain the `bioformats2raw.layout` key: ```{literalinclude} examples/bf2raw/image.json :language: json ``` If the top-level group represents a plate, the `bioformats2raw.layout` metadata will be present but the `plate` key MUST also be present, takes precedence and parsing of such datasets should follow (see [plate metadata](#plate-md)). It is not possible to mix collections of images with plates at present. ```{literalinclude} examples/bf2raw/plate.json :language: json ``` The OME-Zarr Metadata in the `zarr.json` file within the OME group may contain the `series` key: ```{literalinclude} examples/ome/series-2.json :language: json ``` #### Details (bf2raw-details)= Conforming groups: - MUST have the value `3` for the `bioformats2raw.layout` key in their OME-Zarr Metadata in the `zarr.json` at the top of the hierarchy; - SHOULD have OME metadata representing the entire collection of images in a file named `OME/METADATA.ome.xml` which: - MUST adhere to the OME-XML specification but - MUST use `
store.zarr # Root folder of the zarr store
β
βββ zarr.json # coordinate transformations describing the relationship between two image coordinate systems
β # are stored in the attributes of their parent group.
β # transformations between coordinate systems in the 'volume' and 'crop' multiscale images are stored here.
β
βββ coordinateTransformations # transformations that use array storage for their parameters should go in a zarr group named "coordinateTransformations".
β βββ displacements # for example, a zarr array containing a displacement field
β βββ zarr.json
β
βββ volume
β βββ zarr.json # group level attributes (multiscales)
β βββ 0 # a group containing the 0th scale
β βββ image # a zarr array
β βββ zarr.json # physical coordinate system and transformations here
βββ crop
βββ zarr.json # group level attributes (multiscales)
βββ 0 # a group containing the 0th scale
βββ image # a zarr array
βββ zarr.json # physical coordinate system and transformations here
:::{dropdown} Example
(spec:example:coordinate_transformation)=
Two instruments simultaneously image the same sample from two different angles,
and the 3D data from both instruments are calibrated to "micrometer" units.
An analysis of sample A requires measurements from images taken from both instruments at certain points in space.
Suppose a region of interest (ROI) is determined from the image obtained from instrument 2,
but quantification from that region is needed for instrument 1.
Since measurements were collected at different angles,
a measurement by instrument 1 at the point with image array coordinates (x,y,z)
may not correspond to the measurement at the same array coordinates in instrument 2
(i.e., it may not be the same physical location in the sample).
To analyze both images together, they must be transformed to a common coordinate system.
The set of coordinate transformations encodes relationships between coordinate systems,
specifically, how to convert points from one coordinate system to another.
Implementations can apply the coordinate transform to images or points
in coordinate system `sampleA_instrument2` to bring them into the `sampleA_instrument1` coordinate system.
In this case, image data within the ROI defined in image2 should be transformed to the `sampleA_instrument1` coordinate system,
then used for quantification with the instrument 1 image.
The `coordinateTransformations` in the parent-level metadata would contain the following data.
The transformation parameters are stored in a separate zarr-group
under `coordinateTransformations/sampleA_instrument2-to-instrument1` as shown above.
```json
"coordinateTransformations": [
{
"type": "affine",
"path": "coordinateTransformations/sampleA_instrument2-to-instrument1",
"input": "sampleA_instrument2",
"output": "sampleA_instrument1"
}
]
```
And the image at the path `sampleA_instrument1` would have the following as the first coordinate system:
```json
"coordinateSystems": [
{
"name": "sampleA-instrument1",
"axes": [
{"name": "z", "type": "space", "unit": "micrometer"},
{"name": "y", "type": "space", "unit": "micrometer"},
{"name": "x", "type": "space", "unit": "micrometer"}
]
},
]
```
The image at path `sampleA_instrument2` would have this as the first listed coordinate system:
```json
[
{
"name": "sampleA-instrument2",
"axes": [
{"name": "z", "type": "space", "unit": "micrometer"},
{"name": "y", "type": "space", "unit": "micrometer"},
{"name": "x", "type": "space", "unit": "micrometer"}
]
}
],
```
:::
#### Additional details
Most coordinate transformations MUST specify their input and output coordinate systems
using `input` and `output` with a string value
that MUST correspond to the name of a coordinate system or the path to a multiscales group.
Exceptions are if the coordinate transformation is wrapped in another transformation,
e.g. as part of a `transformations` array of a `sequence` or
as `transformation` of an `inverseOf` transformation.
In these two cases input and output could, in some cases, be omitted (see below for details).
If unused, the `input` and `output` fields MAY be null.
If used in a parent-level zarr-group, the `input` and `output` fields
can be the name of a `coordinateSystem` in the same parent-level group or the path to a multiscale image group.
If either `input` or `output` is a path to a multiscale image group,
the authoritative coordinate system for the respective image is the first `coordinateSystem` defined therein.
If the names of `input` or `output` correspond to both an existing path to a multiscale image group
and the name of a `coordinateSystem` defined in the same metadata document,
the `coordinateSystem` MUST take precedent.
For usage in multiscales, see [the multiscales section](#multiscales-md) for details.
Coordinate transformations are functions of *points* in the input space to *points* in the output space.
We call this the "forward" direction.
Points are ordered lists of coordinates,
where a coordinate is the location/value of that point along its corresponding axis.
The indexes of axis dimensions correspond to indexes into transformation parameter arrays.
When rendering transformed images and interpolating,
implementations may need the "inverse" transformation -
from the output to the input coordinate system.
Inverse transformations will not be explicitly specified
when they can be computed in closed form from the forward transformation.
Inverse transformations used for image rendering may be specified using
the `inverseOf` transformation type, for example:
```json
{
"type": "inverseOf",
"transformation" : {
"type": "displacements",
"path": "/path/to/displacements",
},
"input": "input_image",
"output": "output_image",
}
```
Implementations SHOULD be able to compute and apply
the inverse of some coordinate transformations when they are computable
in closed-form (as the [Transformation types](#trafo-types-md) section below indicates).
If an operation is requested that requires
the inverse of a transformation that can not be inverted in closed-form,
implementations MAY estimate an inverse,
or MAY output a warning that the requested operation is unsupported.
#### Matrix transformations
(matrix-trafo-md)=
Two transformation types ([affine](#affine-md) and [rotation](#rotation-md)) are parametrized by matrices.
Matrices are applied to column vectors that represent points in the input coordinate system.
The first and last axes in a coordinate system correspond to the top and bottom entries in the column vector, respectively.
Matrices are stored as two-dimensional arrays, either as json or in a zarr array.
When stored as a 2D zarr array, the first dimension indexes rows and the second dimension indexes columns
(e.g., an array of `"shape":[3,4]` has 3 rows and 4 columns).
When stored as a 2D json array, the inner array contains rows (e.g. `[[1,2,3], [4,5,6]]` has 2 rows and 3 columns).
:::{dropdown} Example
For matrix transformations, points in the coordinate system:
```json
{
"name" : "in",
"axes" : [
{"name" : "z"},
{"name" : "y"},
{"name":"x"}
]
},
```
are represented as column vectors:
```
[z]
[y]
[x]
```
As a result, transforming the point `[z,y,x]=[1,2,3]` with the matrix `[[0,1,0],[-1,0,0],[0,0,-1]]` results in the point `[2,-1,3]`
because it is computed with the matrix-vector multiplication:
```
[ 0 1 0] [1] [ 2]
[-1 0 0] [2] = [-1]
[ 0 0 -1] [3] [-3]
```
:::
#### Transformation types
(trafo-types-md)=
Input and output dimensionality may be determined by the coordinate system referred to by the `input` and `output` fields, respectively.
If the value of `input` is a path to an array, its shape gives the input dimension,
otherwise it is given by the length of `axes` for the coordinate system with the name of the `input`.
If the value of `output` is an array, its shape gives the output dimension,
otherwise it is given by the length of `axes` for the coordinate system with the name of the `output`.
##### identity
(identity-md)=
`identity` transformations map input coordinates to output coordinates without modification.
The position of the i-th axis of the output coordinate system
is set to the position of the ith axis of the input coordinate system.
`identity` transformations are invertible.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), ['byDimension](#bydimension-md) or [`bijection`](#bijection-md)).
:::{dropdown} Example
:animate: fade-in
```{literalinclude} examples/transformations/identity.json
:language: json
```
defines the function:
```
x = i
y = j
```
:::
##### mapAxis
(mapAxis-md)=
`mapAxis` transformations describe axis permutations as a transpose vector of integers.
Transformations MUST include a `mapAxis` field
whose value is an array of integers that specifies the new ordering in terms of indices of the old order.
The length of the array MUST equal the number of dimensions in both the input and output coordinate systems.
Each integer in the array MUST be a valid zero-based index into the input coordinate system's axes
(i.e., between 0 and N-1 for an N-dimensional input).
Each index MUST appear exactly once in the array.
The value at position `i` in the array indicates which input axis becomes the `i`-th output axis.
`mapAxis` transforms are invertible.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), ['byDimension](#bydimension-md) or [`bijection`](#bijection-md)).
:::{dropdown} Example 1
:animate: fade-in
```{literalinclude} examples/transformations/mapAxis1.json
:language: json
```
The "equivalent to identity" transformation defines the function:
```
x = i
y = j
```
and the "permutation" transformation defines the function
```
x = j
y = i
```
:::
:::{dropdown} Example 2
:animate: fade-in
```{literalinclude} examples/transformations/mapAxis2.json
:language: json
```
The `projection_down` transformation defines the function:
```
x = b
```
and the `projection_up` transformation defines the function:
```
x = a
y = b
z = b
```
:::
##### translation
(translation-md)=
`translation` transformations are special cases of affine transformations.
When possible, a translation transformation should be preferred to its equivalent affine.
Input and output dimensionality MUST be identical
and MUST equal the the length of the `translation` array (N).
`translation` transformations are invertible.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), ['byDimension](#bydimension-md) or [`bijection`](#bijection-md)).
path
: The path to a zarr-array containing the translation parameters.
The array at this path MUST be 1D, and its length MUST be `N`.
translation
: The translation parameters stored as a JSON array of numbers.
The array MUST have length `N`.
:::{dropdown} Example
:animate: fade-in
```{literalinclude} examples/transformations/translation.json
:language: json
```
defines the function:
```
x = i + 9
y = j - 1.42
```
:::
##### scale
(scale-md)=
`scale` transformations are special cases of affine transformations.
When possible, a scale transformation SHOULD be preferred to its equivalent affine.
Input and output dimensionality MUST be identical
and MUST equal the the length of the `scale` array (N).
Values in the `scale` array SHOULD be non-zero;
in that case, `scale` transformations are invertible.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), [`byDimension`](#bydimension-md) or [`bijection`](#bijection-md)).
path
: The path to a zarr-array containing the scale parameters.
The array at this path MUST be 1D, and its length MUST be `N`.
scale
: The scale parameters are stored as a JSON array of numbers.
The array MUST have length `N`.
:::{dropdown} Example 1
:animate: fade-in
```{literalinclude} examples/transformations/scale.json
:language: json
```
defines the function:
```
x = 3.12 * i
y = 2 * j
```
i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter.
:::
:::{dropdown} Example 2
:animate: fade-in
If the data contains discrete axes (e.g., channels),
these axes are typically not transformed, but must be represented in the scale parameters.
```{literalinclude} examples/transformations/scale_with_discrete.json
:language: json
```
:::
##### affine
(affine-md)=
`affine`s are [matrix transformations](#matrix-trafo-md) from N-dimensional inputs to M-dimensional outputs.
They are represented as the upper `(M)x(N+1)` sub-matrix of a `(M+1)x(N+1)` matrix in [homogeneous
coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates) (see examples).
This transformation type may be (but is not necessarily) invertible
when `N` equals `M`.
The matrix MUST be stored as a 2D array either as json or as a zarr array.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), ['byDimension](#bydimension-md) or [`bijection`](#bijection-md)).
path
: The path to a zarr-array containing the affine parameters.
The array at this path MUST be 2D whose shape MUST be `(M)x(N+1)`.
affine
: The affine parameters stored in JSON.
The matrix MUST be stored as 2D nested array (an array of arrays of numbers)
where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`.
:::{dropdown} Example 1
:animate: fade-in
A 2D-2D example:
```{literalinclude} examples/transformations/affine2d2d.json
:language: json
```
defines the function:
```
y = 1*j + 2*i + 3
x = 4*j + 5*i + 6
```
it is equivalent to this matrix-vector multiplication in homogeneous coordinates:
```
[ 1 2 3 ][ j ] [ y ]
[ 4 5 6 ][ i ] = [ x ]
[ 0 0 1 ][ 1 ] [ 1 ]
```
where the last row `[0 0 1]` is omitted in the JSON representation.
:::
:::{dropdown} Example 2
:animate: fade-in
An example with two dimensional inputs and three dimensional outputs.
The affine transformation adds a translation by 1 along the new z-axis.
Note that the order of the axes can in general be determined by the application or user.
These axes relate to the memory or on-disk order insofar as the last dimension is contiguous
when the zarr array is c-order (the default for zarr version 2, and the only option for zarr version 3).
```{literalinclude} examples/transformations/affine2d3d.json
:language: json
```
defines the function:
```
z = 0*i + 0*j + 1
y = 3*i + 4*j + 2
x = 6*i + 7*j + 5
```
it is equivalent to this matrix-vector multiplication in homogeneous coordinates:
```
[ 1 0 0 ][ 1 ] [ z ]
[ 2 3 4 ][ i ] = [ y ]
[ 5 6 7 ][ j ] [ x ]
[ 0 0 1 ] [ 1 ]
```
where the last row `[0 0 1]` is omitted in the JSON representation.
:::
:::{dropdown} Example 3
:animate: fade-in
If the image data contains discrete axes (e.g., channels),
these axes are typically not transformed, but must be represented in the transformation matrix.
```{literalinclude} examples/transformations/affine2d2d_with_channel.json
:language: json
```
##### rotation
(rotation-md)=
`rotation`s are [matrix transformations](#matrix-trafo-md) that are special cases of affine transformations.
When possible, a rotation transformation SHOULD be used instead of an equivalent affine.
Input and output dimensionality (N) MUST be identical.
Rotations are stored as `NxN` matrices, see below,
and MUST have determinant equal to one, with orthonormal rows and columns.
The matrix MUST be stored as a 2D array either as json or in a zarr array.
`rotation` transformations are invertible.
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), ['byDimension](#bydimension-md) or [`bijection`](#bijection-md)).
path
: The path to an array containing the affine parameters.
The array at this path MUST be 2D whose shape MUST be `N x N`.
rotation
: The parameters stored in JSON.
The matrix MUST be stored as a 2D nested array (an array of arrays of numbers) where the outer array MUST be length `N`
and the inner arrays MUST be length `N`.
:::{dropdown} Example
:animate: fade-in
A 2D example
```{literalinclude} examples/transformations/rotation.json
:language: json
```
defines the function:
```
x = 0*i - 1*j
y = 1*i + 0*j
```
:::
##### inverseOf
(inverseOf-md)=
An `inverseOf` transformation contains another transformation (often non-linear),
and indicates that transforming points from output to input coordinate systems
is possible using the contained transformation.
Transforming points from the input to the output coordinate systems
requires the inverse of the contained transformation (if it exists).
The `input` and `output` fields MAY be omitted for `inverseOf` transformations
if those fields may be omitted for the transformation it wraps.
```{note}
Software libraries that perform image registration
often return the transformation from fixed image coordinates to moving image coordinates,
because this "inverse" transformation is most often required
when rendering the transformed moving image.
Results such as this may be enclosed in an `inverseOf` transformation.
This enables the "outer" coordinate transformation to specify the moving image coordinates
as `input` and fixed image coordinates as `output`,
a choice that many users and developers find intuitive.
```
:::{dropdown} Example
:animate: fade-in
```{literalinclude} examples/transformations/inverseOf.json
:language: json
```
:::
##### sequence
(sequence-md)=
A `sequence` transformation consists of an ordered array of coordinate transformations,
and is invertible if every coordinate transform in the array is invertible
(though could be invertible in other cases as well).
To apply a sequence transformation to a point in the input coordinate system,
apply the first transformation in the array of transformations.
Next, apply the second transformation to the result.
Repeat until every transformation has been applied.
The output of the last transformation is the result of the sequence.
A sequence transformation MUST NOT be part of another sequence transformation.
The `input` and `output` fields MUST be included for sequence transformations.
transformations
: A non-empty array of transformations.
:::{note}
Considering transformations as functions of points,
if the array contains transformations `[f0, f1, f2]` in that order,
applying this sequence to point `x` is equivalent to:
```
f2(f1(f0(x)))
```
`f0` is applied first, `f1` is applied second, and `f2` is applied last.
:::
:::{dropdown} Example
:animate: fade-in
This sequence:
```{literalinclude} examples/transformations/sequence.json
:language: json
```
describes the function
```
x = (i + 0.1) * 2
y = (j + 0.9) * 3
```
and is invertible.
:::
##### coordinates and displacements
(coordinates-displacements-md)=
`coordinates` and `displacements` transformations store coordinates or displacements in an array
and interpret them as a vector field that defines a transformation.
The arrays must have a dimension corresponding to every axis of the input coordinate system
and one additional dimension to hold components of the vector.
Applying the transformation amounts to looking up the appropriate vector in the array,
interpolating if necessary,
and treating it either as a position directly (`coordinates`)
or a displacement of the input point (`displacements`).
These transformation types refer to an array at location specified by the `path` parameter.
The input and output coordinate systems for these transformations (`input` / `output` coordinate systems)
constrain the array size and the coordinate system metadata for the array (field `coordinateSystem`).
The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output`
(e.g., [`sequence`](#sequence-md), [`inverseOf`](#inverseof-md), [`byDimension`](#bydimension-md) or [`bijection`](#bijection-md)).
* If the input coordinate system has `N` axes,
the array at location path MUST have `N+1` dimensions
* The field coordinate system MUST contain an axis identical to every axis
of its input coordinate system in the same order.
* The field coordinate system MUST contain an axis with type `coordinate` or `displacement`, respectively,
for transformations of type `coordinates` or `displacements`.
* This SHOULD be the last axis (contiguous on disk when c-order).
* If the output coordinate system has `M` axes,
the length of the array along the `coordinate`/`displacement` dimension MUST be of length `M`.
The `i`th value of the array along the `coordinate` or `displacement` axis refers to the coordinate or displacement
of the `i`th output axis. See the example below.
`coordinates` and `displacements` transformations are not invertible in general,
but implementations MAY approximate their inverses.
Metadata for these coordinate transforms have the following fields:
interpolation attributes MAY be provided.
Its value indicates the interpolation to use
if transforming points not on the array's discrete grid.
Values could be:
linear (default)nearestcubicinput_axes and output_axes fields
whose values are arrays of strings.
Every axis name in a child transformation's input_axes
MUST correspond to a name of some axis in this parent object's input coordinate system.
Every axis name in the parent byDimension's output coordinate system
MUST appear in exactly one child transformation's output_axes array.
Each child transformation's input_axes and output_axes arrays
MUST have the same length as that transformation's parameter arrays.