From f0da62ad36bc138c4392df0ef19eb14fbdbcf0cb Mon Sep 17 00:00:00 2001 From: bogovicj Date: Tue, 1 Feb 2022 13:50:56 -0500 Subject: [PATCH 01/50] start of named space and transform metadata --- latest/index.bs | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 55789a21..1e751202 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -212,9 +212,10 @@ keys as specified below for discovering certain types of data, especially images "axes" metadata {#axes-md} -------------------------- -"axes" describes the dimensions of a physical coordinate space. It is a list of dictionaries, where each dictionary describes a dimension (axis) and: +"axes" describes the dimensions of a coordinate space. 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 "space", "time" or "channel", but MAY take other 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' @@ -222,11 +223,47 @@ keys as specified below for discovering certain types of data, especially images If part of [[#multiscale-md]], the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. +"space" metadata {#space-md} +-------------------------- + +A "space" is a collection of dimensions / "axes" with a name. Each space dictionary: +- MUST contain the field "name", that gives the space name. The values MUST be non-empty and unique. +- MUST contain the field "axes", whose value is a valid set of "axes". +- SHOULD be such that the "type" of all "axes" in the list are identical. + +``` +{ + "name" : "volume_micrometers", + "axes" : [ + {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "y", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"} + ] +} +``` + +### "Array space" + +"Array space" is the unique space whose name is the empty string. The array data of every dataset is in "Array +space." Therefore, the dimensionality of "array space" varies according to its corresponding dataset. +In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_space" is the empty string, i.e. +"array space." + + "transformations" metadata {#trafo-md} ------------------------------------- "transformations" describes a series of transformations, e.g. to map discrete data space of an array to the corresponding physical space. -It is a list of dictionaries. Each entry describes a single transformation and MUST contain the field "type". +It is a list of dictionaries. Each transformation dictionary: + +- MUST contain the field "type". +- MUST contain any other fields required by the given "type" (see table below). +- MUST contain the field "name". +- SHOULD require the field "input_space". +- MUST require the field "output_space". +- the value of "output_space" SHOULD not be the empty string. + + The value of "type" MUST be one of the elements of the `type` column in the table below. Additional fields for the entry depend on "type" and are defined by the column `fields`. @@ -236,9 +273,6 @@ Additional fields for the entry depend on "type" and are defined by the column ` | `translation` | one of: `"translation":List[float]`, `"path":str` | translation vector, stored either as a list of floats (`"translation"`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | | `scale` | one of: `"scale":List[float]`, `"path":str` | scale vector, stored either as a list of floats (`scale`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | -In addition, the field "axisIndices" MAY be given to specify the subset of axes that the transformation is applied to, leaving other axes unchanged. If not given, the transformation is applied to all axes. The length of "axisIndices" MUST be equal to the dimensionality of the transformation. If "axisIndices" are not given, the dimensionality of the transformation MUST be equal to the number of dimensions of the space that the transformation is applied to. -If given, "axisIndices" MUST be given in increasing order. It uses zero-based indexing. - The transformations in the list are applied sequentally and in order. From 4331deba9e7fd48ab4c974b78ead2b639a3c5293 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Wed, 9 Feb 2022 09:06:58 -0500 Subject: [PATCH 02/50] fwd and inv coordinate transforms --- latest/index.bs | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 1e751202..e9409002 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -16,6 +16,7 @@ Markup Shorthands: markdown yes Editor: Josh Moore, Open Microscopy Environment (OME) https://www.openmicroscopy.org Editor: Sébastien Besson, Open Microscopy Environment (OME) https://www.openmicroscopy.org Editor: Constantin Pape, European Molecular Biology Laboratory (EMBL) https://www.embl.org/sites/heidelberg/ +Editor: John Bogovic, Hughes Medical Institute Janelia (HHMI) https://www.janelia.org/ Abstract: This document contains next-generation file format (NGFF) Abstract: specifications for storing bioimaging data in the cloud. Abstract: All specifications are submitted to the https://image.sc community for review. @@ -256,11 +257,11 @@ In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_sp "transformations" describes a series of transformations, e.g. to map discrete data space of an array to the corresponding physical space. It is a list of dictionaries. Each transformation dictionary: +- MUST contain the field "name". - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- MUST contain the field "name". -- SHOULD require the field "input_space". -- MUST require the field "output_space". +- SHOULD contain the field "input_space". +- MUST contain the field "output_space". - the value of "output_space" SHOULD not be the empty string. @@ -275,6 +276,44 @@ Additional fields for the entry depend on "type" and are defined by the column ` The transformations in the list are applied sequentally and in order. +### Forward and inverse coordate transformations + +Specified coordinate transforms are in the "forward" direction. They represent functions +from *points* in the input space to *points* in the output space. For example, the transformation `"ij2xy"` + +```json +{ + "name": "ij2xy", + "type": "scale", + "scale": [2, 0.5] + "input_axes" : ["i", "j"] + "output_axes" : ["x", "y"] +} +``` + +represents the function: + +``` +x = 2 * i +y = 0.5 * j +``` + +When rendering images and interpolating, implementations may need the "inverse" transformation - from output to +input space. 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 should be specified using the `inverse` +transformation type, for example: + +```json +{ + "name": "nonlinear-inverse", + "type": "displacement_field", + "path": "/path/to/my/transform", + "input_axes" : ["i", "j"], + "output_axes" : ["x", "y"], + "inverse" : true +} +``` + "multiscales" metadata {#multiscale-md} --------------------------------------- From 180568bbcd8850f61c4b1ce406b38d99ef3ceb9d Mon Sep 17 00:00:00 2001 From: bogovicj Date: Wed, 9 Feb 2022 14:20:42 -0500 Subject: [PATCH 03/50] more transform details * define pixel center coordinate system * add input/output_axes * add transform inverse flag * add affine transform type --- latest/index.bs | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 87be4412..8dfa46fa 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -250,9 +250,20 @@ A "space" is a collection of dimensions / "axes" with a name. Each space dictio } ``` +### Pixel coordinate convention + +**The pixel center is the origin of the continuous coordinate system.** + +It is vital to consistently define relationship between the discrete/arrray and continuous/interpolated +coordinate systems. A pixel is the continuous region (rectangle) that corresponds to a single sample +in the discrete array, i.e., the area corresponding to nearest-neighbor interpolation of that sample. +The center of the pixel corresponding to the discrete origin `(0,0)` is the origin of the continuous space +`(0.0, 0.0)` when the transformation is the identity. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. + + ### "Array space" -"Array space" is the unique space whose name is the empty string. The array data of every dataset is in "Array +"Array space" is the unique space whose name is the empty string (`""`) or `null`. The array data of every dataset is in "Array space." Therefore, the dimensionality of "array space" varies according to its corresponding dataset. In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_space" is the empty string, i.e. "array space." @@ -265,17 +276,19 @@ In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_sp For example, to map a discrete data space of an array to the corresponding physical space. It is a list of dictionaries. Each entry describes a single transformation, and -- MUST contain the field "name". +- MUST contain the field "name". The values MUST be unique across all "name" fields for coordinate transformations. - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- SHOULD contain the field "input_space". -- MUST contain the field "output_space". +- SHOULD contain exactly one of the fields "input_space" or "input_axes". +- MUST contain exactly one of the fields "output_space" or "output_axes". +- OPTIONAL contain the field "inverse". - the value of "output_space" SHOULD not be the empty string.
`identity` identity transformation, is the default transformation and is typically not explicitly defined -
`translation` one of: `"translation":List[float]`, `"path":str` translation vector, stored either as a list of floats (`"translation"`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | -
`scale` one of: `"scale":List[float]`, `"path":str` scale vector, stored either as a list of floats (`scale`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | +
`translation` one of:
`"translation":List[float]`,
`"path":str`
translation vector, stored either as a list of floats (`"translation"`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | +
`scale` one of:
`"scale":List[float]`,
`"path":str`
scale vector, stored either as a list of floats (`scale`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. +
`affine` one of:
`"affine":List[float]`,
`"path":str`
affine transformation matrix defined as list consisting of n sets of n + 1 scalar numbers, stored either as a number[] (affine) or as binary data at a location in this container (path). If both are present, path is preferred. n is number of dimensions
typefieldsdescription
@@ -997,6 +1010,20 @@ Version History {#history} "et al" ], "date": "06 October 2020" + }, + "itk":{ + "id": "itk-book", + "href": "https://itk.org/ItkSoftwareGuide.pdf", + "title": "The ITK Software Guide", + "status": "Informational", + "publisher": "ITK", + "authors": [ + "Hans J. Johnson", + "Matthew M. McCormick", + "Luis Ibanez", + "Insight Software Consortium" + ], + "date": "16 April 2021" } } From 4f4836400103d4e1e59775be76532ac638a340af Mon Sep 17 00:00:00 2001 From: bogovicj Date: Wed, 9 Feb 2022 14:21:47 -0500 Subject: [PATCH 04/50] start page on details and recommendations --- latest/transform-details.bs | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 latest/transform-details.bs diff --git a/latest/transform-details.bs b/latest/transform-details.bs new file mode 100644 index 00000000..b2b744c8 --- /dev/null +++ b/latest/transform-details.bs @@ -0,0 +1,106 @@ + + +Coordinates and Axes {#coords-axes} +===================== + +OME-NGFF datasets are arrays that hold values. The arrays may be indexed by discrete (integer) +coordinates in order to obtain a corresponding value. If values are desired at continuous (real-valued) +coordinates, then interpolation is required. + +Interpolation {#interp} +--------------------- + +Interpolation is the process of producing values at continuous coordinates from data sampled at discrete +coordinates. "Nearest-neighbor" and "N-Linear" are the two most commonly used interpolation methods. + + +Pixel coordinates {#pix-coords} +--------------------- + +**The pixel center is the origin of the continuous coordinate system.** + +### Top-left convention + +A common alternative convention is for the origin in the continuous space is at the "top-left" of the pixel. +This is not recommended, but can be acheived by explicitly adding a half-pixel translation, for example: + +```json +{ + "name": "center_to_top-left", + "type": "translation", + "translation" : [0.5, 0.5], + "output_space" : "top-left-space" +} +``` + +Coordinate Transformations {#coord-tforms} +===================== + +This document describes background and motivation that is outside the NGFF specification. + + +Direction {#direction} +--------------------- + +Specified coordinate transforms are in the "forward" direction. They represent functions +from *points* in the input space to *points* in the output space. For example, the transformation `"ij2xy"` + + +```json +{ + "name": "ij2xy", + "type": "scale", + "scale": [2, 0.5] + "input_axes" : ["i", "j"] + "output_axes" : ["x", "y"] +} +``` + +representes the function + +``` +x = 2 * i +y = 0.5 * j +``` + + +Recommendations {#recommendations} +===================== + + +"Native" physical space +--------------------- + +Datasets SHOULD define a transformation from array space to their "native physical space." +This transformation SHOULD describe physical pixel spacing and origin only, and therefore SHOULD consist of +`scale` and/or `translation` types only. + +Subsequent reorientation / registration transformations SHOULD use this native space as their `input_space`, +i.e., transformations should be defined in physical coordinates. + + From 98a40debe19c09e750168682e862e4cfff6158fd Mon Sep 17 00:00:00 2001 From: bogovicj Date: Thu, 5 May 2022 17:27:12 -0400 Subject: [PATCH 05/50] transformation progress * flesh out array space * define array indexing * add more transformation types * start transformation details section and examples * update example --- latest/index.bs | 149 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 33 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 8dfa46fa..c953ec2d 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -234,39 +234,61 @@ If part of [[#multiscale-md]], the length of "axes" MUST be equal to the number "space" metadata {#space-md} -------------------------- -A "space" is a collection of dimensions / "axes" with a name. Each space dictionary: +A "space" is a collection of "axes" / dimensions with a name and defines a coordinate system. Each space: - MUST contain the field "name", that gives the space name. The values MUST be non-empty and unique. - MUST contain the field "axes", whose value is a valid set of "axes". -- SHOULD be such that the "type" of all "axes" in the list are identical. ``` { "name" : "volume_micrometers", "axes" : [ - {"name": "z", "type": "space", "unit": "micrometer"}, + {"name": "x", "type": "space", "unit": "micrometer"}, {"name": "y", "type": "space", "unit": "micrometer"}, - {"name": "x", "type": "space", "unit": "micrometer"} + {"name": "z", "type": "space", "unit": "micrometer"} ] } ``` -### Pixel coordinate convention +Order of the `"axes"` list matters and defines the index of each dimension and coordinates for points in that +space. For the above example, the `"x"` dimension is the first dimension. -**The pixel center is the origin of the continuous coordinate system.** -It is vital to consistently define relationship between the discrete/arrray and continuous/interpolated -coordinate systems. A pixel is the continuous region (rectangle) that corresponds to a single sample -in the discrete array, i.e., the area corresponding to nearest-neighbor interpolation of that sample. -The center of the pixel corresponding to the discrete origin `(0,0)` is the origin of the continuous space -`(0.0, 0.0)` when the transformation is the identity. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. +### "Array space" and indexing + +"Array space" is a default space for every array 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"`. For example, a 3D array space at path `/my/data/array` is: + +``` +{ + "name" : "/my/data/array", + "axes" : [ + {"name": "dim_0", "type": "array", "unit": ""}, + {"name": "dim_1", "type": "array", "unit": ""}, + {"name": "dim_2", "type": "array", "unit": ""} + ] +} +``` +though this object should not and need not explicitly appear in metadata. The dimensionality of each varies +according to its corresponding array. -### "Array space" +The axis with name `"dim_0"` is the first element of the `"axes"` list and corresponds to the +"first dimension" of the array, where "first dimension" is defined by the byte order. As described in the [zarr +array metadata](https://zarr.readthedocs.io/en/stable/spec/v2.html#arrays), the first dimension of an array "C" +("F") order vary slowest (fastest). -"Array space" is the unique space whose name is the empty string (`""`) or `null`. The array data of every dataset is in "Array -space." Therefore, the dimensionality of "array space" varies according to its corresponding dataset. -In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_space" is the empty string, i.e. -"array space." + +### Coordinate convention + +**The pixel/voxel center is the origin of the continuous coordinate system.** + +It is vital to consistently define relationship between the discrete/arrray 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 space +`(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)`. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. "transformations" metadata {#trafo-md} @@ -274,34 +296,93 @@ In [[#trafo-md]], there SHOULD exist at least one transformation whose "input_sp "coordinateTransformations" describe a series of transformations that map between two coordinate spaces (defined by "axes"). For example, to map a discrete data space of an array to the corresponding physical space. -It is a list of dictionaries. Each entry describes a single transformation, and +It is a list of objects. Each entry describes a single transformation, and -- MUST contain the field "name". The values MUST be unique across all "name" fields for coordinate transformations. - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- SHOULD contain exactly one of the fields "input_space" or "input_axes". - MUST contain exactly one of the fields "output_space" or "output_axes". -- OPTIONAL contain the field "inverse". -- the value of "output_space" SHOULD not be the empty string. +- SHOULD contain exactly one of the fields "input_space" or "input_axes". +- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations.
`identity` identity transformation, is the default transformation and is typically not explicitly defined
`translation` one of:
`"translation":List[float]`,
`"path":str`
translation vector, stored either as a list of floats (`"translation"`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. |
`scale` one of:
`"scale":List[float]`,
`"path":str`
scale vector, stored either as a list of floats (`scale`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions.
`affine` one of:
`"affine":List[float]`,
`"path":str`
affine transformation matrix defined as list consisting of n sets of n + 1 scalar numbers, stored either as a number[] (affine) or as binary data at a location in this container (path). If both are present, path is preferred. n is number of dimensions +
`sequence` + `"transformations":List[Transformation]` + A sequence of transformations, Applying the sequence applies the composition of all transforms in the + list, in order. An empty sequence is the identity transformation. | +
`displacement_field` + `"url":str`
`"path":str`
"interpolation":str +
Displacement field transformation in this container (path) or another container located at (url). +
`coordinate_field` + `"url":str`
`"path":str`
"interpolation":str +
Coordinate field transformation in this container (path) or another container located at (url). +
`inverse_of` + `"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.
typefieldsdescription
+ The transformations in the list are applied sequentally and in order. -### Forward and inverse -Specified coordinate transforms are in the "forward" direction. They represent functions -from *points* in the input space to *points* in the output space. For example, the transformation `"ij2xy"` +### Details + +#### Affine + +Affine transformations from N-dimensions to M-dimensions are represented at `(N)x(M+1)` matrices in homogeneous coordinates and stored as flat arrays +(row-major). + +A 2D-2D example: +```json +{ + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6], + "input_axes" : ["i", "j"], + "output_axes" : ["x", "y"] +} +``` + +defines the function: + +``` +x = 1*i + 2*j + 3 +y = 4*i + 5*j + 6 +``` + +A 2D - 3D example: +```json +{ + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "input_axes" : ["i", "j"], + "output_axes" : ["x", "y", "z"] +} +``` + +defines the function: + +``` +x = 1*i + 2*j + 3 +y = 4*i + 5*j + 6 +z = 7*i + 8*j + 9 +``` + + +### Points, coordinates, direction, and indexing + +Coordinate transformations are 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 transformation: ```json { - "name": "ij2xy", "type": "scale", "scale": [2, 0.5] "input_axes" : ["i", "j"] @@ -309,26 +390,28 @@ from *points* in the input space to *points* in the output space. For example, t } ``` -represents the function: +defines the function: ``` x = 2 * i y = 0.5 * j ``` -When rendering images and interpolating, implementations may need the "inverse" transformation - from output to +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 output to input space. 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 should be specified using the `inverse` transformation type, for example: ```json { - "name": "nonlinear-inverse", - "type": "displacement_field", - "path": "/path/to/my/transform", - "input_axes" : ["i", "j"], - "output_axes" : ["x", "y"], - "inverse" : true + "type": "inverse_of", + "transformation" : { + "name": "nonlinear-inverse", + "type": "displacement_field", + "path": "/path/to/my/transform", + } } ``` From dd7195359515825180d0f860495cf12d653c5e1b Mon Sep 17 00:00:00 2001 From: bogovicj Date: Mon, 9 May 2022 12:18:34 -0400 Subject: [PATCH 06/50] change fields to camelCase --- latest/index.bs | 86 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index c953ec2d..58045718 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -253,7 +253,8 @@ Order of the `"axes"` list matters and defines the index of each dimension and c space. For the above example, the `"x"` dimension is the first dimension. -### "Array space" and indexing +### Array space and indexing {#array-space} + "Array space" is a default space for every array 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 @@ -300,8 +301,8 @@ It is a list of objects. Each entry describes a single transformation, and - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- MUST contain exactly one of the fields "output_space" or "output_axes". -- SHOULD contain exactly one of the fields "input_space" or "input_axes". +- MUST contain exactly one of the fields "outputSpace" or "outputAxes". +- SHOULD contain exactly one of the fields "inputSpace" or "inputAxes". - MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. @@ -313,13 +314,13 @@ It is a list of objects. Each entry describes a single transformation, and
`"transformations":List[Transformation]` A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. An empty sequence is the identity transformation. | -
`displacement_field` +
`displacementField` `"url":str`
`"path":str`
"interpolation":str
Displacement field transformation in this container (path) or another container located at (url). -
`coordinate_field` +
`coordinateField` `"url":str`
`"path":str`
"interpolation":str
Coordinate field transformation in this container (path) or another container located at (url). -
`inverse_of` +
`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` @@ -331,6 +332,63 @@ It is a list of objects. Each entry describes a single transformation, and The transformations in the list are applied sequentally and in order. +### Spaces and axes + +Every coordinate transformation MUST specify its output coordinate system, either using `outputSpace` to +specify the name of an output space, or by using `outputAxes` to specify a list of axis names. Every +coordinate transformation SHOULD specify its input coordinate system, similarly either using `inputSpace` to +`inputAxes`. If neither `inputSpace` nor `inputAxes` are provided, the transformation's input +is in [array-space](#array-space). + +Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. +Which subspace should be defined using the `"inputAxes"` and `"outputAxes"` fields. An axis name MUST appear +in exactly one `"outputAxis"` lists. + +A simple, contrived example +```json +{ + "type" : "sequence", + "transformations" : [ + { + "type": "scale", + "scale" : [ 0.5, 0.5 ], + "inputAxes: ["dim_1", "dim_2"], + "outputAxes: ["x", "y"], + }, + { + "type": "scale", + "scale" : [ 2 ], + "inputAxes: ["dim_0"], + "outputAxes: ["t"], + } + ] +} +``` + +```json +{ + "type" : "sequence", + "transformations" : [ + { + "type": "inverseOf", + "transformation" : { + "name": "nonlinear-inverse", + "type": "displacementField", + "path": "/path/to/my/transform", + "inputAxes: ["i", "j"], + "outputAxes: ["x", "y"], + }, + }, + { + "type": "scale", + "scale" : [ 2 ] + "inputAxes: ["k"], + "outputAxes: ["z"], + } + ] +} +``` + ### Details @@ -344,8 +402,8 @@ A 2D-2D example: { "type": "affine", "affine": [1, 2, 3, 4, 5, 6], - "input_axes" : ["i", "j"], - "output_axes" : ["x", "y"] + "inputAxes" : ["i", "j"], + "outputAxes" : ["x", "y"] } ``` @@ -361,8 +419,8 @@ A 2D - 3D example: { "type": "affine", "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "input_axes" : ["i", "j"], - "output_axes" : ["x", "y", "z"] + "inputAxes" : ["i", "j"], + "outputAxes" : ["x", "y", "z"] } ``` @@ -385,8 +443,8 @@ The indexes of axis dimensions correspond to indexes into transformation paramet { "type": "scale", "scale": [2, 0.5] - "input_axes" : ["i", "j"] - "output_axes" : ["x", "y"] + "inputAxes" : ["i", "j"] + "outputAxes" : ["x", "y"] } ``` @@ -406,10 +464,10 @@ transformation type, for example: ```json { - "type": "inverse_of", + "type": "inverseOf", "transformation" : { "name": "nonlinear-inverse", - "type": "displacement_field", + "type": "displacementField", "path": "/path/to/my/transform", } } From 1b26a8e6beb79145155535073e2c95d1cf477da7 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Thu, 12 May 2022 16:51:06 -0400 Subject: [PATCH 07/50] add details for coordinate and displacement fields --- latest/index.bs | 93 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 58045718..f28a192a 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -222,7 +222,7 @@ keys as specified below for discovering certain types of data, especially images "axes" describes the dimensions of a coordinate space. 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 "space", "time" or "channel", but MAY take other values for custom axis types that are not part of this specification yet. +- SHOULD contain the field "type". It SHOULD be one of "space", "time", "channel", "coordinate", or "displacement" but MAY take other 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' @@ -315,10 +315,10 @@ It is a list of objects. Each entry describes a single transformation, and A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. An empty sequence is the identity transformation. |
`displacementField` - `"url":str`
`"path":str`
"interpolation":str +
`"url":str`
`"path":str`
`"interpolation":str`
Displacement field transformation in this container (path) or another container located at (url).
`coordinateField` - `"url":str`
`"path":str`
"interpolation":str +
`"url":str`
`"path":str`
`"interpolation":str`
Coordinate field transformation in this container (path) or another container located at (url).
`inverseOf` `"transform":Transform` @@ -392,9 +392,9 @@ A simple, contrived example ### Details -#### Affine +#### affine -Affine transformations from N-dimensions to M-dimensions are represented at `(N)x(M+1)` matrices in homogeneous coordinates and stored as flat arrays +`affine` transformations from N-dimensions to M-dimensions are represented at `(N)x(M+1)` matrices in homogeneous coordinates and stored as flat arrays (row-major). A 2D-2D example: @@ -432,6 +432,89 @@ y = 4*i + 5*j + 6 z = 7*i + 8*j + 9 ``` +#### coordinateField and displacementField + +`coordinateField` and `displacementField` transformations store coordinates or displacements in an array. Applying the transformation amounts +to looking up the appropriate locations in the array, and interpolating. Metadata for these coordinate +transforms have the following field: + +
+
path
+
The location of the coordinate array in this (or another) containter.
+
url
+
An optional URL to the container in which the coordinate field array is stored. If not provided + the provided `path` MUST exist in this container.
+
interpolation
+
The `interpolation` attributes MAY be provided. It's value indicates + the interpolation to use if transforing points not on the array's discrete grid. + Values could be: +
    +
  • linear (default)
  • +
  • nearest
  • +
  • cubic
  • +
+
+ +The array data at `path` MUST define space and coordinate transform metadata: + +* space metadata MUST have exactly one axis with `"type" : "coordinate"` +* Every axis in the `coordinateTransform`'s `inputSpace` MUST appear in the space +* The array dimension corresponding to the coordinate axis MUST have length greater than or equal to the dimensionality of the `coordinateTransform` `outputSpace` +* SHOULD have `name` equal to the `name` of the corresponding `coordinateTransform`. + +For a `coordinateField`: + +* If a `coordinateField`s input space has dimensionality `N`, then the array data at `path` MUST have dimensionality equal to `(ni + 1)`. +* space metadata MUST have exactly one axis with `"type" : "coordinate"` + +For a `displacementField`: +* space metadata MUST have exactly one axis with `"type" : "displacement"` +* `inputSpace` and `outputSpace` + + +A simple 1D example, +``` +{ + "name" : "a coordinate field transform", + "type": "coordinateField", + "path" : "coordinates", + "inputAxes" : ["i"], + "outputAxes" : ["x"], + "interpolation" : "nearest" +} +``` + +if the arrray in `coordinates` contains the data: `[-9, 9, 0]`, then this metadata defines the function: + +``` +x = + if ( i < 0.5 ) -9 + else if ( i < 1.5 ) 9 + else 0 +``` + +Example metadata for the array data at path `coordinates` above: + +``` +{ + "spaces" : [ + { + "name" : "a coordinate field transform", + "axes" : [ + { "label": "i", "type": "space", "discrete": true }, + { "label": "c", "type": "coordinate", "discrete": true } + ] + } + ], + "transformations" : [ + { + "type" : "identity", + "outputSpace" : "my coordinate field transform" + } + ] +} +``` + ### Points, coordinates, direction, and indexing From cd9c43139cff2b854e79df8385062337d34d4d22 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 13 May 2022 10:51:11 -0400 Subject: [PATCH 08/50] add details on dimension order --- latest/index.bs | 54 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index f28a192a..2670d236 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -220,9 +220,9 @@ keys as specified below for discovering certain types of data, especially images "axes" metadata {#axes-md} -------------------------- -"axes" describes the dimensions of a coordinate space. It is a list of dictionaries, where each dictionary describes a dimension (axis) and: +"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 "space", "time", "channel", "coordinate", or "displacement" but MAY take other values for custom axis types that are not part of this specification yet. +- SHOULD contain the field "type". It SHOULD be one of "array", "space", "time", "channel", "coordinate", or "displacement" but MAY take other 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' @@ -253,31 +253,53 @@ Order of the `"axes"` list matters and defines the index of each dimension and c space. For the above example, the `"x"` dimension is the first dimension. -### Array space and indexing {#array-space} +### Array space and indexing - -"Array space" is a default space for every array whose parameters need not be explicitly defined. +Every array has a default coordinate system we call "array space" 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"`. For example, a 3D array space at path `/my/data/array` is: +default "name"s. The ith axis has `"name":"dim_i"`. For example, a 3D array space at path `/my/data/array` is: ``` { "name" : "/my/data/array", "axes" : [ - {"name": "dim_0", "type": "array", "unit": ""}, - {"name": "dim_1", "type": "array", "unit": ""}, - {"name": "dim_2", "type": "array", "unit": ""} + {"name": "dim_0", "type": "array"}, + {"name": "dim_1", "type": "array"}, + {"name": "dim_2", "type": "array"} ] } ``` -though this object should not and need not explicitly appear in metadata. The dimensionality of each varies -according to its corresponding array. +though this object should not and need not explicitly appear in metadata. The dimensionality of each array space +equals the dimensionality of its corresponding zarr array. + +The axis with name `"dim_0"` is the first 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. + +and corresponds to the +"first dimension" of the array, where the "first dimension" corresponds to the first element of +the `shape` attribute in the `.zarray` zarr attributes, 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 (row-major) are stored contiguously. For an array in "F" +(column-major) order, the elements of the first dimension are stored contiguously. + +For example, if `/my/data/array/.zarray` contains: + +```json +{ + "chunks": [ 4, 3, 5 ], + "compressor": null, + "dtype": "|u1", + "fill_value": 0, + "filters": null, + "order": "C", + "shape": [ 4, 3, 5 ], + "zarr_format": 2 +} +``` -The axis with name `"dim_0"` is the first element of the `"axes"` list and corresponds to the -"first dimension" of the array, where "first dimension" is defined by the byte order. As described in the [zarr -array metadata](https://zarr.readthedocs.io/en/stable/spec/v2.html#arrays), the first dimension of an array "C" -("F") order vary slowest (fastest). +Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5. ### Coordinate convention @@ -338,7 +360,7 @@ Every coordinate transformation MUST specify its output coordinate system, eithe specify the name of an output space, or by using `outputAxes` to specify a list of axis names. Every coordinate transformation SHOULD specify its input coordinate system, similarly either using `inputSpace` to `inputAxes`. If neither `inputSpace` nor `inputAxes` are provided, the transformation's input -is in [array-space](#array-space). +is in array-space. Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. Which subspace should be defined using the `"inputAxes"` and `"outputAxes"` fields. An axis name MUST appear From e210027312bc2ea5e30f1bd0125f0f07b16a771a Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 13 May 2022 10:56:43 -0400 Subject: [PATCH 09/50] clean up array indexing section --- latest/index.bs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 2670d236..9d2b26d5 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -271,18 +271,12 @@ default "name"s. The ith axis has `"name":"dim_i"`. For example, a 3D array spac ``` though this object should not and need not explicitly appear in metadata. The dimensionality of each array space -equals the dimensionality of its corresponding zarr array. - -The axis with name `"dim_0"` is the first 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. - -and corresponds to the -"first dimension" of the array, where the "first dimension" corresponds to the first element of -the `shape` attribute in the `.zarray` zarr attributes, 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 (row-major) are stored contiguously. For an array in "F" -(column-major) order, the elements of the first dimension are stored contiguously. +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 (row-major) are stored contiguously. For an array in "F" (column-major) order, the elements of the first +dimension are stored contiguously. For example, if `/my/data/array/.zarray` contains: From d8735410a52931cf85c28b903d4d335819ba4be5 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 15 May 2022 11:20:35 -0400 Subject: [PATCH 10/50] add more details for scale, translation, displacement --- latest/index.bs | 147 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 9d2b26d5..296c8f4b 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -320,6 +320,7 @@ It is a list of objects. Each entry describes a single transformation, and - MUST contain exactly one of the fields "outputSpace" or "outputAxes". - SHOULD contain exactly one of the fields "inputSpace" or "inputAxes". - 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` identity transformation, is the default transformation and is typically not explicitly defined @@ -410,8 +411,21 @@ A simple, contrived example #### affine -`affine` transformations from N-dimensions to M-dimensions are represented at `(N)x(M+1)` matrices in homogeneous coordinates and stored as flat arrays -(row-major). +`affine` transformations from N-dimensional inputs to M-dimensional outputs are represented at `(N)x(M+1)` matrices in homogeneous coordinates. +The matrix may be stored as a 2D array or as a 1D array (row-major). + +
+
path
+
The path to an array containing the affine parameters. + The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*(M+1)`. + If 2D, its shape MUST be `N x M+1`.
+
url
+
An optional URL to the container in which the affine array is stored. If not provided, + the provided `path` MUST exist in this container.
+
affine
+
The affine parameters stored in JSON. If stored as a flat list of numbers the list MUST be length `N*(M+1)`. + If stored as a list of lists, the outer list MUST be length `N`, and inner lists MUST be length `(M+1)`.
+
A 2D-2D example: ```json @@ -448,6 +462,82 @@ y = 4*i + 5*j + 6 z = 7*i + 8*j + 9 ``` +The same transformation may also be written as: +```json +{ + "type": "affine", + "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "inputAxes" : ["i", "j"], + "outputAxes" : ["x", "y", "z"] +} +``` + +#### scale + +`scale` transformations are special cases of affine transformations. When possible, a +scale transformation should be preferred to its equivalent affine. + +
+
path
+
The path to an array containing the affine parameters. + The array at this path MUST be 1D, and its length MUST be `N`.
+
url
+
An optional URL to the container in which the affine array is stored. If not provided, + the provided `path` MUST exist in this container.
+
scale
+
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
+
+ +For example +```json +{ + "type": "scale", + "scale": [3, 2], + "inputAxes" : ["i", "j"], + "outputAxes" : ["x", "y"] +} +``` + +defines the function: + +``` +x = 3 * i +y = 2 * j +``` + +#### translation + +`translation` transformations are special cases of affine transformations. When possible, a +translation transformation should be preferred to its equivalent affine. + +
+
path
+
The path to an array containing the affine parameters. + The array at this path MUST be 1D, and its length MUST be `N`.
+
url
+
An optional URL to the container in which the affine array is stored. If not provided, + the provided `path` MUST exist in this container.
+
scale
+
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
+
+ +For example +```json +{ + "type": "translation", + "translation": [9, -1], + "inputAxes" : ["i", "j"], + "outputAxes" : ["x", "y"] +} +``` + +defines the function: + +``` +x = i + 9 +y = j - 1 +``` + #### coordinateField and displacementField `coordinateField` and `displacementField` transformations store coordinates or displacements in an array. Applying the transformation amounts @@ -458,7 +548,7 @@ transforms have the following field:
path
The location of the coordinate array in this (or another) containter.
url
-
An optional URL to the container in which the coordinate field array is stored. If not provided +
An optional URL to the container in which the coordinate field array is stored. If not provided, the provided `path` MUST exist in this container.
interpolation
The `interpolation` attributes MAY be provided. It's value indicates @@ -485,10 +575,9 @@ For a `coordinateField`: For a `displacementField`: * space metadata MUST have exactly one axis with `"type" : "displacement"` -* `inputSpace` and `outputSpace` +* `inputSpace` and `outputSpace` MUST have an equal number of dimensions. - -A simple 1D example, +For example, in 1D: ``` { "name" : "a coordinate field transform", @@ -525,12 +614,56 @@ Example metadata for the array data at path `coordinates` above: "transformations" : [ { "type" : "identity", - "outputSpace" : "my coordinate field transform" + "outputSpace" : "a coordinate field transform" + } + ] +} +``` + +A 1D example displacement field: +``` +{ + "name" : "a displacement field transform", + "type": "displacementField", + "path" : "displacements", + "inputAxes" : ["x"], + "outputAxes" : ["y"], + "interpolation" : "linear" +} +``` + +if the arrray in `displacements` contains the data: `[-1, 0, 1]`. + +Example metadata for the array data at path `displacements` above: + +``` +{ + "spaces" : [ + { + "name" : "a coordinate field transform", + "axes" : [ + { "label": "x", "type": "space", "unit" : "nanometer" }, + { "label": "d", "type": "displacement", "discrete": true } + ] + } + ], + "transformations" : [ + { + "type" : "scale", + "scale" : [2, 1], + "outputSpace" : "a displacement field transform" } ] } ``` +This transformation maps the point `[1.0]` to the point `[0.5]`. A scale +transformation maps the array coordinates to the "x" axis. Using the inverse +of the scale transform, we see that we need the position `0.5` in array coordinates. +The transformation specifies linear interpolation, which in this case yields +`(0.5 * -1) + (0.5 * 0) = -0.5`. That value gives us the displacement of the +input point, hence the output is `1.0 + (-0.5) = 0.5`. + ### Points, coordinates, direction, and indexing From 63ed4ce126df8eb09e4a28b62ef0eec4f7626960 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Mon, 16 May 2022 09:04:55 -0400 Subject: [PATCH 11/50] merge and cleanup --- latest/index.bs | 108 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 296c8f4b..eb0b9408 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -238,7 +238,7 @@ A "space" is a collection of "axes" / dimensions with a name and defines a coord - MUST contain the field "name", that gives the space name. The values MUST be non-empty and unique. - MUST contain the field "axes", whose value is a valid set of "axes". -``` +```json { "name" : "volume_micrometers", "axes" : [ @@ -259,7 +259,7 @@ Every array has a default coordinate system we call "array space" whose paramete 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"`. For example, a 3D array space at path `/my/data/array` is: -``` +```json { "name" : "/my/data/array", "axes" : [ @@ -323,10 +323,10 @@ It is a list of objects. Each entry describes a single transformation, and - Parameter values MUST be compatible with input and output space dimensionality (see details). -
`identity` identity transformation, is the default transformation and is typically not explicitly defined -
`translation` one of:
`"translation":List[float]`,
`"path":str`
translation vector, stored either as a list of floats (`"translation"`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. | -
`scale` one of:
`"scale":List[float]`,
`"path":str`
scale vector, stored either as a list of floats (`scale`) or as binary data at a location in this container (`path`). The length of vector defines number of dimensions. -
`affine` one of:
`"affine":List[float]`,
`"path":str`
affine transformation matrix defined as list consisting of n sets of n + 1 scalar numbers, stored either as a number[] (affine) or as binary data at a location in this container (path). If both are present, path is preferred. n is number of dimensions +
`identity` The identity transformation is the default transformation and is typically not explicitly defined +
`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`). The length of vector defines number of dimensions. | +
`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`). The length of vector defines number of dimensions. +
`affine` one of:
`"affine":List[number]`,
`"path":str`
affine transformation matrix defined as list consisting of n sets of n + 1 numbers, stored either as a number[] (affine) or as binary data at a location in this container (path). If both are present, path is preferred. n is number of dimensions
`sequence` `"transformations":List[Transformation]` A sequence of transformations, Applying the sequence applies the composition of all transforms in the @@ -343,6 +343,10 @@ It is a list of objects. Each entry describes a single transformation, and
`bijection` `"forward":Transform`
`"inverse":Transform`
Explicitly define an invertible transformation by providing a forward transformation and its inverse. +
`dimensionWise` + `"transformations":List[Transformation]` + Define a high dimensional transformation using lower dimensional transformations on subsets of + dimensions.
typefieldsdescription
@@ -361,7 +365,7 @@ Coordinate transformations may be defined as sequences of transformations, each Which subspace should be defined using the `"inputAxes"` and `"outputAxes"` fields. An axis name MUST appear in exactly one `"outputAxis"` lists. -A simple, contrived example +A simple example ```json { "type" : "sequence", @@ -665,6 +669,96 @@ The transformation specifies linear interpolation, which in this case yields input point, hence the output is `1.0 + (-0.5) = 0.5`. +#### dimensionWise + +`dimensionWise` 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 subset of input and output dimensions (axes). + Every transformation in this list MUST have the fields `inputAxes` and `outputAxes` fields. + Every axis name in `inputAxes` MUST appear in this parent objects `inputSpace` axes. + Every axis in the parent `dimensionWise`'s `outputSpace` MUST appear in exactly one + of its child transformations' `outputAxes`. +
+
+ +This is a valid `dimensionWise` transformation: + +```json +{ + "spaces" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "transforms" : [ + { + "name: "in2out", + "type: "dimensionWise", + "inputSpace" : "in", + "outputSpace" : "out", + "transformations" : [ + { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} + ] + } + ] +} +``` + +This is also a valid transformation: + +```json +{ + "spaces" : [ + { "name" : "in", "axes" : [ {"name" : "0"}, {"name" : "1"}, {"name" : "2"}, {"name" : "3"}] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } + ], + "transforms" : [ + { + "name: "in2out", + "type: "dimensionWise", + "inputSpace" : "in", + "outputSpace" : "out", + "transformations" : [ + { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["0","3" ], "outputAxes" : ["y", "x"]} + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["1"], "outputAxes" : ["z"]} + ] + } + ] +} +``` + + +This is an invalid `dimensionWise` transform: + +```json +{ + "spaces" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "transforms" : [ + { + "name: "in2out", + "type: "dimensionWise", + "inputSpace" : "in", + "outputSpace" : "out", + "transformations" : [ + { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["z"]} + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["0"], "outputAxes" : ["y"]} + ] + } + ] +} +``` + +for two reasons. First because inputAxis "0" used by the scale transformation is not an axis of the `dimensionWise` transformation's +`inputSpace`. Second, the "x" axis of the `outputSpace` does not appear in the `outputAxes` of any child +transformation. + + ### Points, coordinates, direction, and indexing Coordinate transformations are are functions of *points* in the input space to *points* in the output space. We call this the "forward" direction. From 9058b4bc78da60cf9ee9a827413b20f07d198491 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Wed, 1 Jun 2022 18:05:44 -0400 Subject: [PATCH 12/50] clarify input and output dimensionality for transforms * use "input" and "output" rather than '*Space' and '*Axes' --- latest/index.bs | 108 ++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 5a0af5f8..0917fa18 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -263,7 +263,7 @@ A "space" is a collection of "axes" / dimensions with a name and defines a coord } ``` -Order of the `"axes"` list matters and defines the index of each dimension and coordinates for points in that +Order of the `"axes"` list matters and defines the index of each array dimension and coordinates for "points" in that space. For the above example, the `"x"` dimension is the first dimension. @@ -331,8 +331,8 @@ It is a list of objects. Each entry describes a single transformation, and - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- MUST contain exactly one of the fields "outputSpace" or "outputAxes". -- SHOULD contain exactly one of the fields "inputSpace" or "inputAxes". +- MUST contain the field "output". +- SHOULD contain the field "input". - 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). @@ -376,8 +376,8 @@ coordinate transformation SHOULD specify its input coordinate system, similarly is in array-space. Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. -Which subspace should be defined using the `"inputAxes"` and `"outputAxes"` fields. An axis name MUST appear -in exactly one `"outputAxis"` lists. +Which subspace should be defined by providing arrays of strings as the values for the `"input"` and `"output"` +fields. An axis name MUST appear in exactly one `"output"` lists. A simple example ```json @@ -387,14 +387,14 @@ A simple example { "type": "scale", "scale" : [ 0.5, 0.5 ], - "inputAxes: ["dim_1", "dim_2"], - "outputAxes: ["x", "y"], + "input" : ["dim_1", "dim_2"], + "output" : ["x", "y"], }, { "type": "scale", "scale" : [ 2 ], - "inputAxes: ["dim_0"], - "outputAxes: ["t"], + "input" : ["dim_0"], + "output" : ["t"], } ] } @@ -410,15 +410,15 @@ A simple example "name": "nonlinear-inverse", "type": "displacementField", "path": "/path/to/my/transform", - "inputAxes: ["i", "j"], - "outputAxes: ["x", "y"], + "input" : ["i", "j"], + "output" : ["x", "y"], }, }, { "type": "scale", "scale" : [ 2 ] - "inputAxes: ["k"], - "outputAxes: ["z"], + "input" : ["k"], + "output" : ["z"], } ] } @@ -427,6 +427,13 @@ A simple example ### Details +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 imension, 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, it's length gives the output dimension, +otherwise it is given by the length of "axes" for the coordinate system with the name of the "output". + #### affine `affine` transformations from N-dimensional inputs to M-dimensional outputs are represented at `(N)x(M+1)` matrices in homogeneous coordinates. @@ -450,8 +457,8 @@ A 2D-2D example: { "type": "affine", "affine": [1, 2, 3, 4, 5, 6], - "inputAxes" : ["i", "j"], - "outputAxes" : ["x", "y"] + "input" : ["i", "j"], + "output" : ["x", "y"] } ``` @@ -467,8 +474,8 @@ A 2D - 3D example: { "type": "affine", "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "inputAxes" : ["i", "j"], - "outputAxes" : ["x", "y", "z"] + "input" : ["i", "j"], + "output" : ["x", "y", "z"] } ``` @@ -485,15 +492,16 @@ The same transformation may also be written as: { "type": "affine", "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - "inputAxes" : ["i", "j"], - "outputAxes" : ["x", "y", "z"] + "input" : ["i", "j"], + "output" : ["x", "y", "z"] } ``` #### scale `scale` transformations are special cases of affine transformations. When possible, a -scale transformation should be preferred to its equivalent affine. +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.
path
@@ -511,8 +519,8 @@ For example { "type": "scale", "scale": [3, 2], - "inputAxes" : ["i", "j"], - "outputAxes" : ["x", "y"] + "input" : ["i", "j"], + "output" : ["x", "y"] } ``` @@ -526,7 +534,8 @@ y = 2 * j #### translation `translation` transformations are special cases of affine transformations. When possible, a -translation transformation should be preferred to its equivalent affine. +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.
path
@@ -544,8 +553,8 @@ For example { "type": "translation", "translation": [9, -1], - "inputAxes" : ["i", "j"], - "outputAxes" : ["x", "y"] + "input" : ["i", "j"], + "output" : ["x", "y"] } ``` @@ -583,7 +592,7 @@ The array data at `path` MUST define space and coordinate transform metadata: * space metadata MUST have exactly one axis with `"type" : "coordinate"` * Every axis in the `coordinateTransform`'s `inputSpace` MUST appear in the space -* The array dimension corresponding to the coordinate axis MUST have length greater than or equal to the dimensionality of the `coordinateTransform` `outputSpace` +* The array dimension corresponding to the coordinate axis MUST have length greater than or equal to the dimensionality of the `coordinateTransform` `output` * SHOULD have `name` equal to the `name` of the corresponding `coordinateTransform`. For a `coordinateField`: @@ -593,7 +602,7 @@ For a `coordinateField`: For a `displacementField`: * space metadata MUST have exactly one axis with `"type" : "displacement"` -* `inputSpace` and `outputSpace` MUST have an equal number of dimensions. +* `input` and `output` MUST have an equal number of dimensions. For example, in 1D: ``` @@ -601,8 +610,8 @@ For example, in 1D: "name" : "a coordinate field transform", "type": "coordinateField", "path" : "coordinates", - "inputAxes" : ["i"], - "outputAxes" : ["x"], + "input" : ["i"], + "output" : ["x"], "interpolation" : "nearest" } ``` @@ -632,7 +641,7 @@ Example metadata for the array data at path `coordinates` above: "transformations" : [ { "type" : "identity", - "outputSpace" : "a coordinate field transform" + "output" : "a coordinate field transform" } ] } @@ -644,8 +653,8 @@ A 1D example displacement field: "name" : "a displacement field transform", "type": "displacementField", "path" : "displacements", - "inputAxes" : ["x"], - "outputAxes" : ["y"], + "input" : ["x"], + "output" : ["y"], "interpolation" : "linear" } ``` @@ -669,7 +678,7 @@ Example metadata for the array data at path `displacements` above: { "type" : "scale", "scale" : [2, 1], - "outputSpace" : "a displacement field transform" + "output" : "a displacement field transform" } ] } @@ -692,9 +701,9 @@ on subsets of dimensions.
transformations
A list of transformations, each of which applies to a subset of input and output dimensions (axes). Every transformation in this list MUST have the fields `inputAxes` and `outputAxes` fields. - Every axis name in `inputAxes` MUST appear in this parent objects `inputSpace` axes. + Every axis name in `input` MUST appear in this parent objects `input` axes. Every axis in the parent `dimensionWise`'s `outputSpace` MUST appear in exactly one - of its child transformations' `outputAxes`. + of its child transformations' `output`.
@@ -710,8 +719,8 @@ This is a valid `dimensionWise` transformation: { "name: "in2out", "type: "dimensionWise", - "inputSpace" : "in", - "outputSpace" : "out", + "input" : "in", + "output" : "out", "transformations" : [ { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} @@ -733,11 +742,11 @@ This is also a valid transformation: { "name: "in2out", "type: "dimensionWise", - "inputSpace" : "in", - "outputSpace" : "out", + "input" : "in", + "output" : "out", "transformations" : [ - { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["0","3" ], "outputAxes" : ["y", "x"]} - { "type" : "scale", "scale" : [2.0], "inputAxes" : ["1"], "outputAxes" : ["z"]} + { "type" : "coordinateField", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} + { "type" : "scale", "scale" : [2.0], "input" : ["1"], "output" : ["z"]} ] } ] @@ -757,20 +766,19 @@ This is an invalid `dimensionWise` transform: { "name: "in2out", "type: "dimensionWise", - "inputSpace" : "in", - "outputSpace" : "out", + "input" : "in", + "output" : "out", "transformations" : [ - { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["z"]} - { "type" : "scale", "scale" : [2.0], "inputAxes" : ["0"], "outputAxes" : ["y"]} + { "type" : "coordinateField", "path" : "/coordinates", "input" : ["i"], "output" : ["z"]} + { "type" : "scale", "scale" : [2.0], "input" : ["0"], "output" : ["y"]} ] } ] } ``` -for two reasons. First because inputAxis "0" used by the scale transformation is not an axis of the `dimensionWise` transformation's -`inputSpace`. Second, the "x" axis of the `outputSpace` does not appear in the `outputAxes` of any child -transformation. +for two reasons. First because input"0" used by the scale transformation is not an axis of the `dimensionWise` transformation's +`input`. Second, the "x" axis of the `output` does not appear in the `output` of any child transformation. ### Points, coordinates, direction, and indexing @@ -783,8 +791,8 @@ The indexes of axis dimensions correspond to indexes into transformation paramet { "type": "scale", "scale": [2, 0.5] - "inputAxes" : ["i", "j"] - "outputAxes" : ["x", "y"] + "input" : ["i", "j"] + "output" : ["x", "y"] } ``` From 38ac6ac16bf6eed1fcc67a4cbac2b933d0a56499 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 3 Jun 2022 08:03:53 -0400 Subject: [PATCH 13/50] update to transformations * reorder details * clean up table * add rotation * details for sequence * describe inverses * wrap examples --- latest/index.bs | 332 ++++++++++++++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 127 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 0917fa18..19eb8c9b 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -322,29 +322,51 @@ The center of a 2d pixel corresponding to the origin `(0,0)` in the discrete arr half-open interval `[-0.5, 0.5) x [-0.5, 0.5)`. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. -"transformations" metadata {#trafo-md} +"coordinateTransformations" metadata {#trafo-md} ------------------------------------- "coordinateTransformations" describe a series of transformations that map between two coordinate spaces (defined by "axes"). For example, to map a discrete data space of an array to the corresponding physical space. It is a list of objects. Each entry describes a single transformation, and +Coordinate transforms are in the "forward" direction. They represent functions from *points* in the +input space to *points* in the output space. Implementations SHOULD be able to compute and apply the inverse +of some coordinate transformations (see the Details section below will note those transform types). + + - MUST contain the field "type". - MUST contain any other fields required by the given "type" (see table below). -- MUST contain the field "output". -- SHOULD contain the field "input". +- MUST contain the field "output", unless part of a `sequence` (see details). +- SHOULD contain the field "input", unless part of a `sequence` (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 -
`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`). The length of vector defines number of dimensions. | -
`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`). The length of vector defines number of dimensions. -
`affine` one of:
`"affine":List[number]`,
`"path":str`
affine transformation matrix defined as list consisting of n sets of n + 1 numbers, stored either as a number[] (affine) or as binary data at a location in this container (path). If both are present, path is preferred. n is number of dimensions +
`identity` + + The identity transformation is the default transformation and is typically not explicitly defined. +
`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[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, path is + preferred. +
`rotation` + one of:
`"rotation":List[number]`,
`"path":str` +
rotation transformation matrix stored as a flat array stored either with json uing the rotation field + or as binary data at a location in this container (path). If both are present, path is + preferred.
`sequence` `"transformations":List[Transformation]` A sequence of transformations, Applying the sequence applies the composition of all transforms in the - list, in order. An empty sequence is the identity transformation. | + list, in order. An empty sequence is the identity transformation.
`displacementField` `"url":str`
`"path":str`
`"interpolation":str`
Displacement field transformation in this container (path) or another container located at (url). @@ -365,64 +387,13 @@ It is a list of objects. Each entry describes a single transformation, and
typefieldsdescription
-The transformations in the list are applied sequentally and in order. - -### Spaces and axes -Every coordinate transformation MUST specify its output coordinate system, either using `outputSpace` to -specify the name of an output space, or by using `outputAxes` to specify a list of axis names. Every -coordinate transformation SHOULD specify its input coordinate system, similarly either using `inputSpace` to -`inputAxes`. If neither `inputSpace` nor `inputAxes` are provided, the transformation's input -is in array-space. +Conforming readers: +- SHOULD be able to apply transformations to points +- MAY enable applying transformations to points +- MAY enable applying transformations to images -Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. -Which subspace should be defined by providing arrays of strings as the values for the `"input"` and `"output"` -fields. An axis name MUST appear in exactly one `"output"` lists. -A simple example -```json -{ - "type" : "sequence", - "transformations" : [ - { - "type": "scale", - "scale" : [ 0.5, 0.5 ], - "input" : ["dim_1", "dim_2"], - "output" : ["x", "y"], - }, - { - "type": "scale", - "scale" : [ 2 ], - "input" : ["dim_0"], - "output" : ["t"], - } - ] -} -``` - -```json -{ - "type" : "sequence", - "transformations" : [ - { - "type": "inverseOf", - "transformation" : { - "name": "nonlinear-inverse", - "type": "displacementField", - "path": "/path/to/my/transform", - "input" : ["i", "j"], - "output" : ["x", "y"], - }, - }, - { - "type": "scale", - "scale" : [ 2 ] - "input" : ["k"], - "output" : ["z"], - } - ] -} -``` ### Details @@ -434,29 +405,34 @@ length gives the input imension, otherwise it is given by the length of "axes" f the name of the "input". If the value of "output" is an array, it's length gives the output dimension, otherwise it is given by the length of "axes" for the coordinate system with the name of the "output". -#### affine +#### identity -`affine` transformations from N-dimensional inputs to M-dimensional outputs are represented at `(N)x(M+1)` matrices in homogeneous coordinates. -The matrix may be stored as a 2D array or as a 1D array (row-major). +`identity` transformation maps input coordinates to output coordinates without modification. `identity` +transformations are invertible. + +#### 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. `translation` transformations are +invertible.
path
The path to an array containing the affine parameters. - The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*(M+1)`. - If 2D, its shape MUST be `N x M+1`.
+ The array at this path MUST be 1D, and its length MUST be `N`.
url
An optional URL to the container in which the affine array is stored. If not provided, the provided `path` MUST exist in this container.
-
affine
-
The affine parameters stored in JSON. If stored as a flat list of numbers the list MUST be length `N*(M+1)`. - If stored as a list of lists, the outer list MUST be length `N`, and inner lists MUST be length `(M+1)`.
+
scale
+
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
-A 2D-2D example: +For example ```json { - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6], + "type": "translation", + "translation": [9, -1], "input" : ["i", "j"], "output" : ["x", "y"] } @@ -465,43 +441,16 @@ A 2D-2D example: defines the function: ``` -x = 1*i + 2*j + 3 -y = 4*i + 5*j + 6 -``` - -A 2D - 3D example: -```json -{ - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "input" : ["i", "j"], - "output" : ["x", "y", "z"] -} -``` - -defines the function: - -``` -x = 1*i + 2*j + 3 -y = 4*i + 5*j + 6 -z = 7*i + 8*j + 9 -``` - -The same transformation may also be written as: -```json -{ - "type": "affine", - "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - "input" : ["i", "j"], - "output" : ["x", "y", "z"] -} +x = i + 9 +y = j - 1 ``` #### 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. +`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. Values in the `scale` array SHOULD be non-zero; in that case, `scale` +transformations are invertible.
path
@@ -531,45 +480,115 @@ x = 3 * i y = 2 * j ``` -#### translation +#### affine -`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. +`affine` transformations from N-dimensional inputs to M-dimensional outputs are represented at `(N)x(M+1)` +matrices in homogeneous coordinates. This transformation type is invertible when `N` equals `M`. +The matrix may be stored as a 2D array or as a 1D array (row-major).
path
The path to an array containing the affine parameters. - The array at this path MUST be 1D, and its length MUST be `N`.
+ The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*(M+1)`. + If 2D, its shape MUST be `N x M+1`.
url
An optional URL to the container in which the affine array is stored. If not provided, the provided `path` MUST exist in this container.
-
scale
-
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
+
affine
+
The affine parameters stored in JSON. If stored as a flat list of numbers the list MUST be length `N*(M+1)`. + If stored as a list of lists, the outer list MUST be length `N`, and inner lists MUST be length `(M+1)`.
-For example +
+ A 2D-2D example: + ```json + { + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6], + "input" : ["i", "j"], + "output" : ["x", "y"] + } + ``` + + defines the function: + + ``` + x = 1*i + 2*j + 3 + y = 4*i + 5*j + 6 + ``` +
+ +
+ An example with two dimensional inputs and three dimensional outputs. + ```json + { + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "input" : ["i", "j"], + "output" : ["x", "y", "z"] + } + ``` + + defines the function: + + ``` + x = 1*i + 2*j + 3 + y = 4*i + 5*j + 6 + z = 7*i + 8*j + 9 + ``` + + The same transformation may also be written as: + ```json + { + "type": "affine", + "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "input" : ["i", "j"], + "output" : ["x", "y", "z"] + } + ``` +
+ + +#### sequence + +A `sequence` transformation consist of an ordered array of coordinate transformations. +`sequence` transformations are invertible if and only if every coordinate transform in the array +is invertible. + +The `input` and `output` fields MAY be omitted for transformations that are in the list +of transformations of a `sequence`. + +
+For example, this sequence: ```json { - "type": "translation", - "translation": [9, -1], + "type" : "sequence", + "transformations" : [ + { "type": "translation", "translation" : [0.1, 0.9] }, + { "type": "scale", "scale" : [2, 3] } + ] "input" : ["i", "j"], "output" : ["x", "y"] } ``` -defines the function: +describes the function ``` -x = i + 9 -y = j - 1 +x = (i + 0.1) * 2 +y = (j + 0.9) * 3 ``` +
+ + + #### coordinateField and displacementField -`coordinateField` and `displacementField` transformations store coordinates or displacements in an array. Applying the transformation amounts -to looking up the appropriate locations in the array, and interpolating. Metadata for these coordinate -transforms have the following field: +`coordinateField` and `displacementField` transformations store coordinates or displacements in an array. +Applying the transformation amounts to looking up the appropriate locations in the array, and interpolating. +`coordinateField` and `displacementField` transformations are note invertible in general, but implementations +MAY approximate their inverses. Metadata for these coordinate transforms have the following field:
path
@@ -591,7 +610,7 @@ transforms have the following field: The array data at `path` MUST define space and coordinate transform metadata: * space metadata MUST have exactly one axis with `"type" : "coordinate"` -* Every axis in the `coordinateTransform`'s `inputSpace` MUST appear in the space +* Every axis name in the `coordinateTransform`'s `input` MUST appear in the space * The array dimension corresponding to the coordinate axis MUST have length greater than or equal to the dimensionality of the `coordinateTransform` `output` * SHOULD have `name` equal to the `name` of the corresponding `coordinateTransform`. @@ -700,9 +719,9 @@ on subsets of dimensions.
transformations
A list of transformations, each of which applies to a subset of input and output dimensions (axes). - Every transformation in this list MUST have the fields `inputAxes` and `outputAxes` fields. - Every axis name in `input` MUST appear in this parent objects `input` axes. - Every axis in the parent `dimensionWise`'s `outputSpace` MUST appear in exactly one + The values of `input` and `output` fields MUST be an array of strings. + Every axis name in `input` MUST appear in this parent object's `input` axes. + Every axis name in the parent dimensionWise's output MUST appear in exactly one of its child transformations' `output`.
@@ -821,6 +840,65 @@ transformation type, for example: } ``` +### Spaces and axes + +Every coordinate transformation MUST specify its output coordinate system using `output` to with a string value +corresponding to the name of an output space or with an array of strings to provide a list of axis names. +Similarly, every coordinate transformation SHOULD specify its input coordinate system either using `input`. If +`input` is not provided the transformation's input is in array-space. + +Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. +Which subspace should be defined by providing arrays of strings as the values for the `input` and `output` +fields. An axis name MUST appear in exactly one `output` lists. + + +
+A simple example +```json +{ + "type" : "sequence", + "transformations" : [ + { + "type": "scale", + "scale" : [ 0.5, 0.5 ], + "input" : ["dim_1", "dim_2"], + "output" : ["x", "y"], + }, + { + "type": "scale", + "scale" : [ 2 ], + "input" : ["dim_0"], + "output" : ["t"], + } + ] +} +``` + +```json +{ + "type" : "sequence", + "transformations" : [ + { + "type": "inverseOf", + "transformation" : { + "name": "nonlinear-inverse", + "type": "displacementField", + "path": "/path/to/my/transform", + "input" : ["i", "j"], + "output" : ["x", "y"], + }, + }, + { + "type": "scale", + "scale" : [ 2 ] + "input" : ["k"], + "output" : ["z"], + } + ] +} +``` +
+ "multiscales" metadata {#multiscale-md} --------------------------------------- From 3a2aae1658166cec96c693f383fc0875b92c5531 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 3 Jun 2022 14:46:48 -0400 Subject: [PATCH 14/50] rotation transformation details * rephrase matrix storage --- latest/index.bs | 62 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 19eb8c9b..310d387f 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -356,13 +356,12 @@ of some coordinate transformations (see the Details section below will note thos
`affine` one of:
`"affine":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, path is - preferred. + 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 a flat array stored either with json uing the rotation field - or as binary data at a location in this container (path). If both are present, path is - preferred. + 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 @@ -448,7 +447,7 @@ y = j - 1 #### 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 +SHOULD be defined to its equivalent affine. Input and output dimensionality MUST be identical and MUST equal the the length of the "scale" array. Values in the `scale` array SHOULD be non-zero; in that case, `scale` transformations are invertible. @@ -495,8 +494,10 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
An optional URL to the container in which the affine array is stored. If not provided, the provided `path` MUST exist in this container.
affine
-
The affine parameters stored in JSON. If stored as a flat list of numbers the list MUST be length `N*(M+1)`. - If stored as a list of lists, the outer list MUST be length `N`, and inner lists MUST be length `(M+1)`.
+
The affine parameters stored in JSON. The matrix may be stored + as a row-major flat list of numbers the list MUST be length `N*(M+1)`. If stored as a list of + lists, the outer list MUST be length `N`, and all inner lists MUST be + length `(M+1)`. In this case, inner lists contain rows of the matrix.
@@ -549,6 +550,51 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
+#### rotation + +`rotation` transformations are special cases of affine transformations. +When possible, a rotation transformation SHOULD be defined rather than +its equivalent affine. Input and output dimensionality (N) MUST be +identical and greater than 1. Rotations are stored as `NxN` matrices, +see below, and MUST have determinant equal to one, with orthonormal rows +and columns. `rotation` transformations are invertible. + +
+
path
+
The path to an array containing the affine parameters. + The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*N`. + If 2D, its shape MUST be `N x N`.
+
url
+
An optional URL to the container in which the affine array is stored. If not provided, + the provided `path` MUST exist in this container.
+
rotation
+
The rotation parameters stored in JSON. The matrix may be stored + as a row-major flat list of numbers. Int that case, the list MUST be + length `N*N`. The matrix may bes stored a list of lists, the outer list MUST be + length `N`, and all inner lists MUST be length `N`. In this case, + inner lists contain rows of the matrix.
+
+ +
+ A 2D example + ```json + { + "type": "rotation", + "affine": [0, 1, 1, 0], + "input" : ["i", "j"], + "output" : ["x", "y"] + } + ``` + + defines the function: + + ``` + x = 0*i + 1*j + y = 1*i + 0*j + ``` +
+ + #### sequence A `sequence` transformation consist of an ordered array of coordinate transformations. From ed121921a11df881f52b5d3bd627585772675dae Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 3 Jun 2022 15:59:56 -0400 Subject: [PATCH 15/50] minor rephrase intro to coordinateTransformations section --- latest/index.bs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 310d387f..fa63722d 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -325,10 +325,8 @@ half-open interval `[-0.5, 0.5) x [-0.5, 0.5)`. See chapter 4 and figure 4.1 of "coordinateTransformations" metadata {#trafo-md} ------------------------------------- -"coordinateTransformations" describe a series of transformations that map between two coordinate spaces (defined by "axes"). +"coordinateTransformations" describe map between two coordinate spaces (defined by "axes"). For example, to map a discrete data space of an array to the corresponding physical space. -It is a list of objects. Each entry describes a single transformation, and - Coordinate transforms are in the "forward" direction. They represent functions from *points* in the input space to *points* in the output space. Implementations SHOULD be able to compute and apply the inverse of some coordinate transformations (see the Details section below will note those transform types). From 3ec8543e1d56771d6fdf1b8bbabb951e2af5752f Mon Sep 17 00:00:00 2001 From: bogovicj Date: Mon, 6 Jun 2022 13:41:19 -0400 Subject: [PATCH 16/50] add details for bijection ct * change to "coordinates", removing "Field" * change to "displacements", removing "Field" --- latest/index.bs | 80 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index fa63722d..63d2ccf1 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -364,10 +364,10 @@ of some coordinate transformations (see the Details section below will note thos
`"transformations":List[Transformation]` A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order. An empty sequence is the identity transformation. -
`displacementField` +
`displacements` `"url":str`
`"path":str`
`"interpolation":str`
Displacement field transformation in this container (path) or another container located at (url). -
`coordinateField` +
`coordinates` `"url":str`
`"path":str`
`"interpolation":str`
Coordinate field transformation in this container (path) or another container located at (url).
`inverseOf` @@ -593,6 +593,10 @@ and columns. `rotation` transformations are invertible. +#### inverseOf + +A + #### sequence A `sequence` transformation consist of an ordered array of coordinate transformations. @@ -627,11 +631,11 @@ y = (j + 0.9) * 3 -#### coordinateField and displacementField +#### coordinates and displacements -`coordinateField` and `displacementField` transformations store coordinates or displacements in an array. +`coordinates` and `displacements` transformations store coordinates or displacements in an array. Applying the transformation amounts to looking up the appropriate locations in the array, and interpolating. -`coordinateField` and `displacementField` transformations are note invertible in general, but implementations +`coordinates` and `displacements` transformations are note invertible in general, but implementations MAY approximate their inverses. Metadata for these coordinate transforms have the following field:
@@ -658,12 +662,12 @@ The array data at `path` MUST define space and coordinate transform metadata: * The array dimension corresponding to the coordinate axis MUST have length greater than or equal to the dimensionality of the `coordinateTransform` `output` * SHOULD have `name` equal to the `name` of the corresponding `coordinateTransform`. -For a `coordinateField`: +For `coordinates`: -* If a `coordinateField`s input space has dimensionality `N`, then the array data at `path` MUST have dimensionality equal to `(ni + 1)`. +* If a `coordinates`' input space has dimensionality `N`, then the array data at `path` MUST have dimensionality equal to `(ni + 1)`. * space metadata MUST have exactly one axis with `"type" : "coordinate"` -For a `displacementField`: +For `displacements`: * space metadata MUST have exactly one axis with `"type" : "displacement"` * `input` and `output` MUST have an equal number of dimensions. @@ -671,7 +675,7 @@ For example, in 1D: ``` { "name" : "a coordinate field transform", - "type": "coordinateField", + "type": "coordinates", "path" : "coordinates", "input" : ["i"], "output" : ["x"], @@ -714,7 +718,7 @@ A 1D example displacement field: ``` { "name" : "a displacement field transform", - "type": "displacementField", + "type": "displacements", "path" : "displacements", "input" : ["x"], "output" : ["y"], @@ -785,7 +789,7 @@ This is a valid `dimensionWise` transformation: "input" : "in", "output" : "out", "transformations" : [ - { "type" : "coordinateField", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} + { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} ] } @@ -808,7 +812,7 @@ This is also a valid transformation: "input" : "in", "output" : "out", "transformations" : [ - { "type" : "coordinateField", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} + { "type" : "coordinates", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} { "type" : "scale", "scale" : [2.0], "input" : ["1"], "output" : ["z"]} ] } @@ -832,7 +836,7 @@ This is an invalid `dimensionWise` transform: "input" : "in", "output" : "out", "transformations" : [ - { "type" : "coordinateField", "path" : "/coordinates", "input" : ["i"], "output" : ["z"]} + { "type" : "coordinates", "path" : "/coordinates", "input" : ["i"], "output" : ["z"]} { "type" : "scale", "scale" : [2.0], "input" : ["0"], "output" : ["y"]} ] } @@ -844,6 +848,52 @@ for two reasons. First because input"0" used by the scale transformation is not `input`. Second, the "x" axis of the `output` does not appear in the `output` of any child transformation. +#### 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 +approprite dimensionality. + +
+For example +```json +{ + "type" : "bijection", + "forward" :{ "type" : "coordinates", "path" : "/forward_coordinates" }, + "inverse" :{ "type" : "coordinates", "path" : "/inverse_coordinates" }, + "input" : "src", + "output" : "tgt" +} +``` + +the input and output of the `forward` and `inverse` transformations are understoood to be: + + +```json +{ + "type" : "bijection", + "forward" :{ "type" : "coordinates", "path" : "/forward_coordinates", + "input" : "src", "output" : "tgt" }, + "inverse" :{ "type" : "coordinates", "path" : "/inverse_coordinates", + "input" : "tgt", "output" : "src" }, + "input" : "src", + "output" : "tgt" +} +``` + +
+ + + ### Points, coordinates, direction, and indexing Coordinate transformations are are functions of *points* in the input space to *points* in the output space. We call this the "forward" direction. @@ -878,7 +928,7 @@ transformation type, for example: "type": "inverseOf", "transformation" : { "name": "nonlinear-inverse", - "type": "displacementField", + "type": "displacements", "path": "/path/to/my/transform", } } @@ -926,7 +976,7 @@ A simple example "type": "inverseOf", "transformation" : { "name": "nonlinear-inverse", - "type": "displacementField", + "type": "displacements", "path": "/path/to/my/transform", "input" : ["i", "j"], "output" : ["x", "y"], From 30cade5073324dd8a5acaec0fa71a592a0c4a3ff Mon Sep 17 00:00:00 2001 From: bogovicj Date: Thu, 9 Jun 2022 11:48:05 -0400 Subject: [PATCH 17/50] big changes to transformations spec * add details for transformation types * (identity, inverseOf, bijection) * describe inputAxes and outputAxes * add new examples --- latest/index.bs | 328 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 296 insertions(+), 32 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 63d2ccf1..c0dbf394 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -269,9 +269,12 @@ space. For the above example, the `"x"` dimension is the first dimension. ### Array space and indexing -Every array has a default coordinate system we call "array space" whose parameters need not be explicitly defined. +Every array has a default coordinate system called "array space" 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"`. For example, a 3D array space at path `/my/data/array` is: +default "name"s. The ith axis has `"name":"dim_i"`. + +
+For example, a 3D array space at path `/my/data/array` is: ```json { @@ -284,30 +287,35 @@ default "name"s. The ith axis has `"name":"dim_i"`. For example, a 3D array spac } ``` -though this object should not and need not explicitly appear in metadata. The dimensionality of each array space -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 (row-major) are stored contiguously. For an array in "F" (column-major) order, the elements of the first -dimension are stored contiguously. +though this object should not and need not explicitly appear in metadata. +
-For example, if `/my/data/array/.zarray` contains: -```json -{ - "chunks": [ 4, 3, 5 ], - "compressor": null, - "dtype": "|u1", - "fill_value": 0, - "filters": null, - "order": "C", - "shape": [ 4, 3, 5 ], - "zarr_format": 2 -} -``` +The dimensionality of each array space 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 (row-major) are stored contiguously. For an array in "F" +(column-major) order, the elements of the first dimension are stored contiguously. -Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5. +
+ For example, if `/my/data/array/.zarray` contains: + + ```json + { + "chunks": [ 4, 3, 5 ], + "compressor": null, + "dtype": "|u1", + "fill_value": 0, + "filters": null, + "order": "C", + "shape": [ 4, 3, 5 ], + "zarr_format": 2 + } + ``` + + Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5. +
### Coordinate convention @@ -328,15 +336,22 @@ half-open interval `[-0.5, 0.5) x [-0.5, 0.5)`. See chapter 4 and figure 4.1 of "coordinateTransformations" describe map between two coordinate spaces (defined by "axes"). For example, to map a discrete data space of an array to the corresponding physical space. Coordinate transforms are in the "forward" direction. They represent functions from *points* in the -input space to *points* in the output space. Implementations SHOULD be able to compute and apply the inverse -of some coordinate transformations (see the Details section below will note those transform types). +input space to *points* in the output space. + +Implementations SHOULD be able to compute and apply the inverse of some coordinate transformations whose +inverses are computable in closed-form (see the Details section below will note those transform types). If an +operation is requested that requires the inverse of a transformation that can not be inverted in closed-form, +implementations MAY estimate their inverses, or MAY output a warning that the requested operation is +unsupported. - 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` (see details). - SHOULD contain the field "input", unless part of a `sequence` (see details). +- MAY contain the fields "inputAxes" and / or "outputAxes" (see below). - MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. +- MAY contain the fields "inputAxes" and "outputAxes" (see below). - Parameter values MUST be compatible with input and output space dimensionality (see details). @@ -392,8 +407,7 @@ Conforming readers: - -### Details +### 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 @@ -404,8 +418,40 @@ otherwise it is given by the length of "axes" for the coordinate system with the #### identity -`identity` transformation maps input coordinates to output coordinates without modification. `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. + +
+ +```json +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { "type" : "identity", "input" : "in", "output" : "out" } + ] +} +``` + +defines the function: + +``` +x = i +y = j +``` + +
+ +`identity` transformations may be used to implement axis permutations by using the `inputAxes` and `outputAxes` +fields (see below). + + +#### axisPermutation + +(The same as identity, but give a different type to mark what it's doing) #### translation @@ -595,11 +641,42 @@ and columns. `rotation` transformations are invertible. #### inverseOf -A +An `inverseOf` transformation contain 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). + +
+ 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 should 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. +
+ + +
+For example + +```json +"coordinateSystems" : [ + { "name" : "moving", "axes" : [{"name" : "x-moving"}, {"name":"y-moving"}] } + { "name" : "fixed", "axes" : [{"name" : "x-fixed"}, {"name":"y-fixed"}] } +], +"coordinateTransformations" : [ + { + "type": "inverseOf", + "transformation" : { "type": "displacements", "path": "/path/to/displacements" } + "input" : "moving", + "output" : "fixed", + } +] +``` +
#### sequence -A `sequence` transformation consist of an ordered array of coordinate transformations. +A `sequence` transformation consists of an ordered array of coordinate transformations. `sequence` transformations are invertible if and only if every coordinate transform in the array is invertible. @@ -631,6 +708,77 @@ y = (j + 0.9) * 3 +This is a valid `sequence` transformation: + +```json +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "coordinateTransformations" : [ + { + "type: "sequence", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} + ] + } + ] +} +``` + +This is also a valid transformation: + +```json +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "0"}, {"name" : "1"}, {"name" : "2"}, {"name" : "3"}] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } + ], + "coordinateTransformations" : [ + { + "type: "sequence", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "coordinates", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} + { "type" : "scale", "scale" : [2.0], "input" : ["1"], "output" : ["z"]} + ] + } + ] +} +``` + + +This is an invalid `sequence` transform: + +```json +{ + "spaces" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "transforms" : [ + { + "type: "sequence", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["z"]} + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["0"], "outputAxes" : ["y"]} + ] + } + ] +} +``` + +for two reasons. First because input"0" used by the scale transformation is not an axis of the `sequence` transformation's +`input`. Second, the "x" axis of the `output` does not appear in the `output` of any child transformation. + + #### coordinates and displacements `coordinates` and `displacements` transformations store coordinates or displacements in an array. @@ -892,6 +1040,122 @@ the input and output of the `forward` and `inverse` transformations are understo +### `inputAxes` and `outputAxes` + +`inputAxes` and `outputAxes` are optional fields for transformations to express that it applies to a subset +or reording of the axes of the input or output coordinate system, respectively. If not present, the `inputAxes` +(`outputAxes`) are understood to be a list of the `input` (`output`) coordinate systems axis names, in the +order they appear in the coordinate system's `axes` list. If present: + +- the values for `inputAxes` and `outputAxes` MUST be a list of strings. +- every string in the `inputAxes` list MUST appear as a name for an axes of the transformations input coordinate system. +- every string in the `outputAxes` list MUST appear as a name for an axes of the transformations output coordinate system. +- every axis name in the output coordinate system MUST appear either as an axis name of the input coordinate system or in the list of `outputAxes` + +
+ +An `identity` may be used to permute axes by setting +the `inputAxes` and / or `outputAxes` fields appropriately (see below). + +```json +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { + "type" : "identity", + "input" : "in", + "output" : "out", + "inputAxes" : ["i", "j"], + "outputAxes" : ["y", "x"] + } + ] +} +``` + +defines the function: + +``` +x = j +y = i +``` + +
+ + +
+ A valid example + + ```json + "coordinateSystems" : [ + { "name " : "in", "axes" : [ {"name" : "x", "name" : "y" }] } + { "name " : "out", "axes" : [ {"name" : "x", "name" : "y" }] } + ] + { + "type": "scale", + "scale" : [2], + "input" : "in" + "output" : "out", + "inputAxes" : ["y"], + "outputAxes" : ["y"] + } + ``` + scales the `y` axis only. + +
+ +
+ An invalid example + + ```json + "coordinateSystems" : [ + { "name " : "in", "axes" : [ {"name" : "i", "name" : "j" }] } + { "name " : "out", "axes" : [ {"name" : "x", "name" : "y" }] } + ] + "coordinateTransformations" : [ + { + "type": "scale", + "scale" : [2], + "input" : "in" + "output" : "out", + "inputAxes" : ["j"], + "outputAxes" : ["y"] + } + ] + ``` + + Is invalid because the axis name `x` of the output coordinate system does not appear in the list of `outputAxes`, + nor does it appear in the axis names of the coordinate system `in`. Instead, a `sequence` transformation can + be defined, such that every axis in the output space appears in at least one of the `outputAxes` of the + sequence's transformations. For example (asuming the same coordinate systems as above): + + ```json + { + "type": "sequence", + "transformations" : [ + { + "type": "scale", + "scale" : [2], + "inputAxes" : ["j"], + "outputAxes" : ["y"] + }, + { + "type": "identity", + "inputAxes" : ["i"], + "outputAxes" : ["x"] + } + ], + "input" : "in" + "output" : "out", + } + ``` + + is valid. + +
+ ### Points, coordinates, direction, and indexing @@ -938,7 +1202,7 @@ transformation type, for example: Every coordinate transformation MUST specify its output coordinate system using `output` to with a string value corresponding to the name of an output space or with an array of strings to provide a list of axis names. -Similarly, every coordinate transformation SHOULD specify its input coordinate system either using `input`. If +Similarly, every coordinate transformation SHOULD specify its input coordinate system using the `input`. If `input` is not provided the transformation's input is in array-space. Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. From df470d7a9d3ba6284f74dafc6419ad4df9b89a62 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Tue, 6 Sep 2022 11:32:55 -0400 Subject: [PATCH 18/50] Add "coordinateSystems" and "coordinateTransformations" fields for examples * some clean up --- latest/index.bs | 122 ++++++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index c0dbf394..9b27bc22 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -338,11 +338,6 @@ For example, to map a discrete data space of an array to the corresponding physi Coordinate transforms are in the "forward" direction. They represent functions from *points* in the input space to *points* in the output space. -Implementations SHOULD be able to compute and apply the inverse of some coordinate transformations whose -inverses are computable in closed-form (see the Details section below will note those transform types). If an -operation is requested that requires the inverse of a transformation that can not be inverted in closed-form, -implementations MAY estimate their inverses, or MAY output a warning that the requested operation is -unsupported. - MUST contain the field "type". @@ -350,8 +345,8 @@ unsupported. - MUST contain the field "output", unless part of a `sequence` (see details). - SHOULD contain the field "input", unless part of a `sequence` (see details). - MAY contain the fields "inputAxes" and / or "outputAxes" (see below). -- MAY contain the field "name". Its value MUST be unique across all "name" fields for coordinate transformations. - MAY contain the fields "inputAxes" and "outputAxes" (see below). +- 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).
@@ -405,6 +400,12 @@ Conforming readers: - MAY enable applying transformations to points - MAY enable applying transformations to images +### Transformation inverses + +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. ### Transformation types @@ -446,8 +447,7 @@ y = j `identity` transformations may be used to implement axis permutations by using the `inputAxes` and `outputAxes` -fields (see below). - +fields (see below [`inputAxes` and `outputAxes`](#inputaxes-and-outputaxes)). #### axisPermutation @@ -473,12 +473,18 @@ invertible. For example ```json -{ - "type": "translation", - "translation": [9, -1], - "input" : ["i", "j"], - "output" : ["x", "y"] -} +"coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } + { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } +], +"coordinateTransformations" : [ + { + "type": "translation", + "translation": [9, -1], + "input" : "ij", + "output" : "xy" + } +] ``` defines the function: @@ -508,12 +514,18 @@ transformations are invertible. For example ```json -{ - "type": "scale", - "scale": [3, 2], - "input" : ["i", "j"], - "output" : ["x", "y"] -} +"coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } + { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } +], +"coordinateTransformations" : [ + { + "type": "scale", + "scale": [3, 2], + "input" : "ij", + "output" : "xy" + } +] ``` defines the function: @@ -547,12 +559,18 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
A 2D-2D example: ```json - { - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6], - "input" : ["i", "j"], - "output" : ["x", "y"] - } + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } + { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } + ], + "coordinateTransformations" : [ + { + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6], + "input" : "ij", + "output" : "xy" + } + ] ``` defines the function: @@ -566,12 +584,18 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
An example with two dimensional inputs and three dimensional outputs. ```json - { - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "input" : ["i", "j"], - "output" : ["x", "y", "z"] - } + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } + { "name" : "xyz", "axes" : [{"name" : "x"}, {"name":"y"}, {"name":"z"}] } + ], + "coordinateTransformations" : [ + { + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "input" : "ij", + "output" : "xyz" + } + ] ``` defines the function: @@ -587,8 +611,8 @@ The matrix may be stored as a 2D array or as a 1D array (row-major). { "type": "affine", "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - "input" : ["i", "j"], - "output" : ["x", "y", "z"] + "input" : "ij", + "output" : "xyz" } ```
@@ -622,18 +646,24 @@ and columns. `rotation` transformations are invertible.
A 2D example ```json - { - "type": "rotation", - "affine": [0, 1, 1, 0], - "input" : ["i", "j"], - "output" : ["x", "y"] - } + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } + { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } + ], + "coordinateTransformations" : [ + { + "type": "rotation", + "affine": [0, -1, 1, 0], + "input" : "ij", + "output" : "xy" + } + ] ``` defines the function: ``` - x = 0*i + 1*j + x = 0*i - 1*j y = 1*i + 0*j ```
@@ -641,7 +671,7 @@ and columns. `rotation` transformations are invertible. #### inverseOf -An `inverseOf` transformation contain another transformation (often non-linear), and indicates that +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). @@ -1047,10 +1077,10 @@ or reording of the axes of the input or output coordinate system, respectively. (`outputAxes`) are understood to be a list of the `input` (`output`) coordinate systems axis names, in the order they appear in the coordinate system's `axes` list. If present: -- the values for `inputAxes` and `outputAxes` MUST be a list of strings. -- every string in the `inputAxes` list MUST appear as a name for an axes of the transformations input coordinate system. -- every string in the `outputAxes` list MUST appear as a name for an axes of the transformations output coordinate system. -- every axis name in the output coordinate system MUST appear either as an axis name of the input coordinate system or in the list of `outputAxes` +- Values for `inputAxes` and `outputAxes` MUST be a list of strings. +- Every string in the `inputAxes` list MUST be the name of an axis of the transformation's input coordinate system. +- Every string in the `outputAxes` list MUST be the name of an axis of the transformation's output coordinate system. +- Every axis name in the output coordinate system MUST in the list of `outputAxes`.
From 99f3a4e8254c1c6bc861c8c12e6c884262c8c831 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Wed, 7 Sep 2022 16:37:52 -0400 Subject: [PATCH 19/50] coordinate transformation updates: * add mapIndex, mapAXis * add examples * affine stored as flat array only --- .../examples/subspace/subspaceMultidim.json | 64 ++++ latest/examples/subspace/subspacePermute.json | 26 ++ .../examples/transformations/affine2d2d.json | 14 + .../examples/transformations/affine2d3d.json | 14 + latest/examples/transformations/identity.json | 9 + latest/examples/transformations/mapAxis1.json | 23 ++ latest/examples/transformations/mapAxis2.json | 23 ++ .../examples/transformations/mapIndex1.json | 23 ++ .../examples/transformations/mapIndex2.json | 23 ++ latest/examples/transformations/scale.json | 14 + .../examples/transformations/translation.json | 14 + latest/index.bs | 290 +++++++++++------- 12 files changed, 424 insertions(+), 113 deletions(-) create mode 100644 latest/examples/subspace/subspaceMultidim.json create mode 100644 latest/examples/subspace/subspacePermute.json create mode 100644 latest/examples/transformations/affine2d2d.json create mode 100644 latest/examples/transformations/affine2d3d.json create mode 100644 latest/examples/transformations/identity.json create mode 100644 latest/examples/transformations/mapAxis1.json create mode 100644 latest/examples/transformations/mapAxis2.json create mode 100644 latest/examples/transformations/mapIndex1.json create mode 100644 latest/examples/transformations/mapIndex2.json create mode 100644 latest/examples/transformations/scale.json create mode 100644 latest/examples/transformations/translation.json diff --git a/latest/examples/subspace/subspaceMultidim.json b/latest/examples/subspace/subspaceMultidim.json new file mode 100644 index 00000000..cfbe5dae --- /dev/null +++ b/latest/examples/subspace/subspaceMultidim.json @@ -0,0 +1,64 @@ +{ + "coordinateSystems" : [ + { "name " : "in", "axes" : [ {"name" : "0", "name" : "1", "name": "2", "name": "3", "name": "4" }] }, + { "name " : "out", "axes" : [ {"name" : "x", "name" : "y", "name" : "z" }] } + ], + "coordinateTransformations" : [ + { + "type" : "sequence", + "name" : "5D-to-3D", + "input" : "in", + "output" : "out", + "transformations" : [ + { + "type": "mapIndex", + "inputAxes" : ["0", "1"], + "outputAxes" : ["x", "y"] + }, + { + "type": "scale", + "scale" : [2], + "inputAxes" : ["3"], + "outputAxes" : ["z"] + } + ] + }, + { + "type" : "sequence", + "name" : "5D-to-3D-not-contiguous", + "input" : "in", + "output" : "out", + "transformations" : [ + { + "type": "mapIndex", + "inputAxes" : ["0", "2"], + "outputAxes" : ["x", "z"] + }, + { + "type": "scale", + "scale" : [2], + "inputAxes" : ["1"], + "outputAxes" : ["y"] + } + ] + }, + { + "type" : "sequence", + "name" : "5D-to-3D-not-contiguous", + "input" : "in", + "output" : "out", + "transformations" : [ + { + "type": "mapAxes", + "map" : {"0":"x", "2":"z"} + }, + { + "type": "scale", + "scale" : [2], + "inputAxes" : ["1"], + "outputAxes" : ["y"] + } + ] + } + ] +} diff --git a/latest/examples/subspace/subspacePermute.json b/latest/examples/subspace/subspacePermute.json new file mode 100644 index 00000000..88c6b4ea --- /dev/null +++ b/latest/examples/subspace/subspacePermute.json @@ -0,0 +1,26 @@ +{ + "coordinateSystems" : [ + { "name " : "in", "axes" : [ {"name" : "i", "name" : "j" }] }, + { "name " : "out", "axes" : [ {"name" : "x", "name" : "y" }] } + ], + "coordinateTransformations" : [ + { + "type" : "sequence", + "input" : "in", + "output" : "out", + "transformations" : [ + { + "type": "identity", + "inputAxes" : ["j"], + "outputAxes" : ["x"] + }, + { + "type": "scale", + "scale" : [2], + "inputAxes" : ["i"], + "outputAxes" : ["y"] + } + ] + } + ] +} diff --git a/latest/examples/transformations/affine2d2d.json b/latest/examples/transformations/affine2d2d.json new file mode 100644 index 00000000..cfbd8eab --- /dev/null +++ b/latest/examples/transformations/affine2d2d.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name" : "j"}] }, + { "name" : "xy", "axes" : [{"name" : "x"}, {"name" : "y"}] } + ], + "coordinateTransformations" : [ + { + "type" : "affine", + "affine" : [1, 2, 3, 4, 5, 6], + "input" : "ij", + "output" : "xy" + } + ] +} diff --git a/latest/examples/transformations/affine2d3d.json b/latest/examples/transformations/affine2d3d.json new file mode 100644 index 00000000..457f93a4 --- /dev/null +++ b/latest/examples/transformations/affine2d3d.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name" : "j"}] }, + { "name" : "xyz", "axes" : [{"name" : "x"}, {"name" : "y"}, {"name" : "z"}] } + ], + "coordinateTransformations" : [ + { + "type" : "affine", + "affine" : [1, 2, 3, 4, 5, 6, 7, 8, 9], + "input" : "ij", + "output" : "xyz" + } + ] +} diff --git a/latest/examples/transformations/identity.json b/latest/examples/transformations/identity.json new file mode 100644 index 00000000..2b488b0d --- /dev/null +++ b/latest/examples/transformations/identity.json @@ -0,0 +1,9 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { "type" : "identity", "input" : "in", "output" : "out" } + ] +} diff --git a/latest/examples/transformations/mapAxis1.json b/latest/examples/transformations/mapAxis1.json new file mode 100644 index 00000000..6ce023f7 --- /dev/null +++ b/latest/examples/transformations/mapAxis1.json @@ -0,0 +1,23 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out1", "axes" : [ {"name" : "x"}, {"name" : "y"} ]}, + { "name" : "out2", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { + "name" : "equivalent to identity", + "type" : "mapAxis", + "mapAxis" : { "x":"i", "y":"j" }, + "input" : "in", + "output" : "out1" + }, + { + "name" : "permutation", + "type" : "mapAxis", + "mapAxis" : { "x":"j", "y":"i" }, + "input" : "in", + "output" : "out2" + } + ] +} diff --git a/latest/examples/transformations/mapAxis2.json b/latest/examples/transformations/mapAxis2.json new file mode 100644 index 00000000..7932ed5f --- /dev/null +++ b/latest/examples/transformations/mapAxis2.json @@ -0,0 +1,23 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "a"}, {"name" : "b"}]}, + { "name" : "out_down", "axes" : [ {"name" : "x"}]}, + { "name" : "out_up", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ]} + ], + "coordinateTransformations" : [ + { + "name" : "projection down", + "type" : "mapAxis", + "mapAxis" : { "x" : "b" }, + "input" : "in", + "output" : "out_down" + }, + { + "name" : "projection up", + "type" : "mapAxis", + "mapAxis" : { "x":"a", "y":"b", "z":"b" }, + "input" : "in", + "output" : "out_up" + } + ] +} diff --git a/latest/examples/transformations/mapIndex1.json b/latest/examples/transformations/mapIndex1.json new file mode 100644 index 00000000..16f70697 --- /dev/null +++ b/latest/examples/transformations/mapIndex1.json @@ -0,0 +1,23 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out1", "axes" : [ {"name" : "x"}, {"name" : "y"} ]}, + { "name" : "out2", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { + "name" : "equivalent to identity", + "type" : "mapIndex", + "mapIndex" : [ 0, 1 ], + "input" : "in", + "output" : "out1" + }, + { + "name" : "permutation", + "type" : "mapIndex", + "mapIndex" : [ 1, 0 ], + "input" : "in", + "output" : "out2" + } + ] +} diff --git a/latest/examples/transformations/mapIndex2.json b/latest/examples/transformations/mapIndex2.json new file mode 100644 index 00000000..e28f8f4d --- /dev/null +++ b/latest/examples/transformations/mapIndex2.json @@ -0,0 +1,23 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "a"}, {"name" : "b"}]}, + { "name" : "out_down", "axes" : [ {"name" : "x"}]}, + { "name" : "out_up", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ]} + ], + "coordinateTransformations" : [ + { + "name" : "projection down", + "type" : "mapIndex", + "mapIndex" : [ 1 ], + "input" : "in", + "output" : "out_down" + }, + { + "name" : "projection up", + "type" : "mapIndex", + "mapIndex" : [ 0, 1, 1 ], + "input" : "in", + "output" : "out_up" + } + ] +} diff --git a/latest/examples/transformations/scale.json b/latest/examples/transformations/scale.json new file mode 100644 index 00000000..98c633a1 --- /dev/null +++ b/latest/examples/transformations/scale.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, + { "name" : "out", "axes" : [{"name" : "x"}, {"name":"y"}] } + ], + "coordinateTransformations" : [ + { + "type": "scale", + "scale": [3.12, 2], + "input" : "in", + "output" : "out" + } + ] +} diff --git a/latest/examples/transformations/translation.json b/latest/examples/transformations/translation.json new file mode 100644 index 00000000..8ea78a9c --- /dev/null +++ b/latest/examples/transformations/translation.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, + { "name" : "out", "axes" : [{"name" : "x"}, {"name":"y"}] } + ], + "coordinateTransformations" : [ + { + "type": "translation", + "translation": [9, -1.42], + "input" : "in", + "output" : "out" + } + ] +} diff --git a/latest/index.bs b/latest/index.bs index 9b27bc22..e72ed636 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -245,13 +245,15 @@ keys as specified below for discovering certain types of data, especially images If part of [[#multiscale-md]], the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. -"space" metadata {#space-md} +"coordinateSystem" metadata {#coord-sys-md} -------------------------- -A "space" is a collection of "axes" / dimensions with a name and defines a coordinate system. Each space: +A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: - MUST contain the field "name", that gives the space name. The values MUST be non-empty and unique. - MUST contain the field "axes", whose value is a valid set of "axes". +
+ ```json { "name" : "volume_micrometers", @@ -262,19 +264,19 @@ A "space" is a collection of "axes" / dimensions with a name and defines a coord ] } ``` +
-Order of the `"axes"` list matters and defines the index of each array dimension and coordinates for "points" in that -space. For the above example, the `"x"` dimension is the first dimension. +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 first dimension. -### Array space and indexing +### Array coordinate systems and indexing -Every array has a default coordinate system called "array space" 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"`. +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"`.
-For example, a 3D array space at path `/my/data/array` is: +For example, a 3D array at path `/my/data/array` defines the coordinate system: ```json { @@ -291,7 +293,7 @@ though this object should not and need not explicitly appear in metadata.
-The dimensionality of each array space equals the dimensionality of its corresponding zarr array. The axis with +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), @@ -317,6 +319,22 @@ the last dimension of an array in "C" order (row-major) are stored contiguously. Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5.
+The name and axes names MAY be customized by including a `arrayCoordinateSystem` field in +the `.zattr` metadata 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"`. + +```json +"arrayCoordinateSystem" : { + "name" : "myDataArray", + "axes" : [ + {"name": "i", "type": "array"}, + {"name": "j", "type": "array"}, + {"name": "k", "type": "array"} + ] +} +``` + ### Coordinate convention @@ -325,27 +343,25 @@ the last dimension of an array in "C" order (row-major) are stored contiguously. It is vital to consistently define relationship between the discrete/arrray 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 space -`(0.0, 0.0)` (when the transformation is the identity). The continuous rectangle of the pixel is given by the +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)`. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. "coordinateTransformations" metadata {#trafo-md} ------------------------------------- -"coordinateTransformations" describe map between two coordinate spaces (defined by "axes"). -For example, to map a discrete data space of an array to the corresponding physical space. +"coordinateTransformations" describe map between two coordinate systems (defined by "axes"). +For example, to map an array's discrete coordinate system to the 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` (see details). - SHOULD contain the field "input", unless part of a `sequence` (see details). - MAY contain the fields "inputAxes" and / or "outputAxes" (see below). -- MAY contain the fields "inputAxes" and "outputAxes" (see below). - 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). @@ -353,6 +369,12 @@ input space to *points* in the output space.
`identity` The identity transformation is the default transformation and is typically not explicitly defined. +
`mapIndex` + `"mapIndex":List[number]` + A `mapIndex` transformation specifies an axis permutation by reordering the input axes. +
`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 @@ -425,17 +447,10 @@ system. `identity` transformations are invertible.
-```json -{ - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} - ], - "coordinateTransformations" : [ - { "type" : "identity", "input" : "in", "output" : "out" } - ] -} -``` +
+path: examples/transformations/identity.json
+highlight: json
+
defines the function: @@ -446,12 +461,112 @@ y = j
-`identity` transformations may be used to implement axis permutations by using the `inputAxes` and `outputAxes` -fields (see below [`inputAxes` and `outputAxes`](#inputaxes-and-outputaxes)). +#### mapIndex + +`mapIndex` transformations describe axis permutations as a reordering of the input dimensions. +Transformations MUST include a `mapIndex` field whose value is an array of integers. If the ith element of the array is j, it +means that output dimension i comes from input dimension j. The length of the `mapIndex` array MUST be equal to the +dimensionality of the output coordinate system. If the input coordinate system has dimension N, then every integer in that array +MUST be less than N. -#### axisPermutation +
+ +
+path: examples/transformations/mapIndex1.json
+highlight: json
+
-(The same as identity, but give a different type to mark what it's doing) +The "equivalent to identity" transformation defines the function: + +``` +x = i +y = j +``` + +and the "permutation" transformation defines the function + +``` +x = j +y = i +``` + +
+ +
+ +
+path: examples/transformations/mapIndex2.json
+highlight: json
+
+ +The "projection_down" transformation defines the function: + +``` +x = b +``` + +and the "projection_up" transformation defines the function: + +``` +x = a +y = b +z = b +``` + +
+ +#### 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. + +
+ +
+path: examples/transformations/mapAxis1.json
+highlight: json
+
+ +The "equivalent to identity" transformation defines the function: + +``` +x = i +y = j +``` + +and the "permutation" transformation defines the function + +``` +x = j +y = i +``` + +
+ +
+ +
+path: examples/transformations/mapAxis2.json
+highlight: json
+
+ +The "projection_down" transformation defines the function: + +``` +x = b +``` + +and the "projection_up" transformation defines the function: + +``` +x = a +y = b +z = b +``` +
#### translation @@ -471,28 +586,20 @@ invertible.
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
-For example -```json -"coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } - { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } -], -"coordinateTransformations" : [ - { - "type": "translation", - "translation": [9, -1], - "input" : "ij", - "output" : "xy" - } -] -``` +
+ +
+path: examples/transformations/translation.json
+highlight: json
+
defines the function: ``` x = i + 9 -y = j - 1 +y = j - 1.42 ``` +
#### scale @@ -512,28 +619,20 @@ transformations are invertible.
The scale parameters stored as a JSON list of numbers. The list MUST have length `N`.
-For example -```json -"coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } - { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } -], -"coordinateTransformations" : [ - { - "type": "scale", - "scale": [3, 2], - "input" : "ij", - "output" : "xy" - } -] -``` +
+ +
+path: examples/transformations/scale.json
+highlight: json
+
defines the function: ``` x = 3 * i -y = 2 * j +y = 2.12 * j ``` +
#### affine @@ -544,34 +643,22 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
path
The path to an array containing the affine parameters. - The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*(M+1)`. - If 2D, its shape MUST be `N x M+1`.
+ The array at this path MUST be 1D, its length MUST be `N*(M+1)`.
url
An optional URL to the container in which the affine array is stored. If not provided, the provided `path` MUST exist in this container.
affine
The affine parameters stored in JSON. The matrix may be stored - as a row-major flat list of numbers the list MUST be length `N*(M+1)`. If stored as a list of - lists, the outer list MUST be length `N`, and all inner lists MUST be - length `(M+1)`. In this case, inner lists contain rows of the matrix.
+ as a row-major flat list of numbers the list MUST be length `N*(M+1)`.
A 2D-2D example: - ```json - "coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } - { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } - ], - "coordinateTransformations" : [ - { - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6], - "input" : "ij", - "output" : "xy" - } - ] - ``` + +
+    path: examples/transformations/affine2d2d.json
+    highlight: json
+    
defines the function: @@ -583,20 +670,10 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
An example with two dimensional inputs and three dimensional outputs. - ```json - "coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } - { "name" : "xyz", "axes" : [{"name" : "x"}, {"name":"y"}, {"name":"z"}] } - ], - "coordinateTransformations" : [ - { - "type": "affine", - "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "input" : "ij", - "output" : "xyz" - } - ] - ``` +
+    path: examples/transformations/affine2d3d.json
+    highlight: json
+    
defines the function: @@ -605,16 +682,6 @@ The matrix may be stored as a 2D array or as a 1D array (row-major). y = 4*i + 5*j + 6 z = 7*i + 8*j + 9 ``` - - The same transformation may also be written as: - ```json - { - "type": "affine", - "affine": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - "input" : "ij", - "output" : "xyz" - } - ```
@@ -630,17 +697,13 @@ and columns. `rotation` transformations are invertible.
path
The path to an array containing the affine parameters. - The array at this path MUST be 1D or 2D. If 1D, its length MUST be `N*N`. - If 2D, its shape MUST be `N x N`.
+ The array at this path MUST be 1D. Its length MUST be `N*N`.
url
An optional URL to the container in which the affine array is stored. If not provided, the provided `path` MUST exist in this container.
rotation
-
The rotation parameters stored in JSON. The matrix may be stored - as a row-major flat list of numbers. Int that case, the list MUST be - length `N*N`. The matrix may bes stored a list of lists, the outer list MUST be - length `N`, and all inner lists MUST be length `N`. In this case, - inner lists contain rows of the matrix.
+
The rotation parameters stored in JSON. The matrix is stored + as a row-major flat list of numbers, its length MUST be `N*N`.
@@ -734,6 +797,7 @@ x = (i + 0.1) * 2 y = (j + 0.9) * 3 ``` +and is invertible.
From de73c6891f5192067a1d048ca0f88fddf160ab52 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 11 Sep 2022 16:38:59 -0400 Subject: [PATCH 20/50] update multiscales_example --- .../multiscales_example.json | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/latest/examples/multiscales_strict/multiscales_example.json b/latest/examples/multiscales_strict/multiscales_example.json index 73e5286c..beed1cd7 100644 --- a/latest/examples/multiscales_strict/multiscales_example.json +++ b/latest/examples/multiscales_strict/multiscales_example.json @@ -3,44 +3,50 @@ { "version": "0.5-dev", "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"} + "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) + // the voxel size for the first scale level (0.5 micrometer), time unit (0.1 milliseconds) "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" : "example" }] }, { "path": "1", "coordinateTransformations": [{ - // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer) + // the voxel size for the second scale level (downscaled by a factor of 2 -> 1 micrometer), time unit (0.1 milliseconds) "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" : "example" }] }, { "path": "2", "coordinateTransformations": [{ - // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer) + // the voxel size for the third scale level (downscaled by a factor of 4 -> 2 micrometer), time unit (0.1 milliseconds) "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" : "example" }] } ], - "coordinateTransformations": [{ - // the time unit (0.1 milliseconds), which is the same for each scale level - "type": "scale", - "scale": [0.1, 1.0, 1.0, 1.0, 1.0] - }], "type": "gaussian", "metadata": { "description": "the fields in metadata depend on the downscaling implementation. Here, the parameters passed to the skimage function are given", @@ -51,4 +57,4 @@ } } ] -} \ No newline at end of file +} From 163c8f4c82554d6dcdd332c9a8d6c3fa01991fe4 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 11 Sep 2022 16:40:49 -0400 Subject: [PATCH 21/50] rename dimensionWise byDimension * sequence does not have by-dimension behavior --- latest/index.bs | 125 ++++++++++-------------------------------------- 1 file changed, 25 insertions(+), 100 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index e72ed636..e4c6f5df 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -394,8 +394,7 @@ input space to *points* in the output space. 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. An empty sequence is the identity transformation. + A sequence of transformations, Applying the sequence applies the composition of all transforms in the list, in order.
`displacements` `"url":str`
`"path":str`
`"interpolation":str`
Displacement field transformation in this container (path) or another container located at (url). @@ -408,7 +407,7 @@ input space to *points* in the output space.
`bijection` `"forward":Transform`
`"inverse":Transform`
Explicitly define an invertible transformation by providing a forward transformation and its inverse. -
`dimensionWise` +
`byDimension` `"transformations":List[Transformation]` Define a high dimensional transformation using lower dimensional transformations on subsets of dimensions. @@ -769,26 +768,24 @@ For example #### sequence -A `sequence` transformation consists of an ordered array of coordinate transformations. -`sequence` transformations are invertible if and only if every coordinate transform in the array -is invertible. +A `sequence` transformation consists of an ordered array of coordinate transformations, and is invertible if and only if every +coordinate transform in the array is invertible. -The `input` and `output` fields MAY be omitted for transformations that are in the list -of transformations of a `sequence`. +
+
transformations
+
A non-empty array of transformations.
+
+ +The `input` and `output` fields MAY be omitted for transformations that are in the list of transformations of a `sequence`.
-For example, this sequence: -```json -{ - "type" : "sequence", - "transformations" : [ - { "type": "translation", "translation" : [0.1, 0.9] }, - { "type": "scale", "scale" : [2, 3] } - ] - "input" : ["i", "j"], - "output" : ["x", "y"] -} -``` + +This sequence: + +
+path: examples/transformations/sequence.json
+highlight: json
+
describes the function @@ -801,83 +798,11 @@ and is invertible.
- -This is a valid `sequence` transformation: - -```json -{ - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } - ], - "coordinateTransformations" : [ - { - "type: "sequence", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} - { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} - ] - } - ] -} -``` - -This is also a valid transformation: - -```json -{ - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "0"}, {"name" : "1"}, {"name" : "2"}, {"name" : "3"}] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } - ], - "coordinateTransformations" : [ - { - "type: "sequence", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} - { "type" : "scale", "scale" : [2.0], "input" : ["1"], "output" : ["z"]} - ] - } - ] -} -``` - - -This is an invalid `sequence` transform: - -```json -{ - "spaces" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } - ], - "transforms" : [ - { - "type: "sequence", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["z"]} - { "type" : "scale", "scale" : [2.0], "inputAxes" : ["0"], "outputAxes" : ["y"]} - ] - } - ] -} -``` - -for two reasons. First because input"0" used by the scale transformation is not an axis of the `sequence` transformation's -`input`. Second, the "x" axis of the `output` does not appear in the `output` of any child transformation. - - #### coordinates and displacements -`coordinates` and `displacements` transformations store coordinates or displacements in an array. -Applying the transformation amounts to looking up the appropriate locations in the array, and interpolating. -`coordinates` and `displacements` transformations are note invertible in general, but implementations +`coordinates` and `displacements` transformations store coordinates or displacements in an array and interpret them as a transformation. +Applying the transformation amounts to looking up the appropriate locations in the array and interpolating. +`coordinates` and `displacements` transformations are not invertible in general, but implementations MAY approximate their inverses. Metadata for these coordinate transforms have the following field:
@@ -918,7 +843,7 @@ For example, in 1D: { "name" : "a coordinate field transform", "type": "coordinates", - "path" : "coordinates", + "path" : "i2xCoordinates", "input" : ["i"], "output" : ["x"], "interpolation" : "nearest" @@ -1001,9 +926,9 @@ The transformation specifies linear interpolation, which in this case yields input point, hence the output is `1.0 + (-0.5) = 0.5`. -#### dimensionWise +#### byDimension -`dimensionWise` transformations build a high dimensional transformation using lower dimensional transformations +`byDimension` transformations build a high dimensional transformation using lower dimensional transformations on subsets of dimensions.
@@ -1027,7 +952,7 @@ This is a valid `dimensionWise` transformation: "transforms" : [ { "name: "in2out", - "type: "dimensionWise", + "type: "byDimension", "input" : "in", "output" : "out", "transformations" : [ @@ -1050,7 +975,7 @@ This is also a valid transformation: "transforms" : [ { "name: "in2out", - "type: "dimensionWise", + "type: "byDimension", "input" : "in", "output" : "out", "transformations" : [ From 7b35c28f87dd9517b3d6e69cd0e82bc8ca422542 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 11 Sep 2022 16:41:15 -0400 Subject: [PATCH 22/50] add more transformation examples --- .../examples/transformations/coordinates1d.json | 14 ++++++++++++++ .../transformations/displacement1d.json | 14 ++++++++++++++ latest/examples/transformations/sequence.json | 17 +++++++++++++++++ .../transformations/sequenceSubspace1.json | 17 +++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 latest/examples/transformations/coordinates1d.json create mode 100644 latest/examples/transformations/displacement1d.json create mode 100644 latest/examples/transformations/sequence.json create mode 100644 latest/examples/transformations/sequenceSubspace1.json diff --git a/latest/examples/transformations/coordinates1d.json b/latest/examples/transformations/coordinates1d.json new file mode 100644 index 00000000..99a87f7e --- /dev/null +++ b/latest/examples/transformations/coordinates1d.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "i", "axes" : [{"name" : "i"}] }, + { "name" : "x", "axes" : [{"name" : "x"}] } + ], + "coordinateTransformations" : [{ + "name" : "a coordinate field transform", + "type": "coordinates", + "path" : "i2xCoordinates", + "input" : "i", + "output" : "x", + "interpolation" : "nearest" + }] +} diff --git a/latest/examples/transformations/displacement1d.json b/latest/examples/transformations/displacement1d.json new file mode 100644 index 00000000..8af48611 --- /dev/null +++ b/latest/examples/transformations/displacement1d.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "i", "axes" : [{"name" : "i"}] }, + { "name" : "x", "axes" : [{"name" : "x"}] } + ], + "coordinateTransformations" : [{ + "name" : "a displacement field transform", + "type": "displacements", + "path" : "i2xCoordinates", + "input" : "i", + "output" : "x", + "interpolation" : "nearest" + }] +} diff --git a/latest/examples/transformations/sequence.json b/latest/examples/transformations/sequence.json new file mode 100644 index 00000000..99eb6311 --- /dev/null +++ b/latest/examples/transformations/sequence.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + ], + "coordinateTransformations" : [ + { + "type" : "sequence", + "transformations" : [ + { "type": "translation", "translation" : [0.1, 0.9] }, + { "type": "scale", "scale" : [2, 3] } + ], + "input" : "in", + "output" : "out" + } + ] +} diff --git a/latest/examples/transformations/sequenceSubspace1.json b/latest/examples/transformations/sequenceSubspace1.json new file mode 100644 index 00000000..e8a5bd35 --- /dev/null +++ b/latest/examples/transformations/sequenceSubspace1.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "coordinateTransformations" : [ + { + "type" : "sequence", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]}, + { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} + ] + } + ] +} From 7baeed76f64f20c9fc11164f5263f3e70e2f4b35 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Sun, 11 Sep 2022 19:21:13 -0400 Subject: [PATCH 23/50] byDimension examples, rm input/outputAxes fields --- .../transformations/byDimension1.json | 17 ++ .../transformations/byDimension2.json | 17 ++ .../transformations/byDimensionInvalid1.json | 17 ++ .../transformations/byDimensionInvalid2.json | 17 ++ latest/index.bs | 233 ++++-------------- 5 files changed, 113 insertions(+), 188 deletions(-) create mode 100644 latest/examples/transformations/byDimension1.json create mode 100644 latest/examples/transformations/byDimension2.json create mode 100644 latest/examples/transformations/byDimensionInvalid1.json create mode 100644 latest/examples/transformations/byDimensionInvalid2.json diff --git a/latest/examples/transformations/byDimension1.json b/latest/examples/transformations/byDimension1.json new file mode 100644 index 00000000..44334d50 --- /dev/null +++ b/latest/examples/transformations/byDimension1.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "coordinateTransformations" : [ + { + "type" : "byDimension", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["x"]}, + { "type" : "scale", "scale" : [2.0], "input" : ["j"], "output" : ["y"]} + ] + } + ] +} diff --git a/latest/examples/transformations/byDimension2.json b/latest/examples/transformations/byDimension2.json new file mode 100644 index 00000000..ebf89a1c --- /dev/null +++ b/latest/examples/transformations/byDimension2.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"}, {"name" : "k"}, {"name" : "l"}] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } + ], + "coordinateTransformations" : [ + { + "type" : "byDimension", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "translation", "translation" : [1, 3], "input" : ["i", "k" ], "output" : ["y", "x"]}, + { "type" : "scale", "scale" : [2.0], "input" : ["j"], "output" : ["z"]} + ] + } + ] +} diff --git a/latest/examples/transformations/byDimensionInvalid1.json b/latest/examples/transformations/byDimensionInvalid1.json new file mode 100644 index 00000000..368ecd7b --- /dev/null +++ b/latest/examples/transformations/byDimensionInvalid1.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "coordinateTransformations" : [ + { + "type" : "byDimension", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["z"]}, + { "type" : "scale", "scale" : [2.0], "input" : ["0"], "output" : ["y"]} + ] + } + ] +} diff --git a/latest/examples/transformations/byDimensionInvalid2.json b/latest/examples/transformations/byDimensionInvalid2.json new file mode 100644 index 00000000..f5f26f6b --- /dev/null +++ b/latest/examples/transformations/byDimensionInvalid2.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems" : [ + { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, + { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + ], + "coordinateTransformations" : [ + { + "type" : "byDimension", + "input" : "in", + "output" : "out", + "transformations" : [ + { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["x"]}, + { "type" : "scale", "scale" : [2.0], "input" : ["i"], "output" : ["x"]} + ] + } + ] +} diff --git a/latest/index.bs b/latest/index.bs index e4c6f5df..e60d2462 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -361,7 +361,6 @@ input space to *points* in the output space. - MUST contain any other fields required by the given "type" (see table below). - MUST contain the field "output", unless part of a `sequence` (see details). - SHOULD contain the field "input", unless part of a `sequence` (see details). -- MAY contain the fields "inputAxes" and / or "outputAxes" (see below). - 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). @@ -776,7 +775,7 @@ coordinate transform in the array is invertible.
A non-empty array of transformations.
-The `input` and `output` fields MAY be omitted for transformations that are in the list of transformations of a `sequence`. +The `input` and `output` fields SHOULD be omitted for transformations that are in the list of transformations of a `sequence`.
@@ -933,86 +932,61 @@ on subsets of dimensions.
transformations
-
A list of transformations, each of which applies to a subset of input and output dimensions (axes). +
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 appear in this parent object's `input` axes. - Every axis name in the parent dimensionWise's output MUST appear in exactly one - of its child transformations' `output`. + 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`.
-This is a valid `dimensionWise` transformation: -```json -{ - "spaces" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } - ], - "transforms" : [ - { - "name: "in2out", - "type: "byDimension", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "inputAxes" : ["i"], "outputAxes" : ["x"]} - { "type" : "scale", "scale" : [2.0], "inputAxes" : ["j"], "outputAxes" : ["y"]} - ] - } - ] -} -``` +
-This is also a valid transformation: +A valid `byDimension` transformation: -```json -{ - "spaces" : [ - { "name" : "in", "axes" : [ {"name" : "0"}, {"name" : "1"}, {"name" : "2"}, {"name" : "3"}] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } - ], - "transforms" : [ - { - "name: "in2out", - "type: "byDimension", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "input" : ["0","3" ], "output" : ["y", "x"]} - { "type" : "scale", "scale" : [2.0], "input" : ["1"], "output" : ["z"]} - ] - } - ] -} -``` +
+path: examples/transformations/byDimension1.json
+highlight: json
+
+
-This is an invalid `dimensionWise` transform: +
-```json -{ - "spaces" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } - ], - "transforms" : [ - { - "name: "in2out", - "type: "dimensionWise", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "coordinates", "path" : "/coordinates", "input" : ["i"], "output" : ["z"]} - { "type" : "scale", "scale" : [2.0], "input" : ["0"], "output" : ["y"]} - ] - } - ] -} -``` +Another valid `byDimension` transformation: -for two reasons. First because input"0" used by the scale transformation is not an axis of the `dimensionWise` transformation's -`input`. Second, the "x" axis of the `output` does not appear in the `output` of any child transformation. +
+path: examples/transformations/byDimension2.json
+highlight: json
+
+ +
+ +
+ +This is an **invalid** `byDimension` transform: + +
+path: examples/transformations/byDimensionInvalid1.json
+highlight: json
+
+ +It is invalid for two reasons. First because input `0` used by the scale transformation is not an axis of the `byDimension` transformation's `input`. Second, the `x` axis of the `output` does not appear in the `output` of any child transformation. + +
+ +
+ +Another **invalid** `byDimension` transform: + +
+path: examples/transformations/byDimensionInvalid2.json
+highlight: json
+
+ +This transformation is invalid because the output axis `x` appears in more than one transformation in the `transformations` list. + +
#### bijection @@ -1059,123 +1033,6 @@ the input and output of the `forward` and `inverse` transformations are understo
-### `inputAxes` and `outputAxes` - -`inputAxes` and `outputAxes` are optional fields for transformations to express that it applies to a subset -or reording of the axes of the input or output coordinate system, respectively. If not present, the `inputAxes` -(`outputAxes`) are understood to be a list of the `input` (`output`) coordinate systems axis names, in the -order they appear in the coordinate system's `axes` list. If present: - -- Values for `inputAxes` and `outputAxes` MUST be a list of strings. -- Every string in the `inputAxes` list MUST be the name of an axis of the transformation's input coordinate system. -- Every string in the `outputAxes` list MUST be the name of an axis of the transformation's output coordinate system. -- Every axis name in the output coordinate system MUST in the list of `outputAxes`. - -
- -An `identity` may be used to permute axes by setting -the `inputAxes` and / or `outputAxes` fields appropriately (see below). - -```json -{ - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} - ], - "coordinateTransformations" : [ - { - "type" : "identity", - "input" : "in", - "output" : "out", - "inputAxes" : ["i", "j"], - "outputAxes" : ["y", "x"] - } - ] -} -``` - -defines the function: - -``` -x = j -y = i -``` - -
- - -
- A valid example - - ```json - "coordinateSystems" : [ - { "name " : "in", "axes" : [ {"name" : "x", "name" : "y" }] } - { "name " : "out", "axes" : [ {"name" : "x", "name" : "y" }] } - ] - { - "type": "scale", - "scale" : [2], - "input" : "in" - "output" : "out", - "inputAxes" : ["y"], - "outputAxes" : ["y"] - } - ``` - scales the `y` axis only. - -
- -
- An invalid example - - ```json - "coordinateSystems" : [ - { "name " : "in", "axes" : [ {"name" : "i", "name" : "j" }] } - { "name " : "out", "axes" : [ {"name" : "x", "name" : "y" }] } - ] - "coordinateTransformations" : [ - { - "type": "scale", - "scale" : [2], - "input" : "in" - "output" : "out", - "inputAxes" : ["j"], - "outputAxes" : ["y"] - } - ] - ``` - - Is invalid because the axis name `x` of the output coordinate system does not appear in the list of `outputAxes`, - nor does it appear in the axis names of the coordinate system `in`. Instead, a `sequence` transformation can - be defined, such that every axis in the output space appears in at least one of the `outputAxes` of the - sequence's transformations. For example (asuming the same coordinate systems as above): - - ```json - { - "type": "sequence", - "transformations" : [ - { - "type": "scale", - "scale" : [2], - "inputAxes" : ["j"], - "outputAxes" : ["y"] - }, - { - "type": "identity", - "inputAxes" : ["i"], - "outputAxes" : ["x"] - } - ], - "input" : "in" - "output" : "out", - } - ``` - - is valid. - -
- - ### Points, coordinates, direction, and indexing From 162a9e9587a7b2395a03940acafb48638c197aca Mon Sep 17 00:00:00 2001 From: bogovicj Date: Thu, 15 Sep 2022 14:45:27 -0400 Subject: [PATCH 24/50] reorder, simplify sections. * flesh out some examples --- latest/index.bs | 257 +++++++++++++++++++++++------------------------- 1 file changed, 125 insertions(+), 132 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index e60d2462..35430256 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -267,14 +267,15 @@ A "coordinate system" is a collection of "axes" / dimensions with a name. Every 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 first dimension. +coordinate system. For the above example, the `"x"` dimension is the first dimension. The "dimensionality" of a coordinate system +is indicated by the length of its "axes" array. The "volume_micrometers" example above is three dimensional (3D). -### Array coordinate systems and indexing +### 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"`. - +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)).
For example, a 3D array at path `/my/data/array` defines the coordinate system: @@ -301,22 +302,22 @@ the last dimension of an array in "C" order (row-major) are stored contiguously. (column-major) order, the elements of the first dimension are stored contiguously.
- For example, if `/my/data/array/.zarray` contains: +For example, if `/my/data/array/.zarray` contains: - ```json - { - "chunks": [ 4, 3, 5 ], - "compressor": null, - "dtype": "|u1", - "fill_value": 0, - "filters": null, - "order": "C", - "shape": [ 4, 3, 5 ], - "zarr_format": 2 - } - ``` +```json +{ + "chunks": [ 4, 3, 5 ], + "compressor": null, + "dtype": "|u1", + "fill_value": 0, + "filters": null, + "order": "C", + "shape": [ 4, 3, 5 ], + "zarr_format": 2 +} +``` - Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5. +Then `dim_0` has length 4, `dim_1` has length 3, and `dim_2` has length 5.
The name and axes names MAY be customized by including a `arrayCoordinateSystem` field in @@ -324,16 +325,14 @@ the `.zattr` metadata of the array whose value is a coordinate system object. Th `axes` MUST be equal to the dimensionality. The value of `"type"` for each object in the axes array MUST equal `"array"`. -```json -"arrayCoordinateSystem" : { - "name" : "myDataArray", - "axes" : [ - {"name": "i", "type": "array"}, - {"name": "j", "type": "array"}, - {"name": "k", "type": "array"} - ] -} -``` +
+ +
+path: examples/coordSystems/arrayCoordSys.json
+highlight: json
+
+ +
### Coordinate convention @@ -417,10 +416,100 @@ input space to *points* in the output space. Conforming readers: - SHOULD be able to apply transformations to points -- MAY enable applying transformations to points -- MAY enable applying transformations to images +- SHOULD be able to apply transformations to images + +### 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 SHOULD be omitted (see below for +details). + +Transformations in the `transformations` list of a `byDimensions` transforemation 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 sequence transformation's input corresponds to an array coordinate system at path "/my/array". + +```json +"coordinateSystems" : [ + { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, + { "name" : "outScale", "axes" : [{"name" : "x"}, {"name":"y"}] } + { "name" : "outSeq", "axes" : [{"name" : "x"}, {"name":"y"}] } + { "name" : "outInv", "axes" : [{"name" : "x"}, {"name":"y"}] } + { "name" : "outByDim", "axes" : [{"name" : "x"}, {"name":"y"}] } +], +"coordinateTransformations" : [ + { + "type": "scale", + "input" : "in", + "output" : "outSeq" + "scale" : [ 0.5, 1.2 ], + }, + { + "type" : "sequence", + "input" : "/my/array", + "output" : "outSeq" + "transformations" : [ + { "type": "scale", "scale" : [ 0.5, 0.6 ] }, + { "type": "translation", "translation" : [ 2, 5 ] } + ], + }, + { + "type": "inverseOf", + "input" : "in", + "output" : "outInv", + "transformation" : { + "type": "displacements", + "path": "/path/to/displacements" + } + }, + { + "type": "byDimension", + "input" : "in", + "output" : "outDim", + "transformations" : [ + { "type" : "translation", "translation" : [1], "input" : ["i"], "output" : ["x"]}, + { "type" : "scale", "scale" : [2.0], "input" : ["j"], "output" : ["y"]} + ] + } +] +``` -### Transformation inverses +
+ +Coordinate transformations are 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 output to +input space. 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 should be specified using the `inverse` +transformation type, for example: + +```json +{ + "type": "inverseOf", + "transformation" : { + "name": "nonlinear-inverse", + "type": "displacements", + "path": "/path/to/my/transform", + } +} +``` 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 @@ -428,6 +517,9 @@ operation is requested that requires the inverse of a transformation that can no implementations MAY estimate an inverse, or MAY output a warning that the requested operation is unsupported. + + + ### Transformation types Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value @@ -627,8 +719,8 @@ highlight: json defines the function: ``` -x = 3 * i -y = 2.12 * j +x = 3.12 * i +y = 2 * j ```
@@ -1034,105 +1126,6 @@ the input and output of the `forward` and `inverse` transformations are understo -### Points, coordinates, direction, and indexing - -Coordinate transformations are 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 transformation: - -```json -{ - "type": "scale", - "scale": [2, 0.5] - "input" : ["i", "j"] - "output" : ["x", "y"] -} -``` - -defines the function: - -``` -x = 2 * i -y = 0.5 * 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 output to -input space. 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 should be specified using the `inverse` -transformation type, for example: - -```json -{ - "type": "inverseOf", - "transformation" : { - "name": "nonlinear-inverse", - "type": "displacements", - "path": "/path/to/my/transform", - } -} -``` - -### Spaces and axes - -Every coordinate transformation MUST specify its output coordinate system using `output` to with a string value -corresponding to the name of an output space or with an array of strings to provide a list of axis names. -Similarly, every coordinate transformation SHOULD specify its input coordinate system using the `input`. If -`input` is not provided the transformation's input is in array-space. - -Coordinate transformations may be defined as sequences of transformations, each of which applies to a subspace. -Which subspace should be defined by providing arrays of strings as the values for the `input` and `output` -fields. An axis name MUST appear in exactly one `output` lists. - - -
-A simple example -```json -{ - "type" : "sequence", - "transformations" : [ - { - "type": "scale", - "scale" : [ 0.5, 0.5 ], - "input" : ["dim_1", "dim_2"], - "output" : ["x", "y"], - }, - { - "type": "scale", - "scale" : [ 2 ], - "input" : ["dim_0"], - "output" : ["t"], - } - ] -} -``` - -```json -{ - "type" : "sequence", - "transformations" : [ - { - "type": "inverseOf", - "transformation" : { - "name": "nonlinear-inverse", - "type": "displacements", - "path": "/path/to/my/transform", - "input" : ["i", "j"], - "output" : ["x", "y"], - }, - }, - { - "type": "scale", - "scale" : [ 2 ] - "input" : ["k"], - "output" : ["z"], - } - ] -} -``` -
- "multiscales" metadata {#multiscale-md} --------------------------------------- From a90662d5dfaa80f81be05057da0f91d092033b61 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Thu, 15 Sep 2022 15:44:47 -0400 Subject: [PATCH 25/50] clean up example formatting --- .../examples/transformations/affine2d2d.json | 12 ++++---- .../examples/transformations/affine2d3d.json | 16 +++++----- .../transformations/byDimension1.json | 20 ++++++------- .../transformations/byDimension2.json | 18 +++++------ .../transformations/byDimensionInvalid1.json | 20 ++++++------- .../transformations/byDimensionInvalid2.json | 20 ++++++------- .../transformations/coordinates1d.json | 18 +++++------ .../transformations/displacement1d.json | 18 +++++------ latest/examples/transformations/identity.json | 10 +++---- latest/examples/transformations/mapAxis1.json | 30 +++++++++---------- latest/examples/transformations/mapAxis2.json | 30 +++++++++---------- .../examples/transformations/mapIndex1.json | 30 +++++++++---------- .../examples/transformations/mapIndex2.json | 30 +++++++++---------- latest/examples/transformations/scale.json | 12 ++++---- latest/examples/transformations/sequence.json | 22 +++++++------- .../examples/transformations/translation.json | 12 ++++---- 16 files changed, 159 insertions(+), 159 deletions(-) diff --git a/latest/examples/transformations/affine2d2d.json b/latest/examples/transformations/affine2d2d.json index cfbd8eab..f85e3b36 100644 --- a/latest/examples/transformations/affine2d2d.json +++ b/latest/examples/transformations/affine2d2d.json @@ -1,14 +1,14 @@ { "coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name" : "j"}] }, - { "name" : "xy", "axes" : [{"name" : "x"}, {"name" : "y"}] } + { "name": "ij", "axes": [{"name": "i"}, {"name": "j"}] }, + { "name": "xy", "axes": [{"name": "x"}, {"name": "y"}] } ], "coordinateTransformations" : [ { - "type" : "affine", - "affine" : [1, 2, 3, 4, 5, 6], - "input" : "ij", - "output" : "xy" + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6], + "input": "ij", + "output": "xy" } ] } diff --git a/latest/examples/transformations/affine2d3d.json b/latest/examples/transformations/affine2d3d.json index 457f93a4..be45fd1b 100644 --- a/latest/examples/transformations/affine2d3d.json +++ b/latest/examples/transformations/affine2d3d.json @@ -1,14 +1,14 @@ { - "coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name" : "j"}] }, - { "name" : "xyz", "axes" : [{"name" : "x"}, {"name" : "y"}, {"name" : "z"}] } + "coordinateSystems": [ + { "name": "ij", "axes": [{"name": "i"}, {"name": "j"}] }, + { "name": "xyz", "axes": [{"name": "x"}, {"name": "y"}, {"name": "z"}] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "affine", - "affine" : [1, 2, 3, 4, 5, 6, 7, 8, 9], - "input" : "ij", - "output" : "xyz" + "type": "affine", + "affine": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "input": "ij", + "output": "xyz" } ] } diff --git a/latest/examples/transformations/byDimension1.json b/latest/examples/transformations/byDimension1.json index 44334d50..a42ed37a 100644 --- a/latest/examples/transformations/byDimension1.json +++ b/latest/examples/transformations/byDimension1.json @@ -1,16 +1,16 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ] }, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "byDimension", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["x"]}, - { "type" : "scale", "scale" : [2.0], "input" : ["j"], "output" : ["y"]} + "type": "byDimension", + "input": "in", + "output": "out", + "transformations": [ + { "type": "translation", "translation": [-1.0], "input": ["i"], "output": ["x"]}, + { "type": "scale", "scale": [2.0], "input": ["j"], "output": ["y"]} ] } ] diff --git a/latest/examples/transformations/byDimension2.json b/latest/examples/transformations/byDimension2.json index ebf89a1c..0568c9bd 100644 --- a/latest/examples/transformations/byDimension2.json +++ b/latest/examples/transformations/byDimension2.json @@ -1,16 +1,16 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"}, {"name" : "k"}, {"name" : "l"}] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ] } + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"}, {"name": "k"}, {"name": "l"}] }, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"}, {"name": "z"} ] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "byDimension", - "input" : "in", - "output" : "out", + "type": "byDimension", + "input": "in", + "output": "out", "transformations" : [ - { "type" : "translation", "translation" : [1, 3], "input" : ["i", "k" ], "output" : ["y", "x"]}, - { "type" : "scale", "scale" : [2.0], "input" : ["j"], "output" : ["z"]} + { "type": "translation", "translation": [1, 3], "input": ["i", "k" ], "output": ["y", "x"]}, + { "type": "scale", "scale": [2.0], "input": ["j"], "output": ["z"]} ] } ] diff --git a/latest/examples/transformations/byDimensionInvalid1.json b/latest/examples/transformations/byDimensionInvalid1.json index 368ecd7b..8d3c9696 100644 --- a/latest/examples/transformations/byDimensionInvalid1.json +++ b/latest/examples/transformations/byDimensionInvalid1.json @@ -1,16 +1,16 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ] }, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "byDimension", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["z"]}, - { "type" : "scale", "scale" : [2.0], "input" : ["0"], "output" : ["y"]} + "type": "byDimension", + "input": "in", + "output": "out", + "transformations": [ + { "type": "translation", "translation": [-1.0], "input": ["i"], "output": ["z"]}, + { "type": "scale", "scale": [2.0], "input": ["0"], "output": ["y"]} ] } ] diff --git a/latest/examples/transformations/byDimensionInvalid2.json b/latest/examples/transformations/byDimensionInvalid2.json index f5f26f6b..fdd3ac4b 100644 --- a/latest/examples/transformations/byDimensionInvalid2.json +++ b/latest/examples/transformations/byDimensionInvalid2.json @@ -1,16 +1,16 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ] }, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ] } + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ] }, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "byDimension", - "input" : "in", - "output" : "out", - "transformations" : [ - { "type" : "translation", "translation" : [-1.0], "input" : ["i"], "output" : ["x"]}, - { "type" : "scale", "scale" : [2.0], "input" : ["i"], "output" : ["x"]} + "type": "byDimension", + "input": "in", + "output": "out", + "transformations": [ + { "type": "translation", "translation": [-1.0], "input": ["i"], "output": ["x"]}, + { "type": "scale", "scale": [2.0], "input": ["i"], "output": ["x"]} ] } ] diff --git a/latest/examples/transformations/coordinates1d.json b/latest/examples/transformations/coordinates1d.json index 99a87f7e..314bc6fb 100644 --- a/latest/examples/transformations/coordinates1d.json +++ b/latest/examples/transformations/coordinates1d.json @@ -1,14 +1,14 @@ { - "coordinateSystems" : [ - { "name" : "i", "axes" : [{"name" : "i"}] }, - { "name" : "x", "axes" : [{"name" : "x"}] } + "coordinateSystems": [ + { "name": "i", "axes": [{"name": "i"}] }, + { "name": "x", "axes": [{"name": "x"}] } ], - "coordinateTransformations" : [{ - "name" : "a coordinate field transform", + "coordinateTransformations": [{ + "name": "a coordinate field transform", "type": "coordinates", - "path" : "i2xCoordinates", - "input" : "i", - "output" : "x", - "interpolation" : "nearest" + "path": "i2xCoordinates", + "input": "i", + "output": "x", + "interpolation": "nearest" }] } diff --git a/latest/examples/transformations/displacement1d.json b/latest/examples/transformations/displacement1d.json index 8af48611..5db76446 100644 --- a/latest/examples/transformations/displacement1d.json +++ b/latest/examples/transformations/displacement1d.json @@ -1,14 +1,14 @@ { - "coordinateSystems" : [ - { "name" : "i", "axes" : [{"name" : "i"}] }, - { "name" : "x", "axes" : [{"name" : "x"}] } + "coordinateSystems": [ + { "name": "i", "axes": [{"name": "i"}] }, + { "name": "x", "axes": [{"name": "x"}] } ], - "coordinateTransformations" : [{ - "name" : "a displacement field transform", + "coordinateTransformations": [{ + "name": "a displacement field transform", "type": "displacements", - "path" : "i2xCoordinates", - "input" : "i", - "output" : "x", - "interpolation" : "nearest" + "path": "i2xCoordinates", + "input": "i", + "output": "x", + "interpolation": "nearest" }] } diff --git a/latest/examples/transformations/identity.json b/latest/examples/transformations/identity.json index 2b488b0d..3ea1529a 100644 --- a/latest/examples/transformations/identity.json +++ b/latest/examples/transformations/identity.json @@ -1,9 +1,9 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ]}, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ]} ], - "coordinateTransformations" : [ - { "type" : "identity", "input" : "in", "output" : "out" } + "coordinateTransformations": [ + { "type": "identity", "input": "in", "output": "out" } ] } diff --git a/latest/examples/transformations/mapAxis1.json b/latest/examples/transformations/mapAxis1.json index 6ce023f7..2b6b0b79 100644 --- a/latest/examples/transformations/mapAxis1.json +++ b/latest/examples/transformations/mapAxis1.json @@ -1,23 +1,23 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out1", "axes" : [ {"name" : "x"}, {"name" : "y"} ]}, - { "name" : "out2", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ]}, + { "name": "out1", "axes": [ {"name": "x"}, {"name": "y"} ]}, + { "name": "out2", "axes": [ {"name": "x"}, {"name": "y"} ]} ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "name" : "equivalent to identity", - "type" : "mapAxis", - "mapAxis" : { "x":"i", "y":"j" }, - "input" : "in", - "output" : "out1" + "name": "equivalent to identity", + "type": "mapAxis", + "mapAxis": { "x":"i", "y":"j" }, + "input": "in", + "output": "out1" }, { - "name" : "permutation", - "type" : "mapAxis", - "mapAxis" : { "x":"j", "y":"i" }, - "input" : "in", - "output" : "out2" + "name": "permutation", + "type": "mapAxis", + "mapAxis": { "x":"j", "y":"i" }, + "input": "in", + "output": "out2" } ] } diff --git a/latest/examples/transformations/mapAxis2.json b/latest/examples/transformations/mapAxis2.json index 7932ed5f..c8336aef 100644 --- a/latest/examples/transformations/mapAxis2.json +++ b/latest/examples/transformations/mapAxis2.json @@ -1,23 +1,23 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "a"}, {"name" : "b"}]}, - { "name" : "out_down", "axes" : [ {"name" : "x"}]}, - { "name" : "out_up", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "a"}, {"name": "b"}]}, + { "name": "out_down", "axes": [ {"name": "x"}]}, + { "name": "out_up", "axes": [ {"name": "x"}, {"name": "y"}, {"name": "z"} ]} ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "name" : "projection down", - "type" : "mapAxis", - "mapAxis" : { "x" : "b" }, - "input" : "in", - "output" : "out_down" + "name": "projection down", + "type": "mapAxis", + "mapAxis": { "x": "b" }, + "input": "in", + "output": "out_down" }, { - "name" : "projection up", - "type" : "mapAxis", - "mapAxis" : { "x":"a", "y":"b", "z":"b" }, - "input" : "in", - "output" : "out_up" + "name": "projection up", + "type": "mapAxis", + "mapAxis": { "x": "a", "y": "b", "z": "b" }, + "input": "in", + "output": "out_up" } ] } diff --git a/latest/examples/transformations/mapIndex1.json b/latest/examples/transformations/mapIndex1.json index 16f70697..db785459 100644 --- a/latest/examples/transformations/mapIndex1.json +++ b/latest/examples/transformations/mapIndex1.json @@ -1,23 +1,23 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out1", "axes" : [ {"name" : "x"}, {"name" : "y"} ]}, - { "name" : "out2", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ]}, + { "name": "out1", "axes": [ {"name": "x"}, {"name": "y"} ]}, + { "name": "out2", "axes": [ {"name": "x"}, {"name": "y"} ]} ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "name" : "equivalent to identity", - "type" : "mapIndex", - "mapIndex" : [ 0, 1 ], - "input" : "in", - "output" : "out1" + "name": "equivalent to identity", + "type": "mapIndex", + "mapIndex": [0, 1], + "input": "in", + "output": "out1" }, { - "name" : "permutation", - "type" : "mapIndex", - "mapIndex" : [ 1, 0 ], - "input" : "in", - "output" : "out2" + "name": "permutation", + "type": "mapIndex", + "mapIndex": [1, 0], + "input": "in", + "output": "out2" } ] } diff --git a/latest/examples/transformations/mapIndex2.json b/latest/examples/transformations/mapIndex2.json index e28f8f4d..411828de 100644 --- a/latest/examples/transformations/mapIndex2.json +++ b/latest/examples/transformations/mapIndex2.json @@ -1,23 +1,23 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "a"}, {"name" : "b"}]}, - { "name" : "out_down", "axes" : [ {"name" : "x"}]}, - { "name" : "out_up", "axes" : [ {"name" : "x"}, {"name" : "y"}, {"name" : "z"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "a"}, {"name": "b"}]}, + { "name": "out_down", "axes": [ {"name": "x"}]}, + { "name": "out_up", "axes": [ {"name": "x"}, {"name": "y"}, {"name": "z"} ]} ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "name" : "projection down", - "type" : "mapIndex", - "mapIndex" : [ 1 ], - "input" : "in", - "output" : "out_down" + "name": "projection down", + "type": "mapIndex", + "mapIndex": [1], + "input": "in", + "output": "out_down" }, { - "name" : "projection up", - "type" : "mapIndex", - "mapIndex" : [ 0, 1, 1 ], - "input" : "in", - "output" : "out_up" + "name": "projection up", + "type": "mapIndex", + "mapIndex": [0, 1, 1], + "input": "in", + "output": "out_up" } ] } diff --git a/latest/examples/transformations/scale.json b/latest/examples/transformations/scale.json index 98c633a1..b5c83309 100644 --- a/latest/examples/transformations/scale.json +++ b/latest/examples/transformations/scale.json @@ -1,14 +1,14 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, - { "name" : "out", "axes" : [{"name" : "x"}, {"name":"y"}] } + "coordinateSystems": [ + { "name": "in", "axes": [{"name": "i"}, {"name": "j"}] }, + { "name": "out", "axes": [{"name": "x"}, {"name": "y"}] } ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { "type": "scale", "scale": [3.12, 2], - "input" : "in", - "output" : "out" + "input": "in", + "output": "out" } ] } diff --git a/latest/examples/transformations/sequence.json b/latest/examples/transformations/sequence.json index 99eb6311..1d88b21d 100644 --- a/latest/examples/transformations/sequence.json +++ b/latest/examples/transformations/sequence.json @@ -1,17 +1,17 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [ {"name" : "i"}, {"name" : "j"} ]}, - { "name" : "out", "axes" : [ {"name" : "x"}, {"name" : "y"} ]} + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ]}, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ]} ], - "coordinateTransformations" : [ + "coordinateTransformations": [ { - "type" : "sequence", - "transformations" : [ - { "type": "translation", "translation" : [0.1, 0.9] }, - { "type": "scale", "scale" : [2, 3] } - ], - "input" : "in", - "output" : "out" + "type": "sequence", + "input": "in", + "output": "out", + "transformations": [ + { "type": "translation", "translation": [0.1, 0.9] }, + { "type": "scale", "scale": [2, 3] } + ] } ] } diff --git a/latest/examples/transformations/translation.json b/latest/examples/transformations/translation.json index 8ea78a9c..cbc32ec9 100644 --- a/latest/examples/transformations/translation.json +++ b/latest/examples/transformations/translation.json @@ -1,14 +1,14 @@ { - "coordinateSystems" : [ - { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, - { "name" : "out", "axes" : [{"name" : "x"}, {"name":"y"}] } + "coordinateSystems": [ + { "name": "in", "axes": [{"name": "i"}, {"name": "j"}] }, + { "name": "out", "axes": [{"name": "x"}, {"name": "y"}] } ], "coordinateTransformations" : [ { "type": "translation", - "translation": [9, -1.42], - "input" : "in", - "output" : "out" + "input": "in", + "output": "out", + "translation": [9, -1.42] } ] } From 32656e557ed62e47e48e4eadda5f6285adf77b20 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 16 Sep 2022 15:25:50 -0400 Subject: [PATCH 26/50] add array coordinate system example --- latest/examples/coordSystems/arrayCoordSys.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 latest/examples/coordSystems/arrayCoordSys.json diff --git a/latest/examples/coordSystems/arrayCoordSys.json b/latest/examples/coordSystems/arrayCoordSys.json new file mode 100644 index 00000000..9c7e1229 --- /dev/null +++ b/latest/examples/coordSystems/arrayCoordSys.json @@ -0,0 +1,10 @@ +{ + "arrayCoordinateSystem" : { + "name" : "myDataArray", + "axes" : [ + {"name": "i", "type": "array"}, + {"name": "j", "type": "array"}, + {"name": "k", "type": "array"} + ] + } +} From 0245ca95216b7802bfdeee766b2c2c361175f6c7 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Fri, 23 Sep 2022 09:11:40 -0400 Subject: [PATCH 27/50] rm duplicate editor names, typo fix --- latest/index.bs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 35430256..57352551 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -13,13 +13,10 @@ Local Boilerplate: header yes Local Boilerplate: copyright yes Boilerplate: style-darkmode off Markup Shorthands: markdown yes -Editor: Josh Moore, Open Microscopy Environment (OME) https://www.openmicroscopy.org -Editor: Sébastien Besson, Open Microscopy Environment (OME) https://www.openmicroscopy.org -Editor: Constantin Pape, European Molecular Biology Laboratory (EMBL) https://www.embl.org/sites/heidelberg/ -Editor: John Bogovic, Hughes Medical Institute Janelia (HHMI) https://www.janelia.org/ Editor: Josh Moore, University of Dundee (UoD) https://www.dundee.ac.uk, https://orcid.org/0000-0003-4028-811X Editor: Sébastien Besson, University of Dundee (UoD) https://www.dundee.ac.uk, https://orcid.org/0000-0001-8783-1429 Editor: Constantin Pape, European Molecular Biology Laboratory (EMBL) https://www.embl.org/sites/heidelberg/, https://orcid.org/0000-0001-6562-7187 +Editor: John Bogovic, Hughes Medical Institute Janelia (HHMI) https://www.janelia.org/, https://orcid.org/0000-0002-4829-9457 Text Macro: NGFFVERSION 0.5-dev Abstract: This document contains next-generation file format (NGFF) Abstract: specifications for storing bioimaging data in the cloud. @@ -483,7 +480,7 @@ The sequence transformation's input corresponds to an array coordinate system at -Coordinate transformations are are functions of *points* in the input space to *points* in the output space. We call this the "forward" direction. +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: From a3ff0b5aabbb73d75b1a5643ff5eaace44df10b5 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 23 Sep 2022 11:01:41 -0400 Subject: [PATCH 28/50] fix sequence example json. add inverseOf to input/output field exception --- latest/index.bs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 57352551..d373c629 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -347,7 +347,7 @@ half-open interval `[-0.5, 0.5) x [-0.5, 0.5)`. See chapter 4 and figure 4.1 of "coordinateTransformations" metadata {#trafo-md} ------------------------------------- -"coordinateTransformations" describe map between two coordinate systems (defined by "axes"). +"coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). For example, to map an array's discrete coordinate system to the 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. @@ -355,8 +355,8 @@ 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` (see details). -- SHOULD contain the field "input", unless part of a `sequence` (see details). +- MUST contain the field "output", unless part of a `sequence` or `inverseOf` (see details). +- SHOULD 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). @@ -436,26 +436,26 @@ The sequence transformation's input corresponds to an array coordinate system at ```json "coordinateSystems" : [ { "name" : "in", "axes" : [{"name" : "i"}, {"name":"j"}] }, - { "name" : "outScale", "axes" : [{"name" : "x"}, {"name":"y"}] } - { "name" : "outSeq", "axes" : [{"name" : "x"}, {"name":"y"}] } - { "name" : "outInv", "axes" : [{"name" : "x"}, {"name":"y"}] } + { "name" : "outScale", "axes" : [{"name" : "x"}, {"name":"y"}] }, + { "name" : "outSeq", "axes" : [{"name" : "x"}, {"name":"y"}] }, + { "name" : "outInv", "axes" : [{"name" : "x"}, {"name":"y"}] }, { "name" : "outByDim", "axes" : [{"name" : "x"}, {"name":"y"}] } ], "coordinateTransformations" : [ { "type": "scale", "input" : "in", - "output" : "outSeq" - "scale" : [ 0.5, 1.2 ], + "output" : "outSeq", + "scale" : [ 0.5, 1.2 ] }, { "type" : "sequence", "input" : "/my/array", - "output" : "outSeq" + "output" : "outSeq", "transformations" : [ { "type": "scale", "scale" : [ 0.5, 0.6 ] }, { "type": "translation", "translation" : [ 2, 5 ] } - ], + ] }, { "type": "inverseOf", @@ -492,18 +492,17 @@ 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 output to -input space. 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 should be specified using the `inverse` +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 should be specified using the `inverseOf` transformation type, for example: ```json { "type": "inverseOf", "transformation" : { - "name": "nonlinear-inverse", "type": "displacements", - "path": "/path/to/my/transform", + "path": "/path/to/displacements", } } ``` @@ -514,9 +513,6 @@ operation is requested that requires the inverse of a transformation that can no implementations MAY estimate an inverse, or MAY output a warning that the requested operation is unsupported. - - - ### Transformation types Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value From 0df402d185120ce166ac5c318c35039b8a999fe2 Mon Sep 17 00:00:00 2001 From: bogovicj Date: Fri, 23 Sep 2022 11:38:16 -0400 Subject: [PATCH 29/50] be clearer re: half-open interval --- latest/index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latest/index.bs b/latest/index.bs index d373c629..2f7bb1a3 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -341,7 +341,7 @@ coordinate systems. A pixel/voxel is the continuous region (rectangle) that corr 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)`. See chapter 4 and figure 4.1 of the ITK Software Guide [[itk]]. +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 [[itk]]. "coordinateTransformations" metadata {#trafo-md} From f05d480941ebb0239e077977232046c743a8d2cc Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Mon, 3 Oct 2022 20:34:37 -0400 Subject: [PATCH 30/50] move axes section into coordinateSystems section --- latest/index.bs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 57352551..01f56e1e 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -228,26 +228,13 @@ Metadata {#metadata} The various `.zattrs` files throughout the above array hierarchy may contain metadata keys as specified below for discovering certain types of data, especially images. -"axes" metadata {#axes-md} --------------------------- - -"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 "array", "space", "time", "channel", "coordinate", or "displacement" but MAY take other 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' - -If part of [[#multiscale-md]], the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. - -"coordinateSystem" metadata {#coord-sys-md} +"coordinateSystems" metadata {#coord-sys-md} -------------------------- A "coordinate system" is a collection of "axes" / dimensions with a name. Every coordinate system: - MUST contain the field "name", that gives the space name. The values MUST be non-empty and unique. -- MUST contain the field "axes", whose value is a valid set of "axes". +- MUST contain the field "axes", whose value is a valid set of "axes" (see below).
@@ -267,6 +254,18 @@ The order of the `"axes"` list matters and defines the index of each array dimen coordinate system. For the above example, the `"x"` dimension is the first dimension. The "dimensionality" of a coordinate system is indicated by the length of its "axes" array. The "volume_micrometers" example above is three dimensional (3D). +### "axes" metadata {#axes-md} + +"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 "array", "space", "time", "channel", "coordinate", or "displacement" but MAY take other 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' + +If part of [[#multiscale-md]], the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. + ### Array coordinate systems From ed17ed5f36181acd2c2185a5da804aef1ff4cdc7 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Mon, 3 Oct 2022 20:50:37 -0400 Subject: [PATCH 31/50] typo fixes --- latest/index.bs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 01f56e1e..6ff965dd 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -335,7 +335,7 @@ highlight: json **The pixel/voxel center is the origin of the continuous coordinate system.** -It is vital to consistently define relationship between the discrete/arrray and continuous/interpolated +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 @@ -514,8 +514,6 @@ implementations MAY estimate an inverse, or MAY output a warning that the reques - - ### Transformation types Input and output dimensionality may be determined by the value of the "input" and "output" fields, respectively. If the value @@ -658,18 +656,18 @@ z = b `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. `translation` transformations are +identical and MUST equal the the length of the "translation" array (N). `translation` transformations are invertible.
path
-
The path to an array containing the affine parameters. +
The path to a zarr-array containing the translation parameters. The array at this path MUST be 1D, and its length MUST be `N`.
url
-
An optional URL to the container in which the affine array is stored. If not provided, +
An optional URL to the container in which the translation zarr-array is stored. If not provided, the provided `path` MUST exist in this container.
scale
-
The scale parameters 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`.
@@ -691,18 +689,18 @@ y = j - 1.42 `scale` transformations are special cases of affine transformations. When possible, a scale transformation SHOULD be defined to its equivalent affine. Input and output dimensionality MUST be identical and MUST equal -the the length of the "scale" array. Values in the `scale` array SHOULD be non-zero; in that case, `scale` +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 an array containing the affine parameters. +
The path to a zarr-array containing the scale parameters. The array at this path MUST be 1D, and its length MUST be `N`.
url
-
An optional URL to the container in which the affine array is stored. If not provided, +
An optional URL to the container in which the scale zarr-array is stored. If not provided, the provided `path` MUST exist in this container.
scale
-
The scale parameters 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`.
@@ -728,10 +726,10 @@ The matrix may be stored as a 2D array or as a 1D array (row-major).
path
-
The path to an array containing the affine parameters. +
The path to a zarr-array containing the affine parameters. The array at this path MUST be 1D, its length MUST be `N*(M+1)`.
url
-
An optional URL to the container in which the affine array is stored. If not provided, +
An optional URL to the container in which the affine zarr-array is stored. If not provided, the provided `path` MUST exist in this container.
affine
The affine parameters stored in JSON. The matrix may be stored From cd01ab7eb76e33dfaf603b67cba515b3802f5b49 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Mon, 3 Oct 2022 21:26:33 -0400 Subject: [PATCH 32/50] add two new coordinate transformation examples * multiscales dataset-level * xarray like coordinates / byDimension --- .../multiscales_example_relative.json | 61 +++++++++++++++++++ .../examples/transformations/xarrayLike.json | 17 ++++++ 2 files changed, 78 insertions(+) create mode 100644 latest/examples/multiscales_strict/multiscales_example_relative.json create mode 100644 latest/examples/transformations/xarrayLike.json diff --git a/latest/examples/multiscales_strict/multiscales_example_relative.json b/latest/examples/multiscales_strict/multiscales_example_relative.json new file mode 100644 index 00000000..04e52b2c --- /dev/null +++ b/latest/examples/multiscales_strict/multiscales_example_relative.json @@ -0,0 +1,61 @@ +{ + "multiscales": [ + { + "version": "0.5-dev", + "name": "example", + "coordinateSystems" : [ + { + "name" : "exampleCoordinateSystem", + "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" + // the transformation of other arrays are defined relative to this, the highest resolution, array + }, + { + "path": "1", + "coordinateTransformations": [{ + // the second scale level (downscaled by a factor of 2 relative to "0" in zyx) + "type": "scale", + "scale": [1, 1, 2, 2, 2], + "input" : "/1`", + "output" : "/0" + }] + }, + { + "path": "2", + "coordinateTransformations": [{ + // the third scale level (downscaled by a factor of 4 relative to "0" in zyx) + "type": "scale", + "scale": [1, 1, 4, 4, 4], + "input" : "/2", + "output" : "/0" + }] + } + ], + "coordinateTransformations": [{ + // the time unit (0.1 milliseconds), the voxel size for all spatial axes of "0" (0.5 micrometers) + "type": "scale", + "scale": [0.1, 1.0, 0.5, 0.5, 0.5], + "input" : "/0", + "output" : "xampleCoordinateSystem" + }], + "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} + } + } + ] +} diff --git a/latest/examples/transformations/xarrayLike.json b/latest/examples/transformations/xarrayLike.json new file mode 100644 index 00000000..fdacf6e0 --- /dev/null +++ b/latest/examples/transformations/xarrayLike.json @@ -0,0 +1,17 @@ +{ + "coordinateSystems": [ + { "name": "in", "axes": [ {"name": "i"}, {"name": "j"} ]}, + { "name": "out", "axes": [ {"name": "x"}, {"name": "y"} ]} + ], + "coordinateTransformations": [ + { + "type": "byDimension", + "input": "in", + "output": "out", + "transformations": [ + { "type": "coordinates", "path": "/xCoordinates", "input" : ["i"], "output" : ["x"] }, + { "type": "coordinates", "path": "/yCoordinates", "input" : ["j"], "output" : ["y"] } + ] + } + ] +} From 86672e655b881d9759c042a51c0414a71cb83477 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 20:21:54 +0200 Subject: [PATCH 33/50] Allow tests to access all schemas --- latest/tests/test_validation.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/latest/tests/test_validation.py b/latest/tests/test_validation.py index 83f49911..fed62a13 100644 --- a/latest/tests/test_validation.py +++ b/latest/tests/test_validation.py @@ -44,6 +44,11 @@ def pytest_generate_tests(metafunc): if "suite" in metafunc.fixturenames: suites: List[Schema] = [] ids: List[str] = [] + schema_store = {} + for filename in glob.glob("schemas/*.schema"): + with open(filename) as o: + schema = json.load(o) + schema_store[schema["$id"]] = schema # Validation for filename in glob.glob("tests/*.json"): @@ -54,7 +59,7 @@ def pytest_generate_tests(metafunc): schema = json.load(f) for test in suite["tests"]: ids.append("validate_" + str(test["formerly"]).split("/")[-1][0:-5]) - suites.append(Suite(schema, {schema["$id"]: schema}, test["data"], test["valid"])) + suites.append(Suite(schema, schema_store, test["data"], test["valid"])) # Examples for config_filename in glob.glob("examples/*/.config.json"): @@ -69,7 +74,7 @@ def pytest_generate_tests(metafunc): data = ''.join(line for line in f if not line.lstrip().startswith('//')) data = json.loads(data) ids.append("example_" + str(filename).split("/")[-1][0:-5]) - suites.append(Suite(schema, {schema["$id"]: schema}, data, True)) # Assume true + suites.append(Suite(schema, schema_store, data, True)) # Assume true metafunc.parametrize("suite", suites, ids=ids, indirect=True) From b34875dff77c91320e91b9d36f0ad49d8995a454 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 21:22:43 +0200 Subject: [PATCH 34/50] Fix how schemas are matched to examples --- latest/examples/transformations/.config.json | 3 +++ latest/tests/test_validation.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 latest/examples/transformations/.config.json diff --git a/latest/examples/transformations/.config.json b/latest/examples/transformations/.config.json new file mode 100644 index 00000000..56e6f885 --- /dev/null +++ b/latest/examples/transformations/.config.json @@ -0,0 +1,3 @@ +{ + "schema": "schemas/coordinate_transforms.schema" +} diff --git a/latest/tests/test_validation.py b/latest/tests/test_validation.py index fed62a13..2799f13b 100644 --- a/latest/tests/test_validation.py +++ b/latest/tests/test_validation.py @@ -1,5 +1,6 @@ import json import glob +from pathlib import Path from dataclasses import dataclass from typing import List @@ -62,13 +63,14 @@ def pytest_generate_tests(metafunc): suites.append(Suite(schema, schema_store, test["data"], test["valid"])) # Examples - for config_filename in glob.glob("examples/*/.config.json"): + for config_filename in Path(".").glob("examples/*/.config.json"): + print(config_filename) with open(config_filename) as o: data = json.load(o) schema = data["schema"] with open(schema) as f: schema = json.load(f) - for filename in glob.glob("examples/*/*.json"): + for filename in config_filename.parent.glob("*.json"): with open(filename) as f: # Strip comments data = ''.join(line for line in f if not line.lstrip().startswith('//')) From 35ea3e38568e9f6c685ac87f5606cd8760b1f742 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 21:23:09 +0200 Subject: [PATCH 35/50] Add initial coordinate system schemas --- latest/schemas/axes.schema | 45 ++++++ latest/schemas/coordinate_transforms.schema | 144 ++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 latest/schemas/axes.schema create mode 100644 latest/schemas/coordinate_transforms.schema diff --git a/latest/schemas/axes.schema b/latest/schemas/axes.schema new file mode 100644 index 00000000..c5eed1b8 --- /dev/null +++ b/latest/schemas/axes.schema @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ngff.openmicroscopy.org/latest/schemas/axes.schema", + "title": "NGFF Axes", + "description": "JSON from OME-NGFF .zattrs", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/axis" + }, + "$defs": { + "axis": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the axis" + }, + "type": { + "type": "string", + "enum": [ + "array", + "space", + "time", + "channel", + "coordinate", + "displacement" + ], + "description": "Dimension of the axis" + }, + "discrete": { + "type": "boolean", + "description": "Whether the dimension is discrete" + }, + "units": { + "type": "string", + "description": "Units for the axis" + } + }, + "required": [ + "name" + ] + } + } +} diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema new file mode 100644 index 00000000..d4fe5aeb --- /dev/null +++ b/latest/schemas/coordinate_transforms.schema @@ -0,0 +1,144 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ngff.openmicroscopy.org/latest/schemas/coordinate_transforms.schema", + "title": "NGFF Coordinate Systems and Transforms", + "description": "Coordinate Systems and transforms for OME-NGFF", + "type": "object", + "properties": { + "coordinateSystems": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/coordinateSystem" + } + }, + "coordinateTransformations": { + "type": "array", + "uniqueItems": true, + "items": { + "allOf": [ + { + "$ref": "#/$defs/coordinateTransformation" + }, + { + "type": "object", + "properties": { + "input": { + "type": "string" + }, + "output": { + "type": "string" + } + }, + "required": [ + "input", + "output" + ] + } + ] + } + } + }, + "$defs": { + "coordinateSystem": { + "description": "Coordinate Systems for OME-NGFF", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of coordinate space" + }, + "axes": { + "$ref": "axes.schema" + } + }, + "required": [ + "name", + "axes" + ] + }, + "coordinateTransformation": { + "description": "OME-NGFF coordinate transformation.", + "allOf": [ + { + "type": "object", + "properties": { + "name": {"type": "string"}, + "type": {"type": "string"} + }, + "required": [ + "type" + ] + }, + { + "oneOf": [ + { + "$ref": "#/$defs/identity" + }, + { + "$ref": "#/$defs/scale" + }, + { + "$ref": "#/$defs/translation" + } + ] + } + ] + }, + "identity": { + "type": "object", + "properties": { + "type": { + "const": "identity" + } + } + }, + "scale": { + "type": "object", + "properties": { + "type": { + "const": "scale" + }, + "oneOf": { + "path": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "scale": { + "type": "array", + "items": { + "type": "number", + "exclusiveMinimum": 0 + } + } + } + } + }, + "translation": { + "type": "object", + "properties": { + "type": { + "const": "translation" + }, + "oneOf": { + "path": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "translation": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + } + } +} From 3fcba533b2a784ceb4200f7881822b586a1bb335 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 22:04:55 +0200 Subject: [PATCH 36/50] Add array coordinate schema --- latest/schemas/coordinate_transforms.schema | 38 +++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index d4fe5aeb..8192b773 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -37,6 +37,36 @@ } ] } + }, + "arrayCoordinateSystem": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of coordinate space" + }, + "axes": { + "allOf": [ + { + "$ref": "axes.schema" + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "const": "array" + } + } + } + } + ] + } + }, + "required": [ + "axes" + ] } }, "$defs": { @@ -63,8 +93,12 @@ { "type": "object", "properties": { - "name": {"type": "string"}, - "type": {"type": "string"} + "name": { + "type": "string" + }, + "type": { + "type": "string" + } }, "required": [ "type" From 883ff1c315956f873b52bb5366388654d57e9af0 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 22:07:16 +0200 Subject: [PATCH 37/50] Add test for array coordinate system --- latest/examples/coordSystems/.config.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 latest/examples/coordSystems/.config.json diff --git a/latest/examples/coordSystems/.config.json b/latest/examples/coordSystems/.config.json new file mode 100644 index 00000000..56e6f885 --- /dev/null +++ b/latest/examples/coordSystems/.config.json @@ -0,0 +1,3 @@ +{ + "schema": "schemas/coordinate_transforms.schema" +} From c2a18c44bfcfb4692e1fc4f0ff9f75639d76d176 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Tue, 4 Oct 2022 22:07:39 +0200 Subject: [PATCH 38/50] Stop adding cases for .config files --- latest/tests/test_validation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/latest/tests/test_validation.py b/latest/tests/test_validation.py index 2799f13b..91c6a65f 100644 --- a/latest/tests/test_validation.py +++ b/latest/tests/test_validation.py @@ -71,6 +71,8 @@ def pytest_generate_tests(metafunc): with open(schema) as f: schema = json.load(f) for filename in config_filename.parent.glob("*.json"): + if filename.name.startswith(".config"): + continue with open(filename) as f: # Strip comments data = ''.join(line for line in f if not line.lstrip().startswith('//')) From 027b55bdeff20f0e3634c903923faf900a33cb29 Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Tue, 4 Oct 2022 20:54:30 -0400 Subject: [PATCH 39/50] describe where to store coordinateTransformations --- latest/index.bs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/latest/index.bs b/latest/index.bs index 649ed411..4786d9ce 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -347,7 +347,7 @@ half-open interval `[-0.5, 0.5) x [-0.5, 0.5)` (i.e., -0.5 is included, +0.5 is ------------------------------------- "coordinateTransformations" describe the mapping between two coordinate systems (defined by "axes"). -For example, to map an array's discrete coordinate system to the corresponding physical coordinates. +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. @@ -414,6 +414,37 @@ 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 ([[#multiscale-md]]), +and MUST be duplicated in the atrributes 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 "transformations" zarr group.
+│   └── displacements           # for example, a zarray 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 From 5bfe44245cd1d8e0f99d6ce2383fdcd809312f2b Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Wed, 5 Oct 2022 10:17:12 -0400 Subject: [PATCH 40/50] corrections for coordinates and displacements --- latest/index.bs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 4786d9ce..aeb9fe27 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -968,7 +968,7 @@ if the arrray in `coordinates` contains the data: `[-9, 9, 0]`, then this metada ``` x = - if ( i < 0.5 ) -9 + if ( i > -0.5 and i < 0.5 ) -9 else if ( i < 1.5 ) 9 else 0 ``` @@ -977,16 +977,16 @@ Example metadata for the array data at path `coordinates` above: ``` { - "spaces" : [ + "coordinateSystems" : [ { "name" : "a coordinate field transform", "axes" : [ - { "label": "i", "type": "space", "discrete": true }, - { "label": "c", "type": "coordinate", "discrete": true } + { "name": "i", "type": "space", "discrete": true }, + { "name": "c", "type": "coordinate", "discrete": true } ] } ], - "transformations" : [ + "coordinateTransformations" : [ { "type" : "identity", "output" : "a coordinate field transform" @@ -1013,16 +1013,16 @@ Example metadata for the array data at path `displacements` above: ``` { - "spaces" : [ + "coordinateSystems" : [ { "name" : "a coordinate field transform", "axes" : [ - { "label": "x", "type": "space", "unit" : "nanometer" }, - { "label": "d", "type": "displacement", "discrete": true } + { "name": "x", "type": "space", "unit" : "nanometer" }, + { "name": "d", "type": "displacement", "discrete": true } ] } ], - "transformations" : [ + "coordinateTransformations" : [ { "type" : "scale", "scale" : [2, 1], From aab746956f574d234c41c330da83ee622a570cdf Mon Sep 17 00:00:00 2001 From: John Bogovic Date: Thu, 6 Oct 2022 11:26:07 -0400 Subject: [PATCH 41/50] add optional "longName" field for axes see #142 --- latest/index.bs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/latest/index.bs b/latest/index.bs index aeb9fe27..3b1943a8 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -263,9 +263,27 @@ is indicated by the length of its "axes" array. The "volume_micrometers" exampl - 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 [[#multiscale-md]], the length of "axes" MUST be equal to the number of dimensions of the arrays that contain the image data. +
+ +Examples of valid axes. + +```json +[ + {"name": "x", "type": "space", "unit": "micrometer"}, + {"name": "t", "type": "time", "unit": "second", "longName": "Unix Epoch time"}, + {"name": "c", "type": "channel", "discrete": true}, + {"name": "i0", "type": "array"}, + {"name": "c", "type": "coordinate", "unit": "parsec"}, + {"name": "v", "type": "displacement", "unit": "nanometer"}, + {"name": "freq", "type": "frequency", "unit": "megahertz"} +] +``` +
+ ### Array coordinate systems From 098bfb8d5f931268eefac2af3ec13cb41b183dec Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 15:36:01 +0200 Subject: [PATCH 42/50] Add longName property to axes schema --- latest/schemas/axes.schema | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/latest/schemas/axes.schema b/latest/schemas/axes.schema index c5eed1b8..083ffcac 100644 --- a/latest/schemas/axes.schema +++ b/latest/schemas/axes.schema @@ -16,6 +16,10 @@ "type": "string", "description": "Name of the axis" }, + "longName": { + "type": "string", + "description": "Longer name or description of the axis." + }, "type": { "type": "string", "enum": [ From f04ebef79981835c9e0b6cef555da5022815c650 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 16:15:53 +0200 Subject: [PATCH 43/50] Add mapAxis and mapIndex schemas --- latest/schemas/coordinate_transforms.schema | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index 8192b773..1e7a5fb9 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -109,6 +109,12 @@ { "$ref": "#/$defs/identity" }, + { + "$ref": "#/$defs/mapIndex" + }, + { + "$ref": "#/$defs/mapAxis" + }, { "$ref": "#/$defs/scale" }, @@ -127,6 +133,44 @@ } } }, + "mapIndex": { + "type": "object", + "description": "Permute axes by position", + "properties": { + "type": { + "const": "mapIndex" + }, + "mapIndex": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "mapIndex" + ] + }, + "mapAxis": { + "type": "object", + "description": "Permute axes by name", + "properties": { + "type": { + "const": "mapAxis" + }, + "mapAxis": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + } + }, + "required": [ + "mapAxis" + ] + } + }, "scale": { "type": "object", "properties": { From ead1f7be1828f48acd5f6d6a1816cd3e266f0852 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 16:37:49 +0200 Subject: [PATCH 44/50] Add affine transfrom, use reference for path_w_url --- latest/schemas/coordinate_transforms.schema | 107 +++++++++++++++----- 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index 1e7a5fb9..a702bb6b 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -70,6 +70,22 @@ } }, "$defs": { + "path_w_url": { + "description": "Path specification. Schema local solution until https://github.com/ome/ngff/issues/144 is resolved.", + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "path" + ] + }, "coordinateSystem": { "description": "Coordinate Systems for OME-NGFF", "type": "object", @@ -120,6 +136,9 @@ }, { "$ref": "#/$defs/translation" + }, + { + "$ref": "#/$defs/affine" } ] } @@ -176,47 +195,79 @@ "properties": { "type": { "const": "scale" + } + }, + "oneOf": [ + { + "$ref": "#/$defs/path_w_url" }, - "oneOf": { - "path": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - }, - "scale": { - "type": "array", - "items": { - "type": "number", - "exclusiveMinimum": 0 + { + "properties": { + "scale": { + "type": "array", + "items": { + "type": "number", + "exclusiveMinimum": 0 + } } - } + }, + "required": [ + "scale" + ] } - } + ] }, "translation": { "type": "object", "properties": { "type": { "const": "translation" + } + }, + "oneOf": [ + { + "$ref": "#/$defs/path_w_url" }, - "oneOf": { - "path": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - }, - "translation": { - "type": "array", - "items": { - "type": "number" + { + "properties": { + "translation": { + "type": "array", + "items": { + "type": "number" + } } + }, + "required": [ + "translation" + ] + } + ] + }, + "affine": { + "type": "object", + "properties": { + "type": { + "const": "affine" + } + }, + "oneOf": [ + { + "$ref": "#/$defs/path_w_url" + }, + { + "properties": { + "affine": { + "type": "array", + "items": { + "type": "number" + } + }, + "required": [ + "affine" + ] } } - } + ] } } } From 281df32956be52800e316afb45891c43db1f4030 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 16:48:50 +0200 Subject: [PATCH 45/50] Move rotation example to example directory --- latest/examples/transformations/rotation.json | 14 ++++++++++++++ latest/index.bs | 19 +++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 latest/examples/transformations/rotation.json diff --git a/latest/examples/transformations/rotation.json b/latest/examples/transformations/rotation.json new file mode 100644 index 00000000..5e73ff33 --- /dev/null +++ b/latest/examples/transformations/rotation.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] }, + { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } + ], + "coordinateTransformations" : [ + { + "type": "rotation", + "rotation": [0, -1, 1, 0], + "input" : "ij", + "output" : "xy" + } + ] +} \ No newline at end of file diff --git a/latest/index.bs b/latest/index.bs index 3b1943a8..37ff9fad 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -839,20 +839,11 @@ and columns. `rotation` transformations are invertible.
A 2D example - ```json - "coordinateSystems" : [ - { "name" : "ij", "axes" : [{"name" : "i"}, {"name":"j"}] } - { "name" : "xy", "axes" : [{"name" : "x"}, {"name":"y"}] } - ], - "coordinateTransformations" : [ - { - "type": "rotation", - "affine": [0, -1, 1, 0], - "input" : "ij", - "output" : "xy" - } - ] - ``` + +
+    path: examples/transformations/rotation.json
+    highlight: json
+    
defines the function: From 87d78e8c03331f5279be7839eb28cae383458c20 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 16:51:19 +0200 Subject: [PATCH 46/50] Add schema for rotation --- latest/schemas/coordinate_transforms.schema | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index a702bb6b..5b9373b8 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -139,6 +139,9 @@ }, { "$ref": "#/$defs/affine" + }, + { + "$ref": "#/$defs/rotation" } ] } @@ -268,6 +271,32 @@ } } ] + }, + "rotation": { + "type": "object", + "properties": { + "type": { + "const": "rotation" + } + }, + "oneOf": [ + { + "$ref": "#/$defs/path_w_url" + }, + { + "properties": { + "rotation": { + "type": "array", + "items": { + "type": "number" + } + }, + "required": [ + "rotation" + ] + } + } + ] } } } From 033d4bba282426bdba0cde07d1230534dd8419c7 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 16:59:06 +0200 Subject: [PATCH 47/50] Move inverseOf example to examples directory --- .../examples/transformations/inverseOf.json | 14 ++++++++++++++ latest/index.bs | 19 +++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 latest/examples/transformations/inverseOf.json diff --git a/latest/examples/transformations/inverseOf.json b/latest/examples/transformations/inverseOf.json new file mode 100644 index 00000000..3e2966c5 --- /dev/null +++ b/latest/examples/transformations/inverseOf.json @@ -0,0 +1,14 @@ +{ + "coordinateSystems" : [ + { "name" : "moving", "axes" : [{"name" : "x-moving"}, {"name":"y-moving"}] }, + { "name" : "fixed", "axes" : [{"name" : "x-fixed"}, {"name":"y-fixed"}] } + ], + "coordinateTransformations" : [ + { + "type": "inverseOf", + "transformation" : { "type": "displacements", "path": "/path/to/displacements" }, + "input" : "moving", + "output" : "fixed" + } + ] +} \ No newline at end of file diff --git a/latest/index.bs b/latest/index.bs index 37ff9fad..bfcf2c27 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -873,20 +873,11 @@ transformation (if it exists).
For example -```json -"coordinateSystems" : [ - { "name" : "moving", "axes" : [{"name" : "x-moving"}, {"name":"y-moving"}] } - { "name" : "fixed", "axes" : [{"name" : "x-fixed"}, {"name":"y-fixed"}] } -], -"coordinateTransformations" : [ - { - "type": "inverseOf", - "transformation" : { "type": "displacements", "path": "/path/to/displacements" } - "input" : "moving", - "output" : "fixed", - } -] -``` +
+    path: examples/transformations/inverseOf.json
+    highlight: json
+    
+
#### sequence From f78e4411cdf2e2e7c66a48653cc7bb8b4b32b772 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 17:00:58 +0200 Subject: [PATCH 48/50] Added schema for inverseOf Note that the test currently does not pass because displacements have not been defined yet. --- latest/schemas/coordinate_transforms.schema | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index 5b9373b8..315c2248 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -142,6 +142,9 @@ }, { "$ref": "#/$defs/rotation" + }, + { + "$ref": "#/$defs/inverseOf" } ] } @@ -297,6 +300,20 @@ } } ] + }, + "inverseOf": { + "type": "object", + "properties": { + "type": { + "const": "inverseOf" + }, + "transformation": { + "$ref": "#/$defs/coordinateTransformation" + } + }, + "required": [ + "transformation" + ] } } } From 55e35bd966606ad117eab9188fd2877e2df686ff Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 17:48:03 +0200 Subject: [PATCH 49/50] Added byDimension transformation The tests mark invalid pass because we would need dynamic validation to check whether the axes were present in the coordinate spaces. --- latest/schemas/coordinate_transforms.schema | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index 315c2248..04c6990e 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -145,11 +145,38 @@ }, { "$ref": "#/$defs/inverseOf" + }, + { + "$ref": "#/$defs/byDimension" } ] } ] }, + "byDimensionTransformation": { + "type": "object", + "description": "Transformation used inside a byDimension transformation", + "allOf": [ + { "$ref": "#/$defs/coordinateTransformation" }, + { + "properties": { + "input": { + "type": "array", + "items": { + "type": "string" + } + }, + "output": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ], + "required": ["input", "output"] + }, "identity": { "type": "object", "properties": { @@ -314,6 +341,18 @@ "required": [ "transformation" ] + }, + "byDimension": { + "type": "object", + "properties": { + "type": { "const": "byDimension" }, + "transformations": { + "type": "array", + "items": { + "$ref": "#/$defs/byDimensionTransformation" + } + } + } } } } From 0833395edb9fe456c38ea7f3cdb6a84e10e41c81 Mon Sep 17 00:00:00 2001 From: Isaac Virshup Date: Sun, 9 Oct 2022 17:54:46 +0200 Subject: [PATCH 50/50] Sequence schemas --- latest/schemas/coordinate_transforms.schema | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/latest/schemas/coordinate_transforms.schema b/latest/schemas/coordinate_transforms.schema index 04c6990e..ddbbb5da 100644 --- a/latest/schemas/coordinate_transforms.schema +++ b/latest/schemas/coordinate_transforms.schema @@ -146,6 +146,9 @@ { "$ref": "#/$defs/inverseOf" }, + { + "$ref": "#/$defs/sequence" + }, { "$ref": "#/$defs/byDimension" } @@ -342,6 +345,19 @@ "transformation" ] }, + "sequence": { + "description": "A sequence of transformations", + "type": "object", + "properties": { + "type": { "const": "sequence" }, + "transformations": { + "type": "array", + "items": { + "$ref": "#/$defs/coordinateTransformation" + } + } + } + }, "byDimension": { "type": "object", "properties": {