diff --git a/.readthedocs.yml b/.readthedocs.yml index 722c2ce..ff6c14c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,6 +11,7 @@ python: path: . extra_requirements: - docs + - pydantic mkdocs: configuration: mkdocs.yml diff --git a/docs/tutorial.py b/docs/tutorial.py index 0c043da..a32da28 100644 --- a/docs/tutorial.py +++ b/docs/tutorial.py @@ -1,55 +1,84 @@ # # Tutorial -# from ome_zarr_models.v04 import Image as OMEZarrImage +from rich.pretty import pprint -# my_image = OMEZarrImage(path="path/to/ome/zarr/directory.ome.zarr") -# print(my_image.multiscales) - -# ## Creating -# -# TODO: exmaple of creating a model from a remote store -# TODO: example of import gcsfs import zarr import zarr.storage +from ome_zarr_models.v04 import Image +from ome_zarr_models.v04.coordinate_transformations import ( + VectorTranslation, +) + +# ## Creating models +# +# We can create an Image model from a zarr group, that points to an +# OME-zarr dataset: + +# Setup zarr group from a remote store bucket = "ucl-hip-ct-35a68e99feaae8932b1d44da0358940b" fs = gcsfs.GCSFileSystem(project=bucket, token="anon", access="read_only") store = zarr.storage.FSStore(url=bucket, fs=fs) group = zarr.open_group( store=store, path="S-20-28/heart/25.27um_complete-organ_bm05.ome.zarr" ) -print(group) -exit() + +ome_zarr_image = Image(group) + +# Oh no, it failed! One of the key goals of this package is to eagerly validatemetadata, so you can realise it's wrong. +# +# Lets try that again, but with some valid OME-zarr data + +group = zarr.open("https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr") +ome_zarr_image = Image(group) +pprint(ome_zarr_image) + +# This image contains both the zarr group, and a model of the multiscales metadata + +multiscales_meta = ome_zarr_image.multiscales +pprint(multiscales_meta) # ## Updating models # -# All the fields in the models can be updated in place. -# When you do this, any validation on the individual field -# you are updating will take place. +# All the fields in the models can be updated in place. When you do this, any validation on the individual field you are updating will take place. # -# For example, we can [do something valid]: +# For example, there is no name for the first multiscales entry, so lets add it + +multiscales_meta[0].name = "The first multiscales entry" +pprint(multiscales_meta) +# One constraint in the OME-zarr spec is that the coordiante transforms have to be a scale, or a scale then tranlsation (strictly in that order). So if we try and make a transformation just a translation, it will raise an error. + +multiscales_meta[0].datasets[0].coordinateTransformations = VectorTranslation( + type="translation", translation=[1, 2, 3] +) -# but if you try and [do something invalid] it raises an error: # This means validation happens early, allowing you to catch errors # before getting too far. # ## Writing metadata # -# TODO: example of writing out the metadata again +# To save the metadata after editing, we can use the ``save_attrs()`` method. +# TODO: Use a local file for testing that we have write access to, so we +# can demonstrate this. +# + +# + +# ome_zarr_image.save_attrs() +# - # ## Accessing data # -# Although these models do not handle reading or writing data, -# they do expose the zarr arrays. +# Although these models do not handle reading or writing data, they do expose the zarr arrays. + +zarr_arr = ome_zarr_image.group[multiscales_meta[0].datasets[0].path] +pprint(zarr_arr) # ## Not using validation # -# If you *really* want to create models that are not validated against -# the OME-zarr specifciation, you can use the ``model_construct`` method. -# For example: +# If you want to create models that are not validated against the OME-zarr specifciation, you can use the ``model_construct`` method on the models. + -# Put some bad code here diff --git a/mkdocs.yml b/mkdocs.yml index 2dd2c4f..63ee94e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,9 @@ theme: name: material plugins: - - mkdocs-jupyter + - mkdocs-jupyter: + execute: true + allow_errors: true - mkdocstrings: handlers: python: diff --git a/pyproject.toml b/pyproject.toml index c9e40d5..42d0ea3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,11 @@ docs = [ "mkdocstrings-python>=1.12.2", "mkdocs-material", "mkdocs-jupyter", + "gcsfs", + "rich", + "zarr<3", ] +pydantic = ["pydantic"] [tool.hatch.version] source = "vcs" @@ -28,6 +32,7 @@ docs = [ "mkdocs-material", "mkdocs-jupyter", "gcsfs", + "rich", "zarr<3", ] dev = [ @@ -40,7 +45,7 @@ dev = [ test = ["pytest"] -pydantic=["pydantic"] +pydantic = ["pydantic"] [tool.uv] default-groups = ["docs", "dev", "pydantic", "test"] diff --git a/src/ome_zarr_models/base.py b/src/ome_zarr_models/base.py index ba9c610..dbf3db1 100644 --- a/src/ome_zarr_models/base.py +++ b/src/ome_zarr_models/base.py @@ -5,3 +5,6 @@ class Base(pydantic.BaseModel): """ The base pydantic model for all metadata classes """ + + class Config: + validate_assignment = True diff --git a/src/ome_zarr_models/utils.py b/src/ome_zarr_models/utils.py index 62785b7..435a193 100644 --- a/src/ome_zarr_models/utils.py +++ b/src/ome_zarr_models/utils.py @@ -3,14 +3,17 @@ from pydantic import create_model from typing import TypeVar + T = TypeVar("T") + def _unique_items_validator(values: list[T]) -> list[T]: for ind, value in enumerate(values, start=1): if value in values[ind:]: raise ValueError(f"Non-unique values in {values}.") return values + def dataclass_to_pydantic(dataclass_type: type) -> type[pydantic.BaseModel]: """Convert a dataclass to a Pydantic model. @@ -18,7 +21,7 @@ def dataclass_to_pydantic(dataclass_type: type) -> type[pydantic.BaseModel]: ---------- dataclass_type : type The dataclass to convert to a Pydantic model. - + Returns ------- type[pydantic.BaseModel] a Pydantic model class. @@ -38,8 +41,4 @@ def dataclass_to_pydantic(dataclass_type: type) -> type[pydantic.BaseModel]: # No default value field_definitions[_field.name] = (_field.type, Ellipsis) -<<<<<<< HEAD - return create_model(dataclass_type.__name__, **field_definitions) -======= return create_model(dataclass_type.__name__, **field_definitions) ->>>>>>> 470f4f1a33aef4ecf2ebf0906c912a3621c8957b diff --git a/src/ome_zarr_models/v04/__init__.py b/src/ome_zarr_models/v04/__init__.py index 1c594f2..1c109b9 100644 --- a/src/ome_zarr_models/v04/__init__.py +++ b/src/ome_zarr_models/v04/__init__.py @@ -5,6 +5,7 @@ VectorScale, VectorTranslation, ) +from ome_zarr_models.v04.image import Image from ome_zarr_models.v04.multiscales import ( Dataset, Multiscale, diff --git a/src/ome_zarr_models/v04/image.py b/src/ome_zarr_models/v04/image.py new file mode 100644 index 0000000..9f93e61 --- /dev/null +++ b/src/ome_zarr_models/v04/image.py @@ -0,0 +1,21 @@ +import zarr + +from ome_zarr_models.v04.multiscales import Multiscale, MultiscaleGroupAttrs +from ome_zarr_models.v04.omero import Omero + + +class Image: + def __init__(self, group: zarr.Group): + self.group = group + self._attrs = MultiscaleGroupAttrs(**group.attrs.asdict()) + + @property + def multiscales(self) -> list[Multiscale]: + return self._attrs.multiscales + + @property + def omero(self) -> Omero | None: + return self._attrs.omero + + def save_attrs(self) -> None: + self.group.attrs.put(self._attrs.model_dump_json()) diff --git a/src/ome_zarr_models/v04/multiscales.py b/src/ome_zarr_models/v04/multiscales.py index 3bfa266..c040d65 100644 --- a/src/ome_zarr_models/v04/multiscales.py +++ b/src/ome_zarr_models/v04/multiscales.py @@ -3,7 +3,7 @@ from pydantic import Field, field_validator from ome_zarr_models.base import Base -from ome_zarr_models.utils import unique_items_validator +from ome_zarr_models.utils import _unique_items_validator from ome_zarr_models.v04.axes import Axis from ome_zarr_models.v04.coordinate_transformations import ( PathScale, @@ -50,7 +50,7 @@ class Multiscale(Base): metadata: Any = None name: Any | None = None type: Any = None - _check_unique = field_validator("axes")(unique_items_validator) + _check_unique = field_validator("axes")(_unique_items_validator) class MultiscaleGroupAttrs(Base): @@ -66,4 +66,4 @@ class MultiscaleGroupAttrs(Base): min_length=1, ) omero: Omero | None = None - _check_unique = field_validator("multiscales")(unique_items_validator) + _check_unique = field_validator("multiscales")(_unique_items_validator) diff --git a/src/ome_zarr_models/v04/well.py b/src/ome_zarr_models/v04/well.py index 09d75c3..4b67832 100644 --- a/src/ome_zarr_models/v04/well.py +++ b/src/ome_zarr_models/v04/well.py @@ -1,7 +1,7 @@ from pydantic import Field, field_validator from ome_zarr_models.base import Base -from ome_zarr_models.utils import unique_items_validator +from ome_zarr_models.utils import _unique_items_validator class ImageInWell(Base): @@ -37,7 +37,7 @@ class Well(Base): ..., description="The images included in this well", min_length=1 ) version: str | None = Field(None, description="The version of the specification") - _check_unique = field_validator("images")(unique_items_validator) + _check_unique = field_validator("images")(_unique_items_validator) class NgffWellMeta(Base):