ngio 0.2.9__py3-none-any.whl → 0.3.0__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.
Files changed (38) hide show
  1. ngio/common/__init__.py +16 -0
  2. ngio/common/_array_pipe.py +50 -27
  3. ngio/common/_table_ops.py +471 -0
  4. ngio/hcs/__init__.py +1 -1
  5. ngio/hcs/{plate.py → _plate.py} +451 -78
  6. ngio/images/__init__.py +3 -3
  7. ngio/images/{image.py → _image.py} +26 -21
  8. ngio/images/{label.py → _label.py} +6 -4
  9. ngio/images/{masked_image.py → _masked_image.py} +2 -2
  10. ngio/images/{ome_zarr_container.py → _ome_zarr_container.py} +152 -86
  11. ngio/ome_zarr_meta/_meta_handlers.py +16 -8
  12. ngio/ome_zarr_meta/ngio_specs/_channels.py +41 -29
  13. ngio/tables/__init__.py +14 -2
  14. ngio/tables/_abstract_table.py +269 -0
  15. ngio/tables/{tables_container.py → _tables_container.py} +186 -100
  16. ngio/tables/backends/__init__.py +20 -0
  17. ngio/tables/backends/_abstract_backend.py +58 -80
  18. ngio/tables/backends/{_anndata_v1.py → _anndata.py} +5 -1
  19. ngio/tables/backends/_csv.py +35 -0
  20. ngio/tables/backends/{_json_v1.py → _json.py} +4 -1
  21. ngio/tables/backends/{_csv_v1.py → _non_zarr_backends.py} +61 -27
  22. ngio/tables/backends/_parquet.py +47 -0
  23. ngio/tables/backends/_table_backends.py +39 -18
  24. ngio/tables/backends/_utils.py +147 -1
  25. ngio/tables/v1/__init__.py +19 -3
  26. ngio/tables/v1/_condition_table.py +71 -0
  27. ngio/tables/v1/_feature_table.py +63 -129
  28. ngio/tables/v1/_generic_table.py +21 -159
  29. ngio/tables/v1/_roi_table.py +285 -201
  30. ngio/utils/_fractal_fsspec_store.py +29 -0
  31. {ngio-0.2.9.dist-info → ngio-0.3.0.dist-info}/METADATA +4 -3
  32. ngio-0.3.0.dist-info/RECORD +61 -0
  33. ngio/tables/_validators.py +0 -108
  34. ngio-0.2.9.dist-info/RECORD +0 -57
  35. /ngio/images/{abstract_image.py → _abstract_image.py} +0 -0
  36. /ngio/images/{create.py → _create.py} +0 -0
  37. {ngio-0.2.9.dist-info → ngio-0.3.0.dist-info}/WHEEL +0 -0
  38. {ngio-0.2.9.dist-info → ngio-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,37 @@ class Table(Protocol):
58
126
 
59
127
 
60
128
  TypedTable = Literal[
61
- "roi_table", "masking_roi_table", "feature_table", "generic_roi_table"
129
+ "generic_table",
130
+ "roi_table",
131
+ "masking_roi_table",
132
+ "feature_table",
133
+ "condition_table",
62
134
  ]
63
135
 
136
+ TypedRoiTable = Literal[
137
+ "roi_table",
138
+ "masking_roi_table",
139
+ ]
140
+
141
+ TableType = TypeVar("TableType", bound=Table)
142
+
143
+
144
+ class TableMeta(BackendMeta):
145
+ """Base class for table metadata."""
146
+
147
+ table_version: str = "1"
148
+ type: str = "generic_table"
64
149
 
65
- def _unique_table_name(type_name, version) -> str:
66
- """Return the unique name for a table."""
67
- return f"{type_name}_v{version}"
150
+ def unique_name(self) -> str:
151
+ """Return the unique name for the table."""
152
+ return f"{self.type}_v{self.table_version}"
153
+
154
+
155
+ def _get_meta(handler: ZarrGroupHandler) -> TableMeta:
156
+ """Get the metadata from the handler."""
157
+ attrs = handler.load_attrs()
158
+ meta = TableMeta(**attrs)
159
+ return meta
68
160
 
69
161
 
70
162
  class ImplementedTables:
@@ -86,77 +178,38 @@ class ImplementedTables:
86
178
 
87
179
  def get_table(
88
180
  self,
89
- type: str,
90
- version: str,
181
+ meta: TableMeta,
91
182
  handler: ZarrGroupHandler,
92
- backend_name: str | None = None,
183
+ backend: TableBackend | None = None,
93
184
  strict: bool = True,
94
185
  ) -> Table:
95
186
  """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
187
+ if strict:
188
+ default = None
189
+ else:
190
+ default = GenericTable
120
191
 
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()}"
192
+ table_cls = self._implemented_tables.get(meta.unique_name(), default)
193
+ if table_cls is None:
194
+ raise NgioValueError(
195
+ f"Table handler for {meta.unique_name()} not implemented."
125
196
  )
126
-
127
- raise NgioValidationError(
128
- f"Could not load table from any known version. Errors: {_errors}"
129
- )
197
+ table = table_cls.from_handler(handler=handler, backend=backend)
198
+ return table
130
199
 
131
200
  def add_implementation(self, handler: type[Table], overwrite: bool = False):
132
201
  """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.")
202
+ meta = TableMeta(
203
+ type=handler.table_type(),
204
+ table_version=handler.version(),
205
+ )
140
206
 
141
- table_unique_name = _unique_table_name(table_type, version)
142
- if table_unique_name in self._implemented_tables and not overwrite:
207
+ if meta.unique_name() in self._implemented_tables and not overwrite:
143
208
  raise NgioValueError(
144
- f"Table handler for {table_unique_name} already exists. "
209
+ f"Table handler for {meta.unique_name()} already implemented. "
145
210
  "Use overwrite=True to replace it."
146
211
  )
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")
212
+ self._implemented_tables[meta.unique_name()] = handler
160
213
 
161
214
 
162
215
  class TablesContainer:
@@ -192,14 +245,7 @@ class TablesContainer:
192
245
  handler = self._group_handler.derive_handler(path=name)
193
246
  return handler
194
247
 
195
- def list_roi_tables(self) -> list[str]:
196
- """List all ROI tables in the group."""
197
- _tables = []
198
- for _type in ["roi_table", "masking_roi_table"]:
199
- _tables.extend(self.list(_type))
200
- return _tables
201
-
202
- def list(self, filter_types: str | None = None) -> list[str]:
248
+ def list(self, filter_types: TypedTable | str | None = None) -> list[str]:
203
249
  """List all labels in the group."""
204
250
  tables = self._get_tables_list()
205
251
  if filter_types is None:
@@ -208,34 +254,52 @@ class TablesContainer:
208
254
  filtered_tables = []
209
255
  for table_name in tables:
210
256
  tb_handler = self._get_table_group_handler(table_name)
211
- table_type = _get_table_type(tb_handler)
257
+ table_type = _get_meta(tb_handler).type
212
258
  if table_type == filter_types:
213
259
  filtered_tables.append(table_name)
214
260
  return filtered_tables
215
261
 
216
262
  def get(
217
- self, name: str, backend_name: str | None = None, strict: bool = True
263
+ self,
264
+ name: str,
265
+ backend: TableBackend | None = None,
266
+ strict: bool = True,
218
267
  ) -> Table:
219
268
  """Get a label from the group."""
220
269
  if name not in self.list():
221
- raise KeyError(f"Table '{name}' not found in the group.")
270
+ raise NgioValueError(f"Table '{name}' not found in the group.")
222
271
 
223
272
  table_handler = self._get_table_group_handler(name)
224
- table_type = _get_table_type(table_handler)
225
- table_version = _get_table_version(table_handler)
273
+
274
+ meta = _get_meta(table_handler)
226
275
  return ImplementedTables().get_table(
227
- type=table_type,
228
- version=table_version,
276
+ meta=meta,
229
277
  handler=table_handler,
230
- backend_name=backend_name,
278
+ backend=backend,
231
279
  strict=strict,
232
280
  )
233
281
 
282
+ def get_as(
283
+ self,
284
+ name: str,
285
+ table_cls: type[TableType],
286
+ backend: TableBackend | None = None,
287
+ ) -> TableType:
288
+ """Get a table from the group as a specific type."""
289
+ if name not in self.list():
290
+ raise NgioValueError(f"Table '{name}' not found in the group.")
291
+
292
+ table_handler = self._get_table_group_handler(name)
293
+ return table_cls.from_handler(
294
+ handler=table_handler,
295
+ backend=backend,
296
+ ) # type: ignore[return-value]
297
+
234
298
  def add(
235
299
  self,
236
300
  name: str,
237
301
  table: Table,
238
- backend: str | None = None,
302
+ backend: TableBackend = "anndata",
239
303
  overwrite: bool = False,
240
304
  ) -> None:
241
305
  """Add a table to the group."""
@@ -253,9 +317,10 @@ class TablesContainer:
253
317
  if backend is None:
254
318
  backend = table.backend_name
255
319
 
256
- table._set_backend(
320
+ table.set_table_data()
321
+ table.set_backend(
257
322
  handler=table_handler,
258
- backend_name=backend,
323
+ backend=backend,
259
324
  )
260
325
  table.consolidate()
261
326
  if name not in existing_tables:
@@ -266,6 +331,7 @@ class TablesContainer:
266
331
  ImplementedTables().add_implementation(RoiTableV1)
267
332
  ImplementedTables().add_implementation(MaskingRoiTableV1)
268
333
  ImplementedTables().add_implementation(FeatureTableV1)
334
+ ImplementedTables().add_implementation(ConditionTableV1)
269
335
 
270
336
  ###################################################################################
271
337
  #
@@ -289,6 +355,7 @@ def open_tables_container(
289
355
 
290
356
  def open_table(
291
357
  store: StoreOrGroup,
358
+ backend: TableBackend | None = None,
292
359
  cache: bool = False,
293
360
  mode: AccessModeLiteral = "a",
294
361
  parallel_safe: bool = False,
@@ -297,15 +364,34 @@ def open_table(
297
364
  handler = ZarrGroupHandler(
298
365
  store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
299
366
  )
367
+ meta = _get_meta(handler)
300
368
  return ImplementedTables().get_table(
301
- _get_table_type(handler), _get_table_version(handler), handler
369
+ meta=meta, handler=handler, backend=backend, strict=False
302
370
  )
303
371
 
304
372
 
373
+ def open_table_as(
374
+ store: StoreOrGroup,
375
+ table_cls: type[TableType],
376
+ backend: TableBackend | None = None,
377
+ cache: bool = False,
378
+ mode: AccessModeLiteral = "a",
379
+ parallel_safe: bool = False,
380
+ ) -> TableType:
381
+ """Open a table from a Zarr store as a specific type."""
382
+ handler = ZarrGroupHandler(
383
+ store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
384
+ )
385
+ return table_cls.from_handler(
386
+ handler=handler,
387
+ backend=backend,
388
+ ) # type: ignore[return-value]
389
+
390
+
305
391
  def write_table(
306
392
  store: StoreOrGroup,
307
393
  table: Table,
308
- backend: str | None = None,
394
+ backend: TableBackend = "anndata",
309
395
  cache: bool = False,
310
396
  mode: AccessModeLiteral = "a",
311
397
  parallel_safe: bool = False,
@@ -314,8 +400,8 @@ def write_table(
314
400
  handler = ZarrGroupHandler(
315
401
  store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
316
402
  )
317
- table._set_backend(
403
+ table.set_backend(
318
404
  handler=handler,
319
- backend_name=backend,
405
+ backend=backend,
320
406
  )
321
407
  table.consolidate()
@@ -1,34 +1,54 @@
1
1
  """Ngio Tables backend implementations."""
2
2
 
3
3
  from ngio.tables.backends._abstract_backend import AbstractTableBackend, BackendMeta
4
+ from ngio.tables.backends._anndata import AnnDataBackend
5
+ from ngio.tables.backends._csv import CsvTableBackend
6
+ from ngio.tables.backends._json import JsonTableBackend
7
+ from ngio.tables.backends._parquet import ParquetTableBackend
4
8
  from ngio.tables.backends._table_backends import (
5
9
  ImplementedTableBackends,
10
+ TableBackend,
6
11
  TableBackendProtocol,
7
12
  )
8
13
  from ngio.tables.backends._utils import (
14
+ TabularData,
9
15
  convert_anndata_to_pandas,
10
16
  convert_anndata_to_polars,
11
17
  convert_pandas_to_anndata,
12
18
  convert_pandas_to_polars,
13
19
  convert_polars_to_anndata,
14
20
  convert_polars_to_pandas,
21
+ convert_to_anndata,
22
+ convert_to_pandas,
23
+ convert_to_polars,
15
24
  normalize_anndata,
16
25
  normalize_pandas_df,
17
26
  normalize_polars_lf,
27
+ normalize_table,
18
28
  )
19
29
 
20
30
  __all__ = [
21
31
  "AbstractTableBackend",
32
+ "AnnDataBackend",
22
33
  "BackendMeta",
34
+ "CsvTableBackend",
23
35
  "ImplementedTableBackends",
36
+ "JsonTableBackend",
37
+ "ParquetTableBackend",
38
+ "TableBackend",
24
39
  "TableBackendProtocol",
40
+ "TabularData",
25
41
  "convert_anndata_to_pandas",
26
42
  "convert_anndata_to_polars",
27
43
  "convert_pandas_to_anndata",
28
44
  "convert_pandas_to_polars",
29
45
  "convert_polars_to_anndata",
30
46
  "convert_polars_to_pandas",
47
+ "convert_to_anndata",
48
+ "convert_to_pandas",
49
+ "convert_to_polars",
31
50
  "normalize_anndata",
32
51
  "normalize_pandas_df",
33
52
  "normalize_polars_lf",
53
+ "normalize_table",
34
54
  ]