Skip to content

Support reading OME-Zarr 0.5 #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: 273-use-zarr-python-3
Choose a base branch
from
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
4 changes: 3 additions & 1 deletion iohub/ngff/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ class VersionMeta(MetaBase):
"""OME-NGFF spec version. Default is the current version (0.4)."""

# SHOULD
version: Literal["0.1", "0.2", "0.3", "0.4"] = "0.4"
version: Literal["0.1", "0.2", "0.3", "0.4", "0.5"] | None = Field(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Workaround for ome/ngff#309.

default=None, exclude=lambda v: v is None
)


class MultiScaleMeta(VersionMeta):
Expand Down
106 changes: 63 additions & 43 deletions iohub/ngff/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,28 @@ def _pad_shape(shape: tuple[int, ...], target: int = 5):


def _open_store(
store_path: StrOrBytesPath,
store_path: StrOrBytesPath | Path,
mode: Literal["r", "r+", "a", "w", "w-"],
version: Literal["0.1", "0.4", "0.5"],
):
if not os.path.isdir(store_path) and mode in ("r", "r+"):
store_path = Path(store_path).resolve()
if not store_path.exists() and mode in ("r", "r+"):
raise FileNotFoundError(
f"Dataset directory not found at {store_path}."
f"Dataset directory not found at {str(store_path)}."
)
if version not in ("0.4", "0.5"):
_logger.warning(
"IOHub is only tested against OME-NGFF v0.4 and v0.5. "
f"Requested version {version} may not work properly."
)
try:
store = zarr.storage.LocalStore(store_path)
root = zarr.open_group(
store, mode=mode, zarr_format=(3 if version == "0.5" else 2)
)
zarr_format = None
if mode in ("w", "w-"):
zarr_format = 3 if version == "0.5" else 2
root = zarr.open_group(store_path, mode=mode, zarr_format=zarr_format)
except Exception as e:
raise RuntimeError(
f"Cannot open Zarr root group at {store_path}"
f"Cannot open Zarr root group at {str(store_path)}"
) from e
return root

Expand Down Expand Up @@ -571,12 +572,13 @@ def _set_meta(
)
example_image: ImageArray = self[
self.metadata.multiscales[0].datasets[0].path
].channels
]
self._channel_names = list(range(example_image.channels))

def _parse_meta(self):
multiscales = self.zattrs.get("multiscales")
omero = self.zattrs.get("omero")
attrs = self.zattrs.get("ome") or self.zattrs
multiscales = attrs.get("multiscales")
omero = attrs.get("omero")
if multiscales:
try:
self._set_meta(multiscales=multiscales, omero=omero)
Expand Down Expand Up @@ -1925,6 +1927,49 @@ def rename_well(self, old: str, new: str):
self.dump_meta()


def _check_file_mode(
store_path: Path,
mode: Literal["r", "r+", "a", "w", "w-"],
disable_path_checking: bool,
) -> bool:
if mode == "a":
mode = ("w-", "r+")[int(store_path.exists())]
parse_meta = False
if mode in ("r", "r+"):
parse_meta = True
elif mode == "w-":
if store_path.exists():
raise FileExistsError(store_path)
elif mode == "w":
if store_path.exists():
if (
".zarr" not in str(store_path.resolve())
and not disable_path_checking
):
raise ValueError(
"Cannot overwrite a path that does not contain '.zarr', "
"use `disable_path_checking=True` if you are sure that "
f"{store_path} should be overwritten."
)
_logger.warning(f"Overwriting data at {store_path}")
else:
raise ValueError(f"Invalid persistence mode '{mode}'.")
return parse_meta


def _detect_layout(meta_keys: list[str]) -> Literal["fov", "hcs"]:
if "plate" in meta_keys:
return "hcs"
elif "multiscales" in meta_keys:
return "fov"
else:
raise KeyError(
"Dataset metadata keys ('plate'/'multiscales') not in "
f"the found store metadata keys: {meta_keys}. "
"Is this a valid OME-Zarr dataset?"
)


def open_ome_zarr(
store_path: StrOrBytesPath | Path,
layout: Literal["auto", "fov", "hcs", "tiled"] = "auto",
Expand Down Expand Up @@ -1998,42 +2043,16 @@ def open_ome_zarr(
or :py:class:`iohub.ngff.TiledPosition`)
"""
store_path = Path(store_path)
if mode == "a":
mode = ("w-", "r+")[int(store_path.exists())]
parse_meta = False
if mode in ("r", "r+"):
parse_meta = True
elif mode == "w-":
if store_path.exists():
raise FileExistsError(store_path)
elif mode == "w":
if store_path.exists():
if (
".zarr" not in str(store_path.resolve())
and not disable_path_checking
):
raise ValueError(
"Cannot overwrite a path that does not contain '.zarr', "
"use `disable_path_checking=True` if you are sure that "
f"{store_path} should be overwritten."
)
_logger.warning(f"Overwriting data at {store_path}")
else:
raise ValueError(f"Invalid persistence mode '{mode}'.")
parse_meta = _check_file_mode(
store_path, mode, disable_path_checking=disable_path_checking
)
root = _open_store(store_path, mode, version)
meta_keys = root.attrs.keys() if parse_meta else []
if "ome" in meta_keys:
meta_keys = root.attrs["ome"].keys()
if layout == "auto":
if parse_meta:
if "plate" in meta_keys:
layout = "hcs"
elif "multiscales" in meta_keys:
layout = "fov"
else:
raise KeyError(
"Dataset metadata keys ('plate'/'multiscales') not in "
f"the found store metadata keys: {meta_keys}. "
"Is this a valid OME-Zarr dataset?"
)
layout = _detect_layout(meta_keys)
else:
raise ValueError(
"Store layout must be specified when creating a new dataset."
Expand All @@ -2054,5 +2073,6 @@ def open_ome_zarr(
parse_meta=parse_meta,
channel_names=channel_names,
axes=axes,
version=version,
**kwargs,
)
6 changes: 3 additions & 3 deletions iohub/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


def _find_ngff_version_in_zarr_group(group: zarr.Group) -> str | None:
for key in ["plate", "well"]:
for key in ["plate", "well", "ome"]:
if key in group.attrs:
if v := group.attrs[key].get("version"):
return v
Expand Down Expand Up @@ -201,8 +201,8 @@ def print_info(path: StrOrBytesPath, verbose=False):
path = Path(path).resolve()
try:
fmt, extra_info = _infer_format(path)
if fmt == "omezarr" and extra_info == "0.4":
reader = open_ome_zarr(path, mode="r")
if fmt == "omezarr" and extra_info in ("0.4", "0.5"):
reader = open_ome_zarr(path, mode="r", version=extra_info)
else:
reader = read_images(path, data_type=fmt)
except (ValueError, RuntimeError):
Expand Down