ngio 0.4.0a4__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.
@@ -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
@@ -16,17 +16,16 @@ from ngio.io_pipes import (
16
16
  DaskRoiGetter,
17
17
  DaskRoiSetter,
18
18
  DaskSetterMasked,
19
- DataGetter,
20
- DataSetter,
21
19
  NumpyGetterMasked,
22
20
  NumpyRoiGetter,
23
21
  NumpyRoiSetter,
24
22
  NumpySetterMasked,
25
23
  TransformProtocol,
26
24
  )
25
+ from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol
27
26
 
28
27
 
29
- class SegmentationIterator(AbstractIteratorBuilder):
28
+ class SegmentationIterator(AbstractIteratorBuilder[np.ndarray, da.Array]):
30
29
  """Base class for iterators over ROIs."""
31
30
 
32
31
  def __init__(
@@ -80,7 +79,7 @@ class SegmentationIterator(AbstractIteratorBuilder):
80
79
  "output_transforms": self._output_transforms,
81
80
  }
82
81
 
83
- def build_numpy_getter(self, roi: Roi) -> DataGetter[np.ndarray]:
82
+ def build_numpy_getter(self, roi: Roi) -> DataGetterProtocol[np.ndarray]:
84
83
  return NumpyRoiGetter(
85
84
  zarr_array=self._input.zarr_array,
86
85
  dimensions=self._input.dimensions,
@@ -90,7 +89,7 @@ class SegmentationIterator(AbstractIteratorBuilder):
90
89
  slicing_dict=self._input_slicing_kwargs,
91
90
  )
92
91
 
93
- def build_numpy_setter(self, roi: Roi) -> DataSetter[np.ndarray]:
92
+ def build_numpy_setter(self, roi: Roi) -> DataSetterProtocol[np.ndarray]:
94
93
  return NumpyRoiSetter(
95
94
  zarr_array=self._output.zarr_array,
96
95
  dimensions=self._output.dimensions,
@@ -100,7 +99,7 @@ class SegmentationIterator(AbstractIteratorBuilder):
100
99
  remove_channel_selection=True,
101
100
  )
102
101
 
103
- def build_dask_getter(self, roi: Roi) -> DataGetter[da.Array]:
102
+ def build_dask_getter(self, roi: Roi) -> DataGetterProtocol[da.Array]:
104
103
  return DaskRoiGetter(
105
104
  zarr_array=self._input.zarr_array,
106
105
  dimensions=self._input.dimensions,
@@ -110,7 +109,7 @@ class SegmentationIterator(AbstractIteratorBuilder):
110
109
  slicing_dict=self._input_slicing_kwargs,
111
110
  )
112
111
 
113
- def build_dask_setter(self, roi: Roi) -> DataSetter[da.Array]:
112
+ def build_dask_setter(self, roi: Roi) -> DataSetterProtocol[da.Array]:
114
113
  return DaskRoiSetter(
115
114
  zarr_array=self._output.zarr_array,
116
115
  dimensions=self._output.dimensions,
@@ -123,36 +122,6 @@ class SegmentationIterator(AbstractIteratorBuilder):
123
122
  def post_consolidate(self):
124
123
  self._output.consolidate()
125
124
 
126
- def iter_as_numpy(
127
- self,
128
- ) -> Generator[tuple[np.ndarray, Callable[[np.ndarray], None]]]:
129
- """Create an iterator over the pixels of the ROIs as Dask arrays.
130
-
131
- Returns:
132
- Generator[tuple[da.Array, DaskWriter]]: An iterator the input
133
- image as Dask arrays and a writer to write the output
134
- to the label image.
135
- """
136
- return super().iter_as_numpy()
137
-
138
- def map_as_numpy(self, func: Callable[[np.ndarray], np.ndarray]) -> None:
139
- """Apply a transformation function to the ROI pixels."""
140
- return super().map_as_numpy(func)
141
-
142
- def iter_as_dask(self) -> Generator[tuple[da.Array, Callable[[da.Array], None]]]:
143
- """Create an iterator over the pixels of the ROIs as Dask arrays.
144
-
145
- Returns:
146
- Generator[tuple[da.Array, DaskWriter]]: An iterator the input
147
- image as Dask arrays and a writer to write the output
148
- to the label image.
149
- """
150
- return super().iter_as_dask()
151
-
152
- def map_as_dask(self, func: Callable[[da.Array], da.Array]) -> None:
153
- """Apply a transformation function to the ROI pixels."""
154
- return super().map_as_dask(func)
155
-
156
125
 
157
126
  class MaskedSegmentationIterator(SegmentationIterator):
158
127
  """Base class for iterators over ROIs."""
@@ -90,7 +90,11 @@ class AbstractImage(Generic[_image_handler]):
90
90
  @property
91
91
  def dimensions(self) -> Dimensions:
92
92
  """Return the dimensions of the image."""
93
- return Dimensions(shape=self.zarr_array.shape, dataset=self.dataset)
93
+ return Dimensions(
94
+ shape=self.zarr_array.shape,
95
+ chunks=self.zarr_array.chunks,
96
+ dataset=self.dataset,
97
+ )
94
98
 
95
99
  @property
96
100
  def pixel_size(self) -> PixelSize:
@@ -169,7 +173,7 @@ class AbstractImage(Generic[_image_handler]):
169
173
 
170
174
  def has_axis(self, axis: str) -> bool:
171
175
  """Return True if the image has the given axis."""
172
- return self.dimensions.has_axis(axis)
176
+ return self.axes_handler.has_axis(axis)
173
177
 
174
178
  def _get_as_numpy(
175
179
  self,
@@ -461,7 +465,7 @@ class AbstractImage(Generic[_image_handler]):
461
465
  return roi_px.to_roi(pixel_size=self.pixel_size)
462
466
 
463
467
  def build_image_roi_table(self, name: str | None = "image") -> RoiTable:
464
- """Build the ROI table for an image."""
468
+ """Build the ROI table containing the ROI covering the entire image."""
465
469
  return RoiTable(rois=[self.roi(name=name)])
466
470
 
467
471
  def require_dimensions_match(
@@ -483,6 +487,25 @@ class AbstractImage(Generic[_image_handler]):
483
487
  other.dimensions, allow_singleton=allow_singleton
484
488
  )
485
489
 
490
+ def check_if_dimensions_match(
491
+ self,
492
+ other: "AbstractImage",
493
+ allow_singleton: bool = False,
494
+ ) -> bool:
495
+ """Check if two images have matching spatial dimensions.
496
+
497
+ Args:
498
+ other: The other image to compare to.
499
+ allow_singleton: If True, allow singleton dimensions to be
500
+ compatible with non-singleton dimensions.
501
+
502
+ Returns:
503
+ bool: True if the images have matching dimensions, False otherwise.
504
+ """
505
+ return self.dimensions.check_if_dimensions_match(
506
+ other.dimensions, allow_singleton=allow_singleton
507
+ )
508
+
486
509
  def require_axes_match(
487
510
  self,
488
511
  other: "AbstractImage",
@@ -497,7 +520,22 @@ class AbstractImage(Generic[_image_handler]):
497
520
  """
498
521
  self.dimensions.require_axes_match(other.dimensions)
499
522
 
500
- def require_can_be_rescaled(
523
+ def check_if_axes_match(
524
+ self,
525
+ other: "AbstractImage",
526
+ ) -> bool:
527
+ """Check if two images have compatible axes.
528
+
529
+ Args:
530
+ other: The other image to compare to.
531
+
532
+ Returns:
533
+ bool: True if the images have compatible axes, False otherwise.
534
+
535
+ """
536
+ return self.dimensions.check_if_axes_match(other.dimensions)
537
+
538
+ def require_rescalable(
501
539
  self,
502
540
  other: "AbstractImage",
503
541
  ) -> None:
@@ -512,7 +550,24 @@ class AbstractImage(Generic[_image_handler]):
512
550
  Raises:
513
551
  NgioValueError: If the images cannot be scaled to each other.
514
552
  """
515
- self.dimensions.require_can_be_rescaled(other.dimensions)
553
+ self.dimensions.require_rescalable(other.dimensions)
554
+
555
+ def check_if_rescalable(
556
+ self,
557
+ other: "AbstractImage",
558
+ ) -> bool:
559
+ """Check if two images can be rescaled to each other.
560
+
561
+ For this to be true, the images must have the same axes, and
562
+ the pixel sizes must be compatible (i.e. one can be scaled to the other).
563
+
564
+ Args:
565
+ other: The other image to compare to.
566
+
567
+ Returns:
568
+ bool: True if the images can be rescaled to each other, False otherwise.
569
+ """
570
+ return self.dimensions.check_if_rescalable(other.dimensions)
516
571
 
517
572
 
518
573
  def consolidate_image(
ngio/images/_image.py CHANGED
@@ -641,6 +641,8 @@ class ImagesContainer:
641
641
  name=name,
642
642
  chunks=chunks,
643
643
  dtype=dtype,
644
+ dimension_separator=dimension_separator,
645
+ compressor=compressor,
644
646
  overwrite=overwrite,
645
647
  )
646
648
 
ngio/images/_label.py CHANGED
@@ -216,6 +216,8 @@ class LabelsContainer:
216
216
  axes_names=axes_names,
217
217
  chunks=chunks,
218
218
  dtype=dtype,
219
+ dimension_separator=dimension_separator,
220
+ compressor=compressor,
219
221
  overwrite=overwrite,
220
222
  )
221
223
 
@@ -217,24 +217,29 @@ class MaskedImage(Image):
217
217
  **slicing_kwargs: SlicingInputType,
218
218
  ) -> np.ndarray | da.Array:
219
219
  """Return the masked array for a given label."""
220
- slicing_kwargs = add_channel_selection_to_slicing_dict(
221
- image=self, channel_selection=channel_selection, slicing_dict=slicing_kwargs
222
- )
220
+ if mode == "numpy":
221
+ return self.get_roi_masked_as_numpy(
222
+ label=label,
223
+ channel_selection=channel_selection,
224
+ zoom_factor=zoom_factor,
225
+ axes_order=axes_order,
226
+ transforms=transforms,
227
+ allow_rescaling=allow_rescaling,
228
+ **slicing_kwargs,
229
+ )
223
230
 
224
- roi = self._masking_roi_table.get_label(label)
225
- roi = roi.zoom(zoom_factor)
226
- masked_getter = NumpyGetterMasked(
227
- roi=roi,
228
- zarr_array=self.zarr_array,
229
- label_zarr_array=self._label.zarr_array,
230
- dimensions=self.dimensions,
231
- label_dimensions=self._label.dimensions,
232
- axes_order=axes_order,
233
- transforms=transforms,
234
- slicing_dict=slicing_kwargs,
235
- allow_rescaling=allow_rescaling,
236
- )
237
- return masked_getter()
231
+ elif mode == "dask":
232
+ return self.get_roi_masked_as_dask(
233
+ label=label,
234
+ channel_selection=channel_selection,
235
+ zoom_factor=zoom_factor,
236
+ axes_order=axes_order,
237
+ transforms=transforms,
238
+ allow_rescaling=allow_rescaling,
239
+ **slicing_kwargs,
240
+ )
241
+ else:
242
+ raise ValueError(f"Unknown mode: {mode}")
238
243
 
239
244
  def set_roi_masked(
240
245
  self,
ngio/io_pipes/__init__.py CHANGED
@@ -1,4 +1,31 @@
1
- """I/O pipes for reading and writing data from zarr to numpy and dask arrays."""
1
+ """I/O pipes for reading and writing data from zarr to numpy and dask arrays.
2
+
3
+ There are 3 main types of I/O pipes:
4
+ - Standard I/O pipes: NumpyGetter, NumpySetter, DaskGetter, DaskSetter:
5
+ These pipes read and write data from simple integer indexing and slicing.
6
+ - ROI I/O pipes: NumpyRoiGetter, NumpyRoiSetter, DaskRoiGetter, DaskRoiSetter:
7
+ These pipes read and write data from a region of interest (ROI) defined in physical
8
+ coordinates.
9
+ - Masked I/O pipes: NumpyGetterMasked, NumpySetterMasked, DaskGetterMasked,
10
+ DaskSetterMasked: These pipes like the ROI pipes read and write data
11
+ from a region of interest (ROI). However they also load a boolean mask
12
+ from a label zarr array to mask the data being read or written.
13
+
14
+ All the io pipes are structured in the same way.
15
+
16
+ When reading data the order of operations is:
17
+ - Step 1: Slice the zarr array to load only the data needed into memory.
18
+ - Step 2: Apply axes operations to reorder, squeeze or expand the axes.
19
+ To match the user desired axes order.
20
+ - Step 3: Apply any additional transforms to the data.
21
+
22
+ When writing data the order of operations is the reverse.
23
+
24
+ The Transforms must implement the TransformProtocol.
25
+ They should be stateless and only depend on the input array and the slicing
26
+ and axes ops. This allows them to be easily reused between different I/O pipes.
27
+
28
+ """
2
29
 
3
30
  from ngio.io_pipes._io_pipes import (
4
31
  DaskGetter,
@@ -20,9 +47,8 @@ from ngio.io_pipes._io_pipes_roi import (
20
47
  NumpyRoiGetter,
21
48
  NumpyRoiSetter,
22
49
  )
23
- from ngio.io_pipes._io_pipes_utils import SlicingInputType
24
50
  from ngio.io_pipes._match_shape import dask_match_shape, numpy_match_shape
25
- from ngio.io_pipes._ops_slices import SlicingOps, SlicingType
51
+ from ngio.io_pipes._ops_slices import SlicingInputType, SlicingOps, SlicingType
26
52
  from ngio.io_pipes._ops_transforms import TransformProtocol
27
53
 
28
54
  __all__ = [
@@ -6,16 +6,20 @@ import numpy as np
6
6
  import zarr
7
7
  from dask.array import Array as DaskArray
8
8
 
9
- from ngio.common import Dimensions
10
- from ngio.io_pipes._io_pipes_utils import SlicingInputType, setup_io_pipe
9
+ from ngio.common._dimensions import Dimensions
10
+ from ngio.common._roi import Roi, RoiPixels
11
11
  from ngio.io_pipes._ops_axes import (
12
+ AxesOps,
13
+ build_axes_ops,
12
14
  get_as_dask_axes_ops,
13
15
  get_as_numpy_axes_ops,
14
16
  set_as_dask_axes_ops,
15
17
  set_as_numpy_axes_ops,
16
18
  )
17
19
  from ngio.io_pipes._ops_slices import (
20
+ SlicingInputType,
18
21
  SlicingOps,
22
+ build_slicing_ops,
19
23
  get_slice_as_dask,
20
24
  get_slice_as_numpy,
21
25
  set_slice_as_dask,
@@ -28,7 +32,29 @@ from ngio.io_pipes._ops_transforms import (
28
32
  set_as_dask_transform,
29
33
  set_as_numpy_transform,
30
34
  )
31
- from ngio.ome_zarr_meta.ngio_specs._axes import AxesOps
35
+
36
+
37
+ def setup_io_pipe(
38
+ *,
39
+ dimensions: Dimensions,
40
+ slicing_dict: dict[str, SlicingInputType] | None = None,
41
+ axes_order: Sequence[str] | None = None,
42
+ remove_channel_selection: bool = False,
43
+ ) -> tuple[SlicingOps, AxesOps]:
44
+ """Setup the slicing tuple and axes ops for an IO pipe."""
45
+ slicing_ops = build_slicing_ops(
46
+ dimensions=dimensions,
47
+ slicing_dict=slicing_dict,
48
+ remove_channel_selection=remove_channel_selection,
49
+ )
50
+
51
+ axes_ops = build_axes_ops(
52
+ dimensions=dimensions,
53
+ input_axes=slicing_ops.slice_axes,
54
+ axes_order=axes_order,
55
+ )
56
+ return slicing_ops, axes_ops
57
+
32
58
 
33
59
  ##############################################################
34
60
  #
@@ -36,21 +62,32 @@ from ngio.ome_zarr_meta.ngio_specs._axes import AxesOps
36
62
  #
37
63
  ##############################################################
38
64
 
39
- ArrayType = TypeVar("ArrayType", np.ndarray, DaskArray)
65
+ T = TypeVar("T")
40
66
 
41
67
 
42
- class DataGetter(ABC, Generic[ArrayType]):
68
+ class DataGetter(ABC, Generic[T]):
43
69
  def __init__(
44
70
  self,
45
71
  zarr_array: zarr.Array,
46
72
  slicing_ops: SlicingOps,
47
73
  axes_ops: AxesOps,
48
74
  transforms: Sequence[TransformProtocol] | None = None,
75
+ roi: Roi | RoiPixels | None = None,
49
76
  ) -> None:
50
77
  self._zarr_array = zarr_array
51
78
  self._slicing_ops = slicing_ops
52
79
  self._axes_ops = axes_ops
53
80
  self._transforms = transforms
81
+ self._roi = roi
82
+
83
+ def __repr__(self) -> str:
84
+ name = self.__class__.__name__
85
+ return (
86
+ f"{name}(zarr_array={self._zarr_array}, "
87
+ f"slicing_ops={self._slicing_ops}, "
88
+ f"axes_ops={self._axes_ops}, "
89
+ f"transforms={self._transforms})"
90
+ )
54
91
 
55
92
  @property
56
93
  def zarr_array(self) -> zarr.Array:
@@ -64,34 +101,48 @@ class DataGetter(ABC, Generic[ArrayType]):
64
101
  def axes_ops(self) -> AxesOps:
65
102
  return self._axes_ops
66
103
 
67
- @property
68
- def in_memory_axes(self) -> tuple[str, ...]:
69
- return self._axes_ops.in_memory_axes
70
-
71
104
  @property
72
105
  def transforms(self) -> Sequence[TransformProtocol] | None:
73
106
  return self._transforms
74
107
 
75
- def __call__(self) -> ArrayType:
108
+ @property
109
+ def roi(self) -> Roi | RoiPixels:
110
+ if self._roi is None:
111
+ name = self.__class__.__name__
112
+ raise ValueError(f"No ROI defined for {name}.")
113
+ return self._roi
114
+
115
+ def __call__(self) -> T:
76
116
  return self.get()
77
117
 
78
118
  @abstractmethod
79
- def get(self) -> ArrayType:
119
+ def get(self) -> T:
80
120
  pass
81
121
 
82
122
 
83
- class DataSetter(ABC, Generic[ArrayType]):
123
+ class DataSetter(ABC, Generic[T]):
84
124
  def __init__(
85
125
  self,
86
126
  zarr_array: zarr.Array,
87
127
  slicing_ops: SlicingOps,
88
128
  axes_ops: AxesOps,
89
129
  transforms: Sequence[TransformProtocol] | None = None,
130
+ roi: Roi | RoiPixels | None = None,
90
131
  ) -> None:
91
132
  self._zarr_array = zarr_array
92
133
  self._slicing_ops = slicing_ops
93
134
  self._axes_ops = axes_ops
94
135
  self._transforms = transforms
136
+ self._roi = roi
137
+
138
+ def __repr__(self) -> str:
139
+ name = self.__class__.__name__
140
+ return (
141
+ f"{name}(zarr_array={self._zarr_array}, "
142
+ f"slicing_ops={self._slicing_ops}, "
143
+ f"axes_ops={self._axes_ops}, "
144
+ f"transforms={self._transforms})"
145
+ )
95
146
 
96
147
  @property
97
148
  def zarr_array(self) -> zarr.Array:
@@ -105,31 +156,36 @@ class DataSetter(ABC, Generic[ArrayType]):
105
156
  def axes_ops(self) -> AxesOps:
106
157
  return self._axes_ops
107
158
 
108
- @property
109
- def in_memory_axes(self) -> tuple[str, ...]:
110
- return self._axes_ops.in_memory_axes
111
-
112
159
  @property
113
160
  def transforms(self) -> Sequence[TransformProtocol] | None:
114
161
  return self._transforms
115
162
 
116
- def __call__(self, patch: ArrayType) -> None:
163
+ @property
164
+ def roi(self) -> Roi | RoiPixels:
165
+ if self._roi is None:
166
+ name = self.__class__.__name__
167
+ raise ValueError(f"No ROI defined for {name}.")
168
+ return self._roi
169
+
170
+ def __call__(self, patch: T) -> None:
117
171
  return self.set(patch)
118
172
 
119
173
  @abstractmethod
120
- def set(self, patch: ArrayType) -> None:
174
+ def set(self, patch: T) -> None:
121
175
  pass
122
176
 
123
177
 
124
178
  class NumpyGetter(DataGetter[np.ndarray]):
125
179
  def __init__(
126
180
  self,
181
+ *,
127
182
  zarr_array: zarr.Array,
128
183
  dimensions: Dimensions,
129
184
  axes_order: Sequence[str] | None = None,
130
185
  transforms: Sequence[TransformProtocol] | None = None,
131
186
  slicing_dict: dict[str, SlicingInputType] | None = None,
132
187
  remove_channel_selection: bool = False,
188
+ roi: Roi | RoiPixels | None = None,
133
189
  ) -> None:
134
190
  """Build a pipe to get a numpy or dask array from a zarr array."""
135
191
  slicing_ops, axes_ops = setup_io_pipe(
@@ -143,9 +199,11 @@ class NumpyGetter(DataGetter[np.ndarray]):
143
199
  slicing_ops=slicing_ops,
144
200
  axes_ops=axes_ops,
145
201
  transforms=transforms,
202
+ roi=roi,
146
203
  )
147
204
 
148
205
  def get(self) -> np.ndarray:
206
+ """Get a numpy array from the zarr array with ops."""
149
207
  array = get_slice_as_numpy(self._zarr_array, slicing_ops=self._slicing_ops)
150
208
  array = get_as_numpy_axes_ops(array, axes_ops=self._axes_ops)
151
209
  array = get_as_numpy_transform(
@@ -160,12 +218,14 @@ class NumpyGetter(DataGetter[np.ndarray]):
160
218
  class DaskGetter(DataGetter[DaskArray]):
161
219
  def __init__(
162
220
  self,
221
+ *,
163
222
  zarr_array: zarr.Array,
164
223
  dimensions: Dimensions,
165
224
  axes_order: Sequence[str] | None = None,
166
225
  transforms: Sequence[TransformProtocol] | None = None,
167
226
  slicing_dict: dict[str, SlicingInputType] | None = None,
168
227
  remove_channel_selection: bool = False,
228
+ roi: Roi | RoiPixels | None = None,
169
229
  ) -> None:
170
230
  """Build a pipe to get a numpy or dask array from a zarr array."""
171
231
  slicing_ops, axes_ops = setup_io_pipe(
@@ -179,9 +239,18 @@ class DaskGetter(DataGetter[DaskArray]):
179
239
  slicing_ops=slicing_ops,
180
240
  axes_ops=axes_ops,
181
241
  transforms=transforms,
242
+ roi=roi,
182
243
  )
183
244
 
184
245
  def get(self) -> DaskArray:
246
+ """Get a dask array from the zarr array with ops.
247
+
248
+ The order of operations is:
249
+ * get slice will load the data from the zarr array
250
+ * get axes ops will reorder, squeeze or expand the axes
251
+ * get transform will apply any additional transforms
252
+
253
+ """
185
254
  array = get_slice_as_dask(self._zarr_array, slicing_ops=self._slicing_ops)
186
255
  array = get_as_dask_axes_ops(array, axes_ops=self._axes_ops)
187
256
  array = get_as_dask_transform(
@@ -203,12 +272,14 @@ class DaskGetter(DataGetter[DaskArray]):
203
272
  class NumpySetter(DataSetter[np.ndarray]):
204
273
  def __init__(
205
274
  self,
275
+ *,
206
276
  zarr_array: zarr.Array,
207
277
  dimensions: Dimensions,
208
278
  axes_order: Sequence[str] | None = None,
209
279
  transforms: Sequence[TransformProtocol] | None = None,
210
280
  slicing_dict: dict[str, SlicingInputType] | None = None,
211
281
  remove_channel_selection: bool = False,
282
+ roi: Roi | RoiPixels | None = None,
212
283
  ) -> None:
213
284
  """Build a pipe to get a numpy or dask array from a zarr array."""
214
285
  slicing_ops, axes_ops = setup_io_pipe(
@@ -222,6 +293,7 @@ class NumpySetter(DataSetter[np.ndarray]):
222
293
  slicing_ops=slicing_ops,
223
294
  axes_ops=axes_ops,
224
295
  transforms=transforms,
296
+ roi=roi,
225
297
  )
226
298
 
227
299
  def set(self, patch: np.ndarray) -> None:
@@ -246,12 +318,14 @@ class NumpySetter(DataSetter[np.ndarray]):
246
318
  class DaskSetter(DataSetter[DaskArray]):
247
319
  def __init__(
248
320
  self,
321
+ *,
249
322
  zarr_array: zarr.Array,
250
323
  dimensions: Dimensions,
251
324
  axes_order: Sequence[str] | None = None,
252
325
  transforms: Sequence[TransformProtocol] | None = None,
253
326
  slicing_dict: dict[str, SlicingInputType] | None = None,
254
327
  remove_channel_selection: bool = False,
328
+ roi: Roi | RoiPixels | None = None,
255
329
  ) -> None:
256
330
  """Build a pipe to get a numpy or dask array from a zarr array."""
257
331
  slicing_ops, axes_ops = setup_io_pipe(
@@ -265,6 +339,7 @@ class DaskSetter(DataSetter[DaskArray]):
265
339
  slicing_ops=slicing_ops,
266
340
  axes_ops=axes_ops,
267
341
  transforms=transforms,
342
+ roi=roi,
268
343
  )
269
344
 
270
345
  def set(self, patch: DaskArray) -> None: