ngio 0.4.0a3__py3-none-any.whl → 0.4.0b1__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 +1 -2
- ngio/common/__init__.py +2 -51
- ngio/common/_dimensions.py +253 -74
- ngio/common/_pyramid.py +42 -23
- ngio/common/_roi.py +49 -413
- ngio/common/_zoom.py +32 -7
- ngio/experimental/iterators/__init__.py +0 -2
- ngio/experimental/iterators/_abstract_iterator.py +246 -26
- ngio/experimental/iterators/_feature.py +90 -52
- ngio/experimental/iterators/_image_processing.py +24 -63
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +4 -4
- ngio/experimental/iterators/_segmentation.py +38 -85
- ngio/images/_abstract_image.py +192 -95
- ngio/images/_create.py +16 -0
- ngio/images/_create_synt_container.py +10 -0
- ngio/images/_image.py +35 -9
- ngio/images/_label.py +26 -3
- ngio/images/_masked_image.py +45 -61
- ngio/images/_ome_zarr_container.py +33 -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 +152 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +376 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +446 -0
- ngio/io_pipes/_ops_slices_utils.py +196 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +175 -0
- ngio/ome_zarr_meta/__init__.py +4 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -4
- ngio/ome_zarr_meta/ngio_specs/_axes.py +129 -141
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/__init__.py +1 -0
- ngio/resources/resource_model.py +1 -0
- ngio/{common/transforms → transforms}/__init__.py +1 -1
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/_datasets.py +5 -0
- ngio/utils/_zarr_utils.py +5 -1
- {ngio-0.4.0a3.dist-info → ngio-0.4.0b1.dist-info}/METADATA +1 -1
- ngio-0.4.0b1.dist-info/RECORD +85 -0
- ngio/common/_array_io_pipes.py +0 -554
- ngio/common/_array_io_utils.py +0 -508
- ngio/common/transforms/_label.py +0 -12
- ngio/common/transforms/_zoom.py +0 -109
- ngio-0.4.0a3.dist-info/RECORD +0 -76
- {ngio-0.4.0a3.dist-info → ngio-0.4.0b1.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a3.dist-info → ngio-0.4.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
from typing import Self
|
|
2
|
+
from collections.abc import Callable, Generator
|
|
3
|
+
from typing import Generic, Literal, Self, TypeVar, overload
|
|
4
4
|
|
|
5
5
|
from ngio import Roi
|
|
6
|
+
from ngio.experimental.iterators._mappers import BasicMapper, MapperProtocol
|
|
6
7
|
from ngio.experimental.iterators._rois_utils import (
|
|
7
8
|
by_chunks,
|
|
8
9
|
by_yx,
|
|
@@ -11,10 +12,16 @@ from ngio.experimental.iterators._rois_utils import (
|
|
|
11
12
|
rois_product,
|
|
12
13
|
)
|
|
13
14
|
from ngio.images._abstract_image import AbstractImage
|
|
15
|
+
from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol
|
|
16
|
+
from ngio.io_pipes._ops_slices_utils import check_if_regions_overlap
|
|
14
17
|
from ngio.tables import GenericRoiTable
|
|
18
|
+
from ngio.utils import NgioValueError
|
|
15
19
|
|
|
20
|
+
NumpyPipeType = TypeVar("NumpyPipeType")
|
|
21
|
+
DaskPipeType = TypeVar("DaskPipeType")
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
|
|
24
|
+
class AbstractIteratorBuilder(ABC, Generic[NumpyPipeType, DaskPipeType]):
|
|
18
25
|
"""Base class for building iterators over ROIs."""
|
|
19
26
|
|
|
20
27
|
_rois: list[Roi]
|
|
@@ -25,7 +32,11 @@ class AbstractIteratorBuilder(ABC):
|
|
|
25
32
|
|
|
26
33
|
@abstractmethod
|
|
27
34
|
def get_init_kwargs(self) -> dict:
|
|
28
|
-
"""Return the initialization arguments for the iterator.
|
|
35
|
+
"""Return the initialization arguments for the iterator.
|
|
36
|
+
|
|
37
|
+
This is used to clone the iterator with the same parameters
|
|
38
|
+
after every "product" operation.
|
|
39
|
+
"""
|
|
29
40
|
pass
|
|
30
41
|
|
|
31
42
|
@property
|
|
@@ -116,22 +127,22 @@ class AbstractIteratorBuilder(ABC):
|
|
|
116
127
|
return self._new_from_rois(rois)
|
|
117
128
|
|
|
118
129
|
@abstractmethod
|
|
119
|
-
def build_numpy_getter(self, roi: Roi):
|
|
130
|
+
def build_numpy_getter(self, roi: Roi) -> DataGetterProtocol[NumpyPipeType]:
|
|
120
131
|
"""Build a getter function for the given ROI."""
|
|
121
132
|
raise NotImplementedError
|
|
122
133
|
|
|
123
134
|
@abstractmethod
|
|
124
|
-
def build_numpy_setter(self, roi: Roi):
|
|
135
|
+
def build_numpy_setter(self, roi: Roi) -> DataSetterProtocol[NumpyPipeType] | None:
|
|
125
136
|
"""Build a setter function for the given ROI."""
|
|
126
137
|
raise NotImplementedError
|
|
127
138
|
|
|
128
139
|
@abstractmethod
|
|
129
|
-
def build_dask_getter(self, roi: Roi):
|
|
140
|
+
def build_dask_getter(self, roi: Roi) -> DataGetterProtocol[DaskPipeType]:
|
|
130
141
|
"""Build a Dask reader function for the given ROI."""
|
|
131
142
|
raise NotImplementedError
|
|
132
143
|
|
|
133
144
|
@abstractmethod
|
|
134
|
-
def build_dask_setter(self, roi: Roi):
|
|
145
|
+
def build_dask_setter(self, roi: Roi) -> DataSetterProtocol[DaskPipeType] | None:
|
|
135
146
|
"""Build a Dask setter function for the given ROI."""
|
|
136
147
|
raise NotImplementedError
|
|
137
148
|
|
|
@@ -140,31 +151,240 @@ class AbstractIteratorBuilder(ABC):
|
|
|
140
151
|
"""Post-process the consolidated data."""
|
|
141
152
|
raise NotImplementedError
|
|
142
153
|
|
|
143
|
-
def
|
|
154
|
+
def _numpy_getters_generator(self) -> Generator[DataGetterProtocol[NumpyPipeType]]:
|
|
155
|
+
"""Return a list of numpy getter functions for all ROIs."""
|
|
156
|
+
yield from (self.build_numpy_getter(roi) for roi in self.rois)
|
|
157
|
+
|
|
158
|
+
def _dask_getters_generator(self) -> Generator[DataGetterProtocol[DaskPipeType]]:
|
|
159
|
+
"""Return a list of dask getter functions for all ROIs."""
|
|
160
|
+
yield from (self.build_dask_getter(roi) for roi in self.rois)
|
|
161
|
+
|
|
162
|
+
def _numpy_setters_generator(
|
|
163
|
+
self,
|
|
164
|
+
) -> Generator[DataSetterProtocol[NumpyPipeType] | None]:
|
|
165
|
+
"""Return a list of numpy setter functions for all ROIs."""
|
|
166
|
+
yield from (self.build_numpy_setter(roi) for roi in self.rois)
|
|
167
|
+
|
|
168
|
+
def _dask_setters_generator(
|
|
169
|
+
self,
|
|
170
|
+
) -> Generator[DataSetterProtocol[DaskPipeType] | None]:
|
|
171
|
+
"""Return a list of dask setter functions for all ROIs."""
|
|
172
|
+
yield from (self.build_dask_setter(roi) for roi in self.rois)
|
|
173
|
+
|
|
174
|
+
def _read_and_write_generator(
|
|
175
|
+
self,
|
|
176
|
+
getters: Generator[
|
|
177
|
+
DataGetterProtocol[NumpyPipeType] | DataGetterProtocol[DaskPipeType]
|
|
178
|
+
],
|
|
179
|
+
setters: Generator[
|
|
180
|
+
DataSetterProtocol[NumpyPipeType] | DataSetterProtocol[DaskPipeType] | None
|
|
181
|
+
],
|
|
182
|
+
) -> Generator[
|
|
183
|
+
tuple[
|
|
184
|
+
DataGetterProtocol[NumpyPipeType] | DataGetterProtocol[DaskPipeType],
|
|
185
|
+
DataSetterProtocol[NumpyPipeType] | DataSetterProtocol[DaskPipeType],
|
|
186
|
+
]
|
|
187
|
+
]:
|
|
144
188
|
"""Create an iterator over the pixels of the ROIs."""
|
|
145
|
-
for
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
for getter, setter in zip(getters, setters, strict=True):
|
|
190
|
+
if setter is None:
|
|
191
|
+
name = self.__class__.__name__
|
|
192
|
+
raise NgioValueError(f"Iterator is read-only: {name}")
|
|
193
|
+
yield getter, setter
|
|
148
194
|
self.post_consolidate()
|
|
149
195
|
|
|
150
|
-
|
|
196
|
+
@overload
|
|
197
|
+
def iter(
|
|
198
|
+
self,
|
|
199
|
+
lazy: Literal[True],
|
|
200
|
+
data_mode: Literal["numpy"],
|
|
201
|
+
iterator_mode: Literal["readwrite"],
|
|
202
|
+
) -> Generator[
|
|
203
|
+
tuple[DataGetterProtocol[NumpyPipeType], DataSetterProtocol[NumpyPipeType]]
|
|
204
|
+
]: ...
|
|
205
|
+
|
|
206
|
+
@overload
|
|
207
|
+
def iter(
|
|
208
|
+
self,
|
|
209
|
+
lazy: Literal[True],
|
|
210
|
+
data_mode: Literal["numpy"],
|
|
211
|
+
iterator_mode: Literal["readonly"] = ...,
|
|
212
|
+
) -> Generator[DataGetterProtocol[NumpyPipeType]]: ...
|
|
213
|
+
|
|
214
|
+
@overload
|
|
215
|
+
def iter(
|
|
216
|
+
self,
|
|
217
|
+
lazy: Literal[True],
|
|
218
|
+
data_mode: Literal["dask"],
|
|
219
|
+
iterator_mode: Literal["readwrite"],
|
|
220
|
+
) -> Generator[
|
|
221
|
+
tuple[DataGetterProtocol[DaskPipeType], DataSetterProtocol[DaskPipeType]]
|
|
222
|
+
]: ...
|
|
223
|
+
|
|
224
|
+
@overload
|
|
225
|
+
def iter(
|
|
226
|
+
self,
|
|
227
|
+
lazy: Literal[True],
|
|
228
|
+
data_mode: Literal["dask"],
|
|
229
|
+
iterator_mode: Literal["readonly"] = ...,
|
|
230
|
+
) -> Generator[DataGetterProtocol[DaskPipeType]]: ...
|
|
231
|
+
|
|
232
|
+
@overload
|
|
233
|
+
def iter(
|
|
234
|
+
self,
|
|
235
|
+
lazy: Literal[False],
|
|
236
|
+
data_mode: Literal["numpy"],
|
|
237
|
+
iterator_mode: Literal["readwrite"],
|
|
238
|
+
) -> Generator[tuple[NumpyPipeType, DataSetterProtocol[NumpyPipeType]]]: ...
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
def iter(
|
|
242
|
+
self,
|
|
243
|
+
lazy: Literal[False],
|
|
244
|
+
data_mode: Literal["numpy"],
|
|
245
|
+
iterator_mode: Literal["readonly"] = ...,
|
|
246
|
+
) -> Generator[NumpyPipeType]: ...
|
|
247
|
+
|
|
248
|
+
@overload
|
|
249
|
+
def iter(
|
|
250
|
+
self,
|
|
251
|
+
lazy: Literal[False],
|
|
252
|
+
data_mode: Literal["dask"],
|
|
253
|
+
iterator_mode: Literal["readwrite"],
|
|
254
|
+
) -> Generator[tuple[DaskPipeType, DataSetterProtocol[DaskPipeType]]]: ...
|
|
255
|
+
|
|
256
|
+
@overload
|
|
257
|
+
def iter(
|
|
258
|
+
self,
|
|
259
|
+
lazy: Literal[False],
|
|
260
|
+
data_mode: Literal["dask"],
|
|
261
|
+
iterator_mode: Literal["readonly"] = ...,
|
|
262
|
+
) -> Generator[DaskPipeType]: ...
|
|
263
|
+
|
|
264
|
+
def iter(
|
|
265
|
+
self,
|
|
266
|
+
lazy: bool = False,
|
|
267
|
+
data_mode: Literal["numpy", "dask"] = "dask",
|
|
268
|
+
iterator_mode: Literal["readwrite", "readonly"] = "readwrite",
|
|
269
|
+
) -> Generator:
|
|
151
270
|
"""Create an iterator over the pixels of the ROIs."""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
271
|
+
if data_mode == "numpy":
|
|
272
|
+
getters = self._numpy_getters_generator()
|
|
273
|
+
setters = self._numpy_setters_generator()
|
|
274
|
+
elif data_mode == "dask":
|
|
275
|
+
getters = self._dask_getters_generator()
|
|
276
|
+
setters = self._dask_setters_generator()
|
|
277
|
+
else:
|
|
278
|
+
raise NgioValueError(f"Invalid mode: {data_mode}")
|
|
279
|
+
|
|
280
|
+
if iterator_mode == "readonly":
|
|
281
|
+
if lazy:
|
|
282
|
+
return getters
|
|
283
|
+
else:
|
|
284
|
+
return (getter() for getter in getters)
|
|
285
|
+
if lazy:
|
|
286
|
+
return self._read_and_write_generator(getters, setters)
|
|
287
|
+
else:
|
|
288
|
+
gen = self._read_and_write_generator(getters, setters)
|
|
289
|
+
return ((getter(), setter) for getter, setter in gen)
|
|
155
290
|
|
|
156
|
-
def
|
|
291
|
+
def iter_as_numpy(
|
|
292
|
+
self,
|
|
293
|
+
):
|
|
294
|
+
"""Create an iterator over the pixels of the ROIs."""
|
|
295
|
+
return self.iter(lazy=False, data_mode="numpy", iterator_mode="readwrite")
|
|
296
|
+
|
|
297
|
+
def iter_as_dask(
|
|
298
|
+
self,
|
|
299
|
+
):
|
|
300
|
+
"""Create an iterator over the pixels of the ROIs."""
|
|
301
|
+
return self.iter(lazy=False, data_mode="dask", iterator_mode="readwrite")
|
|
302
|
+
|
|
303
|
+
def map_as_numpy(
|
|
304
|
+
self,
|
|
305
|
+
func: Callable[[NumpyPipeType], NumpyPipeType],
|
|
306
|
+
mapper: MapperProtocol[NumpyPipeType] | None = None,
|
|
307
|
+
) -> None:
|
|
157
308
|
"""Apply a transformation function to the ROI pixels."""
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
309
|
+
if mapper is None:
|
|
310
|
+
_mapper = BasicMapper[NumpyPipeType]()
|
|
311
|
+
else:
|
|
312
|
+
_mapper = mapper
|
|
313
|
+
|
|
314
|
+
_mapper(
|
|
315
|
+
func=func,
|
|
316
|
+
getters=self._numpy_getters_generator(),
|
|
317
|
+
setters=self._numpy_setters_generator(),
|
|
318
|
+
)
|
|
162
319
|
self.post_consolidate()
|
|
163
320
|
|
|
164
|
-
def map_as_dask(
|
|
321
|
+
def map_as_dask(
|
|
322
|
+
self,
|
|
323
|
+
func: Callable[[DaskPipeType], DaskPipeType],
|
|
324
|
+
mapper: MapperProtocol[DaskPipeType] | None = None,
|
|
325
|
+
) -> None:
|
|
165
326
|
"""Apply a transformation function to the ROI pixels."""
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
327
|
+
if mapper is None:
|
|
328
|
+
_mapper = BasicMapper[DaskPipeType]()
|
|
329
|
+
else:
|
|
330
|
+
_mapper = mapper
|
|
331
|
+
|
|
332
|
+
_mapper(
|
|
333
|
+
func=func,
|
|
334
|
+
getters=self._dask_getters_generator(),
|
|
335
|
+
setters=self._dask_setters_generator(),
|
|
336
|
+
)
|
|
170
337
|
self.post_consolidate()
|
|
338
|
+
|
|
339
|
+
def check_if_regions_overlap(self) -> bool:
|
|
340
|
+
"""Check if any of the ROIs overlap logically.
|
|
341
|
+
|
|
342
|
+
If two ROIs cover the same pixel, they are considered to overlap.
|
|
343
|
+
This does not consider chunking or other storage details.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
bool: True if any ROIs overlap. False otherwise.
|
|
347
|
+
"""
|
|
348
|
+
if len(self.rois) < 2:
|
|
349
|
+
# Less than 2 ROIs cannot overlap
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
slicing_tuples = (
|
|
353
|
+
g.slicing_ops.normalized_slicing_tuple
|
|
354
|
+
for g in self._numpy_getters_generator()
|
|
355
|
+
)
|
|
356
|
+
x = check_if_regions_overlap(slicing_tuples)
|
|
357
|
+
return x
|
|
358
|
+
|
|
359
|
+
def require_no_regions_overlap(self) -> None:
|
|
360
|
+
"""Ensure that the Iterator's ROIs do not overlap."""
|
|
361
|
+
if self.check_if_regions_overlap():
|
|
362
|
+
raise NgioValueError("Some rois overlap.")
|
|
363
|
+
|
|
364
|
+
def check_if_chunks_overlap(self) -> bool:
|
|
365
|
+
"""Check if any of the ROIs overlap in terms of chunks.
|
|
366
|
+
|
|
367
|
+
If two ROIs cover the same chunk, they are considered to overlap in chunks.
|
|
368
|
+
This does not consider pixel-level overlaps.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
bool: True if any ROIs overlap in chunks, False otherwise.
|
|
372
|
+
"""
|
|
373
|
+
from ngio.io_pipes._ops_slices_utils import check_if_chunks_overlap
|
|
374
|
+
|
|
375
|
+
if len(self.rois) < 2:
|
|
376
|
+
# Less than 2 ROIs cannot overlap
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
slicing_tuples = (
|
|
380
|
+
g.slicing_ops.normalized_slicing_tuple
|
|
381
|
+
for g in self._numpy_getters_generator()
|
|
382
|
+
)
|
|
383
|
+
shape = self.ref_image.shape
|
|
384
|
+
chunks = self.ref_image.chunks
|
|
385
|
+
return check_if_chunks_overlap(slicing_tuples, shape, chunks)
|
|
386
|
+
|
|
387
|
+
def require_no_chunks_overlap(self) -> None:
|
|
388
|
+
"""Ensure that the ROIs do not overlap in terms of chunks."""
|
|
389
|
+
if self.check_if_chunks_overlap():
|
|
390
|
+
raise NgioValueError("Some rois overlap in chunks.")
|
|
@@ -1,23 +1,84 @@
|
|
|
1
|
-
from collections.abc import
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import TypeAlias
|
|
2
3
|
|
|
3
4
|
import dask.array as da
|
|
4
5
|
import numpy as np
|
|
5
6
|
|
|
6
|
-
from ngio.common import
|
|
7
|
-
Roi,
|
|
8
|
-
TransformProtocol,
|
|
9
|
-
build_roi_dask_getter,
|
|
10
|
-
build_roi_numpy_getter,
|
|
11
|
-
)
|
|
7
|
+
from ngio.common import Roi, RoiPixels
|
|
12
8
|
from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder
|
|
13
9
|
from ngio.images import Image, Label
|
|
14
10
|
from ngio.images._image import (
|
|
15
11
|
ChannelSlicingInputType,
|
|
16
12
|
add_channel_selection_to_slicing_dict,
|
|
17
13
|
)
|
|
14
|
+
from ngio.io_pipes import (
|
|
15
|
+
DaskRoiGetter,
|
|
16
|
+
DataGetter,
|
|
17
|
+
NumpyRoiGetter,
|
|
18
|
+
TransformProtocol,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
NumpyPipeType: TypeAlias = tuple[np.ndarray, np.ndarray, Roi | RoiPixels]
|
|
22
|
+
DaskPipeType: TypeAlias = tuple[da.Array, da.Array, Roi | RoiPixels]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NumpyFeatureGetter(DataGetter[NumpyPipeType]):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
image_getter: NumpyRoiGetter,
|
|
29
|
+
label_getter: NumpyRoiGetter,
|
|
30
|
+
) -> None:
|
|
31
|
+
self._image_getter = image_getter
|
|
32
|
+
self._label_getter = label_getter
|
|
33
|
+
super().__init__(
|
|
34
|
+
zarr_array=self._image_getter.zarr_array,
|
|
35
|
+
slicing_ops=self._image_getter.slicing_ops,
|
|
36
|
+
axes_ops=self._image_getter.axes_ops,
|
|
37
|
+
transforms=self._image_getter.transforms,
|
|
38
|
+
roi=self._image_getter.roi,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def get(self) -> NumpyPipeType:
|
|
42
|
+
return self._image_getter(), self._label_getter(), self.roi
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def image(self) -> np.ndarray:
|
|
46
|
+
return self._image_getter()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def label(self) -> np.ndarray:
|
|
50
|
+
return self._label_getter()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DaskFeatureGetter(DataGetter[DaskPipeType]):
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
image_getter: DaskRoiGetter,
|
|
57
|
+
label_getter: DaskRoiGetter,
|
|
58
|
+
) -> None:
|
|
59
|
+
self._image_getter = image_getter
|
|
60
|
+
self._label_getter = label_getter
|
|
61
|
+
super().__init__(
|
|
62
|
+
zarr_array=self._image_getter.zarr_array,
|
|
63
|
+
slicing_ops=self._image_getter.slicing_ops,
|
|
64
|
+
axes_ops=self._image_getter.axes_ops,
|
|
65
|
+
transforms=self._image_getter.transforms,
|
|
66
|
+
roi=self._image_getter.roi,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def get(self) -> DaskPipeType:
|
|
70
|
+
return self._image_getter(), self._label_getter(), self.roi
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def image(self) -> da.Array:
|
|
74
|
+
return self._image_getter()
|
|
18
75
|
|
|
76
|
+
@property
|
|
77
|
+
def label(self) -> da.Array:
|
|
78
|
+
return self._label_getter()
|
|
19
79
|
|
|
20
|
-
|
|
80
|
+
|
|
81
|
+
class FeatureExtractorIterator(AbstractIteratorBuilder[NumpyPipeType, DaskPipeType]):
|
|
21
82
|
"""Base class for iterators over ROIs."""
|
|
22
83
|
|
|
23
84
|
def __init__(
|
|
@@ -58,6 +119,9 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
58
119
|
self._input_transforms = input_transforms
|
|
59
120
|
self._label_transforms = label_transforms
|
|
60
121
|
|
|
122
|
+
self._input.require_axes_match(self._input_label)
|
|
123
|
+
self._input.require_rescalable(self._input_label)
|
|
124
|
+
|
|
61
125
|
def get_init_kwargs(self) -> dict:
|
|
62
126
|
"""Return the initialization arguments for the iterator."""
|
|
63
127
|
return {
|
|
@@ -69,83 +133,57 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
69
133
|
"label_transforms": self._label_transforms,
|
|
70
134
|
}
|
|
71
135
|
|
|
72
|
-
def build_numpy_getter(self, roi: Roi):
|
|
73
|
-
data_getter =
|
|
136
|
+
def build_numpy_getter(self, roi: Roi) -> NumpyFeatureGetter:
|
|
137
|
+
data_getter = NumpyRoiGetter(
|
|
74
138
|
zarr_array=self._input.zarr_array,
|
|
75
139
|
dimensions=self._input.dimensions,
|
|
76
140
|
axes_order=self._axes_order,
|
|
77
141
|
transforms=self._input_transforms,
|
|
78
|
-
pixel_size=self._input.pixel_size,
|
|
79
142
|
roi=roi,
|
|
80
143
|
slicing_dict=self._input_slicing_kwargs,
|
|
81
144
|
)
|
|
82
|
-
label_getter =
|
|
145
|
+
label_getter = NumpyRoiGetter(
|
|
83
146
|
zarr_array=self._input_label.zarr_array,
|
|
84
147
|
dimensions=self._input_label.dimensions,
|
|
85
148
|
axes_order=self._axes_order,
|
|
86
149
|
transforms=self._label_transforms,
|
|
87
|
-
pixel_size=self._input_label.pixel_size,
|
|
88
150
|
roi=roi,
|
|
89
151
|
remove_channel_selection=True,
|
|
90
152
|
)
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
def build_numpy_setter(self, roi: Roi):
|
|
94
|
-
return None
|
|
153
|
+
return NumpyFeatureGetter(data_getter, label_getter)
|
|
95
154
|
|
|
96
|
-
def build_dask_getter(self, roi: Roi):
|
|
97
|
-
data_getter =
|
|
155
|
+
def build_dask_getter(self, roi: Roi) -> DaskFeatureGetter:
|
|
156
|
+
data_getter = DaskRoiGetter(
|
|
98
157
|
zarr_array=self._input.zarr_array,
|
|
99
158
|
dimensions=self._input.dimensions,
|
|
100
159
|
axes_order=self._axes_order,
|
|
101
160
|
transforms=self._input_transforms,
|
|
102
|
-
pixel_size=self._input.pixel_size,
|
|
103
161
|
roi=roi,
|
|
104
162
|
slicing_dict=self._input_slicing_kwargs,
|
|
105
163
|
)
|
|
106
|
-
label_getter =
|
|
164
|
+
label_getter = DaskRoiGetter(
|
|
107
165
|
zarr_array=self._input_label.zarr_array,
|
|
108
166
|
dimensions=self._input_label.dimensions,
|
|
109
167
|
axes_order=self._axes_order,
|
|
110
168
|
transforms=self._label_transforms,
|
|
111
|
-
pixel_size=self._input_label.pixel_size,
|
|
112
169
|
roi=roi,
|
|
113
170
|
remove_channel_selection=True,
|
|
114
171
|
)
|
|
115
|
-
return
|
|
172
|
+
return DaskFeatureGetter(data_getter, label_getter)
|
|
116
173
|
|
|
117
|
-
def
|
|
174
|
+
def build_numpy_setter(self, roi: Roi) -> None:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def build_dask_setter(self, roi: Roi) -> None:
|
|
118
178
|
return None
|
|
119
179
|
|
|
120
180
|
def post_consolidate(self):
|
|
121
181
|
pass
|
|
122
182
|
|
|
123
|
-
def iter_as_numpy(self)
|
|
124
|
-
"""Create an iterator over the pixels of the ROIs
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
128
|
-
image as Dask arrays and a writer to write the output
|
|
129
|
-
to the label image.
|
|
130
|
-
"""
|
|
131
|
-
for (data, label, roi), _ in super().iter_as_numpy():
|
|
132
|
-
yield data, label, roi
|
|
133
|
-
|
|
134
|
-
def map_as_numpy(self, func: Callable[[np.ndarray], np.ndarray]) -> None:
|
|
135
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
136
|
-
raise NotImplementedError("Numpy mapping not implemented for this iterator.")
|
|
137
|
-
|
|
138
|
-
def iter_as_dask(self) -> Generator[tuple[da.Array, da.Array, Roi]]: # type: ignore (non compatible override)
|
|
139
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
143
|
-
image as Dask arrays and a writer to write the output
|
|
144
|
-
to the label image.
|
|
145
|
-
"""
|
|
146
|
-
for (data, label, roi), _ in super().iter_as_dask():
|
|
147
|
-
yield data, label, roi
|
|
183
|
+
def iter_as_numpy(self): # type: ignore[override]
|
|
184
|
+
"""Create an iterator over the pixels of the ROIs."""
|
|
185
|
+
return self.iter(lazy=False, data_mode="numpy", iterator_mode="readonly")
|
|
148
186
|
|
|
149
|
-
def
|
|
150
|
-
"""
|
|
151
|
-
|
|
187
|
+
def iter_as_dask(self): # type: ignore[override]
|
|
188
|
+
"""Create an iterator over the pixels of the ROIs."""
|
|
189
|
+
return self.iter(lazy=False, data_mode="dask", iterator_mode="readonly")
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
from collections.abc import
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
3
|
import dask.array as da
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
|
-
from ngio.common import
|
|
7
|
-
Roi,
|
|
8
|
-
TransformProtocol,
|
|
9
|
-
build_roi_dask_getter,
|
|
10
|
-
build_roi_dask_setter,
|
|
11
|
-
build_roi_numpy_getter,
|
|
12
|
-
build_roi_numpy_setter,
|
|
13
|
-
)
|
|
6
|
+
from ngio.common import Roi
|
|
14
7
|
from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder
|
|
15
8
|
from ngio.images import Image
|
|
16
9
|
from ngio.images._image import (
|
|
17
10
|
ChannelSlicingInputType,
|
|
18
11
|
add_channel_selection_to_slicing_dict,
|
|
19
12
|
)
|
|
20
|
-
from ngio.
|
|
13
|
+
from ngio.io_pipes import (
|
|
14
|
+
DaskRoiGetter,
|
|
15
|
+
DaskRoiSetter,
|
|
16
|
+
NumpyRoiGetter,
|
|
17
|
+
NumpyRoiSetter,
|
|
18
|
+
TransformProtocol,
|
|
19
|
+
)
|
|
20
|
+
from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
23
|
+
class ImageProcessingIterator(AbstractIteratorBuilder[np.ndarray, da.Array]):
|
|
24
24
|
"""Base class for iterators over ROIs."""
|
|
25
25
|
|
|
26
26
|
def __init__(
|
|
@@ -72,12 +72,7 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
72
72
|
self._input_transforms = input_transforms
|
|
73
73
|
self._output_transforms = output_transforms
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
if not self._input.dimensions.is_compatible_with(self._output.dimensions):
|
|
77
|
-
raise NgioValidationError(
|
|
78
|
-
"Input image and output label have incompatible dimensions. "
|
|
79
|
-
f"Input: {self._input.dimensions}, Output: {self._output.dimensions}."
|
|
80
|
-
)
|
|
75
|
+
self._input.require_dimensions_match(self._output, allow_singleton=True)
|
|
81
76
|
|
|
82
77
|
def get_init_kwargs(self) -> dict:
|
|
83
78
|
"""Return the initialization arguments for the iterator."""
|
|
@@ -91,79 +86,45 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
91
86
|
"output_transforms": self._output_transforms,
|
|
92
87
|
}
|
|
93
88
|
|
|
94
|
-
def build_numpy_getter(self, roi: Roi):
|
|
95
|
-
return
|
|
89
|
+
def build_numpy_getter(self, roi: Roi) -> DataGetterProtocol[np.ndarray]:
|
|
90
|
+
return NumpyRoiGetter(
|
|
96
91
|
zarr_array=self._input.zarr_array,
|
|
97
92
|
dimensions=self._input.dimensions,
|
|
93
|
+
roi=roi,
|
|
98
94
|
axes_order=self._axes_order,
|
|
99
95
|
transforms=self._input_transforms,
|
|
100
|
-
pixel_size=self._input.pixel_size,
|
|
101
|
-
roi=roi,
|
|
102
96
|
slicing_dict=self._input_slicing_kwargs,
|
|
103
97
|
)
|
|
104
98
|
|
|
105
|
-
def build_numpy_setter(self, roi: Roi):
|
|
106
|
-
return
|
|
99
|
+
def build_numpy_setter(self, roi: Roi) -> DataSetterProtocol[np.ndarray]:
|
|
100
|
+
return NumpyRoiSetter(
|
|
107
101
|
zarr_array=self._output.zarr_array,
|
|
108
102
|
dimensions=self._output.dimensions,
|
|
103
|
+
roi=roi,
|
|
109
104
|
axes_order=self._axes_order,
|
|
110
105
|
transforms=self._output_transforms,
|
|
111
|
-
pixel_size=self._output.pixel_size,
|
|
112
|
-
roi=roi,
|
|
113
106
|
slicing_dict=self._output_slicing_kwargs,
|
|
114
107
|
)
|
|
115
108
|
|
|
116
|
-
def build_dask_getter(self, roi: Roi):
|
|
117
|
-
return
|
|
109
|
+
def build_dask_getter(self, roi: Roi) -> DataGetterProtocol[da.Array]:
|
|
110
|
+
return DaskRoiGetter(
|
|
118
111
|
zarr_array=self._input.zarr_array,
|
|
119
112
|
dimensions=self._input.dimensions,
|
|
113
|
+
roi=roi,
|
|
120
114
|
axes_order=self._axes_order,
|
|
121
115
|
transforms=self._input_transforms,
|
|
122
|
-
pixel_size=self._input.pixel_size,
|
|
123
|
-
roi=roi,
|
|
124
116
|
slicing_dict=self._input_slicing_kwargs,
|
|
125
117
|
)
|
|
126
118
|
|
|
127
|
-
def build_dask_setter(self, roi: Roi):
|
|
128
|
-
return
|
|
119
|
+
def build_dask_setter(self, roi: Roi) -> DataSetterProtocol[da.Array]:
|
|
120
|
+
return DaskRoiSetter(
|
|
129
121
|
zarr_array=self._output.zarr_array,
|
|
130
122
|
dimensions=self._output.dimensions,
|
|
123
|
+
roi=roi,
|
|
131
124
|
axes_order=self._axes_order,
|
|
132
125
|
transforms=self._output_transforms,
|
|
133
|
-
pixel_size=self._output.pixel_size,
|
|
134
|
-
roi=roi,
|
|
135
126
|
slicing_dict=self._output_slicing_kwargs,
|
|
136
127
|
)
|
|
137
128
|
|
|
138
129
|
def post_consolidate(self):
|
|
139
130
|
self._output.consolidate()
|
|
140
|
-
|
|
141
|
-
def iter_as_numpy(
|
|
142
|
-
self,
|
|
143
|
-
) -> Generator[tuple[np.ndarray, Callable[[np.ndarray], None]]]:
|
|
144
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
148
|
-
image as Dask arrays and a writer to write the output
|
|
149
|
-
to the label image.
|
|
150
|
-
"""
|
|
151
|
-
return super().iter_as_numpy()
|
|
152
|
-
|
|
153
|
-
def map_as_numpy(self, func: Callable[[np.ndarray], np.ndarray]) -> None:
|
|
154
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
155
|
-
return super().map_as_numpy(func)
|
|
156
|
-
|
|
157
|
-
def iter_as_dask(self) -> Generator[tuple[da.Array, Callable[[da.Array], None]]]:
|
|
158
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
162
|
-
image as Dask arrays and a writer to write the output
|
|
163
|
-
to the label image.
|
|
164
|
-
"""
|
|
165
|
-
return super().iter_as_dask()
|
|
166
|
-
|
|
167
|
-
def map_as_dask(self, func: Callable[[da.Array], da.Array]) -> None:
|
|
168
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
169
|
-
return super().map_as_dask(func)
|