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.
- 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 +5 -5
- 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.0b1.dist-info}/METADATA +1 -1
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/RECORD +30 -28
- ngio/io_pipes/_io_pipes_utils.py +0 -299
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
@@ -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) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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."""
|
ngio/images/_abstract_image.py
CHANGED
|
@@ -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(
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
ngio/images/_label.py
CHANGED
ngio/images/_masked_image.py
CHANGED
|
@@ -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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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__ = [
|
ngio/io_pipes/_io_pipes.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
T = TypeVar("T")
|
|
40
66
|
|
|
41
67
|
|
|
42
|
-
class DataGetter(ABC, Generic[
|
|
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
|
-
|
|
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) ->
|
|
119
|
+
def get(self) -> T:
|
|
80
120
|
pass
|
|
81
121
|
|
|
82
122
|
|
|
83
|
-
class DataSetter(ABC, Generic[
|
|
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
|
-
|
|
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:
|
|
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:
|