ngio 0.4.0a4__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +11 -6
- 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.1.dist-info}/METADATA +14 -14
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/RECORD +30 -28
- ngio/io_pipes/_io_pipes_utils.py +0 -299
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.1.dist-info}/licenses/LICENSE +0 -0
ngio/common/_dimensions.py
CHANGED
|
@@ -15,6 +15,192 @@ from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
|
15
15
|
from ngio.utils import NgioValueError
|
|
16
16
|
|
|
17
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
|
|
202
|
+
|
|
203
|
+
|
|
18
204
|
class Dimensions:
|
|
19
205
|
"""Dimension metadata Handling Class.
|
|
20
206
|
|
|
@@ -23,18 +209,28 @@ class Dimensions:
|
|
|
23
209
|
such as shape, axes, and properties like is_2d, is_3d, is_time_series, etc.
|
|
24
210
|
"""
|
|
25
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
|
|
218
|
+
|
|
26
219
|
def __init__(
|
|
27
220
|
self,
|
|
28
221
|
shape: tuple[int, ...],
|
|
222
|
+
chunks: tuple[int, ...],
|
|
29
223
|
dataset: Dataset,
|
|
30
224
|
) -> None:
|
|
31
225
|
"""Create a Dimension object from a Zarr array.
|
|
32
226
|
|
|
33
227
|
Args:
|
|
34
228
|
shape: The shape of the Zarr array.
|
|
229
|
+
chunks: The chunks of the Zarr array.
|
|
35
230
|
dataset: The dataset object.
|
|
36
231
|
"""
|
|
37
232
|
self._shape = shape
|
|
233
|
+
self._chunks = chunks
|
|
38
234
|
self._axes_handler = dataset.axes_handler
|
|
39
235
|
self._pixel_size = dataset.pixel_size
|
|
40
236
|
|
|
@@ -70,7 +266,12 @@ class Dimensions:
|
|
|
70
266
|
@property
|
|
71
267
|
def shape(self) -> tuple[int, ...]:
|
|
72
268
|
"""Return the shape as a tuple."""
|
|
73
|
-
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
|
|
74
275
|
|
|
75
276
|
@property
|
|
76
277
|
def axes(self) -> tuple[str, ...]:
|
|
@@ -79,36 +280,36 @@ class Dimensions:
|
|
|
79
280
|
|
|
80
281
|
@property
|
|
81
282
|
def is_time_series(self) -> bool:
|
|
82
|
-
"""Return whether the
|
|
283
|
+
"""Return whether the image is a time series."""
|
|
83
284
|
if self.get("t", default=1) == 1:
|
|
84
285
|
return False
|
|
85
286
|
return True
|
|
86
287
|
|
|
87
288
|
@property
|
|
88
289
|
def is_2d(self) -> bool:
|
|
89
|
-
"""Return whether the
|
|
290
|
+
"""Return whether the image is 2D."""
|
|
90
291
|
if self.get("z", default=1) != 1:
|
|
91
292
|
return False
|
|
92
293
|
return True
|
|
93
294
|
|
|
94
295
|
@property
|
|
95
296
|
def is_2d_time_series(self) -> bool:
|
|
96
|
-
"""Return whether the
|
|
297
|
+
"""Return whether the image is a 2D time series."""
|
|
97
298
|
return self.is_2d and self.is_time_series
|
|
98
299
|
|
|
99
300
|
@property
|
|
100
301
|
def is_3d(self) -> bool:
|
|
101
|
-
"""Return whether the
|
|
302
|
+
"""Return whether the image is 3D."""
|
|
102
303
|
return not self.is_2d
|
|
103
304
|
|
|
104
305
|
@property
|
|
105
306
|
def is_3d_time_series(self) -> bool:
|
|
106
|
-
"""Return whether the
|
|
307
|
+
"""Return whether the image is a 3D time series."""
|
|
107
308
|
return self.is_3d and self.is_time_series
|
|
108
309
|
|
|
109
310
|
@property
|
|
110
311
|
def is_multi_channels(self) -> bool:
|
|
111
|
-
"""Return whether the
|
|
312
|
+
"""Return whether the image has multiple channels."""
|
|
112
313
|
if self.get("c", default=1) == 1:
|
|
113
314
|
return False
|
|
114
315
|
return True
|
|
@@ -122,7 +323,7 @@ class Dimensions:
|
|
|
122
323
|
pass
|
|
123
324
|
|
|
124
325
|
def get(self, axis_name: str, default: int | None = None) -> int | None:
|
|
125
|
-
"""Return the dimension of the given axis name.
|
|
326
|
+
"""Return the dimension/shape of the given axis name.
|
|
126
327
|
|
|
127
328
|
Args:
|
|
128
329
|
axis_name: The name of the axis (either canonical or non-canonical).
|
|
@@ -132,184 +333,3 @@ class Dimensions:
|
|
|
132
333
|
if index is None:
|
|
133
334
|
return default
|
|
134
335
|
return self._shape[index]
|
|
135
|
-
|
|
136
|
-
def get_index(self, axis_name: str) -> int | None:
|
|
137
|
-
"""Return the index of the given axis name.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
axis_name: The name of the axis (either canonical or non-canonical).
|
|
141
|
-
"""
|
|
142
|
-
return self.axes_handler.get_index(axis_name)
|
|
143
|
-
|
|
144
|
-
def has_axis(self, axis_name: str) -> bool:
|
|
145
|
-
"""Return whether the axis exists."""
|
|
146
|
-
index = self.axes_handler.get_index(axis_name)
|
|
147
|
-
if index is None:
|
|
148
|
-
return False
|
|
149
|
-
return True
|
|
150
|
-
|
|
151
|
-
def require_axes_match(self, other: "Dimensions") -> None:
|
|
152
|
-
"""Check if two Dimensions objects have the same axes.
|
|
153
|
-
|
|
154
|
-
Besides the channel axis (which is a special case), all axes must be
|
|
155
|
-
present in both Dimensions objects.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
other (Dimensions): The other dimensions object to compare against.
|
|
159
|
-
|
|
160
|
-
Raises:
|
|
161
|
-
NgioValueError: If the axes do not match.
|
|
162
|
-
"""
|
|
163
|
-
require_axes_match(self, other)
|
|
164
|
-
|
|
165
|
-
def require_dimensions_match(
|
|
166
|
-
self, other: "Dimensions", allow_singleton: bool = False
|
|
167
|
-
) -> None:
|
|
168
|
-
"""Check if two Dimensions objects have the same axes and dimensions.
|
|
169
|
-
|
|
170
|
-
Besides the channel axis, all axes must have the same dimension in
|
|
171
|
-
both images.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
other (Dimensions): The other dimensions object to compare against.
|
|
175
|
-
allow_singleton (bool): Whether to allow singleton dimensions to be
|
|
176
|
-
different. For example, if the input image has shape
|
|
177
|
-
(5, 100, 100) and the label has shape (1, 100, 100).
|
|
178
|
-
|
|
179
|
-
Raises:
|
|
180
|
-
NgioValueError: If the dimensions do not match.
|
|
181
|
-
"""
|
|
182
|
-
require_dimensions_match(self, other, allow_singleton)
|
|
183
|
-
|
|
184
|
-
def require_can_be_rescaled(self, other: "Dimensions") -> None:
|
|
185
|
-
"""Assert that 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
|
-
other (Dimensions): The other dimensions object to compare against.
|
|
192
|
-
|
|
193
|
-
"""
|
|
194
|
-
require_rescalable(self, other)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def _are_compatible(shape1: int, shape2: int, scaling: float) -> bool:
|
|
198
|
-
"""Check if shape2 is consistent with shape1 given pixel sizes.
|
|
199
|
-
|
|
200
|
-
Since we only deal with shape discrepancies due to rounding, we
|
|
201
|
-
shape1, needs to be larger than shape2.
|
|
202
|
-
"""
|
|
203
|
-
if shape1 < shape2:
|
|
204
|
-
return _are_compatible(shape2, shape1, 1 / scaling)
|
|
205
|
-
expected_shape2 = shape1 * scaling
|
|
206
|
-
expected_shape2_floor = math.floor(expected_shape2)
|
|
207
|
-
expected_shape2_ceil = math.ceil(expected_shape2)
|
|
208
|
-
return shape2 in {expected_shape2_floor, expected_shape2_ceil}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def require_axes_match(dimensions1: Dimensions, dimensions2: Dimensions) -> None:
|
|
212
|
-
"""Check if two Dimensions objects have the same axes.
|
|
213
|
-
|
|
214
|
-
Besides the channel axis (which is a special case), all axes must be
|
|
215
|
-
present in both Dimensions objects.
|
|
216
|
-
|
|
217
|
-
Args:
|
|
218
|
-
dimensions1 (Dimensions): The first dimensions object to compare against.
|
|
219
|
-
dimensions2 (Dimensions): The second dimensions object to compare against.
|
|
220
|
-
|
|
221
|
-
Raises:
|
|
222
|
-
NgioValueError: If the axes do not match.
|
|
223
|
-
"""
|
|
224
|
-
for s_axis in dimensions1.axes_handler.axes:
|
|
225
|
-
if s_axis.axis_type == "channel":
|
|
226
|
-
continue
|
|
227
|
-
o_axis = dimensions2.axes_handler.get_axis(s_axis.name)
|
|
228
|
-
if o_axis is None:
|
|
229
|
-
raise NgioValueError(
|
|
230
|
-
f"Axes do not match. The axis {s_axis.name} "
|
|
231
|
-
f"is not present in either dimensions."
|
|
232
|
-
)
|
|
233
|
-
# Check for axes present in the other dimensions but not in this one
|
|
234
|
-
for o_axis in dimensions2.axes_handler.axes:
|
|
235
|
-
if o_axis.axis_type == "channel":
|
|
236
|
-
continue
|
|
237
|
-
s_axis = dimensions1.axes_handler.get_axis(o_axis.name)
|
|
238
|
-
if s_axis is None:
|
|
239
|
-
raise NgioValueError(
|
|
240
|
-
f"Axes do not match. The axis {o_axis.name} "
|
|
241
|
-
f"is not present in either dimensions."
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def require_dimensions_match(
|
|
246
|
-
dimensions1: Dimensions, dimensions2: Dimensions, allow_singleton: bool = False
|
|
247
|
-
) -> None:
|
|
248
|
-
"""Check if two Dimensions objects have the same axes and dimensions.
|
|
249
|
-
|
|
250
|
-
Besides the channel axis, all axes must have the same dimension in
|
|
251
|
-
both images.
|
|
252
|
-
|
|
253
|
-
Args:
|
|
254
|
-
dimensions1 (Dimensions): The first dimensions object to compare against.
|
|
255
|
-
dimensions2 (Dimensions): The second dimensions object to compare against.
|
|
256
|
-
allow_singleton (bool): Whether to allow singleton dimensions to be
|
|
257
|
-
different. For example, if the input image has shape
|
|
258
|
-
(5, 100, 100) and the label has shape (1, 100, 100).
|
|
259
|
-
|
|
260
|
-
Raises:
|
|
261
|
-
NgioValueError: If the dimensions do not match.
|
|
262
|
-
"""
|
|
263
|
-
require_axes_match(dimensions1, dimensions2)
|
|
264
|
-
for s_axis in dimensions1.axes_handler.axes:
|
|
265
|
-
if s_axis.axis_type == "channel":
|
|
266
|
-
continue
|
|
267
|
-
o_axis = dimensions2.axes_handler.get_axis(s_axis.name)
|
|
268
|
-
assert o_axis is not None # already checked in assert_axes_match
|
|
269
|
-
|
|
270
|
-
i_dim = dimensions1.get(s_axis.name, default=1)
|
|
271
|
-
o_dim = dimensions2.get(o_axis.name, default=1)
|
|
272
|
-
|
|
273
|
-
if i_dim != o_dim:
|
|
274
|
-
if allow_singleton and (i_dim == 1 or o_dim == 1):
|
|
275
|
-
continue
|
|
276
|
-
raise NgioValueError(
|
|
277
|
-
f"Dimensions do not match for axis "
|
|
278
|
-
f"{s_axis.name}. Got {i_dim} and {o_dim}."
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def require_rescalable(dimensions1: Dimensions, dimensions2: Dimensions) -> None:
|
|
283
|
-
"""Assert that two images can be rescaled.
|
|
284
|
-
|
|
285
|
-
For this to be true, the images must have the same axes, and
|
|
286
|
-
the pixel sizes must be compatible (i.e. one can be scaled to the other).
|
|
287
|
-
|
|
288
|
-
Args:
|
|
289
|
-
dimensions1 (Dimensions): The first dimensions object to compare against.
|
|
290
|
-
dimensions2 (Dimensions): The second dimensions object to compare against.
|
|
291
|
-
|
|
292
|
-
"""
|
|
293
|
-
require_axes_match(dimensions1, dimensions2)
|
|
294
|
-
for ax1 in dimensions1.axes_handler.axes:
|
|
295
|
-
if ax1.axis_type == "channel":
|
|
296
|
-
continue
|
|
297
|
-
ax2 = dimensions2.axes_handler.get_axis(ax1.name)
|
|
298
|
-
assert ax2 is not None, "Axes do not match."
|
|
299
|
-
px1 = dimensions1.pixel_size.get(ax1.name, default=1.0)
|
|
300
|
-
px2 = dimensions2.pixel_size.get(ax2.name, default=1.0)
|
|
301
|
-
shape1 = dimensions1.get(ax1.name, default=1)
|
|
302
|
-
shape2 = dimensions2.get(ax2.name, default=1)
|
|
303
|
-
scale = px1 / px2
|
|
304
|
-
if not _are_compatible(
|
|
305
|
-
shape1=shape1,
|
|
306
|
-
shape2=shape2,
|
|
307
|
-
scaling=scale,
|
|
308
|
-
):
|
|
309
|
-
raise NgioValueError(
|
|
310
|
-
f"Image1 with shape {dimensions1.shape}, "
|
|
311
|
-
f"and pixel size {dimensions1.pixel_size}, "
|
|
312
|
-
f"cannot be rescaled to "
|
|
313
|
-
f"Image2 with shape {dimensions2.shape}, "
|
|
314
|
-
f"and pixel size {dimensions2.pixel_size}. "
|
|
315
|
-
)
|
ngio/common/_roi.py
CHANGED
|
@@ -40,7 +40,7 @@ T = TypeVar("T", int, float)
|
|
|
40
40
|
class GenericRoi(BaseModel):
|
|
41
41
|
"""A generic Region of Interest (ROI) model."""
|
|
42
42
|
|
|
43
|
-
name: str | None
|
|
43
|
+
name: str | None = None
|
|
44
44
|
x: float
|
|
45
45
|
y: float
|
|
46
46
|
z: float | None = None
|
|
@@ -327,7 +327,7 @@ def zoom_roi(roi: Roi, zoom_factor: float = 1) -> Roi:
|
|
|
327
327
|
If the zoom factor is 1 the ROI will not be changed.
|
|
328
328
|
"""
|
|
329
329
|
if zoom_factor <= 0:
|
|
330
|
-
raise
|
|
330
|
+
raise NgioValueError("Zoom factor must be greater than 0.")
|
|
331
331
|
|
|
332
332
|
# the zoom factor needs to be rescaled
|
|
333
333
|
# from the range [-1, inf) to [0, inf)
|