ngio 0.1.5__py3-none-any.whl → 0.2.0__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 +33 -5
- ngio/common/__init__.py +54 -0
- ngio/common/_array_pipe.py +265 -0
- ngio/common/_axes_transforms.py +64 -0
- ngio/common/_common_types.py +5 -0
- ngio/common/_dimensions.py +120 -0
- ngio/common/_masking_roi.py +158 -0
- ngio/common/_pyramid.py +228 -0
- ngio/common/_roi.py +165 -0
- ngio/common/_slicer.py +96 -0
- ngio/{pipes/_zoom_utils.py → common/_zoom.py} +51 -83
- ngio/hcs/__init__.py +5 -0
- ngio/hcs/plate.py +448 -0
- ngio/images/__init__.py +23 -0
- ngio/images/abstract_image.py +349 -0
- ngio/images/create.py +270 -0
- ngio/images/image.py +453 -0
- ngio/images/label.py +285 -0
- ngio/images/masked_image.py +273 -0
- ngio/images/ome_zarr_container.py +738 -0
- ngio/ome_zarr_meta/__init__.py +47 -0
- ngio/ome_zarr_meta/_meta_handlers.py +791 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +71 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +481 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +389 -0
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +134 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +377 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +489 -0
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +116 -0
- ngio/ome_zarr_meta/v04/__init__.py +23 -0
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +485 -0
- ngio/tables/__init__.py +24 -6
- ngio/tables/_validators.py +190 -0
- ngio/tables/backends/__init__.py +8 -0
- ngio/tables/backends/_abstract_backend.py +71 -0
- ngio/tables/backends/_anndata_utils.py +198 -0
- ngio/tables/backends/_anndata_v1.py +76 -0
- ngio/tables/backends/_json_v1.py +56 -0
- ngio/tables/backends/_table_backends.py +102 -0
- ngio/tables/tables_container.py +310 -0
- ngio/tables/v1/__init__.py +5 -5
- ngio/tables/v1/_feature_table.py +182 -0
- ngio/tables/v1/_generic_table.py +160 -179
- ngio/tables/v1/_roi_table.py +366 -0
- ngio/utils/__init__.py +26 -10
- ngio/utils/_datasets.py +53 -0
- ngio/utils/_errors.py +10 -4
- ngio/utils/_fractal_fsspec_store.py +13 -0
- ngio/utils/_logger.py +3 -1
- ngio/utils/_zarr_utils.py +401 -0
- {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/METADATA +31 -43
- ngio-0.2.0.dist-info/RECORD +54 -0
- ngio/core/__init__.py +0 -7
- ngio/core/dimensions.py +0 -122
- ngio/core/image_handler.py +0 -228
- ngio/core/image_like_handler.py +0 -549
- ngio/core/label_handler.py +0 -410
- ngio/core/ngff_image.py +0 -387
- ngio/core/roi.py +0 -92
- ngio/core/utils.py +0 -287
- ngio/io/__init__.py +0 -19
- ngio/io/_zarr.py +0 -88
- ngio/io/_zarr_array_utils.py +0 -0
- ngio/io/_zarr_group_utils.py +0 -61
- ngio/iterators/__init__.py +0 -1
- ngio/ngff_meta/__init__.py +0 -27
- ngio/ngff_meta/fractal_image_meta.py +0 -1267
- ngio/ngff_meta/meta_handler.py +0 -92
- ngio/ngff_meta/utils.py +0 -235
- ngio/ngff_meta/v04/__init__.py +0 -6
- ngio/ngff_meta/v04/specs.py +0 -158
- ngio/ngff_meta/v04/zarr_utils.py +0 -376
- ngio/pipes/__init__.py +0 -7
- ngio/pipes/_slicer_transforms.py +0 -176
- ngio/pipes/_transforms.py +0 -33
- ngio/pipes/data_pipe.py +0 -52
- ngio/tables/_ad_reader.py +0 -80
- ngio/tables/_utils.py +0 -301
- ngio/tables/tables_group.py +0 -252
- ngio/tables/v1/feature_tables.py +0 -182
- ngio/tables/v1/masking_roi_tables.py +0 -243
- ngio/tables/v1/roi_tables.py +0 -285
- ngio/utils/_common_types.py +0 -5
- ngio/utils/_pydantic_utils.py +0 -52
- ngio-0.1.5.dist-info/RECORD +0 -44
- {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/WHEEL +0 -0
- {ngio-0.1.5.dist-info → ngio-0.2.0.dist-info}/licenses/LICENSE +0 -0
ngio/__init__.py
CHANGED
|
@@ -2,14 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
4
|
|
|
5
|
-
from ngio.core import Image, Label, NgffImage
|
|
6
|
-
|
|
7
|
-
__all__ = ["Image", "Label", "NgffImage"]
|
|
8
|
-
|
|
9
|
-
|
|
10
5
|
try:
|
|
11
6
|
__version__ = version("ngio")
|
|
12
7
|
except PackageNotFoundError: # pragma: no cover
|
|
13
8
|
__version__ = "uninstalled"
|
|
14
9
|
__author__ = "Lorenzo Cerrone"
|
|
15
10
|
__email__ = "lorenzo.cerrone@uzh.ch"
|
|
11
|
+
|
|
12
|
+
from ngio.common import ArrayLike, Dimensions, Roi, RoiPixels
|
|
13
|
+
from ngio.hcs import OmeZarrPlate, create_empty_plate, open_ome_zarr_plate
|
|
14
|
+
from ngio.images import (
|
|
15
|
+
Image,
|
|
16
|
+
Label,
|
|
17
|
+
OmeZarrContainer,
|
|
18
|
+
create_empty_ome_zarr,
|
|
19
|
+
create_ome_zarr_from_array,
|
|
20
|
+
open_image,
|
|
21
|
+
open_ome_zarr_container,
|
|
22
|
+
)
|
|
23
|
+
from ngio.ome_zarr_meta.ngio_specs import AxesSetup, ImageInWellPath, PixelSize
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ArrayLike",
|
|
27
|
+
"AxesSetup",
|
|
28
|
+
"Dimensions",
|
|
29
|
+
"Image",
|
|
30
|
+
"ImageInWellPath",
|
|
31
|
+
"Label",
|
|
32
|
+
"OmeZarrContainer",
|
|
33
|
+
"OmeZarrPlate",
|
|
34
|
+
"PixelSize",
|
|
35
|
+
"Roi",
|
|
36
|
+
"RoiPixels",
|
|
37
|
+
"create_empty_ome_zarr",
|
|
38
|
+
"create_empty_plate",
|
|
39
|
+
"create_ome_zarr_from_array",
|
|
40
|
+
"open_image",
|
|
41
|
+
"open_ome_zarr_container",
|
|
42
|
+
"open_ome_zarr_plate",
|
|
43
|
+
]
|
ngio/common/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Common classes and functions that are used across the package."""
|
|
2
|
+
|
|
3
|
+
from ngio.common._array_pipe import (
|
|
4
|
+
get_masked_pipe,
|
|
5
|
+
get_pipe,
|
|
6
|
+
set_masked_pipe,
|
|
7
|
+
set_pipe,
|
|
8
|
+
)
|
|
9
|
+
from ngio.common._axes_transforms import (
|
|
10
|
+
transform_dask_array,
|
|
11
|
+
transform_list,
|
|
12
|
+
transform_numpy_array,
|
|
13
|
+
)
|
|
14
|
+
from ngio.common._common_types import ArrayLike
|
|
15
|
+
from ngio.common._dimensions import Dimensions
|
|
16
|
+
from ngio.common._masking_roi import compute_masking_roi
|
|
17
|
+
from ngio.common._pyramid import consolidate_pyramid, init_empty_pyramid, on_disk_zoom
|
|
18
|
+
from ngio.common._roi import Roi, RoiPixels, roi_to_slice_kwargs
|
|
19
|
+
from ngio.common._slicer import (
|
|
20
|
+
SliceTransform,
|
|
21
|
+
compute_and_slices,
|
|
22
|
+
dask_get_slice,
|
|
23
|
+
dask_set_slice,
|
|
24
|
+
numpy_get_slice,
|
|
25
|
+
numpy_set_slice,
|
|
26
|
+
)
|
|
27
|
+
from ngio.common._zoom import dask_zoom, numpy_zoom
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ArrayLike",
|
|
31
|
+
"Dimensions",
|
|
32
|
+
"Roi",
|
|
33
|
+
"RoiPixels",
|
|
34
|
+
"SliceTransform",
|
|
35
|
+
"compute_and_slices",
|
|
36
|
+
"compute_masking_roi",
|
|
37
|
+
"consolidate_pyramid",
|
|
38
|
+
"dask_get_slice",
|
|
39
|
+
"dask_set_slice",
|
|
40
|
+
"dask_zoom",
|
|
41
|
+
"get_masked_pipe",
|
|
42
|
+
"get_pipe",
|
|
43
|
+
"init_empty_pyramid",
|
|
44
|
+
"numpy_get_slice",
|
|
45
|
+
"numpy_set_slice",
|
|
46
|
+
"numpy_zoom",
|
|
47
|
+
"on_disk_zoom",
|
|
48
|
+
"roi_to_slice_kwargs",
|
|
49
|
+
"set_masked_pipe",
|
|
50
|
+
"set_pipe",
|
|
51
|
+
"transform_dask_array",
|
|
52
|
+
"transform_list",
|
|
53
|
+
"transform_numpy_array",
|
|
54
|
+
]
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from collections.abc import Collection, Iterable
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import dask
|
|
5
|
+
import dask.delayed
|
|
6
|
+
import numpy as np
|
|
7
|
+
import zarr
|
|
8
|
+
|
|
9
|
+
from ngio.common._axes_transforms import transform_dask_array, transform_numpy_array
|
|
10
|
+
from ngio.common._common_types import ArrayLike
|
|
11
|
+
from ngio.common._dimensions import Dimensions
|
|
12
|
+
from ngio.common._slicer import (
|
|
13
|
+
SliceTransform,
|
|
14
|
+
compute_and_slices,
|
|
15
|
+
dask_get_slice,
|
|
16
|
+
dask_set_slice,
|
|
17
|
+
numpy_get_slice,
|
|
18
|
+
numpy_set_slice,
|
|
19
|
+
)
|
|
20
|
+
from ngio.ome_zarr_meta.ngio_specs import AxesTransformation
|
|
21
|
+
from ngio.utils import NgioValueError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _compute_from_disk_transforms(
|
|
25
|
+
*,
|
|
26
|
+
dimensions: Dimensions,
|
|
27
|
+
axes_order: Collection[str] | None = None,
|
|
28
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
29
|
+
) -> tuple[SliceTransform, tuple[AxesTransformation, ...]]:
|
|
30
|
+
slices = compute_and_slices(dimensions=dimensions, **slice_kwargs)
|
|
31
|
+
|
|
32
|
+
if axes_order is None:
|
|
33
|
+
return slices, ()
|
|
34
|
+
|
|
35
|
+
additional_transformations = dimensions._axes_mapper.to_order(axes_order)
|
|
36
|
+
return slices, additional_transformations
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _compute_to_disk_transforms(
|
|
40
|
+
*,
|
|
41
|
+
dimensions: Dimensions,
|
|
42
|
+
axes_order: Collection[str] | None = None,
|
|
43
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
44
|
+
) -> tuple[SliceTransform, tuple[AxesTransformation, ...]]:
|
|
45
|
+
slices = compute_and_slices(dimensions=dimensions, **slice_kwargs)
|
|
46
|
+
if axes_order is None:
|
|
47
|
+
return slices, ()
|
|
48
|
+
|
|
49
|
+
additional_transformations = dimensions._axes_mapper.from_order(axes_order)
|
|
50
|
+
return slices, additional_transformations
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _numpy_get_pipe(
|
|
54
|
+
array: zarr.Array,
|
|
55
|
+
slices: SliceTransform,
|
|
56
|
+
transformations: tuple[AxesTransformation, ...],
|
|
57
|
+
) -> np.ndarray:
|
|
58
|
+
array = numpy_get_slice(array, slices)
|
|
59
|
+
return transform_numpy_array(array, transformations)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _delayed_numpy_get_pipe(
|
|
63
|
+
array: zarr.Array,
|
|
64
|
+
slices: SliceTransform,
|
|
65
|
+
transformations: tuple[AxesTransformation, ...],
|
|
66
|
+
) -> dask.delayed:
|
|
67
|
+
array = dask.delayed(numpy_get_slice)(array, slices)
|
|
68
|
+
return dask.delayed(transform_numpy_array)(array, transformations)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _dask_get_pipe(
|
|
72
|
+
array: zarr.Array,
|
|
73
|
+
slices: SliceTransform,
|
|
74
|
+
transformations: tuple[AxesTransformation, ...],
|
|
75
|
+
) -> dask.array:
|
|
76
|
+
array = dask_get_slice(array, slices)
|
|
77
|
+
return transform_dask_array(array, transformations)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _numpy_set_pipe(
|
|
81
|
+
array: zarr.Array,
|
|
82
|
+
patch: np.ndarray,
|
|
83
|
+
slices: SliceTransform,
|
|
84
|
+
transformations: tuple[AxesTransformation, ...],
|
|
85
|
+
) -> None:
|
|
86
|
+
patch = transform_numpy_array(patch, transformations)
|
|
87
|
+
numpy_set_slice(array, patch, slices)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _dask_set_pipe(
|
|
91
|
+
array: zarr.Array,
|
|
92
|
+
patch: np.ndarray,
|
|
93
|
+
slices: SliceTransform,
|
|
94
|
+
transformations: tuple[AxesTransformation, ...],
|
|
95
|
+
) -> None:
|
|
96
|
+
patch = transform_dask_array(patch, transformations)
|
|
97
|
+
dask_set_slice(array, patch, slices)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _delayed_numpy_set_pipe(
|
|
101
|
+
array: zarr.Array,
|
|
102
|
+
patch: np.ndarray,
|
|
103
|
+
slices: SliceTransform,
|
|
104
|
+
transformations: tuple[AxesTransformation, ...],
|
|
105
|
+
) -> dask.delayed:
|
|
106
|
+
patch = dask.delayed(transform_numpy_array)(patch, transformations)
|
|
107
|
+
return dask.delayed(numpy_set_slice)(array, patch, slices)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_pipe(
|
|
111
|
+
array: zarr.Array,
|
|
112
|
+
*,
|
|
113
|
+
dimensions: Dimensions,
|
|
114
|
+
axes_order: Collection[str] | None = None,
|
|
115
|
+
mode: Literal["numpy", "dask", "delayed"] = "numpy",
|
|
116
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
117
|
+
):
|
|
118
|
+
slices, transformations = _compute_from_disk_transforms(
|
|
119
|
+
dimensions=dimensions, axes_order=axes_order, **slice_kwargs
|
|
120
|
+
)
|
|
121
|
+
match mode:
|
|
122
|
+
case "numpy":
|
|
123
|
+
return _numpy_get_pipe(array, slices, transformations)
|
|
124
|
+
case "dask":
|
|
125
|
+
return _dask_get_pipe(array, slices, transformations)
|
|
126
|
+
|
|
127
|
+
case "delayed":
|
|
128
|
+
return _delayed_numpy_get_pipe(array, slices, transformations)
|
|
129
|
+
|
|
130
|
+
case _:
|
|
131
|
+
raise NgioValueError(
|
|
132
|
+
f"Unknown get pipe mode {mode}, expected 'numpy', 'dask' or 'delayed'."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def set_pipe(
|
|
137
|
+
array: zarr.Array,
|
|
138
|
+
patch: ArrayLike,
|
|
139
|
+
*,
|
|
140
|
+
dimensions: Dimensions,
|
|
141
|
+
axes_order: Collection[str] | None = None,
|
|
142
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
143
|
+
):
|
|
144
|
+
slices, transformations = _compute_to_disk_transforms(
|
|
145
|
+
dimensions=dimensions, axes_order=axes_order, **slice_kwargs
|
|
146
|
+
)
|
|
147
|
+
if isinstance(patch, dask.array.Array):
|
|
148
|
+
_dask_set_pipe(
|
|
149
|
+
array=array, patch=patch, slices=slices, transformations=transformations
|
|
150
|
+
)
|
|
151
|
+
elif isinstance(patch, np.ndarray):
|
|
152
|
+
_numpy_set_pipe(
|
|
153
|
+
array=array, patch=patch, slices=slices, transformations=transformations
|
|
154
|
+
)
|
|
155
|
+
elif isinstance(patch, dask.delayed.Delayed):
|
|
156
|
+
_delayed_numpy_set_pipe(
|
|
157
|
+
array=array, patch=patch, slices=slices, transformations=transformations
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
raise NgioValueError("Unknown patch type, expected numpy, dask or delayed.")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _mask_pipe_common(
|
|
164
|
+
array: zarr.Array,
|
|
165
|
+
label_array: zarr.Array,
|
|
166
|
+
label: int,
|
|
167
|
+
*,
|
|
168
|
+
dimensions_array: Dimensions,
|
|
169
|
+
dimensions_label: Dimensions,
|
|
170
|
+
axes_order: Collection[str] | None = None,
|
|
171
|
+
mode: Literal["numpy", "dask", "delayed"] = "numpy",
|
|
172
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
173
|
+
):
|
|
174
|
+
array_patch = get_pipe(
|
|
175
|
+
array,
|
|
176
|
+
dimensions=dimensions_array,
|
|
177
|
+
axes_order=axes_order,
|
|
178
|
+
mode=mode,
|
|
179
|
+
**slice_kwargs,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if not dimensions_label.has_axis("c"):
|
|
183
|
+
# Remove the 'c' from the slice_kwargs
|
|
184
|
+
# This will not work if the query uses non-default
|
|
185
|
+
# axes names for channel
|
|
186
|
+
slice_kwargs = {k: v for k, v in slice_kwargs.items() if k != "c"}
|
|
187
|
+
|
|
188
|
+
label_patch = get_pipe(
|
|
189
|
+
label_array,
|
|
190
|
+
dimensions=dimensions_label,
|
|
191
|
+
axes_order=axes_order,
|
|
192
|
+
mode=mode,
|
|
193
|
+
**slice_kwargs,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if isinstance(array_patch, np.ndarray):
|
|
197
|
+
label_patch = np.broadcast_to(label_patch, array_patch.shape)
|
|
198
|
+
elif isinstance(array_patch, dask.array.Array):
|
|
199
|
+
label_patch = dask.array.broadcast_to(label_patch, array_patch.shape)
|
|
200
|
+
else:
|
|
201
|
+
raise NgioValueError(f"Mode {mode} not yet supported for masked array.")
|
|
202
|
+
|
|
203
|
+
mask = label_patch == label
|
|
204
|
+
return array_patch, mask
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_masked_pipe(
|
|
208
|
+
array: zarr.Array,
|
|
209
|
+
label_array: zarr.Array,
|
|
210
|
+
label: int,
|
|
211
|
+
*,
|
|
212
|
+
dimensions_array: Dimensions,
|
|
213
|
+
dimensions_label: Dimensions,
|
|
214
|
+
axes_order: Collection[str] | None = None,
|
|
215
|
+
mode: Literal["numpy", "dask", "delayed"] = "numpy",
|
|
216
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
217
|
+
):
|
|
218
|
+
array_patch, mask = _mask_pipe_common(
|
|
219
|
+
array=array,
|
|
220
|
+
label_array=label_array,
|
|
221
|
+
label=label,
|
|
222
|
+
dimensions_array=dimensions_array,
|
|
223
|
+
dimensions_label=dimensions_label,
|
|
224
|
+
axes_order=axes_order,
|
|
225
|
+
mode=mode,
|
|
226
|
+
**slice_kwargs,
|
|
227
|
+
)
|
|
228
|
+
array_patch[~mask] = 0
|
|
229
|
+
return array_patch
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def set_masked_pipe(
|
|
233
|
+
array: zarr.Array,
|
|
234
|
+
label_array: zarr.Array,
|
|
235
|
+
label: int,
|
|
236
|
+
patch: ArrayLike,
|
|
237
|
+
*,
|
|
238
|
+
dimensions_array: Dimensions,
|
|
239
|
+
dimensions_label: Dimensions,
|
|
240
|
+
axes_order: Collection[str] | None = None,
|
|
241
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
242
|
+
):
|
|
243
|
+
if isinstance(patch, dask.array.Array):
|
|
244
|
+
mode = "dask"
|
|
245
|
+
elif isinstance(patch, np.ndarray):
|
|
246
|
+
mode = "numpy"
|
|
247
|
+
else:
|
|
248
|
+
raise NgioValueError(
|
|
249
|
+
"Mode not yet supported for masked array. Expected a numpy or dask array."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
array_patch, mask = _mask_pipe_common(
|
|
253
|
+
array=array,
|
|
254
|
+
label_array=label_array,
|
|
255
|
+
label=label,
|
|
256
|
+
dimensions_array=dimensions_array,
|
|
257
|
+
dimensions_label=dimensions_label,
|
|
258
|
+
axes_order=axes_order,
|
|
259
|
+
mode=mode,
|
|
260
|
+
**slice_kwargs,
|
|
261
|
+
)
|
|
262
|
+
patch = np.where(mask, patch, array_patch)
|
|
263
|
+
set_pipe(
|
|
264
|
+
array, patch, dimensions=dimensions_array, axes_order=axes_order, **slice_kwargs
|
|
265
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
import dask.array as da
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
7
|
+
AxesExpand,
|
|
8
|
+
AxesSqueeze,
|
|
9
|
+
AxesTransformation,
|
|
10
|
+
AxesTranspose,
|
|
11
|
+
)
|
|
12
|
+
from ngio.utils import NgioValueError
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def transform_list(
|
|
18
|
+
input_list: list[T], default: T, operations: tuple[AxesTransformation, ...]
|
|
19
|
+
) -> list[T]:
|
|
20
|
+
if isinstance(input_list, tuple):
|
|
21
|
+
input_list = list(input_list)
|
|
22
|
+
|
|
23
|
+
for operation in operations:
|
|
24
|
+
if isinstance(operation, AxesTranspose):
|
|
25
|
+
input_list = [input_list[i] for i in operation.axes]
|
|
26
|
+
|
|
27
|
+
if isinstance(operation, AxesExpand):
|
|
28
|
+
for ax in operation.axes:
|
|
29
|
+
input_list.insert(ax, default)
|
|
30
|
+
elif isinstance(operation, AxesSqueeze):
|
|
31
|
+
for offset, ax in enumerate(operation.axes):
|
|
32
|
+
input_list.pop(ax - offset)
|
|
33
|
+
|
|
34
|
+
return input_list
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def transform_numpy_array(
|
|
38
|
+
array: np.ndarray, operations: tuple[AxesTransformation, ...]
|
|
39
|
+
) -> np.ndarray:
|
|
40
|
+
for operation in operations:
|
|
41
|
+
if isinstance(operation, AxesTranspose):
|
|
42
|
+
array = np.transpose(array, operation.axes)
|
|
43
|
+
elif isinstance(operation, AxesExpand):
|
|
44
|
+
array = np.expand_dims(array, axis=operation.axes)
|
|
45
|
+
elif isinstance(operation, AxesSqueeze):
|
|
46
|
+
array = np.squeeze(array, axis=operation.axes)
|
|
47
|
+
else:
|
|
48
|
+
raise NgioValueError(f"Unknown operation {operation}")
|
|
49
|
+
return array
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def transform_dask_array(
|
|
53
|
+
array: da.Array, operations: tuple[AxesTransformation, ...]
|
|
54
|
+
) -> da.Array:
|
|
55
|
+
for operation in operations:
|
|
56
|
+
if isinstance(operation, AxesTranspose):
|
|
57
|
+
array = da.transpose(array, axes=operation.axes)
|
|
58
|
+
elif isinstance(operation, AxesExpand):
|
|
59
|
+
array = da.expand_dims(array, axis=operation.axes)
|
|
60
|
+
elif isinstance(operation, AxesSqueeze):
|
|
61
|
+
array = da.squeeze(array, axis=operation.axes)
|
|
62
|
+
else:
|
|
63
|
+
raise NgioValueError(f"Unknown operation {operation}")
|
|
64
|
+
return array
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Dimension metadata.
|
|
2
|
+
|
|
3
|
+
This is not related to the NGFF metadata,
|
|
4
|
+
but it is based on the actual metadata of the image data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Collection
|
|
8
|
+
|
|
9
|
+
from ngio.common._axes_transforms import transform_list
|
|
10
|
+
from ngio.ome_zarr_meta import AxesMapper
|
|
11
|
+
from ngio.utils import NgioValidationError, NgioValueError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Dimensions:
|
|
15
|
+
"""Dimension metadata."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
shape: tuple[int, ...],
|
|
20
|
+
axes_mapper: AxesMapper,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Create a Dimension object from a Zarr array.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
shape: The shape of the Zarr array.
|
|
26
|
+
axes_mapper: The axes mapper object.
|
|
27
|
+
"""
|
|
28
|
+
self._shape = shape
|
|
29
|
+
self._axes_mapper = axes_mapper
|
|
30
|
+
|
|
31
|
+
if len(self._shape) != len(self._axes_mapper.on_disk_axes):
|
|
32
|
+
raise NgioValidationError(
|
|
33
|
+
"The number of dimensions must match the number of axes. "
|
|
34
|
+
f"Expected Axis {self._axes_mapper.on_disk_axes_names} but got shape "
|
|
35
|
+
f"{self._shape}."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
"""Return the string representation of the object."""
|
|
40
|
+
dims = ", ".join(
|
|
41
|
+
f"{ax.on_disk_name}: {s}"
|
|
42
|
+
for ax, s in zip(self._axes_mapper.on_disk_axes, self._shape, strict=True)
|
|
43
|
+
)
|
|
44
|
+
return f"Dimensions({dims})"
|
|
45
|
+
|
|
46
|
+
def get(self, axis_name: str, strict: bool = True) -> int:
|
|
47
|
+
"""Return the dimension of the given axis name.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
axis_name: The name of the axis (either canonical or non-canonical).
|
|
51
|
+
strict: If True, raise an error if the axis does not exist.
|
|
52
|
+
"""
|
|
53
|
+
index = self._axes_mapper.get_index(axis_name)
|
|
54
|
+
if index is None and strict:
|
|
55
|
+
raise NgioValueError(f"Axis {axis_name} does not exist.")
|
|
56
|
+
elif index is None:
|
|
57
|
+
return 1
|
|
58
|
+
return self._shape[index]
|
|
59
|
+
|
|
60
|
+
def has_axis(self, axis_name: str) -> bool:
|
|
61
|
+
"""Return whether the axis exists."""
|
|
62
|
+
index = self._axes_mapper.get_axis(axis_name)
|
|
63
|
+
if index is None:
|
|
64
|
+
return False
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
def get_shape(self, axes_order: Collection[str]) -> tuple[int, ...]:
|
|
68
|
+
"""Return the shape in the given axes order."""
|
|
69
|
+
transforms = self._axes_mapper.to_order(axes_order)
|
|
70
|
+
return tuple(transform_list(list(self._shape), 1, transforms))
|
|
71
|
+
|
|
72
|
+
def get_canonical_shape(self) -> tuple[int, ...]:
|
|
73
|
+
"""Return the shape in the canonical order."""
|
|
74
|
+
transforms = self._axes_mapper.to_canonical()
|
|
75
|
+
return tuple(transform_list(list(self._shape), 1, transforms))
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
"""Return the string representation of the object."""
|
|
79
|
+
return str(self)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def on_disk_shape(self) -> tuple[int, ...]:
|
|
83
|
+
"""Return the shape as a tuple."""
|
|
84
|
+
return tuple(self._shape)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def is_time_series(self) -> bool:
|
|
88
|
+
"""Return whether the data is a time series."""
|
|
89
|
+
if self.get("t", strict=False) == 1:
|
|
90
|
+
return False
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_2d(self) -> bool:
|
|
95
|
+
"""Return whether the data is 2D."""
|
|
96
|
+
if self.get("z", strict=False) != 1:
|
|
97
|
+
return False
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def is_2d_time_series(self) -> bool:
|
|
102
|
+
"""Return whether the data is a 2D time series."""
|
|
103
|
+
return self.is_2d and self.is_time_series
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_3d(self) -> bool:
|
|
107
|
+
"""Return whether the data is 3D."""
|
|
108
|
+
return not self.is_2d
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def is_3d_time_series(self) -> bool:
|
|
112
|
+
"""Return whether the data is a 3D time series."""
|
|
113
|
+
return self.is_3d and self.is_time_series
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def is_multi_channels(self) -> bool:
|
|
117
|
+
"""Return whether the data has multiple channels."""
|
|
118
|
+
if self.get("c", strict=False) == 1:
|
|
119
|
+
return False
|
|
120
|
+
return True
|