ngio 0.1.6__py3-none-any.whl → 0.2.0a2__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 +31 -5
- ngio/common/__init__.py +44 -0
- ngio/common/_array_pipe.py +160 -0
- ngio/common/_axes_transforms.py +63 -0
- ngio/common/_common_types.py +5 -0
- ngio/common/_dimensions.py +113 -0
- ngio/common/_pyramid.py +223 -0
- ngio/{core/roi.py → common/_roi.py} +22 -23
- ngio/common/_slicer.py +97 -0
- ngio/{pipes/_zoom_utils.py → common/_zoom.py} +2 -78
- ngio/hcs/__init__.py +60 -0
- ngio/images/__init__.py +23 -0
- ngio/images/abstract_image.py +240 -0
- ngio/images/create.py +251 -0
- ngio/images/image.py +389 -0
- ngio/images/label.py +236 -0
- ngio/images/omezarr_container.py +535 -0
- ngio/ome_zarr_meta/__init__.py +35 -0
- ngio/ome_zarr_meta/_generic_handlers.py +320 -0
- ngio/ome_zarr_meta/_meta_handlers.py +142 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +63 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +481 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +378 -0
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +134 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +5 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +434 -0
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +84 -0
- ngio/ome_zarr_meta/v04/__init__.py +11 -0
- ngio/ome_zarr_meta/v04/_meta_handlers.py +54 -0
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +412 -0
- ngio/tables/__init__.py +21 -5
- ngio/tables/_validators.py +192 -0
- ngio/tables/backends/__init__.py +8 -0
- ngio/tables/backends/_abstract_backend.py +71 -0
- ngio/tables/backends/_anndata_utils.py +194 -0
- ngio/tables/backends/_anndata_v1.py +75 -0
- ngio/tables/backends/_json_v1.py +56 -0
- ngio/tables/backends/_table_backends.py +102 -0
- ngio/tables/tables_container.py +300 -0
- ngio/tables/v1/__init__.py +6 -5
- ngio/tables/v1/_feature_table.py +161 -0
- ngio/tables/v1/_generic_table.py +99 -182
- ngio/tables/v1/_masking_roi_table.py +175 -0
- ngio/tables/v1/_roi_table.py +226 -0
- ngio/utils/__init__.py +23 -10
- ngio/utils/_datasets.py +51 -0
- ngio/utils/_errors.py +10 -4
- ngio/utils/_zarr_utils.py +378 -0
- {ngio-0.1.6.dist-info → ngio-0.2.0a2.dist-info}/METADATA +18 -39
- ngio-0.2.0a2.dist-info/RECORD +53 -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/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 -60
- 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.6.dist-info/RECORD +0 -44
- {ngio-0.1.6.dist-info → ngio-0.2.0a2.dist-info}/WHEEL +0 -0
- {ngio-0.1.6.dist-info → ngio-0.2.0a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,19 +4,18 @@ These are the interfaces bwteen the ROI tables / masking ROI tables and
|
|
|
4
4
|
the ImageLikeHandler.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
7
|
import numpy as np
|
|
10
|
-
from pydantic import BaseModel, Field
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
9
|
|
|
12
|
-
from ngio.
|
|
13
|
-
from ngio.
|
|
10
|
+
from ngio.common._dimensions import Dimensions
|
|
11
|
+
from ngio.ome_zarr_meta.ngio_specs import PixelSize, SpaceUnits
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
def _to_raster(value: float, pixel_size: float, max_shape: int) -> int:
|
|
17
15
|
"""Convert to raster coordinates."""
|
|
18
16
|
round_value = int(np.round(value / pixel_size))
|
|
19
|
-
|
|
17
|
+
# Ensure the value is within the image shape boundaries
|
|
18
|
+
return max(0, min(round_value, max_shape))
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def _to_world(value: int, pixel_size: float) -> float:
|
|
@@ -27,6 +26,7 @@ def _to_world(value: int, pixel_size: float) -> float:
|
|
|
27
26
|
class WorldCooROI(BaseModel):
|
|
28
27
|
"""Region of interest (ROI) metadata."""
|
|
29
28
|
|
|
29
|
+
name: str
|
|
30
30
|
x_length: float
|
|
31
31
|
y_length: float
|
|
32
32
|
z_length: float = 1.0
|
|
@@ -34,7 +34,8 @@ class WorldCooROI(BaseModel):
|
|
|
34
34
|
y: float = 0.0
|
|
35
35
|
z: float = 0.0
|
|
36
36
|
unit: SpaceUnits = Field(SpaceUnits.micrometer, repr=False)
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(extra="allow")
|
|
38
39
|
|
|
39
40
|
def to_raster_coo(
|
|
40
41
|
self, pixel_size: PixelSize, dimensions: Dimensions
|
|
@@ -42,51 +43,49 @@ class WorldCooROI(BaseModel):
|
|
|
42
43
|
"""Convert to raster coordinates."""
|
|
43
44
|
dim_x = dimensions.get("x")
|
|
44
45
|
dim_y = dimensions.get("y")
|
|
45
|
-
|
|
46
|
+
# Will default to 1 if z does not exist
|
|
47
|
+
dim_z = dimensions.get("z", strict=False)
|
|
46
48
|
|
|
47
49
|
return RasterCooROI(
|
|
50
|
+
name=self.name,
|
|
48
51
|
x=_to_raster(self.x, pixel_size.x, dim_x),
|
|
49
52
|
y=_to_raster(self.y, pixel_size.y, dim_y),
|
|
50
53
|
z=_to_raster(self.z, pixel_size.z, dim_z),
|
|
51
54
|
x_length=_to_raster(self.x_length, pixel_size.x, dim_x),
|
|
52
55
|
y_length=_to_raster(self.y_length, pixel_size.y, dim_y),
|
|
53
56
|
z_length=_to_raster(self.z_length, pixel_size.z, dim_z),
|
|
54
|
-
original_roi=self,
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
class RasterCooROI(BaseModel):
|
|
59
61
|
"""Region of interest (ROI) metadata."""
|
|
60
62
|
|
|
63
|
+
name: str
|
|
61
64
|
x: int
|
|
62
65
|
y: int
|
|
63
66
|
z: int
|
|
64
67
|
x_length: int
|
|
65
68
|
y_length: int
|
|
66
69
|
z_length: int
|
|
67
|
-
|
|
70
|
+
model_config = ConfigDict(extra="allow")
|
|
68
71
|
|
|
69
72
|
def to_world_coo_roi(self, pixel_size: PixelSize) -> WorldCooROI:
|
|
70
73
|
"""Convert to world coordinates."""
|
|
71
74
|
return WorldCooROI(
|
|
75
|
+
name=self.name,
|
|
72
76
|
x=_to_world(self.x, pixel_size.x),
|
|
73
77
|
y=_to_world(self.y, pixel_size.y),
|
|
74
78
|
z=_to_world(self.z, pixel_size.z),
|
|
75
79
|
x_length=_to_world(self.x_length, pixel_size.x),
|
|
76
80
|
y_length=_to_world(self.y_length, pixel_size.y),
|
|
77
81
|
z_length=_to_world(self.z_length, pixel_size.z),
|
|
78
|
-
unit=pixel_size.
|
|
79
|
-
infos=self.original_roi.infos,
|
|
82
|
+
unit=pixel_size.space_unit,
|
|
80
83
|
)
|
|
81
84
|
|
|
82
|
-
def
|
|
83
|
-
"""Return the
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def z_slice(self) -> slice:
|
|
91
|
-
"""Return the slice for the z-axis."""
|
|
92
|
-
return slice(self.z, self.z + self.z_length)
|
|
85
|
+
def to_slices(self) -> dict[str, slice]:
|
|
86
|
+
"""Return the slices for the ROI."""
|
|
87
|
+
return {
|
|
88
|
+
"x": slice(self.x, self.x + self.x_length),
|
|
89
|
+
"y": slice(self.y, self.y + self.y_length),
|
|
90
|
+
"z": slice(self.z, self.z + self.z_length),
|
|
91
|
+
}
|
ngio/common/_slicer.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# %%
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
|
|
4
|
+
import dask.array as da
|
|
5
|
+
import numpy as np
|
|
6
|
+
import zarr
|
|
7
|
+
|
|
8
|
+
from ngio.common._dimensions import Dimensions
|
|
9
|
+
from ngio.ome_zarr_meta.ngio_specs import AxesTransformation
|
|
10
|
+
from ngio.utils import NgioValueError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _validate_int(value: int, shape: int) -> int:
|
|
14
|
+
if not isinstance(value, int):
|
|
15
|
+
raise NgioValueError(f"Invalid value {value} of type {type(value)}")
|
|
16
|
+
if value < 0 or value >= shape:
|
|
17
|
+
raise NgioValueError(
|
|
18
|
+
f"Invalid value {value}. Index out of bounds for axis of shape {shape}"
|
|
19
|
+
)
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _validate_iter_of_ints(value: Iterable[int], shape: int) -> list[int]:
|
|
24
|
+
if not isinstance(value, list):
|
|
25
|
+
raise NgioValueError(f"Invalid value {value} of type {type(value)}")
|
|
26
|
+
value = [_validate_int(v, shape=shape) for v in value]
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _validate_slice(value: slice, shape: int) -> slice:
|
|
31
|
+
start = value.start if value.start is not None else 0
|
|
32
|
+
start = max(start, 0)
|
|
33
|
+
stop = value.stop if value.stop is not None else shape
|
|
34
|
+
return slice(start, stop)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SliceTransform(AxesTransformation):
|
|
38
|
+
slices: tuple[slice | tuple[int, ...], ...]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def compute_and_slices(
|
|
42
|
+
*,
|
|
43
|
+
dimensions: Dimensions,
|
|
44
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
45
|
+
) -> SliceTransform:
|
|
46
|
+
_slices = {}
|
|
47
|
+
axes_names = dimensions._axes_mapper.on_disk_axes_names
|
|
48
|
+
for axis_name, slice_ in slice_kwargs.items():
|
|
49
|
+
axis = dimensions._axes_mapper.get_axis(axis_name)
|
|
50
|
+
if axis is None:
|
|
51
|
+
raise NgioValueError(
|
|
52
|
+
f"Invalid axis {axis_name}. "
|
|
53
|
+
f"Not found on the on-disk axes {axes_names}. "
|
|
54
|
+
"If you want to get/set a singletorn value include "
|
|
55
|
+
"it in the axes_order parameter."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
shape = dimensions.get(axis.on_disk_name)
|
|
59
|
+
|
|
60
|
+
if isinstance(slice_, int):
|
|
61
|
+
slice_ = _validate_int(slice_, shape)
|
|
62
|
+
slice_ = slice(slice_, slice_ + 1)
|
|
63
|
+
|
|
64
|
+
elif isinstance(slice_, Iterable):
|
|
65
|
+
slice_ = _validate_iter_of_ints(slice_, shape)
|
|
66
|
+
slice_ = tuple(slice_)
|
|
67
|
+
|
|
68
|
+
elif isinstance(slice_, slice):
|
|
69
|
+
slice_ = _validate_slice(slice_, shape)
|
|
70
|
+
|
|
71
|
+
elif not isinstance(slice_, slice):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Invalid slice definition {slice_} of type {type(slice_)}"
|
|
74
|
+
)
|
|
75
|
+
_slices[axis.on_disk_name] = slice_
|
|
76
|
+
|
|
77
|
+
slices = tuple(_slices.get(axis, slice(None)) for axis in axes_names)
|
|
78
|
+
return SliceTransform(slices=slices)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def numpy_get_slice(array: zarr.Array, slices: SliceTransform) -> np.ndarray:
|
|
82
|
+
return array[slices.slices]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def dask_get_slice(array: zarr.Array, slices: SliceTransform) -> da.Array:
|
|
86
|
+
da_array = da.from_zarr(array)
|
|
87
|
+
return da_array[slices.slices]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def numpy_set_slice(
|
|
91
|
+
array: zarr.Array, patch: np.ndarray, slices: SliceTransform
|
|
92
|
+
) -> None:
|
|
93
|
+
array[slices.slices] = patch
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def dask_set_slice(array: zarr.Array, patch: da.Array, slices: SliceTransform) -> None:
|
|
97
|
+
da.to_zarr(arr=patch, url=array, region=slices.slices)
|
|
@@ -3,7 +3,6 @@ from typing import Literal
|
|
|
3
3
|
|
|
4
4
|
import dask.array as da
|
|
5
5
|
import numpy as np
|
|
6
|
-
import zarr
|
|
7
6
|
from scipy.ndimage import zoom as scipy_zoom
|
|
8
7
|
|
|
9
8
|
|
|
@@ -76,7 +75,7 @@ def _zoom_inputs_check(
|
|
|
76
75
|
return _scale, _target_shape
|
|
77
76
|
|
|
78
77
|
|
|
79
|
-
def
|
|
78
|
+
def dask_zoom(
|
|
80
79
|
source_array: da.Array,
|
|
81
80
|
scale: tuple[int, ...] | None = None,
|
|
82
81
|
target_shape: tuple[int, ...] | None = None,
|
|
@@ -127,7 +126,7 @@ def _dask_zoom(
|
|
|
127
126
|
return out_array
|
|
128
127
|
|
|
129
128
|
|
|
130
|
-
def
|
|
129
|
+
def numpy_zoom(
|
|
131
130
|
source_array: np.ndarray,
|
|
132
131
|
scale: tuple[int, ...] | None = None,
|
|
133
132
|
target_shape: tuple[int, ...] | None = None,
|
|
@@ -155,78 +154,3 @@ def _numpy_zoom(
|
|
|
155
154
|
)
|
|
156
155
|
assert isinstance(out_array, np.ndarray)
|
|
157
156
|
return out_array
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def on_disk_zoom(
|
|
161
|
-
source: zarr.Array,
|
|
162
|
-
target: zarr.Array,
|
|
163
|
-
order: Literal[0, 1, 2] = 1,
|
|
164
|
-
mode: Literal["dask", "numpy"] = "dask",
|
|
165
|
-
) -> None:
|
|
166
|
-
"""Apply a zoom operation from a source zarr array to a target zarr array.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
source (zarr.Array): The source array to zoom.
|
|
170
|
-
target (zarr.Array): The target array to save the zoomed result to.
|
|
171
|
-
order (Literal[0, 1, 2]): The order of interpolation. Defaults to 1.
|
|
172
|
-
mode (Literal["dask", "numpy"]): The mode to use. Defaults to "dask".
|
|
173
|
-
"""
|
|
174
|
-
if not isinstance(source, zarr.Array):
|
|
175
|
-
raise ValueError("source must be a zarr array")
|
|
176
|
-
|
|
177
|
-
if not isinstance(target, zarr.Array):
|
|
178
|
-
raise ValueError("target must be a zarr array")
|
|
179
|
-
|
|
180
|
-
if source.dtype != target.dtype:
|
|
181
|
-
raise ValueError("source and target must have the same dtype")
|
|
182
|
-
|
|
183
|
-
assert mode in ["dask", "numpy"], "mode must be either 'dask' or 'numpy'"
|
|
184
|
-
|
|
185
|
-
if mode == "numpy":
|
|
186
|
-
target[...] = _numpy_zoom(source[...], target_shape=target.shape, order=order)
|
|
187
|
-
return None
|
|
188
|
-
|
|
189
|
-
source_array = da.from_zarr(source)
|
|
190
|
-
target_array = _dask_zoom(source_array, target_shape=target.shape, order=order)
|
|
191
|
-
|
|
192
|
-
target_array = target_array.rechunk(target.chunks)
|
|
193
|
-
target_array.compute_chunk_sizes()
|
|
194
|
-
target_array.to_zarr(target)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def on_disk_coarsen(
|
|
198
|
-
source: zarr.Array,
|
|
199
|
-
target: zarr.Array,
|
|
200
|
-
aggregation_function: np.ufunc,
|
|
201
|
-
) -> None:
|
|
202
|
-
"""Apply a coarsening operation from a source zarr array to a target zarr array.
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
source (zarr.Array): The source array to coarsen.
|
|
206
|
-
target (zarr.Array): The target array to save the coarsened result to.
|
|
207
|
-
aggregation_function (np.ufunc): The aggregation function to use.
|
|
208
|
-
"""
|
|
209
|
-
source_array = da.from_zarr(source)
|
|
210
|
-
|
|
211
|
-
_scale, _target_shape = _zoom_inputs_check(
|
|
212
|
-
source_array=source_array, scale=None, target_shape=target.shape
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
assert (
|
|
216
|
-
_target_shape == target.shape
|
|
217
|
-
), "Target shape must match the target array shape"
|
|
218
|
-
coarsening_setup = {}
|
|
219
|
-
for i, s in enumerate(_scale):
|
|
220
|
-
factor = 1 / s
|
|
221
|
-
if factor.is_integer():
|
|
222
|
-
coarsening_setup[i] = int(factor)
|
|
223
|
-
else:
|
|
224
|
-
raise ValueError(
|
|
225
|
-
f"Coarsening factor must be an integer, got {factor} on axis {i}"
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
out_target = da.coarsen(
|
|
229
|
-
aggregation_function, source_array, coarsening_setup, trim_excess=True
|
|
230
|
-
)
|
|
231
|
-
out_target = out_target.rechunk(target.chunks)
|
|
232
|
-
out_target.to_zarr(target)
|
ngio/hcs/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""OME-Zarr HCS objects models."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ngio.images import OmeZarrContainer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OmeZarrPlate:
|
|
10
|
+
"""Placeholder for the OME-Zarr image object."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
"""Initialize the OME-Zarr plate."""
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def wells(self) -> list[str]:
|
|
17
|
+
"""Return the wells."""
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def columns(self) -> list[str]:
|
|
21
|
+
"""Return the number of columns."""
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
def rows(self) -> list[str]:
|
|
25
|
+
"""Return the number of rows."""
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
def get_omezarr_well(self, *args, **kwargs) -> "OmeZarrWell":
|
|
29
|
+
"""Return the OME-Zarr well."""
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
def get_omezarr_image(self, *args, **kwargs) -> "OmeZarrContainer":
|
|
33
|
+
"""Return the OME-Zarr image."""
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OmeZarrWell:
|
|
38
|
+
"""Placeholder for the Image object."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args, **kwargs):
|
|
41
|
+
"""Initialize the OME-Zarr well."""
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
def acquisitions(self) -> list[str]:
|
|
45
|
+
"""Return the acquisition."""
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
|
|
48
|
+
def get_ome_zarr_image(self, *args, **kwargs) -> "OmeZarrContainer":
|
|
49
|
+
"""Return the OME-Zarr image."""
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def open_omezarr_plate(*args, **kwargs):
|
|
54
|
+
"""Open an OME-Zarr plate."""
|
|
55
|
+
return OmeZarrPlate(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def open_omezarr_well(*args, **kwargs):
|
|
59
|
+
"""Open an OME-Zarr well."""
|
|
60
|
+
return OmeZarrWell(*args, **kwargs)
|
ngio/images/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""OME-Zarr object models."""
|
|
2
|
+
|
|
3
|
+
from ngio.images.image import Image, ImagesContainer
|
|
4
|
+
from ngio.images.label import Label, LabelsContainer
|
|
5
|
+
from ngio.images.omezarr_container import (
|
|
6
|
+
OmeZarrContainer,
|
|
7
|
+
create_empty_omezarr,
|
|
8
|
+
create_omezarr_from_array,
|
|
9
|
+
open_image,
|
|
10
|
+
open_omezarr_container,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Image",
|
|
15
|
+
"ImagesContainer",
|
|
16
|
+
"Label",
|
|
17
|
+
"LabelsContainer",
|
|
18
|
+
"OmeZarrContainer",
|
|
19
|
+
"create_empty_omezarr",
|
|
20
|
+
"create_omezarr_from_array",
|
|
21
|
+
"open_image",
|
|
22
|
+
"open_omezarr_container",
|
|
23
|
+
]
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection, Iterable
|
|
4
|
+
from typing import Generic, Literal, TypeVar
|
|
5
|
+
|
|
6
|
+
import zarr
|
|
7
|
+
|
|
8
|
+
from ngio.common import (
|
|
9
|
+
ArrayLike,
|
|
10
|
+
Dimensions,
|
|
11
|
+
WorldCooROI,
|
|
12
|
+
consolidate_pyramid,
|
|
13
|
+
get_pipe,
|
|
14
|
+
set_pipe,
|
|
15
|
+
)
|
|
16
|
+
from ngio.ome_zarr_meta import (
|
|
17
|
+
Dataset,
|
|
18
|
+
ImageMetaHandler,
|
|
19
|
+
LabelMetaHandler,
|
|
20
|
+
PixelSize,
|
|
21
|
+
)
|
|
22
|
+
from ngio.utils import (
|
|
23
|
+
NgioFileExistsError,
|
|
24
|
+
ZarrGroupHandler,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_image_handler = TypeVar("_image_handler", ImageMetaHandler, LabelMetaHandler)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AbstractImage(Generic[_image_handler]):
|
|
31
|
+
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
32
|
+
|
|
33
|
+
This class is meant to be subclassed by specific image types.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
group_handler: ZarrGroupHandler,
|
|
39
|
+
path: str,
|
|
40
|
+
meta_handler: _image_handler,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Initialize the Image at a single level.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
group_handler: The Zarr group handler.
|
|
46
|
+
path: The path to the image in the omezarr file.
|
|
47
|
+
meta_handler: The image metadata handler.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
self._path = path
|
|
51
|
+
self._group_handler = group_handler
|
|
52
|
+
self._meta_handler = meta_handler
|
|
53
|
+
|
|
54
|
+
self._dataset = self._meta_handler.meta.get_dataset(path=path)
|
|
55
|
+
self._pixel_size = self._dataset.pixel_size
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
self._zarr_array = self._group_handler.get_array(self._dataset.path)
|
|
59
|
+
except NgioFileExistsError as e:
|
|
60
|
+
raise NgioFileExistsError(f"Could not find the dataset at {path}.") from e
|
|
61
|
+
|
|
62
|
+
self._dimensions = Dimensions(
|
|
63
|
+
shape=self._zarr_array.shape, axes_mapper=self._dataset.axes_mapper
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self._axer_mapper = self._dataset.axes_mapper
|
|
67
|
+
|
|
68
|
+
def __repr__(self) -> str:
|
|
69
|
+
"""Return a string representation of the image."""
|
|
70
|
+
return f"Image(path={self.path}, {self.dimensions})"
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def meta_handler(self) -> _image_handler:
|
|
74
|
+
"""Return the metadata."""
|
|
75
|
+
return self._meta_handler
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def zarr_array(self) -> zarr.Array:
|
|
79
|
+
"""Return the Zarr array."""
|
|
80
|
+
return self._zarr_array
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def shape(self) -> tuple[int, ...]:
|
|
84
|
+
"""Return the shape of the image."""
|
|
85
|
+
return self.zarr_array.shape
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def dtype(self) -> str:
|
|
89
|
+
"""Return the dtype of the image."""
|
|
90
|
+
return str(self.zarr_array.dtype)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def chunks(self) -> tuple[int, ...]:
|
|
94
|
+
"""Return the chunks of the image."""
|
|
95
|
+
return self.zarr_array.chunks
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def dimensions(self) -> Dimensions:
|
|
99
|
+
"""Return the dimensions of the image."""
|
|
100
|
+
return self._dimensions
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def pixel_size(self) -> PixelSize:
|
|
104
|
+
"""Return the pixel size of the image."""
|
|
105
|
+
return self._pixel_size
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def dataset(self) -> Dataset:
|
|
109
|
+
"""Return the dataset of the image."""
|
|
110
|
+
return self._dataset
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def path(self) -> str:
|
|
114
|
+
"""Return the path of the image."""
|
|
115
|
+
return self._dataset.path
|
|
116
|
+
|
|
117
|
+
def get_array(
|
|
118
|
+
self,
|
|
119
|
+
axes_order: Collection[str] | None = None,
|
|
120
|
+
mode: Literal["numpy", "dask", "delayed"] = "numpy",
|
|
121
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
122
|
+
) -> ArrayLike:
|
|
123
|
+
"""Get a slice of the image.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
axes_order: The order of the axes to return the array.
|
|
127
|
+
mode: The mode to return the array.
|
|
128
|
+
**slice_kwargs: The slices to get the array.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The array of the region of interest.
|
|
132
|
+
"""
|
|
133
|
+
return get_pipe(
|
|
134
|
+
array=self.zarr_array,
|
|
135
|
+
dimensions=self.dimensions,
|
|
136
|
+
axes_order=axes_order,
|
|
137
|
+
mode=mode,
|
|
138
|
+
**slice_kwargs,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def get_roi(
|
|
142
|
+
self,
|
|
143
|
+
roi: WorldCooROI,
|
|
144
|
+
axes_order: Collection[str] | None = None,
|
|
145
|
+
mode: Literal["numpy", "dask", "delayed"] = "numpy",
|
|
146
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
147
|
+
) -> ArrayLike:
|
|
148
|
+
"""Get a slice of the image.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
roi: The region of interest to get the array.
|
|
152
|
+
axes_order: The order of the axes to return the array.
|
|
153
|
+
mode: The mode to return the array.
|
|
154
|
+
**slice_kwargs: The slices to get the array.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The array of the region of interest.
|
|
158
|
+
"""
|
|
159
|
+
raster_roi = roi.to_raster_coo(
|
|
160
|
+
pixel_size=self.pixel_size, dimensions=self.dimensions
|
|
161
|
+
).to_slices()
|
|
162
|
+
|
|
163
|
+
for key in slice_kwargs.keys():
|
|
164
|
+
if key in raster_roi:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Key {key} is already in the slice_kwargs. "
|
|
167
|
+
"Ambiguous which one to use: "
|
|
168
|
+
f"{key}={slice_kwargs[key]} or roi_{key}={raster_roi[key]}"
|
|
169
|
+
)
|
|
170
|
+
return self.get_array(
|
|
171
|
+
axes_order=axes_order, mode=mode, **raster_roi, **slice_kwargs
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def set_array(
|
|
175
|
+
self,
|
|
176
|
+
patch: ArrayLike,
|
|
177
|
+
axes_order: Collection[str] | None = None,
|
|
178
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Set a slice of the image.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
patch: The patch to set.
|
|
184
|
+
axes_order: The order of the axes to set the patch.
|
|
185
|
+
**slice_kwargs: The slices to set the patch.
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
set_pipe(
|
|
189
|
+
array=self.zarr_array,
|
|
190
|
+
patch=patch,
|
|
191
|
+
dimensions=self.dimensions,
|
|
192
|
+
axes_order=axes_order,
|
|
193
|
+
**slice_kwargs,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def set_roi(
|
|
197
|
+
self,
|
|
198
|
+
roi: WorldCooROI,
|
|
199
|
+
patch: ArrayLike,
|
|
200
|
+
axes_order: Collection[str] | None = None,
|
|
201
|
+
**slice_kwargs: slice | int | Iterable[int],
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Set a slice of the image.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
roi: The region of interest to set the patch.
|
|
207
|
+
patch: The patch to set.
|
|
208
|
+
axes_order: The order of the axes to set the patch.
|
|
209
|
+
**slice_kwargs: The slices to set the patch.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
raster_roi = roi.to_raster_coo(
|
|
213
|
+
pixel_size=self.pixel_size, dimensions=self.dimensions
|
|
214
|
+
).to_slices()
|
|
215
|
+
|
|
216
|
+
for key in slice_kwargs.keys():
|
|
217
|
+
if key in raster_roi:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Key {key} is already in the slice_kwargs. "
|
|
220
|
+
"Ambiguous which one to use: "
|
|
221
|
+
f"{key}={slice_kwargs[key]} or roi_{key}={raster_roi[key]}"
|
|
222
|
+
)
|
|
223
|
+
self.set_array(patch=patch, axes_order=axes_order, **raster_roi, **slice_kwargs)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def consolidate_image(
|
|
227
|
+
image: AbstractImage,
|
|
228
|
+
order: Literal[0, 1, 2] = 1,
|
|
229
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Consolidate the image on disk."""
|
|
232
|
+
target_paths = image._meta_handler.meta.paths
|
|
233
|
+
targets = [
|
|
234
|
+
image._group_handler.get_array(path)
|
|
235
|
+
for path in target_paths
|
|
236
|
+
if path != image.path
|
|
237
|
+
]
|
|
238
|
+
consolidate_pyramid(
|
|
239
|
+
source=image.zarr_array, targets=targets, order=order, mode=mode
|
|
240
|
+
)
|