From 0eb5dd1b0fd78f9f9ab450e53481a702d1c144b9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 18 May 2026 15:25:10 +0200 Subject: [PATCH 01/95] feat: implement 0.6 in NgffMultiscales class --- ome_zarr/classes/image.py | 170 +++++++++++++++++++++++++++++++------- ome_zarr/writer.py | 17 ++-- 2 files changed, 146 insertions(+), 41 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index fccc2074..e8fd9829 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -4,31 +4,32 @@ from collections.abc import Sequence from dataclasses import InitVar, dataclass, field from typing import Any, cast +import typing import dask.array as da import numpy as np import zarr from ome_zarr_models.common.image_label_types import LabelBase as Label from ome_zarr_models.common.omero import Omero -from ome_zarr_models.v05.axes import ( + +from ome_zarr_models._v06.coordinate_transforms import ( + Scale, + Translation, + Identity, + Sequence as TransformSequence, Axis, + CoordinateSystem, + CoordinateSystemIdentifier, + AnyTransform, ) -from ome_zarr_models.v05.coordinate_transformations import ( - Identity as Identity, -) -from ome_zarr_models.v05.coordinate_transformations import ( - VectorScale as Scale, -) -from ome_zarr_models.v05.coordinate_transformations import ( - VectorTranslation as Translation, -) -from ome_zarr_models.v05.multiscales import ( +from ome_zarr_models._v06.multiscales import ( Dataset, + Multiscale as MultiscaleV06, ) from ome_zarr_models.v05.multiscales import ( Multiscale as MultiscaleV05, ) -from pydantic import ValidationError +from pydantic import ValidationError, TypeAdapter from ome_zarr.scale import Methods @@ -165,7 +166,7 @@ class NgffMultiscales: list[int] | tuple[int, ...] | list[dict[str, int]] | None ] = None method: str | Methods | None = Methods.RESIZE - coordinateTransformations: InitVar[list[Scale | Translation | Identity] | None] = ( + coordinateTransformations: InitVar[tuple[AnyTransform, ...] | list[dict[str, Any]] | None] = ( None ) labels: ( @@ -173,12 +174,13 @@ class NgffMultiscales: ) = None omero: dict[str, Any] | Omero | None = None image_label: dict[str, Any] | Label | None = None + default_coordinate_system_name: str = "physical" def __post_init__( self, image: NgffImage, scale_factors: list[int] | tuple[int, ...] | list[dict[str, int]] | None, - coordinateTransformations: list[Scale | Translation | Identity] | None, + coordinateTransformations: tuple[AnyTransform, ...] | list[dict[str, Any]] | None, ): from ome_zarr.scale import _build_pyramid @@ -242,7 +244,13 @@ def __post_init__( coordinateTransformations=( Scale( type="scale", - scale=list(level_scale.values()), + scale=tuple(level_scale.values()), + input=CoordinateSystemIdentifier( + path=f"s{idx}" + ), + output=CoordinateSystemIdentifier( + name=self.default_coordinate_system_name + ) ), ), ) @@ -265,11 +273,55 @@ def __post_init__( else: axes.append(Axis(name=d, type="custom", unit=image.axes_units.get(d))) - self.metadata = MultiscaleV05( + coordinate_system = CoordinateSystem( axes=tuple(axes), + name=self.default_coordinate_system_name, + ) + + # coerce coordinateTransformations to ozmp object if passed as dict + transforms = None + if coordinateTransformations is not None: + transforms = [] + for idx, tf in enumerate(coordinateTransformations): + if isinstance(tf, dict): + transforms.append(TypeAdapter(AnyTransform).validate_python(tf)) + elif tf is AnyTransform: + transforms.append(tf) + else: + raise ValueError( + f"Invalid coordinate transformation at index {idx}: {tf}" + ) + transforms = tuple(transforms) + + # Some checks on the transform's input and output coordinate system + for idx, tf in enumerate(transforms): + + # first, check that they are not None + if tf.input is None: + raise ValueError( + f"Coordinate transformation at index {idx} is missing input coordinate system: {tf}" + ) + if tf.output is None: + raise ValueError( + f"Coordinate transformation at index {idx} is missing output coordinate system: {tf}" + ) + + # now check that either input or output name matches the default coordinate system name + if ( + tf.input.name != self.default_coordinate_system_name + and tf.output.name != self.default_coordinate_system_name + ): + raise ValueError( + f"Coordinate transformation at index {idx} must have either " + f"input or output coordinate system name matching the default " + f"coordinate system name '{self.default_coordinate_system_name}': {tf}" + ) + + self.metadata = MultiscaleV06( + coordinateSystems=tuple([coordinate_system]), datasets=tuple(datasets), name=image.name, - coordinateTransformations=coordinateTransformations, + coordinateTransformations=transforms, ) # coerce labels to dict if it's a single NgffMultiscales or a list @@ -281,7 +333,7 @@ def to_ome_zarr( self, group: zarr.Group | str, storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, - version: str | None = "0.5", + version: str | None = "0.6", compute: bool = True, overwrite: bool = True, ) -> list: @@ -340,7 +392,7 @@ def to_ome_zarr( shutil.rmtree(group) fmt: Format | None = None - if version == "0.5": + if version == "0.5" or version == "0.6": fmt = FormatV05() elif version == "0.4": fmt = FormatV04() @@ -355,14 +407,18 @@ def to_ome_zarr( for img in self.images ] + default_cs = [ + next(cs for cs in self.metadata.coordinateSystems if cs.name == self.default_coordinate_system_name) + ][0] + # write the actual image to disk delayed = _write_pyramid_to_zarr( pyramid=pyramid, group=group, - storage_options=storage_options, fmt=fmt, + storage_options=storage_options, + axes=tuple([ax.name for ax in default_cs.axes]), scale=cast(dict[str, float], self.images[0].scale), - axes=[dict(ax) for ax in self.metadata.axes], compute=compute, name=self.name, ) @@ -443,7 +499,7 @@ def to_ome_zarr( metadata_dict = { "version": version, "multiscales": [ - _recursive_pop_nones(write_metadata.model_dump(by_alias=True)) + _recursive_pop_nones(write_metadata.to_version("0.5").model_dump(by_alias=True)) ], } @@ -464,7 +520,28 @@ def to_ome_zarr( } group.attrs["ome"] = metadata_dict - # Update mode: merge new labels with existing labels in metadata + elif version == "0.6": + metadata_dict = { + "version": version, + "multiscales": [ + _recursive_pop_nones(write_metadata.model_dump(by_alias=True)) + ], + } + if self.omero and isinstance(self.omero, Omero): + omero_dict = self.omero.model_dump(by_alias=True) + omero_dict["version"] = version + metadata_dict["omero"] = omero_dict + + if self.image_label and isinstance(self.image_label, Label): + image_label_dict = self.image_label.model_dump(by_alias=True) + image_label_dict["version"] = version + metadata_dict["image-label"] = image_label_dict + + if list_of_labels: + metadata_dict["labels"] = list_of_labels + group.attrs["ome"] = metadata_dict + + # Update mode: only update the labels list in metadata elif list_of_labels: group_labels = group["labels"] @@ -488,6 +565,12 @@ def to_ome_zarr( "version": version, "labels": merged_labels, } + elif version == "0.6": + group_labels = group["labels"] + group_labels.attrs["ome"] = { + "version": version, + "labels": list_of_labels, + } return delayed @@ -546,6 +629,7 @@ def from_ome_zarr( omero_dict = group.attrs.get("omero", None) if "image-label" in group.attrs: image_label_dict = group.attrs.get("image-label", None) + elif version == "0.5": from ome_zarr_models.v05.multiscales import Multiscale as Multiscalev05 @@ -556,30 +640,54 @@ def from_ome_zarr( if "labels" in group: labels_ome_attrs = group["labels"].attrs.get("ome", {}) list_of_labels = labels_ome_attrs.get("labels", []) - if "omero" in ome_attrs: omero_dict = ome_attrs.get("omero", None) if "image-label" in ome_attrs: image_label_dict = ome_attrs.get("image-label", None) + + elif version == "0.6": + ome_attrs = cast(dict, group.attrs.get("ome", {})) + metadata_json = ome_attrs.get("multiscales", [None])[0] + metadata = MultiscaleV06.model_validate(metadata_json) + + if "labels" in group: + labels_json = group["labels"].attrs.get("labels", []) + list_of_labels = labels_json if isinstance(labels_json, list) else [] + if "omero" in group.attrs: + omero_dict = group.attrs.get("omero", None) + if "image-label" in group.attrs: + image_label_dict = group.attrs.get("image-label", None) + else: raise ValueError(f"Unsupported OME-Zarr version: {version}") + # get NgffImage class instances from datasets images = [] - for dataset in metadata.datasets: - path = dataset.path - data = da.from_zarr(group[path]) - scale = dataset.coordinateTransformations[0].scale + for ds in metadata.datasets: + data = da.from_zarr(group[ds.path]) + transform = ds.coordinateTransformations[0] + + cs = [ + next(cs for cs in metadata.coordinateSystems if cs.name == transform.output.name) + ][0] + + if isinstance(transform, Scale): + scale = transform.scale + elif isinstance(transform, Identity): + scale = tuple(1.0 for _ in cs.axes) + elif isinstance(transform, TransformSequence): + scale = transform.transformations[0].scale # Filter out axes with no unit, and set to None if empty axes_units: dict[str, str] | None = { - ax.name: ax.unit for ax in metadata.axes if ax.unit is not None + ax.name: ax.unit for ax in cs.axes if ax.unit is not None } if not axes_units: axes_units = None images.append( NgffImage( data=data, - axes=[ax.name for ax in metadata.axes], - scale={ax.name: s for ax, s in zip(metadata.axes, scale)}, + axes=[ax.name for ax in cs.axes], + scale={d.name: s for d, s in zip(cs.axes, scale)}, axes_units=axes_units, name=str(metadata.name), ) diff --git a/ome_zarr/writer.py b/ome_zarr/writer.py index acdb99ef..6cd17c8e 100644 --- a/ome_zarr/writer.py +++ b/ome_zarr/writer.py @@ -771,8 +771,8 @@ def _write_pyramid_to_zarr( group: zarr.Group, fmt: Format, scale: dict[str, float], + axes: list[str] | tuple[str], axes_units: dict[str, str] | None = None, - axes: AxesType = None, coordinate_transformations: list[list[dict[str, Any]]] | None = None, storage_options: JSONDict | list[JSONDict] | None = None, name: str | None = None, @@ -781,12 +781,11 @@ def _write_pyramid_to_zarr( ) -> list: group, fmt = check_group_fmt(group, fmt) - _axes = _get_valid_axes(len(pyramid[0].shape), axes, axes_units=axes_units, fmt=fmt) - dims = _extract_dims_from_axes(_axes) - # Normalize `scale` so every axis in `dims` is represented. - # Missing axes are allowed and default to 1.0. - scale = {d: scale.get(d, 1.0) for d in dims} + # make sure every axis is represented in `scale`; + # coerce to 1.0 if not provided + # but don't allow missing axes to avoid silent errors + scale = {d: scale.get(d, 1.0) for d in axes} # Set up common kwargs for da.to_zarr # zarr_array_kwargs needs dask 2025.12.0 or later @@ -811,9 +810,7 @@ def _write_pyramid_to_zarr( zarr_array_kwargs["dimension_separator"] = "/" if axes is not None and zarr_format != 2: - zarr_array_kwargs["dimension_names"] = [ - a["name"] for a in axes if isinstance(a, dict) - ] + zarr_array_kwargs["dimension_names"] = axes shapes = [] datasets: list[dict] = [] @@ -919,7 +916,7 @@ def _write_pyramid_to_zarr( for transform in coordinate_transformations: transform[0]["scale"] = [ transform[0]["scale"][i] * scale.get(d, 1.0) - for i, d in enumerate(dims) + for i, d in enumerate(axes) ] # we validate again later, but this catches length mismatch before zip(datasets...) From 44b4253dd1a1e4db04220ee8d195f18beecbcdb5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 13:31:04 +0000 Subject: [PATCH 02/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/image.py | 61 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index e8fd9829..5d90a269 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -4,32 +4,33 @@ from collections.abc import Sequence from dataclasses import InitVar, dataclass, field from typing import Any, cast -import typing import dask.array as da import numpy as np import zarr -from ome_zarr_models.common.image_label_types import LabelBase as Label -from ome_zarr_models.common.omero import Omero - from ome_zarr_models._v06.coordinate_transforms import ( - Scale, - Translation, - Identity, - Sequence as TransformSequence, + AnyTransform, Axis, CoordinateSystem, CoordinateSystemIdentifier, - AnyTransform, + Identity, + Scale, +) +from ome_zarr_models._v06.coordinate_transforms import ( + Sequence as TransformSequence, ) from ome_zarr_models._v06.multiscales import ( Dataset, +) +from ome_zarr_models._v06.multiscales import ( Multiscale as MultiscaleV06, ) +from ome_zarr_models.common.image_label_types import LabelBase as Label +from ome_zarr_models.common.omero import Omero from ome_zarr_models.v05.multiscales import ( Multiscale as MultiscaleV05, ) -from pydantic import ValidationError, TypeAdapter +from pydantic import TypeAdapter, ValidationError from ome_zarr.scale import Methods @@ -166,9 +167,9 @@ class NgffMultiscales: list[int] | tuple[int, ...] | list[dict[str, int]] | None ] = None method: str | Methods | None = Methods.RESIZE - coordinateTransformations: InitVar[tuple[AnyTransform, ...] | list[dict[str, Any]] | None] = ( - None - ) + coordinateTransformations: InitVar[ + tuple[AnyTransform, ...] | list[dict[str, Any]] | None + ] = None labels: ( NgffMultiscales | list[NgffMultiscales] | dict[str, NgffMultiscales] | None ) = None @@ -180,7 +181,9 @@ def __post_init__( self, image: NgffImage, scale_factors: list[int] | tuple[int, ...] | list[dict[str, int]] | None, - coordinateTransformations: tuple[AnyTransform, ...] | list[dict[str, Any]] | None, + coordinateTransformations: ( + tuple[AnyTransform, ...] | list[dict[str, Any]] | None + ), ): from ome_zarr.scale import _build_pyramid @@ -245,12 +248,10 @@ def __post_init__( Scale( type="scale", scale=tuple(level_scale.values()), - input=CoordinateSystemIdentifier( - path=f"s{idx}" - ), + input=CoordinateSystemIdentifier(path=f"s{idx}"), output=CoordinateSystemIdentifier( name=self.default_coordinate_system_name - ) + ), ), ), ) @@ -408,8 +409,12 @@ def to_ome_zarr( ] default_cs = [ - next(cs for cs in self.metadata.coordinateSystems if cs.name == self.default_coordinate_system_name) - ][0] + next( + cs + for cs in self.metadata.coordinateSystems + if cs.name == self.default_coordinate_system_name + ) + ][0] # write the actual image to disk delayed = _write_pyramid_to_zarr( @@ -499,7 +504,9 @@ def to_ome_zarr( metadata_dict = { "version": version, "multiscales": [ - _recursive_pop_nones(write_metadata.to_version("0.5").model_dump(by_alias=True)) + _recursive_pop_nones( + write_metadata.to_version("0.5").model_dump(by_alias=True) + ) ], } @@ -666,11 +673,15 @@ def from_ome_zarr( for ds in metadata.datasets: data = da.from_zarr(group[ds.path]) transform = ds.coordinateTransformations[0] - + cs = [ - next(cs for cs in metadata.coordinateSystems if cs.name == transform.output.name) - ][0] - + next( + cs + for cs in metadata.coordinateSystems + if cs.name == transform.output.name + ) + ][0] + if isinstance(transform, Scale): scale = transform.scale elif isinstance(transform, Identity): From 141472633175a46ad6f187d285a4293f95501b8d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 18 May 2026 16:52:25 +0200 Subject: [PATCH 03/95] feat: add NgffScene class --- ome_zarr/__init__.py | 2 +- ome_zarr/classes/__init__.py | 3 +- ome_zarr/classes/scene.py | 198 +++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 ome_zarr/classes/scene.py diff --git a/ome_zarr/__init__.py b/ome_zarr/__init__.py index 4973ae45..71343e0c 100644 --- a/ome_zarr/__init__.py +++ b/ome_zarr/__init__.py @@ -1,7 +1,7 @@ from dask import __version__ as dask_version from packaging.version import Version -from .classes import NgffImage, NgffMultiscales +from .classes import NgffImage, NgffMultiscales, NgffScene # Expose __version__ and fallback when _version.py doesn't exist. try: diff --git a/ome_zarr/classes/__init__.py b/ome_zarr/classes/__init__.py index 247a502a..9b235223 100644 --- a/ome_zarr/classes/__init__.py +++ b/ome_zarr/classes/__init__.py @@ -1,3 +1,4 @@ from .image import NgffImage, NgffMultiscales +from .scene import NgffScene -__all__ = ["NgffImage", "NgffMultiscales"] +__all__ = ["NgffImage", "NgffMultiscales", "NgffScene"] diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py new file mode 100644 index 00000000..e3265201 --- /dev/null +++ b/ome_zarr/classes/scene.py @@ -0,0 +1,198 @@ +# the class for storage representation, not exposed to the user +import os +from dataclasses import dataclass, field +from typing import Any, Sequence + +from pydantic import TypeAdapter + +from ome_zarr_models._v06.coordinate_transforms import ( + CoordinateSystemIdentifier, + CoordinateSystem, + AnyTransform, + Transform, + Translation, + Sequence as TransformSequence, +) +from ome_zarr_models._v06.scene import SceneAttrs +from zarr.storage import StoreLike +import networkx as nx +import zarr +from .image import NgffMultiscales + + + +# the class exposed to the user +@dataclass(kw_only=True) +class NgffScene: + images: list[NgffMultiscales] + metadata: SceneAttrs = field(init=False) + coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] + coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None + _written_image_names: set[str] = field(default_factory=set, init=False) + + def __post_init__(self): + tf_adapter = TypeAdapter(AnyTransform) + + # parse coordinate systems + if self.coordinate_systems is not None: + coordinate_systems: list[CoordinateSystem] = [] + for cs in self.coordinate_systems: + if isinstance(cs, dict): + coordinate_systems.append(CoordinateSystem.model_validate(cs)) + elif isinstance(cs, CoordinateSystem): + coordinate_systems.append(cs) + self.coordinate_systems = tuple(coordinate_systems) + + # parse transforms + transforms = [] + for tf in self.coordinate_transformations: + if isinstance(tf, dict): + transforms.append(tf_adapter.validate_python(tf)) + else: + transforms.append(tf) + self.coordinate_transformations = tuple(transforms) + + self.metadata = SceneAttrs( + coordinateSystems=self.coordinate_systems, + coordinateTransformations=self.coordinate_transformations + ) + + # self._graph = nx.DiGraph() + + # if self.coordinate_systems is not None: + # for cs in self.coordinate_systems: + # if hasattr(cs, "path") and cs.path is None: + # self._graph.add_node((None, cs.name)) + + # for img in self.images: + # # add all coordinate systems as nodes + # for cs in img.metadata.coordinateSystems: + # node_id = (img.metadata.name, cs.name) + # self._graph.add_node(node_id) + + # for ds in img.metadata.datasets: + # # add all datasets as nodes + # node_id = (img.metadata.name, ds.path) + # self._graph.add_node(node_id) + + # # add scale transformations from dataset as edges + # transform = ds.coordinateTransformations + # self._graph.add_edge( + # (img.metadata.name, ds.path), + # (img.metadata.name, ds.coordinateTransformations[0].output), + # transform=transform, + # ) + + # # add additional transformations from image metadata as edges + # if img.metadata.coordinateTransformations: + # for tf in img.metadata.coordinateTransformations: + # self._graph.add_edge( + # (img.metadata.name, tf.input), + # (img.metadata.name, tf.output), + # transform=tf, + # ) + + # # add scene-level transformations as edges between coordinate systems of different images + # for tf in self.transformations: + # self._graph.add_edge( + # (tf.input.path, tf.input.name), + # (tf.output.path, tf.output.name), + # transform=tf) + + def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): + """ + Write scene to OME-Zarr format. + + Parameters + ---------- + store: StoreLike + A zarr-compatible storage backend (e.g., directory path, in-memory store, etc.) + overwrite: bool + If True, overwrite all images in the store with the current state of the scene. + If False, only write new images that haven't been written before. Existing images in the store will be left unchanged. + + """ + import shutil + + from ..utils import _recursive_pop_nones + + if overwrite and os.path.exists(str(store)): + # Clear the store if it already exists and we're not doing incremental writes + shutil.rmtree(str(store)) + + # Open or create zarr group + mode = "a" if overwrite else "w" + zarr_group = zarr.open(store, mode=mode) + + # Create a subgroup for each image using its name + for img in self.images: + img_name = str(img.metadata.name) + + # Skip if already written (incremental mode) + if not overwrite and img_name in self._written_image_names: + continue + + # Write the image + subgroup = zarr_group.create_group(img_name, overwrite=not overwrite) + img.to_ome_zarr(subgroup) + self._written_image_names.add(img_name) + + # Always update scene metadata + metadata_dict = self.metadata.model_dump() + metadata_dict = _recursive_pop_nones(metadata_dict) + + zarr_group.attrs["ome"] = {"scene": metadata_dict, "version": "0.6"} + + @classmethod + def from_ome_zarr(cls, store: StoreLike): + """ + Load an existing scene from OME-Zarr format. + + Args: + path: Path to the OME-Zarr scene + + Returns: + NgffScene instance with images and metadata loaded from disk + """ + tf_adapter = TypeAdapter(AnyTransform) + + zarr_group = zarr.open(store, mode="r") + + # Load all image subgroups + images = [] + for img_name in zarr_group.group_keys(): + img_group = zarr_group[img_name] + # Assume images have their own to_ome_zarr-like interface + # You may need to adapt based on your NgffMultiscales.from_ome_zarr implementation + img = NgffMultiscales.from_ome_zarr(img_group) + images.append(img) + + # Load scene metadata + ome_metadata = zarr_group.attrs.get("ome", {}) + scene_metadata = ome_metadata.get("scene", {}) + + # Note: Reconstructing Transform and CoordinateSystemIdentifier objects from dicts + # may require additional deserialization logic depending on your models + + transformations = [] + for tf in scene_metadata.get("coordinateTransformations", []): + transformations.append(tf_adapter.validate_python(tf)) + + if "coordinateSystems" in scene_metadata: + coordinate_systems = [ + CoordinateSystem.model_validate(cs) + for cs in scene_metadata.get("coordinateSystems", []) + if cs + ] + else: + coordinate_systems = None + + scene = cls( + images=images, + coordinate_transformations=transformations, + coordinate_systems=coordinate_systems, + ) + # Mark all existing images as already written + scene._written_image_names = {img.metadata.name for img in images} + + return scene \ No newline at end of file From bf584abc8e78308f56c396ad7202cb598fb7953a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 18 May 2026 16:52:42 +0200 Subject: [PATCH 04/95] docs: add example notebook to docs --- docs/source/_toc.yml | 1 + docs/source/advanced/create_scenes.ipynb | 283 +++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 docs/source/advanced/create_scenes.ipynb diff --git a/docs/source/_toc.yml b/docs/source/_toc.yml index bbf2aa35..077390b6 100644 --- a/docs/source/_toc.yml +++ b/docs/source/_toc.yml @@ -20,6 +20,7 @@ parts: sections: - file: advanced/labels/adding_labels - file: advanced/labels/labels_metadata + - file: advanced/create_scenes - file: advanced/sharding - file: advanced/build_custom_pyramid diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb new file mode 100644 index 00000000..a7acedc3 --- /dev/null +++ b/docs/source/advanced/create_scenes.ipynb @@ -0,0 +1,283 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8b0be7e9", + "metadata": {}, + "source": [ + "# Create Scenes\n", + "\n", + "(advanced:create-scenes)=\n", + "\n", + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a49ec1c2", + "metadata": {}, + "outputs": [], + "source": [ + "from ome_zarr import NgffImage, NgffMultiscales, NgffScene\n", + "from skimage import data\n", + "import napari" + ] + }, + { + "cell_type": "markdown", + "id": "98bbabd7", + "metadata": {}, + "source": [ + "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e3023c95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(512, 512)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_image = data.human_mitosis()\n", + "example_image.shape" + ] + }, + { + "cell_type": "markdown", + "id": "71e31e2a", + "metadata": {}, + "source": [ + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "017d4c59", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n" + ] + } + ], + "source": [ + "img1 = NgffImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", + "img2 = NgffImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", + "img3 = NgffImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", + "img4 = NgffImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", + "\n", + "img1_ms = NgffMultiscales(img1)\n", + "img2_ms = NgffMultiscales(img2)\n", + "img3_ms = NgffMultiscales(img3)\n", + "img4_ms = NgffMultiscales(img4)" + ] + }, + { + "cell_type": "markdown", + "id": "cf59df32", + "metadata": {}, + "source": [ + "Next, we need to define a coordinate system into which all images are projected.\n", + "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "994674c2", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_system = {\n", + " \"name\": \"world\",\n", + " \"axes\": [\n", + " {\"name\": \"y\", \"type\": \"space\"},\n", + " {\"name\": \"x\", \"type\": \"space\"},\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c1d629be", + "metadata": {}, + "source": [ + "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", + "In thisexamples, these are simple translations in the y and x dimensions:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d6329caa", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_transformations = [\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 0],\n", + " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 0],\n", + " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 256],\n", + " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 256],\n", + " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4444a8d3", + "metadata": {}, + "source": [ + "We can then create and write a scene like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d05be093", + "metadata": {}, + "outputs": [], + "source": [ + "scene = NgffScene(\n", + " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", + " coordinate_systems=[coordinate_system],\n", + " coordinate_transformations=coordinate_transformations\n", + ")\n", + "\n", + "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "id": "cbf307f3", + "metadata": {}, + "source": [ + "## Optional: View with napari" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d024ab0", + "metadata": {}, + "outputs": [], + "source": [ + "viewer = napari.Viewer()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "84573d7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Root group {'ome': {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}}\n", + "Scene.matches {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}\n", + "Scene\n", + "spec \n", + "Scene.iter_nodes...final outputs -----> {'world'}\n", + "IMAGES\n", + "---------------------\n", + "img1\n", + "---------------------\n", + "img2\n", + "---------------------\n", + "img3\n", + "---------------------\n", + "img4\n", + "Nodes [, , , ]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img1/s0', 'multiscale_path': 'img1'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0], 'input_full_path': 'img1/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img2/s0', 'multiscale_path': 'img2'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0], 'input_full_path': 'img2/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img3/s0', 'multiscale_path': 'img3'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0], 'input_full_path': 'img3/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img4/s0', 'multiscale_path': 'img4'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0], 'input_full_path': 'img4/physical'}]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "viewer.layers.clear()\n", + "viewer.open(\"test_example_scene.zarr\", plugin=\"napari-ome-zarr\")\n", + "viewer.add_image(example_image, name=\"original image\", blending=\"additive\", colormap=\"orange\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5f768a02cc22d9098699596e409e20cabd02d24e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 14:57:28 +0000 Subject: [PATCH 05/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/advanced/create_scenes.ipynb | 5 +- ome_zarr/classes/image.py | 61 ++++++++++++++---------- ome_zarr/classes/scene.py | 29 +++++------ 3 files changed, 52 insertions(+), 43 deletions(-) diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb index a7acedc3..f3c23854 100644 --- a/docs/source/advanced/create_scenes.ipynb +++ b/docs/source/advanced/create_scenes.ipynb @@ -19,9 +19,10 @@ "metadata": {}, "outputs": [], "source": [ - "from ome_zarr import NgffImage, NgffMultiscales, NgffScene\n", + "import napari\n", "from skimage import data\n", - "import napari" + "\n", + "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" ] }, { diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index e8fd9829..5d90a269 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -4,32 +4,33 @@ from collections.abc import Sequence from dataclasses import InitVar, dataclass, field from typing import Any, cast -import typing import dask.array as da import numpy as np import zarr -from ome_zarr_models.common.image_label_types import LabelBase as Label -from ome_zarr_models.common.omero import Omero - from ome_zarr_models._v06.coordinate_transforms import ( - Scale, - Translation, - Identity, - Sequence as TransformSequence, + AnyTransform, Axis, CoordinateSystem, CoordinateSystemIdentifier, - AnyTransform, + Identity, + Scale, +) +from ome_zarr_models._v06.coordinate_transforms import ( + Sequence as TransformSequence, ) from ome_zarr_models._v06.multiscales import ( Dataset, +) +from ome_zarr_models._v06.multiscales import ( Multiscale as MultiscaleV06, ) +from ome_zarr_models.common.image_label_types import LabelBase as Label +from ome_zarr_models.common.omero import Omero from ome_zarr_models.v05.multiscales import ( Multiscale as MultiscaleV05, ) -from pydantic import ValidationError, TypeAdapter +from pydantic import TypeAdapter, ValidationError from ome_zarr.scale import Methods @@ -166,9 +167,9 @@ class NgffMultiscales: list[int] | tuple[int, ...] | list[dict[str, int]] | None ] = None method: str | Methods | None = Methods.RESIZE - coordinateTransformations: InitVar[tuple[AnyTransform, ...] | list[dict[str, Any]] | None] = ( - None - ) + coordinateTransformations: InitVar[ + tuple[AnyTransform, ...] | list[dict[str, Any]] | None + ] = None labels: ( NgffMultiscales | list[NgffMultiscales] | dict[str, NgffMultiscales] | None ) = None @@ -180,7 +181,9 @@ def __post_init__( self, image: NgffImage, scale_factors: list[int] | tuple[int, ...] | list[dict[str, int]] | None, - coordinateTransformations: tuple[AnyTransform, ...] | list[dict[str, Any]] | None, + coordinateTransformations: ( + tuple[AnyTransform, ...] | list[dict[str, Any]] | None + ), ): from ome_zarr.scale import _build_pyramid @@ -245,12 +248,10 @@ def __post_init__( Scale( type="scale", scale=tuple(level_scale.values()), - input=CoordinateSystemIdentifier( - path=f"s{idx}" - ), + input=CoordinateSystemIdentifier(path=f"s{idx}"), output=CoordinateSystemIdentifier( name=self.default_coordinate_system_name - ) + ), ), ), ) @@ -408,8 +409,12 @@ def to_ome_zarr( ] default_cs = [ - next(cs for cs in self.metadata.coordinateSystems if cs.name == self.default_coordinate_system_name) - ][0] + next( + cs + for cs in self.metadata.coordinateSystems + if cs.name == self.default_coordinate_system_name + ) + ][0] # write the actual image to disk delayed = _write_pyramid_to_zarr( @@ -499,7 +504,9 @@ def to_ome_zarr( metadata_dict = { "version": version, "multiscales": [ - _recursive_pop_nones(write_metadata.to_version("0.5").model_dump(by_alias=True)) + _recursive_pop_nones( + write_metadata.to_version("0.5").model_dump(by_alias=True) + ) ], } @@ -666,11 +673,15 @@ def from_ome_zarr( for ds in metadata.datasets: data = da.from_zarr(group[ds.path]) transform = ds.coordinateTransformations[0] - + cs = [ - next(cs for cs in metadata.coordinateSystems if cs.name == transform.output.name) - ][0] - + next( + cs + for cs in metadata.coordinateSystems + if cs.name == transform.output.name + ) + ][0] + if isinstance(transform, Scale): scale = transform.scale elif isinstance(transform, Identity): diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index e3265201..a8a7da77 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -1,24 +1,19 @@ # the class for storage representation, not exposed to the user import os +from collections.abc import Sequence from dataclasses import dataclass, field -from typing import Any, Sequence - -from pydantic import TypeAdapter +from typing import Any +import zarr from ome_zarr_models._v06.coordinate_transforms import ( - CoordinateSystemIdentifier, - CoordinateSystem, AnyTransform, - Transform, - Translation, - Sequence as TransformSequence, + CoordinateSystem, ) from ome_zarr_models._v06.scene import SceneAttrs +from pydantic import TypeAdapter from zarr.storage import StoreLike -import networkx as nx -import zarr + from .image import NgffMultiscales - # the class exposed to the user @@ -27,7 +22,9 @@ class NgffScene: images: list[NgffMultiscales] metadata: SceneAttrs = field(init=False) coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] - coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None + coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = ( + None + ) _written_image_names: set[str] = field(default_factory=set, init=False) def __post_init__(self): @@ -54,9 +51,9 @@ def __post_init__(self): self.metadata = SceneAttrs( coordinateSystems=self.coordinate_systems, - coordinateTransformations=self.coordinate_transformations + coordinateTransformations=self.coordinate_transformations, ) - + # self._graph = nx.DiGraph() # if self.coordinate_systems is not None: @@ -177,7 +174,7 @@ def from_ome_zarr(cls, store: StoreLike): transformations = [] for tf in scene_metadata.get("coordinateTransformations", []): transformations.append(tf_adapter.validate_python(tf)) - + if "coordinateSystems" in scene_metadata: coordinate_systems = [ CoordinateSystem.model_validate(cs) @@ -195,4 +192,4 @@ def from_ome_zarr(cls, store: StoreLike): # Mark all existing images as already written scene._written_image_names = {img.metadata.name for img in images} - return scene \ No newline at end of file + return scene From f478f73e6afe20578ee13a7d9750cce065d262f7 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 21 May 2026 11:25:36 +0200 Subject: [PATCH 06/95] feat: add ozmp parsers --- ome_zarr/classes/scene.py | 110 ++++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index a8a7da77..2b9043ce 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -20,7 +20,7 @@ @dataclass(kw_only=True) class NgffScene: images: list[NgffMultiscales] - metadata: SceneAttrs = field(init=False) + metadata: SceneAttrs = field(init=False, default=None) coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = ( None @@ -28,26 +28,10 @@ class NgffScene: _written_image_names: set[str] = field(default_factory=set, init=False) def __post_init__(self): - tf_adapter = TypeAdapter(AnyTransform) - # parse coordinate systems - if self.coordinate_systems is not None: - coordinate_systems: list[CoordinateSystem] = [] - for cs in self.coordinate_systems: - if isinstance(cs, dict): - coordinate_systems.append(CoordinateSystem.model_validate(cs)) - elif isinstance(cs, CoordinateSystem): - coordinate_systems.append(cs) - self.coordinate_systems = tuple(coordinate_systems) - - # parse transforms - transforms = [] - for tf in self.coordinate_transformations: - if isinstance(tf, dict): - transforms.append(tf_adapter.validate_python(tf)) - else: - transforms.append(tf) - self.coordinate_transformations = tuple(transforms) + # parse coordinate systems and transforms + self.coordinate_systems = self._parse_coordinate_systems(self.coordinate_systems) + self.coordinate_transformations = self._parse_transforms(self.coordinate_transformations) self.metadata = SceneAttrs( coordinateSystems=self.coordinate_systems, @@ -110,7 +94,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil - + import tqdm from ..utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): @@ -122,7 +106,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): zarr_group = zarr.open(store, mode=mode) # Create a subgroup for each image using its name - for img in self.images: + for img in tqdm.tqdm(self.images, desc="Writing images"): img_name = str(img.metadata.name) # Skip if already written (incremental mode) @@ -184,12 +168,80 @@ def from_ome_zarr(cls, store: StoreLike): else: coordinate_systems = None - scene = cls( - images=images, - coordinate_transformations=transformations, - coordinate_systems=coordinate_systems, - ) - # Mark all existing images as already written - scene._written_image_names = {img.metadata.name for img in images} + # Use object.__new__ to create instance without triggering __init__ and __setattr__ + scene = object.__new__(cls) + + # Set fields directly using object.__setattr__ to bypass custom __setattr__ + object.__setattr__(scene, 'images', images) + object.__setattr__(scene, 'coordinate_transformations', tuple(transformations)) + object.__setattr__(scene, 'coordinate_systems', tuple(coordinate_systems) if coordinate_systems else None) + object.__setattr__(scene, '_written_image_names', {img.metadata.name for img in images}) + + # Now set metadata + object.__setattr__(scene, 'metadata', SceneAttrs( + coordinateSystems=scene.coordinate_systems, + coordinateTransformations=scene.coordinate_transformations, + )) return scene + + def __setattr__(self, name: str, value: Any) -> None: + if name == "coordinate_transformations": + # Update metadata when coordinate transformations are set + parsed_transforms = self._parse_transforms(value) + super().__setattr__(name, parsed_transforms) + # Only update metadata if it exists (not during initial construction) + if hasattr(self, 'metadata') and self.metadata is not None: + self.metadata = self.metadata.model_copy(update={"coordinateTransformations": parsed_transforms}) + + elif name == "coordinate_systems": + # Update metadata when coordinate systems are set + parsed_coordinate_systems = self._parse_coordinate_systems(value) + super().__setattr__(name, parsed_coordinate_systems) + # Only update metadata if it exists (not during initial construction) + if hasattr(self, 'metadata') and self.metadata is not None: + self.metadata = self.metadata.model_copy(update={"coordinateSystems": parsed_coordinate_systems}) + + else: + # Default behavior for all other attributes + super().__setattr__(name, value) + + @staticmethod + def _parse_transforms(transforms: Sequence[AnyTransform] | list[dict[str, Any]]) -> tuple[AnyTransform, ...]: + """ + Helper method to parse a sequence of coordinate transformations that may be provided as either + AnyTransform instances or dictionaries. + This ensures that all transformations are stored as AnyTransform objects in the scene metadata. + """ + tf_adapter = TypeAdapter(AnyTransform) + parsed_transforms = [] + + for tf in transforms: + if isinstance(tf, dict): + parsed_transforms.append(tf_adapter.validate_python(tf)) + else: + parsed_transforms.append(tf) + + return tuple(parsed_transforms) + + @staticmethod + def _parse_coordinate_systems( + coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None + ) -> tuple[CoordinateSystem, ...] | None: + """ + Helper method to parse a sequence of coordinate systems that may be provided as either + CoordinateSystem instances or dictionaries. + This ensures that all coordinate systems are stored as CoordinateSystem objects in the scene metadata. + If coordinate_systems is None, it will be returned as None. + """ + if coordinate_systems is None: + return None + + parsed_coordinate_systems = [] + for cs in coordinate_systems: + if isinstance(cs, dict): + parsed_coordinate_systems.append(CoordinateSystem.model_validate(cs)) + elif isinstance(cs, CoordinateSystem): + parsed_coordinate_systems.append(cs) + + return tuple(parsed_coordinate_systems) From 544c11791369e11b844e549c6fdb5b65d128d6a7 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 21 May 2026 11:25:46 +0200 Subject: [PATCH 07/95] Update create_scenes.ipynb --- docs/source/advanced/create_scenes.ipynb | 103 ++++++++++++++++++++++- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb index f3c23854..18a0da1f 100644 --- a/docs/source/advanced/create_scenes.ipynb +++ b/docs/source/advanced/create_scenes.ipynb @@ -19,10 +19,9 @@ "metadata": {}, "outputs": [], "source": [ - "import napari\n", + "from ome_zarr import NgffImage, NgffMultiscales, NgffScene\n", "from skimage import data\n", - "\n", - "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" + "import napari" ] }, { @@ -258,6 +257,104 @@ "viewer.open(\"test_example_scene.zarr\", plugin=\"napari-ome-zarr\")\n", "viewer.add_image(example_image, name=\"original image\", blending=\"additive\", colormap=\"orange\")" ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "2d7a1333", + "metadata": {}, + "outputs": [], + "source": [ + "import SimpleITK as sitk" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d96b2264", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[-80.368095, -21.71694 ],\n", + " [-80.36892 , -21.716684],\n", + " [-80.36974 , -21.716429],\n", + " ...,\n", + " [-81.662254, -21.221333],\n", + " [-81.662895, -21.221027],\n", + " [-81.663536, -21.220722]],\n", + "\n", + " [[-80.36836 , -21.717838],\n", + " [-80.36919 , -21.717583],\n", + " [-80.37002 , -21.717327],\n", + " ...,\n", + " [-81.66253 , -21.22223 ],\n", + " [-81.66317 , -21.221926],\n", + " [-81.6638 , -21.22162 ]],\n", + "\n", + " [[-80.36864 , -21.718739],\n", + " [-80.36946 , -21.718483],\n", + " [-80.370285, -21.718227],\n", + " ...,\n", + " [-81.6628 , -21.22313 ],\n", + " [-81.66344 , -21.222824],\n", + " [-81.66408 , -21.222519]],\n", + "\n", + " ...,\n", + "\n", + " [[-80.57437 , -25.218006],\n", + " [-80.575195, -25.217754],\n", + " [-80.57602 , -25.217505],\n", + " ...,\n", + " [-81.86771 , -24.724539],\n", + " [-81.86834 , -24.72423 ],\n", + " [-81.86898 , -24.723923]],\n", + "\n", + " [[-80.5742 , -25.218779],\n", + " [-80.57503 , -25.218529],\n", + " [-80.57585 , -25.218277],\n", + " ...,\n", + " [-81.86753 , -24.725313],\n", + " [-81.868164, -24.725004],\n", + " [-81.868805, -24.724697]],\n", + "\n", + " [[-80.57403 , -25.219551],\n", + " [-80.57486 , -25.219301],\n", + " [-80.57568 , -25.21905 ],\n", + " ...,\n", + " [-81.867355, -24.726088],\n", + " [-81.868 , -24.725779],\n", + " [-81.86863 , -24.725471]]], shape=(4157, 1768, 2), dtype=float32)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = sitk.ReadImage(r\"c:\\Users\\johan\\Documents\\Globus\\Leap043\\nl-mid-LAICPMSI-a-mip_to_LAICPMSI-a-mosaic_reg_output\\deformationField.mha\")\n", + "\n", + "# get data as numpy array\n", + "sitk.GetArrayFromImage(image)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3567a037", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e7d0ba3", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From e05d925631368e3d2e4604328d8c78be892f5bcc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 09:26:02 +0000 Subject: [PATCH 08/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/advanced/create_scenes.ipynb | 5 +- ome_zarr/classes/scene.py | 66 ++++++++++++++++-------- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb index 18a0da1f..c977d5d0 100644 --- a/docs/source/advanced/create_scenes.ipynb +++ b/docs/source/advanced/create_scenes.ipynb @@ -19,9 +19,10 @@ "metadata": {}, "outputs": [], "source": [ - "from ome_zarr import NgffImage, NgffMultiscales, NgffScene\n", + "import napari\n", "from skimage import data\n", - "import napari" + "\n", + "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" ] }, { diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 2b9043ce..7761f778 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -30,8 +30,12 @@ class NgffScene: def __post_init__(self): # parse coordinate systems and transforms - self.coordinate_systems = self._parse_coordinate_systems(self.coordinate_systems) - self.coordinate_transformations = self._parse_transforms(self.coordinate_transformations) + self.coordinate_systems = self._parse_coordinate_systems( + self.coordinate_systems + ) + self.coordinate_transformations = self._parse_transforms( + self.coordinate_transformations + ) self.metadata = SceneAttrs( coordinateSystems=self.coordinate_systems, @@ -94,7 +98,9 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil + import tqdm + from ..utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): @@ -170,44 +176,60 @@ def from_ome_zarr(cls, store: StoreLike): # Use object.__new__ to create instance without triggering __init__ and __setattr__ scene = object.__new__(cls) - + # Set fields directly using object.__setattr__ to bypass custom __setattr__ - object.__setattr__(scene, 'images', images) - object.__setattr__(scene, 'coordinate_transformations', tuple(transformations)) - object.__setattr__(scene, 'coordinate_systems', tuple(coordinate_systems) if coordinate_systems else None) - object.__setattr__(scene, '_written_image_names', {img.metadata.name for img in images}) - + object.__setattr__(scene, "images", images) + object.__setattr__(scene, "coordinate_transformations", tuple(transformations)) + object.__setattr__( + scene, + "coordinate_systems", + tuple(coordinate_systems) if coordinate_systems else None, + ) + object.__setattr__( + scene, "_written_image_names", {img.metadata.name for img in images} + ) + # Now set metadata - object.__setattr__(scene, 'metadata', SceneAttrs( - coordinateSystems=scene.coordinate_systems, - coordinateTransformations=scene.coordinate_transformations, - )) + object.__setattr__( + scene, + "metadata", + SceneAttrs( + coordinateSystems=scene.coordinate_systems, + coordinateTransformations=scene.coordinate_transformations, + ), + ) return scene - + def __setattr__(self, name: str, value: Any) -> None: if name == "coordinate_transformations": # Update metadata when coordinate transformations are set parsed_transforms = self._parse_transforms(value) super().__setattr__(name, parsed_transforms) # Only update metadata if it exists (not during initial construction) - if hasattr(self, 'metadata') and self.metadata is not None: - self.metadata = self.metadata.model_copy(update={"coordinateTransformations": parsed_transforms}) + if hasattr(self, "metadata") and self.metadata is not None: + self.metadata = self.metadata.model_copy( + update={"coordinateTransformations": parsed_transforms} + ) elif name == "coordinate_systems": # Update metadata when coordinate systems are set parsed_coordinate_systems = self._parse_coordinate_systems(value) super().__setattr__(name, parsed_coordinate_systems) # Only update metadata if it exists (not during initial construction) - if hasattr(self, 'metadata') and self.metadata is not None: - self.metadata = self.metadata.model_copy(update={"coordinateSystems": parsed_coordinate_systems}) + if hasattr(self, "metadata") and self.metadata is not None: + self.metadata = self.metadata.model_copy( + update={"coordinateSystems": parsed_coordinate_systems} + ) else: # Default behavior for all other attributes super().__setattr__(name, value) @staticmethod - def _parse_transforms(transforms: Sequence[AnyTransform] | list[dict[str, Any]]) -> tuple[AnyTransform, ...]: + def _parse_transforms( + transforms: Sequence[AnyTransform] | list[dict[str, Any]], + ) -> tuple[AnyTransform, ...]: """ Helper method to parse a sequence of coordinate transformations that may be provided as either AnyTransform instances or dictionaries. @@ -223,11 +245,13 @@ def _parse_transforms(transforms: Sequence[AnyTransform] | list[dict[str, Any]]) parsed_transforms.append(tf) return tuple(parsed_transforms) - + @staticmethod def _parse_coordinate_systems( - coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None - ) -> tuple[CoordinateSystem, ...] | None: + coordinate_systems: ( + Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None + ), + ) -> tuple[CoordinateSystem, ...] | None: """ Helper method to parse a sequence of coordinate systems that may be provided as either CoordinateSystem instances or dictionaries. From a5d61d5ae4ca2a80118c43b504421288b7a5d139 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 09:01:09 +0000 Subject: [PATCH 09/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 2aa436f4..0b8d673f 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -119,7 +119,7 @@ def __post_init__(self): for d in self.scale: if d not in self.axes: raise ValueError( - f"Scale contains invalid axis: {d}. " f"Valid axes are: {self.axes}" + f"Scale contains invalid axis: {d}. Valid axes are: {self.axes}" ) # warn about missing axes @@ -494,6 +494,7 @@ def from_ome_zarr( elif "0.6" in version: from ome_zarr_models.v06.multiscales import Multiscale as Multiscalev06 + ome_attrs = cast(dict[str, Any], group.attrs.get("ome", {})) metadata_json = ome_attrs.get("multiscales", [None])[0] metadata = Multiscalev06.model_validate(metadata_json) From 5942274d8ae1eb7c692081e9df975ca92205c844 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:10:19 +0000 Subject: [PATCH 10/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/advanced/create_scenes.ipynb | 738 +++++++++++------------ ome_zarr/__init__.py | 2 +- ome_zarr/classes/__init__.py | 2 +- 3 files changed, 371 insertions(+), 371 deletions(-) diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb index c977d5d0..5c51bb27 100644 --- a/docs/source/advanced/create_scenes.ipynb +++ b/docs/source/advanced/create_scenes.ipynb @@ -1,382 +1,382 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "8b0be7e9", - "metadata": {}, - "source": [ - "# Create Scenes\n", - "\n", - "(advanced:create-scenes)=\n", - "\n", - "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a49ec1c2", - "metadata": {}, - "outputs": [], - "source": [ - "import napari\n", - "from skimage import data\n", - "\n", - "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" - ] - }, - { - "cell_type": "markdown", - "id": "98bbabd7", - "metadata": {}, - "source": [ - "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e3023c95", - "metadata": {}, - "outputs": [ + "cells": [ { - "data": { - "text/plain": [ - "(512, 512)" + "cell_type": "markdown", + "id": "8b0be7e9", + "metadata": {}, + "source": [ + "# Create Scenes\n", + "\n", + "(advanced:create-scenes)=\n", + "\n", + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "example_image = data.human_mitosis()\n", - "example_image.shape" - ] - }, - { - "cell_type": "markdown", - "id": "71e31e2a", - "metadata": {}, - "source": [ - "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "017d4c59", - "metadata": {}, - "outputs": [ + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n" - ] - } - ], - "source": [ - "img1 = NgffImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", - "img2 = NgffImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", - "img3 = NgffImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", - "img4 = NgffImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", - "\n", - "img1_ms = NgffMultiscales(img1)\n", - "img2_ms = NgffMultiscales(img2)\n", - "img3_ms = NgffMultiscales(img3)\n", - "img4_ms = NgffMultiscales(img4)" - ] - }, - { - "cell_type": "markdown", - "id": "cf59df32", - "metadata": {}, - "source": [ - "Next, we need to define a coordinate system into which all images are projected.\n", - "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "994674c2", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_system = {\n", - " \"name\": \"world\",\n", - " \"axes\": [\n", - " {\"name\": \"y\", \"type\": \"space\"},\n", - " {\"name\": \"x\", \"type\": \"space\"},\n", - " ],\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "c1d629be", - "metadata": {}, - "source": [ - "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", - "In thisexamples, these are simple translations in the y and x dimensions:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "d6329caa", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_transformations = [\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 0],\n", - " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 0],\n", - " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 256],\n", - " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 256],\n", - " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "4444a8d3", - "metadata": {}, - "source": [ - "We can then create and write a scene like this:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "d05be093", - "metadata": {}, - "outputs": [], - "source": [ - "scene = NgffScene(\n", - " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", - " coordinate_systems=[coordinate_system],\n", - " coordinate_transformations=coordinate_transformations\n", - ")\n", - "\n", - "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" - ] - }, - { - "cell_type": "markdown", - "id": "cbf307f3", - "metadata": {}, - "source": [ - "## Optional: View with napari" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d024ab0", - "metadata": {}, - "outputs": [], - "source": [ - "viewer = napari.Viewer()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "84573d7e", - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 6, + "id": "a49ec1c2", + "metadata": {}, + "outputs": [], + "source": [ + "import napari\n", + "from skimage import data\n", + "\n", + "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Root group {'ome': {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}}\n", - "Scene.matches {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}\n", - "Scene\n", - "spec \n", - "Scene.iter_nodes...final outputs -----> {'world'}\n", - "IMAGES\n", - "---------------------\n", - "img1\n", - "---------------------\n", - "img2\n", - "---------------------\n", - "img3\n", - "---------------------\n", - "img4\n", - "Nodes [, , , ]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img1/s0', 'multiscale_path': 'img1'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0], 'input_full_path': 'img1/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img2/s0', 'multiscale_path': 'img2'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0], 'input_full_path': 'img2/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img3/s0', 'multiscale_path': 'img3'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0], 'input_full_path': 'img3/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img4/s0', 'multiscale_path': 'img4'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0], 'input_full_path': 'img4/physical'}]\n" - ] + "cell_type": "markdown", + "id": "98bbabd7", + "metadata": {}, + "source": [ + "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + ] }, { - "data": { - "text/plain": [ - "" + "cell_type": "code", + "execution_count": 2, + "id": "e3023c95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(512, 512)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_image = data.human_mitosis()\n", + "example_image.shape" ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "viewer.layers.clear()\n", - "viewer.open(\"test_example_scene.zarr\", plugin=\"napari-ome-zarr\")\n", - "viewer.add_image(example_image, name=\"original image\", blending=\"additive\", colormap=\"orange\")" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "2d7a1333", - "metadata": {}, - "outputs": [], - "source": [ - "import SimpleITK as sitk" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "d96b2264", - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "markdown", + "id": "71e31e2a", + "metadata": {}, + "source": [ + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "017d4c59", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", + "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n" + ] + } + ], + "source": [ + "img1 = NgffImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", + "img2 = NgffImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", + "img3 = NgffImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", + "img4 = NgffImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", + "\n", + "img1_ms = NgffMultiscales(img1)\n", + "img2_ms = NgffMultiscales(img2)\n", + "img3_ms = NgffMultiscales(img3)\n", + "img4_ms = NgffMultiscales(img4)" + ] + }, + { + "cell_type": "markdown", + "id": "cf59df32", + "metadata": {}, + "source": [ + "Next, we need to define a coordinate system into which all images are projected.\n", + "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "994674c2", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_system = {\n", + " \"name\": \"world\",\n", + " \"axes\": [\n", + " {\"name\": \"y\", \"type\": \"space\"},\n", + " {\"name\": \"x\", \"type\": \"space\"},\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c1d629be", + "metadata": {}, + "source": [ + "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", + "In thisexamples, these are simple translations in the y and x dimensions:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d6329caa", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_transformations = [\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 0],\n", + " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 0],\n", + " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 256],\n", + " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 256],\n", + " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4444a8d3", + "metadata": {}, + "source": [ + "We can then create and write a scene like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d05be093", + "metadata": {}, + "outputs": [], + "source": [ + "scene = NgffScene(\n", + " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", + " coordinate_systems=[coordinate_system],\n", + " coordinate_transformations=coordinate_transformations\n", + ")\n", + "\n", + "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "id": "cbf307f3", + "metadata": {}, + "source": [ + "## Optional: View with napari" + ] + }, { - "data": { - "text/plain": [ - "array([[[-80.368095, -21.71694 ],\n", - " [-80.36892 , -21.716684],\n", - " [-80.36974 , -21.716429],\n", - " ...,\n", - " [-81.662254, -21.221333],\n", - " [-81.662895, -21.221027],\n", - " [-81.663536, -21.220722]],\n", - "\n", - " [[-80.36836 , -21.717838],\n", - " [-80.36919 , -21.717583],\n", - " [-80.37002 , -21.717327],\n", - " ...,\n", - " [-81.66253 , -21.22223 ],\n", - " [-81.66317 , -21.221926],\n", - " [-81.6638 , -21.22162 ]],\n", - "\n", - " [[-80.36864 , -21.718739],\n", - " [-80.36946 , -21.718483],\n", - " [-80.370285, -21.718227],\n", - " ...,\n", - " [-81.6628 , -21.22313 ],\n", - " [-81.66344 , -21.222824],\n", - " [-81.66408 , -21.222519]],\n", - "\n", - " ...,\n", - "\n", - " [[-80.57437 , -25.218006],\n", - " [-80.575195, -25.217754],\n", - " [-80.57602 , -25.217505],\n", - " ...,\n", - " [-81.86771 , -24.724539],\n", - " [-81.86834 , -24.72423 ],\n", - " [-81.86898 , -24.723923]],\n", - "\n", - " [[-80.5742 , -25.218779],\n", - " [-80.57503 , -25.218529],\n", - " [-80.57585 , -25.218277],\n", - " ...,\n", - " [-81.86753 , -24.725313],\n", - " [-81.868164, -24.725004],\n", - " [-81.868805, -24.724697]],\n", - "\n", - " [[-80.57403 , -25.219551],\n", - " [-80.57486 , -25.219301],\n", - " [-80.57568 , -25.21905 ],\n", - " ...,\n", - " [-81.867355, -24.726088],\n", - " [-81.868 , -24.725779],\n", - " [-81.86863 , -24.725471]]], shape=(4157, 1768, 2), dtype=float32)" + "cell_type": "code", + "execution_count": null, + "id": "3d024ab0", + "metadata": {}, + "outputs": [], + "source": [ + "viewer = napari.Viewer()" ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "84573d7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Root group {'ome': {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}}\n", + "Scene.matches {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}\n", + "Scene\n", + "spec \n", + "Scene.iter_nodes...final outputs -----> {'world'}\n", + "IMAGES\n", + "---------------------\n", + "img1\n", + "---------------------\n", + "img2\n", + "---------------------\n", + "img3\n", + "---------------------\n", + "img4\n", + "Nodes [, , , ]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img1/s0', 'multiscale_path': 'img1'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0], 'input_full_path': 'img1/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img2/s0', 'multiscale_path': 'img2'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0], 'input_full_path': 'img2/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img3/s0', 'multiscale_path': 'img3'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0], 'input_full_path': 'img3/physical'}]\n", + "node \n", + "\n", + "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img4/s0', 'multiscale_path': 'img4'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0], 'input_full_path': 'img4/physical'}]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "viewer.layers.clear()\n", + "viewer.open(\"test_example_scene.zarr\", plugin=\"napari-ome-zarr\")\n", + "viewer.add_image(example_image, name=\"original image\", blending=\"additive\", colormap=\"orange\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "2d7a1333", + "metadata": {}, + "outputs": [], + "source": [ + "import SimpleITK as sitk" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d96b2264", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[-80.368095, -21.71694 ],\n", + " [-80.36892 , -21.716684],\n", + " [-80.36974 , -21.716429],\n", + " ...,\n", + " [-81.662254, -21.221333],\n", + " [-81.662895, -21.221027],\n", + " [-81.663536, -21.220722]],\n", + "\n", + " [[-80.36836 , -21.717838],\n", + " [-80.36919 , -21.717583],\n", + " [-80.37002 , -21.717327],\n", + " ...,\n", + " [-81.66253 , -21.22223 ],\n", + " [-81.66317 , -21.221926],\n", + " [-81.6638 , -21.22162 ]],\n", + "\n", + " [[-80.36864 , -21.718739],\n", + " [-80.36946 , -21.718483],\n", + " [-80.370285, -21.718227],\n", + " ...,\n", + " [-81.6628 , -21.22313 ],\n", + " [-81.66344 , -21.222824],\n", + " [-81.66408 , -21.222519]],\n", + "\n", + " ...,\n", + "\n", + " [[-80.57437 , -25.218006],\n", + " [-80.575195, -25.217754],\n", + " [-80.57602 , -25.217505],\n", + " ...,\n", + " [-81.86771 , -24.724539],\n", + " [-81.86834 , -24.72423 ],\n", + " [-81.86898 , -24.723923]],\n", + "\n", + " [[-80.5742 , -25.218779],\n", + " [-80.57503 , -25.218529],\n", + " [-80.57585 , -25.218277],\n", + " ...,\n", + " [-81.86753 , -24.725313],\n", + " [-81.868164, -24.725004],\n", + " [-81.868805, -24.724697]],\n", + "\n", + " [[-80.57403 , -25.219551],\n", + " [-80.57486 , -25.219301],\n", + " [-80.57568 , -25.21905 ],\n", + " ...,\n", + " [-81.867355, -24.726088],\n", + " [-81.868 , -24.725779],\n", + " [-81.86863 , -24.725471]]], shape=(4157, 1768, 2), dtype=float32)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = sitk.ReadImage(r\"c:\\Users\\johan\\Documents\\Globus\\Leap043\\nl-mid-LAICPMSI-a-mip_to_LAICPMSI-a-mosaic_reg_output\\deformationField.mha\")\n", + "\n", + "# get data as numpy array\n", + "sitk.GetArrayFromImage(image)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3567a037", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e7d0ba3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" } - ], - "source": [ - "image = sitk.ReadImage(r\"c:\\Users\\johan\\Documents\\Globus\\Leap043\\nl-mid-LAICPMSI-a-mip_to_LAICPMSI-a-mosaic_reg_output\\deformationField.mha\")\n", - "\n", - "# get data as numpy array\n", - "sitk.GetArrayFromImage(image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3567a037", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e7d0ba3", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.12.12)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/ome_zarr/__init__.py b/ome_zarr/__init__.py index 80c749d9..d34f5145 100644 --- a/ome_zarr/__init__.py +++ b/ome_zarr/__init__.py @@ -1,7 +1,7 @@ from dask import __version__ as dask_version from packaging.version import Version -from .classes import OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale, NgffScene +from .classes import NgffScene, OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale # Expose __version__ and fallback when _version.py doesn't exist. try: diff --git a/ome_zarr/classes/__init__.py b/ome_zarr/classes/__init__.py index 0dcac4ce..76db74f6 100644 --- a/ome_zarr/classes/__init__.py +++ b/ome_zarr/classes/__init__.py @@ -2,8 +2,8 @@ from .scene import NgffScene __all__ = [ + "NgffScene", "OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", - "NgffScene", ] From 2c5b8fa92dab2aba38f6cae9a60db81a560a3bf6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 1 Jun 2026 23:10:33 +0200 Subject: [PATCH 11/95] use latest models and classes --- ome_zarr/classes/image.py | 10 +++++----- ome_zarr/classes/scene.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 0b8d673f..aac60c02 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -8,7 +8,7 @@ import dask.array as da import numpy as np import zarr -from ome_zarr_models._v06.coordinate_transforms import ( +from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, Axis, CoordinateSystem, @@ -16,13 +16,13 @@ Identity, Scale, ) -from ome_zarr_models._v06.coordinate_transforms import ( +from ome_zarr_models.v06.coordinate_transforms import ( Sequence as TransformSequence, ) -from ome_zarr_models._v06.multiscales import ( +from ome_zarr_models.v06.multiscales import ( Dataset, ) -from ome_zarr_models._v06.multiscales import ( +from ome_zarr_models.v06.multiscales import ( Multiscale as MultiscaleV06, ) from ome_zarr_models.common.image_label_types import LabelBase as Label @@ -319,7 +319,7 @@ def to_ome_zarr( shutil.rmtree(group) fmt: Format | None = None - if version == "0.5" or version == "0.6": + if version == "0.5" or "0.6" in version: fmt = FormatV05() elif version == "0.4": fmt = FormatV04() diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 7761f778..bae582fa 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -5,21 +5,21 @@ from typing import Any import zarr -from ome_zarr_models._v06.coordinate_transforms import ( +from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, CoordinateSystem, ) -from ome_zarr_models._v06.scene import SceneAttrs +from ome_zarr_models.v06.scene import SceneAttrs from pydantic import TypeAdapter from zarr.storage import StoreLike -from .image import NgffMultiscales +from .image import OMEZarrMultiscale # the class exposed to the user @dataclass(kw_only=True) class NgffScene: - images: list[NgffMultiscales] + images: list[OMEZarrMultiscale] metadata: SceneAttrs = field(init=False, default=None) coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = ( @@ -121,7 +121,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): # Write the image subgroup = zarr_group.create_group(img_name, overwrite=not overwrite) - img.to_ome_zarr(subgroup) + img.to_ome_zarr(subgroup, overwrite=overwrite) self._written_image_names.add(img_name) # Always update scene metadata @@ -150,8 +150,8 @@ def from_ome_zarr(cls, store: StoreLike): for img_name in zarr_group.group_keys(): img_group = zarr_group[img_name] # Assume images have their own to_ome_zarr-like interface - # You may need to adapt based on your NgffMultiscales.from_ome_zarr implementation - img = NgffMultiscales.from_ome_zarr(img_group) + # You may need to adapt based on your OMEZarrMultiscale.from_ome_zarr implementation + img = OMEZarrMultiscale.from_ome_zarr(img_group) images.append(img) # Load scene metadata From 9b8cb63223ca153713d7937568420ac9ac206545 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:10:54 +0000 Subject: [PATCH 12/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/image.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index aac60c02..ac882ff3 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -8,6 +8,11 @@ import dask.array as da import numpy as np import zarr +from ome_zarr_models.common.image_label_types import LabelBase as Label +from ome_zarr_models.common.omero import Omero +from ome_zarr_models.v05.multiscales import ( + Multiscale as MultiscaleV05, +) from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, Axis, @@ -25,11 +30,6 @@ from ome_zarr_models.v06.multiscales import ( Multiscale as MultiscaleV06, ) -from ome_zarr_models.common.image_label_types import LabelBase as Label -from ome_zarr_models.common.omero import Omero -from ome_zarr_models.v05.multiscales import ( - Multiscale as MultiscaleV05, -) from pydantic import TypeAdapter, ValidationError from ome_zarr.scale import Methods From d61176202f3190c7baf8387adb7da9ae3d8f449f Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:20:32 +0200 Subject: [PATCH 13/95] refactor: change naming and use graph building --- ome_zarr/classes/image.py | 1 + ome_zarr/classes/scene.py | 89 ++++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index aac60c02..8e8e19f8 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -392,6 +392,7 @@ def to_ome_zarr( _recursive_pop_nones(write_metadata.model_dump(by_alias=True)) ], } + group.attrs["ome"] = metadata_dict # # Update mode: only update the labels list in metadata # elif list_of_labels: diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index bae582fa..89c59b5a 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -3,6 +3,7 @@ from collections.abc import Sequence from dataclasses import dataclass, field from typing import Any +import networkx as nx import zarr from ome_zarr_models.v06.coordinate_transforms import ( @@ -18,7 +19,7 @@ # the class exposed to the user @dataclass(kw_only=True) -class NgffScene: +class OMEZarrScene: images: list[OMEZarrMultiscale] metadata: SceneAttrs = field(init=False, default=None) coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] @@ -42,47 +43,49 @@ def __post_init__(self): coordinateTransformations=self.coordinate_transformations, ) - # self._graph = nx.DiGraph() - - # if self.coordinate_systems is not None: - # for cs in self.coordinate_systems: - # if hasattr(cs, "path") and cs.path is None: - # self._graph.add_node((None, cs.name)) - - # for img in self.images: - # # add all coordinate systems as nodes - # for cs in img.metadata.coordinateSystems: - # node_id = (img.metadata.name, cs.name) - # self._graph.add_node(node_id) - - # for ds in img.metadata.datasets: - # # add all datasets as nodes - # node_id = (img.metadata.name, ds.path) - # self._graph.add_node(node_id) - - # # add scale transformations from dataset as edges - # transform = ds.coordinateTransformations - # self._graph.add_edge( - # (img.metadata.name, ds.path), - # (img.metadata.name, ds.coordinateTransformations[0].output), - # transform=transform, - # ) - - # # add additional transformations from image metadata as edges - # if img.metadata.coordinateTransformations: - # for tf in img.metadata.coordinateTransformations: - # self._graph.add_edge( - # (img.metadata.name, tf.input), - # (img.metadata.name, tf.output), - # transform=tf, - # ) - - # # add scene-level transformations as edges between coordinate systems of different images - # for tf in self.transformations: - # self._graph.add_edge( - # (tf.input.path, tf.input.name), - # (tf.output.path, tf.output.name), - # transform=tf) + + def _build_graph(self): + self._graph = nx.DiGraph() + + if self.coordinate_systems is not None: + for cs in self.coordinate_systems: + if hasattr(cs, "path") and cs.path is None: + self._graph.add_node((None, cs.name)) + + for img in self.images: + # add all coordinate systems as nodes + for cs in img.metadata.coordinateSystems: + node_id = (img.metadata.name, cs.name) + self._graph.add_node(node_id) + + for ds in img.metadata.datasets: + # add all datasets as nodes + node_id = (img.metadata.name, ds.path) + self._graph.add_node(node_id) + + # add scale transformations from dataset as edges + transform = ds.coordinateTransformations + self._graph.add_edge( + (img.metadata.name, ds.path), + (img.metadata.name, ds.coordinateTransformations[0].output), + transform=transform, + ) + + # add additional transformations from image metadata as edges + if img.metadata.coordinateTransformations: + for tf in img.metadata.coordinateTransformations: + self._graph.add_edge( + (img.metadata.name, tf.input), + (img.metadata.name, tf.output), + transform=tf, + ) + + # add scene-level transformations as edges between coordinate systems of different images + for tf in self.coordinate_transformations: + self._graph.add_edge( + (tf.input.path, tf.input.name), + (tf.output.path, tf.output.name), + transform=tf) def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ @@ -121,7 +124,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): # Write the image subgroup = zarr_group.create_group(img_name, overwrite=not overwrite) - img.to_ome_zarr(subgroup, overwrite=overwrite) + img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") self._written_image_names.add(img_name) # Always update scene metadata From f47f2bfd3807cd749f7e81697e32612475aeb012 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:20:49 +0000 Subject: [PATCH 14/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 89c59b5a..ec18991e 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -3,8 +3,8 @@ from collections.abc import Sequence from dataclasses import dataclass, field from typing import Any -import networkx as nx +import networkx as nx import zarr from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, @@ -43,7 +43,6 @@ def __post_init__(self): coordinateTransformations=self.coordinate_transformations, ) - def _build_graph(self): self._graph = nx.DiGraph() @@ -85,7 +84,8 @@ def _build_graph(self): self._graph.add_edge( (tf.input.path, tf.input.name), (tf.output.path, tf.output.name), - transform=tf) + transform=tf, + ) def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ From 8d2b586f55306b8e63a0a1e6fe6a50bfe4a27603 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:35:44 +0200 Subject: [PATCH 15/95] update --- ome_zarr/__init__.py | 4 +- ome_zarr/classes/__init__.py | 4 +- ome_zarr/classes/image.py | 9 +- ome_zarr/classes/scene.py | 159 ++++++++++++++++++----------------- 4 files changed, 92 insertions(+), 84 deletions(-) diff --git a/ome_zarr/__init__.py b/ome_zarr/__init__.py index d34f5145..967b1d5a 100644 --- a/ome_zarr/__init__.py +++ b/ome_zarr/__init__.py @@ -1,7 +1,7 @@ from dask import __version__ as dask_version from packaging.version import Version -from .classes import NgffScene, OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale +from .classes import OMEZarrScene, OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale # Expose __version__ and fallback when _version.py doesn't exist. try: @@ -12,4 +12,4 @@ # If not 2026.3.0 it must be 2025.11.0 or lower. Name indicates kwargs only contain array kwargs in the dask version. USE_DASK_ARRAY_KWARGS = Version(dask_version) >= Version("2026.3.0") -__all__ = ["OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", "__version__"] +__all__ = ["OMEZarrScene", "OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", "__version__"] diff --git a/ome_zarr/classes/__init__.py b/ome_zarr/classes/__init__.py index 76db74f6..9f8197b2 100644 --- a/ome_zarr/classes/__init__.py +++ b/ome_zarr/classes/__init__.py @@ -1,8 +1,8 @@ from .image import OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale -from .scene import NgffScene +from .scene import OMEZarrScene __all__ = [ - "NgffScene", + "OMEZarrScene", "OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 84137545..770da111 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -526,7 +526,6 @@ def from_ome_zarr( } if not axes_units: axes_units = None - axes_names = [str(ax.name) for ax in metadata.axes if ax.name is not None] images.append( OMEZarrImage( data=data, @@ -782,7 +781,7 @@ def _write_additional_meta_data( if version == "0.4": group.attrs["omero"] = omero_dict - elif version == "0.5": + elif version == "0.5" or "0.6" in version: if "ome" not in group.attrs: raise ValueError("OME-Zarr attributes not found in group") ome = cast(dict, group.attrs["ome"]) @@ -823,7 +822,7 @@ def _write_additional_meta_data( # Update labels list in metadata if version == "0.4": label_group.attrs["labels"] = list_of_labels - elif version == "0.5": + elif version == "0.5" or "0.6" in version: label_group.attrs["ome"] = { "version": version, "labels": list_of_labels, @@ -964,7 +963,7 @@ def _read_additional_metadata( if version in ("0.1", "0.2", "0.3", "0.4") and "omero" in group.attrs: omero_dict = cast(dict[str, Any] | None, group.attrs.get("omero", None)) - elif version == "0.5": + elif version == "0.5" or "0.6" in version: ome_attrs = cast(dict[str, Any], group.attrs.get("ome", {})) if "omero" in ome_attrs: omero_dict = cast(dict[str, Any] | None, ome_attrs.get("omero", None)) @@ -983,7 +982,7 @@ def _read_additional_metadata( list_of_labels = ( cast(list[str], labels_json) if isinstance(labels_json, list) else [] ) - elif version == "0.5" and "labels" in group: + elif (version == "0.5" or "0.6" in version) and "labels" in group: labels_ome_attrs = cast( dict[str, Any], group["labels"].attrs.get("ome", {}) ) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 89c59b5a..740cd643 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -1,9 +1,8 @@ # the class for storage representation, not exposed to the user import os from collections.abc import Sequence -from dataclasses import dataclass, field from typing import Any -import networkx as nx +import transformnd as tnd import zarr from ome_zarr_models.v06.coordinate_transforms import ( @@ -17,25 +16,22 @@ from .image import OMEZarrMultiscale -# the class exposed to the user -@dataclass(kw_only=True) class OMEZarrScene: - images: list[OMEZarrMultiscale] - metadata: SceneAttrs = field(init=False, default=None) - coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]] - coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = ( - None - ) - _written_image_names: set[str] = field(default_factory=set, init=False) + def __init__( + self, + images: list[OMEZarrMultiscale], + coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], + coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None + ): - def __post_init__(self): + self.images = images # parse coordinate systems and transforms self.coordinate_systems = self._parse_coordinate_systems( - self.coordinate_systems + coordinate_systems ) self.coordinate_transformations = self._parse_transforms( - self.coordinate_transformations + coordinate_transformations ) self.metadata = SceneAttrs( @@ -43,49 +39,45 @@ def __post_init__(self): coordinateTransformations=self.coordinate_transformations, ) + self._build_graph() + self._written_image_names = set() - def _build_graph(self): - self._graph = nx.DiGraph() - - if self.coordinate_systems is not None: - for cs in self.coordinate_systems: - if hasattr(cs, "path") and cs.path is None: - self._graph.add_node((None, cs.name)) - - for img in self.images: - # add all coordinate systems as nodes - for cs in img.metadata.coordinateSystems: - node_id = (img.metadata.name, cs.name) - self._graph.add_node(node_id) - - for ds in img.metadata.datasets: - # add all datasets as nodes - node_id = (img.metadata.name, ds.path) - self._graph.add_node(node_id) - - # add scale transformations from dataset as edges - transform = ds.coordinateTransformations - self._graph.add_edge( - (img.metadata.name, ds.path), - (img.metadata.name, ds.coordinateTransformations[0].output), - transform=transform, - ) - # add additional transformations from image metadata as edges - if img.metadata.coordinateTransformations: - for tf in img.metadata.coordinateTransformations: - self._graph.add_edge( - (img.metadata.name, tf.input), - (img.metadata.name, tf.output), - transform=tf, - ) + def _build_graph(self): + self._graph = tnd.graph.TransformGraph() + + # for img in self.images: + # # add all coordinate systems as nodes + # for cs in img.metadata.coordinateSystems: + # node_id = (img.metadata.name, cs.name) + # self._graph.add_node(node_id) + + # for ds in img.metadata.datasets: + # # add all datasets as nodes + # node_id = (img.metadata.name, ds.path) + # self._graph.add_node(node_id) + + # # add scale transformations from dataset as edges + # transform = ds.coordinateTransformations + # self._graph.add_edge( + # (img.metadata.name, ds.path), + # (img.metadata.name, ds.coordinateTransformations[0].output), + # transform=transform, + # ) + + # # add additional transformations from image metadata as edges + # if img.metadata.coordinateTransformations: + # for tf in img.metadata.coordinateTransformations: + # self._graph.add_edge( + # (img.metadata.name, tf.input), + # (img.metadata.name, tf.output), + # transform=tf, + # ) # add scene-level transformations as edges between coordinate systems of different images for tf in self.coordinate_transformations: - self._graph.add_edge( - (tf.input.path, tf.input.name), - (tf.output.path, tf.output.name), - transform=tf) + self._graph.add_transforms([_ozmp_tf_to_tnd(tf)]) + def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ @@ -146,7 +138,11 @@ def from_ome_zarr(cls, store: StoreLike): """ tf_adapter = TypeAdapter(AnyTransform) - zarr_group = zarr.open(store, mode="r") + # Handle both StoreLike (string, dict, etc.) and zarr.Group objects + if isinstance(store, zarr.Group): + zarr_group = store + else: + zarr_group = zarr.open(store, mode="r") # Load all image subgroups images = [] @@ -178,28 +174,10 @@ def from_ome_zarr(cls, store: StoreLike): coordinate_systems = None # Use object.__new__ to create instance without triggering __init__ and __setattr__ - scene = object.__new__(cls) - - # Set fields directly using object.__setattr__ to bypass custom __setattr__ - object.__setattr__(scene, "images", images) - object.__setattr__(scene, "coordinate_transformations", tuple(transformations)) - object.__setattr__( - scene, - "coordinate_systems", - tuple(coordinate_systems) if coordinate_systems else None, - ) - object.__setattr__( - scene, "_written_image_names", {img.metadata.name for img in images} - ) - - # Now set metadata - object.__setattr__( - scene, - "metadata", - SceneAttrs( - coordinateSystems=scene.coordinate_systems, - coordinateTransformations=scene.coordinate_transformations, - ), + scene = OMEZarrScene( + images=images, + coordinate_transformations=transformations, + coordinate_systems=coordinate_systems ) return scene @@ -272,3 +250,34 @@ def _parse_coordinate_systems( parsed_coordinate_systems.append(cs) return tuple(parsed_coordinate_systems) + + +def _ozmp_tf_to_tnd(transform: AnyTransform) -> tnd.base.Transform: + """ + Convert an OME-Zarr coordinate transformation to a transformnd Transform object. + This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. + """ + tnd_transform = None + # Example for an affine transformation (this will depend on the actual structure of AnyTransform) + if transform.type == "affine": + tnd_transform = tnd.transforms.Affine.from_linear_map(transform.affine) + elif transform.type == "scale": + tnd_transform = tnd.transforms.Scale(transform.scale) + elif transform.type == "translation": + tnd_transform = tnd.transforms.Translate(transform.translation) + elif transform.type == "rotation": + tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) + elif transform.type == "sequence": + sub_transformations = transform.transformations + tnd_sub_transforms = [_ozmp_tf_to_tnd(sub_tf) for sub_tf in sub_transformations] + tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) + + if transform.input is not None and tnd_transform is not None: + input_path = transform.input.path if transform.input.path is not None else "" + output_path = transform.output.path if transform.output.path is not None else "" + tnd_transform.spaces = tnd.Spaces( + f"{input_path}:{transform.input.name}", + f"{output_path}:{transform.output.name}" + ) + + return tnd_transform \ No newline at end of file From bd2980fbb505fe2f0e6b06cd78fbcdf2d668b368 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 07:36:26 +0000 Subject: [PATCH 16/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/__init__.py | 10 ++++++++-- ome_zarr/classes/__init__.py | 2 +- ome_zarr/classes/scene.py | 32 +++++++++++++++----------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/ome_zarr/__init__.py b/ome_zarr/__init__.py index 967b1d5a..07f8a1db 100644 --- a/ome_zarr/__init__.py +++ b/ome_zarr/__init__.py @@ -1,7 +1,7 @@ from dask import __version__ as dask_version from packaging.version import Version -from .classes import OMEZarrScene, OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale +from .classes import OMEZarrImage, OMEZarrLabels, OMEZarrMultiscale, OMEZarrScene # Expose __version__ and fallback when _version.py doesn't exist. try: @@ -12,4 +12,10 @@ # If not 2026.3.0 it must be 2025.11.0 or lower. Name indicates kwargs only contain array kwargs in the dask version. USE_DASK_ARRAY_KWARGS = Version(dask_version) >= Version("2026.3.0") -__all__ = ["OMEZarrScene", "OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", "__version__"] +__all__ = [ + "OMEZarrImage", + "OMEZarrLabels", + "OMEZarrMultiscale", + "OMEZarrScene", + "__version__", +] diff --git a/ome_zarr/classes/__init__.py b/ome_zarr/classes/__init__.py index 9f8197b2..3a8b13d1 100644 --- a/ome_zarr/classes/__init__.py +++ b/ome_zarr/classes/__init__.py @@ -2,8 +2,8 @@ from .scene import OMEZarrScene __all__ = [ - "OMEZarrScene", "OMEZarrImage", "OMEZarrLabels", "OMEZarrMultiscale", + "OMEZarrScene", ] diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 5f014aad..3a097cde 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -2,8 +2,8 @@ import os from collections.abc import Sequence from typing import Any -import transformnd as tnd +import transformnd as tnd import zarr from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, @@ -18,18 +18,18 @@ class OMEZarrScene: def __init__( - self, - images: list[OMEZarrMultiscale], - coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], - coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None - ): + self, + images: list[OMEZarrMultiscale], + coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], + coordinate_systems: ( + Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None + ) = None, + ): self.images = images # parse coordinate systems and transforms - self.coordinate_systems = self._parse_coordinate_systems( - coordinate_systems - ) + self.coordinate_systems = self._parse_coordinate_systems(coordinate_systems) self.coordinate_transformations = self._parse_transforms( coordinate_transformations ) @@ -42,11 +42,10 @@ def __init__( self._build_graph() self._written_image_names = set() - def _build_graph(self): self._graph = tnd.graph.TransformGraph() - for img in self.images: + for img in self.images: # add additional transformations from image metadata as edges if img.metadata.coordinateTransformations: @@ -60,7 +59,6 @@ def _build_graph(self): # add scene-level transformations as edges between coordinate systems of different images for tf in self.coordinate_transformations: self._graph.add_transforms([_ozmp_tf_to_tnd(tf)]) - def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ @@ -160,7 +158,7 @@ def from_ome_zarr(cls, store: StoreLike): scene = OMEZarrScene( images=images, coordinate_transformations=transformations, - coordinate_systems=coordinate_systems + coordinate_systems=coordinate_systems, ) return scene @@ -260,7 +258,7 @@ def _ozmp_tf_to_tnd(transform: AnyTransform) -> tnd.base.Transform: output_path = transform.output.path if transform.output.path is not None else "" tnd_transform.spaces = tnd.Spaces( f"{input_path}:{transform.input.name}", - f"{output_path}:{transform.output.name}" - ) - - return tnd_transform \ No newline at end of file + f"{output_path}:{transform.output.name}", + ) + + return tnd_transform From 4eeb8c84228f6ffc46c5e3525a904a2734391bc6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:52:53 +0200 Subject: [PATCH 17/95] feat: store images along with paths --- ome_zarr/classes/scene.py | 97 +++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 5f014aad..308ad492 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -19,12 +19,23 @@ class OMEZarrScene: def __init__( self, - images: list[OMEZarrMultiscale], + images: list[OMEZarrMultiscale] | dict[str, OMEZarrMultiscale], coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None ): - - self.images = images + """ + Parameters + ---------- + images : list[OMEZarrMultiscale] | dict[str, OMEZarrMultiscale] + Either a list of images (keyed internally by metadata.name) or a dict + mapping zarr group paths to images. The dict form gives explicit control + over the paths where images will be stored in the zarr hierarchy. + """ + # Coerce list to dict keyed by metadata.name + if isinstance(images, list): + self.images = {str(img.metadata.name): img for img in images} + else: + self.images = images # parse coordinate systems and transforms self.coordinate_systems = self._parse_coordinate_systems( @@ -45,22 +56,31 @@ def __init__( def _build_graph(self): self._graph = tnd.graph.TransformGraph() - - for img in self.images: - - # add additional transformations from image metadata as edges - if img.metadata.coordinateTransformations: - for tf in img.metadata.coordinateTransformations: - self._graph.add_edge( - (img.metadata.name, tf.input), - (img.metadata.name, tf.output), - transform=tf, - ) - - # add scene-level transformations as edges between coordinate systems of different images + # Add scene-level transformations (empty context = root level) for tf in self.coordinate_transformations: - self._graph.add_transforms([_ozmp_tf_to_tnd(tf)]) - + if tf.type == "sequence": + tnd_transform = _ozmp_tf_to_tnd(tf).simplify() + else: + tnd_transform = [_ozmp_tf_to_tnd(tf)] + self._graph.add_transforms(tnd_transform) + + # check if input/output are defined + subgroups = [] + if tf.input.path is not None: + subgroups.append(tf.input.path) + if tf.output.path is not None: + subgroups.append(tf.output.path) + + for subgroup in subgroups: + img = self.images.get(subgroup) + if img is None: + # Image not found in scene - skip or warn + continue + if img.metadata.coordinateTransformations: + for img_tf in img.metadata.coordinateTransformations: + ind_transform = _ozmp_tf_to_tnd(img_tf, zarr_context=subgroup) + self._graph.add_transforms([ind_transform]) + def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ @@ -89,18 +109,16 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): mode = "a" if overwrite else "w" zarr_group = zarr.open(store, mode=mode) - # Create a subgroup for each image using its name - for img in tqdm.tqdm(self.images, desc="Writing images"): - img_name = str(img.metadata.name) - + # Create a subgroup for each image using its path key + for img_path, img in tqdm.tqdm(self.images.items(), desc="Writing images"): # Skip if already written (incremental mode) - if not overwrite and img_name in self._written_image_names: + if not overwrite and img_path in self._written_image_names: continue # Write the image - subgroup = zarr_group.create_group(img_name, overwrite=not overwrite) + subgroup = zarr_group.create_group(img_path, overwrite=not overwrite) img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") - self._written_image_names.add(img_name) + self._written_image_names.add(img_path) # Always update scene metadata metadata_dict = self.metadata.model_dump() @@ -127,14 +145,12 @@ def from_ome_zarr(cls, store: StoreLike): else: zarr_group = zarr.open(store, mode="r") - # Load all image subgroups - images = [] - for img_name in zarr_group.group_keys(): - img_group = zarr_group[img_name] - # Assume images have their own to_ome_zarr-like interface - # You may need to adapt based on your OMEZarrMultiscale.from_ome_zarr implementation + # Load all image subgroups, keyed by their zarr path + images = {} + for img_path in zarr_group.group_keys(): + img_group = zarr_group[img_path] img = OMEZarrMultiscale.from_ome_zarr(img_group) - images.append(img) + images[img_path] = img # Load scene metadata ome_metadata = zarr_group.attrs.get("ome", {}) @@ -235,7 +251,7 @@ def _parse_coordinate_systems( return tuple(parsed_coordinate_systems) -def _ozmp_tf_to_tnd(transform: AnyTransform) -> tnd.base.Transform: +def _ozmp_tf_to_tnd(transform: AnyTransform, zarr_context: str ="") -> tnd.base.Transform: """ Convert an OME-Zarr coordinate transformation to a transformnd Transform object. This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. @@ -252,12 +268,25 @@ def _ozmp_tf_to_tnd(transform: AnyTransform) -> tnd.base.Transform: tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) elif transform.type == "sequence": sub_transformations = transform.transformations - tnd_sub_transforms = [_ozmp_tf_to_tnd(sub_tf) for sub_tf in sub_transformations] + tnd_sub_transforms = [_ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations] tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) if transform.input is not None and tnd_transform is not None: input_path = transform.input.path if transform.input.path is not None else "" output_path = transform.output.path if transform.output.path is not None else "" + + # zarr_context prepends path with relative path from root + # to keep track of global location of coordinate systems in the zarr store + if zarr_context != "" and input_path != "": + input_path = f"{zarr_context}/{input_path}" + elif zarr_context != "": + input_path = zarr_context + + if zarr_context != "" and output_path != "": + output_path = f"{zarr_context}/{output_path}" + elif zarr_context != "": + output_path = zarr_context + tnd_transform.spaces = tnd.Spaces( f"{input_path}:{transform.input.name}", f"{output_path}:{transform.output.name}" From 4259f77d20c73827198993056fe5979bcab5044b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:53:30 +0000 Subject: [PATCH 18/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 308ad492..7584bf15 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -2,8 +2,8 @@ import os from collections.abc import Sequence from typing import Any -import transformnd as tnd +import transformnd as tnd import zarr from ome_zarr_models.v06.coordinate_transforms import ( AnyTransform, @@ -18,16 +18,18 @@ class OMEZarrScene: def __init__( - self, - images: list[OMEZarrMultiscale] | dict[str, OMEZarrMultiscale], - coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], - coordinate_systems: Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None = None - ): + self, + images: list[OMEZarrMultiscale] | dict[str, OMEZarrMultiscale], + coordinate_transformations: Sequence[AnyTransform] | list[dict[str, Any]], + coordinate_systems: ( + Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None + ) = None, + ): """ Parameters ---------- images : list[OMEZarrMultiscale] | dict[str, OMEZarrMultiscale] - Either a list of images (keyed internally by metadata.name) or a dict + Either a list of images (keyed internally by metadata.name) or a dict mapping zarr group paths to images. The dict form gives explicit control over the paths where images will be stored in the zarr hierarchy. """ @@ -38,9 +40,7 @@ def __init__( self.images = images # parse coordinate systems and transforms - self.coordinate_systems = self._parse_coordinate_systems( - coordinate_systems - ) + self.coordinate_systems = self._parse_coordinate_systems(coordinate_systems) self.coordinate_transformations = self._parse_transforms( coordinate_transformations ) @@ -53,7 +53,6 @@ def __init__( self._build_graph() self._written_image_names = set() - def _build_graph(self): self._graph = tnd.graph.TransformGraph() # Add scene-level transformations (empty context = root level) @@ -81,7 +80,6 @@ def _build_graph(self): ind_transform = _ozmp_tf_to_tnd(img_tf, zarr_context=subgroup) self._graph.add_transforms([ind_transform]) - def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ Write scene to OME-Zarr format. @@ -176,7 +174,7 @@ def from_ome_zarr(cls, store: StoreLike): scene = OMEZarrScene( images=images, coordinate_transformations=transformations, - coordinate_systems=coordinate_systems + coordinate_systems=coordinate_systems, ) return scene @@ -251,7 +249,9 @@ def _parse_coordinate_systems( return tuple(parsed_coordinate_systems) -def _ozmp_tf_to_tnd(transform: AnyTransform, zarr_context: str ="") -> tnd.base.Transform: +def _ozmp_tf_to_tnd( + transform: AnyTransform, zarr_context: str = "" +) -> tnd.base.Transform: """ Convert an OME-Zarr coordinate transformation to a transformnd Transform object. This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. @@ -268,7 +268,9 @@ def _ozmp_tf_to_tnd(transform: AnyTransform, zarr_context: str ="") -> tnd.base. tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) elif transform.type == "sequence": sub_transformations = transform.transformations - tnd_sub_transforms = [_ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations] + tnd_sub_transforms = [ + _ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations + ] tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) if transform.input is not None and tnd_transform is not None: @@ -289,7 +291,7 @@ def _ozmp_tf_to_tnd(transform: AnyTransform, zarr_context: str ="") -> tnd.base. tnd_transform.spaces = tnd.Spaces( f"{input_path}:{transform.input.name}", - f"{output_path}:{transform.output.name}" - ) - - return tnd_transform \ No newline at end of file + f"{output_path}:{transform.output.name}", + ) + + return tnd_transform From e358103cd4ab96c465c96a169b57d7786f27d27d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:42:49 +0200 Subject: [PATCH 19/95] add mapaxis --- ome_zarr/classes/scene.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 7584bf15..57a1556a 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -259,7 +259,9 @@ def _ozmp_tf_to_tnd( tnd_transform = None # Example for an affine transformation (this will depend on the actual structure of AnyTransform) if transform.type == "affine": - tnd_transform = tnd.transforms.Affine.from_linear_map(transform.affine) + tnd_transform = tnd.transforms.Affine(transform.affine) + elif transform.type == "mapAxis": + tnd_transform = tnd.transforms.MapAxis(list(transform.mapAxis)) elif transform.type == "scale": tnd_transform = tnd.transforms.Scale(transform.scale) elif transform.type == "translation": From 9ae38cbb26ab2c626c82de36e1da7cba23fb599d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:42:56 +0200 Subject: [PATCH 20/95] remove # from colors --- ome_zarr/classes/image.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 770da111..b64c2ef7 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -36,16 +36,16 @@ SPATIAL_DIMS = ["z", "y", "x"] DEFAULT_COLORS = [ - "#00FFFF", # cyan - "#FF00FF", # magenta - "#FFFF00", # yellow - "#FF0000", # red - "#00FF00", # green - "#0000FF", # blue - "#FFFFFF", # white - "#FFA500", # orange - "#800080", # purple - "#008000", # dark green + "00FFFF", # cyan + "FF00FF", # magenta + "FFFF00", # yellow + "FF0000", # red + "00FF00", # green + "0000FF", # blue + "FFFFFF", # white + "FFA500", # orange + "800080", # purple + "008000", # dark green ] From f8234816e014df6449cc70c42e20f3acbf93058c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:43:27 +0200 Subject: [PATCH 21/95] add transformnd to deps --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3b754ae4..47497d27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "toolz", "rangehttpserver", "ome-zarr-models >= 1.6", + "transformnd >= 0.3.0", "Deprecated", ] classifiers = [ From f568199e0e67cbfcfd68599904bc72fcc8032454 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:45:56 +0200 Subject: [PATCH 22/95] feat: add utility function to discover coordinate system instances --- ome_zarr/classes/scene.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 57a1556a..6649d2b4 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -53,6 +53,37 @@ def __init__( self._build_graph() self._written_image_names = set() + def get_coordinate_system(self, name: str, path: str | None = None) -> CoordinateSystem | None: + """ + Retrieve a coordinate system by name and optional path. + + Parameters + ---------- + name: str + The name of the coordinate system to retrieve. + path: str | None + Optional path to disambiguate coordinate systems with the same name. If None, will return the first match with the given name. + + Returns + ------- + CoordinateSystem or None + The matching CoordinateSystem object, or None if no match is found. + """ + if path is None: + # coordinate system can only be in top-level + possible_coordinate_systems = self.coordinate_systems or [] + + return next((cs for cs in possible_coordinate_systems if cs.name == name), None) + + possible_coordinate_systems = [] + for group in self.images.keys(): + if group == path: + img = self.images[group] + for cs in img.metadata.coordinateSystems: + if cs.name == name: + return cs + return None + def _build_graph(self): self._graph = tnd.graph.TransformGraph() # Add scene-level transformations (empty context = root level) @@ -296,4 +327,4 @@ def _ozmp_tf_to_tnd( f"{output_path}:{transform.output.name}", ) - return tnd_transform + return tnd_transform \ No newline at end of file From 4dc336d69c43d8f39dc52e0852bf347c9a3adfa4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:46:14 +0000 Subject: [PATCH 23/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 6649d2b4..b59a215b 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -53,7 +53,9 @@ def __init__( self._build_graph() self._written_image_names = set() - def get_coordinate_system(self, name: str, path: str | None = None) -> CoordinateSystem | None: + def get_coordinate_system( + self, name: str, path: str | None = None + ) -> CoordinateSystem | None: """ Retrieve a coordinate system by name and optional path. @@ -73,7 +75,9 @@ def get_coordinate_system(self, name: str, path: str | None = None) -> Coordinat # coordinate system can only be in top-level possible_coordinate_systems = self.coordinate_systems or [] - return next((cs for cs in possible_coordinate_systems if cs.name == name), None) + return next( + (cs for cs in possible_coordinate_systems if cs.name == name), None + ) possible_coordinate_systems = [] for group in self.images.keys(): @@ -327,4 +331,4 @@ def _ozmp_tf_to_tnd( f"{output_path}:{transform.output.name}", ) - return tnd_transform \ No newline at end of file + return tnd_transform From dd84c37cd620be0e122fc62f50b44a0a25f27d28 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:47:03 +0000 Subject: [PATCH 24/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_writer.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 78a264c2..5b2945d7 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -179,7 +179,8 @@ def test_additional_transforms(self): ms.to_ome_zarr( zarr.open(self.path / "test_transforms.zarr", mode="w"), version="0.6.dev4", - overwrite=True) + overwrite=True, + ) # make transform go bad additional_transforms["output"]["name"] = "nonexistent" @@ -209,26 +210,22 @@ def test_image_class_versions(self, version): self.path / f"test_versions_{version}.zarr", mode="w", zarr_format=2, - ) + ) else: grp = zarr.open( self.path / f"test_versions_{version}.zarr", mode="w", zarr_format=3, - ) - ms.to_ome_zarr( - grp, - overwrite=True, - version=version ) - + ms.to_ome_zarr(grp, overwrite=True, version=version) + # open the written zarr and check the version out = zarr.open_group(self.path / f"test_versions_{version}.zarr") if version == "0.4": metadata = out.attrs["multiscales"][0] else: metadata = out.attrs.get("ome", {}) - + assert metadata["version"] == version def test_image_class_bad_args(self): From c76e0c21638151cbb82dbfecd2de75b3ffb3d8f8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:53:19 +0200 Subject: [PATCH 25/95] docs: more demo --- docs/source/_toc.yml | 3 +- docs/source/advanced/create_scenes.ipynb | 382 ------------------ .../advanced/transforms/create_scenes.ipynb | 215 ++++++++++ .../advanced/transforms/reading_scenes.ipynb | 91 +++++ 4 files changed, 308 insertions(+), 383 deletions(-) delete mode 100644 docs/source/advanced/create_scenes.ipynb create mode 100644 docs/source/advanced/transforms/create_scenes.ipynb create mode 100644 docs/source/advanced/transforms/reading_scenes.ipynb diff --git a/docs/source/_toc.yml b/docs/source/_toc.yml index 2d06e97d..8665f6c6 100644 --- a/docs/source/_toc.yml +++ b/docs/source/_toc.yml @@ -20,11 +20,12 @@ parts: sections: - file: advanced/labels/adding_labels - file: advanced/labels/labels_metadata - - file: advanced/create_scenes - file: advanced/transforms/index sections: - file: advanced/transforms/add_transforms_to_multiscales + - file: advanced/create_scenes + - file: advanced/reading_scenes - file: advanced/sharding - file: advanced/build_custom_pyramid diff --git a/docs/source/advanced/create_scenes.ipynb b/docs/source/advanced/create_scenes.ipynb deleted file mode 100644 index 5c51bb27..00000000 --- a/docs/source/advanced/create_scenes.ipynb +++ /dev/null @@ -1,382 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8b0be7e9", - "metadata": {}, - "source": [ - "# Create Scenes\n", - "\n", - "(advanced:create-scenes)=\n", - "\n", - "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a49ec1c2", - "metadata": {}, - "outputs": [], - "source": [ - "import napari\n", - "from skimage import data\n", - "\n", - "from ome_zarr import NgffImage, NgffMultiscales, NgffScene" - ] - }, - { - "cell_type": "markdown", - "id": "98bbabd7", - "metadata": {}, - "source": [ - "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e3023c95", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(512, 512)" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "example_image = data.human_mitosis()\n", - "example_image.shape" - ] - }, - { - "cell_type": "markdown", - "id": "71e31e2a", - "metadata": {}, - "source": [ - "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "017d4c59", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n", - "name='physical' axes=(Axis(name='y', type='space', discrete=None, unit=None, longName=None), Axis(name='x', type='space', discrete=None, unit=None, longName=None))\n" - ] - } - ], - "source": [ - "img1 = NgffImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", - "img2 = NgffImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", - "img3 = NgffImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", - "img4 = NgffImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", - "\n", - "img1_ms = NgffMultiscales(img1)\n", - "img2_ms = NgffMultiscales(img2)\n", - "img3_ms = NgffMultiscales(img3)\n", - "img4_ms = NgffMultiscales(img4)" - ] - }, - { - "cell_type": "markdown", - "id": "cf59df32", - "metadata": {}, - "source": [ - "Next, we need to define a coordinate system into which all images are projected.\n", - "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "994674c2", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_system = {\n", - " \"name\": \"world\",\n", - " \"axes\": [\n", - " {\"name\": \"y\", \"type\": \"space\"},\n", - " {\"name\": \"x\", \"type\": \"space\"},\n", - " ],\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "c1d629be", - "metadata": {}, - "source": [ - "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", - "In thisexamples, these are simple translations in the y and x dimensions:" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "d6329caa", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_transformations = [\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 0],\n", - " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 0],\n", - " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 256],\n", - " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 256],\n", - " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "4444a8d3", - "metadata": {}, - "source": [ - "We can then create and write a scene like this:" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "d05be093", - "metadata": {}, - "outputs": [], - "source": [ - "scene = NgffScene(\n", - " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", - " coordinate_systems=[coordinate_system],\n", - " coordinate_transformations=coordinate_transformations\n", - ")\n", - "\n", - "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" - ] - }, - { - "cell_type": "markdown", - "id": "cbf307f3", - "metadata": {}, - "source": [ - "## Optional: View with napari" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d024ab0", - "metadata": {}, - "outputs": [], - "source": [ - "viewer = napari.Viewer()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "84573d7e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Root group {'ome': {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}}\n", - "Scene.matches {'scene': {'coordinateTransformations': [{'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0]}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0]}], 'coordinateSystems': [{'name': 'world', 'axes': [{'name': 'y', 'type': 'space'}, {'name': 'x', 'type': 'space'}]}]}, 'version': '0.6'}\n", - "Scene\n", - "spec \n", - "Scene.iter_nodes...final outputs -----> {'world'}\n", - "IMAGES\n", - "---------------------\n", - "img1\n", - "---------------------\n", - "img2\n", - "---------------------\n", - "img3\n", - "---------------------\n", - "img4\n", - "Nodes [, , , ]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img1/s0', 'multiscale_path': 'img1'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img1'}, 'output': {'name': 'world'}, 'translation': [0.0, 0.0], 'input_full_path': 'img1/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img2/s0', 'multiscale_path': 'img2'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img2'}, 'output': {'name': 'world'}, 'translation': [256.0, 0.0], 'input_full_path': 'img2/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img3/s0', 'multiscale_path': 'img3'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img3'}, 'output': {'name': 'world'}, 'translation': [0.0, 256.0], 'input_full_path': 'img3/physical'}]\n", - "node \n", - "\n", - "ALL transforms: [{'type': 'scale', 'input': {'path': 's0'}, 'output': {'name': 'physical'}, 'scale': [1.0, 1.0], 'input_full_path': 'img4/s0', 'multiscale_path': 'img4'}, {'type': 'translation', 'input': {'name': 'physical', 'path': 'img4'}, 'output': {'name': 'world'}, 'translation': [256.0, 256.0], 'input_full_path': 'img4/physical'}]\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "viewer.layers.clear()\n", - "viewer.open(\"test_example_scene.zarr\", plugin=\"napari-ome-zarr\")\n", - "viewer.add_image(example_image, name=\"original image\", blending=\"additive\", colormap=\"orange\")" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "2d7a1333", - "metadata": {}, - "outputs": [], - "source": [ - "import SimpleITK as sitk" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "d96b2264", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[[-80.368095, -21.71694 ],\n", - " [-80.36892 , -21.716684],\n", - " [-80.36974 , -21.716429],\n", - " ...,\n", - " [-81.662254, -21.221333],\n", - " [-81.662895, -21.221027],\n", - " [-81.663536, -21.220722]],\n", - "\n", - " [[-80.36836 , -21.717838],\n", - " [-80.36919 , -21.717583],\n", - " [-80.37002 , -21.717327],\n", - " ...,\n", - " [-81.66253 , -21.22223 ],\n", - " [-81.66317 , -21.221926],\n", - " [-81.6638 , -21.22162 ]],\n", - "\n", - " [[-80.36864 , -21.718739],\n", - " [-80.36946 , -21.718483],\n", - " [-80.370285, -21.718227],\n", - " ...,\n", - " [-81.6628 , -21.22313 ],\n", - " [-81.66344 , -21.222824],\n", - " [-81.66408 , -21.222519]],\n", - "\n", - " ...,\n", - "\n", - " [[-80.57437 , -25.218006],\n", - " [-80.575195, -25.217754],\n", - " [-80.57602 , -25.217505],\n", - " ...,\n", - " [-81.86771 , -24.724539],\n", - " [-81.86834 , -24.72423 ],\n", - " [-81.86898 , -24.723923]],\n", - "\n", - " [[-80.5742 , -25.218779],\n", - " [-80.57503 , -25.218529],\n", - " [-80.57585 , -25.218277],\n", - " ...,\n", - " [-81.86753 , -24.725313],\n", - " [-81.868164, -24.725004],\n", - " [-81.868805, -24.724697]],\n", - "\n", - " [[-80.57403 , -25.219551],\n", - " [-80.57486 , -25.219301],\n", - " [-80.57568 , -25.21905 ],\n", - " ...,\n", - " [-81.867355, -24.726088],\n", - " [-81.868 , -24.725779],\n", - " [-81.86863 , -24.725471]]], shape=(4157, 1768, 2), dtype=float32)" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "image = sitk.ReadImage(r\"c:\\Users\\johan\\Documents\\Globus\\Leap043\\nl-mid-LAICPMSI-a-mip_to_LAICPMSI-a-mosaic_reg_output\\deformationField.mha\")\n", - "\n", - "# get data as numpy array\n", - "sitk.GetArrayFromImage(image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3567a037", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e7d0ba3", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.12.12)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb new file mode 100644 index 00000000..5db63268 --- /dev/null +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8b0be7e9", + "metadata": {}, + "source": [ + "# Create Scenes\n", + "\n", + "(advanced:create-scenes)=\n", + "\n", + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a49ec1c2", + "metadata": {}, + "outputs": [], + "source": [ + "import napari\n", + "from skimage import data\n", + "\n", + "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" + ] + }, + { + "cell_type": "markdown", + "id": "98bbabd7", + "metadata": {}, + "source": [ + "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e3023c95", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "data": { + "text/plain": [ + "(512, 512)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_image = data.human_mitosis()\n", + "example_image.shape" + ] + }, + { + "cell_type": "markdown", + "id": "71e31e2a", + "metadata": {}, + "source": [ + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "017d4c59", + "metadata": {}, + "outputs": [], + "source": [ + "img1 = OMEZarrImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", + "img2 = OMEZarrImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", + "img3 = OMEZarrImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", + "img4 = OMEZarrImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", + "\n", + "img1_ms = OMEZarrMultiscale(img1)\n", + "img2_ms = OMEZarrMultiscale(img2)\n", + "img3_ms = OMEZarrMultiscale(img3)\n", + "img4_ms = OMEZarrMultiscale(img4)" + ] + }, + { + "cell_type": "markdown", + "id": "cf59df32", + "metadata": {}, + "source": [ + "Next, we need to define a coordinate system into which all images are projected.\n", + "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "994674c2", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_system = {\n", + " \"name\": \"world\",\n", + " \"axes\": [\n", + " {\"name\": \"y\", \"type\": \"space\"},\n", + " {\"name\": \"x\", \"type\": \"space\"},\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c1d629be", + "metadata": {}, + "source": [ + "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", + "In thisexamples, these are simple translations in the y and x dimensions:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d6329caa", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_transformations = [\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 0],\n", + " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 0],\n", + " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 256],\n", + " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 256],\n", + " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4444a8d3", + "metadata": {}, + "source": [ + "We can then create and write a scene like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d05be093", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Writing images: 100%|██████████| 4/4 [00:00<00:00, 12.34it/s]\n" + ] + } + ], + "source": [ + "scene = OMEZarrScene(\n", + " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", + " coordinate_systems=[coordinate_system],\n", + " coordinate_transformations=coordinate_transformations\n", + ")\n", + "\n", + "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/advanced/transforms/reading_scenes.ipynb b/docs/source/advanced/transforms/reading_scenes.ipynb new file mode 100644 index 00000000..3fe79729 --- /dev/null +++ b/docs/source/advanced/transforms/reading_scenes.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28740944", + "metadata": {}, + "source": [ + "# Reading scenes" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3222cfcb", + "metadata": {}, + "outputs": [], + "source": [ + "from ome_zarr import OMEZarrScene\n", + "import networkx as nx" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a9bebd85", + "metadata": {}, + "outputs": [], + "source": [ + "scene = OMEZarrScene.from_ome_zarr(\"https://radosgw.public.os.wwu.de/s2v/P2A_B6_M2.ome.zarr\")" + ] + }, + { + "cell_type": "markdown", + "id": "7e3c003e", + "metadata": {}, + "source": [ + "This step requires matplotlib" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9277576", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApQAAAHzCAYAAACe1o1DAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnQW4FNX7x8+9dEtjgKggKmIjtmInCgaoiP5sBVtRMcAOVAxE7P7bha0oIgoiNmIQEioK0h0X9v98Dr7Xc+fO7s7s7mzc+36fZ72yOzs7c+bE93zfKorFYjGjUCgUCoVCoVCkiOJUv6hQKBQKhUKhUCihVCgUCoVCoVCkDVUoFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKSFqul9XaFQVHYsXVlips1dalaVrDXVqxab1o3rmDo1dGpRKBSKygSd9RUKRWhMmrXYPDt2hhnx62wzY94yE3M+KzLGtGpU23Ru18yc2KmVadu8nrawQqFQVHAUxWIxdy1QKBSKuPh93jLT77XxZtTkOaZKcZFZszb+9CGf79mmibm5awfTslFtbVmFQqGooFBCqVAoAuH5cTNM/2ETTMnaWEIi6UcsqxYXmeu6tDc9OrbS1lYoFIoKCA3KUSgKGA8//LDZYostzLJlyyL9ncEjJpkrXh1vVpasDUUmAcfzPb7PeSpK+7z66qtml112MStWrDD5invuucfe/5o1a0y+4oEHHjAHH3ywWbt2bYVqg8cff9z+7uLFiwvy/AqFqeyE8oUXXrCDjNeXX37pe8zpp59uP991113jfpfX1ltvbfbZZx9z7bXXmnnz5sX9rTFjxgS6tj///LPM+RO93nzzTfudWbNmmSFDhph9993Xvv/UU0+ZdMEENHjwYNO1a1ez0047mY4dO9r/v+qqq8xPP/2UkTaJ93r22WdDX++MGTPMpZdeag444ACzww47mKOOOsrccccdZs6cOUmvtXPnzua6664zCxYsCP27PNezzz7b7L777rav9OzZ0wwfPjzpPW+77bZm7733NmeeeaZ57rnnIiUcc+fONb/++mtki7Eok3d8MDHhMWuWzjeLv3nb/P1//cyfD51tloz/yPc4zvPge19nvE9nu314ppdccok59NBDTc2aNe17zz//vL2fkSNHljv+jDPOsJ+9/vrr5T7r27ev2WqrrcyiRYtMpvHPP//Y+8+UZ9Ps2bMtAdxvv/3s/UBqEmHmzJnm8ssvN3vssYfp1KmTueiii8xff/1V5pju3bvbsZbsXPnSBmH7XlRENurzKxSmsgflzJ8/3w6yatWqmccee8zsvPPOZT6HoD355JOmqKjI1K9f3/e7fA8CsXLlSktKr7jiCkuEvv322zLfkeOXLl0a6NqaNWtWbkE59dRTzffff2++/vrrMu9vsMEGZvz48XbnfuSRR9qF67LLLvMlcWHw2WefmWOOOcY0adLEnHfeeWb77be3bQWRfPTRR83NN99sF0Ym+XTaRI73okWLFqGu9+OPPzaHHXaYJb6Qyo033theK9d59dVX27avUqWK72+z6I8ePdpceeWV5v/+7/9sG9etWzfQ7z7xxBOWDNIOPCPOxf1CavndG264Ie49l5SU2EWM3+aZ0Vb8PsQ004C0QrDr1KljovKZxMydCKtmTzOzX7zW1Gq7i6m12U5mwYjHzNoVS+Ie2/u4k80Jx3TLWJ/ORfswh0CWevfuXfpehw4dbD9477337IZCwIL/4osvWpX0rbfestfjgs8YQ975KN/AuNt///3tfHT44Yebiy++2JKaeIAkHnLIIfbY66+/3jRu3Nh8/vnn5oQTTjAjRowoPa5Ro0bm5JNPtmPqf//7nykurhg6B/MG957vz1WhyBQqHKEUMJGhHN19992lCgJ4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SeaKV8eblSVrkxJJLzie7/F9zlOR2mfJkiVmxYoVJl+xdu1ae/8UFyhUKKFUKBSRo127dqa4uLhcCUYm0ESLiEyyvFavXu37XV4LFy40JSUlCa+B45YtWxb4mpcuXVrmN+K9WKi8WLRokVm8eLHJ9IK7fPnyhMeEbRNUYyrj9OjRw57b/X66159sYQx7rUGRjJB47zFbXl/kYSWK/oEHHojsN1AW7/hgYuDj165cZl9+4DwvOEplFH06W+3zxx9/mObNm5sRI0aUIa9+42nNmjVx5wqZj6IgpjNmzLD3z3jMNLjeoP08zBzphRJKhUKRk0hvCAQTKK/jjjvO93uPP/546TFvv/12ue82a9bMtG7d2rRq1crWDYe43n333eUmTzn+1FNPDXzNJ554oj23vDbccEN7Dv6670PGwM8//2z69+9vttxyS7PeeuuZXXfd1aQLFvEbbrjBbLvttvb+GjdubOrXr2+23nprc8UVV5hff/01rTbZYIMNLJl78skny9wTr0MPPTT09U6bNs2cfvrpZv311zd169a119qxY0dz77332t9Jdq0o2ffff3/o333//ffNkUceaZVWfpN2oha5dwPj/d2WLVva391oo43MUUcdZV5++eXICCYbKmqkMxai8pnsP6zs/fph9ZzfzYJRz5o/Hz7b/D6ou/n76UvjHnv5Y++aC/v2y2ifzkX7XHnllXYMHXLIIaX9gHHctWvXcsf+3//9n+0je+21l28/i4r0RYGhQ4eaTTfd1D47Sqkyz/r5sc+bN8+cdtpptv3lRYAe5DkMlFAqFIqcRnpDAN555x1b8s8LUtnweTxgphWFi9eBBx5oLrroIjNw4MC0r/f1118vo2YRVAT4677/1ltv2fchfiyKr732ms29mS6mTJlitt9+e/P000+ba6+91vqfoh6wIFx99dU2MptFIN02gVxBXL3K66hRo0Jd799//2123nlnM2bMGNt2qD9USOIaCcp67rnnEl4rixpVevr06WPuvPPOwL+LosTCyfd+++03q8agREG2IUAoP/F+V0yMH3/8senQoYPdRBx00EFJleBU0KhRI/t755xzjokCBOCUBDBxLxj9vDFFRaZZt6tM1UYbJjx2zqfPmRG//pOxPp2L9uH50/d69+5d+l61atXMHnvsYT777LNylg/6AoTq22+/LefzzWdg3333NfmO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CoUi81BCqcgb/Pbbb7Hbb789tvPOO9sFoEaNGrEjjzwy9vTTT8cWLFgQq0yAzNEGufbnwt8VUnnSSSflJal89913bTtNnTo1st/o169fbL311rNKIORtr732iuUDuGf8/jC/f/rpp7m+nLwAavpbb70V69atm3Xb4HXsscfafpKP/VehqEhQQqnIS0ybNs06yOMjB2FAgTniiCNsIMT8+fNjFR2iio0aNSrXl2KVr+Li4tgpp5wSqRKYCmbMmGHbCRIRBVCIN91009hpp50W++WXX+xvoZ7nGuPGjYs1b948tskmm9jrquzAjebKK6+0wWw8IwIB77nnntg///yT60tTKCoNlFAq8h7Tp0+3pqvddtvNLhaoDocddljs8ccftya/iho8UFRUFHvsscdi+QBIFKQS3858IpUQvrp161plOwqMHTvW9rkPP/wwdvHFF8caN24cW7FiRSyXIGgKMz9K/qxZs2KVFUSxMwfsueee9hmhIp977rmxr776Sv1IFYocIPryEgpFmqA6yUUXXWRz/v3+++9m4MCBZuHChTZPXPPmzW2VF3LIkZS4ooB8neRYpMRlPuCEE04wTzzxhK0ccu6559pcffkAyt5ttdVWNudpFHj++edtH9t1113t/Z9yyik5TfpOLXHKnVKNasSIEaZZs2amMgERZPTo0bbsJvk1STjO8yAZOSUw77//frPjjjtqOUSFIgfQxOaKggULCFVdXn75ZTNq1ChbFYRKLyR1Puqoo0yTJk1MIWP//fc3DRs2tEnG8wWQKoj8OeecY+tY50MdY67nxx9/NF9++WXGE16zmenWrZvZZZddTM+ePW2Z0c0339xkGxD4yy+/3Nxxxx3m/PPPt1V76O+VBX///bd5+umn7cbxl19+MRtvvLElk1Sxad26da4vT6FQgFzIogpFpjFz5szY4MGDbdAEplkCSQ444IDYgw8+aKM9CxGkOdl2221j+YaHH37YmhjPP//8vDAtDhw40OaIzLQp/pNPPrH3+fnnn1uzaufOnWO5wPLly21gCS4Qd999d6yygMT6r7/+eqxLly52PBOkR8aB4cOH55XbhUKhWAcllIoKByKjhwwZYpMXC7ncb7/9Yg888EBB+ZxJSpx8IG1eDB061JKtiy66KOfXR3UfroVArkwT+latWpUGSJGEP9uYM2dObPfdd7fptIj8rwwgJdOll14aa9asmW138ozef//9FdZfWqGoKFCTt6JCY/bs2bY+L2ZxfM7YRO29997WLI4ps0WLFiZfQY1qamtTs3rDDTc0+YYhQ4aY3r17m0svvdTcfvvtOTN/T58+3Zo93377betPmwmsXr3a+uiddtppZsWKFdaXEv/dbNY3nzJliq0lPX/+fNsXMLtXVCxatMi88MIL1qT9xRdfmMaNG1sXA8za2267ba4vT6FQBIAG5SgqNAhaOOuss8yHH35o/bAefPBBU61aNeuHtsEGG1hyiS/gX3/9ZfINbdu2tX/zJTDHC4Jz7r33XuvX169fP0vWcwH8HOvWrWt++umnjJ3zo48+MnPnzrW+uE899ZQlNtkkk5AqIZDu/1ck0F8+/fRTG+gEeT/77LNLfYb//PNPc/fddyuZVCgKCEooFZUGBOkQHfr++++bWbNmmUceecTUrl3bRpCjAO65556WILGY5QM23XRTU1xcnLeEEpx33nk2QOTWW28111xzTU5IJcrolltumdFI7+eee860a9fOBuEsWLDAnHHGGSZbePXVV03nzp3t748ZM8ZsttlmpiKB8XXzzTfb4CY2dATUsSFBaX7nnXes9SCXkfQKhSJF5NrmrlDkGnPnzrX57A499FCb45JhQc7LQYMG2cTZuQRJtS+77LJYvoPAGNptwIABOfl9kq6TlzFTQTCUvezfv39s1113tcFd2QJ9juCb4447zl5HRQG5O6m6dMghh1i/5lq1asV69eplA580wEahqBhQQqlQOMDxn2o8hx9+uK3OA0miWg8BMiRYzzYOOuig2FFHHVUQz+iWW26x7XXDDTdk/bdJbE6C80wECL3yyiv2Pt544w379+WXX45FDcoCEjXP7/Xt27fCkKwffvghduGFF9qE8DKWHnroodjChQtzfWkKhSLDUEKpUMQB9cOpI07aEiGXqGCocVHWjnbRp0+fWPv27QvmGd144422nW6++eas/u7bb79tfzcTpJ8UPdttt12sd+/esRYtWtj0NVFi6dKltmY9yh3ZCQodlEblPnbaaSf7TIjWJmp7woQJub40hUIRIZRQKhQBgKJC+UHUQvLhsVCyYN52222xKVOmRNaG1CMmZUwhKVaYvWmfqMoh+gGCz2++++67aZ1n0aJF1hx7/fXXx+rXrx+76qqrYlGCNFZsUsijGVU98myA/kl+SPJE0l9J1XXEEUfEXnvttcgJuUKhyA9oUI5CEQD169e35QdJQfTPP//YoA2ii/v372+DJij3RmAKqV4yHelN2hpSBxUKrr32WnP11Vebvn37mkGDBmXlN3kWderUSTswZ9iwYWb58uU2onvx4sWRBuMQ8EP09owZM8zIkSPNYYcdZgoNBNJcd911NoCMyk5ff/21/TcplmhLouTJqqBQKCo+NA+lQpEGlixZYiNTSXVCHkTIyHbbbWeOPfZY+5LUP6li8uTJ9hykPWLBLhRg/bjqqqvMLbfcYu655x6bpilqdOzY0WyzzTa23niqOOKII2y6oJKSEpsVgGcbBYhspiY36XL4DUoJFgrY4Lz++us2Z+Tw4cMtke/evbstgUnN83wox6lQKLIPVSgVijRA/sPjjjvOEkqUyxdffNGmQ7npppvsX5Iy33jjjVaNSgUk7K5atWpepw7yA6SCNrjsssvMBRdcYO6///7If7N9+/ZpKZTz5s2zKaX22GMPM27cOJu/NAqQJJ3NARuPzz//vCDIJBuEb775xvTp08eS4OOPP95uniDv5HAlBdduu+2mZFKhqMRQQqlQZAgoNaiSVPyAXFKdZ6uttrKm8C222MJ06NDBXH/99ebnn38OfE7I5CabbFJwhFJI5W233WbzfEJEhg4dGunv0dYkN081Fyb5H9esWWMT4JOXNNMmaK6L9oCMoei99957Zr311jP5DNRacrNuv/321q2DNiIBORskVFYSvrOpUigUCg3KUSgixrJly2wdZgIWSG0Dt9hqq61snsMff/wx6ffJj0kao0IFqXwkJc7DDz8c2e+8+eab9jdSzR1K7fe9997bPiOeTSaxevXq2FlnnWWv75prrsl5/fNkKYzee+89G+1OdoOqVavGunXrZoOGuA+FQqHwgxJKhSKLIFn166+/HuvZs6eNIoZgbLnllpZkkLPPj2iQx69du3YF/Zy4L9LwkLT7sccei+Q3fvvtN9uekKGwmDlzpr22k046yabv+f333zN2XUSOk9AbYvboo4/G8hWTJ0+OXX311bGNNtrItiPpqu666y4bia5QKBTJoIRSochh9ZBhw4bZiiENGjSwizjEkVQ13333XSm5vP/++20Fn0JXh7ifs88+2xI3ksdHkbqmdu3aNgl9KumZIHzbbLONTXeTKfz555+x7bff3lbeef/992P5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YqlVvnCnrfx2uhOP5et0tk4HXWwVXXu7z1dVzrH5D19kHfYZbm233qfOd5dPf8f22ekGt9377befTZ8+3U4//XR7++23nZhE4O60005266232m9+8xv3nSeeeCIyxnHixIl24YUXuuODZZgYV9z/N910k7PiYvk944wz6oQkkOl/1lln2ciRI5tMln5TRoJSCCGE+AUEI5a51q1bO2smL/BWRUQSFjwvMhFZWDwpg/Tmm286V7svh4RVNCgyWQYQbJdddpnts88+ttN1z9vkB66y+Y9fad1O/n9Wvk6XOudi8fiHbJ0dj7SK9TdzVsr5j1zqBOb6J93hRFqLDTe3Zl02sZ8/fLGeoFwy+Xlrvk4n23vvvd3/jzvuOLdviL111lnH7cvYsWPtvffeqxWUUbz//vu222672V577eX2vXPnzk5UY/GdNm2aCxHg87Db/9lnn7WjjjrKlYRCWIriRoJSCCGE+IXzzjsvMinn3XffdQkruHCxYPLCunfjjTe6GEpviURoRZVDwho5deraxBuEJxbDVu3aW+ez/tc67neuzb37FPtx0rPWYY9T6/xui55bOTEJZS3bWdvtDrJFL95pqypnW/NOa93pbQfsZ4v+7y5bMfcLq9hgrTBctXC2rZg1xdYZfKQtr15jrcvMbcPZZ5/txCSwL4jERFx66aXuO6NHj3YJTsAx+v3vf19vWWJRqT+KW53knoEDB9qTTz4pQdkEkKAUQgghfmHMmDG1VsmGgNDq3bu3e4W55ZZbnMv7X//6l3OTv/flPBtdWWLlbTtZsw4b2oq5n9f7TovudbelWftu7u/qJQvMfhGUrfvuZj+Me8B++vCFWkHJv2nQ2LrfHvZNZZX17baO7b777i6bHdF7wAEH2ODBg2sFYiywYr7xxht28MEHx12W2MoRI0bYY4895qygxGxi0UVcYtEUxY+iZYUQQogcQmY62eTDjjjq18G4RRtbs/LX2pq177eqW7S9pFmF+1uzavmvyzRrYW367W5Lv3jTucVrVi63nz99zSp69LNm7de3ldU1bjnE3uWXX15rISX2cf/993exlbHAnY8ADSb1RIFLG6H88ssv2w8//GBffvmlc4HvuuuutRn0oriRoBRCCCFyCHGW0Lz81yF49U8LraxN+wavs+2A/W1N9Sr7+ZOxVvXZ67ZmRZW12WrPOr+DhfFPf/qTTZ482Yk+XNgk0xx22GEx10ssafv27Z3bPh7PPfecHXLIIc7qGQRhKZoGEpRCCCFEhsE6RyxlFI8++qj7u1HH1kbuM67u6sXfWcteAxv8e806bGAtNtrKJef8NPkFK61oba02G+zWz+/gug5aCslqP/zww50rm6xzn0gUBcuRvBNlyVy1alXt+igdFOTVV1+VoGxCKIZSCCFEkwArW7B2oidYHifWMsQBduzYMenfoiQR7t7XX3+93meIuyuuuMJ17mkx8x2b/dzfXKY2VsZ04PvfP3W9+3ebAftbabMK69GxlbWuKHcWSRJkyDDfbrvtnAv7o48+cm7qo48+Om5Zn7/85S/21ltvuUzua6+91rbeemuXsX7ffffZ8OHDXWLPCSec4BKU7rjjDpeshBWUmp641kkGEsWPBKUQQoiiBvFExjEJMVGQhZxomfPPP7+2zmIQip2vWLGi3vusK1jUPMiVV15pjzzyiF1wwQW2ZP5ia7PlEGu3/eFWUv5rdnlZy7ZW3mFDKyktq/NdluH90or6LRVb9v6tlbXt5NznbfrvaWWlJTZks7VliHBbjxs3zu6++24nEKlBSaY6iUKIwXitF4m1JMv9f/7nf9x2k9hD0hE1LX2WODU8Ed1PP/20jRo1ygYNGuTc4NSpJOM73vpFcVCyJp6dWwghhBAZwWd506XGJ7l8Of8n2/P2NzKyfmIo5/z1OCtbZz3r9vs73XtjR+xivbu0zcj6hYiHYiiFEEKIRmLT9drazr07OWtiuiz7erLVrKhyBc5ZH+uVmBS5QoJSCCGEaERGHtLPytMQlNU/VdqKedNs8RujnHWyTf893PpYrxC5QoJSCCGEyAEk9RA/GE766d6hlV0ztG+D1/vje09b5Qt3WPm6Xa3L4Ve5OMtrh/Z16xUiVyiGUgghhMgD7hr3pd3ycuwi48ly0V597Owh9Tv1CJFNJCiFEEKIPOGxSbPsqjFTrbpmja2uST5nlphJ3NxYJo8cGJ1dLkQ2kaAUQggh8ojZi5baZU9PsQlfLXRCMZ6w9J+TgEPMpNzcorGQoBRCCCHyEEoKjZ44y8ZNX2CzKpdaUFaSwkPRcupMHrd9D2Vzi0ZHglIIIYTIc6pWVNs3lVW2srrG9eamnSIdcITIFyQohRBCCCFEWqhskBBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQoi0kKAUQgghhBBpIUEphBBCCCHSQoJSCCGEEEKkhQSlEEIIIYRICwlKIYQQQgiRFhKUQgghhBAiLSQohRBCCCFEWkhQCiGEEEKItJCgFEIIIYQQaSFBKYQQQggh0kKCUgghhBBCpIUEpRBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQghLh/8PRKDerF9KyvcAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "nx.draw(scene._graph.graph, with_labels=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2766d6c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 6afd3dfc83041204615ddf7eadad586b4e1ca5e9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:53:40 +0200 Subject: [PATCH 26/95] feat: better handle displacements and other transforms --- ome_zarr/classes/scene.py | 154 +++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 52 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index b59a215b..1652d70f 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -24,6 +24,7 @@ def __init__( coordinate_systems: ( Sequence[CoordinateSystem] | Sequence[dict[str, Any]] | None ) = None, + coordinates_displacements: dict[str, OMEZarrMultiscale] | None = None, ): """ Parameters @@ -44,6 +45,7 @@ def __init__( self.coordinate_transformations = self._parse_transforms( coordinate_transformations ) + self.coordinates_displacements = coordinates_displacements self.metadata = SceneAttrs( coordinateSystems=self.coordinate_systems, @@ -93,10 +95,10 @@ def _build_graph(self): # Add scene-level transformations (empty context = root level) for tf in self.coordinate_transformations: if tf.type == "sequence": - tnd_transform = _ozmp_tf_to_tnd(tf).simplify() + tnd_transform = self._ozmp_tf_to_tnd(tf).simplify() else: - tnd_transform = [_ozmp_tf_to_tnd(tf)] - self._graph.add_transforms(tnd_transform) + tnd_transform = self._ozmp_tf_to_tnd(tf) + self._graph.add_transform(tnd_transform) # check if input/output are defined subgroups = [] @@ -112,8 +114,8 @@ def _build_graph(self): continue if img.metadata.coordinateTransformations: for img_tf in img.metadata.coordinateTransformations: - ind_transform = _ozmp_tf_to_tnd(img_tf, zarr_context=subgroup) - self._graph.add_transforms([ind_transform]) + ind_transform = self._ozmp_tf_to_tnd(img_tf, zarr_context=subgroup) + self._graph.add_transform(ind_transform) def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ @@ -152,6 +154,16 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): subgroup = zarr_group.create_group(img_path, overwrite=not overwrite) img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") self._written_image_names.add(img_path) + + for disp_path, disp_img in (self.coordinates_displacements or {}).items(): + # Skip if already written (incremental mode) + if not overwrite and disp_path in self._written_image_names: + continue + + # Write the displacement image + subgroup = zarr_group.create_group(disp_path, overwrite=not overwrite) + disp_img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") + self._written_image_names.add(disp_path) # Always update scene metadata metadata_dict = self.metadata.model_dump() @@ -284,51 +296,89 @@ def _parse_coordinate_systems( return tuple(parsed_coordinate_systems) -def _ozmp_tf_to_tnd( - transform: AnyTransform, zarr_context: str = "" -) -> tnd.base.Transform: - """ - Convert an OME-Zarr coordinate transformation to a transformnd Transform object. - This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. - """ - tnd_transform = None - # Example for an affine transformation (this will depend on the actual structure of AnyTransform) - if transform.type == "affine": - tnd_transform = tnd.transforms.Affine(transform.affine) - elif transform.type == "mapAxis": - tnd_transform = tnd.transforms.MapAxis(list(transform.mapAxis)) - elif transform.type == "scale": - tnd_transform = tnd.transforms.Scale(transform.scale) - elif transform.type == "translation": - tnd_transform = tnd.transforms.Translate(transform.translation) - elif transform.type == "rotation": - tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) - elif transform.type == "sequence": - sub_transformations = transform.transformations - tnd_sub_transforms = [ - _ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations - ] - tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) - - if transform.input is not None and tnd_transform is not None: - input_path = transform.input.path if transform.input.path is not None else "" - output_path = transform.output.path if transform.output.path is not None else "" - - # zarr_context prepends path with relative path from root - # to keep track of global location of coordinate systems in the zarr store - if zarr_context != "" and input_path != "": - input_path = f"{zarr_context}/{input_path}" - elif zarr_context != "": - input_path = zarr_context - - if zarr_context != "" and output_path != "": - output_path = f"{zarr_context}/{output_path}" - elif zarr_context != "": - output_path = zarr_context - - tnd_transform.spaces = tnd.Spaces( - f"{input_path}:{transform.input.name}", - f"{output_path}:{transform.output.name}", - ) + def _ozmp_tf_to_tnd( + self, + transform: AnyTransform, + zarr_context: str = "", + ) -> tnd.base.Transform: + """ + Convert an OME-Zarr coordinate transformation to a transformnd Transform object. + This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. + """ + import numpy as np + tnd_transform = None + # Example for an affine transformation (this will depend on the actual structure of AnyTransform) + if transform.type == "affine":# + aff = np.asarray(transform.affine) + if aff.shape[0] == aff.shape[1]: + tnd_transform = tnd.transforms.Affine(transform.affine) + else: + aff = np.eye(max(aff.shape)) + aff[:aff.shape[0], :aff.shape[1]] = aff + tnd_transform = tnd.transforms.Affine(aff) + + elif transform.type == "displacements": + path_to_dfield = transform.path if transform.path is not None else "" + if zarr_context != "" and path_to_dfield != "": + path_to_dfield = f"{zarr_context}/{path_to_dfield}" + + dfield = self.coordinates_displacements.get(path_to_dfield) + tnd_transform = tnd.transforms.Displacements( + dfield.images[0].data, + index_transform = tnd.transforms.Scale(list(dfield.images[0].scale.values())[1:]), + vector_axis=0, + ) + elif transform.type == "mapAxis": + tnd_transform = tnd.transforms.MapAxis(list(transform.mapAxis)) + + elif transform.type == "scale": + tnd_transform = tnd.transforms.Scale(transform.scale) + + elif transform.type == "translation": + tnd_transform = tnd.transforms.Translate(transform.translation) + + elif transform.type == "rotation": + tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) + + elif transform.type == "byDimension": + sub_transformations = transform.transformations + tnd_sub_transforms = [ + tnd.transforms.by_dimension.SubTransform( + transform=self._ozmp_tf_to_tnd(sub_tf.transformation), + input_axes=sub_tf.input_axes, + output_axes=sub_tf.output_axes + ) for sub_tf in sub_transformations + ] + tnd_transform = tnd.transforms.ByDimension( + subtransforms=tnd_sub_transforms, + fill_identity=0 + ) + elif transform.type == "sequence": + sub_transformations = transform.transformations + tnd_sub_transforms = [ + self._ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations + ] + tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) + + if transform.input is not None and tnd_transform is not None: + input_path = transform.input.path if transform.input.path is not None else "" + output_path = transform.output.path if transform.output.path is not None else "" + + # zarr_context prepends path with relative path from root + # to keep track of global location of coordinate systems in the zarr store + if zarr_context != "" and input_path != "": + input_path = f"{zarr_context}/{input_path}" + elif zarr_context != "": + input_path = zarr_context + + if zarr_context != "" and output_path != "": + output_path = f"{zarr_context}/{output_path}" + elif zarr_context != "": + output_path = zarr_context + + tnd_transform.spaces = tnd.Spaces( + f"{input_path}:{transform.input.name}", + f"{output_path}:{transform.output.name}", + ) - return tnd_transform + return tnd_transform From 711efece7835a02e90248008a35f8e8a2789d9d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:53:56 +0000 Subject: [PATCH 27/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../advanced/transforms/create_scenes.ipynb | 411 +++++++++--------- .../advanced/transforms/reading_scenes.ipynb | 5 +- ome_zarr/classes/scene.py | 47 +- 3 files changed, 236 insertions(+), 227 deletions(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index 5db63268..f8edd1f5 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -1,215 +1,214 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "8b0be7e9", - "metadata": {}, - "source": [ - "# Create Scenes\n", - "\n", - "(advanced:create-scenes)=\n", - "\n", - "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "a49ec1c2", - "metadata": {}, - "outputs": [], - "source": [ - "import napari\n", - "from skimage import data\n", - "\n", - "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" - ] - }, - { - "cell_type": "markdown", - "id": "98bbabd7", - "metadata": {}, - "source": [ - "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e3023c95", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - }, - { - "data": { - "text/plain": [ - "(512, 512)" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "example_image = data.human_mitosis()\n", - "example_image.shape" - ] - }, - { - "cell_type": "markdown", - "id": "71e31e2a", - "metadata": {}, - "source": [ - "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "017d4c59", - "metadata": {}, - "outputs": [], - "source": [ - "img1 = OMEZarrImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", - "img2 = OMEZarrImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", - "img3 = OMEZarrImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", - "img4 = OMEZarrImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", - "\n", - "img1_ms = OMEZarrMultiscale(img1)\n", - "img2_ms = OMEZarrMultiscale(img2)\n", - "img3_ms = OMEZarrMultiscale(img3)\n", - "img4_ms = OMEZarrMultiscale(img4)" - ] - }, - { - "cell_type": "markdown", - "id": "cf59df32", - "metadata": {}, - "source": [ - "Next, we need to define a coordinate system into which all images are projected.\n", - "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "994674c2", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_system = {\n", - " \"name\": \"world\",\n", - " \"axes\": [\n", - " {\"name\": \"y\", \"type\": \"space\"},\n", - " {\"name\": \"x\", \"type\": \"space\"},\n", - " ],\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "c1d629be", - "metadata": {}, - "source": [ - "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", - "In thisexamples, these are simple translations in the y and x dimensions:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d6329caa", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_transformations = [\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 0],\n", - " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 0],\n", - " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 256],\n", - " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 256],\n", - " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - "]" - ] - }, + "cells": [ + { + "cell_type": "markdown", + "id": "8b0be7e9", + "metadata": {}, + "source": [ + "# Create Scenes\n", + "\n", + "(advanced:create-scenes)=\n", + "\n", + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a49ec1c2", + "metadata": {}, + "outputs": [], + "source": [ + "from skimage import data\n", + "\n", + "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" + ] + }, + { + "cell_type": "markdown", + "id": "98bbabd7", + "metadata": {}, + "source": [ + "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e3023c95", + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "id": "4444a8d3", - "metadata": {}, - "source": [ - "We can then create and write a scene like this:" - ] + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] }, { - "cell_type": "code", - "execution_count": 6, - "id": "d05be093", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Writing images: 100%|██████████| 4/4 [00:00<00:00, 12.34it/s]\n" - ] - } - ], - "source": [ - "scene = OMEZarrScene(\n", - " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", - " coordinate_systems=[coordinate_system],\n", - " coordinate_transformations=coordinate_transformations\n", - ")\n", - "\n", - "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + "data": { + "text/plain": [ + "(512, 512)" ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.13.12)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.12" + ], + "source": [ + "example_image = data.human_mitosis()\n", + "example_image.shape" + ] + }, + { + "cell_type": "markdown", + "id": "71e31e2a", + "metadata": {}, + "source": [ + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "017d4c59", + "metadata": {}, + "outputs": [], + "source": [ + "img1 = OMEZarrImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", + "img2 = OMEZarrImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", + "img3 = OMEZarrImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", + "img4 = OMEZarrImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", + "\n", + "img1_ms = OMEZarrMultiscale(img1)\n", + "img2_ms = OMEZarrMultiscale(img2)\n", + "img3_ms = OMEZarrMultiscale(img3)\n", + "img4_ms = OMEZarrMultiscale(img4)" + ] + }, + { + "cell_type": "markdown", + "id": "cf59df32", + "metadata": {}, + "source": [ + "Next, we need to define a coordinate system into which all images are projected.\n", + "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "994674c2", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_system = {\n", + " \"name\": \"world\",\n", + " \"axes\": [\n", + " {\"name\": \"y\", \"type\": \"space\"},\n", + " {\"name\": \"x\", \"type\": \"space\"},\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c1d629be", + "metadata": {}, + "source": [ + "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", + "In thisexamples, these are simple translations in the y and x dimensions:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d6329caa", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_transformations = [\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 0],\n", + " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 0],\n", + " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 256],\n", + " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 256],\n", + " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "4444a8d3", + "metadata": {}, + "source": [ + "We can then create and write a scene like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d05be093", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Writing images: 100%|██████████| 4/4 [00:00<00:00, 12.34it/s]\n" + ] } + ], + "source": [ + "scene = OMEZarrScene(\n", + " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", + " coordinate_systems=[coordinate_system],\n", + " coordinate_transformations=coordinate_transformations\n", + ")\n", + "\n", + "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 5 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/docs/source/advanced/transforms/reading_scenes.ipynb b/docs/source/advanced/transforms/reading_scenes.ipynb index 3fe79729..fd15be54 100644 --- a/docs/source/advanced/transforms/reading_scenes.ipynb +++ b/docs/source/advanced/transforms/reading_scenes.ipynb @@ -15,8 +15,9 @@ "metadata": {}, "outputs": [], "source": [ - "from ome_zarr import OMEZarrScene\n", - "import networkx as nx" + "import networkx as nx\n", + "\n", + "from ome_zarr import OMEZarrScene" ] }, { diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 1652d70f..f586ee7d 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -114,7 +114,9 @@ def _build_graph(self): continue if img.metadata.coordinateTransformations: for img_tf in img.metadata.coordinateTransformations: - ind_transform = self._ozmp_tf_to_tnd(img_tf, zarr_context=subgroup) + ind_transform = self._ozmp_tf_to_tnd( + img_tf, zarr_context=subgroup + ) self._graph.add_transform(ind_transform) def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): @@ -154,7 +156,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): subgroup = zarr_group.create_group(img_path, overwrite=not overwrite) img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") self._written_image_names.add(img_path) - + for disp_path, disp_img in (self.coordinates_displacements or {}).items(): # Skip if already written (incremental mode) if not overwrite and disp_path in self._written_image_names: @@ -295,39 +297,41 @@ def _parse_coordinate_systems( return tuple(parsed_coordinate_systems) - def _ozmp_tf_to_tnd( - self, - transform: AnyTransform, - zarr_context: str = "", + self, + transform: AnyTransform, + zarr_context: str = "", ) -> tnd.base.Transform: """ Convert an OME-Zarr coordinate transformation to a transformnd Transform object. This is a placeholder function and will need to be implemented based on the specific types of transformations you expect to encounter in OME-Zarr metadata. """ import numpy as np + tnd_transform = None # Example for an affine transformation (this will depend on the actual structure of AnyTransform) - if transform.type == "affine":# + if transform.type == "affine": aff = np.asarray(transform.affine) if aff.shape[0] == aff.shape[1]: tnd_transform = tnd.transforms.Affine(transform.affine) else: aff = np.eye(max(aff.shape)) - aff[:aff.shape[0], :aff.shape[1]] = aff + aff[: aff.shape[0], : aff.shape[1]] = aff tnd_transform = tnd.transforms.Affine(aff) elif transform.type == "displacements": path_to_dfield = transform.path if transform.path is not None else "" if zarr_context != "" and path_to_dfield != "": path_to_dfield = f"{zarr_context}/{path_to_dfield}" - + dfield = self.coordinates_displacements.get(path_to_dfield) tnd_transform = tnd.transforms.Displacements( dfield.images[0].data, - index_transform = tnd.transforms.Scale(list(dfield.images[0].scale.values())[1:]), + index_transform=tnd.transforms.Scale( + list(dfield.images[0].scale.values())[1:] + ), vector_axis=0, - ) + ) elif transform.type == "mapAxis": tnd_transform = tnd.transforms.MapAxis(list(transform.mapAxis)) @@ -346,23 +350,28 @@ def _ozmp_tf_to_tnd( tnd.transforms.by_dimension.SubTransform( transform=self._ozmp_tf_to_tnd(sub_tf.transformation), input_axes=sub_tf.input_axes, - output_axes=sub_tf.output_axes - ) for sub_tf in sub_transformations + output_axes=sub_tf.output_axes, + ) + for sub_tf in sub_transformations ] tnd_transform = tnd.transforms.ByDimension( - subtransforms=tnd_sub_transforms, - fill_identity=0 - ) + subtransforms=tnd_sub_transforms, fill_identity=0 + ) elif transform.type == "sequence": sub_transformations = transform.transformations tnd_sub_transforms = [ - self._ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations + self._ozmp_tf_to_tnd(sub_tf, zarr_context) + for sub_tf in sub_transformations ] tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) if transform.input is not None and tnd_transform is not None: - input_path = transform.input.path if transform.input.path is not None else "" - output_path = transform.output.path if transform.output.path is not None else "" + input_path = ( + transform.input.path if transform.input.path is not None else "" + ) + output_path = ( + transform.output.path if transform.output.path is not None else "" + ) # zarr_context prepends path with relative path from root # to keep track of global location of coordinate systems in the zarr store From 940bf8cab381f08c7946bfe8ac06a99f70e3e121 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:07:10 +0000 Subject: [PATCH 28/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../advanced/transforms/create_scenes.ipynb | 410 +++++++++--------- .../advanced/transforms/reading_scenes.ipynb | 172 ++++---- ome_zarr/classes/image.py | 10 +- 3 files changed, 295 insertions(+), 297 deletions(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index f8edd1f5..cf7b9c01 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -1,214 +1,214 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "8b0be7e9", - "metadata": {}, - "source": [ - "# Create Scenes\n", - "\n", - "(advanced:create-scenes)=\n", - "\n", - "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "a49ec1c2", - "metadata": {}, - "outputs": [], - "source": [ - "from skimage import data\n", - "\n", - "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" - ] - }, - { - "cell_type": "markdown", - "id": "98bbabd7", - "metadata": {}, - "source": [ - "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e3023c95", - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] + "cell_type": "markdown", + "id": "8b0be7e9", + "metadata": {}, + "source": [ + "# Create Scenes\n", + "\n", + "(advanced:create-scenes)=\n", + "\n", + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." + ] }, { - "data": { - "text/plain": [ - "(512, 512)" + "cell_type": "code", + "execution_count": 1, + "id": "a49ec1c2", + "metadata": {}, + "outputs": [], + "source": [ + "from skimage import data\n", + "\n", + "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "example_image = data.human_mitosis()\n", - "example_image.shape" - ] - }, - { - "cell_type": "markdown", - "id": "71e31e2a", - "metadata": {}, - "source": [ - "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "017d4c59", - "metadata": {}, - "outputs": [], - "source": [ - "img1 = OMEZarrImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", - "img2 = OMEZarrImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", - "img3 = OMEZarrImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", - "img4 = OMEZarrImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", - "\n", - "img1_ms = OMEZarrMultiscale(img1)\n", - "img2_ms = OMEZarrMultiscale(img2)\n", - "img3_ms = OMEZarrMultiscale(img3)\n", - "img4_ms = OMEZarrMultiscale(img4)" - ] - }, - { - "cell_type": "markdown", - "id": "cf59df32", - "metadata": {}, - "source": [ - "Next, we need to define a coordinate system into which all images are projected.\n", - "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "994674c2", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_system = {\n", - " \"name\": \"world\",\n", - " \"axes\": [\n", - " {\"name\": \"y\", \"type\": \"space\"},\n", - " {\"name\": \"x\", \"type\": \"space\"},\n", - " ],\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "c1d629be", - "metadata": {}, - "source": [ - "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", - "In thisexamples, these are simple translations in the y and x dimensions:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d6329caa", - "metadata": {}, - "outputs": [], - "source": [ - "coordinate_transformations = [\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 0],\n", - " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 0],\n", - " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 256],\n", - " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [256, 256],\n", - " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", - " \"output\": {\"name\": \"world\"},\n", - " },\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "4444a8d3", - "metadata": {}, - "source": [ - "We can then create and write a scene like this:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d05be093", - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "markdown", + "id": "98bbabd7", + "metadata": {}, + "source": [ + "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e3023c95", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "data": { + "text/plain": [ + "(512, 512)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "example_image = data.human_mitosis()\n", + "example_image.shape" + ] + }, + { + "cell_type": "markdown", + "id": "71e31e2a", + "metadata": {}, + "source": [ + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "017d4c59", + "metadata": {}, + "outputs": [], + "source": [ + "img1 = OMEZarrImage(data=example_image[:256, :256], axes=[\"y\", \"x\"], name=\"img1\")\n", + "img2 = OMEZarrImage(data=example_image[256:, :256], axes=[\"y\", \"x\"], name=\"img2\")\n", + "img3 = OMEZarrImage(data=example_image[:256, 256:], axes=[\"y\", \"x\"], name=\"img3\")\n", + "img4 = OMEZarrImage(data=example_image[256:, 256:], axes=[\"y\", \"x\"], name=\"img4\")\n", + "\n", + "img1_ms = OMEZarrMultiscale(img1)\n", + "img2_ms = OMEZarrMultiscale(img2)\n", + "img3_ms = OMEZarrMultiscale(img3)\n", + "img4_ms = OMEZarrMultiscale(img4)" + ] + }, + { + "cell_type": "markdown", + "id": "cf59df32", + "metadata": {}, + "source": [ + "Next, we need to define a coordinate system into which all images are projected.\n", + "This is defined in accordance with the NGFF [coordinate systems specification](https://ngff.openmicroscopy.org/specifications/dev/index.html#coordinatesystems-metadata)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "994674c2", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_system = {\n", + " \"name\": \"world\",\n", + " \"axes\": [\n", + " {\"name\": \"y\", \"type\": \"space\"},\n", + " {\"name\": \"x\", \"type\": \"space\"},\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c1d629be", + "metadata": {}, + "source": [ + "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", + "In thisexamples, these are simple translations in the y and x dimensions:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d6329caa", + "metadata": {}, + "outputs": [], + "source": [ + "coordinate_transformations = [\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 0],\n", + " \"input\": {\"path\": \"img1\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 0],\n", + " \"input\": {\"path\": \"img2\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 256],\n", + " \"input\": {\"path\": \"img3\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [256, 256],\n", + " \"input\": {\"path\": \"img4\", \"name\": \"physical\"},\n", + " \"output\": {\"name\": \"world\"},\n", + " },\n", + "]" + ] + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "Writing images: 100%|██████████| 4/4 [00:00<00:00, 12.34it/s]\n" - ] + "cell_type": "markdown", + "id": "4444a8d3", + "metadata": {}, + "source": [ + "We can then create and write a scene like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d05be093", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 4/4 [00:00<00:00, 12.34it/s]\n" + ] + } + ], + "source": [ + "scene = OMEZarrScene(\n", + " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", + " coordinate_systems=[coordinate_system],\n", + " coordinate_transformations=coordinate_transformations\n", + ")\n", + "\n", + "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" } - ], - "source": [ - "scene = OMEZarrScene(\n", - " images=[img1_ms, img2_ms, img3_ms, img4_ms],\n", - " coordinate_systems=[coordinate_system],\n", - " coordinate_transformations=coordinate_transformations\n", - ")\n", - "\n", - "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.13.12)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/docs/source/advanced/transforms/reading_scenes.ipynb b/docs/source/advanced/transforms/reading_scenes.ipynb index fd15be54..c177c476 100644 --- a/docs/source/advanced/transforms/reading_scenes.ipynb +++ b/docs/source/advanced/transforms/reading_scenes.ipynb @@ -1,92 +1,92 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "28740944", - "metadata": {}, - "source": [ - "# Reading scenes" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "3222cfcb", - "metadata": {}, - "outputs": [], - "source": [ - "import networkx as nx\n", - "\n", - "from ome_zarr import OMEZarrScene" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a9bebd85", - "metadata": {}, - "outputs": [], - "source": [ - "scene = OMEZarrScene.from_ome_zarr(\"https://radosgw.public.os.wwu.de/s2v/P2A_B6_M2.ome.zarr\")" - ] - }, - { - "cell_type": "markdown", - "id": "7e3c003e", - "metadata": {}, - "source": [ - "This step requires matplotlib" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a9277576", - "metadata": {}, - "outputs": [ + "cells": [ + { + "cell_type": "markdown", + "id": "28740944", + "metadata": {}, + "source": [ + "# Reading scenes" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3222cfcb", + "metadata": {}, + "outputs": [], + "source": [ + "import networkx as nx\n", + "\n", + "from ome_zarr import OMEZarrScene" + ] + }, { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAApQAAAHzCAYAAACe1o1DAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnQW4FNX7x8+9dEtjgKggKmIjtmInCgaoiP5sBVtRMcAOVAxE7P7bha0oIgoiNmIQEioK0h0X9v98Dr7Xc+fO7s7s7mzc+36fZ72yOzs7c+bE93zfKorFYjGjUCgUCoVCoVCkiOJUv6hQKBQKhUKhUCihVCgUCoVCoVCkDVUoFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKSFqul9XaFQVHYsXVlips1dalaVrDXVqxab1o3rmDo1dGpRKBSKygSd9RUKRWhMmrXYPDt2hhnx62wzY94yE3M+KzLGtGpU23Ru18yc2KmVadu8nrawQqFQVHAUxWIxdy1QKBSKuPh93jLT77XxZtTkOaZKcZFZszb+9CGf79mmibm5awfTslFtbVmFQqGooFBCqVAoAuH5cTNM/2ETTMnaWEIi6UcsqxYXmeu6tDc9OrbS1lYoFIoKCA3KUSgKGA8//LDZYostzLJlyyL9ncEjJpkrXh1vVpasDUUmAcfzPb7PeSpK+7z66qtml112MStWrDD5invuucfe/5o1a0y+4oEHHjAHH3ywWbt2bYVqg8cff9z+7uLFiwvy/AqFqeyE8oUXXrCDjNeXX37pe8zpp59uP991113jfpfX1ltvbfbZZx9z7bXXmnnz5sX9rTFjxgS6tj///LPM+RO93nzzTfudWbNmmSFDhph9993Xvv/UU0+ZdMEENHjwYNO1a1ez0047mY4dO9r/v+qqq8xPP/2UkTaJ93r22WdDX++MGTPMpZdeag444ACzww47mKOOOsrccccdZs6cOUmvtXPnzua6664zCxYsCP27PNezzz7b7L777rav9OzZ0wwfPjzpPW+77bZm7733NmeeeaZ57rnnIiUcc+fONb/++mtki7Eok3d8MDHhMWuWzjeLv3nb/P1//cyfD51tloz/yPc4zvPge19nvE9nu314ppdccok59NBDTc2aNe17zz//vL2fkSNHljv+jDPOsJ+9/vrr5T7r27ev2WqrrcyiRYtMpvHPP//Y+8+UZ9Ps2bMtAdxvv/3s/UBqEmHmzJnm8ssvN3vssYfp1KmTueiii8xff/1V5pju3bvbsZbsXPnSBmH7XlRENurzKxSmsgflzJ8/3w6yatWqmccee8zsvPPOZT6HoD355JOmqKjI1K9f3/e7fA8CsXLlSktKr7jiCkuEvv322zLfkeOXLl0a6NqaNWtWbkE59dRTzffff2++/vrrMu9vsMEGZvz48XbnfuSRR9qF67LLLvMlcWHw2WefmWOOOcY0adLEnHfeeWb77be3bQWRfPTRR83NN99sF0Ym+XTaRI73okWLFqGu9+OPPzaHHXaYJb6Qyo033theK9d59dVX27avUqWK72+z6I8ePdpceeWV5v/+7/9sG9etWzfQ7z7xxBOWDNIOPCPOxf1CavndG264Ie49l5SU2EWM3+aZ0Vb8PsQ004C0QrDr1KljovKZxMydCKtmTzOzX7zW1Gq7i6m12U5mwYjHzNoVS+Ie2/u4k80Jx3TLWJ/ORfswh0CWevfuXfpehw4dbD9477337IZCwIL/4osvWpX0rbfestfjgs8YQ975KN/AuNt///3tfHT44Yebiy++2JKaeIAkHnLIIfbY66+/3jRu3Nh8/vnn5oQTTjAjRowoPa5Ro0bm5JNPtmPqf//7nykurhg6B/MG957vz1WhyBQqHKEUMJGhHN19992lCgJ4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SeaKV8eblSVrkxJJLzie7/F9zlOR2mfJkiVmxYoVJl+xdu1ae/8UFyhUKKFUKBSRo127dqa4uLhcCUYm0ESLiEyyvFavXu37XV4LFy40JSUlCa+B45YtWxb4mpcuXVrmN+K9WKi8WLRokVm8eLHJ9IK7fPnyhMeEbRNUYyrj9OjRw57b/X66159sYQx7rUGRjJB47zFbXl/kYSWK/oEHHojsN1AW7/hgYuDj165cZl9+4DwvOEplFH06W+3zxx9/mObNm5sRI0aUIa9+42nNmjVx5wqZj6IgpjNmzLD3z3jMNLjeoP08zBzphRJKhUKRk0hvCAQTKK/jjjvO93uPP/546TFvv/12ue82a9bMtG7d2rRq1crWDYe43n333eUmTzn+1FNPDXzNJ554oj23vDbccEN7Dv6670PGwM8//2z69+9vttxyS7PeeuuZXXfd1aQLFvEbbrjBbLvttvb+GjdubOrXr2+23nprc8UVV5hff/01rTbZYIMNLJl78skny9wTr0MPPTT09U6bNs2cfvrpZv311zd169a119qxY0dz77332t9Jdq0o2ffff3/o333//ffNkUceaZVWfpN2oha5dwPj/d2WLVva391oo43MUUcdZV5++eXICCYbKmqkMxai8pnsP6zs/fph9ZzfzYJRz5o/Hz7b/D6ou/n76UvjHnv5Y++aC/v2y2ifzkX7XHnllXYMHXLIIaX9gHHctWvXcsf+3//9n+0je+21l28/i4r0RYGhQ4eaTTfd1D47Sqkyz/r5sc+bN8+cdtpptv3lRYAe5DkMlFAqFIqcRnpDAN555x1b8s8LUtnweTxgphWFi9eBBx5oLrroIjNw4MC0r/f1118vo2YRVAT4677/1ltv2fchfiyKr732ms29mS6mTJlitt9+e/P000+ba6+91vqfoh6wIFx99dU2MptFIN02gVxBXL3K66hRo0Jd799//2123nlnM2bMGNt2qD9USOIaCcp67rnnEl4rixpVevr06WPuvPPOwL+LosTCyfd+++03q8agREG2IUAoP/F+V0yMH3/8senQoYPdRBx00EFJleBU0KhRI/t755xzjokCBOCUBDBxLxj9vDFFRaZZt6tM1UYbJjx2zqfPmRG//pOxPp2L9uH50/d69+5d+l61atXMHnvsYT777LNylg/6AoTq22+/LefzzWdg3333NfmO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CoUi81BCqcgb/Pbbb7Hbb789tvPOO9sFoEaNGrEjjzwy9vTTT8cWLFgQq0yAzNEGufbnwt8VUnnSSSflJal89913bTtNnTo1st/o169fbL311rNKIORtr732iuUDuGf8/jC/f/rpp7m+nLwAavpbb70V69atm3Xb4HXsscfafpKP/VehqEhQQqnIS0ybNs06yOMjB2FAgTniiCNsIMT8+fNjFR2iio0aNSrXl2KVr+Li4tgpp5wSqRKYCmbMmGHbCRIRBVCIN91009hpp50W++WXX+xvoZ7nGuPGjYs1b948tskmm9jrquzAjebKK6+0wWw8IwIB77nnntg///yT60tTKCoNlFAq8h7Tp0+3pqvddtvNLhaoDocddljs8ccftya/iho8UFRUFHvsscdi+QBIFKQS3858IpUQvrp161plOwqMHTvW9rkPP/wwdvHFF8caN24cW7FiRSyXIGgKMz9K/qxZs2KVFUSxMwfsueee9hmhIp977rmxr776Sv1IFYocIPryEgpFmqA6yUUXXWRz/v3+++9m4MCBZuHChTZPXPPmzW2VF3LIkZS4ooB8neRYpMRlPuCEE04wTzzxhK0ccu6559pcffkAyt5ttdVWNudpFHj++edtH9t1113t/Z9yyik5TfpOLXHKnVKNasSIEaZZs2amMgERZPTo0bbsJvk1STjO8yAZOSUw77//frPjjjtqOUSFIgfQxOaKggULCFVdXn75ZTNq1ChbFYRKLyR1Puqoo0yTJk1MIWP//fc3DRs2tEnG8wWQKoj8OeecY+tY50MdY67nxx9/NF9++WXGE16zmenWrZvZZZddTM+ePW2Z0c0339xkGxD4yy+/3Nxxxx3m/PPPt1V76O+VBX///bd5+umn7cbxl19+MRtvvLElk1Sxad26da4vT6FQgFzIogpFpjFz5szY4MGDbdAEplkCSQ444IDYgw8+aKM9CxGkOdl2221j+YaHH37YmhjPP//8vDAtDhw40OaIzLQp/pNPPrH3+fnnn1uzaufOnWO5wPLly21gCS4Qd999d6yygMT6r7/+eqxLly52PBOkR8aB4cOH55XbhUKhWAcllIoKByKjhwwZYpMXC7ncb7/9Yg888EBB+ZxJSpx8IG1eDB061JKtiy66KOfXR3UfroVArkwT+latWpUGSJGEP9uYM2dObPfdd7fptIj8rwwgJdOll14aa9asmW138ozef//9FdZfWqGoKFCTt6JCY/bs2bY+L2ZxfM7YRO29997WLI4ps0WLFiZfQY1qamtTs3rDDTc0+YYhQ4aY3r17m0svvdTcfvvtOTN/T58+3Zo93377betPmwmsXr3a+uiddtppZsWKFdaXEv/dbNY3nzJliq0lPX/+fNsXMLtXVCxatMi88MIL1qT9xRdfmMaNG1sXA8za2267ba4vT6FQBIAG5SgqNAhaOOuss8yHH35o/bAefPBBU61aNeuHtsEGG1hyiS/gX3/9ZfINbdu2tX/zJTDHC4Jz7r33XuvX169fP0vWcwH8HOvWrWt++umnjJ3zo48+MnPnzrW+uE899ZQlNtkkk5AqIZDu/1ck0F8+/fRTG+gEeT/77LNLfYb//PNPc/fddyuZVCgKCEooFZUGBOkQHfr++++bWbNmmUceecTUrl3bRpCjAO65556WILGY5QM23XRTU1xcnLeEEpx33nk2QOTWW28111xzTU5IJcrolltumdFI7+eee860a9fOBuEsWLDAnHHGGSZbePXVV03nzp3t748ZM8ZsttlmpiKB8XXzzTfb4CY2dATUsSFBaX7nnXes9SCXkfQKhSJF5NrmrlDkGnPnzrX57A499FCb45JhQc7LQYMG2cTZuQRJtS+77LJYvoPAGNptwIABOfl9kq6TlzFTQTCUvezfv39s1113tcFd2QJ9juCb4447zl5HRQG5O6m6dMghh1i/5lq1asV69eplA580wEahqBhQQqlQOMDxn2o8hx9+uK3OA0miWg8BMiRYzzYOOuig2FFHHVUQz+iWW26x7XXDDTdk/bdJbE6C80wECL3yyiv2Pt544w379+WXX45FDcoCEjXP7/Xt27fCkKwffvghduGFF9qE8DKWHnroodjChQtzfWkKhSLDUEKpUMQB9cOpI07aEiGXqGCocVHWjnbRp0+fWPv27QvmGd144422nW6++eas/u7bb79tfzcTpJ8UPdttt12sd+/esRYtWtj0NVFi6dKltmY9yh3ZCQodlEblPnbaaSf7TIjWJmp7woQJub40hUIRIZRQKhQBgKJC+UHUQvLhsVCyYN52222xKVOmRNaG1CMmZUwhKVaYvWmfqMoh+gGCz2++++67aZ1n0aJF1hx7/fXXx+rXrx+76qqrYlGCNFZsUsijGVU98myA/kl+SPJE0l9J1XXEEUfEXnvttcgJuUKhyA9oUI5CEQD169e35QdJQfTPP//YoA2ii/v372+DJij3RmAKqV4yHelN2hpSBxUKrr32WnP11Vebvn37mkGDBmXlN3kWderUSTswZ9iwYWb58uU2onvx4sWRBuMQ8EP09owZM8zIkSPNYYcdZgoNBNJcd911NoCMyk5ff/21/TcplmhLouTJqqBQKCo+NA+lQpEGlixZYiNTSXVCHkTIyHbbbWeOPfZY+5LUP6li8uTJ9hykPWLBLhRg/bjqqqvMLbfcYu655x6bpilqdOzY0WyzzTa23niqOOKII2y6oJKSEpsVgGcbBYhspiY36XL4DUoJFgrY4Lz++us2Z+Tw4cMtke/evbstgUnN83wox6lQKLIPVSgVijRA/sPjjjvOEkqUyxdffNGmQ7npppvsX5Iy33jjjVaNSgUk7K5atWpepw7yA6SCNrjsssvMBRdcYO6///7If7N9+/ZpKZTz5s2zKaX22GMPM27cOJu/NAqQJJ3NARuPzz//vCDIJBuEb775xvTp08eS4OOPP95uniDv5HAlBdduu+2mZFKhqMRQQqlQZAgoNaiSVPyAXFKdZ6uttrKm8C222MJ06NDBXH/99ebnn38OfE7I5CabbFJwhFJI5W233WbzfEJEhg4dGunv0dYkN081Fyb5H9esWWMT4JOXNNMmaK6L9oCMoei99957Zr311jP5DNRacrNuv/321q2DNiIBORskVFYSvrOpUigUCg3KUSgixrJly2wdZgIWSG0Dt9hqq61snsMff/wx6ffJj0kao0IFqXwkJc7DDz8c2e+8+eab9jdSzR1K7fe9997bPiOeTSaxevXq2FlnnWWv75prrsl5/fNkKYzee+89G+1OdoOqVavGunXrZoOGuA+FQqHwgxJKhSKLIFn166+/HuvZs6eNIoZgbLnllpZkkLPPj2iQx69du3YF/Zy4L9LwkLT7sccei+Q3fvvtN9uekKGwmDlzpr22k046yabv+f333zN2XUSOk9AbYvboo4/G8hWTJ0+OXX311bGNNtrItiPpqu666y4bia5QKBTJoIRSochh9ZBhw4bZiiENGjSwizjEkVQ13333XSm5vP/++20Fn0JXh7ifs88+2xI3ksdHkbqmdu3aNgl9KumZIHzbbLONTXeTKfz555+x7bff3lbeef/992P5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YqlVvnCnrfx2uhOP5et0tk4HXWwVXXu7z1dVzrH5D19kHfYZbm233qfOd5dPf8f22ekGt9377befTZ8+3U4//XR7++23nZhE4O60005266232m9+8xv3nSeeeCIyxnHixIl24YUXuuODZZgYV9z/N910k7PiYvk944wz6oQkkOl/1lln2ciRI5tMln5TRoJSCCGE+AUEI5a51q1bO2smL/BWRUQSFjwvMhFZWDwpg/Tmm286V7svh4RVNCgyWQYQbJdddpnts88+ttN1z9vkB66y+Y9fad1O/n9Wvk6XOudi8fiHbJ0dj7SK9TdzVsr5j1zqBOb6J93hRFqLDTe3Zl02sZ8/fLGeoFwy+Xlrvk4n23vvvd3/jzvuOLdviL111lnH7cvYsWPtvffeqxWUUbz//vu222672V577eX2vXPnzk5UY/GdNm2aCxHg87Db/9lnn7WjjjrKlYRCWIriRoJSCCGE+IXzzjsvMinn3XffdQkruHCxYPLCunfjjTe6GEpviURoRZVDwho5deraxBuEJxbDVu3aW+ez/tc67neuzb37FPtx0rPWYY9T6/xui55bOTEJZS3bWdvtDrJFL95pqypnW/NOa93pbQfsZ4v+7y5bMfcLq9hgrTBctXC2rZg1xdYZfKQtr15jrcvMbcPZZ5/txCSwL4jERFx66aXuO6NHj3YJTsAx+v3vf19vWWJRqT+KW53knoEDB9qTTz4pQdkEkKAUQgghfmHMmDG1VsmGgNDq3bu3e4W55ZZbnMv7X//6l3OTv/flPBtdWWLlbTtZsw4b2oq5n9f7TovudbelWftu7u/qJQvMfhGUrfvuZj+Me8B++vCFWkHJv2nQ2LrfHvZNZZX17baO7b777i6bHdF7wAEH2ODBg2sFYiywYr7xxht28MEHx12W2MoRI0bYY4895qygxGxi0UVcYtEUxY+iZYUQQogcQmY62eTDjjjq18G4RRtbs/LX2pq177eqW7S9pFmF+1uzavmvyzRrYW367W5Lv3jTucVrVi63nz99zSp69LNm7de3ldU1bjnE3uWXX15rISX2cf/993exlbHAnY8ADSb1RIFLG6H88ssv2w8//GBffvmlc4HvuuuutRn0oriRoBRCCCFyCHGW0Lz81yF49U8LraxN+wavs+2A/W1N9Sr7+ZOxVvXZ67ZmRZW12WrPOr+DhfFPf/qTTZ482Yk+XNgk0xx22GEx10ssafv27Z3bPh7PPfecHXLIIc7qGQRhKZoGEpRCCCFEhsE6RyxlFI8++qj7u1HH1kbuM67u6sXfWcteAxv8e806bGAtNtrKJef8NPkFK61oba02G+zWz+/gug5aCslqP/zww50rm6xzn0gUBcuRvBNlyVy1alXt+igdFOTVV1+VoGxCKIZSCCFEkwArW7B2oidYHifWMsQBduzYMenfoiQR7t7XX3+93meIuyuuuMJ17mkx8x2b/dzfXKY2VsZ04PvfP3W9+3ebAftbabMK69GxlbWuKHcWSRJkyDDfbrvtnAv7o48+cm7qo48+Om5Zn7/85S/21ltvuUzua6+91rbeemuXsX7ffffZ8OHDXWLPCSec4BKU7rjjDpeshBWUmp641kkGEsWPBKUQQoiiBvFExjEJMVGQhZxomfPPP7+2zmIQip2vWLGi3vusK1jUPMiVV15pjzzyiF1wwQW2ZP5ia7PlEGu3/eFWUv5rdnlZy7ZW3mFDKyktq/NdluH90or6LRVb9v6tlbXt5NznbfrvaWWlJTZks7VliHBbjxs3zu6++24nEKlBSaY6iUKIwXitF4m1JMv9f/7nf9x2k9hD0hE1LX2WODU8Ed1PP/20jRo1ygYNGuTc4NSpJOM73vpFcVCyJp6dWwghhBAZwWd506XGJ7l8Of8n2/P2NzKyfmIo5/z1OCtbZz3r9vs73XtjR+xivbu0zcj6hYiHYiiFEEKIRmLT9drazr07OWtiuiz7erLVrKhyBc5ZH+uVmBS5QoJSCCGEaERGHtLPytMQlNU/VdqKedNs8RujnHWyTf893PpYrxC5QoJSCCGEyAEk9RA/GE766d6hlV0ztG+D1/vje09b5Qt3WPm6Xa3L4Ve5OMtrh/Z16xUiVyiGUgghhMgD7hr3pd3ycuwi48ly0V597Owh9Tv1CJFNJCiFEEKIPOGxSbPsqjFTrbpmja2uST5nlphJ3NxYJo8cGJ1dLkQ2kaAUQggh8ojZi5baZU9PsQlfLXRCMZ6w9J+TgEPMpNzcorGQoBRCCCHyEEoKjZ44y8ZNX2CzKpdaUFaSwkPRcupMHrd9D2Vzi0ZHglIIIYTIc6pWVNs3lVW2srrG9eamnSIdcITIFyQohRBCCCFEWqhskBBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQoi0kKAUQgghhBBpIUEphBBCCCHSQoJSCCGEEEKkhQSlEEIIIYRICwlKIYQQQgiRFhKUQgghhBAiLSQohRBCCCFEWkhQCiGEEEKItJCgFEIIIYQQaSFBKYQQQggh0kKCUgghhBBCpIUEpRBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQghLh/8PRKDerF9KyvcAAAAASUVORK5CYII=", - "text/plain": [ - "
" + "cell_type": "code", + "execution_count": 2, + "id": "a9bebd85", + "metadata": {}, + "outputs": [], + "source": [ + "scene = OMEZarrScene.from_ome_zarr(\"https://radosgw.public.os.wwu.de/s2v/P2A_B6_M2.ome.zarr\")" ] - }, - "metadata": {}, - "output_type": "display_data" + }, + { + "cell_type": "markdown", + "id": "7e3c003e", + "metadata": {}, + "source": [ + "This step requires matplotlib" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9277576", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApQAAAHzCAYAAACe1o1DAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnQW4FNX7x8+9dEtjgKggKmIjtmInCgaoiP5sBVtRMcAOVAxE7P7bha0oIgoiNmIQEioK0h0X9v98Dr7Xc+fO7s7s7mzc+36fZ72yOzs7c+bE93zfKorFYjGjUCgUCoVCoVCkiOJUv6hQKBQKhUKhUCihVCgUCoVCoVCkDVUoFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKQFJZQKhUKhUCgUirSghFKhUCgUCoVCkRaUUCoUCoVCoVAo0oISSoVCoVAoFApFWlBCqVAoFAqFQqFIC0ooFQqFQqFQKBRpQQmlQqFQKBQKhSItKKFUKBQKhUKhUKSFqul9XaFQVHYsXVlips1dalaVrDXVqxab1o3rmDo1dGpRKBSKygSd9RUKRWhMmrXYPDt2hhnx62wzY94yE3M+KzLGtGpU23Ru18yc2KmVadu8nrawQqFQVHAUxWIxdy1QKBSKuPh93jLT77XxZtTkOaZKcZFZszb+9CGf79mmibm5awfTslFtbVmFQqGooFBCqVAoAuH5cTNM/2ETTMnaWEIi6UcsqxYXmeu6tDc9OrbS1lYoFIoKCA3KUSgKGA8//LDZYostzLJlyyL9ncEjJpkrXh1vVpasDUUmAcfzPb7PeSpK+7z66qtml112MStWrDD5invuucfe/5o1a0y+4oEHHjAHH3ywWbt2bYVqg8cff9z+7uLFiwvy/AqFqeyE8oUXXrCDjNeXX37pe8zpp59uP991113jfpfX1ltvbfbZZx9z7bXXmnnz5sX9rTFjxgS6tj///LPM+RO93nzzTfudWbNmmSFDhph9993Xvv/UU0+ZdMEENHjwYNO1a1ez0047mY4dO9r/v+qqq8xPP/2UkTaJ93r22WdDX++MGTPMpZdeag444ACzww47mKOOOsrccccdZs6cOUmvtXPnzua6664zCxYsCP27PNezzz7b7L777rav9OzZ0wwfPjzpPW+77bZm7733NmeeeaZ57rnnIiUcc+fONb/++mtki7Eok3d8MDHhMWuWzjeLv3nb/P1//cyfD51tloz/yPc4zvPge19nvE9nu314ppdccok59NBDTc2aNe17zz//vL2fkSNHljv+jDPOsJ+9/vrr5T7r27ev2WqrrcyiRYtMpvHPP//Y+8+UZ9Ps2bMtAdxvv/3s/UBqEmHmzJnm8ssvN3vssYfp1KmTueiii8xff/1V5pju3bvbsZbsXPnSBmH7XlRENurzKxSmsgflzJ8/3w6yatWqmccee8zsvPPOZT6HoD355JOmqKjI1K9f3/e7fA8CsXLlSktKr7jiCkuEvv322zLfkeOXLl0a6NqaNWtWbkE59dRTzffff2++/vrrMu9vsMEGZvz48XbnfuSRR9qF67LLLvMlcWHw2WefmWOOOcY0adLEnHfeeWb77be3bQWRfPTRR83NN99sF0Ym+XTaRI73okWLFqGu9+OPPzaHHXaYJb6Qyo033theK9d59dVX27avUqWK72+z6I8ePdpceeWV5v/+7/9sG9etWzfQ7z7xxBOWDNIOPCPOxf1CavndG264Ie49l5SU2EWM3+aZ0Vb8PsQ004C0QrDr1KljovKZxMydCKtmTzOzX7zW1Gq7i6m12U5mwYjHzNoVS+Ie2/u4k80Jx3TLWJ/ORfswh0CWevfuXfpehw4dbD9477337IZCwIL/4osvWpX0rbfestfjgs8YQ975KN/AuNt///3tfHT44Yebiy++2JKaeIAkHnLIIfbY66+/3jRu3Nh8/vnn5oQTTjAjRowoPa5Ro0bm5JNPtmPqf//7nykurhg6B/MG957vz1WhyBQqHKEUMJGhHN19992lCgJ4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SeaKV8eblSVrkxJJLzie7/F9zlOR2mfJkiVmxYoVJl+xdu1ae/8UFyhUKKFUKBSRo127dqa4uLhcCUYm0ESLiEyyvFavXu37XV4LFy40JSUlCa+B45YtWxb4mpcuXVrmN+K9WKi8WLRokVm8eLHJ9IK7fPnyhMeEbRNUYyrj9OjRw57b/X66159sYQx7rUGRjJB47zFbXl/kYSWK/oEHHojsN1AW7/hgYuDj165cZl9+4DwvOEplFH06W+3zxx9/mObNm5sRI0aUIa9+42nNmjVx5wqZj6IgpjNmzLD3z3jMNLjeoP08zBzphRJKhUKRk0hvCAQTKK/jjjvO93uPP/546TFvv/12ue82a9bMtG7d2rRq1crWDYe43n333eUmTzn+1FNPDXzNJ554oj23vDbccEN7Dv6670PGwM8//2z69+9vttxyS7PeeuuZXXfd1aQLFvEbbrjBbLvttvb+GjdubOrXr2+23nprc8UVV5hff/01rTbZYIMNLJl78skny9wTr0MPPTT09U6bNs2cfvrpZv311zd169a119qxY0dz77332t9Jdq0o2ffff3/o333//ffNkUceaZVWfpN2oha5dwPj/d2WLVva391oo43MUUcdZV5++eXICCYbKmqkMxai8pnsP6zs/fph9ZzfzYJRz5o/Hz7b/D6ou/n76UvjHnv5Y++aC/v2y2ifzkX7XHnllXYMHXLIIaX9gHHctWvXcsf+3//9n+0je+21l28/i4r0RYGhQ4eaTTfd1D47Sqkyz/r5sc+bN8+cdtpptv3lRYAe5DkMlFAqFIqcRnpDAN555x1b8s8LUtnweTxgphWFi9eBBx5oLrroIjNw4MC0r/f1118vo2YRVAT4677/1ltv2fchfiyKr732ms29mS6mTJlitt9+e/P000+ba6+91vqfoh6wIFx99dU2MptFIN02gVxBXL3K66hRo0Jd799//2123nlnM2bMGNt2qD9USOIaCcp67rnnEl4rixpVevr06WPuvPPOwL+LosTCyfd+++03q8agREG2IUAoP/F+V0yMH3/8senQoYPdRBx00EFJleBU0KhRI/t755xzjokCBOCUBDBxLxj9vDFFRaZZt6tM1UYbJjx2zqfPmRG//pOxPp2L9uH50/d69+5d+l61atXMHnvsYT777LNylg/6AoTq22+/LefzzWdg3333NfmO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CoUi81BCqcgb/Pbbb7Hbb789tvPOO9sFoEaNGrEjjzwy9vTTT8cWLFgQq0yAzNEGufbnwt8VUnnSSSflJal89913bTtNnTo1st/o169fbL311rNKIORtr732iuUDuGf8/jC/f/rpp7m+nLwAavpbb70V69atm3Xb4HXsscfafpKP/VehqEhQQqnIS0ybNs06yOMjB2FAgTniiCNsIMT8+fNjFR2iio0aNSrXl2KVr+Li4tgpp5wSqRKYCmbMmGHbCRIRBVCIN91009hpp50W++WXX+xvoZ7nGuPGjYs1b948tskmm9jrquzAjebKK6+0wWw8IwIB77nnntg///yT60tTKCoNlFAq8h7Tp0+3pqvddtvNLhaoDocddljs8ccftya/iho8UFRUFHvsscdi+QBIFKQS3858IpUQvrp161plOwqMHTvW9rkPP/wwdvHFF8caN24cW7FiRSyXIGgKMz9K/qxZs2KVFUSxMwfsueee9hmhIp977rmxr776Sv1IFYocIPryEgpFmqA6yUUXXWRz/v3+++9m4MCBZuHChTZPXPPmzW2VF3LIkZS4ooB8neRYpMRlPuCEE04wTzzxhK0ccu6559pcffkAyt5ttdVWNudpFHj++edtH9t1113t/Z9yyik5TfpOLXHKnVKNasSIEaZZs2amMgERZPTo0bbsJvk1STjO8yAZOSUw77//frPjjjtqOUSFIgfQxOaKggULCFVdXn75ZTNq1ChbFYRKLyR1Puqoo0yTJk1MIWP//fc3DRs2tEnG8wWQKoj8OeecY+tY50MdY67nxx9/NF9++WXGE16zmenWrZvZZZddTM+ePW2Z0c0339xkGxD4yy+/3Nxxxx3m/PPPt1V76O+VBX///bd5+umn7cbxl19+MRtvvLElk1Sxad26da4vT6FQgFzIogpFpjFz5szY4MGDbdAEplkCSQ444IDYgw8+aKM9CxGkOdl2221j+YaHH37YmhjPP//8vDAtDhw40OaIzLQp/pNPPrH3+fnnn1uzaufOnWO5wPLly21gCS4Qd999d6yygMT6r7/+eqxLly52PBOkR8aB4cOH55XbhUKhWAcllIoKByKjhwwZYpMXC7ncb7/9Yg888EBB+ZxJSpx8IG1eDB061JKtiy66KOfXR3UfroVArkwT+latWpUGSJGEP9uYM2dObPfdd7fptIj8rwwgJdOll14aa9asmW138ozef//9FdZfWqGoKFCTt6JCY/bs2bY+L2ZxfM7YRO29997WLI4ps0WLFiZfQY1qamtTs3rDDTc0+YYhQ4aY3r17m0svvdTcfvvtOTN/T58+3Zo93377betPmwmsXr3a+uiddtppZsWKFdaXEv/dbNY3nzJliq0lPX/+fNsXMLtXVCxatMi88MIL1qT9xRdfmMaNG1sXA8za2267ba4vT6FQBIAG5SgqNAhaOOuss8yHH35o/bAefPBBU61aNeuHtsEGG1hyiS/gX3/9ZfINbdu2tX/zJTDHC4Jz7r33XuvX169fP0vWcwH8HOvWrWt++umnjJ3zo48+MnPnzrW+uE899ZQlNtkkk5AqIZDu/1ck0F8+/fRTG+gEeT/77LNLfYb//PNPc/fddyuZVCgKCEooFZUGBOkQHfr++++bWbNmmUceecTUrl3bRpCjAO65556WILGY5QM23XRTU1xcnLeEEpx33nk2QOTWW28111xzTU5IJcrolltumdFI7+eee860a9fOBuEsWLDAnHHGGSZbePXVV03nzp3t748ZM8ZsttlmpiKB8XXzzTfb4CY2dATUsSFBaX7nnXes9SCXkfQKhSJF5NrmrlDkGnPnzrX57A499FCb45JhQc7LQYMG2cTZuQRJtS+77LJYvoPAGNptwIABOfl9kq6TlzFTQTCUvezfv39s1113tcFd2QJ9juCb4447zl5HRQG5O6m6dMghh1i/5lq1asV69eplA580wEahqBhQQqlQOMDxn2o8hx9+uK3OA0miWg8BMiRYzzYOOuig2FFHHVUQz+iWW26x7XXDDTdk/bdJbE6C80wECL3yyiv2Pt544w379+WXX45FDcoCEjXP7/Xt27fCkKwffvghduGFF9qE8DKWHnroodjChQtzfWkKhSLDUEKpUMQB9cOpI07aEiGXqGCocVHWjnbRp0+fWPv27QvmGd144422nW6++eas/u7bb79tfzcTpJ8UPdttt12sd+/esRYtWtj0NVFi6dKltmY9yh3ZCQodlEblPnbaaSf7TIjWJmp7woQJub40hUIRIZRQKhQBgKJC+UHUQvLhsVCyYN52222xKVOmRNaG1CMmZUwhKVaYvWmfqMoh+gGCz2++++67aZ1n0aJF1hx7/fXXx+rXrx+76qqrYlGCNFZsUsijGVU98myA/kl+SPJE0l9J1XXEEUfEXnvttcgJuUKhyA9oUI5CEQD169e35QdJQfTPP//YoA2ii/v372+DJij3RmAKqV4yHelN2hpSBxUKrr32WnP11Vebvn37mkGDBmXlN3kWderUSTswZ9iwYWb58uU2onvx4sWRBuMQ8EP09owZM8zIkSPNYYcdZgoNBNJcd911NoCMyk5ff/21/TcplmhLouTJqqBQKCo+NA+lQpEGlixZYiNTSXVCHkTIyHbbbWeOPfZY+5LUP6li8uTJ9hykPWLBLhRg/bjqqqvMLbfcYu655x6bpilqdOzY0WyzzTa23niqOOKII2y6oJKSEpsVgGcbBYhspiY36XL4DUoJFgrY4Lz++us2Z+Tw4cMtke/evbstgUnN83wox6lQKLIPVSgVijRA/sPjjjvOEkqUyxdffNGmQ7npppvsX5Iy33jjjVaNSgUk7K5atWpepw7yA6SCNrjsssvMBRdcYO6///7If7N9+/ZpKZTz5s2zKaX22GMPM27cOJu/NAqQJJ3NARuPzz//vCDIJBuEb775xvTp08eS4OOPP95uniDv5HAlBdduu+2mZFKhqMRQQqlQZAgoNaiSVPyAXFKdZ6uttrKm8C222MJ06NDBXH/99ebnn38OfE7I5CabbFJwhFJI5W233WbzfEJEhg4dGunv0dYkN081Fyb5H9esWWMT4JOXNNMmaK6L9oCMoei99957Zr311jP5DNRacrNuv/321q2DNiIBORskVFYSvrOpUigUCg3KUSgixrJly2wdZgIWSG0Dt9hqq61snsMff/wx6ffJj0kao0IFqXwkJc7DDz8c2e+8+eab9jdSzR1K7fe9997bPiOeTSaxevXq2FlnnWWv75prrsl5/fNkKYzee+89G+1OdoOqVavGunXrZoOGuA+FQqHwgxJKhSKLIFn166+/HuvZs6eNIoZgbLnllpZkkLPPj2iQx69du3YF/Zy4L9LwkLT7sccei+Q3fvvtN9uekKGwmDlzpr22k046yabv+f333zN2XUSOk9AbYvboo4/G8hWTJ0+OXX311bGNNtrItiPpqu666y4bia5QKBTJoIRSochh9ZBhw4bZiiENGjSwizjEkVQ13333XSm5vP/++20Fn0JXh7ifs88+2xI3ksdHkbqmdu3aNgl9KumZIHzbbLONTXeTKfz555+x7bff3lbeef/992P5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YqlVvnCnrfx2uhOP5et0tk4HXWwVXXu7z1dVzrH5D19kHfYZbm233qfOd5dPf8f22ekGt9377befTZ8+3U4//XR7++23nZhE4O60005266232m9+8xv3nSeeeCIyxnHixIl24YUXuuODZZgYV9z/N910k7PiYvk944wz6oQkkOl/1lln2ciRI5tMln5TRoJSCCGE+AUEI5a51q1bO2smL/BWRUQSFjwvMhFZWDwpg/Tmm286V7svh4RVNCgyWQYQbJdddpnts88+ttN1z9vkB66y+Y9fad1O/n9Wvk6XOudi8fiHbJ0dj7SK9TdzVsr5j1zqBOb6J93hRFqLDTe3Zl02sZ8/fLGeoFwy+Xlrvk4n23vvvd3/jzvuOLdviL111lnH7cvYsWPtvffeqxWUUbz//vu222672V577eX2vXPnzk5UY/GdNm2aCxHg87Db/9lnn7WjjjrKlYRCWIriRoJSCCGE+IXzzjsvMinn3XffdQkruHCxYPLCunfjjTe6GEpviURoRZVDwho5deraxBuEJxbDVu3aW+ez/tc67neuzb37FPtx0rPWYY9T6/xui55bOTEJZS3bWdvtDrJFL95pqypnW/NOa93pbQfsZ4v+7y5bMfcLq9hgrTBctXC2rZg1xdYZfKQtr15jrcvMbcPZZ5/txCSwL4jERFx66aXuO6NHj3YJTsAx+v3vf19vWWJRqT+KW53knoEDB9qTTz4pQdkEkKAUQgghfmHMmDG1VsmGgNDq3bu3e4W55ZZbnMv7X//6l3OTv/flPBtdWWLlbTtZsw4b2oq5n9f7TovudbelWftu7u/qJQvMfhGUrfvuZj+Me8B++vCFWkHJv2nQ2LrfHvZNZZX17baO7b777i6bHdF7wAEH2ODBg2sFYiywYr7xxht28MEHx12W2MoRI0bYY4895qygxGxi0UVcYtEUxY+iZYUQQogcQmY62eTDjjjq18G4RRtbs/LX2pq177eqW7S9pFmF+1uzavmvyzRrYW367W5Lv3jTucVrVi63nz99zSp69LNm7de3ldU1bjnE3uWXX15rISX2cf/993exlbHAnY8ADSb1RIFLG6H88ssv2w8//GBffvmlc4HvuuuutRn0oriRoBRCCCFyCHGW0Lz81yF49U8LraxN+wavs+2A/W1N9Sr7+ZOxVvXZ67ZmRZW12WrPOr+DhfFPf/qTTZ482Yk+XNgk0xx22GEx10ssafv27Z3bPh7PPfecHXLIIc7qGQRhKZoGEpRCCCFEhsE6RyxlFI8++qj7u1HH1kbuM67u6sXfWcteAxv8e806bGAtNtrKJef8NPkFK61oba02G+zWz+/gug5aCslqP/zww50rm6xzn0gUBcuRvBNlyVy1alXt+igdFOTVV1+VoGxCKIZSCCFEkwArW7B2oidYHifWMsQBduzYMenfoiQR7t7XX3+93meIuyuuuMJ17mkx8x2b/dzfXKY2VsZ04PvfP3W9+3ebAftbabMK69GxlbWuKHcWSRJkyDDfbrvtnAv7o48+cm7qo48+Om5Zn7/85S/21ltvuUzua6+91rbeemuXsX7ffffZ8OHDXWLPCSec4BKU7rjjDpeshBWUmp641kkGEsWPBKUQQoiiBvFExjEJMVGQhZxomfPPP7+2zmIQip2vWLGi3vusK1jUPMiVV15pjzzyiF1wwQW2ZP5ia7PlEGu3/eFWUv5rdnlZy7ZW3mFDKyktq/NdluH90or6LRVb9v6tlbXt5NznbfrvaWWlJTZks7VliHBbjxs3zu6++24nEKlBSaY6iUKIwXitF4m1JMv9f/7nf9x2k9hD0hE1LX2WODU8Ed1PP/20jRo1ygYNGuTc4NSpJOM73vpFcVCyJp6dWwghhBAZwWd506XGJ7l8Of8n2/P2NzKyfmIo5/z1OCtbZz3r9vs73XtjR+xivbu0zcj6hYiHYiiFEEKIRmLT9drazr07OWtiuiz7erLVrKhyBc5ZH+uVmBS5QoJSCCGEaERGHtLPytMQlNU/VdqKedNs8RujnHWyTf893PpYrxC5QoJSCCGEyAEk9RA/GE766d6hlV0ztG+D1/vje09b5Qt3WPm6Xa3L4Ve5OMtrh/Z16xUiVyiGUgghhMgD7hr3pd3ycuwi48ly0V597Owh9Tv1CJFNJCiFEEKIPOGxSbPsqjFTrbpmja2uST5nlphJ3NxYJo8cGJ1dLkQ2kaAUQggh8ojZi5baZU9PsQlfLXRCMZ6w9J+TgEPMpNzcorGQoBRCCCHyEEoKjZ44y8ZNX2CzKpdaUFaSwkPRcupMHrd9D2Vzi0ZHglIIIYTIc6pWVNs3lVW2srrG9eamnSIdcITIFyQohRBCCCFEWqhskBBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQoi0kKAUQgghhBBpIUEphBBCCCHSQoJSCCGEEEKkhQSlEEIIIYRICwlKIYQQQgiRFhKUQgghhBAiLSQohRBCCCFEWkhQCiGEEEKItJCgFEIIIYQQaSFBKYQQQggh0kKCUgghhBBCpIUEpRBCCCGESAsJSiGEEEIIkRYSlEIIIYQQIi0kKIUQQgghRFpIUAohhBBCiLSQoBRCCCGEEGkhQSmEEEIIIdJCglIIIYQQQqSFBKUQQgghhEgLCUohhBBCCJEWEpRCCCGEECItJCiFEEIIIURaSFAKIYQQQghLh/8PRKDerF9KyvcAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "nx.draw(scene._graph.graph, with_labels=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2766d6c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" } - ], - "source": [ - "nx.draw(scene._graph.graph, with_labels=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2766d6c", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.13.12)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index a29119b9..f97ea5cf 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -144,9 +144,7 @@ def __post_init__(self): "y": "space", "x": "space", } - self.axes_types = { - d: type_mapping.get(d, None) for d in self.axes - } + self.axes_types = {d: type_mapping.get(d) for d in self.axes} # coerce data to dask array if not isinstance(self.data, da.Array): self.data = da.from_array(self.data) @@ -221,7 +219,7 @@ def __init__( scale=level_scale, axes_units=image.axes_units, name=image.name, - axes_types=image.axes_types + axes_types=image.axes_types, ) ) @@ -257,7 +255,7 @@ def __init__( name=d, type=image.axes_types.get(d), unit=image.axes_units.get(d), - discrete=discrete + discrete=discrete, ) ) @@ -574,7 +572,7 @@ def from_ome_zarr( } if not axes_units: axes_units = None - + axes_types: dict[str, str | None] | None = { str(ax.name): ax.type for ax in cs.axes if ax.type is not None } From f02a412548ec7d537d2a889d00f136ceef9d9323 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:26:30 +0200 Subject: [PATCH 29/95] feat: write correct version number in scenes --- ome_zarr/classes/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index f586ee7d..7827452d 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -171,7 +171,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): metadata_dict = self.metadata.model_dump() metadata_dict = _recursive_pop_nones(metadata_dict) - zarr_group.attrs["ome"] = {"scene": metadata_dict, "version": "0.6"} + zarr_group.attrs["ome"] = {"scene": metadata_dict, "version": "0.6.dev4"} @classmethod def from_ome_zarr(cls, store: StoreLike): From de8aea0163b7ad73fd8b6e222ad89a5b93728133 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:26:42 +0200 Subject: [PATCH 30/95] tests: add first tests for scenes --- tests/test_scene.py | 148 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/test_scene.py diff --git a/tests/test_scene.py b/tests/test_scene.py new file mode 100644 index 00000000..1ae97723 --- /dev/null +++ b/tests/test_scene.py @@ -0,0 +1,148 @@ +import numpy as np +from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene +import pytest +import pathlib +import zarr +import transformnd as tnd + +class TestScene: + TRANSFORMS = [ + {"type": "scale", "scale": [1.0, 1.0]}, + {"type": "translation", "translation": [0.0, 0.0]}, + {"type": "rotation", "rotation": [[0.0, -1.0], [1.0, 0.0]]}, + {"type": "affine", "affine": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]}, + {"type": "mapAxis", "mapAxis": [1, 0]} + ] + @pytest.fixture(autouse=True) + def initdir(self, tmpdir): + self.path = pathlib.Path(tmpdir.mkdir("data")) + + # let's create zarr v3 group too... + self.path = self.path / "v3" + root_v3 = zarr.open_group(self.path, mode="w", zarr_format=3) + self.group_v3 = root_v3.create_group("test") + + def create_data(self, shape, dtype=np.uint8, mean_val=10): + rng = np.random.default_rng(0) + return rng.poisson(mean_val, size=shape).astype(dtype) + + @pytest.mark.parametrize("transform", TRANSFORMS) + def test_create_scene_without_coordinate_systems(self, transform): + shape = (64, 64) + img_a = OMEZarrImage( + data=self.create_data(shape), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_b = OMEZarrImage( + data=self.create_data(shape), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + + transform["input"] = {"name": "physical", "path": "imageA"} + transform["output"] = {"name": "physical", "path": "imageB"} + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[transform], + ) + + scene.to_ome_zarr("test_scene.zarr", overwrite=True) + + assert scene._graph is not None + assert len(scene._graph.graph.nodes) == 2 + + # traverse graph + tf = scene._graph.get_sequence(f"{img_a.name}:physical", f"{img_b.name}:physical") + assert tf is not None + + # write to disk and read back + scene.to_ome_zarr(str(self.path / "test_scene.zarr"), overwrite=True) + scene_read = OMEZarrScene.from_ome_zarr(str(self.path / "test_scene.zarr")) + + assert scene_read._graph is not None + assert len(scene_read._graph.graph.nodes) == 2 + + # open the zarr group and check the metadata + zarr_group = zarr.open_group(str(self.path / "test_scene.zarr"), mode="r") + assert "ome" in zarr_group.attrs + ome_metadata = zarr_group.attrs["ome"] + assert "scene" in ome_metadata + assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" + + # check transforms + assert "coordinateTransformations" in ome_metadata["scene"] + assert len(ome_metadata["scene"]["coordinateTransformations"]) == 1 + + transform_md = ome_metadata["scene"]["coordinateTransformations"][0] + assert transform_md == transform + + @pytest.mark.parametrize("transform", TRANSFORMS) + def test_create_scene_with_coordinate_systems(self, transform): + shape = (64, 64) + img_a = OMEZarrImage( + data=self.create_data(shape), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_b = OMEZarrImage( + data=self.create_data(shape), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + + world1_cs = {"name": "world", "axes": [ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes]} + world2_cs = {"name": "world2", "axes": [ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes]} + + transform1 = transform.copy() + transform1["input"] = {"name": "physical", "path": "imageA"} + transform1["output"] = {"name": "world"} + + transform2 = transform.copy() + transform2["input"] = {"name": "world"} + transform2["output"] = {"name": "world2"} + + transform3 = transform.copy() + transform3["input"] = {"name": "world2"} + transform3["output"] = {"name": "physical", "path": "imageB"} + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[transform1, transform2, transform3], + coordinate_systems=[world1_cs, world2_cs] + ) + + assert scene._graph is not None + assert len(scene._graph.graph.nodes) == 4 + + scene.to_ome_zarr(str(self.path / "test_scene_with_cs.zarr"), overwrite=True) + scene_read = OMEZarrScene.from_ome_zarr(str(self.path / "test_scene_with_cs.zarr")) + + assert scene_read._graph is not None + assert len(scene_read._graph.graph.nodes) == 4 + + # open zarr group and check metadata + zarr_group = zarr.open_group(str(self.path / "test_scene_with_cs.zarr"), mode="r") + assert "ome" in zarr_group.attrs + ome_metadata = zarr_group.attrs["ome"] + assert "scene" in ome_metadata + assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" + assert "coordinateSystems" in ome_metadata["scene"] + assert len(ome_metadata["scene"]["coordinateSystems"]) == 2 + +if __name__ == "__main__": + import pytest + pytest.main([__file__]) \ No newline at end of file From fdedbabe601bef9b21718a58f7b291c435530d7b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:26:56 +0000 Subject: [PATCH 31/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_scene.py | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index 1ae97723..55b7c28c 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -1,9 +1,11 @@ +import pathlib + import numpy as np -from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene import pytest -import pathlib import zarr -import transformnd as tnd + +from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene + class TestScene: TRANSFORMS = [ @@ -11,8 +13,9 @@ class TestScene: {"type": "translation", "translation": [0.0, 0.0]}, {"type": "rotation", "rotation": [[0.0, -1.0], [1.0, 0.0]]}, {"type": "affine", "affine": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]}, - {"type": "mapAxis", "mapAxis": [1, 0]} + {"type": "mapAxis", "mapAxis": [1, 0]}, ] + @pytest.fixture(autouse=True) def initdir(self, tmpdir): self.path = pathlib.Path(tmpdir.mkdir("data")) @@ -60,7 +63,9 @@ def test_create_scene_without_coordinate_systems(self, transform): assert len(scene._graph.graph.nodes) == 2 # traverse graph - tf = scene._graph.get_sequence(f"{img_a.name}:physical", f"{img_b.name}:physical") + tf = scene._graph.get_sequence( + f"{img_a.name}:physical", f"{img_b.name}:physical" + ) assert tf is not None # write to disk and read back @@ -104,8 +109,18 @@ def test_create_scene_with_coordinate_systems(self, transform): img_a_ms = OMEZarrMultiscale(image=img_a) img_b_ms = OMEZarrMultiscale(image=img_b) - world1_cs = {"name": "world", "axes": [ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes]} - world2_cs = {"name": "world2", "axes": [ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes]} + world1_cs = { + "name": "world", + "axes": [ + ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes + ], + } + world2_cs = { + "name": "world2", + "axes": [ + ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes + ], + } transform1 = transform.copy() transform1["input"] = {"name": "physical", "path": "imageA"} @@ -122,20 +137,24 @@ def test_create_scene_with_coordinate_systems(self, transform): scene = OMEZarrScene( images=[img_a_ms, img_b_ms], coordinate_transformations=[transform1, transform2, transform3], - coordinate_systems=[world1_cs, world2_cs] + coordinate_systems=[world1_cs, world2_cs], ) assert scene._graph is not None assert len(scene._graph.graph.nodes) == 4 scene.to_ome_zarr(str(self.path / "test_scene_with_cs.zarr"), overwrite=True) - scene_read = OMEZarrScene.from_ome_zarr(str(self.path / "test_scene_with_cs.zarr")) + scene_read = OMEZarrScene.from_ome_zarr( + str(self.path / "test_scene_with_cs.zarr") + ) assert scene_read._graph is not None assert len(scene_read._graph.graph.nodes) == 4 # open zarr group and check metadata - zarr_group = zarr.open_group(str(self.path / "test_scene_with_cs.zarr"), mode="r") + zarr_group = zarr.open_group( + str(self.path / "test_scene_with_cs.zarr"), mode="r" + ) assert "ome" in zarr_group.attrs ome_metadata = zarr_group.attrs["ome"] assert "scene" in ome_metadata @@ -143,6 +162,8 @@ def test_create_scene_with_coordinate_systems(self, transform): assert "coordinateSystems" in ome_metadata["scene"] assert len(ome_metadata["scene"]["coordinateSystems"]) == 2 + if __name__ == "__main__": import pytest - pytest.main([__file__]) \ No newline at end of file + + pytest.main([__file__]) From aa8ea93e62ebafc5ec4ad57782852b2c4ba0fcda Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:29:51 +0200 Subject: [PATCH 32/95] deps: pin tnd to higher version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ae29c556..f64bbf62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "scikit-image>=0.19.0", "toolz", "rangehttpserver", - "transformnd >= 0.3.0", + "transformnd >= 0.6.0", "ome-zarr-models==1.8.0rc0", "Deprecated", ] From 0ea7b731f45e6314a472c2b67da4e7769facdc45 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:32:49 +0200 Subject: [PATCH 33/95] unpin tnd --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f64bbf62..2257c158 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "scikit-image>=0.19.0", "toolz", "rangehttpserver", - "transformnd >= 0.6.0", + "transformnd", "ome-zarr-models==1.8.0rc0", "Deprecated", ] From 60ff588ce0d766ac7c90dbd5e62c7065b991e8a9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:43:01 +0200 Subject: [PATCH 34/95] Revert "unpin tnd" This reverts commit 0ea7b731f45e6314a472c2b67da4e7769facdc45. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2257c158..f64bbf62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "scikit-image>=0.19.0", "toolz", "rangehttpserver", - "transformnd", + "transformnd >= 0.6.0", "ome-zarr-models==1.8.0rc0", "Deprecated", ] From ca5ea280feda724c8a6346a768a912a7b8a82611 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:47:47 +0200 Subject: [PATCH 35/95] deps: try higher python --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f64bbf62..540d9836 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ description="Implementation of images in Zarr files." license = "BSD-2-Clause" license-files = ["LICENSE"] -requires-python = ">3.11" +requires-python = ">=3.12, <4.0" dependencies = [ "numpy", From 984777f68d3da0b0dc18256bb5e2eb32b1e792c1 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:48:52 +0200 Subject: [PATCH 36/95] deps: pin to newer pythons --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c609bc6b..da68ccd0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,12 +10,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.11', '3.12', '3.13'] + python-version: ['3.12', '3.13', '3.14'] os: ['windows-latest', 'macos-latest', 'ubuntu-latest'] dask-version: [ 'latest' ] include: - os: ubuntu-latest - python-version: '3.11' + python-version: '3.12' dask-version: '2025.2.0' steps: - uses: actions/checkout@v4 From f02f3a49a6f650e7ef00c3d6af4f7fb5305a472a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:25:43 +0200 Subject: [PATCH 37/95] chore: style --- ome_zarr/classes/scene.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 7827452d..24d7abc0 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -133,9 +133,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil - import tqdm - from ..utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): From 38516ea9949b0797670cd7d6b252e92dd71c7cb4 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:25:50 +0200 Subject: [PATCH 38/95] deps: add tqdm to dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 540d9836..b4d6235f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "transformnd >= 0.6.0", "ome-zarr-models==1.8.0rc0", "Deprecated", + "tqdm", ] classifiers = [ "Development Status :: 4 - Beta", From 32b429b8f2c3a66d240d7adea6c754a2e0c1cd12 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:26:05 +0000 Subject: [PATCH 39/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 24d7abc0..7827452d 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -133,7 +133,9 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil + import tqdm + from ..utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): From 29aea78c17daf877bed8a25eac78d3aae4552056 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:27:47 +0200 Subject: [PATCH 40/95] chore: typo and rerun --- .../advanced/transforms/create_scenes.ipynb | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index cf7b9c01..38f6a728 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "id": "a49ec1c2", "metadata": {}, "outputs": [], @@ -29,30 +29,22 @@ "id": "98bbabd7", "metadata": {}, "source": [ - "We create some sample data, whioch we will store as a tiled layout in a scene zarr group:" + "We create some sample data, which we will store as a tiled layout in a scene zarr group:" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "id": "e3023c95", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - }, { "data": { "text/plain": [ "(512, 512)" ] }, - "execution_count": 2, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -72,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "id": "017d4c59", "metadata": {}, "outputs": [], @@ -99,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "id": "994674c2", "metadata": {}, "outputs": [], @@ -124,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "id": "d6329caa", "metadata": {}, "outputs": [], @@ -167,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "id": "d05be093", "metadata": {}, "outputs": [ @@ -175,7 +167,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 4/4 [00:00<00:00, 12.34it/s]\n" + "Writing images: 100%|██████████| 4/4 [00:00<00:00, 5.91it/s]\n" ] } ], @@ -188,6 +180,27 @@ "\n", "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "8b87b923", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scene._graph.get_sequence(source_space=\"img1:physical\", target_space=\":world\")" + ] } ], "metadata": { From e7529fd903016044b9885457d5bc279b5566de4e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:28:13 +0000 Subject: [PATCH 41/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/advanced/transforms/create_scenes.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index 38f6a728..33d2580e 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -167,7 +167,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Writing images: 100%|██████████| 4/4 [00:00<00:00, 5.91it/s]\n" + "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 4/4 [00:00<00:00, 5.91it/s]\n" ] } ], From ebd4605545dcf03dc1295b396148c912134782b0 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:35:46 +0200 Subject: [PATCH 42/95] chore: undo python<4 version pin --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b4d6235f..0d0faf8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ description="Implementation of images in Zarr files." license = "BSD-2-Clause" license-files = ["LICENSE"] -requires-python = ">=3.12, <4.0" +requires-python = ">=3.12" dependencies = [ "numpy", From 208f62718de8423ab45a72f0f2ec9666cb214635 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:41:15 +0200 Subject: [PATCH 43/95] tests: add more description to test cases --- tests/test_scene.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_scene.py b/tests/test_scene.py index 55b7c28c..95604eec 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -8,6 +8,12 @@ class TestScene: + """ + Wrapper class for testing ome zarr scene functionality. + Every test in this class can make use of the `self.path` + attribute which is a temporary directory, + which is automatically created and destroyed for each test. + """ TRANSFORMS = [ {"type": "scale", "scale": [1.0, 1.0]}, {"type": "translation", "translation": [0.0, 0.0]}, @@ -26,11 +32,17 @@ def initdir(self, tmpdir): self.group_v3 = root_v3.create_group("test") def create_data(self, shape, dtype=np.uint8, mean_val=10): + """create some dummy testing data of defined shape and type, + with a given mean value""" rng = np.random.default_rng(0) return rng.poisson(mean_val, size=shape).astype(dtype) @pytest.mark.parametrize("transform", TRANSFORMS) def test_create_scene_without_coordinate_systems(self, transform): + """ + Create a scene with two images and a single coordinate transformation + between them. The data is saved and loaded back in the test. + """ shape = (64, 64) img_a = OMEZarrImage( data=self.create_data(shape), @@ -59,23 +71,31 @@ def test_create_scene_without_coordinate_systems(self, transform): scene.to_ome_zarr("test_scene.zarr", overwrite=True) + # check that the graph is created correctly assert scene._graph is not None + + # check that the graph has the correct number of nodes + # (aka coordinate systems) assert len(scene._graph.graph.nodes) == 2 # traverse graph tf = scene._graph.get_sequence( f"{img_a.name}:physical", f"{img_b.name}:physical" ) + + # check that the transform graph can be traversed (i.e. transform is not None) assert tf is not None # write to disk and read back scene.to_ome_zarr(str(self.path / "test_scene.zarr"), overwrite=True) scene_read = OMEZarrScene.from_ome_zarr(str(self.path / "test_scene.zarr")) + # check that the graph is created correctly on read assert scene_read._graph is not None assert len(scene_read._graph.graph.nodes) == 2 # open the zarr group and check the metadata + # and check that the correct metadata fields are present in the store zarr_group = zarr.open_group(str(self.path / "test_scene.zarr"), mode="r") assert "ome" in zarr_group.attrs ome_metadata = zarr_group.attrs["ome"] @@ -87,10 +107,16 @@ def test_create_scene_without_coordinate_systems(self, transform): assert len(ome_metadata["scene"]["coordinateTransformations"]) == 1 transform_md = ome_metadata["scene"]["coordinateTransformations"][0] + + # make sure that the loaded transform is the same as the original assert transform_md == transform @pytest.mark.parametrize("transform", TRANSFORMS) def test_create_scene_with_coordinate_systems(self, transform): + """ + Create a scene with two images and three coordinate transformations + between them. The data is saved and loaded back in the test. + """ shape = (64, 64) img_a = OMEZarrImage( data=self.create_data(shape), @@ -140,6 +166,8 @@ def test_create_scene_with_coordinate_systems(self, transform): coordinate_systems=[world1_cs, world2_cs], ) + # check that the graph is created correctly + # and has the correct number of nodes (coordinate systems) assert scene._graph is not None assert len(scene._graph.graph.nodes) == 4 @@ -148,6 +176,8 @@ def test_create_scene_with_coordinate_systems(self, transform): str(self.path / "test_scene_with_cs.zarr") ) + # check that the graph is created correctly on read + # and has the correct number of nodes (coordinate systems) assert scene_read._graph is not None assert len(scene_read._graph.graph.nodes) == 4 @@ -155,6 +185,8 @@ def test_create_scene_with_coordinate_systems(self, transform): zarr_group = zarr.open_group( str(self.path / "test_scene_with_cs.zarr"), mode="r" ) + + # make sure that the correct metadata fields are present in the store assert "ome" in zarr_group.attrs ome_metadata = zarr_group.attrs["ome"] assert "scene" in ome_metadata From 5994ac8fd6dc614ac7d676ac4d8cd53a75319adc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:41:29 +0000 Subject: [PATCH 44/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_scene.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_scene.py b/tests/test_scene.py index 95604eec..8380eec9 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -14,6 +14,7 @@ class TestScene: attribute which is a temporary directory, which is automatically created and destroyed for each test. """ + TRANSFORMS = [ {"type": "scale", "scale": [1.0, 1.0]}, {"type": "translation", "translation": [0.0, 0.0]}, From c306121ad9caab69d5cc0d0b75142f8ecaacb0de Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:03:23 +0200 Subject: [PATCH 45/95] docs: Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/source/_toc.yml | 4 ++-- docs/source/advanced/transforms/create_scenes.ipynb | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/source/_toc.yml b/docs/source/_toc.yml index 8665f6c6..65057272 100644 --- a/docs/source/_toc.yml +++ b/docs/source/_toc.yml @@ -24,8 +24,8 @@ parts: - file: advanced/transforms/index sections: - file: advanced/transforms/add_transforms_to_multiscales - - file: advanced/create_scenes - - file: advanced/reading_scenes + - file: advanced/transforms/create_scenes + - file: advanced/transforms/reading_scenes - file: advanced/sharding - file: advanced/build_custom_pyramid diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index 33d2580e..df716b2a 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -9,7 +9,7 @@ "\n", "(advanced:create-scenes)=\n", "\n", - "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.NgffScene` class." + "This tutorial demonstrates basic usage around writing and reading [Ngff Scenes](https://ngff.openmicroscopy.org/specifications/dev/index.html#scene-metadata) using the {py:class}`ome_zarr.classes.scene.OMEZarrScene` class." ] }, { @@ -59,7 +59,7 @@ "id": "71e31e2a", "metadata": {}, "source": [ - "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.NgffMultiscales`:" + "First, we cut the image into four tiles and convert them into instances of {py:class}`ome_zarr.classes.image.OMEZarrMultiscale`:" ] }, { @@ -110,9 +110,8 @@ "id": "c1d629be", "metadata": {}, "source": [ - "We then define transdlations that move each tile into the appropriate position in the world coordinate system.\n", - "In thisexamples, these are simple translations in the y and x dimensions:" - ] + "We then define translations that move each tile into the appropriate position in the world coordinate system.\n", + "In this example, these are simple translations in the y and x dimensions:" }, { "cell_type": "code", From 550a93cd6e8a25737fcc1827b662a80e4b50ee10 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:05:35 +0200 Subject: [PATCH 46/95] fix: correct PEP style of dep --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0d0faf8d..d0d127b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "scikit-image>=0.19.0", "toolz", "rangehttpserver", - "transformnd >= 0.6.0", + "transformnd>=0.6.0", "ome-zarr-models==1.8.0rc0", "Deprecated", "tqdm", From d4b5f370e00645fbf331e0529069fcfbb000c2a9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:05:46 +0200 Subject: [PATCH 47/95] test: avoid mutating in-place --- tests/test_scene.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_scene.py b/tests/test_scene.py index 8380eec9..51634f98 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -62,6 +62,8 @@ def test_create_scene_without_coordinate_systems(self, transform): img_a_ms = OMEZarrMultiscale(image=img_a) img_b_ms = OMEZarrMultiscale(image=img_b) + # avoid leaking transform mutations into other tests + transform = transform.copy() transform["input"] = {"name": "physical", "path": "imageA"} transform["output"] = {"name": "physical", "path": "imageB"} From e9fdfd806242d458a75a30b430e9aa8e284f1515 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:12:04 +0200 Subject: [PATCH 48/95] tests: use fixtures+functions instead of testing class --- tests/test_scene.py | 372 +++++++++++++++++++++----------------------- 1 file changed, 181 insertions(+), 191 deletions(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index 51634f98..a83d0010 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -1,5 +1,3 @@ -import pathlib - import numpy as np import pytest import zarr @@ -7,198 +5,190 @@ from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene -class TestScene: +TRANSFORMS = [ + {"type": "scale", "scale": [1.0, 1.0]}, + {"type": "translation", "translation": [0.0, 0.0]}, + {"type": "rotation", "rotation": [[0.0, -1.0], [1.0, 0.0]]}, + {"type": "affine", "affine": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]}, + {"type": "mapAxis", "mapAxis": [1, 0]}, +] + + +@pytest.fixture +def test_data_dir(tmp_path): + """Create a temporary directory with zarr v3 group for testing.""" + path = tmp_path / "data" / "v3" + root_v3 = zarr.open_group(path, mode="w", zarr_format=3) + root_v3.create_group("test") + return path + + +def create_data(shape, dtype=np.uint8, mean_val=10): + """Create dummy testing data of defined shape and type, + with a given mean value.""" + rng = np.random.default_rng(0) + return rng.poisson(mean_val, size=shape).astype(dtype) + + +@pytest.mark.parametrize("transform", TRANSFORMS) +def test_create_scene_without_coordinate_systems(test_data_dir, transform): """ - Wrapper class for testing ome zarr scene functionality. - Every test in this class can make use of the `self.path` - attribute which is a temporary directory, - which is automatically created and destroyed for each test. + Create a scene with two images and a single coordinate transformation + between them. The data is saved and loaded back in the test. """ - - TRANSFORMS = [ - {"type": "scale", "scale": [1.0, 1.0]}, - {"type": "translation", "translation": [0.0, 0.0]}, - {"type": "rotation", "rotation": [[0.0, -1.0], [1.0, 0.0]]}, - {"type": "affine", "affine": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]}, - {"type": "mapAxis", "mapAxis": [1, 0]}, - ] - - @pytest.fixture(autouse=True) - def initdir(self, tmpdir): - self.path = pathlib.Path(tmpdir.mkdir("data")) - - # let's create zarr v3 group too... - self.path = self.path / "v3" - root_v3 = zarr.open_group(self.path, mode="w", zarr_format=3) - self.group_v3 = root_v3.create_group("test") - - def create_data(self, shape, dtype=np.uint8, mean_val=10): - """create some dummy testing data of defined shape and type, - with a given mean value""" - rng = np.random.default_rng(0) - return rng.poisson(mean_val, size=shape).astype(dtype) - - @pytest.mark.parametrize("transform", TRANSFORMS) - def test_create_scene_without_coordinate_systems(self, transform): - """ - Create a scene with two images and a single coordinate transformation - between them. The data is saved and loaded back in the test. - """ - shape = (64, 64) - img_a = OMEZarrImage( - data=self.create_data(shape), - name="imageA", - axes=["y", "x"], - scale={"y": 1.0, "x": 1.0}, - ) - - img_b = OMEZarrImage( - data=self.create_data(shape), - name="imageB", - axes=["y", "x"], - scale={"y": 1.0, "x": 1.0}, - ) - - img_a_ms = OMEZarrMultiscale(image=img_a) - img_b_ms = OMEZarrMultiscale(image=img_b) - - # avoid leaking transform mutations into other tests - transform = transform.copy() - transform["input"] = {"name": "physical", "path": "imageA"} - transform["output"] = {"name": "physical", "path": "imageB"} - - scene = OMEZarrScene( - images=[img_a_ms, img_b_ms], - coordinate_transformations=[transform], - ) - - scene.to_ome_zarr("test_scene.zarr", overwrite=True) - - # check that the graph is created correctly - assert scene._graph is not None - - # check that the graph has the correct number of nodes - # (aka coordinate systems) - assert len(scene._graph.graph.nodes) == 2 - - # traverse graph - tf = scene._graph.get_sequence( - f"{img_a.name}:physical", f"{img_b.name}:physical" - ) - - # check that the transform graph can be traversed (i.e. transform is not None) - assert tf is not None - - # write to disk and read back - scene.to_ome_zarr(str(self.path / "test_scene.zarr"), overwrite=True) - scene_read = OMEZarrScene.from_ome_zarr(str(self.path / "test_scene.zarr")) - - # check that the graph is created correctly on read - assert scene_read._graph is not None - assert len(scene_read._graph.graph.nodes) == 2 - - # open the zarr group and check the metadata - # and check that the correct metadata fields are present in the store - zarr_group = zarr.open_group(str(self.path / "test_scene.zarr"), mode="r") - assert "ome" in zarr_group.attrs - ome_metadata = zarr_group.attrs["ome"] - assert "scene" in ome_metadata - assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" - - # check transforms - assert "coordinateTransformations" in ome_metadata["scene"] - assert len(ome_metadata["scene"]["coordinateTransformations"]) == 1 - - transform_md = ome_metadata["scene"]["coordinateTransformations"][0] - - # make sure that the loaded transform is the same as the original - assert transform_md == transform - - @pytest.mark.parametrize("transform", TRANSFORMS) - def test_create_scene_with_coordinate_systems(self, transform): - """ - Create a scene with two images and three coordinate transformations - between them. The data is saved and loaded back in the test. - """ - shape = (64, 64) - img_a = OMEZarrImage( - data=self.create_data(shape), - name="imageA", - axes=["y", "x"], - scale={"y": 1.0, "x": 1.0}, - ) - - img_b = OMEZarrImage( - data=self.create_data(shape), - name="imageB", - axes=["y", "x"], - scale={"y": 1.0, "x": 1.0}, - ) - - img_a_ms = OMEZarrMultiscale(image=img_a) - img_b_ms = OMEZarrMultiscale(image=img_b) - - world1_cs = { - "name": "world", - "axes": [ - ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes - ], - } - world2_cs = { - "name": "world2", - "axes": [ - ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes - ], - } - - transform1 = transform.copy() - transform1["input"] = {"name": "physical", "path": "imageA"} - transform1["output"] = {"name": "world"} - - transform2 = transform.copy() - transform2["input"] = {"name": "world"} - transform2["output"] = {"name": "world2"} - - transform3 = transform.copy() - transform3["input"] = {"name": "world2"} - transform3["output"] = {"name": "physical", "path": "imageB"} - - scene = OMEZarrScene( - images=[img_a_ms, img_b_ms], - coordinate_transformations=[transform1, transform2, transform3], - coordinate_systems=[world1_cs, world2_cs], - ) - - # check that the graph is created correctly - # and has the correct number of nodes (coordinate systems) - assert scene._graph is not None - assert len(scene._graph.graph.nodes) == 4 - - scene.to_ome_zarr(str(self.path / "test_scene_with_cs.zarr"), overwrite=True) - scene_read = OMEZarrScene.from_ome_zarr( - str(self.path / "test_scene_with_cs.zarr") - ) - - # check that the graph is created correctly on read - # and has the correct number of nodes (coordinate systems) - assert scene_read._graph is not None - assert len(scene_read._graph.graph.nodes) == 4 - - # open zarr group and check metadata - zarr_group = zarr.open_group( - str(self.path / "test_scene_with_cs.zarr"), mode="r" - ) - - # make sure that the correct metadata fields are present in the store - assert "ome" in zarr_group.attrs - ome_metadata = zarr_group.attrs["ome"] - assert "scene" in ome_metadata - assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" - assert "coordinateSystems" in ome_metadata["scene"] - assert len(ome_metadata["scene"]["coordinateSystems"]) == 2 + shape = (64, 64) + img_a = OMEZarrImage( + data=create_data(shape), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_b = OMEZarrImage( + data=create_data(shape), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + + # avoid leaking transform mutations into other tests + transform = transform.copy() + transform["input"] = {"name": "physical", "path": "imageA"} + transform["output"] = {"name": "physical", "path": "imageB"} + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[transform], + ) + + scene.to_ome_zarr("test_scene.zarr", overwrite=True) + + # check that the graph is created correctly + assert scene._graph is not None + + # check that the graph has the correct number of nodes + # (aka coordinate systems) + assert len(scene._graph.graph.nodes) == 2 + + # traverse graph + tf = scene._graph.get_sequence( + f"{img_a.name}:physical", f"{img_b.name}:physical" + ) + + # check that the transform graph can be traversed (i.e. transform is not None) + assert tf is not None + + # write to disk and read back + scene.to_ome_zarr(str(test_data_dir / "test_scene.zarr"), overwrite=True) + scene_read = OMEZarrScene.from_ome_zarr(str(test_data_dir / "test_scene.zarr")) + + # check that the graph is created correctly on read + assert scene_read._graph is not None + assert len(scene_read._graph.graph.nodes) == 2 + + # open the zarr group and check the metadata + # and check that the correct metadata fields are present in the store + zarr_group = zarr.open_group(str(test_data_dir / "test_scene.zarr"), mode="r") + assert "ome" in zarr_group.attrs + ome_metadata = zarr_group.attrs["ome"] + assert "scene" in ome_metadata + assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" + + # check transforms + assert "coordinateTransformations" in ome_metadata["scene"] + assert len(ome_metadata["scene"]["coordinateTransformations"]) == 1 + + transform_md = ome_metadata["scene"]["coordinateTransformations"][0] + + # make sure that the loaded transform is the same as the original + assert transform_md == transform + +@pytest.mark.parametrize("transform", TRANSFORMS) +def test_create_scene_with_coordinate_systems(test_data_dir, transform): + """ + Create a scene with two images and three coordinate transformations + between them. The data is saved and loaded back in the test. + """ + shape = (64, 64) + img_a = OMEZarrImage( + data=create_data(shape), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_b = OMEZarrImage( + data=create_data(shape), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + + world1_cs = { + "name": "world", + "axes": [ + ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes + ], + } + world2_cs = { + "name": "world2", + "axes": [ + ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes + ], + } + + transform1 = transform.copy() + transform1["input"] = {"name": "physical", "path": "imageA"} + transform1["output"] = {"name": "world"} + + transform2 = transform.copy() + transform2["input"] = {"name": "world"} + transform2["output"] = {"name": "world2"} + + transform3 = transform.copy() + transform3["input"] = {"name": "world2"} + transform3["output"] = {"name": "physical", "path": "imageB"} + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[transform1, transform2, transform3], + coordinate_systems=[world1_cs, world2_cs], + ) + + # check that the graph is created correctly + # and has the correct number of nodes (coordinate systems) + assert scene._graph is not None + assert len(scene._graph.graph.nodes) == 4 + + scene.to_ome_zarr(str(test_data_dir / "test_scene_with_cs.zarr"), overwrite=True) + scene_read = OMEZarrScene.from_ome_zarr( + str(test_data_dir / "test_scene_with_cs.zarr") + ) + + # check that the graph is created correctly on read + # and has the correct number of nodes (coordinate systems) + assert scene_read._graph is not None + assert len(scene_read._graph.graph.nodes) == 4 + + # open zarr group and check metadata + zarr_group = zarr.open_group( + str(test_data_dir / "test_scene_with_cs.zarr"), mode="r" + ) + + # make sure that the correct metadata fields are present in the store + assert "ome" in zarr_group.attrs + ome_metadata = zarr_group.attrs["ome"] + assert "scene" in ome_metadata + assert "version" in ome_metadata and ome_metadata["version"] == "0.6.dev4" + assert "coordinateSystems" in ome_metadata["scene"] + assert len(ome_metadata["scene"]["coordinateSystems"]) == 2 if __name__ == "__main__": - import pytest - pytest.main([__file__]) From ddcad55f5e19958d5298584bbfbe4803a3cc14b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:14:52 +0000 Subject: [PATCH 49/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_scene.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index a83d0010..49b67336 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -4,7 +4,6 @@ from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene - TRANSFORMS = [ {"type": "scale", "scale": [1.0, 1.0]}, {"type": "translation", "translation": [0.0, 0.0]}, @@ -74,9 +73,7 @@ def test_create_scene_without_coordinate_systems(test_data_dir, transform): assert len(scene._graph.graph.nodes) == 2 # traverse graph - tf = scene._graph.get_sequence( - f"{img_a.name}:physical", f"{img_b.name}:physical" - ) + tf = scene._graph.get_sequence(f"{img_a.name}:physical", f"{img_b.name}:physical") # check that the transform graph can be traversed (i.e. transform is not None) assert tf is not None @@ -106,6 +103,7 @@ def test_create_scene_without_coordinate_systems(test_data_dir, transform): # make sure that the loaded transform is the same as the original assert transform_md == transform + @pytest.mark.parametrize("transform", TRANSFORMS) def test_create_scene_with_coordinate_systems(test_data_dir, transform): """ @@ -132,15 +130,11 @@ def test_create_scene_with_coordinate_systems(test_data_dir, transform): world1_cs = { "name": "world", - "axes": [ - ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes - ], + "axes": [ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes], } world2_cs = { "name": "world2", - "axes": [ - ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes - ], + "axes": [ax.model_dump() for ax in img_b_ms.metadata.coordinateSystems[0].axes], } transform1 = transform.copy() From f5683df407879e0b83ec64bc531679aab184665c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:11:43 +0200 Subject: [PATCH 50/95] fix: correct overwrite casing --- ome_zarr/classes/scene.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 7827452d..7aa165ab 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -53,7 +53,6 @@ def __init__( ) self._build_graph() - self._written_image_names = set() def get_coordinate_system( self, name: str, path: str | None = None @@ -143,29 +142,27 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): shutil.rmtree(str(store)) # Open or create zarr group - mode = "a" if overwrite else "w" + mode = "w" if overwrite else "a" zarr_group = zarr.open(store, mode=mode) # Create a subgroup for each image using its path key for img_path, img in tqdm.tqdm(self.images.items(), desc="Writing images"): # Skip if already written (incremental mode) - if not overwrite and img_path in self._written_image_names: + if not overwrite and img_path in zarr_group: continue # Write the image - subgroup = zarr_group.create_group(img_path, overwrite=not overwrite) - img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") - self._written_image_names.add(img_path) + subgroup = zarr_group.create_group(img_path, overwrite=overwrite) + img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") for disp_path, disp_img in (self.coordinates_displacements or {}).items(): # Skip if already written (incremental mode) - if not overwrite and disp_path in self._written_image_names: + if not overwrite and disp_path in zarr_group: continue # Write the displacement image - subgroup = zarr_group.create_group(disp_path, overwrite=not overwrite) - disp_img.to_ome_zarr(subgroup, overwrite=overwrite, version="0.6.dev4") - self._written_image_names.add(disp_path) + subgroup = zarr_group.create_group(disp_path, overwrite=overwrite) + disp_img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") # Always update scene metadata metadata_dict = self.metadata.model_dump() From 60af2637ad636e2609b2575f9a2639439050f49c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:11:56 +0200 Subject: [PATCH 51/95] tests: added a test for appending data to a scene --- tests/test_scene.py | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/test_scene.py b/tests/test_scene.py index a83d0010..cd4458f2 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -190,5 +190,114 @@ def test_create_scene_with_coordinate_systems(test_data_dir, transform): assert len(ome_metadata["scene"]["coordinateSystems"]) == 2 +def test_appending_scene(test_data_dir): + """ + Create a scene with two images and a single coordinate transformation + between them. The data is saved and loaded back. + We then append a third image to the scene and make sure the graph is updated. + We then save the scene again and ensure that we are writing only what's new + (new data and metadata) + """ + img_a = OMEZarrImage( + data=create_data((64, 64)), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + img_b = OMEZarrImage( + data=create_data((64, 64)), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + img_c = OMEZarrImage( + data=create_data((64, 64)), + name="imageC", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + img_c_ms = OMEZarrMultiscale(image=img_c) + + world_cs = { + "name": "world", + "axes": [ + ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes + ], + } + + tf1 = { + "type": "scale", + "scale": [1.0, 1.0], + "input": {"name": "physical", "path": "imageA"}, + "output": {"name": "world"}, + } + + tf2 = { + "type": "scale", + "scale": [1.0, 1.0], + "input": {"name": "world"}, + "output": {"name": "physical", "path": "imageB"}, + } + + tf3 = { + "type": "scale", + "scale": [1.0, 1.0], + "input": {"name": "world"}, + "output": {"name": "physical", "path": "imageC"}, + } + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[tf1, tf2], + coordinate_systems=[world_cs], + ) + + scene.to_ome_zarr( + str(test_data_dir / "test_scene_append.zarr"), + overwrite=True + ) + + # now we load the scene and append a new image to it + scene_read = OMEZarrScene.from_ome_zarr( + str(test_data_dir / "test_scene_append.zarr") + ) + + + new_scene = OMEZarrScene( + images=list(scene_read.images.values()) + [img_c_ms], + coordinate_transformations=list(scene_read.coordinate_transformations) + [tf3], + coordinate_systems=[world_cs], + ) + + new_scene.to_ome_zarr( + str(test_data_dir / "test_scene_append.zarr"), + overwrite=False + ) + + # check that the graph is built correctly + assert new_scene._graph is not None + assert len(new_scene._graph.graph.nodes) == 3 + + # check that the data is written and not empty + zarr_group = zarr.open_group( + str(test_data_dir / "test_scene_append.zarr"), mode="r" + ) + assert "imageA" in zarr_group and "s0" in zarr_group["imageA"] + assert "imageB" in zarr_group and "s0" in zarr_group["imageB"] + assert "imageC" in zarr_group and "s0" in zarr_group["imageC"] + + # check that the metadata is written and correct + assert "ome" in zarr_group.attrs + ome_metadata = zarr_group.attrs["ome"] + assert "scene" in ome_metadata + scene_metadata = ome_metadata["scene"] + assert "coordinateTransformations" in scene_metadata + assert "coordinateSystems" in scene_metadata + assert len(scene_metadata["coordinateTransformations"]) == 3 + assert len(scene_metadata["coordinateSystems"]) == 1 + if __name__ == "__main__": pytest.main([__file__]) From fe748c735daa12a89efe8c5a201459c8755a2b28 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:12:18 +0000 Subject: [PATCH 52/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_scene.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index e7aa0e62..9bf2f936 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -217,9 +217,7 @@ def test_appending_scene(test_data_dir): world_cs = { "name": "world", - "axes": [ - ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes - ], + "axes": [ax.model_dump() for ax in img_a_ms.metadata.coordinateSystems[0].axes], } tf1 = { @@ -249,17 +247,13 @@ def test_appending_scene(test_data_dir): coordinate_systems=[world_cs], ) - scene.to_ome_zarr( - str(test_data_dir / "test_scene_append.zarr"), - overwrite=True - ) - + scene.to_ome_zarr(str(test_data_dir / "test_scene_append.zarr"), overwrite=True) + # now we load the scene and append a new image to it scene_read = OMEZarrScene.from_ome_zarr( str(test_data_dir / "test_scene_append.zarr") ) - new_scene = OMEZarrScene( images=list(scene_read.images.values()) + [img_c_ms], coordinate_transformations=list(scene_read.coordinate_transformations) + [tf3], @@ -267,8 +261,7 @@ def test_appending_scene(test_data_dir): ) new_scene.to_ome_zarr( - str(test_data_dir / "test_scene_append.zarr"), - overwrite=False + str(test_data_dir / "test_scene_append.zarr"), overwrite=False ) # check that the graph is built correctly @@ -293,5 +286,6 @@ def test_appending_scene(test_data_dir): assert len(scene_metadata["coordinateTransformations"]) == 3 assert len(scene_metadata["coordinateSystems"]) == 1 + if __name__ == "__main__": pytest.main([__file__]) From 03000c84e19f4d83ba67c20a6cf9b05589545b7d Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:24:41 +0200 Subject: [PATCH 53/95] tests: fix test --- tests/test_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index e7aa0e62..2e30d134 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -273,7 +273,7 @@ def test_appending_scene(test_data_dir): # check that the graph is built correctly assert new_scene._graph is not None - assert len(new_scene._graph.graph.nodes) == 3 + assert len(new_scene._graph.graph.nodes) == 4 # check that the data is written and not empty zarr_group = zarr.open_group( From 8355ab18aa5a828d184f3d30c46e3d880d6afa1e Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:53:27 +0200 Subject: [PATCH 54/95] refactor: set up spaces before parsing transforms --- ome_zarr/classes/scene.py | 56 +++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 7aa165ab..5a1f9652 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -305,6 +305,33 @@ def _ozmp_tf_to_tnd( """ import numpy as np + if transform.input is not None: + input_path = ( + transform.input.path if transform.input.path is not None else "" + ) + output_path = ( + transform.output.path if transform.output.path is not None else "" + ) + + # zarr_context prepends path with relative path from root + # to keep track of global location of coordinate systems in the zarr store + if zarr_context != "" and input_path != "": + input_path = f"{zarr_context}/{input_path}" + elif zarr_context != "": + input_path = zarr_context + + if zarr_context != "" and output_path != "": + output_path = f"{zarr_context}/{output_path}" + elif zarr_context != "": + output_path = zarr_context + + spaces = tnd.Spaces( + f"{input_path}:{transform.input.name}", + f"{output_path}:{transform.output.name}", + ) + else: + spaces = tnd.Spaces(None, None) + tnd_transform = None # Example for an affine transformation (this will depend on the actual structure of AnyTransform) if transform.type == "affine": @@ -360,31 +387,8 @@ def _ozmp_tf_to_tnd( self._ozmp_tf_to_tnd(sub_tf, zarr_context) for sub_tf in sub_transformations ] - tnd_transform = tnd.base.TransformSequence(tnd_sub_transforms) - - if transform.input is not None and tnd_transform is not None: - input_path = ( - transform.input.path if transform.input.path is not None else "" - ) - output_path = ( - transform.output.path if transform.output.path is not None else "" - ) - - # zarr_context prepends path with relative path from root - # to keep track of global location of coordinate systems in the zarr store - if zarr_context != "" and input_path != "": - input_path = f"{zarr_context}/{input_path}" - elif zarr_context != "": - input_path = zarr_context - - if zarr_context != "" and output_path != "": - output_path = f"{zarr_context}/{output_path}" - elif zarr_context != "": - output_path = zarr_context - - tnd_transform.spaces = tnd.Spaces( - f"{input_path}:{transform.input.name}", - f"{output_path}:{transform.output.name}", - ) + tnd_transform = tnd.base.TransformSequence( + tnd_sub_transforms, + spaces=spaces,) return tnd_transform From 403f7f59659f71b1170804e6ae251e8922d6e7cd Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:54:06 +0200 Subject: [PATCH 55/95] set up spaces first --- ome_zarr/classes/scene.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 5a1f9652..153501b6 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -337,11 +337,15 @@ def _ozmp_tf_to_tnd( if transform.type == "affine": aff = np.asarray(transform.affine) if aff.shape[0] == aff.shape[1]: - tnd_transform = tnd.transforms.Affine(transform.affine) + tnd_transform = tnd.transforms.Affine( + transform.affine, + spaces=spaces, + ) else: aff = np.eye(max(aff.shape)) aff[: aff.shape[0], : aff.shape[1]] = aff - tnd_transform = tnd.transforms.Affine(aff) + tnd_transform = tnd.transforms.Affine( + aff, spaces=spaces) elif transform.type == "displacements": path_to_dfield = transform.path if transform.path is not None else "" @@ -355,18 +359,29 @@ def _ozmp_tf_to_tnd( list(dfield.images[0].scale.values())[1:] ), vector_axis=0, + spaces=spaces, ) elif transform.type == "mapAxis": - tnd_transform = tnd.transforms.MapAxis(list(transform.mapAxis)) + tnd_transform = tnd.transforms.MapAxis( + list(transform.mapAxis), + spaces=spaces, + ) + elif transform.type == "scale": - tnd_transform = tnd.transforms.Scale(transform.scale) + tnd_transform = tnd.transforms.Scale( + transform.scale, spaces=spaces + ) elif transform.type == "translation": - tnd_transform = tnd.transforms.Translate(transform.translation) + tnd_transform = tnd.transforms.Translate( + transform.translation, spaces=spaces + ) elif transform.type == "rotation": - tnd_transform = tnd.transforms.Affine.from_linear_map(transform.rotation) + tnd_transform = tnd.transforms.Affine.from_linear_map( + transform.rotation, spaces=spaces + ) elif transform.type == "byDimension": sub_transformations = transform.transformations @@ -379,7 +394,9 @@ def _ozmp_tf_to_tnd( for sub_tf in sub_transformations ] tnd_transform = tnd.transforms.ByDimension( - subtransforms=tnd_sub_transforms, fill_identity=0 + subtransforms=tnd_sub_transforms, + fill_identity=0, + spaces=spaces, ) elif transform.type == "sequence": sub_transformations = transform.transformations From cf64333cb9aa946f074110c98c4c36935157b406 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:54:56 +0200 Subject: [PATCH 56/95] feat: retrieve coordinate systems during ozmp->tnd parsing to know dims --- ome_zarr/classes/scene.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 153501b6..c97e33e1 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -94,9 +94,21 @@ def _build_graph(self): # Add scene-level transformations (empty context = root level) for tf in self.coordinate_transformations: if tf.type == "sequence": - tnd_transform = self._ozmp_tf_to_tnd(tf).simplify() + source_cs = self.get_coordinate_system( + tf.input.name, tf.input.path + ) + target_cs = self.get_coordinate_system( + tf.output.name, tf.output.path + ) + tnd_transform = self._ozmp_tf_to_tnd( + tf, zarr_context="", + source_cs=source_cs, + target_cs=target_cs, + ).simplify() else: - tnd_transform = self._ozmp_tf_to_tnd(tf) + tnd_transform = self._ozmp_tf_to_tnd( + tf, zarr_context="", + source_cs=None, target_cs=None) self._graph.add_transform(tnd_transform) # check if input/output are defined @@ -298,6 +310,8 @@ def _ozmp_tf_to_tnd( self, transform: AnyTransform, zarr_context: str = "", + source_cs: CoordinateSystem | None = None, + target_cs: CoordinateSystem | None = None, ) -> tnd.base.Transform: """ Convert an OME-Zarr coordinate transformation to a transformnd Transform object. @@ -367,6 +381,14 @@ def _ozmp_tf_to_tnd( spaces=spaces, ) + elif transform.type == "projectAxis": + tnd_transform = tnd.transforms.ProjectAxis( + created=transform.createdOutputs, + dropped=transform.droppedInputs, + spaces=spaces, + source_ndim=len(source_cs.axes) if source_cs is not None else None, + target_ndim=len(target_cs.axes) if target_cs is not None else None, + ) elif transform.type == "scale": tnd_transform = tnd.transforms.Scale( @@ -401,7 +423,7 @@ def _ozmp_tf_to_tnd( elif transform.type == "sequence": sub_transformations = transform.transformations tnd_sub_transforms = [ - self._ozmp_tf_to_tnd(sub_tf, zarr_context) + self._ozmp_tf_to_tnd(sub_tf, zarr_context, source_cs, target_cs) for sub_tf in sub_transformations ] tnd_transform = tnd.base.TransformSequence( From 4d097128b1c187644a79bb6c5efb0009902a93dd Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:55:08 +0200 Subject: [PATCH 57/95] docs: add notebook for changing dims --- docs/source/_toc.yml | 1 + .../advanced/transforms/scene_2d_to_3d.ipynb | 201 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 docs/source/advanced/transforms/scene_2d_to_3d.ipynb diff --git a/docs/source/_toc.yml b/docs/source/_toc.yml index 65057272..fcd90be0 100644 --- a/docs/source/_toc.yml +++ b/docs/source/_toc.yml @@ -26,6 +26,7 @@ parts: - file: advanced/transforms/add_transforms_to_multiscales - file: advanced/transforms/create_scenes - file: advanced/transforms/reading_scenes + - file: advanced/transforms/scene_2d_to_3d - file: advanced/sharding - file: advanced/build_custom_pyramid diff --git a/docs/source/advanced/transforms/scene_2d_to_3d.ipynb b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb new file mode 100644 index 00000000..303b863d --- /dev/null +++ b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb @@ -0,0 +1,201 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "002e4aac", + "metadata": {}, + "source": [ + "# Transforms with changing dimensionality\n", + "\n", + "A particular and important kind of transforms,\n", + "are transforms that change the dimensionality of the data.\n", + "Common examples for this case are:\n", + "- 2D to 3D transforms, e.g. for aligning a 2D slice to a 3D volume\n", + "- 2D + channel to 2D transforms, e.g. for aligning a 2D slice with multiple channels to a 2D slice with a single channel\n", + "\n", + "The transform that expresses this change in dimensionality is the [`ProjectAxis` transform](https://ngff.openmicroscopy.org/specifications/dev/index.html#projectaxis).\n", + "\n", + "This tutorial demonstrates its usage for the case of a 2D to 3D transform,\n", + "where a 2D slice is aligned to a 3D volume." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac4f951d", + "metadata": {}, + "outputs": [], + "source": [ + "from ome_zarr import OMEZarrScene, OMEZarrImage, OMEZarrMultiscale\n", + "from skimage import data\n", + "import transformnd as tnd" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ede90a69", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "data": { + "text/plain": [ + "(2, 60, 256, 256)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "img = data.cells3d().transpose((1, 0, 2, 3))\n", + "img.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9445304", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(256, 256)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "some_slice = img[0, 30, :, :]\n", + "some_slice.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "454e972b", + "metadata": {}, + "outputs": [], + "source": [ + "ngff_img = OMEZarrImage(\n", + " data=img,\n", + " axes=[\"c\", \"z\", \"y\", \"x\"],\n", + " scale={\"c\": 1, \"z\": 1, \"y\": 1, \"x\": 1},\n", + " name=\"cells3d\"\n", + ")\n", + "\n", + "ngff_ms = OMEZarrMultiscale(\n", + " image=ngff_img,\n", + ")\n", + "\n", + "slice_img = OMEZarrImage(\n", + " data=some_slice,\n", + " axes=[\"y\", \"x\"],\n", + " scale={\"y\": 1, \"x\": 1},\n", + " name=\"cells3d_slice\"\n", + ")\n", + "\n", + "slice_ms = OMEZarrMultiscale(\n", + " image=slice_img,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a7b4469f", + "metadata": {}, + "outputs": [], + "source": [ + "transform_to_3d = {\n", + " \"type\": \"sequence\",\n", + " \"input\": {\"path\": \"cells3d_slice\", \"name\": \"physical\"},\n", + " \"output\": {\"path\": \"cells3d\", \"name\": \"physical\"},\n", + " \"transformations\": [\n", + " {\n", + " \"type\": \"projectAxis\",\n", + " \"createdOutputs\": [0, 1]\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 30, 0, 0]\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "abde22ea", + "metadata": {}, + "outputs": [], + "source": [ + "scene = OMEZarrScene(\n", + " images=[ngff_ms, slice_ms],\n", + " coordinate_transformations=[transform_to_3d]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8d723932", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Writing images: 100%|██████████| 2/2 [00:01<00:00, 1.98it/s]\n" + ] + } + ], + "source": [ + "scene.to_ome_zarr(\"scene_2d_to_3d.zarr\", overwrite=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "062391f5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From a354a121e8491ee33a6c206f384057efc7c5bca6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:55:30 +0000 Subject: [PATCH 58/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../advanced/transforms/scene_2d_to_3d.ipynb | 4 +-- ome_zarr/classes/scene.py | 29 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/docs/source/advanced/transforms/scene_2d_to_3d.ipynb b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb index 303b863d..45c97c4a 100644 --- a/docs/source/advanced/transforms/scene_2d_to_3d.ipynb +++ b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb @@ -26,9 +26,9 @@ "metadata": {}, "outputs": [], "source": [ - "from ome_zarr import OMEZarrScene, OMEZarrImage, OMEZarrMultiscale\n", "from skimage import data\n", - "import transformnd as tnd" + "\n", + "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" ] }, { diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index c97e33e1..495d4283 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -94,21 +94,18 @@ def _build_graph(self): # Add scene-level transformations (empty context = root level) for tf in self.coordinate_transformations: if tf.type == "sequence": - source_cs = self.get_coordinate_system( - tf.input.name, tf.input.path - ) - target_cs = self.get_coordinate_system( - tf.output.name, tf.output.path - ) + source_cs = self.get_coordinate_system(tf.input.name, tf.input.path) + target_cs = self.get_coordinate_system(tf.output.name, tf.output.path) tnd_transform = self._ozmp_tf_to_tnd( - tf, zarr_context="", + tf, + zarr_context="", source_cs=source_cs, target_cs=target_cs, ).simplify() else: tnd_transform = self._ozmp_tf_to_tnd( - tf, zarr_context="", - source_cs=None, target_cs=None) + tf, zarr_context="", source_cs=None, target_cs=None + ) self._graph.add_transform(tnd_transform) # check if input/output are defined @@ -354,12 +351,11 @@ def _ozmp_tf_to_tnd( tnd_transform = tnd.transforms.Affine( transform.affine, spaces=spaces, - ) + ) else: aff = np.eye(max(aff.shape)) aff[: aff.shape[0], : aff.shape[1]] = aff - tnd_transform = tnd.transforms.Affine( - aff, spaces=spaces) + tnd_transform = tnd.transforms.Affine(aff, spaces=spaces) elif transform.type == "displacements": path_to_dfield = transform.path if transform.path is not None else "" @@ -391,14 +387,12 @@ def _ozmp_tf_to_tnd( ) elif transform.type == "scale": - tnd_transform = tnd.transforms.Scale( - transform.scale, spaces=spaces - ) + tnd_transform = tnd.transforms.Scale(transform.scale, spaces=spaces) elif transform.type == "translation": tnd_transform = tnd.transforms.Translate( transform.translation, spaces=spaces - ) + ) elif transform.type == "rotation": tnd_transform = tnd.transforms.Affine.from_linear_map( @@ -428,6 +422,7 @@ def _ozmp_tf_to_tnd( ] tnd_transform = tnd.base.TransformSequence( tnd_sub_transforms, - spaces=spaces,) + spaces=spaces, + ) return tnd_transform From 58e425228f02328757340ebd53a6bd6ba283f114 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:39:52 +0200 Subject: [PATCH 59/95] deps: require latest ozmp prerelease --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d0d127b8..726d05b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "toolz", "rangehttpserver", "transformnd>=0.6.0", - "ome-zarr-models==1.8.0rc0", + "ome-zarr-models==1.8.0rc1", "Deprecated", "tqdm", ] From 3a1551a40bfe1684f472ce4dc0b273fdab517cf9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:40:32 +0000 Subject: [PATCH 60/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../advanced/transforms/scene_2d_to_3d.ipynb | 380 +++++++++--------- 1 file changed, 190 insertions(+), 190 deletions(-) diff --git a/docs/source/advanced/transforms/scene_2d_to_3d.ipynb b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb index 45c97c4a..a5493afc 100644 --- a/docs/source/advanced/transforms/scene_2d_to_3d.ipynb +++ b/docs/source/advanced/transforms/scene_2d_to_3d.ipynb @@ -1,201 +1,201 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "002e4aac", - "metadata": {}, - "source": [ - "# Transforms with changing dimensionality\n", - "\n", - "A particular and important kind of transforms,\n", - "are transforms that change the dimensionality of the data.\n", - "Common examples for this case are:\n", - "- 2D to 3D transforms, e.g. for aligning a 2D slice to a 3D volume\n", - "- 2D + channel to 2D transforms, e.g. for aligning a 2D slice with multiple channels to a 2D slice with a single channel\n", - "\n", - "The transform that expresses this change in dimensionality is the [`ProjectAxis` transform](https://ngff.openmicroscopy.org/specifications/dev/index.html#projectaxis).\n", - "\n", - "This tutorial demonstrates its usage for the case of a 2D to 3D transform,\n", - "where a 2D slice is aligned to a 3D volume." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ac4f951d", - "metadata": {}, - "outputs": [], - "source": [ - "from skimage import data\n", - "\n", - "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ede90a69", - "metadata": {}, - "outputs": [ + "cells": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] + "cell_type": "markdown", + "id": "002e4aac", + "metadata": {}, + "source": [ + "# Transforms with changing dimensionality\n", + "\n", + "A particular and important kind of transforms,\n", + "are transforms that change the dimensionality of the data.\n", + "Common examples for this case are:\n", + "- 2D to 3D transforms, e.g. for aligning a 2D slice to a 3D volume\n", + "- 2D + channel to 2D transforms, e.g. for aligning a 2D slice with multiple channels to a 2D slice with a single channel\n", + "\n", + "The transform that expresses this change in dimensionality is the [`ProjectAxis` transform](https://ngff.openmicroscopy.org/specifications/dev/index.html#projectaxis).\n", + "\n", + "This tutorial demonstrates its usage for the case of a 2D to 3D transform,\n", + "where a 2D slice is aligned to a 3D volume." + ] }, { - "data": { - "text/plain": [ - "(2, 60, 256, 256)" + "cell_type": "code", + "execution_count": null, + "id": "ac4f951d", + "metadata": {}, + "outputs": [], + "source": [ + "from skimage import data\n", + "\n", + "from ome_zarr import OMEZarrImage, OMEZarrMultiscale, OMEZarrScene" ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "img = data.cells3d().transpose((1, 0, 2, 3))\n", - "img.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a9445304", - "metadata": {}, - "outputs": [ + }, { - "data": { - "text/plain": [ - "(256, 256)" + "cell_type": "code", + "execution_count": 2, + "id": "ede90a69", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, + { + "data": { + "text/plain": [ + "(2, 60, 256, 256)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "img = data.cells3d().transpose((1, 0, 2, 3))\n", + "img.shape" ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "some_slice = img[0, 30, :, :]\n", - "some_slice.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "454e972b", - "metadata": {}, - "outputs": [], - "source": [ - "ngff_img = OMEZarrImage(\n", - " data=img,\n", - " axes=[\"c\", \"z\", \"y\", \"x\"],\n", - " scale={\"c\": 1, \"z\": 1, \"y\": 1, \"x\": 1},\n", - " name=\"cells3d\"\n", - ")\n", - "\n", - "ngff_ms = OMEZarrMultiscale(\n", - " image=ngff_img,\n", - ")\n", - "\n", - "slice_img = OMEZarrImage(\n", - " data=some_slice,\n", - " axes=[\"y\", \"x\"],\n", - " scale={\"y\": 1, \"x\": 1},\n", - " name=\"cells3d_slice\"\n", - ")\n", - "\n", - "slice_ms = OMEZarrMultiscale(\n", - " image=slice_img,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "a7b4469f", - "metadata": {}, - "outputs": [], - "source": [ - "transform_to_3d = {\n", - " \"type\": \"sequence\",\n", - " \"input\": {\"path\": \"cells3d_slice\", \"name\": \"physical\"},\n", - " \"output\": {\"path\": \"cells3d\", \"name\": \"physical\"},\n", - " \"transformations\": [\n", - " {\n", - " \"type\": \"projectAxis\",\n", - " \"createdOutputs\": [0, 1]\n", - " },\n", - " {\n", - " \"type\": \"translation\",\n", - " \"translation\": [0, 30, 0, 0]\n", - " }\n", - " ]\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "abde22ea", - "metadata": {}, - "outputs": [], - "source": [ - "scene = OMEZarrScene(\n", - " images=[ngff_ms, slice_ms],\n", - " coordinate_transformations=[transform_to_3d]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8d723932", - "metadata": {}, - "outputs": [ + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9445304", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(256, 256)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "some_slice = img[0, 30, :, :]\n", + "some_slice.shape" + ] + }, { - "name": "stderr", - "output_type": "stream", - "text": [ - "Writing images: 100%|██████████| 2/2 [00:01<00:00, 1.98it/s]\n" - ] + "cell_type": "code", + "execution_count": 4, + "id": "454e972b", + "metadata": {}, + "outputs": [], + "source": [ + "ngff_img = OMEZarrImage(\n", + " data=img,\n", + " axes=[\"c\", \"z\", \"y\", \"x\"],\n", + " scale={\"c\": 1, \"z\": 1, \"y\": 1, \"x\": 1},\n", + " name=\"cells3d\"\n", + ")\n", + "\n", + "ngff_ms = OMEZarrMultiscale(\n", + " image=ngff_img,\n", + ")\n", + "\n", + "slice_img = OMEZarrImage(\n", + " data=some_slice,\n", + " axes=[\"y\", \"x\"],\n", + " scale={\"y\": 1, \"x\": 1},\n", + " name=\"cells3d_slice\"\n", + ")\n", + "\n", + "slice_ms = OMEZarrMultiscale(\n", + " image=slice_img,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a7b4469f", + "metadata": {}, + "outputs": [], + "source": [ + "transform_to_3d = {\n", + " \"type\": \"sequence\",\n", + " \"input\": {\"path\": \"cells3d_slice\", \"name\": \"physical\"},\n", + " \"output\": {\"path\": \"cells3d\", \"name\": \"physical\"},\n", + " \"transformations\": [\n", + " {\n", + " \"type\": \"projectAxis\",\n", + " \"createdOutputs\": [0, 1]\n", + " },\n", + " {\n", + " \"type\": \"translation\",\n", + " \"translation\": [0, 30, 0, 0]\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "abde22ea", + "metadata": {}, + "outputs": [], + "source": [ + "scene = OMEZarrScene(\n", + " images=[ngff_ms, slice_ms],\n", + " coordinate_transformations=[transform_to_3d]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8d723932", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 2/2 [00:01<00:00, 1.98it/s]\n" + ] + } + ], + "source": [ + "scene.to_ome_zarr(\"scene_2d_to_3d.zarr\", overwrite=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "062391f5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ome-zarr (3.13.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" } - ], - "source": [ - "scene.to_ome_zarr(\"scene_2d_to_3d.zarr\", overwrite=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "062391f5", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ome-zarr (3.13.12)", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } From 2f972c31d195c6f9379b3537bdf1c62386f0925a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:33:50 +0200 Subject: [PATCH 61/95] feat: write 0.6, not 0.6.dev4 --- .../transforms/add_transforms_to_multiscales.ipynb | 8 ++++---- ome_zarr/classes/image.py | 10 +++++----- tests/test_writer.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb b/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb index 6737276e..bd6ed553 100644 --- a/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb +++ b/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb @@ -78,8 +78,8 @@ "\n", "$\n", "R = \\begin{bmatrix}\n", - "\\cos(45\u00b0) & -\\sin(45\u00b0) \\\\\n", - "\\sin(45\u00b0) & \\cos(45\u00b0)\n", + "\\cos(45°) & -\\sin(45°) \\\\\n", + "\\sin(45°) & \\cos(45°)\n", "\\end{bmatrix} = \\begin{bmatrix}\n", "\\cos(\\pi/4) & -\\sin(\\pi/4) \\\\\n", "\\sin(\\pi/4) & \\cos(\\pi/4)\n", @@ -157,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "2e7d870d", "metadata": {}, "outputs": [ @@ -175,7 +175,7 @@ "source": [ "ms.to_ome_zarr(\n", " \"multiscale_with_transforms.ome.zarr\",\n", - " version=\"0.6.dev4\", # specify the desired ome-zarr version\n", + " version=\"0.6\", # specify the desired ome-zarr version\n", " overwrite=True,\n", ")" ] diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 799e5c3b..dae08539 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -343,7 +343,7 @@ def to_ome_zarr( self, group: zarr.Group | str, storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, - version: Literal["0.6.dev4", "0.5", "0.4"] = "0.5", + version: Literal["0.6", "0.5", "0.4"] = "0.5", compute: bool = True, overwrite: bool = False, ) -> list: @@ -372,7 +372,7 @@ def to_ome_zarr( shutil.rmtree(group) fmt: Format | None = None - if version in {"0.5", "0.6", "0.6.dev4"}: + if version in {"0.5", "0.6", "0.6"}: fmt = FormatV05() elif version == "0.4": fmt = FormatV04() @@ -438,7 +438,7 @@ def to_ome_zarr( group.attrs["ome"] = metadata_dict - elif version == "0.6.dev4": + elif version == "0.6": metadata_dict = { "version": version, "multiscales": [ @@ -615,7 +615,7 @@ def images(self) -> list[OMEZarrImage]: def _write_additional_meta_data( self, group: zarr.Group, - version: Literal["0.6.dev4", "0.5", "0.4"] = "0.5", + version: Literal["0.6", "0.5", "0.4"] = "0.5", storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, compute: bool = True, overwrite: bool = False, @@ -840,7 +840,7 @@ def __init__( def _write_additional_meta_data( self, group: zarr.Group, - version: Literal["0.6.dev4", "0.5", "0.4"] = "0.5", + version: Literal["0.6", "0.5", "0.4"] = "0.5", storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, compute: bool = True, overwrite: bool = False, diff --git a/tests/test_writer.py b/tests/test_writer.py index 54811c11..9f30b048 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -178,7 +178,7 @@ def test_additional_transforms(self): ) ms.to_ome_zarr( zarr.open(self.path / "test_transforms.zarr", mode="w"), - version="0.6.dev4", + version="0.6", overwrite=True, ) @@ -197,7 +197,7 @@ def test_additional_transforms(self): ) @pytest.mark.parametrize( - "version", ("0.4", "0.5", "0.6.dev4"), ids=["V04", "V05", "V06"] + "version", ("0.4", "0.5", "0.6"), ids=["V04", "V05", "V06"] ) def test_image_class_versions(self, version): from ome_zarr_models.v06.multiscales import Multiscale as Multiscale_V06 From 129c3510d0cd496317a990072f303c348ea95bdd Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:34:01 +0200 Subject: [PATCH 62/95] feat write 0.6 --- ome_zarr/classes/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index dae08539..2a5cedba 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -1161,7 +1161,7 @@ def image_label(self, value: Label | dict[str, Any] | None): def _write_additional_meta_data( self, group: zarr.Group, - version: Literal["0.6.dev4", "0.5", "0.4"] = "0.5", + version: Literal["0.6", "0.5", "0.4"] = "0.5", storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, compute: bool = True, overwrite: bool = False, From ee3bc319f9f1e7cb24bb50cff2150b5b36a73ed9 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:34:28 +0200 Subject: [PATCH 63/95] feat: add missing version checks in additional metadata --- ome_zarr/classes/image.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 2a5cedba..73733305 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -853,13 +853,17 @@ def _write_additional_meta_data( if self._omero and isinstance(self._omero, Omero): omero_dict = _recursive_pop_nones(self._omero.model_dump(by_alias=True)) + # in 0.4, omero metadata goes in group attrs if version == "0.4": group.attrs["omero"] = omero_dict - elif version == "0.5": + + # in >=0.5, omero metadata goes in the ome attr + elif version == "0.5" or version.startswith("0.6"): if "ome" not in group.attrs: raise ValueError("OME-Zarr attributes not found in group") ome = cast(dict, group.attrs["ome"]) - omero_dict["version"] = version + if version == "0.5": + omero_dict["version"] = version ome["omero"] = omero_dict group.attrs["ome"] = ome @@ -897,7 +901,7 @@ def _write_additional_meta_data( # Update labels list in metadata if version == "0.4": label_group.attrs["labels"] = list_of_labels - elif version == "0.5": + elif version == "0.5" or version.startswith("0.6"): label_group.attrs["ome"] = { "version": version, "labels": list_of_labels, @@ -1031,7 +1035,7 @@ def _read_additional_metadata( if version in ("0.1", "0.2", "0.3", "0.4") and "omero" in group.attrs: omero_dict = cast(dict[str, Any] | None, group.attrs.get("omero", None)) - elif version == "0.5": + elif version == "0.5" or version.startswith("0.6"): ome_attrs = cast(dict[str, Any], group.attrs.get("ome", {})) if "omero" in ome_attrs: omero_dict = cast(dict[str, Any] | None, ome_attrs.get("omero", None)) @@ -1050,7 +1054,7 @@ def _read_additional_metadata( list_of_labels = ( cast(list[str], labels_json) if isinstance(labels_json, list) else [] ) - elif version == "0.5" and "labels" in group: + elif (version == "0.5" or version.startswith("0.6")) and "labels" in group: labels_ome_attrs = cast( dict[str, Any], group["labels"].attrs.get("ome", {}) ) @@ -1173,7 +1177,7 @@ def _write_additional_meta_data( group.attrs["image-label"] = _recursive_pop_nones( self._image_label.model_dump(by_alias=True) ) - elif version == "0.5": + elif version == "0.5" or version.startswith("0.6"): ome = cast(dict, group.attrs.get("ome", {})) ome["image-label"] = _recursive_pop_nones( self._image_label.model_dump(by_alias=True) @@ -1198,7 +1202,7 @@ def _read_additional_metadata( image_label_dict = cast( dict[str, Any] | None, group.attrs.get("image-label", None) ) - elif version == "0.5": + elif version == "0.5" or version.startswith("0.6"): ome_attrs = cast(dict[str, Any], group.attrs.get("ome", {})) if "image-label" in ome_attrs: image_label_dict = cast( From 37149fb75aab91a672445cf54bdb5c8d82e0b3cf Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:34:40 +0200 Subject: [PATCH 64/95] tests: add 0.6 to tested versions in writer --- tests/test_writer.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 9f30b048..8e71a773 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -15,6 +15,7 @@ from ome_zarr_models.v04 import Well as Models04Well from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image +from ome_zarr_models.v06.image import Image as Models06Image from ome_zarr_models.v05.well import Well as Models05Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec @@ -287,12 +288,15 @@ def test_image_class_bad_args(self): multiscales.to_ome_zarr(self.path / "test_bad_args.zarr", version="0.5.5") @pytest.mark.parametrize("storage_options_list", [True, False]) + @pytest.mark.parametrize( + "version", ["0.4", "0.5", "0.6"], + ids=["V04", "V05", "V06"], + ) def test_image_class_writer( - self, shape, format_version_all, array_constructor, storage_options_list + self, shape, version, array_constructor, storage_options_list ): - version = format_version_all() - if version.version == "0.5": + if version.startswith(("0.5", "0.6")): grp_path = self.path_v3 / "test" else: grp_path = self.path / "test" @@ -351,7 +355,7 @@ def test_image_class_writer( # write image and labels to disk image_multiscales.to_ome_zarr( group=str(grp_path), - version=version.version, + version=version, storage_options=storage_options, overwrite=True, ) @@ -361,15 +365,13 @@ def test_image_class_writer( node_metadata = out.attrs if "ome" in node_metadata: node_metadata = node_metadata["ome"] + + # multiscales and omero data must be present by default assert "multiscales" in node_metadata + assert "omero" in node_metadata + paths = [d["path"] for d in node_metadata["multiscales"][0]["datasets"]] node_data = [da.from_zarr(grp_path / path) for path in paths] - if version.version in ("0.1", "0.2"): - # v0.1 and v0.2 MUST be 5D - assert node_data[0].ndim == 5 - else: - assert node_data[0].shape == shape - print("node.metadata", node_metadata) # check written coordinatetransormations match relative factors between array sizes for level, nd_array in enumerate(node_data): @@ -424,7 +426,7 @@ def test_image_class_writer( # Verify labels data label_group = zarr.open(f"{grp_path}/labels", mode="r") label_group_attrs = label_group.attrs - if version.version == "0.5": + if version == "0.5" or version.startswith("0.6"): label_group_attrs = label_group_attrs["ome"] assert "labels" in label_group_attrs assert labels_name in label_group_attrs["labels"] @@ -434,12 +436,6 @@ def test_image_class_writer( assert labels_name in list(image.labels.keys()) - if version.version == "0.4": - # Validate with ome-zarr-models-py: only supports v0.4 - Models04Image.from_zarr(out) - elif version.version == "0.5": - Models05Image.from_zarr(out) - # verify omero and image-labels metadata if "c" in axes: assert image.omero is not None From 40c7a70d97385846596937f7a3c3a0954d0012a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:37:16 +0000 Subject: [PATCH 65/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../advanced/transforms/add_transforms_to_multiscales.ipynb | 4 ++-- ome_zarr/classes/image.py | 2 +- tests/test_writer.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb b/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb index bd6ed553..f7b503fd 100644 --- a/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb +++ b/docs/source/advanced/transforms/add_transforms_to_multiscales.ipynb @@ -78,8 +78,8 @@ "\n", "$\n", "R = \\begin{bmatrix}\n", - "\\cos(45°) & -\\sin(45°) \\\\\n", - "\\sin(45°) & \\cos(45°)\n", + "\\cos(45\u00b0) & -\\sin(45\u00b0) \\\\\n", + "\\sin(45\u00b0) & \\cos(45\u00b0)\n", "\\end{bmatrix} = \\begin{bmatrix}\n", "\\cos(\\pi/4) & -\\sin(\\pi/4) \\\\\n", "\\sin(\\pi/4) & \\cos(\\pi/4)\n", diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 73733305..2832b793 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -372,7 +372,7 @@ def to_ome_zarr( shutil.rmtree(group) fmt: Format | None = None - if version in {"0.5", "0.6", "0.6"}: + if version in {"0.5", "0.6"}: fmt = FormatV05() elif version == "0.4": fmt = FormatV04() diff --git a/tests/test_writer.py b/tests/test_writer.py index 8e71a773..573cd8bb 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -15,7 +15,6 @@ from ome_zarr_models.v04 import Well as Models04Well from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image -from ome_zarr_models.v06.image import Image as Models06Image from ome_zarr_models.v05.well import Well as Models05Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec @@ -289,7 +288,8 @@ def test_image_class_bad_args(self): @pytest.mark.parametrize("storage_options_list", [True, False]) @pytest.mark.parametrize( - "version", ["0.4", "0.5", "0.6"], + "version", + ["0.4", "0.5", "0.6"], ids=["V04", "V05", "V06"], ) def test_image_class_writer( From 9c7e4f0df4949b3e5e503d12375346d106b6872e Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:52:35 +0200 Subject: [PATCH 66/95] tests: pull out scale transforms correctly irrespective of version --- tests/test_writer.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 573cd8bb..836b7461 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -375,15 +375,22 @@ def test_image_class_writer( # check written coordinatetransormations match relative factors between array sizes for level, nd_array in enumerate(node_data): + ds = node_metadata["multiscales"][0]["datasets"][level] if level == 0: # check first written scale values explicitly match those in TRANSFORMATIONS for d in axes: - assert ( - node_metadata["multiscales"][0]["datasets"][level][ - "coordinateTransformations" - ][0]["scale"][axes.index(d)] - == TRANSFORMATIONS[0][0]["scale"][axes.index(d)] - ) + if version.startswith(("0.4", "0.5")): + tf = ds["coordinateTransformations"][0] + assert ( + tf["scale"][axes.index(d)] + == TRANSFORMATIONS[0][0]["scale"][axes.index(d)] + ) + elif version.startswith("0.6"): + tf = ds["coordinateTransformations"][0]["transformations"][0] + assert ( + tf["scale"][axes.index(d)] + == TRANSFORMATIONS[0][0]["scale"][axes.index(d)] + ) continue # first calculate relative factors between this and previous level @@ -408,9 +415,10 @@ def test_image_class_writer( assert relative_factors["z"] == 1.0 # retrieve written scale factors from metadata and check they match expected - cts = node_metadata["multiscales"][0]["datasets"][level][ - "coordinateTransformations" - ] + if version.startswith(("0.4", "0.5")): + cts = ds["coordinateTransformations"] + elif version.startswith("0.6"): + cts = ds["coordinateTransformations"][0]["transformations"] assert len(cts) == 2 transf = cts[0] assert transf["type"] == "scale" From dc07c5585c5c2b030b1fc4d58891961ad9d22622 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:21:12 +0200 Subject: [PATCH 67/95] feat: default to 0.6 for writing --- docs/source/basic/write_image.ipynb | 8 ++++---- ome_zarr/classes/image.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/basic/write_image.ipynb b/docs/source/basic/write_image.ipynb index 0327fa61..99b58b74 100644 --- a/docs/source/basic/write_image.ipynb +++ b/docs/source/basic/write_image.ipynb @@ -57,14 +57,14 @@ "As a last step, we write the multiscale image to disk using the `to_ome_zarr` method, which will create a valid OME-ZARR file that can be read by any OME-ZARR compatible viewer.\n", "\n", "```{hint}\n", - "The demonstrated writer method below defaults to writing OME-ZARR version `0.5`,\n", - "but also supports writing OME-ZARR version `0.6.dev4` and `0.4`.\n", + "The demonstrated writer method below defaults to writing OME-ZARR version `0.6`.\n", + "Writing OME-ZARR version `0.5` and `0.4` needs to be explicitly specified.\n", "```" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "ce122c48", "metadata": {}, "outputs": [ @@ -95,7 +95,7 @@ " channel_colors=[\"00FFFF\", \"FF00FF\"], # optional\n", " contrast_limits=[(0, 255), (0, 255)] # optional\n", " )\n", - "multiscales.to_ome_zarr(\"test_ngff.ome.zarr\", version=\"0.5\")" + "multiscales.to_ome_zarr(\"test_ngff.ome.zarr\", version=\"0.6\")" ] }, { diff --git a/ome_zarr/classes/image.py b/ome_zarr/classes/image.py index 2832b793..0bf9ef51 100644 --- a/ome_zarr/classes/image.py +++ b/ome_zarr/classes/image.py @@ -343,7 +343,7 @@ def to_ome_zarr( self, group: zarr.Group | str, storage_options: list[dict[str, Any]] | dict[str, Any] | None = None, - version: Literal["0.6", "0.5", "0.4"] = "0.5", + version: Literal["0.6", "0.5", "0.4"] = "0.6", compute: bool = True, overwrite: bool = False, ) -> list: From b12fb12246005651b3b546af629318831ba2902b Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:39:40 +0200 Subject: [PATCH 68/95] feat: add formatV06 class --- ome_zarr/format.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ome_zarr/format.py b/ome_zarr/format.py index 3b2c33e7..7878a8c9 100644 --- a/ome_zarr/format.py +++ b/ome_zarr/format.py @@ -383,5 +383,17 @@ def chunk_key_encoding(self) -> dict[str, str]: # this is default for Zarr v3. Could return None? return {"name": "default", "separator": "/"} +class FormatV06(FormatV05): + """ + Changelog: added FormatV06 (June 2025): writing not supported yet + """ + + @property + def version(self) -> str: + return "0.6" + + @property + def zarr_format(self) -> int: + return 3 -CurrentFormat = FormatV05 +CurrentFormat = FormatV06 From 75b9402fc0bf318bb072663b068ebd25284dc789 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:39:53 +0200 Subject: [PATCH 69/95] tests: add checks for what version is encountered --- tests/test_writer.py | 173 ++++++++++++++++++++++++++++++------------- 1 file changed, 123 insertions(+), 50 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 836b7461..05e99d70 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -16,6 +16,9 @@ from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image from ome_zarr_models.v05.well import Well as Models05Well +from ome_zarr_models.v06.image import Image as Models06Image +from ome_zarr_models.v06.hcs import HCS as Models06HCS +from ome_zarr_models.v06.well import Well as Models06Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec from zarr.codecs import BloscCodec @@ -31,6 +34,7 @@ FormatV03, FormatV04, FormatV05, + FormatV06, format_from_version, ) from ome_zarr.scale import _build_pyramid @@ -58,6 +62,7 @@ FORMAT_VERSIONS = [ pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), + pytest.param(FormatV06, id="V06") ] ARRAY_CONSTRUCTORS = [np.array, da.from_array] @@ -84,11 +89,11 @@ def _make_storage_options(fmt, shape, axes): ) options = { "chunks": (16, 16), - "shards": (32, 32) if fmt.version == "0.5" else None, + "shards": (32, 32) if fmt.version in ("0.5", "0.6") else None, "compressors": compressor, - "serializer": BytesCodec(endian="little") if fmt.version == "0.5" else None, + "serializer": BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None, "fill_value": 0, - "dimension_names": list(axes) if fmt.version == "0.5" else None, + "dimension_names": list(axes) if fmt.version in ("0.5", "0.6") else None, "order": "C", } return {k: v for k, v in options.items() if v is not None} @@ -455,7 +460,7 @@ def test_writer( ): fmt = format_version_all() - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): grp_path = self.path_v3 / "test" else: grp_path = self.path / "test" @@ -487,7 +492,7 @@ def test_writer( ) storage_options = _make_storage_options(fmt, shape, axes) - if fmt.version != "0.5": + if fmt.version not in ("0.5", "0.6"): chunks = [(128, 128), (50, 50), (25, 25), (25, 25), (25, 25), (25, 25)] if storage_options_list: @@ -526,16 +531,30 @@ def test_writer( else: assert node_data[0].shape == shape - for ax in node_metadata["multiscales"][0].get("axes"): + if fmt.version == "0.4": + axes_md = node_metadata["multiscales"][0].get("axes") + elif fmt.version == "0.5": + axes_md = node_metadata["multiscales"][0].get("axes") + elif fmt.version == "0.6": + axes_md = node_metadata["multiscales"][0]["coordinateSystems"][0].get( + "axes" + ) + for ax in axes_md: if ax["name"] in axes_units: assert ax.get("unit") == axes_units[ax["name"]] print("node.metadata", node_metadata) if fmt.version not in ("0.1", "0.2", "0.3"): - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + if fmt.version in ("0.4", "0.5"): + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] + elif fmt.version == "0.6": + cts = [ + d["coordinateTransformations"][0]["transformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for transf, expected in zip(cts, transformations): assert transf == expected assert len(cts) == len(node_data) @@ -552,6 +571,8 @@ def test_writer( Models04Image.from_zarr(out) elif fmt.version == "0.5": Models05Image.from_zarr(out) + elif fmt.version == "0.6": + Models06Image.from_zarr(out) @pytest.mark.parametrize( "format_version", @@ -571,7 +592,7 @@ def test_writer_with_scale( """ version = format_version() - if version.version == "0.5": + if version.version in ("0.5", "0.6"): grp_path = self.path_v3 / "test" else: grp_path = self.path / "test" @@ -623,6 +644,8 @@ def test_writer_with_scale( Models04Image.from_zarr(out) elif version.version == "0.5": Models05Image.from_zarr(out) + elif version.version == "0.6": + Models06Image.from_zarr(out) def test_mix_zarr_formats(self): # check group zarr v2 and v3 matches fmt @@ -688,10 +711,21 @@ def test_write_image_current(self, array_constructor, zarr_format): node_metadata = out.attrs if "ome" in node_metadata: node_metadata = node_metadata["ome"] - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + version = node_metadata.get("version") + assert version in ("0.5", "0.6") + else: + version = "0.4" + + if version in ("0.4", "0.5"): + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] + elif version == "0.6": + cts = [ + d["coordinateTransformations"][0]["transformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for level, transfs in enumerate(cts): assert len(transfs) == 2 assert transfs[0]["type"] == "scale" @@ -784,15 +818,25 @@ def test_write_image_dask(self, read_from_zarr, compute, zarr_format): node_metadata = out.attrs if "ome" in node_metadata: node_metadata = node_metadata["ome"] + version = node_metadata.get("version", "0.6") + + else: + version = node_metadata.get("version", "0.4") paths = [d["path"] for d in node_metadata["multiscales"][0]["datasets"]] image_node_data = [da.from_zarr(out[path]) for path in paths] first_chunk = [c[0] for c in image_node_data[0].chunks] assert tuple(first_chunk) == _retuple(chunks, image_node_data[0].shape) - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + if version in ("0.4", "0.5"): + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] + else: + cts = [ + d["coordinateTransformations"][0]["transformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for level, transfs in enumerate(cts): assert len(transfs) == 2 assert transfs[0]["type"] == "scale" @@ -841,6 +885,8 @@ def test_write_image_dask(self, read_from_zarr, compute, zarr_format): Models04Image.from_zarr(group) elif fmt.version == "0.5": Models05Image.from_zarr(group) + elif fmt.version == "0.6": + Models06Image.from_zarr(group) def test_write_image_scalar_chunks(self): """ @@ -864,6 +910,7 @@ def test_write_image_scalar_chunks(self): ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), + pytest.param(FormatV06, id="V06") ), ) def test_write_image_compressed(self, array_constructor, format_version): @@ -921,17 +968,20 @@ def test_write_image_compressed(self, array_constructor, format_version): "shuffle": Blosc.SHUFFLE, "blocksize": 0, } - assert format_version().version in ("0.4", "0.5") + assert format_version().version in ("0.4", "0.5", "0.6") if format_version().version == "0.4": Models04Image.from_zarr(group) elif format_version().version == "0.5": Models05Image.from_zarr(group) + elif format_version().version == "0.6": + Models06Image.from_zarr(group) @pytest.mark.parametrize( "format_version", ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), + pytest.param(FormatV06, id="V06"), ), ) def test_default_compression(self, array_constructor, format_version): @@ -1116,19 +1166,25 @@ def initdir(self, tmpdir): self.path_v3 = self.path / "v3" self.root_v3 = zarr.open_group(self.path_v3, mode="w", zarr_format=3) - @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) + @pytest.mark.parametrize( + "fmt", ( + FormatV04(), + FormatV05(), + FormatV06() + ) + ) def test_multi_levels_transformations(self, fmt): datasets = [] for level, transf in enumerate(TRANSFORMATIONS): datasets.append({"path": str(level), "coordinateTransformations": transf}) - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): path = self.path_v3 else: path = self.path write_multiscales_metadata(str(path), datasets, axes="tczyx", fmt=fmt) # we want to be sure this is zarr v2 / v3 attrs = zarr.open_group(path).attrs - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): attrs = attrs.get("ome") assert "version" in attrs json_text = (self.path_v3 / "zarr.json").read_text(encoding="utf-8") @@ -1150,8 +1206,10 @@ def test_multi_levels_transformations(self, fmt): out = zarr.open_group(path) if fmt.version == "0.4": Models04Image.from_zarr(out) - if fmt.version == "0.5": + elif fmt.version == "0.5": Models05Image.from_zarr(out) + elif fmt.version == "0.6": + Models06Image.from_zarr(out) @pytest.mark.parametrize( "axes", @@ -1405,9 +1463,12 @@ def test_minimal_plate(self, fmt): assert fmt.version in ("0.4", "0.5") if fmt.version == "0.4": Models04HCS.from_zarr(group) - elif fmt.version == "0.5": + elif fmt.version in ("0.5", "0.6"): # https://github.com/ome-zarr-models/ome-zarr-models-py/issues/218 - Models05HCS.from_zarr(group) + if fmt.version == "0.5": + Models05HCS.from_zarr(group) + elif fmt.version == "0.6": + Models06HCS.from_zarr(group) @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) def test_12wells_plate(self, fmt): @@ -1470,6 +1531,8 @@ def test_12wells_plate(self, fmt): Models04HCS.from_zarr(group) elif fmt.version == "0.5": Models05HCS.from_zarr(group) + elif fmt.version == "0.6": + Models06HCS.from_zarr(group) @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) def test_sparse_plate(self, fmt): @@ -1514,6 +1577,8 @@ def test_sparse_plate(self, fmt): Models04HCS.from_zarr(group) elif fmt.version == "0.5": Models05HCS.from_zarr(group) + elif fmt.version == "0.6": + Models06HCS.from_zarr(group) def test_plate_name(self): # We don't need to test v04 and v05 for all tests since @@ -1728,14 +1793,14 @@ def initdir(self, tmpdir): @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) @pytest.mark.parametrize("images", (["0"], [{"path": "0"}])) def test_minimal_well(self, images, fmt): - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): group = self.root_v3 else: group = self.root write_well_metadata(group, images) # we want to be sure this is zarr v2 / v3, so we load json manually too attrs = group.attrs - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): attrs = attrs.get("ome") assert attrs["version"] == fmt.version json_text = (self.path_v3 / "zarr.json").read_text(encoding="utf-8") @@ -1751,6 +1816,8 @@ def test_minimal_well(self, images, fmt): Models04Well.from_zarr(group) elif fmt.version == "0.5": Models05Well.from_zarr(group) + elif fmt.version == "0.6": + Models06Well.from_zarr(group) @pytest.mark.parametrize( "images", @@ -1892,14 +1959,14 @@ def verify_label_data( # Verify label metadata label_root = zarr.open(f"{img_path}/labels", mode="r") label_attrs = label_root.attrs - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): label_attrs = label_attrs["ome"] assert "labels" in label_attrs assert label_name in label_attrs["labels"] label_group = zarr.open(f"{img_path}/labels/{label_name}", mode="r") imglabel_attrs = label_group.attrs - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): imglabel_attrs = imglabel_attrs["ome"] assert imglabel_attrs["version"] == fmt.version else: @@ -1922,7 +1989,7 @@ def test_write_labels( ): fmt = format_version_all() - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): img_path = self.path_v3 group = self.root_v3 else: @@ -1990,7 +2057,7 @@ def test_write_labels_with_storage_options( array_constructor, ): fmt = format_version_all() - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): group = self.root_v3 else: group = self.root @@ -2050,7 +2117,7 @@ def test_write_labels_with_storage_options( level0.chunks == expected_chunks ), f"Expected chunks {expected_chunks}, got {level0.chunks}" - if USE_DASK_ARRAY_KWARGS and fmt.version == "0.5" and hasattr(level0, "shards"): + if USE_DASK_ARRAY_KWARGS and fmt.version in ("0.5", "0.6") and hasattr(level0, "shards"): expected_shards = _retuple(storage_options["shards"], level0.shape) assert ( level0.shards == expected_shards @@ -2059,7 +2126,7 @@ def test_write_labels_with_storage_options( assert level0.fill_value == 0 if level0.compressors: - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): if USE_DASK_ARRAY_KWARGS: assert level0.compressors[0].cname.name == "zstd" else: @@ -2068,7 +2135,7 @@ def test_write_labels_with_storage_options( assert level0.compressors[0].cname == "zstd" if USE_DASK_ARRAY_KWARGS: assert level0.compressors[0].clevel == 3 - if fmt.version == "0.5" and hasattr(level0, "serializer"): + if fmt.version in ("0.5", "0.6") and hasattr(level0, "serializer"): assert ( level0.metadata.codecs[0].index_codecs[0].endian.name == "little" @@ -2076,7 +2143,7 @@ def test_write_labels_with_storage_options( else: assert level0.compressors[0].to_dict()["configuration"]["level"] == 0 - if fmt.version == "0.5" and hasattr(level0, "dimension_names"): + if fmt.version in ("0.5", "0.6") and hasattr(level0, "dimension_names"): assert level0.dimension_names == tuple(axes) if fmt.version == "0.4": @@ -2086,7 +2153,7 @@ def test_write_multiscale_labels( self, shape, format_version_all, array_constructor ): fmt = format_version_all() - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): img_path = self.path_v3 group = self.root_v3 else: @@ -2156,7 +2223,7 @@ def test_write_multiscale_labels_storage_options( self, shape, format_version_all, array_constructor ): fmt = format_version_all() - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): img_path = self.path_v3 group = self.root_v3 else: @@ -2239,7 +2306,7 @@ def test_write_multiscale_labels_storage_options( if ( USE_DASK_ARRAY_KWARGS - and fmt.version == "0.5" + and fmt.version in ("0.5", "0.6") and hasattr(level, "shards") ): expected_shards = _retuple(storage_options["shards"], level.shape) @@ -2252,7 +2319,7 @@ def test_write_multiscale_labels_storage_options( ), f"Level {level_idx}: Expected fill_value 0, got {level.fill_value}" if level.compressors: - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): if USE_DASK_ARRAY_KWARGS: assert level.compressors[0].cname.name == "zstd" else: @@ -2261,7 +2328,7 @@ def test_write_multiscale_labels_storage_options( assert level.compressors[0].cname == "zstd" if USE_DASK_ARRAY_KWARGS: assert level.compressors[0].clevel == 3 - if fmt.version == "0.5" and hasattr(level, "serializer"): + if fmt.version in ("0.5", "0.6") and hasattr(level, "serializer"): assert ( level.metadata.codecs[0].index_codecs[0].endian.name == "little" @@ -2269,7 +2336,7 @@ def test_write_multiscale_labels_storage_options( else: assert level.compressors[0].to_dict()["configuration"]["level"] == 0 - if fmt.version == "0.5" and hasattr(level, "dimension_names"): + if fmt.version in ("0.5", "0.6") and hasattr(level, "dimension_names"): assert level.dimension_names == tuple( axes ), f"Level {level_idx}: Expected dimension names {tuple(axes)}, got {level.dimension_names}" @@ -2285,10 +2352,13 @@ def test_write_multiscale_labels_storage_options( @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), pytest.param(FormatV05(), id="V05")), + (pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06"), + ), ) def test_two_label_images(self, array_constructor, fmt): - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): img_path = self.path_v3 group = self.root_v3 else: @@ -2349,7 +2419,7 @@ def test_two_label_images(self, array_constructor, fmt): # Verify label metadata label_root = zarr.open(f"{img_path}/labels", mode="r") attrs = label_root.attrs - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): attrs = attrs["ome"] assert "labels" in attrs assert len(attrs["labels"]) == len(label_names) @@ -2357,12 +2427,15 @@ def test_two_label_images(self, array_constructor, fmt): @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), pytest.param(FormatV05(), id="V05")), + (pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06") + ), ) def write_labels_class_API(self, fmt): from ome_zarr import OMEZarrImage, OMEZarrMultiscale - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): img_path = self.path_v3 group = self.root_v3 else: @@ -2392,7 +2465,7 @@ def write_labels_class_API(self, fmt): # now check that the respective groups and metadata exist and is correct label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs @@ -2410,7 +2483,7 @@ def write_labels_class_API(self, fmt): # now check that we still have both labels in the metadata label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs @@ -2426,7 +2499,7 @@ def write_labels_class_API(self, fmt): # Now, only the third label should be present in the metadata and as a zarr group, # but the first and second labels should be gone label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version == "0.5": + if fmt.version in ("0.5", "0.6"): label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs From e0a03cd4bd0c40813c089e031d69af47ae2fb352 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:40:10 +0000 Subject: [PATCH 70/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/format.py | 2 ++ tests/test_writer.py | 42 +++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/ome_zarr/format.py b/ome_zarr/format.py index 7878a8c9..7aecbb7b 100644 --- a/ome_zarr/format.py +++ b/ome_zarr/format.py @@ -383,6 +383,7 @@ def chunk_key_encoding(self) -> dict[str, str]: # this is default for Zarr v3. Could return None? return {"name": "default", "separator": "/"} + class FormatV06(FormatV05): """ Changelog: added FormatV06 (June 2025): writing not supported yet @@ -396,4 +397,5 @@ def version(self) -> str: def zarr_format(self) -> int: return 3 + CurrentFormat = FormatV06 diff --git a/tests/test_writer.py b/tests/test_writer.py index 05e99d70..396401cf 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -16,8 +16,8 @@ from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image from ome_zarr_models.v05.well import Well as Models05Well -from ome_zarr_models.v06.image import Image as Models06Image from ome_zarr_models.v06.hcs import HCS as Models06HCS +from ome_zarr_models.v06.image import Image as Models06Image from ome_zarr_models.v06.well import Well as Models06Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec @@ -62,7 +62,7 @@ FORMAT_VERSIONS = [ pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06") + pytest.param(FormatV06, id="V06"), ] ARRAY_CONSTRUCTORS = [np.array, da.from_array] @@ -91,7 +91,9 @@ def _make_storage_options(fmt, shape, axes): "chunks": (16, 16), "shards": (32, 32) if fmt.version in ("0.5", "0.6") else None, "compressors": compressor, - "serializer": BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None, + "serializer": ( + BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None + ), "fill_value": 0, "dimension_names": list(axes) if fmt.version in ("0.5", "0.6") else None, "order": "C", @@ -531,9 +533,7 @@ def test_writer( else: assert node_data[0].shape == shape - if fmt.version == "0.4": - axes_md = node_metadata["multiscales"][0].get("axes") - elif fmt.version == "0.5": + if fmt.version == "0.4" or fmt.version == "0.5": axes_md = node_metadata["multiscales"][0].get("axes") elif fmt.version == "0.6": axes_md = node_metadata["multiscales"][0]["coordinateSystems"][0].get( @@ -910,7 +910,7 @@ def test_write_image_scalar_chunks(self): ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06") + pytest.param(FormatV06, id="V06"), ), ) def test_write_image_compressed(self, array_constructor, format_version): @@ -1166,13 +1166,7 @@ def initdir(self, tmpdir): self.path_v3 = self.path / "v3" self.root_v3 = zarr.open_group(self.path_v3, mode="w", zarr_format=3) - @pytest.mark.parametrize( - "fmt", ( - FormatV04(), - FormatV05(), - FormatV06() - ) - ) + @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05(), FormatV06())) def test_multi_levels_transformations(self, fmt): datasets = [] for level, transf in enumerate(TRANSFORMATIONS): @@ -2117,7 +2111,11 @@ def test_write_labels_with_storage_options( level0.chunks == expected_chunks ), f"Expected chunks {expected_chunks}, got {level0.chunks}" - if USE_DASK_ARRAY_KWARGS and fmt.version in ("0.5", "0.6") and hasattr(level0, "shards"): + if ( + USE_DASK_ARRAY_KWARGS + and fmt.version in ("0.5", "0.6") + and hasattr(level0, "shards") + ): expected_shards = _retuple(storage_options["shards"], level0.shape) assert ( level0.shards == expected_shards @@ -2352,9 +2350,10 @@ def test_write_multiscale_labels_storage_options( @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06"), + ( + pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06"), ), ) def test_two_label_images(self, array_constructor, fmt): @@ -2427,9 +2426,10 @@ def test_two_label_images(self, array_constructor, fmt): @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06") + ( + pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06"), ), ) def write_labels_class_API(self, fmt): From 934096cb57394d09eca57f7403fbecc694cbbebd Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:29:02 +0200 Subject: [PATCH 71/95] Revert "[pre-commit.ci] auto fixes from pre-commit.com hooks" This reverts commit e0a03cd4bd0c40813c089e031d69af47ae2fb352. --- ome_zarr/format.py | 2 -- tests/test_writer.py | 42 +++++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/ome_zarr/format.py b/ome_zarr/format.py index 7aecbb7b..7878a8c9 100644 --- a/ome_zarr/format.py +++ b/ome_zarr/format.py @@ -383,7 +383,6 @@ def chunk_key_encoding(self) -> dict[str, str]: # this is default for Zarr v3. Could return None? return {"name": "default", "separator": "/"} - class FormatV06(FormatV05): """ Changelog: added FormatV06 (June 2025): writing not supported yet @@ -397,5 +396,4 @@ def version(self) -> str: def zarr_format(self) -> int: return 3 - CurrentFormat = FormatV06 diff --git a/tests/test_writer.py b/tests/test_writer.py index 396401cf..05e99d70 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -16,8 +16,8 @@ from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image from ome_zarr_models.v05.well import Well as Models05Well -from ome_zarr_models.v06.hcs import HCS as Models06HCS from ome_zarr_models.v06.image import Image as Models06Image +from ome_zarr_models.v06.hcs import HCS as Models06HCS from ome_zarr_models.v06.well import Well as Models06Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec @@ -62,7 +62,7 @@ FORMAT_VERSIONS = [ pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06"), + pytest.param(FormatV06, id="V06") ] ARRAY_CONSTRUCTORS = [np.array, da.from_array] @@ -91,9 +91,7 @@ def _make_storage_options(fmt, shape, axes): "chunks": (16, 16), "shards": (32, 32) if fmt.version in ("0.5", "0.6") else None, "compressors": compressor, - "serializer": ( - BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None - ), + "serializer": BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None, "fill_value": 0, "dimension_names": list(axes) if fmt.version in ("0.5", "0.6") else None, "order": "C", @@ -533,7 +531,9 @@ def test_writer( else: assert node_data[0].shape == shape - if fmt.version == "0.4" or fmt.version == "0.5": + if fmt.version == "0.4": + axes_md = node_metadata["multiscales"][0].get("axes") + elif fmt.version == "0.5": axes_md = node_metadata["multiscales"][0].get("axes") elif fmt.version == "0.6": axes_md = node_metadata["multiscales"][0]["coordinateSystems"][0].get( @@ -910,7 +910,7 @@ def test_write_image_scalar_chunks(self): ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06"), + pytest.param(FormatV06, id="V06") ), ) def test_write_image_compressed(self, array_constructor, format_version): @@ -1166,7 +1166,13 @@ def initdir(self, tmpdir): self.path_v3 = self.path / "v3" self.root_v3 = zarr.open_group(self.path_v3, mode="w", zarr_format=3) - @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05(), FormatV06())) + @pytest.mark.parametrize( + "fmt", ( + FormatV04(), + FormatV05(), + FormatV06() + ) + ) def test_multi_levels_transformations(self, fmt): datasets = [] for level, transf in enumerate(TRANSFORMATIONS): @@ -2111,11 +2117,7 @@ def test_write_labels_with_storage_options( level0.chunks == expected_chunks ), f"Expected chunks {expected_chunks}, got {level0.chunks}" - if ( - USE_DASK_ARRAY_KWARGS - and fmt.version in ("0.5", "0.6") - and hasattr(level0, "shards") - ): + if USE_DASK_ARRAY_KWARGS and fmt.version in ("0.5", "0.6") and hasattr(level0, "shards"): expected_shards = _retuple(storage_options["shards"], level0.shape) assert ( level0.shards == expected_shards @@ -2350,10 +2352,9 @@ def test_write_multiscale_labels_storage_options( @pytest.mark.parametrize( "fmt", - ( - pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06"), + (pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06"), ), ) def test_two_label_images(self, array_constructor, fmt): @@ -2426,10 +2427,9 @@ def test_two_label_images(self, array_constructor, fmt): @pytest.mark.parametrize( "fmt", - ( - pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06"), + (pytest.param(FormatV04(), id="V04"), + pytest.param(FormatV05(), id="V05"), + pytest.param(FormatV06(), id="V06") ), ) def write_labels_class_API(self, fmt): From 62ecec63c6d177edb88fb8f23b3df4703b90d4a3 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:29:06 +0200 Subject: [PATCH 72/95] Revert "tests: add checks for what version is encountered" This reverts commit 75b9402fc0bf318bb072663b068ebd25284dc789. --- tests/test_writer.py | 173 +++++++++++++------------------------------ 1 file changed, 50 insertions(+), 123 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 05e99d70..836b7461 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -16,9 +16,6 @@ from ome_zarr_models.v05.hcs import HCS as Models05HCS from ome_zarr_models.v05.image import Image as Models05Image from ome_zarr_models.v05.well import Well as Models05Well -from ome_zarr_models.v06.image import Image as Models06Image -from ome_zarr_models.v06.hcs import HCS as Models06HCS -from ome_zarr_models.v06.well import Well as Models06Well from skimage.data import binary_blobs from zarr.abc.codec import BytesBytesCodec from zarr.codecs import BloscCodec @@ -34,7 +31,6 @@ FormatV03, FormatV04, FormatV05, - FormatV06, format_from_version, ) from ome_zarr.scale import _build_pyramid @@ -62,7 +58,6 @@ FORMAT_VERSIONS = [ pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06") ] ARRAY_CONSTRUCTORS = [np.array, da.from_array] @@ -89,11 +84,11 @@ def _make_storage_options(fmt, shape, axes): ) options = { "chunks": (16, 16), - "shards": (32, 32) if fmt.version in ("0.5", "0.6") else None, + "shards": (32, 32) if fmt.version == "0.5" else None, "compressors": compressor, - "serializer": BytesCodec(endian="little") if fmt.version in ("0.5", "0.6") else None, + "serializer": BytesCodec(endian="little") if fmt.version == "0.5" else None, "fill_value": 0, - "dimension_names": list(axes) if fmt.version in ("0.5", "0.6") else None, + "dimension_names": list(axes) if fmt.version == "0.5" else None, "order": "C", } return {k: v for k, v in options.items() if v is not None} @@ -460,7 +455,7 @@ def test_writer( ): fmt = format_version_all() - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": grp_path = self.path_v3 / "test" else: grp_path = self.path / "test" @@ -492,7 +487,7 @@ def test_writer( ) storage_options = _make_storage_options(fmt, shape, axes) - if fmt.version not in ("0.5", "0.6"): + if fmt.version != "0.5": chunks = [(128, 128), (50, 50), (25, 25), (25, 25), (25, 25), (25, 25)] if storage_options_list: @@ -531,30 +526,16 @@ def test_writer( else: assert node_data[0].shape == shape - if fmt.version == "0.4": - axes_md = node_metadata["multiscales"][0].get("axes") - elif fmt.version == "0.5": - axes_md = node_metadata["multiscales"][0].get("axes") - elif fmt.version == "0.6": - axes_md = node_metadata["multiscales"][0]["coordinateSystems"][0].get( - "axes" - ) - for ax in axes_md: + for ax in node_metadata["multiscales"][0].get("axes"): if ax["name"] in axes_units: assert ax.get("unit") == axes_units[ax["name"]] print("node.metadata", node_metadata) if fmt.version not in ("0.1", "0.2", "0.3"): - if fmt.version in ("0.4", "0.5"): - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] - elif fmt.version == "0.6": - cts = [ - d["coordinateTransformations"][0]["transformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for transf, expected in zip(cts, transformations): assert transf == expected assert len(cts) == len(node_data) @@ -571,8 +552,6 @@ def test_writer( Models04Image.from_zarr(out) elif fmt.version == "0.5": Models05Image.from_zarr(out) - elif fmt.version == "0.6": - Models06Image.from_zarr(out) @pytest.mark.parametrize( "format_version", @@ -592,7 +571,7 @@ def test_writer_with_scale( """ version = format_version() - if version.version in ("0.5", "0.6"): + if version.version == "0.5": grp_path = self.path_v3 / "test" else: grp_path = self.path / "test" @@ -644,8 +623,6 @@ def test_writer_with_scale( Models04Image.from_zarr(out) elif version.version == "0.5": Models05Image.from_zarr(out) - elif version.version == "0.6": - Models06Image.from_zarr(out) def test_mix_zarr_formats(self): # check group zarr v2 and v3 matches fmt @@ -711,21 +688,10 @@ def test_write_image_current(self, array_constructor, zarr_format): node_metadata = out.attrs if "ome" in node_metadata: node_metadata = node_metadata["ome"] - version = node_metadata.get("version") - assert version in ("0.5", "0.6") - else: - version = "0.4" - - if version in ("0.4", "0.5"): - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] - elif version == "0.6": - cts = [ - d["coordinateTransformations"][0]["transformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for level, transfs in enumerate(cts): assert len(transfs) == 2 assert transfs[0]["type"] == "scale" @@ -818,25 +784,15 @@ def test_write_image_dask(self, read_from_zarr, compute, zarr_format): node_metadata = out.attrs if "ome" in node_metadata: node_metadata = node_metadata["ome"] - version = node_metadata.get("version", "0.6") - - else: - version = node_metadata.get("version", "0.4") paths = [d["path"] for d in node_metadata["multiscales"][0]["datasets"]] image_node_data = [da.from_zarr(out[path]) for path in paths] first_chunk = [c[0] for c in image_node_data[0].chunks] assert tuple(first_chunk) == _retuple(chunks, image_node_data[0].shape) - if version in ("0.4", "0.5"): - cts = [ - d["coordinateTransformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] - else: - cts = [ - d["coordinateTransformations"][0]["transformations"] - for d in node_metadata["multiscales"][0]["datasets"] - ] + cts = [ + d["coordinateTransformations"] + for d in node_metadata["multiscales"][0]["datasets"] + ] for level, transfs in enumerate(cts): assert len(transfs) == 2 assert transfs[0]["type"] == "scale" @@ -885,8 +841,6 @@ def test_write_image_dask(self, read_from_zarr, compute, zarr_format): Models04Image.from_zarr(group) elif fmt.version == "0.5": Models05Image.from_zarr(group) - elif fmt.version == "0.6": - Models06Image.from_zarr(group) def test_write_image_scalar_chunks(self): """ @@ -910,7 +864,6 @@ def test_write_image_scalar_chunks(self): ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06") ), ) def test_write_image_compressed(self, array_constructor, format_version): @@ -968,20 +921,17 @@ def test_write_image_compressed(self, array_constructor, format_version): "shuffle": Blosc.SHUFFLE, "blocksize": 0, } - assert format_version().version in ("0.4", "0.5", "0.6") + assert format_version().version in ("0.4", "0.5") if format_version().version == "0.4": Models04Image.from_zarr(group) elif format_version().version == "0.5": Models05Image.from_zarr(group) - elif format_version().version == "0.6": - Models06Image.from_zarr(group) @pytest.mark.parametrize( "format_version", ( pytest.param(FormatV04, id="V04"), pytest.param(FormatV05, id="V05"), - pytest.param(FormatV06, id="V06"), ), ) def test_default_compression(self, array_constructor, format_version): @@ -1166,25 +1116,19 @@ def initdir(self, tmpdir): self.path_v3 = self.path / "v3" self.root_v3 = zarr.open_group(self.path_v3, mode="w", zarr_format=3) - @pytest.mark.parametrize( - "fmt", ( - FormatV04(), - FormatV05(), - FormatV06() - ) - ) + @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) def test_multi_levels_transformations(self, fmt): datasets = [] for level, transf in enumerate(TRANSFORMATIONS): datasets.append({"path": str(level), "coordinateTransformations": transf}) - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": path = self.path_v3 else: path = self.path write_multiscales_metadata(str(path), datasets, axes="tczyx", fmt=fmt) # we want to be sure this is zarr v2 / v3 attrs = zarr.open_group(path).attrs - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": attrs = attrs.get("ome") assert "version" in attrs json_text = (self.path_v3 / "zarr.json").read_text(encoding="utf-8") @@ -1206,10 +1150,8 @@ def test_multi_levels_transformations(self, fmt): out = zarr.open_group(path) if fmt.version == "0.4": Models04Image.from_zarr(out) - elif fmt.version == "0.5": + if fmt.version == "0.5": Models05Image.from_zarr(out) - elif fmt.version == "0.6": - Models06Image.from_zarr(out) @pytest.mark.parametrize( "axes", @@ -1463,12 +1405,9 @@ def test_minimal_plate(self, fmt): assert fmt.version in ("0.4", "0.5") if fmt.version == "0.4": Models04HCS.from_zarr(group) - elif fmt.version in ("0.5", "0.6"): + elif fmt.version == "0.5": # https://github.com/ome-zarr-models/ome-zarr-models-py/issues/218 - if fmt.version == "0.5": - Models05HCS.from_zarr(group) - elif fmt.version == "0.6": - Models06HCS.from_zarr(group) + Models05HCS.from_zarr(group) @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) def test_12wells_plate(self, fmt): @@ -1531,8 +1470,6 @@ def test_12wells_plate(self, fmt): Models04HCS.from_zarr(group) elif fmt.version == "0.5": Models05HCS.from_zarr(group) - elif fmt.version == "0.6": - Models06HCS.from_zarr(group) @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) def test_sparse_plate(self, fmt): @@ -1577,8 +1514,6 @@ def test_sparse_plate(self, fmt): Models04HCS.from_zarr(group) elif fmt.version == "0.5": Models05HCS.from_zarr(group) - elif fmt.version == "0.6": - Models06HCS.from_zarr(group) def test_plate_name(self): # We don't need to test v04 and v05 for all tests since @@ -1793,14 +1728,14 @@ def initdir(self, tmpdir): @pytest.mark.parametrize("fmt", (FormatV04(), FormatV05())) @pytest.mark.parametrize("images", (["0"], [{"path": "0"}])) def test_minimal_well(self, images, fmt): - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": group = self.root_v3 else: group = self.root write_well_metadata(group, images) # we want to be sure this is zarr v2 / v3, so we load json manually too attrs = group.attrs - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": attrs = attrs.get("ome") assert attrs["version"] == fmt.version json_text = (self.path_v3 / "zarr.json").read_text(encoding="utf-8") @@ -1816,8 +1751,6 @@ def test_minimal_well(self, images, fmt): Models04Well.from_zarr(group) elif fmt.version == "0.5": Models05Well.from_zarr(group) - elif fmt.version == "0.6": - Models06Well.from_zarr(group) @pytest.mark.parametrize( "images", @@ -1959,14 +1892,14 @@ def verify_label_data( # Verify label metadata label_root = zarr.open(f"{img_path}/labels", mode="r") label_attrs = label_root.attrs - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": label_attrs = label_attrs["ome"] assert "labels" in label_attrs assert label_name in label_attrs["labels"] label_group = zarr.open(f"{img_path}/labels/{label_name}", mode="r") imglabel_attrs = label_group.attrs - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": imglabel_attrs = imglabel_attrs["ome"] assert imglabel_attrs["version"] == fmt.version else: @@ -1989,7 +1922,7 @@ def test_write_labels( ): fmt = format_version_all() - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": img_path = self.path_v3 group = self.root_v3 else: @@ -2057,7 +1990,7 @@ def test_write_labels_with_storage_options( array_constructor, ): fmt = format_version_all() - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": group = self.root_v3 else: group = self.root @@ -2117,7 +2050,7 @@ def test_write_labels_with_storage_options( level0.chunks == expected_chunks ), f"Expected chunks {expected_chunks}, got {level0.chunks}" - if USE_DASK_ARRAY_KWARGS and fmt.version in ("0.5", "0.6") and hasattr(level0, "shards"): + if USE_DASK_ARRAY_KWARGS and fmt.version == "0.5" and hasattr(level0, "shards"): expected_shards = _retuple(storage_options["shards"], level0.shape) assert ( level0.shards == expected_shards @@ -2126,7 +2059,7 @@ def test_write_labels_with_storage_options( assert level0.fill_value == 0 if level0.compressors: - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": if USE_DASK_ARRAY_KWARGS: assert level0.compressors[0].cname.name == "zstd" else: @@ -2135,7 +2068,7 @@ def test_write_labels_with_storage_options( assert level0.compressors[0].cname == "zstd" if USE_DASK_ARRAY_KWARGS: assert level0.compressors[0].clevel == 3 - if fmt.version in ("0.5", "0.6") and hasattr(level0, "serializer"): + if fmt.version == "0.5" and hasattr(level0, "serializer"): assert ( level0.metadata.codecs[0].index_codecs[0].endian.name == "little" @@ -2143,7 +2076,7 @@ def test_write_labels_with_storage_options( else: assert level0.compressors[0].to_dict()["configuration"]["level"] == 0 - if fmt.version in ("0.5", "0.6") and hasattr(level0, "dimension_names"): + if fmt.version == "0.5" and hasattr(level0, "dimension_names"): assert level0.dimension_names == tuple(axes) if fmt.version == "0.4": @@ -2153,7 +2086,7 @@ def test_write_multiscale_labels( self, shape, format_version_all, array_constructor ): fmt = format_version_all() - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": img_path = self.path_v3 group = self.root_v3 else: @@ -2223,7 +2156,7 @@ def test_write_multiscale_labels_storage_options( self, shape, format_version_all, array_constructor ): fmt = format_version_all() - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": img_path = self.path_v3 group = self.root_v3 else: @@ -2306,7 +2239,7 @@ def test_write_multiscale_labels_storage_options( if ( USE_DASK_ARRAY_KWARGS - and fmt.version in ("0.5", "0.6") + and fmt.version == "0.5" and hasattr(level, "shards") ): expected_shards = _retuple(storage_options["shards"], level.shape) @@ -2319,7 +2252,7 @@ def test_write_multiscale_labels_storage_options( ), f"Level {level_idx}: Expected fill_value 0, got {level.fill_value}" if level.compressors: - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": if USE_DASK_ARRAY_KWARGS: assert level.compressors[0].cname.name == "zstd" else: @@ -2328,7 +2261,7 @@ def test_write_multiscale_labels_storage_options( assert level.compressors[0].cname == "zstd" if USE_DASK_ARRAY_KWARGS: assert level.compressors[0].clevel == 3 - if fmt.version in ("0.5", "0.6") and hasattr(level, "serializer"): + if fmt.version == "0.5" and hasattr(level, "serializer"): assert ( level.metadata.codecs[0].index_codecs[0].endian.name == "little" @@ -2336,7 +2269,7 @@ def test_write_multiscale_labels_storage_options( else: assert level.compressors[0].to_dict()["configuration"]["level"] == 0 - if fmt.version in ("0.5", "0.6") and hasattr(level, "dimension_names"): + if fmt.version == "0.5" and hasattr(level, "dimension_names"): assert level.dimension_names == tuple( axes ), f"Level {level_idx}: Expected dimension names {tuple(axes)}, got {level.dimension_names}" @@ -2352,13 +2285,10 @@ def test_write_multiscale_labels_storage_options( @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06"), - ), + (pytest.param(FormatV04(), id="V04"), pytest.param(FormatV05(), id="V05")), ) def test_two_label_images(self, array_constructor, fmt): - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": img_path = self.path_v3 group = self.root_v3 else: @@ -2419,7 +2349,7 @@ def test_two_label_images(self, array_constructor, fmt): # Verify label metadata label_root = zarr.open(f"{img_path}/labels", mode="r") attrs = label_root.attrs - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": attrs = attrs["ome"] assert "labels" in attrs assert len(attrs["labels"]) == len(label_names) @@ -2427,15 +2357,12 @@ def test_two_label_images(self, array_constructor, fmt): @pytest.mark.parametrize( "fmt", - (pytest.param(FormatV04(), id="V04"), - pytest.param(FormatV05(), id="V05"), - pytest.param(FormatV06(), id="V06") - ), + (pytest.param(FormatV04(), id="V04"), pytest.param(FormatV05(), id="V05")), ) def write_labels_class_API(self, fmt): from ome_zarr import OMEZarrImage, OMEZarrMultiscale - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": img_path = self.path_v3 group = self.root_v3 else: @@ -2465,7 +2392,7 @@ def write_labels_class_API(self, fmt): # now check that the respective groups and metadata exist and is correct label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs @@ -2483,7 +2410,7 @@ def write_labels_class_API(self, fmt): # now check that we still have both labels in the metadata label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs @@ -2499,7 +2426,7 @@ def write_labels_class_API(self, fmt): # Now, only the third label should be present in the metadata and as a zarr group, # but the first and second labels should be gone label_group = zarr.open(f"{img_path}/labels", mode="r") - if fmt.version in ("0.5", "0.6"): + if fmt.version == "0.5": label_attrs = label_group.attrs["ome"] elif fmt.version == "0.4": label_attrs = label_group.attrs From a9da128a6fe3a9cfc6fada18f40c4bb11c4f3443 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:29:10 +0200 Subject: [PATCH 73/95] Revert "feat: add formatV06 class" This reverts commit b12fb12246005651b3b546af629318831ba2902b. --- ome_zarr/format.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/ome_zarr/format.py b/ome_zarr/format.py index 7878a8c9..3b2c33e7 100644 --- a/ome_zarr/format.py +++ b/ome_zarr/format.py @@ -383,17 +383,5 @@ def chunk_key_encoding(self) -> dict[str, str]: # this is default for Zarr v3. Could return None? return {"name": "default", "separator": "/"} -class FormatV06(FormatV05): - """ - Changelog: added FormatV06 (June 2025): writing not supported yet - """ - - @property - def version(self) -> str: - return "0.6" - - @property - def zarr_format(self) -> int: - return 3 -CurrentFormat = FormatV06 +CurrentFormat = FormatV05 From dcd38587aa0f8ceaab06df49849b0964415c2bcf Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:32:22 +0200 Subject: [PATCH 74/95] deps: pin to zarr-python <3.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5df1ad4c..6dc5d2f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ requires-python = ">3.11" dependencies = [ "numpy", "dask>=2025.2.0,!=2025.12.*,!=2026.1.*,!=2026.2.*", - "zarr>=3.0.0", + "zarr>=3.0.0, <3.3.0", "fsspec[s3]>=0.8,!=2021.07.0,!=2023.9.0", "aiohttp", "requests", From 75d9b107b83087f68b3f1333eda16cfc5c10e54f Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 31 Jul 2026 16:57:58 +0200 Subject: [PATCH 75/95] fix(tests): support plain-string codec attributes in zarr >= 3.3 zarr 3.3.0 changed codec configuration attributes such as `BloscCodec.cname` and `BytesCodec.endian` from `Enum` members to plain strings, breaking the `.name` accesses in the label writer storage options tests (12 failures). Add a `_codec_value` helper that normalizes both representations, so the tests pass against zarr 3.2 and 3.3 alike. Test-only change; the library code never accessed these attributes. Assisted-by: ClaudeCode:claude-opus-5 --- tests/test_writer.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_writer.py b/tests/test_writer.py index 54811c11..18ef1ca1 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -70,6 +70,16 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("array_constructor", ARRAY_CONSTRUCTORS) +def _codec_value(value): + """Return the plain value of a codec configuration attribute. + + zarr < 3.3 exposes attributes such as ``BloscCodec.cname`` and + ``BytesCodec.endian`` as (non-str) ``Enum`` members, while zarr >= 3.3 uses + plain strings. + """ + return getattr(value, "value", value) + + def _make_storage_options(fmt, shape, axes): from numcodecs import Blosc from zarr.codecs import ( @@ -2057,7 +2067,7 @@ def test_write_labels_with_storage_options( if level0.compressors: if fmt.version == "0.5": if USE_DASK_ARRAY_KWARGS: - assert level0.compressors[0].cname.name == "zstd" + assert _codec_value(level0.compressors[0].cname) == "zstd" else: assert level0.compressors[0].to_dict()["name"] == "zstd" else: @@ -2066,7 +2076,7 @@ def test_write_labels_with_storage_options( assert level0.compressors[0].clevel == 3 if fmt.version == "0.5" and hasattr(level0, "serializer"): assert ( - level0.metadata.codecs[0].index_codecs[0].endian.name + _codec_value(level0.metadata.codecs[0].index_codecs[0].endian) == "little" ) else: @@ -2250,7 +2260,7 @@ def test_write_multiscale_labels_storage_options( if level.compressors: if fmt.version == "0.5": if USE_DASK_ARRAY_KWARGS: - assert level.compressors[0].cname.name == "zstd" + assert _codec_value(level.compressors[0].cname) == "zstd" else: assert level.compressors[0].to_dict()["name"] == "zstd" else: @@ -2259,7 +2269,9 @@ def test_write_multiscale_labels_storage_options( assert level.compressors[0].clevel == 3 if fmt.version == "0.5" and hasattr(level, "serializer"): assert ( - level.metadata.codecs[0].index_codecs[0].endian.name + _codec_value( + level.metadata.codecs[0].index_codecs[0].endian + ) == "little" ) else: From 90be3598abc2771a701c3f541a81c05bcdb7affb Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:52:15 +0200 Subject: [PATCH 76/95] Revert "deps: pin to zarr-python <3.3.0" This reverts commit dcd38587aa0f8ceaab06df49849b0964415c2bcf. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dc5d2f1..5df1ad4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ requires-python = ">3.11" dependencies = [ "numpy", "dask>=2025.2.0,!=2025.12.*,!=2026.1.*,!=2026.2.*", - "zarr>=3.0.0, <3.3.0", + "zarr>=3.0.0", "fsspec[s3]>=0.8,!=2021.07.0,!=2023.9.0", "aiohttp", "requests", From 1265847b14c0527e1db632b584489c60bb7dfd12 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:16:26 +0200 Subject: [PATCH 77/95] docs: fix jupyter notebook --- docs/source/advanced/transforms/create_scenes.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index df716b2a..ef7bf469 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -112,6 +112,7 @@ "source": [ "We then define translations that move each tile into the appropriate position in the world coordinate system.\n", "In this example, these are simple translations in the y and x dimensions:" + ] }, { "cell_type": "code", From f3d47bba7a8c9544a2c4b013988f0cde48f0f979 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:25:40 +0200 Subject: [PATCH 78/95] chore: pre-commit fixes --- ome_zarr/classes/scene.py | 39 ++++++++++++++++++++------------------- ome_zarr/writer.py | 9 ++++----- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 495d4283..47b2b30a 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -74,14 +74,14 @@ def get_coordinate_system( """ if path is None: # coordinate system can only be in top-level - possible_coordinate_systems = self.coordinate_systems or [] + possible_coordinate_systems: Sequence[CoordinateSystem] = self.coordinate_systems or [] return next( (cs for cs in possible_coordinate_systems if cs.name == name), None ) possible_coordinate_systems = [] - for group in self.images.keys(): + for group in self.images: if group == path: img = self.images[group] for cs in img.metadata.coordinateSystems: @@ -123,7 +123,7 @@ def _build_graph(self): if img.metadata.coordinateTransformations: for img_tf in img.metadata.coordinateTransformations: ind_transform = self._ozmp_tf_to_tnd( - img_tf, zarr_context=subgroup + img_tf, zarr_context=subgroup, source_cs=None, target_cs=None ) self._graph.add_transform(ind_transform) @@ -141,10 +141,8 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil - import tqdm - - from ..utils import _recursive_pop_nones + from ome_zarr.utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): # Clear the store if it already exists and we're not doing incremental writes @@ -199,7 +197,7 @@ def from_ome_zarr(cls, store: StoreLike): zarr_group = zarr.open(store, mode="r") # Load all image subgroups, keyed by their zarr path - images = {} + images: dict[str, OMEZarrMultiscale] = {} for img_path in zarr_group.group_keys(): img_group = zarr_group[img_path] img = OMEZarrMultiscale.from_ome_zarr(img_group) @@ -212,9 +210,10 @@ def from_ome_zarr(cls, store: StoreLike): # Note: Reconstructing Transform and CoordinateSystemIdentifier objects from dicts # may require additional deserialization logic depending on your models - transformations = [] - for tf in scene_metadata.get("coordinateTransformations", []): - transformations.append(tf_adapter.validate_python(tf)) + transformations = [ + tf_adapter.validate_python(tf) + for tf in scene_metadata.get("coordinateTransformations", []) + ] if "coordinateSystems" in scene_metadata: coordinate_systems = [ @@ -362,15 +361,17 @@ def _ozmp_tf_to_tnd( if zarr_context != "" and path_to_dfield != "": path_to_dfield = f"{zarr_context}/{path_to_dfield}" - dfield = self.coordinates_displacements.get(path_to_dfield) - tnd_transform = tnd.transforms.Displacements( - dfield.images[0].data, - index_transform=tnd.transforms.Scale( - list(dfield.images[0].scale.values())[1:] - ), - vector_axis=0, - spaces=spaces, - ) + if self.coordinates_displacements is not None: + dfield = self.coordinates_displacements.get(path_to_dfield) + if dfield is not None: + tnd_transform = tnd.transforms.Displacements( + dfield.images[0].data, + index_transform=tnd.transforms.Scale( + list(dfield.images[0].scale.values())[1:] + ), + vector_axis=0, + spaces=spaces, + ) elif transform.type == "mapAxis": tnd_transform = tnd.transforms.MapAxis( list(transform.mapAxis), diff --git a/ome_zarr/writer.py b/ome_zarr/writer.py index aa3833ce..23489461 100644 --- a/ome_zarr/writer.py +++ b/ome_zarr/writer.py @@ -4,7 +4,7 @@ import warnings from collections.abc import Sequence from pathlib import Path -from typing import Any, TypeAlias +from typing import Any import dask.array as da import numpy as np @@ -19,10 +19,9 @@ LOGGER = logging.getLogger("ome_zarr.writer") -ListOfArrayLike = list[da.Array] | list[np.ndarray] -ArrayLike: TypeAlias = da.Array | np.ndarray - -AxesType = str | list[str] | list[dict[str, str]] | None +type ListOfArrayLike = list[da.Array] | list[np.ndarray] +type ArrayLike = da.Array | np.ndarray +type AxesType = str | list[str] | list[dict[str, str]] | None SPATIAL_DIMS = ("x", "y", "z") From baeb9f4b85980787c594e2d243c1acf97af7b2c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:25:59 +0000 Subject: [PATCH 79/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 47b2b30a..555708e3 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -74,7 +74,9 @@ def get_coordinate_system( """ if path is None: # coordinate system can only be in top-level - possible_coordinate_systems: Sequence[CoordinateSystem] = self.coordinate_systems or [] + possible_coordinate_systems: Sequence[CoordinateSystem] = ( + self.coordinate_systems or [] + ) return next( (cs for cs in possible_coordinate_systems if cs.name == name), None @@ -123,7 +125,10 @@ def _build_graph(self): if img.metadata.coordinateTransformations: for img_tf in img.metadata.coordinateTransformations: ind_transform = self._ozmp_tf_to_tnd( - img_tf, zarr_context=subgroup, source_cs=None, target_cs=None + img_tf, + zarr_context=subgroup, + source_cs=None, + target_cs=None, ) self._graph.add_transform(ind_transform) @@ -141,7 +146,9 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): """ import shutil + import tqdm + from ome_zarr.utils import _recursive_pop_nones if overwrite and os.path.exists(str(store)): From bafd348b1809790be023256bbca15de762a42dde Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:34:31 +0200 Subject: [PATCH 80/95] docs: add loading to documentation --- .../advanced/transforms/create_scenes.ipynb | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index ef7bf469..07d7b225 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "id": "a49ec1c2", "metadata": {}, "outputs": [], @@ -34,17 +34,25 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "e3023c95", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\johan\\Documents\\GitHub\\ome-zarr-py\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + }, { "data": { "text/plain": [ "(512, 512)" ] }, - "execution_count": 5, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -64,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "017d4c59", "metadata": {}, "outputs": [], @@ -91,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "id": "994674c2", "metadata": {}, "outputs": [], @@ -116,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "id": "d6329caa", "metadata": {}, "outputs": [], @@ -159,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "d05be093", "metadata": {}, "outputs": [ @@ -167,7 +175,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 4/4 [00:00<00:00, 5.91it/s]\n" + "Writing images: 100%|██████████| 4/4 [00:00<00:00, 7.08it/s]\n" ] } ], @@ -181,26 +189,46 @@ "scene.to_ome_zarr(\"test_example_scene.zarr\", overwrite=True)" ] }, + { + "cell_type": "markdown", + "id": "9333cd91", + "metadata": {}, + "source": [ + "....and load it back like this:" + ] + }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 12, "id": "8b87b923", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "(Translation(type='translation', input=CoordinateSystemIdentifier(name='physical', path='img1'), output=CoordinateSystemIdentifier(name='world', path=None), name=None, translation=(0.0, 0.0)),\n", + " Translation(type='translation', input=CoordinateSystemIdentifier(name='physical', path='img2'), output=CoordinateSystemIdentifier(name='world', path=None), name=None, translation=(256.0, 0.0)),\n", + " Translation(type='translation', input=CoordinateSystemIdentifier(name='physical', path='img3'), output=CoordinateSystemIdentifier(name='world', path=None), name=None, translation=(0.0, 256.0)),\n", + " Translation(type='translation', input=CoordinateSystemIdentifier(name='physical', path='img4'), output=CoordinateSystemIdentifier(name='world', path=None), name=None, translation=(256.0, 256.0)))" ] }, - "execution_count": 22, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "scene._graph.get_sequence(source_space=\"img1:physical\", target_space=\":world\")" + "loaded_scene = OMEZarrScene.from_ome_zarr(\"test_example_scene.zarr\")\n", + "loaded_scene.coordinate_transformations" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd5dda7c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 2a9127d99e79b48c24b0bf7ff34fe39f55c6405a Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:34:48 +0200 Subject: [PATCH 81/95] feat: use ozmp classes to read and validate scene metadata --- ome_zarr/classes/scene.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 47b2b30a..634e7608 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -188,6 +188,7 @@ def from_ome_zarr(cls, store: StoreLike): Returns: NgffScene instance with images and metadata loaded from disk """ + from ome_zarr_models.v06.scene import SceneAttrs, BaseSceneAttrs tf_adapter = TypeAdapter(AnyTransform) # Handle both StoreLike (string, dict, etc.) and zarr.Group objects @@ -204,27 +205,10 @@ def from_ome_zarr(cls, store: StoreLike): images[img_path] = img # Load scene metadata - ome_metadata = zarr_group.attrs.get("ome", {}) - scene_metadata = ome_metadata.get("scene", {}) - - # Note: Reconstructing Transform and CoordinateSystemIdentifier objects from dicts - # may require additional deserialization logic depending on your models - - transformations = [ - tf_adapter.validate_python(tf) - for tf in scene_metadata.get("coordinateTransformations", []) - ] - - if "coordinateSystems" in scene_metadata: - coordinate_systems = [ - CoordinateSystem.model_validate(cs) - for cs in scene_metadata.get("coordinateSystems", []) - if cs - ] - else: - coordinate_systems = None + scene_metadata = BaseSceneAttrs.model_validate(zarr_group.attrs.get("ome", {})) + transformations = scene_metadata.scene.coordinateTransformations + coordinate_systems = scene_metadata.scene.coordinateSystems - # Use object.__new__ to create instance without triggering __init__ and __setattr__ scene = OMEZarrScene( images=images, coordinate_transformations=transformations, @@ -364,6 +348,10 @@ def _ozmp_tf_to_tnd( if self.coordinates_displacements is not None: dfield = self.coordinates_displacements.get(path_to_dfield) if dfield is not None: + if dfield.images[0].scale is None: + raise ValueError( + f"Displacement field at {path_to_dfield} is missing scale information." + ) tnd_transform = tnd.transforms.Displacements( dfield.images[0].data, index_transform=tnd.transforms.Scale( From 345fa868131958df0698d5f48c02e5fee9838d35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:35:14 +0000 Subject: [PATCH 82/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/advanced/transforms/create_scenes.ipynb | 2 +- ome_zarr/classes/scene.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/advanced/transforms/create_scenes.ipynb b/docs/source/advanced/transforms/create_scenes.ipynb index 07d7b225..07512e66 100644 --- a/docs/source/advanced/transforms/create_scenes.ipynb +++ b/docs/source/advanced/transforms/create_scenes.ipynb @@ -175,7 +175,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Writing images: 100%|██████████| 4/4 [00:00<00:00, 7.08it/s]\n" + "Writing images: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 4/4 [00:00<00:00, 7.08it/s]\n" ] } ], diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 83925b99..bd7218ee 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -195,7 +195,8 @@ def from_ome_zarr(cls, store: StoreLike): Returns: NgffScene instance with images and metadata loaded from disk """ - from ome_zarr_models.v06.scene import SceneAttrs, BaseSceneAttrs + from ome_zarr_models.v06.scene import BaseSceneAttrs + tf_adapter = TypeAdapter(AnyTransform) # Handle both StoreLike (string, dict, etc.) and zarr.Group objects From 32887a1333364e2b5646df6c949a0ce112f95174 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:37:09 +0200 Subject: [PATCH 83/95] chore: remove unused variable --- ome_zarr/classes/scene.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index bd7218ee..bf97b721 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -197,8 +197,6 @@ def from_ome_zarr(cls, store: StoreLike): """ from ome_zarr_models.v06.scene import BaseSceneAttrs - tf_adapter = TypeAdapter(AnyTransform) - # Handle both StoreLike (string, dict, etc.) and zarr.Group objects if isinstance(store, zarr.Group): zarr_group = store From a9bfeae5498fa912370c12feab76cabb9f37b0c8 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:39:20 +0200 Subject: [PATCH 84/95] chore: pre-commit fix --- ome_zarr/classes/scene.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index bf97b721..cdffda2b 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -1,7 +1,7 @@ # the class for storage representation, not exposed to the user import os from collections.abc import Sequence -from typing import Any +from typing import Any, cast import transformnd as tnd import zarr @@ -207,7 +207,7 @@ def from_ome_zarr(cls, store: StoreLike): images: dict[str, OMEZarrMultiscale] = {} for img_path in zarr_group.group_keys(): img_group = zarr_group[img_path] - img = OMEZarrMultiscale.from_ome_zarr(img_group) + img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) images[img_path] = img # Load scene metadata From 9a22b924e2ce7abdbaa504cfb89265ca21881ba1 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:03:28 +0200 Subject: [PATCH 85/95] feat: store coordinateTransformations under according subgroup --- ome_zarr/classes/scene.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index cdffda2b..a159b801 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -175,7 +175,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): continue # Write the displacement image - subgroup = zarr_group.create_group(disp_path, overwrite=overwrite) + subgroup = zarr_group.create_group(f"coordinateTransformations/{disp_path}", overwrite=overwrite) disp_img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") # Always update scene metadata @@ -352,7 +352,7 @@ def _ozmp_tf_to_tnd( path_to_dfield = f"{zarr_context}/{path_to_dfield}" if self.coordinates_displacements is not None: - dfield = self.coordinates_displacements.get(path_to_dfield) + dfield = self.coordinates_displacements.get(path_to_dfield.split("/")[-1]) if dfield is not None: if dfield.images[0].scale is None: raise ValueError( From 1c6cbeca397a3830450d3262fdc22527ff3857e6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:03:35 +0200 Subject: [PATCH 86/95] tests: add test for scene --- tests/test_scene.py | 63 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index 20af7685..c215d0bd 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -286,6 +286,65 @@ def test_appending_scene(test_data_dir): assert len(scene_metadata["coordinateTransformations"]) == 3 assert len(scene_metadata["coordinateSystems"]) == 1 +def test_scene_with_displacements(test_data_dir): + """ + Create a scene with two images and a single coordinate transformation + between them. The data is saved and loaded back in the test. + """ + shape = (64, 64) + img_a = OMEZarrImage( + data=create_data(shape), + name="imageA", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + img_b = OMEZarrImage( + data=create_data(shape), + name="imageB", + axes=["y", "x"], + scale={"y": 1.0, "x": 1.0}, + ) + + vector_field = np.zeros((2, 64, 64), dtype=np.float32) + dfield_img = OMEZarrImage( + data=vector_field, + name="displacementField", + axes=["c", "y", "x"], + scale={"y": 1.0, "x": 1.0}, + axes_types={"c": "displacement", "y": "space", "x": "space"}, + ) + + img_a_ms = OMEZarrMultiscale(image=img_a) + img_b_ms = OMEZarrMultiscale(image=img_b) + dfield_img_ms = OMEZarrMultiscale(image=dfield_img) + + transform = { + "type": "displacements", + "input": {"name": "physical", "path": "imageA"}, + "output": {"name": "physical", "path": "imageB"}, + "path": "coordinateTransformations/displacementField", + } + + scene = OMEZarrScene( + images=[img_a_ms, img_b_ms], + coordinate_transformations=[transform], + coordinates_displacements={"displacementField": dfield_img_ms}, + ) -if __name__ == "__main__": - pytest.main([__file__]) + save_grp = str(test_data_dir / "test_scene_displacement.zarr") + scene.to_ome_zarr(save_grp, overwrite=True) + + # check that all subgroups are there + group = zarr.open_group(save_grp, mode="r") + assert "coordinateTransformations" in group + assert "displacementField" in group["coordinateTransformations"] + assert "imageA" in group + assert "imageB" in group + + # check that the metadata of the displacement field is correct + dfield_attrs = group["coordinateTransformations"]["displacementField"].attrs + assert "ome" in dfield_attrs + axes_md = dfield_attrs["ome"]["multiscales"][0]["coordinateSystems"][0]["axes"] + assert axes_md[0]["type"] == "displacement" + assert axes_md[0]["discrete"] == True From 762b4874e6fe959178a37c3c9673cf52337cdae3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:03:50 +0000 Subject: [PATCH 87/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 8 ++++++-- tests/test_scene.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index a159b801..5fa913db 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -175,7 +175,9 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): continue # Write the displacement image - subgroup = zarr_group.create_group(f"coordinateTransformations/{disp_path}", overwrite=overwrite) + subgroup = zarr_group.create_group( + f"coordinateTransformations/{disp_path}", overwrite=overwrite + ) disp_img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") # Always update scene metadata @@ -352,7 +354,9 @@ def _ozmp_tf_to_tnd( path_to_dfield = f"{zarr_context}/{path_to_dfield}" if self.coordinates_displacements is not None: - dfield = self.coordinates_displacements.get(path_to_dfield.split("/")[-1]) + dfield = self.coordinates_displacements.get( + path_to_dfield.split("/")[-1] + ) if dfield is not None: if dfield.images[0].scale is None: raise ValueError( diff --git a/tests/test_scene.py b/tests/test_scene.py index c215d0bd..39cec732 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -286,6 +286,7 @@ def test_appending_scene(test_data_dir): assert len(scene_metadata["coordinateTransformations"]) == 3 assert len(scene_metadata["coordinateSystems"]) == 1 + def test_scene_with_displacements(test_data_dir): """ Create a scene with two images and a single coordinate transformation From c56ab5cfe34b5f36ddea648dd3e12b95951315a2 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:00:44 +0200 Subject: [PATCH 88/95] deps: undo dependency changes --- .github/workflows/build.yml | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da68ccd0..c609bc6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,12 +10,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.12', '3.13', '3.14'] + python-version: ['3.11', '3.12', '3.13'] os: ['windows-latest', 'macos-latest', 'ubuntu-latest'] dask-version: [ 'latest' ] include: - os: ubuntu-latest - python-version: '3.12' + python-version: '3.11' dask-version: '2025.2.0' steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 726d05b1..549fca1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ description="Implementation of images in Zarr files." license = "BSD-2-Clause" license-files = ["LICENSE"] -requires-python = ">=3.12" +requires-python = ">3.11" dependencies = [ "numpy", From f3ec4f317becad6edc3d6f40184a2ede75dfdc6e Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:58:05 +0200 Subject: [PATCH 89/95] feat: load coordinates/displacements back in before building graph --- ome_zarr/classes/scene.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 5fa913db..154453cb 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -212,6 +212,18 @@ def from_ome_zarr(cls, store: StoreLike): img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) images[img_path] = img + # load coordinateTransformations array data, if it exists + if "coordinateTransformations" in zarr_group: + coordinates_displacements = {} + for disp_path in zarr_group["coordinateTransformations"].group_keys(): + disp_group = zarr_group["coordinateTransformations"][disp_path] + disp_img = cast( + OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(disp_group) + ) + coordinates_displacements[disp_path] = disp_img + else: + coordinates_displacements = None + # Load scene metadata scene_metadata = BaseSceneAttrs.model_validate(zarr_group.attrs.get("ome", {})) transformations = scene_metadata.scene.coordinateTransformations @@ -221,6 +233,7 @@ def from_ome_zarr(cls, store: StoreLike): images=images, coordinate_transformations=transformations, coordinate_systems=coordinate_systems, + coordinates_displacements=coordinates_displacements ) return scene From 196ef15939710d1ed7d63e7af2bc60a702e5b2a6 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:58:21 +0200 Subject: [PATCH 90/95] tests: load displacements back in --- tests/test_scene.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_scene.py b/tests/test_scene.py index 39cec732..271c946c 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -349,3 +349,7 @@ def test_scene_with_displacements(test_data_dir): axes_md = dfield_attrs["ome"]["multiscales"][0]["coordinateSystems"][0]["axes"] assert axes_md[0]["type"] == "displacement" assert axes_md[0]["discrete"] == True + + # read displacements back in and check that the data is correct + scene_read = OMEZarrScene.from_ome_zarr(save_grp) + From 5b1136a958e12bacc8c9978720f3805c72f37b37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 07:58:21 +0000 Subject: [PATCH 91/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 154453cb..09a16969 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -221,7 +221,7 @@ def from_ome_zarr(cls, store: StoreLike): OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(disp_group) ) coordinates_displacements[disp_path] = disp_img - else: + else: coordinates_displacements = None # Load scene metadata @@ -233,7 +233,7 @@ def from_ome_zarr(cls, store: StoreLike): images=images, coordinate_transformations=transformations, coordinate_systems=coordinate_systems, - coordinates_displacements=coordinates_displacements + coordinates_displacements=coordinates_displacements, ) return scene From d0898e1d43717230d4986fdb23a6f774bd15206f Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:58:30 +0200 Subject: [PATCH 92/95] feat: write latest version --- ome_zarr/classes/scene.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 154453cb..e93ab066 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -167,7 +167,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): # Write the image subgroup = zarr_group.create_group(img_path, overwrite=overwrite) - img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") + img.to_ome_zarr(subgroup, overwrite=True, version="0.6") for disp_path, disp_img in (self.coordinates_displacements or {}).items(): # Skip if already written (incremental mode) @@ -178,7 +178,7 @@ def to_ome_zarr(self, store: StoreLike, overwrite: bool = False): subgroup = zarr_group.create_group( f"coordinateTransformations/{disp_path}", overwrite=overwrite ) - disp_img.to_ome_zarr(subgroup, overwrite=True, version="0.6.dev4") + disp_img.to_ome_zarr(subgroup, overwrite=True, version="0.6") # Always update scene metadata metadata_dict = self.metadata.model_dump() From ea5d2f08f10a55dcc7685cdd502ee3f03621f7c7 Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:22:57 +0200 Subject: [PATCH 93/95] feat: infer image paths from metadata --- ome_zarr/classes/scene.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index 2d978dd1..fe72cc85 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -205,13 +205,6 @@ def from_ome_zarr(cls, store: StoreLike): else: zarr_group = zarr.open(store, mode="r") - # Load all image subgroups, keyed by their zarr path - images: dict[str, OMEZarrMultiscale] = {} - for img_path in zarr_group.group_keys(): - img_group = zarr_group[img_path] - img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) - images[img_path] = img - # load coordinateTransformations array data, if it exists if "coordinateTransformations" in zarr_group: coordinates_displacements = {} @@ -229,6 +222,26 @@ def from_ome_zarr(cls, store: StoreLike): transformations = scene_metadata.scene.coordinateTransformations coordinate_systems = scene_metadata.scene.coordinateSystems + # Load all image subgroups, keyed by their zarr path + images = {} + for tf in transformations: + if hasattr(tf, "input"): + path = tf.input.path + if path is not None and path in zarr_group: + img_group = zarr_group[path] + img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) + images[path] = img + elif path is not None and path not in zarr_group: + raise ValueError(f"Image specified in metadata at '{path}' not found in zarr group.") + if hasattr(tf, "output"): + path = tf.output.path + if path is not None and path in zarr_group: + img_group = zarr_group[path] + img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) + images[path] = img + elif path is not None and path not in zarr_group: + raise ValueError(f"Image specified in metadata at '{path}' not found in zarr group.") + scene = OMEZarrScene( images=images, coordinate_transformations=transformations, From 96b2ac7a368922532860c5ee33f8739e1baec965 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:23:13 +0000 Subject: [PATCH 94/95] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ome_zarr/classes/scene.py | 16 ++++++++++++---- tests/test_scene.py | 1 - 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ome_zarr/classes/scene.py b/ome_zarr/classes/scene.py index fe72cc85..a2285a93 100644 --- a/ome_zarr/classes/scene.py +++ b/ome_zarr/classes/scene.py @@ -229,18 +229,26 @@ def from_ome_zarr(cls, store: StoreLike): path = tf.input.path if path is not None and path in zarr_group: img_group = zarr_group[path] - img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) + img = cast( + OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group) + ) images[path] = img elif path is not None and path not in zarr_group: - raise ValueError(f"Image specified in metadata at '{path}' not found in zarr group.") + raise ValueError( + f"Image specified in metadata at '{path}' not found in zarr group." + ) if hasattr(tf, "output"): path = tf.output.path if path is not None and path in zarr_group: img_group = zarr_group[path] - img = cast(OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group)) + img = cast( + OMEZarrMultiscale, OMEZarrMultiscale.from_ome_zarr(img_group) + ) images[path] = img elif path is not None and path not in zarr_group: - raise ValueError(f"Image specified in metadata at '{path}' not found in zarr group.") + raise ValueError( + f"Image specified in metadata at '{path}' not found in zarr group." + ) scene = OMEZarrScene( images=images, diff --git a/tests/test_scene.py b/tests/test_scene.py index 271c946c..8fd4dae2 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -352,4 +352,3 @@ def test_scene_with_displacements(test_data_dir): # read displacements back in and check that the data is correct scene_read = OMEZarrScene.from_ome_zarr(save_grp) - From cba2c89feac48517c45b9e4b5366d7e0395d316c Mon Sep 17 00:00:00 2001 From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:06:07 +0200 Subject: [PATCH 95/95] tests: add more chekcs about displacement field --- tests/test_scene.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_scene.py b/tests/test_scene.py index 271c946c..960b3039 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -346,10 +346,16 @@ def test_scene_with_displacements(test_data_dir): # check that the metadata of the displacement field is correct dfield_attrs = group["coordinateTransformations"]["displacementField"].attrs assert "ome" in dfield_attrs + assert dfield_attrs["ome"]["version"] == "0.6" axes_md = dfield_attrs["ome"]["multiscales"][0]["coordinateSystems"][0]["axes"] assert axes_md[0]["type"] == "displacement" assert axes_md[0]["discrete"] == True - # read displacements back in and check that the data is correct + # read displacements back in and check that the (meta)data is correct scene_read = OMEZarrScene.from_ome_zarr(save_grp) + assert "displacementField" in scene_read.coordinates_displacements + dfield_img = scene_read.coordinates_displacements["displacementField"] + for image in dfield_img.images: + assert image.axes_types["c"] == "displacement" + assert dfield_img.metadata.coordinateSystems[0].axes[0].discrete == True