From 9f29c752438d7ece88c7c4ba564ce9c0be8b5afb Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:51:33 +0200 Subject: [PATCH 001/115] overhaul proposal Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 734 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 734 insertions(+) create mode 100644 rfc/5/versions/1/index.md diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md new file mode 100644 index 00000000..f931578b --- /dev/null +++ b/rfc/5/versions/1/index.md @@ -0,0 +1,734 @@ +# RFC-5 Coordinate systems and transformations + +```{toctree} +:hidden: +:maxdepth: 1 +reviews/index +comments/index +responses/index +versions/index +``` + +Add named coordinate systems and expand and clarify coordinate transformations. + +## Status + +This RFC is currently in RFC state `R1` (send for review). + +```{list-table} Record +:widths: 8, 20, 20, 20, 15, 10 +:header-rows: 1 +:stub-columns: 1 + +* - Role + - Name + - GitHub Handle + - Institution + - Date + - Status +* - Author + - John Bogovic + - @bogovicj + - HHMI Janelia + - 2024-07-30 + - Implemented +* - Author + - Davis Bennett + - @d-v-b + - + - 2024-07-30 + - Implemented validation +* - Author + - Luca Marconato + - @LucaMarconato + - EMBL + - 2024-07-30 + - Implemented +* - Author + - Matt McCormick + - @thewtex + - ITK + - 2024-07-30 + - Implemented +* - Author + - Stephan Saalfeld + - @axtimwalde + - HHMI Janelia + - 2024-07-30 + - Implemented (with JB) +* - Endorser + - Norman Rzepka + - @normanrz + - Scalable Minds + - 2024-08-22 + - +* - Reviewer + - Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster + - toloudis, dyf, fcollman + - Allen Institutes + - 2024-11-28 + - [Review](./reviews/1/index) +* - Reviewer + - Will Moore, Jean-Marie Burel, Jason Swedlow + - will-moore, jburel, jrswedlow + - University of Dundee + - 2025-01-22 + - [Review](./reviews/2/index) +``` + +## Overview + +This RFC provides first-class support for spatial and coordinate transformations in OME-Zarr. + +## Background + +Coordinate and spatial transformation are vitally important for neuro and bio-imaging and broader scientific imaging practices +to enable: + +1. Reproducibility and Consistency: Supporting spatial transformations explicitly in a file format ensures that transformations + are applied consistently across different platforms and applications. This FAIR capability is a cornerstone of scientific + research, and having standardized formats and tools facilitates verification of results by independent + researchers. +2. Integration with Analysis Workflows: Having spatial transformations as a first-class citizen within file formats allows for + seamless integration with various image analysis workflows. Registration transformations can be used in subsequent image + analysis steps without requiring additional conversion. +3. Efficiency and Accuracy: Storing transformations within the file format avoids the need for re-sampling each time the data is + processed. This reduces sampling errors and preserves the accuracy of subsequent analyses. Standardization enables on-demand + transformation, critical for the massive volumes collected by modern microscopy techniques. +4. Flexibility in Analysis: A file format that natively supports spatial transformations allows researchers to apply, modify, or + reverse transformations as needed for different analysis purposes. This flexibility is critical for tasks such as + longitudinal studies, multi-modal imaging, and comparative analysis across different subjects or experimental conditions. + +Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec covering many of the use cases +requested in [this github issue](https://github.com/ome/ngff/issues/84). It also adds "coordinate systems" - named +sets of "axes." Related the relationship of discrete arrays to physical coordinates and the interpretation and motivation for +axis types. + + +## Proposal + +Below is a slightly abridged copy of the proposed changes to the specification (examples are omitted), the full set of changes +including all examples are publicly available on the [github pull request](https://github.com/ome/ngff/pull/138). + + +### "coordinateSystems" metadata + +A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: +- MUST contain the field "name". The value MUST be a non-empty string that is unique among `coordinateSystem`s. +- MUST contain the field "axes", whose value is an array of valid "axes" (see below). + + +The order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that +coordinate system. The "dimensionality" of a coordinate system +is indicated by the length of its "axes" array. The "volume_micrometers" example coordinate system above is three dimensional (3D). + +The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate +system's dimensions. Axis `name`s 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, with identical axes) or can be +transformed to the same coordinate system before doing analysis. See the example below. + + +### "axes" metadata + +"axes" describes the dimensions of a coordinate systems. It is a list of dictionaries, where each dictionary 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. +- 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. +- 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. + +If part of multiscales metadata, 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 of 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 + +Every array has a default coordinate system whose parameters need not be explicitly defined. Its name is the path to the array +in the container, its axes have `"type":"array"`, are unitless, and have default "name"s. The ith axis has `"name":"dim_i"` +(these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). + + +The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. The axis with +name `"dim_i"` is the ith element of the `"axes"` list. The axes and their order align with the `shape` +attribute in the zarr array attributes (in `.zarray`), 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/v2.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. + + +### "coordinateTransformations" metadata + +"coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). +For example, to map an array's discrete coordinate system to its corresponding physical coordinates. +Coordinate transforms are in the "forward" direction. They represent functions from *points* in the +input space to *points* in the output space. + + +- MUST contain the field "type". +- MUST contain any other fields required by the given "type" (see table below). +- MUST contain the field "output", unless part of a `sequence` or `inverseOf` (see details). +- MUST contain the field "input", unless part of a `sequence` or `inverseOf` (see details). +- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. +- Parameter values MUST be compatible with input and output space dimensionality (see details). + + + +
identity + + The identity transformation is the default transformation and is typically not explicitly defined. +
mapAxis + "mapAxis":Dict[String:String] + A maxAxis transformation specifies an axis permutation as a map between axis names. +
translation + one of:
"translation":List[number],
"path":str +
translation vector, stored either as a list of numbers ("translation") or as binary data at a location + in this container (path). +
scale + one of:
"scale":List[number],
"path":str +
scale vector, stored either as a list of numbers (scale) or as binary data at a location in this + container (path). +
affine + one of:
"affine": List[List[number]],
"path":str +
affine transformation matrix stored as a flat array stored either with json uing the affine field + or as binary data at a location in this container (path). If both are present, the binary values at path should be used. +
rotation + one of:
"rotation":List[number],
"path":str +
rotation transformation matrix stored as an array stored either + with json or as binary data at a location in this container (path). + If both are present, the binary parameters at path are used. +
sequence + "transformations":List[Transformation] + A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. +
displacements + "path":str
"interpolation":str +
Displacement field transformation located at (path). +
coordinates + "path":str
"interpolation":str +
Coordinate field transformation located at (path). +
inverseOf + "transform":Transform + The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. +
bijection + "forward":Transform
"inverse":Transform +
Explicitly define an invertible transformation by providing a forward transformation and its inverse. +
byDimension + "transformations":List[Transformation] + Define a high dimensional transformation using lower dimensional transformations on subsets of + dimensions. +
typefieldsdescription +
+ + +Conforming readers: +- MUST parse `identity`, `scale`, `translation` transformations; +- SHOULD parse `mapAxis`, `affine` transformations; +- SHOULD be able to apply transformations to points; +- SHOULD be able to apply transformations to images; + +Coordinate transformations from array to physical coordinates MUST be stored in multiscales, +and MUST be duplicated in the attributes of the zarr array. Transformations between different images MUST be stored in the +attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD +be stored in a zarr group `"coordinateTransformations"`. + +
+store.zarr                      # Root folder of the zarr store
+│
+├── .zattrs                     # coordinate transformations describing the relationship between two image coordinate systems
+│                               # are stored in the attributes of their parent group.
+│                               # transformations between 'volume' and 'crop' coordinate systems are stored here.
+│
+├── coordinateTransformations   # transformations that use array storage go in a "coordinateTransformations" zarr group.
+│   └── displacements           # for example, a zarr array containing a displacement field
+│       ├── .zattrs
+│       └── .zarray
+│
+├── volume
+│   ├── .zattrs                 # group level attributes (multiscales)
+│   └── 0                       # a group containing the 0th scale
+│       └── image               # a zarr array
+│           ├── .zattrs         # physical coordinate system and transformations here
+│           └── .zarray         # the array attributes
+└── crop
+    ├── .zattrs                 # group level attributes (multiscales)
+    └── 0                       # a group containing the 0th scale
+        └── image               # a zarr array
+            ├── .zattrs         # physical coordinate system and transformations here
+            └── .zarray         # the array attributes
+
+ +### Additional details + +Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value +corresponding to the name of a coordinate system. The coordinate system's name may be the path to an array, and therefore may +not appear in the list of coordinate systems. + +Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the +`transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for +details). + +Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output` as arrays +of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below 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. For example, the scale transformation above +defines the function: + +``` +x = 0.5 * i +y = 1.2 * j +``` + +i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. + +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", + } +} +``` + +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](#transformation-types) 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 + +Two transformation types ([affine](#affine) and [rotation](#rotation)) are parametrized by matrices. Matrices are applied to +column vectors that represent points in the input coordinate system. The first (last) axis in a coordinate system is the top +(bottom) entry in the column vector. 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). + + +### Transformation types + +Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value +of "input" is an array, it's length gives the input dimension, otherwise the length of "axes" for the coordinate +system with the name of the "input" value gives the input dimension. If the value of "input" is an array, it's +length 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 length 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` transformations map input coordinates to output coordinates without modification. The position of +the ith axis of the output coordinate system is set to the position of the ith axis of the input coordinate +system. `identity` transformations are invertible. + + +#### mapAxis + +`mapAxis` transformations describe axis permutations as a mapping of axis names. Transformations MUST include a `mapAxis` field +whose value is an object, all of whose values are strings. If the object contains `"x":"i"`, then the transform sets the value +of the output coordinate for axis "x" to the value of the coordinate of input axis "i" (think `x = i`). For every axis in its output coordinate +system, the `mapAxis` MUST have a corresponding field. For every value of the object there MUST be an axis of the input +coordinate system with that name. Note that the order of the keys could be reversed. + + +#### translation + +`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. + +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 list of numbers. The list MUST have length `N`. + + +#### scale + +`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. + +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 stored as a JSON list of numbers. The list MUST have length `N`. + + +#### affine + +`affine`s are [matrix transformations](#matrix-transformations) from N-dimensional inputs to M-dimensional outputs 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. + +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 where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. + + +#### rotation + +`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation +transformation SHOULD be preferred to its 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. + +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 where the outer array MUST be length `N` and the inner arrays MUST be length `N`. + + +#### inverseOf + +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). + +```{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. +``` + + +#### sequence + +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 list 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. + +The transformations included in the `transformations` array may omit their `input` and `output` fields under the conditions +outlined below: + +- The `input` and `output` fields MAY be omitted for the following transformation types: + - `identity`, `scale`, `translation`, `rotation`, `affine`, `displacements`, `coordinates` +- The `input` and `output` fields MAY be omitted for `inverseOf` transformations if those fields may be omitted for the + transformation it wraps +- The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for + both its `forward` and `inverse` transformations +- The `input` and `output` fields MAY be omitted for `sequence` transformations if the fields may be omitted for + all transformations in the sequence after flattening the nested sequence lists. +- The `input` and `output` fields MUST be included for transformations of type: `mapAxis`, and `byDimension` (see the note + below), and under all other conditions. + + +transformations +: A non-empty array of transformations. + + +#### coordinates and displacements + +`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 coordinate system"). + +* 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 field: + +
+
path
+
The location of the coordinate array in this (or another) container.
+
interpolation
+
The interpolation attributes MAY be provided. It's value indicates + the interpolation to use if transforming points not on the array's discrete grid. + Values could be: +
    +
  • linear (default)
  • +
  • nearest
  • +
  • cubic
  • +
+
+ + +For both `coordinates` and `displacements`, the array data at referred to by `path` MUST define coordinate system and coordinate transform metadata: + +* Every axis name in the `coordinateTransform`'s `input` MUST appear in the coordinate system +* The array dimension corresponding to the `coordinate` or `displacement` axis MUST have length equal to the number of dimensions of the `coordinateTransform` `output` +* If the input coordinate system `N` axes, then the array data at `path` MUST have `(N + 1)` dimensions. +* SHOULD have a `name` identical to the `name` of the corresponding `coordinateTransform`. + +For `coordinates`: + +* `coordinateSystem` metadata MUST have exactly one axis with `"type" : "coordinate"` +* the shape of the array along the "coordinate" axis must be exactly `N` + +For `displacements`: + +* `coordinateSystem` metadata MUST have exactly one axis with `"type" : "displacement"` +* the shape of the array along the "displacement" axis must be exactly `N` +* `input` and `output` MUST have an equal number of dimensions. + + +#### byDimension + +`byDimension` transformations build a high dimensional transformation using lower dimensional transformations +on subsets of dimensions. + +
+
transformations
+
A list of transformations, each of which applies to a (non-strict) subset of input and output dimensions (axes). + The values of input and output fields MUST be an array of strings. + Every axis name in input 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 MUST appear in exactly one of its child transformations' output. +
+
+ + +#### bijection + +A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations +are explicitly defined. Each direction SHOULD be a transformation type that is not closed-form invertible. +Its' input and output spaces MUST have equal dimension. The input and output dimensions for the both the forward +and inverse transformations MUST match bijection's input and output space dimensions. + +`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, in which case +the `forward` transformation's `input` and `output` are understood to match the bijection's, and the `inverse` +transformation's `input` (`output`) matches the bijection's `output` (`input`), see the example below. + +Practically, non-invertible transformations have finite extents, so bijection transforms should only be expected +to be correct / consistent for points that fall within those extents. It may not be correct for any point of +appropriate dimensionality. + +## Specific feedback requested + +We ask the reviewers for one specific piece of feedback. Specifically about whether parameters for transformations should +be written as they are currently in the draft pull request, with named parameters at the "top level" e.g.: + +``` +{ + "type": "affine", + "affine": [[1, 2, 3], [4, 5, 6]], + "input": "ji", + "output": "yx" +} +``` + +or alternatively in a `parameters` field: + +``` +{ + "type": "affine", + "parameters": { + "matrix": [[1, 2, 3], [4, 5, 6]] + }, + "input": "ji", + "output": "yx" +} +``` + +In discussions, some authors preferred the latter because it will make the "top-level" keys for transformation +objects all identical, which could make serialization / validation simpler. One downside is that this change +is breaking for the existing `scale` and `translation` transformations + +``` +{ + "type": "scale", + "scale": [2, 3], + "input": "ji", + "output": "yx" +} +``` + +would change to: + +``` +{ + "type": "scale", + "parameters": { + "scale": [2, 3], + }, + "input": "ji", + "output": "yx" +} +``` + +The authors would be interested to hear perspectives from the reviewers on this matter. + + +## Requirements + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [IETF RFC 2119][IETF RFC 2119] + + +## Stakeholders + +People who need to represent the result of image registration algorithms, or any imaging +scientist in need of affine or non-linear transformations. + +This RFC has been discussed in: + +* [PR 138](https://github.com/ome/ngff/pull/138) +* Issues [84](https://github.com/ome/ngff/issues/84), [94](https://github.com/ome/ngff/issues/94), [101](https://github.com/ome/ngff/issues/101), and [146](https://github.com/ome/ngff/issues/146) +* Several OME-Zarr community calls ([one example](https://forum.image.sc/t/ome-ngff-community-call-transforms-and-tables/71792)) + +## Implementation + +Many RFCs have an "implementation" section which details how the implementation +will work. This section should explain the rough specification changes. The +goal is to give an idea to reviewers about the subsystems that require change +and the surface area of those changes. + +This knowledge can result in recommendations for alternate approaches that +perhaps are idiomatic to the project or result in less packages touched. Or, it +may result in the realization that the proposed solution in this RFC is too +complex given the problem. + +For the RFC author, typing out the implementation in a high-level often serves +as "[rubber duck debugging][rubber duck debugging]" and you can catch a lot of +issues or unknown unknowns prior to writing any real code. + +## Drawbacks, risks, alternatives, and unknowns + +Adopting this proposal will add an implementation burden because it adds more transformation types. Though this drawback is +softened by the fact that implementations will be able to choose which transformations to support (e.g., implementations may choose +not to support non-linear transformations). + +An alternative to this proposal would be not to add support transformations directly and instead recommend software use an +existing format (e.g., ITK's). The downside of that is that alternative formats will not integrate well with OME-NGFF as they do +not use JSON or Zarr. + +In all, we believe the benefits of this proposal (outlined in the Background section) far outweigh these drawbacks, and will +better promote software interoperability than alternatives. + + +## Prior art and references + +ITK represents many [types of +transformations](https://itk.org/ITKSoftwareGuide/html/Book2/ITKSoftwareGuide-Book2ch3.html#x26-1170003.9), +and can serialize them to either plain text or to an HDF5 file. This is a practical approach that works +well for software that depend on ITK, the proposed solution encoding transformations will be more +interoperable. + +Displacement fields are typically stored in formats designed for medical imaging (e.g. [Nifti](https://nifti.nimh.nih.gov/)). +While effective, they can only describe one type of non-linear transformation. + +The Saalfeld lab at Janelia developed a [custom +format](https://github.com/saalfeldlab/template-building/wiki/Hdf5-Deformation-fields) for storing affine and displacement field +transformations in HDF5 which is similarly less interoperable than would be ideal. + +## Abandoned Ideas + +One consideration was to change (reverse) the order of parameters for transformations to match the convention used by many +libraries. We opted not to make this change for two reasons. First, to maintain backward-compatibility. Second, the convention +used by the libraries generally applies for 2D and 3D spatial transformations, but the specification should be applicable to +transformations of arbitrary dimension and axis type, where there is not a strong convention we are aware of. + +An early consideration was to use axis names to indicate correspondence across different coordinate systems (i.e. if two +coordinate systems both have the "x" axis, then it is "the same" axis. We abandoned this for several reasons. It was +restrictive - it is useful to have many coordinate systems with an "x" axis without requiring that they be "identical." Under our +early idea, every set of spatial axes would need unique names ("x1", "x2", ...), and this seemed burdensome. As well, this +approach would have also made transformations less explicit and likely would have required more complicated implementations. +For example, points in two coordinate systems with re-ordered axis names `["x","y"]` vs `["y","x"]` would need to be +axis-permuted, even if such a permutation was not explicitly specified. + + +## Future possibilities + +Additional transformation types should be added in the future. Top candidates include: +* thin-plate spline +* b-spline +* velocity fields +* by-coordinate + +## Performance + +This proposal adds new features, and has no effect on performance for existing functionality. + +## Backwards Compatibility + +Adds new transformations, but existing transformations (`scale`, `translation`) are backward-compatible. + +Adds coordinate systems, these contain axes which are backward-compatible with the [axis specification for version +0.4](https://ngff.openmicroscopy.org/0.4/#axes-md). This proposal adds new fields to the axis metadata. + + +## Testing + +Public examples of transformations with expected input/output pairs will be provided. + +## UI/UX + +Implementations SHOULD communicate if it encounters an unsupported transformation (e.g. some software may opt not to support +non-linear transformations), and inform users what action will be taken. The details of this choice should be software / +application dependent, but ignoring the unsupported transformation or falling back to a simpler transformation are likely +to be common choices. + +Implementations MAY choose to communicate if and when an image can be displayed in multiple coordinate systems. Users might +choose between different options, or software could choose a default (e.g. the first listed coordinate system). The +[`multiscales` in version 0.4](https://ngff.openmicroscopy.org/0.4/#multiscale-md) has a similar consideration. + + +## Changelog + +| Date | Description | Link | +| ---------- | ---------------------------- | ---------------------------------------------------------------------------- | +| 2024-10-08 | RFC assigned and published | [https://github.com/ome/ngff/pull/255](https://github.com/ome/ngff/pull/255) | \ No newline at end of file From d2b7e7f4ecde80e3123d64a6888ce7f4eebabe9c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:51:40 +0200 Subject: [PATCH 002/115] reply to reviews Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 202 +++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 rfc/5/responses/1/index.md diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md new file mode 100644 index 00000000..bea793b7 --- /dev/null +++ b/rfc/5/responses/1/index.md @@ -0,0 +1,202 @@ +# RFC-5: Response 1 (2025-05-06 version) + +The authors extend their most sincere thanks and appreciation to all the reviewers of this RFC. + +## General comments + +We have added many motivating examples for common use cases, but also for many edge-cases. +These examples can be found on `s3://ngff-rfc5-coordinate-transformation-examples/`. +The metadata are also mirrored [on github](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples) + +As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) +for viewing these examples with BigWarp, a reference implementation. + +## Review 1 + +Daniel Toloudis, David Feng, Forrest Collman, and Nathalie Gaudreault at the Allen Institute provided +[Review 1](https://ngff.openmicroscopy.org/rfc/5/reviews/1/index.html). + +### Axes metadata + +We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating +examples would be helpful. We will coordinate with the authors of RFC-4 which we agree is essential for ensuring consistent +orientations of spatial (and maybe other) axis types. + +There are now some examples provided using the metadata proposed in RFC-4 that will clarify this point. + +(TODO link) + +### CoordinateSystem + +> Is the 'name' of the coordinate system where users should try to +> standardize strings that let multiple datasets that live in the same +> space be visualized together? + +Yes, exactly. + +> Can we add to the spec a location or manner where such 'common' spaces are written down. + +We agree that this would be valuable, but feel it is out-of-scope for this +RFC. We can imagine a future in which the spec itself does not contain the +location of the common spaces but rather defines a way for that location to +be written down. The point being that we hope the details of this idea for +'common' spaces can be discussed and agreed upon in the future. + +> there are applications, ... where the +> values of the array reflect the height of an object, and so are spatial, +> so it's not clear where such a relationship or 3d coordinate + +We agree that a better annotating for the value(s) stored in an image would be valuable, but +feel is that this is also out-of-scope for this RFC. A discussion has [begun on github](https://github.com/ome/ngff/issues/203) +that we hope continues. + + +### “Array” coordinate system + +Thank you for the feedback, we have edited this section with motivation and clarity[1]. We hope that the edits along with motivating examples will. + +### Units + +> Why have these explicit listed units and not just follow SI and specify the exponent on the SI unit that you are going for? + +This RFC does change some parts of the Axes metadata, but not the specification of units which remains unchanged +relative to [v0.4](https://github.com/ome/ngff/blob/5067681721cc73ddf8b64692456cdda604cc659a/0.4/index.bs#L227-L229). +As such, this is out of the scope of this RFC. + +This is an interesting and valuable idea though, and revisiting units would be good topic for a new RFC in my opinion, since +they seem not to have been reconsidered since they +[were first introduced in 2021](https://github.com/ome/ngff/commit/0661115b93026f197d3787d99b74ec4d01614c99). + +### coordinateTransformations + +> We think that conforming reading MUST be able to parse Affine transformations. + +TODO + +> Rotation: there appears to be inconsistency in the doc +> ... +> Should it be `List[List[number]]`? + +Yes, thank you for catching this. It is now [correct](https://github.com/bogovicj/ngff/commit/649f6234c2a2bef475b5873d1982f70cd6ee8d07). + + +### Parameters + +The feedback is much appreciated. Your point of view that the spec should + +> place the named parameters field at the same level as the 'type' field, as is written in the current draft. + +is the consensus, so the parameters will remain as they are. + +### Process + +> Should it be customary to provide a sample implementation? + + +> Is it okay for an RFC to link out to other things, rather than being +> completely self-contained? If it's not there is a danger of it +> effectively changing without it being properly versioned. + +This is an important point. Exactly how and if linking to outside artifacts is allowed may belong in a broader community +discussion. To keep resources relevant to, but "outside of" this RFC versioned and stable, +they will be posted and archived in a permanent repository, and assigned a DOI. +The next revision of this RFC will refer to that specific version. + + +## Review 2 + +[Review 2](https://ngff.openmicroscopy.org/rfc/5/reviews/2/index.html) was written by William Moore, Jean-Marie Burel, Jason +Swedlow from the University of Dundee. + +### Clarifications + + +### Implementation section + +## Comment-1 + +> Why not require a value for units and then make “arbitrary” or some sentinel the value that people must specify to say “no coordinates?” + +I agree that having some fixed value to mean "no units" would be a reasonable choice. In my opinion, having no units key +reflects that "there are no units" better than a placeholder (and it avoids having to choose the value of the placeholder). + +> I wonder why `Arrays are inherently discrete (see Array coordinate systems, below) but are often used to store discrete samples +> of a continuous variable.` isn’t true of everything? Aren’t the images themselves samplings? In general I wasn’t totally clear +> on how interpolation works - I understand it is a user-applied “transformation” in which case I think that should be clear. + +I agree that digital images always contain samples. The purpose of this distinction is to communicate, to humans and +software, a property of *the signal that is being sampled,* not the representation that is stored. That is the reason +that "array coordinate systems" have discrete axes - because they have no additional interpretation. +I added some clarifying text [1]. + + +## Comment-2 + +> Rebasing to take into account the change to zarr v3 (e.g. remove references to .zarray and replace with zarr.json) / “ome” top-level key would be helpful for clarity. + +[Done.](https://github.com/bogovicj/ngff/commit/52d7924f1522bdf917ea912fc416ae55b3229ebb) + +> Axis type of “array” is a bit confusing. It basically means “unknown”? + +It could mean "unknown" if there are no other coordinate systems that label it. More importantly, it serves as a placeholder +for operations that work in "pixel coordinates," not "physical coordinates." +I added some clarifying text [1]. + + +> arrayCoordinateSystem specifying dimension names is now redundant with zarr v3 dimension names + +Good point, and I agree; but the zarr spec is more permissive than the ngff spec, specifically because it +[allows null or duplicate `dimension_names`](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#dimension-names). +As a result, we will need require adding additional constraints to the dimension names that they be unique and not null [2]. +This is currently a requirement for the names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md) + + +## Other + +In conversations, at the OME community meeting and hackathon in April 2025, +several attendees expressed confusion about how to specify situations with +many coordinate systems, specifically when there exist more than one +physical coordinate system. + +The main questions had to do with whether there were any constraints +regarding the coordinate transforms inside the multiscales' dataset metadata +and those outside the datasets (that were previously said to apply to all +scale levels). Implementers were concerned that if the transformation +corresponding to a particular coordinate system could be found anywhere, +that there would be a large number of valid ways to describe the same set of +coordinate systems and transformations (see the examples below). This would +be an undue burden. We agreed with and shared this concern. + +As a result, a group of hackathon attendees agreed to a set of constraints +that would decrease the burden on implementors, without reducing the +expressibility [3]. To summarize the constraints: + +* The first coordinate system in the list is a *default* coordinate system + * ususally an image's "native" physical coordinate system +* There MUST be exactly one coordinate transformation per dataset in the multiscales whose output is the *default* coordinate system + * as a result, the field was renamed to the singular `coordinateTransformation` and changed from a json list to json object. + * this transformation SHOULD be simple (defined precisely in the spec). +* Any other transformations belong outside the datasets + * the `input`s of these transformations MUST be the *default* coordinate system + * the `output`s of these transformations are the other coordinate systems + +### Example 1 + +* There exist two coordinate systems "A", and "B" and two datasets "0", and "1". +* There exist two coordinate transforms in the "0" dataset, one from "0" to "A", and one from "0" to "B". +* There exist two coordinate transforms in the "1" dataset, one from "1" to "A", and one from "1" to "B". +* There exist no coordinate transformations outside the datasets. + +### Example 2 + +* There exist two coordinate systems "A", and "B" and two datasets "0", and "1". +* There exist one coordinate transforms in the "0" dataset, from "0" to "A". +* There exist one coordinate transforms in the "1" dataset, from "1" to "A". +* There exist one coordinate transforms outside the datasets, from "A" to "B". + + +## References + +[1] [Motivation and clarification of array coordinate systems](https://github.com/bogovicj/ngff/commit/db1e7d1c16206125a83a9c7bd4ea2146f01143e7) +[2] [constraints on zarr `dimension_names`](https://github.com/bogovicj/ngff/commit/cd89608ea3baca8ea36447f88fbb4e3ea1909299) +[3] [constraints on ](https://github.com/bogovicj/ngff/commit/3822dd0d9d2e388a12b6b74d0bffc7a215298e42) \ No newline at end of file From a681c24b4bf00af848e16e6b6b5c34bc34408cc6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:54:40 +0200 Subject: [PATCH 003/115] updated title and version date --- rfc/5/responses/1/index.md | 2 +- rfc/5/versions/1/index.md | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index bea793b7..06dec1d2 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -1,4 +1,4 @@ -# RFC-5: Response 1 (2025-05-06 version) +# RFC-5: Response 1 (2025-10-07 version) The authors extend their most sincere thanks and appreciation to all the reviewers of this RFC. diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index f931578b..0d7f76f6 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -1,13 +1,4 @@ -# RFC-5 Coordinate systems and transformations - -```{toctree} -:hidden: -:maxdepth: 1 -reviews/index -comments/index -responses/index -versions/index -``` +# RFC-5 (2025-10-07 version) Add named coordinate systems and expand and clarify coordinate transformations. From c731cdbf95d1e32b46593112aaedfbd1f6bc6e30 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:54:58 +0200 Subject: [PATCH 004/115] reformatted authors table --- rfc/5/versions/1/index.md | 75 +++++++-------------------------------- 1 file changed, 13 insertions(+), 62 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 0d7f76f6..b71e8e4b 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -4,68 +4,19 @@ Add named coordinate systems and expand and clarify coordinate transformations. ## Status -This RFC is currently in RFC state `R1` (send for review). - -```{list-table} Record -:widths: 8, 20, 20, 20, 15, 10 -:header-rows: 1 -:stub-columns: 1 - -* - Role - - Name - - GitHub Handle - - Institution - - Date - - Status -* - Author - - John Bogovic - - @bogovicj - - HHMI Janelia - - 2024-07-30 - - Implemented -* - Author - - Davis Bennett - - @d-v-b - - - - 2024-07-30 - - Implemented validation -* - Author - - Luca Marconato - - @LucaMarconato - - EMBL - - 2024-07-30 - - Implemented -* - Author - - Matt McCormick - - @thewtex - - ITK - - 2024-07-30 - - Implemented -* - Author - - Stephan Saalfeld - - @axtimwalde - - HHMI Janelia - - 2024-07-30 - - Implemented (with JB) -* - Endorser - - Norman Rzepka - - @normanrz - - Scalable Minds - - 2024-08-22 - - -* - Reviewer - - Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster - - toloudis, dyf, fcollman - - Allen Institutes - - 2024-11-28 - - [Review](./reviews/1/index) -* - Reviewer - - Will Moore, Jean-Marie Burel, Jason Swedlow - - will-moore, jburel, jrswedlow - - University of Dundee - - 2025-01-22 - - [Review](./reviews/2/index) -``` +This RFC is currently in RFC state `R4` (authors prepare responses). + +| **Role** | Name | GitHub Handle | Institution | Date | Status | +|----------|------|---------------|-------------|------|--------| +| **Author** | John Bogovic | @bogovicj | HHMI Janelia | 2024-07-30 | Implemented | +| **Author** | Davis Bennett | @d-v-b | | 2024-07-30 | Implemented validation | +| **Author** | Luca Marconato | @LucaMarconato | EMBL | 2024-07-30 | Implemented | +| **Author** | Matt McCormick | @thewtex | ITK | 2024-07-30 | Implemented | +| **Author** | Stephan Saalfeld | @axtimwalde | HHMI Janelia | 2024-07-30 | Implemented (with JB) | +| **Author** | Johannes Soltwedel | @jo-mueller | German Bioimaging e.V. | 2025-09-19 | Implemented | +| **Endorser** | Norman Rzepka | @normanrz | Scalable Minds | 2024-08-22 | | +| **Reviewer** | Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster | toloudis, dyf, fcollman | Allen Institutes | 2024-11-28 | [Review](rfcs:rfc5:review1) | +| **Reviewer** | Will Moore, Jean-Marie Burel, Jason Swedlow | will-moore, jburel, jrswedlow | University of Dundee | 2025-01-22 | [Review](rfcs:rfc5:review2)| ## Overview From 557fd1919a19dff645821ba20c10b150ed5dccd8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:55:45 +0200 Subject: [PATCH 005/115] used myST references for cross-referencing --- rfc/5/responses/1/index.md | 4 ++-- rfc/5/reviews/1/index.md | 1 + rfc/5/reviews/2/index.md | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 06dec1d2..b59daad7 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -14,7 +14,7 @@ for viewing these examples with BigWarp, a reference implementation. ## Review 1 Daniel Toloudis, David Feng, Forrest Collman, and Nathalie Gaudreault at the Allen Institute provided -[Review 1](https://ngff.openmicroscopy.org/rfc/5/reviews/1/index.html). +[Review 1](rfcs:rfc5:review1). ### Axes metadata @@ -105,7 +105,7 @@ The next revision of this RFC will refer to that specific version. ## Review 2 -[Review 2](https://ngff.openmicroscopy.org/rfc/5/reviews/2/index.html) was written by William Moore, Jean-Marie Burel, Jason +[Review 2](rfcs:rfc5:review2) was written by William Moore, Jean-Marie Burel, Jason Swedlow from the University of Dundee. ### Clarifications diff --git a/rfc/5/reviews/1/index.md b/rfc/5/reviews/1/index.md index 5539fc33..bf785f71 100644 --- a/rfc/5/reviews/1/index.md +++ b/rfc/5/reviews/1/index.md @@ -1,4 +1,5 @@ # RFC-5: Review 1 +(rfcs:rfc5:review1)= ## Review authors This review was written by: Daniel Toloudis1, David Feng2, Forrest Collman3, Nathalie Gaudreault1 diff --git a/rfc/5/reviews/2/index.md b/rfc/5/reviews/2/index.md index 5c90c15e..89688545 100644 --- a/rfc/5/reviews/2/index.md +++ b/rfc/5/reviews/2/index.md @@ -1,4 +1,5 @@ # RFC-5: Review 2 +(rfcs:rfc5:review2)= ## Review authors This review was written by: William Moore1, Jean-Marie Burel1, Jason Swedlow1 From ceeea1ce220585335a64e12f39f4b0c141df072d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:57:28 +0200 Subject: [PATCH 006/115] clarification on process --- rfc/5/versions/1/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index b71e8e4b..f7737e29 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -49,8 +49,10 @@ axis types. ## Proposal -Below is a slightly abridged copy of the proposed changes to the specification (examples are omitted), the full set of changes -including all examples are publicly available on the [github pull request](https://github.com/ome/ngff/pull/138). +Below is a complete copy of the proposed changes including suggestions from reviewers and contributors of the previously +associated [github pull request](https://github.com/ome/ngff/pull/138). +The changes, if approved, shall be translated into bikeshed syntax and added to the ngff repository in a separate PR. +This PR will then comprise complete json schemas when the RFC enters the SPEC phase (see RFC1). ### "coordinateSystems" metadata From 3e076ed8417ef8bf9ac8cf1995adddd0def9eaad Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:01:14 +0200 Subject: [PATCH 007/115] added example for orientations metadata Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index b59daad7..72e6e310 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -18,13 +18,19 @@ Daniel Toloudis, David Feng, Forrest Collman, and Nathalie Gaudreault at the All ### Axes metadata -We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating -examples would be helpful. We will coordinate with the authors of RFC-4 which we agree is essential for ensuring consistent -orientations of spatial (and maybe other) axis types. +We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating examples would be helpful. However, until RFC-4 has officially been made a part of the spec we feel it would be out of scope to reference such examples in this RFC. We do think, though, that the suggested `coordinateSystems` group can provide the necessary structure to remove the ambiguity of orientation adressed in RFC4. An orientation field could easily be added there in the following manner: + +```json +{ + "name" : "volume_micrometers", + "axes" : [ + {"name": "z", "type": "space", "unit": "micrometer", "orientation": "superior-to-inferior"}, + {"name": "y", "type": "space", "unit": "micrometer", "orientation": "anterior-to-posterior"}, + {"name": "x", "type": "space", "unit": "micrometer", "orientation": "left-to-right"} + ] +} +``` -There are now some examples provided using the metadata proposed in RFC-4 that will clarify this point. - -(TODO link) ### CoordinateSystem From 2b52e95d115db0c89c1135586fe718b18141fe76 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:01:33 +0200 Subject: [PATCH 008/115] removed reference to commit --- rfc/5/responses/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 72e6e310..06125aeb 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -59,7 +59,7 @@ that we hope continues. ### “Array” coordinate system -Thank you for the feedback, we have edited this section with motivation and clarity[1]. We hope that the edits along with motivating examples will. +Thank you for the feedback, we have edited this section with motivation and clarity. We hope that the edits along with motivating examples will help to make the applications for array coordinate systems clearer. ### Units From 4a588db0c4ded27c0c46a127fe76ed68ceb0f8de Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:02:15 +0200 Subject: [PATCH 009/115] text style --- rfc/5/responses/1/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 06125aeb..2f003632 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -65,8 +65,7 @@ Thank you for the feedback, we have edited this section with motivation and clar > Why have these explicit listed units and not just follow SI and specify the exponent on the SI unit that you are going for? -This RFC does change some parts of the Axes metadata, but not the specification of units which remains unchanged -relative to [v0.4](https://github.com/ome/ngff/blob/5067681721cc73ddf8b64692456cdda604cc659a/0.4/index.bs#L227-L229). +This RFC does change some parts of the Axes metadata, but not the specification of units which remains unchanged relative to [v0.4](https://github.com/ome/ngff/blob/5067681721cc73ddf8b64692456cdda604cc659a/0.4/index.bs#L227-L229). As such, this is out of the scope of this RFC. This is an interesting and valuable idea though, and revisiting units would be good topic for a new RFC in my opinion, since From a802f3b8fc78b35d5b804a58eccd719bf0ffe303 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:02:53 +0200 Subject: [PATCH 010/115] Update index.md --- rfc/5/responses/1/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 2f003632..a6b826e3 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -68,9 +68,8 @@ Thank you for the feedback, we have edited this section with motivation and clar This RFC does change some parts of the Axes metadata, but not the specification of units which remains unchanged relative to [v0.4](https://github.com/ome/ngff/blob/5067681721cc73ddf8b64692456cdda604cc659a/0.4/index.bs#L227-L229). As such, this is out of the scope of this RFC. -This is an interesting and valuable idea though, and revisiting units would be good topic for a new RFC in my opinion, since -they seem not to have been reconsidered since they -[were first introduced in 2021](https://github.com/ome/ngff/commit/0661115b93026f197d3787d99b74ec4d01614c99). +This is an interesting and valuable idea though, and revisiting units would be good topic for a new RFC in my opinion, +since they seem not to have been reconsidered since they [were first introduced in 2021](https://github.com/ome/ngff/commit/0661115b93026f197d3787d99b74ec4d01614c99). ### coordinateTransformations From 1631604838dd671e40bf14f5eee6796597cf4a99 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:03:40 +0200 Subject: [PATCH 011/115] answer question on affine transformation parsing Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 8 +++++++- rfc/5/versions/1/index.md | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index a6b826e3..6461ce9b 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -75,7 +75,13 @@ since they seem not to have been reconsidered since they [were first introduced > We think that conforming reading MUST be able to parse Affine transformations. -TODO +This is an important point to address. +Some transformations (i.e., scale, translations, rotations) can be expressed in terms of an affine transformation. +However, displaying affine transformations is implemented to different degrees in the field. +For some applications (e.g., image registration), affine transformations are a quintessential part of the metadata whereas other fields rarely encounter it. + +We have added statements to the proposal, recommending that writers SHOULD prefer less expressive transformations (e.g., a sequence of scale and translation) over affine transformations if possible. +If encountering a transformation that cannot be parsed, readers/viewers SHOULD display a warning to inform the user of how metadata is handled. > Rotation: there appears to be inconsistency in the doc > ... diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index f7737e29..9866d9f8 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -193,10 +193,10 @@ input space to *points* in the output space. typefieldsdescription - Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; -- SHOULD parse `mapAxis`, `affine` transformations; +- SHOULD parse `mapAxis`, `affine`, `rotation` transformations; +- SHOULD display an informative warning if encountering transformations that cannot be parsed; - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; From 7d33ce403dd84678454dfc6e4e3d810da5db9202 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:04:19 +0200 Subject: [PATCH 012/115] fix rotation matrix dimensions Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 2 +- rfc/5/versions/1/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 6461ce9b..2b5b3c47 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -87,7 +87,7 @@ If encountering a transformation that cannot be parsed, readers/viewers SHOULD d > ... > Should it be `List[List[number]]`? -Yes, thank you for catching this. It is now [correct](https://github.com/bogovicj/ngff/commit/649f6234c2a2bef475b5873d1982f70cd6ee8d07). +Yes, thank you for catching this. It is now correct. ### Parameters diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 9866d9f8..6402387a 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -166,7 +166,7 @@ input space to *points* in the output space. affine transformation matrix stored as a flat array stored either with json uing the affine field or as binary data at a location in this container (path). If both are present, the binary values at path should be used. rotation - one of:
"rotation":List[number],
"path":str + one of:
"rotation":List[List[number]],
"path":str rotation transformation matrix stored as an array stored either with json or as binary data at a location in this container (path). If both are present, the binary parameters at path are used. From 68b44dcd4b197e82ba3703c418c04f3c31739510 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:04:51 +0200 Subject: [PATCH 013/115] answered question on sample implementation Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 2b5b3c47..59ed7be9 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -100,8 +100,12 @@ is the consensus, so the parameters will remain as they are. ### Process -> Should it be customary to provide a sample implementation? +> Should it be customary to provide a sample implementation? +We believe that sammple implementations are outside the scope of RFCs, even if the changes are substantial as in this case. +Writing implementations would certainly fail to address the variety of programming languages and tools in the community +and thus inadvertantly prioritize some tools over others. +However, one could consider the json-schemas as a sort of implementation that allows implementors to test their written data against a common baseline to ensure the integrity of written data. > Is it okay for an RFC to link out to other things, rather than being > completely self-contained? If it's not there is a danger of it From 02dd25cc3aea6be91e4d1d26be053a660dc5ac2f Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:05:11 +0200 Subject: [PATCH 014/115] answered question on linking outside sources Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 59ed7be9..bf6e7299 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -111,11 +111,11 @@ However, one could consider the json-schemas as a sort of implementation that al > completely self-contained? If it's not there is a danger of it > effectively changing without it being properly versioned. -This is an important point. Exactly how and if linking to outside artifacts is allowed may belong in a broader community -discussion. To keep resources relevant to, but "outside of" this RFC versioned and stable, -they will be posted and archived in a permanent repository, and assigned a DOI. -The next revision of this RFC will refer to that specific version. - +This is an important point. Exactly how and if linking to outside artifacts is allowed may belong in a broader community +discussion. To keep resources relevant to, but "outside of" this RFC versioned and stable, they will be posted and archived +in a permanent repository, and assigned a DOI. The next revision of this RFC will refer to that specific version. +Since the RFC and revision texts (such as this one), are ultimately historic artifacts and not authoritative, we think +that example collections can be part of the RFC text, but should be kept outside the core specification document. ## Review 2 From adc145915fb6fce88d2bc58ac50dcdf832f9777a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:07:05 +0200 Subject: [PATCH 015/115] answer questions on units Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index bf6e7299..8e8dcfdc 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -131,8 +131,7 @@ Swedlow from the University of Dundee. > Why not require a value for units and then make “arbitrary” or some sentinel the value that people must specify to say “no coordinates?” -I agree that having some fixed value to mean "no units" would be a reasonable choice. In my opinion, having no units key -reflects that "there are no units" better than a placeholder (and it avoids having to choose the value of the placeholder). +I agree that having some fixed value to mean "no units" would be a reasonable choice. In my opinion, having no units key reflects that "there are no units" better than a placeholder (and it avoids having to choose the value of the placeholder). > I wonder why `Arrays are inherently discrete (see Array coordinate systems, below) but are often used to store discrete samples > of a continuous variable.` isn’t true of everything? Aren’t the images themselves samplings? In general I wasn’t totally clear From 5a25f0058dec5fe4f4cc1461a679f89e520dd67f Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:09:14 +0200 Subject: [PATCH 016/115] added more information on `arrayCoordinateSystems` Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 20 ++++++++----------- rfc/5/versions/1/index.md | 41 +++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 8e8dcfdc..41e511ab 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -137,10 +137,9 @@ I agree that having some fixed value to mean "no units" would be a reasonable ch > of a continuous variable.` isn’t true of everything? Aren’t the images themselves samplings? In general I wasn’t totally clear > on how interpolation works - I understand it is a user-applied “transformation” in which case I think that should be clear. -I agree that digital images always contain samples. The purpose of this distinction is to communicate, to humans and -software, a property of *the signal that is being sampled,* not the representation that is stored. That is the reason -that "array coordinate systems" have discrete axes - because they have no additional interpretation. -I added some clarifying text [1]. +I agree that digital images always contain samples. The purpose of this distinction is to communicate, to humans and software, a property of *the signal that is being sampled,* not the representation that is stored. That is the reason that "array coordinate systems" have discrete axes - because they have no additional interpretation. + +Some clarifying text was added. ## Comment-2 @@ -151,17 +150,14 @@ I added some clarifying text [1]. > Axis type of “array” is a bit confusing. It basically means “unknown”? -It could mean "unknown" if there are no other coordinate systems that label it. More importantly, it serves as a placeholder -for operations that work in "pixel coordinates," not "physical coordinates." -I added some clarifying text [1]. - +It could mean "unknown" if there are no other coordinate systems that label it. +More importantly, it serves as a placeholder for operations that work in "pixel coordinates," not "physical coordinates." +Some clarifying text under the `Array coordinate systems` section was added. > arrayCoordinateSystem specifying dimension names is now redundant with zarr v3 dimension names -Good point, and I agree; but the zarr spec is more permissive than the ngff spec, specifically because it -[allows null or duplicate `dimension_names`](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#dimension-names). -As a result, we will need require adding additional constraints to the dimension names that they be unique and not null [2]. -This is currently a requirement for the names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md) +Good point, and I agree; but the zarr spec is more permissive than the ngff spec, specifically because it [allows null or duplicate `dimension_names`](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#dimension-names). +As a result, we will need require adding additional constraints to the dimension names that they be unique and not null. This is currently a requirement for the [names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md) ## Other diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 6402387a..72b06080 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -98,25 +98,42 @@ Note: The most common methods for interpolation are "nearest neighbor", "linear" 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 -### 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. Its name is the path to the array -in the container, its axes have `"type":"array"`, are unitless, and have default "name"s. The ith axis has `"name":"dim_i"` -(these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). +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 "name"s. +The ith axis has `"name":"dim_i"` (these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). +The `dimension_names` must be unique and non-null. +````{admonition} Example +```json +{ + "name": "0", + "axes": [ + {"name": "dim_0", "type": "array"}, + {"name": "dim_1", "type": "array"}, + {"name": "dim_2", "type": "array"} + ] +} +``` +```` -The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. The axis with -name `"dim_i"` is the ith element of the `"axes"` list. The axes and their order align with the `shape` -attribute in the zarr array attributes (in `.zarray`), 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/v2.html#arrays), +The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. +The axis with name `"dim_i"` is the ith element of the `"axes"` list. +The axes and their order align with the `shape` attribute in the zarr array attributes (in `.zarray`), +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/v2.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"`. +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 From 9e73f80dfcb403735772ae8651ae27b39a0d2700 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:33:42 +0200 Subject: [PATCH 017/115] justify breaking changes Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 41e511ab..b2eddbdf 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -159,6 +159,14 @@ Some clarifying text under the `Array coordinate systems` section was added. Good point, and I agree; but the zarr spec is more permissive than the ngff spec, specifically because it [allows null or duplicate `dimension_names`](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#dimension-names). As a result, we will need require adding additional constraints to the dimension names that they be unique and not null. This is currently a requirement for the [names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md) +> It is a bit unfortunate that coordinateTransforms now require that the input and output spaces be named for multiscale datasets, +> where it was previously implicit, since (a) it is redundant and (b) it means existing multiscale metadata is no longer valid +> under this new version. + +To our knowledge, every version in the past introduced breaking changes to the specification with the result that ome-zarr files +of newer version could not be read anymore. As for the redundancy of specifiying input- and output-spaces in multiscales transformations, +we agree in principle. However, we also see no harm in additional explicitness. + ## Other From 84ce3b6f48588e9c4dd092156471cebe3e895c87 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:35:43 +0200 Subject: [PATCH 018/115] answer questions on parent-level transformations and input/output values --- rfc/5/responses/1/index.md | 194 ++++++++++++++++++++++++++++++++++++- rfc/5/versions/1/index.md | 30 +++--- 2 files changed, 210 insertions(+), 14 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index b2eddbdf..9418ae03 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -124,6 +124,198 @@ Swedlow from the University of Dundee. ### Clarifications +>> “Coordinate transformations from array to physical coordinates MUST be stored in multiscales, and MUST be duplicated in the attributes of the zarr array” +>> +> Why is this duplication necessary and what does the array zarr.json look like? + +The multiscales metadata block was designed as a self-contained description on how to read the data therein. The addition of an additional `coordinateTransformations` key inside the multiscale's attributes was originally intended to provide a clear, authoritiative coordinate transformation, the configuration of which would apply to all multiscales and make it easier for a reader to identify the correct default coordinate system of a multiscale image. + +However, we have removed this requirement and furthermore tightened the requirements for `coordinateTransformations`. + +- **Inside `multiscales > datasets`**: `coordinateTransformations` herein MUST be restricted to a single `scale`, `identity` or sequence of `translation` and `scale` transformations. + The output of these `coordinateTransformations` MUST be the default coordinate system, which is the last entry in the list of coordinate systems. +- **Inside `multiscales > coordinateTransformations`**: One MAY store additional transformations here. + The `input` to these transformations MUST be the default coordinate system and the `output` can be another coordinate system defined under `multiscales > coordinateSystems`. +- **Parent-level `coordinateTransformations`**: Transformations between two or more images MUST be stored in the parent-level `coordinateTransformations` group. The `input` to these transformations MUST be paths to the respective images. The `output` can be a path to an image or the name of a coordinate system. If both path and name exist, the path takes precedence. The authoritiative coordinate system under `path` is the *first* coordinate system in the list. + +This separation of transformations (inside `multiscales > datasets`, under `multiscales > coordinateTransformations` and under `coordinateTransformations` in the parent-level) provides flexbility for different usecases while still maintaining a level of rigidity for easier implementations. + +````{admonition} Example + +Consider the following example for the use of all possible places to store `coordinateTransformations`. +It comes from the SCAPE microscopy context, where lightsheet stacks are acquired under a skew angle, +and need to be *deskewed* with an affine transformation. The acquired stack also comes with a set of relevant microscope motor coordinates, +which place the object in world coordinates. +One may wish to attach the affine transformation to the multiscales itself, without having to read the parent-level zarr group that defines the world coordinate system. + +The `multiscales` metadata contains this: +```json +{ + "multiscales": [{ + "version": "0.5-dev", + "name": "example", + "coordinateSystems" : [ + { + "name" : "deskewed", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + }, + { + "name" : "physical", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + } + ], + "datasets": [ + { + "path": "0", + // the transformation of other arrays are defined relative to this, the highest resolution, array + "coordinateTransformations": [{ + "type": "identity", + "input": "/0", + "output": "physical" + }] + }, + { + "path": "1", + "coordinateTransformations": [{ + // the second scale level (downscaled by a factor of 2 relative to "0" in zyx) + "type": "scale", + "scale": [2, 2, 2], + "input" : "/1", + "output" : "physical" + }] + }, + { + "path": "2", + "coordinateTransformations": [{ + // the third scale level (downscaled by a factor of 4 relative to "0" in zyx) + "type": "scale", + "scale": [4, 4, 4], + "input" : "/2", + "output" : "physical" + }] + } + ], + }, + "coordinateTransformations": [ + { + "type": "affine", + "name": "deskew-transformation", + "input": "physical", + "output": "deskewed", + "affine": [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0.785, 1, 0], + [0, 0, 0, 1] + ] + } + ] + ] +} +``` + +The metadata on a parent-level zarr group would then look as follows - the `input` to the translation transform that locates the stack at the correct location in world coordinates is the path to the input image. +The output is a defined coordinate system: + +```json +"ome": { + "coordinateSystems":[ + { + "name" : "world", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + } + ], + "coordinateTransformations": [ + { + "name": "stack0-to-world", + "type": "translation", + "translation": [0, 10234, 41232], + "input": "path/to/stack", + "output" "world" + } + ] +} + +``` +The first coordinate system defined under the `multiscales` group above (`deskewed`) serves as the authoritative coordinate system for the multiscales image. +```` + +> In what format or structure is this data stored? + +In a simplified way, the root level of the store should resemble this structure: + + ``` + root + ├─── imageA + ├─── imageA + └─── zarr.json + ``` + + If the inputs and outputs to the transformations to the top-level `zarr.json` are images, the content MUST provide information about the spatial relationship (`coordinateTransformations`) between them: + + ```json + ... + "ome": { + "version": "0.6", + "coordinateTransformations": [ + { + "type": "affine", + "input": "path/to/image_A", + "output": "path/to/image_B", + "affine": ["actual affine matrix"], + "name": "Transform that aligns imageA with imageB" + } + ] + } + ``` + +> Does the parent zarr group contain the paths to the child images? + +In the present form, the spec states that "[...] *the coordinate system's name may be the path to an array.*" +We agree that this lacks clarity. +We therefore added that the `input` of a `coordinateTransformation` entry in the parent-level zarr group MUST be the path to the input image. + +> Do the top-level `coordinateTransformations` refer to coordinateSystems that are in child images? + +Yes, although they do so implictly. +If a `coordinateTransformation` in the parent-level group refers to child images through its `input`/`output` fields, the authoritiative coordinate transformation of the linked (multiscales) image is the *first* `coordinateSystem` therein. +The first `coordinateSystem` therein is authoritiative for the image. +This formalism also provides enough distinction from an image's "default" (aka "physical") coordinate system, +which is the *last* `coordinateSystem` inside the image. +If no additional `coordinateTransformations` are defined under `multiscales > coordinateTransformations`, only one `coordinateSystem` needs to be defined in the multiscales. +This coordinate system then serves as "default" coordinate system as well as authoritative coordinate system for a parent-level reference. + + > Are these child images referred to via a /path/to/volume/zarr.json? + +Yes. We have changed the spec, so that `"input": /path/to/volume/"` is the required way of referencing an input image/coordinateSystem. + + > What is the expectation for a conforming viewer when opening the top-level group? Should the viewer also open and display all the child images? + + This is an important and valid point to raise. The *in silico* behavior that comes to mind, is to open all present images along with their + correct transformations as separate layers, which can be toggled on or off. The spec currently requires conforming readers to read scale and translation transformations, which has remained unchanged. However, readers/viewers are now recommended to output an informative warning if a transformation is encountered that cannot be parsed. + + > It seems like the top-level zarr group with "coordinate transformations describing the relationship between two image coordinate systems" introduces a + “Collection” of images. The discussion on adding support for Collection to the specification has been captured in Collections Specification but it has + not been introduced yet. + > + > Are you also proposing to introduce support for Collection as part of this RFC? In our opinion, this is probably out of scope at this stage, but an example might clarify the importance in the authors’ view. + + It is true that storing several images under the same root-store as proposed here, resembles the proposed [Collections](https://github.com/ome/ngff/issues/31). However, the spatial relationship between images in a root zarr provides a distinctively meaningful kind of collections of images, namely their spatial relationship. Other solutions like storing images with spatial relationships into separate files along with references to each other come at the risk of putting the spatial reationship at the mercy of tidy file management. With this proposal, we seek a self-contained solution to spatial relationships, which require storage in a common location. + +Moreover, while images with coordinate transforms in a root zarr provide a kind of collection, they don't do so more than a multiscale, a plate or a well object does. + ### Implementation section @@ -146,7 +338,7 @@ Some clarifying text was added. > Rebasing to take into account the change to zarr v3 (e.g. remove references to .zarray and replace with zarr.json) / “ome” top-level key would be helpful for clarity. -[Done.](https://github.com/bogovicj/ngff/commit/52d7924f1522bdf917ea912fc416ae55b3229ebb) +Done. > Axis type of “array” is a bit confusing. It basically means “unknown”? diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 72b06080..c4d1a6e3 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -217,10 +217,13 @@ Conforming readers: - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; -Coordinate transformations from array to physical coordinates MUST be stored in multiscales, -and MUST be duplicated in the attributes of the zarr array. Transformations between different images MUST be stored in the -attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD -be stored in a zarr group `"coordinateTransformations"`. +Coordinate transformations can be stored in multiple places to reflect different usecases. + +- Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata) +- Additional transformations for single images MUST be stored in group-level attributes of the multiscales. +- Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group `"coordinateTransformations"`. + +Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations (i.e., sequence[translation, rotation] instead of affine transformation with translation/rotation) component.
 store.zarr                      # Root folder of the zarr store
@@ -250,18 +253,19 @@ store.zarr                      # Root folder of the zarr store
 
 ### Additional details
 
-Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value
-corresponding to the name of a coordinate system. The coordinate system's name may be the path to an array, and therefore may
-not appear in the list of coordinate systems.
+Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value.
+Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the `transformation` of an `inverseOf` transformation.
+In these two cases input and output could, in some cases, be omitted (see below for details).
 
-Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the
-`transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for
-details).
+If used in a parent-level zarr-group, the `input` field MUST be a path to the input image. 
+The authoritative coordinate system for the input image is the first `coordinateSystem` defined therein.
+The `output` field can be a `path` to an output image or the name of a `coordinateSystem` defined in the parent-level zarr group.
+If the names of `input` or `output` can be both a `path` or the name of a `coordinateSystem`, `path` MUST take precedent.
 
-Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output` as arrays
-of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below for
-details).
+For usage in multiscales, see [multiscales section](#multiscales-metadata) for details.
 
+Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output`
+as arrays of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below 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.

From bd2287cad8e77a5bec3f30792a0d3a93dabc57bc Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:37:03 +0200
Subject: [PATCH 019/115] add entire `multiscales` section for completeness

---
 rfc/5/versions/1/index.md | 148 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 148 insertions(+)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index c4d1a6e3..9f8c11dd 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -521,6 +521,154 @@ Practically, non-invertible transformations have finite extents, so bijection tr
 to be correct / consistent for points that fall within those extents. It may not be correct for any point of
 appropriate dimensionality.
 
+### "multiscales" metadata
+
+Metadata about an image can be found under the "multiscales" key in the group-level OME-Zarr Metadata. Here, "image" refers to 2 to 5 dimensional data representing image or volumetric data with optional time or channel axes.
+It is stored in a multiple resolution representation.
+
+"multiscales" contains a list of dictionaries where each entry describes a multiscale image.
+
+Each "multiscales" dictionary MUST contain the field "coordinateSystems", whose value is an array containing valid coordinate
+system metadata (see [coordinate systems](#coordinatesystems-metadata)). The last entry of this array is the "default" coordinate system and MUST contain
+transformations from array to physical coordinates. It should be used for viewing and processing unless a use case dictates
+otherwise. It will generally be a representation of the image in its native physical coordinate system. 
+
+The following MUST hold for all coordinate systems.
+The length of "axes" must be between 2 and 5 and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path").
+The "axes" MUST contain 2 or 3 entries of "type:space" and MAY contain one additional entry of "type:time" and MAY contain one
+additional entry of "type:channel" or a null / custom type.  The order of the entries MUST correspond to the order of dimensions
+of the Zarr arrays. In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present),
+followed by the  "channel" or custom axis (if present) and the axes of type "space".  If there are three spatial axes where two
+correspond to the image plane ("yx") and images are stacked along the other (anisotropic) axis ("z"), the spatial axes SHOULD be
+ordered as "zyx".
+
+Each "multiscales" dictionary MUST contain the field "datasets", which is a list of dictionaries describing the arrays storing
+the individual resolution levels.  Each dictionary in "datasets" MUST contain the field "path", whose value is a string
+containing the path to the Zarr array for this resolution relative to the current Zarr group. The "path"s MUST be ordered from
+largest (i.e. highest resolution) to smallest.  Every Zarr array referred to by a "path" MUST have the same number of dimensions
+and MUST NOT have more than 5 dimensions.  The number of dimensions and order MUST correspond to number and order of "axes".
+
+Each dictionary in "datasets" MUST contain the field "coordinateTransformations", whose value is a dictionary that defines a
+transformation that maps Zarr array coordinates for this resolution level to the default coordinate system (the last entry of
+the "coordinateSystems" array). The transformation is defined according to [transformations metadata](#transformation-types). 
+The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location "path".
+The value of "input" SHOULD equal the value of "path", but implementations should always treat the value of "input"
+as if it were equal to the value of "path".
+The value of the transformation’s "output" MUST be the name of the default coordinate system.
+
+This transformation MUST be one of the following:
+
+* A single scale or identity transformation
+* A sequence transformation containing one scale and one translation transformation.
+
+In these cases, the scale transformation specifies the pixel size in physical units or time duration. If scaling information is
+not available or applicable for one of the axes, the value MUST express the scaling factor between the current resolution and
+the first resolution for the given axis, defaulting to 1.0 if there is no downsampling along the axis. This is strongly
+recommended so that the the "default" coordinate system of the image avoids more complex transformations.
+
+If applications require additional transformations, each "multiscales" dictionary MAY contain the field "coordinateTransformations",
+describing transformations that are applied to all resolution levels in the same manner.
+The value of "input" SHOULD equal the name of the "default" coordinate system.
+
+Each "multiscales" dictionary SHOULD contain the field "name".
+
+Each "multiscales" dictionary SHOULD contain the field "type", which gives the type of downscaling method used to generate the multiscale image pyramid.
+It SHOULD contain the field "metadata", which contains a dictionary with additional information about the downscaling method.
+
+````{admonition} Example
+
+A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution levels could look like this:
+
+```json
+{
+  "zarr_format": 3,
+  "node_type": "group",
+  "attributes": {
+    "ome": {
+      "version": "0.5",
+      "multiscales": [
+        {
+          "name": "example",
+          "coordinateSystems": [
+              {
+                "name": "example",
+                "axes": [
+                  { "name": "t", "type": "time", "unit": "millisecond" },
+                  { "name": "c", "type": "channel" },
+                  { "name": "z", "type": "space", "unit": "micrometer" },
+                  { "name": "y", "type": "space", "unit": "micrometer" },
+                  { "name": "x", "type": "space", "unit": "micrometer" }
+                ]
+              }
+          ],
+          "datasets": [
+            {
+              "path": "0",
+              "coordinateTransformations": {
+                // the voxel size for the first scale level (0.5 micrometer)
+                // and the time unit (0.1 milliseconds), which is the same for each scale level
+                "type": "scale",
+                "scale": [1.0, 1.0, 0.5, 0.5, 0.5],
+                "input": "0",
+                "output": "example"
+              }
+            },
+            {
+              "path": "1",
+              "coordinateTransformations": {
+                // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer)
+                // and the time unit (0.1 milliseconds), which is the same for each scale level
+                "type": "scale",
+                "scale": [1.0, 1.0, 1.0, 1.0, 1.0],
+                "input": "1",
+                "output": "example"
+              }
+            },
+            {
+              "path": "2",
+              "coordinateTransformations": {
+                // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer)
+                // and the time unit (0.1 milliseconds), which is the same for each scale level
+                "type": "scale",
+                "scale": [1.0, 1.0, 2.0, 2.0, 2.0],
+                "input": "2",
+                "output": "example"
+              }
+            }
+          ],
+          "type": "gaussian",
+          "metadata": {
+            "description": "the fields in metadata depend on the downscaling implementation. Here, the parameters passed to the skimage function are given",
+            "method": "skimage.transform.pyramid_gaussian",
+            "version": "0.16.1",
+            "args": "[true]",
+            "kwargs": { "multichannel": true }
+          }
+        }
+      ]
+    }
+  }
+}
+```
+````
+
+
+
+If only one multiscale is provided, use it. Otherwise, the user can choose by
+name, using the first multiscale as a fallback:
+
+```python
+datasets = []
+for named in multiscales:
+    if named["name"] == "3D":
+        datasets = [x["path"] for x in named["datasets"]]
+        break
+if not datasets:
+    # Use the first by default. Or perhaps choose based on chunk size.
+    datasets = [x["path"] for x in multiscales[0]["datasets"]]
+```
+
+
 ## Specific feedback requested
 
 We ask the reviewers for one specific piece of feedback. Specifically about whether parameters for transformations should

From d6ad74f4bee1cb85d4fe25c5f386b3befa9f7dd7 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:37:55 +0200
Subject: [PATCH 020/115] reorganized heading levels

---
 rfc/5/versions/1/index.md | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index 9f8c11dd..7affa5e8 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -74,7 +74,7 @@ regions of interest, etc., SHOULD ensure that they are in the same coordinate sy
 transformed to the same coordinate system before doing analysis. See the example below.
 
 
-### "axes" metadata
+#### "axes" metadata
 
 "axes" describes the dimensions of a coordinate systems. It is a list of dictionaries, where each dictionary 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.
@@ -136,7 +136,7 @@ the last dimension of an array in "C" order are stored contiguously on disk or i
 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
+#### Coordinate convention
 
 **The pixel/voxel center is the origin of the continuous coordinate system.**
 
@@ -251,7 +251,8 @@ store.zarr                      # Root folder of the zarr store
             └── .zarray         # the array attributes
 
-### Additional details + +#### Additional details Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value. Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the `transformation` of an `inverseOf` transformation. @@ -309,7 +310,7 @@ stored as a 2D zarr array, the first dimension indexes rows and the second dimen [4,5,6]]` has 2 rows and 3 columns). -### Transformation types +#### Transformation types Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value of "input" is an array, it's length gives the input dimension, otherwise the length of "axes" for the coordinate @@ -318,14 +319,14 @@ length gives the input dimension, otherwise it is given by the length of "axes" the name of the "input". If the value of "output" is an array, its length 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 `identity` transformations map input coordinates to output coordinates without modification. The position of the ith axis of the output coordinate system is set to the position of the ith axis of the input coordinate system. `identity` transformations are invertible. -#### mapAxis +##### mapAxis `mapAxis` transformations describe axis permutations as a mapping of axis names. Transformations MUST include a `mapAxis` field whose value is an object, all of whose values are strings. If the object contains `"x":"i"`, then the transform sets the value @@ -334,7 +335,7 @@ system, the `mapAxis` MUST have a corresponding field. For every value of the ob coordinate system with that name. Note that the order of the keys could be reversed. -#### translation +##### translation `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 @@ -348,7 +349,7 @@ invertible. : The translation parameters stored as a JSON list of numbers. The list MUST have length `N`. -#### scale +##### scale `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 @@ -362,7 +363,7 @@ transformations are invertible. : The scale parameters stored as a JSON list of numbers. The list MUST have length `N`. -#### affine +##### affine `affine`s are [matrix transformations](#matrix-transformations) from N-dimensional inputs to M-dimensional outputs are represented as the upper `(M)x(N+1)` sub-matrix of a `(M+1)x(N+1)` matrix in [homogeneous @@ -376,7 +377,7 @@ invertible when `N` equals `M`. The matrix MUST be stored as a 2D array either : The affine parameters stored in JSON. The matrix MUST be stored as 2D nested array where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. -#### rotation +##### rotation `rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation transformation SHOULD be preferred to its equivalent affine. Input and output dimensionality (N) MUST be identical. Rotations @@ -390,7 +391,7 @@ MUST be stored as a 2D array either as json or in a zarr array. `rotation` trans : The parameters stored in JSON. The matrix MUST be stored as a 2D nested array where the outer array MUST be length `N` and the inner arrays MUST be length `N`. -#### inverseOf +##### inverseOf 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. @@ -406,7 +407,7 @@ as `input` and fixed image coordinates as `output`, a choice that many users and ``` -#### sequence +##### sequence 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 @@ -433,7 +434,7 @@ outlined below: : A non-empty array of transformations. -#### coordinates and displacements +##### coordinates and displacements `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 @@ -491,7 +492,6 @@ For `displacements`: * `input` and `output` MUST have an equal number of dimensions. -#### byDimension `byDimension` transformations build a high dimensional transformation using lower dimensional transformations on subsets of dimensions. @@ -506,7 +506,7 @@ on subsets of dimensions. -#### bijection +##### bijection A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations are explicitly defined. Each direction SHOULD be a transformation type that is not closed-form invertible. From 9e783e9038a7af2c0522b24a3d0ad16748095393 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:38:28 +0200 Subject: [PATCH 021/115] Point out axis name uniqueness Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 7affa5e8..fbf3a6cc 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -58,7 +58,7 @@ This PR will then comprise complete json schemas when the RFC enters the SPEC ph ### "coordinateSystems" metadata A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: -- MUST contain the field "name". The value MUST be a non-empty string that is unique among `coordinateSystem`s. +- 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). From bd9880e30f44109c3914043559681572e1930d36 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:38:45 +0200 Subject: [PATCH 022/115] added example to `coordinateSystems` metadata Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index fbf3a6cc..da2f3b48 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -61,10 +61,26 @@ A "coordinate system" is a collection of "axes" / dimensions with a name. Every - 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 order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that coordinate system. +For the above 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 above is three dimensional (3D). -The order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that -coordinate system. The "dimensionality" of a coordinate system -is indicated by the length of its "axes" array. The "volume_micrometers" example coordinate system above is three dimensional (3D). +````{admonition} 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 `name`s may contain semantically meaningful information, but can be arbitrary. As a result, two From a8db9d026af6207a9671aebd0773d7df949eda19 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:38:59 +0200 Subject: [PATCH 023/115] text style Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index da2f3b48..33966ca4 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -101,7 +101,7 @@ transformed to the same coordinate system before doing analysis. See the example - 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. -If part of multiscales metadata, the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. +If part of metadata, 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 From 091fd7fde7eebcedc50622e8d9162dd397ec5050 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:39:34 +0200 Subject: [PATCH 024/115] add info on discretness Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 33966ca4..1edaa55a 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -103,16 +103,13 @@ transformed to the same coordinate system before doing analysis. See the example If part of metadata, 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 of 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. +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 of 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 From 02f909aa670cd099549eb91c80076c46cb972abf Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:39:43 +0200 Subject: [PATCH 025/115] text style Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 1edaa55a..7f217ce2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -153,12 +153,8 @@ The name and axes names MAY be customized by including a `arrayCoordinateSystem` **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. +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. ### "coordinateTransformations" metadata From 93a6a5a5e04b499bc3ba1bb9ebd2e0b208e83563 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:39:58 +0200 Subject: [PATCH 026/115] text style Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 7f217ce2..d0639ce3 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -161,9 +161,8 @@ The center of a 2d pixel corresponding to the origin `(0,0)` in the discrete arr "coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). For example, to map an array's discrete coordinate system to its corresponding physical coordinates. -Coordinate transforms are in the "forward" direction. They represent functions from *points* in the -input space to *points* in the output space. - +Coordinate transforms are in the "forward" direction. +They represent functions from *points* in the input space to *points* in the output space. - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). From 72ea8f06f967db52b6f8fd4903ea60218ca5b75c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:40:19 +0200 Subject: [PATCH 027/115] added type hints to `coordinateTransformation` fields Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index d0639ce3..f75477d8 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -164,11 +164,11 @@ For example, to map an array's discrete coordinate system to its corresponding p Coordinate transforms are in the "forward" direction. They represent functions from *points* in the input space to *points* in the output space. -- MUST contain the field "type". +- MUST contain the field "type" (string). - MUST contain any other fields required by the given "type" (see table below). -- MUST contain the field "output", unless part of a `sequence` or `inverseOf` (see details). -- MUST contain the field "input", unless part of a `sequence` or `inverseOf` (see details). -- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. +- MUST contain the field "output" (string), unless part of a `sequence` or `inverseOf` (see details). +- MUST contain the field "input" (string), unless part of a `sequence` or `inverseOf` (see details). The value of "input", if used, MUST specify the path to the input image. +- MAY contain the field "name" (string). Its value MUST be unique across all "name" fields for coordinate transformations. - Parameter values MUST be compatible with input and output space dimensionality (see details). From aad15f6351ff4575c48e3dc20472f328ebc7e249 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:40:37 +0200 Subject: [PATCH 028/115] text style Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index f75477d8..fca47827 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -171,6 +171,7 @@ They represent functions from *points* in the input space to *points* in the out - MAY contain the field "name" (string). Its value MUST be unique across all "name" fields for coordinate transformations. - Parameter values MUST be compatible with input and output space dimensionality (see details). +The following transformations are supported:
identity From da043fa2eb78ab71f8803c8d79233a28bd4715a3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:40:54 +0200 Subject: [PATCH 029/115] added info to axes metadata Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index fca47827..2d1540d1 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -92,7 +92,9 @@ transformed to the same coordinate system before doing analysis. See the example #### "axes" metadata -"axes" describes the dimensions of a coordinate systems. It is a list of dictionaries, where each dictionary describes a dimension (axis) and: +"axes" describes the dimensions of a coordinate systems and adds an interpretation to the samples along that dimension. + +It is a list of dictionaries, where each dictionary 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. - 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. From 846a5274d5efcb738563ab4f49b219ee4a1b69e0 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:41:10 +0200 Subject: [PATCH 030/115] replaced zattrs by zarr.json Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 2d1540d1..5ae5e4a2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -239,27 +239,24 @@ Implementations SHOULD prefer to store transformations as a sequence of less exp
 store.zarr                      # Root folder of the zarr store
 │
-├── .zattrs                     # coordinate transformations describing the relationship between two image coordinate systems
+├── zarr.json                   # coordinate transformations describing the relationship between two image coordinate systems
 │                               # are stored in the attributes of their parent group.
 │                               # transformations between 'volume' and 'crop' coordinate systems are stored here.
 │
 ├── coordinateTransformations   # transformations that use array storage go in a "coordinateTransformations" zarr group.
 │   └── displacements           # for example, a zarr array containing a displacement field
-│       ├── .zattrs
-│       └── .zarray
+│       └── zarr.json
 │
 ├── volume
-│   ├── .zattrs                 # group level attributes (multiscales)
+│   ├── .zarr.json              # group level attributes (multiscales)
 │   └── 0                       # a group containing the 0th scale
 │       └── image               # a zarr array
-│           ├── .zattrs         # physical coordinate system and transformations here
-│           └── .zarray         # the array attributes
+│           └── .zarr.json      # physical coordinate system and transformations here
 └── crop
-    ├── .zattrs                 # group level attributes (multiscales)
+    ├── .zarr.json              # group level attributes (multiscales)
     └── 0                       # a group containing the 0th scale
         └── image               # a zarr array
-            ├── .zattrs         # physical coordinate system and transformations here
-            └── .zarray         # the array attributes
+            └── .zarr.json      # physical coordinate system and transformations here
 
From dd99be31315a56aac205da292d136bfe99a7bad2 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:41:30 +0200 Subject: [PATCH 031/115] added and reformatted comprehensive example to coord. transforms Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 5ae5e4a2..1a304162 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -259,6 +259,65 @@ store.zarr # Root folder of the zarr store └── .zarr.json # physical coordinate system and transformations here +````{admonition} Example +Two instruments simultaneously image the same sample from two different angles, and the 3D data from both instruments are calibrated to "micrometer" units. +Two samples are collected ("sampleA" and "sampleB"). An analysis of sample A requires measurements from both instruments' images 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 coordinates (x,y,z) may not correspond to the measurement at the same point in instrument 2 +(i.e., it may not be the same physical location in the sample). +To analyze both images together, they must be in the same coordinate system. + +The set of coordinate transformations encodes relationships between coordinate systems, +specifically, how to convert points and images to different coordinate systems. +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, the ROI should be transformed to the "sampleA_image1" 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 under `root/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 under `root/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 From cb7f054384009b0fcb82dc6941946520b2481d74 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:41:39 +0200 Subject: [PATCH 032/115] fixed header level Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 1a304162..15d41ba2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -559,6 +559,7 @@ For `displacements`: * `input` and `output` MUST have an equal number of dimensions. +##### byDimension `byDimension` transformations build a high dimensional transformation using lower dimensional transformations on subsets of dimensions. From 2057c4977b0c2cad466c3c8d409c485a271a4862 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:41:48 +0200 Subject: [PATCH 033/115] typo Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 15d41ba2..c72c52d2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -523,7 +523,7 @@ The `i`th value of the array along the `coordinate` or `displacement` axis refer 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 field: +inverses. Metadata for these coordinate transforms have the following fields:
path
From 45a66ffee39b775189996a3999cacd759108f3cc Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:41:56 +0200 Subject: [PATCH 034/115] typo Co-Authored-By: John Bogovic --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index c72c52d2..ca3ffb21 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -427,7 +427,7 @@ transformations are invertible. : 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 stored as a JSON list of numbers. The list MUST have length `N`. +: The scale parameters are stored as a JSON list of numbers. The list MUST have length `N`. ##### affine From d9aeeb2ab258ac088ea5c3aca8902e8c21c180d1 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:42:20 +0200 Subject: [PATCH 035/115] recommend informative warning for unparsed transformations --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index ca3ffb21..3b14ecde 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -903,8 +903,8 @@ non-linear transformations), and inform users what action will be taken. The det application dependent, but ignoring the unsupported transformation or falling back to a simpler transformation are likely to be common choices. -Implementations MAY choose to communicate if and when an image can be displayed in multiple coordinate systems. Users might -choose between different options, or software could choose a default (e.g. the first listed coordinate system). The +Implementations SHOULD communicate if and when an image can be displayed in multiple coordinate systems. Users might +choose between different options, or software could choose a default (e.g. the first or last listed coordinate system). The [`multiscales` in version 0.4](https://ngff.openmicroscopy.org/0.4/#multiscale-md) has a similar consideration. From 5c67d810a190bd6b204bef495472f85652a2c7f1 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:42:33 +0200 Subject: [PATCH 036/115] added link to examples --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 3b14ecde..551cf37c 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -894,7 +894,7 @@ Adds coordinate systems, these contain axes which are backward-compatible with t ## Testing -Public examples of transformations with expected input/output pairs will be provided. +Public examples of transformations with expected input/output pairs are provided [here](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/releases/tag/0.6-dev1-rev1) ## UI/UX From fe5221da935abe79bcdc4cd89b184f245fc4909c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:43:07 +0200 Subject: [PATCH 037/115] Link out to examples --- rfc/5/responses/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 9418ae03..98be35b8 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -6,10 +6,10 @@ The authors extend their most sincere thanks and appreciation to all the reviewe We have added many motivating examples for common use cases, but also for many edge-cases. These examples can be found on `s3://ngff-rfc5-coordinate-transformation-examples/`. -The metadata are also mirrored [on github](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples) +The metadata are also mirrored as versioned [github repository](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples) As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) -for viewing these examples with BigWarp, a reference implementation. +for viewing these examples with BigWarp, a reference implementation. These examples will not be made a part of the specification repository but can still be accessed later as part of the RFC-process. ## Review 1 From 409d7e5eed6eed81b6795dea2cbe02abd27e143d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:43:46 +0200 Subject: [PATCH 038/115] Removed abstract examples and update transform constraints Co-Authored-By: John Bogovic --- rfc/5/responses/1/index.md | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 98be35b8..de7c9c18 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -378,34 +378,16 @@ be an undue burden. We agreed with and shared this concern. As a result, a group of hackathon attendees agreed to a set of constraints that would decrease the burden on implementors, without reducing the -expressibility [3]. To summarize the constraints: +expressibility (see `"multiscales" metadata` section). -* The first coordinate system in the list is a *default* coordinate system - * ususally an image's "native" physical coordinate system -* There MUST be exactly one coordinate transformation per dataset in the multiscales whose output is the *default* coordinate system - * as a result, the field was renamed to the singular `coordinateTransformation` and changed from a json list to json object. - * this transformation SHOULD be simple (defined precisely in the spec). -* Any other transformations belong outside the datasets - * the `input`s of these transformations MUST be the *default* coordinate system - * the `output`s of these transformations are the other coordinate systems - -### Example 1 - -* There exist two coordinate systems "A", and "B" and two datasets "0", and "1". -* There exist two coordinate transforms in the "0" dataset, one from "0" to "A", and one from "0" to "B". -* There exist two coordinate transforms in the "1" dataset, one from "1" to "A", and one from "1" to "B". -* There exist no coordinate transformations outside the datasets. - -### Example 2 +A series of follow-up discussions further refined these constraints and metadata design layouts. -* There exist two coordinate systems "A", and "B" and two datasets "0", and "1". -* There exist one coordinate transforms in the "0" dataset, from "0" to "A". -* There exist one coordinate transforms in the "1" dataset, from "1" to "A". -* There exist one coordinate transforms outside the datasets, from "A" to "B". +To summarize the constraints *inside* multiscales: - -## References - -[1] [Motivation and clarification of array coordinate systems](https://github.com/bogovicj/ngff/commit/db1e7d1c16206125a83a9c7bd4ea2146f01143e7) -[2] [constraints on zarr `dimension_names`](https://github.com/bogovicj/ngff/commit/cd89608ea3baca8ea36447f88fbb4e3ea1909299) -[3] [constraints on ](https://github.com/bogovicj/ngff/commit/3822dd0d9d2e388a12b6b74d0bffc7a215298e42) \ No newline at end of file +* The last coordinate system in the list is a *default* coordinate system + * ususally an image's "native" physical coordinate system +* There MUST be exactly one coordinate transformation per dataset in the multiscales whose output is the *default* coordinate system. + This transformation SHOULD be simple (defined precisely in the spec). +* Any other transformations belong outside the datasets (i.e., under `multiscales > coordinateTransformations`) + * the `input`s of these transformations MUST be the *default* coordinate system + * the `output`s of these transformations are the other coordinate systems \ No newline at end of file From a7d26169ed33855784c98aba6eb1fd50e4589955 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:48:24 +0200 Subject: [PATCH 039/115] allow NULL values for optional `input`/`output` fields --- rfc/5/versions/1/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 551cf37c..bc07029f 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -329,6 +329,7 @@ If used in a parent-level zarr-group, the `input` field MUST be a path to the in The authoritative coordinate system for the input image is the first `coordinateSystem` defined therein. The `output` field can be a `path` to an output image or the name of a `coordinateSystem` defined in the parent-level zarr group. If the names of `input` or `output` can be both a `path` or the name of a `coordinateSystem`, `path` MUST take precedent. +If unused, the `input` and `output` fields MAY be null. For usage in multiscales, see [multiscales section](#multiscales-metadata) for details. From 5a9112b28b46d31c1edd274aff3a62f9eecc2f0c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:48:43 +0200 Subject: [PATCH 040/115] removed duplicate sentence --- rfc/5/versions/1/index.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index bc07029f..03eb86bc 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -380,11 +380,10 @@ stored as a 2D zarr array, the first dimension indexes rows and the second dimen #### Transformation types -Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value -of "input" is an array, it's length gives the input dimension, otherwise the length of "axes" for the coordinate -system with the name of the "input" value gives the input dimension. If the value of "input" is an array, it's -length 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 length gives the output dimension, +Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. +If the value of "input" is 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 From 45fa09c1de73b1897b4a7d04a7a2acb0ef3e1463 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:24:11 +0200 Subject: [PATCH 041/115] removed request on feedback from updated proposal --- rfc/5/versions/1/index.md | 56 --------------------------------------- 1 file changed, 56 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 03eb86bc..eb7c0e61 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -737,62 +737,6 @@ if not datasets: ``` -## Specific feedback requested - -We ask the reviewers for one specific piece of feedback. Specifically about whether parameters for transformations should -be written as they are currently in the draft pull request, with named parameters at the "top level" e.g.: - -``` -{ - "type": "affine", - "affine": [[1, 2, 3], [4, 5, 6]], - "input": "ji", - "output": "yx" -} -``` - -or alternatively in a `parameters` field: - -``` -{ - "type": "affine", - "parameters": { - "matrix": [[1, 2, 3], [4, 5, 6]] - }, - "input": "ji", - "output": "yx" -} -``` - -In discussions, some authors preferred the latter because it will make the "top-level" keys for transformation -objects all identical, which could make serialization / validation simpler. One downside is that this change -is breaking for the existing `scale` and `translation` transformations - -``` -{ - "type": "scale", - "scale": [2, 3], - "input": "ji", - "output": "yx" -} -``` - -would change to: - -``` -{ - "type": "scale", - "parameters": { - "scale": [2, 3], - }, - "input": "ji", - "output": "yx" -} -``` - -The authors would be interested to hear perspectives from the reviewers on this matter. - - ## Requirements The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", From 59c46a5984bf65ac0a1d3690b42c6fe0dfa1dad5 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:03:25 +0200 Subject: [PATCH 042/115] added link to Zenodo repository for examples --- rfc/5/responses/1/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index de7c9c18..7bb28915 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -5,8 +5,7 @@ The authors extend their most sincere thanks and appreciation to all the reviewe ## General comments We have added many motivating examples for common use cases, but also for many edge-cases. -These examples can be found on `s3://ngff-rfc5-coordinate-transformation-examples/`. -The metadata are also mirrored as versioned [github repository](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples) +The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17308336) As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) for viewing these examples with BigWarp, a reference implementation. These examples will not be made a part of the specification repository but can still be accessed later as part of the RFC-process. From 57184d112c309ec81fa6fe4f5a61ddff7f9a14b2 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:03:36 +0200 Subject: [PATCH 043/115] text style --- rfc/5/responses/1/index.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 7bb28915..a090a6ff 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -8,7 +8,8 @@ We have added many motivating examples for common use cases, but also for many e The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17308336) As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) -for viewing these examples with BigWarp, a reference implementation. These examples will not be made a part of the specification repository but can still be accessed later as part of the RFC-process. +for viewing these examples with BigWarp, a reference implementation. +These examples will not be made a part of the specification repository but can still be accessed later as part of the RFC-process. ## Review 1 @@ -17,7 +18,10 @@ Daniel Toloudis, David Feng, Forrest Collman, and Nathalie Gaudreault at the All ### Axes metadata -We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating examples would be helpful. However, until RFC-4 has officially been made a part of the spec we feel it would be out of scope to reference such examples in this RFC. We do think, though, that the suggested `coordinateSystems` group can provide the necessary structure to remove the ambiguity of orientation adressed in RFC4. An orientation field could easily be added there in the following manner: +We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating examples would be helpful. +However, until RFC-4 has officially been made a part of the spec we feel it would be out of scope to reference such examples in this RFC. +We do think, though, that the suggested `coordinateSystems` group can provide the necessary structure to remove the ambiguity of orientation adressed in RFC4. +An orientation field could easily be added there in the following manner: ```json { @@ -58,7 +62,8 @@ that we hope continues. ### “Array” coordinate system -Thank you for the feedback, we have edited this section with motivation and clarity. We hope that the edits along with motivating examples will help to make the applications for array coordinate systems clearer. +Thank you for the feedback, we have edited this section with motivation and clarity. +We hope that the edits along with motivating examples will help to make the applications for array coordinate systems clearer. ### Units @@ -348,7 +353,8 @@ Some clarifying text under the `Array coordinate systems` section was added. > arrayCoordinateSystem specifying dimension names is now redundant with zarr v3 dimension names Good point, and I agree; but the zarr spec is more permissive than the ngff spec, specifically because it [allows null or duplicate `dimension_names`](https://zarr-specs.readthedocs.io/en/latest/v3/core/v3.0.html#dimension-names). -As a result, we will need require adding additional constraints to the dimension names that they be unique and not null. This is currently a requirement for the [names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md) +As a result, we will need require adding additional constraints to the dimension names that they be unique and not null. +This is currently a requirement for the [names of axes](https://ngff.openmicroscopy.org/0.5/index.html#axes-md). > It is a bit unfortunate that coordinateTransforms now require that the input and output spaces be named for multiscale datasets, > where it was previously implicit, since (a) it is redundant and (b) it means existing multiscale metadata is no longer valid @@ -372,8 +378,9 @@ and those outside the datasets (that were previously said to apply to all scale levels). Implementers were concerned that if the transformation corresponding to a particular coordinate system could be found anywhere, that there would be a large number of valid ways to describe the same set of -coordinate systems and transformations (see the examples below). This would -be an undue burden. We agreed with and shared this concern. +coordinate systems and transformations. +This would be an undue burden. +We agreed with and shared this concern. As a result, a group of hackathon attendees agreed to a set of constraints that would decrease the burden on implementors, without reducing the @@ -387,6 +394,6 @@ To summarize the constraints *inside* multiscales: * ususally an image's "native" physical coordinate system * There MUST be exactly one coordinate transformation per dataset in the multiscales whose output is the *default* coordinate system. This transformation SHOULD be simple (defined precisely in the spec). -* Any other transformations belong outside the datasets (i.e., under `multiscales > coordinateTransformations`) +* Any other transformations belong outside the `datasets` (i.e., under `multiscales > coordinateTransformations`) * the `input`s of these transformations MUST be the *default* coordinate system * the `output`s of these transformations are the other coordinate systems \ No newline at end of file From a1bacf7edaf8e955f67384dc58abbdbedf409c42 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:04:48 +0200 Subject: [PATCH 044/115] text style --- rfc/5/versions/1/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index eb7c0e61..15c2df15 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -370,13 +370,13 @@ implementations MAY estimate an inverse, or MAY output a warning that the reques #### Matrix transformations -Two transformation types ([affine](#affine) and [rotation](#rotation)) are parametrized by matrices. Matrices are applied to -column vectors that represent points in the input coordinate system. The first (last) axis in a coordinate system is the top -(bottom) entry in the column vector. 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). - +Two transformation types ([affine](#affine) and [rotation](#rotation)) 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). #### Transformation types From cd32525339bb6cdf3f5e642c8ee8c441339fda26 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:08:28 +0200 Subject: [PATCH 045/115] removed duplicate sentence --- rfc/5/responses/1/index.md | 1 - 1 file changed, 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index a090a6ff..78c5db73 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -295,7 +295,6 @@ We therefore added that the `input` of a `coordinateTransformation` entry in the Yes, although they do so implictly. If a `coordinateTransformation` in the parent-level group refers to child images through its `input`/`output` fields, the authoritiative coordinate transformation of the linked (multiscales) image is the *first* `coordinateSystem` therein. -The first `coordinateSystem` therein is authoritiative for the image. This formalism also provides enough distinction from an image's "default" (aka "physical") coordinate system, which is the *last* `coordinateSystem` inside the image. If no additional `coordinateTransformations` are defined under `multiscales > coordinateTransformations`, only one `coordinateSystem` needs to be defined in the multiscales. From 39cce64359a970ecf5c112b52ba96319a970b586 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:28:44 +0200 Subject: [PATCH 046/115] text style and suggest selecting coordinate systems in viewers --- rfc/5/responses/1/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 78c5db73..0996073c 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -307,7 +307,11 @@ Yes. We have changed the spec, so that `"input": /path/to/volume/"` is the requi > What is the expectation for a conforming viewer when opening the top-level group? Should the viewer also open and display all the child images? This is an important and valid point to raise. The *in silico* behavior that comes to mind, is to open all present images along with their - correct transformations as separate layers, which can be toggled on or off. The spec currently requires conforming readers to read scale and translation transformations, which has remained unchanged. However, readers/viewers are now recommended to output an informative warning if a transformation is encountered that cannot be parsed. + correct transformations as separate layers, which can be toggled on or off. + For more complex transformations, viewer could let users decide the coordinate system in which the data should be displayed, + which may provide useful views on the data for bijection transforms in the registration research field. + The spec currently requires conforming readers to read scale and translation transformations, which has remained unchanged. + However, readers/viewers are now recommended to output an informative warning if a transformation is encountered that cannot be parsed. > It seems like the top-level zarr group with "coordinate transformations describing the relationship between two image coordinate systems" introduces a “Collection” of images. The discussion on adding support for Collection to the specification has been captured in Collections Specification but it has From e31574da6c8c1d508e980eebc981856a9f821d16 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:29:11 +0200 Subject: [PATCH 047/115] point out complementary nature of rfc5 and collections --- rfc/5/responses/1/index.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 0996073c..dc73cc62 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -319,10 +319,15 @@ Yes. We have changed the spec, so that `"input": /path/to/volume/"` is the requi > > Are you also proposing to introduce support for Collection as part of this RFC? In our opinion, this is probably out of scope at this stage, but an example might clarify the importance in the authors’ view. - It is true that storing several images under the same root-store as proposed here, resembles the proposed [Collections](https://github.com/ome/ngff/issues/31). However, the spatial relationship between images in a root zarr provides a distinctively meaningful kind of collections of images, namely their spatial relationship. Other solutions like storing images with spatial relationships into separate files along with references to each other come at the risk of putting the spatial reationship at the mercy of tidy file management. With this proposal, we seek a self-contained solution to spatial relationships, which require storage in a common location. - +It is true that storing several images under the same root-store as proposed here, resembles the proposed [Collections](https://github.com/ome/ngff/issues/31). +However, the spatial relationship between images in a root zarr provides a distinctively meaningful kind of collections of images, namely their spatial relationship. +Other solutions like storing images with spatial relationships into separate files along with references to each other come at the risk of putting the spatial reationship at the mercy of tidy file management. +With this proposal, we seek a self-contained solution to spatial relationships, which require storage in a common location. Moreover, while images with coordinate transforms in a root zarr provide a kind of collection, they don't do so more than a multiscale, a plate or a well object does. +In the future, we envision the transformations metadata to be moved under the `attributes` key of a `Collection` metadata field. +A collection of images bound to each other by spatial relationship would then become merely a particular type of a Collection. + ### Implementation section From d777903574e1bdc7b3e02da0c1fc890f0a17b068 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:29:56 +0200 Subject: [PATCH 048/115] remove generic requirement of `input` being a path --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 15c2df15..4ec7ca1f 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -169,7 +169,7 @@ They represent functions from *points* in the input space to *points* in the out - MUST contain the field "type" (string). - MUST contain any other fields required by the given "type" (see table below). - MUST contain the field "output" (string), unless part of a `sequence` or `inverseOf` (see details). -- MUST contain the field "input" (string), unless part of a `sequence` or `inverseOf` (see details). The value of "input", if used, MUST specify the path to the input image. +- MUST contain the field "input" (string), unless part of a `sequence` or `inverseOf` (see details). - MAY contain the field "name" (string). Its value MUST be unique across all "name" fields for coordinate transformations. - Parameter values MUST be compatible with input and output space dimensionality (see details). From 4ef485441abd7fbece45da1c52c6d717ce98c27e Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:30:23 +0200 Subject: [PATCH 049/115] text style (directives instead of "") --- rfc/5/versions/1/index.md | 50 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 4ec7ca1f..e86bbb0e 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -123,8 +123,8 @@ 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 "name"s. -The ith axis has `"name":"dim_i"` (these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). +Its name is the path to the array in the container, its axes have `"type":"array"`, are unitless, and have default `name`s. +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)). The `dimension_names` must be unique and non-null. ````{admonition} Example @@ -141,7 +141,7 @@ The `dimension_names` must be unique and non-null. ```` The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. -The axis with name `"dim_i"` is the ith element of the `"axes"` list. +The axis with name `"dim_i"` is the i-th element of the `"axes"` list. The axes and their order align with the `shape` attribute in the zarr array attributes (in `.zarray`), 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/v2.html#arrays), @@ -230,7 +230,7 @@ Conforming readers: Coordinate transformations can be stored in multiple places to reflect different usecases. -- Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata) +- Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata). - Additional transformations for single images MUST be stored in group-level attributes of the multiscales. - Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group `"coordinateTransformations"`. @@ -591,12 +591,12 @@ appropriate dimensionality. ### "multiscales" metadata -Metadata about an image can be found under the "multiscales" key in the group-level OME-Zarr Metadata. Here, "image" refers to 2 to 5 dimensional data representing image or volumetric data with optional time or channel axes. +Metadata about an image can be found under the `multiscales` key in the group-level OME-Zarr Metadata. Here, "image" refers to 2 to 5 dimensional data representing image or volumetric data with optional time or channel axes. It is stored in a multiple resolution representation. -"multiscales" contains a list of dictionaries where each entry describes a multiscale image. +`multiscales` contains a list of dictionaries where each entry describes a multiscale image. -Each "multiscales" dictionary MUST contain the field "coordinateSystems", whose value is an array containing valid coordinate +Each `multiscales` dictionary MUST contain the field "coordinateSystems", whose value is an array containing valid coordinate system metadata (see [coordinate systems](#coordinatesystems-metadata)). The last entry of this array is the "default" coordinate system and MUST contain transformations from array to physical coordinates. It should be used for viewing and processing unless a use case dictates otherwise. It will generally be a representation of the image in its native physical coordinate system. @@ -610,19 +610,21 @@ followed by the "channel" or custom axis (if present) and the axes of type "spa correspond to the image plane ("yx") and images are stacked along the other (anisotropic) axis ("z"), the spatial axes SHOULD be ordered as "zyx". -Each "multiscales" dictionary MUST contain the field "datasets", which is a list of dictionaries describing the arrays storing -the individual resolution levels. Each dictionary in "datasets" MUST contain the field "path", whose value is a string -containing the path to the Zarr array for this resolution relative to the current Zarr group. The "path"s MUST be ordered from -largest (i.e. highest resolution) to smallest. Every Zarr array referred to by a "path" MUST have the same number of dimensions -and MUST NOT have more than 5 dimensions. The number of dimensions and order MUST correspond to number and order of "axes". - -Each dictionary in "datasets" MUST contain the field "coordinateTransformations", whose value is a dictionary that defines a -transformation that maps Zarr array coordinates for this resolution level to the default coordinate system (the last entry of -the "coordinateSystems" array). The transformation is defined according to [transformations metadata](#transformation-types). -The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location "path". -The value of "input" SHOULD equal the value of "path", but implementations should always treat the value of "input" -as if it were equal to the value of "path". -The value of the transformation’s "output" MUST be the name of the default coordinate system. +Each `multiscales` dictionary MUST contain the field `dataset`, +which is a list of dictionaries describing the arrays storing the individual resolution levels. +Each dictionary in `dataset` MUST contain the field `path`, +whose value is a string containing the path to the Zarr array for this resolution relative to the current Zarr group. +The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. +Every Zarr array referred to by a `path` MUST have the same number of dimensions and MUST NOT have more than 5 dimensions. +The number of dimensions and order MUST correspond to number and order of `axes`. + +Each dictionary in `dataset` MUST contain the field `coordinateTransformations`, +whose value is a dictionary that defines a transformation that maps Zarr array coordinates for this resolution level to the "default" coordinate system +(the last entry of the `coordinateSystems` array). +The transformation is defined according to [transformations metadata](#transformation-types). +The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location `path`. +The value of "input" SHOULD equal the value of `path`, but implementations should always treat the value of `input` as if it were equal to the value of `path`. +The value of the transformation’s `output` MUST be the name of the default coordinate system. This transformation MUST be one of the following: @@ -634,13 +636,13 @@ not available or applicable for one of the axes, the value MUST express the scal the first resolution for the given axis, defaulting to 1.0 if there is no downsampling along the axis. This is strongly recommended so that the the "default" coordinate system of the image avoids more complex transformations. -If applications require additional transformations, each "multiscales" dictionary MAY contain the field "coordinateTransformations", +If applications require additional transformations, each `multiscales` dictionary MAY contain the field `coordinateTransformations`, describing transformations that are applied to all resolution levels in the same manner. -The value of "input" SHOULD equal the name of the "default" coordinate system. +The value of `input` SHOULD equal the name of the "default" coordinate system. -Each "multiscales" dictionary SHOULD contain the field "name". +Each `multiscales` dictionary SHOULD contain the field `name`. -Each "multiscales" dictionary SHOULD contain the field "type", which gives the type of downscaling method used to generate the multiscale image pyramid. +Each `multiscales` dictionary SHOULD contain the field `type`, which gives the type of downscaling method used to generate the multiscale image pyramid. It SHOULD contain the field "metadata", which contains a dictionary with additional information about the downscaling method. ````{admonition} Example From c0f64633ba56521066ef29ea2ebd704e9646eb39 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:09:10 +0200 Subject: [PATCH 050/115] amended replies to reviews to include links to other discussions. --- rfc/5/responses/1/index.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index dc73cc62..7cd668d2 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -375,6 +375,10 @@ we agree in principle. However, we also see no harm in additional explicitness. ## Other +This section contains other changes to the specification following the reviews as well as other discussions + +### Multiscales constraints + In conversations, at the OME community meeting and hackathon in April 2025, several attendees expressed confusion about how to specify situations with many coordinate systems, specifically when there exist more than one @@ -404,4 +408,13 @@ To summarize the constraints *inside* multiscales: This transformation SHOULD be simple (defined precisely in the spec). * Any other transformations belong outside the `datasets` (i.e., under `multiscales > coordinateTransformations`) * the `input`s of these transformations MUST be the *default* coordinate system - * the `output`s of these transformations are the other coordinate systems \ No newline at end of file + * the `output`s of these transformations are the other coordinate systems + +### Preferred less expressive transforms + +Transformations are powerful and as written before, there are often many different ways to specify the same transform. +For example, a sequence of a rotation and a translation can be combined into a single affine transform. +This was [discussed on github](https://github.com/ome/ngff/issues/331). +As a result of discussion, we recommend writers to use sequences of less expressive transforms +(i.e. `sequence[rotation, translation]` instead of a single affine containing these) to ensure a level of simplicity for image readers. + From 9eca479b96f12965f9e50c34aa96bbb74b03b838 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:09:49 +0200 Subject: [PATCH 051/115] specified requirements for `input`/`output` in additional transforms --- rfc/5/versions/1/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index e86bbb0e..591cdc73 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -638,7 +638,8 @@ recommended so that the the "default" coordinate system of the image avoids more If applications require additional transformations, each `multiscales` dictionary MAY contain the field `coordinateTransformations`, describing transformations that are applied to all resolution levels in the same manner. -The value of `input` SHOULD equal the name of the "default" coordinate system. +The value of `input` MUST equal the name of the "default" coordinate system. +The value of `output` MUST be the name of the output coordinate System which is different from the "default" coordinate system. Each `multiscales` dictionary SHOULD contain the field `name`. From 206575c6cbe49b6a3383396d09f137082d898997 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:10:01 +0200 Subject: [PATCH 052/115] correct field name --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 591cdc73..e0b9d9a2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -610,9 +610,9 @@ followed by the "channel" or custom axis (if present) and the axes of type "spa correspond to the image plane ("yx") and images are stacked along the other (anisotropic) axis ("z"), the spatial axes SHOULD be ordered as "zyx". -Each `multiscales` dictionary MUST contain the field `dataset`, +Each `multiscales` dictionary MUST contain the field `datasets`, which is a list of dictionaries describing the arrays storing the individual resolution levels. -Each dictionary in `dataset` MUST contain the field `path`, +Each dictionary in `datasets` MUST contain the field `path`, whose value is a string containing the path to the Zarr array for this resolution relative to the current Zarr group. The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. Every Zarr array referred to by a `path` MUST have the same number of dimensions and MUST NOT have more than 5 dimensions. From 2b8de8ec446a2235efec7ac36537049b640d9da2 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:10:09 +0200 Subject: [PATCH 053/115] added cross-reference --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index e0b9d9a2..fd6813da 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -624,7 +624,7 @@ whose value is a dictionary that defines a transformation that maps Zarr array c The transformation is defined according to [transformations metadata](#transformation-types). The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location `path`. The value of "input" SHOULD equal the value of `path`, but implementations should always treat the value of `input` as if it were equal to the value of `path`. -The value of the transformation’s `output` MUST be the name of the default coordinate system. +The value of the transformation’s `output` MUST be the name of the default [coordinate system](#coordinatesystems-metadata). This transformation MUST be one of the following: From c5ec41d389c15b0d608304a310aeecfa0d61d116 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:10:16 +0200 Subject: [PATCH 054/115] fixed name field --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index fd6813da..ba16c782 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -618,7 +618,7 @@ The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. Every Zarr array referred to by a `path` MUST have the same number of dimensions and MUST NOT have more than 5 dimensions. The number of dimensions and order MUST correspond to number and order of `axes`. -Each dictionary in `dataset` MUST contain the field `coordinateTransformations`, +Each dictionary in `datasets` MUST contain the field `coordinateTransformations`, whose value is a dictionary that defines a transformation that maps Zarr array coordinates for this resolution level to the "default" coordinate system (the last entry of the `coordinateSystems` array). The transformation is defined according to [transformations metadata](#transformation-types). From cb45aad1cec1520249236f7c10797656474e91ee Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:10:37 +0200 Subject: [PATCH 055/115] Text style and rewording Co-Authored-By: Will Moore <900055+will-moore@users.noreply.github.com> --- rfc/5/versions/1/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index ba16c782..99d1dc6b 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -321,8 +321,10 @@ The image under `root/sampleA_instrument2` would have this as the first listed c #### Additional details -Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value. -Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the `transformation` of an `inverseOf` transformation. +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 the coordinate transformation appears in the `transformations` list of a `sequence` +or is the `transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for details). If used in a parent-level zarr-group, the `input` field MUST be a path to the input image. From b0e48c4b30223e1d163625a91716f516c0d0d4d5 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:11:12 +0200 Subject: [PATCH 056/115] teyt style --- rfc/5/versions/1/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 99d1dc6b..056978d2 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -232,7 +232,9 @@ Coordinate transformations can be stored in multiple places to reflect different - Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata). - Additional transformations for single images MUST be stored in group-level attributes of the multiscales. -- Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group `"coordinateTransformations"`. +- Transformations between two or more images MUST be stored in the attributes of a parent zarr group. + For transformations that store data or parameters in a zarr array, + those zarr arrays SHOULD be stored in a zarr group `coordinateTransformations`. Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations (i.e., sequence[translation, rotation] instead of affine transformation with translation/rotation) component. From 3b9ee1e7708d457352c5b451b498dd381e3092d0 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:00:59 +0200 Subject: [PATCH 057/115] added reference to version title --- rfc/5/versions/1/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 056978d2..1dfa4d44 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -22,6 +22,8 @@ This RFC is currently in RFC state `R4` (authors prepare responses). This RFC provides first-class support for spatial and coordinate transformations in OME-Zarr. +Working version title: **0.6dev2** + ## Background Coordinate and spatial transformation are vitally important for neuro and bio-imaging and broader scientific imaging practices From 5740e3769698cf390166a0b9a96dcc062ddf9761 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:07:25 +0200 Subject: [PATCH 058/115] updated zenodo record --- rfc/5/responses/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 7cd668d2..a9277b43 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -5,7 +5,7 @@ The authors extend their most sincere thanks and appreciation to all the reviewe ## General comments We have added many motivating examples for common use cases, but also for many edge-cases. -The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17308336) +The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17313420) As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) for viewing these examples with BigWarp, a reference implementation. From d0a7248b84d9992eba4ae77b4ed0165c8918de16 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:10:30 +0200 Subject: [PATCH 059/115] added replies to david --- rfc/5/responses/1/index.md | 42 +++++++++++++++++++++++++++++++++ rfc/5/versions/1/index.md | 48 +++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index a9277b43..ea17648a 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -372,6 +372,48 @@ To our knowledge, every version in the past introduced breaking changes to the s of newer version could not be read anymore. As for the redundancy of specifiying input- and output-spaces in multiscales transformations, we agree in principle. However, we also see no harm in additional explicitness. +## Comment-3 + +Thank you for the additional comments in inquiries to this rfc. + +> In our opinion, it is clearer to interpret the transformation metadata when it refers explicitly to axes names instead of indices, so we recommend adapting “translation”, “scale”, “affine”, “rotation”, “coordinates”, and “displacements”. + +It is true that there is some inconsistency regarding how transformation parameters are specified with regard to the different axes of the coordinate systems. +The decision to express the mentioned transforms (`mapAxis` and `byDimension`) originated from discussions at the previous ngff Hackathon, +which we regret aren't reflect in this rfc process. + +Regarding expressing all transformations by named reference to the express, +we decided against this as it would require additinal sets of constraints for the axes ordering of the `input_axes` and `output_axes` fields for matrix transformations. +For example, the same rotation matrix could be expressed with different axis ordering, +which would correspond to a reordering of the column/row vectors of the corresponding transformation matrix. +Simply referring to the axes ordering specified in the `coordinateSystems` seemed like a simple solution for this. + +This is not to say that we do not see merrit in introducing the proposed axis ordering in 0.6dev3 if there is sufficient consensus in the community about it. + +> [...] We recommend that `byDimension` instead has a consistent treatment of the input/output fields to store the input and output coordinate system names, and new fields (input_axes, output_axes) are added to specify the input/output axes. + +TODO + +> It is not clear what inverseOf achieves, that can’t be achieved by defining the same transformation but simply swapping the values of the input and output coordinate system names. [...] + +We agree on the fact that this may seem confusing at first, and it reflects a common confusion regarding the directionality of transformations. +In this rfc5, we set the important constraint that transformations are to be written down in their *forward direction*. +However, many registration tools (noteably, [elastix](https://elastix.dev/index.php)), +specify derived transformations for a given moving and a fixed image in the *opposite* direction (from fixed to moving image) +to be able to restrict the sampling process to the relevant sample locations in the target image's domain (see section 2.6 "Transforms", [elastix manual](https://elastix.dev/doxygen/index.html)). +To alleviate this inconsistency, we introduced the `inverseOf` transformation. +This allows to store such inverse-under-the-hood transformations as forward transformations while informing implementations how to treat them. +Alternatively, users could be instructed to use registration tools like the aforementioned in a backward sense (see elastix manual, section 6.1.6, inverting transforms). +However, we feel like this would introduce requirements that lie out of scope for this rfc (and of the specification, respectively). + +> In the sequence section constraints on whether input/output must be specified are listed that apply to transformations other than “sequence”. +> For clarity we recommend these constraints are moved to the relevant transformations in the RFC, or to their own distinct section. + +Thank you for the suggestion, it was changed accordingly. We also realized that the `sequence` setion previously permitted nested sequences. +This possibility was removed to avoid complex, nested transformations. + + + ## Other diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 1dfa4d44..bd726179 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -386,11 +386,11 @@ When stored as a 2D json array, the inner array contains rows (e.g. `[[1,2,3], [ #### Transformation types -Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. -If the value of "input" is 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". +Input and output dimensionality may be determined by the value of the `input` and `output` fields, respectively. +If the value of `input` is 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 @@ -398,6 +398,8 @@ otherwise it is given by the length of "axes" for the coordinate system with the the ith 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 part of a [`sequence`](#sequence) transformation. + ##### mapAxis @@ -407,6 +409,8 @@ of the output coordinate for axis "x" to the value of the coordinate of input ax system, the `mapAxis` MUST have a corresponding field. For every value of the object there MUST be an axis of the input coordinate system with that name. Note that the order of the keys could be reversed. +The `input` and `output` fields MUST always be included for this transformations type. + ##### translation @@ -415,6 +419,8 @@ translation transformation should be preferred to its equivalent affine. Input a 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 part of a [`sequence`](#sequence) transformation. + 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`. @@ -429,6 +435,8 @@ SHOULD be preferred to its equivalent affine. Input and output dimensionality MU 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 part of a [`sequence`](#sequence) transformation. + 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`. @@ -443,13 +451,14 @@ represented as the upper `(M)x(N+1)` sub-matrix of a `(M+1)x(N+1)` matrix in [ho 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 part of a [`sequence`](#sequence) transformation. + 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 where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. - ##### rotation `rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation @@ -457,6 +466,8 @@ transformation SHOULD be preferred to its equivalent affine. Input and output di 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 part of a [`sequence`](#sequence) transformation. + 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`. @@ -471,6 +482,8 @@ transforming points from output to input coordinate systems is possible using th 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 @@ -488,20 +501,8 @@ to a point in the input coordinate system, apply the first transformation in the transformation to the result. Repeat until every transformation has been applied. The output of the last transformation is the result of the sequence. -The transformations included in the `transformations` array may omit their `input` and `output` fields under the conditions -outlined below: - -- The `input` and `output` fields MAY be omitted for the following transformation types: - - `identity`, `scale`, `translation`, `rotation`, `affine`, `displacements`, `coordinates` -- The `input` and `output` fields MAY be omitted for `inverseOf` transformations if those fields may be omitted for the - transformation it wraps -- The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for - both its `forward` and `inverse` transformations -- The `input` and `output` fields MAY be omitted for `sequence` transformations if the fields may be omitted for - all transformations in the sequence after flattening the nested sequence lists. -- The `input` and `output` fields MUST be included for transformations of type: `mapAxis`, and `byDimension` (see the note - below), and under all other conditions. - +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. @@ -519,6 +520,8 @@ These transformation types refer to an array at location specified by the `"path systems for these transformations ("input / output coordinate systems") constrain the array size and the coordinate system metadata for the array ("field coordinate system"). +The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. + * 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`. @@ -569,6 +572,7 @@ For `displacements`: `byDimension` transformations build a high dimensional transformation using lower dimensional transformations on subsets of dimensions. +The `input` and `output` fields MUST always be included for this transformations type.
transformations
@@ -591,6 +595,8 @@ and inverse transformations MUST match bijection's input and output space dimens the `forward` transformation's `input` and `output` are understood to match the bijection's, and the `inverse` transformation's `input` (`output`) matches the bijection's `output` (`input`), see the example below. +The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for both its `forward` and `inverse` transformations + Practically, non-invertible transformations have finite extents, so bijection transforms should only be expected to be correct / consistent for points that fall within those extents. It may not be correct for any point of appropriate dimensionality. @@ -729,8 +735,6 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l ``` ```` - - If only one multiscale is provided, use it. Otherwise, the user can choose by name, using the first multiscale as a fallback: From 1cb709cc15670512f0696becf2cdfa31f7aefe6d Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Tue, 14 Oct 2025 23:03:08 -0400 Subject: [PATCH 060/115] section on parameters stored in zarr arrays --- rfc/5/responses/1/index.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index ea17648a..5212c179 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -460,3 +460,31 @@ This was [discussed on github](https://github.com/ome/ngff/issues/331). As a result of discussion, we recommend writers to use sequences of less expressive transforms (i.e. `sequence[rotation, translation]` instead of a single affine containing these) to ensure a level of simplicity for image readers. + +### Parameters in zarr arrays + +This RFC allows for the parameters of most transformations to be stored either as zarr arrays or as a JSON array. There has +been [debate on github](https://github.com/ome/ngff/pull/138) as to whether the parameters of "simple" transformaions (scale, +translation, affines) should be restricted to _only_ be in the JSON metadata and not in zarr arrays. + +In summary, we feel that the benefits of the zarr array representation for those who choose to use it is worth the additional +costs at this time. + +We agree there are compelling reasons to prefer / require that the parameters are stored in JSON. Specifically that +implementations could be simpler because the parameters can be in exactly one place, and it would save IO. As well, there are +good reasons to allow storage of parameters in zarr arrays include, specifically that the decoding of floating point numbers +from arrays is more precise and robust that from JSON, and that the array ordering for multidimensional arrays is clearer, among +others. + +First, the issue of floating point precision is a critical one. In principle, it is possible to decode floating point numbers from +their JSON representation reliably, precisely, and consistently across programming languagues. We feel that the mechanism for +this should be specified by Zarr (not by OME-Zarr), and while +[a proposal exists at this time](https://github.com/zarr-developers/zarr-extensions/issues/22) for a relevant zarr extension, +it has not been adopted, nor tested across languages. We should revist this proposal in the future if and when it is adopted. + +Second, regarding additional code complexity: any complete implementation of this RFC requires that parameters be read from zarr +arrays for some transformations (coordinate and displacement fields). As a result, many implementations will necessarily accept +the implementation burden, while others are free not to. + +This is why we feel that, at this time, the implementation burden of storing the parameters in zarr arrays is small enough to justify +their benefits. From 151ff44651a6e0b58b62db4ff41135fc5b54c787 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:29:24 +0200 Subject: [PATCH 061/115] Explain metadata duplication --- rfc/5/responses/1/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index ea17648a..781edc66 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -132,9 +132,11 @@ Swedlow from the University of Dundee. >> > Why is this duplication necessary and what does the array zarr.json look like? -The multiscales metadata block was designed as a self-contained description on how to read the data therein. The addition of an additional `coordinateTransformations` key inside the multiscale's attributes was originally intended to provide a clear, authoritiative coordinate transformation, the configuration of which would apply to all multiscales and make it easier for a reader to identify the correct default coordinate system of a multiscale image. +The requirement for this duplication originates from the fact that some implementations do not provide a native way of opening and displaying multiscales. +Currently, such implementations need to chose a specific scale level to open and then "look up" in the parent level to discover the corresponding metadata. +The suggested duplication would allow easier metadata discovery for such implementation. However, we realized that this may be out of scope for this RFC and have removed the respective statement. -However, we have removed this requirement and furthermore tightened the requirements for `coordinateTransformations`. +We have refined the statements regarding where (and how) `coordinateTransformations` can be stored: - **Inside `multiscales > datasets`**: `coordinateTransformations` herein MUST be restricted to a single `scale`, `identity` or sequence of `translation` and `scale` transformations. The output of these `coordinateTransformations` MUST be the default coordinate system, which is the last entry in the list of coordinate systems. From 449f7820843e3fea3b6186571b94610f26ad2810 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:42:16 +0200 Subject: [PATCH 062/115] text style --- rfc/5/responses/1/index.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 781edc66..3fd3d16d 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -134,17 +134,22 @@ Swedlow from the University of Dundee. The requirement for this duplication originates from the fact that some implementations do not provide a native way of opening and displaying multiscales. Currently, such implementations need to chose a specific scale level to open and then "look up" in the parent level to discover the corresponding metadata. -The suggested duplication would allow easier metadata discovery for such implementation. However, we realized that this may be out of scope for this RFC and have removed the respective statement. +The suggested duplication would allow easier metadata discovery for such implementation. +However, we realized that this may be out of scope for this RFC and have removed the respective statement. We have refined the statements regarding where (and how) `coordinateTransformations` can be stored: - **Inside `multiscales > datasets`**: `coordinateTransformations` herein MUST be restricted to a single `scale`, `identity` or sequence of `translation` and `scale` transformations. - The output of these `coordinateTransformations` MUST be the default coordinate system, which is the last entry in the list of coordinate systems. + The output of these `coordinateTransformations` MUST be the default coordinate system, which is the last entry in the list of coordinate systems. - **Inside `multiscales > coordinateTransformations`**: One MAY store additional transformations here. - The `input` to these transformations MUST be the default coordinate system and the `output` can be another coordinate system defined under `multiscales > coordinateSystems`. -- **Parent-level `coordinateTransformations`**: Transformations between two or more images MUST be stored in the parent-level `coordinateTransformations` group. The `input` to these transformations MUST be paths to the respective images. The `output` can be a path to an image or the name of a coordinate system. If both path and name exist, the path takes precedence. The authoritiative coordinate system under `path` is the *first* coordinate system in the list. + The `input` to these transformations MUST be the default coordinate system and the `output` can be another coordinate system defined under `multiscales > coordinateSystems`. +- **Parent-level `coordinateTransformations`**: Transformations between two or more images MUST be stored in the parent-level `coordinateTransformations` group. + The `input` to these transformations MUST be paths to the respective images. The `output` can be a path to an image or the name of a coordinate system. + If both path and name exist, the path takes precedence. + The authoritiative coordinate system under `path` is the *first* coordinate system in the list. -This separation of transformations (inside `multiscales > datasets`, under `multiscales > coordinateTransformations` and under `coordinateTransformations` in the parent-level) provides flexbility for different usecases while still maintaining a level of rigidity for easier implementations. +This separation of transformations (inside `multiscales > datasets`, under `multiscales > coordinateTransformations` and under parent-level `coordinateTransformations`) +provides flexbility for different usecases while still maintaining a level of rigidity for implementations. ````{admonition} Example @@ -384,7 +389,6 @@ It is true that there is some inconsistency regarding how transformation paramet The decision to express the mentioned transforms (`mapAxis` and `byDimension`) originated from discussions at the previous ngff Hackathon, which we regret aren't reflect in this rfc process. -Regarding expressing all transformations by named reference to the express, we decided against this as it would require additinal sets of constraints for the axes ordering of the `input_axes` and `output_axes` fields for matrix transformations. For example, the same rotation matrix could be expressed with different axis ordering, which would correspond to a reordering of the column/row vectors of the corresponding transformation matrix. From 1559b7b4eabf940df41ae8f2c08b23dbca16d68d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:42:46 +0200 Subject: [PATCH 063/115] change `mapAxis` to integer list --- rfc/5/responses/1/index.md | 3 +- rfc/5/versions/1/index.md | 75 ++++++++++++-------------------------- 2 files changed, 25 insertions(+), 53 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 3fd3d16d..c63c0f5a 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -386,8 +386,9 @@ Thank you for the additional comments in inquiries to this rfc. > In our opinion, it is clearer to interpret the transformation metadata when it refers explicitly to axes names instead of indices, so we recommend adapting “translation”, “scale”, “affine”, “rotation”, “coordinates”, and “displacements”. It is true that there is some inconsistency regarding how transformation parameters are specified with regard to the different axes of the coordinate systems. -The decision to express the mentioned transforms (`mapAxis` and `byDimension`) originated from discussions at the previous ngff Hackathon, +The decision to express the mentioned transforms (`mapAxis` and `byDimension`) originated from discussions at previous hackathons, which we regret aren't reflect in this rfc process. +We have changed the `mapAxis` transformation towards being expressed as a transpose vector of integers that refer to the axis orderings of the inut coordinate system. we decided against this as it would require additinal sets of constraints for the axes ordering of the `input_axes` and `output_axes` fields for matrix transformations. For example, the same rotation matrix could be expressed with different axis ordering, diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index bd726179..5102ca38 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -176,52 +176,21 @@ They represent functions from *points* in the input space to *points* in the out - Parameter values MUST be compatible with input and output space dimensionality (see details). The following transformations are supported: - - -
identity - - The identity transformation is the default transformation and is typically not explicitly defined. -
mapAxis - "mapAxis":Dict[String:String] - A maxAxis transformation specifies an axis permutation as a map between axis names. -
translation - one of:
"translation":List[number],
"path":str -
translation vector, stored either as a list of numbers ("translation") or as binary data at a location - in this container (path). -
scale - one of:
"scale":List[number],
"path":str -
scale vector, stored either as a list of numbers (scale) or as binary data at a location in this - container (path). -
affine - one of:
"affine": List[List[number]],
"path":str -
affine transformation matrix stored as a flat array stored either with json uing the affine field - or as binary data at a location in this container (path). If both are present, the binary values at path should be used. -
rotation - one of:
"rotation":List[List[number]],
"path":str -
rotation transformation matrix stored as an array stored either - with json or as binary data at a location in this container (path). - If both are present, the binary parameters at path are used. -
sequence - "transformations":List[Transformation] - A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. -
displacements - "path":str
"interpolation":str -
Displacement field transformation located at (path). -
coordinates - "path":str
"interpolation":str -
Coordinate field transformation located at (path). -
inverseOf - "transform":Transform - The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. -
bijection - "forward":Transform
"inverse":Transform -
Explicitly define an invertible transformation by providing a forward transformation and its inverse. -
byDimension - "transformations":List[Transformation] - Define a high dimensional transformation using lower dimensional transformations on subsets of - dimensions. -
typefieldsdescription -
+ +| Type | Fields | Description | +|------|--------|-------------| +| `identity` | | The identity transformation is the default transformation and is typically not explicitly defined. | +| `mapAxis` | `"mapAxis":List[number]` | A `mapAxis` transformation specifies an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | +| `translation` | one of:
`"translation":List[number]`,
`"path":str` | translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | +| `scale` | one of:
`"scale":List[number]`,
`"path":str` | scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | +| `affine` | one of:
`"affine": List[List[number]]`,
`"path":str` | affine transformation matrix stored as a flat array stored either with json using the affine field or as binary data at a location in this container (path). If both are present, the binary values at path should be used. | +| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | rotation transformation matrix stored as an array stored either with json or as binary data at a location in this container (path). If both are present, the binary parameters at path are used. | +| `sequence` | `"transformations":List[Transformation]` | A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. | +| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at (path). | +| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at (path). | +| `inverseOf` | `"transform":Transform` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | +| `bijection` | `"forward":Transform`
`"inverse":Transform` | Explicitly define an invertible transformation by providing a forward transformation and its inverse. | +| `byDimension` | `"transformations":List[Transformation]` | Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; @@ -403,13 +372,15 @@ The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequen ##### mapAxis -`mapAxis` transformations describe axis permutations as a mapping of axis names. Transformations MUST include a `mapAxis` field -whose value is an object, all of whose values are strings. If the object contains `"x":"i"`, then the transform sets the value -of the output coordinate for axis "x" to the value of the coordinate of input axis "i" (think `x = i`). For every axis in its output coordinate -system, the `mapAxis` MUST have a corresponding field. For every value of the object there MUST be an axis of the input -coordinate system with that name. Note that the order of the keys could be reversed. +`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. -The `input` and `output` fields MUST always be included for this transformations type. +The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. ##### translation From 3bcc49e66b2a18afe48ce81dcfd10ba11726bce3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:07:26 +0200 Subject: [PATCH 064/115] reformat `byDimension` requirements --- rfc/5/responses/1/index.md | 32 +++++++++++++++++++++++++++++++- rfc/5/versions/1/index.md | 11 +++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index c63c0f5a..cebed0cf 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -399,7 +399,37 @@ This is not to say that we do not see merrit in introducing the proposed axis or > [...] We recommend that `byDimension` instead has a consistent treatment of the input/output fields to store the input and output coordinate system names, and new fields (input_axes, output_axes) are added to specify the input/output axes. -TODO +We agree with the raised recommendation +We have added the fields `input_axes` and `output_axes` to the `by_dimension` transformation. +This harmonizes the interface of the transformations across all transformations. + +However, we specify that the fields `input_axes` and `output_axes` need to be present for all *wrapped* transformations instead of the parent `byDimension` transformation. +Otherwise, allowing `byDimension` to wrap a list of transformations would require complicated mappings to subsets of coordinate systems. +The proposed format offers a clear interface, which allows transformations to be written as follows: + +```` {admonition} Example +```json +{ + "type": "byDimension", + "input": "high_dimensional_coordinatesystem_A", + "output": "high_dimensional_coordinatesystem_B", + "transformations": [ + { + "type": "scale", + "input_axes": ["x", "y"], + "output_axes": ["x", "y"], + "scale": [0.5, 0.5] + }, + { + "type": "translation", + "input_axes": ["z"], + "output_axes": ["z"], + "translation": [10.0] + } + ] +} +``` +```` > It is not clear what inverseOf achieves, that can’t be achieved by defining the same transformation but simply swapping the values of the input and output coordinate system names. [...] diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 5102ca38..c67290c6 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -190,7 +190,7 @@ The following transformations are supported: | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at (path). | | `inverseOf` | `"transform":Transform` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | | `bijection` | `"forward":Transform`
`"inverse":Transform` | Explicitly define an invertible transformation by providing a forward transformation and its inverse. | -| `byDimension` | `"transformations":List[Transformation]` | Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. | +| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; @@ -547,14 +547,13 @@ The `input` and `output` fields MUST always be included for this transformations
transformations
-
A list of transformations, each of which applies to a (non-strict) subset of input and output dimensions (axes). - The values of input and output fields MUST be an array of strings. - Every axis name in input 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 MUST appear in exactly one of its child transformations' output. +
Each child transformation MUST contain input_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.
- ##### bijection A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations From a0d91794d60d3a12d455e446cd93808691f6a19b Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:07:35 +0200 Subject: [PATCH 065/115] text style --- rfc/5/responses/1/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index cebed0cf..be117286 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -446,12 +446,11 @@ However, we feel like this would introduce requirements that lie out of scope fo > In the sequence section constraints on whether input/output must be specified are listed that apply to transformations other than “sequence”. > For clarity we recommend these constraints are moved to the relevant transformations in the RFC, or to their own distinct section. -Thank you for the suggestion, it was changed accordingly. We also realized that the `sequence` setion previously permitted nested sequences. +Thank you for the suggestion, it was changed accordingly. +We also realized that the `sequence` setion previously permitted nested sequences. This possibility was removed to avoid complex, nested transformations. - - ## Other This section contains other changes to the specification following the reviews as well as other discussions From 3f2e5215fbb7cbd5411f099cb639c87f8cc4a78c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:49:34 +0200 Subject: [PATCH 066/115] Added statement on zarr parameter storage discussion. --- rfc/5/responses/1/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index be117286..846d686b 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -496,3 +496,10 @@ This was [discussed on github](https://github.com/ome/ngff/issues/331). As a result of discussion, we recommend writers to use sequences of less expressive transforms (i.e. `sequence[rotation, translation]` instead of a single affine containing these) to ensure a level of simplicity for image readers. +### Parameter storage + +We are aware of and appreciate the in-depth discussion around the nature, fidelity and efficiency of using zarr-arrays for parameter storage. +Hence, we would like to express our gratitude to all participants for valuable insights and pointing out the different technical, legal and other aspects to this topic. +Future versions of this rfc may evolve towards a recommendation on parameter storage as the community discussion develops. +We are looking forward to further discussion about this topic. +We welcome and invite contributors to engage by writing a review or comment to this rfc. \ No newline at end of file From c76168e916106b096c1e387a71f8d4a94376cae6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:18:04 +0200 Subject: [PATCH 067/115] Updated zenodo link to latest --- rfc/5/responses/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 846d686b..8dd0df30 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -5,7 +5,7 @@ The authors extend their most sincere thanks and appreciation to all the reviewe ## General comments We have added many motivating examples for common use cases, but also for many edge-cases. -The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17313420) +The metadata are mirrored as a versioned [zenodo repository](https://zenodo.org/records/17313420/latest) As well, [we provide instructions](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/blob/main/bigwarp/README.md) for viewing these examples with BigWarp, a reference implementation. From cb58b64f7f96bc3ff888908df6348816d3a5079e Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:33:53 +0200 Subject: [PATCH 068/115] remove statement that DOI-ed examples will be added --- rfc/5/responses/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 8dd0df30..aca9a720 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -117,7 +117,7 @@ However, one could consider the json-schemas as a sort of implementation that al This is an important point. Exactly how and if linking to outside artifacts is allowed may belong in a broader community discussion. To keep resources relevant to, but "outside of" this RFC versioned and stable, they will be posted and archived -in a permanent repository, and assigned a DOI. The next revision of this RFC will refer to that specific version. +in a permanent repository, and assigned a DOI. Since the RFC and revision texts (such as this one), are ultimately historic artifacts and not authoritative, we think that example collections can be part of the RFC text, but should be kept outside the core specification document. From 8c8994cb70c47446125272c56489f60b940ed005 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:23:44 +0200 Subject: [PATCH 069/115] typos --- rfc/5/responses/1/index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index aca9a720..d311eb85 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -20,7 +20,7 @@ Daniel Toloudis, David Feng, Forrest Collman, and Nathalie Gaudreault at the All We agree that the issue raised here with respect to axis alignment and orientation is important and additional motivating examples would be helpful. However, until RFC-4 has officially been made a part of the spec we feel it would be out of scope to reference such examples in this RFC. -We do think, though, that the suggested `coordinateSystems` group can provide the necessary structure to remove the ambiguity of orientation adressed in RFC4. +We do think, though, that the suggested `coordinateSystems` group can provide the necessary structure to remove the ambiguity of orientation addressed in RFC4. An orientation field could easily be added there in the following manner: ```json @@ -108,8 +108,8 @@ is the consensus, so the parameters will remain as they are. We believe that sammple implementations are outside the scope of RFCs, even if the changes are substantial as in this case. Writing implementations would certainly fail to address the variety of programming languages and tools in the community -and thus inadvertantly prioritize some tools over others. -However, one could consider the json-schemas as a sort of implementation that allows implementors to test their written data against a common baseline to ensure the integrity of written data. +and thus inadvertently prioritize some tools over others. +However, one could consider the json-schemas as a sort of implementation that allows implementers to test their written data against a common baseline to ensure the integrity of written data. > Is it okay for an RFC to link out to other things, rather than being > completely self-contained? If it's not there is a danger of it @@ -149,7 +149,7 @@ We have refined the statements regarding where (and how) `coordinateTransformati The authoritiative coordinate system under `path` is the *first* coordinate system in the list. This separation of transformations (inside `multiscales > datasets`, under `multiscales > coordinateTransformations` and under parent-level `coordinateTransformations`) -provides flexbility for different usecases while still maintaining a level of rigidity for implementations. +provides flexibility for different usecases while still maintaining a level of rigidity for implementations. ````{admonition} Example @@ -300,7 +300,7 @@ We therefore added that the `input` of a `coordinateTransformation` entry in the > Do the top-level `coordinateTransformations` refer to coordinateSystems that are in child images? -Yes, although they do so implictly. +Yes, although they do so implicitly. If a `coordinateTransformation` in the parent-level group refers to child images through its `input`/`output` fields, the authoritiative coordinate transformation of the linked (multiscales) image is the *first* `coordinateSystem` therein. This formalism also provides enough distinction from an image's "default" (aka "physical") coordinate system, which is the *last* `coordinateSystem` inside the image. @@ -376,7 +376,7 @@ This is currently a requirement for the [names of axes](https://ngff.openmicrosc > under this new version. To our knowledge, every version in the past introduced breaking changes to the specification with the result that ome-zarr files -of newer version could not be read anymore. As for the redundancy of specifiying input- and output-spaces in multiscales transformations, +of newer version could not be read anymore. As for the redundancy of specifying input- and output-spaces in multiscales transformations, we agree in principle. However, we also see no harm in additional explicitness. ## Comment-3 @@ -388,9 +388,9 @@ Thank you for the additional comments in inquiries to this rfc. It is true that there is some inconsistency regarding how transformation parameters are specified with regard to the different axes of the coordinate systems. The decision to express the mentioned transforms (`mapAxis` and `byDimension`) originated from discussions at previous hackathons, which we regret aren't reflect in this rfc process. -We have changed the `mapAxis` transformation towards being expressed as a transpose vector of integers that refer to the axis orderings of the inut coordinate system. +We have changed the `mapAxis` transformation towards being expressed as a transpose vector of integers that refer to the axis orderings of the input coordinate system. -we decided against this as it would require additinal sets of constraints for the axes ordering of the `input_axes` and `output_axes` fields for matrix transformations. +we decided against this as it would require additional sets of constraints for the axes ordering of the `input_axes` and `output_axes` fields for matrix transformations. For example, the same rotation matrix could be expressed with different axis ordering, which would correspond to a reordering of the column/row vectors of the corresponding transformation matrix. Simply referring to the axes ordering specified in the `coordinateSystems` seemed like a simple solution for this. @@ -435,7 +435,7 @@ The proposed format offers a clear interface, which allows transformations to be We agree on the fact that this may seem confusing at first, and it reflects a common confusion regarding the directionality of transformations. In this rfc5, we set the important constraint that transformations are to be written down in their *forward direction*. -However, many registration tools (noteably, [elastix](https://elastix.dev/index.php)), +However, many registration tools (notably, [elastix](https://elastix.dev/index.php)), specify derived transformations for a given moving and a fixed image in the *opposite* direction (from fixed to moving image) to be able to restrict the sampling process to the relevant sample locations in the target image's domain (see section 2.6 "Transforms", [elastix manual](https://elastix.dev/doxygen/index.html)). To alleviate this inconsistency, we introduced the `inverseOf` transformation. @@ -447,7 +447,7 @@ However, we feel like this would introduce requirements that lie out of scope fo > For clarity we recommend these constraints are moved to the relevant transformations in the RFC, or to their own distinct section. Thank you for the suggestion, it was changed accordingly. -We also realized that the `sequence` setion previously permitted nested sequences. +We also realized that the `sequence` section previously permitted nested sequences. This possibility was removed to avoid complex, nested transformations. @@ -473,7 +473,7 @@ This would be an undue burden. We agreed with and shared this concern. As a result, a group of hackathon attendees agreed to a set of constraints -that would decrease the burden on implementors, without reducing the +that would decrease the burden on implementers, without reducing the expressibility (see `"multiscales" metadata` section). A series of follow-up discussions further refined these constraints and metadata design layouts. @@ -481,7 +481,7 @@ A series of follow-up discussions further refined these constraints and metadata To summarize the constraints *inside* multiscales: * The last coordinate system in the list is a *default* coordinate system - * ususally an image's "native" physical coordinate system + * usually an image's "native" physical coordinate system * There MUST be exactly one coordinate transformation per dataset in the multiscales whose output is the *default* coordinate system. This transformation SHOULD be simple (defined precisely in the spec). * Any other transformations belong outside the `datasets` (i.e., under `multiscales > coordinateTransformations`) From 0fdc5ddc9e8422a8d58d728e54a1485f88eaf072 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:24:35 +0200 Subject: [PATCH 070/115] fixed indendations and brackets in example --- rfc/5/responses/1/index.md | 147 +++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index d311eb85..61b37100 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -162,73 +162,74 @@ One may wish to attach the affine transformation to the multiscales itself, with The `multiscales` metadata contains this: ```json { - "multiscales": [{ - "version": "0.5-dev", - "name": "example", - "coordinateSystems" : [ - { - "name" : "deskewed", - "axes": [ - {"name": "z", "type": "space", "unit": "micrometer"}, - {"name": "y", "type": "space", "unit": "micrometer"}, - {"name": "x", "type": "space", "unit": "micrometer"} - ] - }, - { - "name" : "physical", - "axes": [ - {"name": "z", "type": "space", "unit": "micrometer"}, - {"name": "y", "type": "space", "unit": "micrometer"}, - {"name": "x", "type": "space", "unit": "micrometer"} - ] - } - ], - "datasets": [ - { - "path": "0", - // the transformation of other arrays are defined relative to this, the highest resolution, array - "coordinateTransformations": [{ - "type": "identity", - "input": "/0", - "output": "physical" - }] - }, - { - "path": "1", - "coordinateTransformations": [{ - // the second scale level (downscaled by a factor of 2 relative to "0" in zyx) - "type": "scale", - "scale": [2, 2, 2], - "input" : "/1", - "output" : "physical" - }] - }, - { - "path": "2", - "coordinateTransformations": [{ - // the third scale level (downscaled by a factor of 4 relative to "0" in zyx) - "type": "scale", - "scale": [4, 4, 4], - "input" : "/2", - "output" : "physical" - }] - } - ], - }, - "coordinateTransformations": [ - { - "type": "affine", - "name": "deskew-transformation", - "input": "physical", - "output": "deskewed", - "affine": [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0.785, 1, 0], - [0, 0, 0, 1] - ] - } - ] + "multiscales": [ + { + "version": "0.6dev2", + "name": "example", + "coordinateSystems": [ + { + "name": "deskewed", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + }, + { + "name": "physical", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + } + ], + "datasets": [ + { + "path": "0", + // the transformation of other arrays are defined relative to this, the highest resolution, array + "coordinateTransformations": [{ + "type": "identity", + "input": "0", + "output": "physical" + }] + }, + { + "path": "1", + "coordinateTransformations": [{ + // the second scale level (downscaled by a factor of 2 relative to "0" in zyx) + "type": "scale", + "scale": [2, 2, 2], + "input": "1", + "output": "physical" + }] + }, + { + "path": "2", + "coordinateTransformations": [{ + // the third scale level (downscaled by a factor of 4 relative to "0" in zyx) + "type": "scale", + "scale": [4, 4, 4], + "input": "2", + "output": "physical" + }] + } + ], + "coordinateTransformations": [ + { + "type": "affine", + "name": "deskew-transformation", + "input": "physical", + "output": "deskewed", + "affine": [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0.785, 1, 0], + [0, 0, 0, 1] + ] + } + ] + } ] } ``` @@ -238,13 +239,13 @@ The output is a defined coordinate system: ```json "ome": { - "coordinateSystems":[ + "coordinateSystems": [ { - "name" : "world", - "axes": [ - {"name": "z", "type": "space", "unit": "micrometer"}, - {"name": "y", "type": "space", "unit": "micrometer"}, - {"name": "x", "type": "space", "unit": "micrometer"} + "name": "world", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} ] } ], From 0144a684603152b4a22b7bd130af4f5115b85ae4 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:24:53 +0200 Subject: [PATCH 071/115] minor formatting fixes --- rfc/5/responses/1/index.md | 4 ++-- rfc/5/versions/1/index.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 61b37100..742a421e 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -255,7 +255,7 @@ The output is a defined coordinate system: "type": "translation", "translation": [0, 10234, 41232], "input": "path/to/stack", - "output" "world" + "output": "world" } ] } @@ -271,7 +271,7 @@ In a simplified way, the root level of the store should resemble this structure: ``` root ├─── imageA - ├─── imageA + ├─── imageB └─── zarr.json ``` diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index c67290c6..e6cc35a1 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -221,15 +221,15 @@ store.zarr # Root folder of the zarr store │ └── zarr.json │ ├── volume -│ ├── .zarr.json # group level attributes (multiscales) +│ ├── 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 +│ └── zarr.json # physical coordinate system and transformations here └── crop - ├── .zarr.json # group level attributes (multiscales) + ├── 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 + └── zarr.json # physical coordinate system and transformations here ````{admonition} Example From 04f463b96365974e726eb95de9f46abe2f4d9624 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 19 Oct 2025 21:25:16 -0400 Subject: [PATCH 072/115] expand on and edit reply re: inverseOf comment --- rfc/5/responses/1/index.md | 39 +++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 5212c179..54b0e2a7 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -394,17 +394,38 @@ This is not to say that we do not see merrit in introducing the proposed axis or TODO +## inverseOf + > It is not clear what inverseOf achieves, that can’t be achieved by defining the same transformation but simply swapping the values of the input and output coordinate system names. [...] -We agree on the fact that this may seem confusing at first, and it reflects a common confusion regarding the directionality of transformations. -In this rfc5, we set the important constraint that transformations are to be written down in their *forward direction*. -However, many registration tools (noteably, [elastix](https://elastix.dev/index.php)), -specify derived transformations for a given moving and a fixed image in the *opposite* direction (from fixed to moving image) -to be able to restrict the sampling process to the relevant sample locations in the target image's domain (see section 2.6 "Transforms", [elastix manual](https://elastix.dev/doxygen/index.html)). -To alleviate this inconsistency, we introduced the `inverseOf` transformation. -This allows to store such inverse-under-the-hood transformations as forward transformations while informing implementations how to treat them. -Alternatively, users could be instructed to use registration tools like the aforementioned in a backward sense (see elastix manual, section 6.1.6, inverting transforms). -However, we feel like this would introduce requirements that lie out of scope for this rfc (and of the specification, respectively). +This RFC motivates the `inverseOf` tranformation: + +> 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... + +though we appreciate that more clarity could be helpful. The storage of transformations for _images_ (not point coordinates), is +a main motivator of this transformation. + +There is not consensus among image registration algorithms on whether their output transformation takes points from the moving +to fixed image ("forward") or fixed to moving ("inverse"), when the transformation type is closed-form invertible. When +the transformation type is not closed-form invertible, the algorithms are obliged to output the inverse transformation. + +We would like to recommend that registration algorithms store the "forward" transformation (where the input is moving image's +coordinate system, and the output is the fixed image's coordinate system) because this matches the intuitions of users and +practitioners. Given an "inverse" transformation, that is not closed-form invertible, the `inverseOf` wrapper +enables their storage as if they were a "forward" transformations while informing implementations how to treat them. + +It is true that we could remove `inverseOf` and swap the input / output coordinate systems. In that case we do one of + +* not recommend which direction to store for the transformations + * one downside is that implementations will not know what to expect and could distinguish moving from fixed coordinate + system from the transformation +* recommend that the "inverse" transformation is stored + * one downside is that this does not align with intuition + +In our opinion, the cost of adding of this simple transformation type is worth avoiding one of these downsides. > In the sequence section constraints on whether input/output must be specified are listed that apply to transformations other than “sequence”. > For clarity we recommend these constraints are moved to the relevant transformations in the RFC, or to their own distinct section. From 8a52423ca80a45004539a6c82e119f33d337fac3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:53:04 +0200 Subject: [PATCH 073/115] typo --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index e6cc35a1..e3c781e9 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -296,7 +296,7 @@ The image under `root/sampleA_instrument2` would have this as the first listed c 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 the coordinate transformation appears in the `transformations` list of a `sequence` +Exceptions are if the coordinate transformation appears in the `transformations` list of a `sequence` or is the `transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for details). From 216b72750f6e497b79ea5bfd05fc25e9dc2f89da Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:19:38 +0200 Subject: [PATCH 074/115] Semantic line breaks throughout proposal text --- rfc/5/versions/1/index.md | 524 ++++++++++++++++++++++++-------------- 1 file changed, 330 insertions(+), 194 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index e3c781e9..130afba1 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -26,44 +26,56 @@ Working version title: **0.6dev2** ## Background -Coordinate and spatial transformation are vitally important for neuro and bio-imaging and broader scientific imaging practices -to enable: - -1. Reproducibility and Consistency: Supporting spatial transformations explicitly in a file format ensures that transformations - are applied consistently across different platforms and applications. This FAIR capability is a cornerstone of scientific - research, and having standardized formats and tools facilitates verification of results by independent - researchers. -2. Integration with Analysis Workflows: Having spatial transformations as a first-class citizen within file formats allows for - seamless integration with various image analysis workflows. Registration transformations can be used in subsequent image - analysis steps without requiring additional conversion. -3. Efficiency and Accuracy: Storing transformations within the file format avoids the need for re-sampling each time the data is - processed. This reduces sampling errors and preserves the accuracy of subsequent analyses. Standardization enables on-demand - transformation, critical for the massive volumes collected by modern microscopy techniques. -4. Flexibility in Analysis: A file format that natively supports spatial transformations allows researchers to apply, modify, or - reverse transformations as needed for different analysis purposes. This flexibility is critical for tasks such as - longitudinal studies, multi-modal imaging, and comparative analysis across different subjects or experimental conditions. - -Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec covering many of the use cases -requested in [this github issue](https://github.com/ome/ngff/issues/84). It also adds "coordinate systems" - named -sets of "axes." Related the relationship of discrete arrays to physical coordinates and the interpretation and motivation for -axis types. +Coordinate and spatial transformation are vitally important +for neuro and bio-imaging and broader scientific imaging practices to enable: + +1. Reproducibility and Consistency: + Supporting spatial transformations explicitly in a file format ensures that + transformations are applied consistently across different platforms and applications. + This FAIR capability is a cornerstone of scientific research, + and having standardized formats and tools facilitates verification of results by independent researchers. +2. Integration with Analysis Workflows: + Having spatial transformations as a first-class citizen within file formats + allows for seamless integration with various image analysis workflows. + Registration transformations can be used in subsequent image analysis steps + without requiring additional conversion. +3. Efficiency and Accuracy: + Storing transformations within the file format avoids + the need for re-sampling each time the data is processed. + This reduces sampling errors and preserves the accuracy of subsequent analyses. + Standardization enables on-demand transformation, + critical for the massive volumes collected by modern microscopy techniques. +4. Flexibility in Analysis: + A file format that natively supports spatial transformations allows researchers to apply, modify, + or reverse transformations as needed for different analysis purposes. + This flexibility is critical for tasks such as longitudinal studies, multi-modal imaging, + and comparative analysis across different subjects or experimental conditions. + +Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec +covering many of the use cases requested in [this github issue](https://github.com/ome/ngff/issues/84). +It also adds "coordinate systems" - named sets of "axes." +Related the relationship of discrete arrays to physical coordinates +and the interpretation and motivation for axis types. ## Proposal -Below is a complete copy of the proposed changes including suggestions from reviewers and contributors of the previously -associated [github pull request](https://github.com/ome/ngff/pull/138). +Below is a complete copy of the proposed changes including suggestions +from reviewers and contributors of the previously associated [github pull request](https://github.com/ome/ngff/pull/138). The changes, if approved, shall be translated into bikeshed syntax and added to the ngff repository in a separate PR. This PR will then comprise complete json schemas when the RFC enters the SPEC phase (see RFC1). ### "coordinateSystems" metadata -A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: -- MUST contain the field "name". The value MUST be a non-empty string that is unique among all entries under `coordinateSystems`. +A "coordinate system" is a collection of "axes" / dimensions with a name. +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 order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that coordinate system. +The order of the `"axes"` list matters +and defines the index of each array dimension and coordinates for points in that coordinate system. For the above 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 above is three dimensional (3D). @@ -84,30 +96,51 @@ Coordinate Systems metadata example ``` ```` -The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate -system's dimensions. Axis `name`s 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, with identical axes) or can be -transformed to the same coordinate system before doing analysis. See the example below. - +The axes of a coordinate system (see below) give information +about the types, units, and other properties of the coordinate system's dimensions. +Axis `name`s may contain semantically meaningful information, but can be arbitrary. +As a result, two coordinate systems that have identical axes in the same order +ay 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, with identical axes) +or can be transformed to the same coordinate system before doing analysis. +See the example below. #### "axes" metadata -"axes" describes the dimensions of a coordinate systems and adds an interpretation to the samples along that dimension. - -It is a list of dictionaries, where each dictionary 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. -- 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. -- 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. +"axes" describes the dimensions of a coordinate systems +and adds an interpretation to the samples along that dimension. + +It is a list of dictionaries, +where each dictionary 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. +- 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. +- 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. +- 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. If part of metadata, 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 of representing a `channel`, `coordinate`, or `displacement` are usually discrete. +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 of representing a `channel`, `coordinate`, or `displacement` are usually discrete. ```{note} The most common methods for interpolation are "nearest neighbor", "linear", "cubic", and "windowed sinc". @@ -117,7 +150,8 @@ As such, label images may be interpolated using "nearest neighbor" to obtain lab #### 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. +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. @@ -125,7 +159,8 @@ 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 `name`s. +Its name is the path to the array in the container, +its axes have `"type":"array"`, are unitless, and have default `name`s. 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)). The `dimension_names` must be unique and non-null. @@ -149,16 +184,24 @@ 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/v2.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"`. - +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. +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. ### "coordinateTransformations" metadata @@ -170,9 +213,12 @@ They represent functions from *points* in the input space to *points* in the out - MUST contain the field "type" (string). - MUST contain any other fields required by the given "type" (see table below). -- MUST contain the field "output" (string), unless part of a `sequence` or `inverseOf` (see details). -- MUST contain the field "input" (string), unless part of a `sequence` or `inverseOf` (see details). -- MAY contain the field "name" (string). Its value MUST be unique across all "name" fields for coordinate transformations. +- MUST contain the field "output" (string), + unless part of a `sequence` or `inverseOf` (see details). +- MUST contain the field "input" (string), + unless part of a `sequence` or `inverseOf` (see details). +- MAY contain the field "name" (string). + Its value MUST be unique across all "name" fields for coordinate transformations. - Parameter values MUST be compatible with input and output space dimensionality (see details). The following transformations are supported: @@ -201,13 +247,15 @@ Conforming readers: Coordinate transformations can be stored in multiple places to reflect different usecases. -- Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata). +- Multiscale transformations represent a special case of transformations + and are explained [below](#multiscales-metadata). - Additional transformations for single images MUST be stored in group-level attributes of the multiscales. - Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group `coordinateTransformations`. -Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations (i.e., sequence[translation, rotation] instead of affine transformation with translation/rotation) component. +Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations +(i.e., sequence[translation, rotation] instead of affine transformation with translation/rotation) component.
 store.zarr                      # Root folder of the zarr store
@@ -233,22 +281,28 @@ store.zarr                      # Root folder of the zarr store
 
````{admonition} Example -Two instruments simultaneously image the same sample from two different angles, and the 3D data from both instruments are calibrated to "micrometer" units. -Two samples are collected ("sampleA" and "sampleB"). An analysis of sample A requires measurements from both instruments' images 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. +Two instruments simultaneously image the same sample from two different angles, +and the 3D data from both instruments are calibrated to "micrometer" units. +Two samples are collected ("sampleA" and "sampleB"). +An analysis of sample A requires measurements from both instruments' images 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 coordinates (x,y,z) may not correspond to the measurement at the same point in instrument 2 +a measurement by instrument 1 at the point with coordinates (x,y,z) +may not correspond to the measurement at the same point in instrument 2 (i.e., it may not be the same physical location in the sample). To analyze both images together, they must be in the same coordinate system. The set of coordinate transformations encodes relationships between coordinate systems, specifically, how to convert points and images to different coordinate systems. -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, the ROI should be transformed to the "sampleA_image1" coordinate system, then used for quantification with the instrument 1 image. +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, the ROI should be transformed to the "sampleA_image1" 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. +The transformation parameters are stored in a separate zarr-group +under `coordinateTransformations/sampleA_instrument2-to-instrument1` as shown above. ```json "coordinateTransformations": [ @@ -295,7 +349,8 @@ The image under `root/sampleA_instrument2` would have this as the first listed c #### 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. +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 appears in the `transformations` list of a `sequence` or is the `transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for details). @@ -308,13 +363,17 @@ If unused, the `input` and `output` fields MAY be null. For usage in multiscales, see [multiscales section](#multiscales-metadata) for details. -Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output` -as arrays of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below for details). +Transformations in the `transformations` list of a `byDimensions` transformation +MUST provide `input` and `output` as arrays of strings +corresponding to axis names of the parent transformation's input and output coordinate systems +(see below 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. For example, the scale transformation above -defines the function: +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. +For example, the scale transformation above defines the function: ``` x = 0.5 * i @@ -323,10 +382,13 @@ y = 1.2 * j i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. -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: +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 { @@ -338,10 +400,13 @@ transformation type, for example: } ``` -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](#transformation-types) 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. +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](#transformation-types) 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 @@ -363,9 +428,10 @@ otherwise it is given by the length of `axes` for the coordinate system with the ##### identity -`identity` transformations map input coordinates to output coordinates without modification. The position of -the ith axis of the output coordinate system is set to the position of the ith axis of the input coordinate -system. `identity` transformations are invertible. +`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 part of a [`sequence`](#sequence) transformation. @@ -373,7 +439,8 @@ The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequen ##### mapAxis `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. +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). @@ -382,95 +449,118 @@ The value at position `i` in the array indicates which input axis becomes the `i The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. - ##### translation -`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. +`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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 list of numbers. The list MUST have length `N`. +: The translation parameters stored as a JSON list of numbers. +The list MUST have length `N`. ##### scale -`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. +`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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 list of numbers. The list MUST have length `N`. - +: The scale parameters are stored as a JSON list of numbers. +The list MUST have length `N`. ##### affine -`affine`s are [matrix transformations](#matrix-transformations) from N-dimensional inputs to M-dimensional outputs 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. +`affine`s are [matrix transformations](#matrix-transformations) 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 part of a [`sequence`](#sequence) transformation. 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)`. +: 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 where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. +: The affine parameters stored in JSON. +The matrix MUST be stored as 2D nested array +where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. ##### rotation -`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation -transformation SHOULD be preferred to its 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. +`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. +When possible, a rotation transformation SHOULD be preferred to its 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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 where the outer array MUST be length `N` and the inner arrays MUST be length `N`. +: The parameters stored in JSON. +The matrix MUST be stored as a 2D nested array where the outer array MUST be length `N` +and the inner arrays MUST be length `N`. ##### inverseOf -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). +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. +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. +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. ``` - ##### sequence -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 list 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 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 list 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. @@ -481,36 +571,45 @@ The `input` and `output` fields MUST be included for sequence transformations. ##### coordinates and displacements -`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`). +`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 coordinate system"). +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 coordinate system"). The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. -* 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`. +* 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`. +* 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: +`coordinates` and `displacements` transformations are not invertible in general, +but implementations MAY approximate their inverses. +Metadata for these coordinate transforms have the following fields:
path
The location of the coordinate array in this (or another) container.
interpolation
-
The interpolation attributes MAY be provided. It's value indicates - the interpolation to use if transforming points not on the array's discrete grid. +
The interpolation attributes MAY be provided. + It's value indicates the interpolation to use + if transforming points not on the array's discrete grid. Values could be:
  • linear (default)
  • @@ -520,11 +619,16 @@ inverses. Metadata for these coordinate transforms have the following fields:
-For both `coordinates` and `displacements`, the array data at referred to by `path` MUST define coordinate system and coordinate transform metadata: +For both `coordinates` and `displacements`, +the array data at referred to by `path` MUST define coordinate system +and coordinate transform metadata: -* Every axis name in the `coordinateTransform`'s `input` MUST appear in the coordinate system -* The array dimension corresponding to the `coordinate` or `displacement` axis MUST have length equal to the number of dimensions of the `coordinateTransform` `output` -* If the input coordinate system `N` axes, then the array data at `path` MUST have `(N + 1)` dimensions. +* Every axis name in the `coordinateTransform`'s `input` + MUST appear in the coordinate system. +* The array dimension corresponding to the `coordinate` or `displacement` axis + MUST have length equal to the number of dimensions of the `coordinateTransform` `output` +* If the input coordinate system `N` axes, + then the array data at `path` MUST have `(N + 1)` dimensions. * SHOULD have a `name` identical to the `name` of the corresponding `coordinateTransform`. For `coordinates`: @@ -541,71 +645,92 @@ For `displacements`: ##### byDimension -`byDimension` transformations build a high dimensional transformation using lower dimensional transformations -on subsets of dimensions. +`byDimension` transformations build a high dimensional transformation +using lower dimensional transformations on subsets of dimensions. The `input` and `output` fields MUST always be included for this transformations type.
transformations
-
Each child transformation MUST contain input_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. +
Each child transformation MUST contain input_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.
##### bijection -A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations -are explicitly defined. Each direction SHOULD be a transformation type that is not closed-form invertible. -Its' input and output spaces MUST have equal dimension. The input and output dimensions for the both the forward -and inverse transformations MUST match bijection's input and output space dimensions. +A bijection transformation is an invertible transformation in +which both the `forward` and `inverse` transformations are explicitly defined. +Each direction SHOULD be a transformation type that is not closed-form invertible. +Its input and output spaces MUST have equal dimension. +The input and output dimensions for the both the forward and inverse transformations +MUST match bijection's input and output space dimensions. -`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, in which case -the `forward` transformation's `input` and `output` are understood to match the bijection's, and the `inverse` -transformation's `input` (`output`) matches the bijection's `output` (`input`), see the example below. +`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, +in which case the `forward` transformation's `input` and `output` are understood to match the bijection's, +and the `inverse` transformation's `input` (`output`) matches the bijection's `output` (`input`), +see the example below. -The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for both its `forward` and `inverse` transformations +The `input` and `output` fields MAY be omitted for `bijection` transformations +if the fields may be omitted for both its `forward` and `inverse` transformations -Practically, non-invertible transformations have finite extents, so bijection transforms should only be expected -to be correct / consistent for points that fall within those extents. It may not be correct for any point of -appropriate dimensionality. +Practically, non-invertible transformations have finite extents, +so bijection transforms should only be expected to be correct / consistent for points that fall within those extents. +It may not be correct for any point of appropriate dimensionality. ### "multiscales" metadata -Metadata about an image can be found under the `multiscales` key in the group-level OME-Zarr Metadata. Here, "image" refers to 2 to 5 dimensional data representing image or volumetric data with optional time or channel axes. +Metadata about an image can be found under the `multiscales` key in the group-level OME-Zarr Metadata. +Here, "image" refers to 2 to 5 dimensional data representing image +or volumetric data with optional time or channel axes. It is stored in a multiple resolution representation. `multiscales` contains a list of dictionaries where each entry describes a multiscale image. -Each `multiscales` dictionary MUST contain the field "coordinateSystems", whose value is an array containing valid coordinate -system metadata (see [coordinate systems](#coordinatesystems-metadata)). The last entry of this array is the "default" coordinate system and MUST contain -transformations from array to physical coordinates. It should be used for viewing and processing unless a use case dictates -otherwise. It will generally be a representation of the image in its native physical coordinate system. +Each `multiscales` dictionary MUST contain the field "coordinateSystems", +whose value is an array containing valid coordinate system metadata +(see [coordinate systems](#coordinatesystems-metadata)). +The last entry of this array is the "default" coordinate system +and MUST contain transformations from array to physical coordinates. +It should be used for viewing and processing unless a use case dictates otherwise. +It will generally be a representation of the image in its native physical coordinate system. The following MUST hold for all coordinate systems. -The length of "axes" must be between 2 and 5 and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path"). -The "axes" MUST contain 2 or 3 entries of "type:space" and MAY contain one additional entry of "type:time" and MAY contain one -additional entry of "type:channel" or a null / custom type. The order of the entries MUST correspond to the order of dimensions -of the Zarr arrays. In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), -followed by the "channel" or custom axis (if present) and the axes of type "space". If there are three spatial axes where two -correspond to the image plane ("yx") and images are stacked along the other (anisotropic) axis ("z"), the spatial axes SHOULD be -ordered as "zyx". +The length of "axes" must be between 2 and 5 +and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path"). +The "axes" MUST contain 2 or 3 entries of "type:space" +and MAY contain one additional entry of "type:time" +and MAY contain one additional entry of "type:channel" or a null / custom type. +The order of the entries MUST correspond to the order of dimensions of the Zarr arrays. +In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), +followed by the "channel" or custom axis (if present) and the axes of type "space". +If there are three spatial axes where two correspond to the image plane ("yx") +and images are stacked along the other (anisotropic) axis ("z"), +the spatial axes SHOULD be ordered as "zyx". Each `multiscales` dictionary MUST contain the field `datasets`, which is a list of dictionaries describing the arrays storing the individual resolution levels. Each dictionary in `datasets` MUST contain the field `path`, whose value is a string containing the path to the Zarr array for this resolution relative to the current Zarr group. The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. -Every Zarr array referred to by a `path` MUST have the same number of dimensions and MUST NOT have more than 5 dimensions. +Every Zarr array referred to by a `path` MUST have the same number of dimensions +and MUST NOT have more than 5 dimensions. The number of dimensions and order MUST correspond to number and order of `axes`. Each dictionary in `datasets` MUST contain the field `coordinateTransformations`, -whose value is a dictionary that defines a transformation that maps Zarr array coordinates for this resolution level to the "default" coordinate system +whose value is a list of dictionaries that define a transformation +that maps Zarr array coordinates for this resolution level to the "default" coordinate system (the last entry of the `coordinateSystems` array). The transformation is defined according to [transformations metadata](#transformation-types). -The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location `path`. -The value of "input" SHOULD equal the value of `path`, but implementations should always treat the value of `input` as if it were equal to the value of `path`. +The transformation MUST take as input points in the array coordinate system +corresponding to the Zarr array at location `path`. +The value of "input" SHOULD equal the value of `path`, +but implementations should always treat the value of `input` as if it were equal to the value of `path`. The value of the transformation’s `output` MUST be the name of the default [coordinate system](#coordinatesystems-metadata). This transformation MUST be one of the following: @@ -613,20 +738,27 @@ This transformation MUST be one of the following: * A single scale or identity transformation * A sequence transformation containing one scale and one translation transformation. -In these cases, the scale transformation specifies the pixel size in physical units or time duration. If scaling information is -not available or applicable for one of the axes, the value MUST express the scaling factor between the current resolution and -the first resolution for the given axis, defaulting to 1.0 if there is no downsampling along the axis. This is strongly -recommended so that the the "default" coordinate system of the image avoids more complex transformations. +In these cases, the scale transformation specifies the pixel size in physical units or time duration. +If scaling information is not available or applicable for one of the axes, +the value MUST express the scaling factor between the current resolution +and the first resolution for the given axis, +defaulting to 1.0 if there is no downsampling along the axis. +This is strongly recommended +so that the the "default" coordinate system of the imageavoids more complex transformations. -If applications require additional transformations, each `multiscales` dictionary MAY contain the field `coordinateTransformations`, +If applications require additional transformations, +each `multiscales` dictionary MAY contain the field `coordinateTransformations`, describing transformations that are applied to all resolution levels in the same manner. The value of `input` MUST equal the name of the "default" coordinate system. -The value of `output` MUST be the name of the output coordinate System which is different from the "default" coordinate system. +The value of `output` MUST be the name of the output coordinate System +which is different from the "default" coordinate system. Each `multiscales` dictionary SHOULD contain the field `name`. -Each `multiscales` dictionary SHOULD contain the field `type`, which gives the type of downscaling method used to generate the multiscale image pyramid. -It SHOULD contain the field "metadata", which contains a dictionary with additional information about the downscaling method. +Each `multiscales` dictionary SHOULD contain the field `type`, +which gives the type of downscaling method used to generate the multiscale image pyramid. +It SHOULD contain the field "metadata", +which contains a dictionary with additional information about the downscaling method. ````{admonition} Example @@ -705,8 +837,9 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l ``` ```` -If only one multiscale is provided, use it. Otherwise, the user can choose by -name, using the first multiscale as a fallback: +If only one multiscale is provided, use it. +Otherwise, the user can choose by name, +using the first multiscale as a fallback: ```python datasets = [] @@ -756,16 +889,19 @@ issues or unknown unknowns prior to writing any real code. ## Drawbacks, risks, alternatives, and unknowns -Adopting this proposal will add an implementation burden because it adds more transformation types. Though this drawback is -softened by the fact that implementations will be able to choose which transformations to support (e.g., implementations may choose -not to support non-linear transformations). +Adopting this proposal will add an implementation burden because it adds more transformation types. +Though this drawback is softened by the fact that implementations +will be able to choose which transformations to support +(e.g., implementations may choose not to support non-linear transformations). -An alternative to this proposal would be not to add support transformations directly and instead recommend software use an -existing format (e.g., ITK's). The downside of that is that alternative formats will not integrate well with OME-NGFF as they do -not use JSON or Zarr. +An alternative to this proposal would be not to add support transformations directly +and instead recommend software use an existing format (e.g., ITK's). +The downside of that is that alternative formats will not integrate well with OME-NGFF +as they do not use JSON or Zarr. -In all, we believe the benefits of this proposal (outlined in the Background section) far outweigh these drawbacks, and will -better promote software interoperability than alternatives. +In all, we believe the benefits of this proposal (outlined in the Background section) +far outweigh these drawbacks, +and will better promote software interoperability than alternatives. ## Prior art and references From b891549fa92d7a48a362880d43ffe90ab4fc1479 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:53:44 +0200 Subject: [PATCH 075/115] fix `transformation` reference for `inverseOf` --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 130afba1..641f1b2e 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -234,8 +234,8 @@ The following transformations are supported: | `sequence` | `"transformations":List[Transformation]` | A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. | | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at (path). | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at (path). | -| `inverseOf` | `"transform":Transform` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | -| `bijection` | `"forward":Transform`
`"inverse":Transform` | Explicitly define an invertible transformation by providing a forward transformation and its inverse. | +| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | +| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | Explicitly define an invertible transformation by providing a forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Conforming readers: From 2e5e97a11800e2ad178d80e27bd66ef989f1837d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:56:00 +0200 Subject: [PATCH 076/115] correct inverseOf example --- rfc/5/versions/1/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 641f1b2e..17d144e4 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -396,7 +396,9 @@ the `inverseOf` transformation type, for example: "transformation" : { "type": "displacements", "path": "/path/to/displacements", - } + }, + "input": "input_image", + "output": "output_image", } ``` From b0db7478467ed5af04ec267525f82f600c680fe8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:56:07 +0200 Subject: [PATCH 077/115] text style --- rfc/5/versions/1/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 17d144e4..a6a97544 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -249,7 +249,9 @@ Coordinate transformations can be stored in multiple places to reflect different - Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata). -- Additional transformations for single images MUST be stored in group-level attributes of the multiscales. +- Additional transformations for single images MUST be stored under a field `coordinateTransformations` + in the multiscales dictionary. + This `coordinateTransformations` field MUST contain a list of valid [transformations](#transformation-types). - Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group `coordinateTransformations`. From 5d64472eab3d11dd8c2d537b93b5e1ea0b7bf837 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:58:37 +0200 Subject: [PATCH 078/115] correct plural for multiscales list --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index a6a97544..8be0688d 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -250,7 +250,7 @@ Coordinate transformations can be stored in multiple places to reflect different - Multiscale transformations represent a special case of transformations and are explained [below](#multiscales-metadata). - Additional transformations for single images MUST be stored under a field `coordinateTransformations` - in the multiscales dictionary. + in the multiscales dictionaries. This `coordinateTransformations` field MUST contain a list of valid [transformations](#transformation-types). - Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, From 595cbc8c8c2dcf565b3e920e0457cb7d84f98c09 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:13:50 +0200 Subject: [PATCH 079/115] Removed deprecated statement about transformations in `byDimension` transform --- rfc/5/versions/1/index.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 8be0688d..8f50a5f3 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -365,11 +365,6 @@ If unused, the `input` and `output` fields MAY be null. For usage in multiscales, see [multiscales section](#multiscales-metadata) for details. -Transformations in the `transformations` list of a `byDimensions` transformation -MUST provide `input` and `output` as arrays of strings -corresponding to axis names of the parent transformation's input and output coordinate systems -(see below 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, From 4a75e4b2039cf75d0bd2d97c4d9c6648104954b9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:37:08 +0200 Subject: [PATCH 080/115] Clarify precedence of coordinateSystem over path Updated documentation to specify that the name of a coordinateSystem takes precedence over path when both are present in coordinateTransformations. This can reduce the number of path checks, which can be important for remote storage --- rfc/5/responses/1/index.md | 4 +++- rfc/5/versions/1/index.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index 742a421e..4b471a68 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -2,6 +2,8 @@ The authors extend their most sincere thanks and appreciation to all the reviewers of this RFC. + + ## General comments We have added many motivating examples for common use cases, but also for many edge-cases. @@ -145,7 +147,7 @@ We have refined the statements regarding where (and how) `coordinateTransformati The `input` to these transformations MUST be the default coordinate system and the `output` can be another coordinate system defined under `multiscales > coordinateSystems`. - **Parent-level `coordinateTransformations`**: Transformations between two or more images MUST be stored in the parent-level `coordinateTransformations` group. The `input` to these transformations MUST be paths to the respective images. The `output` can be a path to an image or the name of a coordinate system. - If both path and name exist, the path takes precedence. + If both path and name exist, the name (i.e., the corresponding `coordinateSystem`) takes precedence. The authoritiative coordinate system under `path` is the *first* coordinate system in the list. This separation of transformations (inside `multiscales > datasets`, under `multiscales > coordinateTransformations` and under parent-level `coordinateTransformations`) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 8f50a5f3..1763541f 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -360,7 +360,7 @@ In these two cases input and output could, in some cases, be omitted (see below If used in a parent-level zarr-group, the `input` field MUST be a path to the input image. The authoritative coordinate system for the input image is the first `coordinateSystem` defined therein. The `output` field can be a `path` to an output image or the name of a `coordinateSystem` defined in the parent-level zarr group. -If the names of `input` or `output` can be both a `path` or the name of a `coordinateSystem`, `path` MUST take precedent. +If the names of `input` or `output` can be both a `path` or the name of a `coordinateSystem`, `coordinateSystem` MUST take precedent. If unused, the `input` and `output` fields MAY be null. For usage in multiscales, see [multiscales section](#multiscales-metadata) for details. From afeeb98f6346688181976bd53a21578243079e6d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:00:02 +0200 Subject: [PATCH 081/115] Apply suggestions from code review Co-Authored-By: David Stansby --- rfc/5/versions/1/index.md | 107 +++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 1763541f..24b167b7 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -76,9 +76,9 @@ Every coordinate system: The order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that coordinate system. -For the above example, the `"x"` dimension is the last dimension. +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 above is three dimensional (3D). +The "volume_micrometers" example coordinate system below is three dimensional (3D). ````{admonition} Example @@ -98,12 +98,12 @@ Coordinate Systems metadata example The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate system's dimensions. -Axis `name`s may contain semantically meaningful information, but can be arbitrary. +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 -ay not be "the same" in the sense that measurements at the same point +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, with identical axes) +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. @@ -115,7 +115,7 @@ and adds an interpretation to the samples along that dimension. It is a list of dictionaries, where each dictionary 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. + 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. @@ -140,7 +140,7 @@ If an axis is continuous (`"discrete" : false`), it indicates that interpolation 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 of representing a `channel`, `coordinate`, or `displacement` are usually discrete. +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". @@ -158,11 +158,11 @@ 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. +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 `name`s. -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)). -The `dimension_names` must be unique and non-null. +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. ````{admonition} Example ```json @@ -177,11 +177,9 @@ The `dimension_names` must be unique and non-null. ``` ```` -The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. -The axis with name `"dim_i"` is the i-th element of the `"axes"` list. The axes and their order align with the `shape` attribute in the zarr array attributes (in `.zarray`), 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/v2.html#arrays), +As described in the [Zarr array metadata](https://zarr.readthedocs.io/en/stable/spec/v2.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 @@ -206,10 +204,11 @@ See chapter 4 and figure 4.1 of the ITK Software Guide. ### "coordinateTransformations" metadata -"coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). +"coordinateTransformations" describe the mapping between two coordinate systems (defined by "coordinateSystems"). For example, to map an array's discrete coordinate system to its corresponding physical coordinates. Coordinate transforms are in the "forward" direction. -They represent functions from *points* in the input space to *points* in the output space. +They represent functions from *points* in the input space to *points* in the output space. +They: - MUST contain the field "type" (string). - MUST contain any other fields required by the given "type" (see table below). @@ -225,18 +224,18 @@ The following transformations are supported: | Type | Fields | Description | |------|--------|-------------| -| `identity` | | The identity transformation is the default transformation and is typically not explicitly defined. | -| `mapAxis` | `"mapAxis":List[number]` | A `mapAxis` transformation specifies an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | -| `translation` | one of:
`"translation":List[number]`,
`"path":str` | translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | -| `scale` | one of:
`"scale":List[number]`,
`"path":str` | scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | -| `affine` | one of:
`"affine": List[List[number]]`,
`"path":str` | affine transformation matrix stored as a flat array stored either with json using the affine field or as binary data at a location in this container (path). If both are present, the binary values at path should be used. | -| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | rotation transformation matrix stored as an array stored either with json or as binary data at a location in this container (path). If both are present, the binary parameters at path are used. | -| `sequence` | `"transformations":List[Transformation]` | A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. | -| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at (path). | -| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at (path). | +| `identity` | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | +| `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | +| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | +| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | +| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | +| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| +| `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | +| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | +| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | | `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | -| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | Explicitly define an invertible transformation by providing a forward transformation and its inverse. | -| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. | +| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing a forward transformation and its inverse. | +| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; @@ -247,59 +246,58 @@ Conforming readers: Coordinate transformations can be stored in multiple places to reflect different usecases. -- Multiscale transformations represent a special case of transformations +- Transformations in indvidual mutliscale datasets represent a special case of transformations and are explained [below](#multiscales-metadata). -- Additional transformations for single images MUST be stored under a field `coordinateTransformations` +- Additional transformations for single multiscale images MUST be stored under a field `coordinateTransformations` in the multiscales dictionaries. This `coordinateTransformations` field MUST contain a list of valid [transformations](#transformation-types). - Transformations between two or more images MUST be stored in the attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, - those zarr arrays SHOULD be stored in a zarr group `coordinateTransformations`. + those zarr arrays SHOULD be stored in a zarr group called "coordinateTransformations". -Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations -(i.e., sequence[translation, rotation] instead of affine transformation with translation/rotation) component. +Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible +(e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation).
 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 'volume' and 'crop' coordinate systems are stored here.
+│                               # transformations between coordinate systems in the 'volume' and 'crop' multiscale images are stored here.
 │
-├── coordinateTransformations   # transformations that use array storage go in a "coordinateTransformations" zarr group.
+├── 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
+│   └── 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
+    └── 0                      # a group containing the 0th scale
+        └── image              # a zarr array
             └── zarr.json      # physical coordinate system and transformations here
 
````{admonition} Example Two instruments simultaneously image the same sample from two different angles, and the 3D data from both instruments are calibrated to "micrometer" units. -Two samples are collected ("sampleA" and "sampleB"). -An analysis of sample A requires measurements from both instruments' images at certain points in space. +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 coordinates (x,y,z) may not correspond to the measurement at the same point in instrument 2 (i.e., it may not be the same physical location in the sample). -To analyze both images together, they must be in the same coordinate system. +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 and images to different 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, the ROI should be transformed to the "sampleA_image1" coordinate system, +In this case, image data within the ROI definned in image2 should be transformed to the "sampleA_image1" coordinate system, then used for quantification with the instrument 1 image. The `coordinateTransformations` in the parent-level metadata would contain the following data. @@ -317,7 +315,7 @@ under `coordinateTransformations/sampleA_instrument2-to-instrument1` as shown ab ] ``` -And the image under `root/sampleA_instrument1` would have the following as the first coordinate system: +And the image at the path `sampleA_instrument1` would have the following as the first coordinate system: ```json "coordinateSystems": [ @@ -332,7 +330,7 @@ And the image under `root/sampleA_instrument1` would have the following as the f ] ``` -The image under `root/sampleA_instrument2` would have this as the first listed coordinate system: +The image at path `sampleA_instrument2` would have this as the first listed coordinate system: ```json [ @@ -357,13 +355,13 @@ Exceptions are if the coordinate transformation appears in the `transformations` or is the `transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for details). -If used in a parent-level zarr-group, the `input` field MUST be a path to the input image. +If used in a parent-level zarr-group, the `input` field MUST be a path to the input multiscale image group. The authoritative coordinate system for the input image is the first `coordinateSystem` defined therein. -The `output` field can be a `path` to an output image or the name of a `coordinateSystem` defined in the parent-level zarr group. -If the names of `input` or `output` can be both a `path` or the name of a `coordinateSystem`, `coordinateSystem` MUST take precedent. +The `output` field can be a `path` to another output multiscale image group or the name of a `coordinateSystem` defined in the same parent-level zarr group. +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. If unused, the `input` and `output` fields MAY be null. -For usage in multiscales, see [multiscales section](#multiscales-metadata) for details. +For usage in multiscales, see [the multiscales section](#multiscales-metadata) for details. Coordinate transformations are functions of *points* in the input space to *points* in the output space. We call this the "forward" direction. @@ -419,8 +417,8 @@ When stored as a 2D json array, the inner array contains rows (e.g. `[[1,2,3], [ #### Transformation types -Input and output dimensionality may be determined by the value of the `input` and `output` fields, respectively. -If the value of `input` is an array, its shape gives the input dimension, +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`. @@ -445,6 +443,8 @@ Each integer in the array MUST be a valid zero-based index into the input coordi (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. +`mapAxis` transforms are invertible. The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. @@ -699,13 +699,12 @@ and MUST contain transformations from array to physical coordinates. It should be used for viewing and processing unless a use case dictates otherwise. It will generally be a representation of the image in its native physical coordinate system. -The following MUST hold for all coordinate systems. +The following MUST hold for all coordinate systems inside multiscales metadata. The length of "axes" must be between 2 and 5 and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path"). The "axes" MUST contain 2 or 3 entries of "type:space" and MAY contain one additional entry of "type:time" and MAY contain one additional entry of "type:channel" or a null / custom type. -The order of the entries MUST correspond to the order of dimensions of the Zarr arrays. In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), followed by the "channel" or custom axis (if present) and the axes of type "space". If there are three spatial axes where two correspond to the image plane ("yx") @@ -743,7 +742,7 @@ the value MUST express the scaling factor between the current resolution and the first resolution for the given axis, defaulting to 1.0 if there is no downsampling along the axis. This is strongly recommended -so that the the "default" coordinate system of the imageavoids more complex transformations. +so that the the "default" coordinate system of the image avoids more complex transformations. If applications require additional transformations, each `multiscales` dictionary MAY contain the field `coordinateTransformations`, From f121fd140174b1bd11411bd2b7123e81015d50a8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:20:28 +0200 Subject: [PATCH 082/115] Minor text style --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 24b167b7..5e68c565 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -131,7 +131,7 @@ where each dictionary describes a dimension (axis) and: The value MUST be a string, and can provide a longer name or description of an axis and its properties. -If part of metadata, the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. +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. From 24379e674d45e3c7daba72735b7f0d32272ed228 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:21:34 +0200 Subject: [PATCH 083/115] update link to zarr arrays --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 5e68c565..6d89c52d 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -179,7 +179,7 @@ As with all coordinate systems, the `dimension_names` must be unique and non-nul The axes and their order align with the `shape` attribute in the zarr array attributes (in `.zarray`), 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/v2.html#arrays), +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 From b01148e5915aaaa6adb15825b9273c464c04da20 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:36:23 +0200 Subject: [PATCH 084/115] transformation matrices must be 2D --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 6d89c52d..250da27e 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -228,8 +228,8 @@ The following transformations are supported: | `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | | `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | | `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | -| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | -| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| +| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | +| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| | `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | From 2fe869f4a10ff2174ec696133d948c25325e9ddb Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:38:04 +0200 Subject: [PATCH 085/115] `input` in multiscales trafo MUST equal path --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 250da27e..6205ea75 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -727,8 +727,8 @@ that maps Zarr array coordinates for this resolution level to the "default" coor The transformation is defined according to [transformations metadata](#transformation-types). The transformation MUST take as input points in the array coordinate system corresponding to the Zarr array at location `path`. -The value of "input" SHOULD equal the value of `path`, -but implementations should always treat the value of `input` as if it were equal to the value of `path`. +The value of "input" MUST equal the value of `path`, +implementations should always treat the value of `input` as if it were equal to the value of `path`. The value of the transformation’s `output` MUST be the name of the default [coordinate system](#coordinatesystems-metadata). This transformation MUST be one of the following: From 5b75f4526f2fe49e75b57781d93a059e1a17e545 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:39:17 +0200 Subject: [PATCH 086/115] text style --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 6205ea75..23c00b0d 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -207,7 +207,7 @@ See chapter 4 and figure 4.1 of the ITK Software Guide. "coordinateTransformations" describe the mapping between two coordinate systems (defined by "coordinateSystems"). For example, to map an array's discrete coordinate system to its corresponding physical coordinates. Coordinate transforms are in the "forward" direction. -They represent functions from *points* in the input space to *points* in the output space. +This means they represent functions from *points* in the input space to *points* in the output space. They: - MUST contain the field "type" (string). @@ -234,7 +234,7 @@ The following transformations are supported: | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | | `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | -| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing a forward transformation and its inverse. | +| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Conforming readers: From cab8ef811e3395a86f1d472ba2fb56c8d0810048 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:41:37 +0200 Subject: [PATCH 087/115] typos --- rfc/5/responses/1/index.md | 6 +++--- rfc/5/versions/1/index.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rfc/5/responses/1/index.md b/rfc/5/responses/1/index.md index aaa65095..bf966a7e 100644 --- a/rfc/5/responses/1/index.md +++ b/rfc/5/responses/1/index.md @@ -438,7 +438,7 @@ The proposed format offers a clear interface, which allows transformations to be > It is not clear what inverseOf achieves, that can’t be achieved by defining the same transformation but simply swapping the values of the input and output coordinate system names. [...] -The RFC motivates the `inverseOf` tranformation: +The RFC motivates the `inverseOf` transformation: > 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 @@ -540,8 +540,8 @@ from arrays is more precise and robust that from JSON, and that the array orderi others. First, the issue of floating point precision is a critical one. In principle, it is possible to decode floating point numbers from -their JSON representation reliably, precisely, and consistently across programming languagues. We feel that the mechanism for -this should be specified by Zarr (not by OME-Zarr), and while +their JSON representation reliably, precisely, and consistently across programming languages. We feel that the mechanism for +this should be specified by Zarr (not by OME-Zarr), and while [a proposal exists at this time](https://github.com/zarr-developers/zarr-extensions/issues/22) for a relevant zarr extension, it has not been adopted, nor tested across languages. We should revisit this proposal in the future if and when it is adopted. diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 23c00b0d..0a0dba93 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -246,7 +246,7 @@ Conforming readers: Coordinate transformations can be stored in multiple places to reflect different usecases. -- Transformations in indvidual mutliscale datasets represent a special case of transformations +- Transformations in individual mutliscale datasets represent a special case of transformations and are explained [below](#multiscales-metadata). - Additional transformations for single multiscale images MUST be stored under a field `coordinateTransformations` in the multiscales dictionaries. @@ -297,7 +297,7 @@ The set of coordinate transformations encodes relationships between coordinate s 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 definned in image2 should be transformed to the "sampleA_image1" coordinate system, +In this case, image data within the ROI defined in image2 should be transformed to the "sampleA_image1" coordinate system, then used for quantification with the instrument 1 image. The `coordinateTransformations` in the parent-level metadata would contain the following data. From a8f87138d8d2de5f5d982768f8cb2342c73f2cff Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:44:58 +0200 Subject: [PATCH 088/115] add link to bijection forward/inverse to `inverseOf` description --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 0a0dba93..80dfce81 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -233,7 +233,7 @@ The following transformations are supported: | `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | -| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. | +| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijections) for details and examples. | | `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | From eab1de24b1fac7cdb5fc9d9bf9ebd3a663c8a745 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:51:50 +0200 Subject: [PATCH 089/115] fix example json --- rfc/5/versions/1/index.md | 54 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 80dfce81..90d20b7a 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -787,36 +787,42 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l "datasets": [ { "path": "0", - "coordinateTransformations": { - // the voxel size for the first scale level (0.5 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 0.5, 0.5, 0.5], - "input": "0", - "output": "example" - } + "coordinateTransformations": [ + { + // the voxel size for the first scale level (0.5 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 0.5, 0.5, 0.5], + "input": "0", + "output": "example" + } + ] }, { "path": "1", - "coordinateTransformations": { - // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 1.0, 1.0, 1.0], - "input": "1", - "output": "example" - } + "coordinateTransformations": [ + { + // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 1.0, 1.0, 1.0], + "input": "1", + "output": "example" + } + ] }, { "path": "2", - "coordinateTransformations": { - // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 2.0, 2.0, 2.0], - "input": "2", - "output": "example" - } + "coordinateTransformations": [ + { + // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 2.0, 2.0, 2.0], + "input": "2", + "output": "example" + } + ] } ], "type": "gaussian", From 2d00d958a71f31a74e93ea18b775ed62bc4dd95f Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:05:33 +0200 Subject: [PATCH 090/115] fix and add links --- rfc/5/versions/1/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 90d20b7a..490f75fc 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -105,7 +105,7 @@ refer to different physical entities and therefore should not be analyzed jointl 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. +See the [example below](example:coordinate_transformation). #### "axes" metadata @@ -233,7 +233,7 @@ The following transformations are supported: | `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | -| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijections) for details and examples. | +| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijection) for details and examples. | | `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | @@ -282,6 +282,7 @@ store.zarr # Root folder of the zarr store ````{admonition} Example +(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. From 9fab599acb42d6dd76a423dcfb1bf95f5b3991a5 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:05:44 +0200 Subject: [PATCH 091/115] text style --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 490f75fc..a5679309 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -696,7 +696,7 @@ Each `multiscales` dictionary MUST contain the field "coordinateSystems", whose value is an array containing valid coordinate system metadata (see [coordinate systems](#coordinatesystems-metadata)). The last entry of this array is the "default" coordinate system -and MUST contain transformations from array to physical coordinates. +and MUST contain axis information pertaining to physical coordinates. It should be used for viewing and processing unless a use case dictates otherwise. It will generally be a representation of the image in its native physical coordinate system. From 40982f745f06e198bab96ef8bbddb704e931aba5 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:09:00 +0200 Subject: [PATCH 092/115] Update rfc/5/versions/1/index.md Co-authored-by: David Stansby --- rfc/5/versions/1/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index a5679309..e0b33328 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -289,8 +289,8 @@ An analysis of sample A requires measurements from images taken from both instru 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 coordinates (x,y,z) -may not correspond to the measurement at the same point in instrument 2 +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 coordiantes 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. From 866ade399220a383dd89eabe63d935daaae71e8b Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:29:45 +0200 Subject: [PATCH 093/115] Expanded example, moved down and added link --- rfc/5/versions/1/index.md | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index a5679309..2871634e 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -207,7 +207,9 @@ See chapter 4 and figure 4.1 of the ITK Software Guide. "coordinateTransformations" describe the mapping between two coordinate systems (defined by "coordinateSystems"). For example, to map an array's discrete coordinate system to its corresponding physical coordinates. Coordinate transforms are in the "forward" direction. -This means they represent functions from *points* in the input space to *points* in the output space. +This means they represent functions from *points* in the input space to *points* in the output space +(see [example below](example:coordinate_transformation_scale)). + They: - MUST contain the field "type" (string). @@ -237,6 +239,37 @@ The following transformations are supported: | `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | +````{admonition} Example +(example:coordinate_transformation_scale)= + +```json +{ + "coordinateSystems": [ + { "name": "in", "axes": [{"name": "j"}, {"name": "i"}] }, + { "name": "out", "axes": [{"name": "y"}, {"name": "x"}] } + ], + "coordinateTransformations": [ + { + "type": "scale", + "scale": [2, 3.12], + "input": "in", + "output": "out" + } + ] +} + +``` + +For example, the scale transformation above 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. +```` + Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; - SHOULD parse `mapAxis`, `affine`, `rotation` transformations; @@ -369,14 +402,6 @@ 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. -For example, the scale transformation above defines the function: - -``` -x = 0.5 * i -y = 1.2 * j -``` - -i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. When rendering transformed images and interpolating, implementations may need the "inverse" transformation - From 64e7f578954a5d07f02faf82fed95de3fb75234a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:31:06 +0200 Subject: [PATCH 094/115] typo --- rfc/5/versions/1/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 1bedc626..db524bb8 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -323,7 +323,7 @@ Suppose a region of interest (ROI) is determined from the image obtained from in 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 coordiantes in instrument 2 +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. From a67e25d540dac1c6441864a63d9fdcffbe21203c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:10:54 +0200 Subject: [PATCH 095/115] Moved statement up --- rfc/5/versions/1/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index db524bb8..e7069455 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -239,6 +239,9 @@ The following transformations are supported: | `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | | `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | +Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible +(e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation). + ````{admonition} Example (example:coordinate_transformation_scale)= @@ -288,9 +291,6 @@ Coordinate transformations can be stored in multiple places to reflect different For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD be stored in a zarr group called "coordinateTransformations". -Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible -(e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation). -
 store.zarr                      # Root folder of the zarr store
 │

From 5d724137c6623e0aad6422b3672b67781e8caa8b Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Thu, 23 Oct 2025 14:16:08 +0200
Subject: [PATCH 096/115] renamed "default" to "intrinsic"

---
 rfc/5/versions/1/index.md | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index e7069455..a934bf99 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -720,10 +720,10 @@ It is stored in a multiple resolution representation.
 Each `multiscales` dictionary MUST contain the field "coordinateSystems",
 whose value is an array containing valid coordinate system metadata
 (see [coordinate systems](#coordinatesystems-metadata)).
-The last entry of this array is the "default" coordinate system
+The last entry of this array is the "intrinsic" coordinate system
 and MUST contain axis information pertaining to physical coordinates.
 It should be used for viewing and processing unless a use case dictates otherwise.
-It will generally be a representation of the image in its native physical coordinate system. 
+It will generally be a representation of the image in its native physical coordinate system.
 
 The following MUST hold for all coordinate systems inside multiscales metadata.
 The length of "axes" must be between 2 and 5
@@ -748,14 +748,14 @@ The number of dimensions and order MUST correspond to number and order of `axes`
 
 Each dictionary in `datasets` MUST contain the field `coordinateTransformations`,
 whose value is a list of dictionaries that define a transformation
-that maps Zarr array coordinates for this resolution level to the "default" coordinate system
+that maps Zarr array coordinates for this resolution level to the "intrinsic" coordinate system
 (the last entry of the `coordinateSystems` array).
 The transformation is defined according to [transformations metadata](#transformation-types).
 The transformation MUST take as input points in the array coordinate system
 corresponding to the Zarr array at location `path`.
 The value of "input" MUST equal the value of `path`, 
 implementations should always treat the value of `input` as if it were equal to the value of `path`.
-The value of the transformation’s `output` MUST be the name of the default [coordinate system](#coordinatesystems-metadata).
+The value of the transformation’s `output` MUST be the name of the "intrinsic" [coordinate system](#coordinatesystems-metadata).
 
 This transformation MUST be one of the following:
 
@@ -768,14 +768,14 @@ the value MUST express the scaling factor between the current resolution
 and the first resolution for the given axis,
 defaulting to 1.0 if there is no downsampling along the axis.
 This is strongly recommended
-so that the the "default" coordinate system of the image avoids more complex transformations.
+so that the the "intrinsic" coordinate system of the image avoids more complex transformations.
 
 If applications require additional transformations,
 each `multiscales` dictionary MAY contain the field `coordinateTransformations`,
 describing transformations that are applied to all resolution levels in the same manner.
-The value of `input` MUST equal the name of the "default" coordinate system.
+The value of `input` MUST equal the name of the "intrinsic" coordinate system.
 The value of `output` MUST be the name of the output coordinate System
-which is different from the "default" coordinate system.
+which is different from the "intrinsic" coordinate system.
 
 Each `multiscales` dictionary SHOULD contain the field `name`.
 
@@ -800,7 +800,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l
           "name": "example",
           "coordinateSystems": [
               {
-                "name": "example",
+                "name": "intrinsic",
                 "axes": [
                   { "name": "t", "type": "time", "unit": "millisecond" },
                   { "name": "c", "type": "channel" },
@@ -820,7 +820,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l
                   "type": "scale",
                   "scale": [1.0, 1.0, 0.5, 0.5, 0.5],
                   "input": "0",
-                  "output": "example"
+                  "output": "intrinsic"
                 }
               ]
             },
@@ -833,7 +833,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l
                   "type": "scale",
                   "scale": [1.0, 1.0, 1.0, 1.0, 1.0],
                   "input": "1",
-                  "output": "example"
+                  "output": "intrinsic"
                 }
               ]
             },
@@ -846,7 +846,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l
                   "type": "scale",
                   "scale": [1.0, 1.0, 2.0, 2.0, 2.0],
                   "input": "2",
-                  "output": "example"
+                  "output": "intrinsic"
                 }
               ]
             }

From f1b651c075adc79b6e0fff2b1fbc233baa9199ed Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Thu, 23 Oct 2025 15:21:43 +0200
Subject: [PATCH 097/115] Improved wording on path/coordSys precedence in
 parent-level transforms

---
 rfc/5/versions/1/index.md | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index a934bf99..4dfde6cf 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -388,13 +388,16 @@ that MUST correspond to the name of a coordinate system or the path to a multisc
 Exceptions are if the coordinate transformation appears in the `transformations` list of a `sequence`
 or is the `transformation` of an `inverseOf` transformation.
 In these two cases input and output could, in some cases, be omitted (see below for details).
-
-If used in a parent-level zarr-group, the `input` field MUST be a path to the input multiscale image group. 
-The authoritative coordinate system for the input image is the first `coordinateSystem` defined therein.
-The `output` field can be a `path` to another output multiscale image group or the name of a `coordinateSystem` defined in the same parent-level zarr group.
-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.
 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-metadata) for details.
 
 Coordinate transformations are functions of *points* in the input space to *points* in the output space.

From 958c566aff6e47915fba67c3a342db25b14c9c20 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Thu, 23 Oct 2025 15:22:39 +0200
Subject: [PATCH 098/115] add WIll moore as endorser

see https://github.com/ome/ngff/pull/350#issuecomment-3436688451
---
 rfc/5/versions/1/index.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index 4dfde6cf..8c0ad2a5 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -14,6 +14,7 @@ This RFC is currently in RFC state `R4` (authors prepare responses).
 | **Author** | Matt McCormick | @thewtex | ITK | 2024-07-30 | Implemented |
 | **Author** | Stephan Saalfeld | @axtimwalde | HHMI Janelia | 2024-07-30 | Implemented (with JB) |
 | **Author** | Johannes Soltwedel | @jo-mueller | German Bioimaging e.V. | 2025-09-19 | Implemented |
+| **Endorser** | Will Moore | @will-moore | University of Dundee | 2025-10-23 | Implemented |
 | **Endorser** | Norman Rzepka | @normanrz | Scalable Minds | 2024-08-22 | |
 | **Reviewer** | Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster | toloudis, dyf, fcollman | Allen Institutes | 2024-11-28 | [Review](rfcs:rfc5:review1) |
 | **Reviewer** | Will Moore, Jean-Marie Burel, Jason Swedlow | will-moore, jburel, jrswedlow | University of Dundee | 2025-01-22 | [Review](rfcs:rfc5:review2)|

From f922e2d292a8fea4989f9c7bad439eef81784c93 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Thu, 23 Oct 2025 15:29:11 +0200
Subject: [PATCH 099/115] add dstansby as endorser

See https://github.com/ome/ngff/pull/350#issuecomment-3436972241
---
 rfc/5/versions/1/index.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index 8c0ad2a5..cefa6eb2 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -15,6 +15,7 @@ This RFC is currently in RFC state `R4` (authors prepare responses).
 | **Author** | Stephan Saalfeld | @axtimwalde | HHMI Janelia | 2024-07-30 | Implemented (with JB) |
 | **Author** | Johannes Soltwedel | @jo-mueller | German Bioimaging e.V. | 2025-09-19 | Implemented |
 | **Endorser** | Will Moore | @will-moore | University of Dundee | 2025-10-23 | Implemented |
+| **Endorser** | David Stansby | @dstansby | University College London | 2025-10-23 | Implemented |
 | **Endorser** | Norman Rzepka | @normanrz | Scalable Minds | 2024-08-22 | |
 | **Reviewer** | Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster | toloudis, dyf, fcollman | Allen Institutes | 2024-11-28 | [Review](rfcs:rfc5:review1) |
 | **Reviewer** | Will Moore, Jean-Marie Burel, Jason Swedlow | will-moore, jburel, jrswedlow | University of Dundee | 2025-01-22 | [Review](rfcs:rfc5:review2)|

From 89059e71ea2178899f383b14e12f5871c213fa93 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Tue, 28 Oct 2025 13:56:57 +0100
Subject: [PATCH 100/115] fix reference to .zarray

---
 rfc/5/versions/1/index.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md
index cefa6eb2..87e47c5f 100644
--- a/rfc/5/versions/1/index.md
+++ b/rfc/5/versions/1/index.md
@@ -179,7 +179,7 @@ As with all coordinate systems, the `dimension_names` must be unique and non-nul
 ```
 ````
 
-The axes and their order align with the `shape` attribute in the zarr array attributes (in `.zarray`),
+The axes and their order align with the `shape` attribute in the zarr array attributes (in `zarr.json`),
 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. 

From d3bb6471d9ef048c1cae2a0b2a30de2b568990e4 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 29 Oct 2025 09:38:10 +0100
Subject: [PATCH 101/115] Moved updated proposal to front and legacy document
 into versions

---
 rfc/5/index.md            | 1144 +++++++++++++++++++++++--------------
 rfc/5/versions/1/index.md | 1142 ++++++++++++++----------------------
 2 files changed, 1143 insertions(+), 1143 deletions(-)

diff --git a/rfc/5/index.md b/rfc/5/index.md
index 83986762..77470653 100644
--- a/rfc/5/index.md
+++ b/rfc/5/index.md
@@ -1,4 +1,4 @@
-# RFC-5 Coordinate systems and transformations
+# RFC-5: Coordinate Systems and Transformations
 
 ```{toctree}
 :hidden:
@@ -9,315 +9,421 @@ responses/index
 versions/index
 ```
 
-Add named coordinate systems and expand and clarify coordinate transformations.
+Add named coordinate systems and expand and clarify coordinate transformations. This document represent the updated proposal following the [original RFC5 proposal](./versions/1/index.md) and incorporates feedback from reviewers and implementers.
 
 ## Status
 
-This RFC is currently in RFC state `R1` (send for review).
-
-```{list-table} Record
-:widths: 8, 20, 20, 20, 15, 10
-:header-rows: 1
-:stub-columns: 1
-
-*   - Role
-    - Name
-    - GitHub Handle
-    - Institution
-    - Date
-    - Status
-*   - Author
-    - John Bogovic
-    - @bogovicj
-    - HHMI Janelia
-    - 2024-07-30
-    - Implemented
-*   - Author
-    - Davis Bennett
-    - @d-v-b
-    -
-    - 2024-07-30
-    - Implemented validation
-*   - Author
-    - Luca Marconato
-    - @LucaMarconato
-    - EMBL
-    - 2024-07-30
-    - Implemented
-*   - Author
-    - Matt McCormick
-    - @thewtex
-    - ITK
-    - 2024-07-30
-    - Implemented
-*   - Author
-    - Stephan Saalfeld
-    - @axtimwalde
-    - HHMI Janelia
-    - 2024-07-30
-    - Implemented (with JB)
-*   - Endorser
-    - Norman Rzepka
-    - @normanrz
-    - Scalable Minds
-    - 2024-08-22
-    -
-*   - Reviewer
-    - Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster
-    - toloudis, dyf, fcollman
-    - Allen Institutes
-    - 2024-11-28
-    - [Review](./reviews/1/index)
-*   - Reviewer
-    - Will Moore, Jean-Marie Burel, Jason Swedlow
-    - will-moore, jburel, jrswedlow
-    - University of Dundee
-    - 2025-01-22
-    - [Review](./reviews/2/index)
-```
+This RFC is currently in RFC state `R4` (authors prepare responses).
+
+| **Role** | Name | GitHub Handle | Institution | Date | Status |
+|----------|------|---------------|-------------|------|--------|
+| **Author** | John Bogovic | @bogovicj | HHMI Janelia | 2024-07-30 | Implemented |
+| **Author** | Davis Bennett | @d-v-b | | 2024-07-30 | Implemented validation |
+| **Author** | Luca Marconato | @LucaMarconato | EMBL | 2024-07-30 | Implemented |
+| **Author** | Matt McCormick | @thewtex | ITK | 2024-07-30 | Implemented |
+| **Author** | Stephan Saalfeld | @axtimwalde | HHMI Janelia | 2024-07-30 | Implemented (with JB) |
+| **Author** | Johannes Soltwedel | @jo-mueller | German Bioimaging e.V. | 2025-10-07 | Implemented |
+| **Endorser** | Will Moore | @will-moore | University of Dundee | 2025-10-23 | Implemented |
+| **Endorser** | David Stansby | @dstansby | University College London | 2025-10-23 | Implemented |
+| **Endorser** | Norman Rzepka | @normanrz | Scalable Minds | 2024-08-22 | |
+| **Reviewer** | Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster | toloudis, dyf, fcollman | Allen Institutes | 2024-11-28 | [Review](rfcs:rfc5:review1) |
+| **Reviewer** | Will Moore, Jean-Marie Burel, Jason Swedlow | will-moore, jburel, jrswedlow | University of Dundee | 2025-01-22 | [Review](rfcs:rfc5:review2)|
 
 ## Overview
 
 This RFC provides first-class support for spatial and coordinate transformations in OME-Zarr.
 
+Working version title: **0.6dev2**
+
 ## Background
 
-Coordinate and spatial transformation are vitally important for neuro and bio-imaging and broader scientific imaging practices
-to enable:
-
-1. Reproducibility and Consistency: Supporting spatial transformations explicitly in a file format ensures that transformations
-   are applied consistently across different platforms and applications. This FAIR capability is a cornerstone of scientific
-   research, and having standardized formats and tools facilitates verification of results by independent
-   researchers.
-2. Integration with Analysis Workflows: Having spatial transformations as a first-class citizen within file formats allows for
-   seamless integration with various image analysis workflows. Registration transformations can be used in subsequent image
-   analysis steps without requiring additional conversion.
-3. Efficiency and Accuracy: Storing transformations within the file format avoids the need for re-sampling each time the data is
-   processed. This reduces sampling errors and preserves the accuracy of subsequent analyses. Standardization enables on-demand
-   transformation, critical for the massive volumes collected by modern microscopy techniques.
-4. Flexibility in Analysis: A file format that natively supports spatial transformations allows researchers to apply, modify, or
-   reverse transformations as needed for different analysis purposes. This flexibility is critical for tasks such as
-   longitudinal studies, multi-modal imaging, and comparative analysis across different subjects or experimental conditions.
-
-Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec covering many of the use cases 
-requested in [this github issue](https://github.com/ome/ngff/issues/84). It also adds "coordinate systems" - named
-sets of "axes." Related the relationship of discrete arrays to physical coordinates and the interpretation and motivation for
-axis types.
+Coordinate and spatial transformation are vitally important
+for neuro and bio-imaging and broader scientific imaging practices to enable:
+
+1. Reproducibility and Consistency:
+  Supporting spatial transformations explicitly in a file format ensures that
+  transformations are applied consistently across different platforms and applications.
+  This FAIR capability is a cornerstone of scientific research,
+  and having standardized formats and tools facilitates verification of results by independent researchers.
+2. Integration with Analysis Workflows: 
+  Having spatial transformations as a first-class citizen within file formats
+  allows for seamless integration with various image analysis workflows.
+  Registration transformations can be used in subsequent image analysis steps
+  without requiring additional conversion.
+3. Efficiency and Accuracy:
+  Storing transformations within the file format avoids
+  the need for re-sampling each time the data is processed.
+  This reduces sampling errors and preserves the accuracy of subsequent analyses.
+  Standardization enables on-demand transformation,
+  critical for the massive volumes collected by modern microscopy techniques.
+4. Flexibility in Analysis:
+  A file format that natively supports spatial transformations allows researchers to apply, modify,
+  or reverse transformations as needed for different analysis purposes.
+  This flexibility is critical for tasks such as longitudinal studies, multi-modal imaging,
+  and comparative analysis across different subjects or experimental conditions.
+
+Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec
+covering many of the use cases requested in [this github issue](https://github.com/ome/ngff/issues/84).
+It also adds "coordinate systems" - named sets of "axes."
+Related the relationship of discrete arrays to physical coordinates
+and the interpretation and motivation for axis types.
 
 
 ## Proposal
 
-Below is a slightly abridged copy of the proposed changes to the specification (examples are omitted), the full set of changes
-including all examples are publicly available on the [github pull request](https://github.com/ome/ngff/pull/138).
+Below is a complete copy of the proposed changes including suggestions
+from reviewers and contributors of the previously associated [github pull request](https://github.com/ome/ngff/pull/138).
+The changes, if approved, shall be translated into bikeshed syntax and added to the ngff repository in a separate PR.
+This PR will then comprise complete json schemas when the RFC enters the SPEC phase (see RFC1).
 
 
 ### "coordinateSystems" metadata
 
-A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system:
-- MUST contain the field "name". The value MUST be a non-empty string that is unique among `coordinateSystem`s.
+A "coordinate system" is a collection of "axes" / dimensions with a name.
+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 order of the `"axes"` list matters
+and defines 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).
 
-The order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that
-coordinate system. The "dimensionality" of a coordinate system
-is indicated by the length of its "axes" array. The "volume_micrometers" example coordinate system above is three dimensional (3D).
-
-The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate
-system's dimensions. Axis `name`s 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, with identical axes) or can be
-transformed to the same coordinate system before doing analysis. See the example below.
+````{admonition} Example
 
+Coordinate Systems metadata example
 
-### "axes" metadata
-
-"axes" describes the dimensions of a coordinate systems. It is a list of dictionaries, where each dictionary 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.
-- 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.
-- 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.
+```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](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 a list of dictionaries,
+where each dictionary 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.
+- 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.
+- 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.
 
-If part of multiscales metadata, the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data.
+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 of representing a `channel`, `coordinate`, or `displacement` are
-usually discrete.
+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.
+```{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
 
-### 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.  Its name is the path to the array
-in the container, its axes have `"type":"array"`, are unitless, and have default "name"s. The ith axis has `"name":"dim_i"`
-(these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)).
+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.
 
+````{admonition} Example
+```json
+{
+    "name": "0",
+    "axes": [
+        {"name": "dim_0", "type": "array"},
+        {"name": "dim_1", "type": "array"},
+        {"name": "dim_2", "type": "array"}
+    ]
+}
+```
+````
 
-The dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array.  The axis with
-name `"dim_i"` is the ith element of the `"axes"` list. The axes and their order align with the `shape`
-attribute in the zarr array attributes (in `.zarray`), 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/v2.html#arrays),
+The axes and their order align with the `shape` attribute in the zarr array attributes (in `zarr.json`),
+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"`.
 
-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
+#### 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.
+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.
 
 
 ### "coordinateTransformations" metadata
 
-"coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes").
+"coordinateTransformations" describe the mapping between two coordinate systems (defined by "coordinateSystems").
 For example, to map an array's discrete coordinate system to its corresponding physical coordinates.
-Coordinate transforms are in the "forward" direction. They represent functions from *points* in the
-input space to *points* in the output space. 
+Coordinate transforms are in the "forward" direction.
+This means they represent functions from *points* in the input space to *points* in the output space
+(see [example below](example:coordinate_transformation_scale)).
 
+They:
 
-- MUST contain the field "type".
+- MUST contain the field "type" (string).
 - MUST contain any other fields required by the given "type" (see table below).
-- MUST contain the field "output", unless part of a `sequence` or `inverseOf` (see details).
-- MUST contain the field "input", unless part of a `sequence` or `inverseOf` (see details).
-- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations.
+- MUST contain the field "output" (string),
+  unless part of a `sequence` or `inverseOf` (see details).
+- MUST contain the field "input" (string),
+  unless part of a `sequence` or `inverseOf` (see details).
+- MAY contain the field "name" (string).
+  Its value MUST be unique across all "name" fields for coordinate transformations.
 - Parameter values MUST be compatible with input and output space dimensionality (see details).
 
-
-  
-   
identity - - The identity transformation is the default transformation and is typically not explicitly defined. -
mapAxis - "mapAxis":Dict[String:String] - A maxAxis transformation specifies an axis permutation as a map between axis names. -
translation - one of:
"translation":List[number],
"path":str -
translation vector, stored either as a list of numbers ("translation") or as binary data at a location - in this container (path). -
scale - one of:
"scale":List[number],
"path":str -
scale vector, stored either as a list of numbers (scale) or as binary data at a location in this - container (path). -
affine - one of:
"affine": List[List[number]],
"path":str -
affine transformation matrix stored as a flat array stored either with json uing the affine field - or as binary data at a location in this container (path). If both are present, the binary values at path should be used. -
rotation - one of:
"rotation":List[number],
"path":str -
rotation transformation matrix stored as an array stored either - with json or as binary data at a location in this container (path). - If both are present, the binary parameters at path are used. -
sequence - "transformations":List[Transformation] - A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. -
displacements - "path":str
"interpolation":str -
Displacement field transformation located at (path). -
coordinates - "path":str
"interpolation":str -
Coordinate field transformation located at (path). -
inverseOf - "transform":Transform - The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. -
bijection - "forward":Transform
"inverse":Transform -
Explicitly define an invertible transformation by providing a forward transformation and its inverse. -
byDimension - "transformations":List[Transformation] - Define a high dimensional transformation using lower dimensional transformations on subsets of - dimensions. -
typefieldsdescription -
+The following transformations are supported: + +| Type | Fields | Description | +|------|--------|-------------| +| `identity` | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | +| `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | +| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | +| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | +| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | +| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| +| `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | +| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | +| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | +| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijection) for details and examples. | +| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | +| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | + +Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible +(e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation). + +````{admonition} Example +(example:coordinate_transformation_scale)= + +```json +{ + "coordinateSystems": [ + { "name": "in", "axes": [{"name": "j"}, {"name": "i"}] }, + { "name": "out", "axes": [{"name": "y"}, {"name": "x"}] } + ], + "coordinateTransformations": [ + { + "type": "scale", + "scale": [2, 3.12], + "input": "in", + "output": "out" + } + ] +} + +``` + +For example, the scale transformation above 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. +```` Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; -- SHOULD parse `mapAxis`, `affine` transformations; +- SHOULD parse `mapAxis`, `affine`, `rotation` transformations; +- SHOULD display an informative warning if encountering transformations that cannot be parsed; - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; -Coordinate transformations from array to physical coordinates MUST be stored in multiscales, -and MUST be duplicated in the attributes of the zarr array. Transformations between different images MUST be stored in the -attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD -be stored in a zarr group `"coordinateTransformations"`. +Coordinate transformations can be stored in multiple places to reflect different usecases. + +- Transformations in individual mutliscale datasets represent a special case of transformations + and are explained [below](#multiscales-metadata). +- Additional transformations for single multiscale images MUST be stored under a field `coordinateTransformations` + in the multiscales dictionaries. + This `coordinateTransformations` field MUST contain a list of valid [transformations](#transformation-types). +- Transformations between two or more images MUST be stored in the attributes of a parent zarr group. + For transformations that store data or parameters in a zarr array, + those zarr arrays SHOULD be stored in a zarr group called "coordinateTransformations".
 store.zarr                      # Root folder of the zarr store
 │
-├── .zattrs                     # coordinate transformations describing the relationship between two image coordinate systems
+├── zarr.json                   # coordinate transformations describing the relationship between two image coordinate systems
 │                               # are stored in the attributes of their parent group.
-│                               # transformations between 'volume' and 'crop' coordinate systems are stored here.
+│                               # transformations between coordinate systems in the 'volume' and 'crop' multiscale images are stored here.
 │
-├── coordinateTransformations   # transformations that use array storage go in a "coordinateTransformations" zarr group.
+├── 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
-│       ├── .zattrs
-│       └── .zarray
+│       └── zarr.json
 │
 ├── volume
-│   ├── .zattrs                 # group level attributes (multiscales)
-│   └── 0                       # a group containing the 0th scale
-│       └── image               # a zarr array
-│           ├── .zattrs         # physical coordinate system and transformations here
-│           └── .zarray         # the array attributes
+│   ├── 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
-    ├── .zattrs                 # group level attributes (multiscales)
-    └── 0                       # a group containing the 0th scale
-        └── image               # a zarr array
-            ├── .zattrs         # physical coordinate system and transformations here
-            └── .zarray         # the array attributes
+    ├── 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
 
-### Additional details - -Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value -corresponding to the name of a coordinate system. The coordinate system's name may be the path to an array, and therefore may -not appear in the list of coordinate systems. - -Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the -`transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for -details). - -Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output` as arrays -of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below for -details). +````{admonition} Example +(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_image1" 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" + } +] +``` -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. For example, the scale transformation above -defines the function: +And the image at the path `sampleA_instrument1` would have the following as the first coordinate system: -``` -x = 0.5 * i -y = 1.2 * j +```json +"coordinateSystems": [ + { + "name": "sampleA-instrument1", + "axes": [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] + }, +] ``` -i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. +The image at path `sampleA_instrument2` would have this as the first listed coordinate system: -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 +[ + { + "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 appears in the `transformations` list of a `sequence` +or is the `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-metadata) 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 { @@ -325,179 +431,224 @@ transformation type, for example: "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](#transformation-types) 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. +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](#transformation-types) 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 -Two transformation types ([affine](#affine) and [rotation](#rotation)) are parametrized by matrices. Matrices are applied to -column vectors that represent points in the input coordinate system. The first (last) axis in a coordinate system is the top -(bottom) entry in the column vector. 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). +Two transformation types ([affine](#affine) and [rotation](#rotation)) 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). +#### Transformation types -### Transformation types +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`. -Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value -of "input" is an array, it's length gives the input dimension, otherwise the length of "axes" for the coordinate -system with the name of the "input" value gives the input dimension. If the value of "input" is an array, it's -length 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 length 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 +`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. -`identity` transformations map input coordinates to output coordinates without modification. The position of -the ith 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 part of a [`sequence`](#sequence) transformation. -#### mapAxis +##### mapAxis -`mapAxis` transformations describe axis permutations as a mapping of axis names. Transformations MUST include a `mapAxis` field -whose value is an object, all of whose values are strings. If the object contains `"x":"i"`, then the transform sets the value -of the output coordinate for axis "x" to the value of the coordinate of input axis "i" (think `x = i`). For every axis in its output coordinate -system, the `mapAxis` MUST have a corresponding field. For every value of the object there MUST be an axis of the input -coordinate system with that name. Note that the order of the keys could be reversed. +`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. +`mapAxis` transforms are invertible. +The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. -#### translation +##### translation -`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. +`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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 list of numbers. The list MUST have length `N`. +: The translation parameters stored as a JSON list of numbers. +The list MUST have length `N`. + +##### scale -#### scale +`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. -`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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 stored as a JSON list of numbers. The list MUST have length `N`. +: The scale parameters are stored as a JSON list of numbers. +The list MUST have length `N`. +##### affine -#### affine +`affine`s are [matrix transformations](#matrix-transformations) 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. -`affine`s are [matrix transformations](#matrix-transformations) from N-dimensional inputs to M-dimensional outputs 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 part of a [`sequence`](#sequence) transformation. 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)`. +: 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 where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. +: The affine parameters stored in JSON. +The matrix MUST be stored as 2D nested array +where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. +##### rotation -#### rotation +`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. +When possible, a rotation transformation SHOULD be preferred to its 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. -`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation -transformation SHOULD be preferred to its 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 part of a [`sequence`](#sequence) transformation. 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`. +: 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 where the outer array MUST be length `N` and the inner arrays MUST be length `N`. +: The parameters stored in JSON. +The matrix MUST be stored as a 2D nested array where the outer array MUST be length `N` +and the inner arrays MUST be length `N`. -#### inverseOf +##### inverseOf -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). +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. +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. ``` +##### sequence -#### sequence - -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 list 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. - -The transformations included in the `transformations` array may omit their `input` and `output` fields under the conditions -outlined below: - -- The `input` and `output` fields MAY be omitted for the following transformation types: - - `identity`, `scale`, `translation`, `rotation`, `affine`, `displacements`, `coordinates` -- The `input` and `output` fields MAY be omitted for `inverseOf` transformations if those fields may be omitted for the - transformation it wraps -- The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for - both its `forward` and `inverse` transformations -- The `input` and `output` fields MAY be omitted for `sequence` transformations if the fields may be omitted for - all transformations in the sequence after flattening the nested sequence lists. -- The `input` and `output` fields MUST be included for transformations of type: `mapAxis`, and `byDimension` (see the note - below), and under all other conditions. +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 list 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. -#### coordinates and displacements +##### coordinates and displacements + +`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`). -`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 coordinate system"). -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 coordinate system"). +The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. -* 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`. +* 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`. +* 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 field: +`coordinates` and `displacements` transformations are not invertible in general, +but implementations MAY approximate their inverses. +Metadata for these coordinate transforms have the following fields:
path
The location of the coordinate array in this (or another) container.
interpolation
-
The interpolation attributes MAY be provided. It's value indicates - the interpolation to use if transforming points not on the array's discrete grid. +
The interpolation attributes MAY be provided. + It's value indicates the interpolation to use + if transforming points not on the array's discrete grid. Values could be:
  • linear (default)
  • @@ -507,11 +658,16 @@ inverses. Metadata for these coordinate transforms have the following field:
-For both `coordinates` and `displacements`, the array data at referred to by `path` MUST define coordinate system and coordinate transform metadata: +For both `coordinates` and `displacements`, +the array data at referred to by `path` MUST define coordinate system +and coordinate transform metadata: -* Every axis name in the `coordinateTransform`'s `input` MUST appear in the coordinate system -* The array dimension corresponding to the `coordinate` or `displacement` axis MUST have length equal to the number of dimensions of the `coordinateTransform` `output` -* If the input coordinate system `N` axes, then the array data at `path` MUST have `(N + 1)` dimensions. +* Every axis name in the `coordinateTransform`'s `input` + MUST appear in the coordinate system. +* The array dimension corresponding to the `coordinate` or `displacement` axis + MUST have length equal to the number of dimensions of the `coordinateTransform` `output` +* If the input coordinate system `N` axes, + then the array data at `path` MUST have `(N + 1)` dimensions. * SHOULD have a `name` identical to the `name` of the corresponding `coordinateTransform`. For `coordinates`: @@ -526,90 +682,219 @@ For `displacements`: * `input` and `output` MUST have an equal number of dimensions. -#### byDimension +##### byDimension -`byDimension` transformations build a high dimensional transformation using lower dimensional transformations -on subsets of dimensions. +`byDimension` transformations build a high dimensional transformation +using lower dimensional transformations on subsets of dimensions. +The `input` and `output` fields MUST always be included for this transformations type.
transformations
-
A list of transformations, each of which applies to a (non-strict) subset of input and output dimensions (axes). - The values of input and output fields MUST be an array of strings. - Every axis name in input 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 MUST appear in exactly one of its child transformations' output. +
Each child transformation MUST contain input_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.
+##### bijection + +A bijection transformation is an invertible transformation in +which both the `forward` and `inverse` transformations are explicitly defined. +Each direction SHOULD be a transformation type that is not closed-form invertible. +Its input and output spaces MUST have equal dimension. +The input and output dimensions for the both the forward and inverse transformations +MUST match bijection's input and output space dimensions. + +`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, +in which case the `forward` transformation's `input` and `output` are understood to match the bijection's, +and the `inverse` transformation's `input` (`output`) matches the bijection's `output` (`input`), +see the example below. + +The `input` and `output` fields MAY be omitted for `bijection` transformations +if the fields may be omitted for both its `forward` and `inverse` transformations + +Practically, non-invertible transformations have finite extents, +so bijection transforms should only be expected to be correct / consistent for points that fall within those extents. +It may not be correct for any point of appropriate dimensionality. + +### "multiscales" metadata + +Metadata about an image can be found under the `multiscales` key in the group-level OME-Zarr Metadata. +Here, "image" refers to 2 to 5 dimensional data representing image +or volumetric data with optional time or channel axes. +It is stored in a multiple resolution representation. + +`multiscales` contains a list of dictionaries where each entry describes a multiscale image. + +Each `multiscales` dictionary MUST contain the field "coordinateSystems", +whose value is an array containing valid coordinate system metadata +(see [coordinate systems](#coordinatesystems-metadata)). +The last entry of this array is the "intrinsic" coordinate system +and MUST contain axis information pertaining to physical coordinates. +It should be used for viewing and processing unless a use case dictates otherwise. +It will generally be a representation of the image in its native physical coordinate system. + +The following MUST hold for all coordinate systems inside multiscales metadata. +The length of "axes" must be between 2 and 5 +and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path"). +The "axes" MUST contain 2 or 3 entries of "type:space" +and MAY contain one additional entry of "type:time" +and MAY contain one additional entry of "type:channel" or a null / custom type. +In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), +followed by the "channel" or custom axis (if present) and the axes of type "space". +If there are three spatial axes where two correspond to the image plane ("yx") +and images are stacked along the other (anisotropic) axis ("z"), +the spatial axes SHOULD be ordered as "zyx". + +Each `multiscales` dictionary MUST contain the field `datasets`, +which is a list of dictionaries describing the arrays storing the individual resolution levels. +Each dictionary in `datasets` MUST contain the field `path`, +whose value is a string containing the path to the Zarr array for this resolution relative to the current Zarr group. +The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. +Every Zarr array referred to by a `path` MUST have the same number of dimensions +and MUST NOT have more than 5 dimensions. +The number of dimensions and order MUST correspond to number and order of `axes`. + +Each dictionary in `datasets` MUST contain the field `coordinateTransformations`, +whose value is a list of dictionaries that define a transformation +that maps Zarr array coordinates for this resolution level to the "intrinsic" coordinate system +(the last entry of the `coordinateSystems` array). +The transformation is defined according to [transformations metadata](#transformation-types). +The transformation MUST take as input points in the array coordinate system +corresponding to the Zarr array at location `path`. +The value of "input" MUST equal the value of `path`, +implementations should always treat the value of `input` as if it were equal to the value of `path`. +The value of the transformation’s `output` MUST be the name of the "intrinsic" [coordinate system](#coordinatesystems-metadata). + +This transformation MUST be one of the following: + +* A single scale or identity transformation +* A sequence transformation containing one scale and one translation transformation. + +In these cases, the scale transformation specifies the pixel size in physical units or time duration. +If scaling information is not available or applicable for one of the axes, +the value MUST express the scaling factor between the current resolution +and the first resolution for the given axis, +defaulting to 1.0 if there is no downsampling along the axis. +This is strongly recommended +so that the the "intrinsic" coordinate system of the image avoids more complex transformations. + +If applications require additional transformations, +each `multiscales` dictionary MAY contain the field `coordinateTransformations`, +describing transformations that are applied to all resolution levels in the same manner. +The value of `input` MUST equal the name of the "intrinsic" coordinate system. +The value of `output` MUST be the name of the output coordinate System +which is different from the "intrinsic" coordinate system. + +Each `multiscales` dictionary SHOULD contain the field `name`. + +Each `multiscales` dictionary SHOULD contain the field `type`, +which gives the type of downscaling method used to generate the multiscale image pyramid. +It SHOULD contain the field "metadata", +which contains a dictionary with additional information about the downscaling method. + +````{admonition} Example + +A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution levels could look like this: -#### bijection - -A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations -are explicitly defined. Each direction SHOULD be a transformation type that is not closed-form invertible. -Its' input and output spaces MUST have equal dimension. The input and output dimensions for the both the forward -and inverse transformations MUST match bijection's input and output space dimensions. - -`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, in which case -the `forward` transformation's `input` and `output` are understood to match the bijection's, and the `inverse` -transformation's `input` (`output`) matches the bijection's `output` (`input`), see the example below. - -Practically, non-invertible transformations have finite extents, so bijection transforms should only be expected -to be correct / consistent for points that fall within those extents. It may not be correct for any point of -appropriate dimensionality. - -## Specific feedback requested - -We ask the reviewers for one specific piece of feedback. Specifically about whether parameters for transformations should -be written as they are currently in the draft pull request, with named parameters at the "top level" e.g.: - -``` -{ - "type": "affine", - "affine": [[1, 2, 3], [4, 5, 6]], - "input": "ji", - "output": "yx" -} -``` - -or alternatively in a `parameters` field: - -``` -{ - "type": "affine", - "parameters": { - "matrix": [[1, 2, 3], [4, 5, 6]] - }, - "input": "ji", - "output": "yx" -} -``` - -In discussions, some authors preferred the latter because it will make the "top-level" keys for transformation -objects all identical, which could make serialization / validation simpler. One downside is that this change -is breaking for the existing `scale` and `translation` transformations - -``` +```json { - "type": "scale", - "scale": [2, 3], - "input": "ji", - "output": "yx" + "zarr_format": 3, + "node_type": "group", + "attributes": { + "ome": { + "version": "0.5", + "multiscales": [ + { + "name": "example", + "coordinateSystems": [ + { + "name": "intrinsic", + "axes": [ + { "name": "t", "type": "time", "unit": "millisecond" }, + { "name": "c", "type": "channel" }, + { "name": "z", "type": "space", "unit": "micrometer" }, + { "name": "y", "type": "space", "unit": "micrometer" }, + { "name": "x", "type": "space", "unit": "micrometer" } + ] + } + ], + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + // the voxel size for the first scale level (0.5 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 0.5, 0.5, 0.5], + "input": "0", + "output": "intrinsic" + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 1.0, 1.0, 1.0], + "input": "1", + "output": "intrinsic" + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) + // and the time unit (0.1 milliseconds), which is the same for each scale level + "type": "scale", + "scale": [1.0, 1.0, 2.0, 2.0, 2.0], + "input": "2", + "output": "intrinsic" + } + ] + } + ], + "type": "gaussian", + "metadata": { + "description": "the fields in metadata depend on the downscaling implementation. Here, the parameters passed to the skimage function are given", + "method": "skimage.transform.pyramid_gaussian", + "version": "0.16.1", + "args": "[true]", + "kwargs": { "multichannel": true } + } + } + ] + } + } } ``` - -would change to: - +```` + +If only one multiscale is provided, use it. +Otherwise, the user can choose by name, +using the first multiscale as a fallback: + +```python +datasets = [] +for named in multiscales: + if named["name"] == "3D": + datasets = [x["path"] for x in named["datasets"]] + break +if not datasets: + # Use the first by default. Or perhaps choose based on chunk size. + datasets = [x["path"] for x in multiscales[0]["datasets"]] ``` -{ - "type": "scale", - "parameters": { - "scale": [2, 3], - }, - "input": "ji", - "output": "yx" -} -``` - -The authors would be interested to hear perspectives from the reviewers on this matter. ## Requirements @@ -648,16 +933,19 @@ issues or unknown unknowns prior to writing any real code. ## Drawbacks, risks, alternatives, and unknowns -Adopting this proposal will add an implementation burden because it adds more transformation types. Though this drawback is -softened by the fact that implementations will be able to choose which transformations to support (e.g., implementations may choose -not to support non-linear transformations). +Adopting this proposal will add an implementation burden because it adds more transformation types. +Though this drawback is softened by the fact that implementations +will be able to choose which transformations to support +(e.g., implementations may choose not to support non-linear transformations). -An alternative to this proposal would be not to add support transformations directly and instead recommend software use an -existing format (e.g., ITK's). The downside of that is that alternative formats will not integrate well with OME-NGFF as they do -not use JSON or Zarr. +An alternative to this proposal would be not to add support transformations directly +and instead recommend software use an existing format (e.g., ITK's). +The downside of that is that alternative formats will not integrate well with OME-NGFF +as they do not use JSON or Zarr. -In all, we believe the benefits of this proposal (outlined in the Background section) far outweigh these drawbacks, and will -better promote software interoperability than alternatives. +In all, we believe the benefits of this proposal (outlined in the Background section) +far outweigh these drawbacks, +and will better promote software interoperability than alternatives. ## Prior art and references @@ -713,7 +1001,7 @@ Adds coordinate systems, these contain axes which are backward-compatible with t ## Testing -Public examples of transformations with expected input/output pairs will be provided. +Public examples of transformations with expected input/output pairs are provided [here](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/releases/tag/0.6-dev1-rev1) ## UI/UX @@ -722,8 +1010,8 @@ non-linear transformations), and inform users what action will be taken. The det application dependent, but ignoring the unsupported transformation or falling back to a simpler transformation are likely to be common choices. -Implementations MAY choose to communicate if and when an image can be displayed in multiple coordinate systems. Users might -choose between different options, or software could choose a default (e.g. the first listed coordinate system). The +Implementations SHOULD communicate if and when an image can be displayed in multiple coordinate systems. Users might +choose between different options, or software could choose a default (e.g. the first or last listed coordinate system). The [`multiscales` in version 0.4](https://ngff.openmicroscopy.org/0.4/#multiscale-md) has a similar consideration. @@ -731,4 +1019,4 @@ choose between different options, or software could choose a default (e.g. the f | Date | Description | Link | | ---------- | ---------------------------- | ---------------------------------------------------------------------------- | -| 2024-10-08 | RFC assigned and published | [https://github.com/ome/ngff/pull/255](https://github.com/ome/ngff/pull/255) | +| 2024-10-08 | RFC assigned and published | [https://github.com/ome/ngff/pull/255](https://github.com/ome/ngff/pull/255) | \ No newline at end of file diff --git a/rfc/5/versions/1/index.md b/rfc/5/versions/1/index.md index 87e47c5f..9200139e 100644 --- a/rfc/5/versions/1/index.md +++ b/rfc/5/versions/1/index.md @@ -1,420 +1,314 @@ -# RFC-5 (2025-10-07 version) +# RFC-5 Coordinate systems and transformations (2024-07-30 version) Add named coordinate systems and expand and clarify coordinate transformations. ## Status -This RFC is currently in RFC state `R4` (authors prepare responses). - -| **Role** | Name | GitHub Handle | Institution | Date | Status | -|----------|------|---------------|-------------|------|--------| -| **Author** | John Bogovic | @bogovicj | HHMI Janelia | 2024-07-30 | Implemented | -| **Author** | Davis Bennett | @d-v-b | | 2024-07-30 | Implemented validation | -| **Author** | Luca Marconato | @LucaMarconato | EMBL | 2024-07-30 | Implemented | -| **Author** | Matt McCormick | @thewtex | ITK | 2024-07-30 | Implemented | -| **Author** | Stephan Saalfeld | @axtimwalde | HHMI Janelia | 2024-07-30 | Implemented (with JB) | -| **Author** | Johannes Soltwedel | @jo-mueller | German Bioimaging e.V. | 2025-09-19 | Implemented | -| **Endorser** | Will Moore | @will-moore | University of Dundee | 2025-10-23 | Implemented | -| **Endorser** | David Stansby | @dstansby | University College London | 2025-10-23 | Implemented | -| **Endorser** | Norman Rzepka | @normanrz | Scalable Minds | 2024-08-22 | | -| **Reviewer** | Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster | toloudis, dyf, fcollman | Allen Institutes | 2024-11-28 | [Review](rfcs:rfc5:review1) | -| **Reviewer** | Will Moore, Jean-Marie Burel, Jason Swedlow | will-moore, jburel, jrswedlow | University of Dundee | 2025-01-22 | [Review](rfcs:rfc5:review2)| +This RFC is currently in RFC state `R1` (send for review). + +```{list-table} Record +:widths: 8, 20, 20, 20, 15, 10 +:header-rows: 1 +:stub-columns: 1 + +* - Role + - Name + - GitHub Handle + - Institution + - Date + - Status +* - Author + - John Bogovic + - @bogovicj + - HHMI Janelia + - 2024-07-30 + - Implemented +* - Author + - Davis Bennett + - @d-v-b + - + - 2024-07-30 + - Implemented validation +* - Author + - Luca Marconato + - @LucaMarconato + - EMBL + - 2024-07-30 + - Implemented +* - Author + - Matt McCormick + - @thewtex + - ITK + - 2024-07-30 + - Implemented +* - Author + - Stephan Saalfeld + - @axtimwalde + - HHMI Janelia + - 2024-07-30 + - Implemented (with JB) +* - Endorser + - Norman Rzepka + - @normanrz + - Scalable Minds + - 2024-08-22 + - +* - Reviewer + - Dan Toloudis, David Feng, Forrest Collman, Nathalie GAudreault, Gideon Dunster + - toloudis, dyf, fcollman + - Allen Institutes + - 2024-11-28 + - [Review](rfcs:rfc5:review1) +* - Reviewer + - Will Moore, Jean-Marie Burel, Jason Swedlow + - will-moore, jburel, jrswedlow + - University of Dundee + - 2025-01-22 + - [Review](rfcs:rfc5:review2) +``` ## Overview This RFC provides first-class support for spatial and coordinate transformations in OME-Zarr. -Working version title: **0.6dev2** - ## Background -Coordinate and spatial transformation are vitally important -for neuro and bio-imaging and broader scientific imaging practices to enable: - -1. Reproducibility and Consistency: - Supporting spatial transformations explicitly in a file format ensures that - transformations are applied consistently across different platforms and applications. - This FAIR capability is a cornerstone of scientific research, - and having standardized formats and tools facilitates verification of results by independent researchers. -2. Integration with Analysis Workflows: - Having spatial transformations as a first-class citizen within file formats - allows for seamless integration with various image analysis workflows. - Registration transformations can be used in subsequent image analysis steps - without requiring additional conversion. -3. Efficiency and Accuracy: - Storing transformations within the file format avoids - the need for re-sampling each time the data is processed. - This reduces sampling errors and preserves the accuracy of subsequent analyses. - Standardization enables on-demand transformation, - critical for the massive volumes collected by modern microscopy techniques. -4. Flexibility in Analysis: - A file format that natively supports spatial transformations allows researchers to apply, modify, - or reverse transformations as needed for different analysis purposes. - This flexibility is critical for tasks such as longitudinal studies, multi-modal imaging, - and comparative analysis across different subjects or experimental conditions. - -Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec -covering many of the use cases requested in [this github issue](https://github.com/ome/ngff/issues/84). -It also adds "coordinate systems" - named sets of "axes." -Related the relationship of discrete arrays to physical coordinates -and the interpretation and motivation for axis types. +Coordinate and spatial transformation are vitally important for neuro and bio-imaging and broader scientific imaging practices +to enable: + +1. Reproducibility and Consistency: Supporting spatial transformations explicitly in a file format ensures that transformations + are applied consistently across different platforms and applications. This FAIR capability is a cornerstone of scientific + research, and having standardized formats and tools facilitates verification of results by independent + researchers. +2. Integration with Analysis Workflows: Having spatial transformations as a first-class citizen within file formats allows for + seamless integration with various image analysis workflows. Registration transformations can be used in subsequent image + analysis steps without requiring additional conversion. +3. Efficiency and Accuracy: Storing transformations within the file format avoids the need for re-sampling each time the data is + processed. This reduces sampling errors and preserves the accuracy of subsequent analyses. Standardization enables on-demand + transformation, critical for the massive volumes collected by modern microscopy techniques. +4. Flexibility in Analysis: A file format that natively supports spatial transformations allows researchers to apply, modify, or + reverse transformations as needed for different analysis purposes. This flexibility is critical for tasks such as + longitudinal studies, multi-modal imaging, and comparative analysis across different subjects or experimental conditions. + +Toward these goals, this RFC expands the set of transformations in the OME-Zarr spec covering many of the use cases +requested in [this github issue](https://github.com/ome/ngff/issues/84). It also adds "coordinate systems" - named +sets of "axes." Related the relationship of discrete arrays to physical coordinates and the interpretation and motivation for +axis types. ## Proposal -Below is a complete copy of the proposed changes including suggestions -from reviewers and contributors of the previously associated [github pull request](https://github.com/ome/ngff/pull/138). -The changes, if approved, shall be translated into bikeshed syntax and added to the ngff repository in a separate PR. -This PR will then comprise complete json schemas when the RFC enters the SPEC phase (see RFC1). +Below is a slightly abridged copy of the proposed changes to the specification (examples are omitted), the full set of changes +including all examples are publicly available on the [github pull request](https://github.com/ome/ngff/pull/138). ### "coordinateSystems" metadata -A "coordinate system" is a collection of "axes" / dimensions with a name. -Every coordinate system: -- MUST contain the field "name". - The value MUST be a non-empty string that is unique among all entries under `coordinateSystems`. +A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: +- MUST contain the field "name". The value MUST be a non-empty string that is unique among `coordinateSystem`s. - MUST contain the field "axes", whose value is an array of valid "axes" (see below). -The order of the `"axes"` list matters -and defines 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). -````{admonition} Example +The order of the `"axes"` list matters and defines the index of each array dimension and coordinates for points in that +coordinate system. The "dimensionality" of a coordinate system +is indicated by the length of its "axes" array. The "volume_micrometers" example coordinate system above is three dimensional (3D). -Coordinate Systems metadata example +The axes of a coordinate system (see below) give information about the types, units, and other properties of the coordinate +system's dimensions. Axis `name`s 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, with identical axes) or can be +transformed to the same coordinate system before doing analysis. See the example below. -```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](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 a list of dictionaries, -where each dictionary 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. -- 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. + +### "axes" metadata + +"axes" describes the dimensions of a coordinate systems. It is a list of dictionaries, where each dictionary 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. +- 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. +- 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. +- 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. +If part of multiscales metadata, 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. +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 of 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. -``` +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**. +### 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. +Every array has a default coordinate system whose parameters need not be explicitly defined. Its name is the path to the array +in the container, its axes have `"type":"array"`, are unitless, and have default "name"s. The ith axis has `"name":"dim_i"` +(these are the same default names used by [xarray](https://docs.xarray.dev/en/stable/user-guide/terminology.html)). -````{admonition} Example -```json -{ - "name": "0", - "axes": [ - {"name": "dim_0", "type": "array"}, - {"name": "dim_1", "type": "array"}, - {"name": "dim_2", "type": "array"} - ] -} -``` -```` -The axes and their order align with the `shape` attribute in the zarr array attributes (in `zarr.json`), -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 dimensionality of each array coordinate system equals the dimensionality of its corresponding zarr array. The axis with +name `"dim_i"` is the ith element of the `"axes"` list. The axes and their order align with the `shape` +attribute in the zarr array attributes (in `.zarray`), 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/v2.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 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. +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. ### "coordinateTransformations" metadata -"coordinateTransformations" describe the mapping between two coordinate systems (defined by "coordinateSystems"). +"coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). For example, to map an array's discrete coordinate system to its corresponding physical coordinates. -Coordinate transforms are in the "forward" direction. -This means they represent functions from *points* in the input space to *points* in the output space -(see [example below](example:coordinate_transformation_scale)). +Coordinate transforms are in the "forward" direction. They represent functions from *points* in the +input space to *points* in the output space. -They: -- MUST contain the field "type" (string). +- MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- MUST contain the field "output" (string), - unless part of a `sequence` or `inverseOf` (see details). -- MUST contain the field "input" (string), - unless part of a `sequence` or `inverseOf` (see details). -- MAY contain the field "name" (string). - Its value MUST be unique across all "name" fields for coordinate transformations. +- MUST contain the field "output", unless part of a `sequence` or `inverseOf` (see details). +- MUST contain the field "input", unless part of a `sequence` or `inverseOf` (see details). +- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. - Parameter values MUST be compatible with input and output space dimensionality (see details). -The following transformations are supported: - -| Type | Fields | Description | -|------|--------|-------------| -| `identity` | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | -| `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | -| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | -| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | -| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | -| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| -| `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | -| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | -| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | -| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijection) for details and examples. | -| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | -| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | - -Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible -(e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation). - -````{admonition} Example -(example:coordinate_transformation_scale)= - -```json -{ - "coordinateSystems": [ - { "name": "in", "axes": [{"name": "j"}, {"name": "i"}] }, - { "name": "out", "axes": [{"name": "y"}, {"name": "x"}] } - ], - "coordinateTransformations": [ - { - "type": "scale", - "scale": [2, 3.12], - "input": "in", - "output": "out" - } - ] -} - -``` - -For example, the scale transformation above defines the function: - -``` -x = 3.12 * i -y = 2 * j -``` + + +
identity + + The identity transformation is the default transformation and is typically not explicitly defined. +
mapAxis + "mapAxis":Dict[String:String] + A maxAxis transformation specifies an axis permutation as a map between axis names. +
translation + one of:
"translation":List[number],
"path":str +
translation vector, stored either as a list of numbers ("translation") or as binary data at a location + in this container (path). +
scale + one of:
"scale":List[number],
"path":str +
scale vector, stored either as a list of numbers (scale) or as binary data at a location in this + container (path). +
affine + one of:
"affine": List[List[number]],
"path":str +
affine transformation matrix stored as a flat array stored either with json uing the affine field + or as binary data at a location in this container (path). If both are present, the binary values at path should be used. +
rotation + one of:
"rotation":List[number],
"path":str +
rotation transformation matrix stored as an array stored either + with json or as binary data at a location in this container (path). + If both are present, the binary parameters at path are used. +
sequence + "transformations":List[Transformation] + A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. +
displacements + "path":str
"interpolation":str +
Displacement field transformation located at (path). +
coordinates + "path":str
"interpolation":str +
Coordinate field transformation located at (path). +
inverseOf + "transform":Transform + The inverse of a transformation. Useful if a transform is not closed-form invertible. See Forward and inverse for details and examples. +
bijection + "forward":Transform
"inverse":Transform +
Explicitly define an invertible transformation by providing a forward transformation and its inverse. +
byDimension + "transformations":List[Transformation] + Define a high dimensional transformation using lower dimensional transformations on subsets of + dimensions. +
typefieldsdescription +
-i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. -```` Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; -- SHOULD parse `mapAxis`, `affine`, `rotation` transformations; -- SHOULD display an informative warning if encountering transformations that cannot be parsed; +- SHOULD parse `mapAxis`, `affine` transformations; - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; -Coordinate transformations can be stored in multiple places to reflect different usecases. - -- Transformations in individual mutliscale datasets represent a special case of transformations - and are explained [below](#multiscales-metadata). -- Additional transformations for single multiscale images MUST be stored under a field `coordinateTransformations` - in the multiscales dictionaries. - This `coordinateTransformations` field MUST contain a list of valid [transformations](#transformation-types). -- Transformations between two or more images MUST be stored in the attributes of a parent zarr group. - For transformations that store data or parameters in a zarr array, - those zarr arrays SHOULD be stored in a zarr group called "coordinateTransformations". +Coordinate transformations from array to physical coordinates MUST be stored in multiscales, +and MUST be duplicated in the attributes of the zarr array. Transformations between different images MUST be stored in the +attributes of a parent zarr group. For transformations that store data or parameters in a zarr array, those zarr arrays SHOULD +be stored in a zarr group `"coordinateTransformations"`.
 store.zarr                      # Root folder of the zarr store
 │
-├── zarr.json                   # coordinate transformations describing the relationship between two image coordinate systems
+├── .zattrs                     # 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.
+│                               # transformations between 'volume' and 'crop' coordinate systems are stored here.
 │
-├── coordinateTransformations   # transformations that use array storage for their parameters should go in a zarr group named "coordinateTransformations".
+├── coordinateTransformations   # transformations that use array storage go in a "coordinateTransformations" zarr group.
 │   └── displacements           # for example, a zarr array containing a displacement field
-│       └── zarr.json
+│       ├── .zattrs
+│       └── .zarray
 │
 ├── 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
+│   ├── .zattrs                 # group level attributes (multiscales)
+│   └── 0                       # a group containing the 0th scale
+│       └── image               # a zarr array
+│           ├── .zattrs         # physical coordinate system and transformations here
+│           └── .zarray         # the array attributes
 └── 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
+    ├── .zattrs                 # group level attributes (multiscales)
+    └── 0                       # a group containing the 0th scale
+        └── image               # a zarr array
+            ├── .zattrs         # physical coordinate system and transformations here
+            └── .zarray         # the array attributes
 
-````{admonition} Example -(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_image1" 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. +### Additional details -```json -"coordinateTransformations": [ - { - "type": "affine", - "path": "coordinateTransformations/sampleA_instrument2-to-instrument1", - "input": "sampleA_instrument2", - "output": "sampleA_instrument1" - } -] -``` +Most coordinate transformations MUST specify their input and output coordinate systems using `input` and `output` with a string value +corresponding to the name of a coordinate system. The coordinate system's name may be the path to an array, and therefore may +not appear in the list of coordinate systems. -And the image at the path `sampleA_instrument1` would have the following as the first coordinate system: +Exceptions are if the the coordinate transformation appears in the `transformations` list of a `sequence` or is the +`transformation` of an `inverseOf` transformation. In these two cases input and output could, in some cases, be omitted (see below for +details). -```json -"coordinateSystems": [ - { - "name": "sampleA-instrument1", - "axes": [ - {"name": "z", "type": "space", "unit": "micrometer"}, - {"name": "y", "type": "space", "unit": "micrometer"}, - {"name": "x", "type": "space", "unit": "micrometer"} - ] - }, -] -``` +Transformations in the `transformations` list of a `byDimensions` transformation MUST provide `input` and `output` as arrays +of strings corresponding to axis names of the parent transformation's input and output coordinate systems (see below for +details). -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"} - ] - } -], +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. For example, the scale transformation above +defines the function: + +``` +x = 0.5 * i +y = 1.2 * j ``` -```` - -#### 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 appears in the `transformations` list of a `sequence` -or is the `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-metadata) 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: + +i.e., the mapping from the first input axis to the first output axis is determined by the first scale parameter. + +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 { @@ -422,224 +316,179 @@ the `inverseOf` transformation type, for example: "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](#transformation-types) 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. +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](#transformation-types) 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 -Two transformation types ([affine](#affine) and [rotation](#rotation)) 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). +Two transformation types ([affine](#affine) and [rotation](#rotation)) are parametrized by matrices. Matrices are applied to +column vectors that represent points in the input coordinate system. The first (last) axis in a coordinate system is the top +(bottom) entry in the column vector. 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). -#### Transformation types -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`. +### Transformation types -##### identity +Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value +of "input" is an array, it's length gives the input dimension, otherwise the length of "axes" for the coordinate +system with the name of the "input" value gives the input dimension. If the value of "input" is an array, it's +length 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 length gives the output dimension, +otherwise it is given by the length of "axes" for the coordinate system with the name of the "output". -`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. +#### identity -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +`identity` transformations map input coordinates to output coordinates without modification. The position of +the ith axis of the output coordinate system is set to the position of the ith axis of the input coordinate +system. `identity` transformations are invertible. -##### mapAxis +#### mapAxis -`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. -`mapAxis` transforms are invertible. +`mapAxis` transformations describe axis permutations as a mapping of axis names. Transformations MUST include a `mapAxis` field +whose value is an object, all of whose values are strings. If the object contains `"x":"i"`, then the transform sets the value +of the output coordinate for axis "x" to the value of the coordinate of input axis "i" (think `x = i`). For every axis in its output coordinate +system, the `mapAxis` MUST have a corresponding field. For every value of the object there MUST be an axis of the input +coordinate system with that name. Note that the order of the keys could be reversed. -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. -##### translation +#### translation -`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 part of a [`sequence`](#sequence) transformation. +`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. 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`. +: 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 list of numbers. -The list MUST have length `N`. - +: The translation parameters stored as a JSON list of numbers. The list MUST have length `N`. -##### scale -`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. +#### scale -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +`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. 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`. +: 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 list of numbers. -The list MUST have length `N`. +: The scale parameters stored as a JSON list of numbers. The list MUST have length `N`. -##### affine -`affine`s are [matrix transformations](#matrix-transformations) 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. +#### affine -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +`affine`s are [matrix transformations](#matrix-transformations) from N-dimensional inputs to M-dimensional outputs 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. 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)`. +: 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 -where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. +: The affine parameters stored in JSON. The matrix MUST be stored as 2D nested array where the outer array MUST be length `M` and the inner arrays MUST be length `N+1`. -##### rotation -`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. -When possible, a rotation transformation SHOULD be preferred to its 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. +#### rotation -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +`rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. When possible, a rotation +transformation SHOULD be preferred to its 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. 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`. +: 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 where the outer array MUST be length `N` -and the inner arrays MUST be length `N`. +: The parameters stored in JSON. The matrix MUST be stored as a 2D nested array where the outer array MUST be length `N` and the inner arrays MUST be length `N`. -##### inverseOf +#### inverseOf -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. +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). ```{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. +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. ``` -##### sequence -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 list 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. +#### sequence + +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 list 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. + +The transformations included in the `transformations` array may omit their `input` and `output` fields under the conditions +outlined below: + +- The `input` and `output` fields MAY be omitted for the following transformation types: + - `identity`, `scale`, `translation`, `rotation`, `affine`, `displacements`, `coordinates` +- The `input` and `output` fields MAY be omitted for `inverseOf` transformations if those fields may be omitted for the + transformation it wraps +- The `input` and `output` fields MAY be omitted for `bijection` transformations if the fields may be omitted for + both its `forward` and `inverse` transformations +- The `input` and `output` fields MAY be omitted for `sequence` transformations if the fields may be omitted for + all transformations in the sequence after flattening the nested sequence lists. +- The `input` and `output` fields MUST be included for transformations of type: `mapAxis`, and `byDimension` (see the note + below), and under all other conditions. -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. -##### coordinates and displacements - -`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`). +#### coordinates and 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 coordinate system"). +`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`). -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +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 coordinate system"). -* 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`. +* 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`. +* 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: +`coordinates` and `displacements` transformations are not invertible in general, but implementations MAY approximate their +inverses. Metadata for these coordinate transforms have the following field:
path
The location of the coordinate array in this (or another) container.
interpolation
-
The interpolation attributes MAY be provided. - It's value indicates the interpolation to use - if transforming points not on the array's discrete grid. +
The interpolation attributes MAY be provided. It's value indicates + the interpolation to use if transforming points not on the array's discrete grid. Values could be:
  • linear (default)
  • @@ -649,16 +498,11 @@ Metadata for these coordinate transforms have the following fields:
-For both `coordinates` and `displacements`, -the array data at referred to by `path` MUST define coordinate system -and coordinate transform metadata: +For both `coordinates` and `displacements`, the array data at referred to by `path` MUST define coordinate system and coordinate transform metadata: -* Every axis name in the `coordinateTransform`'s `input` - MUST appear in the coordinate system. -* The array dimension corresponding to the `coordinate` or `displacement` axis - MUST have length equal to the number of dimensions of the `coordinateTransform` `output` -* If the input coordinate system `N` axes, - then the array data at `path` MUST have `(N + 1)` dimensions. +* Every axis name in the `coordinateTransform`'s `input` MUST appear in the coordinate system +* The array dimension corresponding to the `coordinate` or `displacement` axis MUST have length equal to the number of dimensions of the `coordinateTransform` `output` +* If the input coordinate system `N` axes, then the array data at `path` MUST have `(N + 1)` dimensions. * SHOULD have a `name` identical to the `name` of the corresponding `coordinateTransform`. For `coordinates`: @@ -673,220 +517,91 @@ For `displacements`: * `input` and `output` MUST have an equal number of dimensions. -##### byDimension +#### byDimension -`byDimension` transformations build a high dimensional transformation -using lower dimensional transformations on subsets of dimensions. -The `input` and `output` fields MUST always be included for this transformations type. +`byDimension` transformations build a high dimensional transformation using lower dimensional transformations +on subsets of dimensions.
transformations
-
Each child transformation MUST contain input_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. +
A list of transformations, each of which applies to a (non-strict) subset of input and output dimensions (axes). + The values of input and output fields MUST be an array of strings. + Every axis name in input 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 MUST appear in exactly one of its child transformations' output.
-##### bijection - -A bijection transformation is an invertible transformation in -which both the `forward` and `inverse` transformations are explicitly defined. -Each direction SHOULD be a transformation type that is not closed-form invertible. -Its input and output spaces MUST have equal dimension. -The input and output dimensions for the both the forward and inverse transformations -MUST match bijection's input and output space dimensions. - -`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, -in which case the `forward` transformation's `input` and `output` are understood to match the bijection's, -and the `inverse` transformation's `input` (`output`) matches the bijection's `output` (`input`), -see the example below. - -The `input` and `output` fields MAY be omitted for `bijection` transformations -if the fields may be omitted for both its `forward` and `inverse` transformations - -Practically, non-invertible transformations have finite extents, -so bijection transforms should only be expected to be correct / consistent for points that fall within those extents. -It may not be correct for any point of appropriate dimensionality. - -### "multiscales" metadata - -Metadata about an image can be found under the `multiscales` key in the group-level OME-Zarr Metadata. -Here, "image" refers to 2 to 5 dimensional data representing image -or volumetric data with optional time or channel axes. -It is stored in a multiple resolution representation. - -`multiscales` contains a list of dictionaries where each entry describes a multiscale image. - -Each `multiscales` dictionary MUST contain the field "coordinateSystems", -whose value is an array containing valid coordinate system metadata -(see [coordinate systems](#coordinatesystems-metadata)). -The last entry of this array is the "intrinsic" coordinate system -and MUST contain axis information pertaining to physical coordinates. -It should be used for viewing and processing unless a use case dictates otherwise. -It will generally be a representation of the image in its native physical coordinate system. - -The following MUST hold for all coordinate systems inside multiscales metadata. -The length of "axes" must be between 2 and 5 -and MUST be equal to the dimensionality of the Zarr arrays storing the image data (see "datasets:path"). -The "axes" MUST contain 2 or 3 entries of "type:space" -and MAY contain one additional entry of "type:time" -and MAY contain one additional entry of "type:channel" or a null / custom type. -In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), -followed by the "channel" or custom axis (if present) and the axes of type "space". -If there are three spatial axes where two correspond to the image plane ("yx") -and images are stacked along the other (anisotropic) axis ("z"), -the spatial axes SHOULD be ordered as "zyx". - -Each `multiscales` dictionary MUST contain the field `datasets`, -which is a list of dictionaries describing the arrays storing the individual resolution levels. -Each dictionary in `datasets` MUST contain the field `path`, -whose value is a string containing the path to the Zarr array for this resolution relative to the current Zarr group. -The `path`s MUST be ordered from largest (i.e. highest resolution) to smallest. -Every Zarr array referred to by a `path` MUST have the same number of dimensions -and MUST NOT have more than 5 dimensions. -The number of dimensions and order MUST correspond to number and order of `axes`. - -Each dictionary in `datasets` MUST contain the field `coordinateTransformations`, -whose value is a list of dictionaries that define a transformation -that maps Zarr array coordinates for this resolution level to the "intrinsic" coordinate system -(the last entry of the `coordinateSystems` array). -The transformation is defined according to [transformations metadata](#transformation-types). -The transformation MUST take as input points in the array coordinate system -corresponding to the Zarr array at location `path`. -The value of "input" MUST equal the value of `path`, -implementations should always treat the value of `input` as if it were equal to the value of `path`. -The value of the transformation’s `output` MUST be the name of the "intrinsic" [coordinate system](#coordinatesystems-metadata). - -This transformation MUST be one of the following: - -* A single scale or identity transformation -* A sequence transformation containing one scale and one translation transformation. - -In these cases, the scale transformation specifies the pixel size in physical units or time duration. -If scaling information is not available or applicable for one of the axes, -the value MUST express the scaling factor between the current resolution -and the first resolution for the given axis, -defaulting to 1.0 if there is no downsampling along the axis. -This is strongly recommended -so that the the "intrinsic" coordinate system of the image avoids more complex transformations. - -If applications require additional transformations, -each `multiscales` dictionary MAY contain the field `coordinateTransformations`, -describing transformations that are applied to all resolution levels in the same manner. -The value of `input` MUST equal the name of the "intrinsic" coordinate system. -The value of `output` MUST be the name of the output coordinate System -which is different from the "intrinsic" coordinate system. - -Each `multiscales` dictionary SHOULD contain the field `name`. - -Each `multiscales` dictionary SHOULD contain the field `type`, -which gives the type of downscaling method used to generate the multiscale image pyramid. -It SHOULD contain the field "metadata", -which contains a dictionary with additional information about the downscaling method. - -````{admonition} Example - -A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution levels could look like this: -```json +#### bijection + +A bijection transformation is an invertible transformation in which both the `forward` and `inverse` transformations +are explicitly defined. Each direction SHOULD be a transformation type that is not closed-form invertible. +Its' input and output spaces MUST have equal dimension. The input and output dimensions for the both the forward +and inverse transformations MUST match bijection's input and output space dimensions. + +`input` and `output` fields MAY be omitted for the `forward` and `inverse` transformations, in which case +the `forward` transformation's `input` and `output` are understood to match the bijection's, and the `inverse` +transformation's `input` (`output`) matches the bijection's `output` (`input`), see the example below. + +Practically, non-invertible transformations have finite extents, so bijection transforms should only be expected +to be correct / consistent for points that fall within those extents. It may not be correct for any point of +appropriate dimensionality. + +## Specific feedback requested + +We ask the reviewers for one specific piece of feedback. Specifically about whether parameters for transformations should +be written as they are currently in the draft pull request, with named parameters at the "top level" e.g.: + +``` { - "zarr_format": 3, - "node_type": "group", - "attributes": { - "ome": { - "version": "0.5", - "multiscales": [ - { - "name": "example", - "coordinateSystems": [ - { - "name": "intrinsic", - "axes": [ - { "name": "t", "type": "time", "unit": "millisecond" }, - { "name": "c", "type": "channel" }, - { "name": "z", "type": "space", "unit": "micrometer" }, - { "name": "y", "type": "space", "unit": "micrometer" }, - { "name": "x", "type": "space", "unit": "micrometer" } - ] - } - ], - "datasets": [ - { - "path": "0", - "coordinateTransformations": [ - { - // the voxel size for the first scale level (0.5 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 0.5, 0.5, 0.5], - "input": "0", - "output": "intrinsic" - } - ] - }, - { - "path": "1", - "coordinateTransformations": [ - { - // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 1.0, 1.0, 1.0], - "input": "1", - "output": "intrinsic" - } - ] - }, - { - "path": "2", - "coordinateTransformations": [ - { - // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) - // and the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [1.0, 1.0, 2.0, 2.0, 2.0], - "input": "2", - "output": "intrinsic" - } - ] - } - ], - "type": "gaussian", - "metadata": { - "description": "the fields in metadata depend on the downscaling implementation. Here, the parameters passed to the skimage function are given", - "method": "skimage.transform.pyramid_gaussian", - "version": "0.16.1", - "args": "[true]", - "kwargs": { "multichannel": true } - } - } - ] - } - } + "type": "affine", + "affine": [[1, 2, 3], [4, 5, 6]], + "input": "ji", + "output": "yx" } ``` -```` - -If only one multiscale is provided, use it. -Otherwise, the user can choose by name, -using the first multiscale as a fallback: - -```python -datasets = [] -for named in multiscales: - if named["name"] == "3D": - datasets = [x["path"] for x in named["datasets"]] - break -if not datasets: - # Use the first by default. Or perhaps choose based on chunk size. - datasets = [x["path"] for x in multiscales[0]["datasets"]] + +or alternatively in a `parameters` field: + +``` +{ + "type": "affine", + "parameters": { + "matrix": [[1, 2, 3], [4, 5, 6]] + }, + "input": "ji", + "output": "yx" +} ``` +In discussions, some authors preferred the latter because it will make the "top-level" keys for transformation +objects all identical, which could make serialization / validation simpler. One downside is that this change +is breaking for the existing `scale` and `translation` transformations + +``` +{ + "type": "scale", + "scale": [2, 3], + "input": "ji", + "output": "yx" +} +``` + +would change to: + +``` +{ + "type": "scale", + "parameters": { + "scale": [2, 3], + }, + "input": "ji", + "output": "yx" +} +``` + +The authors would be interested to hear perspectives from the reviewers on this matter. + ## Requirements @@ -924,19 +639,16 @@ issues or unknown unknowns prior to writing any real code. ## Drawbacks, risks, alternatives, and unknowns -Adopting this proposal will add an implementation burden because it adds more transformation types. -Though this drawback is softened by the fact that implementations -will be able to choose which transformations to support -(e.g., implementations may choose not to support non-linear transformations). +Adopting this proposal will add an implementation burden because it adds more transformation types. Though this drawback is +softened by the fact that implementations will be able to choose which transformations to support (e.g., implementations may choose +not to support non-linear transformations). -An alternative to this proposal would be not to add support transformations directly -and instead recommend software use an existing format (e.g., ITK's). -The downside of that is that alternative formats will not integrate well with OME-NGFF -as they do not use JSON or Zarr. +An alternative to this proposal would be not to add support transformations directly and instead recommend software use an +existing format (e.g., ITK's). The downside of that is that alternative formats will not integrate well with OME-NGFF as they do +not use JSON or Zarr. -In all, we believe the benefits of this proposal (outlined in the Background section) -far outweigh these drawbacks, -and will better promote software interoperability than alternatives. +In all, we believe the benefits of this proposal (outlined in the Background section) far outweigh these drawbacks, and will +better promote software interoperability than alternatives. ## Prior art and references @@ -992,7 +704,7 @@ Adds coordinate systems, these contain axes which are backward-compatible with t ## Testing -Public examples of transformations with expected input/output pairs are provided [here](https://github.com/bogovicj/ngff-rfc5-coordinate-transformation-examples/releases/tag/0.6-dev1-rev1) +Public examples of transformations with expected input/output pairs will be provided. ## UI/UX @@ -1001,8 +713,8 @@ non-linear transformations), and inform users what action will be taken. The det application dependent, but ignoring the unsupported transformation or falling back to a simpler transformation are likely to be common choices. -Implementations SHOULD communicate if and when an image can be displayed in multiple coordinate systems. Users might -choose between different options, or software could choose a default (e.g. the first or last listed coordinate system). The +Implementations MAY choose to communicate if and when an image can be displayed in multiple coordinate systems. Users might +choose between different options, or software could choose a default (e.g. the first listed coordinate system). The [`multiscales` in version 0.4](https://ngff.openmicroscopy.org/0.4/#multiscale-md) has a similar consideration. @@ -1010,4 +722,4 @@ choose between different options, or software could choose a default (e.g. the f | Date | Description | Link | | ---------- | ---------------------------- | ---------------------------------------------------------------------------- | -| 2024-10-08 | RFC assigned and published | [https://github.com/ome/ngff/pull/255](https://github.com/ome/ngff/pull/255) | \ No newline at end of file +| 2024-10-08 | RFC assigned and published | [https://github.com/ome/ngff/pull/255](https://github.com/ome/ngff/pull/255) | From 6174a5eaa2cacea83ba084e142051ac976495866 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:41:25 +0100 Subject: [PATCH 102/115] Update rfc/5/index.md Co-authored-by: David Stansby --- rfc/5/index.md | 1 - 1 file changed, 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 77470653..36bb2b60 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -484,7 +484,6 @@ Each integer in the array MUST be a valid zero-based index into the input coordi 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. -`mapAxis` transforms are invertible. The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. From f42eee002e61cdcb5077ed017778df8850bbd0e4 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:15:05 +0100 Subject: [PATCH 103/115] use "zarr array" instead of "binary data" --- rfc/5/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 36bb2b60..241e613f 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -239,10 +239,10 @@ The following transformations are supported: |------|--------|-------------| | `identity` | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | | `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | -| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as binary data at a location in this container (`path`). | -| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as binary data at a location in this container (`path`). | -| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as binary data at a location in this container (`path`). | -| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as binary data at a location in this container (`path`).| +| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as a zarr array at a location in this container (`path`). | +| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as a zarr array at a location in this container (`path`). | +| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as a zarr array at a location in this container (`path`). | +| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as a zarr array at a location in this container (`path`).| | `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | | `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | | `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | From 8237047f67759fff8a4276f3669d5de54be610ca Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 00:50:51 +0100 Subject: [PATCH 104/115] add hint --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 241e613f..366d41f2 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -132,7 +132,7 @@ where each dictionary describes a dimension (axis) and: 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. + 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. From f3ab3573076264e50ef3c3b056186d609a6276a1 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:02:43 +0100 Subject: [PATCH 105/115] added cross-references to trafo details in tabular view --- rfc/5/index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 366d41f2..9637fd11 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -237,18 +237,18 @@ The following transformations are supported: | Type | Fields | Description | |------|--------|-------------| -| `identity` | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | -| `mapAxis` | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | -| `translation` | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as a zarr array at a location in this container (`path`). | -| `scale` | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as a zarr array at a location in this container (`path`). | -| `affine` | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as a zarr array at a location in this container (`path`). | -| `rotation` | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as a zarr array at a location in this container (`path`).| -| `sequence` | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | -| `displacements` | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | -| `coordinates` | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | -| `inverseOf` | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijection) for details and examples. | -| `bijection` | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | -| `byDimension` | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | +| [`identity`](#identity) | | The identity transformation is the do-nothing transformation and is typically not explicitly defined. | +| [`mapAxis`](#mapaxis) | `"mapAxis":List[number]` | an axis permutation as a transpose array of integer indices that refer to the ordering of the axes in the respective coordinate system. | +| [`translation`](#translation) | one of:
`"translation":List[number]`,
`"path":str` | Translation vector, stored either as a list of numbers (`"translation"`) or as a zarr array at a location in this container (`path`). | +| [`scale`](#scale) | one of:
`"scale":List[number]`,
`"path":str` | Scale vector, stored either as a list of numbers (`scale`) or as a zarr array at a location in this container (`path`). | +| [`affine`](#affine) | one of:
`"affine":List[List[number]]`,
`"path":str` | 2D affine transformation matrix stored either with JSON (`affine`) or as a zarr array at a location in this container (`path`). | +| [`rotation`](#rotation) | one of:
`"rotation":List[List[number]]`,
`"path":str` | 2D rotation transformation matrix stored as an array stored either with json (`rotation`) or as a zarr array at a location in this container (`path`).| +| [`sequence`](#sequence) | `"transformations":List[Transformation]` | sequence of transformations. Applying the sequence applies the composition of all transforms in the list, in order. | +| [`displacements`](#coordinates-and-displacements) | `"path":str`
`"interpolation":str` | Displacement field transformation located at `path`. | +| [`coordinates`](#coordinates-and-displacements) | `"path":str`
`"interpolation":str` | Coordinate field transformation located at `path`. | +| [`inverseOf`](#inverseof) | `"transformation":Transformation` | The inverse of a transformation. Useful if a transform is not closed-form invertible. See forward and inverse of [bijections](#bijection) for details and examples. | +| [`bijection`](#bijection) | `"forward":Transformation`
`"inverse":Transformation` | An invertible transformation providing an explicit forward transformation and its inverse. | +| [`byDimension`](#bydimension) | `"transformations":List[Transformation]`,
`"input_axes": List[str]`,
`"output_axes": List[str]` | A high dimensional transformation using lower dimensional transformations on subsets of dimensions. | Implementations SHOULD prefer to store transformations as a sequence of less expressive transformations where possible (e.g., sequence[translation, rotation], instead of affine transformation with translation/rotation). From a8ad5fd243fce455a4fad741b441673ab9c22dcf Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 01:06:11 +0100 Subject: [PATCH 106/115] clarify nesting convention Co-Authored-By: Davis Bennett --- rfc/5/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 9637fd11..788f577a 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -542,7 +542,7 @@ 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 +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`. ##### rotation @@ -563,7 +563,7 @@ 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 where the outer array MUST be length `N` +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`. From 7f93e2c783dff1a03f3a6233f200cd09716f58db Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:06:30 +0100 Subject: [PATCH 107/115] correct scale values --- rfc/5/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 788f577a..516a3c7d 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -831,7 +831,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l // the voxel size for the first scale level (0.5 micrometer) // and the time unit (0.1 milliseconds), which is the same for each scale level "type": "scale", - "scale": [1.0, 1.0, 0.5, 0.5, 0.5], + "scale": [0.1, 1.0, 0.5, 0.5, 0.5], "input": "0", "output": "intrinsic" } @@ -844,7 +844,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) // and the time unit (0.1 milliseconds), which is the same for each scale level "type": "scale", - "scale": [1.0, 1.0, 1.0, 1.0, 1.0], + "scale": [0.1, 1.0, 1.0, 1.0, 1.0], "input": "1", "output": "intrinsic" } @@ -857,7 +857,7 @@ A complete example of json-file for a 5D (TCZYX) multiscales with 3 resolution l // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) // and the time unit (0.1 milliseconds), which is the same for each scale level "type": "scale", - "scale": [1.0, 1.0, 2.0, 2.0, 2.0], + "scale": [0.1, 1.0, 2.0, 2.0, 2.0], "input": "2", "output": "intrinsic" } From 72685be0358bd0e5f916d3ad2c2c83285d4f6430 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:14:37 +0100 Subject: [PATCH 108/115] Apply suggestions from code review Co-authored-by: Davis Bennett --- rfc/5/index.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 516a3c7d..7b2251bf 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -79,14 +79,13 @@ This PR will then comprise complete json schemas when the RFC enters the SPEC ph ### "coordinateSystems" metadata -A "coordinate system" is a collection of "axes" / dimensions with a name. +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 order of the `"axes"` list matters -and defines the index of each array dimension and coordinates for points in that coordinate system. +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). @@ -188,7 +187,7 @@ As with all coordinate systems, the `dimension_names` must be unique and non-nul ``` ```` -The axes and their order align with the `shape` attribute in the zarr array attributes (in `zarr.json`), +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. @@ -293,7 +292,7 @@ Conforming readers: Coordinate transformations can be stored in multiple places to reflect different usecases. -- Transformations in individual mutliscale datasets represent a special case of transformations +- Transformations in individual multiscale datasets represent a special case of transformations and are explained [below](#multiscales-metadata). - Additional transformations for single multiscale images MUST be stored under a field `coordinateTransformations` in the multiscales dictionaries. @@ -646,7 +645,7 @@ Metadata for these coordinate transforms have the following fields:
The location of the coordinate array in this (or another) container.
interpolation
The interpolation attributes MAY be provided. - It's value indicates the interpolation to use + Its value indicates the interpolation to use if transforming points not on the array's discrete grid. Values could be:
    @@ -731,7 +730,7 @@ It is stored in a multiple resolution representation. `multiscales` contains a list of dictionaries where each entry describes a multiscale image. Each `multiscales` dictionary MUST contain the field "coordinateSystems", -whose value is an array containing valid coordinate system metadata +whose value is an array containing coordinate system metadata (see [coordinate systems](#coordinatesystems-metadata)). The last entry of this array is the "intrinsic" coordinate system and MUST contain axis information pertaining to physical coordinates. From dd10ff69e4a388c5836e48bfbfe014bbbe766762 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:20:23 +0100 Subject: [PATCH 109/115] extend recommendation to readers and viewers --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 516a3c7d..f8f3c587 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -287,7 +287,7 @@ i.e., the mapping from the first input axis to the first output axis is determin Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; - SHOULD parse `mapAxis`, `affine`, `rotation` transformations; -- SHOULD display an informative warning if encountering transformations that cannot be parsed; +- SHOULD display an informative warning if encountering transformations that cannot be parsed or displayed; - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; From 1687c6ebfd37f8c17a94b1e1a640e69c1d1f60e3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:21:36 +0100 Subject: [PATCH 110/115] text style --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index ee332b24..fce31d80 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -286,7 +286,7 @@ i.e., the mapping from the first input axis to the first output axis is determin Conforming readers: - MUST parse `identity`, `scale`, `translation` transformations; - SHOULD parse `mapAxis`, `affine`, `rotation` transformations; -- SHOULD display an informative warning if encountering transformations that cannot be parsed or displayed; +- SHOULD display an informative warning if encountering transformations that cannot be parsed or displayed by a viewer; - SHOULD be able to apply transformations to points; - SHOULD be able to apply transformations to images; From e02ba891cc81fb4766fe0432486dbccb90c10492 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:29:28 +0100 Subject: [PATCH 111/115] Broaden statement about inclusivity of `input`/`output` fields --- rfc/5/index.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index fce31d80..1a2c9180 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -395,8 +395,9 @@ The image at path `sampleA_instrument2` would have this as the first listed coor 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 appears in the `transformations` list of a `sequence` -or is the `transformation` of an `inverseOf` transformation. +Exceptions are if the coordinate transformation is wrapped in another transformation, +e.g. as part of a `transformations` list 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. @@ -469,7 +470,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). ##### mapAxis @@ -484,7 +486,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). ##### translation @@ -494,7 +497,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). path : The path to a zarr-array containing the translation parameters. @@ -504,7 +508,6 @@ The array at this path MUST be 1D, and its length MUST be `N`. : The translation parameters stored as a JSON list of numbers. The list MUST have length `N`. - ##### scale `scale` transformations are special cases of affine transformations. @@ -514,7 +517,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). path : The path to a zarr-array containing the scale parameters. @@ -533,7 +537,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). path : The path to a zarr-array containing the affine parameters. @@ -554,7 +559,8 @@ 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 part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). path : The path to an array containing the affine parameters. @@ -621,7 +627,8 @@ These transformation types refer to an array at location specified by the `"path 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 coordinate system"). -The `input` and `output` fields MAY be omitted if part of a [`sequence`](#sequence) transformation. +The `input` and `output` fields MAY be omitted if wrapped in another transformation that provides `input`/`output` +(e.g., [`sequence`](#sequence), [`inverseOf`](#inverseof), ['byDimension](#bydimension) or [`bijection`](#bijection)). * If the input coordinate system has `N` axes, the array at location path MUST have `N+1` dimensions From 2962c7d76dc5d3408465c1b7ed0a12835b3e55a8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:34:33 +0100 Subject: [PATCH 112/115] expand arrayCoordinateSystem example --- rfc/5/index.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 1a2c9180..d79cf143 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -177,14 +177,30 @@ As with all coordinate systems, the `dimension_names` must be unique and non-nul ````{admonition} Example ```json { - "name": "0", - "axes": [ - {"name": "dim_0", "type": "array"}, - {"name": "dim_1", "type": "array"}, - {"name": "dim_2", "type": "array"} + "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, From dd082e67f3cdf82ac0e0f8361ba87688df3b1ac3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:37:06 +0100 Subject: [PATCH 113/115] typo --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index d79cf143..375f18af 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -172,7 +172,7 @@ The dimensionality of each array coordinate system equals the dimensionality of 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. +As with all coordinate systems, the dimension names must be unique and non-null. ````{admonition} Example ```json From b7d9fae2be69cf860cbb6b34723a076f7bc48a40 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:06:33 +0100 Subject: [PATCH 114/115] Update rfc/5/index.md Co-authored-by: Davis Bennett --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index d79cf143..79d81fe1 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -568,7 +568,7 @@ where the outer array MUST be length `M` and the inner arrays MUST be length `N+ ##### rotation `rotation`s are [matrix transformations](#matrix-transformations) that are special cases of affine transformations. -When possible, a rotation transformation SHOULD be preferred to its equivalent affine. +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. From e87d0c4136a8792c9ce32df84aeb894465834776 Mon Sep 17 00:00:00 2001 From: Josh Moore Date: Fri, 31 Oct 2025 14:58:43 +0100 Subject: [PATCH 115/115] Update rfc/5/index.md --- rfc/5/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfc/5/index.md b/rfc/5/index.md index 67af280a..644ff879 100644 --- a/rfc/5/index.md +++ b/rfc/5/index.md @@ -9,7 +9,7 @@ responses/index versions/index ``` -Add named coordinate systems and expand and clarify coordinate transformations. This document represent the updated proposal following the [original RFC5 proposal](./versions/1/index.md) and incorporates feedback from reviewers and implementers. +Add named coordinate systems and expand and clarify coordinate transformations. This document represents the updated proposal following the [original RFC5 proposal](./versions/1/index.md) and incorporates feedback from reviewers and implementers. ## Status