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.
Files changed (61) hide show
  1. ngio/__init__.py +6 -0
  2. ngio/common/__init__.py +50 -48
  3. ngio/common/_array_io_pipes.py +549 -0
  4. ngio/common/_array_io_utils.py +508 -0
  5. ngio/common/_dimensions.py +63 -27
  6. ngio/common/_masking_roi.py +38 -10
  7. ngio/common/_pyramid.py +9 -7
  8. ngio/common/_roi.py +571 -72
  9. ngio/common/_synt_images_utils.py +101 -0
  10. ngio/common/_zoom.py +17 -12
  11. ngio/common/transforms/__init__.py +5 -0
  12. ngio/common/transforms/_label.py +12 -0
  13. ngio/common/transforms/_zoom.py +109 -0
  14. ngio/experimental/__init__.py +5 -0
  15. ngio/experimental/iterators/__init__.py +17 -0
  16. ngio/experimental/iterators/_abstract_iterator.py +170 -0
  17. ngio/experimental/iterators/_feature.py +151 -0
  18. ngio/experimental/iterators/_image_processing.py +169 -0
  19. ngio/experimental/iterators/_rois_utils.py +127 -0
  20. ngio/experimental/iterators/_segmentation.py +278 -0
  21. ngio/hcs/_plate.py +41 -36
  22. ngio/images/__init__.py +22 -1
  23. ngio/images/_abstract_image.py +247 -117
  24. ngio/images/_create.py +15 -15
  25. ngio/images/_create_synt_container.py +128 -0
  26. ngio/images/_image.py +425 -62
  27. ngio/images/_label.py +33 -30
  28. ngio/images/_masked_image.py +396 -122
  29. ngio/images/_ome_zarr_container.py +203 -66
  30. ngio/{common → images}/_table_ops.py +41 -41
  31. ngio/ome_zarr_meta/ngio_specs/__init__.py +2 -8
  32. ngio/ome_zarr_meta/ngio_specs/_axes.py +151 -128
  33. ngio/ome_zarr_meta/ngio_specs/_channels.py +55 -18
  34. ngio/ome_zarr_meta/ngio_specs/_dataset.py +7 -7
  35. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +3 -3
  36. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +11 -68
  37. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +1 -1
  38. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  39. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  40. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  41. ngio/resources/__init__.py +54 -0
  42. ngio/resources/resource_model.py +35 -0
  43. ngio/tables/backends/_abstract_backend.py +5 -6
  44. ngio/tables/backends/_anndata.py +1 -1
  45. ngio/tables/backends/_anndata_utils.py +3 -3
  46. ngio/tables/backends/_non_zarr_backends.py +1 -1
  47. ngio/tables/backends/_table_backends.py +0 -1
  48. ngio/tables/backends/_utils.py +3 -3
  49. ngio/tables/v1/_roi_table.py +156 -69
  50. ngio/utils/__init__.py +2 -3
  51. ngio/utils/_logger.py +19 -0
  52. ngio/utils/_zarr_utils.py +1 -5
  53. {ngio-0.3.5.dist-info → ngio-0.4.0a1.dist-info}/METADATA +3 -1
  54. ngio-0.4.0a1.dist-info/RECORD +76 -0
  55. ngio/common/_array_pipe.py +0 -288
  56. ngio/common/_axes_transforms.py +0 -64
  57. ngio/common/_common_types.py +0 -5
  58. ngio/common/_slicer.py +0 -96
  59. ngio-0.3.5.dist-info/RECORD +0 -61
  60. {ngio-0.3.5.dist-info → ngio-0.4.0a1.dist-info}/WHEEL +0 -0
  61. {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 Collection
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 Channel, ChannelsMeta
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.on_disk_axes_names
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 | Collection[str],
70
- axes_names: Collection[str],
69
+ levels: int | Sequence[str],
70
+ axes_names: Sequence[str],
71
71
  pixel_size: PixelSize,
72
- scaling_factors: Collection[float] | None = None,
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, # type: ignore[arg-type]
80
- time_units=pixel_size.time_unit, # type: ignore[arg-type]
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[return-value]
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.on_disk_axes_names)
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.on_disk_axes_names:
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.on_disk_axes:
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 mode == "pandas" or isinstance(table_data, DataFrame):
222
- self.write_from_pandas(table_data) # type: ignore[arg-type]
223
- elif mode == "anndata" or isinstance(table_data, AnnData):
224
- self.write_from_anndata(table_data) # type: ignore[arg-type]
225
- elif mode == "polars" or isinstance(table_data, PolarsDataFrame | LazyFrame):
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(
@@ -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, Collection
20
+ from collections.abc import Callable, Sequence
21
21
 
22
22
 
23
23
  def custom_anndata_read_zarr(
24
- store: StoreOrGroup, elem_to_read: Collection[str] | None = None
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 (Collection[str] | None): The elements to read from the store.
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
 
@@ -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()