ngio 0.2.0a2__py3-none-any.whl → 0.5.0b4__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 (106) hide show
  1. ngio/__init__.py +40 -12
  2. ngio/common/__init__.py +16 -32
  3. ngio/common/_dimensions.py +270 -48
  4. ngio/common/_masking_roi.py +153 -0
  5. ngio/common/_pyramid.py +267 -73
  6. ngio/common/_roi.py +290 -66
  7. ngio/common/_synt_images_utils.py +101 -0
  8. ngio/common/_zoom.py +54 -22
  9. ngio/experimental/__init__.py +5 -0
  10. ngio/experimental/iterators/__init__.py +15 -0
  11. ngio/experimental/iterators/_abstract_iterator.py +390 -0
  12. ngio/experimental/iterators/_feature.py +189 -0
  13. ngio/experimental/iterators/_image_processing.py +130 -0
  14. ngio/experimental/iterators/_mappers.py +48 -0
  15. ngio/experimental/iterators/_rois_utils.py +126 -0
  16. ngio/experimental/iterators/_segmentation.py +235 -0
  17. ngio/hcs/__init__.py +17 -58
  18. ngio/hcs/_plate.py +1354 -0
  19. ngio/images/__init__.py +30 -9
  20. ngio/images/_abstract_image.py +968 -0
  21. ngio/images/_create_synt_container.py +132 -0
  22. ngio/images/_create_utils.py +423 -0
  23. ngio/images/_image.py +926 -0
  24. ngio/images/_label.py +417 -0
  25. ngio/images/_masked_image.py +531 -0
  26. ngio/images/_ome_zarr_container.py +1235 -0
  27. ngio/images/_table_ops.py +471 -0
  28. ngio/io_pipes/__init__.py +75 -0
  29. ngio/io_pipes/_io_pipes.py +361 -0
  30. ngio/io_pipes/_io_pipes_masked.py +488 -0
  31. ngio/io_pipes/_io_pipes_roi.py +146 -0
  32. ngio/io_pipes/_io_pipes_types.py +56 -0
  33. ngio/io_pipes/_match_shape.py +377 -0
  34. ngio/io_pipes/_ops_axes.py +344 -0
  35. ngio/io_pipes/_ops_slices.py +411 -0
  36. ngio/io_pipes/_ops_slices_utils.py +199 -0
  37. ngio/io_pipes/_ops_transforms.py +104 -0
  38. ngio/io_pipes/_zoom_transform.py +180 -0
  39. ngio/ome_zarr_meta/__init__.py +39 -15
  40. ngio/ome_zarr_meta/_meta_handlers.py +490 -96
  41. ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
  42. ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
  43. ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
  44. ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
  45. ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
  46. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
  47. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
  48. ngio/ome_zarr_meta/v04/__init__.py +21 -5
  49. ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
  50. ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
  51. ngio/ome_zarr_meta/v05/__init__.py +27 -0
  52. ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
  53. ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
  54. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/mask.png +0 -0
  55. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  56. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/raw.jpg +0 -0
  57. ngio/resources/__init__.py +55 -0
  58. ngio/resources/resource_model.py +36 -0
  59. ngio/tables/__init__.py +20 -4
  60. ngio/tables/_abstract_table.py +270 -0
  61. ngio/tables/_tables_container.py +449 -0
  62. ngio/tables/backends/__init__.py +50 -1
  63. ngio/tables/backends/_abstract_backend.py +200 -31
  64. ngio/tables/backends/_anndata.py +139 -0
  65. ngio/tables/backends/_anndata_utils.py +10 -114
  66. ngio/tables/backends/_csv.py +19 -0
  67. ngio/tables/backends/_json.py +92 -0
  68. ngio/tables/backends/_parquet.py +19 -0
  69. ngio/tables/backends/_py_arrow_backends.py +222 -0
  70. ngio/tables/backends/_table_backends.py +162 -38
  71. ngio/tables/backends/_utils.py +608 -0
  72. ngio/tables/v1/__init__.py +19 -4
  73. ngio/tables/v1/_condition_table.py +71 -0
  74. ngio/tables/v1/_feature_table.py +79 -115
  75. ngio/tables/v1/_generic_table.py +21 -90
  76. ngio/tables/v1/_roi_table.py +486 -137
  77. ngio/transforms/__init__.py +5 -0
  78. ngio/transforms/_zoom.py +19 -0
  79. ngio/utils/__init__.py +16 -14
  80. ngio/utils/_cache.py +48 -0
  81. ngio/utils/_datasets.py +121 -13
  82. ngio/utils/_fractal_fsspec_store.py +42 -0
  83. ngio/utils/_zarr_utils.py +374 -218
  84. ngio-0.5.0b4.dist-info/METADATA +147 -0
  85. ngio-0.5.0b4.dist-info/RECORD +88 -0
  86. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
  87. ngio/common/_array_pipe.py +0 -160
  88. ngio/common/_axes_transforms.py +0 -63
  89. ngio/common/_common_types.py +0 -5
  90. ngio/common/_slicer.py +0 -97
  91. ngio/images/abstract_image.py +0 -240
  92. ngio/images/create.py +0 -251
  93. ngio/images/image.py +0 -389
  94. ngio/images/label.py +0 -236
  95. ngio/images/omezarr_container.py +0 -535
  96. ngio/ome_zarr_meta/_generic_handlers.py +0 -320
  97. ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
  98. ngio/tables/_validators.py +0 -192
  99. ngio/tables/backends/_anndata_v1.py +0 -75
  100. ngio/tables/backends/_json_v1.py +0 -56
  101. ngio/tables/tables_container.py +0 -300
  102. ngio/tables/v1/_masking_roi_table.py +0 -175
  103. ngio/utils/_logger.py +0 -29
  104. ngio-0.2.0a2.dist-info/METADATA +0 -95
  105. ngio-0.2.0a2.dist-info/RECORD +0 -53
  106. {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,344 @@
1
+ from collections.abc import Sequence
2
+ from typing import TypeVar
3
+
4
+ import dask.array as da
5
+ import numpy as np
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from ngio.common._dimensions import Dimensions
9
+ from ngio.utils import NgioValueError
10
+
11
+ ##############################################################
12
+ #
13
+ # "AxesOps" Model
14
+ #
15
+ ##############################################################
16
+
17
+
18
+ class AxesOps(BaseModel):
19
+ """Model to represent axes operations.
20
+
21
+ This model will be used to transform objects from on disk axes to in memory axes.
22
+ """
23
+
24
+ input_axes: tuple[str, ...]
25
+ output_axes: tuple[str, ...]
26
+ transpose_op: tuple[int, ...] | None = None
27
+ expand_op: tuple[int, ...] | None = None
28
+ squeeze_op: tuple[int, ...] | None = None
29
+ model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
30
+
31
+ @property
32
+ def is_no_op(self) -> bool:
33
+ """Check if all operations are no ops."""
34
+ if (
35
+ self.transpose_op is None
36
+ and self.expand_op is None
37
+ and self.squeeze_op is None
38
+ ):
39
+ return True
40
+ return False
41
+
42
+ @property
43
+ def get_transpose_op(self) -> tuple[int, ...] | None:
44
+ """Get the transpose axes."""
45
+ return self.transpose_op
46
+
47
+ @property
48
+ def get_expand_op(self) -> tuple[int, ...] | None:
49
+ """Get the expand axes."""
50
+ return self.expand_op
51
+
52
+ @property
53
+ def get_squeeze_op(self) -> tuple[int, ...] | None:
54
+ """Get the squeeze axes."""
55
+ return self.squeeze_op
56
+
57
+ @property
58
+ def set_transpose_op(self) -> tuple[int, ...] | None:
59
+ """Set the transpose axes."""
60
+ if self.transpose_op is None:
61
+ return None
62
+ return tuple(np.argsort(self.transpose_op))
63
+
64
+ @property
65
+ def set_expand_op(self) -> tuple[int, ...] | None:
66
+ """Set the expand axes."""
67
+ return self.squeeze_op
68
+
69
+ @property
70
+ def set_squeeze_op(self) -> tuple[int, ...] | None:
71
+ """Set the squeeze axes."""
72
+ return self.expand_op
73
+
74
+
75
+ ##############################################################
76
+ #
77
+ # Axes Operations implementations
78
+ #
79
+ ##############################################################
80
+
81
+
82
+ def _apply_numpy_axes_ops(
83
+ array: np.ndarray,
84
+ squeeze_axes: tuple[int, ...] | None = None,
85
+ transpose_axes: tuple[int, ...] | None = None,
86
+ expand_axes: tuple[int, ...] | None = None,
87
+ ) -> np.ndarray:
88
+ """Apply axes operations to a numpy array."""
89
+ if squeeze_axes is not None:
90
+ array = np.squeeze(array, axis=squeeze_axes)
91
+ if transpose_axes is not None:
92
+ array = np.transpose(array, axes=transpose_axes)
93
+ if expand_axes is not None:
94
+ array = np.expand_dims(array, axis=expand_axes)
95
+ return array
96
+
97
+
98
+ def _apply_dask_axes_ops(
99
+ array: da.Array,
100
+ squeeze_axes: tuple[int, ...] | None = None,
101
+ transpose_axes: tuple[int, ...] | None = None,
102
+ expand_axes: tuple[int, ...] | None = None,
103
+ ) -> da.Array:
104
+ """Apply axes operations to a dask array."""
105
+ if squeeze_axes is not None:
106
+ array = da.squeeze(array, axis=squeeze_axes)
107
+ if transpose_axes is not None:
108
+ array = da.transpose(array, axes=transpose_axes)
109
+ if expand_axes is not None:
110
+ array = da.expand_dims(array, axis=expand_axes)
111
+ return array
112
+
113
+
114
+ T = TypeVar("T")
115
+
116
+
117
+ def _apply_sequence_axes_ops(
118
+ input_: Sequence[T],
119
+ default: T,
120
+ squeeze_axes: tuple[int, ...] | None = None,
121
+ transpose_axes: tuple[int, ...] | None = None,
122
+ expand_axes: tuple[int, ...] | None = None,
123
+ ) -> list[T]:
124
+ input_list = list(input_)
125
+ if squeeze_axes is not None:
126
+ for offset, ax in enumerate(squeeze_axes):
127
+ input_list.pop(ax - offset)
128
+
129
+ if transpose_axes is not None:
130
+ input_list = [input_list[i] for i in transpose_axes]
131
+
132
+ if expand_axes is not None:
133
+ for ax in expand_axes:
134
+ input_list.insert(ax, default)
135
+
136
+ return input_list
137
+
138
+
139
+ def get_as_numpy_axes_ops(
140
+ array: np.ndarray,
141
+ axes_ops: AxesOps,
142
+ ) -> np.ndarray:
143
+ """Apply axes operations to a numpy array."""
144
+ return _apply_numpy_axes_ops(
145
+ array,
146
+ squeeze_axes=axes_ops.get_squeeze_op,
147
+ transpose_axes=axes_ops.get_transpose_op,
148
+ expand_axes=axes_ops.get_expand_op,
149
+ )
150
+
151
+
152
+ def get_as_dask_axes_ops(
153
+ array: da.Array,
154
+ axes_ops: AxesOps,
155
+ ) -> da.Array:
156
+ """Apply axes operations to a dask array."""
157
+ return _apply_dask_axes_ops(
158
+ array,
159
+ squeeze_axes=axes_ops.get_squeeze_op,
160
+ transpose_axes=axes_ops.get_transpose_op,
161
+ expand_axes=axes_ops.get_expand_op,
162
+ )
163
+
164
+
165
+ def get_as_sequence_axes_ops(
166
+ input_: Sequence[T],
167
+ axes_ops: AxesOps,
168
+ default: T,
169
+ ) -> list[T]:
170
+ """Apply axes operations to a sequence."""
171
+ return _apply_sequence_axes_ops(
172
+ input_,
173
+ default=default,
174
+ squeeze_axes=axes_ops.get_squeeze_op,
175
+ transpose_axes=axes_ops.get_transpose_op,
176
+ expand_axes=axes_ops.get_expand_op,
177
+ )
178
+
179
+
180
+ def set_as_numpy_axes_ops(
181
+ array: np.ndarray,
182
+ axes_ops: AxesOps,
183
+ ) -> np.ndarray:
184
+ """Apply inverse axes operations to a numpy array."""
185
+ return _apply_numpy_axes_ops(
186
+ array,
187
+ squeeze_axes=axes_ops.set_squeeze_op,
188
+ transpose_axes=axes_ops.set_transpose_op,
189
+ expand_axes=axes_ops.set_expand_op,
190
+ )
191
+
192
+
193
+ def set_as_dask_axes_ops(
194
+ array: da.Array,
195
+ axes_ops: AxesOps,
196
+ ) -> da.Array:
197
+ """Apply inverse axes operations to a dask array."""
198
+ return _apply_dask_axes_ops(
199
+ array,
200
+ squeeze_axes=axes_ops.set_squeeze_op,
201
+ transpose_axes=axes_ops.set_transpose_op,
202
+ expand_axes=axes_ops.set_expand_op,
203
+ )
204
+
205
+
206
+ def set_as_sequence_axes_ops(
207
+ input_: Sequence[T],
208
+ axes_ops: AxesOps,
209
+ default: T,
210
+ ) -> list[T]:
211
+ """Apply inverse axes operations to a sequence."""
212
+ return _apply_sequence_axes_ops(
213
+ input_,
214
+ default=default,
215
+ squeeze_axes=axes_ops.set_squeeze_op,
216
+ transpose_axes=axes_ops.set_transpose_op,
217
+ expand_axes=axes_ops.set_expand_op,
218
+ )
219
+
220
+
221
+ ##############################################################
222
+ #
223
+ # Builder functions
224
+ #
225
+ ##############################################################
226
+
227
+
228
+ def _check_output_axes(axes: Sequence[str]) -> None:
229
+ """Check that the input axes are valid."""
230
+ unique_names = set(axes)
231
+ if len(unique_names) != len(axes):
232
+ raise NgioValueError(
233
+ "Duplicate axis names found. Please provide unique names for each axis."
234
+ )
235
+ for name in axes:
236
+ if not isinstance(name, str):
237
+ raise NgioValueError(
238
+ f"Invalid axis name '{name}'. Axis names must be strings."
239
+ )
240
+
241
+
242
+ def _build_squeeze_tuple(
243
+ input_axes: tuple[str, ...], output_axes: tuple[str, ...]
244
+ ) -> tuple[tuple[int, ...], tuple[str, ...]]:
245
+ """Build a tuple of axes to squeeze."""
246
+ axes_to_squeeze = []
247
+ axes_after_squeeze = []
248
+ for i, ax in enumerate(input_axes):
249
+ if ax not in output_axes:
250
+ axes_to_squeeze.append(i)
251
+ else:
252
+ axes_after_squeeze.append(ax)
253
+ return tuple(axes_to_squeeze), tuple(axes_after_squeeze)
254
+
255
+
256
+ def _build_transpose_tuple(
257
+ input_axes: tuple[str, ...], output_axes: tuple[str, ...]
258
+ ) -> tuple[tuple[int, ...], tuple[str, ...]]:
259
+ """Build a tuple of axes to transpose."""
260
+ transposition_order = []
261
+ axes_names_after_transpose = []
262
+ for ax in output_axes:
263
+ if ax in input_axes:
264
+ transposition_order.append(input_axes.index(ax))
265
+ axes_names_after_transpose.append(ax)
266
+ return tuple(transposition_order), tuple(axes_names_after_transpose)
267
+
268
+
269
+ def _build_expand_tuple(
270
+ input_axes: tuple[str, ...], output_axes: tuple[str, ...]
271
+ ) -> tuple[int, ...]:
272
+ """Build a tuple of axes to expand."""
273
+ axes_to_expand = []
274
+ for i, ax in enumerate(output_axes):
275
+ if ax not in input_axes:
276
+ axes_to_expand.append(i)
277
+ return tuple(axes_to_expand)
278
+
279
+
280
+ def _build_axes_ops(
281
+ input_axes: tuple[str, ...], output_axes: tuple[str, ...]
282
+ ) -> AxesOps:
283
+ """Change the order of the axes."""
284
+ # Validate the names
285
+ _check_output_axes(output_axes)
286
+ # Step 1: Check find squeeze axes
287
+ axes_to_squeeze, input_axes = _build_squeeze_tuple(input_axes, output_axes)
288
+ # Step 2: Find the transposition order
289
+ transposition_order, input_axes = _build_transpose_tuple(input_axes, output_axes)
290
+ # Step 3: Find axes to expand
291
+ axes_to_expand = _build_expand_tuple(input_axes, output_axes)
292
+
293
+ # If the operations are empty, make them None
294
+ if len(axes_to_squeeze) == 0:
295
+ axes_to_squeeze = None
296
+
297
+ if np.allclose(transposition_order, np.arange(len(transposition_order))):
298
+ # If the transposition order is the identity, we don't need to transpose
299
+ transposition_order = None
300
+ if len(axes_to_expand) == 0:
301
+ axes_to_expand = None
302
+
303
+ return AxesOps(
304
+ input_axes=input_axes,
305
+ output_axes=output_axes,
306
+ transpose_op=transposition_order,
307
+ expand_op=axes_to_expand,
308
+ squeeze_op=axes_to_squeeze,
309
+ )
310
+
311
+
312
+ def _normalize_axes_order(
313
+ dimensions: Dimensions,
314
+ axes_order: Sequence[str],
315
+ ) -> tuple[str, ...]:
316
+ """Convert axes order to the on-disk axes names.
317
+
318
+ In this way there is not unambiguity in the axes order.
319
+ """
320
+ new_axes_order = []
321
+ for axis_name in axes_order:
322
+ axis = dimensions.axes_handler.get_axis(axis_name)
323
+ if axis is None:
324
+ new_axes_order.append(axis_name)
325
+ else:
326
+ new_axes_order.append(axis.name)
327
+ return tuple(new_axes_order)
328
+
329
+
330
+ def build_axes_ops(
331
+ *,
332
+ dimensions: Dimensions,
333
+ input_axes: tuple[str, ...],
334
+ axes_order: Sequence[str] | None,
335
+ ) -> AxesOps:
336
+ if axes_order is None:
337
+ return AxesOps(
338
+ input_axes=input_axes,
339
+ output_axes=input_axes,
340
+ )
341
+ output_axes = _normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
342
+
343
+ axes_ops = _build_axes_ops(input_axes=input_axes, output_axes=output_axes)
344
+ return axes_ops