ngio 0.2.2__py3-none-any.whl → 0.2.3__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 +5 -1
- ngio/hcs/plate.py +121 -2
- ngio/images/abstract_image.py +1 -0
- ngio/images/image.py +42 -0
- ngio/images/label.py +15 -7
- ngio/images/ome_zarr_container.py +4 -7
- ngio/tables/_validators.py +1 -83
- ngio/tables/backends/__init__.py +27 -1
- ngio/tables/backends/_abstract_backend.py +207 -22
- ngio/tables/backends/_anndata_utils.py +3 -109
- ngio/tables/backends/_anndata_v1.py +43 -46
- ngio/tables/backends/_csv_v1.py +162 -0
- ngio/tables/backends/_json_v1.py +54 -18
- ngio/tables/backends/_table_backends.py +98 -18
- ngio/tables/backends/_utils.py +458 -0
- ngio/tables/tables_container.py +3 -1
- ngio/tables/v1/_feature_table.py +20 -11
- ngio/tables/v1/_generic_table.py +20 -15
- ngio/tables/v1/_roi_table.py +7 -9
- ngio/utils/_zarr_utils.py +46 -32
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/METADATA +3 -1
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/RECORD +24 -22
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/WHEEL +0 -0
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from collections.abc import Collection
|
|
3
2
|
from typing import Literal
|
|
4
3
|
|
|
5
4
|
from anndata import AnnData
|
|
6
5
|
from pandas import DataFrame
|
|
6
|
+
from polars import DataFrame as PolarsDataFrame
|
|
7
|
+
from polars import LazyFrame
|
|
8
|
+
from pydantic import BaseModel
|
|
7
9
|
|
|
8
|
-
from ngio.
|
|
10
|
+
from ngio.tables.backends._utils import (
|
|
11
|
+
convert_anndata_to_pandas,
|
|
12
|
+
convert_anndata_to_polars,
|
|
13
|
+
convert_pandas_to_anndata,
|
|
14
|
+
convert_pandas_to_polars,
|
|
15
|
+
convert_polars_to_anndata,
|
|
16
|
+
convert_polars_to_pandas,
|
|
17
|
+
)
|
|
18
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BackendMeta(BaseModel):
|
|
22
|
+
"""Metadata for the backend."""
|
|
23
|
+
|
|
24
|
+
backend: str | None = None
|
|
25
|
+
index_key: str | None = None
|
|
26
|
+
index_type: Literal["int", "str"] | None = None
|
|
9
27
|
|
|
10
28
|
|
|
11
29
|
class AbstractTableBackend(ABC):
|
|
@@ -15,10 +33,12 @@ class AbstractTableBackend(ABC):
|
|
|
15
33
|
self,
|
|
16
34
|
group_handler: ZarrGroupHandler,
|
|
17
35
|
index_key: str | None = None,
|
|
18
|
-
index_type: Literal["int", "str"] =
|
|
36
|
+
index_type: Literal["int", "str"] | None = None,
|
|
19
37
|
):
|
|
20
38
|
"""Initialize the handler.
|
|
21
39
|
|
|
40
|
+
This is a base class for the table backends protocol.
|
|
41
|
+
|
|
22
42
|
Args:
|
|
23
43
|
group_handler (ZarrGroupHandler): An object to handle the Zarr group
|
|
24
44
|
containing the table data.
|
|
@@ -32,40 +52,205 @@ class AbstractTableBackend(ABC):
|
|
|
32
52
|
@staticmethod
|
|
33
53
|
@abstractmethod
|
|
34
54
|
def backend_name() -> str:
|
|
35
|
-
"""
|
|
55
|
+
"""Return the name of the backend.
|
|
56
|
+
|
|
57
|
+
As a convention we set name as:
|
|
58
|
+
{backend_name}_v{version}
|
|
59
|
+
|
|
60
|
+
Where the version is a integer.
|
|
61
|
+
"""
|
|
36
62
|
raise NotImplementedError
|
|
37
63
|
|
|
38
64
|
@staticmethod
|
|
39
65
|
@abstractmethod
|
|
40
66
|
def implements_anndata() -> bool:
|
|
41
|
-
"""
|
|
67
|
+
"""Check if the backend implements the anndata protocol.
|
|
68
|
+
|
|
69
|
+
If this is True, the backend should implement the
|
|
70
|
+
`load_as_anndata` and `write_from_anndata` methods.
|
|
71
|
+
|
|
72
|
+
If this is False, these methods should raise a
|
|
73
|
+
`NotImplementedError`.
|
|
74
|
+
"""
|
|
42
75
|
raise NotImplementedError
|
|
43
76
|
|
|
44
77
|
@staticmethod
|
|
45
78
|
@abstractmethod
|
|
46
|
-
def
|
|
47
|
-
"""
|
|
79
|
+
def implements_pandas() -> bool:
|
|
80
|
+
"""Check if the backend implements the pandas protocol.
|
|
81
|
+
|
|
82
|
+
If this is True, the backend should implement the
|
|
83
|
+
`load_as_dataframe` and `write_from_dataframe` methods.
|
|
84
|
+
|
|
85
|
+
If this is False, these methods should raise a
|
|
86
|
+
`NotImplementedError`.
|
|
87
|
+
"""
|
|
48
88
|
raise NotImplementedError
|
|
49
89
|
|
|
90
|
+
@staticmethod
|
|
50
91
|
@abstractmethod
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
53
|
-
raise NotImplementedError
|
|
92
|
+
def implements_polars() -> bool:
|
|
93
|
+
"""Check if the backend implements the polars protocol.
|
|
54
94
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise NotImplementedError
|
|
95
|
+
If this is True, the backend should implement the
|
|
96
|
+
`load_as_polars` and `write_from_polars` methods.
|
|
58
97
|
|
|
59
|
-
|
|
60
|
-
|
|
98
|
+
If this is False, these methods should raise a
|
|
99
|
+
`NotImplementedError`.
|
|
100
|
+
"""
|
|
61
101
|
raise NotImplementedError
|
|
62
102
|
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
@property
|
|
104
|
+
def group_handler(self) -> ZarrGroupHandler:
|
|
105
|
+
"""Get the group handler."""
|
|
106
|
+
return self._group_handler
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def index_key(self) -> str | None:
|
|
110
|
+
"""Get the index key."""
|
|
111
|
+
return self._index_key
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def index_type(self) -> Literal["int", "str"] | None:
|
|
115
|
+
"""Get the index type."""
|
|
116
|
+
if self._index_type is None:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
if self._index_type not in ["int", "str"]:
|
|
120
|
+
raise NgioValueError(
|
|
121
|
+
f"Invalid index type {self._index_type}. Must be 'int' or 'str'."
|
|
122
|
+
)
|
|
123
|
+
return self._index_type # type: ignore[return-value]
|
|
124
|
+
|
|
125
|
+
def load_as_anndata(self) -> AnnData:
|
|
126
|
+
"""Load the table as an AnnData object.
|
|
127
|
+
|
|
128
|
+
Since the AnnData object is more complex than a DataFrame,
|
|
129
|
+
selecting columns is not implemented, because it is not
|
|
130
|
+
straightforward to do so for an arbitrary AnnData object.
|
|
131
|
+
"""
|
|
132
|
+
if self.implements_pandas():
|
|
133
|
+
return convert_pandas_to_anndata(
|
|
134
|
+
self.load_as_pandas_df(),
|
|
135
|
+
index_key=self.index_key,
|
|
136
|
+
)
|
|
137
|
+
elif self.implements_polars():
|
|
138
|
+
return convert_polars_to_anndata(
|
|
139
|
+
self.load_as_polars_lf(),
|
|
140
|
+
index_key=self.index_key,
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
raise NgioValueError(
|
|
144
|
+
"Backend does not implement any of the protocols. "
|
|
145
|
+
"A backend should implement at least one of the "
|
|
146
|
+
"following protocols: anndata, pandas, polars."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def load_as_pandas_df(self) -> DataFrame:
|
|
150
|
+
"""Load the table as a pandas DataFrame.
|
|
151
|
+
|
|
152
|
+
If columns are provided, the table should be filtered
|
|
153
|
+
"""
|
|
154
|
+
if self.implements_anndata():
|
|
155
|
+
return convert_anndata_to_pandas(
|
|
156
|
+
self.load_as_anndata(),
|
|
157
|
+
index_key=self.index_key,
|
|
158
|
+
index_type=self.index_type,
|
|
159
|
+
)
|
|
160
|
+
elif self.implements_polars():
|
|
161
|
+
return convert_polars_to_pandas(
|
|
162
|
+
self.load_as_polars_lf(),
|
|
163
|
+
index_key=self.index_key,
|
|
164
|
+
index_type=self.index_type,
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
raise NgioValueError(
|
|
168
|
+
"Backend does not implement any of the protocols. "
|
|
169
|
+
"A backend should implement at least one of the "
|
|
170
|
+
"following protocols: anndata, pandas, polars."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def load_as_polars_lf(self) -> LazyFrame:
|
|
174
|
+
"""Load the table as a polars LazyFrame.
|
|
175
|
+
|
|
176
|
+
If columns are provided, the table should be filtered
|
|
177
|
+
"""
|
|
178
|
+
if self.implements_anndata():
|
|
179
|
+
return convert_anndata_to_polars(
|
|
180
|
+
self.load_as_anndata(),
|
|
181
|
+
index_key=self.index_key,
|
|
182
|
+
index_type=self.index_type,
|
|
183
|
+
).lazy()
|
|
184
|
+
elif self.implements_pandas():
|
|
185
|
+
return convert_pandas_to_polars(
|
|
186
|
+
self.load_as_pandas_df(),
|
|
187
|
+
index_key=self.index_key,
|
|
188
|
+
index_type=self.index_type,
|
|
189
|
+
).lazy()
|
|
190
|
+
else:
|
|
191
|
+
raise NgioValueError(
|
|
192
|
+
"Backend does not implement any of the protocols. "
|
|
193
|
+
"A backend should implement at least one of the "
|
|
194
|
+
"following protocols: anndata, pandas, polars."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def write_from_pandas(self, table: DataFrame) -> None:
|
|
198
|
+
"""Serialize the table from a pandas DataFrame."""
|
|
199
|
+
raise NotImplementedError(
|
|
200
|
+
f"Backend {self.backend_name()} does not support "
|
|
201
|
+
"serialization of DataFrame objects."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def write_from_anndata(self, table: AnnData) -> None:
|
|
205
|
+
"""Serialize the table from an AnnData object."""
|
|
206
|
+
raise NotImplementedError(
|
|
207
|
+
f"Backend {self.backend_name()} does not support "
|
|
208
|
+
"serialization of AnnData objects."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def write_from_polars(self, table: PolarsDataFrame | LazyFrame) -> None:
|
|
212
|
+
"""Serialize the table from a polars DataFrame or LazyFrame."""
|
|
213
|
+
raise NotImplementedError(
|
|
214
|
+
f"Backend {self.backend_name()} does not support "
|
|
215
|
+
"serialization of Polars objects."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def write_metadata(self, metadata: dict | None = None) -> None:
|
|
219
|
+
"""Write the metadata to the store."""
|
|
220
|
+
if metadata is None:
|
|
221
|
+
metadata = {}
|
|
222
|
+
|
|
223
|
+
backend_metadata = BackendMeta(
|
|
224
|
+
backend=self.backend_name(),
|
|
225
|
+
index_key=self.index_key,
|
|
226
|
+
index_type=self.index_type,
|
|
227
|
+
).model_dump(exclude_none=True)
|
|
228
|
+
metadata.update(backend_metadata)
|
|
229
|
+
self._group_handler.write_attrs(metadata)
|
|
230
|
+
|
|
231
|
+
def write(
|
|
232
|
+
self,
|
|
233
|
+
table: DataFrame | AnnData | PolarsDataFrame | LazyFrame,
|
|
234
|
+
metadata: dict | None = None,
|
|
235
|
+
mode: Literal["pandas", "anndata", "polars"] | None = None,
|
|
65
236
|
) -> None:
|
|
66
|
-
"""
|
|
67
|
-
raise NotImplementedError
|
|
237
|
+
"""Serialize the table to the store, and write the metadata.
|
|
68
238
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
239
|
+
This is a generic write method.
|
|
240
|
+
Based on the explicit mode or the type of the table,
|
|
241
|
+
it will call the appropriate write method.
|
|
242
|
+
"""
|
|
243
|
+
if mode == "pandas" or isinstance(table, DataFrame):
|
|
244
|
+
self.write_from_pandas(table) # type: ignore[arg-type]
|
|
245
|
+
elif mode == "anndata" or isinstance(table, AnnData):
|
|
246
|
+
self.write_from_anndata(table) # type: ignore[arg-type]
|
|
247
|
+
elif mode == "polars" or isinstance(table, PolarsDataFrame | LazyFrame):
|
|
248
|
+
self.write_from_polars(table)
|
|
249
|
+
else:
|
|
250
|
+
raise NgioValueError(
|
|
251
|
+
f"Unsupported table type {type(table)}. "
|
|
252
|
+
"Please specify the mode explicitly. "
|
|
253
|
+
"Supported serialization modes are: "
|
|
254
|
+
"'pandas', 'anndata', 'polars'."
|
|
255
|
+
)
|
|
256
|
+
self.write_metadata(metadata)
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
import anndata as ad
|
|
6
|
-
import numpy as np
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import pandas.api.types as ptypes
|
|
9
5
|
import zarr
|
|
10
6
|
from anndata import AnnData
|
|
11
7
|
from anndata._io.specs import read_elem
|
|
@@ -14,9 +10,7 @@ from anndata._io.zarr import read_dataframe
|
|
|
14
10
|
from anndata.compat import _clean_uns
|
|
15
11
|
from anndata.experimental import read_dispatched
|
|
16
12
|
|
|
17
|
-
from ngio.tables._validators import validate_index_dtype, validate_index_key
|
|
18
13
|
from ngio.utils import (
|
|
19
|
-
NgioTableValidationError,
|
|
20
14
|
NgioValueError,
|
|
21
15
|
StoreOrGroup,
|
|
22
16
|
open_group_wrapper,
|
|
@@ -26,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
26
20
|
from collections.abc import Callable, Collection
|
|
27
21
|
|
|
28
22
|
|
|
29
|
-
def
|
|
23
|
+
def custom_anndata_read_zarr(
|
|
30
24
|
store: StoreOrGroup, elem_to_read: Collection[str] | None = None
|
|
31
25
|
) -> AnnData:
|
|
32
26
|
"""Read from a hierarchical Zarr array store.
|
|
@@ -39,7 +33,7 @@ def custom_read_zarr(
|
|
|
39
33
|
store (StoreOrGroup): A store or group to read the AnnData from.
|
|
40
34
|
elem_to_read (Collection[str] | None): The elements to read from the store.
|
|
41
35
|
"""
|
|
42
|
-
group
|
|
36
|
+
group = open_group_wrapper(store=store, mode="r")
|
|
43
37
|
|
|
44
38
|
if not isinstance(group.store, zarr.DirectoryStore):
|
|
45
39
|
elem_to_read = ["X", "obs", "var"]
|
|
@@ -96,103 +90,3 @@ def custom_read_zarr(
|
|
|
96
90
|
if not isinstance(adata, AnnData):
|
|
97
91
|
raise NgioValueError(f"Expected an AnnData object, but got {type(adata)}")
|
|
98
92
|
return adata
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _check_for_mixed_types(series: pd.Series) -> None:
|
|
102
|
-
"""Check if the column has mixed types."""
|
|
103
|
-
if series.apply(type).nunique() > 1: # type: ignore
|
|
104
|
-
raise NgioTableValidationError(
|
|
105
|
-
f"Column {series.name} has mixed types: "
|
|
106
|
-
f"{series.apply(type).unique()}. " # type: ignore
|
|
107
|
-
"Type of all elements must be the same."
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _check_for_supported_types(series: pd.Series) -> Literal["str", "int", "numeric"]:
|
|
112
|
-
"""Check if the column has supported types."""
|
|
113
|
-
if ptypes.is_string_dtype(series):
|
|
114
|
-
return "str"
|
|
115
|
-
if ptypes.is_integer_dtype(series):
|
|
116
|
-
return "int"
|
|
117
|
-
if ptypes.is_numeric_dtype(series):
|
|
118
|
-
return "numeric"
|
|
119
|
-
raise NgioTableValidationError(
|
|
120
|
-
f"Column {series.name} has unsupported type: {series.dtype}."
|
|
121
|
-
" Supported types are string and numerics."
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def dataframe_to_anndata(
|
|
126
|
-
dataframe: pd.DataFrame,
|
|
127
|
-
index_key: str | None = None,
|
|
128
|
-
overwrite: bool = False,
|
|
129
|
-
) -> ad.AnnData:
|
|
130
|
-
"""Convert a table DataFrame to an AnnData object.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
dataframe (pd.DataFrame): A pandas DataFrame representing a fractal table.
|
|
134
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
135
|
-
Default is None.
|
|
136
|
-
overwrite (bool): Whether to overwrite the index if a different index is found.
|
|
137
|
-
Default is False.
|
|
138
|
-
"""
|
|
139
|
-
# Check if the index_key is present in the data frame + optional validations
|
|
140
|
-
dataframe = validate_index_key(dataframe, index_key, overwrite=overwrite)
|
|
141
|
-
dataframe = validate_index_dtype(dataframe, index_type="str")
|
|
142
|
-
|
|
143
|
-
str_columns, int_columns, num_columns = [], [], []
|
|
144
|
-
for c_name in dataframe.columns:
|
|
145
|
-
column_df = dataframe[c_name]
|
|
146
|
-
_check_for_mixed_types(column_df) # Mixed types are not allowed in the table
|
|
147
|
-
c_type = _check_for_supported_types(
|
|
148
|
-
column_df
|
|
149
|
-
) # Only string and numeric types are allowed
|
|
150
|
-
|
|
151
|
-
if c_type == "str":
|
|
152
|
-
str_columns.append(c_name)
|
|
153
|
-
|
|
154
|
-
elif c_type == "int":
|
|
155
|
-
int_columns.append(c_name)
|
|
156
|
-
|
|
157
|
-
elif c_type == "numeric":
|
|
158
|
-
num_columns.append(c_name)
|
|
159
|
-
|
|
160
|
-
# Converting all observations to string
|
|
161
|
-
obs_df = dataframe[str_columns + int_columns]
|
|
162
|
-
obs_df.index = dataframe.index
|
|
163
|
-
|
|
164
|
-
x_df = dataframe[num_columns]
|
|
165
|
-
|
|
166
|
-
if x_df.dtypes.nunique() > 1:
|
|
167
|
-
x_df = x_df.astype("float64")
|
|
168
|
-
|
|
169
|
-
if x_df.empty:
|
|
170
|
-
# If there are no numeric columns, create an empty array
|
|
171
|
-
# to avoid AnnData failing to create the object
|
|
172
|
-
x_df = np.zeros((len(obs_df), 0), dtype="float64")
|
|
173
|
-
|
|
174
|
-
return ad.AnnData(X=x_df, obs=obs_df)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def anndata_to_dataframe(
|
|
178
|
-
anndata: ad.AnnData,
|
|
179
|
-
index_key: str | None = "label",
|
|
180
|
-
index_type: str = "int",
|
|
181
|
-
overwrite: bool = False,
|
|
182
|
-
) -> pd.DataFrame:
|
|
183
|
-
"""Convert a AnnData object representing a fractal table to a pandas DataFrame.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
anndata (ad.AnnData): An AnnData object representing a fractal table.
|
|
187
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
188
|
-
Default is 'label'.
|
|
189
|
-
index_type (str): The type of the index column in the DataFrame.
|
|
190
|
-
Either 'str' or 'int'. Default is 'int'.
|
|
191
|
-
overwrite (bool): Whether to overwrite the index if a different index is found.
|
|
192
|
-
Default is False.
|
|
193
|
-
"""
|
|
194
|
-
dataframe = anndata.to_df()
|
|
195
|
-
dataframe[anndata.obs_keys()] = anndata.obs
|
|
196
|
-
dataframe = validate_index_key(dataframe, index_key, overwrite=overwrite)
|
|
197
|
-
dataframe = validate_index_dtype(dataframe, index_type)
|
|
198
|
-
return dataframe
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from collections.abc import Collection
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
1
|
from anndata import AnnData
|
|
5
2
|
from pandas import DataFrame
|
|
3
|
+
from polars import DataFrame as PolarsDataFrame
|
|
4
|
+
from polars import LazyFrame
|
|
6
5
|
|
|
7
6
|
from ngio.tables.backends._abstract_backend import AbstractTableBackend
|
|
8
7
|
from ngio.tables.backends._anndata_utils import (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
custom_anndata_read_zarr,
|
|
9
|
+
)
|
|
10
|
+
from ngio.tables.backends._utils import (
|
|
11
|
+
convert_pandas_to_anndata,
|
|
12
|
+
convert_polars_to_anndata,
|
|
13
|
+
normalize_anndata,
|
|
12
14
|
)
|
|
13
15
|
from ngio.utils import NgioValueError
|
|
14
16
|
|
|
@@ -18,59 +20,54 @@ class AnnDataBackend(AbstractTableBackend):
|
|
|
18
20
|
|
|
19
21
|
@staticmethod
|
|
20
22
|
def backend_name() -> str:
|
|
21
|
-
"""
|
|
23
|
+
"""Return the name of the backend."""
|
|
22
24
|
return "anndata_v1"
|
|
23
25
|
|
|
24
26
|
@staticmethod
|
|
25
27
|
def implements_anndata() -> bool:
|
|
26
|
-
"""
|
|
28
|
+
"""Check if the backend implements the anndata protocol."""
|
|
27
29
|
return True
|
|
28
30
|
|
|
29
31
|
@staticmethod
|
|
30
|
-
def
|
|
32
|
+
def implements_pandas() -> bool:
|
|
31
33
|
"""Whether the handler implements the dataframe protocol."""
|
|
32
34
|
return True
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
@staticmethod
|
|
37
|
+
def implements_polars() -> bool:
|
|
38
|
+
"""Whether the handler implements the polars protocol."""
|
|
39
|
+
return True
|
|
37
40
|
|
|
38
|
-
def load_as_anndata(self
|
|
39
|
-
"""Load the
|
|
40
|
-
anndata =
|
|
41
|
-
|
|
42
|
-
raise NotImplementedError(
|
|
43
|
-
"Selecting columns is not implemented for AnnData."
|
|
44
|
-
)
|
|
41
|
+
def load_as_anndata(self) -> AnnData:
|
|
42
|
+
"""Load the table as an AnnData object."""
|
|
43
|
+
anndata = custom_anndata_read_zarr(self._group_handler._group)
|
|
44
|
+
anndata = normalize_anndata(anndata, index_key=self.index_key)
|
|
45
45
|
return anndata
|
|
46
46
|
|
|
47
|
-
def
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
index_key=self._index_key,
|
|
52
|
-
index_type=self._index_type,
|
|
53
|
-
)
|
|
54
|
-
if columns is not None:
|
|
55
|
-
dataframe = dataframe[columns]
|
|
56
|
-
return dataframe
|
|
57
|
-
|
|
58
|
-
def write_from_dataframe(
|
|
59
|
-
self, table: DataFrame, metadata: dict | None = None
|
|
60
|
-
) -> None:
|
|
61
|
-
"""Consolidate the metadata in the store."""
|
|
62
|
-
anndata = dataframe_to_anndata(table, index_key=self._index_key)
|
|
63
|
-
self.write_from_anndata(anndata, metadata)
|
|
64
|
-
|
|
65
|
-
def write_from_anndata(self, table: AnnData, metadata: dict | None = None) -> None:
|
|
66
|
-
"""Consolidate the metadata in the store."""
|
|
67
|
-
store = self._group_handler.store
|
|
68
|
-
if not isinstance(store, str | Path):
|
|
47
|
+
def write_from_anndata(self, table: AnnData) -> None:
|
|
48
|
+
"""Serialize the table from an AnnData object."""
|
|
49
|
+
full_url = self._group_handler.full_url
|
|
50
|
+
if full_url is None:
|
|
69
51
|
raise NgioValueError(
|
|
70
|
-
"
|
|
52
|
+
f"Ngio does not support writing file from a "
|
|
53
|
+
f"store of type {type(self._group_handler)}. "
|
|
54
|
+
"Please make sure to use a compatible "
|
|
55
|
+
"store like a zarr.DirectoryStore."
|
|
71
56
|
)
|
|
57
|
+
table.write_zarr(full_url) # type: ignore
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
table.
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
def write_from_pandas(self, table: DataFrame) -> None:
|
|
60
|
+
"""Serialize the table from a pandas DataFrame."""
|
|
61
|
+
anndata = convert_pandas_to_anndata(
|
|
62
|
+
table,
|
|
63
|
+
index_key=self.index_key,
|
|
64
|
+
)
|
|
65
|
+
self.write_from_anndata(anndata)
|
|
66
|
+
|
|
67
|
+
def write_from_polars(self, table: PolarsDataFrame | LazyFrame) -> None:
|
|
68
|
+
"""Consolidate the metadata in the store."""
|
|
69
|
+
anndata = convert_polars_to_anndata(
|
|
70
|
+
table,
|
|
71
|
+
index_key=self.index_key,
|
|
72
|
+
)
|
|
73
|
+
self.write_from_anndata(anndata)
|