ngio 0.4.0a4__py3-none-any.whl → 0.4.1__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/common/_dimensions.py +209 -189
- ngio/common/_roi.py +2 -2
- ngio/experimental/iterators/__init__.py +0 -2
- ngio/experimental/iterators/_abstract_iterator.py +246 -26
- ngio/experimental/iterators/_feature.py +84 -41
- ngio/experimental/iterators/_image_processing.py +7 -36
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_segmentation.py +7 -38
- ngio/images/_abstract_image.py +60 -5
- ngio/images/_image.py +2 -0
- ngio/images/_label.py +2 -0
- ngio/images/_masked_image.py +22 -17
- ngio/io_pipes/__init__.py +29 -3
- ngio/io_pipes/_io_pipes.py +93 -18
- ngio/io_pipes/_io_pipes_masked.py +17 -10
- ngio/io_pipes/_io_pipes_roi.py +10 -1
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_ops_axes.py +199 -1
- ngio/io_pipes/_ops_slices.py +255 -27
- ngio/io_pipes/_ops_slices_utils.py +196 -0
- ngio/io_pipes/_ops_transforms.py +1 -1
- ngio/io_pipes/_zoom_transform.py +11 -6
- ngio/ome_zarr_meta/__init__.py +0 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +0 -2
- ngio/ome_zarr_meta/ngio_specs/_axes.py +7 -131
- ngio/utils/_datasets.py +5 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/METADATA +14 -14
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/RECORD +30 -28
- ngio/io_pipes/_io_pipes_utils.py +0 -299
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.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,19 +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 Roi
|
|
7
|
+
from ngio.common import Roi, RoiPixels
|
|
7
8
|
from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder
|
|
8
9
|
from ngio.images import Image, Label
|
|
9
10
|
from ngio.images._image import (
|
|
10
11
|
ChannelSlicingInputType,
|
|
11
12
|
add_channel_selection_to_slicing_dict,
|
|
12
13
|
)
|
|
13
|
-
from ngio.io_pipes import
|
|
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()
|
|
14
75
|
|
|
76
|
+
@property
|
|
77
|
+
def label(self) -> da.Array:
|
|
78
|
+
return self._label_getter()
|
|
15
79
|
|
|
16
|
-
|
|
80
|
+
|
|
81
|
+
class FeatureExtractorIterator(AbstractIteratorBuilder[NumpyPipeType, DaskPipeType]):
|
|
17
82
|
"""Base class for iterators over ROIs."""
|
|
18
83
|
|
|
19
84
|
def __init__(
|
|
@@ -55,7 +120,7 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
55
120
|
self._label_transforms = label_transforms
|
|
56
121
|
|
|
57
122
|
self._input.require_axes_match(self._input_label)
|
|
58
|
-
self._input.
|
|
123
|
+
self._input.require_rescalable(self._input_label)
|
|
59
124
|
|
|
60
125
|
def get_init_kwargs(self) -> dict:
|
|
61
126
|
"""Return the initialization arguments for the iterator."""
|
|
@@ -68,7 +133,7 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
68
133
|
"label_transforms": self._label_transforms,
|
|
69
134
|
}
|
|
70
135
|
|
|
71
|
-
def build_numpy_getter(self, roi: Roi):
|
|
136
|
+
def build_numpy_getter(self, roi: Roi) -> NumpyFeatureGetter:
|
|
72
137
|
data_getter = NumpyRoiGetter(
|
|
73
138
|
zarr_array=self._input.zarr_array,
|
|
74
139
|
dimensions=self._input.dimensions,
|
|
@@ -85,12 +150,9 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
85
150
|
roi=roi,
|
|
86
151
|
remove_channel_selection=True,
|
|
87
152
|
)
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
def build_numpy_setter(self, roi: Roi):
|
|
91
|
-
return None
|
|
153
|
+
return NumpyFeatureGetter(data_getter, label_getter)
|
|
92
154
|
|
|
93
|
-
def build_dask_getter(self, roi: Roi):
|
|
155
|
+
def build_dask_getter(self, roi: Roi) -> DaskFeatureGetter:
|
|
94
156
|
data_getter = DaskRoiGetter(
|
|
95
157
|
zarr_array=self._input.zarr_array,
|
|
96
158
|
dimensions=self._input.dimensions,
|
|
@@ -107,40 +169,21 @@ class FeatureExtractorIterator(AbstractIteratorBuilder):
|
|
|
107
169
|
roi=roi,
|
|
108
170
|
remove_channel_selection=True,
|
|
109
171
|
)
|
|
110
|
-
return
|
|
172
|
+
return DaskFeatureGetter(data_getter, label_getter)
|
|
111
173
|
|
|
112
|
-
def
|
|
174
|
+
def build_numpy_setter(self, roi: Roi) -> None:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def build_dask_setter(self, roi: Roi) -> None:
|
|
113
178
|
return None
|
|
114
179
|
|
|
115
180
|
def post_consolidate(self):
|
|
116
181
|
pass
|
|
117
182
|
|
|
118
|
-
def iter_as_numpy(self)
|
|
119
|
-
"""Create an iterator over the pixels of the ROIs
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
123
|
-
image as Dask arrays and a writer to write the output
|
|
124
|
-
to the label image.
|
|
125
|
-
"""
|
|
126
|
-
for (data, label, roi), _ in super().iter_as_numpy():
|
|
127
|
-
yield data, label, roi
|
|
128
|
-
|
|
129
|
-
def map_as_numpy(self, func: Callable[[np.ndarray], np.ndarray]) -> None:
|
|
130
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
131
|
-
raise NotImplementedError("Numpy mapping not implemented for this iterator.")
|
|
132
|
-
|
|
133
|
-
def iter_as_dask(self) -> Generator[tuple[da.Array, da.Array, Roi]]: # type: ignore (non compatible override)
|
|
134
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
138
|
-
image as Dask arrays and a writer to write the output
|
|
139
|
-
to the label image.
|
|
140
|
-
"""
|
|
141
|
-
for (data, label, roi), _ in super().iter_as_dask():
|
|
142
|
-
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")
|
|
143
186
|
|
|
144
|
-
def
|
|
145
|
-
"""
|
|
146
|
-
|
|
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,4 +1,4 @@
|
|
|
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
|
|
@@ -17,9 +17,10 @@ from ngio.io_pipes import (
|
|
|
17
17
|
NumpyRoiSetter,
|
|
18
18
|
TransformProtocol,
|
|
19
19
|
)
|
|
20
|
+
from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
23
|
+
class ImageProcessingIterator(AbstractIteratorBuilder[np.ndarray, da.Array]):
|
|
23
24
|
"""Base class for iterators over ROIs."""
|
|
24
25
|
|
|
25
26
|
def __init__(
|
|
@@ -85,7 +86,7 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
85
86
|
"output_transforms": self._output_transforms,
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
def build_numpy_getter(self, roi: Roi):
|
|
89
|
+
def build_numpy_getter(self, roi: Roi) -> DataGetterProtocol[np.ndarray]:
|
|
89
90
|
return NumpyRoiGetter(
|
|
90
91
|
zarr_array=self._input.zarr_array,
|
|
91
92
|
dimensions=self._input.dimensions,
|
|
@@ -95,7 +96,7 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
95
96
|
slicing_dict=self._input_slicing_kwargs,
|
|
96
97
|
)
|
|
97
98
|
|
|
98
|
-
def build_numpy_setter(self, roi: Roi):
|
|
99
|
+
def build_numpy_setter(self, roi: Roi) -> DataSetterProtocol[np.ndarray]:
|
|
99
100
|
return NumpyRoiSetter(
|
|
100
101
|
zarr_array=self._output.zarr_array,
|
|
101
102
|
dimensions=self._output.dimensions,
|
|
@@ -105,7 +106,7 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
105
106
|
slicing_dict=self._output_slicing_kwargs,
|
|
106
107
|
)
|
|
107
108
|
|
|
108
|
-
def build_dask_getter(self, roi: Roi):
|
|
109
|
+
def build_dask_getter(self, roi: Roi) -> DataGetterProtocol[da.Array]:
|
|
109
110
|
return DaskRoiGetter(
|
|
110
111
|
zarr_array=self._input.zarr_array,
|
|
111
112
|
dimensions=self._input.dimensions,
|
|
@@ -115,7 +116,7 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
115
116
|
slicing_dict=self._input_slicing_kwargs,
|
|
116
117
|
)
|
|
117
118
|
|
|
118
|
-
def build_dask_setter(self, roi: Roi):
|
|
119
|
+
def build_dask_setter(self, roi: Roi) -> DataSetterProtocol[da.Array]:
|
|
119
120
|
return DaskRoiSetter(
|
|
120
121
|
zarr_array=self._output.zarr_array,
|
|
121
122
|
dimensions=self._output.dimensions,
|
|
@@ -127,33 +128,3 @@ class ImageProcessingIterator(AbstractIteratorBuilder):
|
|
|
127
128
|
|
|
128
129
|
def post_consolidate(self):
|
|
129
130
|
self._output.consolidate()
|
|
130
|
-
|
|
131
|
-
def iter_as_numpy(
|
|
132
|
-
self,
|
|
133
|
-
) -> Generator[tuple[np.ndarray, Callable[[np.ndarray], None]]]:
|
|
134
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
138
|
-
image as Dask arrays and a writer to write the output
|
|
139
|
-
to the label image.
|
|
140
|
-
"""
|
|
141
|
-
return super().iter_as_numpy()
|
|
142
|
-
|
|
143
|
-
def map_as_numpy(self, func: Callable[[np.ndarray], np.ndarray]) -> None:
|
|
144
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
145
|
-
return super().map_as_numpy(func)
|
|
146
|
-
|
|
147
|
-
def iter_as_dask(self) -> Generator[tuple[da.Array, Callable[[da.Array], None]]]:
|
|
148
|
-
"""Create an iterator over the pixels of the ROIs as Dask arrays.
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
Generator[tuple[da.Array, DaskWriter]]: An iterator the input
|
|
152
|
-
image as Dask arrays and a writer to write the output
|
|
153
|
-
to the label image.
|
|
154
|
-
"""
|
|
155
|
-
return super().iter_as_dask()
|
|
156
|
-
|
|
157
|
-
def map_as_dask(self, func: Callable[[da.Array], da.Array]) -> None:
|
|
158
|
-
"""Apply a transformation function to the ROI pixels."""
|
|
159
|
-
return super().map_as_dask(func)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Mappers for iterators.
|
|
2
|
+
|
|
3
|
+
Mappers are classes that can be passed to the `map` method of iterators to
|
|
4
|
+
transform the items yielded by the iterator.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable, Iterable
|
|
9
|
+
from typing import Generic, Protocol, TypeVar
|
|
10
|
+
|
|
11
|
+
from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol
|
|
12
|
+
from ngio.utils import NgioValueError
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MapperProtocol(Protocol[T]):
|
|
18
|
+
"""Protocol for mappers."""
|
|
19
|
+
|
|
20
|
+
def __call__(
|
|
21
|
+
self,
|
|
22
|
+
func: Callable[[T], T],
|
|
23
|
+
getters: Iterable[DataGetterProtocol[T]],
|
|
24
|
+
setters: Iterable[DataSetterProtocol[T] | None],
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Map an item to another item."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BasicMapper(Generic[T]):
|
|
31
|
+
"""A basic mapper that simply applies a function to the data."""
|
|
32
|
+
|
|
33
|
+
def __call__(
|
|
34
|
+
self,
|
|
35
|
+
func: Callable[[T], T],
|
|
36
|
+
getters: Iterable[DataGetterProtocol[T]],
|
|
37
|
+
setters: Iterable[DataSetterProtocol[T] | None],
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Map an item to another item."""
|
|
40
|
+
for getter, setter in zip(getters, setters, strict=True):
|
|
41
|
+
data = getter()
|
|
42
|
+
data = func(data)
|
|
43
|
+
if setter is None:
|
|
44
|
+
raise NgioValueError(
|
|
45
|
+
"Error in BasicMapper: setter is None, "
|
|
46
|
+
"this iterator is read-only, mapping is not possible."
|
|
47
|
+
)
|
|
48
|
+
setter(data)
|