ngio 0.4.8__py3-none-any.whl → 0.5.0a1__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/common/_pyramid.py +41 -28
- ngio/images/_create.py +23 -16
- ngio/images/_create_synt_container.py +9 -8
- ngio/images/_image.py +26 -16
- ngio/images/_label.py +15 -15
- ngio/images/_ome_zarr_container.py +29 -29
- ngio/ome_zarr_meta/_meta_handlers.py +29 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +12 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +2 -2
- ngio/ome_zarr_meta/v05/__init__.py +23 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec_utils.py +518 -0
- ngio/tables/_tables_container.py +3 -3
- ngio/tables/backends/_abstract_backend.py +7 -0
- ngio/tables/backends/_anndata.py +4 -1
- ngio/tables/backends/_anndata_utils.py +4 -1
- ngio/tables/backends/_non_zarr_backends.py +6 -6
- ngio/tables/backends/_utils.py +1 -1
- ngio/tables/v1/_roi_table.py +3 -3
- ngio/utils/_datasets.py +0 -6
- ngio/utils/_zarr_utils.py +108 -32
- {ngio-0.4.8.dist-info → ngio-0.5.0a1.dist-info}/METADATA +6 -6
- {ngio-0.4.8.dist-info → ngio-0.5.0a1.dist-info}/RECORD +25 -22
- {ngio-0.4.8.dist-info → ngio-0.5.0a1.dist-info}/WHEEL +1 -1
- {ngio-0.4.8.dist-info → ngio-0.5.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"""Utilities for OME-Zarr v05 specs.
|
|
2
|
+
|
|
3
|
+
This module provides a set of classes to internally handle the metadata
|
|
4
|
+
of the OME-Zarr v05 specification.
|
|
5
|
+
|
|
6
|
+
For Images and Labels implements the following functionalities:
|
|
7
|
+
- A function to find if a dict view of the metadata is a valid OME-Zarr v05 metadata.
|
|
8
|
+
- A function to convert a v05 image metadata to a ngio image metadata.
|
|
9
|
+
- A function to convert a ngio image metadata to a v05 image metadata.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from ome_zarr_models.common.omero import Channel as ChannelV05
|
|
13
|
+
from ome_zarr_models.common.omero import Omero as OmeroV05
|
|
14
|
+
from ome_zarr_models.common.omero import Window as WindowV05
|
|
15
|
+
from ome_zarr_models.v05.axes import Axis as AxisV05
|
|
16
|
+
from ome_zarr_models.v05.coordinate_transformations import VectorScale as VectorScaleV05
|
|
17
|
+
from ome_zarr_models.v05.coordinate_transformations import (
|
|
18
|
+
VectorTranslation as VectorTranslationV05,
|
|
19
|
+
)
|
|
20
|
+
from ome_zarr_models.v05.hcs import HCSAttrs as HCSAttrsV05
|
|
21
|
+
from ome_zarr_models.v05.image import ImageAttrs as ImageAttrsV05
|
|
22
|
+
from ome_zarr_models.v05.image_label import ImageLabelAttrs as LabelAttrsV05
|
|
23
|
+
from ome_zarr_models.v05.multiscales import Dataset as DatasetV05
|
|
24
|
+
from ome_zarr_models.v05.multiscales import Multiscale as MultiscaleV05
|
|
25
|
+
from ome_zarr_models.v05.multiscales import ValidTransform as ValidTransformV05
|
|
26
|
+
from pydantic import BaseModel, ValidationError
|
|
27
|
+
|
|
28
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
29
|
+
AxesHandler,
|
|
30
|
+
AxesSetup,
|
|
31
|
+
Axis,
|
|
32
|
+
AxisType,
|
|
33
|
+
Channel,
|
|
34
|
+
ChannelsMeta,
|
|
35
|
+
ChannelVisualisation,
|
|
36
|
+
Dataset,
|
|
37
|
+
ImageLabelSource,
|
|
38
|
+
NgioImageMeta,
|
|
39
|
+
NgioLabelMeta,
|
|
40
|
+
NgioPlateMeta,
|
|
41
|
+
NgioWellMeta,
|
|
42
|
+
default_channel_name,
|
|
43
|
+
)
|
|
44
|
+
from ngio.ome_zarr_meta.v05._custom_models import CustomWellAttrs as WellAttrsV05
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ImageV05AttrsWithOmero(ImageAttrsV05):
|
|
48
|
+
omero: OmeroV05 | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ImageV05WithOmero(BaseModel):
|
|
52
|
+
ome: ImageV05AttrsWithOmero
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ImageLabelV05(BaseModel):
|
|
56
|
+
ome: LabelAttrsV05
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _is_v05_image_meta(metadata: dict) -> ImageV05WithOmero | ValidationError:
|
|
60
|
+
"""Check if the metadata is a valid OME-Zarr v05 metadata.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
metadata (dict): The metadata to check.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
bool: True if the metadata is a valid OME-Zarr v05 metadata, False otherwise.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
return ImageV05WithOmero(**metadata)
|
|
70
|
+
except ValidationError as e:
|
|
71
|
+
return e
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _is_v05_label_meta(metadata: dict) -> ImageLabelV05 | ValidationError:
|
|
75
|
+
"""Check if the metadata is a valid OME-Zarr v05 metadata.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
metadata (dict): The metadata to check.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
bool: True if the metadata is a valid OME-Zarr v05 metadata, False otherwise.
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
return ImageLabelV05(**metadata)
|
|
85
|
+
except ValidationError as e:
|
|
86
|
+
return e
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _v05_omero_to_channels(v05_omero: OmeroV05 | None) -> ChannelsMeta | None:
|
|
90
|
+
if v05_omero is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
ngio_channels = []
|
|
94
|
+
for idx, v05_channel in enumerate(v05_omero.channels):
|
|
95
|
+
channel_extra = v05_channel.model_extra
|
|
96
|
+
|
|
97
|
+
if channel_extra is None:
|
|
98
|
+
channel_extra = {}
|
|
99
|
+
|
|
100
|
+
if "label" in channel_extra:
|
|
101
|
+
label = channel_extra.pop("label")
|
|
102
|
+
else:
|
|
103
|
+
label = default_channel_name(idx)
|
|
104
|
+
|
|
105
|
+
if "wavelength_id" in channel_extra:
|
|
106
|
+
wavelength_id = channel_extra.pop("wavelength_id")
|
|
107
|
+
else:
|
|
108
|
+
wavelength_id = label
|
|
109
|
+
|
|
110
|
+
if "active" in channel_extra:
|
|
111
|
+
active = channel_extra.pop("active")
|
|
112
|
+
else:
|
|
113
|
+
active = True
|
|
114
|
+
|
|
115
|
+
channel_visualisation = ChannelVisualisation(
|
|
116
|
+
color=v05_channel.color,
|
|
117
|
+
start=v05_channel.window.start,
|
|
118
|
+
end=v05_channel.window.end,
|
|
119
|
+
min=v05_channel.window.min,
|
|
120
|
+
max=v05_channel.window.max,
|
|
121
|
+
active=active,
|
|
122
|
+
**channel_extra,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
ngio_channels.append(
|
|
126
|
+
Channel(
|
|
127
|
+
label=label,
|
|
128
|
+
wavelength_id=wavelength_id,
|
|
129
|
+
channel_visualisation=channel_visualisation,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
v05_omero_extra = v05_omero.model_extra if v05_omero.model_extra is not None else {}
|
|
134
|
+
return ChannelsMeta(channels=ngio_channels, **v05_omero_extra)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _compute_scale_translation(
|
|
138
|
+
v05_transforms: ValidTransformV05,
|
|
139
|
+
scale: list[float],
|
|
140
|
+
translation: list[float],
|
|
141
|
+
) -> tuple[list[float], list[float]]:
|
|
142
|
+
for v05_transform in v05_transforms:
|
|
143
|
+
if isinstance(v05_transform, VectorScaleV05):
|
|
144
|
+
scale = [t1 * t2 for t1, t2 in zip(scale, v05_transform.scale, strict=True)]
|
|
145
|
+
|
|
146
|
+
elif isinstance(v05_transform, VectorTranslationV05):
|
|
147
|
+
translation = [
|
|
148
|
+
t1 + t2
|
|
149
|
+
for t1, t2 in zip(translation, v05_transform.translation, strict=True)
|
|
150
|
+
]
|
|
151
|
+
else:
|
|
152
|
+
raise NotImplementedError(
|
|
153
|
+
f"Coordinate transformation {v05_transform} is not supported."
|
|
154
|
+
)
|
|
155
|
+
return scale, translation
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _v05_to_ngio_datasets(
|
|
159
|
+
v05_multiscale: MultiscaleV05,
|
|
160
|
+
axes_setup: AxesSetup,
|
|
161
|
+
allow_non_canonical_axes: bool = False,
|
|
162
|
+
strict_canonical_order: bool = True,
|
|
163
|
+
) -> list[Dataset]:
|
|
164
|
+
"""Convert a v05 multiscale to a list of ngio datasets."""
|
|
165
|
+
datasets = []
|
|
166
|
+
|
|
167
|
+
global_scale = [1.0] * len(v05_multiscale.axes)
|
|
168
|
+
global_translation = [0.0] * len(v05_multiscale.axes)
|
|
169
|
+
|
|
170
|
+
if v05_multiscale.coordinateTransformations is not None:
|
|
171
|
+
global_scale, global_translation = _compute_scale_translation(
|
|
172
|
+
v05_multiscale.coordinateTransformations, global_scale, global_translation
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Prepare axes handler
|
|
176
|
+
axes = []
|
|
177
|
+
for v05_axis in v05_multiscale.axes:
|
|
178
|
+
unit = v05_axis.unit
|
|
179
|
+
if unit is not None and not isinstance(unit, str):
|
|
180
|
+
unit = str(unit)
|
|
181
|
+
axes.append(
|
|
182
|
+
Axis(
|
|
183
|
+
name=str(v05_axis.name),
|
|
184
|
+
axis_type=AxisType(v05_axis.type),
|
|
185
|
+
# (for some reason the type is a generic JsonValue,
|
|
186
|
+
# but it should be a string or None)
|
|
187
|
+
unit=v05_axis.unit, # type: ignore
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
axes_handler = AxesHandler(
|
|
191
|
+
axes=axes,
|
|
192
|
+
axes_setup=axes_setup,
|
|
193
|
+
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
194
|
+
strict_canonical_order=strict_canonical_order,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
for v05_dataset in v05_multiscale.datasets:
|
|
198
|
+
_scale, _translation = _compute_scale_translation(
|
|
199
|
+
v05_dataset.coordinateTransformations, global_scale, global_translation
|
|
200
|
+
)
|
|
201
|
+
datasets.append(
|
|
202
|
+
Dataset(
|
|
203
|
+
path=v05_dataset.path,
|
|
204
|
+
axes_handler=axes_handler,
|
|
205
|
+
scale=_scale,
|
|
206
|
+
translation=_translation,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
return datasets
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def v05_to_ngio_image_meta(
|
|
213
|
+
metadata: dict,
|
|
214
|
+
axes_setup: AxesSetup | None = None,
|
|
215
|
+
allow_non_canonical_axes: bool = False,
|
|
216
|
+
strict_canonical_order: bool = True,
|
|
217
|
+
) -> tuple[bool, NgioImageMeta | ValidationError]:
|
|
218
|
+
"""Convert a v05 image metadata to a ngio image metadata.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
metadata (dict): The v05 image metadata.
|
|
222
|
+
axes_setup (AxesSetup, optional): The axes setup. This is
|
|
223
|
+
required to convert image with non-canonical axes names.
|
|
224
|
+
allow_non_canonical_axes (bool, optional): Allow non-canonical axes.
|
|
225
|
+
strict_canonical_order (bool, optional): Strict canonical order.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
NgioImageMeta: The ngio image metadata.
|
|
229
|
+
"""
|
|
230
|
+
v05_image = _is_v05_image_meta(metadata)
|
|
231
|
+
if isinstance(v05_image, ValidationError):
|
|
232
|
+
return False, v05_image
|
|
233
|
+
v05_image = v05_image.ome
|
|
234
|
+
if len(v05_image.multiscales) > 1:
|
|
235
|
+
raise NotImplementedError(
|
|
236
|
+
"Multiple multiscales in a single image are not supported in ngio."
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
v05_multiscale = v05_image.multiscales[0]
|
|
240
|
+
|
|
241
|
+
channels_meta = _v05_omero_to_channels(v05_image.omero)
|
|
242
|
+
axes_setup = axes_setup if axes_setup is not None else AxesSetup()
|
|
243
|
+
datasets = _v05_to_ngio_datasets(
|
|
244
|
+
v05_multiscale,
|
|
245
|
+
axes_setup=axes_setup,
|
|
246
|
+
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
247
|
+
strict_canonical_order=strict_canonical_order,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
name = v05_multiscale.name
|
|
251
|
+
if name is not None and not isinstance(name, str):
|
|
252
|
+
name = str(name)
|
|
253
|
+
return True, NgioImageMeta(
|
|
254
|
+
version="0.5",
|
|
255
|
+
name=name,
|
|
256
|
+
datasets=datasets,
|
|
257
|
+
channels=channels_meta,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def v05_to_ngio_label_meta(
|
|
262
|
+
metadata: dict,
|
|
263
|
+
axes_setup: AxesSetup | None = None,
|
|
264
|
+
allow_non_canonical_axes: bool = False,
|
|
265
|
+
strict_canonical_order: bool = True,
|
|
266
|
+
) -> tuple[bool, NgioLabelMeta | ValidationError]:
|
|
267
|
+
"""Convert a v05 image metadata to a ngio image metadata.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
metadata (dict): The v05 image metadata.
|
|
271
|
+
axes_setup (AxesSetup, optional): The axes setup. This is
|
|
272
|
+
required to convert image with non-canonical axes names.
|
|
273
|
+
allow_non_canonical_axes (bool, optional): Allow non-canonical axes.
|
|
274
|
+
strict_canonical_order (bool, optional): Strict canonical order.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
NgioImageMeta: The ngio image metadata.
|
|
278
|
+
"""
|
|
279
|
+
v05_label = _is_v05_label_meta(metadata)
|
|
280
|
+
if isinstance(v05_label, ValidationError):
|
|
281
|
+
return False, v05_label
|
|
282
|
+
v05_label = v05_label.ome
|
|
283
|
+
|
|
284
|
+
if len(v05_label.multiscales) > 1:
|
|
285
|
+
raise NotImplementedError(
|
|
286
|
+
"Multiple multiscales in a single image are not supported in ngio."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
v05_multiscale = v05_label.multiscales[0]
|
|
290
|
+
|
|
291
|
+
axes_setup = axes_setup if axes_setup is not None else AxesSetup()
|
|
292
|
+
datasets = _v05_to_ngio_datasets(
|
|
293
|
+
v05_multiscale,
|
|
294
|
+
axes_setup=axes_setup,
|
|
295
|
+
allow_non_canonical_axes=allow_non_canonical_axes,
|
|
296
|
+
strict_canonical_order=strict_canonical_order,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
if v05_label.image_label is not None:
|
|
300
|
+
source = v05_label.image_label.source
|
|
301
|
+
if source is None:
|
|
302
|
+
image_label_source = None
|
|
303
|
+
else:
|
|
304
|
+
source = v05_label.image_label.source
|
|
305
|
+
if source is None:
|
|
306
|
+
image_label_source = None
|
|
307
|
+
else:
|
|
308
|
+
image_label_source = source.image
|
|
309
|
+
image_label_source = ImageLabelSource(
|
|
310
|
+
version="0.5",
|
|
311
|
+
source={"image": image_label_source},
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
image_label_source = None
|
|
315
|
+
name = v05_multiscale.name
|
|
316
|
+
if name is not None and not isinstance(name, str):
|
|
317
|
+
name = str(name)
|
|
318
|
+
|
|
319
|
+
return True, NgioLabelMeta(
|
|
320
|
+
version="0.5",
|
|
321
|
+
name=name,
|
|
322
|
+
datasets=datasets,
|
|
323
|
+
image_label=image_label_source,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _ngio_to_v05_multiscale(name: str | None, datasets: list[Dataset]) -> MultiscaleV05:
|
|
328
|
+
"""Convert a ngio multiscale to a v05 multiscale.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
name (str | None): The name of the multiscale.
|
|
332
|
+
datasets (list[Dataset]): The ngio datasets.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
MultiscaleV05: The v05 multiscale.
|
|
336
|
+
"""
|
|
337
|
+
ax_mapper = datasets[0].axes_handler
|
|
338
|
+
v05_axes = []
|
|
339
|
+
for axis in ax_mapper.axes:
|
|
340
|
+
v05_axes.append(
|
|
341
|
+
AxisV05(
|
|
342
|
+
name=axis.name,
|
|
343
|
+
type=axis.axis_type.value if axis.axis_type is not None else None,
|
|
344
|
+
unit=axis.unit if axis.unit is not None else None,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
v05_datasets = []
|
|
349
|
+
for dataset in datasets:
|
|
350
|
+
transform = [VectorScaleV05(type="scale", scale=list(dataset._scale))]
|
|
351
|
+
if sum(dataset._translation) > 0:
|
|
352
|
+
transform = (
|
|
353
|
+
VectorScaleV05(type="scale", scale=list(dataset._scale)),
|
|
354
|
+
VectorTranslationV05(
|
|
355
|
+
type="translation", translation=list(dataset._translation)
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
transform = (VectorScaleV05(type="scale", scale=list(dataset._scale)),)
|
|
360
|
+
|
|
361
|
+
v05_datasets.append(
|
|
362
|
+
DatasetV05(path=dataset.path, coordinateTransformations=transform)
|
|
363
|
+
)
|
|
364
|
+
return MultiscaleV05(axes=v05_axes, datasets=tuple(v05_datasets), name=name)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _ngio_to_v05_omero(channels: ChannelsMeta | None) -> OmeroV05 | None:
|
|
368
|
+
"""Convert a ngio channels to a v05 omero."""
|
|
369
|
+
if channels is None:
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
v05_channels = []
|
|
373
|
+
for channel in channels.channels:
|
|
374
|
+
_model_extra = {
|
|
375
|
+
"label": channel.label,
|
|
376
|
+
"wavelength_id": channel.wavelength_id,
|
|
377
|
+
"active": channel.channel_visualisation.active,
|
|
378
|
+
}
|
|
379
|
+
if channel.channel_visualisation.model_extra is not None:
|
|
380
|
+
_model_extra.update(channel.channel_visualisation.model_extra)
|
|
381
|
+
|
|
382
|
+
v05_channels.append(
|
|
383
|
+
ChannelV05(
|
|
384
|
+
color=channel.channel_visualisation.valid_color,
|
|
385
|
+
window=WindowV05(
|
|
386
|
+
start=channel.channel_visualisation.start,
|
|
387
|
+
end=channel.channel_visualisation.end,
|
|
388
|
+
min=channel.channel_visualisation.min,
|
|
389
|
+
max=channel.channel_visualisation.max,
|
|
390
|
+
),
|
|
391
|
+
**_model_extra,
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
_model_extra = channels.model_extra if channels.model_extra is not None else {}
|
|
396
|
+
return OmeroV05(channels=v05_channels, **_model_extra)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def ngio_to_v05_image_meta(metadata: NgioImageMeta) -> dict:
|
|
400
|
+
"""Convert a ngio image metadata to a v05 image metadata.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
metadata (NgioImageMeta): The ngio image metadata.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
dict: The v05 image metadata.
|
|
407
|
+
"""
|
|
408
|
+
v05_muliscale = _ngio_to_v05_multiscale(
|
|
409
|
+
name=metadata.name, datasets=metadata.datasets
|
|
410
|
+
)
|
|
411
|
+
v05_omero = _ngio_to_v05_omero(metadata._channels_meta)
|
|
412
|
+
|
|
413
|
+
v05_image_attrs = ImageV05AttrsWithOmero(
|
|
414
|
+
multiscales=[v05_muliscale], omero=v05_omero, version="0.5"
|
|
415
|
+
)
|
|
416
|
+
v05_image = ImageV05WithOmero(
|
|
417
|
+
ome=v05_image_attrs,
|
|
418
|
+
)
|
|
419
|
+
return v05_image.model_dump(exclude_none=True, by_alias=True)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def ngio_to_v05_label_meta(metadata: NgioLabelMeta) -> dict:
|
|
423
|
+
"""Convert a ngio image metadata to a v05 image metadata.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
metadata (NgioImageMeta): The ngio image metadata.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
dict: The v05 image metadata.
|
|
430
|
+
"""
|
|
431
|
+
v05_muliscale = _ngio_to_v05_multiscale(
|
|
432
|
+
name=metadata.name, datasets=metadata.datasets
|
|
433
|
+
)
|
|
434
|
+
labels_meta = {
|
|
435
|
+
"multiscales": [v05_muliscale],
|
|
436
|
+
"image-label": metadata.image_label.model_dump(),
|
|
437
|
+
}
|
|
438
|
+
v05_label = LabelAttrsV05(**labels_meta, version="0.5")
|
|
439
|
+
v05_label = ImageLabelV05(
|
|
440
|
+
ome=v05_label,
|
|
441
|
+
)
|
|
442
|
+
return v05_label.model_dump(exclude_none=True, by_alias=True)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class WellV05(BaseModel):
|
|
446
|
+
ome: WellAttrsV05
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class HCSV05(BaseModel):
|
|
450
|
+
ome: HCSAttrsV05
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def v05_to_ngio_well_meta(
|
|
454
|
+
metadata: dict,
|
|
455
|
+
) -> tuple[bool, NgioWellMeta | ValidationError]:
|
|
456
|
+
"""Convert a v05 well metadata to a ngio well metadata.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
metadata (dict): The v05 well metadata.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
result (bool): True if the conversion was successful, False otherwise.
|
|
463
|
+
ngio_well_meta (NgioWellMeta): The ngio well metadata.
|
|
464
|
+
"""
|
|
465
|
+
try:
|
|
466
|
+
v05_well = WellV05(**metadata)
|
|
467
|
+
except ValidationError as e:
|
|
468
|
+
return False, e
|
|
469
|
+
|
|
470
|
+
return True, NgioWellMeta(**v05_well.ome.model_dump())
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def v05_to_ngio_plate_meta(
|
|
474
|
+
metadata: dict,
|
|
475
|
+
) -> tuple[bool, NgioPlateMeta | ValidationError]:
|
|
476
|
+
"""Convert a v05 plate metadata to a ngio plate metadata.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
metadata (dict): The v05 plate metadata.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
result (bool): True if the conversion was successful, False otherwise.
|
|
483
|
+
ngio_plate_meta (NgioPlateMeta): The ngio plate metadata.
|
|
484
|
+
"""
|
|
485
|
+
try:
|
|
486
|
+
v05_plate = HCSV05(**metadata)
|
|
487
|
+
except ValidationError as e:
|
|
488
|
+
return False, e
|
|
489
|
+
|
|
490
|
+
return True, NgioPlateMeta(**v05_plate.ome.model_dump())
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def ngio_to_v05_well_meta(metadata: NgioWellMeta) -> dict:
|
|
494
|
+
"""Convert a ngio well metadata to a v05 well metadata.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
metadata (NgioWellMeta): The ngio well metadata.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
dict: The v05 well metadata.
|
|
501
|
+
"""
|
|
502
|
+
v05_well = WellAttrsV05(**metadata.model_dump())
|
|
503
|
+
v05_well = WellV05(ome=v05_well)
|
|
504
|
+
return v05_well.model_dump(exclude_none=True, by_alias=True)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def ngio_to_v05_plate_meta(metadata: NgioPlateMeta) -> dict:
|
|
508
|
+
"""Convert a ngio plate metadata to a v05 plate metadata.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
metadata (NgioPlateMeta): The ngio plate metadata.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
dict: The v05 plate metadata.
|
|
515
|
+
"""
|
|
516
|
+
v05_plate = HCSAttrsV05(**metadata.model_dump())
|
|
517
|
+
v05_plate = HCSV05(ome=v05_plate)
|
|
518
|
+
return v05_plate.model_dump(exclude_none=True, by_alias=True)
|
ngio/tables/_tables_container.py
CHANGED
|
@@ -359,7 +359,7 @@ ImplementedTables().add_implementation(ConditionTableV1)
|
|
|
359
359
|
def open_tables_container(
|
|
360
360
|
store: StoreOrGroup,
|
|
361
361
|
cache: bool = False,
|
|
362
|
-
mode: AccessModeLiteral = "
|
|
362
|
+
mode: AccessModeLiteral = "a",
|
|
363
363
|
parallel_safe: bool = False,
|
|
364
364
|
) -> TablesContainer:
|
|
365
365
|
"""Open a table handler from a Zarr store."""
|
|
@@ -373,7 +373,7 @@ def open_table(
|
|
|
373
373
|
store: StoreOrGroup,
|
|
374
374
|
backend: TableBackend | None = None,
|
|
375
375
|
cache: bool = False,
|
|
376
|
-
mode: AccessModeLiteral = "
|
|
376
|
+
mode: AccessModeLiteral = "a",
|
|
377
377
|
parallel_safe: bool = False,
|
|
378
378
|
) -> Table:
|
|
379
379
|
"""Open a table from a Zarr store."""
|
|
@@ -391,7 +391,7 @@ def open_table_as(
|
|
|
391
391
|
table_cls: type[TableType],
|
|
392
392
|
backend: TableBackend | None = None,
|
|
393
393
|
cache: bool = False,
|
|
394
|
-
mode: AccessModeLiteral = "
|
|
394
|
+
mode: AccessModeLiteral = "a",
|
|
395
395
|
parallel_safe: bool = False,
|
|
396
396
|
) -> TableType:
|
|
397
397
|
"""Open a table from a Zarr store as a specific type."""
|
|
@@ -198,6 +198,13 @@ class AbstractTableBackend(ABC):
|
|
|
198
198
|
if metadata is None:
|
|
199
199
|
metadata = {}
|
|
200
200
|
|
|
201
|
+
attrs = self._group_handler.reopen_group().attrs.asdict()
|
|
202
|
+
# This is required by anndata to identify the format
|
|
203
|
+
if "encoding-type" in attrs:
|
|
204
|
+
metadata["encoding-type"] = attrs["encoding-type"]
|
|
205
|
+
if "encoding-version" in attrs:
|
|
206
|
+
metadata["encoding-version"] = attrs["encoding-version"]
|
|
207
|
+
|
|
201
208
|
backend_metadata = BackendMeta(
|
|
202
209
|
backend=self.backend_name(),
|
|
203
210
|
index_key=self.index_key,
|
ngio/tables/backends/_anndata.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from anndata import AnnData
|
|
2
|
+
from anndata._settings import settings
|
|
2
3
|
from pandas import DataFrame
|
|
3
4
|
from polars import DataFrame as PolarsDataFrame
|
|
4
5
|
from polars import LazyFrame
|
|
@@ -40,6 +41,7 @@ class AnnDataBackend(AbstractTableBackend):
|
|
|
40
41
|
|
|
41
42
|
def load_as_anndata(self) -> AnnData:
|
|
42
43
|
"""Load the table as an AnnData object."""
|
|
44
|
+
settings.zarr_write_format = self._group_handler.zarr_format
|
|
43
45
|
anndata = custom_anndata_read_zarr(self._group_handler._group)
|
|
44
46
|
anndata = normalize_anndata(anndata, index_key=self.index_key)
|
|
45
47
|
return anndata
|
|
@@ -58,7 +60,8 @@ class AnnDataBackend(AbstractTableBackend):
|
|
|
58
60
|
"Please make sure to use a compatible "
|
|
59
61
|
"store like a zarr.DirectoryStore."
|
|
60
62
|
)
|
|
61
|
-
|
|
63
|
+
settings.zarr_write_format = self._group_handler.zarr_format
|
|
64
|
+
table.write_zarr(full_url)
|
|
62
65
|
|
|
63
66
|
def write_from_pandas(self, table: DataFrame) -> None:
|
|
64
67
|
"""Serialize the table from a pandas DataFrame."""
|
|
@@ -9,6 +9,7 @@ from anndata._io.utils import _read_legacy_raw
|
|
|
9
9
|
from anndata._io.zarr import read_dataframe
|
|
10
10
|
from anndata.compat import _clean_uns
|
|
11
11
|
from anndata.experimental import read_dispatched
|
|
12
|
+
from zarr.storage import LocalStore
|
|
12
13
|
|
|
13
14
|
from ngio.utils import (
|
|
14
15
|
NgioValueError,
|
|
@@ -35,7 +36,7 @@ def custom_anndata_read_zarr(
|
|
|
35
36
|
"""
|
|
36
37
|
group = open_group_wrapper(store=store, mode="r")
|
|
37
38
|
|
|
38
|
-
if not isinstance(group.store,
|
|
39
|
+
if not isinstance(group.store, LocalStore):
|
|
39
40
|
elem_to_read = ["X", "obs", "var"]
|
|
40
41
|
|
|
41
42
|
if elem_to_read is None:
|
|
@@ -87,6 +88,8 @@ def custom_anndata_read_zarr(
|
|
|
87
88
|
if isinstance(group["obs"], zarr.Array):
|
|
88
89
|
_clean_uns(adata)
|
|
89
90
|
|
|
91
|
+
if isinstance(adata, dict):
|
|
92
|
+
adata = AnnData(**adata)
|
|
90
93
|
if not isinstance(adata, AnnData):
|
|
91
94
|
raise NgioValueError(f"Expected an AnnData object, but got {type(adata)}")
|
|
92
95
|
return adata
|
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
from pandas import DataFrame
|
|
6
6
|
from polars import DataFrame as PolarsDataFrame
|
|
7
7
|
from polars import LazyFrame
|
|
8
|
-
from zarr.storage import
|
|
8
|
+
from zarr.storage import FsspecStore, LocalStore
|
|
9
9
|
|
|
10
10
|
from ngio.tables.backends._abstract_backend import AbstractTableBackend
|
|
11
11
|
from ngio.tables.backends._utils import normalize_pandas_df, normalize_polars_lf
|
|
@@ -88,9 +88,9 @@ class NonZarrBaseBackend(AbstractTableBackend):
|
|
|
88
88
|
def load_as_pandas_df(self) -> DataFrame:
|
|
89
89
|
"""Load the table as a pandas DataFrame."""
|
|
90
90
|
store = self._group_handler.store
|
|
91
|
-
if isinstance(store,
|
|
91
|
+
if isinstance(store, LocalStore):
|
|
92
92
|
dataframe = self._load_from_directory_store(reader=self.df_reader)
|
|
93
|
-
elif isinstance(store,
|
|
93
|
+
elif isinstance(store, FsspecStore):
|
|
94
94
|
dataframe = self._load_from_fs_store_df(reader=self.df_reader)
|
|
95
95
|
else:
|
|
96
96
|
ext = self.table_name.split(".")[-1]
|
|
@@ -117,9 +117,9 @@ class NonZarrBaseBackend(AbstractTableBackend):
|
|
|
117
117
|
def load_as_polars_lf(self) -> LazyFrame:
|
|
118
118
|
"""Load the table as a polars LazyFrame."""
|
|
119
119
|
store = self._group_handler.store
|
|
120
|
-
if isinstance(store,
|
|
120
|
+
if isinstance(store, LocalStore):
|
|
121
121
|
lazy_frame = self._load_from_directory_store(reader=self.lf_reader)
|
|
122
|
-
elif isinstance(store,
|
|
122
|
+
elif isinstance(store, FsspecStore):
|
|
123
123
|
lazy_frame = self._load_from_fs_store_lf(reader=self.lf_reader)
|
|
124
124
|
else:
|
|
125
125
|
ext = self.table_name.split(".")[-1]
|
|
@@ -146,7 +146,7 @@ class NonZarrBaseBackend(AbstractTableBackend):
|
|
|
146
146
|
def _get_store_url(self) -> str:
|
|
147
147
|
"""Get the store URL."""
|
|
148
148
|
store = self._group_handler.store
|
|
149
|
-
if isinstance(store,
|
|
149
|
+
if isinstance(store, LocalStore):
|
|
150
150
|
full_url = self._group_handler.full_url
|
|
151
151
|
else:
|
|
152
152
|
ext = self.table_name.split(".")[-1]
|
ngio/tables/backends/_utils.py
CHANGED
|
@@ -403,7 +403,7 @@ def convert_anndata_to_pandas(
|
|
|
403
403
|
DataFrame: Converted and normalized pandas DataFrame.
|
|
404
404
|
"""
|
|
405
405
|
pandas_df = anndata.to_df()
|
|
406
|
-
pandas_df[anndata.
|
|
406
|
+
pandas_df[anndata.obs.columns.to_list()] = anndata.obs
|
|
407
407
|
pandas_df = normalize_pandas_df(
|
|
408
408
|
pandas_df,
|
|
409
409
|
index_key=index_key,
|
ngio/tables/v1/_roi_table.py
CHANGED
|
@@ -86,10 +86,10 @@ def _dataframe_to_rois(
|
|
|
86
86
|
) -> dict[str, Roi]:
|
|
87
87
|
"""Convert a DataFrame to a WorldCooROI object."""
|
|
88
88
|
# Validate the columns of the DataFrame
|
|
89
|
-
|
|
90
|
-
if len(
|
|
89
|
+
_required_columns = set(dataframe.columns).intersection(set(required_columns))
|
|
90
|
+
if len(_required_columns) != len(required_columns):
|
|
91
91
|
raise NgioTableValidationError(
|
|
92
|
-
f"Could not find required columns: {
|
|
92
|
+
f"Could not find required columns: {_required_columns} in the table."
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
extra_columns = set(dataframe.columns).difference(
|
ngio/utils/_datasets.py
CHANGED
|
@@ -155,11 +155,5 @@ def download_ome_zarr_dataset(
|
|
|
155
155
|
path=download_dir,
|
|
156
156
|
processor=processor,
|
|
157
157
|
progressbar=progressbar,
|
|
158
|
-
# Add User-Agent to avoid 403 errors from Zenodo
|
|
159
|
-
downloader=pooch.HTTPDownloader(
|
|
160
|
-
headers={
|
|
161
|
-
"User-Agent": f"pooch/{pooch.__version__} (https://github.com/BioVisionCenter/ngio)"
|
|
162
|
-
}
|
|
163
|
-
),
|
|
164
158
|
)
|
|
165
159
|
return processor.output_file()
|