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
ngio/images/create.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Utility functions for working with OME-Zarr images."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
from ngio.common._pyramid import init_empty_pyramid
|
|
7
|
+
from ngio.ome_zarr_meta import (
|
|
8
|
+
ImplementedImageMetaHandlers,
|
|
9
|
+
ImplementedLabelMetaHandlers,
|
|
10
|
+
NgioImageMeta,
|
|
11
|
+
NgioLabelMeta,
|
|
12
|
+
PixelSize,
|
|
13
|
+
)
|
|
14
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
15
|
+
SpaceUnits,
|
|
16
|
+
TimeUnits,
|
|
17
|
+
canonical_axes_order,
|
|
18
|
+
canonical_label_axes_order,
|
|
19
|
+
)
|
|
20
|
+
from ngio.utils import StoreOrGroup, ZarrGroupHandler
|
|
21
|
+
|
|
22
|
+
_image_or_label_meta = TypeVar("_image_or_label_meta", NgioImageMeta, NgioLabelMeta)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _init_generic_meta(
|
|
26
|
+
meta_type: type[_image_or_label_meta],
|
|
27
|
+
xy_pixelsize: float,
|
|
28
|
+
axes_names: Collection[str],
|
|
29
|
+
z_spacing: float = 1.0,
|
|
30
|
+
time_spacing: float = 1.0,
|
|
31
|
+
levels: int | list[str] = 5,
|
|
32
|
+
xy_scaling_factor: float = 2.0,
|
|
33
|
+
z_scaling_factor: float = 1.0,
|
|
34
|
+
space_unit: SpaceUnits | str | None = None,
|
|
35
|
+
time_unit: TimeUnits | str | None = None,
|
|
36
|
+
name: str | None = None,
|
|
37
|
+
version: str = "0.4",
|
|
38
|
+
) -> tuple[_image_or_label_meta, list[float]]:
|
|
39
|
+
"""Initialize the metadata for an image or label."""
|
|
40
|
+
scaling_factors = []
|
|
41
|
+
for ax in axes_names:
|
|
42
|
+
if ax == "z":
|
|
43
|
+
scaling_factors.append(z_scaling_factor)
|
|
44
|
+
elif ax in ["x", "y"]:
|
|
45
|
+
scaling_factors.append(xy_scaling_factor)
|
|
46
|
+
else:
|
|
47
|
+
scaling_factors.append(1.0)
|
|
48
|
+
|
|
49
|
+
if space_unit is None:
|
|
50
|
+
space_unit = SpaceUnits.micrometer
|
|
51
|
+
elif isinstance(space_unit, str):
|
|
52
|
+
space_unit = SpaceUnits(space_unit)
|
|
53
|
+
elif not isinstance(space_unit, SpaceUnits):
|
|
54
|
+
raise ValueError(f"space_unit can not be {type(space_unit)}.")
|
|
55
|
+
|
|
56
|
+
if time_unit is None:
|
|
57
|
+
time_unit = TimeUnits.seconds
|
|
58
|
+
elif isinstance(time_unit, str):
|
|
59
|
+
time_unit = TimeUnits(time_unit)
|
|
60
|
+
elif not isinstance(time_unit, TimeUnits):
|
|
61
|
+
raise ValueError(f"time_units can not be {type(time_unit)}.")
|
|
62
|
+
|
|
63
|
+
pixel_sizes = PixelSize(
|
|
64
|
+
x=xy_pixelsize,
|
|
65
|
+
y=xy_pixelsize,
|
|
66
|
+
z=z_spacing,
|
|
67
|
+
t=time_spacing,
|
|
68
|
+
space_unit=space_unit,
|
|
69
|
+
time_unit=time_unit,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
meta = meta_type.default_init(
|
|
73
|
+
name=name,
|
|
74
|
+
levels=levels,
|
|
75
|
+
axes_names=axes_names,
|
|
76
|
+
pixel_size=pixel_sizes,
|
|
77
|
+
scaling_factors=scaling_factors,
|
|
78
|
+
version=version,
|
|
79
|
+
)
|
|
80
|
+
return meta, scaling_factors
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _create_empty_label(
|
|
84
|
+
store: StoreOrGroup,
|
|
85
|
+
shape: Collection[int],
|
|
86
|
+
xy_pixelsize: float,
|
|
87
|
+
z_spacing: float = 1.0,
|
|
88
|
+
time_spacing: float = 1.0,
|
|
89
|
+
levels: int | list[str] = 5,
|
|
90
|
+
xy_scaling_factor: float = 2.0,
|
|
91
|
+
z_scaling_factor: float = 1.0,
|
|
92
|
+
space_unit: SpaceUnits | str | None = None,
|
|
93
|
+
time_unit: TimeUnits | str | None = None,
|
|
94
|
+
axes_names: Collection[str] | None = None,
|
|
95
|
+
name: str | None = None,
|
|
96
|
+
chunks: Collection[int] | None = None,
|
|
97
|
+
dtype: str = "uint16",
|
|
98
|
+
overwrite: bool = False,
|
|
99
|
+
version: str = "0.4",
|
|
100
|
+
) -> ZarrGroupHandler:
|
|
101
|
+
"""Create an empty label with the given shape and metadata.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
105
|
+
shape (Collection[int]): The shape of the image.
|
|
106
|
+
xy_pixelsize (float): The pixel size in x and y dimensions.
|
|
107
|
+
z_spacing (float, optional): The spacing between z slices. Defaults to 1.0.
|
|
108
|
+
time_spacing (float, optional): The spacing between time points.
|
|
109
|
+
Defaults to 1.0.
|
|
110
|
+
levels (int | list[str], optional): The number of levels in the pyramid or a
|
|
111
|
+
list of level names. Defaults to 5.
|
|
112
|
+
xy_scaling_factor (float, optional): The down-scaling factor in x and y
|
|
113
|
+
dimensions. Defaults to 2.0.
|
|
114
|
+
z_scaling_factor (float, optional): The down-scaling factor in z dimension.
|
|
115
|
+
Defaults to 1.0.
|
|
116
|
+
space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
|
|
117
|
+
None.
|
|
118
|
+
time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
|
|
119
|
+
None.
|
|
120
|
+
axes_names (Collection[str] | None, optional): The names of the axes.
|
|
121
|
+
If None the canonical names are used. Defaults to None.
|
|
122
|
+
name (str | None, optional): The name of the image. Defaults to None.
|
|
123
|
+
chunks (Collection[int] | None, optional): The chunk shape. If None the shape
|
|
124
|
+
is used. Defaults to None.
|
|
125
|
+
dtype (str, optional): The data type of the image. Defaults to "uint16".
|
|
126
|
+
overwrite (bool, optional): Whether to overwrite an existing image.
|
|
127
|
+
Defaults to True.
|
|
128
|
+
version (str, optional): The version of the OME-Zarr specification.
|
|
129
|
+
Defaults to "0.4".
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
if axes_names is None:
|
|
133
|
+
axes_names = canonical_label_axes_order()[-len(shape) :]
|
|
134
|
+
|
|
135
|
+
meta, scaling_factors = _init_generic_meta(
|
|
136
|
+
meta_type=NgioLabelMeta,
|
|
137
|
+
xy_pixelsize=xy_pixelsize,
|
|
138
|
+
z_spacing=z_spacing,
|
|
139
|
+
time_spacing=time_spacing,
|
|
140
|
+
levels=levels,
|
|
141
|
+
xy_scaling_factor=xy_scaling_factor,
|
|
142
|
+
z_scaling_factor=z_scaling_factor,
|
|
143
|
+
space_unit=space_unit,
|
|
144
|
+
time_unit=time_unit,
|
|
145
|
+
axes_names=axes_names,
|
|
146
|
+
name=name,
|
|
147
|
+
version=version,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
mode = "w" if overwrite else "w-"
|
|
151
|
+
group_handler = ZarrGroupHandler(store=store, mode=mode, cache=False)
|
|
152
|
+
image_handler = ImplementedLabelMetaHandlers().get_handler(
|
|
153
|
+
version=version, group_handler=group_handler
|
|
154
|
+
)
|
|
155
|
+
image_handler.write_meta(meta)
|
|
156
|
+
|
|
157
|
+
init_empty_pyramid(
|
|
158
|
+
store=store,
|
|
159
|
+
paths=meta.paths,
|
|
160
|
+
scaling_factors=scaling_factors,
|
|
161
|
+
ref_shape=shape,
|
|
162
|
+
chunks=chunks,
|
|
163
|
+
dtype=dtype,
|
|
164
|
+
mode="a",
|
|
165
|
+
)
|
|
166
|
+
return group_handler
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _create_empty_image(
|
|
170
|
+
store: StoreOrGroup,
|
|
171
|
+
shape: Collection[int],
|
|
172
|
+
xy_pixelsize: float,
|
|
173
|
+
z_spacing: float = 1.0,
|
|
174
|
+
time_spacing: float = 1.0,
|
|
175
|
+
levels: int | list[str] = 5,
|
|
176
|
+
xy_scaling_factor: float = 2,
|
|
177
|
+
z_scaling_factor: float = 1.0,
|
|
178
|
+
space_unit: SpaceUnits | str | None = None,
|
|
179
|
+
time_unit: TimeUnits | str | None = None,
|
|
180
|
+
axes_names: Collection[str] | None = None,
|
|
181
|
+
name: str | None = None,
|
|
182
|
+
chunks: Collection[int] | None = None,
|
|
183
|
+
dtype: str = "uint16",
|
|
184
|
+
overwrite: bool = False,
|
|
185
|
+
version: str = "0.4",
|
|
186
|
+
) -> ZarrGroupHandler:
|
|
187
|
+
"""Create an empty OME-Zarr image with the given shape and metadata.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
191
|
+
shape (Collection[int]): The shape of the image.
|
|
192
|
+
xy_pixelsize (float): The pixel size in x and y dimensions.
|
|
193
|
+
z_spacing (float, optional): The spacing between z slices. Defaults to 1.0.
|
|
194
|
+
time_spacing (float, optional): The spacing between time points.
|
|
195
|
+
Defaults to 1.0.
|
|
196
|
+
levels (int | list[str], optional): The number of levels in the pyramid or a
|
|
197
|
+
list of level names. Defaults to 5.
|
|
198
|
+
xy_scaling_factor (float, optional): The down-scaling factor in x and y
|
|
199
|
+
dimensions. Defaults to 2.0.
|
|
200
|
+
z_scaling_factor (float, optional): The down-scaling factor in z dimension.
|
|
201
|
+
Defaults to 1.0.
|
|
202
|
+
space_unit (SpaceUnits | str | None, optional): The unit of space. Defaults to
|
|
203
|
+
None.
|
|
204
|
+
time_unit (TimeUnits | str | None, optional): The unit of time. Defaults to
|
|
205
|
+
None.
|
|
206
|
+
axes_names (Collection[str] | None, optional): The names of the axes.
|
|
207
|
+
If None the canonical names are used. Defaults to None.
|
|
208
|
+
name (str | None, optional): The name of the image. Defaults to None.
|
|
209
|
+
chunks (Collection[int] | None, optional): The chunk shape. If None the shape
|
|
210
|
+
is used. Defaults to None.
|
|
211
|
+
dtype (str, optional): The data type of the image. Defaults to "uint16".
|
|
212
|
+
overwrite (bool, optional): Whether to overwrite an existing image.
|
|
213
|
+
Defaults to True.
|
|
214
|
+
version (str, optional): The version of the OME-Zarr specification.
|
|
215
|
+
Defaults to "0.4".
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
if axes_names is None:
|
|
219
|
+
axes_names = canonical_axes_order()[-len(shape) :]
|
|
220
|
+
|
|
221
|
+
meta, scaling_factors = _init_generic_meta(
|
|
222
|
+
meta_type=NgioImageMeta,
|
|
223
|
+
xy_pixelsize=xy_pixelsize,
|
|
224
|
+
z_spacing=z_spacing,
|
|
225
|
+
time_spacing=time_spacing,
|
|
226
|
+
levels=levels,
|
|
227
|
+
xy_scaling_factor=xy_scaling_factor,
|
|
228
|
+
z_scaling_factor=z_scaling_factor,
|
|
229
|
+
space_unit=space_unit,
|
|
230
|
+
time_unit=time_unit,
|
|
231
|
+
axes_names=axes_names,
|
|
232
|
+
name=name,
|
|
233
|
+
version=version,
|
|
234
|
+
)
|
|
235
|
+
mode = "w" if overwrite else "w-"
|
|
236
|
+
group_handler = ZarrGroupHandler(store=store, mode=mode, cache=False)
|
|
237
|
+
image_handler = ImplementedImageMetaHandlers().get_handler(
|
|
238
|
+
version=version, group_handler=group_handler
|
|
239
|
+
)
|
|
240
|
+
image_handler.write_meta(meta)
|
|
241
|
+
|
|
242
|
+
init_empty_pyramid(
|
|
243
|
+
store=store,
|
|
244
|
+
paths=meta.paths,
|
|
245
|
+
scaling_factors=scaling_factors,
|
|
246
|
+
ref_shape=shape,
|
|
247
|
+
chunks=chunks,
|
|
248
|
+
dtype=dtype,
|
|
249
|
+
mode="a",
|
|
250
|
+
)
|
|
251
|
+
return group_handler
|
ngio/images/image.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from dask import array as da
|
|
7
|
+
|
|
8
|
+
from ngio.common import Dimensions
|
|
9
|
+
from ngio.images.abstract_image import AbstractImage, consolidate_image
|
|
10
|
+
from ngio.images.create import _create_empty_image
|
|
11
|
+
from ngio.ome_zarr_meta import (
|
|
12
|
+
ImageMetaHandler,
|
|
13
|
+
ImplementedImageMetaHandlers,
|
|
14
|
+
NgioImageMeta,
|
|
15
|
+
PixelSize,
|
|
16
|
+
)
|
|
17
|
+
from ngio.ome_zarr_meta.ngio_specs import Channel, ChannelsMeta, ChannelVisualisation
|
|
18
|
+
from ngio.utils import (
|
|
19
|
+
NgioValidationError,
|
|
20
|
+
StoreOrGroup,
|
|
21
|
+
ZarrGroupHandler,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _check_channel_meta(meta: NgioImageMeta, dimension: Dimensions) -> ChannelsMeta:
|
|
26
|
+
"""Check the channel metadata."""
|
|
27
|
+
c_dim = dimension.get("c", strict=False)
|
|
28
|
+
c_dim = 1 if c_dim is None else c_dim
|
|
29
|
+
|
|
30
|
+
if meta.channels_meta is None:
|
|
31
|
+
return ChannelsMeta.default_init(labels=c_dim)
|
|
32
|
+
|
|
33
|
+
if len(meta.channels) != c_dim:
|
|
34
|
+
raise NgioValidationError(
|
|
35
|
+
"The number of channels does not match the image. "
|
|
36
|
+
f"Expected {len(meta.channels)} channels, got {c_dim}."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return meta.channels_meta
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Image(AbstractImage[ImageMetaHandler]):
|
|
43
|
+
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
44
|
+
|
|
45
|
+
This class is meant to be subclassed by specific image types.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
group_handler: ZarrGroupHandler,
|
|
51
|
+
path: str,
|
|
52
|
+
meta_handler: ImageMetaHandler | None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the Image at a single level.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
group_handler: The Zarr group handler.
|
|
58
|
+
path: The path to the image in the omezarr file.
|
|
59
|
+
meta_handler: The image metadata handler.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
if meta_handler is None:
|
|
63
|
+
meta_handler = ImplementedImageMetaHandlers().find_meta_handler(
|
|
64
|
+
group_handler
|
|
65
|
+
)
|
|
66
|
+
super().__init__(
|
|
67
|
+
group_handler=group_handler, path=path, meta_handler=meta_handler
|
|
68
|
+
)
|
|
69
|
+
self._channels_meta = _check_channel_meta(self.meta, self.dimensions)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def meta(self) -> NgioImageMeta:
|
|
73
|
+
"""Return the metadata."""
|
|
74
|
+
return self._meta_handler.meta
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def channel_labels(self) -> list[str]:
|
|
78
|
+
"""Return the channels of the image."""
|
|
79
|
+
channel_labels = []
|
|
80
|
+
for c in self._channels_meta.channels:
|
|
81
|
+
channel_labels.append(c.label)
|
|
82
|
+
return channel_labels
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def wavelength_ids(self) -> list[str | None]:
|
|
86
|
+
"""Return the list of wavelength of the image."""
|
|
87
|
+
wavelength_ids = []
|
|
88
|
+
for c in self._channels_meta.channels:
|
|
89
|
+
wavelength_ids.append(c.wavelength_id)
|
|
90
|
+
return wavelength_ids
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def num_channels(self) -> int:
|
|
94
|
+
"""Return the number of channels."""
|
|
95
|
+
return len(self._channels_meta.channels)
|
|
96
|
+
|
|
97
|
+
def consolidate(
|
|
98
|
+
self,
|
|
99
|
+
order: Literal[0, 1, 2] = 1,
|
|
100
|
+
mode: Literal["dask", "numpy", "coarsen"] = "dask",
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Consolidate the label on disk."""
|
|
103
|
+
consolidate_image(self, order=order, mode=mode)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ImagesContainer:
|
|
107
|
+
"""A class to handle the /labels group in an OME-NGFF file."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, group_handler: ZarrGroupHandler) -> None:
|
|
110
|
+
"""Initialize the LabelGroupHandler."""
|
|
111
|
+
self._group_handler = group_handler
|
|
112
|
+
self._meta_handler = ImplementedImageMetaHandlers().find_meta_handler(
|
|
113
|
+
group_handler
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def meta(self) -> NgioImageMeta:
|
|
118
|
+
"""Return the metadata."""
|
|
119
|
+
return self._meta_handler.meta
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def levels(self) -> int:
|
|
123
|
+
"""Return the number of levels in the image."""
|
|
124
|
+
return self._meta_handler.meta.levels
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def levels_paths(self) -> list[str]:
|
|
128
|
+
"""Return the paths of the levels in the image."""
|
|
129
|
+
return self._meta_handler.meta.paths
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def num_channels(self) -> int:
|
|
133
|
+
"""Return the number of channels."""
|
|
134
|
+
image = self.get()
|
|
135
|
+
return image.num_channels
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def channel_labels(self) -> list[str]:
|
|
139
|
+
"""Return the channels of the image."""
|
|
140
|
+
image = self.get()
|
|
141
|
+
return image.channel_labels
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def wavelength_ids(self) -> list[str | None]:
|
|
145
|
+
"""Return the wavelength of the image."""
|
|
146
|
+
image = self.get()
|
|
147
|
+
return image.wavelength_ids
|
|
148
|
+
|
|
149
|
+
def initialize_channel_meta(
|
|
150
|
+
self,
|
|
151
|
+
labels: Collection[str] | int | None = None,
|
|
152
|
+
wavelength_id: Collection[str] | None = None,
|
|
153
|
+
percentiles: tuple[float, float] | None = None,
|
|
154
|
+
colors: Collection[str] | None = None,
|
|
155
|
+
active: Collection[bool] | None = None,
|
|
156
|
+
**omero_kwargs: dict,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Create a ChannelsMeta object with the default unit.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
labels(Collection[str] | int): The list of channels names in the image.
|
|
162
|
+
If an integer is provided, the channels will be named "channel_i".
|
|
163
|
+
wavelength_id(Collection[str] | None): The wavelength ID of the channel.
|
|
164
|
+
If None, the wavelength ID will be the same as the channel name.
|
|
165
|
+
percentiles(tuple[float, float] | None): The start and end percentiles
|
|
166
|
+
for each channel. If None, the percentiles will not be computed.
|
|
167
|
+
colors(Collection[str, NgioColors] | None): The list of colors for the
|
|
168
|
+
channels. If None, the colors will be random.
|
|
169
|
+
active (Collection[bool] | None):active(bool): Whether the channel should
|
|
170
|
+
be shown by default.
|
|
171
|
+
omero_kwargs(dict): Extra fields to store in the omero attributes.
|
|
172
|
+
"""
|
|
173
|
+
ref = self.get()
|
|
174
|
+
|
|
175
|
+
if percentiles is not None:
|
|
176
|
+
start, end = compute_image_percentile(
|
|
177
|
+
ref, start_percentile=percentiles[0], end_percentile=percentiles[1]
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
start, end = None, None
|
|
181
|
+
|
|
182
|
+
if labels is None:
|
|
183
|
+
labels = ref.num_channels
|
|
184
|
+
|
|
185
|
+
channel_meta = ChannelsMeta.default_init(
|
|
186
|
+
labels=labels,
|
|
187
|
+
wavelength_id=wavelength_id,
|
|
188
|
+
colors=colors,
|
|
189
|
+
start=start,
|
|
190
|
+
end=end,
|
|
191
|
+
active=active,
|
|
192
|
+
data_type=ref.dtype,
|
|
193
|
+
**omero_kwargs,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
meta = self.meta
|
|
197
|
+
meta.set_channels_meta(channel_meta)
|
|
198
|
+
self._meta_handler.write_meta(meta)
|
|
199
|
+
|
|
200
|
+
def update_percentiles(
|
|
201
|
+
self,
|
|
202
|
+
start_percentile: float = 0.1,
|
|
203
|
+
end_percentile: float = 99.9,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Update the percentiles of the channels."""
|
|
206
|
+
if self.meta._channels_meta is None:
|
|
207
|
+
raise NgioValidationError("The channels meta is not initialized.")
|
|
208
|
+
|
|
209
|
+
image = self.get()
|
|
210
|
+
starts, ends = compute_image_percentile(
|
|
211
|
+
image, start_percentile=start_percentile, end_percentile=end_percentile
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
channels = []
|
|
215
|
+
for c, channel in enumerate(self.meta._channels_meta.channels):
|
|
216
|
+
new_v = ChannelVisualisation(
|
|
217
|
+
start=starts[c],
|
|
218
|
+
end=ends[c],
|
|
219
|
+
**channel.channel_visualisation.model_dump(exclude={"start", "end"}),
|
|
220
|
+
)
|
|
221
|
+
new_c = Channel(
|
|
222
|
+
channel_visualisation=new_v,
|
|
223
|
+
**channel.model_dump(exclude={"channel_visualisation"}),
|
|
224
|
+
)
|
|
225
|
+
channels.append(new_c)
|
|
226
|
+
|
|
227
|
+
new_meta = ChannelsMeta(channels=channels)
|
|
228
|
+
|
|
229
|
+
meta = self.meta
|
|
230
|
+
meta.set_channels_meta(new_meta)
|
|
231
|
+
self._meta_handler.write_meta(meta)
|
|
232
|
+
|
|
233
|
+
def derive(
|
|
234
|
+
self,
|
|
235
|
+
store: StoreOrGroup,
|
|
236
|
+
ref_path: str | None = None,
|
|
237
|
+
shape: Collection[int] | None = None,
|
|
238
|
+
chunks: Collection[int] | None = None,
|
|
239
|
+
xy_scaling_factor: float = 2.0,
|
|
240
|
+
z_scaling_factor: float = 1.0,
|
|
241
|
+
overwrite: bool = False,
|
|
242
|
+
) -> "ImagesContainer":
|
|
243
|
+
"""Create an OME-Zarr image from a numpy array."""
|
|
244
|
+
return derive_image_container(
|
|
245
|
+
image_container=self,
|
|
246
|
+
store=store,
|
|
247
|
+
ref_path=ref_path,
|
|
248
|
+
shape=shape,
|
|
249
|
+
chunks=chunks,
|
|
250
|
+
xy_scaling_factor=xy_scaling_factor,
|
|
251
|
+
z_scaling_factor=z_scaling_factor,
|
|
252
|
+
overwrite=overwrite,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def get(
|
|
256
|
+
self,
|
|
257
|
+
path: str | None = None,
|
|
258
|
+
pixel_size: PixelSize | None = None,
|
|
259
|
+
highest_resolution: bool = True,
|
|
260
|
+
) -> Image:
|
|
261
|
+
"""Get an image at a specific level."""
|
|
262
|
+
if path is not None or pixel_size is not None:
|
|
263
|
+
highest_resolution = False
|
|
264
|
+
dataset = self._meta_handler.meta.get_dataset(
|
|
265
|
+
path=path, pixel_size=pixel_size, highest_resolution=highest_resolution
|
|
266
|
+
)
|
|
267
|
+
return Image(
|
|
268
|
+
group_handler=self._group_handler,
|
|
269
|
+
path=dataset.path,
|
|
270
|
+
meta_handler=self._meta_handler,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def compute_image_percentile(
|
|
275
|
+
image: Image,
|
|
276
|
+
start_percentile: float = 0.1,
|
|
277
|
+
end_percentile: float = 99.9,
|
|
278
|
+
) -> tuple[list[float], list[float]]:
|
|
279
|
+
"""Compute the start and end percentiles for each channel of an image.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
image: The image to compute the percentiles for.
|
|
283
|
+
start_percentile: The start percentile to compute.
|
|
284
|
+
end_percentile: The end percentile to compute.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
A tuple containing the start and end percentiles for each channel.
|
|
288
|
+
"""
|
|
289
|
+
starts, ends = [], []
|
|
290
|
+
for c in range(image.num_channels):
|
|
291
|
+
if image.num_channels == 1:
|
|
292
|
+
data = image.get_array(mode="dask").ravel()
|
|
293
|
+
else:
|
|
294
|
+
data = image.get_array(c=c, mode="dask").ravel()
|
|
295
|
+
# remove all the zeros
|
|
296
|
+
mask = data > 1e-16
|
|
297
|
+
data = data[mask]
|
|
298
|
+
_data = data.compute()
|
|
299
|
+
if _data.size == 0:
|
|
300
|
+
starts.append(0.0)
|
|
301
|
+
ends.append(0.0)
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
# compute the percentiles
|
|
305
|
+
_s_perc, _e_perc = da.percentile(
|
|
306
|
+
data, [start_percentile, end_percentile], method="nearest"
|
|
307
|
+
).compute()
|
|
308
|
+
|
|
309
|
+
starts.append(float(_s_perc))
|
|
310
|
+
ends.append(float(_e_perc))
|
|
311
|
+
|
|
312
|
+
return starts, ends
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def derive_image_container(
|
|
316
|
+
image_container: ImagesContainer,
|
|
317
|
+
store: StoreOrGroup,
|
|
318
|
+
ref_path: str | None = None,
|
|
319
|
+
shape: Collection[int] | None = None,
|
|
320
|
+
chunks: Collection[int] | None = None,
|
|
321
|
+
xy_scaling_factor: float = 2.0,
|
|
322
|
+
z_scaling_factor: float = 1.0,
|
|
323
|
+
overwrite: bool = False,
|
|
324
|
+
) -> ImagesContainer:
|
|
325
|
+
"""Create an OME-Zarr image from a numpy array."""
|
|
326
|
+
if ref_path is None:
|
|
327
|
+
ref_image = image_container.get()
|
|
328
|
+
else:
|
|
329
|
+
ref_image = image_container.get(path=ref_path)
|
|
330
|
+
|
|
331
|
+
ref_meta = ref_image.meta
|
|
332
|
+
|
|
333
|
+
if shape is None:
|
|
334
|
+
shape = ref_image.shape
|
|
335
|
+
else:
|
|
336
|
+
if len(shape) != len(ref_image.shape):
|
|
337
|
+
raise NgioValidationError(
|
|
338
|
+
"The shape of the new image does not match the reference image."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if chunks is None:
|
|
342
|
+
chunks = ref_image.chunks
|
|
343
|
+
else:
|
|
344
|
+
if len(chunks) != len(ref_image.chunks):
|
|
345
|
+
raise NgioValidationError(
|
|
346
|
+
"The chunks of the new image does not match the reference image."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
handler = _create_empty_image(
|
|
350
|
+
store=store,
|
|
351
|
+
shape=shape,
|
|
352
|
+
xy_pixelsize=ref_image.pixel_size.x,
|
|
353
|
+
z_spacing=ref_image.pixel_size.z,
|
|
354
|
+
time_spacing=ref_image.pixel_size.t,
|
|
355
|
+
levels=ref_meta.levels,
|
|
356
|
+
xy_scaling_factor=xy_scaling_factor,
|
|
357
|
+
z_scaling_factor=z_scaling_factor,
|
|
358
|
+
time_unit=ref_image.pixel_size.time_unit,
|
|
359
|
+
space_unit=ref_image.pixel_size.space_unit,
|
|
360
|
+
axes_names=ref_image.dataset.axes_mapper.on_disk_axes_names,
|
|
361
|
+
chunks=chunks,
|
|
362
|
+
dtype=ref_image.dtype,
|
|
363
|
+
overwrite=overwrite,
|
|
364
|
+
version=ref_meta.version,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
image_container = ImagesContainer(handler)
|
|
368
|
+
|
|
369
|
+
if ref_image.num_channels == image_container.num_channels:
|
|
370
|
+
labels = ref_image.channel_labels
|
|
371
|
+
wavelength_id = ref_image.wavelength_ids
|
|
372
|
+
colors = [
|
|
373
|
+
c.channel_visualisation.color for c in ref_image._channels_meta.channels
|
|
374
|
+
]
|
|
375
|
+
active = [
|
|
376
|
+
c.channel_visualisation.active for c in ref_image._channels_meta.channels
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
image_container.initialize_channel_meta(
|
|
380
|
+
labels=labels,
|
|
381
|
+
wavelength_id=wavelength_id,
|
|
382
|
+
percentiles=None,
|
|
383
|
+
colors=colors,
|
|
384
|
+
active=active,
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
image_container.initialize_channel_meta()
|
|
388
|
+
|
|
389
|
+
return image_container
|