ngio 0.1.6__py3-none-any.whl → 0.2.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.
Files changed (84) hide show
  1. ngio/__init__.py +31 -5
  2. ngio/common/__init__.py +44 -0
  3. ngio/common/_array_pipe.py +160 -0
  4. ngio/common/_axes_transforms.py +63 -0
  5. ngio/common/_common_types.py +5 -0
  6. ngio/common/_dimensions.py +113 -0
  7. ngio/common/_pyramid.py +222 -0
  8. ngio/{core/roi.py → common/_roi.py} +22 -23
  9. ngio/common/_slicer.py +97 -0
  10. ngio/{pipes/_zoom_utils.py → common/_zoom.py} +2 -78
  11. ngio/hcs/__init__.py +60 -0
  12. ngio/images/__init__.py +23 -0
  13. ngio/images/abstract_image.py +240 -0
  14. ngio/images/create.py +251 -0
  15. ngio/images/image.py +383 -0
  16. ngio/images/label.py +96 -0
  17. ngio/images/omezarr_container.py +512 -0
  18. ngio/ome_zarr_meta/__init__.py +35 -0
  19. ngio/ome_zarr_meta/_generic_handlers.py +320 -0
  20. ngio/ome_zarr_meta/_meta_handlers.py +142 -0
  21. ngio/ome_zarr_meta/ngio_specs/__init__.py +63 -0
  22. ngio/ome_zarr_meta/ngio_specs/_axes.py +481 -0
  23. ngio/ome_zarr_meta/ngio_specs/_channels.py +378 -0
  24. ngio/ome_zarr_meta/ngio_specs/_dataset.py +134 -0
  25. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +5 -0
  26. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +434 -0
  27. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +84 -0
  28. ngio/ome_zarr_meta/v04/__init__.py +11 -0
  29. ngio/ome_zarr_meta/v04/_meta_handlers.py +54 -0
  30. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +412 -0
  31. ngio/tables/__init__.py +21 -5
  32. ngio/tables/_validators.py +192 -0
  33. ngio/tables/backends/__init__.py +8 -0
  34. ngio/tables/backends/_abstract_backend.py +71 -0
  35. ngio/tables/backends/_anndata_utils.py +194 -0
  36. ngio/tables/backends/_anndata_v1.py +75 -0
  37. ngio/tables/backends/_json_v1.py +56 -0
  38. ngio/tables/backends/_table_backends.py +102 -0
  39. ngio/tables/tables_container.py +300 -0
  40. ngio/tables/v1/__init__.py +6 -5
  41. ngio/tables/v1/_feature_table.py +161 -0
  42. ngio/tables/v1/_generic_table.py +99 -182
  43. ngio/tables/v1/_masking_roi_table.py +175 -0
  44. ngio/tables/v1/_roi_table.py +226 -0
  45. ngio/utils/__init__.py +23 -10
  46. ngio/utils/_datasets.py +51 -0
  47. ngio/utils/_errors.py +10 -4
  48. ngio/utils/_zarr_utils.py +378 -0
  49. {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/METADATA +18 -39
  50. ngio-0.2.0a1.dist-info/RECORD +53 -0
  51. ngio/core/__init__.py +0 -7
  52. ngio/core/dimensions.py +0 -122
  53. ngio/core/image_handler.py +0 -228
  54. ngio/core/image_like_handler.py +0 -549
  55. ngio/core/label_handler.py +0 -410
  56. ngio/core/ngff_image.py +0 -387
  57. ngio/core/utils.py +0 -287
  58. ngio/io/__init__.py +0 -19
  59. ngio/io/_zarr.py +0 -88
  60. ngio/io/_zarr_array_utils.py +0 -0
  61. ngio/io/_zarr_group_utils.py +0 -60
  62. ngio/iterators/__init__.py +0 -1
  63. ngio/ngff_meta/__init__.py +0 -27
  64. ngio/ngff_meta/fractal_image_meta.py +0 -1267
  65. ngio/ngff_meta/meta_handler.py +0 -92
  66. ngio/ngff_meta/utils.py +0 -235
  67. ngio/ngff_meta/v04/__init__.py +0 -6
  68. ngio/ngff_meta/v04/specs.py +0 -158
  69. ngio/ngff_meta/v04/zarr_utils.py +0 -376
  70. ngio/pipes/__init__.py +0 -7
  71. ngio/pipes/_slicer_transforms.py +0 -176
  72. ngio/pipes/_transforms.py +0 -33
  73. ngio/pipes/data_pipe.py +0 -52
  74. ngio/tables/_ad_reader.py +0 -80
  75. ngio/tables/_utils.py +0 -301
  76. ngio/tables/tables_group.py +0 -252
  77. ngio/tables/v1/feature_tables.py +0 -182
  78. ngio/tables/v1/masking_roi_tables.py +0 -243
  79. ngio/tables/v1/roi_tables.py +0 -285
  80. ngio/utils/_common_types.py +0 -5
  81. ngio/utils/_pydantic_utils.py +0 -52
  82. ngio-0.1.6.dist-info/RECORD +0 -44
  83. {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/WHEEL +0 -0
  84. {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,300 @@
1
+ """Module for handling the /tables group in an OME-NGFF file."""
2
+
3
+ from typing import Literal, Protocol
4
+
5
+ from ngio.tables.v1 import FeatureTableV1, MaskingROITableV1, RoiTableV1
6
+ from ngio.tables.v1._generic_table import GenericTable
7
+ from ngio.utils import (
8
+ AccessModeLiteral,
9
+ NgioValidationError,
10
+ NgioValueError,
11
+ StoreOrGroup,
12
+ ZarrGroupHandler,
13
+ )
14
+
15
+ RoiTable = RoiTableV1
16
+ MaskingROITable = MaskingROITableV1
17
+ FeatureTable = FeatureTableV1
18
+
19
+
20
+ class Table(Protocol):
21
+ """Placeholder class for a table."""
22
+
23
+ @staticmethod
24
+ def type() -> str | None:
25
+ """Return the type of the table."""
26
+ ...
27
+
28
+ @staticmethod
29
+ def version() -> str | None:
30
+ """Return the version of the table."""
31
+ ...
32
+
33
+ @property
34
+ def backend_name(self) -> str | None:
35
+ """The name of the backend."""
36
+ ...
37
+
38
+ @classmethod
39
+ def _from_handler(
40
+ cls, handler: ZarrGroupHandler, backend_name: str | None = None
41
+ ) -> "Table":
42
+ """Create a new table from a Zarr group handler."""
43
+ ...
44
+
45
+ def _set_backend(
46
+ self,
47
+ handler: ZarrGroupHandler,
48
+ backend_name: str | None = None,
49
+ ) -> None:
50
+ """Set the backend store and path for the table."""
51
+ ...
52
+
53
+ def consolidate(self) -> None:
54
+ """Consolidate the table on disk."""
55
+ ...
56
+
57
+
58
+ TypedTable = Literal["roi_table", "masking_roi_table", "feature_table"]
59
+
60
+
61
+ def _unique_table_name(type_name, version) -> str:
62
+ """Return the unique name for a table."""
63
+ return f"{type_name}_v{version}"
64
+
65
+
66
+ class ImplementedTables:
67
+ """A singleton class to manage the available table handler plugins."""
68
+
69
+ _instance = None
70
+ _implemented_tables: dict[str, type[Table]]
71
+
72
+ def __new__(cls):
73
+ """Create a new instance of the class if it does not exist."""
74
+ if cls._instance is None:
75
+ cls._instance = super().__new__(cls)
76
+ cls._instance._implemented_tables = {}
77
+ return cls._instance
78
+
79
+ def available_implementations(self) -> list[str]:
80
+ """Get the available table handler versions."""
81
+ return list(self._implemented_tables.keys())
82
+
83
+ def get_table(
84
+ self,
85
+ type: str,
86
+ version: str,
87
+ handler: ZarrGroupHandler,
88
+ backend_name: str | None = None,
89
+ ) -> Table:
90
+ """Try to get a handler for the given store based on the metadata version."""
91
+ _errors = {}
92
+ for name, table_cls in self._implemented_tables.items():
93
+ if name != _unique_table_name(type, version):
94
+ continue
95
+ try:
96
+ table = table_cls._from_handler(
97
+ handler=handler, backend_name=backend_name
98
+ )
99
+ return table
100
+ except Exception as e:
101
+ _errors[name] = e
102
+ print(_errors)
103
+ # If no table was found, we can try to load the table from a generic table
104
+ try:
105
+ table = GenericTable._from_handler(
106
+ handler=handler, backend_name=backend_name
107
+ )
108
+ return table
109
+ except Exception as e:
110
+ _errors["generic"] = e
111
+
112
+ if len(_errors) == 0:
113
+ raise NgioValidationError(
114
+ f"Could not find a table implementation for {type} v{version}. "
115
+ f"Available tables: {self.available_implementations()}"
116
+ )
117
+
118
+ raise NgioValidationError(
119
+ f"Could not load table from any known version. Errors: {_errors}"
120
+ )
121
+
122
+ def add_implementation(self, handler: type[Table], overwrite: bool = False):
123
+ """Register a new table handler."""
124
+ table_type = handler.type()
125
+ version = handler.version()
126
+ if table_type is None:
127
+ raise NgioValueError("Table handler must have a type.")
128
+
129
+ if version is None:
130
+ raise NgioValueError("Table handler must have a version.")
131
+
132
+ table_unique_name = _unique_table_name(table_type, version)
133
+ if table_unique_name in self._implemented_tables and not overwrite:
134
+ raise NgioValueError(
135
+ f"Table handler for {table_unique_name} already exists. "
136
+ "Use overwrite=True to replace it."
137
+ )
138
+ self._implemented_tables[table_unique_name] = handler
139
+
140
+
141
+ def _get_table_type(handler: ZarrGroupHandler) -> str:
142
+ """Get the type of the table from the handler."""
143
+ attrs = handler.load_attrs()
144
+ return attrs.get("type", "None")
145
+
146
+
147
+ def _get_table_version(handler: ZarrGroupHandler) -> str:
148
+ """Get the version of the table from the handler."""
149
+ attrs = handler.load_attrs()
150
+ return attrs.get("fractal_table_version", "None")
151
+
152
+
153
+ class TablesContainer:
154
+ """A class to handle the /labels group in an OME-NGFF file."""
155
+
156
+ def __init__(self, group_handler: ZarrGroupHandler) -> None:
157
+ """Initialize the LabelGroupHandler."""
158
+ self._group_handler = group_handler
159
+
160
+ # Validate the group
161
+ # Either contains a tables attribute or is empty
162
+ attrs = self._group_handler.load_attrs()
163
+ if len(attrs) == 0:
164
+ # It's an empty group
165
+ pass
166
+ elif "tables" in attrs and isinstance(attrs["tables"], list):
167
+ # It's a valid group
168
+ pass
169
+ else:
170
+ raise NgioValidationError(
171
+ f"Invalid /tables group. "
172
+ f"Expected a single tables attribute with a list of table names. "
173
+ f"Found: {attrs}"
174
+ )
175
+
176
+ def _get_tables_list(self) -> list[str]:
177
+ """Create the /tables group if it doesn't exist."""
178
+ attrs = self._group_handler.load_attrs()
179
+ return attrs.get("tables", [])
180
+
181
+ def _get_table_group_handler(self, name: str) -> ZarrGroupHandler:
182
+ """Get the group handler for a table."""
183
+ handler = self._group_handler.derive_handler(path=name)
184
+ return handler
185
+
186
+ def list(self, filter_types: str | None = None) -> list[str]:
187
+ """List all labels in the group."""
188
+ tables = self._get_tables_list()
189
+ if filter_types is None:
190
+ return tables
191
+
192
+ filtered_tables = []
193
+ for table_name in tables:
194
+ tb_handler = self._get_table_group_handler(table_name)
195
+ table_type = _get_table_type(tb_handler)
196
+ if table_type == filter_types:
197
+ filtered_tables.append(table_name)
198
+ return filtered_tables
199
+
200
+ def get(self, name: str, backend_name: str | None = None) -> Table:
201
+ """Get a label from the group."""
202
+ if name not in self.list():
203
+ raise KeyError(f"Table '{name}' not found in the group.")
204
+
205
+ table_handler = self._get_table_group_handler(name)
206
+ table_type = _get_table_type(table_handler)
207
+ table_version = _get_table_version(table_handler)
208
+ return ImplementedTables().get_table(
209
+ type=table_type,
210
+ version=table_version,
211
+ handler=table_handler,
212
+ backend_name=backend_name,
213
+ )
214
+
215
+ def add(
216
+ self,
217
+ name: str,
218
+ table: Table,
219
+ backend: str | None = None,
220
+ overwrite: bool = False,
221
+ ) -> None:
222
+ """Add a table to the group."""
223
+ existing_tables = self._get_tables_list()
224
+ if name in existing_tables and not overwrite:
225
+ raise NgioValueError(
226
+ f"Table '{name}' already exists in the group. "
227
+ "Use overwrite=True to replace it."
228
+ )
229
+
230
+ table_handler = self._group_handler.derive_handler(path=name)
231
+
232
+ if backend is None:
233
+ backend = table.backend_name
234
+
235
+ table._set_backend(
236
+ handler=table_handler,
237
+ backend_name=backend,
238
+ )
239
+ table.consolidate()
240
+ if name not in existing_tables:
241
+ existing_tables.append(name)
242
+ self._group_handler.write_attrs({"tables": existing_tables})
243
+
244
+
245
+ ImplementedTables().add_implementation(RoiTableV1)
246
+ ImplementedTables().add_implementation(MaskingROITableV1)
247
+ ImplementedTables().add_implementation(FeatureTableV1)
248
+
249
+ ###################################################################################
250
+ #
251
+ # Utility functions to open and write tables
252
+ #
253
+ ###################################################################################
254
+
255
+
256
+ def open_tables_container(
257
+ store: StoreOrGroup,
258
+ cache: bool = False,
259
+ mode: AccessModeLiteral = "a",
260
+ parallel_safe: bool = False,
261
+ ) -> TablesContainer:
262
+ """Open a table handler from a Zarr store."""
263
+ handler = ZarrGroupHandler(
264
+ store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
265
+ )
266
+ return TablesContainer(handler)
267
+
268
+
269
+ def open_table(
270
+ store: StoreOrGroup,
271
+ cache: bool = False,
272
+ mode: AccessModeLiteral = "a",
273
+ parallel_safe: bool = False,
274
+ ) -> Table:
275
+ """Open a table from a Zarr store."""
276
+ handler = ZarrGroupHandler(
277
+ store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
278
+ )
279
+ return ImplementedTables().get_table(
280
+ _get_table_type(handler), _get_table_version(handler), handler
281
+ )
282
+
283
+
284
+ def write_table(
285
+ store: StoreOrGroup,
286
+ table: Table,
287
+ backend: str | None = None,
288
+ cache: bool = False,
289
+ mode: AccessModeLiteral = "a",
290
+ parallel_safe: bool = False,
291
+ ) -> None:
292
+ """Write a table to a Zarr store."""
293
+ handler = ZarrGroupHandler(
294
+ store=store, cache=cache, mode=mode, parallel_safe=parallel_safe
295
+ )
296
+ table._set_backend(
297
+ handler=handler,
298
+ backend_name=backend,
299
+ )
300
+ table.consolidate()
@@ -1,7 +1,8 @@
1
- """This module contains the objects to handle the fractal tables V1."""
1
+ """Tables implementations for fractal_tables v1."""
2
2
 
3
- from ngio.tables.v1.feature_tables import FeatureTableV1
4
- from ngio.tables.v1.masking_roi_tables import MaskingROITableV1
5
- from ngio.tables.v1.roi_tables import ROITableV1
3
+ from ngio.tables.v1._feature_table import FeatureTableV1
4
+ from ngio.tables.v1._generic_table import GenericTable
5
+ from ngio.tables.v1._masking_roi_table import MaskingROITableV1
6
+ from ngio.tables.v1._roi_table import RoiTableV1
6
7
 
7
- __all__ = ["ROITableV1", "FeatureTableV1", "MaskingROITableV1"]
8
+ __all__ = ["FeatureTableV1", "GenericTable", "MaskingROITableV1", "RoiTableV1"]
@@ -0,0 +1,161 @@
1
+ """Implementation of the FeatureTableV1 class.
2
+
3
+ This class follows the roi_table specification at:
4
+ https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
5
+ """
6
+
7
+ from typing import Literal
8
+
9
+ import pandas as pd
10
+ from pydantic import BaseModel
11
+
12
+ from ngio.tables._validators import validate_index_key
13
+ from ngio.tables.backends import ImplementedTableBackends
14
+ from ngio.utils import ZarrGroupHandler
15
+
16
+
17
+ class RegionMeta(BaseModel):
18
+ """Metadata for the region."""
19
+
20
+ path: str
21
+
22
+
23
+ class FeatureTableMeta(BaseModel):
24
+ """Metadata for the ROI table."""
25
+
26
+ fractal_table_version: Literal["1"] = "1"
27
+ type: Literal["feature_table"] = "feature_table"
28
+ backend: str | None = None
29
+ region: RegionMeta | None = None
30
+ instance_key: str = "label"
31
+
32
+
33
+ class FeatureTableV1:
34
+ """Class to represent a feature table.
35
+
36
+ This can be used to load any table that does not have
37
+ a specific definition.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ dataframe: pd.DataFrame | None = None,
43
+ reference_label: str | None = None,
44
+ ) -> None:
45
+ """Initialize the GenericTable."""
46
+ if reference_label is None:
47
+ self._meta = FeatureTableMeta()
48
+ else:
49
+ path = f"../labels/{reference_label}"
50
+ self._meta = FeatureTableMeta(region=RegionMeta(path=path))
51
+
52
+ self._reference_label = reference_label
53
+ self._instance_key = "label"
54
+ if dataframe is None:
55
+ self._dataframe = None
56
+ else:
57
+ self._dataframe = validate_index_key(
58
+ dataframe, self._instance_key, overwrite=True
59
+ )
60
+ self._table_backend = None
61
+
62
+ @staticmethod
63
+ def type() -> str:
64
+ """Return the type of the table."""
65
+ return "feature_table"
66
+
67
+ @staticmethod
68
+ def version() -> str:
69
+ """The generic table does not have a version.
70
+
71
+ Since does not follow a specific schema.
72
+ """
73
+ return "1"
74
+
75
+ @property
76
+ def backend_name(self) -> str | None:
77
+ """Return the name of the backend."""
78
+ if self._table_backend is None:
79
+ return None
80
+ return self._table_backend.backend_name()
81
+
82
+ @property
83
+ def dataframe(self) -> pd.DataFrame:
84
+ """Return the table as a DataFrame."""
85
+ if self._dataframe is None and self._table_backend is None:
86
+ raise ValueError(
87
+ "The table does not have a DataFrame in memory nor a backend."
88
+ )
89
+
90
+ if self._dataframe is None and self._table_backend is not None:
91
+ self._dataframe = self._table_backend.load_as_dataframe()
92
+
93
+ if self._dataframe is None:
94
+ raise ValueError(
95
+ "The table does not have a DataFrame in memory nor a backend."
96
+ )
97
+ return self._dataframe
98
+
99
+ @dataframe.setter
100
+ def dataframe(self, dataframe: pd.DataFrame) -> None:
101
+ """Set the table as a DataFrame."""
102
+ self._dataframe = dataframe
103
+
104
+ @classmethod
105
+ def _from_handler(
106
+ cls, handler: ZarrGroupHandler, backend_name: str | None = None
107
+ ) -> "FeatureTableV1":
108
+ """Create a new ROI table from a Zarr group handler."""
109
+ meta = FeatureTableMeta(**handler.load_attrs())
110
+ instance_key = "label" if meta.instance_key is None else meta.instance_key
111
+ if backend_name is None:
112
+ backend = ImplementedTableBackends().get_backend(
113
+ backend_name=meta.backend,
114
+ group_handler=handler,
115
+ index_key=instance_key,
116
+ index_type="int",
117
+ )
118
+ else:
119
+ backend = ImplementedTableBackends().get_backend(
120
+ backend_name=backend_name,
121
+ group_handler=handler,
122
+ index_key=instance_key,
123
+ index_type="int",
124
+ )
125
+ meta.backend = backend_name
126
+
127
+ if not backend.implements_dataframe:
128
+ raise ValueError("The backend does not implement the dataframe protocol.")
129
+
130
+ table = cls()
131
+ table._meta = meta
132
+ table._table_backend = backend
133
+ return table
134
+
135
+ def _set_backend(
136
+ self,
137
+ handler: ZarrGroupHandler,
138
+ backend_name: str | None = None,
139
+ ) -> None:
140
+ """Set the backend of the table."""
141
+ instance_key = "label" if self._instance_key is None else self._instance_key
142
+ backend = ImplementedTableBackends().get_backend(
143
+ backend_name=backend_name,
144
+ group_handler=handler,
145
+ index_key=instance_key,
146
+ index_type="int",
147
+ )
148
+ self._meta.backend = backend_name
149
+ self._table_backend = backend
150
+
151
+ def consolidate(self) -> None:
152
+ """Write the current state of the table to the Zarr file."""
153
+ if self._table_backend is None:
154
+ raise ValueError(
155
+ "No backend set for the table. "
156
+ "Please add the table to a OME-Zarr Image before calling consolidate."
157
+ )
158
+
159
+ self._table_backend.write_from_dataframe(
160
+ self.dataframe, metadata=self._meta.model_dump(exclude_none=True)
161
+ )