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.
@@ -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
- class AbstractIteratorBuilder(ABC):
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 iter_as_numpy(self):
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 roi in self.rois:
146
- data = self.build_numpy_getter(roi)()
147
- yield data, self.build_numpy_setter(roi)
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
- def iter_as_dask(self):
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
- for roi in self.rois:
153
- data = self.build_dask_getter(roi)()
154
- yield data, self.build_dask_setter(roi)
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 map_as_numpy(self, func: Callable) -> None:
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
- for roi in self.rois:
159
- data = self.build_numpy_getter(roi)()
160
- data = func(data)
161
- self.build_numpy_setter(roi)
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(self, func: Callable) -> None:
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
- for roi in self.rois:
167
- data = self.build_dask_getter(roi)()
168
- data = func(data)
169
- self.build_dask_setter(roi)
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 Callable, Generator, Sequence
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 DaskRoiGetter, NumpyRoiGetter, TransformProtocol
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
- class FeatureExtractorIterator(AbstractIteratorBuilder):
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.require_can_be_rescaled(self._input_label)
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 lambda: (data_getter(), label_getter(), roi)
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 lambda: (data_getter(), label_getter(), roi)
172
+ return DaskFeatureGetter(data_getter, label_getter)
111
173
 
112
- def build_dask_setter(self, roi: Roi):
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) -> Generator[tuple[np.ndarray, np.ndarray, Roi]]: # type: ignore (non compatible override)
119
- """Create an iterator over the pixels of the ROIs as Dask arrays.
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 map_as_dask(self, func: Callable[[da.Array], da.Array]) -> None:
145
- """Apply a transformation function to the ROI pixels."""
146
- raise NotImplementedError("Dask mapping not implemented for this iterator.")
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 Callable, Generator, Sequence
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)