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
ngio/images/_image.py
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
import dask.array as da
|
|
7
|
+
import numpy as np
|
|
8
|
+
from pydantic import BaseModel, model_validator
|
|
9
|
+
from zarr.core.array import CompressorLike
|
|
10
|
+
|
|
11
|
+
from ngio.common import (
|
|
12
|
+
Dimensions,
|
|
13
|
+
InterpolationOrder,
|
|
14
|
+
Roi,
|
|
15
|
+
)
|
|
16
|
+
from ngio.common._pyramid import ChunksLike, ShardsLike
|
|
17
|
+
from ngio.images._abstract_image import AbstractImage, abstract_derive
|
|
18
|
+
from ngio.io_pipes import (
|
|
19
|
+
SlicingInputType,
|
|
20
|
+
TransformProtocol,
|
|
21
|
+
)
|
|
22
|
+
from ngio.ome_zarr_meta import (
|
|
23
|
+
ImageMetaHandler,
|
|
24
|
+
NgioImageMeta,
|
|
25
|
+
PixelSize,
|
|
26
|
+
)
|
|
27
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
28
|
+
Channel,
|
|
29
|
+
ChannelsMeta,
|
|
30
|
+
ChannelVisualisation,
|
|
31
|
+
DefaultSpaceUnit,
|
|
32
|
+
DefaultTimeUnit,
|
|
33
|
+
NgffVersions,
|
|
34
|
+
SpaceUnits,
|
|
35
|
+
TimeUnits,
|
|
36
|
+
)
|
|
37
|
+
from ngio.utils import (
|
|
38
|
+
NgioValueError,
|
|
39
|
+
StoreOrGroup,
|
|
40
|
+
ZarrGroupHandler,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ChannelSelectionModel(BaseModel):
|
|
45
|
+
"""Model for channel selection.
|
|
46
|
+
|
|
47
|
+
This model is used to select a channel by label, wavelength ID, or index.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
identifier (str): Unique identifier for the channel.
|
|
51
|
+
This can be a channel label, wavelength ID, or index.
|
|
52
|
+
mode (Literal["label", "wavelength_id", "index"]): Specifies how to
|
|
53
|
+
interpret the identifier. Can be "label", "wavelength_id", or
|
|
54
|
+
"index" (must be an integer).
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
mode: Literal["label", "wavelength_id", "index"] = "label"
|
|
59
|
+
identifier: str
|
|
60
|
+
|
|
61
|
+
@model_validator(mode="after")
|
|
62
|
+
def check_channel_selection(self):
|
|
63
|
+
if self.mode == "index":
|
|
64
|
+
try:
|
|
65
|
+
int(self.identifier)
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Identifier must be an integer when mode is 'index'"
|
|
69
|
+
) from e
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
ChannelSlicingInputType = (
|
|
74
|
+
None
|
|
75
|
+
| int
|
|
76
|
+
| str
|
|
77
|
+
| ChannelSelectionModel
|
|
78
|
+
| Sequence[str | ChannelSelectionModel | int]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _check_channel_meta(meta: NgioImageMeta, dimension: Dimensions) -> ChannelsMeta:
|
|
83
|
+
"""Check the channel metadata."""
|
|
84
|
+
c_dim = dimension.get("c", default=1)
|
|
85
|
+
|
|
86
|
+
if meta.channels_meta is None:
|
|
87
|
+
return ChannelsMeta.default_init(labels=c_dim)
|
|
88
|
+
|
|
89
|
+
if len(meta.channels_meta.channels) != c_dim:
|
|
90
|
+
raise NgioValueError(
|
|
91
|
+
"The number of channels does not match the image. "
|
|
92
|
+
f"Expected {len(meta.channels_meta.channels)} channels, got {c_dim}."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return meta.channels_meta
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Image(AbstractImage):
|
|
99
|
+
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
100
|
+
|
|
101
|
+
This class is meant to be subclassed by specific image types.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
group_handler: ZarrGroupHandler,
|
|
107
|
+
path: str,
|
|
108
|
+
meta_handler: ImageMetaHandler | None,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Initialize the Image at a single level.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
group_handler: The Zarr group handler.
|
|
114
|
+
path: The path to the image in the ome_zarr file.
|
|
115
|
+
meta_handler: The image metadata handler.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
if meta_handler is None:
|
|
119
|
+
meta_handler = ImageMetaHandler(group_handler)
|
|
120
|
+
super().__init__(
|
|
121
|
+
group_handler=group_handler, path=path, meta_handler=meta_handler
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def meta_handler(self) -> ImageMetaHandler:
|
|
126
|
+
"""Return the metadata handler."""
|
|
127
|
+
assert isinstance(self._meta_handler, ImageMetaHandler)
|
|
128
|
+
return self._meta_handler
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def meta(self) -> NgioImageMeta:
|
|
132
|
+
"""Return the metadata."""
|
|
133
|
+
meta = self.meta_handler.get_meta()
|
|
134
|
+
assert isinstance(meta, NgioImageMeta)
|
|
135
|
+
return meta
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def channels_meta(self) -> ChannelsMeta:
|
|
139
|
+
"""Return the channels metadata."""
|
|
140
|
+
return _check_channel_meta(self.meta, self.dimensions)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def channel_labels(self) -> list[str]:
|
|
144
|
+
"""Return the channels of the image."""
|
|
145
|
+
return self.channels_meta.channel_labels
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def wavelength_ids(self) -> list[str | None]:
|
|
149
|
+
"""Return the list of wavelength of the image."""
|
|
150
|
+
return self.channels_meta.channel_wavelength_ids
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def num_channels(self) -> int:
|
|
154
|
+
"""Return the number of channels."""
|
|
155
|
+
return len(self.channel_labels)
|
|
156
|
+
|
|
157
|
+
def get_channel_idx(
|
|
158
|
+
self, channel_label: str | None = None, wavelength_id: str | None = None
|
|
159
|
+
) -> int:
|
|
160
|
+
"""Get the index of a channel by its label or wavelength ID."""
|
|
161
|
+
return self.channels_meta.get_channel_idx(
|
|
162
|
+
channel_label=channel_label, wavelength_id=wavelength_id
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get_as_numpy(
|
|
166
|
+
self,
|
|
167
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
168
|
+
axes_order: Sequence[str] | None = None,
|
|
169
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
170
|
+
**slicing_kwargs: slice | int | Sequence[int] | None,
|
|
171
|
+
) -> np.ndarray:
|
|
172
|
+
"""Get the image as a numpy array.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
channel_selection: Select a specific channel by label.
|
|
176
|
+
If None, all channels are returned.
|
|
177
|
+
Alternatively, you can slice arbitrary channels
|
|
178
|
+
using the slice_kwargs (c=[0, 2]).
|
|
179
|
+
axes_order: The order of the axes to return the array.
|
|
180
|
+
transforms: The transforms to apply to the array.
|
|
181
|
+
**slicing_kwargs: The slices to get the array.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The array of the region of interest.
|
|
185
|
+
"""
|
|
186
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
187
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
188
|
+
)
|
|
189
|
+
return self._get_as_numpy(
|
|
190
|
+
axes_order=axes_order, transforms=transforms, **_slicing_kwargs
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def get_roi_as_numpy(
|
|
194
|
+
self,
|
|
195
|
+
roi: Roi,
|
|
196
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
197
|
+
axes_order: Sequence[str] | None = None,
|
|
198
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
199
|
+
**slicing_kwargs: SlicingInputType,
|
|
200
|
+
) -> np.ndarray:
|
|
201
|
+
"""Get the image as a numpy array for a region of interest.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
roi: The region of interest to get the array.
|
|
205
|
+
channel_selection: Select a what subset of channels to return.
|
|
206
|
+
If None, all channels are returned.
|
|
207
|
+
axes_order: The order of the axes to return the array.
|
|
208
|
+
transforms: The transforms to apply to the array.
|
|
209
|
+
**slicing_kwargs: The slices to get the array.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The array of the region of interest.
|
|
213
|
+
"""
|
|
214
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
215
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
216
|
+
)
|
|
217
|
+
return self._get_roi_as_numpy(
|
|
218
|
+
roi=roi, axes_order=axes_order, transforms=transforms, **_slicing_kwargs
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def get_as_dask(
|
|
222
|
+
self,
|
|
223
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
224
|
+
axes_order: Sequence[str] | None = None,
|
|
225
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
226
|
+
**slicing_kwargs: SlicingInputType,
|
|
227
|
+
) -> da.Array:
|
|
228
|
+
"""Get the image as a dask array.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
channel_selection: Select a what subset of channels to return.
|
|
232
|
+
If None, all channels are returned.
|
|
233
|
+
axes_order: The order of the axes to return the array.
|
|
234
|
+
transforms: The transforms to apply to the array.
|
|
235
|
+
**slicing_kwargs: The slices to get the array.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The dask array of the region of interest.
|
|
239
|
+
"""
|
|
240
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
241
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
242
|
+
)
|
|
243
|
+
return self._get_as_dask(
|
|
244
|
+
axes_order=axes_order, transforms=transforms, **_slicing_kwargs
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def get_roi_as_dask(
|
|
248
|
+
self,
|
|
249
|
+
roi: Roi,
|
|
250
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
251
|
+
axes_order: Sequence[str] | None = None,
|
|
252
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
253
|
+
**slicing_kwargs: SlicingInputType,
|
|
254
|
+
) -> da.Array:
|
|
255
|
+
"""Get the image as a dask array for a region of interest.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
roi: The region of interest to get the array.
|
|
259
|
+
channel_selection: Select a what subset of channels to return.
|
|
260
|
+
If None, all channels are returned.
|
|
261
|
+
axes_order: The order of the axes to return the array.
|
|
262
|
+
transforms: The transforms to apply to the array.
|
|
263
|
+
**slicing_kwargs: The slices to get the array.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The dask array of the region of interest.
|
|
267
|
+
"""
|
|
268
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
269
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
270
|
+
)
|
|
271
|
+
return self._get_roi_as_dask(
|
|
272
|
+
roi=roi, axes_order=axes_order, transforms=transforms, **_slicing_kwargs
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def get_array(
|
|
276
|
+
self,
|
|
277
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
278
|
+
axes_order: Sequence[str] | None = None,
|
|
279
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
280
|
+
mode: Literal["numpy", "dask"] = "numpy",
|
|
281
|
+
**slicing_kwargs: SlicingInputType,
|
|
282
|
+
) -> np.ndarray | da.Array:
|
|
283
|
+
"""Get the image as a zarr array.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
channel_selection: Select a what subset of channels to return.
|
|
287
|
+
If None, all channels are returned.
|
|
288
|
+
axes_order: The order of the axes to return the array.
|
|
289
|
+
transforms: The transforms to apply to the array.
|
|
290
|
+
mode: The object type to return.
|
|
291
|
+
Can be "dask", "numpy".
|
|
292
|
+
**slicing_kwargs: The slices to get the array.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The zarr array of the region of interest.
|
|
296
|
+
"""
|
|
297
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
298
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
299
|
+
)
|
|
300
|
+
return self._get_array(
|
|
301
|
+
axes_order=axes_order, mode=mode, transforms=transforms, **_slicing_kwargs
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def get_roi(
|
|
305
|
+
self,
|
|
306
|
+
roi: Roi,
|
|
307
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
308
|
+
axes_order: Sequence[str] | None = None,
|
|
309
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
310
|
+
mode: Literal["numpy", "dask"] = "numpy",
|
|
311
|
+
**slicing_kwargs: SlicingInputType,
|
|
312
|
+
) -> np.ndarray | da.Array:
|
|
313
|
+
"""Get the image as a zarr array for a region of interest.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
roi: The region of interest to get the array.
|
|
317
|
+
channel_selection: Select a what subset of channels to return.
|
|
318
|
+
If None, all channels are returned.
|
|
319
|
+
axes_order: The order of the axes to return the array.
|
|
320
|
+
transforms: The transforms to apply to the array.
|
|
321
|
+
mode: The object type to return.
|
|
322
|
+
Can be "dask", "numpy".
|
|
323
|
+
**slicing_kwargs: The slices to get the array.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
The zarr array of the region of interest.
|
|
327
|
+
"""
|
|
328
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
329
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
330
|
+
)
|
|
331
|
+
return self._get_roi(
|
|
332
|
+
roi=roi,
|
|
333
|
+
axes_order=axes_order,
|
|
334
|
+
mode=mode,
|
|
335
|
+
transforms=transforms,
|
|
336
|
+
**_slicing_kwargs,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def set_array(
|
|
340
|
+
self,
|
|
341
|
+
patch: np.ndarray | da.Array,
|
|
342
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
343
|
+
axes_order: Sequence[str] | None = None,
|
|
344
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
345
|
+
**slicing_kwargs: SlicingInputType,
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Set the image array.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
patch: The array to set.
|
|
351
|
+
channel_selection: Select a what subset of channels to return.
|
|
352
|
+
If None, all channels are set.
|
|
353
|
+
axes_order: The order of the axes to set the array.
|
|
354
|
+
transforms: The transforms to apply to the array.
|
|
355
|
+
**slicing_kwargs: The slices to set the array.
|
|
356
|
+
"""
|
|
357
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
358
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
359
|
+
)
|
|
360
|
+
self._set_array(
|
|
361
|
+
patch=patch, axes_order=axes_order, transforms=transforms, **_slicing_kwargs
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def set_roi(
|
|
365
|
+
self,
|
|
366
|
+
roi: Roi,
|
|
367
|
+
patch: np.ndarray | da.Array,
|
|
368
|
+
channel_selection: ChannelSlicingInputType = None,
|
|
369
|
+
axes_order: Sequence[str] | None = None,
|
|
370
|
+
transforms: Sequence[TransformProtocol] | None = None,
|
|
371
|
+
**slicing_kwargs: SlicingInputType,
|
|
372
|
+
) -> None:
|
|
373
|
+
"""Set the image array for a region of interest.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
roi: The region of interest to set the array.
|
|
377
|
+
patch: The array to set.
|
|
378
|
+
channel_selection: Select a what subset of channels to return.
|
|
379
|
+
axes_order: The order of the axes to set the array.
|
|
380
|
+
transforms: The transforms to apply to the array.
|
|
381
|
+
**slicing_kwargs: The slices to set the array.
|
|
382
|
+
"""
|
|
383
|
+
_slicing_kwargs = add_channel_selection_to_slicing_dict(
|
|
384
|
+
image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
|
|
385
|
+
)
|
|
386
|
+
self._set_roi(
|
|
387
|
+
roi=roi,
|
|
388
|
+
patch=patch,
|
|
389
|
+
axes_order=axes_order,
|
|
390
|
+
transforms=transforms,
|
|
391
|
+
**_slicing_kwargs,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
def consolidate(
|
|
395
|
+
self,
|
|
396
|
+
order: InterpolationOrder = "linear",
|
|
397
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
398
|
+
) -> None:
|
|
399
|
+
"""Consolidate the label on disk."""
|
|
400
|
+
self._consolidate(order=order, mode=mode)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class ImagesContainer:
|
|
404
|
+
"""A class to handle the /labels group in an OME-NGFF file."""
|
|
405
|
+
|
|
406
|
+
def __init__(self, group_handler: ZarrGroupHandler) -> None:
|
|
407
|
+
"""Initialize the LabelGroupHandler."""
|
|
408
|
+
self._group_handler = group_handler
|
|
409
|
+
self._meta_handler = ImageMetaHandler(group_handler)
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def meta(self) -> NgioImageMeta:
|
|
413
|
+
"""Return the metadata."""
|
|
414
|
+
return self._meta_handler.get_meta()
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def levels(self) -> int:
|
|
418
|
+
"""Return the number of levels in the image."""
|
|
419
|
+
return self._meta_handler.get_meta().levels
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def levels_paths(self) -> list[str]:
|
|
423
|
+
"""Return the paths of the levels in the image."""
|
|
424
|
+
return self._meta_handler.get_meta().paths
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def num_channels(self) -> int:
|
|
428
|
+
"""Return the number of channels."""
|
|
429
|
+
image = self.get()
|
|
430
|
+
return image.num_channels
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def channel_labels(self) -> list[str]:
|
|
434
|
+
"""Return the channels of the image."""
|
|
435
|
+
image = self.get()
|
|
436
|
+
return image.channel_labels
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def wavelength_ids(self) -> list[str | None]:
|
|
440
|
+
"""Return the wavelength of the image."""
|
|
441
|
+
image = self.get()
|
|
442
|
+
return image.wavelength_ids
|
|
443
|
+
|
|
444
|
+
def get_channel_idx(
|
|
445
|
+
self, channel_label: str | None = None, wavelength_id: str | None = None
|
|
446
|
+
) -> int:
|
|
447
|
+
"""Get the index of a channel by label or wavelength ID.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
channel_label (str | None): The label of the channel.
|
|
451
|
+
If None a wavelength ID must be provided.
|
|
452
|
+
wavelength_id (str | None): The wavelength ID of the channel.
|
|
453
|
+
If None a channel label must be provided.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
int: The index of the channel.
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
image = self.get()
|
|
460
|
+
return image.get_channel_idx(
|
|
461
|
+
channel_label=channel_label, wavelength_id=wavelength_id
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
def _set_channel_meta(
|
|
465
|
+
self,
|
|
466
|
+
channels_meta: ChannelsMeta,
|
|
467
|
+
) -> None:
|
|
468
|
+
"""Set the channels metadata."""
|
|
469
|
+
meta = self.meta
|
|
470
|
+
meta.set_channels_meta(channels_meta)
|
|
471
|
+
self._meta_handler.update_meta(meta)
|
|
472
|
+
|
|
473
|
+
def set_channel_meta(
|
|
474
|
+
self,
|
|
475
|
+
labels: Sequence[str | None] | int | None = None,
|
|
476
|
+
wavelength_id: Sequence[str | None] | None = None,
|
|
477
|
+
start: Sequence[float | None] | None = None,
|
|
478
|
+
end: Sequence[float | None] | None = None,
|
|
479
|
+
percentiles: tuple[float, float] | None = None,
|
|
480
|
+
colors: Sequence[str | None] | None = None,
|
|
481
|
+
active: Sequence[bool | None] | None = None,
|
|
482
|
+
**omero_kwargs: dict,
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Create a ChannelsMeta object with the default unit.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
labels(Sequence[str | None] | int): The list of channels names
|
|
488
|
+
in the image. If an integer is provided, the channels will
|
|
489
|
+
be named "channel_i".
|
|
490
|
+
wavelength_id(Sequence[str | None]): The wavelength ID of the channel.
|
|
491
|
+
If None, the wavelength ID will be the same as the channel name.
|
|
492
|
+
start(Sequence[float | None]): The start value for each channel.
|
|
493
|
+
If None, the start value will be computed from the image.
|
|
494
|
+
end(Sequence[float | None]): The end value for each channel.
|
|
495
|
+
If None, the end value will be computed from the image.
|
|
496
|
+
percentiles(tuple[float, float] | None): The start and end
|
|
497
|
+
percentiles for each channel. If None, the percentiles will
|
|
498
|
+
not be computed.
|
|
499
|
+
colors(Sequence[str | None]): The list of colors for the
|
|
500
|
+
channels. If None, the colors will be random.
|
|
501
|
+
active (Sequence[bool | None]): Whether the channel should
|
|
502
|
+
be shown by default.
|
|
503
|
+
omero_kwargs(dict): Extra fields to store in the omero attributes.
|
|
504
|
+
"""
|
|
505
|
+
low_res_dataset = self.meta.get_lowest_resolution_dataset()
|
|
506
|
+
ref_image = self.get(path=low_res_dataset.path)
|
|
507
|
+
|
|
508
|
+
if start is not None and end is None:
|
|
509
|
+
raise NgioValueError("If start is provided, end must be provided as well.")
|
|
510
|
+
if end is not None and start is None:
|
|
511
|
+
raise NgioValueError("If end is provided, start must be provided as well.")
|
|
512
|
+
|
|
513
|
+
if start is not None and percentiles is not None:
|
|
514
|
+
raise NgioValueError(
|
|
515
|
+
"If start and end are provided, percentiles must be None."
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if percentiles is not None:
|
|
519
|
+
start, end = compute_image_percentile(
|
|
520
|
+
ref_image,
|
|
521
|
+
start_percentile=percentiles[0],
|
|
522
|
+
end_percentile=percentiles[1],
|
|
523
|
+
)
|
|
524
|
+
elif start is not None and end is not None:
|
|
525
|
+
if len(start) != len(end):
|
|
526
|
+
raise NgioValueError(
|
|
527
|
+
"The start and end lists must have the same length."
|
|
528
|
+
)
|
|
529
|
+
if len(start) != self.num_channels:
|
|
530
|
+
raise NgioValueError(
|
|
531
|
+
"The start and end lists must have the same length as "
|
|
532
|
+
"the number of channels."
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
start = list(start)
|
|
536
|
+
end = list(end)
|
|
537
|
+
|
|
538
|
+
else:
|
|
539
|
+
start, end = None, None
|
|
540
|
+
|
|
541
|
+
if labels is None:
|
|
542
|
+
labels = ref_image.num_channels
|
|
543
|
+
|
|
544
|
+
channel_meta = ChannelsMeta.default_init(
|
|
545
|
+
labels=labels,
|
|
546
|
+
wavelength_id=wavelength_id,
|
|
547
|
+
colors=colors,
|
|
548
|
+
start=start,
|
|
549
|
+
end=end,
|
|
550
|
+
active=active,
|
|
551
|
+
data_type=ref_image.dtype,
|
|
552
|
+
**omero_kwargs,
|
|
553
|
+
)
|
|
554
|
+
self._set_channel_meta(channel_meta)
|
|
555
|
+
|
|
556
|
+
def set_channel_percentiles(
|
|
557
|
+
self,
|
|
558
|
+
start_percentile: float = 0.1,
|
|
559
|
+
end_percentile: float = 99.9,
|
|
560
|
+
) -> None:
|
|
561
|
+
"""Update the percentiles of the channels."""
|
|
562
|
+
if self.meta._channels_meta is None:
|
|
563
|
+
raise NgioValueError("The channels meta is not initialized.")
|
|
564
|
+
|
|
565
|
+
low_res_dataset = self.meta.get_lowest_resolution_dataset()
|
|
566
|
+
ref_image = self.get(path=low_res_dataset.path)
|
|
567
|
+
starts, ends = compute_image_percentile(
|
|
568
|
+
ref_image, start_percentile=start_percentile, end_percentile=end_percentile
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
channels = []
|
|
572
|
+
for c, channel in enumerate(self.meta._channels_meta.channels):
|
|
573
|
+
new_v = ChannelVisualisation(
|
|
574
|
+
start=starts[c],
|
|
575
|
+
end=ends[c],
|
|
576
|
+
**channel.channel_visualisation.model_dump(exclude={"start", "end"}),
|
|
577
|
+
)
|
|
578
|
+
new_c = Channel(
|
|
579
|
+
channel_visualisation=new_v,
|
|
580
|
+
**channel.model_dump(exclude={"channel_visualisation"}),
|
|
581
|
+
)
|
|
582
|
+
channels.append(new_c)
|
|
583
|
+
|
|
584
|
+
new_meta = ChannelsMeta(channels=channels)
|
|
585
|
+
|
|
586
|
+
meta = self.meta
|
|
587
|
+
meta.set_channels_meta(new_meta)
|
|
588
|
+
self._meta_handler.update_meta(meta)
|
|
589
|
+
|
|
590
|
+
def set_axes_unit(
|
|
591
|
+
self,
|
|
592
|
+
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
593
|
+
time_unit: TimeUnits = DefaultTimeUnit,
|
|
594
|
+
) -> None:
|
|
595
|
+
"""Set the axes unit of the image.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
space_unit (SpaceUnits): The space unit of the image.
|
|
599
|
+
time_unit (TimeUnits): The time unit of the image.
|
|
600
|
+
"""
|
|
601
|
+
meta = self.meta
|
|
602
|
+
meta = meta.to_units(space_unit=space_unit, time_unit=time_unit)
|
|
603
|
+
self._meta_handler.update_meta(meta)
|
|
604
|
+
|
|
605
|
+
def derive(
|
|
606
|
+
self,
|
|
607
|
+
store: StoreOrGroup,
|
|
608
|
+
ref_path: str | None = None,
|
|
609
|
+
# Metadata parameters
|
|
610
|
+
shape: Sequence[int] | None = None,
|
|
611
|
+
pixelsize: float | tuple[float, float] | None = None,
|
|
612
|
+
z_spacing: float | None = None,
|
|
613
|
+
time_spacing: float | None = None,
|
|
614
|
+
name: str | None = None,
|
|
615
|
+
channels_meta: Sequence[str | Channel] | None = None,
|
|
616
|
+
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
617
|
+
ngff_version: NgffVersions | None = None,
|
|
618
|
+
# Zarr Array parameters
|
|
619
|
+
chunks: ChunksLike | None = None,
|
|
620
|
+
shards: ShardsLike | None = None,
|
|
621
|
+
dtype: str = "uint16",
|
|
622
|
+
dimension_separator: Literal[".", "/"] = "/",
|
|
623
|
+
compressors: CompressorLike = "auto",
|
|
624
|
+
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
625
|
+
overwrite: bool = False,
|
|
626
|
+
# Deprecated arguments
|
|
627
|
+
labels: Sequence[str] | None = None,
|
|
628
|
+
pixel_size: PixelSize | None = None,
|
|
629
|
+
) -> "ImagesContainer":
|
|
630
|
+
"""Create an empty OME-Zarr image from an existing image.
|
|
631
|
+
|
|
632
|
+
If a kwarg is not provided, the value from the reference image will be used.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
image_container (ImagesContainer): The image container to derive the new
|
|
636
|
+
image.
|
|
637
|
+
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
638
|
+
ref_path (str | None): The path to the reference image in the image
|
|
639
|
+
container.
|
|
640
|
+
shape (Sequence[int] | None): The shape of the new image.
|
|
641
|
+
pixelsize (float | tuple[float, float] | None): The pixel size of the new
|
|
642
|
+
image.
|
|
643
|
+
z_spacing (float | None): The z spacing of the new image.
|
|
644
|
+
time_spacing (float | None): The time spacing of the new image.
|
|
645
|
+
name (str | None): The name of the new image.
|
|
646
|
+
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
647
|
+
of the new image.
|
|
648
|
+
channels_policy (Literal["same", "squeeze", "singleton"] | int):
|
|
649
|
+
Possible policies:
|
|
650
|
+
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
651
|
+
- If "same", the channels axis will be kept as is (if it exists).
|
|
652
|
+
- If "singleton", the channels axis will be set to size 1.
|
|
653
|
+
- If an integer is provided, the channels axis will be changed to have
|
|
654
|
+
that size.
|
|
655
|
+
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
656
|
+
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
657
|
+
shards (ShardsLike | None): The shard shape of the new image.
|
|
658
|
+
dtype (str | None): The data type of the new image.
|
|
659
|
+
dimension_separator (DIMENSION_SEPARATOR | None): The separator to use for
|
|
660
|
+
dimensions.
|
|
661
|
+
compressors (CompressorLike | None): The compressors to use.
|
|
662
|
+
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
663
|
+
the zarr array creation.
|
|
664
|
+
overwrite (bool): Whether to overwrite an existing image.
|
|
665
|
+
labels (Sequence[str] | None): The labels of the new image.
|
|
666
|
+
This argument is deprecated please use channels_meta instead.
|
|
667
|
+
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
668
|
+
This argument is deprecated please use pixelsize, z_spacing,
|
|
669
|
+
and time_spacing instead.
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
ImagesContainer: The new derived image.
|
|
673
|
+
|
|
674
|
+
"""
|
|
675
|
+
return derive_image_container(
|
|
676
|
+
image_container=self,
|
|
677
|
+
store=store,
|
|
678
|
+
ref_path=ref_path,
|
|
679
|
+
shape=shape,
|
|
680
|
+
pixelsize=pixelsize,
|
|
681
|
+
z_spacing=z_spacing,
|
|
682
|
+
time_spacing=time_spacing,
|
|
683
|
+
name=name,
|
|
684
|
+
channels_meta=channels_meta,
|
|
685
|
+
channels_policy=channels_policy,
|
|
686
|
+
ngff_version=ngff_version,
|
|
687
|
+
chunks=chunks,
|
|
688
|
+
shards=shards,
|
|
689
|
+
dtype=dtype,
|
|
690
|
+
dimension_separator=dimension_separator,
|
|
691
|
+
compressors=compressors,
|
|
692
|
+
extra_array_kwargs=extra_array_kwargs,
|
|
693
|
+
overwrite=overwrite,
|
|
694
|
+
labels=labels,
|
|
695
|
+
pixel_size=pixel_size,
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
def get(
|
|
699
|
+
self,
|
|
700
|
+
path: str | None = None,
|
|
701
|
+
pixel_size: PixelSize | None = None,
|
|
702
|
+
strict: bool = False,
|
|
703
|
+
) -> Image:
|
|
704
|
+
"""Get an image at a specific level.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
path (str | None): The path to the image in the ome_zarr file.
|
|
708
|
+
pixel_size (PixelSize | None): The pixel size of the image.
|
|
709
|
+
strict (bool): Only used if the pixel size is provided. If True, the
|
|
710
|
+
pixel size must match the image pixel size exactly. If False, the
|
|
711
|
+
closest pixel size level will be returned.
|
|
712
|
+
|
|
713
|
+
"""
|
|
714
|
+
dataset = self._meta_handler.get_meta().get_dataset(
|
|
715
|
+
path=path, pixel_size=pixel_size, strict=strict
|
|
716
|
+
)
|
|
717
|
+
return Image(
|
|
718
|
+
group_handler=self._group_handler,
|
|
719
|
+
path=dataset.path,
|
|
720
|
+
meta_handler=self._meta_handler,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def compute_image_percentile(
|
|
725
|
+
image: Image,
|
|
726
|
+
start_percentile: float = 0.1,
|
|
727
|
+
end_percentile: float = 99.9,
|
|
728
|
+
) -> tuple[list[float], list[float]]:
|
|
729
|
+
"""Compute the start and end percentiles for each channel of an image.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
image: The image to compute the percentiles for.
|
|
733
|
+
start_percentile: The start percentile to compute.
|
|
734
|
+
end_percentile: The end percentile to compute.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
A tuple containing the start and end percentiles for each channel.
|
|
738
|
+
"""
|
|
739
|
+
starts, ends = [], []
|
|
740
|
+
for c in range(image.num_channels):
|
|
741
|
+
if image.num_channels == 1:
|
|
742
|
+
data = image.get_as_dask()
|
|
743
|
+
else:
|
|
744
|
+
data = image.get_as_dask(c=c)
|
|
745
|
+
|
|
746
|
+
data = da.ravel(data)
|
|
747
|
+
# remove all the zeros
|
|
748
|
+
mask = data > 1e-16
|
|
749
|
+
data = data[mask]
|
|
750
|
+
_data = data.compute()
|
|
751
|
+
if _data.size == 0:
|
|
752
|
+
starts.append(0.0)
|
|
753
|
+
ends.append(0.0)
|
|
754
|
+
continue
|
|
755
|
+
|
|
756
|
+
# compute the percentiles
|
|
757
|
+
_s_perc, _e_perc = da.percentile(
|
|
758
|
+
data, [start_percentile, end_percentile], method="nearest"
|
|
759
|
+
).compute() # type: ignore (return type is a tuple of floats)
|
|
760
|
+
|
|
761
|
+
starts.append(float(_s_perc))
|
|
762
|
+
ends.append(float(_e_perc))
|
|
763
|
+
return starts, ends
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def derive_image_container(
|
|
767
|
+
*,
|
|
768
|
+
image_container: ImagesContainer,
|
|
769
|
+
store: StoreOrGroup,
|
|
770
|
+
ref_path: str | None = None,
|
|
771
|
+
# Metadata parameters
|
|
772
|
+
shape: Sequence[int] | None = None,
|
|
773
|
+
pixelsize: float | tuple[float, float] | None = None,
|
|
774
|
+
z_spacing: float | None = None,
|
|
775
|
+
time_spacing: float | None = None,
|
|
776
|
+
name: str | None = None,
|
|
777
|
+
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
778
|
+
channels_meta: Sequence[str | Channel] | None = None,
|
|
779
|
+
ngff_version: NgffVersions | None = None,
|
|
780
|
+
# Zarr Array parameters
|
|
781
|
+
chunks: ChunksLike | None = None,
|
|
782
|
+
shards: ShardsLike | None = None,
|
|
783
|
+
dtype: str | None = None,
|
|
784
|
+
dimension_separator: Literal[".", "/"] | None = None,
|
|
785
|
+
compressors: CompressorLike | None = None,
|
|
786
|
+
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
787
|
+
overwrite: bool = False,
|
|
788
|
+
# Deprecated arguments
|
|
789
|
+
labels: Sequence[str] | None = None,
|
|
790
|
+
pixel_size: PixelSize | None = None,
|
|
791
|
+
) -> ImagesContainer:
|
|
792
|
+
"""Derive a new OME-Zarr image container from an existing image.
|
|
793
|
+
|
|
794
|
+
If a kwarg is not provided, the value from the reference image will be used.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
image_container (ImagesContainer): The image container to derive the new image
|
|
798
|
+
from.
|
|
799
|
+
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
800
|
+
ref_path (str | None): The path to the reference image in the image container.
|
|
801
|
+
shape (Sequence[int] | None): The shape of the new image.
|
|
802
|
+
pixelsize (float | tuple[float, float] | None): The pixel size of the new image.
|
|
803
|
+
z_spacing (float | None): The z spacing of the new image.
|
|
804
|
+
time_spacing (float | None): The time spacing of the new image.
|
|
805
|
+
name (str | None): The name of the new image.
|
|
806
|
+
channels_policy (Literal["squeeze", "same", "singleton"] | int): Possible
|
|
807
|
+
policies:
|
|
808
|
+
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
809
|
+
- If "same", the channels axis will be kept as is (if it exists).
|
|
810
|
+
- If "singleton", the channels axis will be set to size 1.
|
|
811
|
+
- If an integer is provided, the channels axis will be changed to have
|
|
812
|
+
that size.
|
|
813
|
+
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
814
|
+
of the new image.
|
|
815
|
+
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
816
|
+
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
817
|
+
shards (ShardsLike | None): The shard shape of the new image.
|
|
818
|
+
dtype (str | None): The data type of the new image.
|
|
819
|
+
dimension_separator (Literal[".", "/"] | None): The separator to use for
|
|
820
|
+
dimensions.
|
|
821
|
+
compressors (CompressorLike | None): The compressors to use.
|
|
822
|
+
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
823
|
+
the zarr array creation.
|
|
824
|
+
overwrite (bool): Whether to overwrite an existing image. Defaults to False.
|
|
825
|
+
labels (Sequence[str] | None): Deprecated. This argument is deprecated,
|
|
826
|
+
please use channels_meta instead.
|
|
827
|
+
pixel_size (PixelSize | None): Deprecated. The pixel size of the new image.
|
|
828
|
+
This argument is deprecated, please use pixelsize, z_spacing,
|
|
829
|
+
and time_spacing instead.
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
ImagesContainer: The new derived image container.
|
|
833
|
+
|
|
834
|
+
"""
|
|
835
|
+
ref_image = image_container.get(path=ref_path)
|
|
836
|
+
group_handler = abstract_derive(
|
|
837
|
+
ref_image=ref_image,
|
|
838
|
+
meta_type=NgioImageMeta,
|
|
839
|
+
store=store,
|
|
840
|
+
shape=shape,
|
|
841
|
+
pixelsize=pixelsize,
|
|
842
|
+
z_spacing=z_spacing,
|
|
843
|
+
time_spacing=time_spacing,
|
|
844
|
+
name=name,
|
|
845
|
+
channels_meta=channels_meta,
|
|
846
|
+
channels_policy=channels_policy,
|
|
847
|
+
ngff_version=ngff_version,
|
|
848
|
+
chunks=chunks,
|
|
849
|
+
shards=shards,
|
|
850
|
+
dtype=dtype,
|
|
851
|
+
dimension_separator=dimension_separator,
|
|
852
|
+
compressors=compressors,
|
|
853
|
+
extra_array_kwargs=extra_array_kwargs,
|
|
854
|
+
overwrite=overwrite,
|
|
855
|
+
labels=labels,
|
|
856
|
+
pixel_size=pixel_size,
|
|
857
|
+
)
|
|
858
|
+
return ImagesContainer(group_handler=group_handler)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _parse_str_or_model(
|
|
862
|
+
image: Image, channel_selection: int | str | ChannelSelectionModel
|
|
863
|
+
) -> int:
|
|
864
|
+
"""Parse a string or ChannelSelectionModel to an integer channel index."""
|
|
865
|
+
if isinstance(channel_selection, int):
|
|
866
|
+
if channel_selection < 0:
|
|
867
|
+
raise NgioValueError("Channel index must be a non-negative integer.")
|
|
868
|
+
if channel_selection >= image.num_channels:
|
|
869
|
+
raise NgioValueError(
|
|
870
|
+
"Channel index must be less than the number "
|
|
871
|
+
f"of channels ({image.num_channels})."
|
|
872
|
+
)
|
|
873
|
+
return channel_selection
|
|
874
|
+
elif isinstance(channel_selection, str):
|
|
875
|
+
return image.get_channel_idx(channel_label=channel_selection)
|
|
876
|
+
elif isinstance(channel_selection, ChannelSelectionModel):
|
|
877
|
+
if channel_selection.mode == "label":
|
|
878
|
+
return image.get_channel_idx(
|
|
879
|
+
channel_label=str(channel_selection.identifier)
|
|
880
|
+
)
|
|
881
|
+
elif channel_selection.mode == "wavelength_id":
|
|
882
|
+
return image.get_channel_idx(
|
|
883
|
+
wavelength_id=str(channel_selection.identifier)
|
|
884
|
+
)
|
|
885
|
+
elif channel_selection.mode == "index":
|
|
886
|
+
return int(channel_selection.identifier)
|
|
887
|
+
raise NgioValueError(
|
|
888
|
+
"Invalid channel selection type. "
|
|
889
|
+
f"{channel_selection} is of type {type(channel_selection)} ",
|
|
890
|
+
"supported types are str, ChannelSelectionModel, and int.",
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def _parse_channel_selection(
|
|
895
|
+
image: Image, channel_selection: ChannelSlicingInputType
|
|
896
|
+
) -> dict[str, SlicingInputType]:
|
|
897
|
+
"""Parse the channel selection input into a list of channel indices."""
|
|
898
|
+
if channel_selection is None:
|
|
899
|
+
return {}
|
|
900
|
+
if isinstance(channel_selection, int | str | ChannelSelectionModel):
|
|
901
|
+
channel_index = _parse_str_or_model(image, channel_selection)
|
|
902
|
+
return {"c": channel_index}
|
|
903
|
+
elif isinstance(channel_selection, Sequence):
|
|
904
|
+
_sequence = [_parse_str_or_model(image, cs) for cs in channel_selection]
|
|
905
|
+
return {"c": _sequence}
|
|
906
|
+
raise NgioValueError(
|
|
907
|
+
f"Invalid channel selection type {type(channel_selection)}. "
|
|
908
|
+
"Supported types are int, str, ChannelSelectionModel, and Sequence."
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def add_channel_selection_to_slicing_dict(
|
|
913
|
+
image: Image,
|
|
914
|
+
channel_selection: ChannelSlicingInputType,
|
|
915
|
+
slicing_dict: dict[str, SlicingInputType],
|
|
916
|
+
) -> dict[str, SlicingInputType]:
|
|
917
|
+
"""Add channel selection information to the slicing dictionary."""
|
|
918
|
+
channel_info = _parse_channel_selection(image, channel_selection)
|
|
919
|
+
if "c" in slicing_dict and channel_info:
|
|
920
|
+
raise NgioValueError(
|
|
921
|
+
"Both channel_selection and 'c' in slicing_kwargs are provided. "
|
|
922
|
+
"Which channel selection should be used is ambiguous. "
|
|
923
|
+
"Please provide only one."
|
|
924
|
+
)
|
|
925
|
+
slicing_dict = slicing_dict | channel_info
|
|
926
|
+
return slicing_dict
|