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.
- ngio/__init__.py +31 -5
- ngio/common/__init__.py +44 -0
- ngio/common/_array_pipe.py +160 -0
- ngio/common/_axes_transforms.py +63 -0
- ngio/common/_common_types.py +5 -0
- ngio/common/_dimensions.py +113 -0
- ngio/common/_pyramid.py +222 -0
- ngio/{core/roi.py → common/_roi.py} +22 -23
- ngio/common/_slicer.py +97 -0
- ngio/{pipes/_zoom_utils.py → common/_zoom.py} +2 -78
- ngio/hcs/__init__.py +60 -0
- ngio/images/__init__.py +23 -0
- ngio/images/abstract_image.py +240 -0
- ngio/images/create.py +251 -0
- ngio/images/image.py +383 -0
- ngio/images/label.py +96 -0
- ngio/images/omezarr_container.py +512 -0
- ngio/ome_zarr_meta/__init__.py +35 -0
- ngio/ome_zarr_meta/_generic_handlers.py +320 -0
- ngio/ome_zarr_meta/_meta_handlers.py +142 -0
- ngio/ome_zarr_meta/ngio_specs/__init__.py +63 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +481 -0
- ngio/ome_zarr_meta/ngio_specs/_channels.py +378 -0
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +134 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +5 -0
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +434 -0
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +84 -0
- ngio/ome_zarr_meta/v04/__init__.py +11 -0
- ngio/ome_zarr_meta/v04/_meta_handlers.py +54 -0
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +412 -0
- ngio/tables/__init__.py +21 -5
- ngio/tables/_validators.py +192 -0
- ngio/tables/backends/__init__.py +8 -0
- ngio/tables/backends/_abstract_backend.py +71 -0
- ngio/tables/backends/_anndata_utils.py +194 -0
- ngio/tables/backends/_anndata_v1.py +75 -0
- ngio/tables/backends/_json_v1.py +56 -0
- ngio/tables/backends/_table_backends.py +102 -0
- ngio/tables/tables_container.py +300 -0
- ngio/tables/v1/__init__.py +6 -5
- ngio/tables/v1/_feature_table.py +161 -0
- ngio/tables/v1/_generic_table.py +99 -182
- ngio/tables/v1/_masking_roi_table.py +175 -0
- ngio/tables/v1/_roi_table.py +226 -0
- ngio/utils/__init__.py +23 -10
- ngio/utils/_datasets.py +51 -0
- ngio/utils/_errors.py +10 -4
- ngio/utils/_zarr_utils.py +378 -0
- {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/METADATA +18 -39
- ngio-0.2.0a1.dist-info/RECORD +53 -0
- ngio/core/__init__.py +0 -7
- ngio/core/dimensions.py +0 -122
- ngio/core/image_handler.py +0 -228
- ngio/core/image_like_handler.py +0 -549
- ngio/core/label_handler.py +0 -410
- ngio/core/ngff_image.py +0 -387
- ngio/core/utils.py +0 -287
- ngio/io/__init__.py +0 -19
- ngio/io/_zarr.py +0 -88
- ngio/io/_zarr_array_utils.py +0 -0
- ngio/io/_zarr_group_utils.py +0 -60
- ngio/iterators/__init__.py +0 -1
- ngio/ngff_meta/__init__.py +0 -27
- ngio/ngff_meta/fractal_image_meta.py +0 -1267
- ngio/ngff_meta/meta_handler.py +0 -92
- ngio/ngff_meta/utils.py +0 -235
- ngio/ngff_meta/v04/__init__.py +0 -6
- ngio/ngff_meta/v04/specs.py +0 -158
- ngio/ngff_meta/v04/zarr_utils.py +0 -376
- ngio/pipes/__init__.py +0 -7
- ngio/pipes/_slicer_transforms.py +0 -176
- ngio/pipes/_transforms.py +0 -33
- ngio/pipes/data_pipe.py +0 -52
- ngio/tables/_ad_reader.py +0 -80
- ngio/tables/_utils.py +0 -301
- ngio/tables/tables_group.py +0 -252
- ngio/tables/v1/feature_tables.py +0 -182
- ngio/tables/v1/masking_roi_tables.py +0 -243
- ngio/tables/v1/roi_tables.py +0 -285
- ngio/utils/_common_types.py +0 -5
- ngio/utils/_pydantic_utils.py +0 -52
- ngio-0.1.6.dist-info/RECORD +0 -44
- {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/WHEEL +0 -0
- {ngio-0.1.6.dist-info → ngio-0.2.0a1.dist-info}/licenses/LICENSE +0 -0
ngio/tables/_ad_reader.py
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
4
|
-
|
|
5
|
-
import zarr
|
|
6
|
-
from anndata import AnnData
|
|
7
|
-
from anndata._io.specs import read_elem
|
|
8
|
-
from anndata._io.utils import _read_legacy_raw
|
|
9
|
-
from anndata._io.zarr import read_dataframe
|
|
10
|
-
from anndata.compat import _clean_uns
|
|
11
|
-
from anndata.experimental import read_dispatched
|
|
12
|
-
|
|
13
|
-
from ngio.io import open_group_wrapper
|
|
14
|
-
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from collections.abc import Callable
|
|
17
|
-
|
|
18
|
-
from ngio.io import StoreOrGroup
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def custom_read_zarr(store: StoreOrGroup) -> AnnData:
|
|
22
|
-
"""Read from a hierarchical Zarr array store.
|
|
23
|
-
|
|
24
|
-
# Implementation originally from https://github.com/scverse/anndata/blob/main/src/anndata/_io/zarr.py
|
|
25
|
-
# Original implementation would not work with remote storages so we had to copy it
|
|
26
|
-
# here and slightly modified it to work with remote storages.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
store (StoreOrGroup): A store or group to read the AnnData from.
|
|
30
|
-
"""
|
|
31
|
-
group = open_group_wrapper(store=store, mode="r")
|
|
32
|
-
|
|
33
|
-
# Read with handling for backwards compat
|
|
34
|
-
def callback(func: Callable, elem_name: str, elem: Any, iospec: Any) -> Any:
|
|
35
|
-
if iospec.encoding_type == "anndata" or elem_name.endswith("/"):
|
|
36
|
-
ad_kwargs = {}
|
|
37
|
-
# Some of these elem fail on https
|
|
38
|
-
# So we only include the ones that are strictly necessary
|
|
39
|
-
# for fractal tables
|
|
40
|
-
base_elem = [
|
|
41
|
-
"X",
|
|
42
|
-
# "layers",
|
|
43
|
-
"obs",
|
|
44
|
-
# "obsm",
|
|
45
|
-
# "obsp",
|
|
46
|
-
# "uns",
|
|
47
|
-
"var",
|
|
48
|
-
# "varm",
|
|
49
|
-
# "varp",
|
|
50
|
-
]
|
|
51
|
-
# This fails on some https
|
|
52
|
-
# base_elem += list(elem.keys())
|
|
53
|
-
for k in set(base_elem):
|
|
54
|
-
v = elem.get(k)
|
|
55
|
-
if v is not None and not k.startswith("raw."):
|
|
56
|
-
ad_kwargs[k] = read_dispatched(v, callback)
|
|
57
|
-
return AnnData(**ad_kwargs)
|
|
58
|
-
|
|
59
|
-
elif elem_name.startswith("/raw."):
|
|
60
|
-
return None
|
|
61
|
-
elif elem_name in {"/obs", "/var"}:
|
|
62
|
-
return read_dataframe(elem)
|
|
63
|
-
elif elem_name == "/raw":
|
|
64
|
-
# Backwards compat
|
|
65
|
-
return _read_legacy_raw(group, func(elem), read_dataframe, func)
|
|
66
|
-
return func(elem)
|
|
67
|
-
|
|
68
|
-
adata = read_dispatched(group, callback=callback)
|
|
69
|
-
|
|
70
|
-
# Backwards compat (should figure out which version)
|
|
71
|
-
if "raw.X" in group:
|
|
72
|
-
raw = AnnData(**_read_legacy_raw(group, adata.raw, read_dataframe, read_elem))
|
|
73
|
-
raw.obs_names = adata.obs_names
|
|
74
|
-
adata.raw = raw
|
|
75
|
-
|
|
76
|
-
# Backwards compat for <0.7
|
|
77
|
-
if isinstance(group["obs"], zarr.Array):
|
|
78
|
-
_clean_uns(adata)
|
|
79
|
-
|
|
80
|
-
return adata
|
ngio/tables/_utils.py
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
2
|
-
from typing import Literal
|
|
3
|
-
|
|
4
|
-
import anndata as ad
|
|
5
|
-
import numpy as np
|
|
6
|
-
import pandas as pd
|
|
7
|
-
import pandas.api.types as ptypes
|
|
8
|
-
|
|
9
|
-
from ngio.utils import NgioTableValidationError
|
|
10
|
-
|
|
11
|
-
Validator = Callable[[pd.DataFrame], pd.DataFrame]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _check_for_mixed_types(series: pd.Series) -> None:
|
|
15
|
-
"""Check if the column has mixed types."""
|
|
16
|
-
if series.apply(type).nunique() > 1:
|
|
17
|
-
raise NgioTableValidationError(
|
|
18
|
-
f"Column {series.name} has mixed types: "
|
|
19
|
-
f"{series.apply(type).unique()}. "
|
|
20
|
-
"Type of all elements must be the same."
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _check_for_supported_types(series: pd.Series) -> Literal["str", "int", "numeric"]:
|
|
25
|
-
"""Check if the column has supported types."""
|
|
26
|
-
if ptypes.is_string_dtype(series):
|
|
27
|
-
return "str"
|
|
28
|
-
if ptypes.is_integer_dtype(series):
|
|
29
|
-
return "int"
|
|
30
|
-
if ptypes.is_numeric_dtype(series):
|
|
31
|
-
return "numeric"
|
|
32
|
-
raise NgioTableValidationError(
|
|
33
|
-
f"Column {series.name} has unsupported type: {series.dtype}."
|
|
34
|
-
" Supported types are string and numerics."
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _check_index_key(
|
|
39
|
-
table_df: pd.DataFrame, index_key: str, index_type: Literal["str", "int"]
|
|
40
|
-
) -> pd.DataFrame:
|
|
41
|
-
"""Check if the index_key correctness.
|
|
42
|
-
|
|
43
|
-
- Check if the index_key is present in the data frame.
|
|
44
|
-
(If the index_key is a column in the DataFrame, it is set as the index)
|
|
45
|
-
- Check if the index_key is of the correct type.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
table_df (pd.DataFrame): The DataFrame to validate.
|
|
49
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
50
|
-
index_type (str): The type of the index column in the DataFrame.
|
|
51
|
-
Either 'str' or 'int'. Default is 'int'.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
pd.DataFrame: The validated DataFrame.
|
|
55
|
-
"""
|
|
56
|
-
columns = table_df.columns
|
|
57
|
-
if index_key in columns:
|
|
58
|
-
table_df = table_df.set_index(index_key)
|
|
59
|
-
|
|
60
|
-
if table_df.index.name != index_key:
|
|
61
|
-
raise NgioTableValidationError(
|
|
62
|
-
f"index_key: {index_key} not found in data frame"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
if index_type == "str":
|
|
66
|
-
if ptypes.is_integer_dtype(table_df.index):
|
|
67
|
-
# Convert the int index to string is generally safe
|
|
68
|
-
table_df.index = table_df.index.astype(str)
|
|
69
|
-
|
|
70
|
-
if not ptypes.is_string_dtype(table_df.index):
|
|
71
|
-
raise NgioTableValidationError(
|
|
72
|
-
f"index_key {index_key} must be of string type"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
elif index_type == "int":
|
|
76
|
-
if ptypes.is_string_dtype(table_df.index):
|
|
77
|
-
# Try to convert the string index to int
|
|
78
|
-
try:
|
|
79
|
-
table_df.index = table_df.index.astype(int)
|
|
80
|
-
except ValueError as e:
|
|
81
|
-
if "invalid literal for int() with base 10" in str(e):
|
|
82
|
-
raise NgioTableValidationError(
|
|
83
|
-
f"index_key {index_key} must be of "
|
|
84
|
-
"integer type, but found string. We "
|
|
85
|
-
"tried implicit conversion failed."
|
|
86
|
-
) from None
|
|
87
|
-
else:
|
|
88
|
-
raise e from e
|
|
89
|
-
|
|
90
|
-
if not ptypes.is_integer_dtype(table_df.index):
|
|
91
|
-
raise NgioTableValidationError(
|
|
92
|
-
f"index_key {index_key} must be of integer type"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
else:
|
|
96
|
-
raise NgioTableValidationError(f"index_type {index_type} not recognized")
|
|
97
|
-
|
|
98
|
-
return table_df
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def validate_table(
|
|
102
|
-
table_df: pd.DataFrame,
|
|
103
|
-
index_key: str,
|
|
104
|
-
index_type: Literal["str", "int"],
|
|
105
|
-
validators: list[Validator] | None,
|
|
106
|
-
) -> pd.DataFrame:
|
|
107
|
-
"""Validate the table DataFrame.
|
|
108
|
-
|
|
109
|
-
- Check if the index_key is present in the data frame.
|
|
110
|
-
(If the index_key is a column in the DataFrame, it is set as the index)
|
|
111
|
-
- Check if the index_key is of the correct type.
|
|
112
|
-
- Apply all provided optional validators.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
table_df (pd.DataFrame): The DataFrame to validate.
|
|
116
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
117
|
-
index_type (str): The type of the index column in the DataFrame.
|
|
118
|
-
validators (list[Validator]): A list of functions to further validate table.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
pd.DataFrame: The validated DataFrame.
|
|
122
|
-
"""
|
|
123
|
-
table_df = _check_index_key(table_df, index_key, index_type)
|
|
124
|
-
|
|
125
|
-
if validators is None:
|
|
126
|
-
return table_df
|
|
127
|
-
|
|
128
|
-
# Apply all provided validators
|
|
129
|
-
for validator in validators:
|
|
130
|
-
table_df = validator(table_df)
|
|
131
|
-
|
|
132
|
-
return table_df
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def table_df_to_ad(
|
|
136
|
-
table_df: pd.DataFrame,
|
|
137
|
-
index_key: str,
|
|
138
|
-
index_type: Literal["str", "int"] = "int",
|
|
139
|
-
validators: list[Validator] | None = None,
|
|
140
|
-
) -> ad.AnnData:
|
|
141
|
-
"""Convert a table DataFrame to an AnnData object.
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
table_df (pd.DataFrame): A pandas DataFrame representing a fractal table.
|
|
145
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
146
|
-
index_type (str): The type of the index column in the DataFrame.
|
|
147
|
-
Either 'str' or 'int'. Default is 'int'.
|
|
148
|
-
validators (list[Validator]): A list of functions to further validate the table.
|
|
149
|
-
"""
|
|
150
|
-
# Check if the index_key is present in the data frame + optional validations
|
|
151
|
-
table_df = validate_table(
|
|
152
|
-
table_df=table_df,
|
|
153
|
-
index_key=index_key,
|
|
154
|
-
index_type=index_type,
|
|
155
|
-
validators=validators,
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
# DO NOT SKIP
|
|
159
|
-
# Convert the index to string ALWAYS to avoid casting issues in AnnData
|
|
160
|
-
table_df.index = table_df.index.astype(str)
|
|
161
|
-
|
|
162
|
-
str_columns, int_columns, num_columns = [], [], []
|
|
163
|
-
for c_name in table_df.columns:
|
|
164
|
-
column_df = table_df[c_name]
|
|
165
|
-
_check_for_mixed_types(column_df) # Mixed types are not allowed in the table
|
|
166
|
-
c_type = _check_for_supported_types(
|
|
167
|
-
column_df
|
|
168
|
-
) # Only string and numeric types are allowed
|
|
169
|
-
|
|
170
|
-
if c_type == "str":
|
|
171
|
-
str_columns.append(c_name)
|
|
172
|
-
|
|
173
|
-
elif c_type == "int":
|
|
174
|
-
int_columns.append(c_name)
|
|
175
|
-
|
|
176
|
-
elif c_type == "numeric":
|
|
177
|
-
num_columns.append(c_name)
|
|
178
|
-
|
|
179
|
-
# Converting all observations to string
|
|
180
|
-
obs_df = table_df[str_columns + int_columns]
|
|
181
|
-
obs_df.index = table_df.index
|
|
182
|
-
|
|
183
|
-
x_df = table_df[num_columns]
|
|
184
|
-
|
|
185
|
-
if x_df.dtypes.nunique() > 1:
|
|
186
|
-
x_df = x_df.astype("float64")
|
|
187
|
-
|
|
188
|
-
if x_df.empty:
|
|
189
|
-
# If there are no numeric columns, create an empty array
|
|
190
|
-
# to avoid AnnData failing to create the object
|
|
191
|
-
x_df = np.zeros((0, 0), dtype="float64")
|
|
192
|
-
|
|
193
|
-
return ad.AnnData(X=x_df, obs=obs_df)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def table_ad_to_df(
|
|
197
|
-
table_ad: ad.AnnData,
|
|
198
|
-
index_key: str = "label",
|
|
199
|
-
index_type: Literal["str", "int"] = "int",
|
|
200
|
-
validators: list[Validator] | None = None,
|
|
201
|
-
validate_index_name: bool = False,
|
|
202
|
-
) -> pd.DataFrame:
|
|
203
|
-
"""Convert a AnnData object representing a fractal table to a pandas DataFrame.
|
|
204
|
-
|
|
205
|
-
Args:
|
|
206
|
-
table_ad (ad.AnnData): An AnnData object representing a fractal table.
|
|
207
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
208
|
-
Default is 'label'.
|
|
209
|
-
index_type (str): The type of the index column in the DataFrame.
|
|
210
|
-
Either 'str' or 'int'. Default is 'int'.
|
|
211
|
-
validators (list[Validator]): A list of functions to further validate the table.
|
|
212
|
-
validate_index_name (bool): If True, the index name is validated.
|
|
213
|
-
"""
|
|
214
|
-
table_df = table_ad.to_df()
|
|
215
|
-
table_df[table_ad.obs_keys()] = table_ad.obs
|
|
216
|
-
|
|
217
|
-
# Set the index of the DataFrame
|
|
218
|
-
if index_key in table_df.columns:
|
|
219
|
-
table_df = table_df.set_index(index_key)
|
|
220
|
-
elif table_ad.obs.index.name is not None:
|
|
221
|
-
if validate_index_name:
|
|
222
|
-
if table_ad.obs.index.name != index_key:
|
|
223
|
-
raise NgioTableValidationError(
|
|
224
|
-
f"Index key {index_key} not found in AnnData object."
|
|
225
|
-
)
|
|
226
|
-
table_df.index = table_ad.obs.index
|
|
227
|
-
elif table_ad.obs.index.name is None:
|
|
228
|
-
table_df.index = table_ad.obs.index
|
|
229
|
-
table_df.index.name = index_key
|
|
230
|
-
else:
|
|
231
|
-
raise NgioTableValidationError(
|
|
232
|
-
f"Index key {index_key} not found in AnnData object."
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
table_df = validate_table(
|
|
236
|
-
table_df=table_df,
|
|
237
|
-
index_key=index_key,
|
|
238
|
-
index_type=index_type,
|
|
239
|
-
validators=validators,
|
|
240
|
-
)
|
|
241
|
-
return table_df
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
####################################################################################################
|
|
245
|
-
#
|
|
246
|
-
# Common table validators
|
|
247
|
-
#
|
|
248
|
-
####################################################################################################
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def validate_columns(
|
|
252
|
-
table_df: pd.DataFrame,
|
|
253
|
-
required_columns: list[str],
|
|
254
|
-
optional_columns: list[str] | None = None,
|
|
255
|
-
) -> pd.DataFrame:
|
|
256
|
-
"""Validate the columns headers of the table.
|
|
257
|
-
|
|
258
|
-
If a required column is missing, a TableValidationError is raised.
|
|
259
|
-
If a list of optional columns is provided, only required and optional columns are
|
|
260
|
-
allowed in the table.
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
table_df (pd.DataFrame): The DataFrame to validate.
|
|
264
|
-
required_columns (list[str]): A list of required columns.
|
|
265
|
-
optional_columns (list[str] | None): A list of optional columns.
|
|
266
|
-
Default is None.
|
|
267
|
-
|
|
268
|
-
Returns:
|
|
269
|
-
pd.DataFrame: The validated DataFrame.
|
|
270
|
-
"""
|
|
271
|
-
table_header = table_df.columns
|
|
272
|
-
for column in required_columns:
|
|
273
|
-
if column not in table_header:
|
|
274
|
-
raise NgioTableValidationError(
|
|
275
|
-
f"Could not find required column: {column} in the table"
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
if optional_columns is None:
|
|
279
|
-
return table_df
|
|
280
|
-
|
|
281
|
-
possible_columns = [*required_columns, *optional_columns]
|
|
282
|
-
for column in table_header:
|
|
283
|
-
if column not in possible_columns:
|
|
284
|
-
raise NgioTableValidationError(
|
|
285
|
-
f"Could not find column: {column} in the list of possible columns. ",
|
|
286
|
-
f"Possible columns are: {possible_columns}",
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
return table_df
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def validate_unique_index(table_df: pd.DataFrame) -> pd.DataFrame:
|
|
293
|
-
"""Validate that the index of the table is unique."""
|
|
294
|
-
if table_df.index.is_unique:
|
|
295
|
-
return table_df
|
|
296
|
-
|
|
297
|
-
# Find the duplicates
|
|
298
|
-
duplicates = table_df.index[table_df.index.duplicated()].tolist()
|
|
299
|
-
raise NgioTableValidationError(
|
|
300
|
-
f"Index of the table contains duplicates values. Duplicate: {duplicates}"
|
|
301
|
-
)
|
ngio/tables/tables_group.py
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
"""Module for handling the /tables group in an OME-NGFF file."""
|
|
2
|
-
|
|
3
|
-
from typing import Literal
|
|
4
|
-
from warnings import warn
|
|
5
|
-
|
|
6
|
-
import zarr
|
|
7
|
-
from pydantic import ValidationError
|
|
8
|
-
|
|
9
|
-
from ngio.core.utils import State
|
|
10
|
-
from ngio.io import AccessModeLiteral, StoreLike
|
|
11
|
-
from ngio.tables.v1 import FeatureTableV1, MaskingROITableV1, ROITableV1
|
|
12
|
-
from ngio.utils import ngio_logger
|
|
13
|
-
from ngio.utils._pydantic_utils import BaseWithExtraFields
|
|
14
|
-
|
|
15
|
-
ROITable = ROITableV1
|
|
16
|
-
IMPLEMENTED_ROI_TABLES = {"1": ROITableV1}
|
|
17
|
-
|
|
18
|
-
FeatureTable = FeatureTableV1
|
|
19
|
-
IMPLEMENTED_FEATURE_TABLES = {"1": FeatureTableV1}
|
|
20
|
-
|
|
21
|
-
MaskingROITable = MaskingROITableV1
|
|
22
|
-
IMPLEMENTED_MASKING_ROI_TABLES = {"1": MaskingROITableV1}
|
|
23
|
-
|
|
24
|
-
Table = ROITable | FeatureTable | MaskingROITable
|
|
25
|
-
|
|
26
|
-
TableType = Literal["roi_table", "feature_table", "masking_roi_table"]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class CommonMeta(BaseWithExtraFields):
|
|
30
|
-
"""Common metadata for all tables."""
|
|
31
|
-
|
|
32
|
-
type: TableType
|
|
33
|
-
fractal_table_version: str = "1"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _find_table_impl(
|
|
37
|
-
table_type: TableType,
|
|
38
|
-
version: str,
|
|
39
|
-
) -> Table:
|
|
40
|
-
"""Find the type of table in the group."""
|
|
41
|
-
if table_type == "roi_table":
|
|
42
|
-
if version not in IMPLEMENTED_ROI_TABLES:
|
|
43
|
-
raise ValueError(f"ROI Table version {version} not implemented.")
|
|
44
|
-
return IMPLEMENTED_ROI_TABLES[version]
|
|
45
|
-
|
|
46
|
-
elif table_type == "feature_table":
|
|
47
|
-
if version not in IMPLEMENTED_FEATURE_TABLES:
|
|
48
|
-
raise ValueError(f"Feature Table version {version} not implemented.")
|
|
49
|
-
return IMPLEMENTED_FEATURE_TABLES[version]
|
|
50
|
-
|
|
51
|
-
elif table_type == "masking_roi_table":
|
|
52
|
-
if version not in IMPLEMENTED_MASKING_ROI_TABLES:
|
|
53
|
-
raise ValueError(f"Masking ROI Table version {version} not implemented.")
|
|
54
|
-
return IMPLEMENTED_MASKING_ROI_TABLES[version]
|
|
55
|
-
|
|
56
|
-
else:
|
|
57
|
-
raise ValueError(f"Table type {table_type} not recognized.")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _get_table_impl(
|
|
61
|
-
group: zarr.Group,
|
|
62
|
-
name: str,
|
|
63
|
-
validate_metadata: bool = True,
|
|
64
|
-
table_type: TableType | None = None,
|
|
65
|
-
validate_table: bool = True,
|
|
66
|
-
index_key: str | None = None,
|
|
67
|
-
) -> Table:
|
|
68
|
-
if validate_metadata:
|
|
69
|
-
common_meta = CommonMeta(**group.attrs)
|
|
70
|
-
table_type = common_meta.type
|
|
71
|
-
else:
|
|
72
|
-
common_meta = CommonMeta.model_construct(**group.attrs)
|
|
73
|
-
if table_type is None:
|
|
74
|
-
raise ValueError(
|
|
75
|
-
"Table type must be provided if metadata is not validated."
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
version = common_meta.fractal_table_version
|
|
79
|
-
return _find_table_impl(table_type=table_type, version=version)(
|
|
80
|
-
group=group,
|
|
81
|
-
name=name,
|
|
82
|
-
validate_metadata=validate_metadata,
|
|
83
|
-
validate_table=validate_table,
|
|
84
|
-
index_key=index_key,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class TableGroup:
|
|
89
|
-
"""A class to handle the /labels group in an OME-NGFF file."""
|
|
90
|
-
|
|
91
|
-
def __init__(
|
|
92
|
-
self, group: StoreLike | zarr.Group, mode: AccessModeLiteral = "r+"
|
|
93
|
-
) -> None:
|
|
94
|
-
"""Initialize the LabelGroupHandler."""
|
|
95
|
-
self._mode = mode
|
|
96
|
-
if not isinstance(group, zarr.Group):
|
|
97
|
-
group = zarr.open_group(group, mode=self._mode)
|
|
98
|
-
|
|
99
|
-
table_group = group.get("tables", None)
|
|
100
|
-
|
|
101
|
-
if table_group is None and not group.read_only:
|
|
102
|
-
table_group = group.create_group("tables")
|
|
103
|
-
table_group.attrs["tables"] = []
|
|
104
|
-
|
|
105
|
-
assert isinstance(table_group, zarr.Group) or table_group is None
|
|
106
|
-
self._table_group = table_group
|
|
107
|
-
|
|
108
|
-
if table_group is None or table_group.read_only:
|
|
109
|
-
self._state = State.MEMORY
|
|
110
|
-
else:
|
|
111
|
-
self._state = State.CONSOLIDATED
|
|
112
|
-
|
|
113
|
-
self._virtual_tables: list[Table] = []
|
|
114
|
-
|
|
115
|
-
def _validate_list_of_tables(self, list_of_tables: list[str]) -> None:
|
|
116
|
-
"""Validate the list of tables.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
list_of_tables (list[str]): The list of tables to validate.
|
|
120
|
-
"""
|
|
121
|
-
if self._table_group is None:
|
|
122
|
-
return None
|
|
123
|
-
|
|
124
|
-
for table_name in list_of_tables:
|
|
125
|
-
table = self._table_group.get(table_name, None)
|
|
126
|
-
if table is None:
|
|
127
|
-
ngio_logger.warning(
|
|
128
|
-
f"Table {table_name} not found in the group. "
|
|
129
|
-
"Consider removing it from the list of tables."
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
def _get_list_of_tables(self) -> list[str]:
|
|
133
|
-
"""Return the list of tables."""
|
|
134
|
-
if self._table_group is None:
|
|
135
|
-
return []
|
|
136
|
-
|
|
137
|
-
list_of_tables = self._table_group.attrs.get("tables", [])
|
|
138
|
-
self._validate_list_of_tables(list_of_tables)
|
|
139
|
-
assert isinstance(list_of_tables, list)
|
|
140
|
-
assert all(isinstance(table_name, str) for table_name in list_of_tables)
|
|
141
|
-
return list_of_tables
|
|
142
|
-
|
|
143
|
-
def list(
|
|
144
|
-
self,
|
|
145
|
-
table_type: TableType | None = None,
|
|
146
|
-
) -> list[str]:
|
|
147
|
-
"""List all labels in the group.
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
table_type (str): The type of table to list.
|
|
151
|
-
If None, all tables are listed.
|
|
152
|
-
Allowed values are: 'roi_table', 'feature_table', 'masking_roi_table'.
|
|
153
|
-
"""
|
|
154
|
-
if self._table_group is None:
|
|
155
|
-
return []
|
|
156
|
-
|
|
157
|
-
list_of_tables = self._get_list_of_tables()
|
|
158
|
-
self._validate_list_of_tables(list_of_tables=list_of_tables)
|
|
159
|
-
|
|
160
|
-
all_table_types = ["roi_table", "feature_table", "masking_roi_table"]
|
|
161
|
-
|
|
162
|
-
if table_type is None:
|
|
163
|
-
return list_of_tables
|
|
164
|
-
|
|
165
|
-
else:
|
|
166
|
-
if table_type not in all_table_types:
|
|
167
|
-
raise ValueError(
|
|
168
|
-
f"Table type {table_type} not recognized. "
|
|
169
|
-
f" Allowed values are: {all_table_types}"
|
|
170
|
-
)
|
|
171
|
-
list_of_typed_tables = []
|
|
172
|
-
for table_name in list_of_tables:
|
|
173
|
-
table = self._table_group[table_name]
|
|
174
|
-
try:
|
|
175
|
-
common_meta = CommonMeta(**table.attrs)
|
|
176
|
-
if common_meta.type == table_type:
|
|
177
|
-
list_of_typed_tables.append(table_name)
|
|
178
|
-
except ValidationError:
|
|
179
|
-
warn(
|
|
180
|
-
f"Table {table_name} metadata is not correctly formatted.",
|
|
181
|
-
stacklevel=1,
|
|
182
|
-
)
|
|
183
|
-
return list_of_typed_tables
|
|
184
|
-
|
|
185
|
-
def get_table(
|
|
186
|
-
self,
|
|
187
|
-
name: str,
|
|
188
|
-
table_type: TableType | None = None,
|
|
189
|
-
validate_metadata: bool = True,
|
|
190
|
-
validate_table: bool = True,
|
|
191
|
-
index_key: str | None = None,
|
|
192
|
-
) -> Table:
|
|
193
|
-
"""Get a label from the group.
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
name (str): The name of the table to get.
|
|
197
|
-
table_type (str): The type of table to get.
|
|
198
|
-
If None, all the table type will be inferred from the metadata.
|
|
199
|
-
Allowed values are: 'roi_table', 'feature_table', 'masking_roi_table'.
|
|
200
|
-
validate_metadata (bool): Whether to validate the metadata of the table.
|
|
201
|
-
validate_table (bool): Whether to validate the table.
|
|
202
|
-
index_key (str): The column name to use as the index of the DataFrame.
|
|
203
|
-
This is usually defined in the metadata of the table, if given here,
|
|
204
|
-
it will overwrite the metadata.
|
|
205
|
-
"""
|
|
206
|
-
if self._table_group is None:
|
|
207
|
-
raise ValueError("No tables group found in the group.")
|
|
208
|
-
|
|
209
|
-
list_of_tables = self._get_list_of_tables()
|
|
210
|
-
if name not in list_of_tables:
|
|
211
|
-
raise ValueError(f"Table {name} not found in the group.")
|
|
212
|
-
|
|
213
|
-
return _get_table_impl(
|
|
214
|
-
group=self._table_group[name],
|
|
215
|
-
name=name,
|
|
216
|
-
validate_metadata=validate_metadata,
|
|
217
|
-
table_type=table_type,
|
|
218
|
-
validate_table=validate_table,
|
|
219
|
-
index_key=index_key,
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
def new(
|
|
223
|
-
self,
|
|
224
|
-
name: str,
|
|
225
|
-
table_type: TableType = "roi_table",
|
|
226
|
-
overwrite: bool = False,
|
|
227
|
-
version: str = "1",
|
|
228
|
-
**type_specific_kwargs: dict,
|
|
229
|
-
) -> Table:
|
|
230
|
-
"""Add a new table to the group."""
|
|
231
|
-
if self._table_group is None:
|
|
232
|
-
raise ValueError("No tables group found in the group.")
|
|
233
|
-
|
|
234
|
-
list_of_tables = self._get_list_of_tables()
|
|
235
|
-
if not overwrite and name in list_of_tables:
|
|
236
|
-
raise ValueError(f"Table {name} already exists in the group.")
|
|
237
|
-
|
|
238
|
-
if overwrite and name in list_of_tables:
|
|
239
|
-
list_of_tables.remove(name)
|
|
240
|
-
|
|
241
|
-
table_impl = _find_table_impl(table_type=table_type, version=version)
|
|
242
|
-
new_table = table_impl._new(
|
|
243
|
-
parent_group=self._table_group,
|
|
244
|
-
name=name,
|
|
245
|
-
overwrite=overwrite,
|
|
246
|
-
**type_specific_kwargs,
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
self._table_group.attrs["tables"] = [*list_of_tables, name]
|
|
250
|
-
|
|
251
|
-
assert isinstance(new_table, ROITable | FeatureTable | MaskingROITable)
|
|
252
|
-
return new_table
|