ngio 0.5.0b6__py3-none-any.whl → 0.5.1__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/common/_masking_roi.py +18 -5
- ngio/common/_pyramid.py +129 -30
- ngio/hcs/_plate.py +30 -24
- ngio/images/_abstract_image.py +182 -60
- ngio/images/_create_synt_container.py +5 -1
- ngio/images/_create_utils.py +69 -74
- ngio/images/_image.py +350 -86
- ngio/images/_label.py +39 -31
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +263 -96
- ngio/io_pipes/_match_shape.py +10 -14
- ngio/io_pipes/_ops_slices.py +6 -4
- ngio/io_pipes/_ops_slices_utils.py +8 -7
- ngio/ome_zarr_meta/_meta_handlers.py +2 -26
- ngio/ome_zarr_meta/ngio_specs/__init__.py +2 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +161 -58
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +78 -32
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +36 -0
- ngio/ome_zarr_meta/v04/_v04_spec.py +5 -22
- ngio/ome_zarr_meta/v05/_v05_spec.py +7 -23
- ngio/resources/__init__.py +1 -1
- ngio/resources/resource_model.py +1 -1
- ngio/tables/_tables_container.py +39 -7
- ngio/tables/v1/_roi_table.py +4 -4
- ngio/utils/_zarr_utils.py +8 -15
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/METADATA +3 -2
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/RECORD +29 -29
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/WHEEL +0 -0
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/licenses/LICENSE +0 -0
ngio/io_pipes/_match_shape.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
2
|
from collections.abc import Sequence
|
|
3
3
|
from enum import Enum
|
|
4
4
|
|
|
@@ -7,6 +7,8 @@ import numpy as np
|
|
|
7
7
|
|
|
8
8
|
from ngio.utils import NgioValueError
|
|
9
9
|
|
|
10
|
+
logger = logging.getLogger(f"ngio:{__name__}")
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class Action(str, Enum):
|
|
12
14
|
NONE = "none"
|
|
@@ -29,10 +31,9 @@ def _compute_pad_widths(
|
|
|
29
31
|
pad_def.append((before, after))
|
|
30
32
|
else:
|
|
31
33
|
pad_def.append((0, 0))
|
|
32
|
-
|
|
34
|
+
logger.warning(
|
|
33
35
|
f"Images have a different shape ({array_shape} vs {target_shape}). "
|
|
34
|
-
f"Resolving by padding: {pad_def}"
|
|
35
|
-
stacklevel=2,
|
|
36
|
+
f"Resolving by padding: {pad_def}"
|
|
36
37
|
)
|
|
37
38
|
return tuple(pad_def)
|
|
38
39
|
|
|
@@ -76,10 +77,9 @@ def _compute_trim_slices(
|
|
|
76
77
|
else:
|
|
77
78
|
slices.append(slice(0, s))
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
logger.warning(
|
|
80
81
|
f"Images have a different shape ({array_shape} vs {target_shape}). "
|
|
81
|
-
f"Resolving by trimming: {slices}"
|
|
82
|
-
stacklevel=2,
|
|
82
|
+
f"Resolving by trimming: {slices}"
|
|
83
83
|
)
|
|
84
84
|
return tuple(slices)
|
|
85
85
|
|
|
@@ -118,10 +118,9 @@ def _compute_rescaling_shape(
|
|
|
118
118
|
rescaling_shape.append(s)
|
|
119
119
|
factor.append(1.0)
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
logger.warning(
|
|
122
122
|
f"Images have a different shape ({array_shape} vs {target_shape}). "
|
|
123
|
-
f"Resolving by scaling with factors {factor}."
|
|
124
|
-
stacklevel=2,
|
|
123
|
+
f"Resolving by scaling with factors {factor}."
|
|
125
124
|
)
|
|
126
125
|
return tuple(rescaling_shape)
|
|
127
126
|
|
|
@@ -266,14 +265,11 @@ def numpy_match_shape(
|
|
|
266
265
|
reference_axes (Sequence[str]): The axes names of the reference shape.
|
|
267
266
|
tolerance (int): The maximum number of pixels by which dimensions
|
|
268
267
|
can differ when matching shapes.
|
|
269
|
-
allow_broadcast (bool): If True, allow broadcasting new dimensions to
|
|
270
|
-
match the reference shape. If False, single-dimension axes will
|
|
271
|
-
be left as is.
|
|
272
268
|
pad_mode (str): The mode to use for padding. See numpy.pad for options.
|
|
273
269
|
pad_values (int | float): The constant value to use for padding if
|
|
274
270
|
pad_mode is 'constant'.
|
|
275
271
|
allow_rescaling (bool): If True, when the array differs more than the
|
|
276
|
-
tolerance, it will be
|
|
272
|
+
tolerance, it will be rescaled to the reference shape. If False,
|
|
277
273
|
an error will be raised.
|
|
278
274
|
"""
|
|
279
275
|
_check_axes(
|
ngio/io_pipes/_ops_slices.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import math
|
|
2
3
|
from collections.abc import Mapping, Sequence
|
|
3
4
|
from typing import TypeAlias, assert_never
|
|
4
|
-
from warnings import warn
|
|
5
5
|
|
|
6
6
|
import dask.array as da
|
|
7
7
|
import numpy as np
|
|
@@ -13,6 +13,9 @@ from ngio.io_pipes._ops_slices_utils import compute_slice_chunks
|
|
|
13
13
|
from ngio.ome_zarr_meta.ngio_specs import Axis
|
|
14
14
|
from ngio.utils import NgioValueError
|
|
15
15
|
|
|
16
|
+
logger = logging.getLogger(f"ngio:{__name__}")
|
|
17
|
+
|
|
18
|
+
|
|
16
19
|
SlicingInputType: TypeAlias = slice | Sequence[int] | int | None
|
|
17
20
|
SlicingType: TypeAlias = slice | list[int] | int
|
|
18
21
|
|
|
@@ -141,12 +144,11 @@ def _check_list_in_slicing_tuple(
|
|
|
141
144
|
# Complex case, we have exactly one tuple in the slicing tuple
|
|
142
145
|
ax, first_tuple = list_in_slice[0]
|
|
143
146
|
if len(first_tuple) > 100:
|
|
144
|
-
|
|
147
|
+
logger.warning(
|
|
145
148
|
"Performance warning: "
|
|
146
149
|
"Non-contiguous slicing with a tuple/list with more than 100 elements is "
|
|
147
150
|
"not natively supported by zarr. This is implemented by Ngio by performing "
|
|
148
|
-
"multiple reads and stacking the result."
|
|
149
|
-
stacklevel=2,
|
|
151
|
+
"multiple reads and stacking the result."
|
|
150
152
|
)
|
|
151
153
|
return ax, first_tuple
|
|
152
154
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
2
|
from collections.abc import Iterable, Iterator
|
|
3
3
|
from itertools import product
|
|
4
4
|
from typing import TypeAlias, TypeVar
|
|
5
5
|
|
|
6
6
|
from ngio.utils import NgioValueError
|
|
7
7
|
|
|
8
|
+
logger = logging.getLogger(f"ngio:{__name__}")
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
T = TypeVar("T")
|
|
9
12
|
|
|
10
13
|
##############################################################
|
|
@@ -86,10 +89,9 @@ def check_if_regions_overlap(slices: Iterable[tuple[SlicingType, ...]]) -> bool:
|
|
|
86
89
|
return True
|
|
87
90
|
|
|
88
91
|
if it == 10_000:
|
|
89
|
-
|
|
92
|
+
logger.warning(
|
|
90
93
|
"Performance Warning check_for_overlaps is O(n^2) and may be slow for "
|
|
91
|
-
"large numbers of regions."
|
|
92
|
-
stacklevel=2,
|
|
94
|
+
"large numbers of regions."
|
|
93
95
|
)
|
|
94
96
|
return False
|
|
95
97
|
|
|
@@ -191,9 +193,8 @@ def check_if_chunks_overlap(
|
|
|
191
193
|
if si & sj:
|
|
192
194
|
return True
|
|
193
195
|
if it == 10_000:
|
|
194
|
-
|
|
196
|
+
logger.warning(
|
|
195
197
|
"Performance Warning check_for_chunks_overlaps is O(n^2) and may be "
|
|
196
|
-
"slow for large numbers of regions."
|
|
197
|
-
stacklevel=2,
|
|
198
|
+
"slow for large numbers of regions."
|
|
198
199
|
)
|
|
199
200
|
return False
|
|
@@ -176,8 +176,6 @@ def get_ngio_image_meta(
|
|
|
176
176
|
group_handler: ZarrGroupHandler,
|
|
177
177
|
version: str | None = None,
|
|
178
178
|
axes_setup: AxesSetup | None = None,
|
|
179
|
-
allow_non_canonical_axes: bool = False,
|
|
180
|
-
strict_canonical_order: bool = True,
|
|
181
179
|
) -> NgioImageMeta:
|
|
182
180
|
"""Retrieve the NGIO image metadata from the Zarr group.
|
|
183
181
|
|
|
@@ -185,19 +183,16 @@ def get_ngio_image_meta(
|
|
|
185
183
|
group_handler (ZarrGroupHandler): The Zarr group handler.
|
|
186
184
|
version (str | None): Optional NGFF version to use for decoding.
|
|
187
185
|
axes_setup (AxesSetup | None): Optional axes setup for validation.
|
|
188
|
-
allow_non_canonical_axes (bool): Whether to allow non-canonical axes.
|
|
189
|
-
strict_canonical_order (bool): Whether to enforce strict canonical order.
|
|
190
186
|
|
|
191
187
|
Returns:
|
|
192
188
|
NgioImageMeta: The NGIO image metadata.
|
|
193
189
|
"""
|
|
190
|
+
# axes_setup = axes_setup or AxesSetup(x="XX")
|
|
194
191
|
return get_ngio_meta(
|
|
195
192
|
group_handler=group_handler,
|
|
196
193
|
meta_type=NgioImageMeta,
|
|
197
194
|
version=version,
|
|
198
195
|
axes_setup=axes_setup,
|
|
199
|
-
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
200
|
-
strict_canonical_order=strict_canonical_order,
|
|
201
196
|
)
|
|
202
197
|
|
|
203
198
|
|
|
@@ -224,15 +219,10 @@ class ImageMetaHandler:
|
|
|
224
219
|
group_handler: ZarrGroupHandler,
|
|
225
220
|
version: str | None = None,
|
|
226
221
|
axes_setup: AxesSetup | None = None,
|
|
227
|
-
allow_non_canonical_axes: bool = False,
|
|
228
|
-
strict_canonical_order: bool = True,
|
|
229
222
|
):
|
|
230
223
|
self._group_handler = group_handler
|
|
231
224
|
self._version = version
|
|
232
225
|
self._axes_setup = axes_setup
|
|
233
|
-
self._allow_non_canonical_axes = allow_non_canonical_axes
|
|
234
|
-
self._strict_canonical_order = strict_canonical_order
|
|
235
|
-
|
|
236
226
|
# Validate metadata
|
|
237
227
|
meta = self.get_meta()
|
|
238
228
|
# Store the resolved version
|
|
@@ -244,8 +234,6 @@ class ImageMetaHandler:
|
|
|
244
234
|
group_handler=self._group_handler,
|
|
245
235
|
version=self._version,
|
|
246
236
|
axes_setup=self._axes_setup,
|
|
247
|
-
allow_non_canonical_axes=self._allow_non_canonical_axes,
|
|
248
|
-
strict_canonical_order=self._strict_canonical_order,
|
|
249
237
|
)
|
|
250
238
|
|
|
251
239
|
def update_meta(self, ngio_meta: NgioImageMeta) -> None:
|
|
@@ -260,8 +248,6 @@ def get_ngio_label_meta(
|
|
|
260
248
|
group_handler: ZarrGroupHandler,
|
|
261
249
|
version: str | None = None,
|
|
262
250
|
axes_setup: AxesSetup | None = None,
|
|
263
|
-
allow_non_canonical_axes: bool = False,
|
|
264
|
-
strict_canonical_order: bool = True,
|
|
265
251
|
) -> NgioLabelMeta:
|
|
266
252
|
"""Retrieve the NGIO label metadata from the Zarr group.
|
|
267
253
|
|
|
@@ -269,8 +255,6 @@ def get_ngio_label_meta(
|
|
|
269
255
|
group_handler (ZarrGroupHandler): The Zarr group handler.
|
|
270
256
|
version (str | None): Optional NGFF version to use for decoding.
|
|
271
257
|
axes_setup (AxesSetup | None): Optional axes setup for validation.
|
|
272
|
-
allow_non_canonical_axes (bool): Whether to allow non-canonical axes.
|
|
273
|
-
strict_canonical_order (bool): Whether to enforce strict canonical order.
|
|
274
258
|
|
|
275
259
|
Returns:
|
|
276
260
|
NgioLabelMeta: The NGIO label metadata.
|
|
@@ -280,8 +264,6 @@ def get_ngio_label_meta(
|
|
|
280
264
|
meta_type=NgioLabelMeta,
|
|
281
265
|
version=version,
|
|
282
266
|
axes_setup=axes_setup,
|
|
283
|
-
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
284
|
-
strict_canonical_order=strict_canonical_order,
|
|
285
267
|
)
|
|
286
268
|
|
|
287
269
|
|
|
@@ -306,16 +288,12 @@ class LabelMetaHandler:
|
|
|
306
288
|
def __init__(
|
|
307
289
|
self,
|
|
308
290
|
group_handler: ZarrGroupHandler,
|
|
309
|
-
version: str | None = None,
|
|
310
291
|
axes_setup: AxesSetup | None = None,
|
|
311
|
-
|
|
312
|
-
strict_canonical_order: bool = True,
|
|
292
|
+
version: str | None = None,
|
|
313
293
|
):
|
|
314
294
|
self._group_handler = group_handler
|
|
315
295
|
self._version = version
|
|
316
296
|
self._axes_setup = axes_setup
|
|
317
|
-
self._allow_non_canonical_axes = allow_non_canonical_axes
|
|
318
|
-
self._strict_canonical_order = strict_canonical_order
|
|
319
297
|
|
|
320
298
|
# Validate metadata
|
|
321
299
|
meta = self.get_meta()
|
|
@@ -328,8 +306,6 @@ class LabelMetaHandler:
|
|
|
328
306
|
group_handler=self._group_handler,
|
|
329
307
|
version=self._version,
|
|
330
308
|
axes_setup=self._axes_setup,
|
|
331
|
-
allow_non_canonical_axes=self._allow_non_canonical_axes,
|
|
332
|
-
strict_canonical_order=self._strict_canonical_order,
|
|
333
309
|
)
|
|
334
310
|
|
|
335
311
|
def update_meta(self, ngio_meta: NgioLabelMeta) -> None:
|
|
@@ -15,6 +15,7 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
|
15
15
|
DefaultTimeUnit,
|
|
16
16
|
SpaceUnits,
|
|
17
17
|
TimeUnits,
|
|
18
|
+
build_axes_handler,
|
|
18
19
|
build_canonical_axes_handler,
|
|
19
20
|
canonical_axes_order,
|
|
20
21
|
canonical_label_axes_order,
|
|
@@ -69,6 +70,7 @@ __all__ = [
|
|
|
69
70
|
"PixelSize",
|
|
70
71
|
"SpaceUnits",
|
|
71
72
|
"TimeUnits",
|
|
73
|
+
"build_axes_handler",
|
|
72
74
|
"build_canonical_axes_handler",
|
|
73
75
|
"canonical_axes_order",
|
|
74
76
|
"canonical_label_axes_order",
|
|
@@ -4,7 +4,7 @@ from collections.abc import Sequence
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Literal, TypeAlias, TypeVar
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
8
8
|
|
|
9
9
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
10
10
|
|
|
@@ -153,9 +153,51 @@ class AxesSetup(BaseModel):
|
|
|
153
153
|
c: str = "c"
|
|
154
154
|
t: str = "t"
|
|
155
155
|
others: list[str] = Field(default_factory=list)
|
|
156
|
+
allow_non_canonical_axes: bool = False
|
|
157
|
+
strict_canonical_order: bool = False
|
|
156
158
|
|
|
157
159
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
158
160
|
|
|
161
|
+
@model_validator(mode="after")
|
|
162
|
+
def _validate_axes_values(self) -> "AxesSetup":
|
|
163
|
+
"""Validate the axes values."""
|
|
164
|
+
canonical = {"x", "y", "z", "c", "t"}
|
|
165
|
+
axes = {"x": self.x, "y": self.y, "z": self.z, "c": self.c, "t": self.t}
|
|
166
|
+
|
|
167
|
+
for axis_name, axis_value in axes.items():
|
|
168
|
+
reserved = canonical - {axis_name}
|
|
169
|
+
if axis_value in reserved:
|
|
170
|
+
raise NgioValueError(
|
|
171
|
+
f"The {axis_name} axis cannot be called: '{axis_value}'. "
|
|
172
|
+
f"{axis_value} is reserved. If you want to set a non canonical "
|
|
173
|
+
"axis order, please set the 'strict_canonical_order'to False."
|
|
174
|
+
)
|
|
175
|
+
return self
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def from_ordered_list(
|
|
179
|
+
cls, axes_names: Sequence[str], canonical_order: Sequence[str]
|
|
180
|
+
):
|
|
181
|
+
"""Create an AxesSetup from an ordered list of axes names."""
|
|
182
|
+
# Make sure to only keep as many default axes as provided in axes_names
|
|
183
|
+
if len(axes_names) > len(canonical_order):
|
|
184
|
+
raise NgioValueError(
|
|
185
|
+
f"Cannot create AxesSetup from axes names {axes_names} "
|
|
186
|
+
f"and canonical order {canonical_order}. "
|
|
187
|
+
"The number of axes names cannot be greater than the "
|
|
188
|
+
"number of canonical axes."
|
|
189
|
+
)
|
|
190
|
+
canonical_order = list(canonical_order)
|
|
191
|
+
chanonical_axes = canonical_axes_order()
|
|
192
|
+
axes_mapping = {}
|
|
193
|
+
for ax in reversed(axes_names):
|
|
194
|
+
c_ax = canonical_order.pop()
|
|
195
|
+
if ax in chanonical_axes:
|
|
196
|
+
axes_mapping[ax] = ax
|
|
197
|
+
else:
|
|
198
|
+
axes_mapping[c_ax] = ax
|
|
199
|
+
return cls(**axes_mapping)
|
|
200
|
+
|
|
159
201
|
def canonical_map(self) -> dict[str, str]:
|
|
160
202
|
"""Get the canonical map of axes."""
|
|
161
203
|
return {
|
|
@@ -197,9 +239,9 @@ def _check_unique_names(axes: Sequence[Axis]):
|
|
|
197
239
|
)
|
|
198
240
|
|
|
199
241
|
|
|
200
|
-
def _check_non_canonical_axes(axes_setup: AxesSetup
|
|
242
|
+
def _check_non_canonical_axes(axes_setup: AxesSetup):
|
|
201
243
|
"""Check if all axes are known."""
|
|
202
|
-
if not allow_non_canonical_axes and len(axes_setup.others) > 0:
|
|
244
|
+
if not axes_setup.allow_non_canonical_axes and len(axes_setup.others) > 0:
|
|
203
245
|
raise NgioValidationError(
|
|
204
246
|
f"Unknown axes {axes_setup.others}. Please set "
|
|
205
247
|
"`allow_non_canonical_axes=True` to ignore them"
|
|
@@ -219,11 +261,9 @@ def _check_axes_validity(axes: Sequence[Axis], axes_setup: AxesSetup):
|
|
|
219
261
|
)
|
|
220
262
|
|
|
221
263
|
|
|
222
|
-
def _check_canonical_order(
|
|
223
|
-
axes: Sequence[Axis], axes_setup: AxesSetup, strict_canonical_order: bool
|
|
224
|
-
):
|
|
264
|
+
def _check_canonical_order(axes: Sequence[Axis], axes_setup: AxesSetup):
|
|
225
265
|
"""Check if the axes are in the canonical order."""
|
|
226
|
-
if not strict_canonical_order:
|
|
266
|
+
if not axes_setup.strict_canonical_order:
|
|
227
267
|
return
|
|
228
268
|
_names = [ax.name for ax in axes]
|
|
229
269
|
_canonical_order = []
|
|
@@ -242,24 +282,18 @@ def _check_canonical_order(
|
|
|
242
282
|
def validate_axes(
|
|
243
283
|
axes: Sequence[Axis],
|
|
244
284
|
axes_setup: AxesSetup,
|
|
245
|
-
allow_non_canonical_axes: bool = False,
|
|
246
|
-
strict_canonical_order: bool = False,
|
|
247
285
|
) -> None:
|
|
248
286
|
"""Validate the axes."""
|
|
249
|
-
if allow_non_canonical_axes and strict_canonical_order:
|
|
250
|
-
raise
|
|
287
|
+
if axes_setup.allow_non_canonical_axes and axes_setup.strict_canonical_order:
|
|
288
|
+
raise NgioValueError(
|
|
251
289
|
"`allow_non_canonical_axes` and"
|
|
252
290
|
"`strict_canonical_order` cannot be true at the same time."
|
|
253
291
|
"If non canonical axes are allowed, the order cannot be checked."
|
|
254
292
|
)
|
|
255
293
|
_check_unique_names(axes=axes)
|
|
256
|
-
_check_non_canonical_axes(
|
|
257
|
-
axes_setup=axes_setup, allow_non_canonical_axes=allow_non_canonical_axes
|
|
258
|
-
)
|
|
294
|
+
_check_non_canonical_axes(axes_setup=axes_setup)
|
|
259
295
|
_check_axes_validity(axes=axes, axes_setup=axes_setup)
|
|
260
|
-
_check_canonical_order(
|
|
261
|
-
axes=axes, axes_setup=axes_setup, strict_canonical_order=strict_canonical_order
|
|
262
|
-
)
|
|
296
|
+
_check_canonical_order(axes=axes, axes_setup=axes_setup)
|
|
263
297
|
|
|
264
298
|
|
|
265
299
|
class AxesHandler:
|
|
@@ -278,29 +312,22 @@ class AxesHandler:
|
|
|
278
312
|
axes: Sequence[Axis],
|
|
279
313
|
# user defined args
|
|
280
314
|
axes_setup: AxesSetup | None = None,
|
|
281
|
-
allow_non_canonical_axes: bool = False,
|
|
282
|
-
strict_canonical_order: bool = False,
|
|
283
315
|
):
|
|
284
316
|
"""Create a new AxesMapper object.
|
|
285
317
|
|
|
286
318
|
Args:
|
|
287
319
|
axes (list[Axis]): The axes on disk.
|
|
288
320
|
axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
|
|
289
|
-
allow_non_canonical_axes (bool, optional): Allow non canonical axes.
|
|
290
|
-
strict_canonical_order (bool, optional): Check if the axes are in the
|
|
291
|
-
canonical order. Defaults to False.
|
|
292
321
|
"""
|
|
293
322
|
axes_setup = axes_setup if axes_setup is not None else AxesSetup()
|
|
294
323
|
|
|
295
324
|
validate_axes(
|
|
296
325
|
axes=axes,
|
|
297
326
|
axes_setup=axes_setup,
|
|
298
|
-
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
299
|
-
strict_canonical_order=strict_canonical_order,
|
|
300
327
|
)
|
|
301
328
|
|
|
302
|
-
self._allow_non_canonical_axes = allow_non_canonical_axes
|
|
303
|
-
self._strict_canonical_order = strict_canonical_order
|
|
329
|
+
self._allow_non_canonical_axes = axes_setup.allow_non_canonical_axes
|
|
330
|
+
self._strict_canonical_order = axes_setup.strict_canonical_order
|
|
304
331
|
|
|
305
332
|
self._canonical_order = canonical_axes_order()
|
|
306
333
|
|
|
@@ -422,8 +449,48 @@ class AxesHandler:
|
|
|
422
449
|
return AxesHandler(
|
|
423
450
|
axes=new_axes,
|
|
424
451
|
axes_setup=self.axes_setup,
|
|
425
|
-
|
|
426
|
-
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def rename_axes(self, axes_names: Sequence[str]) -> "AxesHandler":
|
|
455
|
+
"""Rename the axes.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
axes_names (Sequence[str]): The new axes names.
|
|
459
|
+
"""
|
|
460
|
+
if len(axes_names) != len(self.axes):
|
|
461
|
+
raise NgioValueError(
|
|
462
|
+
f"Cannot rename axes. "
|
|
463
|
+
f"Expected {len(self.axes)} axes, but got {len(axes_names)}."
|
|
464
|
+
)
|
|
465
|
+
new_axes = []
|
|
466
|
+
axes_setup = self.axes_setup
|
|
467
|
+
for ax, new_name in zip(self.axes, axes_names, strict=True):
|
|
468
|
+
if ax.name == new_name:
|
|
469
|
+
new_axes.append(ax)
|
|
470
|
+
continue
|
|
471
|
+
new_ax = Axis(name=new_name, axis_type=ax.axis_type, unit=ax.unit)
|
|
472
|
+
match ax.name:
|
|
473
|
+
case axes_setup.x:
|
|
474
|
+
axes_setup = axes_setup.model_copy(update={"x": new_name})
|
|
475
|
+
case axes_setup.y:
|
|
476
|
+
axes_setup = axes_setup.model_copy(update={"y": new_name})
|
|
477
|
+
case axes_setup.z:
|
|
478
|
+
axes_setup = axes_setup.model_copy(update={"z": new_name})
|
|
479
|
+
case axes_setup.c:
|
|
480
|
+
axes_setup = axes_setup.model_copy(update={"c": new_name})
|
|
481
|
+
case axes_setup.t:
|
|
482
|
+
axes_setup = axes_setup.model_copy(update={"t": new_name})
|
|
483
|
+
case _:
|
|
484
|
+
if ax.name in axes_setup.others:
|
|
485
|
+
others = axes_setup.others.copy()
|
|
486
|
+
others.remove(ax.name)
|
|
487
|
+
others.append(new_name)
|
|
488
|
+
axes_setup = axes_setup.model_copy(update={"others": others})
|
|
489
|
+
new_axes.append(new_ax)
|
|
490
|
+
# Update the axes setup
|
|
491
|
+
return AxesHandler(
|
|
492
|
+
axes=new_axes,
|
|
493
|
+
axes_setup=axes_setup,
|
|
427
494
|
)
|
|
428
495
|
|
|
429
496
|
def get_index(self, name: str) -> int | None:
|
|
@@ -465,14 +532,33 @@ class AxesHandler:
|
|
|
465
532
|
self._axes = new_axes
|
|
466
533
|
|
|
467
534
|
|
|
535
|
+
def _build_axes_list_from_names(
|
|
536
|
+
axes_names: Sequence[str],
|
|
537
|
+
axes_setup: AxesSetup,
|
|
538
|
+
space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
|
|
539
|
+
time_unit: TimeUnits | str | None = DefaultTimeUnit,
|
|
540
|
+
) -> list[Axis]:
|
|
541
|
+
"""Build a list of Axis objects from a list of axis names."""
|
|
542
|
+
axes = []
|
|
543
|
+
for name in axes_names:
|
|
544
|
+
c_name = axes_setup.get_canonical_name(name)
|
|
545
|
+
match c_name:
|
|
546
|
+
case "t":
|
|
547
|
+
axes.append(Axis(name=name, axis_type=AxisType.time, unit=time_unit))
|
|
548
|
+
case "c":
|
|
549
|
+
axes.append(Axis(name=name, axis_type=AxisType.channel))
|
|
550
|
+
case "z" | "y" | "x":
|
|
551
|
+
axes.append(Axis(name=name, axis_type=AxisType.space, unit=space_unit))
|
|
552
|
+
case _:
|
|
553
|
+
axes.append(Axis(name=name, axis_type=AxisType.space))
|
|
554
|
+
return axes
|
|
555
|
+
|
|
556
|
+
|
|
468
557
|
def build_canonical_axes_handler(
|
|
469
558
|
axes_names: Sequence[str],
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
axes_setup: AxesSetup | None = None,
|
|
474
|
-
allow_non_canonical_axes: bool = False,
|
|
475
|
-
strict_canonical_order: bool = False,
|
|
559
|
+
canonical_channel_order: Sequence[str] | None = None,
|
|
560
|
+
space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
|
|
561
|
+
time_unit: TimeUnits | str | None = DefaultTimeUnit,
|
|
476
562
|
) -> AxesHandler:
|
|
477
563
|
"""Create a new canonical axes mapper.
|
|
478
564
|
|
|
@@ -483,33 +569,50 @@ def build_canonical_axes_handler(
|
|
|
483
569
|
- If an integer is provided, the axes are created from the last axis
|
|
484
570
|
to the first
|
|
485
571
|
e.g. 3 -> ["z", "y", "x"]
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
Defaults to False.
|
|
491
|
-
strict_canonical_order (bool, optional): Check if the axes are in the
|
|
492
|
-
canonical order. Defaults to False.
|
|
493
|
-
|
|
572
|
+
canonical_channel_order (Sequence[str], optional): The canonical channel
|
|
573
|
+
order. Defaults to None, which uses the default order.
|
|
574
|
+
space_unit (SpaceUnits, optional): The space units. Defaults to None.
|
|
575
|
+
time_unit (TimeUnits, optional): The time units. Defaults to None.
|
|
494
576
|
"""
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
577
|
+
if canonical_channel_order is None:
|
|
578
|
+
canonical_channel_order = canonical_axes_order()
|
|
579
|
+
axes_setup = AxesSetup.from_ordered_list(
|
|
580
|
+
axes_names=axes_names, canonical_order=canonical_channel_order
|
|
581
|
+
)
|
|
582
|
+
axes = _build_axes_list_from_names(
|
|
583
|
+
axes_names=axes_names,
|
|
584
|
+
axes_setup=axes_setup,
|
|
585
|
+
space_unit=space_unit,
|
|
586
|
+
time_unit=time_unit,
|
|
587
|
+
)
|
|
588
|
+
return AxesHandler(
|
|
589
|
+
axes=axes,
|
|
590
|
+
axes_setup=axes_setup,
|
|
591
|
+
)
|
|
592
|
+
|
|
509
593
|
|
|
594
|
+
def build_axes_handler(
|
|
595
|
+
axes_names: Sequence[str],
|
|
596
|
+
axes_setup: AxesSetup | None = None,
|
|
597
|
+
space_unit: SpaceUnits | str | None = DefaultSpaceUnit,
|
|
598
|
+
time_unit: TimeUnits | str | None = DefaultTimeUnit,
|
|
599
|
+
) -> AxesHandler:
|
|
600
|
+
"""Create a new axes mapper.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
axes_names (Sequence[str]): The axes names on disk.
|
|
604
|
+
axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
|
|
605
|
+
space_unit (SpaceUnits, optional): The space units. Defaults to None.
|
|
606
|
+
time_unit (TimeUnits, optional): The time units. Defaults to None.
|
|
607
|
+
"""
|
|
608
|
+
axes_setup = axes_setup if axes_setup is not None else AxesSetup()
|
|
609
|
+
axes = _build_axes_list_from_names(
|
|
610
|
+
axes_names=axes_names,
|
|
611
|
+
axes_setup=axes_setup,
|
|
612
|
+
space_unit=space_unit,
|
|
613
|
+
time_unit=time_unit,
|
|
614
|
+
)
|
|
510
615
|
return AxesHandler(
|
|
511
616
|
axes=axes,
|
|
512
617
|
axes_setup=axes_setup,
|
|
513
|
-
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
514
|
-
strict_canonical_order=strict_canonical_order,
|
|
515
618
|
)
|