From 4f319612af57dbdfb663e2fa6383069be042dedb Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 7 Apr 2026 10:13:23 -0400 Subject: [PATCH 1/7] Readability improvements to README --- README.md | 105 +++++++++++++++++++++++++++++------------------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 9e988c9..d7e283e 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,21 @@ ## Description -This Zarr Convention defines metadata for describing the relationship between array indices and spatial coordinates. All properties use the `spatial:` namespace prefix and are placed at the root `attributes` level following the [Zarr Conventions Specification](https://github.com/zarr-conventions/zarr-conventions-spec). +This Zarr Convention defines metadata for describing the relationship between array indices and spatial coordinates. All properties use the `spatial:` namespace prefix. Properties are placed at the root `attributes` level following the [Zarr Conventions Specification](https://github.com/zarr-conventions/zarr-conventions-spec) except as described below with `multiscales`. This convention is designed to be composable with other conventions: -- Use with a `proj:` convention to add coordinate reference system (CRS) information for geospatial data -- Use with `multiscales` to define spatial properties at different resolution levels +- Combine with the [`geo-proj`] convention to add coordinate reference system (CRS) information for geospatial data +- Combine with the [`multiscales`] convention to define spatial properties at different resolution levels - Use standalone for non-geospatial data that has spatial relationships (e.g., microscopy, medical imaging) +[`geo-proj`]: https://github.com/zarr-conventions/geo-proj +[`multiscales`]: https://github.com/zarr-conventions/multiscales + Examples: -- [composition with proj](examples/proj.json) -- [composition with multiscales](examples/multiscales.json) +- [Composition with `geo-proj`](examples/proj.json) +- [Composition with `multiscales`](examples/multiscales.json) ## Motivation @@ -29,7 +32,7 @@ Examples: - **Broad applicability**: Useful for both geospatial and non-geospatial data with spatial relationships - **Simplicity**: No complex inheritance or override mechanisms needed - **Interoperability**: Compatible with existing geospatial tools (GDAL, rasterio) when composed with CRS information -- **Composability**: Can extend other conventions like multiscales with spatial-specific properties +- **Composability**: Can extend other conventions like [`multiscales`] with spatial-specific properties ## Convention Registration @@ -58,7 +61,7 @@ This convention can be used with these parts of the Zarr hierarchy: ## Properties -All properties use the `spatial:` namespace prefix and are placed at the root `attributes` level. +All properties use the `spatial:` namespace prefix and are placed at the root `attributes` level (except for **`spatial:transform`**, which must be placed inside the `layout` array when composed with [`multiscales`]). | Property | Type | Description | Required | Reference | | -------------------------- | ---------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------ | @@ -69,11 +72,11 @@ All properties use the `spatial:` namespace prefix and are placed at the root `a | **spatial:shape** | `number[]` | Shape of spatial dimensions [height, width] | No | [spatial:shape](#spatialshape) | | **spatial:registration** | `string` | Grid cell registration (i.e., raster space) type (default: "pixel") | No | [spatial:registration](#spatialregistration) | -### Field Details +### Additional Properties Additional properties are allowed. -#### spatial:dimensions +### spatial:dimensions Names of spatial dimensions @@ -87,7 +90,7 @@ For 3D spatial data, provide 3 elements: `["z", "y", "x"]` The dimension names must match the dimension names defined in the array's shape. -#### spatial:bbox +### spatial:bbox Bounding box in coordinate space @@ -101,7 +104,7 @@ For 3D spatial data: `[xmin, ymin, zmin, xmax, ymax, zmax]` (6 elements) The coordinates represent the minimum and maximum values along each spatial axis. The interpretation of these coordinates depends on any associated coordinate reference system (e.g., from a `proj:` convention) or can represent abstract spatial units. -#### spatial:transform_type +### spatial:transform_type Type of coordinate transformation @@ -119,7 +122,7 @@ Specifies the type of transformation used to map array indices to spatial coordi When `spatial:transform_type` is omitted, implementations MUST assume `"affine"` for backwards compatibility. Implementations encountering unknown transform types SHOULD handle them gracefully (e.g., warn or skip processing rather than fail). -#### spatial:transform +### spatial:transform Affine transformation coefficients @@ -152,7 +155,7 @@ Where: - `e`: pixel height (n-s pixel resolution, negative value for north-up images) - `f`: y-coordinate of the upper-left corner of the upper-left pixel -**Coordinate convention:** +#### Coordinate convention The transform operates on array indices where `(0, 0)` is at the **top-left corner** of the top-left pixel, and `(width, height)` is at the bottom-right corner of the bottom-right pixel. The center of the top-left pixel is at `(0.5, 0.5)`. @@ -160,19 +163,23 @@ This follows the GDAL geotransform and Python's Affine library convention. Note: Rasterio's `xy()` and `rowcol()` methods automatically add/subtract 0.5 to convert between pixel coordinates and corner coordinates. For example, `transformer.xy(0, 0)` is equivalent to applying this transform to `(0.5, 0.5)`, giving the coordinate at the center of the first pixel. -**Coefficient ordering:** +#### Coefficient ordering This format uses the Rasterio/Affine coefficient ordering: `[a, b, c, d, e, f]` This is the same ordering used by: -- Rasterio's `Affine` transformation and `.transform` attribute -- Python's `affine` library +- [Rasterio's `Affine` transformation](https://rasterio.readthedocs.io/en/stable/topics/transforms.html) and `.transform` attribute +- Python's [`Affine`] library - The matrix form commonly used in geospatial Python libraries -GDAL's `GetGeoTransform` uses a different order: `[c, a, b, f, d, e]` or `[GT(0), GT(1), GT(2), GT(3), GT(4), GT(5)]` +[`Affine`]: https://affine.readthedocs.io/en/latest/ + +**Converting to/from GDAL's coefficient ordering:** + +GDAL's [`GetGeoTransform`] uses a different order: `[c, a, b, f, d, e]` or `[GT(0), GT(1), GT(2), GT(3), GT(4), GT(5)]` -**Converting between formats:** +[`GetGeoTransform`]: https://gdal.org/en/stable/tutorials/geotransforms_tut.html ```python # From GDAL GetGeoTransform to spatial:transform @@ -186,7 +193,27 @@ affine_transform = src.transform # Affine object spatial_transform = list(affine_transform)[:6] # Direct conversion ``` -#### spatial:shape +#### Usage with [`multiscales`] convention + +The multiscales convention defines transformations between resolution levels using the formula: + +``` +X_current = X_source × scale + translation +``` + +This is a domain-agnostic affine transformation (scale followed by translation). When composing with the `spatial:` convention, the interpretation depends on `spatial:registration`: + +When `spatial:registration` is `"pixel"` (the default), the pixel origin is defined by the `spatial:transform` (coefficients `c` and `f`) or `spatial:bbox` at each layout level. For standard geospatial overviews where all resolution levels share the same coordinate origin, the multiscales `translation` should be `[0.0, 0.0]`—only the pixel resolution (scale) changes between levels. + +The `spatial:transform` origin coordinates (`c`, `f`) remain constant across levels, while the pixel size (`a`, `e`) varies according to the resolution. Implementations must ensure consistency: if a level has `"scale": [2.0, 2.0]` relative to the base, its `spatial:transform` pixel dimensions should be twice those of the base level. + +Non-zero translation values are valid when there are actual spatial offsets between levels (e.g., cropped extents), but this is uncommon for georeferenced image pyramids. + +**Recommendation:** When composing with multiscales, it is highly recommended to specify `spatial:transform` and `spatial:shape` explicitly at each layout level. This ensures unambiguous georeferencing and avoids relying on derived calculations from multiscales transform parameters. + +See [examples/multiscales.json](examples/multiscales.json) for a complete example. + +### spatial:shape Shape of spatial dimensions @@ -204,7 +231,7 @@ This property is particularly useful when: - Used with multiscales convention to specify shape at different resolution levels - Documenting the spatial extent explicitly -#### spatial:registration +### spatial:registration Grid cell registration type @@ -215,7 +242,7 @@ Grid cell registration type Specifies whether the grid uses node registration (grid-registered) or pixel registration (cell-registered). This property is particularly important for grids where the interpretation of coordinate ranges differs between registration types. -**Node Registration (grid/node):** +#### Node Registration (grid/node) Node-registered grids have cells centered on the grid-lines. The coordinate ranges (`spatial:bbox` and `spatial:transform`) refer to the centers of the cells on the outside border of the grid, and the footprints of the cells extend 1/2 cell width outside these ranges. @@ -224,7 +251,7 @@ Node-registered grids have cells centered on the grid-lines. The coordinate rang - A global grid will have cells centered directly on the North and South Poles - Has one more row and one more column than a pixel-registered grid with identical range -**Pixel Registration (cell/pixel):** +#### Pixel Registration (cell/pixel) Pixel-registered grids have cells lying between the grid-lines. The coordinate ranges refer to the outside edges of the boundaries of the grid. @@ -233,7 +260,7 @@ Pixel-registered grids have cells lying between the grid-lines. The coordinate r - A global grid will touch the edges of the poles without covering their centers - Each cell in one registration overlaps quadrants of four cells in the corresponding node-registration -**Important considerations:** +#### Important considerations - Converting between registration types results in relief flattening, as each cell in one registration overlies corners of four cells in the other - The conversion process averages values, reducing local highs and raising local deeps @@ -242,7 +269,7 @@ Pixel-registered grids have cells lying between the grid-lines. The coordinate r When `spatial:registration` is omitted, implementations MUST assume `"pixel"` registration for backwards compatibility. -**Relationship to other formats:** +#### Relationship to other formats: This property corresponds to similar concepts in other geospatial formats: @@ -250,7 +277,7 @@ This property corresponds to similar concepts in other geospatial formats: - **GMT**: `"pixel"` = pixel registration, `"node"` = gridline registration - **NetCDF-CF**: Related to the interpretation of coordinate bounds -**References:** +#### References: For more detailed information on grid cell registration concepts: @@ -424,36 +451,16 @@ In this example: - This enables efficient storage of multi-resolution geospatial data with proper georeferencing at each level - Note how CRS information (`proj:`) is separated from spatial coordinate information (`spatial:`) -**Transform parameter interpretation with spatial:** - -The multiscales convention defines transformations between resolution levels using the formula: - -``` -X_current = X_source × scale + translation -``` - -This is a domain-agnostic affine transformation (scale followed by translation). When composing with the `spatial:` convention, the interpretation depends on `spatial:registration`: - -When `spatial:registration` is `"pixel"` (the default), the pixel origin is defined by the `spatial:transform` (coefficients `c` and `f`) or `spatial:bbox` at each layout level. For standard geospatial overviews where all resolution levels share the same coordinate origin, the multiscales `translation` should be `[0.0, 0.0]`—only the pixel resolution (scale) changes between levels. - -The `spatial:transform` origin coordinates (`c`, `f`) remain constant across levels, while the pixel size (`a`, `e`) varies according to the resolution. Implementations must ensure consistency: if a level has `"scale": [2.0, 2.0]` relative to the base, its `spatial:transform` pixel dimensions should be twice those of the base level. - -Non-zero translation values are valid when there are actual spatial offsets between levels (e.g., cropped extents), but this is uncommon for georeferenced image pyramids. - -**Recommendation:** When composing with multiscales, it is highly recommended to specify `spatial:transform` and `spatial:shape` explicitly at each layout level. This ensures unambiguous georeferencing and avoids relying on derived calculations from multiscales transform parameters. - -See [examples/multiscales.json](examples/multiscales.json) for a complete example. - ## FAQ -### Why are spatial: and proj: separate conventions? +### Why are `spatial` and [`geo-proj`] separate conventions? As explained in the [rasterio documentation](https://rasterio.readthedocs.io/): "There are two parts to the georeferencing of raster datasets: the definition of the local, regional, or global system in which a raster's pixels are located; and the parameters by which pixel coordinates are transformed into coordinates in that system." This fundamental distinction motivated the design decision ([zarr-conventions issue #9](https://github.com/zarr-conventions/zarr-conventions/issues/9)) to separate these concerns into two conventions: -1. **`proj:`** - Defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON -2. **`spatial:`** - Defines the coordinate transformation: the "parameters by which pixel coordinates are transformed" including transform matrices, bounding boxes, and dimension mappings +1. **[`geo-proj`]:** - Defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON +2. **`spatial`:** - Defines the coordinate transformation: the "parameters by which pixel coordinates are transformed" including transform matrices, bounding boxes, and dimension mappings This separation provides several benefits: @@ -472,7 +479,7 @@ The STAC Projection Extension combines CRS and spatial coordinate information in Both approaches are valid. The separated approach prioritizes modularity and broader applicability, while STAC prioritizes simplicity for the geospatial-only use case. -### Can I use spatial: without proj:? +### Can I use `spatial` without [`geo-proj`]? Yes! The `spatial:` convention is useful on its own for: From 75bf02411af7e871c600224e076167251c07a09a Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:48:01 -0400 Subject: [PATCH 2/7] Apply suggestion from @emmanuelmathot Co-authored-by: Emmanuel Mathot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7e283e..0862b76 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ This convention can be used with these parts of the Zarr hierarchy: ## Properties -All properties use the `spatial:` namespace prefix and are placed at the root `attributes` level (except for **`spatial:transform`**, which must be placed inside the `layout` array when composed with [`multiscales`]). +All properties use the `spatial:` namespace prefix and are placed at the root `attributes` level. When composed with [`multiscales`], `spatial:transform` and `spatial:shape` may additionally be placed inside each `layout` item to specify values per resolution level (see [Usage with `multiscales` convention](#usage-with-multiscales-convention)). | Property | Type | Description | Required | Reference | | -------------------------- | ---------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------ | From a777be5deb6ee2ef240901e61a1026dc52f78410 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:48:40 -0400 Subject: [PATCH 3/7] Apply suggestion from @emmanuelmathot Co-authored-by: Emmanuel Mathot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15ea528..e50a094 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ Pixel-registered grids have cells lying between the grid-lines. The coordinate r When `spatial:registration` is omitted, implementations MUST assume `"pixel"` registration for backwards compatibility. -#### Relationship to other formats: +#### Relationship to other formats This property corresponds to similar concepts in other geospatial formats: From c14706c084c5f635fc126dec1ed47370eb590c36 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:49:00 -0400 Subject: [PATCH 4/7] Apply suggestion from @emmanuelmathot Co-authored-by: Emmanuel Mathot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e50a094..84d6e72 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ This property corresponds to similar concepts in other geospatial formats: - **GMT**: `"pixel"` = pixel registration, `"node"` = gridline registration - **NetCDF-CF**: Related to the interpretation of coordinate bounds -#### References: +#### References For more detailed information on grid cell registration concepts: From e7785177c16b143afa5cc38f5a70d0557200b433 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:50:11 -0400 Subject: [PATCH 5/7] rename `geo-proj` to `proj` --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 84d6e72..00bc584 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ The `spatial:` convention describes the **two horizontal (X/Y) spatial axes only This convention is designed to be composable with other conventions: -- Combine with the [`geo-proj`] convention to add coordinate reference system (CRS) information for geospatial data +- Combine with the [`proj`] convention to add coordinate reference system (CRS) information for geospatial data - Combine with the [`multiscales`] convention to define spatial properties at different resolution levels - Use standalone for non-geospatial data that has spatial relationships (e.g., microscopy, medical imaging) -[`geo-proj`]: https://github.com/zarr-conventions/geo-proj +[`proj`]: https://github.com/zarr-conventions/proj [`multiscales`]: https://github.com/zarr-conventions/multiscales Examples: -- [Composition with `geo-proj`](examples/proj.json) +- [Composition with `proj`](examples/proj.json) - [Composition with `multiscales`](examples/multiscales.json) ## Motivation @@ -358,8 +358,8 @@ For geospatial data, combine `spatial:` with `proj:` for complete coordinate inf "attributes": { "zarr_conventions": [ { - "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json", - "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md", + "schema_url": "https://raw.githubusercontent.com/zarr-experimental/proj/refs/tags/v1/schema.json", + "spec_url": "https://github.com/zarr-experimental/proj/blob/v1/README.md", "uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", "name": "proj:", "description": "Coordinate reference system information for geospatial data" @@ -401,8 +401,8 @@ The spatial: convention can extend multiscales layouts by adding spatial propert "description": "Multiscale layout of zarr datasets" }, { - "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json", - "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md", + "schema_url": "https://raw.githubusercontent.com/zarr-experimental/proj/refs/tags/v1/schema.json", + "spec_url": "https://github.com/zarr-experimental/proj/blob/v1/README.md", "uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", "name": "proj:", "description": "Coordinate reference system information for geospatial data" @@ -455,13 +455,13 @@ In this example: ## FAQ -### Why are `spatial` and [`geo-proj`] separate conventions? +### Why are `spatial` and [`proj`] separate conventions? As explained in the [rasterio documentation](https://rasterio.readthedocs.io/): "There are two parts to the georeferencing of raster datasets: the definition of the local, regional, or global system in which a raster's pixels are located; and the parameters by which pixel coordinates are transformed into coordinates in that system." This fundamental distinction motivated the design decision ([zarr-conventions issue #9](https://github.com/zarr-conventions/zarr-conventions/issues/9)) to separate these concerns into two conventions: -1. **[`geo-proj`]:** - Defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON +1. **[`proj`]:** - Defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON 2. **`spatial`:** - Defines the coordinate transformation: the "parameters by which pixel coordinates are transformed" including transform matrices, bounding boxes, and dimension mappings This separation provides several benefits: @@ -481,7 +481,7 @@ The STAC Projection Extension combines CRS and spatial coordinate information in Both approaches are valid. The separated approach prioritizes modularity and broader applicability, while STAC prioritizes simplicity for the geospatial-only use case. -### Can I use `spatial` without [`geo-proj`]? +### Can I use `spatial` without [`proj`]? Yes! The `spatial:` convention is useful on its own for: From 1de11be766884ee551842d0ea4686ddf531b8e64 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:57:38 -0400 Subject: [PATCH 6/7] clean up extra `-` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00bc584..a7d8806 100644 --- a/README.md +++ b/README.md @@ -461,8 +461,8 @@ As explained in the [rasterio documentation](https://rasterio.readthedocs.io/): This fundamental distinction motivated the design decision ([zarr-conventions issue #9](https://github.com/zarr-conventions/zarr-conventions/issues/9)) to separate these concerns into two conventions: -1. **[`proj`]:** - Defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON -2. **`spatial`:** - Defines the coordinate transformation: the "parameters by which pixel coordinates are transformed" including transform matrices, bounding boxes, and dimension mappings +1. **[`proj`]:** defines the coordinate reference system (CRS): the "local, regional, or global system" using EPSG codes, WKT2, or PROJJSON +2. **`spatial`:** Defines the coordinate transformation: the "parameters by which pixel coordinates are transformed" including transform matrices, bounding boxes, and dimension mappings This separation provides several benefits: From 0352a984a9d44f85f5bcac2271e15cf5da3518ce Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 19 May 2026 14:58:57 -0400 Subject: [PATCH 7/7] fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7d8806..54ce693 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Bounding box in coordinate space Bounding box of the X/Y spatial extent in the coordinate space: `[xmin, ymin, xmax, ymax]` (exactly 4 elements). -The coordinates represent the minimum and maximum values along the X and Y axes. The interpretation of these coordinates depends on any associated coordinate reference system (e.g., from a `proj:` convention) or can represent abstract spatial units. +The coordinates represent the minimum and maximum values along the X and Y axes. The interpretation of these coordinates depends on any associated coordinate reference system (e.g., from the [`proj`] convention) or can represent abstract spatial units. ### spatial:transform_type