ngio 0.2.1__py3-none-any.whl → 0.2.2__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.
@@ -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
+ )