Skip to content

Commit 9d340b8

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

30 files changed

+486
-150
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: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,66 @@
11
from collections.abc import MutableMapping
22
from pathlib import Path
3-
from typing import Union
3+
from typing import Union, Optional
44

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

915
from .ngff_image import NgffImage
1016
from .to_multiscales import Multiscales
11-
from .zarr_metadata import Axis, Dataset, Metadata, Scale, Translation
17+
from .v04.zarr_metadata import Axis, Dataset, Scale, Translation
1218
from .validate import validate as validate_ngff
1319

1420

1521
def from_ngff_zarr(
16-
store: Union[MutableMapping, str, Path, BaseStore],
22+
store: StoreLike,
1723
validate: bool = False,
24+
version: Optional[str] = None,
1825
) -> Multiscales:
1926
"""
2027
Read an OME-Zarr NGFF Multiscales data structure from a Zarr store.
2128
22-
store : MutableMapping, str or Path, zarr.storage.BaseStore
29+
store : StoreLike
2330
Store or path to directory in file system.
2431
2532
validate : bool
2633
If True, validate the NGFF metadata against the schema.
2734
35+
version : string, optional
36+
OME-Zarr version, if known.
37+
2838
Returns
2939
-------
3040
3141
multiscales: multiscale ngff image with dask-chunked arrays for data
3242
3343
"""
3444

35-
root = zarr.open_group(store, mode="r")
45+
format_kwargs = {}
46+
if version:
47+
format_kwargs = {"zarr_format": 2} if version == "0.4" else {"zarr_format": 3}
48+
root = zarr.open_group(store, mode="r", **format_kwargs)
49+
root_attrs = root.attrs.asdict()
50+
51+
if not version:
52+
if "ome" in root_attrs:
53+
version = root_attrs["ome"]["version"]
54+
else:
55+
version = root_attrs["multiscales"][0].get("version", "0.4")
56+
3657
if validate:
37-
validate_ngff(root.attrs.asdict())
38-
metadata = root.attrs["multiscales"][0]
58+
validate_ngff(root_attrs, version=version)
59+
60+
if "ome" in root_attrs:
61+
metadata = root.attrs["ome"]["multiscales"][0]
62+
else:
63+
metadata = root.attrs["multiscales"][0]
3964

4065
dims = [a["name"] for a in metadata["axes"]]
4166

@@ -82,12 +107,24 @@ def from_ngff_zarr(
82107
coordinateTransformations = None
83108
if "coordinateTransformations" in metadata:
84109
coordinateTransformations = metadata["coordinateTransformations"]
85-
metadata = Metadata(
86-
axes=axes,
87-
datasets=datasets,
88-
name=name,
89-
version=metadata["version"],
90-
coordinateTransformations=coordinateTransformations,
91-
)
110+
if version == "0.5":
111+
from .v05.zarr_metadata import Metadata
112+
113+
metadata = Metadata(
114+
axes=axes,
115+
datasets=datasets,
116+
name=name,
117+
coordinateTransformations=coordinateTransformations,
118+
)
119+
else:
120+
from .v04.zarr_metadata import Metadata
121+
122+
metadata = Metadata(
123+
axes=axes,
124+
datasets=datasets,
125+
name=name,
126+
version=metadata["version"],
127+
coordinateTransformations=coordinateTransformations,
128+
)
92129

93130
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)