ngio 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngio/common/_pyramid.py +5 -1
- ngio/hcs/plate.py +121 -2
- ngio/images/abstract_image.py +1 -0
- ngio/images/image.py +42 -0
- ngio/images/label.py +15 -7
- ngio/images/ome_zarr_container.py +4 -7
- ngio/tables/_validators.py +1 -83
- ngio/tables/backends/__init__.py +27 -1
- ngio/tables/backends/_abstract_backend.py +207 -22
- ngio/tables/backends/_anndata_utils.py +3 -109
- ngio/tables/backends/_anndata_v1.py +43 -46
- ngio/tables/backends/_csv_v1.py +162 -0
- ngio/tables/backends/_json_v1.py +54 -18
- ngio/tables/backends/_table_backends.py +98 -18
- ngio/tables/backends/_utils.py +458 -0
- ngio/tables/tables_container.py +3 -1
- ngio/tables/v1/_feature_table.py +20 -11
- ngio/tables/v1/_generic_table.py +20 -15
- ngio/tables/v1/_roi_table.py +7 -9
- ngio/utils/_zarr_utils.py +46 -32
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/METADATA +3 -1
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/RECORD +24 -22
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/WHEEL +0 -0
- {ngio-0.2.2.dist-info → ngio-0.2.3.dist-info}/licenses/LICENSE +0 -0
ngio/common/_pyramid.py
CHANGED
|
@@ -190,12 +190,16 @@ def init_empty_pyramid(
|
|
|
190
190
|
"The shape and chunks must have the same number of dimensions."
|
|
191
191
|
)
|
|
192
192
|
|
|
193
|
+
if chunks is not None:
|
|
194
|
+
chunks = [min(c, s) for c, s in zip(chunks, ref_shape, strict=True)]
|
|
195
|
+
|
|
193
196
|
if len(ref_shape) != len(scaling_factors):
|
|
194
197
|
raise NgioValueError(
|
|
195
198
|
"The shape and scaling factor must have the same number of dimensions."
|
|
196
199
|
)
|
|
197
200
|
|
|
198
|
-
root_group
|
|
201
|
+
root_group = open_group_wrapper(store, mode=mode)
|
|
202
|
+
|
|
199
203
|
for path in paths:
|
|
200
204
|
if any(s < 1 for s in ref_shape):
|
|
201
205
|
raise NgioValueError(
|
ngio/hcs/plate.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""A module for handling the Plate Collection in an OME-Zarr file."""
|
|
2
2
|
|
|
3
|
+
from typing import Literal, overload
|
|
4
|
+
|
|
3
5
|
from ngio.images import OmeZarrContainer
|
|
4
6
|
from ngio.ome_zarr_meta import (
|
|
5
7
|
ImageInWellPath,
|
|
@@ -12,7 +14,29 @@ from ngio.ome_zarr_meta import (
|
|
|
12
14
|
get_well_meta_handler,
|
|
13
15
|
path_in_well_validation,
|
|
14
16
|
)
|
|
15
|
-
from ngio.
|
|
17
|
+
from ngio.tables import (
|
|
18
|
+
FeatureTable,
|
|
19
|
+
GenericRoiTable,
|
|
20
|
+
MaskingRoiTable,
|
|
21
|
+
RoiTable,
|
|
22
|
+
Table,
|
|
23
|
+
TablesContainer,
|
|
24
|
+
TypedTable,
|
|
25
|
+
)
|
|
26
|
+
from ngio.utils import (
|
|
27
|
+
AccessModeLiteral,
|
|
28
|
+
NgioValidationError,
|
|
29
|
+
NgioValueError,
|
|
30
|
+
StoreOrGroup,
|
|
31
|
+
ZarrGroupHandler,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _default_table_container(handler: ZarrGroupHandler) -> TablesContainer | None:
|
|
36
|
+
"""Return a default table container."""
|
|
37
|
+
success, table_handler = handler.safe_derive_handler("tables")
|
|
38
|
+
if success and isinstance(table_handler, ZarrGroupHandler):
|
|
39
|
+
return TablesContainer(table_handler)
|
|
16
40
|
|
|
17
41
|
|
|
18
42
|
# Mock lock class that does nothing
|
|
@@ -165,14 +189,20 @@ class OmeZarrWell:
|
|
|
165
189
|
class OmeZarrPlate:
|
|
166
190
|
"""A class to handle the Plate Collection in an OME-Zarr file."""
|
|
167
191
|
|
|
168
|
-
def __init__(
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
group_handler: ZarrGroupHandler,
|
|
195
|
+
table_container: TablesContainer | None = None,
|
|
196
|
+
) -> None:
|
|
169
197
|
"""Initialize the LabelGroupHandler.
|
|
170
198
|
|
|
171
199
|
Args:
|
|
172
200
|
group_handler: The Zarr group handler that contains the Plate.
|
|
201
|
+
table_container: The tables container that contains plate level tables.
|
|
173
202
|
"""
|
|
174
203
|
self._group_handler = group_handler
|
|
175
204
|
self._meta_handler = find_plate_meta_handler(group_handler)
|
|
205
|
+
self._tables_container = table_container
|
|
176
206
|
|
|
177
207
|
def __repr__(self) -> str:
|
|
178
208
|
"""Return a string representation of the plate."""
|
|
@@ -638,6 +668,95 @@ class OmeZarrPlate:
|
|
|
638
668
|
parallel_safe=parallel_safe,
|
|
639
669
|
)
|
|
640
670
|
|
|
671
|
+
@property
|
|
672
|
+
def tables_container(self) -> TablesContainer:
|
|
673
|
+
"""Return the tables container."""
|
|
674
|
+
if self._tables_container is None:
|
|
675
|
+
self._tables_container = _default_table_container(self._group_handler)
|
|
676
|
+
if self._tables_container is None:
|
|
677
|
+
raise NgioValidationError("No tables found in the image.")
|
|
678
|
+
return self._tables_container
|
|
679
|
+
|
|
680
|
+
@property
|
|
681
|
+
def list_tables(self) -> list[str]:
|
|
682
|
+
"""List all tables in the image."""
|
|
683
|
+
return self.tables_container.list()
|
|
684
|
+
|
|
685
|
+
def list_roi_tables(self) -> list[str]:
|
|
686
|
+
"""List all ROI tables in the image."""
|
|
687
|
+
return self.tables_container.list_roi_tables()
|
|
688
|
+
|
|
689
|
+
@overload
|
|
690
|
+
def get_table(self, name: str, check_type: None) -> Table: ...
|
|
691
|
+
|
|
692
|
+
@overload
|
|
693
|
+
def get_table(self, name: str, check_type: Literal["roi_table"]) -> RoiTable: ...
|
|
694
|
+
|
|
695
|
+
@overload
|
|
696
|
+
def get_table(
|
|
697
|
+
self, name: str, check_type: Literal["masking_roi_table"]
|
|
698
|
+
) -> MaskingRoiTable: ...
|
|
699
|
+
|
|
700
|
+
@overload
|
|
701
|
+
def get_table(
|
|
702
|
+
self, name: str, check_type: Literal["feature_table"]
|
|
703
|
+
) -> FeatureTable: ...
|
|
704
|
+
|
|
705
|
+
@overload
|
|
706
|
+
def get_table(
|
|
707
|
+
self, name: str, check_type: Literal["generic_roi_table"]
|
|
708
|
+
) -> GenericRoiTable: ...
|
|
709
|
+
|
|
710
|
+
def get_table(self, name: str, check_type: TypedTable | None = None) -> Table:
|
|
711
|
+
"""Get a table from the image."""
|
|
712
|
+
table = self.tables_container.get(name)
|
|
713
|
+
match check_type:
|
|
714
|
+
case "roi_table":
|
|
715
|
+
if not isinstance(table, RoiTable):
|
|
716
|
+
raise NgioValueError(
|
|
717
|
+
f"Table '{name}' is not a ROI table. Found type: {table.type()}"
|
|
718
|
+
)
|
|
719
|
+
return table
|
|
720
|
+
case "masking_roi_table":
|
|
721
|
+
if not isinstance(table, MaskingRoiTable):
|
|
722
|
+
raise NgioValueError(
|
|
723
|
+
f"Table '{name}' is not a masking ROI table. "
|
|
724
|
+
f"Found type: {table.type()}"
|
|
725
|
+
)
|
|
726
|
+
return table
|
|
727
|
+
|
|
728
|
+
case "generic_roi_table":
|
|
729
|
+
if not isinstance(table, GenericRoiTable):
|
|
730
|
+
raise NgioValueError(
|
|
731
|
+
f"Table '{name}' is not a generic ROI table. "
|
|
732
|
+
f"Found type: {table.type()}"
|
|
733
|
+
)
|
|
734
|
+
return table
|
|
735
|
+
|
|
736
|
+
case "feature_table":
|
|
737
|
+
if not isinstance(table, FeatureTable):
|
|
738
|
+
raise NgioValueError(
|
|
739
|
+
f"Table '{name}' is not a feature table. "
|
|
740
|
+
f"Found type: {table.type()}"
|
|
741
|
+
)
|
|
742
|
+
return table
|
|
743
|
+
case None:
|
|
744
|
+
return table
|
|
745
|
+
case _:
|
|
746
|
+
raise NgioValueError(f"Unknown check_type: {check_type}")
|
|
747
|
+
|
|
748
|
+
def add_table(
|
|
749
|
+
self,
|
|
750
|
+
name: str,
|
|
751
|
+
table: Table,
|
|
752
|
+
backend: str | None = None,
|
|
753
|
+
overwrite: bool = False,
|
|
754
|
+
) -> None:
|
|
755
|
+
"""Add a table to the image."""
|
|
756
|
+
self.tables_container.add(
|
|
757
|
+
name=name, table=table, backend=backend, overwrite=overwrite
|
|
758
|
+
)
|
|
759
|
+
|
|
641
760
|
|
|
642
761
|
def open_ome_zarr_plate(
|
|
643
762
|
store: StoreOrGroup,
|
ngio/images/abstract_image.py
CHANGED
ngio/images/image.py
CHANGED
|
@@ -154,6 +154,8 @@ class ImagesContainer:
|
|
|
154
154
|
self,
|
|
155
155
|
labels: Collection[str] | int | None = None,
|
|
156
156
|
wavelength_id: Collection[str] | None = None,
|
|
157
|
+
start: Collection[float] | None = None,
|
|
158
|
+
end: Collection[float] | None = None,
|
|
157
159
|
percentiles: tuple[float, float] | None = None,
|
|
158
160
|
colors: Collection[str] | None = None,
|
|
159
161
|
active: Collection[bool] | None = None,
|
|
@@ -166,6 +168,10 @@ class ImagesContainer:
|
|
|
166
168
|
If an integer is provided, the channels will be named "channel_i".
|
|
167
169
|
wavelength_id(Collection[str] | None): The wavelength ID of the channel.
|
|
168
170
|
If None, the wavelength ID will be the same as the channel name.
|
|
171
|
+
start(Collection[float] | None): The start value for each channel.
|
|
172
|
+
If None, the start value will be computed from the image.
|
|
173
|
+
end(Collection[float] | None): The end value for each channel.
|
|
174
|
+
If None, the end value will be computed from the image.
|
|
169
175
|
percentiles(tuple[float, float] | None): The start and end percentiles
|
|
170
176
|
for each channel. If None, the percentiles will not be computed.
|
|
171
177
|
colors(Collection[str, NgioColors] | None): The list of colors for the
|
|
@@ -177,12 +183,40 @@ class ImagesContainer:
|
|
|
177
183
|
low_res_dataset = self.meta.get_lowest_resolution_dataset()
|
|
178
184
|
ref_image = self.get(path=low_res_dataset.path)
|
|
179
185
|
|
|
186
|
+
if start is not None and end is None:
|
|
187
|
+
raise NgioValidationError(
|
|
188
|
+
"If start is provided, end must be provided as well."
|
|
189
|
+
)
|
|
190
|
+
if end is not None and start is None:
|
|
191
|
+
raise NgioValidationError(
|
|
192
|
+
"If end is provided, start must be provided as well."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if start is not None and percentiles is not None:
|
|
196
|
+
raise NgioValidationError(
|
|
197
|
+
"If start and end are provided, percentiles must be None."
|
|
198
|
+
)
|
|
199
|
+
|
|
180
200
|
if percentiles is not None:
|
|
181
201
|
start, end = compute_image_percentile(
|
|
182
202
|
ref_image,
|
|
183
203
|
start_percentile=percentiles[0],
|
|
184
204
|
end_percentile=percentiles[1],
|
|
185
205
|
)
|
|
206
|
+
elif start is not None and end is not None:
|
|
207
|
+
if len(start) != len(end):
|
|
208
|
+
raise NgioValidationError(
|
|
209
|
+
"The start and end lists must have the same length."
|
|
210
|
+
)
|
|
211
|
+
if len(start) != self.num_channels:
|
|
212
|
+
raise NgioValidationError(
|
|
213
|
+
"The start and end lists must have the same length as "
|
|
214
|
+
"the number of channels."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
start = list(start)
|
|
218
|
+
end = list(end)
|
|
219
|
+
|
|
186
220
|
else:
|
|
187
221
|
start, end = None, None
|
|
188
222
|
|
|
@@ -462,11 +496,17 @@ def derive_image_container(
|
|
|
462
496
|
active = [
|
|
463
497
|
c.channel_visualisation.active for c in ref_image._channels_meta.channels
|
|
464
498
|
]
|
|
499
|
+
start = [
|
|
500
|
+
c.channel_visualisation.start for c in ref_image._channels_meta.channels
|
|
501
|
+
]
|
|
502
|
+
end = [c.channel_visualisation.end for c in ref_image._channels_meta.channels]
|
|
465
503
|
else:
|
|
466
504
|
_labels = None
|
|
467
505
|
wavelength_id = None
|
|
468
506
|
colors = None
|
|
469
507
|
active = None
|
|
508
|
+
start = None
|
|
509
|
+
end = None
|
|
470
510
|
|
|
471
511
|
if labels is not None:
|
|
472
512
|
if len(labels) != image_container.num_channels:
|
|
@@ -481,5 +521,7 @@ def derive_image_container(
|
|
|
481
521
|
percentiles=None,
|
|
482
522
|
colors=colors,
|
|
483
523
|
active=active,
|
|
524
|
+
start=start,
|
|
525
|
+
end=end,
|
|
484
526
|
)
|
|
485
527
|
return image_container
|
ngio/images/label.py
CHANGED
|
@@ -133,6 +133,12 @@ class LabelsContainer:
|
|
|
133
133
|
closest pixel size level will be returned.
|
|
134
134
|
|
|
135
135
|
"""
|
|
136
|
+
if name not in self.list():
|
|
137
|
+
raise NgioValueError(
|
|
138
|
+
f"Label '{name}' not found in the Labels group. "
|
|
139
|
+
f"Available labels: {self.list()}"
|
|
140
|
+
)
|
|
141
|
+
|
|
136
142
|
group_handler = self._group_handler.derive_handler(name)
|
|
137
143
|
label_meta_handler = find_label_meta_handler(group_handler)
|
|
138
144
|
path = label_meta_handler.meta.get_dataset(
|
|
@@ -143,7 +149,7 @@ class LabelsContainer:
|
|
|
143
149
|
def derive(
|
|
144
150
|
self,
|
|
145
151
|
name: str,
|
|
146
|
-
ref_image: Image,
|
|
152
|
+
ref_image: Image | Label,
|
|
147
153
|
shape: Collection[int] | None = None,
|
|
148
154
|
pixel_size: PixelSize | None = None,
|
|
149
155
|
axes_names: Collection[str] | None = None,
|
|
@@ -157,7 +163,8 @@ class LabelsContainer:
|
|
|
157
163
|
|
|
158
164
|
Args:
|
|
159
165
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
160
|
-
ref_image (Image):
|
|
166
|
+
ref_image (Image | Label): A reference image that will be used to create
|
|
167
|
+
the new image.
|
|
161
168
|
name (str): The name of the new image.
|
|
162
169
|
shape (Collection[int] | None): The shape of the new image.
|
|
163
170
|
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
@@ -174,13 +181,13 @@ class LabelsContainer:
|
|
|
174
181
|
existing_labels = self.list()
|
|
175
182
|
if name in existing_labels and not overwrite:
|
|
176
183
|
raise NgioValueError(
|
|
177
|
-
f"
|
|
184
|
+
f"Label '{name}' already exists in the group. "
|
|
178
185
|
"Use overwrite=True to replace it."
|
|
179
186
|
)
|
|
180
187
|
|
|
181
188
|
label_group = self._group_handler.get_group(name, create_mode=True)
|
|
182
189
|
|
|
183
|
-
|
|
190
|
+
derive_label(
|
|
184
191
|
store=label_group,
|
|
185
192
|
ref_image=ref_image,
|
|
186
193
|
name=name,
|
|
@@ -199,9 +206,9 @@ class LabelsContainer:
|
|
|
199
206
|
return self.get(name)
|
|
200
207
|
|
|
201
208
|
|
|
202
|
-
def
|
|
209
|
+
def derive_label(
|
|
203
210
|
store: StoreOrGroup,
|
|
204
|
-
ref_image: Image,
|
|
211
|
+
ref_image: Image | Label,
|
|
205
212
|
name: str,
|
|
206
213
|
shape: Collection[int] | None = None,
|
|
207
214
|
pixel_size: PixelSize | None = None,
|
|
@@ -214,7 +221,8 @@ def _derive_label(
|
|
|
214
221
|
|
|
215
222
|
Args:
|
|
216
223
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
217
|
-
ref_image (Image):
|
|
224
|
+
ref_image (Image | Label): A reference image that will be used to
|
|
225
|
+
create the new image.
|
|
218
226
|
name (str): The name of the new image.
|
|
219
227
|
shape (Collection[int] | None): The shape of the new image.
|
|
220
228
|
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
@@ -502,7 +502,7 @@ class OmeZarrContainer:
|
|
|
502
502
|
def derive_label(
|
|
503
503
|
self,
|
|
504
504
|
name: str,
|
|
505
|
-
ref_image: Image | None = None,
|
|
505
|
+
ref_image: Image | Label | None = None,
|
|
506
506
|
shape: Collection[int] | None = None,
|
|
507
507
|
pixel_size: PixelSize | None = None,
|
|
508
508
|
axes_names: Collection[str] | None = None,
|
|
@@ -515,9 +515,9 @@ class OmeZarrContainer:
|
|
|
515
515
|
And add the label to the /labels group.
|
|
516
516
|
|
|
517
517
|
Args:
|
|
518
|
-
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
519
|
-
ref_image (Image): The reference image.
|
|
520
518
|
name (str): The name of the new image.
|
|
519
|
+
ref_image (Image | Label | None): A reference image that will be used
|
|
520
|
+
to create the new image.
|
|
521
521
|
shape (Collection[int] | None): The shape of the new image.
|
|
522
522
|
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
523
523
|
axes_names (Collection[str] | None): The axes names of the new image.
|
|
@@ -605,7 +605,6 @@ def create_empty_ome_zarr(
|
|
|
605
605
|
dtype: str = "uint16",
|
|
606
606
|
channel_labels: list[str] | None = None,
|
|
607
607
|
channel_wavelengths: list[str] | None = None,
|
|
608
|
-
percentiles: tuple[float, float] | None = None,
|
|
609
608
|
channel_colors: Collection[str] | None = None,
|
|
610
609
|
channel_active: Collection[bool] | None = None,
|
|
611
610
|
overwrite: bool = False,
|
|
@@ -640,8 +639,6 @@ def create_empty_ome_zarr(
|
|
|
640
639
|
Defaults to None.
|
|
641
640
|
channel_wavelengths (list[str] | None, optional): The wavelengths of the
|
|
642
641
|
channels. Defaults to None.
|
|
643
|
-
percentiles (tuple[float, float] | None, optional): The percentiles of the
|
|
644
|
-
channels. Defaults to None.
|
|
645
642
|
channel_colors (Collection[str] | None, optional): The colors of the channels.
|
|
646
643
|
Defaults to None.
|
|
647
644
|
channel_active (Collection[bool] | None, optional): Whether the channels are
|
|
@@ -674,7 +671,7 @@ def create_empty_ome_zarr(
|
|
|
674
671
|
ome_zarr.set_channel_meta(
|
|
675
672
|
labels=channel_labels,
|
|
676
673
|
wavelength_id=channel_wavelengths,
|
|
677
|
-
percentiles=
|
|
674
|
+
percentiles=None,
|
|
678
675
|
colors=channel_colors,
|
|
679
676
|
active=channel_active,
|
|
680
677
|
)
|
ngio/tables/_validators.py
CHANGED
|
@@ -2,9 +2,8 @@ from collections.abc import Iterable
|
|
|
2
2
|
from typing import Protocol
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
|
-
import pandas.api.types as ptypes
|
|
6
5
|
|
|
7
|
-
from ngio.utils import NgioTableValidationError
|
|
6
|
+
from ngio.utils import NgioTableValidationError
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class TableValidator(Protocol):
|
|
@@ -56,87 +55,6 @@ def validate_table(
|
|
|
56
55
|
# Common table validators
|
|
57
56
|
#
|
|
58
57
|
####################################################################################################
|
|
59
|
-
def validate_index_key(
|
|
60
|
-
dataframe: pd.DataFrame, index_key: str | None, overwrite: bool = False
|
|
61
|
-
) -> pd.DataFrame:
|
|
62
|
-
"""Correctly set the index of the DataFrame.
|
|
63
|
-
|
|
64
|
-
This function checks if the index_key is present in the DataFrame.
|
|
65
|
-
If not it tries to set sensible defaults.
|
|
66
|
-
|
|
67
|
-
In order:
|
|
68
|
-
- If index_key is None, nothing can be done.
|
|
69
|
-
- If index_key is already the index of the DataFrame, nothing is done.
|
|
70
|
-
- If index_key is in the columns, we set the index to that column.
|
|
71
|
-
- If current index is None, we set the index to the index_key.
|
|
72
|
-
- If current index is not None and overwrite is True,
|
|
73
|
-
we set the index to the index_key.
|
|
74
|
-
|
|
75
|
-
"""
|
|
76
|
-
if index_key is None:
|
|
77
|
-
# Nothing to do
|
|
78
|
-
return dataframe
|
|
79
|
-
|
|
80
|
-
if dataframe.index.name == index_key:
|
|
81
|
-
# Index is already set to index_key correctly
|
|
82
|
-
return dataframe
|
|
83
|
-
|
|
84
|
-
if index_key in dataframe.columns:
|
|
85
|
-
dataframe = dataframe.set_index(index_key)
|
|
86
|
-
return dataframe
|
|
87
|
-
|
|
88
|
-
if dataframe.index.name is None:
|
|
89
|
-
dataframe.index.name = index_key
|
|
90
|
-
return dataframe
|
|
91
|
-
|
|
92
|
-
elif overwrite:
|
|
93
|
-
dataframe.index.name = index_key
|
|
94
|
-
return dataframe
|
|
95
|
-
else:
|
|
96
|
-
raise NgioTableValidationError(
|
|
97
|
-
f"Index key {index_key} not found in DataFrame. "
|
|
98
|
-
f"Current index is {dataframe.index.name}. If you want to overwrite the "
|
|
99
|
-
"index set overwrite=True."
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def validate_index_dtype(dataframe: pd.DataFrame, index_type: str) -> pd.DataFrame:
|
|
104
|
-
"""Check if the index of the DataFrame has the correct dtype."""
|
|
105
|
-
match index_type:
|
|
106
|
-
case "str":
|
|
107
|
-
if ptypes.is_integer_dtype(dataframe.index):
|
|
108
|
-
# Convert the int index to string is generally safe
|
|
109
|
-
dataframe = dataframe.set_index(dataframe.index.astype(str))
|
|
110
|
-
|
|
111
|
-
if not ptypes.is_string_dtype(dataframe.index):
|
|
112
|
-
raise NgioTableValidationError(
|
|
113
|
-
f"Table index must be of string type, got {dataframe.index.dtype}"
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
case "int":
|
|
117
|
-
if ptypes.is_string_dtype(dataframe.index):
|
|
118
|
-
# Try to convert the string index to int
|
|
119
|
-
try:
|
|
120
|
-
dataframe = dataframe.set_index(dataframe.index.astype(int))
|
|
121
|
-
except ValueError as e:
|
|
122
|
-
if "invalid literal for int() with base 10" in str(e):
|
|
123
|
-
raise NgioTableValidationError(
|
|
124
|
-
"Table index must be of integer type, got str."
|
|
125
|
-
f" We tried implicit conversion and failed: {e}"
|
|
126
|
-
) from None
|
|
127
|
-
else:
|
|
128
|
-
raise e from e
|
|
129
|
-
|
|
130
|
-
if not ptypes.is_integer_dtype(dataframe.index):
|
|
131
|
-
raise NgioTableValidationError(
|
|
132
|
-
f"Table index must be of integer type, got {dataframe.index.dtype}"
|
|
133
|
-
)
|
|
134
|
-
case _:
|
|
135
|
-
raise NgioValueError(f"index_type {index_type} not recognized")
|
|
136
|
-
|
|
137
|
-
return dataframe
|
|
138
|
-
|
|
139
|
-
|
|
140
58
|
def validate_columns(
|
|
141
59
|
table_df: pd.DataFrame,
|
|
142
60
|
required_columns: list[str],
|
ngio/tables/backends/__init__.py
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
"""Ngio Tables backend implementations."""
|
|
2
2
|
|
|
3
|
+
from ngio.tables.backends._abstract_backend import AbstractTableBackend, BackendMeta
|
|
3
4
|
from ngio.tables.backends._table_backends import (
|
|
4
5
|
ImplementedTableBackends,
|
|
5
6
|
TableBackendProtocol,
|
|
6
7
|
)
|
|
8
|
+
from ngio.tables.backends._utils import (
|
|
9
|
+
convert_anndata_to_pandas,
|
|
10
|
+
convert_anndata_to_polars,
|
|
11
|
+
convert_pandas_to_anndata,
|
|
12
|
+
convert_pandas_to_polars,
|
|
13
|
+
convert_polars_to_anndata,
|
|
14
|
+
convert_polars_to_pandas,
|
|
15
|
+
normalize_anndata,
|
|
16
|
+
normalize_pandas_df,
|
|
17
|
+
normalize_polars_lf,
|
|
18
|
+
)
|
|
7
19
|
|
|
8
|
-
__all__ = [
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AbstractTableBackend",
|
|
22
|
+
"BackendMeta",
|
|
23
|
+
"ImplementedTableBackends",
|
|
24
|
+
"TableBackendProtocol",
|
|
25
|
+
"convert_anndata_to_pandas",
|
|
26
|
+
"convert_anndata_to_polars",
|
|
27
|
+
"convert_pandas_to_anndata",
|
|
28
|
+
"convert_pandas_to_polars",
|
|
29
|
+
"convert_polars_to_anndata",
|
|
30
|
+
"convert_polars_to_pandas",
|
|
31
|
+
"normalize_anndata",
|
|
32
|
+
"normalize_pandas_df",
|
|
33
|
+
"normalize_polars_lf",
|
|
34
|
+
]
|