Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 57 additions & 13 deletions py/ngff_zarr/from_ngff_zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,13 @@ def from_ngff_zarr(
else:
metadata = root.attrs["multiscales"][0]

if "axes" not in metadata:
if "axes" in metadata:
dims = tuple([a["name"] if "name" in a else a for a in metadata["axes"]])
elif "coordinateSystems" in metadata:
dims = tuple([a["name"] for a in metadata["coordinateSystems"][0]["axes"]])
else:
from .v04.zarr_metadata import supported_dims

dims = tuple(reversed(supported_dims))
else:
dims = tuple(a["name"] if "name" in a else a for a in metadata["axes"])

name = "image"
if name in metadata:
Expand All @@ -146,25 +147,51 @@ def from_ngff_zarr(

scale = {d: 1.0 for d in dims}
translation = {d: 0.0 for d in dims}
coordinateTransformations = []
if "coordinateTransformations" in dataset:
for transformation in dataset["coordinateTransformations"]:
if "scale" in transformation:
scale = transformation["scale"]
scale = dict(zip(dims, scale))
coordinateTransformations.append(Scale(transformation["scale"]))
elif "translation" in transformation:
translation = transformation["translation"]
translation = dict(zip(dims, translation))
coordinateTransformations.append(
Translation(transformation["translation"])
)
datasets.append(
Dataset(

if version == "0.4" or version == "0.5":
from .v04.zarr_metadata import Dataset, Scale, Translation
ds = Dataset(
path=dataset["path"],
coordinateTransformations=[
Scale(list(scale.values())),
Translation(list(translation.values())),
],
)

elif version.startswith("0.6"):
from .v06.zarr_metadata import Dataset, Scale, Translation, TransformSequence
coordinateTransformations = TransformSequence(
input=dataset["path"],
output='physical',
transformations=[
Scale(scale=list(scale.values())),
Translation(translation=list(translation.values())),
]
)
ds = Dataset(
path=dataset["path"],
coordinateTransformations=coordinateTransformations,
)
)
# assume v04 metadata for unknown (older) versions
else:
from .v04.zarr_metadata import Dataset, Scale, Translation
ds = Dataset(
path=dataset["path"],
coordinateTransformations=[
Scale(list(scale.values())),
Translation(list(translation.values())),
],
)

datasets.append(ds)

ngff_image = NgffImage(data, dims, scale, translation, name, units)
images.append(ngff_image)
Expand All @@ -183,6 +210,10 @@ def from_ngff_zarr(
"x": "space",
}
axes = [Axis(name=axis, type=type_dict[axis]) for axis in metadata["axes"]]
elif "coordinateSystems" in metadata:
axes = [
Axis(**axis) for axis in metadata["coordinateSystems"][0]["axes"]
]
else:
axes = [
Axis(name="t", type="time"),
Expand All @@ -192,6 +223,7 @@ def from_ngff_zarr(
Axis(name="x", type="space"),
]

# additional coordinate transformations under multiscales > coordinateTransformations
coordinateTransformations = None
if "coordinateTransformations" in metadata:
coordinateTransformations = metadata["coordinateTransformations"]
Expand Down Expand Up @@ -304,14 +336,26 @@ def from_ngff_zarr(
type=method_type,
metadata=method_metadata,
)
elif version.startswith("0.6"):
from .v06.zarr_metadata import Metadata

metadata_obj = Metadata(
datasets=datasets,
coordinateSystems=[(metadata.get("coordinateSystems") or [{}])[0]],
coordinateTransformations=coordinateTransformations,
name=name,
omero=omero,
type=method_type,
metadata=method_metadata,
)
else:
from .v04.zarr_metadata import Metadata

metadata_obj = Metadata(
axes=axes,
datasets=datasets,
name=name,
version=metadata["version"],
version=version,
coordinateTransformations=coordinateTransformations,
omero=omero,
type=method_type,
Expand Down
13 changes: 10 additions & 3 deletions py/ngff_zarr/to_multiscales.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from .ngff_image import NgffImage
from .rich_dask_progress import NgffProgress, NgffProgressCallback
from .to_ngff_image import to_ngff_image
from .v04.zarr_metadata import Axis, Dataset, Metadata, Scale, Translation
from .v06.zarr_metadata import CoordinateSystem, Dataset, Metadata, Scale, Translation, TransformSequence, Axis


def _ngff_image_scale_factors(ngff_image, min_length, out_chunks):
Expand Down Expand Up @@ -451,7 +451,14 @@ def to_multiscales(
translation.append(image.translation[dim])
else:
translation.append(0.0)
coordinateTransformations = [Scale(scale), Translation(translation)]
coordinateTransformations = TransformSequence(
input=path,
output='physical',
transformations=[
Scale(scale=scale),
Translation(translation=translation),
]
)
dataset = Dataset(
path=path, coordinateTransformations=coordinateTransformations
)
Expand All @@ -464,7 +471,7 @@ def to_multiscales(
method_metadata = get_method_metadata(method)

metadata = Metadata(
axes=axes,
coordinateSystems=[CoordinateSystem(name='physical', axes=axes)],
datasets=datasets,
name=ngff_image.name,
coordinateTransformations=None,
Expand Down
100 changes: 78 additions & 22 deletions py/ngff_zarr/to_ngff_zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ._zarr_open_array import open_array
from .v04.zarr_metadata import Metadata as Metadata_v04
from .v05.zarr_metadata import Metadata as Metadata_v05
from .v06.zarr_metadata import Metadata as Metadata_v06
from .rfc4 import is_rfc4_enabled
from .rfc9_zip import is_ozx_path, write_store_to_zip

Expand All @@ -48,13 +49,24 @@


def _pop_metadata_optionals(metadata_dict, enabled_rfcs: Optional[List[int]] = None):
for ax in metadata_dict["axes"]:
if ax["unit"] is None:
ax.pop("unit")
if "axes" in metadata_dict:
for ax in metadata_dict["axes"]:
if ax["unit"] is None:
ax.pop("unit")

# Handle RFC 4: Remove orientation if RFC 4 is not enabled
if not is_rfc4_enabled(enabled_rfcs) and "orientation" in ax:
ax.pop("orientation")
# Handle RFC 4: Remove orientation if RFC 4 is not enabled
if not is_rfc4_enabled(enabled_rfcs) and "orientation" in ax:
ax.pop("orientation")

if "coordinateSystems" in metadata_dict:
for cs in metadata_dict["coordinateSystems"]:
for ax in cs["axes"]:
if ax.get("unit") is None:
ax.pop("unit")
Comment on lines +52 to +65
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent unit handling: uses ax["unit"] at line 54 but ax.get("unit") at line 64. For coordinateSystems axes, the code safely uses .get() to avoid KeyError if "unit" is missing, but for regular axes it directly accesses ax["unit"] which could raise KeyError. Consider using ax.get("unit") for consistency and safety at line 54 as well, or ensure the dict access is safe.

Copilot uses AI. Check for mistakes.

# Handle RFC 4: Remove orientation if RFC 4 is not enabled
if not is_rfc4_enabled(enabled_rfcs) and "orientation" in ax:
ax.pop("orientation")

if metadata_dict["coordinateTransformations"] is None:
metadata_dict.pop("coordinateTransformations")
Expand Down Expand Up @@ -364,30 +376,74 @@ def _prepare_metadata(
method_type = multiscales.method.value
method_metadata = get_method_metadata(multiscales.method)

if version == "0.4" and isinstance(metadata, Metadata_v05):
metadata = Metadata_v04(
axes=metadata.axes,
datasets=metadata.datasets,
coordinateTransformations=metadata.coordinateTransformations,
name=metadata.name,
type=method_type,
metadata=method_metadata,
)
elif version == "0.5" and isinstance(metadata, Metadata_v04):
metadata = Metadata_v05(
dimension_names = None
if isinstance(metadata, Metadata_v04):
dimension_names = tuple([ax.name for ax in metadata.axes])
if version == "0.4":
# Already in v0.5 format, just update type and metadata
if hasattr(metadata, "type"):
metadata.type = method_type
elif version == "0.5":
metadata = Metadata_v05(
axes=metadata.axes,
datasets=metadata.datasets,
coordinateTransformations=metadata.coordinateTransformations,
name=metadata.name,
type=method_type,
metadata=method_metadata,
)
elif version == "0.6":
from .v06.zarr_metadata import CoordinateSystem
metadata = Metadata_v06(
coordinateSystems=[
CoordinateSystem(
name="default",
axes=metadata.axes,
)
],
datasets=metadata.datasets,
coordinateTransformations=metadata.coordinateTransformations,
name=metadata.name,
type=method_type,
metadata=method_metadata,
)

elif isinstance(metadata, Metadata_v05):
dimension_names = tuple([ax.name for ax in metadata.axes])
if version == "0.4":
metadata = Metadata_v04(
axes=metadata.axes,
datasets=metadata.datasets,
coordinateTransformations=metadata.coordinateTransformations,
name=metadata.name,
type=method_type,
metadata=method_metadata,
)
else:
# Update the existing metadata object with the type
if hasattr(metadata, "type"):
metadata.type = method_type

elif version == "0.5":
# Already in v0.5 format, just update type and metadata
if hasattr(metadata, "type"):
metadata.type = method_type

elif version == "0.6":
from .v06.zarr_metadata import CoordinateSystem
metadata = Metadata_v06(
coordinateSystems=[
CoordinateSystem(
name="default",
axes=metadata.axes,
)
],
datasets=metadata.datasets,
coordinateTransformations=metadata.coordinateTransformations,
name=metadata.name,
type=method_type,
metadata=method_metadata,
)
elif isinstance(metadata, Metadata_v06):
dimension_names = tuple([ax.name for ax in metadata.coordinateSystems[0].axes])


dimension_names = tuple([ax.name for ax in metadata.axes])
dimension_names_kwargs = (
{"dimension_names": dimension_names} if version != "0.4" else {}
)
Expand Down
57 changes: 57 additions & 0 deletions py/ngff_zarr/v06/zarr_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional, Union

from ..v04.zarr_metadata import Axis, Omero, MethodMetadata

@dataclass
class CoordinateSystem:
name: str
axes: List[Axis]

@dataclass
class Scale:
scale: List[float]
name: Optional[str]
input: Optional[Union[str, CoordinateSystem]]
output: Optional[Union[str, CoordinateSystem]]
type: str = "scale"


@dataclass
class Translation:
translation: List[float]
name: Optional[str]
input: Optional[Union[str, CoordinateSystem]]
output: Optional[Union[str, CoordinateSystem]]
type: str = "translation"


coordinateTransformations = Union[Scale, Translation]


@dataclass
class TransformSequence:
input: Optional[Union[str, CoordinateSystem]]
output: Optional[Union[str, CoordinateSystem]]
transformations: List[coordinateTransformations]
type: str = "sequence"
name: Optional[str] = None


@dataclass
class Dataset:
path: str
coordinateTransformations: Optional[Union[coordinateTransformations, TransformSequence]]


@dataclass
class Metadata:
datasets: List[Dataset]
coordinateSystems: List[CoordinateSystem]
coordinateTransformations: Optional[List[Union[Scale, Translation, TransformSequence]]] = None
omero: Optional[Omero] = None
name: str = "image"
type: Optional[str] = None
metadata: Optional[MethodMetadata] = None
Loading