ngio 0.2.0a2__py3-none-any.whl → 0.5.0b4__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 (106) hide show
  1. ngio/__init__.py +40 -12
  2. ngio/common/__init__.py +16 -32
  3. ngio/common/_dimensions.py +270 -48
  4. ngio/common/_masking_roi.py +153 -0
  5. ngio/common/_pyramid.py +267 -73
  6. ngio/common/_roi.py +290 -66
  7. ngio/common/_synt_images_utils.py +101 -0
  8. ngio/common/_zoom.py +54 -22
  9. ngio/experimental/__init__.py +5 -0
  10. ngio/experimental/iterators/__init__.py +15 -0
  11. ngio/experimental/iterators/_abstract_iterator.py +390 -0
  12. ngio/experimental/iterators/_feature.py +189 -0
  13. ngio/experimental/iterators/_image_processing.py +130 -0
  14. ngio/experimental/iterators/_mappers.py +48 -0
  15. ngio/experimental/iterators/_rois_utils.py +126 -0
  16. ngio/experimental/iterators/_segmentation.py +235 -0
  17. ngio/hcs/__init__.py +17 -58
  18. ngio/hcs/_plate.py +1354 -0
  19. ngio/images/__init__.py +30 -9
  20. ngio/images/_abstract_image.py +968 -0
  21. ngio/images/_create_synt_container.py +132 -0
  22. ngio/images/_create_utils.py +423 -0
  23. ngio/images/_image.py +926 -0
  24. ngio/images/_label.py +417 -0
  25. ngio/images/_masked_image.py +531 -0
  26. ngio/images/_ome_zarr_container.py +1235 -0
  27. ngio/images/_table_ops.py +471 -0
  28. ngio/io_pipes/__init__.py +75 -0
  29. ngio/io_pipes/_io_pipes.py +361 -0
  30. ngio/io_pipes/_io_pipes_masked.py +488 -0
  31. ngio/io_pipes/_io_pipes_roi.py +146 -0
  32. ngio/io_pipes/_io_pipes_types.py +56 -0
  33. ngio/io_pipes/_match_shape.py +377 -0
  34. ngio/io_pipes/_ops_axes.py +344 -0
  35. ngio/io_pipes/_ops_slices.py +411 -0
  36. ngio/io_pipes/_ops_slices_utils.py +199 -0
  37. ngio/io_pipes/_ops_transforms.py +104 -0
  38. ngio/io_pipes/_zoom_transform.py +180 -0
  39. ngio/ome_zarr_meta/__init__.py +39 -15
  40. ngio/ome_zarr_meta/_meta_handlers.py +490 -96
  41. ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
  42. ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
  43. ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
  44. ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
  45. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
  46. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
  47. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
  48. ngio/ome_zarr_meta/v04/__init__.py +21 -5
  49. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  50. ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
  51. ngio/ome_zarr_meta/v05/__init__.py +27 -0
  52. ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
  53. ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
  54. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  55. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  56. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  57. ngio/resources/__init__.py +55 -0
  58. ngio/resources/resource_model.py +36 -0
  59. ngio/tables/__init__.py +20 -4
  60. ngio/tables/_abstract_table.py +270 -0
  61. ngio/tables/_tables_container.py +449 -0
  62. ngio/tables/backends/__init__.py +50 -1
  63. ngio/tables/backends/_abstract_backend.py +200 -31
  64. ngio/tables/backends/_anndata.py +139 -0
  65. ngio/tables/backends/_anndata_utils.py +10 -114
  66. ngio/tables/backends/_csv.py +19 -0
  67. ngio/tables/backends/_json.py +92 -0
  68. ngio/tables/backends/_parquet.py +19 -0
  69. ngio/tables/backends/_py_arrow_backends.py +222 -0
  70. ngio/tables/backends/_table_backends.py +162 -38
  71. ngio/tables/backends/_utils.py +608 -0
  72. ngio/tables/v1/__init__.py +19 -4
  73. ngio/tables/v1/_condition_table.py +71 -0
  74. ngio/tables/v1/_feature_table.py +79 -115
  75. ngio/tables/v1/_generic_table.py +21 -90
  76. ngio/tables/v1/_roi_table.py +486 -137
  77. ngio/transforms/__init__.py +5 -0
  78. ngio/transforms/_zoom.py +19 -0
  79. ngio/utils/__init__.py +16 -14
  80. ngio/utils/_cache.py +48 -0
  81. ngio/utils/_datasets.py +121 -13
  82. ngio/utils/_fractal_fsspec_store.py +42 -0
  83. ngio/utils/_zarr_utils.py +374 -218
  84. ngio-0.5.0b4.dist-info/METADATA +147 -0
  85. ngio-0.5.0b4.dist-info/RECORD +88 -0
  86. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
  87. ngio/common/_array_pipe.py +0 -160
  88. ngio/common/_axes_transforms.py +0 -63
  89. ngio/common/_common_types.py +0 -5
  90. ngio/common/_slicer.py +0 -97
  91. ngio/images/abstract_image.py +0 -240
  92. ngio/images/create.py +0 -251
  93. ngio/images/image.py +0 -389
  94. ngio/images/label.py +0 -236
  95. ngio/images/omezarr_container.py +0 -535
  96. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  97. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  98. ngio/tables/_validators.py +0 -192
  99. ngio/tables/backends/_anndata_v1.py +0 -75
  100. ngio/tables/backends/_json_v1.py +0 -56
  101. ngio/tables/tables_container.py +0 -300
  102. ngio/tables/v1/_masking_roi_table.py +0 -175
  103. ngio/utils/_logger.py +0 -29
  104. ngio-0.2.0a2.dist-info/METADATA +0 -95
  105. ngio-0.2.0a2.dist-info/RECORD +0 -53
  106. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
@@ -3,13 +3,13 @@
3
3
  Stores the same information as the Omero section of the ngff 0.4 metadata.
4
4
  """
5
5
 
6
- from collections.abc import Collection
6
+ from collections.abc import Sequence
7
7
  from difflib import SequenceMatcher
8
8
  from enum import Enum
9
9
  from typing import Any, TypeVar
10
10
 
11
11
  import numpy as np
12
- from pydantic import BaseModel, ConfigDict, Field, field_validator
12
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
13
13
 
14
14
  from ngio.utils import NgioValidationError, NgioValueError
15
15
 
@@ -77,7 +77,8 @@ class NgioColors(str, Enum):
77
77
  # try to match the color to the channel name
78
78
  similarity[color] = SequenceMatcher(None, channel_name, color).ratio()
79
79
  # Get the color with the highest similarity
80
- color_str = max(similarity, key=similarity.get) # type: ignore
80
+ color_str = max(similarity, key=similarity.get) # type: ignore (max type overload fails to infer type)
81
+ assert isinstance(color_str, str), "Color name must be a string."
81
82
  return NgioColors.__members__[color_str]
82
83
 
83
84
 
@@ -101,6 +102,20 @@ def valid_hex_color(v: str) -> bool:
101
102
  return True
102
103
 
103
104
 
105
+ def into_valid_hex_color(v: str) -> str:
106
+ """Convert a string into a valid hexadecimal color.
107
+
108
+ If the string is already a valid hexadecimal color, return it.
109
+ Otherwise, return a hexadecimal color based on the hash of the string.
110
+ """
111
+ # strip leading '#' if present
112
+ v = v.lstrip("#")
113
+ if valid_hex_color(v):
114
+ return v
115
+
116
+ return NgioColors.semi_random_pick(v.lower()).value
117
+
118
+
104
119
  class ChannelVisualisation(BaseModel):
105
120
  """Channel visualisation model.
106
121
 
@@ -124,7 +139,6 @@ class ChannelVisualisation(BaseModel):
124
139
  model_config = ConfigDict(extra="allow", frozen=True)
125
140
 
126
141
  @field_validator("color", mode="after")
127
- @classmethod
128
142
  def validate_color(cls, value: str | NgioColors) -> str:
129
143
  """Color validator.
130
144
 
@@ -135,16 +149,27 @@ class ChannelVisualisation(BaseModel):
135
149
  """
136
150
  if value is None:
137
151
  return NgioColors.semi_random_pick().value
138
- if isinstance(value, str) and valid_hex_color(value):
139
- return value
152
+ if isinstance(value, str):
153
+ return into_valid_hex_color(value)
140
154
  elif isinstance(value, NgioColors):
141
155
  return value.value
142
- elif isinstance(value, str):
143
- value_lower = value.lower()
144
- return NgioColors.semi_random_pick(value_lower).value
145
156
  else:
146
157
  raise NgioValueError(f"Invalid color {value}.")
147
158
 
159
+ @model_validator(mode="before")
160
+ def check_start_end(cls, data):
161
+ """Check that the start and end values are valid.
162
+
163
+ If the start and end values are equal, set the end value to start + 1
164
+ """
165
+ start = data.get("start", None)
166
+ end = data.get("end", None)
167
+ if start is None or end is None:
168
+ return data
169
+ if abs(end - start) < 1e-6:
170
+ data["end"] = start + 1
171
+ return data
172
+
148
173
  @classmethod
149
174
  def default_init(
150
175
  cls,
@@ -261,7 +286,7 @@ class Channel(BaseModel):
261
286
  T = TypeVar("T")
262
287
 
263
288
 
264
- def _check_elements(elements: Collection[T], expected_type: Any) -> Collection[T]:
289
+ def _check_elements(elements: Sequence[T], expected_type: Any) -> Sequence[T]:
265
290
  """Check that the elements are of the same type."""
266
291
  if len(elements) == 0:
267
292
  raise NgioValidationError("At least one element must be provided.")
@@ -275,7 +300,7 @@ def _check_elements(elements: Collection[T], expected_type: Any) -> Collection[T
275
300
  return elements
276
301
 
277
302
 
278
- def _check_unique(elements: Collection[T]) -> Collection[T]:
303
+ def _check_unique(elements: Sequence[T]) -> Sequence[T]:
279
304
  """Check that the elements are unique."""
280
305
  if len(set(elements)) != len(elements):
281
306
  raise NgioValidationError("All elements must be unique.")
@@ -303,33 +328,35 @@ class ChannelsMeta(BaseModel):
303
328
  @classmethod
304
329
  def default_init(
305
330
  cls,
306
- labels: Collection[str] | int,
307
- wavelength_id: Collection[str] | None = None,
308
- colors: Collection[str | NgioColors] | None = None,
309
- start: Collection[int | float] | int | float | None = None,
310
- end: Collection[int | float] | int | float | None = None,
311
- active: Collection[bool] | None = None,
331
+ labels: Sequence[str | None] | int,
332
+ wavelength_id: Sequence[str | None] | None = None,
333
+ colors: Sequence[str | NgioColors | None] | None = None,
334
+ start: Sequence[int | float | None] | int | float | None = None,
335
+ end: Sequence[int | float | None] | int | float | None = None,
336
+ active: Sequence[bool | None] | None = None,
312
337
  data_type: Any = np.uint16,
313
338
  **omero_kwargs: dict,
314
339
  ) -> "ChannelsMeta":
315
340
  """Create a ChannelsMeta object with the default unit.
316
341
 
317
342
  Args:
318
- labels(Collection[str] | int): The list of channels names in the image.
319
- If an integer is provided, the channels will be named "channel_i".
320
- wavelength_id(Collection[str] | None): The wavelength ID of the channel.
321
- If None, the wavelength ID will be the same as the channel name.
322
- colors(Collection[str, NgioColors] | None): The list of colors for the
323
- channels. If None, the colors will be random.
324
- start(Collection[int | float] | int | float | None): The start value of the
325
- channel. If None, the start value will be the minimum value of the
326
- data type.
327
- end(Collection[int | float] | int | float | None): The end value of the
328
- channel. If None, the end value will be the maximum value of the
329
- data type.
343
+ labels(Sequence[str | None] | int): The list of channels names
344
+ in the image. If an integer is provided, the channels will be
345
+ named "channel_i".
346
+ wavelength_id(Sequence[str | None] | None): The wavelength ID of the
347
+ channel. If None, the wavelength ID will be the same as the
348
+ channel name.
349
+ colors(Sequence[str | NgioColors | None] | None): The list of
350
+ colors for the channels. If None, the colors will be random.
351
+ start(Sequence[int | float | None] | int | float | None): The start
352
+ value of the channel. If None, the start value will be the
353
+ minimum value of the data type.
354
+ end(Sequence[int | float | None] | int | float | None): The end
355
+ value of the channel. If None, the end value will be the
356
+ maximum value of the data type.
330
357
  data_type(Any): The data type of the channel. Will be used to set the
331
358
  min and max values of the channel.
332
- active (Collection[bool] | None):active(bool): Whether the channel should
359
+ active (Sequence[bool | None] | None): Whether the channel should
333
360
  be shown by default.
334
361
  omero_kwargs(dict): Extra fields to store in the omero attributes.
335
362
  """
@@ -339,26 +366,47 @@ class ChannelsMeta(BaseModel):
339
366
  labels = _check_elements(labels, str)
340
367
  labels = _check_unique(labels)
341
368
 
342
- _wavelength_id: Collection[str | None] = [None] * len(labels)
343
- if isinstance(wavelength_id, Collection):
369
+ _wavelength_id: Sequence[str | None] = [None] * len(labels)
370
+ if wavelength_id is None:
371
+ _wavelength_id: Sequence[str | None] = [None] * len(labels)
372
+ else:
344
373
  _wavelength_id = _check_elements(wavelength_id, str)
345
374
  _wavelength_id = _check_unique(wavelength_id)
346
375
 
347
- _colors: Collection[str | NgioColors] = ["random"] * len(labels)
348
- if isinstance(colors, Collection):
376
+ if colors is None:
377
+ _colors = [NgioColors.semi_random_pick(label) for label in labels]
378
+ else:
349
379
  _colors = _check_elements(colors, str | NgioColors)
350
380
 
351
- _start: Collection[int | float | None] = [None] * len(labels)
352
- if isinstance(start, Collection):
381
+ if start is None:
382
+ _start = [None] * len(labels)
383
+ elif isinstance(start, int | float):
384
+ _start = [start] * len(labels)
385
+ else:
353
386
  _start = _check_elements(start, (int, float))
354
387
 
355
- _end: Collection[int | float | None] = [None] * len(labels)
356
- if isinstance(end, Collection):
388
+ if end is None:
389
+ _end = [None] * len(labels)
390
+ elif isinstance(end, int | float):
391
+ _end = [end] * len(labels)
392
+ else:
357
393
  _end = _check_elements(end, (int, float))
358
394
 
359
- _active: Collection[bool] = [True] * len(labels)
360
- if isinstance(active, Collection):
361
- _active = _check_elements(active, bool)
395
+ if active is None:
396
+ _active = [True] * len(labels)
397
+ else:
398
+ _active = _check_elements(active, (bool,))
399
+
400
+ all_lengths = [
401
+ len(labels),
402
+ len(_wavelength_id),
403
+ len(_colors),
404
+ len(_start),
405
+ len(_end),
406
+ len(_active),
407
+ ]
408
+ if len(set(all_lengths)) != 1:
409
+ raise NgioValueError("Channels information must all have the same length.")
362
410
 
363
411
  channels = []
364
412
  for ch_name, w_id, color, s, e, a in zip(
@@ -376,3 +424,39 @@ class ChannelsMeta(BaseModel):
376
424
  )
377
425
  )
378
426
  return cls(channels=channels, **omero_kwargs)
427
+
428
+ @property
429
+ def channel_labels(self) -> list[str]:
430
+ """Get the labels of the channels in the image."""
431
+ return [channel.label for channel in self.channels]
432
+
433
+ @property
434
+ def channel_wavelength_ids(self) -> list[str | None]:
435
+ """Get the wavelength IDs of the channels in the image."""
436
+ return [channel.wavelength_id for channel in self.channels]
437
+
438
+ def get_channel_idx(
439
+ self, channel_label: str | None = None, wavelength_id: str | None = None
440
+ ) -> int:
441
+ """Get the index of a channel by its label or wavelength ID."""
442
+ # Only one of the arguments must be provided
443
+ if channel_label is not None and wavelength_id is not None:
444
+ raise NgioValueError(
445
+ "get_channel_idx must receive either label or wavelength_id, not both."
446
+ )
447
+
448
+ if channel_label is not None:
449
+ if channel_label not in self.channel_labels:
450
+ raise NgioValueError(f"Channel with label {channel_label} not found.")
451
+ return self.channel_labels.index(channel_label)
452
+
453
+ if wavelength_id is not None:
454
+ if wavelength_id not in self.channel_wavelength_ids:
455
+ raise NgioValueError(
456
+ f"Channel with wavelength ID {wavelength_id} not found."
457
+ )
458
+ return self.channel_wavelength_ids.index(wavelength_id)
459
+
460
+ raise NgioValueError(
461
+ "get_channel_idx must receive either label or wavelength_id"
462
+ )
@@ -1,84 +1,51 @@
1
1
  """Fractal internal module for dataset metadata handling."""
2
2
 
3
- from collections.abc import Collection
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
- SpaceUnits,
10
- TimeUnits,
6
+ AxesHandler,
11
7
  )
12
8
  from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
13
9
  from ngio.utils import NgioValidationError
14
10
 
15
11
 
16
12
  class Dataset:
17
- """Model for a dataset in the multiscale.
18
-
19
- To initialize the Dataset object, the path, the axes, scale, and translation list
20
- can be provided with on_disk order.
21
- """
13
+ """Model for a dataset in the multiscale."""
22
14
 
23
15
  def __init__(
24
16
  self,
25
17
  *,
26
18
  # args coming from ngff specs
27
19
  path: str,
28
- on_disk_axes: Collection[Axis],
29
- on_disk_scale: Collection[float],
30
- on_disk_translation: Collection[float] | None = None,
31
- # user defined args
32
- axes_setup: AxesSetup | None = None,
33
- allow_non_canonical_axes: bool = False,
34
- strict_canonical_order: bool = False,
20
+ axes_handler: AxesHandler,
21
+ scale: Sequence[float],
22
+ translation: Sequence[float] | None = None,
35
23
  ):
36
24
  """Initialize the Dataset object.
37
25
 
38
26
  Args:
39
27
  path (str): The path of the dataset.
40
- on_disk_axes (list[Axis]): The list of axes in the multiscale.
41
- 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.
42
30
  The scale transformation must have the same length as the axes.
43
- on_disk_translation (list[float] | None): The list of translation.
44
- axes_setup (AxesSetup): The axes setup object
45
- allow_non_canonical_axes (bool): Allow non-canonical axes.
46
- 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.
47
33
  """
48
34
  self._path = path
49
- self._axes_mapper = AxesMapper(
50
- on_disk_axes=on_disk_axes,
51
- axes_setup=axes_setup,
52
- allow_non_canonical_axes=allow_non_canonical_axes,
53
- strict_canonical_order=strict_canonical_order,
54
- )
35
+ self._axes_handler = axes_handler
55
36
 
56
- if len(on_disk_scale) != len(on_disk_axes):
37
+ if len(scale) != len(axes_handler.axes):
57
38
  raise NgioValidationError(
58
39
  "The length of the scale transformation must be the same as the axes."
59
40
  )
60
- self._on_disk_scale = list(on_disk_scale)
41
+ self._scale = list(scale)
61
42
 
62
- on_disk_translation = on_disk_translation or [0.0] * len(on_disk_axes)
63
- 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):
64
45
  raise NgioValidationError(
65
46
  "The length of the translation must be the same as the axes."
66
47
  )
67
- self._on_disk_translation = list(on_disk_translation)
68
-
69
- def get_scale(self, axis_name: str) -> float:
70
- """Return the scale for a given axis."""
71
- idx = self._axes_mapper.get_index(axis_name)
72
- if idx is None:
73
- return 1.0
74
- return self._on_disk_scale[idx]
75
-
76
- def get_translation(self, axis_name: str) -> float:
77
- """Return the translation for a given axis."""
78
- idx = self._axes_mapper.get_index(axis_name)
79
- if idx is None:
80
- return 0.0
81
- return self._on_disk_translation[idx]
48
+ self._translation = list(translation)
82
49
 
83
50
  @property
84
51
  def path(self) -> str:
@@ -86,49 +53,37 @@ class Dataset:
86
53
  return self._path
87
54
 
88
55
  @property
89
- def space_unit(self) -> SpaceUnits:
90
- """Return the space unit for a given axis."""
91
- x_axis = self._axes_mapper.get_axis("x")
92
- y_axis = self._axes_mapper.get_axis("y")
93
-
94
- if x_axis is None or y_axis is None:
95
- raise NgioValidationError(
96
- "The dataset must have x and y axes to determine the space unit."
97
- )
98
-
99
- if x_axis.unit == y_axis.unit:
100
- if not isinstance(x_axis.unit, SpaceUnits):
101
- raise NgioValidationError("The space unit must be of type SpaceUnits.")
102
- return x_axis.unit
103
- else:
104
- raise NgioValidationError(
105
- "Inconsistent space units. "
106
- f"x={x_axis.unit} and y={y_axis.unit} should have the same unit."
107
- )
108
-
109
- @property
110
- def time_unit(self) -> TimeUnits | None:
111
- """Return the time unit for a given axis."""
112
- t_axis = self._axes_mapper.get_axis("t")
113
- if t_axis is None:
114
- return None
115
- if not isinstance(t_axis.unit, TimeUnits):
116
- raise NgioValidationError("The time unit must be of type TimeUnits.")
117
- return t_axis.unit
56
+ def axes_handler(self) -> AxesHandler:
57
+ """Return the axes handler object."""
58
+ return self._axes_handler
118
59
 
119
60
  @property
120
61
  def pixel_size(self) -> PixelSize:
121
62
  """Return the pixel size for the dataset."""
63
+ scale = self._scale
64
+ pix_size_dict = {}
65
+ # Mandatory axes: x, y
66
+ for ax in ["x", "y"]:
67
+ index = self.axes_handler.get_index(ax)
68
+ assert index is not None
69
+ pix_size_dict[ax] = scale[index]
70
+
71
+ for ax in ["z", "t"]:
72
+ index = self.axes_handler.get_index(ax)
73
+ pix_size_dict[ax] = scale[index] if index is not None else 1.0
74
+
122
75
  return PixelSize(
123
- x=self.get_scale("x"),
124
- y=self.get_scale("y"),
125
- z=self.get_scale("z"),
126
- t=self.get_scale("t"),
127
- space_unit=self.space_unit,
128
- time_unit=self.time_unit,
76
+ **pix_size_dict,
77
+ space_unit=self.axes_handler.space_unit,
78
+ time_unit=self.axes_handler.time_unit,
129
79
  )
130
80
 
131
81
  @property
132
- def axes_mapper(self) -> AxesMapper:
133
- """Return the axes mapper object."""
134
- return self._axes_mapper
82
+ def scale(self) -> tuple[float, ...]:
83
+ """Return the scale transformation as a tuple."""
84
+ return tuple(self._scale)
85
+
86
+ @property
87
+ def translation(self) -> tuple[float, ...]:
88
+ """Return the translation as a tuple."""
89
+ return tuple(self._translation)