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.
- ngio/__init__.py +40 -12
- ngio/common/__init__.py +16 -32
- ngio/common/_dimensions.py +270 -48
- ngio/common/_masking_roi.py +153 -0
- ngio/common/_pyramid.py +267 -73
- ngio/common/_roi.py +290 -66
- ngio/common/_synt_images_utils.py +101 -0
- ngio/common/_zoom.py +54 -22
- ngio/experimental/__init__.py +5 -0
- ngio/experimental/iterators/__init__.py +15 -0
- ngio/experimental/iterators/_abstract_iterator.py +390 -0
- ngio/experimental/iterators/_feature.py +189 -0
- ngio/experimental/iterators/_image_processing.py +130 -0
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_rois_utils.py +126 -0
- ngio/experimental/iterators/_segmentation.py +235 -0
- ngio/hcs/__init__.py +17 -58
- ngio/hcs/_plate.py +1354 -0
- ngio/images/__init__.py +30 -9
- ngio/images/_abstract_image.py +968 -0
- ngio/images/_create_synt_container.py +132 -0
- ngio/images/_create_utils.py +423 -0
- ngio/images/_image.py +926 -0
- ngio/images/_label.py +417 -0
- ngio/images/_masked_image.py +531 -0
- ngio/images/_ome_zarr_container.py +1235 -0
- ngio/images/_table_ops.py +471 -0
- ngio/io_pipes/__init__.py +75 -0
- ngio/io_pipes/_io_pipes.py +361 -0
- ngio/io_pipes/_io_pipes_masked.py +488 -0
- ngio/io_pipes/_io_pipes_roi.py +146 -0
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_match_shape.py +377 -0
- ngio/io_pipes/_ops_axes.py +344 -0
- ngio/io_pipes/_ops_slices.py +411 -0
- ngio/io_pipes/_ops_slices_utils.py +199 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +180 -0
- ngio/ome_zarr_meta/__init__.py +39 -15
- ngio/ome_zarr_meta/_meta_handlers.py +490 -96
- ngio/ome_zarr_meta/ngio_specs/__init__.py +24 -10
- ngio/ome_zarr_meta/ngio_specs/_axes.py +268 -234
- ngio/ome_zarr_meta/ngio_specs/_channels.py +125 -41
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +42 -87
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +536 -2
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +202 -198
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +72 -34
- ngio/ome_zarr_meta/v04/__init__.py +21 -5
- ngio/ome_zarr_meta/v04/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +151 -90
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +511 -0
- 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 +55 -0
- ngio/resources/resource_model.py +36 -0
- ngio/tables/__init__.py +20 -4
- ngio/tables/_abstract_table.py +270 -0
- ngio/tables/_tables_container.py +449 -0
- ngio/tables/backends/__init__.py +50 -1
- ngio/tables/backends/_abstract_backend.py +200 -31
- ngio/tables/backends/_anndata.py +139 -0
- ngio/tables/backends/_anndata_utils.py +10 -114
- ngio/tables/backends/_csv.py +19 -0
- ngio/tables/backends/_json.py +92 -0
- ngio/tables/backends/_parquet.py +19 -0
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_table_backends.py +162 -38
- ngio/tables/backends/_utils.py +608 -0
- ngio/tables/v1/__init__.py +19 -4
- ngio/tables/v1/_condition_table.py +71 -0
- ngio/tables/v1/_feature_table.py +79 -115
- ngio/tables/v1/_generic_table.py +21 -90
- ngio/tables/v1/_roi_table.py +486 -137
- ngio/transforms/__init__.py +5 -0
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/__init__.py +16 -14
- ngio/utils/_cache.py +48 -0
- ngio/utils/_datasets.py +121 -13
- ngio/utils/_fractal_fsspec_store.py +42 -0
- ngio/utils/_zarr_utils.py +374 -218
- ngio-0.5.0b4.dist-info/METADATA +147 -0
- ngio-0.5.0b4.dist-info/RECORD +88 -0
- {ngio-0.2.0a2.dist-info → ngio-0.5.0b4.dist-info}/WHEEL +1 -1
- ngio/common/_array_pipe.py +0 -160
- ngio/common/_axes_transforms.py +0 -63
- ngio/common/_common_types.py +0 -5
- ngio/common/_slicer.py +0 -97
- ngio/images/abstract_image.py +0 -240
- ngio/images/create.py +0 -251
- ngio/images/image.py +0 -389
- ngio/images/label.py +0 -236
- ngio/images/omezarr_container.py +0 -535
- ngio/ome_zarr_meta/_generic_handlers.py +0 -320
- ngio/ome_zarr_meta/v04/_meta_handlers.py +0 -54
- ngio/tables/_validators.py +0 -192
- ngio/tables/backends/_anndata_v1.py +0 -75
- ngio/tables/backends/_json_v1.py +0 -56
- ngio/tables/tables_container.py +0 -300
- ngio/tables/v1/_masking_roi_table.py +0 -175
- ngio/utils/_logger.py +0 -29
- ngio-0.2.0a2.dist-info/METADATA +0 -95
- ngio-0.2.0a2.dist-info/RECORD +0 -53
- {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
|