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.
@@ -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
- from ngio.tables.v1 import FeatureTableV1, MaskingRoiTableV1, RoiTableV1
6
- from ngio.tables.v1._generic_table import GenericTable
7
- from ngio.tables.v1._roi_table import _GenericRoiTableV1
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
+ TableBackend,
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 = _GenericRoiTableV1
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 type() -> str | None:
41
+ def table_type() -> str:
27
42
  """Return the type of the table."""
28
43
  ...
29
44
 
30
45
  @staticmethod
31
- def version() -> str | None:
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: TableBackend = "anndata",
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 _from_handler(
42
- cls, handler: ZarrGroupHandler, backend_name: str | None = None
105
+ def from_handler(
106
+ cls,
107
+ handler: ZarrGroupHandler,
108
+ backend: TableBackend | None = None,
43
109
  ) -> "Table":
44
110
  """Create a new table from a Zarr group handler."""
45
111
  ...
46
112
 
47
- def _set_backend(
48
- self,
49
- handler: ZarrGroupHandler,
50
- backend_name: str | None = None,
51
- ) -> None:
52
- """Set the backend store and path for the table."""
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", "masking_roi_table", "feature_table", "generic_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
+ table_version: str = "1"
143
+ type: str = "generic_table"
64
144
 
65
- def _unique_table_name(type_name, version) -> str:
66
- """Return the unique name for a table."""
67
- return f"{type_name}_v{version}"
145
+ def unique_name(self) -> str:
146
+ """Return the unique name for the table."""
147
+ return f"{self.type}_v{self.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
- type: str,
90
- version: str,
176
+ meta: TableMeta,
91
177
  handler: ZarrGroupHandler,
92
- backend_name: str | None = None,
178
+ backend: TableBackend | 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
- _errors = {}
97
- for name, table_cls in self._implemented_tables.items():
98
- if name != _unique_table_name(type, version):
99
- continue
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
- if len(_errors) == 0:
122
- raise NgioValidationError(
123
- f"Could not find a table implementation for {type} v{version}. "
124
- f"Available tables: {self.available_implementations()}"
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
- raise NgioValidationError(
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
- table_type = handler.type()
134
- version = handler.version()
135
- if table_type is None:
136
- raise NgioValueError("Table handler must have a type.")
137
-
138
- if version is None:
139
- raise NgioValueError("Table handler must have a version.")
197
+ meta = TableMeta(
198
+ type=handler.table_type(),
199
+ table_version=handler.version(),
200
+ )
140
201
 
141
- table_unique_name = _unique_table_name(table_type, version)
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 {table_unique_name} already exists. "
204
+ f"Table handler for {meta.unique_name()} already implemented. "
145
205
  "Use overwrite=True to replace it."
146
206
  )
147
- self._implemented_tables[table_unique_name] = handler
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 = _get_table_type(tb_handler)
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, name: str, backend_name: str | None = None, strict: bool = True
265
+ self,
266
+ name: str,
267
+ backend: TableBackend | 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 KeyError(f"Table '{name}' not found in the group.")
272
+ raise NgioValueError(f"Table '{name}' not found in the group.")
222
273
 
223
274
  table_handler = self._get_table_group_handler(name)
224
- table_type = _get_table_type(table_handler)
225
- table_version = _get_table_version(table_handler)
275
+
276
+ meta = _get_meta(table_handler)
226
277
  return ImplementedTables().get_table(
227
- type=table_type,
228
- version=table_version,
278
+ meta=meta,
229
279
  handler=table_handler,
230
- backend_name=backend_name,
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: TableBackend | 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 | None = None,
304
+ backend: TableBackend = "anndata",
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._set_backend(
322
+ table.set_table_data()
323
+ table.set_backend(
257
324
  handler=table_handler,
258
- backend_name=backend,
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: TableBackend | 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
- _get_table_type(handler), _get_table_version(handler), handler
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: TableBackend | None = None,
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 | None = None,
396
+ backend: TableBackend = "anndata",
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._set_backend(
405
+ table.set_backend(
318
406
  handler=handler,
319
- backend_name=backend,
407
+ backend=backend,
320
408
  )
321
409
  table.consolidate()
@@ -1,7 +1,23 @@
1
1
  """Tables implementations for fractal_tables v1."""
2
2
 
3
- from ngio.tables.v1._feature_table import FeatureTableV1
3
+ from ngio.tables.v1._condition_table import ConditionTableMeta, ConditionTableV1
4
+ from ngio.tables.v1._feature_table import FeatureTableMeta, FeatureTableV1
4
5
  from ngio.tables.v1._generic_table import GenericTable
5
- from ngio.tables.v1._roi_table import MaskingRoiTableV1, RoiTableV1
6
+ from ngio.tables.v1._roi_table import (
7
+ MaskingRoiTableV1,
8
+ MaskingRoiTableV1Meta,
9
+ RoiTableV1,
10
+ RoiTableV1Meta,
11
+ )
6
12
 
7
- __all__ = ["FeatureTableV1", "GenericTable", "MaskingRoiTableV1", "RoiTableV1"]
13
+ __all__ = [
14
+ "ConditionTableMeta",
15
+ "ConditionTableV1",
16
+ "FeatureTableMeta",
17
+ "FeatureTableV1",
18
+ "GenericTable",
19
+ "MaskingRoiTableV1",
20
+ "MaskingRoiTableV1Meta",
21
+ "RoiTableV1",
22
+ "RoiTableV1Meta",
23
+ ]
@@ -0,0 +1,71 @@
1
+ """Implementation of a generic table class."""
2
+
3
+ from ngio.tables.abstract_table import AbstractBaseTable
4
+ from ngio.tables.backends import (
5
+ BackendMeta,
6
+ TableBackend,
7
+ TabularData,
8
+ )
9
+ from ngio.utils import ZarrGroupHandler
10
+
11
+
12
+ class ConditionTableMeta(BackendMeta):
13
+ """Metadata for the condition table."""
14
+
15
+ table_version: str | None = "1"
16
+ type: str | None = "condition_table"
17
+
18
+
19
+ class ConditionTableV1(AbstractBaseTable):
20
+ """Condition table class.
21
+
22
+ This class is used to load a condition table.
23
+ The condition table is a generic table that does not
24
+ have a specific definition.
25
+
26
+ It is used to store informations about the particular conditions
27
+ used to generate the data.
28
+ - How much drug was used in the experiment
29
+ - What treatment was used
30
+ - etc.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ table_data: TabularData | None = None,
36
+ *,
37
+ meta: ConditionTableMeta | None = None,
38
+ ) -> None:
39
+ """Initialize the ConditionTable."""
40
+ if meta is None:
41
+ meta = ConditionTableMeta()
42
+
43
+ super().__init__(
44
+ table_data=table_data,
45
+ meta=meta,
46
+ )
47
+
48
+ @staticmethod
49
+ def table_type() -> str:
50
+ """Return the type of the table."""
51
+ return "condition_table"
52
+
53
+ @staticmethod
54
+ def version() -> str:
55
+ """The generic table does not have a version.
56
+
57
+ Since does not follow a specific schema.
58
+ """
59
+ return "1"
60
+
61
+ @classmethod
62
+ def from_handler(
63
+ cls,
64
+ handler: ZarrGroupHandler,
65
+ backend: TableBackend | None = None,
66
+ ) -> "ConditionTableV1":
67
+ return cls._from_handler(
68
+ handler=handler,
69
+ backend=backend,
70
+ meta_model=ConditionTableMeta,
71
+ )