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
@@ -9,7 +9,6 @@ These functions are used to validate and normalize the tables
9
9
  to ensure that conversion between formats is consistent.
10
10
  """
11
11
 
12
- # %%
13
12
  from copy import deepcopy
14
13
  from typing import Literal
15
14
 
@@ -24,6 +23,8 @@ from polars import LazyFrame
24
23
 
25
24
  from ngio.utils import NgioTableValidationError, NgioValueError
26
25
 
26
+ TabularData = AnnData | DataFrame | PolarsDataFrame | LazyFrame
27
+
27
28
  # -----------------
28
29
  # Validation utils
29
30
  # -----------------
@@ -460,3 +461,148 @@ def convert_polars_to_anndata(
460
461
  pandas_df,
461
462
  index_key=index_key,
462
463
  )
464
+
465
+
466
+ # -----------------
467
+ # Conversion functions
468
+ # -----------------
469
+
470
+
471
+ def normalize_table(
472
+ table_data: TabularData,
473
+ index_key: str | None = None,
474
+ index_type: Literal["int", "str"] | None = None,
475
+ ) -> TabularData:
476
+ """Normalize a table to a specific format.
477
+
478
+ Args:
479
+ table_data (TabularData): The table to normalize.
480
+ index_key (str | None): The column name to use as the index of the DataFrame.
481
+ Default is None.
482
+ index_type (str | None): The type of the index column in the DataFrame.
483
+ Either 'str' or 'int'. Default is None.
484
+
485
+ Returns:
486
+ DataFrame | AnnData | PolarsDataFrame | LazyFrame: Normalized table.
487
+ """
488
+ if isinstance(table_data, DataFrame):
489
+ return normalize_pandas_df(
490
+ table_data,
491
+ index_key=index_key,
492
+ index_type=index_type,
493
+ reset_index=False,
494
+ )
495
+ if isinstance(table_data, AnnData):
496
+ return normalize_anndata(table_data, index_key=index_key)
497
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
498
+ return normalize_polars_lf(
499
+ table_data,
500
+ index_key=index_key,
501
+ index_type=index_type,
502
+ )
503
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
504
+
505
+
506
+ def convert_to_anndata(
507
+ table_data: TabularData,
508
+ index_key: str | None = None,
509
+ ) -> AnnData:
510
+ """Convert a table to an AnnData object.
511
+
512
+ Args:
513
+ table_data (TabularData): The table to convert.
514
+ index_key (str | None): The column name to use as the index of the DataFrame.
515
+ Default is None.
516
+
517
+ Returns:
518
+ AnnData: Converted AnnData object.
519
+ """
520
+ if isinstance(table_data, AnnData):
521
+ return normalize_anndata(table_data, index_key=index_key)
522
+ if isinstance(table_data, DataFrame):
523
+ return convert_pandas_to_anndata(table_data, index_key=index_key)
524
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
525
+ return convert_polars_to_anndata(table_data, index_key=index_key)
526
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
527
+
528
+
529
+ def convert_to_pandas(
530
+ table_data: TabularData,
531
+ index_key: str | None = None,
532
+ index_type: Literal["int", "str"] | None = None,
533
+ reset_index: bool = False,
534
+ ) -> DataFrame:
535
+ """Convert a table to a pandas DataFrame.
536
+
537
+ Args:
538
+ table_data (TabularData): The table to convert.
539
+ index_key (str | None): The column name to use as the index of the DataFrame.
540
+ Default is None.
541
+ index_type (str | None): The type of the index column in the DataFrame.
542
+ Either 'str' or 'int'. Default is None.
543
+ reset_index (bool): If True the index will be reset (i.e., the index will be
544
+ converted to a column). If False, the index will be kept as is.
545
+
546
+ Returns:
547
+ DataFrame: Converted pandas DataFrame.
548
+ """
549
+ if isinstance(table_data, DataFrame):
550
+ return normalize_pandas_df(
551
+ table_data,
552
+ index_key=index_key,
553
+ index_type=index_type,
554
+ reset_index=reset_index,
555
+ )
556
+ if isinstance(table_data, AnnData):
557
+ return convert_anndata_to_pandas(
558
+ table_data,
559
+ index_key=index_key,
560
+ index_type=index_type,
561
+ reset_index=reset_index,
562
+ )
563
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
564
+ return convert_polars_to_pandas(
565
+ table_data,
566
+ index_key=index_key,
567
+ index_type=index_type,
568
+ reset_index=reset_index,
569
+ )
570
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
571
+
572
+
573
+ def convert_to_polars(
574
+ table_data: TabularData,
575
+ index_key: str | None = None,
576
+ index_type: Literal["int", "str"] | None = None,
577
+ ) -> LazyFrame:
578
+ """Convert a table to a polars LazyFrame.
579
+
580
+ Args:
581
+ table_data (TabularData): The table to convert.
582
+ index_key (str | None): The column name to use as the index of the DataFrame.
583
+ Default is None.
584
+ index_type (str | None): The type of the index column in the DataFrame.
585
+ Either 'str' or 'int'. Default is None.
586
+
587
+ Returns:
588
+ LazyFrame: Converted polars LazyFrame.
589
+ """
590
+ if isinstance(table_data, PolarsDataFrame) or isinstance(table_data, LazyFrame):
591
+ return normalize_polars_lf(
592
+ table_data,
593
+ index_key=index_key,
594
+ index_type=index_type,
595
+ )
596
+ if isinstance(table_data, DataFrame):
597
+ return convert_pandas_to_polars(
598
+ table_data,
599
+ index_key=index_key,
600
+ index_type=index_type,
601
+ )
602
+ if isinstance(table_data, AnnData):
603
+ return convert_anndata_to_polars(
604
+ table_data,
605
+ index_key=index_key,
606
+ index_type=index_type,
607
+ )
608
+ raise NgioValueError(f"Unsupported table type: {type(table_data)}")
@@ -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
+ )
@@ -6,12 +6,12 @@ https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
6
6
 
7
7
  from typing import Literal
8
8
 
9
- import pandas as pd
10
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, Field
11
10
 
12
- from ngio.tables.backends import BackendMeta, ImplementedTableBackends
13
- from ngio.tables.backends._utils import normalize_pandas_df
14
- from ngio.utils import NgioValueError, ZarrGroupHandler
11
+ from ngio.tables._abstract_table import AbstractBaseTable
12
+ from ngio.tables.backends import BackendMeta, TableBackend, TabularData
13
+ from ngio.utils import NgioValueError
14
+ from ngio.utils._zarr_utils import ZarrGroupHandler
15
15
 
16
16
 
17
17
  class RegionMeta(BaseModel):
@@ -23,42 +23,52 @@ class RegionMeta(BaseModel):
23
23
  class FeatureTableMeta(BackendMeta):
24
24
  """Metadata for the ROI table."""
25
25
 
26
- fractal_table_version: Literal["1"] = "1"
26
+ table_version: Literal["1"] = "1"
27
27
  type: Literal["feature_table"] = "feature_table"
28
28
  region: RegionMeta | None = None
29
- instance_key: str = "label"
29
+ instance_key: str = "label" # Legacy field, kept for compatibility
30
+ # Backend metadata
31
+ index_key: str | None = "label"
32
+ index_type: Literal["int", "str"] | None = "int"
33
+ # Columns optional types
34
+ categorical_columns: list[str] = Field(default_factory=list)
35
+ measurement_columns: list[str] = Field(default_factory=list)
36
+ metadata_columns: list[str] = Field(default_factory=list)
30
37
 
31
38
 
32
- class FeatureTableV1:
33
- """Class to represent a feature table.
34
-
35
- This can be used to load any table that does not have
36
- a specific definition.
37
- """
38
-
39
+ class FeatureTableV1(AbstractBaseTable):
39
40
  def __init__(
40
41
  self,
41
- dataframe: pd.DataFrame | None = None,
42
+ table_data: TabularData | None = None,
43
+ *,
42
44
  reference_label: str | None = None,
45
+ meta: FeatureTableMeta | None = None,
43
46
  ) -> None:
44
47
  """Initialize the GenericTable."""
45
- if reference_label is None:
46
- self._meta = FeatureTableMeta()
47
- else:
48
+ if meta is None:
49
+ meta = FeatureTableMeta()
50
+
51
+ if reference_label is not None:
48
52
  path = f"../labels/{reference_label}"
49
- self._meta = FeatureTableMeta(region=RegionMeta(path=path))
50
-
51
- self._instance_key = "label"
52
- if dataframe is None:
53
- self._dataframe = None
54
- else:
55
- self._dataframe = normalize_pandas_df(
56
- dataframe,
57
- index_key=self._instance_key,
58
- index_type="int",
59
- reset_index=False,
53
+ meta = FeatureTableMeta(region=RegionMeta(path=path))
54
+
55
+ if table_data is not None and not isinstance(table_data, TabularData):
56
+ raise NgioValueError(
57
+ f"The table is not of type SupportedTables. Got {type(table_data)}"
60
58
  )
61
- self._table_backend = None
59
+
60
+ if meta.index_key is None:
61
+ meta.index_key = "label"
62
+
63
+ if meta.index_type is None:
64
+ meta.index_type = "int"
65
+
66
+ meta.instance_key = meta.index_key
67
+
68
+ super().__init__(
69
+ table_data=table_data,
70
+ meta=meta,
71
+ )
62
72
 
63
73
  def __repr__(self) -> str:
64
74
  """Return a string representation of the table."""
@@ -69,8 +79,29 @@ class FeatureTableV1:
69
79
  properties += f", reference_label={self.reference_label}"
70
80
  return f"FeatureTableV1({properties})"
71
81
 
82
+ @classmethod
83
+ def from_handler(
84
+ cls,
85
+ handler: ZarrGroupHandler,
86
+ backend: TableBackend | None = None,
87
+ ) -> "FeatureTableV1":
88
+ return cls._from_handler(
89
+ handler=handler,
90
+ backend=backend,
91
+ meta_model=FeatureTableMeta,
92
+ )
93
+
94
+ @property
95
+ def meta(self) -> FeatureTableMeta:
96
+ """Return the metadata of the table."""
97
+ if not isinstance(self._meta, FeatureTableMeta):
98
+ raise NgioValueError(
99
+ "The metadata of the table is not of type FeatureTableMeta."
100
+ )
101
+ return self._meta
102
+
72
103
  @staticmethod
73
- def type() -> str:
104
+ def table_type() -> str:
74
105
  """Return the type of the table."""
75
106
  return "feature_table"
76
107
 
@@ -82,110 +113,13 @@ class FeatureTableV1:
82
113
  """
83
114
  return "1"
84
115
 
85
- @property
86
- def backend_name(self) -> str | None:
87
- """Return the name of the backend."""
88
- if self._table_backend is None:
89
- return None
90
- return self._table_backend.backend_name()
91
-
92
116
  @property
93
117
  def reference_label(self) -> str | None:
94
118
  """Return the reference label."""
95
- path = self._meta.region
119
+ path = self.meta.region
96
120
  if path is None:
97
121
  return None
98
122
 
99
123
  path = path.path
100
124
  path = path.split("/")[-1]
101
125
  return path
102
-
103
- @property
104
- def dataframe(self) -> pd.DataFrame:
105
- """Return the table as a DataFrame."""
106
- if self._dataframe is None and self._table_backend is None:
107
- raise NgioValueError(
108
- "The table does not have a DataFrame in memory nor a backend."
109
- )
110
-
111
- if self._dataframe is None and self._table_backend is not None:
112
- self._dataframe = self._table_backend.load_as_pandas_df()
113
-
114
- if self._dataframe is None:
115
- raise NgioValueError(
116
- "The table does not have a DataFrame in memory nor a backend."
117
- )
118
- return self._dataframe
119
-
120
- @dataframe.setter
121
- def dataframe(self, dataframe: pd.DataFrame) -> None:
122
- """Set the table as a DataFrame."""
123
- self._dataframe = normalize_pandas_df(
124
- dataframe,
125
- index_key=self._instance_key,
126
- index_type="int",
127
- reset_index=False,
128
- )
129
-
130
- @classmethod
131
- def _from_handler(
132
- cls, handler: ZarrGroupHandler, backend_name: str | None = None
133
- ) -> "FeatureTableV1":
134
- """Create a new ROI table from a Zarr group handler."""
135
- meta = FeatureTableMeta(**handler.load_attrs())
136
- instance_key = "label" if meta.instance_key is None else meta.instance_key
137
- if backend_name is None:
138
- backend = ImplementedTableBackends().get_backend(
139
- backend_name=meta.backend,
140
- group_handler=handler,
141
- index_key=instance_key,
142
- index_type="int",
143
- )
144
- else:
145
- backend = ImplementedTableBackends().get_backend(
146
- backend_name=backend_name,
147
- group_handler=handler,
148
- index_key=instance_key,
149
- index_type="int",
150
- )
151
- meta.backend = backend_name
152
-
153
- if not backend.implements_pandas:
154
- raise NgioValueError(
155
- "The backend does not implement the dataframe protocol."
156
- )
157
-
158
- table = cls()
159
- table._meta = meta
160
- table._table_backend = backend
161
- return table
162
-
163
- def _set_backend(
164
- self,
165
- handler: ZarrGroupHandler,
166
- backend_name: str | None = None,
167
- ) -> None:
168
- """Set the backend of the table."""
169
- instance_key = "label" if self._instance_key is None else self._instance_key
170
- backend = ImplementedTableBackends().get_backend(
171
- backend_name=backend_name,
172
- group_handler=handler,
173
- index_key=instance_key,
174
- index_type="int",
175
- )
176
- self._meta.backend = backend_name
177
- self._table_backend = backend
178
-
179
- def consolidate(self) -> None:
180
- """Write the current state of the table to the Zarr file."""
181
- if self._table_backend is None:
182
- raise NgioValueError(
183
- "No backend set for the table. "
184
- "Please add the table to a OME-Zarr Image before calling consolidate."
185
- )
186
-
187
- self._table_backend.write(
188
- self.dataframe,
189
- metadata=self._meta.model_dump(exclude_none=True),
190
- mode="pandas",
191
- )