ngio 0.2.0a2__py3-none-any.whl → 0.2.0b1__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/__init__.py +4 -4
- ngio/common/__init__.py +12 -2
- ngio/common/_array_pipe.py +106 -0
- ngio/common/_axes_transforms.py +3 -2
- ngio/common/_dimensions.py +7 -0
- ngio/common/_masking_roi.py +158 -0
- ngio/common/_pyramid.py +16 -11
- ngio/common/_roi.py +74 -0
- ngio/common/_slicer.py +1 -2
- ngio/common/_zoom.py +5 -3
- ngio/hcs/__init__.py +2 -57
- ngio/hcs/plate.py +399 -0
- ngio/images/abstract_image.py +97 -28
- ngio/images/create.py +48 -29
- ngio/images/image.py +121 -57
- ngio/images/label.py +131 -86
- ngio/images/masked_image.py +259 -0
- ngio/images/omezarr_container.py +250 -77
- ngio/ome_zarr_meta/__init__.py +25 -13
- ngio/ome_zarr_meta/_meta_handlers.py +718 -69
- ngio/ome_zarr_meta/ngio_specs/__init__.py +8 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +11 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +374 -2
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +174 -113
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +35 -3
- ngio/ome_zarr_meta/v04/__init__.py +17 -5
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +85 -12
- ngio/tables/__init__.py +2 -0
- ngio/tables/_validators.py +2 -4
- ngio/tables/backends/_anndata_utils.py +2 -1
- ngio/tables/backends/_anndata_v1.py +2 -1
- ngio/tables/backends/_json_v1.py +1 -1
- ngio/tables/tables_container.py +12 -2
- ngio/tables/v1/__init__.py +1 -2
- ngio/tables/v1/_feature_table.py +7 -5
- ngio/tables/v1/_generic_table.py +65 -11
- ngio/tables/v1/_roi_table.py +145 -27
- ngio/utils/__init__.py +3 -0
- ngio/utils/_datasets.py +4 -2
- ngio/utils/_fractal_fsspec_store.py +13 -0
- ngio/utils/_logger.py +3 -1
- ngio/utils/_zarr_utils.py +25 -2
- {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/METADATA +4 -1
- ngio-0.2.0b1.dist-info/RECORD +54 -0
- ngio/ome_zarr_meta/_generic_handlers.py +0 -320
- ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
- ngio/tables/v1/_masking_roi_table.py +0 -175
- ngio-0.2.0a2.dist-info/RECORD +0 -53
- {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/WHEEL +0 -0
- {ngio-0.2.0a2.dist-info → ngio-0.2.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,6 +10,7 @@ from ngio.tables.backends._anndata_utils import (
|
|
|
10
10
|
custom_read_zarr,
|
|
11
11
|
dataframe_to_anndata,
|
|
12
12
|
)
|
|
13
|
+
from ngio.utils import NgioValueError
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class AnnDataBackend(AbstractTableBackend):
|
|
@@ -65,7 +66,7 @@ class AnnDataBackend(AbstractTableBackend):
|
|
|
65
66
|
"""Consolidate the metadata in the store."""
|
|
66
67
|
store = self._group_handler.store
|
|
67
68
|
if not isinstance(store, str | Path):
|
|
68
|
-
raise
|
|
69
|
+
raise NgioValueError(
|
|
69
70
|
"To write an AnnData object the store must be a local path/str."
|
|
70
71
|
)
|
|
71
72
|
|
ngio/tables/backends/_json_v1.py
CHANGED
ngio/tables/tables_container.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Literal, Protocol
|
|
|
4
4
|
|
|
5
5
|
from ngio.tables.v1 import FeatureTableV1, MaskingROITableV1, RoiTableV1
|
|
6
6
|
from ngio.tables.v1._generic_table import GenericTable
|
|
7
|
+
from ngio.tables.v1._roi_table import _GenericRoiTableV1
|
|
7
8
|
from ngio.utils import (
|
|
8
9
|
AccessModeLiteral,
|
|
9
10
|
NgioValidationError,
|
|
@@ -12,6 +13,7 @@ from ngio.utils import (
|
|
|
12
13
|
ZarrGroupHandler,
|
|
13
14
|
)
|
|
14
15
|
|
|
16
|
+
GenericRoiTable = _GenericRoiTableV1
|
|
15
17
|
RoiTable = RoiTableV1
|
|
16
18
|
MaskingROITable = MaskingROITableV1
|
|
17
19
|
FeatureTable = FeatureTableV1
|
|
@@ -55,7 +57,9 @@ class Table(Protocol):
|
|
|
55
57
|
...
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
TypedTable = Literal[
|
|
60
|
+
TypedTable = Literal[
|
|
61
|
+
"roi_table", "masking_roi_table", "feature_table", "generic_roi_table"
|
|
62
|
+
]
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
def _unique_table_name(type_name, version) -> str:
|
|
@@ -99,7 +103,6 @@ class ImplementedTables:
|
|
|
99
103
|
return table
|
|
100
104
|
except Exception as e:
|
|
101
105
|
_errors[name] = e
|
|
102
|
-
print(_errors)
|
|
103
106
|
# If no table was found, we can try to load the table from a generic table
|
|
104
107
|
try:
|
|
105
108
|
table = GenericTable._from_handler(
|
|
@@ -183,6 +186,13 @@ class TablesContainer:
|
|
|
183
186
|
handler = self._group_handler.derive_handler(path=name)
|
|
184
187
|
return handler
|
|
185
188
|
|
|
189
|
+
def list_roi_tables(self) -> list[str]:
|
|
190
|
+
"""List all ROI tables in the group."""
|
|
191
|
+
_tables = []
|
|
192
|
+
for _type in ["roi_table", "masking_roi_table"]:
|
|
193
|
+
_tables.extend(self.list(_type))
|
|
194
|
+
return _tables
|
|
195
|
+
|
|
186
196
|
def list(self, filter_types: str | None = None) -> list[str]:
|
|
187
197
|
"""List all labels in the group."""
|
|
188
198
|
tables = self._get_tables_list()
|
ngio/tables/v1/__init__.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from ngio.tables.v1._feature_table import FeatureTableV1
|
|
4
4
|
from ngio.tables.v1._generic_table import GenericTable
|
|
5
|
-
from ngio.tables.v1.
|
|
6
|
-
from ngio.tables.v1._roi_table import RoiTableV1
|
|
5
|
+
from ngio.tables.v1._roi_table import MaskingROITableV1, RoiTableV1
|
|
7
6
|
|
|
8
7
|
__all__ = ["FeatureTableV1", "GenericTable", "MaskingROITableV1", "RoiTableV1"]
|
ngio/tables/v1/_feature_table.py
CHANGED
|
@@ -11,7 +11,7 @@ from pydantic import BaseModel
|
|
|
11
11
|
|
|
12
12
|
from ngio.tables._validators import validate_index_key
|
|
13
13
|
from ngio.tables.backends import ImplementedTableBackends
|
|
14
|
-
from ngio.utils import ZarrGroupHandler
|
|
14
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class RegionMeta(BaseModel):
|
|
@@ -83,7 +83,7 @@ class FeatureTableV1:
|
|
|
83
83
|
def dataframe(self) -> pd.DataFrame:
|
|
84
84
|
"""Return the table as a DataFrame."""
|
|
85
85
|
if self._dataframe is None and self._table_backend is None:
|
|
86
|
-
raise
|
|
86
|
+
raise NgioValueError(
|
|
87
87
|
"The table does not have a DataFrame in memory nor a backend."
|
|
88
88
|
)
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ class FeatureTableV1:
|
|
|
91
91
|
self._dataframe = self._table_backend.load_as_dataframe()
|
|
92
92
|
|
|
93
93
|
if self._dataframe is None:
|
|
94
|
-
raise
|
|
94
|
+
raise NgioValueError(
|
|
95
95
|
"The table does not have a DataFrame in memory nor a backend."
|
|
96
96
|
)
|
|
97
97
|
return self._dataframe
|
|
@@ -125,7 +125,9 @@ class FeatureTableV1:
|
|
|
125
125
|
meta.backend = backend_name
|
|
126
126
|
|
|
127
127
|
if not backend.implements_dataframe:
|
|
128
|
-
raise
|
|
128
|
+
raise NgioValueError(
|
|
129
|
+
"The backend does not implement the dataframe protocol."
|
|
130
|
+
)
|
|
129
131
|
|
|
130
132
|
table = cls()
|
|
131
133
|
table._meta = meta
|
|
@@ -151,7 +153,7 @@ class FeatureTableV1:
|
|
|
151
153
|
def consolidate(self) -> None:
|
|
152
154
|
"""Write the current state of the table to the Zarr file."""
|
|
153
155
|
if self._table_backend is None:
|
|
154
|
-
raise
|
|
156
|
+
raise NgioValueError(
|
|
155
157
|
"No backend set for the table. "
|
|
156
158
|
"Please add the table to a OME-Zarr Image before calling consolidate."
|
|
157
159
|
)
|
ngio/tables/v1/_generic_table.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"""Implementation of a generic table class."""
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
|
+
from anndata import AnnData
|
|
4
5
|
from pydantic import BaseModel
|
|
5
6
|
|
|
6
7
|
from ngio.tables.backends import ImplementedTableBackends
|
|
7
|
-
from ngio.
|
|
8
|
+
from ngio.tables.backends._anndata_utils import (
|
|
9
|
+
anndata_to_dataframe,
|
|
10
|
+
dataframe_to_anndata,
|
|
11
|
+
)
|
|
12
|
+
from ngio.utils import NgioValueError, ZarrGroupHandler
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
class GenericTableMeta(BaseModel):
|
|
@@ -24,11 +29,26 @@ class GenericTable:
|
|
|
24
29
|
|
|
25
30
|
def __init__(
|
|
26
31
|
self,
|
|
27
|
-
dataframe: pd.DataFrame,
|
|
32
|
+
dataframe: pd.DataFrame | None = None,
|
|
33
|
+
anndata: AnnData | None = None,
|
|
28
34
|
) -> None:
|
|
29
35
|
"""Initialize the GenericTable."""
|
|
30
36
|
self._meta = GenericTableMeta()
|
|
37
|
+
if dataframe is None and anndata is None:
|
|
38
|
+
raise NgioValueError(
|
|
39
|
+
"Either a DataFrame or an AnnData object must be provided."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if dataframe is not None and anndata is not None:
|
|
43
|
+
raise NgioValueError(
|
|
44
|
+
"Only one of DataFrame or AnnData object can be provided."
|
|
45
|
+
)
|
|
46
|
+
|
|
31
47
|
self._dataframe = dataframe
|
|
48
|
+
self._anndata = anndata
|
|
49
|
+
|
|
50
|
+
self.anndata_native = True if anndata is not None else False
|
|
51
|
+
|
|
32
52
|
self._table_backend = None
|
|
33
53
|
|
|
34
54
|
@staticmethod
|
|
@@ -54,12 +74,35 @@ class GenericTable:
|
|
|
54
74
|
@property
|
|
55
75
|
def dataframe(self) -> pd.DataFrame:
|
|
56
76
|
"""Return the table as a DataFrame."""
|
|
57
|
-
|
|
77
|
+
if self._dataframe is not None:
|
|
78
|
+
return self._dataframe
|
|
79
|
+
|
|
80
|
+
if self._anndata is not None:
|
|
81
|
+
return anndata_to_dataframe(self._anndata)
|
|
82
|
+
|
|
83
|
+
raise NgioValueError("No table loaded.")
|
|
58
84
|
|
|
59
85
|
@dataframe.setter
|
|
60
86
|
def dataframe(self, dataframe: pd.DataFrame) -> None:
|
|
61
87
|
"""Set the table as a DataFrame."""
|
|
62
88
|
self._dataframe = dataframe
|
|
89
|
+
self.anndata_native = False
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def anndata(self) -> AnnData:
|
|
93
|
+
"""Return the table as an AnnData object."""
|
|
94
|
+
if self._anndata is not None:
|
|
95
|
+
return self._anndata
|
|
96
|
+
|
|
97
|
+
if self._dataframe is not None:
|
|
98
|
+
return dataframe_to_anndata(self._dataframe)
|
|
99
|
+
raise NgioValueError("No table loaded.")
|
|
100
|
+
|
|
101
|
+
@anndata.setter
|
|
102
|
+
def anndata(self, anndata: AnnData) -> None:
|
|
103
|
+
"""Set the table as an AnnData object."""
|
|
104
|
+
self._anndata = anndata
|
|
105
|
+
self.anndata_native = True
|
|
63
106
|
|
|
64
107
|
@classmethod
|
|
65
108
|
def _from_handler(
|
|
@@ -81,12 +124,18 @@ class GenericTable:
|
|
|
81
124
|
)
|
|
82
125
|
meta.backend = backend_name
|
|
83
126
|
|
|
84
|
-
if
|
|
85
|
-
|
|
127
|
+
if backend.implements_anndata():
|
|
128
|
+
anndata = backend.load_as_anndata()
|
|
129
|
+
table = cls(anndata=anndata)
|
|
86
130
|
|
|
87
|
-
|
|
131
|
+
elif backend.implements_dataframe():
|
|
132
|
+
dataframe = backend.load_as_dataframe()
|
|
133
|
+
table = cls(dataframe=dataframe)
|
|
134
|
+
else:
|
|
135
|
+
raise NgioValueError(
|
|
136
|
+
"The backend does not implement the dataframe protocol."
|
|
137
|
+
)
|
|
88
138
|
|
|
89
|
-
table = cls(dataframe)
|
|
90
139
|
table._meta = meta
|
|
91
140
|
table._table_backend = backend
|
|
92
141
|
return table
|
|
@@ -108,11 +157,16 @@ class GenericTable:
|
|
|
108
157
|
def consolidate(self) -> None:
|
|
109
158
|
"""Write the current state of the table to the Zarr file."""
|
|
110
159
|
if self._table_backend is None:
|
|
111
|
-
raise
|
|
160
|
+
raise NgioValueError(
|
|
112
161
|
"No backend set for the table. "
|
|
113
162
|
"Please add the table to a OME-Zarr Image before calling consolidate."
|
|
114
163
|
)
|
|
115
164
|
|
|
116
|
-
self.
|
|
117
|
-
self.
|
|
118
|
-
|
|
165
|
+
if self.anndata_native:
|
|
166
|
+
self._table_backend.write_from_anndata(
|
|
167
|
+
self.anndata, metadata=self._meta.model_dump(exclude_none=True)
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
self._table_backend.write_from_dataframe(
|
|
171
|
+
self.dataframe, metadata=self._meta.model_dump(exclude_none=True)
|
|
172
|
+
)
|
ngio/tables/v1/_roi_table.py
CHANGED
|
@@ -4,8 +4,10 @@ This class follows the roi_table specification at:
|
|
|
4
4
|
https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
# Import _type to avoid name conflict with table.type
|
|
8
|
+
from builtins import type as _type
|
|
7
9
|
from collections.abc import Iterable
|
|
8
|
-
from typing import Literal
|
|
10
|
+
from typing import Generic, Literal, TypeVar
|
|
9
11
|
|
|
10
12
|
import pandas as pd
|
|
11
13
|
from pydantic import BaseModel
|
|
@@ -89,7 +91,7 @@ def _rois_to_dataframe(rois: dict[str, WorldCooROI], index_key: str) -> pd.DataF
|
|
|
89
91
|
return dataframe
|
|
90
92
|
|
|
91
93
|
|
|
92
|
-
class
|
|
94
|
+
class RoiTableV1Meta(BaseModel):
|
|
93
95
|
"""Metadata for the ROI table."""
|
|
94
96
|
|
|
95
97
|
fractal_table_version: Literal["1"] = "1"
|
|
@@ -97,17 +99,37 @@ class ROITableV1Meta(BaseModel):
|
|
|
97
99
|
backend: str | None = None
|
|
98
100
|
|
|
99
101
|
|
|
100
|
-
class
|
|
101
|
-
"""
|
|
102
|
+
class RegionMeta(BaseModel):
|
|
103
|
+
"""Metadata for the region."""
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
specification at:
|
|
105
|
-
https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
|
|
106
|
-
"""
|
|
105
|
+
path: str
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
|
|
108
|
+
class MaskingRoiTableV1Meta(BaseModel):
|
|
109
|
+
"""Metadata for the ROI table."""
|
|
110
|
+
|
|
111
|
+
fractal_table_version: Literal["1"] = "1"
|
|
112
|
+
type: Literal["masking_roi_table"] = "masking_roi_table"
|
|
113
|
+
backend: str | None = None
|
|
114
|
+
region: RegionMeta | None = None
|
|
115
|
+
instance_key: str = "label"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
_roi_meta = TypeVar("_roi_meta", RoiTableV1Meta, MaskingRoiTableV1Meta)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class _GenericRoiTableV1(Generic[_roi_meta]):
|
|
122
|
+
"""Class to a non-specific table."""
|
|
123
|
+
|
|
124
|
+
_meta: _roi_meta
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self, meta: _roi_meta | None = None, rois: Iterable[WorldCooROI] | None = None
|
|
128
|
+
) -> None:
|
|
109
129
|
"""Create a new ROI table."""
|
|
110
|
-
|
|
130
|
+
if meta is None:
|
|
131
|
+
raise NgioValueError("Metadata must be provided.")
|
|
132
|
+
self._meta = meta
|
|
111
133
|
self._table_backend = None
|
|
112
134
|
|
|
113
135
|
self._rois = {}
|
|
@@ -115,15 +137,30 @@ class RoiTableV1:
|
|
|
115
137
|
self.add(rois)
|
|
116
138
|
|
|
117
139
|
@staticmethod
|
|
118
|
-
def type() ->
|
|
140
|
+
def type() -> str:
|
|
119
141
|
"""Return the type of the table."""
|
|
120
|
-
|
|
142
|
+
raise NotImplementedError
|
|
121
143
|
|
|
122
144
|
@staticmethod
|
|
123
145
|
def version() -> Literal["1"]:
|
|
124
146
|
"""Return the version of the fractal table."""
|
|
125
147
|
return "1"
|
|
126
148
|
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _index_key() -> str:
|
|
151
|
+
"""Return the index key of the table."""
|
|
152
|
+
raise NotImplementedError
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _index_type() -> Literal["int", "str"]:
|
|
156
|
+
"""Return the index type of the table."""
|
|
157
|
+
raise NotImplementedError
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _meta_type() -> _type[_roi_meta]:
|
|
161
|
+
"""Return the metadata type of the table."""
|
|
162
|
+
raise NotImplementedError
|
|
163
|
+
|
|
127
164
|
@property
|
|
128
165
|
def backend_name(self) -> str | None:
|
|
129
166
|
"""Return the name of the backend."""
|
|
@@ -134,23 +171,23 @@ class RoiTableV1:
|
|
|
134
171
|
@classmethod
|
|
135
172
|
def _from_handler(
|
|
136
173
|
cls, handler: ZarrGroupHandler, backend_name: str | None = None
|
|
137
|
-
) -> "
|
|
174
|
+
) -> "_GenericRoiTableV1":
|
|
138
175
|
"""Create a new ROI table from a Zarr store."""
|
|
139
|
-
meta =
|
|
176
|
+
meta = cls._meta_type()(**handler.load_attrs())
|
|
140
177
|
|
|
141
178
|
if backend_name is None:
|
|
142
179
|
backend = ImplementedTableBackends().get_backend(
|
|
143
180
|
backend_name=meta.backend,
|
|
144
181
|
group_handler=handler,
|
|
145
|
-
index_key=
|
|
146
|
-
index_type=
|
|
182
|
+
index_key=cls._index_key(),
|
|
183
|
+
index_type=cls._index_type(),
|
|
147
184
|
)
|
|
148
185
|
else:
|
|
149
186
|
backend = ImplementedTableBackends().get_backend(
|
|
150
187
|
backend_name=backend_name,
|
|
151
188
|
group_handler=handler,
|
|
152
|
-
index_key=
|
|
153
|
-
index_type=
|
|
189
|
+
index_key=cls._index_key(),
|
|
190
|
+
index_type=cls._index_type(),
|
|
154
191
|
)
|
|
155
192
|
meta.backend = backend_name
|
|
156
193
|
|
|
@@ -159,6 +196,7 @@ class RoiTableV1:
|
|
|
159
196
|
"The backend does not implement the dataframe protocol."
|
|
160
197
|
)
|
|
161
198
|
|
|
199
|
+
# This will be implemented in the child classes
|
|
162
200
|
table = cls()
|
|
163
201
|
table._meta = meta
|
|
164
202
|
table._table_backend = backend
|
|
@@ -181,8 +219,8 @@ class RoiTableV1:
|
|
|
181
219
|
backend = ImplementedTableBackends().get_backend(
|
|
182
220
|
backend_name=backend_name,
|
|
183
221
|
group_handler=handler,
|
|
184
|
-
index_key=
|
|
185
|
-
index_type=
|
|
222
|
+
index_key=self._index_key(),
|
|
223
|
+
index_type=self._index_type(),
|
|
186
224
|
)
|
|
187
225
|
self._meta.backend = backend_name
|
|
188
226
|
self._table_backend = backend
|
|
@@ -191,12 +229,6 @@ class RoiTableV1:
|
|
|
191
229
|
"""List all ROIs in the table."""
|
|
192
230
|
return list(self._rois.values())
|
|
193
231
|
|
|
194
|
-
def get(self, roi_name: str) -> WorldCooROI:
|
|
195
|
-
"""Get an ROI from the table."""
|
|
196
|
-
if roi_name not in self._rois:
|
|
197
|
-
raise NgioValueError(f"ROI {roi_name} not found in the table.")
|
|
198
|
-
return self._rois[roi_name]
|
|
199
|
-
|
|
200
232
|
def add(self, roi: WorldCooROI | Iterable[WorldCooROI]) -> None:
|
|
201
233
|
"""Append ROIs to the current table."""
|
|
202
234
|
if isinstance(roi, WorldCooROI):
|
|
@@ -215,7 +247,7 @@ class RoiTableV1:
|
|
|
215
247
|
"Please add the table to a OME-Zarr Image before calling consolidate."
|
|
216
248
|
)
|
|
217
249
|
|
|
218
|
-
dataframe = _rois_to_dataframe(self._rois, index_key=
|
|
250
|
+
dataframe = _rois_to_dataframe(self._rois, index_key=self._index_key())
|
|
219
251
|
dataframe = validate_columns(
|
|
220
252
|
dataframe,
|
|
221
253
|
required_columns=REQUIRED_COLUMNS,
|
|
@@ -224,3 +256,89 @@ class RoiTableV1:
|
|
|
224
256
|
self._table_backend.write_from_dataframe(
|
|
225
257
|
dataframe, metadata=self._meta.model_dump(exclude_none=True)
|
|
226
258
|
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class RoiTableV1(_GenericRoiTableV1[RoiTableV1Meta]):
|
|
262
|
+
"""Class to handle fractal ROI tables.
|
|
263
|
+
|
|
264
|
+
To know more about the ROI table format, please refer to the
|
|
265
|
+
specification at:
|
|
266
|
+
https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(self, rois: Iterable[WorldCooROI] | None = None) -> None:
|
|
270
|
+
"""Create a new ROI table."""
|
|
271
|
+
super().__init__(RoiTableV1Meta(), rois)
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def type() -> Literal["roi_table"]:
|
|
275
|
+
"""Return the type of the table."""
|
|
276
|
+
return "roi_table"
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _index_key() -> str:
|
|
280
|
+
"""Return the index key of the table."""
|
|
281
|
+
return "FieldIndex"
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def _index_type() -> Literal["int", "str"]:
|
|
285
|
+
"""Return the index type of the table."""
|
|
286
|
+
return "str"
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _meta_type() -> _type[RoiTableV1Meta]:
|
|
290
|
+
"""Return the metadata type of the table."""
|
|
291
|
+
return RoiTableV1Meta
|
|
292
|
+
|
|
293
|
+
def get(self, roi_name: str) -> WorldCooROI:
|
|
294
|
+
"""Get an ROI from the table."""
|
|
295
|
+
if roi_name not in self._rois:
|
|
296
|
+
raise NgioValueError(f"ROI {roi_name} not found in the table.")
|
|
297
|
+
return self._rois[roi_name]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class MaskingROITableV1(_GenericRoiTableV1[MaskingRoiTableV1Meta]):
|
|
301
|
+
"""Class to handle fractal ROI tables.
|
|
302
|
+
|
|
303
|
+
To know more about the ROI table format, please refer to the
|
|
304
|
+
specification at:
|
|
305
|
+
https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
def __init__(
|
|
309
|
+
self,
|
|
310
|
+
rois: Iterable[WorldCooROI] | None = None,
|
|
311
|
+
reference_label: str | None = None,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Create a new ROI table."""
|
|
314
|
+
meta = MaskingRoiTableV1Meta()
|
|
315
|
+
if reference_label is not None:
|
|
316
|
+
meta.region = RegionMeta(path=reference_label)
|
|
317
|
+
super().__init__(meta, rois)
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def type() -> Literal["masking_roi_table"]:
|
|
321
|
+
"""Return the type of the table."""
|
|
322
|
+
return "masking_roi_table"
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def _index_key() -> str:
|
|
326
|
+
"""Return the index key of the table."""
|
|
327
|
+
return "label"
|
|
328
|
+
|
|
329
|
+
@staticmethod
|
|
330
|
+
def _index_type() -> Literal["int", "str"]:
|
|
331
|
+
"""Return the index type of the table."""
|
|
332
|
+
return "int"
|
|
333
|
+
|
|
334
|
+
@staticmethod
|
|
335
|
+
def _meta_type() -> _type[MaskingRoiTableV1Meta]:
|
|
336
|
+
"""Return the metadata type of the table."""
|
|
337
|
+
return MaskingRoiTableV1Meta
|
|
338
|
+
|
|
339
|
+
def get(self, label: int) -> WorldCooROI:
|
|
340
|
+
"""Get an ROI from the table."""
|
|
341
|
+
_label = str(label)
|
|
342
|
+
if _label not in self._rois:
|
|
343
|
+
raise KeyError(f"ROI {_label} not found in the table.")
|
|
344
|
+
return self._rois[_label]
|
ngio/utils/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from ngio.utils._errors import (
|
|
|
11
11
|
NgioValidationError,
|
|
12
12
|
NgioValueError,
|
|
13
13
|
)
|
|
14
|
+
from ngio.utils._fractal_fsspec_store import fractal_fsspec_store
|
|
14
15
|
from ngio.utils._logger import ngio_logger, set_logger_level
|
|
15
16
|
from ngio.utils._zarr_utils import (
|
|
16
17
|
AccessModeLiteral,
|
|
@@ -35,6 +36,8 @@ __all__ = [
|
|
|
35
36
|
"ZarrGroupHandler",
|
|
36
37
|
# Datasets
|
|
37
38
|
"download_ome_zarr_dataset",
|
|
39
|
+
# Fractal
|
|
40
|
+
"fractal_fsspec_store",
|
|
38
41
|
"list_ome_zarr_datasets",
|
|
39
42
|
# Logger
|
|
40
43
|
"ngio_logger",
|
ngio/utils/_datasets.py
CHANGED
|
@@ -4,6 +4,8 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import pooch
|
|
6
6
|
|
|
7
|
+
from ngio.utils._errors import NgioValueError
|
|
8
|
+
|
|
7
9
|
_ome_zarr_zoo = {
|
|
8
10
|
"CardiomyocyteTiny": {
|
|
9
11
|
"url": "https://zenodo.org/records/13305156/files/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr.zip",
|
|
@@ -27,7 +29,7 @@ def list_ome_zarr_datasets() -> list[str]:
|
|
|
27
29
|
|
|
28
30
|
def download_ome_zarr_dataset(
|
|
29
31
|
dataset_name: str,
|
|
30
|
-
download_dir: str = "data",
|
|
32
|
+
download_dir: str | Path = "data",
|
|
31
33
|
) -> Path:
|
|
32
34
|
"""Download an OME-Zarr dataset.
|
|
33
35
|
|
|
@@ -38,7 +40,7 @@ def download_ome_zarr_dataset(
|
|
|
38
40
|
download_dir (str): The download directory. Defaults to "data".
|
|
39
41
|
"""
|
|
40
42
|
if dataset_name not in _ome_zarr_zoo:
|
|
41
|
-
raise
|
|
43
|
+
raise NgioValueError(f"Dataset {dataset_name} not found in the OME-Zarr zoo.")
|
|
42
44
|
ome_zarr_url = _ome_zarr_zoo[dataset_name]
|
|
43
45
|
pooch.retrieve(
|
|
44
46
|
path=download_dir,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fsspec.implementations.http
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def fractal_fsspec_store(
|
|
5
|
+
url: str, fractal_token: str | None = None, client_kwargs: dict | None = None
|
|
6
|
+
) -> fsspec.mapping.FSMap:
|
|
7
|
+
"""Simple function to get an http fsspec store from a url."""
|
|
8
|
+
client_kwargs = {} if client_kwargs is None else client_kwargs
|
|
9
|
+
if fractal_token is not None:
|
|
10
|
+
client_kwargs["headers"] = {"Authorization": f"Bearer {fractal_token}"}
|
|
11
|
+
fs = fsspec.implementations.http.HTTPFileSystem(client_kwargs=client_kwargs)
|
|
12
|
+
store = fs.get_mapper(url)
|
|
13
|
+
return store
|
ngio/utils/_logger.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
+
from ngio.utils._errors import NgioValueError
|
|
4
|
+
|
|
3
5
|
# Configure the logger
|
|
4
6
|
ngio_logger = logging.getLogger("NgioLogger")
|
|
5
7
|
ngio_logger.setLevel(logging.ERROR)
|
|
@@ -24,6 +26,6 @@ def set_logger_level(level: str) -> None:
|
|
|
24
26
|
Must be one of "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".
|
|
25
27
|
"""
|
|
26
28
|
if level not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
|
27
|
-
raise
|
|
29
|
+
raise NgioValueError(f"Invalid log level: {level}")
|
|
28
30
|
|
|
29
31
|
ngio_logger.setLevel(level)
|
ngio/utils/_zarr_utils.py
CHANGED
|
@@ -131,7 +131,7 @@ class ZarrGroupHandler:
|
|
|
131
131
|
"The store needs to be a path to use the lock mechanism."
|
|
132
132
|
)
|
|
133
133
|
self._lock_path = f"{_store}.lock"
|
|
134
|
-
self._lock = FileLock(self._lock_path)
|
|
134
|
+
self._lock = FileLock(self._lock_path, timeout=10)
|
|
135
135
|
|
|
136
136
|
else:
|
|
137
137
|
self._lock_path = None
|
|
@@ -168,8 +168,13 @@ class ZarrGroupHandler:
|
|
|
168
168
|
return self._mode # type: ignore
|
|
169
169
|
|
|
170
170
|
@property
|
|
171
|
-
def lock(self) -> BaseFileLock
|
|
171
|
+
def lock(self) -> BaseFileLock:
|
|
172
172
|
"""Return the lock."""
|
|
173
|
+
if self._lock is None:
|
|
174
|
+
raise NgioValueError(
|
|
175
|
+
"The handler is not parallel safe. "
|
|
176
|
+
"Reopen the handler with parallel_safe=True."
|
|
177
|
+
)
|
|
173
178
|
return self._lock
|
|
174
179
|
|
|
175
180
|
@property
|
|
@@ -376,3 +381,21 @@ class ZarrGroupHandler:
|
|
|
376
381
|
return True, self.derive_handler(path)
|
|
377
382
|
except NgioError as e:
|
|
378
383
|
return False, e
|
|
384
|
+
|
|
385
|
+
def copy_handler(self, handler: "ZarrGroupHandler") -> None:
|
|
386
|
+
"""Copy the group to a new store."""
|
|
387
|
+
_, n_skipped, _ = zarr.copy_store(
|
|
388
|
+
source=self.group.store,
|
|
389
|
+
dest=handler.group.store,
|
|
390
|
+
source_path=self.group.path,
|
|
391
|
+
dest_path=handler.group.path,
|
|
392
|
+
if_exists="replace",
|
|
393
|
+
)
|
|
394
|
+
if n_skipped > 0:
|
|
395
|
+
raise NgioValueError(
|
|
396
|
+
f"Error copying group to {handler.full_path}, "
|
|
397
|
+
f"#{n_skipped} files where skipped."
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# %%
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ngio
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0b1
|
|
4
4
|
Summary: Next Generation file format IO
|
|
5
5
|
Project-URL: homepage, https://github.com/lorenzocerrone/ngio
|
|
6
6
|
Project-URL: repository, https://github.com/lorenzocerrone/ngio
|
|
@@ -29,6 +29,7 @@ Requires-Dist: requests
|
|
|
29
29
|
Requires-Dist: xarray
|
|
30
30
|
Requires-Dist: zarr<3
|
|
31
31
|
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: devtools; extra == 'dev'
|
|
32
33
|
Requires-Dist: matplotlib; extra == 'dev'
|
|
33
34
|
Requires-Dist: mypy; extra == 'dev'
|
|
34
35
|
Requires-Dist: napari; extra == 'dev'
|
|
@@ -38,6 +39,7 @@ Requires-Dist: pre-commit; extra == 'dev'
|
|
|
38
39
|
Requires-Dist: pyqt5; extra == 'dev'
|
|
39
40
|
Requires-Dist: rich; extra == 'dev'
|
|
40
41
|
Requires-Dist: ruff; extra == 'dev'
|
|
42
|
+
Requires-Dist: scikit-image; extra == 'dev'
|
|
41
43
|
Provides-Extra: docs
|
|
42
44
|
Requires-Dist: mkdocs; extra == 'docs'
|
|
43
45
|
Requires-Dist: mkdocs-autorefs; extra == 'docs'
|
|
@@ -50,6 +52,7 @@ Requires-Dist: scikit-image; extra == 'docs'
|
|
|
50
52
|
Provides-Extra: test
|
|
51
53
|
Requires-Dist: pytest; extra == 'test'
|
|
52
54
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
55
|
+
Requires-Dist: scikit-image; extra == 'test'
|
|
53
56
|
Description-Content-Type: text/markdown
|
|
54
57
|
|
|
55
58
|
# NGIO - Next Generation file format IO
|