ngio 0.5.0__py3-none-any.whl → 0.5.0a2__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 (53) hide show
  1. ngio/__init__.py +2 -5
  2. ngio/common/__init__.py +6 -11
  3. ngio/common/_masking_roi.py +54 -34
  4. ngio/common/_pyramid.py +85 -309
  5. ngio/common/_roi.py +330 -258
  6. ngio/experimental/iterators/_feature.py +3 -3
  7. ngio/experimental/iterators/_rois_utils.py +11 -10
  8. ngio/hcs/_plate.py +60 -132
  9. ngio/images/_abstract_image.py +35 -539
  10. ngio/images/_create.py +287 -0
  11. ngio/images/_create_synt_container.py +42 -39
  12. ngio/images/_image.py +250 -516
  13. ngio/images/_label.py +172 -249
  14. ngio/images/_masked_image.py +2 -2
  15. ngio/images/_ome_zarr_container.py +241 -644
  16. ngio/io_pipes/_io_pipes.py +9 -9
  17. ngio/io_pipes/_io_pipes_masked.py +7 -7
  18. ngio/io_pipes/_io_pipes_roi.py +6 -6
  19. ngio/io_pipes/_io_pipes_types.py +3 -3
  20. ngio/io_pipes/_match_shape.py +8 -6
  21. ngio/io_pipes/_ops_slices_utils.py +5 -8
  22. ngio/ome_zarr_meta/__init__.py +18 -29
  23. ngio/ome_zarr_meta/_meta_handlers.py +708 -392
  24. ngio/ome_zarr_meta/ngio_specs/__init__.py +0 -4
  25. ngio/ome_zarr_meta/ngio_specs/_axes.py +51 -152
  26. ngio/ome_zarr_meta/ngio_specs/_dataset.py +22 -13
  27. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +91 -129
  28. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +68 -57
  29. ngio/ome_zarr_meta/v04/__init__.py +1 -5
  30. ngio/ome_zarr_meta/v04/{_v04_spec.py → _v04_spec_utils.py} +85 -54
  31. ngio/ome_zarr_meta/v05/__init__.py +1 -5
  32. ngio/ome_zarr_meta/v05/{_v05_spec.py → _v05_spec_utils.py} +87 -64
  33. ngio/resources/__init__.py +1 -1
  34. ngio/resources/resource_model.py +1 -1
  35. ngio/tables/_tables_container.py +11 -62
  36. ngio/tables/backends/_anndata.py +8 -58
  37. ngio/tables/backends/_anndata_utils.py +6 -1
  38. ngio/tables/backends/_csv.py +19 -3
  39. ngio/tables/backends/_json.py +13 -10
  40. ngio/tables/backends/_non_zarr_backends.py +196 -0
  41. ngio/tables/backends/_parquet.py +31 -3
  42. ngio/tables/v1/_roi_table.py +24 -41
  43. ngio/utils/__init__.py +12 -6
  44. ngio/utils/_datasets.py +0 -6
  45. ngio/utils/_logger.py +50 -0
  46. ngio/utils/_zarr_utils.py +58 -167
  47. {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/METADATA +4 -11
  48. ngio-0.5.0a2.dist-info/RECORD +89 -0
  49. {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/WHEEL +1 -1
  50. ngio/images/_create_utils.py +0 -406
  51. ngio/tables/backends/_py_arrow_backends.py +0 -222
  52. ngio-0.5.0.dist-info/RECORD +0 -88
  53. {ngio-0.5.0.dist-info → ngio-0.5.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,6 @@ from ngio.ome_zarr_meta.ngio_specs._axes import (
15
15
  DefaultTimeUnit,
16
16
  SpaceUnits,
17
17
  TimeUnits,
18
- build_axes_handler,
19
18
  build_canonical_axes_handler,
20
19
  canonical_axes_order,
21
20
  canonical_label_axes_order,
@@ -41,7 +40,6 @@ from ngio.ome_zarr_meta.ngio_specs._ngio_image import (
41
40
  NgioImageLabelMeta,
42
41
  NgioImageMeta,
43
42
  NgioLabelMeta,
44
- NgioLabelsGroupMeta,
45
43
  )
46
44
  from ngio.ome_zarr_meta.ngio_specs._pixel_size import PixelSize
47
45
 
@@ -64,13 +62,11 @@ __all__ = [
64
62
  "NgioImageLabelMeta",
65
63
  "NgioImageMeta",
66
64
  "NgioLabelMeta",
67
- "NgioLabelsGroupMeta",
68
65
  "NgioPlateMeta",
69
66
  "NgioWellMeta",
70
67
  "PixelSize",
71
68
  "SpaceUnits",
72
69
  "TimeUnits",
73
- "build_axes_handler",
74
70
  "build_canonical_axes_handler",
75
71
  "canonical_axes_order",
76
72
  "canonical_label_axes_order",
@@ -4,7 +4,7 @@ from collections.abc import Sequence
4
4
  from enum import Enum
5
5
  from typing import Literal, TypeAlias, TypeVar
6
6
 
7
- from pydantic import BaseModel, ConfigDict, Field, model_validator
7
+ from pydantic import BaseModel, ConfigDict, Field
8
8
 
9
9
  from ngio.utils import NgioValidationError, NgioValueError
10
10
 
@@ -153,51 +153,9 @@ class AxesSetup(BaseModel):
153
153
  c: str = "c"
154
154
  t: str = "t"
155
155
  others: list[str] = Field(default_factory=list)
156
- allow_non_canonical_axes: bool = False
157
- strict_canonical_order: bool = False
158
156
 
159
157
  model_config = ConfigDict(extra="forbid", frozen=True)
160
158
 
161
- @model_validator(mode="after")
162
- def _validate_axes_values(self) -> "AxesSetup":
163
- """Validate the axes values."""
164
- canonical = {"x", "y", "z", "c", "t"}
165
- axes = {"x": self.x, "y": self.y, "z": self.z, "c": self.c, "t": self.t}
166
-
167
- for axis_name, axis_value in axes.items():
168
- reserved = canonical - {axis_name}
169
- if axis_value in reserved:
170
- raise NgioValueError(
171
- f"The {axis_name} axis cannot be called: '{axis_value}'. "
172
- f"{axis_value} is reserved. If you want to set a non canonical "
173
- "axis order, please set the 'strict_canonical_order'to False."
174
- )
175
- return self
176
-
177
- @classmethod
178
- def from_ordered_list(
179
- cls, axes_names: Sequence[str], canonical_order: Sequence[str]
180
- ):
181
- """Create an AxesSetup from an ordered list of axes names."""
182
- # Make sure to only keep as many default axes as provided in axes_names
183
- if len(axes_names) > len(canonical_order):
184
- raise NgioValueError(
185
- f"Cannot create AxesSetup from axes names {axes_names} "
186
- f"and canonical order {canonical_order}. "
187
- "The number of axes names cannot be greater than the "
188
- "number of canonical axes."
189
- )
190
- canonical_order = list(canonical_order)
191
- chanonical_axes = canonical_axes_order()
192
- axes_mapping = {}
193
- for ax in reversed(axes_names):
194
- c_ax = canonical_order.pop()
195
- if ax in chanonical_axes:
196
- axes_mapping[ax] = ax
197
- else:
198
- axes_mapping[c_ax] = ax
199
- return cls(**axes_mapping)
200
-
201
159
  def canonical_map(self) -> dict[str, str]:
202
160
  """Get the canonical map of axes."""
203
161
  return {
@@ -239,9 +197,9 @@ def _check_unique_names(axes: Sequence[Axis]):
239
197
  )
240
198
 
241
199
 
242
- def _check_non_canonical_axes(axes_setup: AxesSetup):
200
+ def _check_non_canonical_axes(axes_setup: AxesSetup, allow_non_canonical_axes: bool):
243
201
  """Check if all axes are known."""
244
- if not axes_setup.allow_non_canonical_axes and len(axes_setup.others) > 0:
202
+ if not allow_non_canonical_axes and len(axes_setup.others) > 0:
245
203
  raise NgioValidationError(
246
204
  f"Unknown axes {axes_setup.others}. Please set "
247
205
  "`allow_non_canonical_axes=True` to ignore them"
@@ -261,9 +219,11 @@ def _check_axes_validity(axes: Sequence[Axis], axes_setup: AxesSetup):
261
219
  )
262
220
 
263
221
 
264
- def _check_canonical_order(axes: Sequence[Axis], axes_setup: AxesSetup):
222
+ def _check_canonical_order(
223
+ axes: Sequence[Axis], axes_setup: AxesSetup, strict_canonical_order: bool
224
+ ):
265
225
  """Check if the axes are in the canonical order."""
266
- if not axes_setup.strict_canonical_order:
226
+ if not strict_canonical_order:
267
227
  return
268
228
  _names = [ax.name for ax in axes]
269
229
  _canonical_order = []
@@ -282,18 +242,24 @@ def _check_canonical_order(axes: Sequence[Axis], axes_setup: AxesSetup):
282
242
  def validate_axes(
283
243
  axes: Sequence[Axis],
284
244
  axes_setup: AxesSetup,
245
+ allow_non_canonical_axes: bool = False,
246
+ strict_canonical_order: bool = False,
285
247
  ) -> None:
286
248
  """Validate the axes."""
287
- if axes_setup.allow_non_canonical_axes and axes_setup.strict_canonical_order:
288
- raise NgioValueError(
249
+ if allow_non_canonical_axes and strict_canonical_order:
250
+ raise NgioValidationError(
289
251
  "`allow_non_canonical_axes` and"
290
252
  "`strict_canonical_order` cannot be true at the same time."
291
253
  "If non canonical axes are allowed, the order cannot be checked."
292
254
  )
293
255
  _check_unique_names(axes=axes)
294
- _check_non_canonical_axes(axes_setup=axes_setup)
256
+ _check_non_canonical_axes(
257
+ axes_setup=axes_setup, allow_non_canonical_axes=allow_non_canonical_axes
258
+ )
295
259
  _check_axes_validity(axes=axes, axes_setup=axes_setup)
296
- _check_canonical_order(axes=axes, axes_setup=axes_setup)
260
+ _check_canonical_order(
261
+ axes=axes, axes_setup=axes_setup, strict_canonical_order=strict_canonical_order
262
+ )
297
263
 
298
264
 
299
265
  class AxesHandler:
@@ -312,22 +278,29 @@ class AxesHandler:
312
278
  axes: Sequence[Axis],
313
279
  # user defined args
314
280
  axes_setup: AxesSetup | None = None,
281
+ allow_non_canonical_axes: bool = False,
282
+ strict_canonical_order: bool = False,
315
283
  ):
316
284
  """Create a new AxesMapper object.
317
285
 
318
286
  Args:
319
287
  axes (list[Axis]): The axes on disk.
320
288
  axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
289
+ allow_non_canonical_axes (bool, optional): Allow non canonical axes.
290
+ strict_canonical_order (bool, optional): Check if the axes are in the
291
+ canonical order. Defaults to False.
321
292
  """
322
293
  axes_setup = axes_setup if axes_setup is not None else AxesSetup()
323
294
 
324
295
  validate_axes(
325
296
  axes=axes,
326
297
  axes_setup=axes_setup,
298
+ allow_non_canonical_axes=allow_non_canonical_axes,
299
+ strict_canonical_order=strict_canonical_order,
327
300
  )
328
301
 
329
- self._allow_non_canonical_axes = axes_setup.allow_non_canonical_axes
330
- self._strict_canonical_order = axes_setup.strict_canonical_order
302
+ self._allow_non_canonical_axes = allow_non_canonical_axes
303
+ self._strict_canonical_order = strict_canonical_order
331
304
 
332
305
  self._canonical_order = canonical_axes_order()
333
306
 
@@ -379,7 +352,6 @@ class AxesHandler:
379
352
 
380
353
  @property
381
354
  def axes_names(self) -> tuple[str, ...]:
382
- """On disk axes names."""
383
355
  return tuple(ax.name for ax in self._axes)
384
356
 
385
357
  @property
@@ -449,48 +421,8 @@ class AxesHandler:
449
421
  return AxesHandler(
450
422
  axes=new_axes,
451
423
  axes_setup=self.axes_setup,
452
- )
453
-
454
- def rename_axes(self, axes_names: Sequence[str]) -> "AxesHandler":
455
- """Rename the axes.
456
-
457
- Args:
458
- axes_names (Sequence[str]): The new axes names.
459
- """
460
- if len(axes_names) != len(self.axes):
461
- raise NgioValueError(
462
- f"Cannot rename axes. "
463
- f"Expected {len(self.axes)} axes, but got {len(axes_names)}."
464
- )
465
- new_axes = []
466
- axes_setup = self.axes_setup
467
- for ax, new_name in zip(self.axes, axes_names, strict=True):
468
- if ax.name == new_name:
469
- new_axes.append(ax)
470
- continue
471
- new_ax = Axis(name=new_name, axis_type=ax.axis_type, unit=ax.unit)
472
- match ax.name:
473
- case axes_setup.x:
474
- axes_setup = axes_setup.model_copy(update={"x": new_name})
475
- case axes_setup.y:
476
- axes_setup = axes_setup.model_copy(update={"y": new_name})
477
- case axes_setup.z:
478
- axes_setup = axes_setup.model_copy(update={"z": new_name})
479
- case axes_setup.c:
480
- axes_setup = axes_setup.model_copy(update={"c": new_name})
481
- case axes_setup.t:
482
- axes_setup = axes_setup.model_copy(update={"t": new_name})
483
- case _:
484
- if ax.name in axes_setup.others:
485
- others = axes_setup.others.copy()
486
- others.remove(ax.name)
487
- others.append(new_name)
488
- axes_setup = axes_setup.model_copy(update={"others": others})
489
- new_axes.append(new_ax)
490
- # Update the axes setup
491
- return AxesHandler(
492
- axes=new_axes,
493
- axes_setup=axes_setup,
424
+ allow_non_canonical_axes=self.allow_non_canonical_axes,
425
+ strict_canonical_order=self.strict_canonical_order,
494
426
  )
495
427
 
496
428
  def get_index(self, name: str) -> int | None:
@@ -532,33 +464,14 @@ class AxesHandler:
532
464
  self._axes = new_axes
533
465
 
534
466
 
535
- def _build_axes_list_from_names(
536
- axes_names: Sequence[str],
537
- axes_setup: AxesSetup,
538
- space_units: SpaceUnits | str | None = DefaultSpaceUnit,
539
- time_units: TimeUnits | str | None = DefaultTimeUnit,
540
- ) -> list[Axis]:
541
- """Build a list of Axis objects from a list of axis names."""
542
- axes = []
543
- for name in axes_names:
544
- c_name = axes_setup.get_canonical_name(name)
545
- match c_name:
546
- case "t":
547
- axes.append(Axis(name=name, axis_type=AxisType.time, unit=time_units))
548
- case "c":
549
- axes.append(Axis(name=name, axis_type=AxisType.channel))
550
- case "z" | "y" | "x":
551
- axes.append(Axis(name=name, axis_type=AxisType.space, unit=space_units))
552
- case _:
553
- axes.append(Axis(name=name, axis_type=AxisType.space))
554
- return axes
555
-
556
-
557
467
  def build_canonical_axes_handler(
558
468
  axes_names: Sequence[str],
559
- canonical_channel_order: Sequence[str] | None = None,
560
469
  space_units: SpaceUnits | str | None = DefaultSpaceUnit,
561
470
  time_units: TimeUnits | str | None = DefaultTimeUnit,
471
+ # user defined args
472
+ axes_setup: AxesSetup | None = None,
473
+ allow_non_canonical_axes: bool = False,
474
+ strict_canonical_order: bool = False,
562
475
  ) -> AxesHandler:
563
476
  """Create a new canonical axes mapper.
564
477
 
@@ -569,47 +482,33 @@ def build_canonical_axes_handler(
569
482
  - If an integer is provided, the axes are created from the last axis
570
483
  to the first
571
484
  e.g. 3 -> ["z", "y", "x"]
572
- canonical_channel_order (Sequence[str], optional): The canonical channel
573
- order. Defaults to None, which uses the default order.
574
485
  space_units (SpaceUnits, optional): The space units. Defaults to None.
575
486
  time_units (TimeUnits, optional): The time units. Defaults to None.
576
- """
577
- if canonical_channel_order is None:
578
- canonical_channel_order = canonical_axes_order()
579
- axes_setup = AxesSetup.from_ordered_list(
580
- axes_names=axes_names, canonical_order=canonical_channel_order
581
- )
582
- axes = _build_axes_list_from_names(
583
- axes_names=axes_names,
584
- axes_setup=axes_setup,
585
- space_units=space_units,
586
- time_units=time_units,
587
- )
588
- return AxesHandler(
589
- axes=axes,
590
- axes_setup=axes_setup,
591
- )
592
-
593
-
594
- def build_axes_handler(
595
- axes_names: Sequence[str],
596
- axes_setup: AxesSetup | None = None,
597
- ) -> AxesHandler:
598
- """Create a new axes mapper.
599
-
600
- Args:
601
- axes_names (Sequence[str]): The axes names on disk.
602
487
  axes_setup (AxesSetup, optional): The axis setup. Defaults to None.
603
488
  allow_non_canonical_axes (bool, optional): Allow non canonical axes.
489
+ Defaults to False.
604
490
  strict_canonical_order (bool, optional): Check if the axes are in the
605
491
  canonical order. Defaults to False.
492
+
606
493
  """
607
- axes_setup = axes_setup if axes_setup is not None else AxesSetup()
608
- axes = _build_axes_list_from_names(
609
- axes_names=axes_names,
610
- axes_setup=axes_setup,
611
- )
494
+ axes = []
495
+ for name in axes_names:
496
+ match name:
497
+ case "t":
498
+ axes.append(Axis(name=name, axis_type=AxisType.time, unit=time_units))
499
+ case "c":
500
+ axes.append(Axis(name=name, axis_type=AxisType.channel))
501
+ case "z" | "y" | "x":
502
+ axes.append(Axis(name=name, axis_type=AxisType.space, unit=space_units))
503
+ case _:
504
+ raise NgioValueError(
505
+ f"Invalid axis name '{name}'. "
506
+ "Only 't', 'c', 'z', 'y', 'x' are allowed."
507
+ )
508
+
612
509
  return AxesHandler(
613
510
  axes=axes,
614
511
  axes_setup=axes_setup,
512
+ allow_non_canonical_axes=allow_non_canonical_axes,
513
+ strict_canonical_order=strict_canonical_order,
615
514
  )
@@ -60,20 +60,11 @@ class Dataset:
60
60
  @property
61
61
  def pixel_size(self) -> PixelSize:
62
62
  """Return the pixel size for the dataset."""
63
- scale = self._scale
64
- pix_size_dict = {}
65
- # Mandatory axes: x, y
66
- for ax in ["x", "y"]:
67
- index = self.axes_handler.get_index(ax)
68
- assert index is not None
69
- pix_size_dict[ax] = scale[index]
70
-
71
- for ax in ["z", "t"]:
72
- index = self.axes_handler.get_index(ax)
73
- pix_size_dict[ax] = scale[index] if index is not None else 1.0
74
-
75
63
  return PixelSize(
76
- **pix_size_dict,
64
+ x=self.get_scale("x", default=1.0),
65
+ y=self.get_scale("y", default=1.0),
66
+ z=self.get_scale("z", default=1.0),
67
+ t=self.get_scale("t", default=1.0),
77
68
  space_unit=self.axes_handler.space_unit,
78
69
  time_unit=self.axes_handler.time_unit,
79
70
  )
@@ -87,3 +78,21 @@ class Dataset:
87
78
  def translation(self) -> tuple[float, ...]:
88
79
  """Return the translation as a tuple."""
89
80
  return tuple(self._translation)
81
+
82
+ def get_scale(self, axis_name: str, default: float | None = None) -> float:
83
+ """Return the scale for a given axis."""
84
+ idx = self.axes_handler.get_index(axis_name)
85
+ if idx is None:
86
+ if default is not None:
87
+ return default
88
+ raise ValueError(f"Axis {axis_name} not found in axes {self.axes_handler}.")
89
+ return self._scale[idx]
90
+
91
+ def get_translation(self, axis_name: str, default: float | None = None) -> float:
92
+ """Return the translation for a given axis."""
93
+ idx = self.axes_handler.get_index(axis_name)
94
+ if idx is None:
95
+ if default is not None:
96
+ return default
97
+ raise ValueError(f"Axis {axis_name} not found in axes {self.axes_handler}.")
98
+ return self._translation[idx]