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.
- ngio/__init__.py +40 -12
- ngio/common/__init__.py +16 -32
- ngio/common/_dimensions.py +270 -48
- ngio/common/_masking_roi.py +153 -0
- ngio/common/_pyramid.py +267 -73
- ngio/common/_roi.py +290 -66
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +54 -22
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +126 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/__init__.py +17 -58
- ngio/hcs/_plate.py +1354 -0
- ngio/images/__init__.py +30 -9
- ngio/images/_abstract_image.py +968 -0
- ngio/images/_create_synt_container.py +132 -0
- ngio/images/_create_utils.py +423 -0
- ngio/images/_image.py +926 -0
- ngio/images/_label.py +417 -0
- ngio/images/_masked_image.py +531 -0
- ngio/images/_ome_zarr_container.py +1235 -0
- ngio/images/_table_ops.py +471 -0
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +146 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +377 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +411 -0
- ngio/io_pipes/_ops_slices_utils.py +199 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +180 -0
- ngio/ome_zarr_meta/__init__.py +39 -15
- ngio/ome_zarr_meta/_meta_handlers.py +490 -96
- ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
- ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
- ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
- ngio/ome_zarr_meta/v04/__init__.py +21 -5
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/__init__.py +20 -4
- ngio/tables/_abstract_table.py +270 -0
- ngio/tables/_tables_container.py +449 -0
- ngio/tables/backends/__init__.py +50 -1
- ngio/tables/backends/_abstract_backend.py +200 -31
- ngio/tables/backends/_anndata.py +139 -0
- ngio/tables/backends/_anndata_utils.py +10 -114
- ngio/tables/backends/_csv.py +19 -0
- ngio/tables/backends/_json.py +92 -0
- ngio/tables/backends/_parquet.py +19 -0
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_table_backends.py +162 -38
- ngio/tables/backends/_utils.py +608 -0
- ngio/tables/v1/__init__.py +19 -4
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +79 -115
- ngio/tables/v1/_generic_table.py +21 -90
- ngio/tables/v1/_roi_table.py +486 -137
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +16 -14
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +121 -13
- ngio/utils/_fractal_fsspec_store.py +42 -0
- ngio/utils/_zarr_utils.py +374 -218
- ngio-0.5.0b4.dist-info/METADATA +147 -0
- ngio-0.5.0b4.dist-info/RECORD +88 -0
- {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
- ngio/common/_array_pipe.py +0 -160
- ngio/common/_axes_transforms.py +0 -63
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -97
- ngio/images/abstract_image.py +0 -240
- ngio/images/create.py +0 -251
- ngio/images/image.py +0 -389
- ngio/images/label.py +0 -236
- ngio/images/omezarr_container.py +0 -535
- ngio/ome_zarr_meta/_generic_handlers.py +0 -320
- ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
- ngio/tables/_validators.py +0 -192
- ngio/tables/backends/_anndata_v1.py +0 -75
- ngio/tables/backends/_json_v1.py +0 -56
- ngio/tables/tables_container.py +0 -300
- ngio/tables/v1/_masking_roi_table.py +0 -175
- ngio/utils/_logger.py +0 -29
- ngio-0.2.0a2.dist-info/METADATA +0 -95
- ngio-0.2.0a2.dist-info/RECORD +0 -53
- {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
|
|
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)
|
|
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:
|
|
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:
|
|
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:
|
|
307
|
-
wavelength_id:
|
|
308
|
-
colors:
|
|
309
|
-
start:
|
|
310
|
-
end:
|
|
311
|
-
active:
|
|
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(
|
|
319
|
-
If an integer is provided, the channels will be
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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 (
|
|
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:
|
|
343
|
-
if
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
3
|
+
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
6
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
41
|
+
self._scale = list(scale)
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
if len(
|
|
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.
|
|
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
|
|
90
|
-
"""Return the
|
|
91
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
133
|
-
"""Return the
|
|
134
|
-
return self.
|
|
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)
|