ngio 0.2.1__py3-none-any.whl → 0.2.3__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 (38) hide show
  1. ngio/__init__.py +20 -2
  2. ngio/common/_pyramid.py +5 -1
  3. ngio/common/_roi.py +2 -2
  4. ngio/hcs/__init__.py +16 -2
  5. ngio/hcs/plate.py +496 -18
  6. ngio/images/abstract_image.py +11 -0
  7. ngio/images/create.py +25 -36
  8. ngio/images/image.py +80 -6
  9. ngio/images/label.py +38 -9
  10. ngio/images/ome_zarr_container.py +70 -33
  11. ngio/ome_zarr_meta/__init__.py +5 -3
  12. ngio/ome_zarr_meta/ngio_specs/__init__.py +10 -2
  13. ngio/ome_zarr_meta/ngio_specs/_axes.py +90 -65
  14. ngio/ome_zarr_meta/ngio_specs/_dataset.py +46 -8
  15. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +242 -70
  16. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +49 -11
  17. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +28 -11
  18. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  19. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +2 -2
  20. ngio/tables/_validators.py +1 -83
  21. ngio/tables/backends/__init__.py +27 -1
  22. ngio/tables/backends/_abstract_backend.py +207 -22
  23. ngio/tables/backends/_anndata_utils.py +3 -109
  24. ngio/tables/backends/_anndata_v1.py +43 -46
  25. ngio/tables/backends/_csv_v1.py +162 -0
  26. ngio/tables/backends/_json_v1.py +54 -18
  27. ngio/tables/backends/_table_backends.py +98 -18
  28. ngio/tables/backends/_utils.py +458 -0
  29. ngio/tables/tables_container.py +3 -1
  30. ngio/tables/v1/_feature_table.py +20 -11
  31. ngio/tables/v1/_generic_table.py +20 -15
  32. ngio/tables/v1/_roi_table.py +7 -9
  33. ngio/utils/_zarr_utils.py +46 -32
  34. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/METADATA +3 -1
  35. ngio-0.2.3.dist-info/RECORD +57 -0
  36. ngio-0.2.1.dist-info/RECORD +0 -54
  37. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/WHEEL +0 -0
  38. {ngio-0.2.1.dist-info → ngio-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ from typing import Literal, overload
5
5
 
6
6
  import numpy as np
7
7
 
8
- from ngio.images.create import _create_empty_image
8
+ from ngio.images.create import create_empty_image_container
9
9
  from ngio.images.image import Image, ImagesContainer
10
10
  from ngio.images.label import Label, LabelsContainer
11
11
  from ngio.images.masked_image import MaskedImage, MaskedLabel
@@ -13,7 +13,14 @@ from ngio.ome_zarr_meta import (
13
13
  NgioImageMeta,
14
14
  PixelSize,
15
15
  )
16
- from ngio.ome_zarr_meta.ngio_specs import NgffVersion, SpaceUnits, TimeUnits
16
+ from ngio.ome_zarr_meta.ngio_specs import (
17
+ DefaultNgffVersion,
18
+ DefaultSpaceUnit,
19
+ DefaultTimeUnit,
20
+ NgffVersions,
21
+ SpaceUnits,
22
+ TimeUnits,
23
+ )
17
24
  from ngio.tables import (
18
25
  FeatureTable,
19
26
  GenericRoiTable,
@@ -152,7 +159,17 @@ class OmeZarrContainer:
152
159
  """Return True if the image is multichannel."""
153
160
  return self.get_image().is_multi_channels
154
161
 
155
- def initialize_channel_meta(
162
+ @property
163
+ def space_unit(self) -> str | None:
164
+ """Return the space unit of the image."""
165
+ return self.image_meta.space_unit
166
+
167
+ @property
168
+ def time_unit(self) -> str | None:
169
+ """Return the time unit of the image."""
170
+ return self.image_meta.time_unit
171
+
172
+ def set_channel_meta(
156
173
  self,
157
174
  labels: Collection[str] | int | None = None,
158
175
  wavelength_id: Collection[str] | None = None,
@@ -162,7 +179,7 @@ class OmeZarrContainer:
162
179
  **omero_kwargs: dict,
163
180
  ) -> None:
164
181
  """Create a ChannelsMeta object with the default unit."""
165
- self._images_container.initialize_channel_meta(
182
+ self._images_container.set_channel_meta(
166
183
  labels=labels,
167
184
  wavelength_id=wavelength_id,
168
185
  percentiles=percentiles,
@@ -171,16 +188,36 @@ class OmeZarrContainer:
171
188
  **omero_kwargs,
172
189
  )
173
190
 
174
- def update_percentiles(
191
+ def set_channel_percentiles(
175
192
  self,
176
193
  start_percentile: float = 0.1,
177
194
  end_percentile: float = 99.9,
178
195
  ) -> None:
179
196
  """Update the percentiles of the image."""
180
- self._images_container.update_percentiles(
197
+ self._images_container.set_channel_percentiles(
181
198
  start_percentile=start_percentile, end_percentile=end_percentile
182
199
  )
183
200
 
201
+ def set_axes_units(
202
+ self,
203
+ space_unit: SpaceUnits = DefaultSpaceUnit,
204
+ time_unit: TimeUnits = DefaultTimeUnit,
205
+ set_labels: bool = True,
206
+ ) -> None:
207
+ """Set the units of the image.
208
+
209
+ Args:
210
+ space_unit (SpaceUnits): The unit of space.
211
+ time_unit (TimeUnits): The unit of time.
212
+ set_labels (bool): Whether to set the units for the labels as well.
213
+ """
214
+ self._images_container.set_axes_unit(space_unit=space_unit, time_unit=time_unit)
215
+ if not set_labels:
216
+ return
217
+ for label_name in self.list_labels():
218
+ label = self.get_label(label_name)
219
+ label.set_axes_unit(space_unit=space_unit, time_unit=time_unit)
220
+
184
221
  def get_image(
185
222
  self,
186
223
  path: str | None = None,
@@ -247,6 +284,7 @@ class OmeZarrContainer:
247
284
  labels: Collection[str] | None = None,
248
285
  pixel_size: PixelSize | None = None,
249
286
  axes_names: Collection[str] | None = None,
287
+ name: str | None = None,
250
288
  chunks: Collection[int] | None = None,
251
289
  dtype: str | None = None,
252
290
  copy_labels: bool = False,
@@ -265,6 +303,7 @@ class OmeZarrContainer:
265
303
  axes_names (Collection[str] | None): The axes names of the new image.
266
304
  chunks (Collection[int] | None): The chunk shape of the new image.
267
305
  dtype (str | None): The data type of the new image.
306
+ name (str | None): The name of the new image.
268
307
  copy_labels (bool): Whether to copy the labels from the reference image.
269
308
  copy_tables (bool): Whether to copy the tables from the reference image.
270
309
  overwrite (bool): Whether to overwrite an existing image.
@@ -280,6 +319,7 @@ class OmeZarrContainer:
280
319
  labels=labels,
281
320
  pixel_size=pixel_size,
282
321
  axes_names=axes_names,
322
+ name=name,
283
323
  chunks=chunks,
284
324
  dtype=dtype,
285
325
  overwrite=overwrite,
@@ -462,7 +502,7 @@ class OmeZarrContainer:
462
502
  def derive_label(
463
503
  self,
464
504
  name: str,
465
- ref_image: Image | None = None,
505
+ ref_image: Image | Label | None = None,
466
506
  shape: Collection[int] | None = None,
467
507
  pixel_size: PixelSize | None = None,
468
508
  axes_names: Collection[str] | None = None,
@@ -475,9 +515,9 @@ class OmeZarrContainer:
475
515
  And add the label to the /labels group.
476
516
 
477
517
  Args:
478
- store (StoreOrGroup): The Zarr store or group to create the image in.
479
- ref_image (Image): The reference image.
480
518
  name (str): The name of the new image.
519
+ ref_image (Image | Label | None): A reference image that will be used
520
+ to create the new image.
481
521
  shape (Collection[int] | None): The shape of the new image.
482
522
  pixel_size (PixelSize | None): The pixel size of the new image.
483
523
  axes_names (Collection[str] | None): The axes names of the new image.
@@ -557,19 +597,18 @@ def create_empty_ome_zarr(
557
597
  levels: int | list[str] = 5,
558
598
  xy_scaling_factor: float = 2,
559
599
  z_scaling_factor: float = 1.0,
560
- space_unit: SpaceUnits | str | None = None,
561
- time_unit: TimeUnits | str | None = None,
600
+ space_unit: SpaceUnits = DefaultSpaceUnit,
601
+ time_unit: TimeUnits = DefaultTimeUnit,
562
602
  axes_names: Collection[str] | None = None,
563
603
  name: str | None = None,
564
604
  chunks: Collection[int] | None = None,
565
605
  dtype: str = "uint16",
566
606
  channel_labels: list[str] | None = None,
567
607
  channel_wavelengths: list[str] | None = None,
568
- percentiles: tuple[float, float] | None = None,
569
608
  channel_colors: Collection[str] | None = None,
570
609
  channel_active: Collection[bool] | None = None,
571
610
  overwrite: bool = False,
572
- version: NgffVersion = "0.4",
611
+ version: NgffVersions = DefaultNgffVersion,
573
612
  ) -> OmeZarrContainer:
574
613
  """Create an empty OME-Zarr image with the given shape and metadata.
575
614
 
@@ -586,10 +625,10 @@ def create_empty_ome_zarr(
586
625
  dimensions. Defaults to 2.0.
587
626
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
588
627
  Defaults to 1.0.
589
- space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
590
- None.
591
- time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
592
- None.
628
+ space_unit (SpaceUnits, optional): The unit of space. Defaults to
629
+ DefaultSpaceUnit.
630
+ time_unit (TimeUnits, optional): The unit of time. Defaults to
631
+ DefaultTimeUnit.
593
632
  axes_names (Collection[str] | None, optional): The names of the axes.
594
633
  If None the canonical names are used. Defaults to None.
595
634
  name (str | None, optional): The name of the image. Defaults to None.
@@ -600,8 +639,6 @@ def create_empty_ome_zarr(
600
639
  Defaults to None.
601
640
  channel_wavelengths (list[str] | None, optional): The wavelengths of the
602
641
  channels. Defaults to None.
603
- percentiles (tuple[float, float] | None, optional): The percentiles of the
604
- channels. Defaults to None.
605
642
  channel_colors (Collection[str] | None, optional): The colors of the channels.
606
643
  Defaults to None.
607
644
  channel_active (Collection[bool] | None, optional): Whether the channels are
@@ -609,9 +646,9 @@ def create_empty_ome_zarr(
609
646
  overwrite (bool, optional): Whether to overwrite an existing image.
610
647
  Defaults to True.
611
648
  version (NgffVersion, optional): The version of the OME-Zarr specification.
612
- Defaults to "0.4".
649
+ Defaults to DefaultNgffVersion.
613
650
  """
614
- handler = _create_empty_image(
651
+ handler = create_empty_image_container(
615
652
  store=store,
616
653
  shape=shape,
617
654
  pixelsize=xy_pixelsize,
@@ -631,10 +668,10 @@ def create_empty_ome_zarr(
631
668
  )
632
669
 
633
670
  ome_zarr = OmeZarrContainer(group_handler=handler)
634
- ome_zarr.initialize_channel_meta(
671
+ ome_zarr.set_channel_meta(
635
672
  labels=channel_labels,
636
673
  wavelength_id=channel_wavelengths,
637
- percentiles=percentiles,
674
+ percentiles=None,
638
675
  colors=channel_colors,
639
676
  active=channel_active,
640
677
  )
@@ -650,8 +687,8 @@ def create_ome_zarr_from_array(
650
687
  levels: int | list[str] = 5,
651
688
  xy_scaling_factor: float = 2.0,
652
689
  z_scaling_factor: float = 1.0,
653
- space_unit: SpaceUnits | str | None = None,
654
- time_unit: TimeUnits | str | None = None,
690
+ space_unit: SpaceUnits = DefaultSpaceUnit,
691
+ time_unit: TimeUnits = DefaultTimeUnit,
655
692
  axes_names: Collection[str] | None = None,
656
693
  channel_labels: list[str] | None = None,
657
694
  channel_wavelengths: list[str] | None = None,
@@ -661,7 +698,7 @@ def create_ome_zarr_from_array(
661
698
  name: str | None = None,
662
699
  chunks: Collection[int] | None = None,
663
700
  overwrite: bool = False,
664
- version: NgffVersion = "0.4",
701
+ version: NgffVersions = DefaultNgffVersion,
665
702
  ) -> OmeZarrContainer:
666
703
  """Create an OME-Zarr image from a numpy array.
667
704
 
@@ -678,10 +715,10 @@ def create_ome_zarr_from_array(
678
715
  dimensions. Defaults to 2.0.
679
716
  z_scaling_factor (float, optional): The down-scaling factor in z dimension.
680
717
  Defaults to 1.0.
681
- space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
682
- None.
683
- time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
684
- None.
718
+ space_unit (SpaceUnits, optional): The unit of space. Defaults to
719
+ DefaultSpaceUnit.
720
+ time_unit (TimeUnits, optional): The unit of time. Defaults to
721
+ DefaultTimeUnit.
685
722
  axes_names (Collection[str] | None, optional): The names of the axes.
686
723
  If None the canonical names are used. Defaults to None.
687
724
  name (str | None, optional): The name of the image. Defaults to None.
@@ -700,9 +737,9 @@ def create_ome_zarr_from_array(
700
737
  overwrite (bool, optional): Whether to overwrite an existing image.
701
738
  Defaults to True.
702
739
  version (str, optional): The version of the OME-Zarr specification.
703
- Defaults to "0.4".
740
+ Defaults to DefaultNgffVersion.
704
741
  """
705
- handler = _create_empty_image(
742
+ handler = create_empty_image_container(
706
743
  store=store,
707
744
  shape=array.shape,
708
745
  pixelsize=xy_pixelsize,
@@ -725,7 +762,7 @@ def create_ome_zarr_from_array(
725
762
  image = ome_zarr.get_image()
726
763
  image.set_array(array)
727
764
  image.consolidate()
728
- ome_zarr.initialize_channel_meta(
765
+ ome_zarr.set_channel_meta(
729
766
  labels=channel_labels,
730
767
  wavelength_id=channel_wavelengths,
731
768
  percentiles=percentiles,
@@ -16,12 +16,13 @@ from ngio.ome_zarr_meta.ngio_specs import (
16
16
  AxesMapper,
17
17
  Dataset,
18
18
  ImageInWellPath,
19
- NgffVersion,
19
+ NgffVersions,
20
20
  NgioImageMeta,
21
21
  NgioLabelMeta,
22
22
  NgioPlateMeta,
23
23
  NgioWellMeta,
24
24
  PixelSize,
25
+ path_in_well_validation,
25
26
  )
26
27
 
27
28
  __all__ = [
@@ -32,8 +33,8 @@ __all__ = [
32
33
  "ImageMetaHandler",
33
34
  "LabelMetaHandler",
34
35
  "LabelMetaHandler",
35
- "NgffVersion",
36
- "NgffVersion",
36
+ "NgffVersions",
37
+ "NgffVersions",
37
38
  "NgioImageMeta",
38
39
  "NgioLabelMeta",
39
40
  "NgioPlateMeta",
@@ -47,4 +48,5 @@ __all__ = [
47
48
  "get_label_meta_handler",
48
49
  "get_plate_meta_handler",
49
50
  "get_well_meta_handler",
51
+ "path_in_well_validation",
50
52
  ]
@@ -15,6 +15,8 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
15
15
  AxesTranspose,
16
16
  Axis,
17
17
  AxisType,
18
+ DefaultSpaceUnit,
19
+ DefaultTimeUnit,
18
20
  SpaceUnits,
19
21
  TimeUnits,
20
22
  canonical_axes_order,
@@ -32,10 +34,12 @@ from ngio.ome_zarr_meta.ngio_specs._ngio_hcs import (
32
34
  ImageInWellPath,
33
35
  NgioPlateMeta,
34
36
  NgioWellMeta,
37
+ path_in_well_validation,
35
38
  )
36
39
  from ngio.ome_zarr_meta.ngio_specs._ngio_image import (
40
+ DefaultNgffVersion,
37
41
  ImageLabelSource,
38
- NgffVersion,
42
+ NgffVersions,
39
43
  NgioImageLabelMeta,
40
44
  NgioImageMeta,
41
45
  NgioLabelMeta,
@@ -55,9 +59,12 @@ __all__ = [
55
59
  "ChannelVisualisation",
56
60
  "ChannelsMeta",
57
61
  "Dataset",
62
+ "DefaultNgffVersion",
63
+ "DefaultSpaceUnit",
64
+ "DefaultTimeUnit",
58
65
  "ImageInWellPath",
59
66
  "ImageLabelSource",
60
- "NgffVersion",
67
+ "NgffVersions",
61
68
  "NgioColors",
62
69
  "NgioImageLabelMeta",
63
70
  "NgioImageMeta",
@@ -70,4 +77,5 @@ __all__ = [
70
77
  "canonical_axes_order",
71
78
  "canonical_label_axes_order",
72
79
  "default_channel_name",
80
+ "path_in_well_validation",
73
81
  ]
@@ -3,7 +3,7 @@
3
3
  from collections.abc import Collection
4
4
  from enum import Enum
5
5
  from logging import Logger
6
- from typing import TypeVar
6
+ from typing import Literal, TypeVar
7
7
 
8
8
  import numpy as np
9
9
  from pydantic import BaseModel, ConfigDict, Field
@@ -32,98 +32,108 @@ class AxisType(str, Enum):
32
32
  space = "space"
33
33
 
34
34
 
35
- class SpaceUnits(str, Enum):
36
- """Allowed space units."""
37
-
38
- nanometer = "nanometer"
39
- nm = "nm"
40
- micrometer = "micrometer"
41
- um = "um"
42
- millimeter = "millimeter"
43
- mm = "mm"
44
- centimeter = "centimeter"
45
- cm = "cm"
46
-
47
- @classmethod
48
- def default(cls) -> "SpaceUnits":
49
- return SpaceUnits.um
50
-
51
-
52
- class TimeUnits(str, Enum):
53
- """Allowed time units."""
54
-
55
- seconds = "seconds"
56
- s = "s"
57
-
58
- @classmethod
59
- def default(cls) -> "TimeUnits":
60
- return TimeUnits.s
35
+ SpaceUnits = Literal[
36
+ "micrometer",
37
+ "nanometer",
38
+ "angstrom",
39
+ "picometer",
40
+ "millimeter",
41
+ "centimeter",
42
+ "decimeter",
43
+ "meter",
44
+ "inch",
45
+ "foot",
46
+ "yard",
47
+ "mile",
48
+ "kilometer",
49
+ "hectometer",
50
+ "megameter",
51
+ "gigameter",
52
+ "terameter",
53
+ "petameter",
54
+ "exameter",
55
+ "parsec",
56
+ "femtometer",
57
+ "attometer",
58
+ "zeptometer",
59
+ "yoctometer",
60
+ "zettameter",
61
+ "yottameter",
62
+ ]
63
+ DefaultSpaceUnit = "micrometer"
64
+
65
+ TimeUnits = Literal[
66
+ "attosecond",
67
+ "centisecond",
68
+ "day",
69
+ "decisecond",
70
+ "exasecond",
71
+ "femtosecond",
72
+ "gigasecond",
73
+ "hectosecond",
74
+ "hour",
75
+ "kilosecond",
76
+ "megasecond",
77
+ "microsecond",
78
+ "millisecond",
79
+ "minute",
80
+ "nanosecond",
81
+ "petasecond",
82
+ "picosecond",
83
+ "second",
84
+ "terasecond",
85
+ "yoctosecond",
86
+ "yottasecond",
87
+ "zeptosecond",
88
+ "zettasecond",
89
+ ]
90
+ DefaultTimeUnit = "second"
61
91
 
62
92
 
63
93
  class Axis(BaseModel):
64
94
  """Axis infos model."""
65
95
 
66
96
  on_disk_name: str
67
- unit: SpaceUnits | TimeUnits | None = None
97
+ unit: str | None = None
68
98
  axis_type: AxisType | None = None
69
99
 
70
100
  model_config = ConfigDict(extra="forbid", frozen=True)
71
101
 
72
102
  def implicit_type_cast(self, cast_type: AxisType) -> "Axis":
103
+ unit = self.unit
73
104
  if self.axis_type != cast_type:
74
105
  logger.warning(
75
106
  f"Axis {self.on_disk_name} has type {self.axis_type}. "
76
107
  f"Casting to {cast_type}."
77
108
  )
78
- new_axis = Axis(
79
- on_disk_name=self.on_disk_name, axis_type=cast_type, unit=self.unit
80
- )
81
- if cast_type == AxisType.time and not isinstance(self.unit, TimeUnits):
109
+
110
+ if cast_type == AxisType.time and unit is None:
82
111
  logger.warning(
83
112
  f"Time axis {self.on_disk_name} has unit {self.unit}. "
84
- f"Casting to {TimeUnits.default()}."
85
- )
86
- new_axis = Axis(
87
- on_disk_name=self.on_disk_name,
88
- axis_type=AxisType.time,
89
- unit=TimeUnits.default(),
90
- )
91
- elif cast_type == AxisType.space and not isinstance(self.unit, SpaceUnits):
92
- logger.warning(
93
- f"Space axis {self.on_disk_name} has unit {self.unit}. "
94
- f"Casting to {SpaceUnits.default()}."
113
+ f"Casting to {DefaultSpaceUnit}."
95
114
  )
96
- new_axis = Axis(
97
- on_disk_name=self.on_disk_name,
98
- axis_type=AxisType.space,
99
- unit=SpaceUnits.default(),
100
- )
101
- elif cast_type == AxisType.channel and self.unit is not None:
115
+ unit = DefaultTimeUnit
116
+
117
+ if cast_type == AxisType.space and unit is None:
102
118
  logger.warning(
103
- f"Channel axis {self.on_disk_name} has unit {self.unit}. Removing unit."
104
- )
105
- new_axis = Axis(
106
- on_disk_name=self.on_disk_name,
107
- axis_type=AxisType.channel,
108
- unit=None,
119
+ f"Space axis {self.on_disk_name} has unit {unit}. "
120
+ f"Casting to {DefaultSpaceUnit}."
109
121
  )
110
- return new_axis
122
+ unit = DefaultSpaceUnit
123
+
124
+ return Axis(on_disk_name=self.on_disk_name, axis_type=cast_type, unit=unit)
111
125
 
112
126
  def canonical_axis_cast(self, canonical_name: str) -> "Axis":
113
127
  """Cast the implicit axis to the correct type."""
114
128
  match canonical_name:
115
129
  case "t":
116
- if self.axis_type != AxisType.time or not isinstance(
117
- self.unit, TimeUnits
118
- ):
130
+ if self.axis_type != AxisType.time or self.unit is None:
119
131
  return self.implicit_type_cast(AxisType.time)
120
132
  case "c":
121
- if self.axis_type != AxisType.channel or self.unit is not None:
133
+ if self.axis_type != AxisType.channel:
122
134
  return self.implicit_type_cast(AxisType.channel)
123
135
  case "z" | "y" | "x":
124
- if self.axis_type != AxisType.space or not isinstance(
125
- self.unit, SpaceUnits
126
- ):
136
+ if self.axis_type != AxisType.space or self.unit is None:
127
137
  return self.implicit_type_cast(AxisType.space)
128
138
  return self
129
139
 
@@ -345,6 +355,11 @@ class AxesMapper:
345
355
  _index_mapping[canonical_key] = None
346
356
  return _index_mapping
347
357
 
358
+ @property
359
+ def axes_setup(self) -> AxesSetup:
360
+ """Return the axes setup."""
361
+ return self._axes_setup
362
+
348
363
  @property
349
364
  def on_disk_axes(self) -> list[Axis]:
350
365
  return list(self._on_disk_axes)
@@ -353,6 +368,16 @@ class AxesMapper:
353
368
  def on_disk_axes_names(self) -> list[str]:
354
369
  return [ax.on_disk_name for ax in self._on_disk_axes]
355
370
 
371
+ @property
372
+ def allow_non_canonical_axes(self) -> bool:
373
+ """Return if non canonical axes are allowed."""
374
+ return self._allow_non_canonical_axes
375
+
376
+ @property
377
+ def strict_canonical_order(self) -> bool:
378
+ """Return if strict canonical order is enforced."""
379
+ return self._strict_canonical_order
380
+
356
381
  def get_index(self, name: str) -> int | None:
357
382
  """Get the index of the axis by name."""
358
383
  if name not in self._index_mapping.keys():
@@ -443,8 +468,8 @@ class AxesMapper:
443
468
 
444
469
  def canonical_axes(
445
470
  axes_names: Collection[str],
446
- space_units: SpaceUnits | None = None,
447
- time_units: TimeUnits | None = None,
471
+ space_units: SpaceUnits | None = DefaultSpaceUnit,
472
+ time_units: TimeUnits | None = DefaultTimeUnit,
448
473
  ) -> list[Axis]:
449
474
  """Create a new canonical axes mapper.
450
475
 
@@ -6,6 +6,9 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
6
6
  AxesMapper,
7
7
  AxesSetup,
8
8
  Axis,
9
+ AxisType,
10
+ DefaultSpaceUnit,
11
+ DefaultTimeUnit,
9
12
  SpaceUnits,
10
13
  TimeUnits,
11
14
  )
@@ -86,7 +89,7 @@ class Dataset:
86
89
  return self._path
87
90
 
88
91
  @property
89
- def space_unit(self) -> SpaceUnits:
92
+ def space_unit(self) -> str | None:
90
93
  """Return the space unit for a given axis."""
91
94
  x_axis = self._axes_mapper.get_axis("x")
92
95
  y_axis = self._axes_mapper.get_axis("y")
@@ -97,8 +100,6 @@ class Dataset:
97
100
  )
98
101
 
99
102
  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
103
  return x_axis.unit
103
104
  else:
104
105
  raise NgioValidationError(
@@ -107,13 +108,11 @@ class Dataset:
107
108
  )
108
109
 
109
110
  @property
110
- def time_unit(self) -> TimeUnits | None:
111
+ def time_unit(self) -> str | None:
111
112
  """Return the time unit for a given axis."""
112
113
  t_axis = self._axes_mapper.get_axis("t")
113
114
  if t_axis is None:
114
115
  return None
115
- if not isinstance(t_axis.unit, TimeUnits):
116
- raise NgioValidationError("The time unit must be of type TimeUnits.")
117
116
  return t_axis.unit
118
117
 
119
118
  @property
@@ -124,11 +123,50 @@ class Dataset:
124
123
  y=self.get_scale("y"),
125
124
  z=self.get_scale("z"),
126
125
  t=self.get_scale("t"),
127
- space_unit=self.space_unit,
128
- time_unit=self.time_unit,
126
+ space_unit=self.space_unit, # type: ignore
127
+ time_unit=self.time_unit, # type: ignore
129
128
  )
130
129
 
131
130
  @property
132
131
  def axes_mapper(self) -> AxesMapper:
133
132
  """Return the axes mapper object."""
134
133
  return self._axes_mapper
134
+
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.
142
+
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.on_disk_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)
163
+
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
+ )