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/core/ngff_image.py
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
"""Abstract class for handling OME-NGFF images."""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import dask.array as da
|
|
6
|
-
import numpy as np
|
|
7
|
-
import zarr
|
|
8
|
-
|
|
9
|
-
from ngio.core.image_handler import Image
|
|
10
|
-
from ngio.core.label_handler import LabelGroup
|
|
11
|
-
from ngio.core.utils import create_empty_ome_zarr_image
|
|
12
|
-
from ngio.io import AccessModeLiteral, StoreLike, open_group_wrapper
|
|
13
|
-
from ngio.ngff_meta import get_ngff_image_meta_handler
|
|
14
|
-
from ngio.ngff_meta.fractal_image_meta import ImageMeta, PixelSize
|
|
15
|
-
from ngio.tables.tables_group import TableGroup
|
|
16
|
-
from ngio.utils import ngio_logger
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class NgffImage:
|
|
20
|
-
"""A class to handle OME-NGFF images."""
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self, store: StoreLike, cache: bool = False, mode: AccessModeLiteral = "r+"
|
|
24
|
-
) -> None:
|
|
25
|
-
"""Initialize the NGFFImage in read mode."""
|
|
26
|
-
self.store = store
|
|
27
|
-
self._mode = mode
|
|
28
|
-
self._group = open_group_wrapper(store=store, mode=self._mode)
|
|
29
|
-
|
|
30
|
-
if self._group.read_only:
|
|
31
|
-
self._mode = "r"
|
|
32
|
-
|
|
33
|
-
self._image_meta = get_ngff_image_meta_handler(
|
|
34
|
-
self._group, meta_mode="image", cache=cache
|
|
35
|
-
)
|
|
36
|
-
self._metadata_cache = cache
|
|
37
|
-
self.tables = TableGroup(self._group, mode=self._mode)
|
|
38
|
-
self.labels = LabelGroup(
|
|
39
|
-
self._group, image_ref=self.get_image(), mode=self._mode
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
ngio_logger.info(f"Opened image located in store: {store}")
|
|
43
|
-
ngio_logger.info(f"- Image number of levels: {self.num_levels}")
|
|
44
|
-
|
|
45
|
-
def __repr__(self) -> str:
|
|
46
|
-
"""Get the string representation of the image."""
|
|
47
|
-
name = "NGFFImage("
|
|
48
|
-
len_name = len(name)
|
|
49
|
-
return (
|
|
50
|
-
f"{name}"
|
|
51
|
-
f"group_path={self.group_path}, \n"
|
|
52
|
-
f"{' ':>{len_name}}paths={self.levels_paths}, \n"
|
|
53
|
-
f"{' ':>{len_name}}labels={self.labels.list()}, \n"
|
|
54
|
-
f"{' ':>{len_name}}tables={self.tables.list()}, \n"
|
|
55
|
-
")"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def group(self) -> zarr.Group:
|
|
60
|
-
"""Get the group of the image."""
|
|
61
|
-
return self._group
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def root_path(self) -> str:
|
|
65
|
-
"""Get the root path of the image."""
|
|
66
|
-
return str(self._group.store.path)
|
|
67
|
-
|
|
68
|
-
@property
|
|
69
|
-
def group_path(self) -> str:
|
|
70
|
-
"""Get the path of the group."""
|
|
71
|
-
root = self.root_path
|
|
72
|
-
if root.endswith("/"):
|
|
73
|
-
root = root[:-1]
|
|
74
|
-
return f"{root}/{self._group.path}"
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def image_meta(self) -> ImageMeta:
|
|
78
|
-
"""Get the image metadata."""
|
|
79
|
-
meta = self._image_meta.load_meta()
|
|
80
|
-
assert isinstance(meta, ImageMeta)
|
|
81
|
-
return meta
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def num_levels(self) -> int:
|
|
85
|
-
"""Get the number of levels in the image."""
|
|
86
|
-
return self.image_meta.num_levels
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def levels_paths(self) -> list[str]:
|
|
90
|
-
"""Get the paths of the levels in the image."""
|
|
91
|
-
return self.image_meta.levels_paths
|
|
92
|
-
|
|
93
|
-
def get_image(
|
|
94
|
-
self,
|
|
95
|
-
*,
|
|
96
|
-
path: str | None = None,
|
|
97
|
-
pixel_size: PixelSize | None = None,
|
|
98
|
-
highest_resolution: bool = True,
|
|
99
|
-
) -> Image:
|
|
100
|
-
"""Get an image handler for the given level.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
path (str | None, optional): The path to the level.
|
|
104
|
-
pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel
|
|
105
|
-
size of the level.
|
|
106
|
-
highest_resolution (bool, optional): Whether to get the highest
|
|
107
|
-
resolution level
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
ImageHandler: The image handler.
|
|
111
|
-
"""
|
|
112
|
-
if path is not None or pixel_size is not None:
|
|
113
|
-
highest_resolution = False
|
|
114
|
-
|
|
115
|
-
image = Image(
|
|
116
|
-
store=self._group,
|
|
117
|
-
path=path,
|
|
118
|
-
pixel_size=pixel_size,
|
|
119
|
-
highest_resolution=highest_resolution,
|
|
120
|
-
label_group=LabelGroup(self._group, image_ref=None, mode=self._mode),
|
|
121
|
-
cache=self._metadata_cache,
|
|
122
|
-
mode=self._mode,
|
|
123
|
-
)
|
|
124
|
-
ngio_logger.info(f"Opened image at path: {image.path}")
|
|
125
|
-
ngio_logger.info(f"- {image.dimensions}")
|
|
126
|
-
ngio_logger.info(f"- {image.pixel_size}")
|
|
127
|
-
return image
|
|
128
|
-
|
|
129
|
-
def _compute_percentiles(
|
|
130
|
-
self, start_percentile: float, end_percentile: float
|
|
131
|
-
) -> tuple[list[float], list[float]]:
|
|
132
|
-
"""Compute the percentiles for the window.
|
|
133
|
-
|
|
134
|
-
This will setup percentiles based values for the window of each channel.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
start_percentile (int): The start percentile.
|
|
138
|
-
end_percentile (int): The end percentile
|
|
139
|
-
|
|
140
|
-
"""
|
|
141
|
-
meta = self.image_meta
|
|
142
|
-
|
|
143
|
-
lowest_res_image = self.get_image(highest_resolution=True)
|
|
144
|
-
lowest_res_shape = lowest_res_image.shape
|
|
145
|
-
for path in self.levels_paths:
|
|
146
|
-
image = self.get_image(path=path)
|
|
147
|
-
if np.prod(image.shape) < np.prod(lowest_res_shape):
|
|
148
|
-
lowest_res_shape = image.shape
|
|
149
|
-
lowest_res_image = image
|
|
150
|
-
|
|
151
|
-
num_c = lowest_res_image.dimensions.get("c", 1)
|
|
152
|
-
|
|
153
|
-
if meta.omero is None:
|
|
154
|
-
raise NotImplementedError(
|
|
155
|
-
"OMERO metadata not found. Please add OMERO metadata to the image."
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
channel_list = meta.omero.channels
|
|
159
|
-
if len(channel_list) != num_c:
|
|
160
|
-
raise ValueError("The number of channels does not match the image.")
|
|
161
|
-
|
|
162
|
-
starts, ends = [], []
|
|
163
|
-
for c in range(num_c):
|
|
164
|
-
data = lowest_res_image.get_array(c=c, mode="dask").ravel()
|
|
165
|
-
_start_percentile, _end_percentile = da.percentile(
|
|
166
|
-
data, [start_percentile, end_percentile], method="nearest"
|
|
167
|
-
).compute()
|
|
168
|
-
|
|
169
|
-
starts.append(_start_percentile)
|
|
170
|
-
ends.append(_end_percentile)
|
|
171
|
-
|
|
172
|
-
return starts, ends
|
|
173
|
-
|
|
174
|
-
def lazy_init_omero(
|
|
175
|
-
self,
|
|
176
|
-
labels: list[str] | int | None = None,
|
|
177
|
-
wavelength_ids: list[str] | None = None,
|
|
178
|
-
colors: list[str] | None = None,
|
|
179
|
-
active: list[bool] | None = None,
|
|
180
|
-
start_percentile: float | None = 1,
|
|
181
|
-
end_percentile: float | None = 99,
|
|
182
|
-
data_type: Any = np.uint16,
|
|
183
|
-
consolidate: bool = True,
|
|
184
|
-
) -> None:
|
|
185
|
-
"""Set the OMERO metadata for the image.
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
labels (list[str] | int | None): The labels of the channels.
|
|
189
|
-
wavelength_ids (list[str] | None): The wavelengths of the channels.
|
|
190
|
-
colors (list[str] | None): The colors of the channels.
|
|
191
|
-
active (list[bool] | None): Whether the channels are active.
|
|
192
|
-
start_percentile (float | None): The start percentile for computing the data
|
|
193
|
-
range. If None, the start is the same as the min value of the data type.
|
|
194
|
-
end_percentile (float | None): The end percentile for for computing the data
|
|
195
|
-
range. If None, the start is the same as the max value of the data type.
|
|
196
|
-
data_type (Any): The data type of the image.
|
|
197
|
-
consolidate (bool): Whether to consolidate the metadata.
|
|
198
|
-
"""
|
|
199
|
-
if labels is None:
|
|
200
|
-
ref = self.get_image()
|
|
201
|
-
labels = ref.num_channels
|
|
202
|
-
|
|
203
|
-
if start_percentile is not None and end_percentile is not None:
|
|
204
|
-
start, end = self._compute_percentiles(
|
|
205
|
-
start_percentile=start_percentile, end_percentile=end_percentile
|
|
206
|
-
)
|
|
207
|
-
elif start_percentile is None and end_percentile is None:
|
|
208
|
-
raise ValueError("Both start and end percentiles cannot be None.")
|
|
209
|
-
elif end_percentile is None and start_percentile is not None:
|
|
210
|
-
raise ValueError(
|
|
211
|
-
"End percentile cannot be None if start percentile is not."
|
|
212
|
-
)
|
|
213
|
-
else:
|
|
214
|
-
start, end = None, None
|
|
215
|
-
|
|
216
|
-
self.image_meta.lazy_init_omero(
|
|
217
|
-
labels=labels,
|
|
218
|
-
wavelength_ids=wavelength_ids,
|
|
219
|
-
colors=colors,
|
|
220
|
-
start=start,
|
|
221
|
-
end=end,
|
|
222
|
-
active=active,
|
|
223
|
-
data_type=data_type,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
if consolidate:
|
|
227
|
-
self._image_meta.write_meta(self.image_meta)
|
|
228
|
-
|
|
229
|
-
def update_omero_window(
|
|
230
|
-
self,
|
|
231
|
-
start_percentile: int = 1,
|
|
232
|
-
end_percentile: int = 99,
|
|
233
|
-
min_value: int | float | None = None,
|
|
234
|
-
max_value: int | float | None = None,
|
|
235
|
-
) -> None:
|
|
236
|
-
"""Update the OMERO window.
|
|
237
|
-
|
|
238
|
-
This will setup percentiles based values for the window of each channel.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
start_percentile (int): The start percentile.
|
|
242
|
-
end_percentile (int): The end percentile
|
|
243
|
-
min_value (int | float | None): The minimum value of the window.
|
|
244
|
-
max_value (int | float | None): The maximum value of the window.
|
|
245
|
-
|
|
246
|
-
"""
|
|
247
|
-
start, ends = self._compute_percentiles(
|
|
248
|
-
start_percentile=start_percentile, end_percentile=end_percentile
|
|
249
|
-
)
|
|
250
|
-
meta = self.image_meta
|
|
251
|
-
ref_image = self.get_image()
|
|
252
|
-
|
|
253
|
-
for func in [np.iinfo, np.finfo]:
|
|
254
|
-
try:
|
|
255
|
-
type_max = func(ref_image.on_disk_array.dtype).max
|
|
256
|
-
type_min = func(ref_image.on_disk_array.dtype).min
|
|
257
|
-
break
|
|
258
|
-
except ValueError:
|
|
259
|
-
continue
|
|
260
|
-
else:
|
|
261
|
-
raise ValueError("Data type not recognized.")
|
|
262
|
-
|
|
263
|
-
if min_value is None:
|
|
264
|
-
min_value = type_min
|
|
265
|
-
if max_value is None:
|
|
266
|
-
max_value = type_max
|
|
267
|
-
|
|
268
|
-
num_c = ref_image.dimensions.get("c", 1)
|
|
269
|
-
|
|
270
|
-
if meta.omero is None:
|
|
271
|
-
raise NotImplementedError(
|
|
272
|
-
"OMERO metadata not found. Please add OMERO metadata to the image."
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
channel_list = meta.omero.channels
|
|
276
|
-
if len(channel_list) != num_c:
|
|
277
|
-
raise ValueError("The number of channels does not match the image.")
|
|
278
|
-
|
|
279
|
-
if len(channel_list) != len(start):
|
|
280
|
-
raise ValueError("The number of channels does not match the image.")
|
|
281
|
-
|
|
282
|
-
for c, (channel, s, e) in enumerate(
|
|
283
|
-
zip(channel_list, start, ends, strict=True)
|
|
284
|
-
):
|
|
285
|
-
channel.channel_visualisation.start = s
|
|
286
|
-
channel.channel_visualisation.end = e
|
|
287
|
-
channel.channel_visualisation.min = min_value
|
|
288
|
-
channel.channel_visualisation.max = max_value
|
|
289
|
-
|
|
290
|
-
ngio_logger.info(
|
|
291
|
-
f"Updated window for channel {channel.label}. "
|
|
292
|
-
f"Start: {start_percentile}, End: {end_percentile}"
|
|
293
|
-
)
|
|
294
|
-
meta.omero.channels[c] = channel
|
|
295
|
-
|
|
296
|
-
self._image_meta.write_meta(meta)
|
|
297
|
-
|
|
298
|
-
def derive_new_image(
|
|
299
|
-
self,
|
|
300
|
-
store: StoreLike,
|
|
301
|
-
name: str,
|
|
302
|
-
overwrite: bool = True,
|
|
303
|
-
copy_labels: bool = False,
|
|
304
|
-
copy_tables: bool = False,
|
|
305
|
-
**kwargs: dict,
|
|
306
|
-
) -> "NgffImage":
|
|
307
|
-
"""Derive a new image from the current image.
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
store (StoreLike): The store to create the new image in.
|
|
311
|
-
name (str): The name of the new image.
|
|
312
|
-
overwrite (bool): Whether to overwrite the image if it exists
|
|
313
|
-
copy_labels (bool): Whether to copy the labels from the current image
|
|
314
|
-
to the new image.
|
|
315
|
-
copy_tables (bool): Whether to copy the tables from the current image
|
|
316
|
-
to the new image.
|
|
317
|
-
**kwargs: Additional keyword arguments.
|
|
318
|
-
Follow the same signature as `create_empty_ome_zarr_image`.
|
|
319
|
-
|
|
320
|
-
Returns:
|
|
321
|
-
NgffImage: The new image.
|
|
322
|
-
"""
|
|
323
|
-
image_0 = self.get_image(highest_resolution=True)
|
|
324
|
-
|
|
325
|
-
# Get the channel information if it exists
|
|
326
|
-
omero = self.image_meta.omero
|
|
327
|
-
if omero is not None:
|
|
328
|
-
channels = omero.channels
|
|
329
|
-
omero_kwargs = omero.extra_fields
|
|
330
|
-
else:
|
|
331
|
-
channels = []
|
|
332
|
-
omero_kwargs = {}
|
|
333
|
-
|
|
334
|
-
default_kwargs = {
|
|
335
|
-
"store": store,
|
|
336
|
-
"on_disk_shape": image_0.on_disk_shape,
|
|
337
|
-
"chunks": image_0.on_disk_array.chunks,
|
|
338
|
-
"dtype": image_0.on_disk_array.dtype,
|
|
339
|
-
"on_disk_axis": image_0.dataset.on_disk_axes_names,
|
|
340
|
-
"pixel_sizes": image_0.pixel_size,
|
|
341
|
-
"xy_scaling_factor": self.image_meta.xy_scaling_factor,
|
|
342
|
-
"z_scaling_factor": self.image_meta.z_scaling_factor,
|
|
343
|
-
"time_spacing": image_0.dataset.time_spacing,
|
|
344
|
-
"time_units": image_0.dataset.time_axis_unit,
|
|
345
|
-
"levels": self.num_levels,
|
|
346
|
-
"name": name,
|
|
347
|
-
"channel_labels": image_0.channel_labels,
|
|
348
|
-
"channel_wavelengths": [ch.wavelength_id for ch in channels],
|
|
349
|
-
"channel_visualization": [ch.channel_visualisation for ch in channels],
|
|
350
|
-
"omero_kwargs": omero_kwargs,
|
|
351
|
-
"overwrite": overwrite,
|
|
352
|
-
"version": self.image_meta.version,
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
default_kwargs.update(kwargs)
|
|
356
|
-
|
|
357
|
-
create_empty_ome_zarr_image(
|
|
358
|
-
**default_kwargs,
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
new_image = NgffImage(store=store)
|
|
362
|
-
|
|
363
|
-
if copy_tables:
|
|
364
|
-
# TODO: to be refactored when the table location is changed in the spec
|
|
365
|
-
source_tables_group = self.tables._table_group
|
|
366
|
-
|
|
367
|
-
if source_tables_group is None:
|
|
368
|
-
raise ValueError("No tables group found in the source image.")
|
|
369
|
-
|
|
370
|
-
zarr.copy(source=source_tables_group, dest=new_image.group)
|
|
371
|
-
|
|
372
|
-
# Reopen the image to get the new tables
|
|
373
|
-
new_image = NgffImage(store=store)
|
|
374
|
-
|
|
375
|
-
if copy_labels:
|
|
376
|
-
# TODO: to be refactored when the label location is changed in the spec
|
|
377
|
-
source_labels_group = self.labels._label_group
|
|
378
|
-
|
|
379
|
-
if source_labels_group is None:
|
|
380
|
-
raise ValueError("No labels group found in the source image.")
|
|
381
|
-
|
|
382
|
-
zarr.copy(source=source_labels_group, dest=new_image.group)
|
|
383
|
-
|
|
384
|
-
# Reopen the image to get the new labels
|
|
385
|
-
new_image = NgffImage(store=store)
|
|
386
|
-
|
|
387
|
-
return new_image
|
ngio/core/utils.py
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
"""Utility functions for creating and manipulating images."""
|
|
2
|
-
|
|
3
|
-
import math
|
|
4
|
-
from collections.abc import Collection
|
|
5
|
-
from enum import Enum
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
import fsspec.implementations.http
|
|
9
|
-
|
|
10
|
-
from ngio.io import Group, StoreLike
|
|
11
|
-
from ngio.ngff_meta import (
|
|
12
|
-
ImageLabelMeta,
|
|
13
|
-
create_image_metadata,
|
|
14
|
-
create_label_metadata,
|
|
15
|
-
get_ngff_image_meta_handler,
|
|
16
|
-
)
|
|
17
|
-
from ngio.ngff_meta.fractal_image_meta import (
|
|
18
|
-
ChannelVisualisation,
|
|
19
|
-
PixelSize,
|
|
20
|
-
TimeUnits,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_fsspec_http_store(
|
|
25
|
-
url: str, client_kwargs: dict | None = None
|
|
26
|
-
) -> fsspec.mapping.FSMap:
|
|
27
|
-
"""Simple function to get an http fsspec store from a url."""
|
|
28
|
-
client_kwargs = {} if client_kwargs is None else client_kwargs
|
|
29
|
-
fs = fsspec.implementations.http.HTTPFileSystem(client_kwargs=client_kwargs)
|
|
30
|
-
store = fs.get_mapper(url)
|
|
31
|
-
return store
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class State(Enum):
|
|
35
|
-
"""The state of an object.
|
|
36
|
-
|
|
37
|
-
It can either be:
|
|
38
|
-
- "Memory"
|
|
39
|
-
- "Consolidated"
|
|
40
|
-
If the state is "Memory" means that some data/metadata is not stored on disk.
|
|
41
|
-
The state can be write on disk using .consolidate()
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
MEMORY = "Memory"
|
|
45
|
-
CONSOLIDATED = "Consolidated"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _build_empty_pyramid(
|
|
49
|
-
group: Group,
|
|
50
|
-
image_meta: ImageLabelMeta,
|
|
51
|
-
on_disk_shape: Collection[int],
|
|
52
|
-
chunks: Collection[int] | None = None,
|
|
53
|
-
dtype: str = "uint16",
|
|
54
|
-
on_disk_axis: Collection[str] = ("t", "c", "z", "y"),
|
|
55
|
-
xy_scaling_factor: float = 2.0,
|
|
56
|
-
z_scaling_factor: float = 1.0,
|
|
57
|
-
) -> None:
|
|
58
|
-
# Return the an Image object
|
|
59
|
-
scaling_factor = []
|
|
60
|
-
for ax in on_disk_axis:
|
|
61
|
-
if ax in ["x", "y"]:
|
|
62
|
-
scaling_factor.append(xy_scaling_factor)
|
|
63
|
-
elif ax == "z":
|
|
64
|
-
scaling_factor.append(z_scaling_factor)
|
|
65
|
-
else:
|
|
66
|
-
scaling_factor.append(1.0)
|
|
67
|
-
|
|
68
|
-
if chunks is not None and len(on_disk_shape) != len(chunks):
|
|
69
|
-
raise ValueError(
|
|
70
|
-
"The shape and chunks must have the same number of dimensions."
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
if len(on_disk_shape) != len(scaling_factor):
|
|
74
|
-
raise ValueError(
|
|
75
|
-
"The shape and scaling factor must have the same number of dimensions."
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
if len(on_disk_shape) != len(on_disk_axis):
|
|
79
|
-
raise ValueError(
|
|
80
|
-
"The shape and on-disk axis must have the same number of dimensions."
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
for dataset in image_meta.datasets:
|
|
84
|
-
path = dataset.path
|
|
85
|
-
|
|
86
|
-
# V3
|
|
87
|
-
# group.create_array(
|
|
88
|
-
# name=path, fill_value=0, shape=shape, dtype=dtype, chunks=chunks,
|
|
89
|
-
# )
|
|
90
|
-
|
|
91
|
-
group.zeros(
|
|
92
|
-
name=path,
|
|
93
|
-
shape=on_disk_shape,
|
|
94
|
-
dtype=dtype,
|
|
95
|
-
chunks=chunks,
|
|
96
|
-
dimension_separator="/",
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Todo redo this with when a proper build of pyramid is implemented
|
|
100
|
-
_shape = []
|
|
101
|
-
for s, sc in zip(on_disk_shape, scaling_factor, strict=True):
|
|
102
|
-
if math.floor(s / sc) % 2 == 0:
|
|
103
|
-
_shape.append(math.floor(s / sc))
|
|
104
|
-
else:
|
|
105
|
-
_shape.append(math.ceil(s / sc))
|
|
106
|
-
on_disk_shape = list(_shape)
|
|
107
|
-
|
|
108
|
-
if chunks is not None:
|
|
109
|
-
chunks = [min(c, s) for c, s in zip(chunks, on_disk_shape, strict=True)]
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def create_empty_ome_zarr_image(
|
|
114
|
-
store: StoreLike,
|
|
115
|
-
on_disk_shape: Collection[int],
|
|
116
|
-
on_disk_axis: Collection[str] = ("t", "c", "z", "y", "x"),
|
|
117
|
-
chunks: Collection[int] | None = None,
|
|
118
|
-
dtype: str = "uint16",
|
|
119
|
-
pixel_sizes: PixelSize | None = None,
|
|
120
|
-
xy_scaling_factor: float = 2.0,
|
|
121
|
-
z_scaling_factor: float = 1.0,
|
|
122
|
-
time_spacing: float = 1.0,
|
|
123
|
-
time_units: TimeUnits | str = TimeUnits.s,
|
|
124
|
-
levels: int | list[str] = 5,
|
|
125
|
-
name: str | None = None,
|
|
126
|
-
channel_labels: list[str] | None = None,
|
|
127
|
-
channel_wavelengths: list[str] | None = None,
|
|
128
|
-
channel_visualization: list[ChannelVisualisation] | None = None,
|
|
129
|
-
omero_kwargs: dict[str, Any] | None = None,
|
|
130
|
-
overwrite: bool = True,
|
|
131
|
-
version: str = "0.4",
|
|
132
|
-
) -> None:
|
|
133
|
-
"""Create an empty OME-Zarr image with the given shape and metadata.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
store (StoreLike): The store to create the image in.
|
|
137
|
-
on_disk_shape (Collection[int]): The shape of the image on disk.
|
|
138
|
-
on_disk_axis (Collection[str]): The order of the axes on disk.
|
|
139
|
-
chunks (Collection[int] | None): The chunk shape for the image.
|
|
140
|
-
dtype (str): The data type of the image.
|
|
141
|
-
pixel_sizes (PixelSize | None): The pixel size of the image.
|
|
142
|
-
xy_scaling_factor (float): The scaling factor in the x and y dimensions.
|
|
143
|
-
z_scaling_factor (float): The scaling factor in the z dimension.
|
|
144
|
-
time_spacing (float): The spacing between time points.
|
|
145
|
-
time_units (TimeUnits | str): The units of the time axis.
|
|
146
|
-
levels (int | list[str]): The number of levels in the pyramid.
|
|
147
|
-
path_names (list[str] | None): The names of the paths in the image.
|
|
148
|
-
name (str | None): The name of the image.
|
|
149
|
-
channel_labels (list[str] | None): The labels of the channels.
|
|
150
|
-
channel_wavelengths (list[str] | None): The wavelengths of the channels.
|
|
151
|
-
channel_visualization (list[ChannelVisualisation] | None): A list of
|
|
152
|
-
channel visualisation objects.
|
|
153
|
-
omero_kwargs (dict[str, Any] | None): The extra fields for the image.
|
|
154
|
-
overwrite (bool): Whether to overwrite the image if it exists.
|
|
155
|
-
version (str): The version of the OME-Zarr format.
|
|
156
|
-
|
|
157
|
-
"""
|
|
158
|
-
if len(on_disk_shape) != len(on_disk_axis):
|
|
159
|
-
raise ValueError(
|
|
160
|
-
"The number of dimensions in the shape must match the number of "
|
|
161
|
-
"axes in the on-disk axis."
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if "c" in on_disk_axis:
|
|
165
|
-
on_disk_shape = tuple(on_disk_shape)
|
|
166
|
-
on_disk_axis = tuple(on_disk_axis)
|
|
167
|
-
num_channels = on_disk_shape[on_disk_axis.index("c")]
|
|
168
|
-
if channel_labels is None:
|
|
169
|
-
channel_labels = [f"C{i:02d}" for i in range(num_channels)]
|
|
170
|
-
else:
|
|
171
|
-
if len(channel_labels) != num_channels:
|
|
172
|
-
raise ValueError(
|
|
173
|
-
"The number of channel labels must match the number "
|
|
174
|
-
f"of channels in the shape. Got {len(channel_labels)} "
|
|
175
|
-
f"labels for {num_channels} channels."
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
image_meta = create_image_metadata(
|
|
179
|
-
on_disk_axis=on_disk_axis,
|
|
180
|
-
pixel_sizes=pixel_sizes,
|
|
181
|
-
xy_scaling_factor=xy_scaling_factor,
|
|
182
|
-
z_scaling_factor=z_scaling_factor,
|
|
183
|
-
time_spacing=time_spacing,
|
|
184
|
-
time_units=time_units,
|
|
185
|
-
levels=levels,
|
|
186
|
-
name=name,
|
|
187
|
-
channel_labels=channel_labels,
|
|
188
|
-
channel_wavelengths=channel_wavelengths,
|
|
189
|
-
channel_visualization=channel_visualization,
|
|
190
|
-
omero_kwargs=omero_kwargs,
|
|
191
|
-
version=version,
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
# Open the store (if it is not empty, fail)
|
|
195
|
-
mode = "w" if overwrite else "w-"
|
|
196
|
-
meta_handler = get_ngff_image_meta_handler(
|
|
197
|
-
store=store, version=version, meta_mode="image", mode=mode
|
|
198
|
-
)
|
|
199
|
-
meta_handler.write_meta(image_meta)
|
|
200
|
-
group = meta_handler.group
|
|
201
|
-
|
|
202
|
-
# Create the empty image at each level in the pyramid
|
|
203
|
-
_build_empty_pyramid(
|
|
204
|
-
group=group,
|
|
205
|
-
image_meta=image_meta,
|
|
206
|
-
on_disk_shape=on_disk_shape,
|
|
207
|
-
chunks=chunks,
|
|
208
|
-
dtype=dtype,
|
|
209
|
-
on_disk_axis=on_disk_axis,
|
|
210
|
-
xy_scaling_factor=xy_scaling_factor,
|
|
211
|
-
z_scaling_factor=z_scaling_factor,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def create_empty_ome_zarr_label(
|
|
216
|
-
store: StoreLike,
|
|
217
|
-
on_disk_shape: Collection[int],
|
|
218
|
-
chunks: Collection[int] | None = None,
|
|
219
|
-
dtype: str = "uint16",
|
|
220
|
-
on_disk_axis: Collection[str] = ("t", "z", "y", "x"),
|
|
221
|
-
pixel_sizes: PixelSize | None = None,
|
|
222
|
-
xy_scaling_factor: float = 2.0,
|
|
223
|
-
z_scaling_factor: float = 1.0,
|
|
224
|
-
time_spacing: float = 1.0,
|
|
225
|
-
time_units: TimeUnits | str | None = None,
|
|
226
|
-
levels: int | list[str] = 5,
|
|
227
|
-
name: str | None = None,
|
|
228
|
-
overwrite: bool = True,
|
|
229
|
-
version: str = "0.4",
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Create an empty OME-Zarr image with the given shape and metadata.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
store (StoreLike): The store to create the image in.
|
|
235
|
-
on_disk_shape (Collection[int]): The shape of the image on disk.
|
|
236
|
-
chunks (Collection[int] | None): The chunk shape for the image.
|
|
237
|
-
dtype (str): The data type of the image.
|
|
238
|
-
on_disk_axis (Collection[str]): The order of the axes on disk.
|
|
239
|
-
pixel_sizes (PixelSize | None): The pixel size of the image.
|
|
240
|
-
xy_scaling_factor (float): The scaling factor in the x and y dimensions.
|
|
241
|
-
z_scaling_factor (float): The scaling factor in the z dimension.
|
|
242
|
-
time_spacing (float): The spacing between time points.
|
|
243
|
-
time_units (TimeUnits | str | None): The units of the time axis.
|
|
244
|
-
levels (int | list[str]): The number of levels in the pyramid.
|
|
245
|
-
name (str | None): The name of the image.
|
|
246
|
-
overwrite (bool): Whether to overwrite the image if it exists.
|
|
247
|
-
version (str): The version of the OME-Zarr format
|
|
248
|
-
|
|
249
|
-
"""
|
|
250
|
-
if len(on_disk_shape) != len(on_disk_axis):
|
|
251
|
-
raise ValueError(
|
|
252
|
-
"The number of dimensions in the shape must match the number of "
|
|
253
|
-
"axes in the on-disk axis."
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
image_meta = create_label_metadata(
|
|
257
|
-
on_disk_axis=on_disk_axis,
|
|
258
|
-
pixel_sizes=pixel_sizes,
|
|
259
|
-
xy_scaling_factor=xy_scaling_factor,
|
|
260
|
-
z_scaling_factor=z_scaling_factor,
|
|
261
|
-
time_spacing=time_spacing,
|
|
262
|
-
time_units=time_units,
|
|
263
|
-
levels=levels,
|
|
264
|
-
name=name,
|
|
265
|
-
version=version,
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
# Open the store (if it is not empty, fail)
|
|
269
|
-
mode = "w" if overwrite else "w-"
|
|
270
|
-
meta_handler = get_ngff_image_meta_handler(
|
|
271
|
-
store=store, version=version, meta_mode="label", mode=mode
|
|
272
|
-
)
|
|
273
|
-
meta_handler.write_meta(image_meta)
|
|
274
|
-
group = meta_handler.group
|
|
275
|
-
group.attrs["image-label"] = {"version": version, "source": {"image": "../../"}}
|
|
276
|
-
|
|
277
|
-
# Create the empty image at each level in the pyramid
|
|
278
|
-
_build_empty_pyramid(
|
|
279
|
-
group=group,
|
|
280
|
-
image_meta=image_meta,
|
|
281
|
-
on_disk_shape=on_disk_shape,
|
|
282
|
-
chunks=chunks,
|
|
283
|
-
dtype=dtype,
|
|
284
|
-
on_disk_axis=on_disk_axis,
|
|
285
|
-
xy_scaling_factor=xy_scaling_factor,
|
|
286
|
-
z_scaling_factor=z_scaling_factor,
|
|
287
|
-
)
|
ngio/io/__init__.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""Collection of helper functions to work with Zarr groups."""
|
|
2
|
-
|
|
3
|
-
from zarr import Group
|
|
4
|
-
|
|
5
|
-
from ngio.io._zarr import AccessModeLiteral, StoreLike, StoreOrGroup
|
|
6
|
-
from ngio.io._zarr_group_utils import (
|
|
7
|
-
open_group_wrapper,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
# Zarr V3 imports
|
|
11
|
-
# from zarr.store.common import StoreLike
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"Group",
|
|
15
|
-
"StoreLike",
|
|
16
|
-
"AccessModeLiteral",
|
|
17
|
-
"StoreOrGroup",
|
|
18
|
-
"open_group_wrapper",
|
|
19
|
-
]
|