Skip to content

Commit 15106f8

Browse files
committed
ENH: Add RFC 2, OME-Zarr v0.5 support
1 parent 6271079 commit 15106f8

30 files changed

+409
-110
lines changed

ngff_zarr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .to_ngff_image import to_ngff_image
1919
from .to_ngff_zarr import to_ngff_zarr
2020
from .validate import validate
21-
from .zarr_metadata import (
21+
from .v04.zarr_metadata import (
2222
AxesType,
2323
SpatialDims,
2424
SupportedDims,

ngff_zarr/_zarr_kwargs.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import zarr
2+
from packaging import version
3+
4+
zarr_version = version.parse(zarr.__version__)
5+
if zarr_version >= version.parse("3.0.0b1"):
6+
zarr_kwargs = {}
7+
else:
8+
zarr_kwargs = {"dimension_separator": "/"}

ngff_zarr/_zarr_open_array.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import zarr
2+
from packaging import version
3+
4+
zarr_version = version.parse(zarr.__version__)
5+
if zarr_version >= version.parse("3.0.0b1"):
6+
from zarr.api.synchronous import open_array
7+
else:
8+
from zarr.creation import open_array
9+
open_array = open_array

ngff_zarr/cli.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
from .to_multiscales import to_multiscales
4242
from .to_ngff_image import to_ngff_image
4343
from .to_ngff_zarr import to_ngff_zarr
44-
from .zarr_metadata import is_unit_supported
44+
from .v04.zarr_metadata import is_unit_supported
45+
from ._zarr_kwargs import zarr_kwargs
4546

4647

4748
def _multiscales_to_ngff_zarr(
@@ -235,9 +236,7 @@ def main():
235236
cache_dir = Path(args.cache_dir).resolve()
236237
if not cache_dir.exists():
237238
Path.makedirs(cache_dir, parents=True)
238-
config.cache_store = zarr.storage.DirectoryStore(
239-
cache_dir, dimension_separator="/"
240-
)
239+
config.cache_store = zarr.storage.DirectoryStore(cache_dir, **zarr_kwargs)
241240

242241
console = Console()
243242
progress = RichProgress(
@@ -304,7 +303,7 @@ def shutdown_client(sig_id, frame): # noqa: ARG001
304303
)
305304
output_store = None
306305
if args.output and output_backend is ConversionBackend.NGFF_ZARR:
307-
output_store = DirectoryStore(args.output, dimension_separator="/")
306+
output_store = DirectoryStore(args.output, **zarr_kwargs)
308307

309308
subtitle = "[red]generation"
310309
if not args.output:

ngff_zarr/config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
from pathlib import Path
33

44
import dask.config
5-
import zarr
65
from platformdirs import user_cache_dir
76
from zarr.storage import StoreLike
7+
from ._zarr_kwargs import zarr_kwargs
88

99
if dask.config.get("temporary-directory") is not None:
1010
_store_dir = dask.config.get("temporary-directory")
@@ -13,7 +13,14 @@
1313

1414

1515
def default_store_factory():
16-
return zarr.storage.DirectoryStore(_store_dir, dimension_separator="/")
16+
try:
17+
from zarr.storage import DirectoryStore
18+
19+
return DirectoryStore(_store_dir, **zarr_kwargs)
20+
except ImportError:
21+
from zarr.storage import LocalStore
22+
23+
return LocalStore(_store_dir)
1724

1825

1926
try:

ngff_zarr/from_ngff_zarr.py

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,70 @@
11
from collections.abc import MutableMapping
22
from pathlib import Path
3-
from typing import Union
3+
from typing import Union, Optional
4+
from packaging import version
45

56
import dask.array
67
import zarr
7-
from zarr.storage import BaseStore
8+
import zarr.storage
9+
10+
# Zarr Python 3
11+
if hasattr(zarr.storage, "StoreLike"):
12+
StoreLike = zarr.storage.StoreLike
13+
else:
14+
StoreLike = Union[MutableMapping, str, Path, zarr.storage.BaseStore]
815

916
from .ngff_image import NgffImage
1017
from .to_multiscales import Multiscales
11-
from .zarr_metadata import Axis, Dataset, Metadata, Scale, Translation
18+
from .v04.zarr_metadata import Axis, Dataset, Scale, Translation
1219
from .validate import validate as validate_ngff
1320

21+
zarr_version = version.parse(zarr.__version__)
22+
zarr_version_major = zarr_version.major
23+
1424

1525
def from_ngff_zarr(
16-
store: Union[MutableMapping, str, Path, BaseStore],
26+
store: StoreLike,
1727
validate: bool = False,
28+
version: Optional[str] = None,
1829
) -> Multiscales:
1930
"""
2031
Read an OME-Zarr NGFF Multiscales data structure from a Zarr store.
2132
22-
store : MutableMapping, str or Path, zarr.storage.BaseStore
33+
store : StoreLike
2334
Store or path to directory in file system.
2435
2536
validate : bool
2637
If True, validate the NGFF metadata against the schema.
2738
39+
version : string, optional
40+
OME-Zarr version, if known.
41+
2842
Returns
2943
-------
3044
3145
multiscales: multiscale ngff image with dask-chunked arrays for data
3246
3347
"""
3448

35-
root = zarr.open_group(store, mode="r")
49+
format_kwargs = {}
50+
if version and zarr_version_major >= 3:
51+
format_kwargs = {"zarr_format": 2} if version == "0.4" else {"zarr_format": 3}
52+
root = zarr.open_group(store, mode="r", **format_kwargs)
53+
root_attrs = root.attrs.asdict()
54+
55+
if not version:
56+
if "ome" in root_attrs:
57+
version = root_attrs["ome"]["version"]
58+
else:
59+
version = root_attrs["multiscales"][0].get("version", "0.4")
60+
3661
if validate:
37-
validate_ngff(root.attrs.asdict())
38-
metadata = root.attrs["multiscales"][0]
62+
validate_ngff(root_attrs, version=version)
63+
64+
if "ome" in root_attrs:
65+
metadata = root.attrs["ome"]["multiscales"][0]
66+
else:
67+
metadata = root.attrs["multiscales"][0]
3968

4069
dims = [a["name"] for a in metadata["axes"]]
4170

@@ -82,12 +111,24 @@ def from_ngff_zarr(
82111
coordinateTransformations = None
83112
if "coordinateTransformations" in metadata:
84113
coordinateTransformations = metadata["coordinateTransformations"]
85-
metadata = Metadata(
86-
axes=axes,
87-
datasets=datasets,
88-
name=name,
89-
version=metadata["version"],
90-
coordinateTransformations=coordinateTransformations,
91-
)
114+
if version == "0.5":
115+
from .v05.zarr_metadata import Metadata
116+
117+
metadata = Metadata(
118+
axes=axes,
119+
datasets=datasets,
120+
name=name,
121+
coordinateTransformations=coordinateTransformations,
122+
)
123+
else:
124+
from .v04.zarr_metadata import Metadata
125+
126+
metadata = Metadata(
127+
axes=axes,
128+
datasets=datasets,
129+
name=name,
130+
version=metadata["version"],
131+
coordinateTransformations=coordinateTransformations,
132+
)
92133

93134
return Multiscales(images, metadata)

ngff_zarr/multiscales.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from .methods import Methods
55
from .ngff_image import NgffImage
6-
from .zarr_metadata import Metadata
6+
from .v04.zarr_metadata import Metadata
77

88

99
@dataclass

ngff_zarr/ngff_image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from dask.array.core import Array as DaskArray
55

6-
from .zarr_metadata import Units
6+
from .v04.zarr_metadata import Units
77

88
ComputedCallback = Callable[[], None]
99

ngff_zarr/to_multiscales.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
import zarr
1212
from dask.array.core import Array as DaskArray
1313
from numpy.typing import ArrayLike
14-
from zarr.core import Array as ZarrArray
14+
15+
try:
16+
from zarr.core import Array as ZarrArray
17+
except ImportError:
18+
from zarr.core.array import Array as ZarrArray
19+
from ._zarr_kwargs import zarr_kwargs
20+
from ._zarr_open_array import open_array
21+
import zarr.storage
1522

1623
from .config import config
1724
from .memory_usage import memory_usage
@@ -30,7 +37,7 @@
3037
from .ngff_image import NgffImage
3138
from .rich_dask_progress import NgffProgress, NgffProgressCallback
3239
from .to_ngff_image import to_ngff_image
33-
from .zarr_metadata import Axis, Dataset, Metadata, Scale, Translation
40+
from .v04.zarr_metadata import Axis, Dataset, Metadata, Scale, Translation
3441

3542

3643
def _ngff_image_scale_factors(ngff_image, min_length, out_chunks):
@@ -82,10 +89,18 @@ def _large_image_serialization(
8289
def remove_from_cache_store(sig_id, frame): # noqa: ARG001
8390
nonlocal base_path_removed
8491
if not base_path_removed:
85-
if isinstance(cache_store, zarr.storage.DirectoryStore):
92+
if hasattr(zarr.storage, "DirectoryStore") and isinstance(
93+
cache_store, zarr.storage.DirectoryStore
94+
):
8695
full_path = Path(cache_store.dir_path()) / base_path
8796
if full_path.exists():
8897
shutil.rmtree(full_path, ignore_errors=True)
98+
elif hasattr(zarr.storage, "LocalStore") and isinstance(
99+
cache_store, zarr.storage.LocalStore
100+
):
101+
full_path = Path(cache_store.root) / base_path
102+
if full_path.exists():
103+
shutil.rmtree(full_path, ignore_errors=True)
89104
else:
90105
zarr.storage.rmdir(cache_store, base_path)
91106
base_path_removed = True
@@ -129,14 +144,14 @@ def remove_from_cache_store(sig_id, frame): # noqa: ARG001
129144
slabs.chunks,
130145
meta=slabs,
131146
)
132-
zarr_array = zarr.creation.open_array(
147+
zarr_array = open_array(
133148
shape=data.shape,
134149
chunks=chunks,
135150
dtype=data.dtype,
136151
store=cache_store,
137152
path=path,
138153
mode="a",
139-
dimension_separator="/",
154+
**zarr_kwargs,
140155
)
141156

142157
n_slabs = int(np.ceil(data.shape[z_index] / slab_slices))
@@ -164,7 +179,7 @@ def remove_from_cache_store(sig_id, frame): # noqa: ARG001
164179
overwrite=False,
165180
compute=True,
166181
return_stored=False,
167-
dimension_separator="/",
182+
**zarr_kwargs,
168183
)
169184
data = dask.array.from_zarr(cache_store, component=path)
170185
if optimized_chunks < data.shape[z_index] and slab_slices < optimized_chunks:
@@ -173,14 +188,14 @@ def remove_from_cache_store(sig_id, frame): # noqa: ARG001
173188
path = f"{base_path}/optimized_chunks"
174189
chunks = tuple([c[0] for c in optimized.chunks])
175190
data = data.rechunk(chunks)
176-
zarr_array = zarr.creation.open_array(
191+
zarr_array = open_array(
177192
shape=data.shape,
178193
chunks=chunks,
179194
dtype=data.dtype,
180195
store=cache_store,
181196
path=path,
182197
mode="a",
183-
dimension_separator="/",
198+
**zarr_kwargs,
184199
)
185200
n_slabs = int(np.ceil(data.shape[z_index] / optimized_chunks))
186201
for slab_index in range(n_slabs):
@@ -205,7 +220,7 @@ def remove_from_cache_store(sig_id, frame): # noqa: ARG001
205220
overwrite=False,
206221
compute=True,
207222
return_stored=False,
208-
dimension_separator="/",
223+
**zarr_kwargs,
209224
)
210225
data = dask.array.from_zarr(cache_store, component=path)
211226
else:
@@ -223,7 +238,7 @@ def remove_from_cache_store(sig_id, frame): # noqa: ARG001
223238
overwrite=False,
224239
compute=True,
225240
return_stored=False,
226-
dimension_separator="/",
241+
**zarr_kwargs,
227242
)
228243
data = dask.array.from_zarr(cache_store, component=path)
229244

ngff_zarr/to_ngff_image.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import dask
55
from dask.array.core import Array as DaskArray
66
from numpy.typing import ArrayLike
7-
from zarr.core import Array as ZarrArray
7+
8+
try:
9+
from zarr.core import Array as ZarrArray
10+
except ImportError:
11+
from zarr.core.array import Array as ZarrArray
812

913
from .methods._support import _spatial_dims
1014
from .ngff_image import NgffImage
11-
from .zarr_metadata import SupportedDims, Units
15+
from .v04.zarr_metadata import SupportedDims, Units
1216

1317

1418
def to_ngff_image(

0 commit comments

Comments
 (0)