ngio 0.2.0a2__py3-none-any.whl → 0.5.0b4__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 +40 -12
- ngio/common/__init__.py +16 -32
- ngio/common/_dimensions.py +270 -48
- ngio/common/_masking_roi.py +153 -0
- ngio/common/_pyramid.py +267 -73
- ngio/common/_roi.py +290 -66
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +54 -22
- 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 +17 -58
- ngio/hcs/_plate.py +1354 -0
- ngio/images/__init__.py +30 -9
- ngio/images/_abstract_image.py +968 -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 +417 -0
- ngio/images/_masked_image.py +531 -0
- ngio/images/_ome_zarr_container.py +1235 -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 +39 -15
- ngio/ome_zarr_meta/_meta_handlers.py +490 -96
- ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
- ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
- ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
- ngio/ome_zarr_meta/v04/__init__.py +21 -5
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
- 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 +20 -4
- ngio/tables/_abstract_table.py +270 -0
- ngio/tables/_tables_container.py +449 -0
- ngio/tables/backends/__init__.py +50 -1
- ngio/tables/backends/_abstract_backend.py +200 -31
- ngio/tables/backends/_anndata.py +139 -0
- ngio/tables/backends/_anndata_utils.py +10 -114
- 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 +162 -38
- ngio/tables/backends/_utils.py +608 -0
- ngio/tables/v1/__init__.py +19 -4
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +79 -115
- ngio/tables/v1/_generic_table.py +21 -90
- ngio/tables/v1/_roi_table.py +486 -137
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +16 -14
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +121 -13
- ngio/utils/_fractal_fsspec_store.py +42 -0
- ngio/utils/_zarr_utils.py +374 -218
- ngio-0.5.0b4.dist-info/METADATA +147 -0
- ngio-0.5.0b4.dist-info/RECORD +88 -0
- {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
- ngio/common/_array_pipe.py +0 -160
- ngio/common/_axes_transforms.py +0 -63
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -97
- ngio/images/abstract_image.py +0 -240
- ngio/images/create.py +0 -251
- ngio/images/image.py +0 -389
- ngio/images/label.py +0 -236
- ngio/images/omezarr_container.py +0 -535
- ngio/ome_zarr_meta/_generic_handlers.py +0 -320
- ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
- ngio/tables/_validators.py +0 -192
- ngio/tables/backends/_anndata_v1.py +0 -75
- ngio/tables/backends/_json_v1.py +0 -56
- ngio/tables/tables_container.py +0 -300
- ngio/tables/v1/_masking_roi_table.py +0 -175
- ngio/utils/_logger.py +0 -29
- ngio-0.2.0a2.dist-info/METADATA +0 -95
- ngio-0.2.0a2.dist-info/RECORD +0 -53
- {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
import dask.array as da
|
|
9
|
+
import numpy as np
|
|
10
|
+
import zarr
|
|
11
|
+
from zarr.core.array import CompressorLike
|
|
12
|
+
|
|
13
|
+
from ngio.common import (
|
|
14
|
+
Dimensions,
|
|
15
|
+
InterpolationOrder,
|
|
16
|
+
Roi,
|
|
17
|
+
consolidate_pyramid,
|
|
18
|
+
)
|
|
19
|
+
from ngio.common._pyramid import ChunksLike, ShardsLike, shapes_from_scaling_factors
|
|
20
|
+
from ngio.images._create_utils import (
|
|
21
|
+
_image_or_label_meta,
|
|
22
|
+
init_image_like_from_shapes,
|
|
23
|
+
)
|
|
24
|
+
from ngio.io_pipes import (
|
|
25
|
+
DaskGetter,
|
|
26
|
+
DaskRoiGetter,
|
|
27
|
+
DaskRoiSetter,
|
|
28
|
+
DaskSetter,
|
|
29
|
+
NumpyGetter,
|
|
30
|
+
NumpyRoiGetter,
|
|
31
|
+
NumpyRoiSetter,
|
|
32
|
+
NumpySetter,
|
|
33
|
+
SlicingInputType,
|
|
34
|
+
TransformProtocol,
|
|
35
|
+
)
|
|
36
|
+
from ngio.ome_zarr_meta import (
|
|
37
|
+
AxesHandler,
|
|
38
|
+
Dataset,
|
|
39
|
+
ImageMetaHandler,
|
|
40
|
+
LabelMetaHandler,
|
|
41
|
+
NgioImageMeta,
|
|
42
|
+
PixelSize,
|
|
43
|
+
)
|
|
44
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
45
|
+
Channel,
|
|
46
|
+
NgffVersions,
|
|
47
|
+
NgioLabelMeta,
|
|
48
|
+
)
|
|
49
|
+
from ngio.tables import RoiTable
|
|
50
|
+
from ngio.utils import (
|
|
51
|
+
NgioFileExistsError,
|
|
52
|
+
NgioValueError,
|
|
53
|
+
StoreOrGroup,
|
|
54
|
+
ZarrGroupHandler,
|
|
55
|
+
)
|
|
56
|
+
from ngio.utils._zarr_utils import find_dimension_separator
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AbstractImage(ABC):
|
|
60
|
+
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
61
|
+
|
|
62
|
+
This class is meant to be subclassed by specific image types.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
group_handler: ZarrGroupHandler,
|
|
68
|
+
path: str,
|
|
69
|
+
meta_handler: ImageMetaHandler | LabelMetaHandler,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize the Image at a single level.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
group_handler: The Zarr group handler.
|
|
75
|
+
path: The path to the image in the ome_zarr file.
|
|
76
|
+
meta_handler: The image metadata handler.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
self._path = path
|
|
80
|
+
self._group_handler = group_handler
|
|
81
|
+
self._meta_handler = meta_handler
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
self._zarr_array = self._group_handler.get_array(self._path)
|
|
85
|
+
except NgioFileExistsError as e:
|
|
86
|
+
raise NgioFileExistsError(f"Could not find the dataset at {path}.") from e
|
|
87
|
+
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
"""Return a string representation of the image."""
|
|
90
|
+
return f"Image(path={self.path}, {self.dimensions})"
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def path(self) -> str:
|
|
94
|
+
"""Return the path of the image."""
|
|
95
|
+
return self._path
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def meta_handler(self) -> ImageMetaHandler | LabelMetaHandler:
|
|
100
|
+
"""Return the metadata."""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def meta(self) -> NgioImageMeta | NgioLabelMeta:
|
|
106
|
+
"""Return the metadata."""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def dataset(self) -> Dataset:
|
|
111
|
+
"""Return the dataset of the image."""
|
|
112
|
+
return self.meta_handler.get_meta().get_dataset(path=self.path)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def dimensions(self) -> Dimensions:
|
|
116
|
+
"""Return the dimensions of the image."""
|
|
117
|
+
return Dimensions(
|
|
118
|
+
shape=self.zarr_array.shape,
|
|
119
|
+
chunks=self.zarr_array.chunks,
|
|
120
|
+
dataset=self.dataset,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def pixel_size(self) -> PixelSize:
|
|
125
|
+
"""Return the pixel size of the image."""
|
|
126
|
+
return self.dataset.pixel_size
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def axes_handler(self) -> AxesHandler:
|
|
130
|
+
"""Return the axes handler of the image."""
|
|
131
|
+
return self.dataset.axes_handler
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def axes(self) -> tuple[str, ...]:
|
|
135
|
+
"""Return the axes of the image."""
|
|
136
|
+
return self.dimensions.axes
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def zarr_array(self) -> zarr.Array:
|
|
140
|
+
"""Return the Zarr array."""
|
|
141
|
+
return self._zarr_array
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def shape(self) -> tuple[int, ...]:
|
|
145
|
+
"""Return the shape of the image."""
|
|
146
|
+
return self.zarr_array.shape
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def dtype(self) -> str:
|
|
150
|
+
"""Return the dtype of the image."""
|
|
151
|
+
return str(self.zarr_array.dtype)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def chunks(self) -> tuple[int, ...]:
|
|
155
|
+
"""Return the chunks of the image."""
|
|
156
|
+
return self.zarr_array.chunks
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def is_3d(self) -> bool:
|
|
160
|
+
"""Return True if the image is 3D."""
|
|
161
|
+
return self.dimensions.is_3d
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def is_2d(self) -> bool:
|
|
165
|
+
"""Return True if the image is 2D."""
|
|
166
|
+
return self.dimensions.is_2d
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def is_time_series(self) -> bool:
|
|
170
|
+
"""Return True if the image is a time series."""
|
|
171
|
+
return self.dimensions.is_time_series
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def is_2d_time_series(self) -> bool:
|
|
175
|
+
"""Return True if the image is a 2D time series."""
|
|
176
|
+
return self.dimensions.is_2d_time_series
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def is_3d_time_series(self) -> bool:
|
|
180
|
+
"""Return True if the image is a 3D time series."""
|
|
181
|
+
return self.dimensions.is_3d_time_series
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def is_multi_channels(self) -> bool:
|
|
185
|
+
"""Return True if the image is multichannel."""
|
|
186
|
+
return self.dimensions.is_multi_channels
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def space_unit(self) -> str | None:
|
|
190
|
+
"""Return the space unit of the image."""
|
|
191
|
+
return self.axes_handler.space_unit
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def time_unit(self) -> str | None:
|
|
195
|
+
"""Return the time unit of the image."""
|
|
196
|
+
return self.axes_handler.time_unit
|
|
197
|
+
|
|
198
|
+
def has_axis(self, axis: str) -> bool:
|
|
199
|
+
"""Return True if the image has the given axis."""
|
|
200
|
+
return self.axes_handler.has_axis(axis)
|
|
201
|
+
|
|
202
|
+
def _get_as_numpy(
|
|
203
|
+
self,
|
|
204
|
+
axes_order: Sequence[str] | None = None,
|
|
205
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
206
|
+
**slicing_kwargs: SlicingInputType,
|
|
207
|
+
) -> np.ndarray:
|
|
208
|
+
"""Get the image as a numpy array.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
axes_order: The order of the axes to return the array.
|
|
212
|
+
transforms: The transforms to apply to the array.
|
|
213
|
+
**slicing_kwargs: The slices to get the array.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The array of the region of interest.
|
|
217
|
+
"""
|
|
218
|
+
numpy_getter = NumpyGetter(
|
|
219
|
+
zarr_array=self.zarr_array,
|
|
220
|
+
dimensions=self.dimensions,
|
|
221
|
+
axes_order=axes_order,
|
|
222
|
+
transforms=transforms,
|
|
223
|
+
slicing_dict=slicing_kwargs,
|
|
224
|
+
)
|
|
225
|
+
return numpy_getter()
|
|
226
|
+
|
|
227
|
+
def _get_roi_as_numpy(
|
|
228
|
+
self,
|
|
229
|
+
roi: Roi,
|
|
230
|
+
axes_order: Sequence[str] | None = None,
|
|
231
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
232
|
+
**slicing_kwargs: SlicingInputType,
|
|
233
|
+
) -> np.ndarray:
|
|
234
|
+
"""Get the image as a numpy array for a region of interest.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
roi: The region of interest to get the array.
|
|
238
|
+
axes_order: The order of the axes to return the array.
|
|
239
|
+
transforms: The transforms to apply to the array.
|
|
240
|
+
**slicing_kwargs: The slices to get the array.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The array of the region of interest.
|
|
244
|
+
"""
|
|
245
|
+
numpy_roi_getter = NumpyRoiGetter(
|
|
246
|
+
zarr_array=self.zarr_array,
|
|
247
|
+
dimensions=self.dimensions,
|
|
248
|
+
roi=roi,
|
|
249
|
+
axes_order=axes_order,
|
|
250
|
+
transforms=transforms,
|
|
251
|
+
slicing_dict=slicing_kwargs,
|
|
252
|
+
)
|
|
253
|
+
return numpy_roi_getter()
|
|
254
|
+
|
|
255
|
+
def _get_as_dask(
|
|
256
|
+
self,
|
|
257
|
+
axes_order: Sequence[str] | None = None,
|
|
258
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
259
|
+
**slicing_kwargs: SlicingInputType,
|
|
260
|
+
) -> da.Array:
|
|
261
|
+
"""Get the image as a dask array.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
axes_order: The order of the axes to return the array.
|
|
265
|
+
transforms: The transforms to apply to the array.
|
|
266
|
+
**slicing_kwargs: The slices to get the array.
|
|
267
|
+
"""
|
|
268
|
+
dask_getter = DaskGetter(
|
|
269
|
+
zarr_array=self.zarr_array,
|
|
270
|
+
dimensions=self.dimensions,
|
|
271
|
+
axes_order=axes_order,
|
|
272
|
+
transforms=transforms,
|
|
273
|
+
slicing_dict=slicing_kwargs,
|
|
274
|
+
)
|
|
275
|
+
return dask_getter()
|
|
276
|
+
|
|
277
|
+
def _get_roi_as_dask(
|
|
278
|
+
self,
|
|
279
|
+
roi: Roi,
|
|
280
|
+
axes_order: Sequence[str] | None = None,
|
|
281
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
282
|
+
**slicing_kwargs: SlicingInputType,
|
|
283
|
+
) -> da.Array:
|
|
284
|
+
"""Get the image as a dask array for a region of interest.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
roi: The region of interest to get the array.
|
|
288
|
+
axes_order: The order of the axes to return the array.
|
|
289
|
+
transforms: The transforms to apply to the array.
|
|
290
|
+
**slicing_kwargs: The slices to get the array.
|
|
291
|
+
"""
|
|
292
|
+
roi_dask_getter = DaskRoiGetter(
|
|
293
|
+
zarr_array=self.zarr_array,
|
|
294
|
+
dimensions=self.dimensions,
|
|
295
|
+
roi=roi,
|
|
296
|
+
axes_order=axes_order,
|
|
297
|
+
transforms=transforms,
|
|
298
|
+
slicing_dict=slicing_kwargs,
|
|
299
|
+
)
|
|
300
|
+
return roi_dask_getter()
|
|
301
|
+
|
|
302
|
+
def _get_array(
|
|
303
|
+
self,
|
|
304
|
+
axes_order: Sequence[str] | None = None,
|
|
305
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
306
|
+
mode: Literal["numpy", "dask"] = "numpy",
|
|
307
|
+
**slicing_kwargs: SlicingInputType,
|
|
308
|
+
) -> np.ndarray | da.Array:
|
|
309
|
+
"""Get a slice of the image.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
axes_order: The order of the axes to return the array.
|
|
313
|
+
transforms: The transforms to apply to the array.
|
|
314
|
+
mode: The object type to return.
|
|
315
|
+
Can be "dask", "numpy".
|
|
316
|
+
**slicing_kwargs: The slices to get the array.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
The array of the region of interest.
|
|
320
|
+
"""
|
|
321
|
+
if mode == "numpy":
|
|
322
|
+
return self._get_as_numpy(
|
|
323
|
+
axes_order=axes_order, transforms=transforms, **slicing_kwargs
|
|
324
|
+
)
|
|
325
|
+
elif mode == "dask":
|
|
326
|
+
return self._get_as_dask(
|
|
327
|
+
axes_order=axes_order, transforms=transforms, **slicing_kwargs
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
raise ValueError(
|
|
331
|
+
f"Unsupported mode: {mode}. Supported modes are: numpy, dask."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _get_roi(
|
|
335
|
+
self,
|
|
336
|
+
roi: Roi,
|
|
337
|
+
axes_order: Sequence[str] | None = None,
|
|
338
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
339
|
+
mode: Literal["numpy", "dask"] = "numpy",
|
|
340
|
+
**slice_kwargs: SlicingInputType,
|
|
341
|
+
) -> np.ndarray | da.Array:
|
|
342
|
+
"""Get a slice of the image.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
roi: The region of interest to get the array.
|
|
346
|
+
axes_order: The order of the axes to return the array.
|
|
347
|
+
transforms: The transforms to apply to the array.
|
|
348
|
+
mode: The mode to return the array.
|
|
349
|
+
Can be "dask", "numpy".
|
|
350
|
+
**slice_kwargs: The slices to get the array.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
The array of the region of interest.
|
|
354
|
+
"""
|
|
355
|
+
if mode == "numpy":
|
|
356
|
+
return self._get_roi_as_numpy(
|
|
357
|
+
roi=roi, axes_order=axes_order, transforms=transforms, **slice_kwargs
|
|
358
|
+
)
|
|
359
|
+
elif mode == "dask":
|
|
360
|
+
return self._get_roi_as_dask(
|
|
361
|
+
roi=roi, axes_order=axes_order, transforms=transforms, **slice_kwargs
|
|
362
|
+
)
|
|
363
|
+
else:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"Unsupported mode: {mode}. Supported modes are: numpy, dask."
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def _set_array(
|
|
369
|
+
self,
|
|
370
|
+
patch: np.ndarray | da.Array,
|
|
371
|
+
axes_order: Sequence[str] | None = None,
|
|
372
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
373
|
+
**slicing_kwargs: SlicingInputType,
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Set a slice of the image.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
patch: The patch to set.
|
|
379
|
+
axes_order: The order of the axes to set the patch.
|
|
380
|
+
transforms: The transforms to apply to the patch.
|
|
381
|
+
**slicing_kwargs: The slices to set the patch.
|
|
382
|
+
|
|
383
|
+
"""
|
|
384
|
+
if isinstance(patch, np.ndarray):
|
|
385
|
+
numpy_setter = NumpySetter(
|
|
386
|
+
zarr_array=self.zarr_array,
|
|
387
|
+
dimensions=self.dimensions,
|
|
388
|
+
axes_order=axes_order,
|
|
389
|
+
transforms=transforms,
|
|
390
|
+
slicing_dict=slicing_kwargs,
|
|
391
|
+
)
|
|
392
|
+
numpy_setter(patch)
|
|
393
|
+
|
|
394
|
+
elif isinstance(patch, da.Array):
|
|
395
|
+
dask_setter = DaskSetter(
|
|
396
|
+
zarr_array=self.zarr_array,
|
|
397
|
+
dimensions=self.dimensions,
|
|
398
|
+
axes_order=axes_order,
|
|
399
|
+
transforms=transforms,
|
|
400
|
+
slicing_dict=slicing_kwargs,
|
|
401
|
+
)
|
|
402
|
+
dask_setter(patch)
|
|
403
|
+
else:
|
|
404
|
+
raise TypeError(
|
|
405
|
+
f"Unsupported patch type: {type(patch)}. "
|
|
406
|
+
"Supported types are: "
|
|
407
|
+
"numpy.ndarray, dask.array.Array."
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _set_roi(
|
|
411
|
+
self,
|
|
412
|
+
roi: Roi,
|
|
413
|
+
patch: np.ndarray | da.Array,
|
|
414
|
+
axes_order: Sequence[str] | None = None,
|
|
415
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
416
|
+
**slicing_kwargs: SlicingInputType,
|
|
417
|
+
) -> None:
|
|
418
|
+
"""Set a slice of the image.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
roi: The region of interest to set the patch.
|
|
422
|
+
patch: The patch to set.
|
|
423
|
+
axes_order: The order of the axes to set the patch.
|
|
424
|
+
transforms: The transforms to apply to the patch.
|
|
425
|
+
**slicing_kwargs: The slices to set the patch.
|
|
426
|
+
|
|
427
|
+
"""
|
|
428
|
+
if isinstance(patch, np.ndarray):
|
|
429
|
+
roi_numpy_setter = NumpyRoiSetter(
|
|
430
|
+
zarr_array=self.zarr_array,
|
|
431
|
+
dimensions=self.dimensions,
|
|
432
|
+
roi=roi,
|
|
433
|
+
axes_order=axes_order,
|
|
434
|
+
transforms=transforms,
|
|
435
|
+
slicing_dict=slicing_kwargs,
|
|
436
|
+
)
|
|
437
|
+
roi_numpy_setter(patch)
|
|
438
|
+
|
|
439
|
+
elif isinstance(patch, da.Array):
|
|
440
|
+
roi_dask_setter = DaskRoiSetter(
|
|
441
|
+
zarr_array=self.zarr_array,
|
|
442
|
+
dimensions=self.dimensions,
|
|
443
|
+
roi=roi,
|
|
444
|
+
axes_order=axes_order,
|
|
445
|
+
transforms=transforms,
|
|
446
|
+
slicing_dict=slicing_kwargs,
|
|
447
|
+
)
|
|
448
|
+
roi_dask_setter(patch)
|
|
449
|
+
else:
|
|
450
|
+
raise TypeError(
|
|
451
|
+
f"Unsupported patch type: {type(patch)}. "
|
|
452
|
+
"Supported types are: "
|
|
453
|
+
"numpy.ndarray, dask.array.Array."
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
def _consolidate(
|
|
457
|
+
self,
|
|
458
|
+
order: InterpolationOrder = "linear",
|
|
459
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
460
|
+
) -> None:
|
|
461
|
+
"""Consolidate the image on disk.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
order: The order of the consolidation.
|
|
465
|
+
mode: The mode of the consolidation.
|
|
466
|
+
"""
|
|
467
|
+
consolidate_image(image=self, order=order, mode=mode)
|
|
468
|
+
|
|
469
|
+
def roi(self, name: str | None = "image") -> Roi:
|
|
470
|
+
"""Return the ROI covering the entire image."""
|
|
471
|
+
slices = {}
|
|
472
|
+
for ax_name in ["t", "z", "y", "x"]:
|
|
473
|
+
axis_size = self.dimensions.get(ax_name, default=None)
|
|
474
|
+
if axis_size is None:
|
|
475
|
+
continue
|
|
476
|
+
slices[ax_name] = slice(0, axis_size)
|
|
477
|
+
roi_px = Roi.from_values(name=name, slices=slices, space="pixel")
|
|
478
|
+
return roi_px.to_world(pixel_size=self.pixel_size)
|
|
479
|
+
|
|
480
|
+
def build_image_roi_table(self, name: str | None = "image") -> RoiTable:
|
|
481
|
+
"""Build the ROI table containing the ROI covering the entire image."""
|
|
482
|
+
return RoiTable(rois=[self.roi(name=name)])
|
|
483
|
+
|
|
484
|
+
def require_dimensions_match(
|
|
485
|
+
self,
|
|
486
|
+
other: "AbstractImage",
|
|
487
|
+
allow_singleton: bool = False,
|
|
488
|
+
) -> None:
|
|
489
|
+
"""Assert that two images have matching spatial dimensions.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
other: The other image to compare to.
|
|
493
|
+
allow_singleton: If True, allow singleton dimensions to be
|
|
494
|
+
compatible with non-singleton dimensions.
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
NgioValueError: If the images do not have compatible dimensions.
|
|
498
|
+
"""
|
|
499
|
+
self.dimensions.require_dimensions_match(
|
|
500
|
+
other.dimensions, allow_singleton=allow_singleton
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
def check_if_dimensions_match(
|
|
504
|
+
self,
|
|
505
|
+
other: "AbstractImage",
|
|
506
|
+
allow_singleton: bool = False,
|
|
507
|
+
) -> bool:
|
|
508
|
+
"""Check if two images have matching spatial dimensions.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
other: The other image to compare to.
|
|
512
|
+
allow_singleton: If True, allow singleton dimensions to be
|
|
513
|
+
compatible with non-singleton dimensions.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
bool: True if the images have matching dimensions, False otherwise.
|
|
517
|
+
"""
|
|
518
|
+
return self.dimensions.check_if_dimensions_match(
|
|
519
|
+
other.dimensions, allow_singleton=allow_singleton
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
def require_axes_match(
|
|
523
|
+
self,
|
|
524
|
+
other: "AbstractImage",
|
|
525
|
+
) -> None:
|
|
526
|
+
"""Assert that two images have compatible axes.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
other: The other image to compare to.
|
|
530
|
+
|
|
531
|
+
Raises:
|
|
532
|
+
NgioValueError: If the images do not have compatible axes.
|
|
533
|
+
"""
|
|
534
|
+
self.dimensions.require_axes_match(other.dimensions)
|
|
535
|
+
|
|
536
|
+
def check_if_axes_match(
|
|
537
|
+
self,
|
|
538
|
+
other: "AbstractImage",
|
|
539
|
+
) -> bool:
|
|
540
|
+
"""Check if two images have compatible axes.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
other: The other image to compare to.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
bool: True if the images have compatible axes, False otherwise.
|
|
547
|
+
|
|
548
|
+
"""
|
|
549
|
+
return self.dimensions.check_if_axes_match(other.dimensions)
|
|
550
|
+
|
|
551
|
+
def require_rescalable(
|
|
552
|
+
self,
|
|
553
|
+
other: "AbstractImage",
|
|
554
|
+
) -> None:
|
|
555
|
+
"""Assert that two images can be rescaled to each other.
|
|
556
|
+
|
|
557
|
+
For this to be true, the images must have the same axes, and
|
|
558
|
+
the pixel sizes must be compatible (i.e. one can be scaled to the other).
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
other: The other image to compare to.
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
NgioValueError: If the images cannot be scaled to each other.
|
|
565
|
+
"""
|
|
566
|
+
self.dimensions.require_rescalable(other.dimensions)
|
|
567
|
+
|
|
568
|
+
def check_if_rescalable(
|
|
569
|
+
self,
|
|
570
|
+
other: "AbstractImage",
|
|
571
|
+
) -> bool:
|
|
572
|
+
"""Check if two images can be rescaled to each other.
|
|
573
|
+
|
|
574
|
+
For this to be true, the images must have the same axes, and
|
|
575
|
+
the pixel sizes must be compatible (i.e. one can be scaled to the other).
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
other: The other image to compare to.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
bool: True if the images can be rescaled to each other, False otherwise.
|
|
582
|
+
"""
|
|
583
|
+
return self.dimensions.check_if_rescalable(other.dimensions)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def consolidate_image(
|
|
587
|
+
image: AbstractImage,
|
|
588
|
+
order: InterpolationOrder = "linear",
|
|
589
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
590
|
+
) -> None:
|
|
591
|
+
"""Consolidate the image on disk."""
|
|
592
|
+
target_paths = image.meta_handler.get_meta().paths
|
|
593
|
+
targets = [
|
|
594
|
+
image._group_handler.get_array(path)
|
|
595
|
+
for path in target_paths
|
|
596
|
+
if path != image.path
|
|
597
|
+
]
|
|
598
|
+
consolidate_pyramid(
|
|
599
|
+
source=image.zarr_array, targets=targets, order=order, mode=mode
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _shapes_from_ref_image(
|
|
604
|
+
ref_image: AbstractImage,
|
|
605
|
+
) -> list[tuple[int, ...]]:
|
|
606
|
+
"""Rebuild base shape based on a new shape."""
|
|
607
|
+
paths = ref_image.meta.paths
|
|
608
|
+
index_path = paths.index(ref_image.path)
|
|
609
|
+
sub_paths = paths[index_path:]
|
|
610
|
+
group_handler = ref_image._group_handler
|
|
611
|
+
shapes = []
|
|
612
|
+
for path in sub_paths:
|
|
613
|
+
zarr_array = group_handler.get_array(path)
|
|
614
|
+
shapes.append(zarr_array.shape)
|
|
615
|
+
if len(shapes) == len(paths):
|
|
616
|
+
return shapes
|
|
617
|
+
missing_levels = len(paths) - len(shapes)
|
|
618
|
+
extended_shapes = shapes_from_scaling_factors(
|
|
619
|
+
base_shape=shapes[-1],
|
|
620
|
+
scaling_factors=ref_image.meta.scaling_factor(),
|
|
621
|
+
num_levels=missing_levels + 1,
|
|
622
|
+
)
|
|
623
|
+
shapes.extend(extended_shapes[1:])
|
|
624
|
+
return shapes
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _shapes_from_new_shape(
|
|
628
|
+
ref_image: AbstractImage,
|
|
629
|
+
shape: Sequence[int],
|
|
630
|
+
) -> list[tuple[int, ...]]:
|
|
631
|
+
"""Rebuild pyramid shapes based on a new base shape."""
|
|
632
|
+
if len(shape) != len(ref_image.shape):
|
|
633
|
+
raise NgioValueError(
|
|
634
|
+
"The shape of the new image does not match the reference image."
|
|
635
|
+
f"Got shape {shape} for reference shape {ref_image.shape}."
|
|
636
|
+
)
|
|
637
|
+
base_shape = tuple(shape)
|
|
638
|
+
scaling_factors = ref_image.meta.scaling_factor()
|
|
639
|
+
num_levels = len(ref_image.meta.paths)
|
|
640
|
+
return shapes_from_scaling_factors(
|
|
641
|
+
base_shape=base_shape,
|
|
642
|
+
scaling_factors=scaling_factors,
|
|
643
|
+
num_levels=num_levels,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _compute_pyramid_shapes(
|
|
648
|
+
ref_image: AbstractImage,
|
|
649
|
+
shape: Sequence[int] | None,
|
|
650
|
+
) -> list[tuple[int, ...]]:
|
|
651
|
+
"""Rebuild pyramid shapes based on a new base shape."""
|
|
652
|
+
if shape is None:
|
|
653
|
+
return _shapes_from_ref_image(ref_image=ref_image)
|
|
654
|
+
return _shapes_from_new_shape(ref_image=ref_image, shape=shape)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def _check_chunks_and_shards_compatibility(
|
|
658
|
+
ref_shape: tuple[int, ...],
|
|
659
|
+
chunks: ChunksLike,
|
|
660
|
+
shards: ShardsLike | None,
|
|
661
|
+
) -> None:
|
|
662
|
+
"""Check if the chunks and shards are compatible with the reference shape.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
ref_shape: The reference shape.
|
|
666
|
+
chunks: The chunks to check.
|
|
667
|
+
shards: The shards to check.
|
|
668
|
+
"""
|
|
669
|
+
if chunks != "auto":
|
|
670
|
+
if len(chunks) != len(ref_shape):
|
|
671
|
+
raise NgioValueError(
|
|
672
|
+
"The length of the chunks must be the same as the number of dimensions."
|
|
673
|
+
)
|
|
674
|
+
if shards is not None and shards != "auto":
|
|
675
|
+
if len(shards) != len(ref_shape):
|
|
676
|
+
raise NgioValueError(
|
|
677
|
+
"The length of the shards must be the same as the number of dimensions."
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _apply_channel_policy(
|
|
682
|
+
ref_image: AbstractImage,
|
|
683
|
+
channels_policy: Literal["squeeze", "same", "singleton"] | int,
|
|
684
|
+
shapes: list[tuple[int, ...]],
|
|
685
|
+
axes: tuple[str, ...],
|
|
686
|
+
chunks: ChunksLike,
|
|
687
|
+
shards: ShardsLike | None,
|
|
688
|
+
) -> tuple[list[tuple[int, ...]], tuple[str, ...], ChunksLike, ShardsLike | None]:
|
|
689
|
+
"""Apply the channel policy to the shapes and axes.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
ref_image: The reference image.
|
|
693
|
+
channels_policy: The channels policy to apply.
|
|
694
|
+
shapes: The shapes of the pyramid levels.
|
|
695
|
+
axes: The axes of the image.
|
|
696
|
+
chunks: The chunks of the image.
|
|
697
|
+
shards: The shards of the image.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
The new shapes and axes after applying the channel policy.
|
|
701
|
+
"""
|
|
702
|
+
if channels_policy == "same":
|
|
703
|
+
return shapes, axes, chunks, shards
|
|
704
|
+
|
|
705
|
+
if channels_policy == "singleton":
|
|
706
|
+
# Treat 'singleton' as setting channel size to 1
|
|
707
|
+
channels_policy = 1
|
|
708
|
+
|
|
709
|
+
channel_index = ref_image.axes_handler.get_index("c")
|
|
710
|
+
if channel_index is None:
|
|
711
|
+
if channels_policy == "squeeze":
|
|
712
|
+
return shapes, axes, chunks, shards
|
|
713
|
+
raise NgioValueError(
|
|
714
|
+
f"Cannot apply channel policy {channels_policy=} to an image "
|
|
715
|
+
"without channels axis."
|
|
716
|
+
)
|
|
717
|
+
if channels_policy == "squeeze":
|
|
718
|
+
new_shapes = []
|
|
719
|
+
for shape in shapes:
|
|
720
|
+
new_shape = shape[:channel_index] + shape[channel_index + 1 :]
|
|
721
|
+
new_shapes.append(new_shape)
|
|
722
|
+
new_axes = axes[:channel_index] + axes[channel_index + 1 :]
|
|
723
|
+
if chunks == "auto":
|
|
724
|
+
new_chunks: ChunksLike = "auto"
|
|
725
|
+
else:
|
|
726
|
+
new_chunks = chunks[:channel_index] + chunks[channel_index + 1 :]
|
|
727
|
+
if shards == "auto" or shards is None:
|
|
728
|
+
new_shards: ShardsLike | None = shards
|
|
729
|
+
else:
|
|
730
|
+
new_shards = shards[:channel_index] + shards[channel_index + 1 :]
|
|
731
|
+
return new_shapes, new_axes, new_chunks, new_shards
|
|
732
|
+
elif isinstance(channels_policy, int):
|
|
733
|
+
new_shapes = []
|
|
734
|
+
for shape in shapes:
|
|
735
|
+
new_shape = (
|
|
736
|
+
*shape[:channel_index],
|
|
737
|
+
channels_policy,
|
|
738
|
+
*shape[channel_index + 1 :],
|
|
739
|
+
)
|
|
740
|
+
new_shapes.append(new_shape)
|
|
741
|
+
return new_shapes, axes, chunks, shards
|
|
742
|
+
else:
|
|
743
|
+
raise NgioValueError(
|
|
744
|
+
f"Invalid channels policy: {channels_policy}. "
|
|
745
|
+
"Must be 'squeeze', 'same', or an integer."
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _check_channels_meta_compatibility(
|
|
750
|
+
meta_type: type[_image_or_label_meta],
|
|
751
|
+
ref_image: AbstractImage,
|
|
752
|
+
channels_meta: Sequence[str | Channel] | None,
|
|
753
|
+
) -> Sequence[str | Channel] | None:
|
|
754
|
+
"""Check if the channels metadata is compatible with the reference image.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
meta_type: The metadata type.
|
|
758
|
+
ref_image: The reference image.
|
|
759
|
+
channels_meta: The channels metadata to check.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
The channels metadata if compatible, None otherwise.
|
|
763
|
+
"""
|
|
764
|
+
if issubclass(meta_type, NgioLabelMeta):
|
|
765
|
+
if channels_meta is not None:
|
|
766
|
+
raise NgioValueError("Cannot set channels_meta for a label image.")
|
|
767
|
+
return None
|
|
768
|
+
if channels_meta is not None:
|
|
769
|
+
return channels_meta
|
|
770
|
+
assert isinstance(ref_image.meta, NgioImageMeta)
|
|
771
|
+
ref_meta = ref_image.meta
|
|
772
|
+
index_c = ref_meta.axes_handler.get_index("c")
|
|
773
|
+
if index_c is None:
|
|
774
|
+
return None
|
|
775
|
+
|
|
776
|
+
# If the channels number does not match, return None
|
|
777
|
+
# Else return the channels metadata from the reference image
|
|
778
|
+
ref_shape = ref_image.shape
|
|
779
|
+
ref_num_channels = ref_shape[index_c] if index_c is not None else 1
|
|
780
|
+
channels_ = ref_meta.channels_meta.channels if ref_meta.channels_meta else []
|
|
781
|
+
# Reset to None if number of channels do not match
|
|
782
|
+
channels_meta_ = channels_ if ref_num_channels == len(channels_) else None
|
|
783
|
+
return channels_meta_
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def abstract_derive(
|
|
787
|
+
*,
|
|
788
|
+
ref_image: AbstractImage,
|
|
789
|
+
meta_type: type[_image_or_label_meta],
|
|
790
|
+
store: StoreOrGroup,
|
|
791
|
+
overwrite: bool = False,
|
|
792
|
+
# Metadata parameters
|
|
793
|
+
shape: Sequence[int] | None = None,
|
|
794
|
+
pixelsize: float | tuple[float, float] | None = None,
|
|
795
|
+
z_spacing: float | None = None,
|
|
796
|
+
time_spacing: float | None = None,
|
|
797
|
+
name: str | None = None,
|
|
798
|
+
channels_policy: Literal["squeeze", "same", "singleton"] | int = "same",
|
|
799
|
+
channels_meta: Sequence[str | Channel] | None = None,
|
|
800
|
+
ngff_version: NgffVersions | None = None,
|
|
801
|
+
# Zarr Array parameters
|
|
802
|
+
chunks: ChunksLike | None = None,
|
|
803
|
+
shards: ShardsLike | None = None,
|
|
804
|
+
dtype: str | None = None,
|
|
805
|
+
dimension_separator: Literal[".", "/"] | None = None,
|
|
806
|
+
compressors: CompressorLike | None = None,
|
|
807
|
+
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
808
|
+
# Deprecated arguments
|
|
809
|
+
labels: Sequence[str] | None = None,
|
|
810
|
+
pixel_size: PixelSize | None = None,
|
|
811
|
+
) -> ZarrGroupHandler:
|
|
812
|
+
"""Create an empty OME-Zarr image from an existing image.
|
|
813
|
+
|
|
814
|
+
If a kwarg is not provided, the value from the reference image will be used.
|
|
815
|
+
|
|
816
|
+
Args:
|
|
817
|
+
ref_image (AbstractImage): The reference image to derive from.
|
|
818
|
+
meta_type (type[_image_or_label_meta]): The metadata type to use.
|
|
819
|
+
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
820
|
+
overwrite (bool): Whether to overwrite an existing image.
|
|
821
|
+
shape (Sequence[int] | None): The shape of the new image.
|
|
822
|
+
pixelsize (float | tuple[float, float] | None): The pixel size of the new image.
|
|
823
|
+
z_spacing (float | None): The z spacing of the new image.
|
|
824
|
+
time_spacing (float | None): The time spacing of the new image.
|
|
825
|
+
axes_names (Sequence[str] | None): The axes names of the new image.
|
|
826
|
+
name (str | None): The name of the new image.
|
|
827
|
+
channels_policy (Literal["squeeze", "same", "singleton"] | int):
|
|
828
|
+
Possible policies:
|
|
829
|
+
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
830
|
+
- If "same", the channels axis will be kept as is (if it exists).
|
|
831
|
+
- If "singleton", the channels axis will be set to size 1.
|
|
832
|
+
- If an integer is provided, the channels axis will be changed to have that
|
|
833
|
+
size.
|
|
834
|
+
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
835
|
+
of the new image.
|
|
836
|
+
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
837
|
+
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
838
|
+
shards (ShardsLike | None): The shard shape of the new image.
|
|
839
|
+
dtype (str | None): The data type of the new image.
|
|
840
|
+
dimension_separator (DIMENSION_SEPARATOR | None): The separator to use for
|
|
841
|
+
dimensions.
|
|
842
|
+
compressors (CompressorLike | None): The compressors to use.
|
|
843
|
+
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
844
|
+
the zarr array creation.
|
|
845
|
+
labels (Sequence[str] | None): The labels of the new image.
|
|
846
|
+
This argument is DEPRECATED please use channels_meta instead.
|
|
847
|
+
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
848
|
+
This argument is DEPRECATED please use pixelsize, z_spacing,
|
|
849
|
+
and time_spacing instead.
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
ImagesContainer: The new derived image.
|
|
853
|
+
|
|
854
|
+
"""
|
|
855
|
+
# TODO: remove in ngio 0.6
|
|
856
|
+
if labels is not None:
|
|
857
|
+
warnings.warn(
|
|
858
|
+
"The 'labels' argument is deprecated and will be removed in "
|
|
859
|
+
"a future release.",
|
|
860
|
+
DeprecationWarning,
|
|
861
|
+
stacklevel=2,
|
|
862
|
+
)
|
|
863
|
+
channels_meta = list(labels)
|
|
864
|
+
if pixel_size is not None:
|
|
865
|
+
warnings.warn(
|
|
866
|
+
"The 'pixel_size' argument is deprecated and will be removed in "
|
|
867
|
+
"a future release.",
|
|
868
|
+
DeprecationWarning,
|
|
869
|
+
stacklevel=2,
|
|
870
|
+
)
|
|
871
|
+
pixelsize_ = (pixel_size.y, pixel_size.x)
|
|
872
|
+
z_spacing_ = pixel_size.z
|
|
873
|
+
time_spacing_ = pixel_size.t
|
|
874
|
+
else:
|
|
875
|
+
if pixelsize is None:
|
|
876
|
+
pixelsize_ = (ref_image.pixel_size.y, ref_image.pixel_size.x)
|
|
877
|
+
else:
|
|
878
|
+
pixelsize_ = pixelsize
|
|
879
|
+
|
|
880
|
+
if z_spacing is None:
|
|
881
|
+
z_spacing_ = ref_image.pixel_size.z
|
|
882
|
+
else:
|
|
883
|
+
z_spacing_ = z_spacing
|
|
884
|
+
|
|
885
|
+
if time_spacing is None:
|
|
886
|
+
time_spacing_ = ref_image.pixel_size.t
|
|
887
|
+
else:
|
|
888
|
+
time_spacing_ = time_spacing
|
|
889
|
+
ref_meta = ref_image.meta
|
|
890
|
+
|
|
891
|
+
shapes = _compute_pyramid_shapes(
|
|
892
|
+
shape=shape,
|
|
893
|
+
ref_image=ref_image,
|
|
894
|
+
)
|
|
895
|
+
ref_shape = next(iter(shapes))
|
|
896
|
+
|
|
897
|
+
if pixelsize is None:
|
|
898
|
+
pixelsize = (ref_image.pixel_size.y, ref_image.pixel_size.x)
|
|
899
|
+
|
|
900
|
+
if z_spacing is None:
|
|
901
|
+
z_spacing = ref_image.pixel_size.z
|
|
902
|
+
|
|
903
|
+
if time_spacing is None:
|
|
904
|
+
time_spacing = ref_image.pixel_size.t
|
|
905
|
+
|
|
906
|
+
if name is None:
|
|
907
|
+
name = ref_meta.name
|
|
908
|
+
|
|
909
|
+
if dtype is None:
|
|
910
|
+
dtype = ref_image.dtype
|
|
911
|
+
|
|
912
|
+
if dimension_separator is None:
|
|
913
|
+
dimension_separator = find_dimension_separator(ref_image.zarr_array)
|
|
914
|
+
|
|
915
|
+
if compressors is None:
|
|
916
|
+
compressors = ref_image.zarr_array.compressors # type: ignore
|
|
917
|
+
|
|
918
|
+
if chunks is None:
|
|
919
|
+
chunks = ref_image.zarr_array.chunks
|
|
920
|
+
if shards is None:
|
|
921
|
+
shards = ref_image.zarr_array.shards
|
|
922
|
+
|
|
923
|
+
_check_chunks_and_shards_compatibility(
|
|
924
|
+
ref_shape=ref_shape,
|
|
925
|
+
chunks=chunks,
|
|
926
|
+
shards=shards,
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
if ngff_version is None:
|
|
930
|
+
ngff_version = ref_meta.version
|
|
931
|
+
|
|
932
|
+
shapes, axes, chunks, shards = _apply_channel_policy(
|
|
933
|
+
ref_image=ref_image,
|
|
934
|
+
channels_policy=channels_policy,
|
|
935
|
+
shapes=shapes,
|
|
936
|
+
axes=ref_image.axes,
|
|
937
|
+
chunks=chunks,
|
|
938
|
+
shards=shards,
|
|
939
|
+
)
|
|
940
|
+
channels_meta_ = _check_channels_meta_compatibility(
|
|
941
|
+
meta_type=meta_type,
|
|
942
|
+
ref_image=ref_image,
|
|
943
|
+
channels_meta=channels_meta,
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
handler = init_image_like_from_shapes(
|
|
947
|
+
store=store,
|
|
948
|
+
meta_type=meta_type,
|
|
949
|
+
shapes=shapes,
|
|
950
|
+
pixelsize=pixelsize_,
|
|
951
|
+
z_spacing=z_spacing_,
|
|
952
|
+
time_spacing=time_spacing_,
|
|
953
|
+
levels=ref_meta.paths,
|
|
954
|
+
time_unit=ref_image.time_unit,
|
|
955
|
+
space_unit=ref_image.space_unit,
|
|
956
|
+
axes_names=axes,
|
|
957
|
+
name=name,
|
|
958
|
+
channels_meta=channels_meta_,
|
|
959
|
+
chunks=chunks,
|
|
960
|
+
shards=shards,
|
|
961
|
+
dtype=dtype,
|
|
962
|
+
dimension_separator=dimension_separator,
|
|
963
|
+
compressors=compressors,
|
|
964
|
+
overwrite=overwrite,
|
|
965
|
+
ngff_version=ngff_version,
|
|
966
|
+
extra_array_kwargs=extra_array_kwargs,
|
|
967
|
+
)
|
|
968
|
+
return handler
|