ngio 0.4.0a3__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.
Files changed (50) hide show
  1. ngio/__init__.py +1 -2
  2. ngio/common/__init__.py +2 -51
  3. ngio/common/_dimensions.py +223 -64
  4. ngio/common/_pyramid.py +42 -23
  5. ngio/common/_roi.py +47 -411
  6. ngio/common/_zoom.py +32 -7
  7. ngio/experimental/iterators/_abstract_iterator.py +2 -2
  8. ngio/experimental/iterators/_feature.py +9 -14
  9. ngio/experimental/iterators/_image_processing.py +17 -27
  10. ngio/experimental/iterators/_rois_utils.py +4 -4
  11. ngio/experimental/iterators/_segmentation.py +37 -53
  12. ngio/images/_abstract_image.py +135 -93
  13. ngio/images/_create.py +16 -0
  14. ngio/images/_create_synt_container.py +10 -0
  15. ngio/images/_image.py +33 -9
  16. ngio/images/_label.py +24 -3
  17. ngio/images/_masked_image.py +60 -81
  18. ngio/images/_ome_zarr_container.py +33 -0
  19. ngio/io_pipes/__init__.py +49 -0
  20. ngio/io_pipes/_io_pipes.py +286 -0
  21. ngio/io_pipes/_io_pipes_masked.py +481 -0
  22. ngio/io_pipes/_io_pipes_roi.py +143 -0
  23. ngio/io_pipes/_io_pipes_utils.py +299 -0
  24. ngio/io_pipes/_match_shape.py +376 -0
  25. ngio/io_pipes/_ops_axes.py +146 -0
  26. ngio/io_pipes/_ops_slices.py +218 -0
  27. ngio/io_pipes/_ops_transforms.py +104 -0
  28. ngio/io_pipes/_zoom_transform.py +175 -0
  29. ngio/ome_zarr_meta/__init__.py +6 -2
  30. ngio/ome_zarr_meta/ngio_specs/__init__.py +6 -4
  31. ngio/ome_zarr_meta/ngio_specs/_axes.py +182 -70
  32. ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
  33. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
  34. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
  35. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
  36. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  37. ngio/resources/__init__.py +1 -0
  38. ngio/resources/resource_model.py +1 -0
  39. ngio/{common/transforms → transforms}/__init__.py +1 -1
  40. ngio/transforms/_zoom.py +19 -0
  41. ngio/utils/_zarr_utils.py +5 -1
  42. {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/METADATA +1 -1
  43. ngio-0.4.0a4.dist-info/RECORD +83 -0
  44. ngio/common/_array_io_pipes.py +0 -554
  45. ngio/common/_array_io_utils.py +0 -508
  46. ngio/common/transforms/_label.py +0 -12
  47. ngio/common/transforms/_zoom.py +0 -109
  48. ngio-0.4.0a3.dist-info/RECORD +0 -76
  49. {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/WHEEL +0 -0
  50. {ngio-0.4.0a3.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
- AxesMapper,
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
- "AxesMapper",
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
- on_disk_name: str
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(on_disk_name=self.on_disk_name, axis_type=cast_type, unit=unit)
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.on_disk_name for ax in axes]
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.on_disk_name not in _all_known_axes:
215
+ if ax.name not in _all_known_axes:
206
216
  raise NgioValidationError(
207
- f"Invalid axis name '{ax.on_disk_name}'. "
208
- f"Please correct map `{ax.on_disk_name}` "
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
- _on_disk_names = [ax.on_disk_name for ax in axes]
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 _on_disk_names:
233
+ if mapped_name in _names:
224
234
  _canonical_order.append(mapped_name)
225
235
 
226
- if _on_disk_names != _canonical_order:
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 {_on_disk_names}"
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 SlicingOps(BaseModel):
257
- slice_tuple: tuple[SlicingType, ...] | None = None
258
- transpose_axes: tuple[int, ...] | None = None
259
- expand_axes: tuple[int, ...] | None = None
260
- squeeze_axes: tuple[int, ...] | None = None
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 requires_axes_ops(self) -> bool:
265
- """Check if the slicing operations require axes operations."""
266
- if self.expand_axes or self.transpose_axes or self.squeeze_axes:
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
- This class is used to map the on disk axes to the canonical axes.
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
- on_disk_axes: Sequence[Axis],
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
- on_disk_axes (list[Axis]): The axes on disk.
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=on_disk_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._on_disk_axes = on_disk_axes
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 canonical_key, on_disk_value in canonical_map.items():
343
- if on_disk_value in _index_mapping.keys():
344
- _index_mapping[canonical_key] = _index_mapping[on_disk_value]
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._on_disk_axes)
409
+ return tuple(self._axes)
355
410
 
356
411
  @property
357
412
  def axes_names(self) -> tuple[str, ...]:
358
- return tuple(ax.on_disk_name for ax in self._on_disk_axes)
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._on_disk_axes = new_axes
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 to_order(self, names: Sequence[str]) -> SlicingOps:
455
- """Get the new order of the axes."""
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 SlicingOps(
458
- transpose_axes=transposition_order,
459
- expand_axes=axes_to_expand,
460
- squeeze_axes=axes_to_squeeze,
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 from_order(self, names: Sequence[str]) -> SlicingOps:
464
- """Get the new order of the axes."""
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.to_order(other + list(self._canonical_order))
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
- ) -> list[Axis]:
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(on_disk_name=name, axis_type=AxisType.channel))
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 axes
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
- AxesMapper,
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
- on_disk_axes: Sequence[Axis],
32
- on_disk_scale: Sequence[float],
33
- on_disk_translation: Sequence[float] | None = None,
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
- on_disk_axes (list[Axis]): The list of axes in the multiscale.
44
- on_disk_scale (list[float]): The list of scale transformation.
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
- on_disk_translation (list[float] | None): The list of translation.
47
- axes_setup (AxesSetup): The axes setup object
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._axes_mapper = AxesMapper(
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(on_disk_scale) != len(on_disk_axes):
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._on_disk_scale = list(on_disk_scale)
41
+ self._scale = list(scale)
64
42
 
65
- on_disk_translation = on_disk_translation or [0.0] * len(on_disk_axes)
66
- if len(on_disk_translation) != len(on_disk_axes):
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._on_disk_translation = list(on_disk_translation)
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 space_unit(self) -> str | None:
93
- """Return the space unit for a given axis."""
94
- x_axis = self._axes_mapper.get_axis("x")
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 axes_mapper(self) -> AxesMapper:
132
- """Return the axes mapper object."""
133
- return self._axes_mapper
73
+ def scale(self) -> tuple[float, ...]:
74
+ """Return the scale transformation as a tuple."""
75
+ return tuple(self._scale)
134
76
 
135
- def to_units(
136
- self,
137
- *,
138
- space_unit: SpaceUnits = DefaultSpaceUnit,
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
- Args:
144
- space_unit(str): The space unit to convert to.
145
- time_unit(str): The time unit to convert to.
146
- """
147
- new_axes = []
148
- for ax in self.axes_mapper.axes:
149
- if ax.axis_type == AxisType.space:
150
- new_ax = Axis(
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
- return Dataset(
165
- path=self.path,
166
- on_disk_axes=new_axes,
167
- on_disk_scale=self._on_disk_scale,
168
- on_disk_translation=self._on_disk_translation,
169
- axes_setup=self.axes_mapper.axes_setup,
170
- allow_non_canonical_axes=self.axes_mapper.allow_non_canonical_axes,
171
- strict_canonical_order=self.axes_mapper.strict_canonical_order,
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]