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.
@@ -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 tuple(self._shape)
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 data is a time series."""
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 data is 2D."""
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 data is a 2D time series."""
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 data is 3D."""
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 data is a 3D time series."""
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 data has multiple channels."""
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 ValueError("Zoom factor must be greater than 0.")
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)
@@ -7,8 +7,6 @@ from ngio.experimental.iterators._segmentation import (
7
7
  SegmentationIterator,
8
8
  )
9
9
 
10
- # from ngio.experimental.iterators._builder import IteratorBuilder
11
-
12
10
  __all__ = [
13
11
  "FeatureExtractorIterator",
14
12
  "ImageProcessingIterator",