ngio 0.5.0b6__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 +69 -0
- ngio/common/__init__.py +28 -0
- ngio/common/_dimensions.py +335 -0
- ngio/common/_masking_roi.py +153 -0
- ngio/common/_pyramid.py +408 -0
- ngio/common/_roi.py +315 -0
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +188 -0
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +126 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/__init__.py +19 -0
- ngio/hcs/_plate.py +1354 -0
- ngio/images/__init__.py +44 -0
- ngio/images/_abstract_image.py +967 -0
- ngio/images/_create_synt_container.py +132 -0
- ngio/images/_create_utils.py +423 -0
- ngio/images/_image.py +926 -0
- ngio/images/_label.py +411 -0
- ngio/images/_masked_image.py +531 -0
- ngio/images/_ome_zarr_container.py +1237 -0
- ngio/images/_table_ops.py +471 -0
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +146 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +377 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +411 -0
- ngio/io_pipes/_ops_slices_utils.py +199 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +180 -0
- ngio/ome_zarr_meta/__init__.py +65 -0
- ngio/ome_zarr_meta/_meta_handlers.py +536 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +77 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +515 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +462 -0
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +89 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +539 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +438 -0
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +122 -0
- ngio/ome_zarr_meta/v04/__init__.py +27 -0
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/_v04_spec.py +473 -0
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/__init__.py +43 -0
- ngio/tables/_abstract_table.py +270 -0
- ngio/tables/_tables_container.py +449 -0
- ngio/tables/backends/__init__.py +57 -0
- ngio/tables/backends/_abstract_backend.py +240 -0
- ngio/tables/backends/_anndata.py +139 -0
- ngio/tables/backends/_anndata_utils.py +90 -0
- ngio/tables/backends/_csv.py +19 -0
- ngio/tables/backends/_json.py +92 -0
- ngio/tables/backends/_parquet.py +19 -0
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_table_backends.py +226 -0
- ngio/tables/backends/_utils.py +608 -0
- ngio/tables/v1/__init__.py +23 -0
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +125 -0
- ngio/tables/v1/_generic_table.py +49 -0
- ngio/tables/v1/_roi_table.py +575 -0
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +45 -0
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +165 -0
- ngio/utils/_errors.py +37 -0
- ngio/utils/_fractal_fsspec_store.py +42 -0
- ngio/utils/_zarr_utils.py +534 -0
- ngio-0.5.0b6.dist-info/METADATA +148 -0
- ngio-0.5.0b6.dist-info/RECORD +88 -0
- ngio-0.5.0b6.dist-info/WHEEL +4 -0
- ngio-0.5.0b6.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
import pyarrow as pa
|
|
5
|
+
import pyarrow.csv as pa_csv
|
|
6
|
+
import pyarrow.dataset as pa_ds
|
|
7
|
+
import pyarrow.fs as pa_fs
|
|
8
|
+
import pyarrow.parquet as pa_parquet
|
|
9
|
+
from pandas import DataFrame
|
|
10
|
+
from polars import DataFrame as PolarsDataFrame
|
|
11
|
+
from polars import LazyFrame
|
|
12
|
+
from zarr.storage import FsspecStore, LocalStore, MemoryStore, ZipStore
|
|
13
|
+
|
|
14
|
+
from ngio.tables.backends._abstract_backend import AbstractTableBackend
|
|
15
|
+
from ngio.tables.backends._utils import normalize_pandas_df, normalize_polars_lf
|
|
16
|
+
from ngio.utils import NgioValueError
|
|
17
|
+
from ngio.utils._zarr_utils import _make_sync_fs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PyArrowBackend(AbstractTableBackend):
|
|
21
|
+
"""A class to load and write small tables in CSV format."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
table_name: str,
|
|
26
|
+
table_format: Literal["csv", "parquet"] = "parquet",
|
|
27
|
+
):
|
|
28
|
+
self.table_name = table_name
|
|
29
|
+
self.table_format = table_format
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def implements_anndata() -> bool:
|
|
33
|
+
"""Whether the handler implements the anndata protocol."""
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def implements_pandas() -> bool:
|
|
38
|
+
"""Whether the handler implements the dataframe protocol."""
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def implements_polars() -> bool:
|
|
43
|
+
"""Whether the handler implements the polars protocol."""
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def backend_name() -> str:
|
|
48
|
+
"""Return the name of the backend."""
|
|
49
|
+
raise NotImplementedError(
|
|
50
|
+
"The backend_name method must be implemented in the subclass."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def _raise_store_type_not_supported(self):
|
|
54
|
+
"""Raise an error for unsupported store types."""
|
|
55
|
+
ext = self.table_name.split(".")[-1]
|
|
56
|
+
store = self._group_handler.store
|
|
57
|
+
raise NgioValueError(
|
|
58
|
+
f"Ngio does not support reading a {ext} table from a "
|
|
59
|
+
f"store of type {type(store)}. "
|
|
60
|
+
"Please make sure to use a compatible "
|
|
61
|
+
"store like a LocalStore, or "
|
|
62
|
+
"FsspecStore, or MemoryStore, or ZipStore."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _load_from_local_store(self, store: LocalStore, path: str) -> pa_ds.Dataset:
|
|
66
|
+
"""Load the table from a directory store."""
|
|
67
|
+
root_path = store.root
|
|
68
|
+
table_path = f"{root_path}/{path}/{self.table_name}"
|
|
69
|
+
dataset = pa_ds.dataset(table_path, format=self.table_format)
|
|
70
|
+
return dataset
|
|
71
|
+
|
|
72
|
+
def _load_from_fsspec_store(self, store: FsspecStore, path: str) -> pa_ds.Dataset:
|
|
73
|
+
"""Load the table from an FS store."""
|
|
74
|
+
table_path = f"{store.path}/{path}/{self.table_name}"
|
|
75
|
+
fs = _make_sync_fs(store.fs)
|
|
76
|
+
dataset = pa_ds.dataset(table_path, format=self.table_format, filesystem=fs)
|
|
77
|
+
return dataset
|
|
78
|
+
|
|
79
|
+
def _load_from_in_memory_store(
|
|
80
|
+
self, store: MemoryStore, path: str
|
|
81
|
+
) -> pa_ds.Dataset:
|
|
82
|
+
"""Load the table from an in-memory store."""
|
|
83
|
+
table_path = f"{path}/{self.table_name}"
|
|
84
|
+
table = store._store_dict.get(table_path, None)
|
|
85
|
+
if table is None:
|
|
86
|
+
raise NgioValueError(
|
|
87
|
+
f"Table {self.table_name} not found in the in-memory store at "
|
|
88
|
+
f"path {path}."
|
|
89
|
+
)
|
|
90
|
+
assert isinstance(table, pa.Table)
|
|
91
|
+
dataset = pa_ds.dataset(table)
|
|
92
|
+
return dataset
|
|
93
|
+
|
|
94
|
+
def _load_from_zip_store(self, store: ZipStore, path: str) -> pa_ds.Dataset:
|
|
95
|
+
"""Load the table from a zip store."""
|
|
96
|
+
raise NotImplementedError("Zip store loading is not implemented yet.")
|
|
97
|
+
|
|
98
|
+
def _load_pyarrow_dataset(self) -> pa_ds.Dataset:
|
|
99
|
+
"""Load the table as a pyarrow Dataset."""
|
|
100
|
+
store = self._group_handler.store
|
|
101
|
+
path = self._group_handler.group.path
|
|
102
|
+
if isinstance(store, LocalStore):
|
|
103
|
+
return self._load_from_local_store(store, path)
|
|
104
|
+
elif isinstance(store, FsspecStore):
|
|
105
|
+
return self._load_from_fsspec_store(store, path)
|
|
106
|
+
elif isinstance(store, MemoryStore):
|
|
107
|
+
return self._load_from_in_memory_store(store, path)
|
|
108
|
+
elif isinstance(store, ZipStore):
|
|
109
|
+
return self._load_from_zip_store(store, path)
|
|
110
|
+
self._raise_store_type_not_supported()
|
|
111
|
+
|
|
112
|
+
def load_as_pandas_df(self) -> DataFrame:
|
|
113
|
+
"""Load the table as a pandas DataFrame."""
|
|
114
|
+
dataset = self._load_pyarrow_dataset()
|
|
115
|
+
dataframe = dataset.to_table().to_pandas()
|
|
116
|
+
dataframe = normalize_pandas_df(
|
|
117
|
+
dataframe,
|
|
118
|
+
index_key=self.index_key,
|
|
119
|
+
index_type=self.index_type,
|
|
120
|
+
reset_index=False,
|
|
121
|
+
)
|
|
122
|
+
return dataframe
|
|
123
|
+
|
|
124
|
+
def load(self) -> DataFrame:
|
|
125
|
+
"""Load the table as a pandas DataFrame."""
|
|
126
|
+
return self.load_as_pandas_df()
|
|
127
|
+
|
|
128
|
+
def load_as_polars_lf(self) -> LazyFrame:
|
|
129
|
+
"""Load the table as a polars LazyFrame."""
|
|
130
|
+
dataset = self._load_pyarrow_dataset()
|
|
131
|
+
lazy_frame = pl.scan_pyarrow_dataset(dataset)
|
|
132
|
+
if not isinstance(lazy_frame, LazyFrame):
|
|
133
|
+
raise NgioValueError(
|
|
134
|
+
"Table is not a lazy frame. Please report this issue as an ngio bug."
|
|
135
|
+
f" {type(lazy_frame)}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
lazy_frame = normalize_polars_lf(
|
|
139
|
+
lazy_frame,
|
|
140
|
+
index_key=self.index_key,
|
|
141
|
+
index_type=self.index_type,
|
|
142
|
+
)
|
|
143
|
+
return lazy_frame
|
|
144
|
+
|
|
145
|
+
def _write_to_stream(self, stream, table: pa.Table) -> None:
|
|
146
|
+
"""Write the table to a stream."""
|
|
147
|
+
if self.table_format == "parquet":
|
|
148
|
+
pa_parquet.write_table(table, stream)
|
|
149
|
+
elif self.table_format == "csv":
|
|
150
|
+
pa_csv.write_csv(table, stream)
|
|
151
|
+
else:
|
|
152
|
+
raise NgioValueError(
|
|
153
|
+
f"Unsupported table format: {self.table_format}. "
|
|
154
|
+
"Supported formats are 'parquet' and 'csv'."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def _write_to_local_store(
|
|
158
|
+
self, store: LocalStore, path: str, table: pa.Table
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Write the table to a directory store."""
|
|
161
|
+
root_path = store.root
|
|
162
|
+
table_path = f"{root_path}/{path}/{self.table_name}"
|
|
163
|
+
self._write_to_stream(table_path, table)
|
|
164
|
+
|
|
165
|
+
def _write_to_fsspec_store(
|
|
166
|
+
self, store: FsspecStore, path: str, table: pa.Table
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Write the table to an FS store."""
|
|
169
|
+
table_path = f"{store.path}/{path}/{self.table_name}"
|
|
170
|
+
fs = _make_sync_fs(store.fs)
|
|
171
|
+
fs = pa_fs.PyFileSystem(pa_fs.FSSpecHandler(fs))
|
|
172
|
+
with fs.open_output_stream(table_path) as out_stream:
|
|
173
|
+
self._write_to_stream(out_stream, table)
|
|
174
|
+
|
|
175
|
+
def _write_to_in_memory_store(
|
|
176
|
+
self, store: MemoryStore, path: str, table: pa.Table
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Write the table to an in-memory store."""
|
|
179
|
+
table_path = f"{path}/{self.table_name}"
|
|
180
|
+
store._store_dict[table_path] = table
|
|
181
|
+
|
|
182
|
+
def _write_to_zip_store(self, store: ZipStore, path: str, table: pa.Table) -> None:
|
|
183
|
+
"""Write the table to a zip store."""
|
|
184
|
+
raise NotImplementedError("Writing to zip store is not implemented yet.")
|
|
185
|
+
|
|
186
|
+
def _write_pyarrow_dataset(self, dataset: pa.Table) -> None:
|
|
187
|
+
"""Write the table from a pyarrow Dataset."""
|
|
188
|
+
store = self._group_handler.store
|
|
189
|
+
path = self._group_handler.group.path
|
|
190
|
+
if isinstance(store, LocalStore):
|
|
191
|
+
return self._write_to_local_store(store=store, path=path, table=dataset)
|
|
192
|
+
elif isinstance(store, FsspecStore):
|
|
193
|
+
return self._write_to_fsspec_store(store=store, path=path, table=dataset)
|
|
194
|
+
elif isinstance(store, MemoryStore):
|
|
195
|
+
return self._write_to_in_memory_store(store=store, path=path, table=dataset)
|
|
196
|
+
elif isinstance(store, ZipStore):
|
|
197
|
+
return self._write_to_zip_store(store=store, path=path, table=dataset)
|
|
198
|
+
self._raise_store_type_not_supported()
|
|
199
|
+
|
|
200
|
+
def write_from_pandas(self, table: DataFrame) -> None:
|
|
201
|
+
"""Write the table from a pandas DataFrame."""
|
|
202
|
+
table = normalize_pandas_df(
|
|
203
|
+
table,
|
|
204
|
+
index_key=self.index_key,
|
|
205
|
+
index_type=self.index_type,
|
|
206
|
+
reset_index=True,
|
|
207
|
+
)
|
|
208
|
+
table = pa.Table.from_pandas(table, preserve_index=False)
|
|
209
|
+
self._write_pyarrow_dataset(table)
|
|
210
|
+
|
|
211
|
+
def write_from_polars(self, table: PolarsDataFrame | LazyFrame) -> None:
|
|
212
|
+
"""Write the table from a polars DataFrame or LazyFrame."""
|
|
213
|
+
table = normalize_polars_lf(
|
|
214
|
+
table,
|
|
215
|
+
index_key=self.index_key,
|
|
216
|
+
index_type=self.index_type,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if isinstance(table, LazyFrame):
|
|
220
|
+
table = table.collect()
|
|
221
|
+
table = table.to_arrow()
|
|
222
|
+
self._write_pyarrow_dataset(table)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Protocol for table backends handlers."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Protocol
|
|
4
|
+
|
|
5
|
+
from anndata import AnnData
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
from polars import DataFrame as PolarsDataFrame
|
|
8
|
+
from polars import LazyFrame
|
|
9
|
+
|
|
10
|
+
from ngio.tables.backends._anndata import AnnDataBackend, AnnDataBackendV1
|
|
11
|
+
from ngio.tables.backends._csv import CsvTableBackend
|
|
12
|
+
from ngio.tables.backends._json import JsonTableBackend
|
|
13
|
+
from ngio.tables.backends._parquet import ParquetTableBackend
|
|
14
|
+
from ngio.tables.backends._utils import TabularData
|
|
15
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TableBackendProtocol(Protocol):
|
|
19
|
+
def set_group_handler(
|
|
20
|
+
self,
|
|
21
|
+
group_handler: ZarrGroupHandler,
|
|
22
|
+
index_key: str | None = None,
|
|
23
|
+
index_type: Literal["int", "str"] | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Attach a group handler to the backend.
|
|
26
|
+
|
|
27
|
+
Index keys and index types are used to ensure that the
|
|
28
|
+
serialization and deserialization of the table
|
|
29
|
+
is consistent across different backends.
|
|
30
|
+
|
|
31
|
+
Making sure that this is consistent is
|
|
32
|
+
a duty of the backend implementations.
|
|
33
|
+
"""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def backend_name() -> str:
|
|
38
|
+
"""Return the name of the backend.
|
|
39
|
+
|
|
40
|
+
As a convention we set name as:
|
|
41
|
+
{backend_name}_v{version}
|
|
42
|
+
|
|
43
|
+
Where the version is a integer.
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def group_handler(self) -> ZarrGroupHandler:
|
|
49
|
+
"""Return the group handler."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def implements_anndata() -> bool:
|
|
54
|
+
"""Check if the backend implements the anndata protocol.
|
|
55
|
+
|
|
56
|
+
If this is True, the backend should implement the
|
|
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.
|
|
62
|
+
|
|
63
|
+
If this is False, these methods should raise a
|
|
64
|
+
`NotImplementedError`.
|
|
65
|
+
"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def implements_pandas() -> bool:
|
|
70
|
+
"""Check if the backend implements the pandas protocol.
|
|
71
|
+
|
|
72
|
+
If this is True, the backend should implement the
|
|
73
|
+
`write_from_dataframe` methods.
|
|
74
|
+
|
|
75
|
+
If this is False, these methods should raise a
|
|
76
|
+
`NotImplementedError`.
|
|
77
|
+
"""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def implements_polars() -> bool:
|
|
82
|
+
"""Check if the backend implements the polars protocol.
|
|
83
|
+
|
|
84
|
+
If this is True, the backend should implement the
|
|
85
|
+
`write_from_polars` methods.
|
|
86
|
+
|
|
87
|
+
If this is False, these methods should raise a
|
|
88
|
+
`NotImplementedError`.
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
def load_as_anndata(self) -> AnnData:
|
|
93
|
+
"""Load the table as an AnnData object."""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
def load_as_pandas_df(self) -> DataFrame:
|
|
97
|
+
"""Load the table as a pandas DataFrame."""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
def load_as_polars_lf(self) -> LazyFrame:
|
|
101
|
+
"""Load the table as a polars LazyFrame."""
|
|
102
|
+
...
|
|
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
|
+
|
|
114
|
+
def write_from_pandas(self, table: DataFrame) -> None:
|
|
115
|
+
"""Serialize the table from a pandas DataFrame."""
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
def write_from_anndata(self, table: AnnData) -> None:
|
|
119
|
+
"""Serialize the table from an AnnData object."""
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
def write_from_polars(self, table: LazyFrame | PolarsDataFrame) -> None:
|
|
123
|
+
"""Serialize the table from a polars DataFrame or LazyFrame."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
def write(
|
|
127
|
+
self,
|
|
128
|
+
table_data: DataFrame | AnnData | PolarsDataFrame | LazyFrame,
|
|
129
|
+
metadata: dict[str, str] | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""This is a generic write method.
|
|
132
|
+
|
|
133
|
+
Will call the appropriate write method
|
|
134
|
+
depending on the type of the table.
|
|
135
|
+
|
|
136
|
+
Moreover it will also write the metadata
|
|
137
|
+
if provided, and the backend methadata
|
|
138
|
+
|
|
139
|
+
the backend should write in the zarr group attributes
|
|
140
|
+
- backend: the backend name (self.backend_name())
|
|
141
|
+
- index_key: the index key
|
|
142
|
+
- index_type: the index type
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ImplementedTableBackends:
|
|
148
|
+
"""A class to manage the available table backends."""
|
|
149
|
+
|
|
150
|
+
_instance = None
|
|
151
|
+
_implemented_backends: dict[str, type[TableBackendProtocol]]
|
|
152
|
+
|
|
153
|
+
def __new__(cls):
|
|
154
|
+
"""Create a new instance of the class if it does not exist."""
|
|
155
|
+
if cls._instance is None:
|
|
156
|
+
cls._instance = super().__new__(cls)
|
|
157
|
+
cls._instance._implemented_backends = {}
|
|
158
|
+
return cls._instance
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def available_backends(self) -> list[str]:
|
|
162
|
+
"""Return the available table backends."""
|
|
163
|
+
return list(self._implemented_backends.keys())
|
|
164
|
+
|
|
165
|
+
def get_backend(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
group_handler: ZarrGroupHandler,
|
|
169
|
+
backend_name: str = "anndata",
|
|
170
|
+
index_key: str | None = None,
|
|
171
|
+
index_type: Literal["int", "str"] | None = None,
|
|
172
|
+
) -> TableBackendProtocol:
|
|
173
|
+
"""Try to get a handler for the given store based on the metadata version."""
|
|
174
|
+
if backend_name not in self._implemented_backends:
|
|
175
|
+
raise NgioValueError(f"Table backend {backend_name} not implemented.")
|
|
176
|
+
backend = self._implemented_backends[backend_name]()
|
|
177
|
+
backend.set_group_handler(
|
|
178
|
+
group_handler=group_handler, index_key=index_key, index_type=index_type
|
|
179
|
+
)
|
|
180
|
+
return backend
|
|
181
|
+
|
|
182
|
+
def _add_backend(
|
|
183
|
+
self,
|
|
184
|
+
table_backend: type[TableBackendProtocol],
|
|
185
|
+
name: str,
|
|
186
|
+
overwrite: bool = False,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Register a new handler."""
|
|
189
|
+
if name in self._implemented_backends and not overwrite:
|
|
190
|
+
raise NgioValueError(
|
|
191
|
+
f"Table backend {name} already implemented. "
|
|
192
|
+
"Use the `overwrite=True` parameter to overwrite it."
|
|
193
|
+
)
|
|
194
|
+
self._implemented_backends[name] = table_backend
|
|
195
|
+
|
|
196
|
+
def add_backend(
|
|
197
|
+
self,
|
|
198
|
+
table_backend: type[TableBackendProtocol],
|
|
199
|
+
overwrite: bool = False,
|
|
200
|
+
aliases: list[str] | None = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Register a new handler."""
|
|
203
|
+
self._add_backend(
|
|
204
|
+
table_backend=table_backend,
|
|
205
|
+
name=table_backend.backend_name(),
|
|
206
|
+
overwrite=overwrite,
|
|
207
|
+
)
|
|
208
|
+
if aliases is not None:
|
|
209
|
+
for alias in aliases:
|
|
210
|
+
self._add_backend(
|
|
211
|
+
table_backend=table_backend, name=alias, overwrite=overwrite
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
ImplementedTableBackends().add_backend(AnnDataBackend)
|
|
216
|
+
ImplementedTableBackends().add_backend(AnnDataBackendV1)
|
|
217
|
+
ImplementedTableBackends().add_backend(
|
|
218
|
+
JsonTableBackend, aliases=["experimental_json_v1"]
|
|
219
|
+
)
|
|
220
|
+
ImplementedTableBackends().add_backend(CsvTableBackend, aliases=["experimental_csv_v1"])
|
|
221
|
+
ImplementedTableBackends().add_backend(
|
|
222
|
+
ParquetTableBackend, aliases=["experimental_parquet_v1"]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
TableBackend = Literal["anndata", "json", "csv", "parquet"] | str | TableBackendProtocol
|
|
226
|
+
DefaultTableBackend = "anndata_v1"
|