ngio 0.3.5__py3-none-any.whl → 0.4.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 +6 -0
- ngio/common/__init__.py +50 -48
- ngio/common/_array_io_pipes.py +549 -0
- ngio/common/_array_io_utils.py +508 -0
- ngio/common/_dimensions.py +63 -27
- ngio/common/_masking_roi.py +38 -10
- ngio/common/_pyramid.py +9 -7
- ngio/common/_roi.py +571 -72
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +17 -12
- ngio/common/transforms/__init__.py +5 -0
- ngio/common/transforms/_label.py +12 -0
- ngio/common/transforms/_zoom.py +109 -0
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +17 -0
- ngio/experimental/iterators/_abstract_iterator.py +170 -0
- ngio/experimental/iterators/_feature.py +151 -0
- ngio/experimental/iterators/_image_processing.py +169 -0
- ngio/experimental/iterators/_rois_utils.py +127 -0
- ngio/experimental/iterators/_segmentation.py +278 -0
- ngio/hcs/_plate.py +41 -36
- ngio/images/__init__.py +22 -1
- ngio/images/_abstract_image.py +247 -117
- ngio/images/_create.py +15 -15
- ngio/images/_create_synt_container.py +128 -0
- ngio/images/_image.py +425 -62
- ngio/images/_label.py +33 -30
- ngio/images/_masked_image.py +396 -122
- ngio/images/_ome_zarr_container.py +203 -66
- ngio/{common → images}/_table_ops.py +41 -41
- ngio/ome_zarr_meta/ngio_specs/__init__.py +2 -8
- ngio/ome_zarr_meta/ngio_specs/_axes.py +151 -128
- ngio/ome_zarr_meta/ngio_specs/_channels.py +55 -18
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +7 -7
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +3 -3
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +11 -68
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +1 -1
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
- ngio/resources/__init__.py +54 -0
- ngio/resources/resource_model.py +35 -0
- ngio/tables/backends/_abstract_backend.py +5 -6
- ngio/tables/backends/_anndata.py +1 -1
- ngio/tables/backends/_anndata_utils.py +3 -3
- ngio/tables/backends/_non_zarr_backends.py +1 -1
- ngio/tables/backends/_table_backends.py +0 -1
- ngio/tables/backends/_utils.py +3 -3
- ngio/tables/v1/_roi_table.py +156 -69
- ngio/utils/__init__.py +2 -3
- ngio/utils/_logger.py +19 -0
- ngio/utils/_zarr_utils.py +1 -5
- {ngio-0.3.5.dist-info → ngio-0.4.0a1.dist-info}/METADATA +3 -1
- ngio-0.4.0a1.dist-info/RECORD +76 -0
- ngio/common/_array_pipe.py +0 -288
- ngio/common/_axes_transforms.py +0 -64
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -96
- ngio-0.3.5.dist-info/RECORD +0 -61
- {ngio-0.3.5.dist-info → ngio-0.4.0a1.dist-info}/WHEEL +0 -0
- {ngio-0.3.5.dist-info → ngio-0.4.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ But they can be built from the OME standard metadata, and the
|
|
|
6
6
|
can be converted to the OME standard.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from collections.abc import
|
|
9
|
+
from collections.abc import Sequence
|
|
10
10
|
from typing import Any, Literal, TypeVar
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
@@ -19,7 +19,7 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
|
|
|
19
19
|
TimeUnits,
|
|
20
20
|
canonical_axes,
|
|
21
21
|
)
|
|
22
|
-
from ngio.ome_zarr_meta.ngio_specs._channels import
|
|
22
|
+
from ngio.ome_zarr_meta.ngio_specs._channels import ChannelsMeta
|
|
23
23
|
from ngio.ome_zarr_meta.ngio_specs._dataset import Dataset
|
|
24
24
|
from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
|
|
25
25
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
@@ -60,24 +60,24 @@ class AbstractNgioImageMeta:
|
|
|
60
60
|
def __repr__(self):
|
|
61
61
|
class_name = type(self).__name__
|
|
62
62
|
paths = [dataset.path for dataset in self.datasets]
|
|
63
|
-
on_disk_axes = self.axes_mapper.
|
|
63
|
+
on_disk_axes = self.axes_mapper.axes_names
|
|
64
64
|
return f"{class_name}(name={self.name}, datasets={paths}, axes={on_disk_axes})"
|
|
65
65
|
|
|
66
66
|
@classmethod
|
|
67
67
|
def default_init(
|
|
68
68
|
cls,
|
|
69
|
-
levels: int |
|
|
70
|
-
axes_names:
|
|
69
|
+
levels: int | Sequence[str],
|
|
70
|
+
axes_names: Sequence[str],
|
|
71
71
|
pixel_size: PixelSize,
|
|
72
|
-
scaling_factors:
|
|
72
|
+
scaling_factors: Sequence[float] | None = None,
|
|
73
73
|
name: str | None = None,
|
|
74
74
|
version: NgffVersions = DefaultNgffVersion,
|
|
75
75
|
):
|
|
76
76
|
"""Initialize the ImageMeta object."""
|
|
77
77
|
axes = canonical_axes(
|
|
78
78
|
axes_names,
|
|
79
|
-
space_units=pixel_size.space_unit,
|
|
80
|
-
time_units=pixel_size.time_unit,
|
|
79
|
+
space_units=pixel_size.space_unit,
|
|
80
|
+
time_units=pixel_size.time_unit,
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
px_size_dict = pixel_size.as_dict()
|
|
@@ -136,7 +136,7 @@ class AbstractNgioImageMeta:
|
|
|
136
136
|
@property
|
|
137
137
|
def version(self) -> NgffVersions:
|
|
138
138
|
"""Version of the OME-NFF metadata used to build the object."""
|
|
139
|
-
return self._version # type: ignore
|
|
139
|
+
return self._version # type: ignore (version is a Literal type)
|
|
140
140
|
|
|
141
141
|
@property
|
|
142
142
|
def name(self) -> str | None:
|
|
@@ -321,11 +321,11 @@ class AbstractNgioImageMeta:
|
|
|
321
321
|
def scaling_factor(self, path: str | None = None) -> list[float]:
|
|
322
322
|
"""Get the scaling factors from a dataset to its lower resolution."""
|
|
323
323
|
if self.levels == 1:
|
|
324
|
-
return [1.0] * len(self.axes_mapper.
|
|
324
|
+
return [1.0] * len(self.axes_mapper.axes_names)
|
|
325
325
|
dataset, lr_dataset = self._get_closest_datasets(path=path)
|
|
326
326
|
|
|
327
327
|
scaling_factors = []
|
|
328
|
-
for ax_name in self.axes_mapper.
|
|
328
|
+
for ax_name in self.axes_mapper.axes_names:
|
|
329
329
|
s_d = dataset.get_scale(ax_name)
|
|
330
330
|
s_lr_d = lr_dataset.get_scale(ax_name)
|
|
331
331
|
scaling_factors.append(s_lr_d / s_d)
|
|
@@ -462,62 +462,5 @@ class NgioImageMeta(AbstractNgioImageMeta):
|
|
|
462
462
|
)
|
|
463
463
|
self.set_channels_meta(channels_meta=channels_meta)
|
|
464
464
|
|
|
465
|
-
@property
|
|
466
|
-
def channels(self) -> list[Channel]:
|
|
467
|
-
"""Get the channels in the image."""
|
|
468
|
-
if self._channels_meta is None:
|
|
469
|
-
return []
|
|
470
|
-
assert self.channels_meta is not None
|
|
471
|
-
return self.channels_meta.channels
|
|
472
|
-
|
|
473
|
-
@property
|
|
474
|
-
def channel_labels(self) -> list[str]:
|
|
475
|
-
"""Get the labels of the channels in the image."""
|
|
476
|
-
return [channel.label for channel in self.channels]
|
|
477
|
-
|
|
478
|
-
@property
|
|
479
|
-
def channel_wavelength_ids(self) -> list[str | None]:
|
|
480
|
-
"""Get the wavelength IDs of the channels in the image."""
|
|
481
|
-
return [channel.wavelength_id for channel in self.channels]
|
|
482
|
-
|
|
483
|
-
def _get_channel_idx_by_label(self, label: str) -> int | None:
|
|
484
|
-
"""Get the index of a channel by its label."""
|
|
485
|
-
if self._channels_meta is None:
|
|
486
|
-
return None
|
|
487
|
-
|
|
488
|
-
if label not in self.channel_labels:
|
|
489
|
-
raise NgioValueError(f"Channel with label {label} not found.")
|
|
490
|
-
|
|
491
|
-
return self.channel_labels.index(label)
|
|
492
|
-
|
|
493
|
-
def _get_channel_idx_by_wavelength_id(self, wavelength_id: str) -> int | None:
|
|
494
|
-
"""Get the index of a channel by its wavelength ID."""
|
|
495
|
-
if self._channels_meta is None:
|
|
496
|
-
return None
|
|
497
|
-
|
|
498
|
-
if wavelength_id not in self.channel_wavelength_ids:
|
|
499
|
-
raise NgioValueError(
|
|
500
|
-
f"Channel with wavelength ID {wavelength_id} not found."
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
return self.channel_wavelength_ids.index(wavelength_id)
|
|
504
|
-
|
|
505
|
-
def get_channel_idx(
|
|
506
|
-
self, label: str | None = None, wavelength_id: str | None = None
|
|
507
|
-
) -> int | None:
|
|
508
|
-
"""Get the index of a channel by its label or wavelength ID."""
|
|
509
|
-
# Only one of the arguments must be provided
|
|
510
|
-
if sum([label is not None, wavelength_id is not None]) != 1:
|
|
511
|
-
raise NgioValueError("get_channel_idx must receive only one argument.")
|
|
512
|
-
|
|
513
|
-
if label is not None:
|
|
514
|
-
return self._get_channel_idx_by_label(label)
|
|
515
|
-
elif wavelength_id is not None:
|
|
516
|
-
return self._get_channel_idx_by_wavelength_id(wavelength_id)
|
|
517
|
-
else:
|
|
518
|
-
raise NgioValueError(
|
|
519
|
-
"get_channel_idx must receive either label or wavelength_id."
|
|
520
|
-
)
|
|
521
|
-
|
|
522
465
|
|
|
523
466
|
NgioImageLabelMeta = NgioImageMeta | NgioLabelMeta
|
|
@@ -316,7 +316,7 @@ def _ngio_to_v04_multiscale(name: str | None, datasets: list[Dataset]) -> Multis
|
|
|
316
316
|
"""
|
|
317
317
|
ax_mapper = datasets[0].axes_mapper
|
|
318
318
|
v04_axes = []
|
|
319
|
-
for axis in ax_mapper.
|
|
319
|
+
for axis in ax_mapper.axes:
|
|
320
320
|
v04_axes.append(
|
|
321
321
|
AxisV04(
|
|
322
322
|
name=axis.on_disk_name,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Predefined resources for testing and demonstration purposes."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from ngio.resources.resource_model import LabelsInfo, SampleInfo
|
|
7
|
+
|
|
8
|
+
resources = Path(__file__).parent.resolve()
|
|
9
|
+
|
|
10
|
+
_resources = {
|
|
11
|
+
"Cardiomyocyte": SampleInfo(
|
|
12
|
+
img_path=resources
|
|
13
|
+
/ "20200812-CardiomyocyteDifferentiation14-Cycle1_B03"
|
|
14
|
+
/ "raw.jpg",
|
|
15
|
+
labels=[
|
|
16
|
+
LabelsInfo(
|
|
17
|
+
name="nuclei",
|
|
18
|
+
label_path=resources
|
|
19
|
+
/ "20200812-CardiomyocyteDifferentiation14-Cycle1_B03"
|
|
20
|
+
/ "nuclei.png",
|
|
21
|
+
create_masking_table=False,
|
|
22
|
+
ensure_unique_labels=True,
|
|
23
|
+
),
|
|
24
|
+
LabelsInfo(
|
|
25
|
+
name="nuclei_mask",
|
|
26
|
+
label_path=resources
|
|
27
|
+
/ "20200812-CardiomyocyteDifferentiation14-Cycle1_B03"
|
|
28
|
+
/ "mask.png",
|
|
29
|
+
create_masking_table=True,
|
|
30
|
+
ensure_unique_labels=False,
|
|
31
|
+
),
|
|
32
|
+
],
|
|
33
|
+
xy_pixelsize=0.325,
|
|
34
|
+
z_spacing=1.0,
|
|
35
|
+
time_spacing=1.0,
|
|
36
|
+
name="Cardiomyocyte Differentiation",
|
|
37
|
+
info="20200812-CardiomyocyteDifferentiation14-Cycle1_B03",
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AVAILABLE_SAMPLES = Literal["Cardiomyocyte"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_sample_info(name: AVAILABLE_SAMPLES) -> SampleInfo:
|
|
45
|
+
"""Get a predefined resource by name."""
|
|
46
|
+
image_info = _resources.get(name)
|
|
47
|
+
if image_info is None:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Sample '{name}' not found. Available samples: {_resources.keys()}"
|
|
50
|
+
)
|
|
51
|
+
return image_info
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["AVAILABLE_SAMPLES", "LabelsInfo", "SampleInfo"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Models for programmatic description of image resources."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from ngio.ome_zarr_meta.ngio_specs import (
|
|
8
|
+
DefaultSpaceUnit,
|
|
9
|
+
DefaultTimeUnit,
|
|
10
|
+
SpaceUnits,
|
|
11
|
+
TimeUnits,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LabelsInfo(BaseModel):
|
|
16
|
+
"""Metadata for a label image."""
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
label_path: Path
|
|
20
|
+
ensure_unique_labels: bool = True
|
|
21
|
+
create_masking_table: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SampleInfo(BaseModel):
|
|
25
|
+
"""Metadata necessary to create an OME-Ngff from image files."""
|
|
26
|
+
|
|
27
|
+
img_path: Path
|
|
28
|
+
labels: list[LabelsInfo] = Field(default_factory=list)
|
|
29
|
+
xy_pixelsize: float
|
|
30
|
+
z_spacing: float = 1.0
|
|
31
|
+
time_spacing: float = 1.0
|
|
32
|
+
space_unit: SpaceUnits = DefaultSpaceUnit
|
|
33
|
+
time_unit: TimeUnits = DefaultTimeUnit
|
|
34
|
+
name: str | None = None
|
|
35
|
+
info: str | None = None
|
|
@@ -210,7 +210,6 @@ class AbstractTableBackend(ABC):
|
|
|
210
210
|
self,
|
|
211
211
|
table_data: TabularData,
|
|
212
212
|
metadata: dict | None = None,
|
|
213
|
-
mode: Literal["pandas", "anndata", "polars"] | None = None,
|
|
214
213
|
) -> None:
|
|
215
214
|
"""Serialize the table to the store, and write the metadata.
|
|
216
215
|
|
|
@@ -218,11 +217,11 @@ class AbstractTableBackend(ABC):
|
|
|
218
217
|
Based on the explicit mode or the type of the table,
|
|
219
218
|
it will call the appropriate write method.
|
|
220
219
|
"""
|
|
221
|
-
if
|
|
222
|
-
self.write_from_pandas(table_data)
|
|
223
|
-
elif
|
|
224
|
-
self.write_from_anndata(table_data)
|
|
225
|
-
elif
|
|
220
|
+
if isinstance(table_data, DataFrame):
|
|
221
|
+
self.write_from_pandas(table_data)
|
|
222
|
+
elif isinstance(table_data, AnnData):
|
|
223
|
+
self.write_from_anndata(table_data)
|
|
224
|
+
elif isinstance(table_data, PolarsDataFrame | LazyFrame):
|
|
226
225
|
self.write_from_polars(table_data)
|
|
227
226
|
else:
|
|
228
227
|
raise NgioValueError(
|
ngio/tables/backends/_anndata.py
CHANGED
|
@@ -58,7 +58,7 @@ class AnnDataBackend(AbstractTableBackend):
|
|
|
58
58
|
"Please make sure to use a compatible "
|
|
59
59
|
"store like a zarr.DirectoryStore."
|
|
60
60
|
)
|
|
61
|
-
table.write_zarr(full_url) # type: ignore
|
|
61
|
+
table.write_zarr(full_url) # type: ignore (AnnData writer requires a str path)
|
|
62
62
|
|
|
63
63
|
def write_from_pandas(self, table: DataFrame) -> None:
|
|
64
64
|
"""Serialize the table from a pandas DataFrame."""
|
|
@@ -17,11 +17,11 @@ from ngio.utils import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
|
-
from collections.abc import Callable,
|
|
20
|
+
from collections.abc import Callable, Sequence
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def custom_anndata_read_zarr(
|
|
24
|
-
store: StoreOrGroup, elem_to_read:
|
|
24
|
+
store: StoreOrGroup, elem_to_read: Sequence[str] | None = None
|
|
25
25
|
) -> AnnData:
|
|
26
26
|
"""Read from a hierarchical Zarr array store.
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ def custom_anndata_read_zarr(
|
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
33
|
store (StoreOrGroup): A store or group to read the AnnData from.
|
|
34
|
-
elem_to_read (
|
|
34
|
+
elem_to_read (Sequence[str] | None): The elements to read from the store.
|
|
35
35
|
"""
|
|
36
36
|
group = open_group_wrapper(store=store, mode="r")
|
|
37
37
|
|
|
@@ -80,7 +80,7 @@ class NonZarrBaseBackend(AbstractTableBackend):
|
|
|
80
80
|
"""Load the table from an FS store."""
|
|
81
81
|
full_url = self._group_handler.full_url
|
|
82
82
|
parquet_path = f"{full_url}/{self.table_name}"
|
|
83
|
-
store_fs = self._group_handler.store.fs # type: ignore
|
|
83
|
+
store_fs = self._group_handler.store.fs # type: ignore (in this context, store_fs is a fs.FSStore)
|
|
84
84
|
with store_fs.open(parquet_path, "rb") as f:
|
|
85
85
|
dataframe = reader(f)
|
|
86
86
|
return dataframe
|
|
@@ -127,7 +127,6 @@ class TableBackendProtocol(Protocol):
|
|
|
127
127
|
self,
|
|
128
128
|
table_data: DataFrame | AnnData | PolarsDataFrame | LazyFrame,
|
|
129
129
|
metadata: dict[str, str] | None = None,
|
|
130
|
-
mode: Literal["pandas", "anndata", "polars"] | None = None,
|
|
131
130
|
) -> None:
|
|
132
131
|
"""This is a generic write method.
|
|
133
132
|
|
ngio/tables/backends/_utils.py
CHANGED
|
@@ -126,10 +126,10 @@ def _check_for_mixed_types(series: pd.Series) -> None:
|
|
|
126
126
|
Raises:
|
|
127
127
|
NgioTableValidationError: If the column has mixed types.
|
|
128
128
|
"""
|
|
129
|
-
if series.apply(type).nunique() > 1: # type: ignore
|
|
129
|
+
if series.apply(type).nunique() > 1: # type: ignore (type lint fails here)
|
|
130
130
|
raise NgioTableValidationError(
|
|
131
131
|
f"Column {series.name} has mixed types: "
|
|
132
|
-
f"{series.apply(type).unique()}. " # type: ignore
|
|
132
|
+
f"{series.apply(type).unique()}. " # type: ignore (type lint fails here)
|
|
133
133
|
"Type of all elements must be the same."
|
|
134
134
|
)
|
|
135
135
|
|
|
@@ -186,7 +186,7 @@ def normalize_pandas_df(
|
|
|
186
186
|
pandas_df = _validate_index_key_df(pandas_df, index_key)
|
|
187
187
|
pandas_df = _validate_cast_index_dtype_df(pandas_df, index_type)
|
|
188
188
|
if pandas_df.index.name is not None:
|
|
189
|
-
index_key = pandas_df.index.name
|
|
189
|
+
index_key = str(pandas_df.index.name)
|
|
190
190
|
|
|
191
191
|
if reset_index and pandas_df.index.name is not None:
|
|
192
192
|
pandas_df = pandas_df.reset_index()
|