From 4ef3940cecef7a6416bcb6e47aafc29f1af24334 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Tue, 30 Apr 2024 20:52:46 +0300 Subject: [PATCH 01/10] feat: remove axis restrictions --- latest/index.bs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/latest/index.bs b/latest/index.bs index 26e27b3c..b9a1a518 100644 --- a/latest/index.bs +++ b/latest/index.bs @@ -77,7 +77,7 @@ Images {#image-layout} The following layout describes the expected Zarr hierarchy for images with multiple levels of resolutions and optionally associated labels. -Note that the number of dimensions is variable between 2 and 5 and that axis names are arbitrary, see [[#multiscale-md]] for details. +Note that the number of dimensions is variable and that axis names are arbitrary, see [[#multiscale-md]] for details. For this example we assume an image with 5 dimensions and axes called `t,c,z,y,x`.
@@ -98,8 +98,8 @@ For this example we assume an image with 5 dimensions and axes called `t,c,z,y,x
     ├── n                     # The name of the array is arbitrary with the ordering defined by
     │   │                     # by the "multiscales" metadata, but is often a sequence starting at 0.
     │   │
-    │   ├── .zarray           # All image arrays must be up to 5-dimensional
-    │   │                     # with the axis of type time before type channel, before spatial axes.
+    │   ├── .zarray           
+    │   │                     
     │   │
     │   └─ t                  # Chunks are stored with the nested directory layout.
     │      └─ c               # All but the last chunk element are stored as directories.
@@ -303,10 +303,8 @@ Metadata about an image can be found under the "multiscales" key in the group-le
 "multiscales" contains a list of dictionaries where each entry describes a multiscale image.
 
 Each "multiscales" dictionary MUST contain the field "axes", see [[#axes-md]].
-The length of "axes" must be between 2 and 5 and MUST be equal to the dimensionality of the zarr arrays storing the image data (see "datasets:path").
-The "axes" MUST contain 2 or 3 entries of "type:space" and MAY contain one additional entry of "type:time" and MAY contain one additional entry of "type:channel" or a null / custom type.
-The order of the entries MUST correspond to the order of dimensions of the zarr arrays. In addition, the entries MUST be ordered by "type" where the "time" axis must come first (if present), followed by the  "channel" or custom axis (if present) and the axes of type "space".
-If there are three spatial axes where two correspond to the image plane ("yx") and images are stacked along the other (anisotropic) axis ("z"), the spatial axes SHOULD be ordered as "zyx".
+The length of "axes" must be equal to the dimensionality of the zarr arrays storing the image data (see "datasets:path").
+The order of the entries MUST correspond to the order of dimensions of the zarr arrays. 
 
 Each "multiscales" dictionary MUST contain the field "datasets", which is a list of dictionaries describing the arrays storing the individual resolution levels.
 Each dictionary in "datasets" MUST contain the field "path", whose value contains the path to the array for this resolution relative

From a19b3db6b3a1be3067b7274af7b01b64de0980cb Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Thu, 2 May 2024 16:04:05 +0300
Subject: [PATCH 02/10] chore: remove additional statements of axis
 restrictions

---
 latest/index.bs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/latest/index.bs b/latest/index.bs
index b9a1a518..54479099 100644
--- a/latest/index.bs
+++ b/latest/index.bs
@@ -298,7 +298,7 @@ The transformations in the list are applied sequentially and in order.
 "multiscales" metadata {#multiscale-md}
 ---------------------------------------
 
-Metadata about an image can be found under the "multiscales" key in the group-level metadata. Here, image refers to 2 to 5 dimensional data representing image or volumetric data with optional time or channel axes. It is stored in a multiple resolution representation.
+Metadata about an image can be found under the "multiscales" key in the group-level metadata. Images are stored in a multiple resolution representation.
 
 "multiscales" contains a list of dictionaries where each entry describes a multiscale image.
 

From f9993cbe63a91d50019067045f34ba8f8377d1d6 Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Thu, 2 May 2024 16:09:32 +0300
Subject: [PATCH 03/10] chore: remove additional statements of axis
 restrictions

---
 latest/index.bs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/latest/index.bs b/latest/index.bs
index 54479099..f13a2df0 100644
--- a/latest/index.bs
+++ b/latest/index.bs
@@ -308,9 +308,8 @@ The order of the entries MUST correspond to the order of dimensions of the zarr
 
 Each "multiscales" dictionary MUST contain the field "datasets", which is a list of dictionaries describing the arrays storing the individual resolution levels.
 Each dictionary in "datasets" MUST contain the field "path", whose value contains the path to the array for this resolution relative
-to the current zarr group. The "path"s MUST be ordered from largest (i.e. highest resolution) to smallest.
+to the current zarr group. The "path"s MUST be ordered from largest (i.e. highest resolution) to smallest. All arrays denoted by a "path" field MUST have the same number of dimensions. The number of dimensions of each array must match the length of the "axes" metadata.
 
-Each "datasets" dictionary MUST have the same number of dimensions and MUST NOT have more than 5 dimensions. The number of dimensions and order MUST correspond to number and order of "axes".
 Each dictionary in "datasets" MUST contain the field "coordinateTransformations", which contains a list of transformations that map the data coordinates to the physical coordinates (as specified by "axes") for this resolution level.
 The transformations are defined according to [[#trafo-md]]. The transformation MUST only be of type `translation` or `scale`.
 They MUST contain exactly one `scale` transformation that specifies the pixel size in physical units or time duration. If scaling information is not available or applicable for one of the axes, the value MUST express the scaling factor between the current resolution and the first resolution for the given axis, defaulting to 1.0 if there is no downsampling along the axis.

From 0507c5e606dee44e01d2d59cc1e2e9469c081524 Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Sat, 4 May 2024 13:45:06 +0200
Subject: [PATCH 04/10] feat: use pydantic models for making schemas

---
 .gitignore                      |   1 +
 latest/models.py                |  77 +++++
 latest/schemas/image.schema     | 495 +++++++++++++++++++-------------
 latest/schemas/image_old.schema | 229 +++++++++++++++
 4 files changed, 600 insertions(+), 202 deletions(-)
 create mode 100644 latest/models.py
 create mode 100644 latest/schemas/image_old.schema

diff --git a/.gitignore b/.gitignore
index 89502188..3fadeb08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ _build
 _bikeshed
 .tox
 .vscode
+.venv
\ No newline at end of file
diff --git a/latest/models.py b/latest/models.py
new file mode 100644
index 00000000..2323749f
--- /dev/null
+++ b/latest/models.py
@@ -0,0 +1,77 @@
+from __future__ import annotations
+from typing import Literal, Optional
+from pydantic import BaseModel, ConfigDict, conlist
+from typing import Annotated, Hashable, List, TypeVar
+from pydantic_core import PydanticCustomError
+from pydantic import AfterValidator, Field, ValidationError
+
+T = TypeVar('T', bound=Hashable)
+
+def _validate_unique_list(v: list[T]) -> list[T]:
+    if len(v) != len(set(v)):
+        raise PydanticCustomError('unique_list', 'List must be unique')
+    return v
+
+UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True})]
+
+class Axis(BaseModel, frozen=True):
+    name: str
+    type: Optional[str] = None
+    unit: Optional[str] = None
+
+class ScaleTransform(BaseModel, frozen=True):
+    type: Literal["scale"]
+    scale: conlist(float, min_length=1)
+
+class TranslationTransform(BaseModel, frozen=True):
+    type: Literal["translation"]
+    translation: conlist(float, min_length=1)
+
+class Dataset(BaseModel, frozen=True):
+    path: str
+    coordinateTransformations: tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]
+
+class Multiscale(BaseModel, frozen=True):
+    """
+    The multiscale datasets for this image
+    """
+    name: Optional[str] = None
+    datasets: conlist(Dataset, min_length=1)
+    axes: UniqueList[Axis]
+    coordinateTransformations: Optional[tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]] = None
+    version: Literal['0.5-dev']
+
+class Window(BaseModel, frozen=True):
+    start: float
+    end: float
+    min: float
+    max: float
+
+class RenderingSettings(BaseModel, frozen=True):
+    window: Window
+    label: str
+    family: str
+    color: str
+    active: bool
+
+class Omero(BaseModel, frozen=True):
+    channels: list[RenderingSettings]
+
+class GroupMetadata(BaseModel, frozen=True):
+    """
+    JSON from OME-NGFF .zattrs
+    """
+    model_config = ConfigDict(title="NGFF Image")
+    multiscales: conlist(Multiscale, min_length=1)
+    omero: Optional[Omero] = None
+
+def make_schema():
+    import json
+    schema = GroupMetadata.model_json_schema()
+    schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
+    schema["$id"] = "https://ngff.openmicroscopy.org/latest/schemas/image.schema"
+
+    print(json.dumps(schema, indent=2))
+
+if __name__ == '__main__':
+    make_schema()
\ No newline at end of file
diff --git a/latest/schemas/image.schema b/latest/schemas/image.schema
index cf2b477f..dd2e18df 100644
--- a/latest/schemas/image.schema
+++ b/latest/schemas/image.schema
@@ -1,229 +1,320 @@
 {
-  "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://ngff.openmicroscopy.org/latest/schemas/image.schema",
-  "title": "NGFF Image",
-  "description": "JSON from OME-NGFF .zattrs",
-  "type": "object",
-  "properties": {
-    "multiscales": {
-      "description": "The multiscale datasets for this image",
-      "type": "array",
-      "items": {
-        "type": "object",
-        "properties": {
-          "name": {
-            "type": "string"
-          },
-          "datasets": {
-            "type": "array",
-            "minItems": 1,
-            "items": {
-              "type": "object",
-              "properties": {
-                "path": {
-                  "type": "string"
+  "$defs": {
+    "Axis": {
+      "properties": {
+        "name": {
+          "title": "Name",
+          "type": "string"
+        },
+        "type": {
+          "anyOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "title": "Type"
+        },
+        "unit": {
+          "anyOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "title": "Unit"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "title": "Axis",
+      "type": "object"
+    },
+    "Dataset": {
+      "properties": {
+        "path": {
+          "title": "Path",
+          "type": "string"
+        },
+        "coordinateTransformations": {
+          "anyOf": [
+            {
+              "maxItems": 1,
+              "minItems": 1,
+              "prefixItems": [
+                {
+                  "$ref": "#/$defs/ScaleTransform"
+                }
+              ],
+              "type": "array"
+            },
+            {
+              "maxItems": 2,
+              "minItems": 2,
+              "prefixItems": [
+                {
+                  "$ref": "#/$defs/ScaleTransform"
                 },
-                "coordinateTransformations": {
-                  "$ref": "#/$defs/coordinateTransformations"
+                {
+                  "$ref": "#/$defs/TranslationTransform"
                 }
-              },
-              "required": ["path", "coordinateTransformations"]
+              ],
+              "type": "array"
             }
+          ],
+          "title": "Coordinatetransformations"
+        }
+      },
+      "required": [
+        "path",
+        "coordinateTransformations"
+      ],
+      "title": "Dataset",
+      "type": "object"
+    },
+    "Multiscale": {
+      "description": "The multiscale datasets for this image",
+      "properties": {
+        "name": {
+          "anyOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "title": "Name"
+        },
+        "datasets": {
+          "items": {
+            "$ref": "#/$defs/Dataset"
           },
-          "version": {
-            "type": "string",
-            "enum": [
-              "0.5-dev"
-            ]
-          },
-          "axes": {
-            "$ref": "#/$defs/axes"
+          "minItems": 1,
+          "title": "Datasets",
+          "type": "array"
+        },
+        "axes": {
+          "items": {
+            "$ref": "#/$defs/Axis"
           },
-          "coordinateTransformations": {
-            "$ref": "#/$defs/coordinateTransformations"
+          "title": "Axes",
+          "type": "array",
+          "uniqueItems": true
+        },
+        "coordinateTransformations": {
+          "anyOf": [
+            {
+              "maxItems": 1,
+              "minItems": 1,
+              "prefixItems": [
+                {
+                  "$ref": "#/$defs/ScaleTransform"
                 }
+              ],
+              "type": "array"
+            },
+            {
+              "maxItems": 2,
+              "minItems": 2,
+              "prefixItems": [
+                {
+                  "$ref": "#/$defs/ScaleTransform"
+                },
+                {
+                  "$ref": "#/$defs/TranslationTransform"
+                }
+              ],
+              "type": "array"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "title": "Coordinatetransformations"
         },
-        "required": [
-          "datasets", "axes", "version"
-        ]
+        "version": {
+          "const": "0.5-dev",
+          "enum": [
+            "0.5-dev"
+          ],
+          "title": "Version",
+          "type": "string"
+        }
       },
-      "minItems": 1,
-      "uniqueItems": true
+      "required": [
+        "datasets",
+        "axes",
+        "version"
+      ],
+      "title": "Multiscale",
+      "type": "object"
     },
-    "omero": {
-      "type": "object",
+    "Omero": {
       "properties": {
         "channels": {
-          "type": "array",
           "items": {
-            "type": "object",
-            "properties": {
-              "window": {
-                "type": "object",
-                "properties": {
-                  "end": {
-                    "type": "number"
-                  },
-                  "max": {
-                    "type": "number"
-                  },
-                  "min": {
-                    "type": "number"
-                  },
-                  "start": {
-                    "type": "number"
-                  }
-                },
-                "required": [
-                  "start",
-                  "min",
-                  "end",
-                  "max"
-                ]
-              },
-              "label": {
-                "type": "string"
-              },
-              "family": {
-                "type": "string"
-              },
-              "color": {
-                "type": "string"
-              },
-              "active": {
-                "type": "boolean"
-              }
-            }
-          }
+            "$ref": "#/$defs/RenderingSettings"
+          },
+          "title": "Channels",
+          "type": "array"
         }
       },
       "required": [
         "channels"
-      ]
-    }
-  },
-  "required": [ "multiscales" ],
-
-  "$defs": {
-    "axes": {
-      "type": "array",
-      "uniqueItems": true,
-      "minItems": 2,
-      "maxItems": 5,
-      "contains": {
-        "type": "object",
-        "properties": {
-          "name": {
-            "type": "string"
-          },
-          "type": {
-            "type": "string",
-            "enum": ["space"]
-          },
-          "unit": {
-            "type": "string"
-          }
+      ],
+      "title": "Omero",
+      "type": "object"
+    },
+    "RenderingSettings": {
+      "properties": {
+        "window": {
+          "$ref": "#/$defs/Window"
+        },
+        "label": {
+          "title": "Label",
+          "type": "string"
+        },
+        "family": {
+          "title": "Family",
+          "type": "string"
+        },
+        "color": {
+          "title": "Color",
+          "type": "string"
+        },
+        "active": {
+          "title": "Active",
+          "type": "boolean"
         }
       },
-      "minContains": 2,
-      "maxContains": 3,
-      "items": {
-        "oneOf": [
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "type": {
-                "type": "string",
-                "enum": ["channel", "time", "space"]
-              }
-            },
-            "required": ["name", "type"]
+      "required": [
+        "window",
+        "label",
+        "family",
+        "color",
+        "active"
+      ],
+      "title": "RenderingSettings",
+      "type": "object"
+    },
+    "ScaleTransform": {
+      "properties": {
+        "type": {
+          "const": "scale",
+          "enum": [
+            "scale"
+          ],
+          "title": "Type",
+          "type": "string"
+        },
+        "scale": {
+          "items": {
+            "type": "number"
           },
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "type": {
-                "type": "string",
-                "not": {
-                  "enum": ["space", "time", "channel"]
-                }
-              }
-            },
-            "required": ["name"]
-          }
-        ]
-      }
+          "minItems": 1,
+          "title": "Scale",
+          "type": "array"
+        }
+      },
+      "required": [
+        "type",
+        "scale"
+      ],
+      "title": "ScaleTransform",
+      "type": "object"
     },
-    "coordinateTransformations": {
-      "type": "array",
-      "minItems": 1,
-      "contains": {
-        "type": "object",
-        "properties": {
-          "type": {
-            "type": "string",
-            "enum": [
-              "scale"
-            ]
+    "TranslationTransform": {
+      "properties": {
+        "type": {
+          "const": "translation",
+          "enum": [
+            "translation"
+          ],
+          "title": "Type",
+          "type": "string"
+        },
+        "translation": {
+          "items": {
+            "type": "number"
           },
-          "scale": {
-            "type": "array",
-            "minItems": 2,
-            "items": {
-              "type": "number"
-            }
-          }
+          "minItems": 1,
+          "title": "Translation",
+          "type": "array"
         }
       },
-      "maxContains": 1,
+      "required": [
+        "type",
+        "translation"
+      ],
+      "title": "TranslationTransform",
+      "type": "object"
+    },
+    "Window": {
+      "properties": {
+        "start": {
+          "title": "Start",
+          "type": "number"
+        },
+        "end": {
+          "title": "End",
+          "type": "number"
+        },
+        "min": {
+          "title": "Min",
+          "type": "number"
+        },
+        "max": {
+          "title": "Max",
+          "type": "number"
+        }
+      },
+      "required": [
+        "start",
+        "end",
+        "min",
+        "max"
+      ],
+      "title": "Window",
+      "type": "object"
+    }
+  },
+  "description": "JSON from OME-NGFF .zattrs",
+  "properties": {
+    "multiscales": {
       "items": {
-        "oneOf": [
-          {
-            "type": "object",
-            "properties": {
-              "type": {
-                "type": "string",
-                "enum": [
-                  "scale"
-                ]
-              },
-              "scale": {
-                "type": "array",
-                "minItems": 2,
-                "items": {
-                  "type": "number"
-                }
-              }
-            },
-            "required": ["type", "scale"]
-          },
-          {
-            "type": "object",
-            "properties": {
-              "type": {
-                "type": "string",
-                "enum": [
-                  "translation"
-                ]
-              },
-              "translation": {
-                "type": "array",
-                "minItems": 2,
-                "items": {
-                  "type": "number"
-                }
-              }
-            },
-            "required": ["type", "translation"]
-          }
-        ]
-      }
+        "$ref": "#/$defs/Multiscale"
+      },
+      "minItems": 1,
+      "title": "Multiscales",
+      "type": "array"
+    },
+    "omero": {
+      "anyOf": [
+        {
+          "$ref": "#/$defs/Omero"
+        },
+        {
+          "type": "null"
+        }
+      ],
+      "default": null
     }
-  }
+  },
+  "required": [
+    "multiscales"
+  ],
+  "title": "NGFF Image",
+  "type": "object",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://ngff.openmicroscopy.org/latest/schemas/image.schema"
 }
diff --git a/latest/schemas/image_old.schema b/latest/schemas/image_old.schema
new file mode 100644
index 00000000..cf2b477f
--- /dev/null
+++ b/latest/schemas/image_old.schema
@@ -0,0 +1,229 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://ngff.openmicroscopy.org/latest/schemas/image.schema",
+  "title": "NGFF Image",
+  "description": "JSON from OME-NGFF .zattrs",
+  "type": "object",
+  "properties": {
+    "multiscales": {
+      "description": "The multiscale datasets for this image",
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string"
+          },
+          "datasets": {
+            "type": "array",
+            "minItems": 1,
+            "items": {
+              "type": "object",
+              "properties": {
+                "path": {
+                  "type": "string"
+                },
+                "coordinateTransformations": {
+                  "$ref": "#/$defs/coordinateTransformations"
+                }
+              },
+              "required": ["path", "coordinateTransformations"]
+            }
+          },
+          "version": {
+            "type": "string",
+            "enum": [
+              "0.5-dev"
+            ]
+          },
+          "axes": {
+            "$ref": "#/$defs/axes"
+          },
+          "coordinateTransformations": {
+            "$ref": "#/$defs/coordinateTransformations"
+                }
+        },
+        "required": [
+          "datasets", "axes", "version"
+        ]
+      },
+      "minItems": 1,
+      "uniqueItems": true
+    },
+    "omero": {
+      "type": "object",
+      "properties": {
+        "channels": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "properties": {
+              "window": {
+                "type": "object",
+                "properties": {
+                  "end": {
+                    "type": "number"
+                  },
+                  "max": {
+                    "type": "number"
+                  },
+                  "min": {
+                    "type": "number"
+                  },
+                  "start": {
+                    "type": "number"
+                  }
+                },
+                "required": [
+                  "start",
+                  "min",
+                  "end",
+                  "max"
+                ]
+              },
+              "label": {
+                "type": "string"
+              },
+              "family": {
+                "type": "string"
+              },
+              "color": {
+                "type": "string"
+              },
+              "active": {
+                "type": "boolean"
+              }
+            }
+          }
+        }
+      },
+      "required": [
+        "channels"
+      ]
+    }
+  },
+  "required": [ "multiscales" ],
+
+  "$defs": {
+    "axes": {
+      "type": "array",
+      "uniqueItems": true,
+      "minItems": 2,
+      "maxItems": 5,
+      "contains": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string"
+          },
+          "type": {
+            "type": "string",
+            "enum": ["space"]
+          },
+          "unit": {
+            "type": "string"
+          }
+        }
+      },
+      "minContains": 2,
+      "maxContains": 3,
+      "items": {
+        "oneOf": [
+          {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "type": {
+                "type": "string",
+                "enum": ["channel", "time", "space"]
+              }
+            },
+            "required": ["name", "type"]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "type": {
+                "type": "string",
+                "not": {
+                  "enum": ["space", "time", "channel"]
+                }
+              }
+            },
+            "required": ["name"]
+          }
+        ]
+      }
+    },
+    "coordinateTransformations": {
+      "type": "array",
+      "minItems": 1,
+      "contains": {
+        "type": "object",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": [
+              "scale"
+            ]
+          },
+          "scale": {
+            "type": "array",
+            "minItems": 2,
+            "items": {
+              "type": "number"
+            }
+          }
+        }
+      },
+      "maxContains": 1,
+      "items": {
+        "oneOf": [
+          {
+            "type": "object",
+            "properties": {
+              "type": {
+                "type": "string",
+                "enum": [
+                  "scale"
+                ]
+              },
+              "scale": {
+                "type": "array",
+                "minItems": 2,
+                "items": {
+                  "type": "number"
+                }
+              }
+            },
+            "required": ["type", "scale"]
+          },
+          {
+            "type": "object",
+            "properties": {
+              "type": {
+                "type": "string",
+                "enum": [
+                  "translation"
+                ]
+              },
+              "translation": {
+                "type": "array",
+                "minItems": 2,
+                "items": {
+                  "type": "number"
+                }
+              }
+            },
+            "required": ["type", "translation"]
+          }
+        ]
+      }
+    }
+  }
+}

From d58c8145542e9cda54bcd7dc206ec65a087396d7 Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Tue, 21 May 2024 11:46:19 +0200
Subject: [PATCH 05/10] remove outdated tests

---
 latest/tests/image_suite.json | 166 ----------------------------------
 1 file changed, 166 deletions(-)

diff --git a/latest/tests/image_suite.json b/latest/tests/image_suite.json
index bec52214..ee3da555 100644
--- a/latest/tests/image_suite.json
+++ b/latest/tests/image_suite.json
@@ -298,42 +298,6 @@
       },
       "valid": false
     },
-    {
-      "formerly": "invalid/missing_space_axes.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "t",
-                "type": "time"
-              },
-              {
-                "name": "c",
-                "type": "channel"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
     {
       "formerly": "invalid/invalid_transformation_type.json",
       "description": "TBD",
@@ -410,62 +374,6 @@
       },
       "valid": false
     },
-    {
-      "formerly": "invalid/too_many_axes.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "angle",
-                "type": "custom"
-              },
-              {
-                "name": "t",
-                "type": "time"
-              },
-              {
-                "name": "c",
-                "type": "channel"
-              },
-              {
-                "name": "z",
-                "type": "space"
-              },
-              {
-                "name": "y",
-                "type": "space"
-              },
-              {
-                "name": "x",
-                "type": "space"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1,
-                      1,
-                      1,
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
     {
       "formerly": "invalid/invalid_channels_color.json",
       "description": "TBD",
@@ -557,80 +465,6 @@
       },
       "valid": false
     },
-    {
-      "formerly": "invalid/invalid_axes_count.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "y",
-                "type": "space",
-                "unit": "micrometer"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
-    {
-      "formerly": "invalid/one_space_axes.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "t",
-                "type": "time"
-              },
-              {
-                "name": "c",
-                "type": "channel"
-              },
-              {
-                "name": "x",
-                "type": "space"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
     {
       "formerly": "invalid/invalid_path.json",
       "description": "TBD",

From cf45296c172c856fa96255469acc2ee43395e44c Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Tue, 21 May 2024 13:23:19 +0200
Subject: [PATCH 06/10] move models to their own folder, and update omero model

---
 latest/models/image.py      | 83 +++++++++++++++++++++++++++++++++++++
 latest/schemas/image.schema | 35 ++++++++++++++--
 2 files changed, 114 insertions(+), 4 deletions(-)
 create mode 100644 latest/models/image.py

diff --git a/latest/models/image.py b/latest/models/image.py
new file mode 100644
index 00000000..33eda6f2
--- /dev/null
+++ b/latest/models/image.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+from typing import Literal, Optional
+from pydantic import BaseModel, ConfigDict, conlist
+from typing import Annotated, Hashable, List, TypeVar
+from pydantic_core import PydanticCustomError
+from pydantic import AfterValidator, Field, ValidationError
+import json
+
+T = TypeVar('T', bound=Hashable)
+
+def _validate_unique_list(v: list[T]) -> list[T]:
+    if len(v) != len(set(v)):
+        raise PydanticCustomError('unique_list', 'List must be unique')
+    return v
+
+UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True})]
+
+class Axis(BaseModel, frozen=True):
+    name: str
+    type: Optional[str] = None
+    unit: Optional[str] = None
+
+class ScaleTransform(BaseModel, frozen=True):
+    type: Literal["scale"]
+    scale: conlist(float, min_length=1)
+
+class TranslationTransform(BaseModel, frozen=True):
+    type: Literal["translation"]
+    translation: conlist(float, min_length=1)
+
+class Dataset(BaseModel, frozen=True):
+    path: str
+    coordinateTransformations: tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]
+
+class Multiscale(BaseModel, frozen=True):
+    """
+    The multiscale datasets for this image
+    """
+    name: Optional[str] = None
+    datasets: conlist(Dataset, min_length=1)
+    axes: UniqueList[Axis]
+    coordinateTransformations: Optional[tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]] = None
+    version: Literal['0.5-dev']
+
+class Window(BaseModel, frozen=True):
+    start: float
+    end: float
+    min: float
+    max: float
+
+class OmeroRenderingSettings(BaseModel, frozen=True):
+    window: Window
+    label: str
+    family: str
+    color: str
+    active: bool
+
+class OmeroRdef(BaseModel, frozen=True):
+    defaultT: int
+    defaultZ: int
+    model: str
+
+class Omero(BaseModel, frozen=True):
+    channels: list[OmeroRenderingSettings]
+    rdefs: OmeroRdef
+
+class GroupMetadata(BaseModel, frozen=True):
+    """
+    JSON from OME-NGFF .zattrs
+    """
+    model_config = ConfigDict(title="NGFF Image")
+    multiscales: conlist(Multiscale, min_length=1)
+    omero: Optional[Omero] = None
+
+def make_schema():
+    schema = GroupMetadata.model_json_schema()
+    schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
+    schema["$id"] = "https://ngff.openmicroscopy.org/latest/schemas/image.schema"
+
+    print(json.dumps(schema, indent=2))
+
+if __name__ == '__main__':
+    make_schema()
\ No newline at end of file
diff --git a/latest/schemas/image.schema b/latest/schemas/image.schema
index dd2e18df..1fa9059f 100644
--- a/latest/schemas/image.schema
+++ b/latest/schemas/image.schema
@@ -163,19 +163,46 @@
       "properties": {
         "channels": {
           "items": {
-            "$ref": "#/$defs/RenderingSettings"
+            "$ref": "#/$defs/OmeroRenderingSettings"
           },
           "title": "Channels",
           "type": "array"
+        },
+        "rdefs": {
+          "$ref": "#/$defs/OmeroRdef"
         }
       },
       "required": [
-        "channels"
+        "channels",
+        "rdefs"
       ],
       "title": "Omero",
       "type": "object"
     },
-    "RenderingSettings": {
+    "OmeroRdef": {
+      "properties": {
+        "defaultT": {
+          "title": "Defaultt",
+          "type": "integer"
+        },
+        "defaultZ": {
+          "title": "Defaultz",
+          "type": "integer"
+        },
+        "model": {
+          "title": "Model",
+          "type": "string"
+        }
+      },
+      "required": [
+        "defaultT",
+        "defaultZ",
+        "model"
+      ],
+      "title": "OmeroRdef",
+      "type": "object"
+    },
+    "OmeroRenderingSettings": {
       "properties": {
         "window": {
           "$ref": "#/$defs/Window"
@@ -204,7 +231,7 @@
         "color",
         "active"
       ],
-      "title": "RenderingSettings",
+      "title": "OmeroRenderingSettings",
       "type": "object"
     },
     "ScaleTransform": {

From 77333b526f5bce1b820ac84bd8e1fcb4e9b86b51 Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Tue, 21 May 2024 13:30:40 +0200
Subject: [PATCH 07/10] remove extra model.py file, update schema and tests

---
 latest/models.py              | 77 --------------------------------
 latest/models/image.py        |  2 +-
 latest/schemas/image.schema   |  1 +
 latest/tests/image_suite.json | 84 -----------------------------------
 4 files changed, 2 insertions(+), 162 deletions(-)
 delete mode 100644 latest/models.py

diff --git a/latest/models.py b/latest/models.py
deleted file mode 100644
index 2323749f..00000000
--- a/latest/models.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from __future__ import annotations
-from typing import Literal, Optional
-from pydantic import BaseModel, ConfigDict, conlist
-from typing import Annotated, Hashable, List, TypeVar
-from pydantic_core import PydanticCustomError
-from pydantic import AfterValidator, Field, ValidationError
-
-T = TypeVar('T', bound=Hashable)
-
-def _validate_unique_list(v: list[T]) -> list[T]:
-    if len(v) != len(set(v)):
-        raise PydanticCustomError('unique_list', 'List must be unique')
-    return v
-
-UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True})]
-
-class Axis(BaseModel, frozen=True):
-    name: str
-    type: Optional[str] = None
-    unit: Optional[str] = None
-
-class ScaleTransform(BaseModel, frozen=True):
-    type: Literal["scale"]
-    scale: conlist(float, min_length=1)
-
-class TranslationTransform(BaseModel, frozen=True):
-    type: Literal["translation"]
-    translation: conlist(float, min_length=1)
-
-class Dataset(BaseModel, frozen=True):
-    path: str
-    coordinateTransformations: tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]
-
-class Multiscale(BaseModel, frozen=True):
-    """
-    The multiscale datasets for this image
-    """
-    name: Optional[str] = None
-    datasets: conlist(Dataset, min_length=1)
-    axes: UniqueList[Axis]
-    coordinateTransformations: Optional[tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]] = None
-    version: Literal['0.5-dev']
-
-class Window(BaseModel, frozen=True):
-    start: float
-    end: float
-    min: float
-    max: float
-
-class RenderingSettings(BaseModel, frozen=True):
-    window: Window
-    label: str
-    family: str
-    color: str
-    active: bool
-
-class Omero(BaseModel, frozen=True):
-    channels: list[RenderingSettings]
-
-class GroupMetadata(BaseModel, frozen=True):
-    """
-    JSON from OME-NGFF .zattrs
-    """
-    model_config = ConfigDict(title="NGFF Image")
-    multiscales: conlist(Multiscale, min_length=1)
-    omero: Optional[Omero] = None
-
-def make_schema():
-    import json
-    schema = GroupMetadata.model_json_schema()
-    schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
-    schema["$id"] = "https://ngff.openmicroscopy.org/latest/schemas/image.schema"
-
-    print(json.dumps(schema, indent=2))
-
-if __name__ == '__main__':
-    make_schema()
\ No newline at end of file
diff --git a/latest/models/image.py b/latest/models/image.py
index 33eda6f2..0a6bae65 100644
--- a/latest/models/image.py
+++ b/latest/models/image.py
@@ -13,7 +13,7 @@ def _validate_unique_list(v: list[T]) -> list[T]:
         raise PydanticCustomError('unique_list', 'List must be unique')
     return v
 
-UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True})]
+UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True}, min_length=1)]
 
 class Axis(BaseModel, frozen=True):
     name: str
diff --git a/latest/schemas/image.schema b/latest/schemas/image.schema
index 1fa9059f..cff2114b 100644
--- a/latest/schemas/image.schema
+++ b/latest/schemas/image.schema
@@ -106,6 +106,7 @@
           "items": {
             "$ref": "#/$defs/Axis"
           },
+          "minItems": 1,
           "title": "Axes",
           "type": "array",
           "uniqueItems": true
diff --git a/latest/tests/image_suite.json b/latest/tests/image_suite.json
index ee3da555..0af93ec0 100644
--- a/latest/tests/image_suite.json
+++ b/latest/tests/image_suite.json
@@ -728,44 +728,6 @@
       },
       "valid": false
     },
-    {
-      "formerly": "invalid/invalid_axis_type.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "y",
-                "type": "invalid",
-                "unit": "micrometer"
-              },
-              {
-                "name": "x",
-                "type": "space",
-                "unit": "micrometer"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
     {
       "formerly": "invalid/duplicate_scale.json",
       "description": "TBD",
@@ -838,52 +800,6 @@
       },
       "valid": false
     },
-    {
-      "formerly": "invalid/too_many_space_axes.json",
-      "description": "TBD",
-      "data": {
-        "multiscales": [
-          {
-            "axes": [
-              {
-                "name": "X",
-                "type": "space"
-              },
-              {
-                "name": "z",
-                "type": "space"
-              },
-              {
-                "name": "y",
-                "type": "space"
-              },
-              {
-                "name": "x",
-                "type": "space"
-              }
-            ],
-            "datasets": [
-              {
-                "path": "0",
-                "coordinateTransformations": [
-                  {
-                    "scale": [
-                      1,
-                      1,
-                      1,
-                      1
-                    ],
-                    "type": "scale"
-                  }
-                ]
-              }
-            ],
-            "version": "0.5-dev"
-          }
-        ]
-      },
-      "valid": false
-    },
     {
       "formerly": "invalid/no_multiscales.json",
       "description": "TBD",

From 766f9356b6289ace5bb5bafae7677b70b9f3a5ca Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Tue, 21 May 2024 13:45:43 +0200
Subject: [PATCH 08/10] remove old image schema

---
 latest/schemas/image_old.schema | 229 --------------------------------
 1 file changed, 229 deletions(-)
 delete mode 100644 latest/schemas/image_old.schema

diff --git a/latest/schemas/image_old.schema b/latest/schemas/image_old.schema
deleted file mode 100644
index cf2b477f..00000000
--- a/latest/schemas/image_old.schema
+++ /dev/null
@@ -1,229 +0,0 @@
-{
-  "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://ngff.openmicroscopy.org/latest/schemas/image.schema",
-  "title": "NGFF Image",
-  "description": "JSON from OME-NGFF .zattrs",
-  "type": "object",
-  "properties": {
-    "multiscales": {
-      "description": "The multiscale datasets for this image",
-      "type": "array",
-      "items": {
-        "type": "object",
-        "properties": {
-          "name": {
-            "type": "string"
-          },
-          "datasets": {
-            "type": "array",
-            "minItems": 1,
-            "items": {
-              "type": "object",
-              "properties": {
-                "path": {
-                  "type": "string"
-                },
-                "coordinateTransformations": {
-                  "$ref": "#/$defs/coordinateTransformations"
-                }
-              },
-              "required": ["path", "coordinateTransformations"]
-            }
-          },
-          "version": {
-            "type": "string",
-            "enum": [
-              "0.5-dev"
-            ]
-          },
-          "axes": {
-            "$ref": "#/$defs/axes"
-          },
-          "coordinateTransformations": {
-            "$ref": "#/$defs/coordinateTransformations"
-                }
-        },
-        "required": [
-          "datasets", "axes", "version"
-        ]
-      },
-      "minItems": 1,
-      "uniqueItems": true
-    },
-    "omero": {
-      "type": "object",
-      "properties": {
-        "channels": {
-          "type": "array",
-          "items": {
-            "type": "object",
-            "properties": {
-              "window": {
-                "type": "object",
-                "properties": {
-                  "end": {
-                    "type": "number"
-                  },
-                  "max": {
-                    "type": "number"
-                  },
-                  "min": {
-                    "type": "number"
-                  },
-                  "start": {
-                    "type": "number"
-                  }
-                },
-                "required": [
-                  "start",
-                  "min",
-                  "end",
-                  "max"
-                ]
-              },
-              "label": {
-                "type": "string"
-              },
-              "family": {
-                "type": "string"
-              },
-              "color": {
-                "type": "string"
-              },
-              "active": {
-                "type": "boolean"
-              }
-            }
-          }
-        }
-      },
-      "required": [
-        "channels"
-      ]
-    }
-  },
-  "required": [ "multiscales" ],
-
-  "$defs": {
-    "axes": {
-      "type": "array",
-      "uniqueItems": true,
-      "minItems": 2,
-      "maxItems": 5,
-      "contains": {
-        "type": "object",
-        "properties": {
-          "name": {
-            "type": "string"
-          },
-          "type": {
-            "type": "string",
-            "enum": ["space"]
-          },
-          "unit": {
-            "type": "string"
-          }
-        }
-      },
-      "minContains": 2,
-      "maxContains": 3,
-      "items": {
-        "oneOf": [
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "type": {
-                "type": "string",
-                "enum": ["channel", "time", "space"]
-              }
-            },
-            "required": ["name", "type"]
-          },
-          {
-            "type": "object",
-            "properties": {
-              "name": {
-                "type": "string"
-              },
-              "type": {
-                "type": "string",
-                "not": {
-                  "enum": ["space", "time", "channel"]
-                }
-              }
-            },
-            "required": ["name"]
-          }
-        ]
-      }
-    },
-    "coordinateTransformations": {
-      "type": "array",
-      "minItems": 1,
-      "contains": {
-        "type": "object",
-        "properties": {
-          "type": {
-            "type": "string",
-            "enum": [
-              "scale"
-            ]
-          },
-          "scale": {
-            "type": "array",
-            "minItems": 2,
-            "items": {
-              "type": "number"
-            }
-          }
-        }
-      },
-      "maxContains": 1,
-      "items": {
-        "oneOf": [
-          {
-            "type": "object",
-            "properties": {
-              "type": {
-                "type": "string",
-                "enum": [
-                  "scale"
-                ]
-              },
-              "scale": {
-                "type": "array",
-                "minItems": 2,
-                "items": {
-                  "type": "number"
-                }
-              }
-            },
-            "required": ["type", "scale"]
-          },
-          {
-            "type": "object",
-            "properties": {
-              "type": {
-                "type": "string",
-                "enum": [
-                  "translation"
-                ]
-              },
-              "translation": {
-                "type": "array",
-                "minItems": 2,
-                "items": {
-                  "type": "number"
-                }
-              }
-            },
-            "required": ["type", "translation"]
-          }
-        ]
-      }
-    }
-  }
-}

From 800374dbf9d361e0bba31b6b9c68c369d1d50b58 Mon Sep 17 00:00:00 2001
From: Davis Vann Bennett 
Date: Tue, 21 May 2024 18:31:20 +0200
Subject: [PATCH 09/10] add guidance for partial implementations

---
 latest/index.bs | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/latest/index.bs b/latest/index.bs
index f13a2df0..54222953 100644
--- a/latest/index.bs
+++ b/latest/index.bs
@@ -562,6 +562,22 @@ were added before this was adopted, but they should be updated in due course.
 Implementations {#implementations}
 ==================================
 
+Partial support {#partial-support}
+----------------------------------
+
+Inevitably, some software libraries and applications will not support this entire specification, but rather some of it. 
+How should partial implementations respond to data defined via unsupported parts of the specification? This specification does not answer this question in general, but it does provide guidance for a specific scenario:
+if a partial implementation normalizes input data, e.g. by converting that data to a form consistent with the subset of the specification supported by the implementation, then 
+the implementation SHOULD provide a clear indication to users and developers when such data normalization occurs. 
+
+For example, suppose an implementation can only represent 3-dimensional images, and additionally that implementation reads 2-dimensional images by converting them internally to a 3-dimensional representation; 
+In this case, the implementation SHOULD make a reasonable effort to inform users that this transformation is occurring, e.g. by displaying an alert message or warning, or by printing relevant information to logs.
+
+This allows implementations flexibility to implement a subset of the specification, while also protecting users from surprising data transformations.
+
+List of implementations {#list-of-implementations}
+--------------------------------------------------
+
 Projects which support reading and/or writing OME-NGFF data include:
 
 
From aa5c9532d1155bf3040d7bdf951ae63fab8aa560 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Wed, 22 May 2024 10:18:09 +0200 Subject: [PATCH 10/10] remove pydantic models --- latest/models/image.py | 83 ------------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 latest/models/image.py diff --git a/latest/models/image.py b/latest/models/image.py deleted file mode 100644 index 0a6bae65..00000000 --- a/latest/models/image.py +++ /dev/null @@ -1,83 +0,0 @@ -from __future__ import annotations -from typing import Literal, Optional -from pydantic import BaseModel, ConfigDict, conlist -from typing import Annotated, Hashable, List, TypeVar -from pydantic_core import PydanticCustomError -from pydantic import AfterValidator, Field, ValidationError -import json - -T = TypeVar('T', bound=Hashable) - -def _validate_unique_list(v: list[T]) -> list[T]: - if len(v) != len(set(v)): - raise PydanticCustomError('unique_list', 'List must be unique') - return v - -UniqueList = Annotated[List[T], AfterValidator(_validate_unique_list), Field(json_schema_extra={'uniqueItems': True}, min_length=1)] - -class Axis(BaseModel, frozen=True): - name: str - type: Optional[str] = None - unit: Optional[str] = None - -class ScaleTransform(BaseModel, frozen=True): - type: Literal["scale"] - scale: conlist(float, min_length=1) - -class TranslationTransform(BaseModel, frozen=True): - type: Literal["translation"] - translation: conlist(float, min_length=1) - -class Dataset(BaseModel, frozen=True): - path: str - coordinateTransformations: tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform] - -class Multiscale(BaseModel, frozen=True): - """ - The multiscale datasets for this image - """ - name: Optional[str] = None - datasets: conlist(Dataset, min_length=1) - axes: UniqueList[Axis] - coordinateTransformations: Optional[tuple[ScaleTransform] | tuple[ScaleTransform, TranslationTransform]] = None - version: Literal['0.5-dev'] - -class Window(BaseModel, frozen=True): - start: float - end: float - min: float - max: float - -class OmeroRenderingSettings(BaseModel, frozen=True): - window: Window - label: str - family: str - color: str - active: bool - -class OmeroRdef(BaseModel, frozen=True): - defaultT: int - defaultZ: int - model: str - -class Omero(BaseModel, frozen=True): - channels: list[OmeroRenderingSettings] - rdefs: OmeroRdef - -class GroupMetadata(BaseModel, frozen=True): - """ - JSON from OME-NGFF .zattrs - """ - model_config = ConfigDict(title="NGFF Image") - multiscales: conlist(Multiscale, min_length=1) - omero: Optional[Omero] = None - -def make_schema(): - schema = GroupMetadata.model_json_schema() - schema["$schema"] = "https://json-schema.org/draft/2020-12/schema" - schema["$id"] = "https://ngff.openmicroscopy.org/latest/schemas/image.schema" - - print(json.dumps(schema, indent=2)) - -if __name__ == '__main__': - make_schema() \ No newline at end of file