ngio 0.3.4__py3-none-any.whl → 0.4.0__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/__init__.py +7 -2
- ngio/common/__init__.py +5 -52
- ngio/common/_dimensions.py +270 -55
- ngio/common/_masking_roi.py +38 -10
- ngio/common/_pyramid.py +51 -30
- ngio/common/_roi.py +269 -82
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +49 -19
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +127 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/_plate.py +41 -36
- ngio/images/__init__.py +22 -1
- ngio/images/_abstract_image.py +403 -176
- ngio/images/_create.py +31 -15
- ngio/images/_create_synt_container.py +138 -0
- ngio/images/_image.py +452 -63
- ngio/images/_label.py +56 -30
- ngio/images/_masked_image.py +387 -129
- ngio/images/_ome_zarr_container.py +237 -67
- ngio/{common → images}/_table_ops.py +41 -41
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +152 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +376 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +446 -0
- ngio/io_pipes/_ops_slices_utils.py +196 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +175 -0
- ngio/ome_zarr_meta/__init__.py +4 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -10
- ngio/ome_zarr_meta/ngio_specs/_axes.py +186 -175
- ngio/ome_zarr_meta/ngio_specs/_channels.py +55 -18
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +48 -122
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +6 -15
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +38 -87
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +34 -31
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/backends/_abstract_backend.py +5 -6
- ngio/tables/backends/_anndata.py +1 -2
- ngio/tables/backends/_anndata_utils.py +3 -3
- ngio/tables/backends/_non_zarr_backends.py +1 -1
- ngio/tables/backends/_table_backends.py +0 -1
- ngio/tables/backends/_utils.py +3 -3
- ngio/tables/v1/_roi_table.py +165 -70
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +2 -3
- ngio/utils/_datasets.py +5 -0
- ngio/utils/_logger.py +19 -0
- ngio/utils/_zarr_utils.py +6 -6
- {ngio-0.3.4.dist-info → ngio-0.4.0.dist-info}/METADATA +24 -22
- ngio-0.4.0.dist-info/RECORD +85 -0
- ngio/common/_array_pipe.py +0 -288
- ngio/common/_axes_transforms.py +0 -64
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -96
- ngio-0.3.4.dist-info/RECORD +0 -61
- {ngio-0.3.4.dist-info → ngio-0.4.0.dist-info}/WHEEL +0 -0
- {ngio-0.3.4.dist-info → ngio-0.4.0.dist-info}/licenses/LICENSE +0 -0
ngio/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ except PackageNotFoundError: # pragma: no cover
|
|
|
9
9
|
__author__ = "Lorenzo Cerrone"
|
|
10
10
|
__email__ = "lorenzo.cerrone@uzh.ch"
|
|
11
11
|
|
|
12
|
-
from ngio.common import
|
|
12
|
+
from ngio.common import Dimensions, Roi, RoiPixels
|
|
13
13
|
from ngio.hcs import (
|
|
14
14
|
OmeZarrPlate,
|
|
15
15
|
OmeZarrWell,
|
|
@@ -19,12 +19,15 @@ from ngio.hcs import (
|
|
|
19
19
|
open_ome_zarr_well,
|
|
20
20
|
)
|
|
21
21
|
from ngio.images import (
|
|
22
|
+
ChannelSelectionModel,
|
|
22
23
|
Image,
|
|
23
24
|
Label,
|
|
24
25
|
OmeZarrContainer,
|
|
25
26
|
create_empty_ome_zarr,
|
|
26
27
|
create_ome_zarr_from_array,
|
|
28
|
+
create_synthetic_ome_zarr,
|
|
27
29
|
open_image,
|
|
30
|
+
open_label,
|
|
28
31
|
open_ome_zarr_container,
|
|
29
32
|
)
|
|
30
33
|
from ngio.ome_zarr_meta.ngio_specs import (
|
|
@@ -36,8 +39,8 @@ from ngio.ome_zarr_meta.ngio_specs import (
|
|
|
36
39
|
)
|
|
37
40
|
|
|
38
41
|
__all__ = [
|
|
39
|
-
"ArrayLike",
|
|
40
42
|
"AxesSetup",
|
|
43
|
+
"ChannelSelectionModel",
|
|
41
44
|
"DefaultNgffVersion",
|
|
42
45
|
"Dimensions",
|
|
43
46
|
"Image",
|
|
@@ -54,7 +57,9 @@ __all__ = [
|
|
|
54
57
|
"create_empty_plate",
|
|
55
58
|
"create_empty_well",
|
|
56
59
|
"create_ome_zarr_from_array",
|
|
60
|
+
"create_synthetic_ome_zarr",
|
|
57
61
|
"open_image",
|
|
62
|
+
"open_label",
|
|
58
63
|
"open_ome_zarr_container",
|
|
59
64
|
"open_ome_zarr_plate",
|
|
60
65
|
"open_ome_zarr_well",
|
ngio/common/__init__.py
CHANGED
|
@@ -1,70 +1,23 @@
|
|
|
1
1
|
"""Common classes and functions that are used across the package."""
|
|
2
2
|
|
|
3
|
-
from ngio.common._array_pipe import (
|
|
4
|
-
get_masked_pipe,
|
|
5
|
-
get_pipe,
|
|
6
|
-
set_masked_pipe,
|
|
7
|
-
set_pipe,
|
|
8
|
-
)
|
|
9
|
-
from ngio.common._axes_transforms import (
|
|
10
|
-
transform_dask_array,
|
|
11
|
-
transform_list,
|
|
12
|
-
transform_numpy_array,
|
|
13
|
-
)
|
|
14
|
-
from ngio.common._common_types import ArrayLike
|
|
15
3
|
from ngio.common._dimensions import Dimensions
|
|
16
4
|
from ngio.common._masking_roi import compute_masking_roi
|
|
17
5
|
from ngio.common._pyramid import consolidate_pyramid, init_empty_pyramid, on_disk_zoom
|
|
18
|
-
from ngio.common._roi import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
compute_and_slices,
|
|
22
|
-
dask_get_slice,
|
|
23
|
-
dask_set_slice,
|
|
24
|
-
numpy_get_slice,
|
|
25
|
-
numpy_set_slice,
|
|
26
|
-
)
|
|
27
|
-
from ngio.common._table_ops import (
|
|
28
|
-
concatenate_image_tables,
|
|
29
|
-
concatenate_image_tables_as,
|
|
30
|
-
concatenate_image_tables_as_async,
|
|
31
|
-
concatenate_image_tables_async,
|
|
32
|
-
conctatenate_tables,
|
|
33
|
-
list_image_tables,
|
|
34
|
-
list_image_tables_async,
|
|
6
|
+
from ngio.common._roi import (
|
|
7
|
+
Roi,
|
|
8
|
+
RoiPixels,
|
|
35
9
|
)
|
|
36
|
-
from ngio.common._zoom import dask_zoom, numpy_zoom
|
|
10
|
+
from ngio.common._zoom import InterpolationOrder, dask_zoom, numpy_zoom
|
|
37
11
|
|
|
38
12
|
__all__ = [
|
|
39
|
-
"ArrayLike",
|
|
40
13
|
"Dimensions",
|
|
14
|
+
"InterpolationOrder",
|
|
41
15
|
"Roi",
|
|
42
16
|
"RoiPixels",
|
|
43
|
-
"SliceTransform",
|
|
44
|
-
"compute_and_slices",
|
|
45
17
|
"compute_masking_roi",
|
|
46
|
-
"concatenate_image_tables",
|
|
47
|
-
"concatenate_image_tables_as",
|
|
48
|
-
"concatenate_image_tables_as_async",
|
|
49
|
-
"concatenate_image_tables_async",
|
|
50
|
-
"conctatenate_tables",
|
|
51
18
|
"consolidate_pyramid",
|
|
52
|
-
"dask_get_slice",
|
|
53
|
-
"dask_set_slice",
|
|
54
19
|
"dask_zoom",
|
|
55
|
-
"get_masked_pipe",
|
|
56
|
-
"get_pipe",
|
|
57
20
|
"init_empty_pyramid",
|
|
58
|
-
"list_image_tables",
|
|
59
|
-
"list_image_tables_async",
|
|
60
|
-
"numpy_get_slice",
|
|
61
|
-
"numpy_set_slice",
|
|
62
21
|
"numpy_zoom",
|
|
63
22
|
"on_disk_zoom",
|
|
64
|
-
"roi_to_slice_kwargs",
|
|
65
|
-
"set_masked_pipe",
|
|
66
|
-
"set_pipe",
|
|
67
|
-
"transform_dask_array",
|
|
68
|
-
"transform_list",
|
|
69
|
-
"transform_numpy_array",
|
|
70
23
|
]
|
ngio/common/_dimensions.py
CHANGED
|
@@ -4,117 +4,332 @@ This is not related to the NGFF metadata,
|
|
|
4
4
|
but it is based on the actual metadata of the image data.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import math
|
|
8
|
+
from typing import overload
|
|
8
9
|
|
|
9
|
-
from ngio.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
from ngio.ome_zarr_meta import (
|
|
11
|
+
AxesHandler,
|
|
12
|
+
)
|
|
13
|
+
from ngio.ome_zarr_meta.ngio_specs._dataset import Dataset
|
|
14
|
+
from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
15
|
+
from ngio.utils import NgioValueError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _are_compatible(shape1: int, shape2: int, scaling: float) -> bool:
|
|
19
|
+
"""Check if shape2 is consistent with shape1 given pixel sizes.
|
|
20
|
+
|
|
21
|
+
Since we only deal with shape discrepancies due to rounding, we
|
|
22
|
+
shape1, needs to be larger than shape2.
|
|
23
|
+
"""
|
|
24
|
+
if shape1 < shape2:
|
|
25
|
+
return _are_compatible(shape2, shape1, 1 / scaling)
|
|
26
|
+
expected_shape2 = shape1 * scaling
|
|
27
|
+
expected_shape2_floor = math.floor(expected_shape2)
|
|
28
|
+
expected_shape2_ceil = math.ceil(expected_shape2)
|
|
29
|
+
return shape2 in {expected_shape2_floor, expected_shape2_ceil}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def require_axes_match(reference: "Dimensions", other: "Dimensions") -> None:
|
|
33
|
+
"""Check if two Dimensions objects have the same axes.
|
|
34
|
+
|
|
35
|
+
Besides the channel axis (which is a special case), all axes must be
|
|
36
|
+
present in both Dimensions objects.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
40
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
NgioValueError: If the axes do not match.
|
|
44
|
+
"""
|
|
45
|
+
for s_axis in reference.axes_handler.axes:
|
|
46
|
+
if s_axis.axis_type == "channel":
|
|
47
|
+
continue
|
|
48
|
+
o_axis = other.axes_handler.get_axis(s_axis.name)
|
|
49
|
+
if o_axis is None:
|
|
50
|
+
raise NgioValueError(
|
|
51
|
+
f"Axes do not match. The axis {s_axis.name} "
|
|
52
|
+
f"is not present in either dimensions."
|
|
53
|
+
)
|
|
54
|
+
# Check for axes present in the other dimensions but not in this one
|
|
55
|
+
for o_axis in other.axes_handler.axes:
|
|
56
|
+
if o_axis.axis_type == "channel":
|
|
57
|
+
continue
|
|
58
|
+
s_axis = reference.axes_handler.get_axis(o_axis.name)
|
|
59
|
+
if s_axis is None:
|
|
60
|
+
raise NgioValueError(
|
|
61
|
+
f"Axes do not match. The axis {o_axis.name} "
|
|
62
|
+
f"is not present in either dimensions."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_if_axes_match(reference: "Dimensions", other: "Dimensions") -> bool:
|
|
67
|
+
"""Check if two Dimensions objects have the same axes.
|
|
68
|
+
|
|
69
|
+
Besides the channel axis (which is a special case), all axes must be
|
|
70
|
+
present in both Dimensions objects.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
74
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: True if the axes match, False otherwise.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
require_axes_match(reference, other)
|
|
81
|
+
return True
|
|
82
|
+
except NgioValueError:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def require_dimensions_match(
|
|
87
|
+
reference: "Dimensions", other: "Dimensions", allow_singleton: bool = False
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Check if two Dimensions objects have the same axes and dimensions.
|
|
90
|
+
|
|
91
|
+
Besides the channel axis, all axes must have the same dimension in
|
|
92
|
+
both images.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
96
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
97
|
+
allow_singleton (bool): Whether to allow singleton dimensions to be
|
|
98
|
+
different. For example, if the input image has shape
|
|
99
|
+
(5, 100, 100) and the label has shape (1, 100, 100).
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
NgioValueError: If the dimensions do not match.
|
|
103
|
+
"""
|
|
104
|
+
require_axes_match(reference, other)
|
|
105
|
+
for r_axis in reference.axes_handler.axes:
|
|
106
|
+
if r_axis.axis_type == "channel":
|
|
107
|
+
continue
|
|
108
|
+
o_axis = other.axes_handler.get_axis(r_axis.name)
|
|
109
|
+
assert o_axis is not None # already checked in assert_axes_match
|
|
110
|
+
|
|
111
|
+
r_dim = reference.get(r_axis.name, default=1)
|
|
112
|
+
o_dim = other.get(o_axis.name, default=1)
|
|
113
|
+
|
|
114
|
+
if r_dim != o_dim:
|
|
115
|
+
if allow_singleton and (r_dim == 1 or o_dim == 1):
|
|
116
|
+
continue
|
|
117
|
+
raise NgioValueError(
|
|
118
|
+
f"Dimensions do not match for axis "
|
|
119
|
+
f"{r_axis.name}. Got {r_dim} and {o_dim}."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def check_if_dimensions_match(
|
|
124
|
+
reference: "Dimensions", other: "Dimensions", allow_singleton: bool = False
|
|
125
|
+
) -> bool:
|
|
126
|
+
"""Check if two Dimensions objects have the same axes and dimensions.
|
|
127
|
+
|
|
128
|
+
Besides the channel axis, all axes must have the same dimension in
|
|
129
|
+
both images.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
133
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
134
|
+
allow_singleton (bool): Whether to allow singleton dimensions to be
|
|
135
|
+
different. For example, if the input image has shape
|
|
136
|
+
(5, 100, 100) and the label has shape (1, 100, 100).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
bool: True if the dimensions match, False otherwise.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
require_dimensions_match(reference, other, allow_singleton)
|
|
143
|
+
return True
|
|
144
|
+
except NgioValueError:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def require_rescalable(reference: "Dimensions", other: "Dimensions") -> None:
|
|
149
|
+
"""Assert that two images can be rescaled.
|
|
150
|
+
|
|
151
|
+
For this to be true, the images must have the same axes, and
|
|
152
|
+
the pixel sizes must be compatible (i.e. one can be scaled to the other).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
156
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
require_axes_match(reference, other)
|
|
160
|
+
for ax_r in reference.axes_handler.axes:
|
|
161
|
+
if ax_r.axis_type == "channel":
|
|
162
|
+
continue
|
|
163
|
+
ax_o = other.axes_handler.get_axis(ax_r.name)
|
|
164
|
+
assert ax_o is not None, "Axes do not match."
|
|
165
|
+
px_r = reference.pixel_size.get(ax_r.name, default=1.0)
|
|
166
|
+
px_o = other.pixel_size.get(ax_o.name, default=1.0)
|
|
167
|
+
shape_r = reference.get(ax_r.name, default=1)
|
|
168
|
+
shape_o = other.get(ax_o.name, default=1)
|
|
169
|
+
scale = px_r / px_o
|
|
170
|
+
if not _are_compatible(
|
|
171
|
+
shape1=shape_r,
|
|
172
|
+
shape2=shape_o,
|
|
173
|
+
scaling=scale,
|
|
174
|
+
):
|
|
175
|
+
raise NgioValueError(
|
|
176
|
+
f"Reference image with shape {reference.shape}, "
|
|
177
|
+
f"and pixel size {reference.pixel_size}, "
|
|
178
|
+
f"cannot be rescaled to "
|
|
179
|
+
f"image with shape {other.shape} "
|
|
180
|
+
f"and pixel size {other.pixel_size}. "
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def check_if_rescalable(reference: "Dimensions", other: "Dimensions") -> bool:
|
|
185
|
+
"""Check if two images can be rescaled.
|
|
186
|
+
|
|
187
|
+
For this to be true, the images must have the same axes, and
|
|
188
|
+
the pixel sizes must be compatible (i.e. one can be scaled to the other).
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
reference (Dimensions): The reference dimensions object to compare against.
|
|
192
|
+
other (Dimensions): The other dimensions object to compare against.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
bool: True if the images can be rescaled, False otherwise.
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
require_rescalable(reference, other)
|
|
199
|
+
return True
|
|
200
|
+
except NgioValueError:
|
|
201
|
+
return False
|
|
12
202
|
|
|
13
203
|
|
|
14
204
|
class Dimensions:
|
|
15
|
-
"""Dimension metadata.
|
|
205
|
+
"""Dimension metadata Handling Class.
|
|
206
|
+
|
|
207
|
+
This class is used to handle and manipulate dimension metadata.
|
|
208
|
+
It provides methods to access and validate dimension information,
|
|
209
|
+
such as shape, axes, and properties like is_2d, is_3d, is_time_series, etc.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
require_axes_match = require_axes_match
|
|
213
|
+
check_if_axes_match = check_if_axes_match
|
|
214
|
+
require_dimensions_match = require_dimensions_match
|
|
215
|
+
check_if_dimensions_match = check_if_dimensions_match
|
|
216
|
+
require_rescalable = require_rescalable
|
|
217
|
+
check_if_rescalable = check_if_rescalable
|
|
16
218
|
|
|
17
219
|
def __init__(
|
|
18
220
|
self,
|
|
19
221
|
shape: tuple[int, ...],
|
|
20
|
-
|
|
222
|
+
chunks: tuple[int, ...],
|
|
223
|
+
dataset: Dataset,
|
|
21
224
|
) -> None:
|
|
22
225
|
"""Create a Dimension object from a Zarr array.
|
|
23
226
|
|
|
24
227
|
Args:
|
|
25
228
|
shape: The shape of the Zarr array.
|
|
26
|
-
|
|
229
|
+
chunks: The chunks of the Zarr array.
|
|
230
|
+
dataset: The dataset object.
|
|
27
231
|
"""
|
|
28
232
|
self._shape = shape
|
|
29
|
-
self.
|
|
233
|
+
self._chunks = chunks
|
|
234
|
+
self._axes_handler = dataset.axes_handler
|
|
235
|
+
self._pixel_size = dataset.pixel_size
|
|
30
236
|
|
|
31
|
-
if len(self._shape) != len(self.
|
|
32
|
-
raise
|
|
237
|
+
if len(self._shape) != len(self._axes_handler.axes):
|
|
238
|
+
raise NgioValueError(
|
|
33
239
|
"The number of dimensions must match the number of axes. "
|
|
34
|
-
f"Expected Axis {self.
|
|
240
|
+
f"Expected Axis {self._axes_handler.axes_names} but got shape "
|
|
35
241
|
f"{self._shape}."
|
|
36
242
|
)
|
|
37
243
|
|
|
38
244
|
def __str__(self) -> str:
|
|
39
245
|
"""Return the string representation of the object."""
|
|
40
246
|
dims = ", ".join(
|
|
41
|
-
f"{ax.
|
|
42
|
-
for ax, s in zip(self.
|
|
247
|
+
f"{ax.name}: {s}"
|
|
248
|
+
for ax, s in zip(self._axes_handler.axes, self._shape, strict=True)
|
|
43
249
|
)
|
|
44
250
|
return f"Dimensions({dims})"
|
|
45
251
|
|
|
46
|
-
def get(self, axis_name: str, strict: bool = True) -> int:
|
|
47
|
-
"""Return the dimension of the given axis name.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
axis_name: The name of the axis (either canonical or non-canonical).
|
|
51
|
-
strict: If True, raise an error if the axis does not exist.
|
|
52
|
-
"""
|
|
53
|
-
index = self._axes_mapper.get_index(axis_name)
|
|
54
|
-
if index is None and strict:
|
|
55
|
-
raise NgioValueError(f"Axis {axis_name} does not exist.")
|
|
56
|
-
elif index is None:
|
|
57
|
-
return 1
|
|
58
|
-
return self._shape[index]
|
|
59
|
-
|
|
60
|
-
def has_axis(self, axis_name: str) -> bool:
|
|
61
|
-
"""Return whether the axis exists."""
|
|
62
|
-
index = self._axes_mapper.get_axis(axis_name)
|
|
63
|
-
if index is None:
|
|
64
|
-
return False
|
|
65
|
-
return True
|
|
66
|
-
|
|
67
|
-
def get_shape(self, axes_order: Collection[str]) -> tuple[int, ...]:
|
|
68
|
-
"""Return the shape in the given axes order."""
|
|
69
|
-
transforms = self._axes_mapper.to_order(axes_order)
|
|
70
|
-
return tuple(transform_list(list(self._shape), 1, transforms))
|
|
71
|
-
|
|
72
|
-
def get_canonical_shape(self) -> tuple[int, ...]:
|
|
73
|
-
"""Return the shape in the canonical order."""
|
|
74
|
-
transforms = self._axes_mapper.to_canonical()
|
|
75
|
-
return tuple(transform_list(list(self._shape), 1, transforms))
|
|
76
|
-
|
|
77
252
|
def __repr__(self) -> str:
|
|
78
253
|
"""Return the string representation of the object."""
|
|
79
254
|
return str(self)
|
|
80
255
|
|
|
81
256
|
@property
|
|
82
|
-
def
|
|
257
|
+
def axes_handler(self) -> AxesHandler:
|
|
258
|
+
"""Return the axes handler object."""
|
|
259
|
+
return self._axes_handler
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def pixel_size(self) -> PixelSize:
|
|
263
|
+
"""Return the pixel size object."""
|
|
264
|
+
return self._pixel_size
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def shape(self) -> tuple[int, ...]:
|
|
83
268
|
"""Return the shape as a tuple."""
|
|
84
|
-
return
|
|
269
|
+
return self._shape
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def chunks(self) -> tuple[int, ...]:
|
|
273
|
+
"""Return the chunks as a tuple."""
|
|
274
|
+
return self._chunks
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def axes(self) -> tuple[str, ...]:
|
|
278
|
+
"""Return the axes as a tuple of strings."""
|
|
279
|
+
return self.axes_handler.axes_names
|
|
85
280
|
|
|
86
281
|
@property
|
|
87
282
|
def is_time_series(self) -> bool:
|
|
88
|
-
"""Return whether the
|
|
89
|
-
if self.get("t",
|
|
283
|
+
"""Return whether the image is a time series."""
|
|
284
|
+
if self.get("t", default=1) == 1:
|
|
90
285
|
return False
|
|
91
286
|
return True
|
|
92
287
|
|
|
93
288
|
@property
|
|
94
289
|
def is_2d(self) -> bool:
|
|
95
|
-
"""Return whether the
|
|
96
|
-
if self.get("z",
|
|
290
|
+
"""Return whether the image is 2D."""
|
|
291
|
+
if self.get("z", default=1) != 1:
|
|
97
292
|
return False
|
|
98
293
|
return True
|
|
99
294
|
|
|
100
295
|
@property
|
|
101
296
|
def is_2d_time_series(self) -> bool:
|
|
102
|
-
"""Return whether the
|
|
297
|
+
"""Return whether the image is a 2D time series."""
|
|
103
298
|
return self.is_2d and self.is_time_series
|
|
104
299
|
|
|
105
300
|
@property
|
|
106
301
|
def is_3d(self) -> bool:
|
|
107
|
-
"""Return whether the
|
|
302
|
+
"""Return whether the image is 3D."""
|
|
108
303
|
return not self.is_2d
|
|
109
304
|
|
|
110
305
|
@property
|
|
111
306
|
def is_3d_time_series(self) -> bool:
|
|
112
|
-
"""Return whether the
|
|
307
|
+
"""Return whether the image is a 3D time series."""
|
|
113
308
|
return self.is_3d and self.is_time_series
|
|
114
309
|
|
|
115
310
|
@property
|
|
116
311
|
def is_multi_channels(self) -> bool:
|
|
117
|
-
"""Return whether the
|
|
118
|
-
if self.get("c",
|
|
312
|
+
"""Return whether the image has multiple channels."""
|
|
313
|
+
if self.get("c", default=1) == 1:
|
|
119
314
|
return False
|
|
120
315
|
return True
|
|
316
|
+
|
|
317
|
+
@overload
|
|
318
|
+
def get(self, axis_name: str, default: None = None) -> int | None:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
@overload
|
|
322
|
+
def get(self, axis_name: str, default: int) -> int:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
def get(self, axis_name: str, default: int | None = None) -> int | None:
|
|
326
|
+
"""Return the dimension/shape of the given axis name.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
axis_name: The name of the axis (either canonical or non-canonical).
|
|
330
|
+
default: The default value to return if the axis does not exist.
|
|
331
|
+
"""
|
|
332
|
+
index = self.axes_handler.get_index(axis_name)
|
|
333
|
+
if index is None:
|
|
334
|
+
return default
|
|
335
|
+
return self._shape[index]
|
ngio/common/_masking_roi.py
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
|
|
5
|
-
import dask
|
|
6
5
|
import dask.array as da
|
|
7
|
-
import dask.delayed
|
|
8
6
|
import numpy as np
|
|
9
7
|
import scipy.ndimage as ndi
|
|
8
|
+
from dask.delayed import delayed
|
|
10
9
|
|
|
11
10
|
from ngio.common._roi import Roi, RoiPixels
|
|
12
11
|
from ngio.ome_zarr_meta import PixelSize
|
|
@@ -39,7 +38,7 @@ def _adjust_slices(slices, offset):
|
|
|
39
38
|
return adjusted_slices
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
@
|
|
41
|
+
@delayed
|
|
43
42
|
def _process_chunk(chunk, offset):
|
|
44
43
|
"""Process a single chunk.
|
|
45
44
|
|
|
@@ -63,7 +62,7 @@ def _merge_slices(
|
|
|
63
62
|
return tuple(merged)
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
@
|
|
65
|
+
@delayed
|
|
67
66
|
def _collect_slices(
|
|
68
67
|
local_slices: list[dict[int, tuple[slice, ...]]],
|
|
69
68
|
) -> dict[int, tuple[slice]]:
|
|
@@ -101,7 +100,7 @@ def compute_slices(segmentation: np.ndarray) -> dict[int, tuple[slice, ...]]:
|
|
|
101
100
|
def lazy_compute_slices(segmentation: da.Array) -> dict[int, tuple[slice, ...]]:
|
|
102
101
|
"""Compute slices for each label in a segmentation."""
|
|
103
102
|
global_offsets = _compute_offsets(segmentation.chunks)
|
|
104
|
-
delayed_chunks = segmentation.to_delayed()
|
|
103
|
+
delayed_chunks = segmentation.to_delayed() # type: ignore
|
|
105
104
|
|
|
106
105
|
grid_shape = tuple(len(c) for c in segmentation.chunks)
|
|
107
106
|
|
|
@@ -125,8 +124,8 @@ def compute_masking_roi(
|
|
|
125
124
|
Other axes orders are not supported.
|
|
126
125
|
|
|
127
126
|
"""
|
|
128
|
-
if segmentation.ndim not in [2, 3]:
|
|
129
|
-
raise NgioValueError("Only 2D and
|
|
127
|
+
if segmentation.ndim not in [2, 3, 4]:
|
|
128
|
+
raise NgioValueError("Only 2D, 3D, and 4D segmentations are supported.")
|
|
130
129
|
|
|
131
130
|
if isinstance(segmentation, da.Array):
|
|
132
131
|
slices = lazy_compute_slices(segmentation)
|
|
@@ -136,21 +135,50 @@ def compute_masking_roi(
|
|
|
136
135
|
rois = []
|
|
137
136
|
for label, slice_ in slices.items():
|
|
138
137
|
if len(slice_) == 2:
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
min_t, max_t = None, None
|
|
139
|
+
min_z, max_z = None, None
|
|
140
|
+
min_y, min_x = slice_[0].start, slice_[1].start
|
|
141
|
+
max_y, max_x = slice_[0].stop, slice_[1].stop
|
|
141
142
|
elif len(slice_) == 3:
|
|
143
|
+
min_t, max_t = None, None
|
|
142
144
|
min_z, min_y, min_x = slice_[0].start, slice_[1].start, slice_[2].start
|
|
143
145
|
max_z, max_y, max_x = slice_[0].stop, slice_[1].stop, slice_[2].stop
|
|
146
|
+
elif len(slice_) == 4:
|
|
147
|
+
min_t, min_z, min_y, min_x = (
|
|
148
|
+
slice_[0].start,
|
|
149
|
+
slice_[1].start,
|
|
150
|
+
slice_[2].start,
|
|
151
|
+
slice_[3].start,
|
|
152
|
+
)
|
|
153
|
+
max_t, max_z, max_y, max_x = (
|
|
154
|
+
slice_[0].stop,
|
|
155
|
+
slice_[1].stop,
|
|
156
|
+
slice_[2].stop,
|
|
157
|
+
slice_[3].stop,
|
|
158
|
+
)
|
|
144
159
|
else:
|
|
145
160
|
raise ValueError("Invalid slice length.")
|
|
161
|
+
|
|
162
|
+
if max_t is None:
|
|
163
|
+
t_length = None
|
|
164
|
+
else:
|
|
165
|
+
t_length = max_t - min_t
|
|
166
|
+
|
|
167
|
+
if max_z is None:
|
|
168
|
+
z_length = None
|
|
169
|
+
else:
|
|
170
|
+
z_length = max_z - min_z
|
|
171
|
+
|
|
146
172
|
roi = RoiPixels(
|
|
147
173
|
name=str(label),
|
|
148
174
|
x_length=max_x - min_x,
|
|
149
175
|
y_length=max_y - min_y,
|
|
150
|
-
z_length=
|
|
176
|
+
z_length=z_length,
|
|
177
|
+
t_length=t_length,
|
|
151
178
|
x=min_x,
|
|
152
179
|
y=min_y,
|
|
153
180
|
z=min_z,
|
|
181
|
+
label=label,
|
|
154
182
|
)
|
|
155
183
|
|
|
156
184
|
roi = roi.to_roi(pixel_size)
|