ngio 0.5.0b6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngio/__init__.py +69 -0
- ngio/common/__init__.py +28 -0
- ngio/common/_dimensions.py +335 -0
- ngio/common/_masking_roi.py +153 -0
- ngio/common/_pyramid.py +408 -0
- ngio/common/_roi.py +315 -0
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +188 -0
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +126 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/__init__.py +19 -0
- ngio/hcs/_plate.py +1354 -0
- ngio/images/__init__.py +44 -0
- ngio/images/_abstract_image.py +967 -0
- ngio/images/_create_synt_container.py +132 -0
- ngio/images/_create_utils.py +423 -0
- ngio/images/_image.py +926 -0
- ngio/images/_label.py +411 -0
- ngio/images/_masked_image.py +531 -0
- ngio/images/_ome_zarr_container.py +1237 -0
- ngio/images/_table_ops.py +471 -0
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +146 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +377 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +411 -0
- ngio/io_pipes/_ops_slices_utils.py +199 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +180 -0
- ngio/ome_zarr_meta/__init__.py +65 -0
- ngio/ome_zarr_meta/_meta_handlers.py +536 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +77 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +515 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +462 -0
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +89 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +539 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +438 -0
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +122 -0
- ngio/ome_zarr_meta/v04/__init__.py +27 -0
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/_v04_spec.py +473 -0
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/__init__.py +43 -0
- ngio/tables/_abstract_table.py +270 -0
- ngio/tables/_tables_container.py +449 -0
- ngio/tables/backends/__init__.py +57 -0
- ngio/tables/backends/_abstract_backend.py +240 -0
- ngio/tables/backends/_anndata.py +139 -0
- ngio/tables/backends/_anndata_utils.py +90 -0
- ngio/tables/backends/_csv.py +19 -0
- ngio/tables/backends/_json.py +92 -0
- ngio/tables/backends/_parquet.py +19 -0
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_table_backends.py +226 -0
- ngio/tables/backends/_utils.py +608 -0
- ngio/tables/v1/__init__.py +23 -0
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +125 -0
- ngio/tables/v1/_generic_table.py +49 -0
- ngio/tables/v1/_roi_table.py +575 -0
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +45 -0
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +165 -0
- ngio/utils/_errors.py +37 -0
- ngio/utils/_fractal_fsspec_store.py +42 -0
- ngio/utils/_zarr_utils.py +534 -0
- ngio-0.5.0b6.dist-info/METADATA +148 -0
- ngio-0.5.0b6.dist-info/RECORD +88 -0
- ngio-0.5.0b6.dist-info/WHEEL +4 -0
- ngio-0.5.0b6.dist-info/licenses/LICENSE +28 -0
ngio/common/_pyramid.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
import dask.array as da
|
|
5
|
+
import numpy as np
|
|
6
|
+
import zarr
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
8
|
+
|
|
9
|
+
from ngio.common._zoom import (
|
|
10
|
+
InterpolationOrder,
|
|
11
|
+
_zoom_inputs_check,
|
|
12
|
+
dask_zoom,
|
|
13
|
+
numpy_zoom,
|
|
14
|
+
)
|
|
15
|
+
from ngio.utils import (
|
|
16
|
+
NgioValueError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _on_disk_numpy_zoom(
|
|
21
|
+
source: zarr.Array,
|
|
22
|
+
target: zarr.Array,
|
|
23
|
+
order: InterpolationOrder,
|
|
24
|
+
) -> None:
|
|
25
|
+
source_array = source[...]
|
|
26
|
+
if not isinstance(source_array, np.ndarray):
|
|
27
|
+
raise NgioValueError("source zarr array could not be read as a numpy array")
|
|
28
|
+
target[...] = numpy_zoom(source_array, target_shape=target.shape, order=order)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _on_disk_dask_zoom(
|
|
32
|
+
source: zarr.Array,
|
|
33
|
+
target: zarr.Array,
|
|
34
|
+
order: InterpolationOrder,
|
|
35
|
+
) -> None:
|
|
36
|
+
source_array = da.from_zarr(source)
|
|
37
|
+
target_array = dask_zoom(source_array, target_shape=target.shape, order=order)
|
|
38
|
+
|
|
39
|
+
# This is a potential fix for Dask 2025.11
|
|
40
|
+
# import dask.config
|
|
41
|
+
# chunk_size_bytes = np.prod(target.chunks) * target_array.dtype.itemsize
|
|
42
|
+
# current_chunk_size = dask.config.get("array.chunk-size")
|
|
43
|
+
# Increase the chunk size to avoid dask potentially creating
|
|
44
|
+
# corrupted chunks when writing chunks that are not multiple of the
|
|
45
|
+
# target chunk size
|
|
46
|
+
# dask.config.set({"array.chunk-size": f"{chunk_size_bytes}B"})
|
|
47
|
+
target_array = target_array.rechunk(target.chunks)
|
|
48
|
+
target_array = target_array.compute_chunk_sizes()
|
|
49
|
+
target_array.to_zarr(target)
|
|
50
|
+
# Restore previous chunk size
|
|
51
|
+
# dask.config.set({"array.chunk-size": current_chunk_size})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _on_disk_coarsen(
|
|
55
|
+
source: zarr.Array,
|
|
56
|
+
target: zarr.Array,
|
|
57
|
+
order: InterpolationOrder = "linear",
|
|
58
|
+
aggregation_function: Callable | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Apply a coarsening operation from a source zarr array to a target zarr array.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
source (zarr.Array): The source array to coarsen.
|
|
64
|
+
target (zarr.Array): The target array to save the coarsened result to.
|
|
65
|
+
order (InterpolationOrder): The order of interpolation is not really implemented
|
|
66
|
+
for coarsening, but it is kept for compatibility with the zoom function.
|
|
67
|
+
order="linear" -> linear interpolation ~ np.mean
|
|
68
|
+
order="nearest" -> nearest interpolation ~ np.max
|
|
69
|
+
aggregation_function (np.ufunc): The aggregation function to use.
|
|
70
|
+
"""
|
|
71
|
+
source_array = da.from_zarr(source)
|
|
72
|
+
|
|
73
|
+
_scale, _target_shape = _zoom_inputs_check(
|
|
74
|
+
source_array=source_array, scale=None, target_shape=target.shape
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert _target_shape == target.shape, (
|
|
78
|
+
"Target shape must match the target array shape"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if aggregation_function is None:
|
|
82
|
+
if order == "linear":
|
|
83
|
+
aggregation_function = np.mean
|
|
84
|
+
elif order == "nearest":
|
|
85
|
+
aggregation_function = np.max
|
|
86
|
+
elif order == "cubic":
|
|
87
|
+
raise NgioValueError("Cubic interpolation is not supported for coarsening.")
|
|
88
|
+
else:
|
|
89
|
+
raise NgioValueError(
|
|
90
|
+
f"Aggregation function must be provided for order {order}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
coarsening_setup = {}
|
|
94
|
+
for i, s in enumerate(_scale):
|
|
95
|
+
coarsening_setup[i] = int(np.round(1 / s))
|
|
96
|
+
|
|
97
|
+
out_target = da.coarsen(
|
|
98
|
+
aggregation_function, source_array, coarsening_setup, trim_excess=True
|
|
99
|
+
)
|
|
100
|
+
out_target = out_target.rechunk(target.chunks)
|
|
101
|
+
out_target.to_zarr(target)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def on_disk_zoom(
|
|
105
|
+
source: zarr.Array,
|
|
106
|
+
target: zarr.Array,
|
|
107
|
+
order: InterpolationOrder = "linear",
|
|
108
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Apply a zoom operation from a source zarr array to a target zarr array.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
source (zarr.Array): The source array to zoom.
|
|
114
|
+
target (zarr.Array): The target array to save the zoomed result to.
|
|
115
|
+
order (InterpolationOrder): The order of interpolation. Defaults to "linear".
|
|
116
|
+
mode (Literal["dask", "numpy", "coarsen"]): The mode to use. Defaults to "dask".
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(source, zarr.Array):
|
|
119
|
+
raise NgioValueError("source must be a zarr array")
|
|
120
|
+
|
|
121
|
+
if not isinstance(target, zarr.Array):
|
|
122
|
+
raise NgioValueError("target must be a zarr array")
|
|
123
|
+
|
|
124
|
+
if source.dtype != target.dtype:
|
|
125
|
+
raise NgioValueError("source and target must have the same dtype")
|
|
126
|
+
|
|
127
|
+
match mode:
|
|
128
|
+
case "numpy":
|
|
129
|
+
return _on_disk_numpy_zoom(source, target, order)
|
|
130
|
+
case "dask":
|
|
131
|
+
return _on_disk_dask_zoom(source, target, order)
|
|
132
|
+
case "coarsen":
|
|
133
|
+
return _on_disk_coarsen(
|
|
134
|
+
source,
|
|
135
|
+
target,
|
|
136
|
+
)
|
|
137
|
+
case _:
|
|
138
|
+
raise NgioValueError("mode must be either 'dask', 'numpy' or 'coarsen'")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _find_closest_arrays(
|
|
142
|
+
processed: list[zarr.Array], to_be_processed: list[zarr.Array]
|
|
143
|
+
) -> tuple[np.intp, np.intp]:
|
|
144
|
+
dist_matrix = np.zeros((len(processed), len(to_be_processed)))
|
|
145
|
+
for i, arr_to_proc in enumerate(to_be_processed):
|
|
146
|
+
for j, proc_arr in enumerate(processed):
|
|
147
|
+
dist_matrix[j, i] = np.sqrt(
|
|
148
|
+
np.sum(
|
|
149
|
+
[
|
|
150
|
+
(s1 - s2) ** 2
|
|
151
|
+
for s1, s2 in zip(
|
|
152
|
+
arr_to_proc.shape, proc_arr.shape, strict=False
|
|
153
|
+
)
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
indices = np.unravel_index(dist_matrix.argmin(), dist_matrix.shape)
|
|
159
|
+
assert len(indices) == 2, "Indices must be of length 2"
|
|
160
|
+
return indices
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def consolidate_pyramid(
|
|
164
|
+
source: zarr.Array,
|
|
165
|
+
targets: list[zarr.Array],
|
|
166
|
+
order: InterpolationOrder = "linear",
|
|
167
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Consolidate the Zarr array."""
|
|
170
|
+
processed = [source]
|
|
171
|
+
to_be_processed = targets
|
|
172
|
+
|
|
173
|
+
while to_be_processed:
|
|
174
|
+
source_id, target_id = _find_closest_arrays(processed, to_be_processed)
|
|
175
|
+
|
|
176
|
+
source_image = processed[source_id]
|
|
177
|
+
target_image = to_be_processed.pop(target_id)
|
|
178
|
+
|
|
179
|
+
on_disk_zoom(
|
|
180
|
+
source=source_image,
|
|
181
|
+
target=target_image,
|
|
182
|
+
mode=mode,
|
|
183
|
+
order=order,
|
|
184
|
+
)
|
|
185
|
+
processed.append(target_image)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
################################################
|
|
189
|
+
#
|
|
190
|
+
# Builders for image pyramids
|
|
191
|
+
#
|
|
192
|
+
################################################
|
|
193
|
+
|
|
194
|
+
ChunksLike = tuple[int, ...] | Literal["auto"]
|
|
195
|
+
ShardsLike = tuple[int, ...] | Literal["auto"]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def shapes_from_scaling_factors(
|
|
199
|
+
base_shape: tuple[int, ...],
|
|
200
|
+
scaling_factors: tuple[float, ...],
|
|
201
|
+
num_levels: int,
|
|
202
|
+
) -> list[tuple[int, ...]]:
|
|
203
|
+
"""Compute the shapes of each level in the pyramid from scaling factors.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
base_shape (tuple[int, ...]): The shape of the base level.
|
|
207
|
+
scaling_factors (tuple[float, ...]): The scaling factors between levels.
|
|
208
|
+
num_levels (int): The number of levels in the pyramid.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
list[tuple[int, ...]]: The shapes of each level in the pyramid.
|
|
212
|
+
"""
|
|
213
|
+
shapes = []
|
|
214
|
+
current_shape = base_shape
|
|
215
|
+
for _ in range(num_levels):
|
|
216
|
+
shapes.append(current_shape)
|
|
217
|
+
current_shape = tuple(
|
|
218
|
+
max(1, int(s / f))
|
|
219
|
+
for s, f in zip(current_shape, scaling_factors, strict=True)
|
|
220
|
+
)
|
|
221
|
+
return shapes
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _check_order(shapes: Sequence[tuple[int, ...]]):
|
|
225
|
+
"""Check if the shapes are in decreasing order."""
|
|
226
|
+
num_pixels = [np.prod(shape) for shape in shapes]
|
|
227
|
+
for i in range(1, len(num_pixels)):
|
|
228
|
+
if num_pixels[i] >= num_pixels[i - 1]:
|
|
229
|
+
raise NgioValueError("Shapes are not in decreasing order.")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class PyramidLevel(BaseModel):
|
|
233
|
+
path: str
|
|
234
|
+
shape: tuple[int, ...]
|
|
235
|
+
scale: tuple[float, ...]
|
|
236
|
+
chunks: ChunksLike = "auto"
|
|
237
|
+
shards: ShardsLike | None = None
|
|
238
|
+
|
|
239
|
+
@model_validator(mode="after")
|
|
240
|
+
def _model_validation(self) -> "PyramidLevel":
|
|
241
|
+
# Same length as shape
|
|
242
|
+
if len(self.scale) != len(self.shape):
|
|
243
|
+
raise NgioValueError(
|
|
244
|
+
"Scale must have the same length as shape "
|
|
245
|
+
f"({len(self.shape)}), got {len(self.scale)}"
|
|
246
|
+
)
|
|
247
|
+
if any(isinstance(s, float) and s < 0 for s in self.scale):
|
|
248
|
+
raise NgioValueError("Scale values must be positive.")
|
|
249
|
+
|
|
250
|
+
if isinstance(self.chunks, tuple):
|
|
251
|
+
if len(self.chunks) != len(self.shape):
|
|
252
|
+
raise NgioValueError(
|
|
253
|
+
"Chunks must have the same length as shape "
|
|
254
|
+
f"({len(self.shape)}), got {len(self.chunks)}"
|
|
255
|
+
)
|
|
256
|
+
normalized_chunks = []
|
|
257
|
+
for dim_size, chunk_size in zip(self.shape, self.chunks, strict=True):
|
|
258
|
+
normalized_chunks.append(min(dim_size, chunk_size))
|
|
259
|
+
self.chunks = tuple(normalized_chunks)
|
|
260
|
+
|
|
261
|
+
if isinstance(self.shards, tuple):
|
|
262
|
+
if len(self.shards) != len(self.shape):
|
|
263
|
+
raise NgioValueError(
|
|
264
|
+
"Shards must have the same length as shape "
|
|
265
|
+
f"({len(self.shape)}), got {len(self.shards)}"
|
|
266
|
+
)
|
|
267
|
+
normalized_shards = []
|
|
268
|
+
for dim_size, shard_size in zip(self.shape, self.shards, strict=True):
|
|
269
|
+
normalized_shards.append(min(dim_size, shard_size))
|
|
270
|
+
self.shards = tuple(normalized_shards)
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class ImagePyramidBuilder(BaseModel):
|
|
275
|
+
levels: list[PyramidLevel]
|
|
276
|
+
axes: tuple[str, ...]
|
|
277
|
+
data_type: str = "uint16"
|
|
278
|
+
dimension_separator: Literal[".", "/"] = "/"
|
|
279
|
+
compressors: Any = "auto"
|
|
280
|
+
zarr_format: Literal[2, 3] = 2
|
|
281
|
+
other_array_kwargs: Mapping[str, Any] = {}
|
|
282
|
+
|
|
283
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
284
|
+
|
|
285
|
+
@classmethod
|
|
286
|
+
def from_scaling_factors(
|
|
287
|
+
cls,
|
|
288
|
+
levels_paths: tuple[str, ...],
|
|
289
|
+
scaling_factors: tuple[float, ...],
|
|
290
|
+
base_shape: tuple[int, ...],
|
|
291
|
+
base_scale: tuple[float, ...],
|
|
292
|
+
axes: tuple[str, ...],
|
|
293
|
+
chunks: ChunksLike = "auto",
|
|
294
|
+
shards: ShardsLike | None = None,
|
|
295
|
+
data_type: str = "uint16",
|
|
296
|
+
dimension_separator: Literal[".", "/"] = "/",
|
|
297
|
+
compressors: Any = "auto",
|
|
298
|
+
zarr_format: Literal[2, 3] = 2,
|
|
299
|
+
other_array_kwargs: Mapping[str, Any] | None = None,
|
|
300
|
+
) -> "ImagePyramidBuilder":
|
|
301
|
+
shapes = shapes_from_scaling_factors(
|
|
302
|
+
base_shape=base_shape,
|
|
303
|
+
scaling_factors=scaling_factors,
|
|
304
|
+
num_levels=len(levels_paths),
|
|
305
|
+
)
|
|
306
|
+
return cls.from_shapes(
|
|
307
|
+
shapes=shapes,
|
|
308
|
+
base_scale=base_scale,
|
|
309
|
+
axes=axes,
|
|
310
|
+
levels_paths=levels_paths,
|
|
311
|
+
chunks=chunks,
|
|
312
|
+
shards=shards,
|
|
313
|
+
data_type=data_type,
|
|
314
|
+
dimension_separator=dimension_separator,
|
|
315
|
+
compressors=compressors,
|
|
316
|
+
zarr_format=zarr_format,
|
|
317
|
+
other_array_kwargs=other_array_kwargs,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def from_shapes(
|
|
322
|
+
cls,
|
|
323
|
+
shapes: Sequence[tuple[int, ...]],
|
|
324
|
+
base_scale: tuple[float, ...],
|
|
325
|
+
axes: tuple[str, ...],
|
|
326
|
+
levels_paths: Sequence[str] | None = None,
|
|
327
|
+
chunks: ChunksLike = "auto",
|
|
328
|
+
shards: ShardsLike | None = None,
|
|
329
|
+
data_type: str = "uint16",
|
|
330
|
+
dimension_separator: Literal[".", "/"] = "/",
|
|
331
|
+
compressors: Any = "auto",
|
|
332
|
+
zarr_format: Literal[2, 3] = 2,
|
|
333
|
+
other_array_kwargs: Mapping[str, Any] | None = None,
|
|
334
|
+
) -> "ImagePyramidBuilder":
|
|
335
|
+
levels = []
|
|
336
|
+
if levels_paths is None:
|
|
337
|
+
levels_paths = tuple(str(i) for i in range(len(shapes)))
|
|
338
|
+
_check_order(shapes)
|
|
339
|
+
scale_ = base_scale
|
|
340
|
+
for i, (path, shape) in enumerate(zip(levels_paths, shapes, strict=True)):
|
|
341
|
+
levels.append(
|
|
342
|
+
PyramidLevel(
|
|
343
|
+
path=path,
|
|
344
|
+
shape=shape,
|
|
345
|
+
scale=scale_,
|
|
346
|
+
chunks=chunks,
|
|
347
|
+
shards=shards,
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
if i + 1 < len(shapes):
|
|
351
|
+
# This only works for downsampling pyramids
|
|
352
|
+
# The _check_order function ensures that
|
|
353
|
+
# shapes are decreasing
|
|
354
|
+
next_shape = shapes[i + 1]
|
|
355
|
+
scaling_factor = tuple(
|
|
356
|
+
s1 / s2
|
|
357
|
+
for s1, s2 in zip(
|
|
358
|
+
shape,
|
|
359
|
+
next_shape,
|
|
360
|
+
strict=True,
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
scale_ = tuple(
|
|
364
|
+
s * f for s, f in zip(scale_, scaling_factor, strict=True)
|
|
365
|
+
)
|
|
366
|
+
other_array_kwargs = other_array_kwargs or {}
|
|
367
|
+
return cls(
|
|
368
|
+
levels=levels,
|
|
369
|
+
axes=axes,
|
|
370
|
+
data_type=data_type,
|
|
371
|
+
dimension_separator=dimension_separator,
|
|
372
|
+
compressors=compressors,
|
|
373
|
+
zarr_format=zarr_format,
|
|
374
|
+
other_array_kwargs=other_array_kwargs,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def to_zarr(self, group: zarr.Group) -> None:
|
|
378
|
+
"""Save the pyramid specification to a Zarr group.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
group (zarr.Group): The Zarr group to save the pyramid specification to.
|
|
382
|
+
"""
|
|
383
|
+
array_static_kwargs = {
|
|
384
|
+
"dtype": self.data_type,
|
|
385
|
+
"overwrite": True,
|
|
386
|
+
"compressors": self.compressors,
|
|
387
|
+
**self.other_array_kwargs,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if self.zarr_format == 2:
|
|
391
|
+
array_static_kwargs["chunk_key_encoding"] = {
|
|
392
|
+
"name": "v2",
|
|
393
|
+
"separator": self.dimension_separator,
|
|
394
|
+
}
|
|
395
|
+
else:
|
|
396
|
+
array_static_kwargs["chunk_key_encoding"] = {
|
|
397
|
+
"name": "default",
|
|
398
|
+
"separator": self.dimension_separator,
|
|
399
|
+
}
|
|
400
|
+
array_static_kwargs["dimension_names"] = self.axes
|
|
401
|
+
for p_level in self.levels:
|
|
402
|
+
group.create_array(
|
|
403
|
+
name=p_level.path,
|
|
404
|
+
shape=tuple(p_level.shape),
|
|
405
|
+
chunks=p_level.chunks,
|
|
406
|
+
shards=p_level.shards,
|
|
407
|
+
**array_static_kwargs,
|
|
408
|
+
)
|