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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Abstract class for handling OME-NGFF images."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from collections.abc import Collection
|
|
4
|
-
from typing import Literal, overload
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
@@ -22,12 +22,15 @@ from ngio.ome_zarr_meta.ngio_specs import (
|
|
|
22
22
|
TimeUnits,
|
|
23
23
|
)
|
|
24
24
|
from ngio.tables import (
|
|
25
|
+
ConditionTable,
|
|
25
26
|
FeatureTable,
|
|
26
27
|
GenericRoiTable,
|
|
27
28
|
MaskingRoiTable,
|
|
28
29
|
RoiTable,
|
|
29
30
|
Table,
|
|
31
|
+
TableBackendProtocol,
|
|
30
32
|
TablesContainer,
|
|
33
|
+
TableType,
|
|
31
34
|
TypedTable,
|
|
32
35
|
)
|
|
33
36
|
from ngio.utils import (
|
|
@@ -264,9 +267,7 @@ class OmeZarrContainer:
|
|
|
264
267
|
if masking_table_name is None:
|
|
265
268
|
masking_table = masking_label.build_masking_roi_table()
|
|
266
269
|
else:
|
|
267
|
-
masking_table = self.
|
|
268
|
-
masking_table_name, check_type="masking_roi_table"
|
|
269
|
-
)
|
|
270
|
+
masking_table = self.get_masking_roi_table(name=masking_table_name)
|
|
270
271
|
|
|
271
272
|
return MaskedImage(
|
|
272
273
|
group_handler=image._group_handler,
|
|
@@ -345,84 +346,116 @@ class OmeZarrContainer:
|
|
|
345
346
|
)
|
|
346
347
|
return new_ome_zarr
|
|
347
348
|
|
|
348
|
-
def list_tables(self) -> list[str]:
|
|
349
|
+
def list_tables(self, filter_types: str | None = None) -> list[str]:
|
|
349
350
|
"""List all tables in the image."""
|
|
350
|
-
return self.tables_container.list()
|
|
351
|
+
return self.tables_container.list(filter_types=filter_types)
|
|
351
352
|
|
|
352
353
|
def list_roi_tables(self) -> list[str]:
|
|
353
354
|
"""List all ROI tables in the image."""
|
|
354
355
|
return self.tables_container.list_roi_tables()
|
|
355
356
|
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
def get_roi_table(self, name: str) -> RoiTable:
|
|
358
|
+
"""Get a ROI table from the image.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
name (str): The name of the table.
|
|
362
|
+
"""
|
|
363
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
364
|
+
if not isinstance(table, RoiTable):
|
|
365
|
+
raise NgioValueError(f"Table {name} is not a ROI table. Got {type(table)}")
|
|
366
|
+
return table
|
|
367
|
+
|
|
368
|
+
def get_masking_roi_table(self, name: str) -> MaskingRoiTable:
|
|
369
|
+
"""Get a masking ROI table from the image.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
name (str): The name of the table.
|
|
373
|
+
"""
|
|
374
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
375
|
+
if not isinstance(table, MaskingRoiTable):
|
|
376
|
+
raise NgioValueError(
|
|
377
|
+
f"Table {name} is not a masking ROI table. Got {type(table)}"
|
|
378
|
+
)
|
|
379
|
+
return table
|
|
380
|
+
|
|
381
|
+
def get_feature_table(self, name: str) -> FeatureTable:
|
|
382
|
+
"""Get a feature table from the image.
|
|
358
383
|
|
|
359
|
-
|
|
360
|
-
|
|
384
|
+
Args:
|
|
385
|
+
name (str): The name of the table.
|
|
386
|
+
"""
|
|
387
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
388
|
+
if not isinstance(table, FeatureTable):
|
|
389
|
+
raise NgioValueError(
|
|
390
|
+
f"Table {name} is not a feature table. Got {type(table)}"
|
|
391
|
+
)
|
|
392
|
+
return table
|
|
361
393
|
|
|
362
|
-
|
|
363
|
-
|
|
394
|
+
def get_generic_roi_table(self, name: str) -> GenericRoiTable:
|
|
395
|
+
"""Get a generic ROI table from the image.
|
|
364
396
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
397
|
+
Args:
|
|
398
|
+
name (str): The name of the table.
|
|
399
|
+
"""
|
|
400
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
401
|
+
if not isinstance(table, GenericRoiTable):
|
|
402
|
+
raise NgioValueError(
|
|
403
|
+
f"Table {name} is not a generic ROI table. Got {type(table)}"
|
|
404
|
+
)
|
|
405
|
+
return table
|
|
369
406
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
self, name: str, check_type: Literal["feature_table"]
|
|
373
|
-
) -> FeatureTable: ...
|
|
407
|
+
def get_condition_table(self, name: str) -> ConditionTable:
|
|
408
|
+
"""Get a condition table from the image.
|
|
374
409
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
410
|
+
Args:
|
|
411
|
+
name (str): The name of the table.
|
|
412
|
+
"""
|
|
413
|
+
table = self.tables_container.get(name=name, strict=True)
|
|
414
|
+
if not isinstance(table, ConditionTable):
|
|
415
|
+
raise NgioValueError(
|
|
416
|
+
f"Table {name} is not a condition table. Got {type(table)}"
|
|
417
|
+
)
|
|
418
|
+
return table
|
|
379
419
|
|
|
380
420
|
def get_table(self, name: str, check_type: TypedTable | None = None) -> Table:
|
|
381
421
|
"""Get a table from the image.
|
|
382
422
|
|
|
383
423
|
Args:
|
|
384
424
|
name (str): The name of the table.
|
|
385
|
-
check_type (TypedTable | None):
|
|
386
|
-
|
|
387
|
-
|
|
425
|
+
check_type (TypedTable | None): Deprecated. Please use
|
|
426
|
+
'get_table_as' instead, or one of the type specific
|
|
427
|
+
get_*table() methods.
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
if check_type is not None:
|
|
431
|
+
warnings.warn(
|
|
432
|
+
"The 'check_type' argument is deprecated, and will be removed in "
|
|
433
|
+
"ngio=0.3. Use 'get_table_as' instead or one of the "
|
|
434
|
+
"type specific get_*table() methods.",
|
|
435
|
+
DeprecationWarning,
|
|
436
|
+
stacklevel=2,
|
|
437
|
+
)
|
|
438
|
+
return self.tables_container.get(name=name, strict=False)
|
|
439
|
+
|
|
440
|
+
def get_table_as(
|
|
441
|
+
self,
|
|
442
|
+
name: str,
|
|
443
|
+
table_cls: type[TableType],
|
|
444
|
+
backend: str | TableBackendProtocol | None = None,
|
|
445
|
+
) -> TableType:
|
|
446
|
+
"""Get a table from the image as a specific type.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
name (str): The name of the table.
|
|
450
|
+
table_cls (type[TableType]): The type of the table.
|
|
451
|
+
backend (str | TableBackendProtocol | None): The backend to use. If None,
|
|
452
|
+
the default backend is used.
|
|
388
453
|
"""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
match check_type:
|
|
395
|
-
case "roi_table":
|
|
396
|
-
if not isinstance(table, RoiTable):
|
|
397
|
-
raise NgioValueError(
|
|
398
|
-
f"Table '{name}' is not a ROI table. Found type: {table.type()}"
|
|
399
|
-
)
|
|
400
|
-
return table
|
|
401
|
-
case "masking_roi_table":
|
|
402
|
-
if not isinstance(table, MaskingRoiTable):
|
|
403
|
-
raise NgioValueError(
|
|
404
|
-
f"Table '{name}' is not a masking ROI table. "
|
|
405
|
-
f"Found type: {table.type()}"
|
|
406
|
-
)
|
|
407
|
-
return table
|
|
408
|
-
|
|
409
|
-
case "generic_roi_table":
|
|
410
|
-
if not isinstance(table, GenericRoiTable):
|
|
411
|
-
raise NgioValueError(
|
|
412
|
-
f"Table '{name}' is not a generic ROI table. "
|
|
413
|
-
f"Found type: {table.type()}"
|
|
414
|
-
)
|
|
415
|
-
return table
|
|
416
|
-
|
|
417
|
-
case "feature_table":
|
|
418
|
-
if not isinstance(table, FeatureTable):
|
|
419
|
-
raise NgioValueError(
|
|
420
|
-
f"Table '{name}' is not a feature table. "
|
|
421
|
-
f"Found type: {table.type()}"
|
|
422
|
-
)
|
|
423
|
-
return table
|
|
424
|
-
case _:
|
|
425
|
-
raise NgioValueError(f"Unknown check_type: {check_type}")
|
|
454
|
+
return self.tables_container.get_as(
|
|
455
|
+
name=name,
|
|
456
|
+
table_cls=table_cls,
|
|
457
|
+
backend=backend,
|
|
458
|
+
)
|
|
426
459
|
|
|
427
460
|
def build_image_roi_table(self, name: str = "image") -> RoiTable:
|
|
428
461
|
"""Compute the ROI table for an image."""
|
|
@@ -436,7 +469,7 @@ class OmeZarrContainer:
|
|
|
436
469
|
self,
|
|
437
470
|
name: str,
|
|
438
471
|
table: Table,
|
|
439
|
-
backend: str |
|
|
472
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
440
473
|
overwrite: bool = False,
|
|
441
474
|
) -> None:
|
|
442
475
|
"""Add a table to the image."""
|
|
@@ -499,9 +532,7 @@ class OmeZarrContainer:
|
|
|
499
532
|
if masking_table_name is None:
|
|
500
533
|
masking_table = masking_label.build_masking_roi_table()
|
|
501
534
|
else:
|
|
502
|
-
masking_table = self.
|
|
503
|
-
masking_table_name, check_type="masking_roi_table"
|
|
504
|
-
)
|
|
535
|
+
masking_table = self.get_masking_roi_table(name=masking_table_name)
|
|
505
536
|
|
|
506
537
|
return MaskedLabel(
|
|
507
538
|
group_handler=label._group_handler,
|
|
@@ -9,7 +9,7 @@ from enum import Enum
|
|
|
9
9
|
from typing import Any, TypeVar
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
|
-
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
13
13
|
|
|
14
14
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
15
15
|
|
|
@@ -124,7 +124,6 @@ class ChannelVisualisation(BaseModel):
|
|
|
124
124
|
model_config = ConfigDict(extra="allow", frozen=True)
|
|
125
125
|
|
|
126
126
|
@field_validator("color", mode="after")
|
|
127
|
-
@classmethod
|
|
128
127
|
def validate_color(cls, value: str | NgioColors) -> str:
|
|
129
128
|
"""Color validator.
|
|
130
129
|
|
|
@@ -145,6 +144,33 @@ class ChannelVisualisation(BaseModel):
|
|
|
145
144
|
else:
|
|
146
145
|
raise NgioValueError(f"Invalid color {value}.")
|
|
147
146
|
|
|
147
|
+
@model_validator(mode="before")
|
|
148
|
+
def check_start_end(cls, data):
|
|
149
|
+
"""Check that the start and end values are valid.
|
|
150
|
+
|
|
151
|
+
If the start and end values are equal, set the end value to start + 1
|
|
152
|
+
"""
|
|
153
|
+
start = data.get("start", None)
|
|
154
|
+
end = data.get("end", None)
|
|
155
|
+
if start is None or end is None:
|
|
156
|
+
return data
|
|
157
|
+
if abs(end - start) < 1e-6:
|
|
158
|
+
data["end"] = start + 1
|
|
159
|
+
return data
|
|
160
|
+
|
|
161
|
+
@model_validator(mode="after")
|
|
162
|
+
def check_model(self) -> "ChannelVisualisation":
|
|
163
|
+
"""Check that the start and end values are within the min and max values."""
|
|
164
|
+
if self.start < self.min or self.start > self.max:
|
|
165
|
+
raise NgioValidationError(
|
|
166
|
+
f"Start value {self.start} is out of range [{self.min}, {self.max}]"
|
|
167
|
+
)
|
|
168
|
+
if self.end < self.min or self.end > self.max:
|
|
169
|
+
raise NgioValidationError(
|
|
170
|
+
f"End value {self.end} is out of range [{self.min}, {self.max}]"
|
|
171
|
+
)
|
|
172
|
+
return self
|
|
173
|
+
|
|
148
174
|
@classmethod
|
|
149
175
|
def default_init(
|
|
150
176
|
cls,
|
ngio/tables/__init__.py
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
"""Ngio Tables implementations."""
|
|
2
2
|
|
|
3
|
-
from ngio.tables.backends import ImplementedTableBackends
|
|
3
|
+
from ngio.tables.backends import ImplementedTableBackends, TableBackendProtocol
|
|
4
4
|
from ngio.tables.tables_container import (
|
|
5
|
+
ConditionTable,
|
|
5
6
|
FeatureTable,
|
|
6
7
|
GenericRoiTable,
|
|
7
8
|
MaskingRoiTable,
|
|
8
9
|
RoiTable,
|
|
9
10
|
Table,
|
|
10
11
|
TablesContainer,
|
|
12
|
+
TableType,
|
|
11
13
|
TypedTable,
|
|
12
14
|
open_table,
|
|
15
|
+
open_table_as,
|
|
13
16
|
open_tables_container,
|
|
14
17
|
)
|
|
15
18
|
from ngio.tables.v1._generic_table import GenericTable
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
21
|
+
"ConditionTable",
|
|
18
22
|
"FeatureTable",
|
|
19
23
|
"GenericRoiTable",
|
|
20
24
|
"GenericTable",
|
|
@@ -22,8 +26,11 @@ __all__ = [
|
|
|
22
26
|
"MaskingRoiTable",
|
|
23
27
|
"RoiTable",
|
|
24
28
|
"Table",
|
|
29
|
+
"TableBackendProtocol",
|
|
30
|
+
"TableType",
|
|
25
31
|
"TablesContainer",
|
|
26
32
|
"TypedTable",
|
|
27
33
|
"open_table",
|
|
34
|
+
"open_table_as",
|
|
28
35
|
"open_tables_container",
|
|
29
36
|
]
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Implementation of a generic table class."""
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Literal, Self
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import polars as pl
|
|
9
|
+
from anndata import AnnData
|
|
10
|
+
|
|
11
|
+
from ngio.tables.backends import (
|
|
12
|
+
BackendMeta,
|
|
13
|
+
ImplementedTableBackends,
|
|
14
|
+
TableBackendProtocol,
|
|
15
|
+
TabularData,
|
|
16
|
+
convert_to_anndata,
|
|
17
|
+
convert_to_pandas,
|
|
18
|
+
convert_to_polars,
|
|
19
|
+
normalize_table,
|
|
20
|
+
)
|
|
21
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AbstractBaseTable(ABC):
|
|
25
|
+
"""Abstract base class for a table.
|
|
26
|
+
|
|
27
|
+
This is used to define common methods and properties
|
|
28
|
+
for all tables.
|
|
29
|
+
|
|
30
|
+
This class is not meant to be used directly.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
table_data: TabularData | None = None,
|
|
36
|
+
*,
|
|
37
|
+
meta: BackendMeta | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Initialize the table."""
|
|
40
|
+
if meta is None:
|
|
41
|
+
meta = BackendMeta()
|
|
42
|
+
|
|
43
|
+
self._meta = meta
|
|
44
|
+
if table_data is not None:
|
|
45
|
+
table_data = normalize_table(
|
|
46
|
+
table_data,
|
|
47
|
+
index_key=meta.index_key,
|
|
48
|
+
index_type=meta.index_type,
|
|
49
|
+
)
|
|
50
|
+
self._table_data = table_data
|
|
51
|
+
self._table_backend = None
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
"""Return a string representation of the table."""
|
|
55
|
+
return f"{self.__class__.__name__}"
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def table_type() -> str:
|
|
60
|
+
"""Return the type of the table."""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def version() -> str:
|
|
66
|
+
"""The generic table does not have a version.
|
|
67
|
+
|
|
68
|
+
Since does not follow a specific schema.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def backend_name(self) -> str | None:
|
|
74
|
+
"""Return the name of the backend."""
|
|
75
|
+
if self._table_backend is None:
|
|
76
|
+
return None
|
|
77
|
+
return self._table_backend.backend_name()
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def meta(self) -> BackendMeta:
|
|
81
|
+
"""Return the metadata of the table."""
|
|
82
|
+
return self._meta
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def index_key(self) -> str | None:
|
|
86
|
+
"""Get the index key."""
|
|
87
|
+
return self._meta.index_key
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def index_type(self) -> Literal["int", "str"] | None:
|
|
91
|
+
"""Get the index type."""
|
|
92
|
+
return self._meta.index_type
|
|
93
|
+
|
|
94
|
+
def load_as_anndata(self) -> AnnData:
|
|
95
|
+
"""Load the table as an AnnData object."""
|
|
96
|
+
if self._table_backend is None:
|
|
97
|
+
raise NgioValueError("No backend set for the table.")
|
|
98
|
+
return self._table_backend.load_as_anndata()
|
|
99
|
+
|
|
100
|
+
def load_as_pandas_df(self) -> pd.DataFrame:
|
|
101
|
+
"""Load the table as a pandas DataFrame."""
|
|
102
|
+
if self._table_backend is None:
|
|
103
|
+
raise NgioValueError("No backend set for the table.")
|
|
104
|
+
return self._table_backend.load_as_pandas_df()
|
|
105
|
+
|
|
106
|
+
def load_as_polars_lf(self) -> pl.LazyFrame:
|
|
107
|
+
"""Load the table as a polars LazyFrame."""
|
|
108
|
+
if self._table_backend is None:
|
|
109
|
+
raise NgioValueError("No backend set for the table.")
|
|
110
|
+
return self._table_backend.load_as_polars_lf()
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def table_data(self) -> TabularData:
|
|
114
|
+
"""Return the table."""
|
|
115
|
+
if self._table_data is not None:
|
|
116
|
+
return self._table_data
|
|
117
|
+
|
|
118
|
+
if self._table_backend is None:
|
|
119
|
+
raise NgioValueError(
|
|
120
|
+
"The table does not have a DataFrame in memory nor a backend."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
self._table_data = self._table_backend.load()
|
|
124
|
+
return self._table_data
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def dataframe(self) -> pd.DataFrame:
|
|
128
|
+
"""Return the table as a DataFrame."""
|
|
129
|
+
return convert_to_pandas(
|
|
130
|
+
self.table_data, index_key=self.index_key, index_type=self.index_type
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def lazy_frame(self) -> pl.LazyFrame:
|
|
135
|
+
"""Return the table as a LazyFrame."""
|
|
136
|
+
return convert_to_polars(
|
|
137
|
+
self.table_data, index_key=self.index_key, index_type=self.index_type
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def anndata(self) -> AnnData:
|
|
142
|
+
"""Return the table as an AnnData object."""
|
|
143
|
+
return convert_to_anndata(self.table_data, index_key=self.index_key)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _load_backend(
|
|
147
|
+
meta: BackendMeta,
|
|
148
|
+
handler: ZarrGroupHandler,
|
|
149
|
+
backend: str | TableBackendProtocol,
|
|
150
|
+
) -> TableBackendProtocol:
|
|
151
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
152
|
+
if isinstance(backend, str):
|
|
153
|
+
return ImplementedTableBackends().get_backend(
|
|
154
|
+
backend_name=backend,
|
|
155
|
+
group_handler=handler,
|
|
156
|
+
index_key=meta.index_key,
|
|
157
|
+
index_type=meta.index_type,
|
|
158
|
+
)
|
|
159
|
+
backend.set_group_handler(
|
|
160
|
+
group_handler=handler,
|
|
161
|
+
index_key=meta.index_key,
|
|
162
|
+
index_type=meta.index_type,
|
|
163
|
+
)
|
|
164
|
+
return backend
|
|
165
|
+
|
|
166
|
+
def set_table_data(
|
|
167
|
+
self,
|
|
168
|
+
table_data: TabularData | None = None,
|
|
169
|
+
refresh: bool = False,
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Set the table.
|
|
172
|
+
|
|
173
|
+
If an object is passed, it will be used as the table.
|
|
174
|
+
If None is passed, the table will be loaded from the backend.
|
|
175
|
+
|
|
176
|
+
If refresh is True, the table will be reloaded from the backend.
|
|
177
|
+
If table is not None, this will be ignored.
|
|
178
|
+
"""
|
|
179
|
+
if table_data is not None:
|
|
180
|
+
if not isinstance(table_data, TabularData):
|
|
181
|
+
raise NgioValueError(
|
|
182
|
+
"The table must be a pandas DataFrame, polars LazyFrame, "
|
|
183
|
+
" or AnnData object."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self._table_data = normalize_table(
|
|
187
|
+
table_data,
|
|
188
|
+
index_key=self.index_key,
|
|
189
|
+
index_type=self.index_type,
|
|
190
|
+
)
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
if self._table_data is not None and not refresh:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
if self._table_backend is None:
|
|
197
|
+
raise NgioValueError(
|
|
198
|
+
"The table does not have a DataFrame in memory nor a backend."
|
|
199
|
+
)
|
|
200
|
+
self._table_data = self._table_backend.load()
|
|
201
|
+
|
|
202
|
+
def set_backend(
|
|
203
|
+
self,
|
|
204
|
+
handler: ZarrGroupHandler | None = None,
|
|
205
|
+
backend: str | TableBackendProtocol = "anndata_v1",
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Set the backend of the table."""
|
|
208
|
+
if handler is None:
|
|
209
|
+
if self._table_backend is None:
|
|
210
|
+
raise NgioValueError(
|
|
211
|
+
"No backend set for the table yet. "
|
|
212
|
+
"A ZarrGroupHandler must be provided."
|
|
213
|
+
)
|
|
214
|
+
handler = self._table_backend.group_handler
|
|
215
|
+
|
|
216
|
+
meta = self._meta
|
|
217
|
+
_backend = self._load_backend(
|
|
218
|
+
meta=meta,
|
|
219
|
+
handler=handler,
|
|
220
|
+
backend=backend,
|
|
221
|
+
)
|
|
222
|
+
self._table_backend = _backend
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def _from_handler(
|
|
226
|
+
cls,
|
|
227
|
+
handler: ZarrGroupHandler,
|
|
228
|
+
meta_model: builtins.type[BackendMeta],
|
|
229
|
+
backend: str | TableBackendProtocol | None = None,
|
|
230
|
+
) -> Self:
|
|
231
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
232
|
+
meta = meta_model(**handler.load_attrs())
|
|
233
|
+
table = cls(meta=meta)
|
|
234
|
+
if backend is None:
|
|
235
|
+
backend = meta.backend
|
|
236
|
+
table.set_backend(handler=handler, backend=backend)
|
|
237
|
+
return table
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
@abstractmethod
|
|
241
|
+
def from_handler(
|
|
242
|
+
cls,
|
|
243
|
+
handler: ZarrGroupHandler,
|
|
244
|
+
backend: str | TableBackendProtocol | None = None,
|
|
245
|
+
) -> Self:
|
|
246
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def from_table_data(cls, table_data: TabularData, meta: BackendMeta) -> Self:
|
|
251
|
+
"""Create a new ROI table from a Zarr group handler."""
|
|
252
|
+
return cls(
|
|
253
|
+
table_data=table_data,
|
|
254
|
+
meta=meta,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def consolidate(self) -> None:
|
|
258
|
+
"""Write the current state of the table to the Zarr file."""
|
|
259
|
+
if self._table_backend is None:
|
|
260
|
+
raise NgioValueError(
|
|
261
|
+
"No backend set for the table. "
|
|
262
|
+
"Please add the table to a OME-Zarr Image before calling consolidate."
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
self._table_backend.write(
|
|
266
|
+
self.table_data,
|
|
267
|
+
metadata=self._meta.model_dump(exclude_none=True),
|
|
268
|
+
)
|
ngio/tables/backends/__init__.py
CHANGED
|
@@ -1,34 +1,52 @@
|
|
|
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_v1 import AnnDataBackend
|
|
5
|
+
from ngio.tables.backends._csv_v1 import CsvTableBackend
|
|
6
|
+
from ngio.tables.backends._json_v1 import JsonTableBackend
|
|
7
|
+
from ngio.tables.backends._parquet_v1 import ParquetTableBackend
|
|
4
8
|
from ngio.tables.backends._table_backends import (
|
|
5
9
|
ImplementedTableBackends,
|
|
6
10
|
TableBackendProtocol,
|
|
7
11
|
)
|
|
8
12
|
from ngio.tables.backends._utils import (
|
|
13
|
+
TabularData,
|
|
9
14
|
convert_anndata_to_pandas,
|
|
10
15
|
convert_anndata_to_polars,
|
|
11
16
|
convert_pandas_to_anndata,
|
|
12
17
|
convert_pandas_to_polars,
|
|
13
18
|
convert_polars_to_anndata,
|
|
14
19
|
convert_polars_to_pandas,
|
|
20
|
+
convert_to_anndata,
|
|
21
|
+
convert_to_pandas,
|
|
22
|
+
convert_to_polars,
|
|
15
23
|
normalize_anndata,
|
|
16
24
|
normalize_pandas_df,
|
|
17
25
|
normalize_polars_lf,
|
|
26
|
+
normalize_table,
|
|
18
27
|
)
|
|
19
28
|
|
|
20
29
|
__all__ = [
|
|
21
30
|
"AbstractTableBackend",
|
|
31
|
+
"AnnDataBackend",
|
|
22
32
|
"BackendMeta",
|
|
33
|
+
"CsvTableBackend",
|
|
23
34
|
"ImplementedTableBackends",
|
|
35
|
+
"JsonTableBackend",
|
|
36
|
+
"ParquetTableBackend",
|
|
24
37
|
"TableBackendProtocol",
|
|
38
|
+
"TabularData",
|
|
25
39
|
"convert_anndata_to_pandas",
|
|
26
40
|
"convert_anndata_to_polars",
|
|
27
41
|
"convert_pandas_to_anndata",
|
|
28
42
|
"convert_pandas_to_polars",
|
|
29
43
|
"convert_polars_to_anndata",
|
|
30
44
|
"convert_polars_to_pandas",
|
|
45
|
+
"convert_to_anndata",
|
|
46
|
+
"convert_to_pandas",
|
|
47
|
+
"convert_to_polars",
|
|
31
48
|
"normalize_anndata",
|
|
32
49
|
"normalize_pandas_df",
|
|
33
50
|
"normalize_polars_lf",
|
|
51
|
+
"normalize_table",
|
|
34
52
|
]
|