ngio 0.2.7__py3-none-any.whl → 0.3.0a0__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/ngio_specs/_channels.py +28 -2
- ngio/tables/__init__.py +8 -1
- ngio/tables/abstract_table.py +268 -0
- ngio/tables/backends/__init__.py +18 -0
- ngio/tables/backends/_abstract_backend.py +58 -80
- ngio/tables/backends/_anndata_v1.py +4 -0
- ngio/tables/backends/_csv_v1.py +23 -150
- ngio/tables/backends/_json_v1.py +3 -0
- ngio/tables/backends/_non_zarr_backends_v1.py +196 -0
- ngio/tables/backends/_parquet_v1.py +47 -0
- ngio/tables/backends/_table_backends.py +34 -15
- ngio/tables/backends/_utils.py +147 -1
- ngio/tables/tables_container.py +180 -92
- ngio/tables/v1/__init__.py +8 -1
- ngio/tables/v1/_condition_table.py +67 -0
- ngio/tables/v1/_feature_table.py +62 -126
- ngio/tables/v1/_generic_table.py +14 -163
- ngio/tables/v1/_roi_table.py +281 -201
- ngio/utils/_fractal_fsspec_store.py +29 -0
- {ngio-0.2.7.dist-info → ngio-0.3.0a0.dist-info}/METADATA +3 -3
- {ngio-0.2.7.dist-info → ngio-0.3.0a0.dist-info}/RECORD +27 -23
- ngio/tables/_validators.py +0 -108
- {ngio-0.2.7.dist-info → ngio-0.3.0a0.dist-info}/WHEEL +0 -0
- {ngio-0.2.7.dist-info → ngio-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,17 +10,19 @@ from polars import LazyFrame
|
|
|
10
10
|
from ngio.tables.backends._anndata_v1 import AnnDataBackend
|
|
11
11
|
from ngio.tables.backends._csv_v1 import CsvTableBackend
|
|
12
12
|
from ngio.tables.backends._json_v1 import JsonTableBackend
|
|
13
|
+
from ngio.tables.backends._parquet_v1 import ParquetTableBackend
|
|
14
|
+
from ngio.tables.backends._utils import TabularData
|
|
13
15
|
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class TableBackendProtocol(Protocol):
|
|
17
|
-
def
|
|
19
|
+
def set_group_handler(
|
|
18
20
|
self,
|
|
19
21
|
group_handler: ZarrGroupHandler,
|
|
20
22
|
index_key: str | None = None,
|
|
21
23
|
index_type: Literal["int", "str"] | None = None,
|
|
22
|
-
):
|
|
23
|
-
"""
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Attach a group handler to the backend.
|
|
24
26
|
|
|
25
27
|
Index keys and index types are used to ensure that the
|
|
26
28
|
serialization and deserialization of the table
|
|
@@ -42,12 +44,21 @@ class TableBackendProtocol(Protocol):
|
|
|
42
44
|
"""
|
|
43
45
|
...
|
|
44
46
|
|
|
47
|
+
@property
|
|
48
|
+
def group_handler(self) -> ZarrGroupHandler:
|
|
49
|
+
"""Return the group handler."""
|
|
50
|
+
...
|
|
51
|
+
|
|
45
52
|
@staticmethod
|
|
46
53
|
def implements_anndata() -> bool:
|
|
47
54
|
"""Check if the backend implements the anndata protocol.
|
|
48
55
|
|
|
49
56
|
If this is True, the backend should implement the
|
|
50
|
-
`
|
|
57
|
+
`write_from_anndata` method.
|
|
58
|
+
|
|
59
|
+
AnnData objects are more complex than DataFrames,
|
|
60
|
+
so if this is true the backend should implement the
|
|
61
|
+
full serialization of the AnnData object.
|
|
51
62
|
|
|
52
63
|
If this is False, these methods should raise a
|
|
53
64
|
`NotImplementedError`.
|
|
@@ -59,7 +70,7 @@ class TableBackendProtocol(Protocol):
|
|
|
59
70
|
"""Check if the backend implements the pandas protocol.
|
|
60
71
|
|
|
61
72
|
If this is True, the backend should implement the
|
|
62
|
-
`
|
|
73
|
+
`write_from_dataframe` methods.
|
|
63
74
|
|
|
64
75
|
If this is False, these methods should raise a
|
|
65
76
|
`NotImplementedError`.
|
|
@@ -71,7 +82,7 @@ class TableBackendProtocol(Protocol):
|
|
|
71
82
|
"""Check if the backend implements the polars protocol.
|
|
72
83
|
|
|
73
84
|
If this is True, the backend should implement the
|
|
74
|
-
`
|
|
85
|
+
`write_from_polars` methods.
|
|
75
86
|
|
|
76
87
|
If this is False, these methods should raise a
|
|
77
88
|
`NotImplementedError`.
|
|
@@ -90,6 +101,16 @@ class TableBackendProtocol(Protocol):
|
|
|
90
101
|
"""Load the table as a polars LazyFrame."""
|
|
91
102
|
...
|
|
92
103
|
|
|
104
|
+
def load(self) -> TabularData:
|
|
105
|
+
"""The default load method.
|
|
106
|
+
|
|
107
|
+
This method will be default way to load the table
|
|
108
|
+
from the backend. This method should wrap one of the
|
|
109
|
+
`load_as_anndata`, `load_as_dataframe` or `load_as_polars`
|
|
110
|
+
methods depending on the backend implementation.
|
|
111
|
+
"""
|
|
112
|
+
...
|
|
113
|
+
|
|
93
114
|
def write_from_pandas(self, table: DataFrame) -> None:
|
|
94
115
|
"""Serialize the table from a pandas DataFrame."""
|
|
95
116
|
...
|
|
@@ -104,7 +125,7 @@ class TableBackendProtocol(Protocol):
|
|
|
104
125
|
|
|
105
126
|
def write(
|
|
106
127
|
self,
|
|
107
|
-
|
|
128
|
+
table_data: DataFrame | AnnData | PolarsDataFrame | LazyFrame,
|
|
108
129
|
metadata: dict[str, str] | None = None,
|
|
109
130
|
mode: Literal["pandas", "anndata", "polars"] | None = None,
|
|
110
131
|
) -> None:
|
|
@@ -144,23 +165,20 @@ class ImplementedTableBackends:
|
|
|
144
165
|
|
|
145
166
|
def get_backend(
|
|
146
167
|
self,
|
|
147
|
-
|
|
168
|
+
*,
|
|
148
169
|
group_handler: ZarrGroupHandler,
|
|
170
|
+
backend_name: str = "anndata_v1",
|
|
149
171
|
index_key: str | None = None,
|
|
150
172
|
index_type: Literal["int", "str"] | None = None,
|
|
151
173
|
) -> TableBackendProtocol:
|
|
152
174
|
"""Try to get a handler for the given store based on the metadata version."""
|
|
153
|
-
if backend_name is None:
|
|
154
|
-
# Default to anndata since it is currently
|
|
155
|
-
# the only backend in use.
|
|
156
|
-
backend_name = "anndata_v1"
|
|
157
|
-
|
|
158
175
|
if backend_name not in self._implemented_backends:
|
|
159
176
|
raise NgioValueError(f"Table backend {backend_name} not implemented.")
|
|
160
|
-
|
|
177
|
+
backend = self._implemented_backends[backend_name]()
|
|
178
|
+
backend.set_group_handler(
|
|
161
179
|
group_handler=group_handler, index_key=index_key, index_type=index_type
|
|
162
180
|
)
|
|
163
|
-
return
|
|
181
|
+
return backend
|
|
164
182
|
|
|
165
183
|
def add_backend(
|
|
166
184
|
self,
|
|
@@ -180,3 +198,4 @@ class ImplementedTableBackends:
|
|
|
180
198
|
ImplementedTableBackends().add_backend(AnnDataBackend)
|
|
181
199
|
ImplementedTableBackends().add_backend(JsonTableBackend)
|
|
182
200
|
ImplementedTableBackends().add_backend(CsvTableBackend)
|
|
201
|
+
ImplementedTableBackends().add_backend(ParquetTableBackend)
|
ngio/tables/backends/_utils.py
CHANGED
|
@@ -9,7 +9,6 @@ These functions are used to validate and normalize the tables
|
|
|
9
9
|
to ensure that conversion between formats is consistent.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
# %%
|
|
13
12
|
from copy import deepcopy
|
|
14
13
|
from typing import Literal
|
|
15
14
|
|
|
@@ -24,6 +23,8 @@ from polars import LazyFrame
|
|
|
24
23
|
|
|
25
24
|
from ngio.utils import NgioTableValidationError, NgioValueError
|
|
26
25
|
|
|
26
|
+
TabularData = AnnData | DataFrame | PolarsDataFrame | LazyFrame
|
|
27
|
+
|
|
27
28
|
# -----------------
|
|
28
29
|
# Validation utils
|
|
29
30
|
# -----------------
|
|
@@ -460,3 +461,148 @@ def convert_polars_to_anndata(
|
|
|
460
461
|
pandas_df,
|
|
461
462
|
index_key=index_key,
|
|
462
463
|
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# -----------------
|
|
467
|
+
# Conversion functions
|
|
468
|
+
# -----------------
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def normalize_table(
|
|
472
|
+
table_data: TabularData,
|
|
473
|
+
index_key: str | None = None,
|
|
474
|
+
index_type: Literal["int", "str"] | None = None,
|
|
475
|
+
) -> TabularData:
|
|
476
|
+
"""Normalize a table to a specific format.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
table_data (TabularData): The table to normalize.
|
|
480
|
+
index_key (str | None): The column name to use as the index of the DataFrame.
|
|
481
|
+
Default is None.
|
|
482
|
+
index_type (str | None): The type of the index column in the DataFrame.
|
|
483
|
+
Either 'str' or 'int'. Default is None.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
DataFrame | AnnData | PolarsDataFrame | LazyFrame: Normalized table.
|
|
487
|
+
"""
|
|
488
|
+
if isinstance(table_data, DataFrame):
|
|
489
|
+
return normalize_pandas_df(
|
|
490
|
+
table_data,
|
|
491
|
+
index_key=index_key,
|
|
492
|
+
index_type=index_type,
|
|
493
|
+
reset_index=False,
|
|
494
|
+
)
|
|
495
|
+
if isinstance(table_data, AnnData):
|
|
496
|
+
return normalize_anndata(table_data, index_key=index_key)
|
|
497
|
+
if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
|
|
498
|
+
return normalize_polars_lf(
|
|
499
|
+
table_data,
|
|
500
|
+
index_key=index_key,
|
|
501
|
+
index_type=index_type,
|
|
502
|
+
)
|
|
503
|
+
raise NgioValueError(f"Unsupported table type: {type(table_data)}")
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def convert_to_anndata(
|
|
507
|
+
table_data: TabularData,
|
|
508
|
+
index_key: str | None = None,
|
|
509
|
+
) -> AnnData:
|
|
510
|
+
"""Convert a table to an AnnData object.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
table_data (TabularData): The table to convert.
|
|
514
|
+
index_key (str | None): The column name to use as the index of the DataFrame.
|
|
515
|
+
Default is None.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
AnnData: Converted AnnData object.
|
|
519
|
+
"""
|
|
520
|
+
if isinstance(table_data, AnnData):
|
|
521
|
+
return normalize_anndata(table_data, index_key=index_key)
|
|
522
|
+
if isinstance(table_data, DataFrame):
|
|
523
|
+
return convert_pandas_to_anndata(table_data, index_key=index_key)
|
|
524
|
+
if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
|
|
525
|
+
return convert_polars_to_anndata(table_data, index_key=index_key)
|
|
526
|
+
raise NgioValueError(f"Unsupported table type: {type(table_data)}")
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def convert_to_pandas(
|
|
530
|
+
table_data: TabularData,
|
|
531
|
+
index_key: str | None = None,
|
|
532
|
+
index_type: Literal["int", "str"] | None = None,
|
|
533
|
+
reset_index: bool = False,
|
|
534
|
+
) -> DataFrame:
|
|
535
|
+
"""Convert a table to a pandas DataFrame.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
table_data (TabularData): The table to convert.
|
|
539
|
+
index_key (str | None): The column name to use as the index of the DataFrame.
|
|
540
|
+
Default is None.
|
|
541
|
+
index_type (str | None): The type of the index column in the DataFrame.
|
|
542
|
+
Either 'str' or 'int'. Default is None.
|
|
543
|
+
reset_index (bool): If True the index will be reset (i.e., the index will be
|
|
544
|
+
converted to a column). If False, the index will be kept as is.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
DataFrame: Converted pandas DataFrame.
|
|
548
|
+
"""
|
|
549
|
+
if isinstance(table_data, DataFrame):
|
|
550
|
+
return normalize_pandas_df(
|
|
551
|
+
table_data,
|
|
552
|
+
index_key=index_key,
|
|
553
|
+
index_type=index_type,
|
|
554
|
+
reset_index=reset_index,
|
|
555
|
+
)
|
|
556
|
+
if isinstance(table_data, AnnData):
|
|
557
|
+
return convert_anndata_to_pandas(
|
|
558
|
+
table_data,
|
|
559
|
+
index_key=index_key,
|
|
560
|
+
index_type=index_type,
|
|
561
|
+
reset_index=reset_index,
|
|
562
|
+
)
|
|
563
|
+
if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
|
|
564
|
+
return convert_polars_to_pandas(
|
|
565
|
+
table_data,
|
|
566
|
+
index_key=index_key,
|
|
567
|
+
index_type=index_type,
|
|
568
|
+
reset_index=reset_index,
|
|
569
|
+
)
|
|
570
|
+
raise NgioValueError(f"Unsupported table type: {type(table_data)}")
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def convert_to_polars(
|
|
574
|
+
table_data: TabularData,
|
|
575
|
+
index_key: str | None = None,
|
|
576
|
+
index_type: Literal["int", "str"] | None = None,
|
|
577
|
+
) -> LazyFrame:
|
|
578
|
+
"""Convert a table to a polars LazyFrame.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
table_data (TabularData): The table to convert.
|
|
582
|
+
index_key (str | None): The column name to use as the index of the DataFrame.
|
|
583
|
+
Default is None.
|
|
584
|
+
index_type (str | None): The type of the index column in the DataFrame.
|
|
585
|
+
Either 'str' or 'int'. Default is None.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
LazyFrame: Converted polars LazyFrame.
|
|
589
|
+
"""
|
|
590
|
+
if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
|
|
591
|
+
return normalize_polars_lf(
|
|
592
|
+
table_data,
|
|
593
|
+
index_key=index_key,
|
|
594
|
+
index_type=index_type,
|
|
595
|
+
)
|
|
596
|
+
if isinstance(table_data, DataFrame):
|
|
597
|
+
return convert_pandas_to_polars(
|
|
598
|
+
table_data,
|
|
599
|
+
index_key=index_key,
|
|
600
|
+
index_type=index_type,
|
|
601
|
+
)
|
|
602
|
+
if isinstance(table_data, AnnData):
|
|
603
|
+
return convert_anndata_to_polars(
|
|
604
|
+
table_data,
|
|
605
|
+
index_key=index_key,
|
|
606
|
+
index_type=index_type,
|
|
607
|
+
)
|
|
608
|
+
raise NgioValueError(f"Unsupported table type: {type(table_data)}")
|
ngio/tables/tables_container.py
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
"""Module for handling the /tables group in an OME-NGFF file."""
|
|
2
2
|
|
|
3
|
-
from typing import Literal, Protocol
|
|
3
|
+
from typing import Literal, Protocol, TypeVar
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import anndata as ad
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
from ngio.tables.backends import (
|
|
10
|
+
BackendMeta,
|
|
11
|
+
TableBackendProtocol,
|
|
12
|
+
TabularData,
|
|
13
|
+
)
|
|
14
|
+
from ngio.tables.v1 import (
|
|
15
|
+
ConditionTableV1,
|
|
16
|
+
FeatureTableV1,
|
|
17
|
+
GenericTable,
|
|
18
|
+
MaskingRoiTableV1,
|
|
19
|
+
RoiTableV1,
|
|
20
|
+
)
|
|
21
|
+
from ngio.tables.v1._roi_table import GenericRoiTableV1
|
|
8
22
|
from ngio.utils import (
|
|
9
23
|
AccessModeLiteral,
|
|
10
24
|
NgioValidationError,
|
|
@@ -13,22 +27,23 @@ from ngio.utils import (
|
|
|
13
27
|
ZarrGroupHandler,
|
|
14
28
|
)
|
|
15
29
|
|
|
16
|
-
GenericRoiTable =
|
|
30
|
+
GenericRoiTable = GenericRoiTableV1
|
|
17
31
|
RoiTable = RoiTableV1
|
|
18
32
|
MaskingRoiTable = MaskingRoiTableV1
|
|
19
33
|
FeatureTable = FeatureTableV1
|
|
34
|
+
ConditionTable = ConditionTableV1
|
|
20
35
|
|
|
21
36
|
|
|
22
37
|
class Table(Protocol):
|
|
23
38
|
"""Placeholder class for a table."""
|
|
24
39
|
|
|
25
40
|
@staticmethod
|
|
26
|
-
def
|
|
41
|
+
def table_type() -> str:
|
|
27
42
|
"""Return the type of the table."""
|
|
28
43
|
...
|
|
29
44
|
|
|
30
45
|
@staticmethod
|
|
31
|
-
def version() -> str
|
|
46
|
+
def version() -> str:
|
|
32
47
|
"""Return the version of the table."""
|
|
33
48
|
...
|
|
34
49
|
|
|
@@ -37,19 +52,72 @@ class Table(Protocol):
|
|
|
37
52
|
"""The name of the backend."""
|
|
38
53
|
...
|
|
39
54
|
|
|
55
|
+
@property
|
|
56
|
+
def meta(self) -> BackendMeta:
|
|
57
|
+
"""Return the metadata for the table."""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def dataframe(self) -> pd.DataFrame:
|
|
62
|
+
"""Return the table as a DataFrame."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def lazy_frame(self) -> pl.LazyFrame:
|
|
67
|
+
"""Return the table as a LazyFrame."""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def anndata(self) -> ad.AnnData:
|
|
72
|
+
"""Return the table as an AnnData object."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def set_table_data(
|
|
76
|
+
self,
|
|
77
|
+
table_data: TabularData | None = None,
|
|
78
|
+
refresh: bool = False,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Make sure that the table data is set (exist in memory).
|
|
81
|
+
|
|
82
|
+
If an object is passed, it will be used as the table.
|
|
83
|
+
If None is passed, the table will be loaded from the backend.
|
|
84
|
+
|
|
85
|
+
If refresh is True, the table will be reloaded from the backend.
|
|
86
|
+
If table is not None, this will be ignored.
|
|
87
|
+
"""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
def set_backend(
|
|
91
|
+
self,
|
|
92
|
+
handler: ZarrGroupHandler | None = None,
|
|
93
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Set the backend store and path for the table.
|
|
96
|
+
|
|
97
|
+
Either a handler or a backend must be provided.
|
|
98
|
+
|
|
99
|
+
If the hanlder in none it will be inferred from the backend.
|
|
100
|
+
If the backend is none, it will be inferred from the group attrs
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
40
104
|
@classmethod
|
|
41
|
-
def
|
|
42
|
-
cls,
|
|
105
|
+
def from_handler(
|
|
106
|
+
cls,
|
|
107
|
+
handler: ZarrGroupHandler,
|
|
108
|
+
backend: str | TableBackendProtocol | None = None,
|
|
43
109
|
) -> "Table":
|
|
44
110
|
"""Create a new table from a Zarr group handler."""
|
|
45
111
|
...
|
|
46
112
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_table_data(cls, table_data: TabularData, meta: BackendMeta) -> "Table":
|
|
115
|
+
"""Create a new table from a DataFrame."""
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def table_data(self) -> TabularData:
|
|
120
|
+
"""Return the table."""
|
|
53
121
|
...
|
|
54
122
|
|
|
55
123
|
def consolidate(self) -> None:
|
|
@@ -58,13 +126,32 @@ class Table(Protocol):
|
|
|
58
126
|
|
|
59
127
|
|
|
60
128
|
TypedTable = Literal[
|
|
61
|
-
"roi_table",
|
|
129
|
+
"roi_table",
|
|
130
|
+
"masking_roi_table",
|
|
131
|
+
"feature_table",
|
|
132
|
+
"generic_roi_table",
|
|
133
|
+
"condition_table",
|
|
62
134
|
]
|
|
63
135
|
|
|
136
|
+
TableType = TypeVar("TableType", bound=Table)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TableMeta(BackendMeta):
|
|
140
|
+
"""Base class for table metadata."""
|
|
141
|
+
|
|
142
|
+
fractal_table_version: str = "1"
|
|
143
|
+
type: str = "generic_table"
|
|
64
144
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
145
|
+
def unique_name(self) -> str:
|
|
146
|
+
"""Return the unique name for the table."""
|
|
147
|
+
return f"{self.type}_v{self.fractal_table_version}"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _get_meta(handler: ZarrGroupHandler) -> TableMeta:
|
|
151
|
+
"""Get the metadata from the handler."""
|
|
152
|
+
attrs = handler.load_attrs()
|
|
153
|
+
meta = TableMeta(**attrs)
|
|
154
|
+
return meta
|
|
68
155
|
|
|
69
156
|
|
|
70
157
|
class ImplementedTables:
|
|
@@ -86,77 +173,38 @@ class ImplementedTables:
|
|
|
86
173
|
|
|
87
174
|
def get_table(
|
|
88
175
|
self,
|
|
89
|
-
|
|
90
|
-
version: str,
|
|
176
|
+
meta: TableMeta,
|
|
91
177
|
handler: ZarrGroupHandler,
|
|
92
|
-
|
|
178
|
+
backend: str | TableBackendProtocol | None = None,
|
|
93
179
|
strict: bool = True,
|
|
94
180
|
) -> Table:
|
|
95
181
|
"""Try to get a handler for the given store based on the metadata version."""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
table = table_cls._from_handler(
|
|
102
|
-
handler=handler, backend_name=backend_name
|
|
103
|
-
)
|
|
104
|
-
return table
|
|
105
|
-
except Exception as e:
|
|
106
|
-
if strict:
|
|
107
|
-
raise NgioValidationError(
|
|
108
|
-
f"Could not load table {name} from handler. Error: {e}"
|
|
109
|
-
) from e
|
|
110
|
-
else:
|
|
111
|
-
_errors[name] = e
|
|
112
|
-
# If no table was found, we can try to load the table from a generic table
|
|
113
|
-
try:
|
|
114
|
-
table = GenericTable._from_handler(
|
|
115
|
-
handler=handler, backend_name=backend_name
|
|
116
|
-
)
|
|
117
|
-
return table
|
|
118
|
-
except Exception as e:
|
|
119
|
-
_errors["generic"] = e
|
|
182
|
+
if strict:
|
|
183
|
+
default = None
|
|
184
|
+
else:
|
|
185
|
+
default = GenericTable
|
|
120
186
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
f"
|
|
187
|
+
table_cls = self._implemented_tables.get(meta.unique_name(), default)
|
|
188
|
+
if table_cls is None:
|
|
189
|
+
raise NgioValueError(
|
|
190
|
+
f"Table handler for {meta.unique_name()} not implemented."
|
|
125
191
|
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
f"Could not load table from any known version. Errors: {_errors}"
|
|
129
|
-
)
|
|
192
|
+
table = table_cls.from_handler(handler=handler, backend=backend)
|
|
193
|
+
return table
|
|
130
194
|
|
|
131
195
|
def add_implementation(self, handler: type[Table], overwrite: bool = False):
|
|
132
196
|
"""Register a new table handler."""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if version is None:
|
|
139
|
-
raise NgioValueError("Table handler must have a version.")
|
|
197
|
+
meta = TableMeta(
|
|
198
|
+
type=handler.table_type(),
|
|
199
|
+
fractal_table_version=handler.version(),
|
|
200
|
+
)
|
|
140
201
|
|
|
141
|
-
|
|
142
|
-
if table_unique_name in self._implemented_tables and not overwrite:
|
|
202
|
+
if meta.unique_name() in self._implemented_tables and not overwrite:
|
|
143
203
|
raise NgioValueError(
|
|
144
|
-
f"Table handler for {
|
|
204
|
+
f"Table handler for {meta.unique_name()} already implemented. "
|
|
145
205
|
"Use overwrite=True to replace it."
|
|
146
206
|
)
|
|
147
|
-
self._implemented_tables[
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _get_table_type(handler: ZarrGroupHandler) -> str:
|
|
151
|
-
"""Get the type of the table from the handler."""
|
|
152
|
-
attrs = handler.load_attrs()
|
|
153
|
-
return attrs.get("type", "None")
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _get_table_version(handler: ZarrGroupHandler) -> str:
|
|
157
|
-
"""Get the version of the table from the handler."""
|
|
158
|
-
attrs = handler.load_attrs()
|
|
159
|
-
return attrs.get("fractal_table_version", "None")
|
|
207
|
+
self._implemented_tables[meta.unique_name()] = handler
|
|
160
208
|
|
|
161
209
|
|
|
162
210
|
class TablesContainer:
|
|
@@ -208,34 +256,52 @@ class TablesContainer:
|
|
|
208
256
|
filtered_tables = []
|
|
209
257
|
for table_name in tables:
|
|
210
258
|
tb_handler = self._get_table_group_handler(table_name)
|
|
211
|
-
table_type =
|
|
259
|
+
table_type = _get_meta(tb_handler).type
|
|
212
260
|
if table_type == filter_types:
|
|
213
261
|
filtered_tables.append(table_name)
|
|
214
262
|
return filtered_tables
|
|
215
263
|
|
|
216
264
|
def get(
|
|
217
|
-
self,
|
|
265
|
+
self,
|
|
266
|
+
name: str,
|
|
267
|
+
backend: str | TableBackendProtocol | None = None,
|
|
268
|
+
strict: bool = True,
|
|
218
269
|
) -> Table:
|
|
219
270
|
"""Get a label from the group."""
|
|
220
271
|
if name not in self.list():
|
|
221
|
-
raise
|
|
272
|
+
raise NgioValueError(f"Table '{name}' not found in the group.")
|
|
222
273
|
|
|
223
274
|
table_handler = self._get_table_group_handler(name)
|
|
224
|
-
|
|
225
|
-
|
|
275
|
+
|
|
276
|
+
meta = _get_meta(table_handler)
|
|
226
277
|
return ImplementedTables().get_table(
|
|
227
|
-
|
|
228
|
-
version=table_version,
|
|
278
|
+
meta=meta,
|
|
229
279
|
handler=table_handler,
|
|
230
|
-
|
|
280
|
+
backend=backend,
|
|
231
281
|
strict=strict,
|
|
232
282
|
)
|
|
233
283
|
|
|
284
|
+
def get_as(
|
|
285
|
+
self,
|
|
286
|
+
name: str,
|
|
287
|
+
table_cls: type[TableType],
|
|
288
|
+
backend: str | TableBackendProtocol | None = None,
|
|
289
|
+
) -> TableType:
|
|
290
|
+
"""Get a table from the group as a specific type."""
|
|
291
|
+
if name not in self.list():
|
|
292
|
+
raise NgioValueError(f"Table '{name}' not found in the group.")
|
|
293
|
+
|
|
294
|
+
table_handler = self._get_table_group_handler(name)
|
|
295
|
+
return table_cls.from_handler(
|
|
296
|
+
handler=table_handler,
|
|
297
|
+
backend=backend,
|
|
298
|
+
) # type: ignore[return-value]
|
|
299
|
+
|
|
234
300
|
def add(
|
|
235
301
|
self,
|
|
236
302
|
name: str,
|
|
237
303
|
table: Table,
|
|
238
|
-
backend: str |
|
|
304
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
239
305
|
overwrite: bool = False,
|
|
240
306
|
) -> None:
|
|
241
307
|
"""Add a table to the group."""
|
|
@@ -253,9 +319,10 @@ class TablesContainer:
|
|
|
253
319
|
if backend is None:
|
|
254
320
|
backend = table.backend_name
|
|
255
321
|
|
|
256
|
-
table.
|
|
322
|
+
table.set_table_data()
|
|
323
|
+
table.set_backend(
|
|
257
324
|
handler=table_handler,
|
|
258
|
-
|
|
325
|
+
backend=backend,
|
|
259
326
|
)
|
|
260
327
|
table.consolidate()
|
|
261
328
|
if name not in existing_tables:
|
|
@@ -266,6 +333,7 @@ class TablesContainer:
|
|
|
266
333
|
ImplementedTables().add_implementation(RoiTableV1)
|
|
267
334
|
ImplementedTables().add_implementation(MaskingRoiTableV1)
|
|
268
335
|
ImplementedTables().add_implementation(FeatureTableV1)
|
|
336
|
+
ImplementedTables().add_implementation(ConditionTableV1)
|
|
269
337
|
|
|
270
338
|
###################################################################################
|
|
271
339
|
#
|
|
@@ -289,6 +357,7 @@ def open_tables_container(
|
|
|
289
357
|
|
|
290
358
|
def open_table(
|
|
291
359
|
store: StoreOrGroup,
|
|
360
|
+
backend: str | TableBackendProtocol | None = None,
|
|
292
361
|
cache: bool = False,
|
|
293
362
|
mode: AccessModeLiteral = "a",
|
|
294
363
|
parallel_safe: bool = False,
|
|
@@ -297,15 +366,34 @@ def open_table(
|
|
|
297
366
|
handler = ZarrGroupHandler(
|
|
298
367
|
store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
|
|
299
368
|
)
|
|
369
|
+
meta = _get_meta(handler)
|
|
300
370
|
return ImplementedTables().get_table(
|
|
301
|
-
|
|
371
|
+
meta=meta, handler=handler, backend=backend, strict=False
|
|
302
372
|
)
|
|
303
373
|
|
|
304
374
|
|
|
375
|
+
def open_table_as(
|
|
376
|
+
store: StoreOrGroup,
|
|
377
|
+
table_cls: type[TableType],
|
|
378
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
379
|
+
cache: bool = False,
|
|
380
|
+
mode: AccessModeLiteral = "a",
|
|
381
|
+
parallel_safe: bool = False,
|
|
382
|
+
) -> TableType:
|
|
383
|
+
"""Open a table from a Zarr store as a specific type."""
|
|
384
|
+
handler = ZarrGroupHandler(
|
|
385
|
+
store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
|
|
386
|
+
)
|
|
387
|
+
return table_cls.from_handler(
|
|
388
|
+
handler=handler,
|
|
389
|
+
backend=backend,
|
|
390
|
+
) # type: ignore[return-value]
|
|
391
|
+
|
|
392
|
+
|
|
305
393
|
def write_table(
|
|
306
394
|
store: StoreOrGroup,
|
|
307
395
|
table: Table,
|
|
308
|
-
backend: str |
|
|
396
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
309
397
|
cache: bool = False,
|
|
310
398
|
mode: AccessModeLiteral = "a",
|
|
311
399
|
parallel_safe: bool = False,
|
|
@@ -314,8 +402,8 @@ def write_table(
|
|
|
314
402
|
handler = ZarrGroupHandler(
|
|
315
403
|
store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
|
|
316
404
|
)
|
|
317
|
-
table.
|
|
405
|
+
table.set_backend(
|
|
318
406
|
handler=handler,
|
|
319
|
-
|
|
407
|
+
backend=backend,
|
|
320
408
|
)
|
|
321
409
|
table.consolidate()
|