ngio 0.4.0a2__py3-none-any.whl → 0.4.0a4__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 +1 -2
- ngio/common/__init__.py +2 -51
- ngio/common/_dimensions.py +223 -64
- ngio/common/_pyramid.py +42 -23
- ngio/common/_roi.py +94 -418
- ngio/common/_zoom.py +32 -7
- ngio/experimental/iterators/_abstract_iterator.py +2 -2
- ngio/experimental/iterators/_feature.py +10 -15
- ngio/experimental/iterators/_image_processing.py +18 -28
- ngio/experimental/iterators/_rois_utils.py +6 -6
- ngio/experimental/iterators/_segmentation.py +38 -54
- ngio/images/_abstract_image.py +136 -94
- ngio/images/_create.py +16 -0
- ngio/images/_create_synt_container.py +10 -0
- ngio/images/_image.py +33 -9
- ngio/images/_label.py +24 -3
- ngio/images/_masked_image.py +60 -81
- ngio/images/_ome_zarr_container.py +34 -1
- ngio/io_pipes/__init__.py +49 -0
- ngio/io_pipes/_io_pipes.py +286 -0
- ngio/io_pipes/_io_pipes_masked.py +481 -0
- ngio/io_pipes/_io_pipes_roi.py +143 -0
- ngio/io_pipes/_io_pipes_utils.py +299 -0
- ngio/io_pipes/_match_shape.py +376 -0
- ngio/io_pipes/_ops_axes.py +146 -0
- ngio/io_pipes/_ops_slices.py +218 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +175 -0
- ngio/ome_zarr_meta/__init__.py +6 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +6 -4
- ngio/ome_zarr_meta/ngio_specs/_axes.py +182 -70
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/__init__.py +1 -0
- ngio/resources/resource_model.py +1 -0
- ngio/tables/v1/_roi_table.py +11 -3
- ngio/{common/transforms → transforms}/__init__.py +1 -1
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/_zarr_utils.py +5 -1
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/METADATA +1 -1
- ngio-0.4.0a4.dist-info/RECORD +83 -0
- ngio/common/_array_io_pipes.py +0 -554
- ngio/common/_array_io_utils.py +0 -508
- ngio/common/transforms/_label.py +0 -12
- ngio/common/transforms/_zoom.py +0 -109
- ngio-0.4.0a2.dist-info/RECORD +0 -76
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,15 +7,16 @@ This models can be tr
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
10
|
-
|
|
10
|
+
AxesHandler,
|
|
11
|
+
AxesOps,
|
|
11
12
|
AxesSetup,
|
|
12
13
|
Axis,
|
|
13
14
|
AxisType,
|
|
14
15
|
DefaultSpaceUnit,
|
|
15
16
|
DefaultTimeUnit,
|
|
16
|
-
SlicingOps,
|
|
17
17
|
SpaceUnits,
|
|
18
18
|
TimeUnits,
|
|
19
|
+
build_canonical_axes_handler,
|
|
19
20
|
canonical_axes_order,
|
|
20
21
|
canonical_label_axes_order,
|
|
21
22
|
)
|
|
@@ -44,7 +45,8 @@ from ngio.ome_zarr_meta.ngio_specs._ngio_image import (
|
|
|
44
45
|
from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
45
46
|
|
|
46
47
|
__all__ = [
|
|
47
|
-
"
|
|
48
|
+
"AxesHandler",
|
|
49
|
+
"AxesOps",
|
|
48
50
|
"AxesSetup",
|
|
49
51
|
"Axis",
|
|
50
52
|
"AxisType",
|
|
@@ -65,9 +67,9 @@ __all__ = [
|
|
|
65
67
|
"NgioPlateMeta",
|
|
66
68
|
"NgioWellMeta",
|
|
67
69
|
"PixelSize",
|
|
68
|
-
"SlicingOps",
|
|
69
70
|
"SpaceUnits",
|
|
70
71
|
"TimeUnits",
|
|
72
|
+
"build_canonical_axes_handler",
|
|
71
73
|
"canonical_axes_order",
|
|
72
74
|
"canonical_label_axes_order",
|
|
73
75
|
"default_channel_name",
|
|
@@ -91,7 +91,7 @@ DefaultTimeUnit = "second"
|
|
|
91
91
|
class Axis(BaseModel):
|
|
92
92
|
"""Axis infos model."""
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
name: str
|
|
95
95
|
unit: str | None = None
|
|
96
96
|
axis_type: AxisType | None = None
|
|
97
97
|
|
|
@@ -105,7 +105,7 @@ class Axis(BaseModel):
|
|
|
105
105
|
if cast_type == AxisType.space and unit is None:
|
|
106
106
|
unit = DefaultSpaceUnit
|
|
107
107
|
|
|
108
|
-
return Axis(
|
|
108
|
+
return Axis(name=self.name, axis_type=cast_type, unit=unit)
|
|
109
109
|
|
|
110
110
|
def canonical_axis_cast(self, canonical_name: str) -> "Axis":
|
|
111
111
|
"""Cast the implicit axis to the correct type."""
|
|
@@ -167,6 +167,11 @@ class AxesSetup(BaseModel):
|
|
|
167
167
|
"x": self.x,
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
def get_on_disk_name(self, canonical_name: str) -> str | None:
|
|
171
|
+
"""Get the on disk name of the axis by its canonical name."""
|
|
172
|
+
canonical_map = self.canonical_map()
|
|
173
|
+
return canonical_map.get(canonical_name, None)
|
|
174
|
+
|
|
170
175
|
def inverse_canonical_map(self) -> dict[str, str]:
|
|
171
176
|
"""Get the on disk map of axes."""
|
|
172
177
|
return {
|
|
@@ -177,10 +182,15 @@ class AxesSetup(BaseModel):
|
|
|
177
182
|
self.x: "x",
|
|
178
183
|
}
|
|
179
184
|
|
|
185
|
+
def get_canonical_name(self, on_disk_name: str) -> str | None:
|
|
186
|
+
"""Get the canonical name of the axis by its on disk name."""
|
|
187
|
+
inv_map = self.inverse_canonical_map()
|
|
188
|
+
return inv_map.get(on_disk_name, None)
|
|
189
|
+
|
|
180
190
|
|
|
181
191
|
def _check_unique_names(axes: Sequence[Axis]):
|
|
182
192
|
"""Check if all axes on disk have unique names."""
|
|
183
|
-
names = [ax.
|
|
193
|
+
names = [ax.name for ax in axes]
|
|
184
194
|
if len(set(names)) != len(names):
|
|
185
195
|
duplicates = {item for item in names if names.count(item) > 1}
|
|
186
196
|
raise NgioValidationError(
|
|
@@ -202,10 +212,10 @@ def _check_axes_validity(axes: Sequence[Axis], axes_setup: AxesSetup):
|
|
|
202
212
|
_axes_setup = axes_setup.model_dump(exclude={"others"})
|
|
203
213
|
_all_known_axes = [*_axes_setup.values(), *axes_setup.others]
|
|
204
214
|
for ax in axes:
|
|
205
|
-
if ax.
|
|
215
|
+
if ax.name not in _all_known_axes:
|
|
206
216
|
raise NgioValidationError(
|
|
207
|
-
f"Invalid axis name '{ax.
|
|
208
|
-
f"Please correct map `{ax.
|
|
217
|
+
f"Invalid axis name '{ax.name}'. "
|
|
218
|
+
f"Please correct map `{ax.name}` "
|
|
209
219
|
f"using the AxesSetup model {axes_setup}"
|
|
210
220
|
)
|
|
211
221
|
|
|
@@ -216,17 +226,17 @@ def _check_canonical_order(
|
|
|
216
226
|
"""Check if the axes are in the canonical order."""
|
|
217
227
|
if not strict_canonical_order:
|
|
218
228
|
return
|
|
219
|
-
|
|
229
|
+
_names = [ax.name for ax in axes]
|
|
220
230
|
_canonical_order = []
|
|
221
231
|
for name in canonical_axes_order():
|
|
222
232
|
mapped_name = getattr(axes_setup, name)
|
|
223
|
-
if mapped_name in
|
|
233
|
+
if mapped_name in _names:
|
|
224
234
|
_canonical_order.append(mapped_name)
|
|
225
235
|
|
|
226
|
-
if
|
|
236
|
+
if _names != _canonical_order:
|
|
227
237
|
raise NgioValidationError(
|
|
228
238
|
f"Invalid axes order. The axes must be in the canonical order. "
|
|
229
|
-
f"Expected {_canonical_order}, but found {
|
|
239
|
+
f"Expected {_canonical_order}, but found {_names}"
|
|
230
240
|
)
|
|
231
241
|
|
|
232
242
|
|
|
@@ -253,32 +263,77 @@ def validate_axes(
|
|
|
253
263
|
)
|
|
254
264
|
|
|
255
265
|
|
|
256
|
-
class
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
class AxesOps(BaseModel):
|
|
267
|
+
"""Model to represent axes operations.
|
|
268
|
+
|
|
269
|
+
This model will be used to transform objects from on disk axes to in memory axes.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
on_disk_axes: tuple[str, ...]
|
|
273
|
+
in_memory_axes: tuple[str, ...]
|
|
274
|
+
transpose_op: tuple[int, ...] | None = None
|
|
275
|
+
expand_op: tuple[int, ...] | None = None
|
|
276
|
+
squeeze_op: tuple[int, ...] | None = None
|
|
261
277
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
|
262
278
|
|
|
263
279
|
@property
|
|
264
|
-
def
|
|
265
|
-
"""Check if
|
|
266
|
-
if
|
|
280
|
+
def is_no_op(self) -> bool:
|
|
281
|
+
"""Check if all operations are no ops."""
|
|
282
|
+
if (
|
|
283
|
+
self.transpose_op is None
|
|
284
|
+
and self.expand_op is None
|
|
285
|
+
and self.squeeze_op is None
|
|
286
|
+
):
|
|
267
287
|
return True
|
|
268
288
|
return False
|
|
269
289
|
|
|
290
|
+
@property
|
|
291
|
+
def get_transpose_op(self) -> tuple[int, ...] | None:
|
|
292
|
+
"""Get the transpose axes."""
|
|
293
|
+
return self.transpose_op
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def get_expand_op(self) -> tuple[int, ...] | None:
|
|
297
|
+
"""Get the expand axes."""
|
|
298
|
+
return self.expand_op
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def get_squeeze_op(self) -> tuple[int, ...] | None:
|
|
302
|
+
"""Get the squeeze axes."""
|
|
303
|
+
return self.squeeze_op
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def set_transpose_op(self) -> tuple[int, ...] | None:
|
|
307
|
+
"""Set the transpose axes."""
|
|
308
|
+
if self.transpose_op is None:
|
|
309
|
+
return None
|
|
310
|
+
return tuple(np.argsort(self.transpose_op))
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def set_expand_op(self) -> tuple[int, ...] | None:
|
|
314
|
+
"""Set the expand axes."""
|
|
315
|
+
return self.squeeze_op
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def set_squeeze_op(self) -> tuple[int, ...] | None:
|
|
319
|
+
"""Set the squeeze axes."""
|
|
320
|
+
return self.expand_op
|
|
270
321
|
|
|
271
|
-
class AxesMapper:
|
|
272
|
-
"""Map on disk axes to canonical axes.
|
|
273
322
|
|
|
274
|
-
|
|
323
|
+
class AxesHandler:
|
|
324
|
+
"""This class is used to handle and operate on OME-Zarr axes.
|
|
275
325
|
|
|
326
|
+
The class also provides:
|
|
327
|
+
- methods to reorder, squeeze and expand axes.
|
|
328
|
+
- methods to validate the axes.
|
|
329
|
+
- methods to get axis by name or index.
|
|
330
|
+
- methods to operate on the axes.
|
|
276
331
|
"""
|
|
277
332
|
|
|
278
333
|
def __init__(
|
|
279
334
|
self,
|
|
280
335
|
# spec dictated args
|
|
281
|
-
|
|
336
|
+
axes: Sequence[Axis],
|
|
282
337
|
# user defined args
|
|
283
338
|
axes_setup: AxesSetup | None = None,
|
|
284
339
|
allow_non_canonical_axes: bool = False,
|
|
@@ -287,7 +342,7 @@ class AxesMapper:
|
|
|
287
342
|
"""Create a new AxesMapper object.
|
|
288
343
|
|
|
289
344
|
Args:
|
|
290
|
-
|
|
345
|
+
axes (list[Axis]): The axes on disk.
|
|
291
346
|
axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
|
|
292
347
|
allow_non_canonical_axes (bool, optional): Allow non canonical axes.
|
|
293
348
|
strict_canonical_order (bool, optional): Check if the axes are in the
|
|
@@ -296,7 +351,7 @@ class AxesMapper:
|
|
|
296
351
|
axes_setup = axes_setup if axes_setup is not None else AxesSetup()
|
|
297
352
|
|
|
298
353
|
validate_axes(
|
|
299
|
-
axes=
|
|
354
|
+
axes=axes,
|
|
300
355
|
axes_setup=axes_setup,
|
|
301
356
|
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
302
357
|
strict_canonical_order=strict_canonical_order,
|
|
@@ -307,7 +362,7 @@ class AxesMapper:
|
|
|
307
362
|
|
|
308
363
|
self._canonical_order = canonical_axes_order()
|
|
309
364
|
|
|
310
|
-
self.
|
|
365
|
+
self._axes = axes
|
|
311
366
|
self._axes_setup = axes_setup
|
|
312
367
|
|
|
313
368
|
self._index_mapping = self._compute_index_mapping()
|
|
@@ -339,9 +394,9 @@ class AxesMapper:
|
|
|
339
394
|
_index_mapping[ax] = i
|
|
340
395
|
# If the axis is not in the canonical order we also set it.
|
|
341
396
|
canonical_map = self._axes_setup.canonical_map()
|
|
342
|
-
for
|
|
343
|
-
if
|
|
344
|
-
_index_mapping[
|
|
397
|
+
for canonical_name, on_disk_name in canonical_map.items():
|
|
398
|
+
if on_disk_name in _index_mapping.keys():
|
|
399
|
+
_index_mapping[canonical_name] = _index_mapping[on_disk_name]
|
|
345
400
|
return _index_mapping
|
|
346
401
|
|
|
347
402
|
@property
|
|
@@ -351,11 +406,11 @@ class AxesMapper:
|
|
|
351
406
|
|
|
352
407
|
@property
|
|
353
408
|
def axes(self) -> tuple[Axis, ...]:
|
|
354
|
-
return tuple(self.
|
|
409
|
+
return tuple(self._axes)
|
|
355
410
|
|
|
356
411
|
@property
|
|
357
412
|
def axes_names(self) -> tuple[str, ...]:
|
|
358
|
-
return tuple(ax.
|
|
413
|
+
return tuple(ax.name for ax in self._axes)
|
|
359
414
|
|
|
360
415
|
@property
|
|
361
416
|
def allow_non_canonical_axes(self) -> bool:
|
|
@@ -367,10 +422,75 @@ class AxesMapper:
|
|
|
367
422
|
"""Return if strict canonical order is enforced."""
|
|
368
423
|
return self._strict_canonical_order
|
|
369
424
|
|
|
425
|
+
@property
|
|
426
|
+
def space_unit(self) -> str | None:
|
|
427
|
+
"""Return the space unit for a given axis."""
|
|
428
|
+
x_axis = self.get_axis("x")
|
|
429
|
+
y_axis = self.get_axis("y")
|
|
430
|
+
|
|
431
|
+
if x_axis is None or y_axis is None:
|
|
432
|
+
raise NgioValidationError(
|
|
433
|
+
"The dataset must have x and y axes to determine the space unit."
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
if x_axis.unit == y_axis.unit:
|
|
437
|
+
return x_axis.unit
|
|
438
|
+
else:
|
|
439
|
+
raise NgioValidationError(
|
|
440
|
+
"Inconsistent space units. "
|
|
441
|
+
f"x={x_axis.unit} and y={y_axis.unit} should have the same unit."
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def time_unit(self) -> str | None:
|
|
446
|
+
"""Return the time unit for a given axis."""
|
|
447
|
+
t_axis = self.get_axis("t")
|
|
448
|
+
if t_axis is None:
|
|
449
|
+
return None
|
|
450
|
+
return t_axis.unit
|
|
451
|
+
|
|
452
|
+
def to_units(
|
|
453
|
+
self,
|
|
454
|
+
*,
|
|
455
|
+
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
456
|
+
time_unit: TimeUnits = DefaultTimeUnit,
|
|
457
|
+
) -> "AxesHandler":
|
|
458
|
+
"""Convert the pixel size to the given units.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
space_unit(str): The space unit to convert to.
|
|
462
|
+
time_unit(str): The time unit to convert to.
|
|
463
|
+
"""
|
|
464
|
+
new_axes = []
|
|
465
|
+
for ax in self.axes:
|
|
466
|
+
if ax.axis_type == AxisType.space:
|
|
467
|
+
new_ax = Axis(
|
|
468
|
+
name=ax.name,
|
|
469
|
+
axis_type=ax.axis_type,
|
|
470
|
+
unit=space_unit,
|
|
471
|
+
)
|
|
472
|
+
new_axes.append(new_ax)
|
|
473
|
+
elif ax.axis_type == AxisType.time:
|
|
474
|
+
new_ax = Axis(name=ax.name, axis_type=ax.axis_type, unit=time_unit)
|
|
475
|
+
new_axes.append(new_ax)
|
|
476
|
+
else:
|
|
477
|
+
new_axes.append(ax)
|
|
478
|
+
|
|
479
|
+
return AxesHandler(
|
|
480
|
+
axes=new_axes,
|
|
481
|
+
axes_setup=self.axes_setup,
|
|
482
|
+
allow_non_canonical_axes=self.allow_non_canonical_axes,
|
|
483
|
+
strict_canonical_order=self.strict_canonical_order,
|
|
484
|
+
)
|
|
485
|
+
|
|
370
486
|
def get_index(self, name: str) -> int | None:
|
|
371
487
|
"""Get the index of the axis by name."""
|
|
372
488
|
return self._index_mapping.get(name, None)
|
|
373
489
|
|
|
490
|
+
def get_canonical_name(self, name: str) -> str | None:
|
|
491
|
+
"""Get the canonical name of the axis by name."""
|
|
492
|
+
return self._axes_setup.get_canonical_name(name)
|
|
493
|
+
|
|
374
494
|
def get_axis(self, name: str) -> Axis | None:
|
|
375
495
|
"""Get the axis object by name."""
|
|
376
496
|
index = self.get_index(name)
|
|
@@ -392,7 +512,7 @@ class AxesMapper:
|
|
|
392
512
|
break
|
|
393
513
|
else:
|
|
394
514
|
new_axes.append(axes)
|
|
395
|
-
self.
|
|
515
|
+
self._axes = new_axes
|
|
396
516
|
|
|
397
517
|
def _reorder_axes(
|
|
398
518
|
self, names: Sequence[str]
|
|
@@ -451,46 +571,32 @@ class AxesMapper:
|
|
|
451
571
|
axes_to_expand = tuple(_axes_to_expand) if len(_axes_to_expand) > 0 else None
|
|
452
572
|
return axes_to_squeeze, transposition_order, axes_to_expand
|
|
453
573
|
|
|
454
|
-
def
|
|
455
|
-
"""Get the
|
|
574
|
+
def get_axes_ops(self, names: Sequence[str]) -> AxesOps:
|
|
575
|
+
"""Get the axes operations to go from on-disk to in-memory axes."""
|
|
456
576
|
axes_to_squeeze, transposition_order, axes_to_expand = self._reorder_axes(names)
|
|
457
|
-
return
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
577
|
+
return AxesOps(
|
|
578
|
+
on_disk_axes=self.axes_names,
|
|
579
|
+
in_memory_axes=tuple(names),
|
|
580
|
+
transpose_op=transposition_order,
|
|
581
|
+
expand_op=axes_to_expand,
|
|
582
|
+
squeeze_op=axes_to_squeeze,
|
|
461
583
|
)
|
|
462
584
|
|
|
463
|
-
def
|
|
464
|
-
"""Get the
|
|
465
|
-
axes_to_squeeze, transposition_order, axes_to_expand = self._reorder_axes(names)
|
|
466
|
-
# Inverse transpose is just the transpose with the inverse indices
|
|
467
|
-
if transposition_order is None:
|
|
468
|
-
_reverse_indices = None
|
|
469
|
-
else:
|
|
470
|
-
_reverse_indices = tuple(np.argsort(transposition_order))
|
|
471
|
-
|
|
472
|
-
return SlicingOps(
|
|
473
|
-
transpose_axes=_reverse_indices,
|
|
474
|
-
expand_axes=axes_to_squeeze,
|
|
475
|
-
squeeze_axes=axes_to_expand,
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
def to_canonical(self) -> SlicingOps:
|
|
479
|
-
"""Get the new order of the axes."""
|
|
585
|
+
def get_canonical_axes_ops(self) -> AxesOps:
|
|
586
|
+
"""Get the axes operations to go from on-disk to canonical in-memory axes."""
|
|
480
587
|
other = self._axes_setup.others
|
|
481
|
-
return self.
|
|
588
|
+
return self.get_axes_ops(list(self._canonical_order) + other)
|
|
482
589
|
|
|
483
|
-
def from_canonical(self) -> SlicingOps:
|
|
484
|
-
"""Get the new order of the axes."""
|
|
485
|
-
other = self._axes_setup.others
|
|
486
|
-
return self.from_order(other + list(self._canonical_order))
|
|
487
590
|
|
|
488
|
-
|
|
489
|
-
def canonical_axes(
|
|
591
|
+
def build_canonical_axes_handler(
|
|
490
592
|
axes_names: Sequence[str],
|
|
491
593
|
space_units: SpaceUnits | str | None = DefaultSpaceUnit,
|
|
492
594
|
time_units: TimeUnits | str | None = DefaultTimeUnit,
|
|
493
|
-
|
|
595
|
+
# user defined args
|
|
596
|
+
axes_setup: AxesSetup | None = None,
|
|
597
|
+
allow_non_canonical_axes: bool = False,
|
|
598
|
+
strict_canonical_order: bool = False,
|
|
599
|
+
) -> AxesHandler:
|
|
494
600
|
"""Create a new canonical axes mapper.
|
|
495
601
|
|
|
496
602
|
Args:
|
|
@@ -502,25 +608,31 @@ def canonical_axes(
|
|
|
502
608
|
e.g. 3 -> ["z", "y", "x"]
|
|
503
609
|
space_units (SpaceUnits, optional): The space units. Defaults to None.
|
|
504
610
|
time_units (TimeUnits, optional): The time units. Defaults to None.
|
|
611
|
+
axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
|
|
612
|
+
allow_non_canonical_axes (bool, optional): Allow non canonical axes.
|
|
613
|
+
Defaults to False.
|
|
614
|
+
strict_canonical_order (bool, optional): Check if the axes are in the
|
|
615
|
+
canonical order. Defaults to False.
|
|
505
616
|
|
|
506
617
|
"""
|
|
507
618
|
axes = []
|
|
508
619
|
for name in axes_names:
|
|
509
620
|
match name:
|
|
510
621
|
case "t":
|
|
511
|
-
axes.append(
|
|
512
|
-
Axis(on_disk_name=name, axis_type=AxisType.time, unit=time_units)
|
|
513
|
-
)
|
|
622
|
+
axes.append(Axis(name=name, axis_type=AxisType.time, unit=time_units))
|
|
514
623
|
case "c":
|
|
515
|
-
axes.append(Axis(
|
|
624
|
+
axes.append(Axis(name=name, axis_type=AxisType.channel))
|
|
516
625
|
case "z" | "y" | "x":
|
|
517
|
-
axes.append(
|
|
518
|
-
Axis(on_disk_name=name, axis_type=AxisType.space, unit=space_units)
|
|
519
|
-
)
|
|
626
|
+
axes.append(Axis(name=name, axis_type=AxisType.space, unit=space_units))
|
|
520
627
|
case _:
|
|
521
628
|
raise NgioValueError(
|
|
522
629
|
f"Invalid axis name '{name}'. "
|
|
523
630
|
"Only 't', 'c', 'z', 'y', 'x' are allowed."
|
|
524
631
|
)
|
|
525
632
|
|
|
526
|
-
return
|
|
633
|
+
return AxesHandler(
|
|
634
|
+
axes=axes,
|
|
635
|
+
axes_setup=axes_setup,
|
|
636
|
+
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
637
|
+
strict_canonical_order=strict_canonical_order,
|
|
638
|
+
)
|
|
@@ -3,85 +3,49 @@
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
6
|
-
|
|
7
|
-
AxesSetup,
|
|
8
|
-
Axis,
|
|
9
|
-
AxisType,
|
|
10
|
-
DefaultSpaceUnit,
|
|
11
|
-
DefaultTimeUnit,
|
|
12
|
-
SpaceUnits,
|
|
13
|
-
TimeUnits,
|
|
6
|
+
AxesHandler,
|
|
14
7
|
)
|
|
15
8
|
from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
16
9
|
from ngio.utils import NgioValidationError
|
|
17
10
|
|
|
18
11
|
|
|
19
12
|
class Dataset:
|
|
20
|
-
"""Model for a dataset in the multiscale.
|
|
21
|
-
|
|
22
|
-
To initialize the Dataset object, the path, the axes, scale, and translation list
|
|
23
|
-
can be provided with on_disk order.
|
|
24
|
-
"""
|
|
13
|
+
"""Model for a dataset in the multiscale."""
|
|
25
14
|
|
|
26
15
|
def __init__(
|
|
27
16
|
self,
|
|
28
17
|
*,
|
|
29
18
|
# args coming from ngff specs
|
|
30
19
|
path: str,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# user defined args
|
|
35
|
-
axes_setup: AxesSetup | None = None,
|
|
36
|
-
allow_non_canonical_axes: bool = False,
|
|
37
|
-
strict_canonical_order: bool = False,
|
|
20
|
+
axes_handler: AxesHandler,
|
|
21
|
+
scale: Sequence[float],
|
|
22
|
+
translation: Sequence[float] | None = None,
|
|
38
23
|
):
|
|
39
24
|
"""Initialize the Dataset object.
|
|
40
25
|
|
|
41
26
|
Args:
|
|
42
27
|
path (str): The path of the dataset.
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
axes_handler (AxesHandler): The axes handler object.
|
|
29
|
+
scale (list[float]): The list of scale transformation.
|
|
45
30
|
The scale transformation must have the same length as the axes.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
allow_non_canonical_axes (bool): Allow non-canonical axes.
|
|
49
|
-
strict_canonical_order (bool): Strict canonical order.
|
|
31
|
+
translation (list[float] | None): The list of translation.
|
|
32
|
+
The translation must have the same length as the axes.
|
|
50
33
|
"""
|
|
51
34
|
self._path = path
|
|
52
|
-
self.
|
|
53
|
-
on_disk_axes=on_disk_axes,
|
|
54
|
-
axes_setup=axes_setup,
|
|
55
|
-
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
56
|
-
strict_canonical_order=strict_canonical_order,
|
|
57
|
-
)
|
|
35
|
+
self._axes_handler = axes_handler
|
|
58
36
|
|
|
59
|
-
if len(
|
|
37
|
+
if len(scale) != len(axes_handler.axes):
|
|
60
38
|
raise NgioValidationError(
|
|
61
39
|
"The length of the scale transformation must be the same as the axes."
|
|
62
40
|
)
|
|
63
|
-
self.
|
|
41
|
+
self._scale = list(scale)
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
if len(
|
|
43
|
+
translation = translation or [0.0] * len(axes_handler.axes)
|
|
44
|
+
if len(translation) != len(axes_handler.axes):
|
|
67
45
|
raise NgioValidationError(
|
|
68
46
|
"The length of the translation must be the same as the axes."
|
|
69
47
|
)
|
|
70
|
-
self.
|
|
71
|
-
|
|
72
|
-
def get_scale(self, axis_name: str) -> float:
|
|
73
|
-
"""Return the scale for a given axis."""
|
|
74
|
-
idx = self._axes_mapper.get_index(axis_name)
|
|
75
|
-
if idx is None:
|
|
76
|
-
return 1.0
|
|
77
|
-
return self._on_disk_scale[idx]
|
|
78
|
-
|
|
79
|
-
def get_translation(self, axis_name: str) -> float:
|
|
80
|
-
"""Return the translation for a given axis."""
|
|
81
|
-
idx = self._axes_mapper.get_index(axis_name)
|
|
82
|
-
if idx is None:
|
|
83
|
-
return 0.0
|
|
84
|
-
return self._on_disk_translation[idx]
|
|
48
|
+
self._translation = list(translation)
|
|
85
49
|
|
|
86
50
|
@property
|
|
87
51
|
def path(self) -> str:
|
|
@@ -89,84 +53,46 @@ class Dataset:
|
|
|
89
53
|
return self._path
|
|
90
54
|
|
|
91
55
|
@property
|
|
92
|
-
def
|
|
93
|
-
"""Return the
|
|
94
|
-
|
|
95
|
-
y_axis = self._axes_mapper.get_axis("y")
|
|
96
|
-
|
|
97
|
-
if x_axis is None or y_axis is None:
|
|
98
|
-
raise NgioValidationError(
|
|
99
|
-
"The dataset must have x and y axes to determine the space unit."
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
if x_axis.unit == y_axis.unit:
|
|
103
|
-
return x_axis.unit
|
|
104
|
-
else:
|
|
105
|
-
raise NgioValidationError(
|
|
106
|
-
"Inconsistent space units. "
|
|
107
|
-
f"x={x_axis.unit} and y={y_axis.unit} should have the same unit."
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def time_unit(self) -> str | None:
|
|
112
|
-
"""Return the time unit for a given axis."""
|
|
113
|
-
t_axis = self._axes_mapper.get_axis("t")
|
|
114
|
-
if t_axis is None:
|
|
115
|
-
return None
|
|
116
|
-
return t_axis.unit
|
|
56
|
+
def axes_handler(self) -> AxesHandler:
|
|
57
|
+
"""Return the axes handler object."""
|
|
58
|
+
return self._axes_handler
|
|
117
59
|
|
|
118
60
|
@property
|
|
119
61
|
def pixel_size(self) -> PixelSize:
|
|
120
62
|
"""Return the pixel size for the dataset."""
|
|
121
63
|
return PixelSize(
|
|
122
|
-
x=self.get_scale("x"),
|
|
123
|
-
y=self.get_scale("y"),
|
|
124
|
-
z=self.get_scale("z"),
|
|
125
|
-
t=self.get_scale("t"),
|
|
126
|
-
space_unit=self.space_unit,
|
|
127
|
-
time_unit=self.time_unit,
|
|
64
|
+
x=self.get_scale("x", default=1.0),
|
|
65
|
+
y=self.get_scale("y", default=1.0),
|
|
66
|
+
z=self.get_scale("z", default=1.0),
|
|
67
|
+
t=self.get_scale("t", default=1.0),
|
|
68
|
+
space_unit=self.axes_handler.space_unit,
|
|
69
|
+
time_unit=self.axes_handler.time_unit,
|
|
128
70
|
)
|
|
129
71
|
|
|
130
72
|
@property
|
|
131
|
-
def
|
|
132
|
-
"""Return the
|
|
133
|
-
return self.
|
|
73
|
+
def scale(self) -> tuple[float, ...]:
|
|
74
|
+
"""Return the scale transformation as a tuple."""
|
|
75
|
+
return tuple(self._scale)
|
|
134
76
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
time_unit: TimeUnits = DefaultTimeUnit,
|
|
140
|
-
) -> "Dataset":
|
|
141
|
-
"""Convert the pixel size to the given units.
|
|
77
|
+
@property
|
|
78
|
+
def translation(self) -> tuple[float, ...]:
|
|
79
|
+
"""Return the translation as a tuple."""
|
|
80
|
+
return tuple(self._translation)
|
|
142
81
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
on_disk_name=ax.on_disk_name,
|
|
152
|
-
axis_type=ax.axis_type,
|
|
153
|
-
unit=space_unit,
|
|
154
|
-
)
|
|
155
|
-
new_axes.append(new_ax)
|
|
156
|
-
elif ax.axis_type == AxisType.time:
|
|
157
|
-
new_ax = Axis(
|
|
158
|
-
on_disk_name=ax.on_disk_name, axis_type=ax.axis_type, unit=time_unit
|
|
159
|
-
)
|
|
160
|
-
new_axes.append(new_ax)
|
|
161
|
-
else:
|
|
162
|
-
new_axes.append(ax)
|
|
82
|
+
def get_scale(self, axis_name: str, default: float | None = None) -> float:
|
|
83
|
+
"""Return the scale for a given axis."""
|
|
84
|
+
idx = self.axes_handler.get_index(axis_name)
|
|
85
|
+
if idx is None:
|
|
86
|
+
if default is not None:
|
|
87
|
+
return default
|
|
88
|
+
raise ValueError(f"Axis {axis_name} not found in axes {self.axes_handler}.")
|
|
89
|
+
return self._scale[idx]
|
|
163
90
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
91
|
+
def get_translation(self, axis_name: str, default: float | None = None) -> float:
|
|
92
|
+
"""Return the translation for a given axis."""
|
|
93
|
+
idx = self.axes_handler.get_index(axis_name)
|
|
94
|
+
if idx is None:
|
|
95
|
+
if default is not None:
|
|
96
|
+
return default
|
|
97
|
+
raise ValueError(f"Axis {axis_name} not found in axes {self.axes_handler}.")
|
|
98
|
+
return self._translation[idx]
|