29
29
import math
30
30
from dataclasses import dataclass , replace
31
31
from functools import cached_property
32
+ from importlib .metadata import version
32
33
from typing import Any , Self
33
34
from warnings import warn
34
35
35
36
import numpy as np
37
+ from packaging .version import Version
36
38
37
39
import numcodecs
38
40
39
41
try :
40
- import zarr
42
+ import zarr # noqa: F401
41
43
42
- if zarr . __version__ < "3.0.0" : # pragma: no cover
44
+ if Version ( version ( ' zarr' )) < Version ( "3.0.0" ) : # pragma: no cover
43
45
raise ImportError ("zarr 3.0.0 or later is required to use the numcodecs zarr integration." )
44
46
except ImportError as e : # pragma: no cover
45
47
raise ImportError (
56
58
CODEC_PREFIX = "numcodecs."
57
59
58
60
61
+ def _from_zarr_dtype (dtype : Any ) -> np .dtype :
62
+ """
63
+ Get a numpy data type from an array spec, depending on the zarr version.
64
+ """
65
+ if Version (version ('zarr' )) >= Version ("3.1.0" ):
66
+ return dtype .to_native_dtype ()
67
+ return dtype # pragma: no cover
68
+
69
+
70
+ def _to_zarr_dtype (dtype : np .dtype ) -> Any :
71
+ if Version (version ('zarr' )) >= Version ("3.1.0" ):
72
+ from zarr .dtype import parse_data_type
73
+
74
+ return parse_data_type (dtype , zarr_format = 3 )
75
+ return dtype # pragma: no cover
76
+
77
+
59
78
def _expect_name_prefix (codec_name : str ) -> str :
60
79
if not codec_name .startswith (CODEC_PREFIX ):
61
80
raise ValueError (
@@ -224,15 +243,17 @@ class LZMA(_NumcodecsBytesBytesCodec, codec_name="lzma"):
224
243
class Shuffle (_NumcodecsBytesBytesCodec , codec_name = "shuffle" ):
225
244
def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Shuffle :
226
245
if self .codec_config .get ("elementsize" ) is None :
227
- return Shuffle (** {** self .codec_config , "elementsize" : array_spec .dtype .itemsize })
246
+ dtype = _from_zarr_dtype (array_spec .dtype )
247
+ return Shuffle (** {** self .codec_config , "elementsize" : dtype .itemsize })
228
248
return self # pragma: no cover
229
249
230
250
231
251
# array-to-array codecs ("filters")
232
252
class Delta (_NumcodecsArrayArrayCodec , codec_name = "delta" ):
233
253
def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
234
254
if astype := self .codec_config .get ("astype" ):
235
- return replace (chunk_spec , dtype = np .dtype (astype )) # type: ignore[call-overload]
255
+ dtype = _to_zarr_dtype (np .dtype (astype )) # type: ignore[call-overload]
256
+ return replace (chunk_spec , dtype = dtype )
236
257
return chunk_spec
237
258
238
259
@@ -243,12 +264,14 @@ class BitRound(_NumcodecsArrayArrayCodec, codec_name="bitround"):
243
264
class FixedScaleOffset (_NumcodecsArrayArrayCodec , codec_name = "fixedscaleoffset" ):
244
265
def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
245
266
if astype := self .codec_config .get ("astype" ):
246
- return replace (chunk_spec , dtype = np .dtype (astype )) # type: ignore[call-overload]
267
+ dtype = _to_zarr_dtype (np .dtype (astype )) # type: ignore[call-overload]
268
+ return replace (chunk_spec , dtype = dtype )
247
269
return chunk_spec
248
270
249
271
def evolve_from_array_spec (self , array_spec : ArraySpec ) -> FixedScaleOffset :
250
272
if self .codec_config .get ("dtype" ) is None :
251
- return FixedScaleOffset (** {** self .codec_config , "dtype" : str (array_spec .dtype )})
273
+ dtype = _from_zarr_dtype (array_spec .dtype )
274
+ return FixedScaleOffset (** {** self .codec_config , "dtype" : str (dtype )})
252
275
return self
253
276
254
277
@@ -258,7 +281,8 @@ def __init__(self, **codec_config: JSON) -> None:
258
281
259
282
def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Quantize :
260
283
if self .codec_config .get ("dtype" ) is None :
261
- return Quantize (** {** self .codec_config , "dtype" : str (array_spec .dtype )})
284
+ dtype = _from_zarr_dtype (array_spec .dtype )
285
+ return Quantize (** {** self .codec_config , "dtype" : str (dtype )})
262
286
return self
263
287
264
288
@@ -267,21 +291,27 @@ def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
267
291
return replace (
268
292
chunk_spec ,
269
293
shape = (1 + math .ceil (product (chunk_spec .shape ) / 8 ),),
270
- dtype = np .dtype ("uint8" ),
294
+ dtype = _to_zarr_dtype ( np .dtype ("uint8" ) ),
271
295
)
272
296
273
- def validate (self , * , dtype : np .dtype [Any ], ** _kwargs ) -> None :
274
- if dtype != np .dtype ("bool" ):
297
+ # todo: remove this type: ignore when this class can be defined w.r.t.
298
+ # a single zarr dtype API
299
+ def validate (self , * , dtype : np .dtype [Any ], ** _kwargs ) -> None : # type: ignore[override]
300
+ _dtype = _from_zarr_dtype (dtype )
301
+ if _dtype != np .dtype ("bool" ):
275
302
raise ValueError (f"Packbits filter requires bool dtype. Got { dtype } ." )
276
303
277
304
278
305
class AsType (_NumcodecsArrayArrayCodec , codec_name = "astype" ):
279
306
def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
280
- return replace (chunk_spec , dtype = np .dtype (self .codec_config ["encode_dtype" ])) # type: ignore[arg-type]
307
+ dtype = _to_zarr_dtype (np .dtype (self .codec_config ["encode_dtype" ])) # type: ignore[arg-type]
308
+ return replace (chunk_spec , dtype = dtype )
281
309
282
310
def evolve_from_array_spec (self , array_spec : ArraySpec ) -> AsType :
283
311
if self .codec_config .get ("decode_dtype" ) is None :
284
- return AsType (** {** self .codec_config , "decode_dtype" : str (array_spec .dtype )})
312
+ # TODO: remove these coverage exemptions the correct way, i.e. with tests
313
+ dtype = _from_zarr_dtype (array_spec .dtype ) # pragma: no cover
314
+ return AsType (** {** self .codec_config , "decode_dtype" : str (dtype )}) # pragma: no cover
285
315
return self
286
316
287
317
0 commit comments