ngio 0.4.0a2__py3-none-any.whl → 0.4.0a4__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 +1 -2
- ngio/common/__init__.py +2 -51
- ngio/common/_dimensions.py +223 -64
- ngio/common/_pyramid.py +42 -23
- ngio/common/_roi.py +94 -418
- ngio/common/_zoom.py +32 -7
- ngio/experimental/iterators/_abstract_iterator.py +2 -2
- ngio/experimental/iterators/_feature.py +10 -15
- ngio/experimental/iterators/_image_processing.py +18 -28
- ngio/experimental/iterators/_rois_utils.py +6 -6
- ngio/experimental/iterators/_segmentation.py +38 -54
- ngio/images/_abstract_image.py +136 -94
- ngio/images/_create.py +16 -0
- ngio/images/_create_synt_container.py +10 -0
- ngio/images/_image.py +33 -9
- ngio/images/_label.py +24 -3
- ngio/images/_masked_image.py +60 -81
- ngio/images/_ome_zarr_container.py +34 -1
- ngio/io_pipes/__init__.py +49 -0
- ngio/io_pipes/_io_pipes.py +286 -0
- ngio/io_pipes/_io_pipes_masked.py +481 -0
- ngio/io_pipes/_io_pipes_roi.py +143 -0
- ngio/io_pipes/_io_pipes_utils.py +299 -0
- ngio/io_pipes/_match_shape.py +376 -0
- ngio/io_pipes/_ops_axes.py +146 -0
- ngio/io_pipes/_ops_slices.py +218 -0
- ngio/io_pipes/_ops_transforms.py +104 -0
- ngio/io_pipes/_zoom_transform.py +175 -0
- ngio/ome_zarr_meta/__init__.py +6 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +6 -4
- ngio/ome_zarr_meta/ngio_specs/_axes.py +182 -70
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
- ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
- ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
- ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
- ngio/resources/__init__.py +1 -0
- ngio/resources/resource_model.py +1 -0
- ngio/tables/v1/_roi_table.py +11 -3
- ngio/{common/transforms → transforms}/__init__.py +1 -1
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/_zarr_utils.py +5 -1
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/METADATA +1 -1
- ngio-0.4.0a4.dist-info/RECORD +83 -0
- ngio/common/_array_io_pipes.py +0 -554
- ngio/common/_array_io_utils.py +0 -508
- ngio/common/transforms/_label.py +0 -12
- ngio/common/transforms/_zoom.py +0 -109
- ngio-0.4.0a2.dist-info/RECORD +0 -76
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a2.dist-info → ngio-0.4.0a4.dist-info}/licenses/LICENSE +0 -0
ngio/common/_array_io_utils.py
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping, Sequence
|
|
2
|
-
from typing import Protocol, TypeAlias, TypeVar
|
|
3
|
-
|
|
4
|
-
import dask.array as da
|
|
5
|
-
import numpy as np
|
|
6
|
-
import zarr
|
|
7
|
-
from dask.array import Array as DaskArray
|
|
8
|
-
|
|
9
|
-
from ngio.common._dimensions import Dimensions
|
|
10
|
-
from ngio.ome_zarr_meta.ngio_specs import Axis, SlicingOps
|
|
11
|
-
from ngio.utils import NgioValueError
|
|
12
|
-
|
|
13
|
-
SlicingInputType: TypeAlias = slice | Sequence[int] | int | None
|
|
14
|
-
SlicingType: TypeAlias = slice | tuple[int, ...] | int
|
|
15
|
-
ArrayLike: TypeAlias = np.ndarray | DaskArray
|
|
16
|
-
|
|
17
|
-
##############################################################
|
|
18
|
-
#
|
|
19
|
-
# Slicing Operations
|
|
20
|
-
#
|
|
21
|
-
##############################################################
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _validate_int(value: int, shape: int) -> int:
|
|
25
|
-
"""Validate an integer value for slicing."""
|
|
26
|
-
if not isinstance(value, int):
|
|
27
|
-
raise NgioValueError(f"Invalid value {value} of type {type(value)}")
|
|
28
|
-
if value < 0 or value >= shape:
|
|
29
|
-
raise NgioValueError(
|
|
30
|
-
f"Invalid value {value}. Index out of bounds for axis of shape {shape}"
|
|
31
|
-
)
|
|
32
|
-
return value
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _try_to_slice(input: Sequence[int]) -> slice | tuple[int, ...]:
|
|
36
|
-
"""Try to convert a list of integers into a slice if they are contiguous.
|
|
37
|
-
|
|
38
|
-
- If the input is empty, return an empty tuple.
|
|
39
|
-
- If the input is sorted, and contains contiguous integers,
|
|
40
|
-
return a slice from the minimum to the maximum integer.
|
|
41
|
-
- Otherwise, return the input as a tuple.
|
|
42
|
-
|
|
43
|
-
This is useful for optimizing array slicing operations
|
|
44
|
-
by allowing the use of slices when possible, which can be more efficient.
|
|
45
|
-
"""
|
|
46
|
-
if not input:
|
|
47
|
-
return ()
|
|
48
|
-
|
|
49
|
-
# If the input is not sorted, return it as a tuple
|
|
50
|
-
max_input = max(input)
|
|
51
|
-
min_input = min(input)
|
|
52
|
-
assert min_input >= 0, "Input must contain non-negative integers"
|
|
53
|
-
assert max_input >= 0, "Input must contain non-negative integers"
|
|
54
|
-
|
|
55
|
-
if sorted(input) == list(range(min_input, max_input + 1)):
|
|
56
|
-
return slice(min_input, max_input + 1)
|
|
57
|
-
|
|
58
|
-
return tuple(input)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _validate_iter_of_ints(value: Sequence, shape: int) -> slice | tuple[int, ...]:
|
|
62
|
-
value = [_validate_int(v, shape=shape) for v in value]
|
|
63
|
-
return _try_to_slice(value)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _validate_slice(value: slice, shape: int) -> slice:
|
|
67
|
-
"""Validate a slice object and return it with adjusted start and stop."""
|
|
68
|
-
start = value.start if value.start is not None else 0
|
|
69
|
-
start = max(start, 0)
|
|
70
|
-
stop = value.stop if value.stop is not None else shape
|
|
71
|
-
return slice(start, stop)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _remove_channel_slicing(
|
|
75
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
76
|
-
dimensions: Dimensions,
|
|
77
|
-
) -> dict[str, SlicingInputType]:
|
|
78
|
-
"""This utility function removes the channel selection from the slice kwargs.
|
|
79
|
-
|
|
80
|
-
if ignore_channel_selection is True, it will remove the channel selection
|
|
81
|
-
regardless of the dimensions. If the ignore_channel_selection is False
|
|
82
|
-
it will fail.
|
|
83
|
-
"""
|
|
84
|
-
if dimensions.is_multi_channels:
|
|
85
|
-
return slicing_dict
|
|
86
|
-
|
|
87
|
-
if "c" in slicing_dict:
|
|
88
|
-
slicing_dict.pop("c", None)
|
|
89
|
-
return slicing_dict
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _check_slicing_virtual_axes(slice_: SlicingInputType) -> bool:
|
|
93
|
-
"""Check if the slice_ is compatible with virtual axes.
|
|
94
|
-
|
|
95
|
-
Virtual axes are axes that are not present in the actual data,
|
|
96
|
-
such as time or channel axes in some datasets.
|
|
97
|
-
So the only valid slices for virtual axes are:
|
|
98
|
-
- None: means all data along the axis
|
|
99
|
-
- 0: means the first element along the axis
|
|
100
|
-
- slice([0, None], [1, None])
|
|
101
|
-
"""
|
|
102
|
-
if slice_ is None or slice_ == 0:
|
|
103
|
-
return True
|
|
104
|
-
if isinstance(slice_, slice):
|
|
105
|
-
if slice_.start is None and slice_.stop is None:
|
|
106
|
-
return True
|
|
107
|
-
if slice_.start == 0 and slice_.stop is None:
|
|
108
|
-
return True
|
|
109
|
-
if slice_.start is None and slice_.stop == 0:
|
|
110
|
-
return True
|
|
111
|
-
if slice_.start == 0 and slice_.stop == 1:
|
|
112
|
-
return True
|
|
113
|
-
if isinstance(slice_, Sequence):
|
|
114
|
-
if len(slice_) == 1 and slice_[0] == 0:
|
|
115
|
-
return True
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _normalize_slicing_dict(
|
|
120
|
-
dimensions: Dimensions,
|
|
121
|
-
slicing_dict: Mapping[str, SlicingInputType],
|
|
122
|
-
remove_channel_selection: bool = False,
|
|
123
|
-
) -> dict[str, SlicingInputType]:
|
|
124
|
-
"""Convert slice kwargs to the on-disk axes names."""
|
|
125
|
-
normalized_slicing_dict: dict[str, SlicingInputType] = {}
|
|
126
|
-
for axis_name, slice_ in slicing_dict.items():
|
|
127
|
-
axis = dimensions.axes_mapper.get_axis(axis_name)
|
|
128
|
-
if axis is None:
|
|
129
|
-
# Virtual axes should be allowed to be selected
|
|
130
|
-
# Common use case is still allowing channel_selection
|
|
131
|
-
# When the zarr has not channel axis.
|
|
132
|
-
if not _check_slicing_virtual_axes(slice_):
|
|
133
|
-
raise NgioValueError(
|
|
134
|
-
f"Invalid axis selection:{axis_name}={slice_}. "
|
|
135
|
-
f"Not found on the on-disk axes {dimensions.axes}."
|
|
136
|
-
)
|
|
137
|
-
# Virtual axes can be safely ignored
|
|
138
|
-
continue
|
|
139
|
-
on_disk_name = axis.on_disk_name
|
|
140
|
-
if on_disk_name in normalized_slicing_dict:
|
|
141
|
-
raise NgioValueError(
|
|
142
|
-
f"Duplicate axis {on_disk_name} in slice kwargs. "
|
|
143
|
-
"Please provide unique axis names."
|
|
144
|
-
)
|
|
145
|
-
normalized_slicing_dict[axis.on_disk_name] = slice_
|
|
146
|
-
|
|
147
|
-
if remove_channel_selection:
|
|
148
|
-
normalized_slicing_dict = _remove_channel_slicing(
|
|
149
|
-
slicing_dict=normalized_slicing_dict, dimensions=dimensions
|
|
150
|
-
)
|
|
151
|
-
return normalized_slicing_dict
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _normalize_axes_order(
|
|
155
|
-
dimensions: Dimensions,
|
|
156
|
-
axes_order: Sequence[str],
|
|
157
|
-
) -> list[str]:
|
|
158
|
-
"""Convert axes order to the on-disk axes names.
|
|
159
|
-
|
|
160
|
-
In this way there is not unambiguity in the axes order.
|
|
161
|
-
"""
|
|
162
|
-
new_axes_order = []
|
|
163
|
-
for axis_name in axes_order:
|
|
164
|
-
axis = dimensions.axes_mapper.get_axis(axis_name)
|
|
165
|
-
if axis is None:
|
|
166
|
-
new_axes_order.append(axis_name)
|
|
167
|
-
else:
|
|
168
|
-
new_axes_order.append(axis.on_disk_name)
|
|
169
|
-
return new_axes_order
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _normalize_slice_input(
|
|
173
|
-
axis: Axis,
|
|
174
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
175
|
-
dimensions: Dimensions,
|
|
176
|
-
requires_axes_ops: bool,
|
|
177
|
-
axes_order: list[str],
|
|
178
|
-
) -> SlicingType:
|
|
179
|
-
"""Normalize a slice input to a tuple of slices.
|
|
180
|
-
|
|
181
|
-
Make sure that the slice is valid for the given axis and dimensions.
|
|
182
|
-
And transform it to either a slice or a tuple of integers.
|
|
183
|
-
If the axis is not present in the slicing_dict, return a full slice.
|
|
184
|
-
"""
|
|
185
|
-
axis_name = axis.on_disk_name
|
|
186
|
-
if axis_name not in slicing_dict:
|
|
187
|
-
# If no slice is provided for the axis, use a full slice
|
|
188
|
-
return slice(None)
|
|
189
|
-
|
|
190
|
-
value = slicing_dict[axis_name]
|
|
191
|
-
if value is None:
|
|
192
|
-
return slice(None)
|
|
193
|
-
|
|
194
|
-
shape = dimensions.get(axis_name, default=None)
|
|
195
|
-
if shape is None:
|
|
196
|
-
raise NgioValueError(f"Unknown dimension {axis_name} for axis {axis}.")
|
|
197
|
-
|
|
198
|
-
if isinstance(value, int):
|
|
199
|
-
value = _validate_int(value, shape)
|
|
200
|
-
if requires_axes_ops or axis_name in axes_order:
|
|
201
|
-
# Axes ops require all dimensions to be preserved
|
|
202
|
-
value = slice(value, value + 1)
|
|
203
|
-
return value
|
|
204
|
-
elif isinstance(value, Sequence):
|
|
205
|
-
return _validate_iter_of_ints(value, shape)
|
|
206
|
-
elif isinstance(value, slice):
|
|
207
|
-
return _validate_slice(value, shape)
|
|
208
|
-
|
|
209
|
-
raise NgioValueError(f"Invalid slice definition {value} of type {type(value)}")
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def _build_slicing_tuple(
|
|
213
|
-
*,
|
|
214
|
-
dimensions: Dimensions,
|
|
215
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
216
|
-
axes_order: list[str] | None = None,
|
|
217
|
-
requires_axes_ops: bool = False,
|
|
218
|
-
remove_channel_selection: bool = False,
|
|
219
|
-
) -> tuple[SlicingType, ...] | None:
|
|
220
|
-
"""Assemble slices to be used to query the array."""
|
|
221
|
-
if len(slicing_dict) == 0:
|
|
222
|
-
# Skip unnecessary computation if no slicing is requested
|
|
223
|
-
return None
|
|
224
|
-
_axes_order = (
|
|
225
|
-
_normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
|
|
226
|
-
if axes_order is not None
|
|
227
|
-
else []
|
|
228
|
-
)
|
|
229
|
-
_slicing_dict = _normalize_slicing_dict(
|
|
230
|
-
dimensions=dimensions,
|
|
231
|
-
slicing_dict=slicing_dict,
|
|
232
|
-
remove_channel_selection=remove_channel_selection,
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
slicing_tuple = tuple(
|
|
236
|
-
_normalize_slice_input(
|
|
237
|
-
axis=axis,
|
|
238
|
-
slicing_dict=_slicing_dict,
|
|
239
|
-
dimensions=dimensions,
|
|
240
|
-
requires_axes_ops=requires_axes_ops,
|
|
241
|
-
axes_order=_axes_order,
|
|
242
|
-
)
|
|
243
|
-
for axis in dimensions.axes_mapper.axes
|
|
244
|
-
)
|
|
245
|
-
return slicing_tuple
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def get_slice_as_numpy(
|
|
249
|
-
zarr_array: zarr.Array, slice_tuple: tuple[SlicingType, ...] | None
|
|
250
|
-
) -> np.ndarray:
|
|
251
|
-
if slice_tuple is None:
|
|
252
|
-
return zarr_array[...]
|
|
253
|
-
|
|
254
|
-
if all(not isinstance(s, tuple) for s in slice_tuple):
|
|
255
|
-
return zarr_array[slice_tuple]
|
|
256
|
-
|
|
257
|
-
# If there are tuple[int, ...] we need to handle them separately
|
|
258
|
-
# this is a workaround for the fact that zarr does not support
|
|
259
|
-
# non-contiguous slicing with tuples/lists.
|
|
260
|
-
first_slice_tuple = []
|
|
261
|
-
for s in slice_tuple:
|
|
262
|
-
if isinstance(s, tuple):
|
|
263
|
-
first_slice_tuple.append(slice(None))
|
|
264
|
-
else:
|
|
265
|
-
first_slice_tuple.append(s)
|
|
266
|
-
second_slice_tuple = []
|
|
267
|
-
for s in slice_tuple:
|
|
268
|
-
if isinstance(s, tuple):
|
|
269
|
-
second_slice_tuple.append(s)
|
|
270
|
-
else:
|
|
271
|
-
second_slice_tuple.append(slice(None))
|
|
272
|
-
|
|
273
|
-
return zarr_array[tuple(first_slice_tuple)][tuple(second_slice_tuple)]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def get_slice_as_dask(
|
|
277
|
-
zarr_array: zarr.Array, slice_tuple: tuple[SlicingType, ...] | None
|
|
278
|
-
) -> da.Array:
|
|
279
|
-
da_array = da.from_zarr(zarr_array)
|
|
280
|
-
if slice_tuple is None:
|
|
281
|
-
return da_array
|
|
282
|
-
|
|
283
|
-
if any(isinstance(s, tuple) for s in slice_tuple):
|
|
284
|
-
raise NotImplementedError(
|
|
285
|
-
"Slicing with non-contiguous tuples/lists "
|
|
286
|
-
"is not supported yet for Dask arrays. Use the "
|
|
287
|
-
"numpy api to get the correct array slice."
|
|
288
|
-
)
|
|
289
|
-
return da_array[slice_tuple]
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def set_numpy_patch(
|
|
293
|
-
zarr_array: zarr.Array,
|
|
294
|
-
patch: np.ndarray,
|
|
295
|
-
slice_tuple: tuple[SlicingType, ...] | None,
|
|
296
|
-
) -> None:
|
|
297
|
-
if slice_tuple is None:
|
|
298
|
-
zarr_array[...] = patch
|
|
299
|
-
return
|
|
300
|
-
zarr_array[slice_tuple] = patch
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def set_dask_patch(
|
|
304
|
-
zarr_array: zarr.Array, patch: da.Array, slice_tuple: tuple[SlicingType, ...] | None
|
|
305
|
-
) -> None:
|
|
306
|
-
da.to_zarr(arr=patch, url=zarr_array, region=slice_tuple)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
##############################################################
|
|
310
|
-
#
|
|
311
|
-
# Array Axes Operations
|
|
312
|
-
#
|
|
313
|
-
##############################################################
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def apply_numpy_axes_ops(
|
|
317
|
-
array: np.ndarray,
|
|
318
|
-
squeeze_axes: tuple[int, ...] | None = None,
|
|
319
|
-
transpose_axes: tuple[int, ...] | None = None,
|
|
320
|
-
expand_axes: tuple[int, ...] | None = None,
|
|
321
|
-
) -> np.ndarray:
|
|
322
|
-
"""Apply axes operations to a numpy array."""
|
|
323
|
-
if squeeze_axes is not None:
|
|
324
|
-
array = np.squeeze(array, axis=squeeze_axes)
|
|
325
|
-
if transpose_axes is not None:
|
|
326
|
-
array = np.transpose(array, axes=transpose_axes)
|
|
327
|
-
if expand_axes is not None:
|
|
328
|
-
array = np.expand_dims(array, axis=expand_axes)
|
|
329
|
-
return array
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
def apply_dask_axes_ops(
|
|
333
|
-
array: da.Array,
|
|
334
|
-
squeeze_axes: tuple[int, ...] | None = None,
|
|
335
|
-
transpose_axes: tuple[int, ...] | None = None,
|
|
336
|
-
expand_axes: tuple[int, ...] | None = None,
|
|
337
|
-
) -> da.Array:
|
|
338
|
-
"""Apply axes operations to a dask array."""
|
|
339
|
-
if squeeze_axes is not None:
|
|
340
|
-
array = da.squeeze(array, axis=squeeze_axes)
|
|
341
|
-
if transpose_axes is not None:
|
|
342
|
-
array = da.transpose(array, axes=transpose_axes)
|
|
343
|
-
if expand_axes is not None:
|
|
344
|
-
array = da.expand_dims(array, axis=expand_axes)
|
|
345
|
-
return array
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
T = TypeVar("T")
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def apply_sequence_axes_ops(
|
|
352
|
-
input_: Sequence[T],
|
|
353
|
-
default: T,
|
|
354
|
-
squeeze_axes: tuple[int, ...] | None = None,
|
|
355
|
-
transpose_axes: tuple[int, ...] | None = None,
|
|
356
|
-
expand_axes: tuple[int, ...] | None = None,
|
|
357
|
-
) -> list[T]:
|
|
358
|
-
input_list = list(input_)
|
|
359
|
-
if squeeze_axes is not None:
|
|
360
|
-
for offset, ax in enumerate(squeeze_axes):
|
|
361
|
-
input_list.pop(ax - offset)
|
|
362
|
-
|
|
363
|
-
if transpose_axes is not None:
|
|
364
|
-
input_list = [input_list[i] for i in transpose_axes]
|
|
365
|
-
|
|
366
|
-
if expand_axes is not None:
|
|
367
|
-
for ax in expand_axes:
|
|
368
|
-
input_list.insert(ax, default)
|
|
369
|
-
|
|
370
|
-
return input_list
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
#############################################################
|
|
374
|
-
#
|
|
375
|
-
# Transform Protocol
|
|
376
|
-
#
|
|
377
|
-
#############################################################
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
class TransformProtocol(Protocol):
|
|
381
|
-
"""Protocol for a generic transform."""
|
|
382
|
-
|
|
383
|
-
def apply_numpy_transform(
|
|
384
|
-
self, array: np.ndarray, slicing_ops: SlicingOps
|
|
385
|
-
) -> np.ndarray:
|
|
386
|
-
"""A transformation to be applied after loading a numpy array."""
|
|
387
|
-
...
|
|
388
|
-
|
|
389
|
-
def apply_dask_transform(
|
|
390
|
-
self, array: da.Array, slicing_ops: SlicingOps
|
|
391
|
-
) -> da.Array:
|
|
392
|
-
"""A transformation to be applied after loading a dask array."""
|
|
393
|
-
...
|
|
394
|
-
|
|
395
|
-
def apply_inverse_numpy_transform(
|
|
396
|
-
self, array: np.ndarray, slicing_ops: SlicingOps
|
|
397
|
-
) -> np.ndarray:
|
|
398
|
-
"""A transformation to be applied before writing a numpy array."""
|
|
399
|
-
...
|
|
400
|
-
|
|
401
|
-
def apply_inverse_dask_transform(
|
|
402
|
-
self, array: da.Array, slicing_ops: SlicingOps
|
|
403
|
-
) -> da.Array:
|
|
404
|
-
"""A transformation to be applied before writing a dask array."""
|
|
405
|
-
...
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def apply_numpy_transforms(
|
|
409
|
-
array: np.ndarray,
|
|
410
|
-
slicing_ops: SlicingOps,
|
|
411
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
412
|
-
) -> np.ndarray:
|
|
413
|
-
"""Apply a numpy transform to an array."""
|
|
414
|
-
if transforms is None:
|
|
415
|
-
return array
|
|
416
|
-
|
|
417
|
-
for transform in transforms:
|
|
418
|
-
array = transform.apply_numpy_transform(array, slicing_ops=slicing_ops)
|
|
419
|
-
return array
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def apply_dask_transforms(
|
|
423
|
-
array: da.Array,
|
|
424
|
-
slicing_ops: SlicingOps,
|
|
425
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
426
|
-
) -> da.Array:
|
|
427
|
-
"""Apply a dask transform to an array."""
|
|
428
|
-
if transforms is None:
|
|
429
|
-
return array
|
|
430
|
-
|
|
431
|
-
for transform in transforms:
|
|
432
|
-
array = transform.apply_dask_transform(array, slicing_ops=slicing_ops)
|
|
433
|
-
return array
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
def apply_inverse_numpy_transforms(
|
|
437
|
-
array: np.ndarray,
|
|
438
|
-
slicing_ops: SlicingOps,
|
|
439
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
440
|
-
) -> np.ndarray:
|
|
441
|
-
"""Apply inverse numpy transforms to an array."""
|
|
442
|
-
if transforms is None:
|
|
443
|
-
return array
|
|
444
|
-
|
|
445
|
-
for transform in transforms:
|
|
446
|
-
array = transform.apply_inverse_numpy_transform(array, slicing_ops=slicing_ops)
|
|
447
|
-
return array
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
def apply_inverse_dask_transforms(
|
|
451
|
-
array: da.Array,
|
|
452
|
-
slicing_ops: SlicingOps,
|
|
453
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
454
|
-
) -> da.Array:
|
|
455
|
-
"""Apply inverse dask transforms to an array."""
|
|
456
|
-
if transforms is None:
|
|
457
|
-
return array
|
|
458
|
-
|
|
459
|
-
for transform in transforms:
|
|
460
|
-
array = transform.apply_inverse_dask_transform(array, slicing_ops=slicing_ops)
|
|
461
|
-
return array
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def setup_from_disk_pipe(
|
|
465
|
-
*,
|
|
466
|
-
dimensions: Dimensions,
|
|
467
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
468
|
-
axes_order: Sequence[str] | None = None,
|
|
469
|
-
remove_channel_selection: bool = False,
|
|
470
|
-
) -> SlicingOps:
|
|
471
|
-
if axes_order is not None:
|
|
472
|
-
axes_order = _normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
|
|
473
|
-
slicing_ops = dimensions.axes_mapper.to_order(axes_order)
|
|
474
|
-
else:
|
|
475
|
-
slicing_ops = SlicingOps()
|
|
476
|
-
|
|
477
|
-
slicing_tuple = _build_slicing_tuple(
|
|
478
|
-
dimensions=dimensions,
|
|
479
|
-
slicing_dict=slicing_dict,
|
|
480
|
-
axes_order=axes_order,
|
|
481
|
-
requires_axes_ops=slicing_ops.requires_axes_ops,
|
|
482
|
-
remove_channel_selection=remove_channel_selection,
|
|
483
|
-
)
|
|
484
|
-
slicing_ops.slice_tuple = slicing_tuple
|
|
485
|
-
return slicing_ops
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
def setup_to_disk_pipe(
|
|
489
|
-
*,
|
|
490
|
-
dimensions: Dimensions,
|
|
491
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
492
|
-
axes_order: Sequence[str] | None = None,
|
|
493
|
-
remove_channel_selection: bool = False,
|
|
494
|
-
) -> SlicingOps:
|
|
495
|
-
if axes_order is not None:
|
|
496
|
-
axes_order = _normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
|
|
497
|
-
slicing_ops = dimensions.axes_mapper.from_order(axes_order)
|
|
498
|
-
else:
|
|
499
|
-
slicing_ops = SlicingOps()
|
|
500
|
-
|
|
501
|
-
slicing_tuple = _build_slicing_tuple(
|
|
502
|
-
dimensions=dimensions,
|
|
503
|
-
slicing_dict=slicing_dict,
|
|
504
|
-
requires_axes_ops=slicing_ops.requires_axes_ops,
|
|
505
|
-
remove_channel_selection=remove_channel_selection,
|
|
506
|
-
)
|
|
507
|
-
slicing_ops.slice_tuple = slicing_tuple
|
|
508
|
-
return slicing_ops
|
ngio/common/transforms/_label.py
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"""Transforms and pre-post-processors for label images."""
|
|
2
|
-
|
|
3
|
-
# def make_unique_label_np(x: np.ndarray, p: int, n: int) -> np.ndarray:
|
|
4
|
-
# """Make a unique label for the patch."""
|
|
5
|
-
# x = np.where(x > 0, (1 + p - n) + x * n, 0)
|
|
6
|
-
# return x
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# def make_unique_label_da(x: da.Array, p: int, n: int) -> da.Array:
|
|
10
|
-
# """Make a unique label for the patch."""
|
|
11
|
-
# x = da.where(x > 0, (1 + p - n) + x * n, 0)
|
|
12
|
-
# return x
|
ngio/common/transforms/_zoom.py
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
from collections.abc import Sequence
|
|
2
|
-
from typing import Literal
|
|
3
|
-
|
|
4
|
-
import dask.array as da
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
from ngio.common._array_io_utils import apply_sequence_axes_ops
|
|
8
|
-
from ngio.common._dimensions import Dimensions
|
|
9
|
-
from ngio.common._zoom import dask_zoom, numpy_zoom
|
|
10
|
-
from ngio.ome_zarr_meta.ngio_specs import SlicingOps
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ZoomTransform:
|
|
14
|
-
def __init__(self, scale: Sequence[float], order: Literal[0, 1, 2]):
|
|
15
|
-
self._scale = tuple(scale)
|
|
16
|
-
self._order: Literal[0, 1, 2] = order
|
|
17
|
-
|
|
18
|
-
@property
|
|
19
|
-
def scale(self) -> tuple[float, ...]:
|
|
20
|
-
return self._scale
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def inv_scale(self) -> tuple[float, ...]:
|
|
24
|
-
return tuple([1 / s for s in self._scale])
|
|
25
|
-
|
|
26
|
-
@classmethod
|
|
27
|
-
def from_dimensions(
|
|
28
|
-
cls,
|
|
29
|
-
original_dimension: Dimensions,
|
|
30
|
-
target_dimension: Dimensions,
|
|
31
|
-
order: Literal[0, 1, 2],
|
|
32
|
-
):
|
|
33
|
-
scale = []
|
|
34
|
-
for o_ax_name in original_dimension.axes_mapper.axes_names:
|
|
35
|
-
t_ax = target_dimension.axes_mapper.get_axis(name=o_ax_name)
|
|
36
|
-
if t_ax is None:
|
|
37
|
-
_scale = 1
|
|
38
|
-
else:
|
|
39
|
-
t_shape = target_dimension.get(o_ax_name)
|
|
40
|
-
o_shape = original_dimension.get(o_ax_name)
|
|
41
|
-
assert t_shape is not None and o_shape is not None
|
|
42
|
-
_scale = t_shape / o_shape
|
|
43
|
-
scale.append(_scale)
|
|
44
|
-
|
|
45
|
-
return cls(scale, order)
|
|
46
|
-
|
|
47
|
-
def apply_numpy_transform(
|
|
48
|
-
self, array: np.ndarray, slicing_ops: SlicingOps
|
|
49
|
-
) -> np.ndarray:
|
|
50
|
-
"""Apply the scaling transformation to a numpy array."""
|
|
51
|
-
scale = tuple(
|
|
52
|
-
apply_sequence_axes_ops(
|
|
53
|
-
self.scale,
|
|
54
|
-
default=1,
|
|
55
|
-
squeeze_axes=slicing_ops.squeeze_axes,
|
|
56
|
-
transpose_axes=slicing_ops.transpose_axes,
|
|
57
|
-
expand_axes=slicing_ops.expand_axes,
|
|
58
|
-
)
|
|
59
|
-
)
|
|
60
|
-
array = numpy_zoom(source_array=array, scale=scale, order=self._order)
|
|
61
|
-
return array
|
|
62
|
-
|
|
63
|
-
def apply_dask_transform(
|
|
64
|
-
self, array: da.Array, slicing_ops: SlicingOps
|
|
65
|
-
) -> da.Array:
|
|
66
|
-
"""Apply the scaling transformation to a dask array."""
|
|
67
|
-
scale = tuple(
|
|
68
|
-
apply_sequence_axes_ops(
|
|
69
|
-
self.scale,
|
|
70
|
-
default=1,
|
|
71
|
-
squeeze_axes=slicing_ops.squeeze_axes,
|
|
72
|
-
transpose_axes=slicing_ops.transpose_axes,
|
|
73
|
-
expand_axes=slicing_ops.expand_axes,
|
|
74
|
-
)
|
|
75
|
-
)
|
|
76
|
-
array = dask_zoom(source_array=array, scale=scale, order=self._order)
|
|
77
|
-
return array
|
|
78
|
-
|
|
79
|
-
def apply_inverse_numpy_transform(
|
|
80
|
-
self, array: np.ndarray, slicing_ops: SlicingOps
|
|
81
|
-
) -> np.ndarray:
|
|
82
|
-
"""Apply the inverse scaling transformation to a numpy array."""
|
|
83
|
-
scale = tuple(
|
|
84
|
-
apply_sequence_axes_ops(
|
|
85
|
-
self.inv_scale,
|
|
86
|
-
default=1,
|
|
87
|
-
squeeze_axes=slicing_ops.squeeze_axes,
|
|
88
|
-
transpose_axes=slicing_ops.transpose_axes,
|
|
89
|
-
expand_axes=slicing_ops.expand_axes,
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
array = numpy_zoom(source_array=array, scale=scale, order=self._order)
|
|
93
|
-
return array
|
|
94
|
-
|
|
95
|
-
def apply_inverse_dask_transform(
|
|
96
|
-
self, array: da.Array, slicing_ops: SlicingOps
|
|
97
|
-
) -> da.Array:
|
|
98
|
-
"""Apply the inverse scaling transformation to a dask array."""
|
|
99
|
-
scale = tuple(
|
|
100
|
-
apply_sequence_axes_ops(
|
|
101
|
-
self.inv_scale,
|
|
102
|
-
default=1,
|
|
103
|
-
squeeze_axes=slicing_ops.squeeze_axes,
|
|
104
|
-
transpose_axes=slicing_ops.transpose_axes,
|
|
105
|
-
expand_axes=slicing_ops.expand_axes,
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
array = dask_zoom(source_array=array, scale=scale, order=self._order)
|
|
109
|
-
return array
|