ngio 0.2.9__py3-none-any.whl → 0.3.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/__init__.py +16 -0
- ngio/common/_table_ops.py +471 -0
- ngio/hcs/plate.py +430 -72
- ngio/images/ome_zarr_container.py +99 -68
- ngio/ome_zarr_meta/_meta_handlers.py +16 -8
- ngio/ome_zarr_meta/ngio_specs/_axes.py +7 -4
- ngio/tables/__init__.py +13 -1
- ngio/tables/abstract_table.py +269 -0
- ngio/tables/backends/__init__.py +20 -0
- ngio/tables/backends/_abstract_backend.py +58 -80
- ngio/tables/backends/{_anndata_v1.py → _anndata.py} +5 -1
- ngio/tables/backends/_csv.py +35 -0
- ngio/tables/backends/{_json_v1.py → _json.py} +4 -1
- ngio/tables/backends/{_csv_v1.py → _non_zarr_backends.py} +61 -27
- ngio/tables/backends/_parquet.py +47 -0
- ngio/tables/backends/_table_backends.py +39 -18
- ngio/tables/backends/_utils.py +147 -1
- ngio/tables/tables_container.py +180 -92
- ngio/tables/v1/__init__.py +19 -3
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +63 -129
- ngio/tables/v1/_generic_table.py +21 -159
- ngio/tables/v1/_roi_table.py +285 -201
- ngio/utils/_fractal_fsspec_store.py +29 -0
- {ngio-0.2.9.dist-info → ngio-0.3.0a1.dist-info}/METADATA +4 -3
- {ngio-0.2.9.dist-info → ngio-0.3.0a1.dist-info}/RECORD +28 -24
- ngio/tables/_validators.py +0 -108
- {ngio-0.2.9.dist-info → ngio-0.3.0a1.dist-info}/WHEEL +0 -0
- {ngio-0.2.9.dist-info → ngio-0.3.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Abstract class for handling OME-NGFF images."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from collections.abc import Collection
|
|
4
|
-
from typing import Literal, overload
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
@@ -22,12 +22,15 @@ from ngio.ome_zarr_meta.ngio_specs import (
|
|
|
22
22
|
TimeUnits,
|
|
23
23
|
)
|
|
24
24
|
from ngio.tables import (
|
|
25
|
+
ConditionTable,
|
|
25
26
|
FeatureTable,
|
|
26
27
|
GenericRoiTable,
|
|
27
28
|
MaskingRoiTable,
|
|
28
29
|
RoiTable,
|
|
29
30
|
Table,
|
|
31
|
+
TableBackend,
|
|
30
32
|
TablesContainer,
|
|
33
|
+
TableType,
|
|
31
34
|
TypedTable,
|
|
32
35
|
)
|
|
33
36
|
from ngio.utils import (
|
|
@@ -264,9 +267,7 @@ class OmeZarrContainer:
|
|
|
264
267
|
if masking_table_name is None:
|
|
265
268
|
masking_table = masking_label.build_masking_roi_table()
|
|
266
269
|
else:
|
|
267
|
-
masking_table = self.
|
|
268
|
-
masking_table_name, check_type="masking_roi_table"
|
|
269
|
-
)
|
|
270
|
+
masking_table = self.get_masking_roi_table(name=masking_table_name)
|
|
270
271
|
|
|
271
272
|
return MaskedImage(
|
|
272
273
|
group_handler=image._group_handler,
|
|
@@ -345,84 +346,116 @@ class OmeZarrContainer:
|
|
|
345
346
|
)
|
|
346
347
|
return new_ome_zarr
|
|
347
348
|
|
|
348
|
-
def list_tables(self) -> list[str]:
|
|
349
|
+
def list_tables(self, filter_types: str | None = None) -> list[str]:
|
|
349
350
|
"""List all tables in the image."""
|
|
350
|
-
return self.tables_container.list()
|
|
351
|
+
return self.tables_container.list(filter_types=filter_types)
|
|
351
352
|
|
|
352
353
|
def list_roi_tables(self) -> list[str]:
|
|
353
354
|
"""List all ROI tables in the image."""
|
|
354
355
|
return self.tables_container.list_roi_tables()
|
|
355
356
|
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
def get_roi_table(self, name: str) -> RoiTable:
|
|
358
|
+
"""Get a ROI table from the image.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
name (str): The name of the table.
|
|
362
|
+
"""
|
|
363
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
364
|
+
if not isinstance(table, RoiTable):
|
|
365
|
+
raise NgioValueError(f"Table {name} is not a ROI table. Got {type(table)}")
|
|
366
|
+
return table
|
|
367
|
+
|
|
368
|
+
def get_masking_roi_table(self, name: str) -> MaskingRoiTable:
|
|
369
|
+
"""Get a masking ROI table from the image.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
name (str): The name of the table.
|
|
373
|
+
"""
|
|
374
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
375
|
+
if not isinstance(table, MaskingRoiTable):
|
|
376
|
+
raise NgioValueError(
|
|
377
|
+
f"Table {name} is not a masking ROI table. Got {type(table)}"
|
|
378
|
+
)
|
|
379
|
+
return table
|
|
380
|
+
|
|
381
|
+
def get_feature_table(self, name: str) -> FeatureTable:
|
|
382
|
+
"""Get a feature table from the image.
|
|
358
383
|
|
|
359
|
-
|
|
360
|
-
|
|
384
|
+
Args:
|
|
385
|
+
name (str): The name of the table.
|
|
386
|
+
"""
|
|
387
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
388
|
+
if not isinstance(table, FeatureTable):
|
|
389
|
+
raise NgioValueError(
|
|
390
|
+
f"Table {name} is not a feature table. Got {type(table)}"
|
|
391
|
+
)
|
|
392
|
+
return table
|
|
361
393
|
|
|
362
|
-
|
|
363
|
-
|
|
394
|
+
def get_generic_roi_table(self, name: str) -> GenericRoiTable:
|
|
395
|
+
"""Get a generic ROI table from the image.
|
|
364
396
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
397
|
+
Args:
|
|
398
|
+
name (str): The name of the table.
|
|
399
|
+
"""
|
|
400
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
401
|
+
if not isinstance(table, GenericRoiTable):
|
|
402
|
+
raise NgioValueError(
|
|
403
|
+
f"Table {name} is not a generic ROI table. Got {type(table)}"
|
|
404
|
+
)
|
|
405
|
+
return table
|
|
369
406
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
self, name: str, check_type: Literal["feature_table"]
|
|
373
|
-
) -> FeatureTable: ...
|
|
407
|
+
def get_condition_table(self, name: str) -> ConditionTable:
|
|
408
|
+
"""Get a condition table from the image.
|
|
374
409
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
410
|
+
Args:
|
|
411
|
+
name (str): The name of the table.
|
|
412
|
+
"""
|
|
413
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
414
|
+
if not isinstance(table, ConditionTable):
|
|
415
|
+
raise NgioValueError(
|
|
416
|
+
f"Table {name} is not a condition table. Got {type(table)}"
|
|
417
|
+
)
|
|
418
|
+
return table
|
|
379
419
|
|
|
380
420
|
def get_table(self, name: str, check_type: TypedTable | None = None) -> Table:
|
|
381
421
|
"""Get a table from the image.
|
|
382
422
|
|
|
383
423
|
Args:
|
|
384
424
|
name (str): The name of the table.
|
|
385
|
-
check_type (TypedTable | None):
|
|
386
|
-
|
|
387
|
-
|
|
425
|
+
check_type (TypedTable | None): Deprecated. Please use
|
|
426
|
+
'get_table_as' instead, or one of the type specific
|
|
427
|
+
get_*table() methods.
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
if check_type is not None:
|
|
431
|
+
warnings.warn(
|
|
432
|
+
"The 'check_type' argument is deprecated, and will be removed in "
|
|
433
|
+
"ngio=0.3. Use 'get_table_as' instead or one of the "
|
|
434
|
+
"type specific get_*table() methods.",
|
|
435
|
+
DeprecationWarning,
|
|
436
|
+
stacklevel=2,
|
|
437
|
+
)
|
|
438
|
+
return self.tables_container.get(name=name, strict=False)
|
|
439
|
+
|
|
440
|
+
def get_table_as(
|
|
441
|
+
self,
|
|
442
|
+
name: str,
|
|
443
|
+
table_cls: type[TableType],
|
|
444
|
+
backend: TableBackend | None = None,
|
|
445
|
+
) -> TableType:
|
|
446
|
+
"""Get a table from the image as a specific type.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
name (str): The name of the table.
|
|
450
|
+
table_cls (type[TableType]): The type of the table.
|
|
451
|
+
backend (TableBackend | None): The backend to use. If None,
|
|
452
|
+
the default backend is used.
|
|
388
453
|
"""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
match check_type:
|
|
395
|
-
case "roi_table":
|
|
396
|
-
if not isinstance(table, RoiTable):
|
|
397
|
-
raise NgioValueError(
|
|
398
|
-
f"Table '{name}' is not a ROI table. Found type: {table.type()}"
|
|
399
|
-
)
|
|
400
|
-
return table
|
|
401
|
-
case "masking_roi_table":
|
|
402
|
-
if not isinstance(table, MaskingRoiTable):
|
|
403
|
-
raise NgioValueError(
|
|
404
|
-
f"Table '{name}' is not a masking ROI table. "
|
|
405
|
-
f"Found type: {table.type()}"
|
|
406
|
-
)
|
|
407
|
-
return table
|
|
408
|
-
|
|
409
|
-
case "generic_roi_table":
|
|
410
|
-
if not isinstance(table, GenericRoiTable):
|
|
411
|
-
raise NgioValueError(
|
|
412
|
-
f"Table '{name}' is not a generic ROI table. "
|
|
413
|
-
f"Found type: {table.type()}"
|
|
414
|
-
)
|
|
415
|
-
return table
|
|
416
|
-
|
|
417
|
-
case "feature_table":
|
|
418
|
-
if not isinstance(table, FeatureTable):
|
|
419
|
-
raise NgioValueError(
|
|
420
|
-
f"Table '{name}' is not a feature table. "
|
|
421
|
-
f"Found type: {table.type()}"
|
|
422
|
-
)
|
|
423
|
-
return table
|
|
424
|
-
case _:
|
|
425
|
-
raise NgioValueError(f"Unknown check_type: {check_type}")
|
|
454
|
+
return self.tables_container.get_as(
|
|
455
|
+
name=name,
|
|
456
|
+
table_cls=table_cls,
|
|
457
|
+
backend=backend,
|
|
458
|
+
)
|
|
426
459
|
|
|
427
460
|
def build_image_roi_table(self, name: str = "image") -> RoiTable:
|
|
428
461
|
"""Compute the ROI table for an image."""
|
|
@@ -436,7 +469,7 @@ class OmeZarrContainer:
|
|
|
436
469
|
self,
|
|
437
470
|
name: str,
|
|
438
471
|
table: Table,
|
|
439
|
-
backend:
|
|
472
|
+
backend: TableBackend = "anndata",
|
|
440
473
|
overwrite: bool = False,
|
|
441
474
|
) -> None:
|
|
442
475
|
"""Add a table to the image."""
|
|
@@ -499,9 +532,7 @@ class OmeZarrContainer:
|
|
|
499
532
|
if masking_table_name is None:
|
|
500
533
|
masking_table = masking_label.build_masking_roi_table()
|
|
501
534
|
else:
|
|
502
|
-
masking_table = self.
|
|
503
|
-
masking_table_name, check_type="masking_roi_table"
|
|
504
|
-
)
|
|
535
|
+
masking_table = self.get_masking_roi_table(name=masking_table_name)
|
|
505
536
|
|
|
506
537
|
return MaskedLabel(
|
|
507
538
|
group_handler=label._group_handler,
|
|
@@ -187,10 +187,6 @@ class GenericMetaHandler(
|
|
|
187
187
|
|
|
188
188
|
raise NgioValueError(f"Could not load metadata: {meta_or_error}")
|
|
189
189
|
|
|
190
|
-
def safe_load_meta(self) -> _image_meta | ConverterError:
|
|
191
|
-
"""Load the metadata from the store."""
|
|
192
|
-
return self._load_meta(return_error=True)
|
|
193
|
-
|
|
194
190
|
def _write_meta(self, meta) -> None:
|
|
195
191
|
"""Write the metadata to the store."""
|
|
196
192
|
_meta = self._meta_exporter(metadata=meta)
|
|
@@ -217,6 +213,10 @@ class ImageMetaHandler(
|
|
|
217
213
|
return meta
|
|
218
214
|
raise NgioValueError(f"Could not load metadata: {meta}")
|
|
219
215
|
|
|
216
|
+
def safe_load_meta(self) -> NgioImageMeta | ConverterError:
|
|
217
|
+
"""Load the metadata from the store."""
|
|
218
|
+
return self._load_meta(return_error=True)
|
|
219
|
+
|
|
220
220
|
|
|
221
221
|
class LabelMetaHandler(
|
|
222
222
|
GenericMetaHandler[NgioLabelMeta, LabelMetaImporter, LabelMetaExporter]
|
|
@@ -230,6 +230,10 @@ class LabelMetaHandler(
|
|
|
230
230
|
return meta
|
|
231
231
|
raise NgioValueError(f"Could not load metadata: {meta}")
|
|
232
232
|
|
|
233
|
+
def safe_load_meta(self) -> NgioLabelMeta | ConverterError:
|
|
234
|
+
"""Load the metadata from the store."""
|
|
235
|
+
return self._load_meta(return_error=True)
|
|
236
|
+
|
|
233
237
|
|
|
234
238
|
###########################################################################
|
|
235
239
|
#
|
|
@@ -267,10 +271,6 @@ class GenericHCSMetaHandler(Generic[_hcs_meta, _hcs_meta_importer, _hcs_meta_exp
|
|
|
267
271
|
|
|
268
272
|
raise NgioValueError(f"Could not load metadata: {meta_or_error}")
|
|
269
273
|
|
|
270
|
-
def safe_load_meta(self) -> _hcs_meta | ConverterError:
|
|
271
|
-
"""Load the metadata from the store."""
|
|
272
|
-
return self._load_meta(return_error=True)
|
|
273
|
-
|
|
274
274
|
def _write_meta(self, meta) -> None:
|
|
275
275
|
_meta = self._meta_exporter(metadata=meta)
|
|
276
276
|
self._group_handler.write_attrs(_meta)
|
|
@@ -295,6 +295,10 @@ class WellMetaHandler(
|
|
|
295
295
|
return meta
|
|
296
296
|
raise NgioValueError(f"Could not load metadata: {meta}")
|
|
297
297
|
|
|
298
|
+
def safe_load_meta(self) -> NgioWellMeta | ConverterError:
|
|
299
|
+
"""Load the metadata from the store."""
|
|
300
|
+
return self._load_meta(return_error=True)
|
|
301
|
+
|
|
298
302
|
|
|
299
303
|
class PlateMetaHandler(
|
|
300
304
|
GenericHCSMetaHandler[NgioPlateMeta, PlateMetaImporter, PlateMetaExporter]
|
|
@@ -308,6 +312,10 @@ class PlateMetaHandler(
|
|
|
308
312
|
return meta
|
|
309
313
|
raise NgioValueError(f"Could not load metadata: {meta}")
|
|
310
314
|
|
|
315
|
+
def safe_load_meta(self) -> NgioPlateMeta | ConverterError:
|
|
316
|
+
"""Load the metadata from the store."""
|
|
317
|
+
return self._load_meta(return_error=True)
|
|
318
|
+
|
|
311
319
|
|
|
312
320
|
###########################################################################
|
|
313
321
|
#
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Collection
|
|
4
4
|
from enum import Enum
|
|
5
|
+
from logging import Logger
|
|
5
6
|
from typing import Literal, TypeVar
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
from pydantic import BaseModel, ConfigDict, Field
|
|
9
10
|
|
|
10
|
-
from ngio.utils import NgioValidationError, NgioValueError
|
|
11
|
+
from ngio.utils import NgioValidationError, NgioValueError
|
|
12
|
+
|
|
13
|
+
logger = Logger(__name__)
|
|
11
14
|
|
|
12
15
|
T = TypeVar("T")
|
|
13
16
|
|
|
@@ -99,20 +102,20 @@ class Axis(BaseModel):
|
|
|
99
102
|
def implicit_type_cast(self, cast_type: AxisType) -> "Axis":
|
|
100
103
|
unit = self.unit
|
|
101
104
|
if self.axis_type != cast_type:
|
|
102
|
-
|
|
105
|
+
logger.warning(
|
|
103
106
|
f"Axis {self.on_disk_name} has type {self.axis_type}. "
|
|
104
107
|
f"Casting to {cast_type}."
|
|
105
108
|
)
|
|
106
109
|
|
|
107
110
|
if cast_type == AxisType.time and unit is None:
|
|
108
|
-
|
|
111
|
+
logger.warning(
|
|
109
112
|
f"Time axis {self.on_disk_name} has unit {self.unit}. "
|
|
110
113
|
f"Casting to {DefaultSpaceUnit}."
|
|
111
114
|
)
|
|
112
115
|
unit = DefaultTimeUnit
|
|
113
116
|
|
|
114
117
|
if cast_type == AxisType.space and unit is None:
|
|
115
|
-
|
|
118
|
+
logger.warning(
|
|
116
119
|
f"Space axis {self.on_disk_name} has unit {unit}. "
|
|
117
120
|
f"Casting to {DefaultSpaceUnit}."
|
|
118
121
|
)
|
ngio/tables/__init__.py
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
"""Ngio Tables implementations."""
|
|
2
2
|
|
|
3
|
-
from ngio.tables.backends import
|
|
3
|
+
from ngio.tables.backends import (
|
|
4
|
+
ImplementedTableBackends,
|
|
5
|
+
TableBackend,
|
|
6
|
+
TableBackendProtocol,
|
|
7
|
+
)
|
|
4
8
|
from ngio.tables.tables_container import (
|
|
9
|
+
ConditionTable,
|
|
5
10
|
FeatureTable,
|
|
6
11
|
GenericRoiTable,
|
|
7
12
|
MaskingRoiTable,
|
|
8
13
|
RoiTable,
|
|
9
14
|
Table,
|
|
10
15
|
TablesContainer,
|
|
16
|
+
TableType,
|
|
11
17
|
TypedTable,
|
|
12
18
|
open_table,
|
|
19
|
+
open_table_as,
|
|
13
20
|
open_tables_container,
|
|
14
21
|
)
|
|
15
22
|
from ngio.tables.v1._generic_table import GenericTable
|
|
16
23
|
|
|
17
24
|
__all__ = [
|
|
25
|
+
"ConditionTable",
|
|
18
26
|
"FeatureTable",
|
|
19
27
|
"GenericRoiTable",
|
|
20
28
|
"GenericTable",
|
|
@@ -22,8 +30,12 @@ __all__ = [
|
|
|
22
30
|
"MaskingRoiTable",
|
|
23
31
|
"RoiTable",
|
|
24
32
|
"Table",
|
|
33
|
+
"TableBackend",
|
|
34
|
+
"TableBackendProtocol",
|
|
35
|
+
"TableType",
|
|
25
36
|
"TablesContainer",
|
|
26
37
|
"TypedTable",
|
|
27
38
|
"open_table",
|
|
39
|
+
"open_table_as",
|
|
28
40
|
"open_tables_container",
|
|
29
41
|
]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Implementation of a generic table class."""
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Literal, Self
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import polars as pl
|
|
9
|
+
from anndata import AnnData
|
|
10
|
+
|
|
11
|
+
from ngio.tables.backends import (
|
|
12
|
+
BackendMeta,
|
|
13
|
+
ImplementedTableBackends,
|
|
14
|
+
TableBackend,
|
|
15
|
+
TableBackendProtocol,
|
|
16
|
+
TabularData,
|
|
17
|
+
convert_to_anndata,
|
|
18
|
+
convert_to_pandas,
|
|
19
|
+
convert_to_polars,
|
|
20
|
+
normalize_table,
|
|
21
|
+
)
|
|
22
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AbstractBaseTable(ABC):
|
|
26
|
+
"""Abstract base class for a table.
|
|
27
|
+
|
|
28
|
+
This is used to define common methods and properties
|
|
29
|
+
for all tables.
|
|
30
|
+
|
|
31
|
+
This class is not meant to be used directly.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
table_data: TabularData | None = None,
|
|
37
|
+
*,
|
|
38
|
+
meta: BackendMeta | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Initialize the table."""
|
|
41
|
+
if meta is None:
|
|
42
|
+
meta = BackendMeta()
|
|
43
|
+
|
|
44
|
+
self._meta = meta
|
|
45
|
+
if table_data is not None:
|
|
46
|
+
table_data = normalize_table(
|
|
47
|
+
table_data,
|
|
48
|
+
index_key=meta.index_key,
|
|
49
|
+
index_type=meta.index_type,
|
|
50
|
+
)
|
|
51
|
+
self._table_data = table_data
|
|
52
|
+
self._table_backend = None
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
"""Return a string representation of the table."""
|
|
56
|
+
return f"{self.__class__.__name__}"
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def table_type() -> str:
|
|
61
|
+
"""Return the type of the table."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def version() -> str:
|
|
67
|
+
"""The generic table does not have a version.
|
|
68
|
+
|
|
69
|
+
Since does not follow a specific schema.
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def backend_name(self) -> str | None:
|
|
75
|
+
"""Return the name of the backend."""
|
|
76
|
+
if self._table_backend is None:
|
|
77
|
+
return None
|
|
78
|
+
return self._table_backend.backend_name()
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def meta(self) -> BackendMeta:
|
|
82
|
+
"""Return the metadata of the table."""
|
|
83
|
+
return self._meta
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def index_key(self) -> str | None:
|
|
87
|
+
"""Get the index key."""
|
|
88
|
+
return self._meta.index_key
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def index_type(self) -> Literal["int", "str"] | None:
|
|
92
|
+
"""Get the index type."""
|
|
93
|
+
return self._meta.index_type
|
|
94
|
+
|
|
95
|
+
def load_as_anndata(self) -> AnnData:
|
|
96
|
+
"""Load the table as an AnnData object."""
|
|
97
|
+
if self._table_backend is None:
|
|
98
|
+
raise NgioValueError("No backend set for the table.")
|
|
99
|
+
return self._table_backend.load_as_anndata()
|
|
100
|
+
|
|
101
|
+
def load_as_pandas_df(self) -> pd.DataFrame:
|
|
102
|
+
"""Load the table as a pandas DataFrame."""
|
|
103
|
+
if self._table_backend is None:
|
|
104
|
+
raise NgioValueError("No backend set for the table.")
|
|
105
|
+
return self._table_backend.load_as_pandas_df()
|
|
106
|
+
|
|
107
|
+
def load_as_polars_lf(self) -> pl.LazyFrame:
|
|
108
|
+
"""Load the table as a polars LazyFrame."""
|
|
109
|
+
if self._table_backend is None:
|
|
110
|
+
raise NgioValueError("No backend set for the table.")
|
|
111
|
+
return self._table_backend.load_as_polars_lf()
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def table_data(self) -> TabularData:
|
|
115
|
+
"""Return the table."""
|
|
116
|
+
if self._table_data is not None:
|
|
117
|
+
return self._table_data
|
|
118
|
+
|
|
119
|
+
if self._table_backend is None:
|
|
120
|
+
raise NgioValueError(
|
|
121
|
+
"The table does not have a DataFrame in memory nor a backend."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self._table_data = self._table_backend.load()
|
|
125
|
+
return self._table_data
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def dataframe(self) -> pd.DataFrame:
|
|
129
|
+
"""Return the table as a DataFrame."""
|
|
130
|
+
return convert_to_pandas(
|
|
131
|
+
self.table_data, index_key=self.index_key, index_type=self.index_type
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def lazy_frame(self) -> pl.LazyFrame:
|
|
136
|
+
"""Return the table as a LazyFrame."""
|
|
137
|
+
return convert_to_polars(
|
|
138
|
+
self.table_data, index_key=self.index_key, index_type=self.index_type
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def anndata(self) -> AnnData:
|
|
143
|
+
"""Return the table as an AnnData object."""
|
|
144
|
+
return convert_to_anndata(self.table_data, index_key=self.index_key)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _load_backend(
|
|
148
|
+
meta: BackendMeta,
|
|
149
|
+
handler: ZarrGroupHandler,
|
|
150
|
+
backend: TableBackend,
|
|
151
|
+
) -> TableBackendProtocol:
|
|
152
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
153
|
+
if isinstance(backend, str):
|
|
154
|
+
return ImplementedTableBackends().get_backend(
|
|
155
|
+
backend_name=backend,
|
|
156
|
+
group_handler=handler,
|
|
157
|
+
index_key=meta.index_key,
|
|
158
|
+
index_type=meta.index_type,
|
|
159
|
+
)
|
|
160
|
+
backend.set_group_handler(
|
|
161
|
+
group_handler=handler,
|
|
162
|
+
index_key=meta.index_key,
|
|
163
|
+
index_type=meta.index_type,
|
|
164
|
+
)
|
|
165
|
+
return backend
|
|
166
|
+
|
|
167
|
+
def set_table_data(
|
|
168
|
+
self,
|
|
169
|
+
table_data: TabularData | None = None,
|
|
170
|
+
refresh: bool = False,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Set the table.
|
|
173
|
+
|
|
174
|
+
If an object is passed, it will be used as the table.
|
|
175
|
+
If None is passed, the table will be loaded from the backend.
|
|
176
|
+
|
|
177
|
+
If refresh is True, the table will be reloaded from the backend.
|
|
178
|
+
If table is not None, this will be ignored.
|
|
179
|
+
"""
|
|
180
|
+
if table_data is not None:
|
|
181
|
+
if not isinstance(table_data, TabularData):
|
|
182
|
+
raise NgioValueError(
|
|
183
|
+
"The table must be a pandas DataFrame, polars LazyFrame, "
|
|
184
|
+
" or AnnData object."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._table_data = normalize_table(
|
|
188
|
+
table_data,
|
|
189
|
+
index_key=self.index_key,
|
|
190
|
+
index_type=self.index_type,
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
if self._table_data is not None and not refresh:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
if self._table_backend is None:
|
|
198
|
+
raise NgioValueError(
|
|
199
|
+
"The table does not have a DataFrame in memory nor a backend."
|
|
200
|
+
)
|
|
201
|
+
self._table_data = self._table_backend.load()
|
|
202
|
+
|
|
203
|
+
def set_backend(
|
|
204
|
+
self,
|
|
205
|
+
handler: ZarrGroupHandler | None = None,
|
|
206
|
+
backend: TableBackend = "anndata",
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Set the backend of the table."""
|
|
209
|
+
if handler is None:
|
|
210
|
+
if self._table_backend is None:
|
|
211
|
+
raise NgioValueError(
|
|
212
|
+
"No backend set for the table yet. "
|
|
213
|
+
"A ZarrGroupHandler must be provided."
|
|
214
|
+
)
|
|
215
|
+
handler = self._table_backend.group_handler
|
|
216
|
+
|
|
217
|
+
meta = self._meta
|
|
218
|
+
_backend = self._load_backend(
|
|
219
|
+
meta=meta,
|
|
220
|
+
handler=handler,
|
|
221
|
+
backend=backend,
|
|
222
|
+
)
|
|
223
|
+
self._table_backend = _backend
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def _from_handler(
|
|
227
|
+
cls,
|
|
228
|
+
handler: ZarrGroupHandler,
|
|
229
|
+
meta_model: builtins.type[BackendMeta],
|
|
230
|
+
backend: TableBackend | None = None,
|
|
231
|
+
) -> Self:
|
|
232
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
233
|
+
meta = meta_model(**handler.load_attrs())
|
|
234
|
+
table = cls(meta=meta)
|
|
235
|
+
if backend is None:
|
|
236
|
+
backend = meta.backend
|
|
237
|
+
table.set_backend(handler=handler, backend=backend)
|
|
238
|
+
return table
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
@abstractmethod
|
|
242
|
+
def from_handler(
|
|
243
|
+
cls,
|
|
244
|
+
handler: ZarrGroupHandler,
|
|
245
|
+
backend: TableBackend | None = None,
|
|
246
|
+
) -> Self:
|
|
247
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def from_table_data(cls, table_data: TabularData, meta: BackendMeta) -> Self:
|
|
252
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
253
|
+
return cls(
|
|
254
|
+
table_data=table_data,
|
|
255
|
+
meta=meta,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def consolidate(self) -> None:
|
|
259
|
+
"""Write the current state of the table to the Zarr file."""
|
|
260
|
+
if self._table_backend is None:
|
|
261
|
+
raise NgioValueError(
|
|
262
|
+
"No backend set for the table. "
|
|
263
|
+
"Please add the table to a OME-Zarr Image before calling consolidate."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
self._table_backend.write(
|
|
267
|
+
self.table_data,
|
|
268
|
+
metadata=self._meta.model_dump(exclude_none=True),
|
|
269
|
+
)
|