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.
Files changed (54) hide show
  1. ngio/__init__.py +1 -2
  2. ngio/common/__init__.py +2 -51
  3. ngio/common/_dimensions.py +253 -74
  4. ngio/common/_pyramid.py +42 -23
  5. ngio/common/_roi.py +49 -413
  6. ngio/common/_zoom.py +32 -7
  7. ngio/experimental/iterators/__init__.py +0 -2
  8. ngio/experimental/iterators/_abstract_iterator.py +246 -26
  9. ngio/experimental/iterators/_feature.py +90 -52
  10. ngio/experimental/iterators/_image_processing.py +24 -63
  11. ngio/experimental/iterators/_mappers.py +48 -0
  12. ngio/experimental/iterators/_rois_utils.py +4 -4
  13. ngio/experimental/iterators/_segmentation.py +38 -85
  14. ngio/images/_abstract_image.py +192 -95
  15. ngio/images/_create.py +16 -0
  16. ngio/images/_create_synt_container.py +10 -0
  17. ngio/images/_image.py +35 -9
  18. ngio/images/_label.py +26 -3
  19. ngio/images/_masked_image.py +45 -61
  20. ngio/images/_ome_zarr_container.py +33 -0
  21. ngio/io_pipes/__init__.py +75 -0
  22. ngio/io_pipes/_io_pipes.py +361 -0
  23. ngio/io_pipes/_io_pipes_masked.py +488 -0
  24. ngio/io_pipes/_io_pipes_roi.py +152 -0
  25. ngio/io_pipes/_io_pipes_types.py +56 -0
  26. ngio/io_pipes/_match_shape.py +376 -0
  27. ngio/io_pipes/_ops_axes.py +344 -0
  28. ngio/io_pipes/_ops_slices.py +446 -0
  29. ngio/io_pipes/_ops_slices_utils.py +196 -0
  30. ngio/io_pipes/_ops_transforms.py +104 -0
  31. ngio/io_pipes/_zoom_transform.py +175 -0
  32. ngio/ome_zarr_meta/__init__.py +4 -2
  33. ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -4
  34. ngio/ome_zarr_meta/ngio_specs/_axes.py +129 -141
  35. ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
  36. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
  37. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
  38. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
  39. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  40. ngio/resources/__init__.py +1 -0
  41. ngio/resources/resource_model.py +1 -0
  42. ngio/{common/transforms → transforms}/__init__.py +1 -1
  43. ngio/transforms/_zoom.py +19 -0
  44. ngio/utils/_datasets.py +5 -0
  45. ngio/utils/_zarr_utils.py +5 -1
  46. {ngio-0.4.0a3.dist-info → ngio-0.4.0b1.dist-info}/METADATA +1 -1
  47. ngio-0.4.0b1.dist-info/RECORD +85 -0
  48. ngio/common/_array_io_pipes.py +0 -554
  49. ngio/common/_array_io_utils.py +0 -508
  50. ngio/common/transforms/_label.py +0 -12
  51. ngio/common/transforms/_zoom.py +0 -109
  52. ngio-0.4.0a3.dist-info/RECORD +0 -76
  53. {ngio-0.4.0a3.dist-info → ngio-0.4.0b1.dist-info}/WHEEL +0 -0
  54. {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
- 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,23 +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 (
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
- class FeatureExtractorIterator(AbstractIteratorBuilder):
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 = build_roi_numpy_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 = build_roi_numpy_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 lambda: (data_getter(), label_getter(), roi)
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 = build_roi_dask_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 = build_roi_dask_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 lambda: (data_getter(), label_getter(), roi)
172
+ return DaskFeatureGetter(data_getter, label_getter)
116
173
 
117
- 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:
118
178
  return None
119
179
 
120
180
  def post_consolidate(self):
121
181
  pass
122
182
 
123
- def iter_as_numpy(self) -> Generator[tuple[np.ndarray, np.ndarray, Roi]]: # type: ignore (non compatible override)
124
- """Create an iterator over the pixels of the ROIs as Dask arrays.
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 map_as_dask(self, func: Callable[[da.Array], da.Array]) -> None:
150
- """Apply a transformation function to the ROI pixels."""
151
- 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,26 +1,26 @@
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
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.utils._errors import NgioValidationError
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
- # Check compatibility between input and output images
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 build_roi_numpy_getter(
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 build_roi_numpy_setter(
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 build_roi_dask_getter(
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 build_roi_dask_setter(
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)