ngio 0.4.0a3__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 +47 -411
- ngio/common/_zoom.py +32 -7
- ngio/experimental/iterators/_abstract_iterator.py +2 -2
- ngio/experimental/iterators/_feature.py +9 -14
- ngio/experimental/iterators/_image_processing.py +17 -27
- ngio/experimental/iterators/_rois_utils.py +4 -4
- ngio/experimental/iterators/_segmentation.py +37 -53
- ngio/images/_abstract_image.py +135 -93
- 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 +33 -0
- 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/{common/transforms → transforms}/__init__.py +1 -1
- ngio/transforms/_zoom.py +19 -0
- ngio/utils/_zarr_utils.py +5 -1
- {ngio-0.4.0a3.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.0a3.dist-info/RECORD +0 -76
- {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/licenses/LICENSE +0 -0
ngio/common/_roi.py
CHANGED
|
@@ -4,41 +4,32 @@ These are the interfaces bwteen the ROI tables / masking ROI tables and
|
|
|
4
4
|
the ImageLikeHandler.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from typing import Generic, TypeVar
|
|
7
|
+
from typing import TypeVar
|
|
9
8
|
from warnings import warn
|
|
10
9
|
|
|
11
|
-
import dask.array as da
|
|
12
|
-
import numpy as np
|
|
13
|
-
import zarr
|
|
14
10
|
from pydantic import BaseModel, ConfigDict
|
|
15
11
|
|
|
16
|
-
from ngio.common._array_io_pipes import (
|
|
17
|
-
build_dask_getter,
|
|
18
|
-
build_dask_setter,
|
|
19
|
-
build_masked_dask_getter,
|
|
20
|
-
build_masked_dask_setter,
|
|
21
|
-
build_masked_numpy_getter,
|
|
22
|
-
build_masked_numpy_setter,
|
|
23
|
-
build_numpy_getter,
|
|
24
|
-
build_numpy_setter,
|
|
25
|
-
)
|
|
26
|
-
from ngio.common._array_io_utils import SlicingInputType, TransformProtocol
|
|
27
12
|
from ngio.common._dimensions import Dimensions
|
|
28
13
|
from ngio.ome_zarr_meta.ngio_specs import DefaultSpaceUnit, PixelSize, SpaceUnits
|
|
29
14
|
from ngio.utils import NgioValueError
|
|
30
15
|
|
|
31
16
|
|
|
32
|
-
def _to_raster(value: float,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if max_shape is not None:
|
|
37
|
-
return max(0, min(round_value, max_shape))
|
|
38
|
-
return round_value
|
|
17
|
+
def _to_raster(value: float, length: float, pixel_size: float) -> tuple[float, float]:
|
|
18
|
+
raster_value = value / pixel_size
|
|
19
|
+
raster_length = length / pixel_size
|
|
20
|
+
return raster_value, raster_length
|
|
39
21
|
|
|
40
22
|
|
|
41
|
-
def
|
|
23
|
+
def _to_slice(start: float | None, length: float | None) -> slice:
|
|
24
|
+
if length is not None:
|
|
25
|
+
assert start is not None
|
|
26
|
+
end = start + length
|
|
27
|
+
else:
|
|
28
|
+
end = None
|
|
29
|
+
return slice(start, end)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _to_world(value: int | float, pixel_size: float) -> float:
|
|
42
33
|
"""Convert to world coordinates."""
|
|
43
34
|
return value * pixel_size
|
|
44
35
|
|
|
@@ -46,24 +37,24 @@ def _to_world(value: int, pixel_size: float) -> float:
|
|
|
46
37
|
T = TypeVar("T", int, float)
|
|
47
38
|
|
|
48
39
|
|
|
49
|
-
class GenericRoi(BaseModel
|
|
40
|
+
class GenericRoi(BaseModel):
|
|
50
41
|
"""A generic Region of Interest (ROI) model."""
|
|
51
42
|
|
|
52
43
|
name: str | None
|
|
53
|
-
x:
|
|
54
|
-
y:
|
|
55
|
-
z:
|
|
56
|
-
t:
|
|
57
|
-
x_length:
|
|
58
|
-
y_length:
|
|
59
|
-
z_length:
|
|
60
|
-
t_length:
|
|
44
|
+
x: float
|
|
45
|
+
y: float
|
|
46
|
+
z: float | None = None
|
|
47
|
+
t: float | None = None
|
|
48
|
+
x_length: float
|
|
49
|
+
y_length: float
|
|
50
|
+
z_length: float | None = None
|
|
51
|
+
t_length: float | None = None
|
|
61
52
|
label: int | None = None
|
|
62
53
|
unit: SpaceUnits | str | None = None
|
|
63
54
|
|
|
64
55
|
model_config = ConfigDict(extra="allow")
|
|
65
56
|
|
|
66
|
-
def intersection(self, other: "GenericRoi
|
|
57
|
+
def intersection(self, other: "GenericRoi") -> "GenericRoi | None":
|
|
67
58
|
"""Calculate the intersection of this ROI with another ROI."""
|
|
68
59
|
return roi_intersection(self, other)
|
|
69
60
|
|
|
@@ -129,9 +120,7 @@ def _1d_intersection(
|
|
|
129
120
|
return start, length
|
|
130
121
|
|
|
131
122
|
|
|
132
|
-
def roi_intersection(
|
|
133
|
-
ref_roi: GenericRoi[T], other_roi: GenericRoi[T]
|
|
134
|
-
) -> GenericRoi[T] | None:
|
|
123
|
+
def roi_intersection(ref_roi: GenericRoi, other_roi: GenericRoi) -> GenericRoi | None:
|
|
135
124
|
"""Calculate the intersection of two ROIs."""
|
|
136
125
|
if (
|
|
137
126
|
ref_roi.unit is not None
|
|
@@ -200,52 +189,29 @@ def roi_intersection(
|
|
|
200
189
|
)
|
|
201
190
|
|
|
202
191
|
|
|
203
|
-
class Roi(GenericRoi
|
|
192
|
+
class Roi(GenericRoi):
|
|
204
193
|
x: float = 0.0
|
|
205
194
|
y: float = 0.0
|
|
206
195
|
unit: SpaceUnits | str | None = DefaultSpaceUnit
|
|
207
196
|
|
|
208
|
-
def to_roi_pixels(
|
|
209
|
-
self, pixel_size: PixelSize, dimensions: Dimensions | None = None
|
|
210
|
-
) -> "RoiPixels":
|
|
197
|
+
def to_roi_pixels(self, pixel_size: PixelSize) -> "RoiPixels":
|
|
211
198
|
"""Convert to raster coordinates."""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
dim_x, dim_y, dim_z, dim_t = None, None, None, None
|
|
215
|
-
else:
|
|
216
|
-
dim_x, dim_y, dim_z, dim_t = (
|
|
217
|
-
dimensions.get("x"),
|
|
218
|
-
dimensions.get("y"),
|
|
219
|
-
dimensions.get("z"),
|
|
220
|
-
dimensions.get("t"),
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
x = _to_raster(self.x, pixel_size.x, dim_x)
|
|
224
|
-
x_length = _to_raster(self.x_length, pixel_size.x, dim_x)
|
|
225
|
-
y = _to_raster(self.y, pixel_size.y, dim_y)
|
|
226
|
-
y_length = _to_raster(self.y_length, pixel_size.y, dim_y)
|
|
199
|
+
x, x_length = _to_raster(self.x, self.x_length, pixel_size.x)
|
|
200
|
+
y, y_length = _to_raster(self.y, self.y_length, pixel_size.y)
|
|
227
201
|
|
|
228
202
|
if self.z is None:
|
|
229
|
-
z = None
|
|
203
|
+
z, z_length = None, None
|
|
230
204
|
else:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if self.z_length is None:
|
|
234
|
-
z_length = None
|
|
235
|
-
else:
|
|
236
|
-
z_length = _to_raster(self.z_length, pixel_size.z, dim_z)
|
|
205
|
+
assert self.z_length is not None
|
|
206
|
+
z, z_length = _to_raster(self.z, self.z_length, pixel_size.z)
|
|
237
207
|
|
|
238
208
|
if self.t is None:
|
|
239
|
-
t = None
|
|
240
|
-
else:
|
|
241
|
-
t = _to_raster(self.t, pixel_size.t, dim_t)
|
|
242
|
-
|
|
243
|
-
if self.t_length is None:
|
|
244
|
-
t_length = None
|
|
209
|
+
t, t_length = None, None
|
|
245
210
|
else:
|
|
246
|
-
|
|
247
|
-
|
|
211
|
+
assert self.t_length is not None
|
|
212
|
+
t, t_length = _to_raster(self.t, self.t_length, pixel_size.t)
|
|
248
213
|
extra_dict = self.model_extra if self.model_extra else {}
|
|
214
|
+
|
|
249
215
|
return RoiPixels(
|
|
250
216
|
name=self.name,
|
|
251
217
|
x=x,
|
|
@@ -272,7 +238,7 @@ class Roi(GenericRoi[float]):
|
|
|
272
238
|
stacklevel=2,
|
|
273
239
|
)
|
|
274
240
|
|
|
275
|
-
return self.to_roi_pixels(pixel_size=pixel_size
|
|
241
|
+
return self.to_roi_pixels(pixel_size=pixel_size)
|
|
276
242
|
|
|
277
243
|
def zoom(self, zoom_factor: float = 1) -> "Roi":
|
|
278
244
|
"""Zoom the ROI by a factor.
|
|
@@ -286,11 +252,11 @@ class Roi(GenericRoi[float]):
|
|
|
286
252
|
return zoom_roi(self, zoom_factor)
|
|
287
253
|
|
|
288
254
|
|
|
289
|
-
class RoiPixels(GenericRoi
|
|
255
|
+
class RoiPixels(GenericRoi):
|
|
290
256
|
"""Region of interest (ROI) in pixel coordinates."""
|
|
291
257
|
|
|
292
|
-
x:
|
|
293
|
-
y:
|
|
258
|
+
x: float = 0
|
|
259
|
+
y: float = 0
|
|
294
260
|
unit: SpaceUnits | str | None = None
|
|
295
261
|
|
|
296
262
|
def to_roi(self, pixel_size: PixelSize) -> "Roi":
|
|
@@ -336,17 +302,12 @@ class RoiPixels(GenericRoi[int]):
|
|
|
336
302
|
**extra_dict,
|
|
337
303
|
)
|
|
338
304
|
|
|
339
|
-
def to_slicing_dict(self) -> dict[str,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
z_slice = slice(None)
|
|
346
|
-
if self.t is not None and self.t_length is not None:
|
|
347
|
-
t_slice = slice(self.t, self.t + self.t_length)
|
|
348
|
-
else:
|
|
349
|
-
t_slice = slice(None)
|
|
305
|
+
def to_slicing_dict(self) -> dict[str, slice]:
|
|
306
|
+
"""Convert to a slicing dictionary."""
|
|
307
|
+
x_slice = _to_slice(self.x, self.x_length)
|
|
308
|
+
y_slice = _to_slice(self.y, self.y_length)
|
|
309
|
+
z_slice = _to_slice(self.z, self.z_length)
|
|
310
|
+
t_slice = _to_slice(self.t, self.t_length)
|
|
350
311
|
return {
|
|
351
312
|
"x": x_slice,
|
|
352
313
|
"y": y_slice,
|
|
@@ -391,328 +352,3 @@ def zoom_roi(roi: Roi, zoom_factor: float = 1) -> Roi:
|
|
|
391
352
|
unit=roi.unit,
|
|
392
353
|
)
|
|
393
354
|
return new_roi
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
def roi_to_slicing_dict(
|
|
397
|
-
roi: Roi | RoiPixels,
|
|
398
|
-
dimensions: Dimensions,
|
|
399
|
-
pixel_size: PixelSize | None = None,
|
|
400
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
401
|
-
) -> dict[str, SlicingInputType]:
|
|
402
|
-
"""Convert a ROI to a slicing dictionary."""
|
|
403
|
-
if isinstance(roi, Roi):
|
|
404
|
-
if pixel_size is None:
|
|
405
|
-
raise NgioValueError(
|
|
406
|
-
"pixel_size must be provided when converting a Roi to slice_kwargs."
|
|
407
|
-
)
|
|
408
|
-
roi = roi.to_roi_pixels(pixel_size=pixel_size, dimensions=dimensions)
|
|
409
|
-
|
|
410
|
-
roi_slicing_dict = roi.to_slicing_dict()
|
|
411
|
-
if slicing_dict is None:
|
|
412
|
-
return roi_slicing_dict
|
|
413
|
-
|
|
414
|
-
# Additional slice kwargs can be provided
|
|
415
|
-
# and will override the ones from the ROI
|
|
416
|
-
roi_slicing_dict.update(slicing_dict)
|
|
417
|
-
return roi_slicing_dict
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
def build_roi_numpy_getter(
|
|
421
|
-
zarr_array: zarr.Array,
|
|
422
|
-
dimensions: Dimensions,
|
|
423
|
-
roi: Roi | RoiPixels,
|
|
424
|
-
pixel_size: PixelSize | None = None,
|
|
425
|
-
axes_order: Sequence[str] | None = None,
|
|
426
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
427
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
428
|
-
remove_channel_selection: bool = False,
|
|
429
|
-
) -> Callable[[], np.ndarray]:
|
|
430
|
-
"""Prepare slice kwargs for setting an array."""
|
|
431
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
432
|
-
roi=roi,
|
|
433
|
-
dimensions=dimensions,
|
|
434
|
-
pixel_size=pixel_size,
|
|
435
|
-
slicing_dict=slicing_dict,
|
|
436
|
-
)
|
|
437
|
-
return build_numpy_getter(
|
|
438
|
-
zarr_array=zarr_array,
|
|
439
|
-
dimensions=dimensions,
|
|
440
|
-
axes_order=axes_order,
|
|
441
|
-
transforms=transforms,
|
|
442
|
-
slicing_dict=input_slice_kwargs,
|
|
443
|
-
remove_channel_selection=remove_channel_selection,
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def build_roi_numpy_setter(
|
|
448
|
-
zarr_array: zarr.Array,
|
|
449
|
-
dimensions: Dimensions,
|
|
450
|
-
roi: Roi | RoiPixels,
|
|
451
|
-
pixel_size: PixelSize | None = None,
|
|
452
|
-
axes_order: Sequence[str] | None = None,
|
|
453
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
454
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
455
|
-
remove_channel_selection: bool = False,
|
|
456
|
-
) -> Callable[[np.ndarray], None]:
|
|
457
|
-
"""Prepare slice kwargs for setting an array."""
|
|
458
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
459
|
-
roi=roi,
|
|
460
|
-
dimensions=dimensions,
|
|
461
|
-
pixel_size=pixel_size,
|
|
462
|
-
slicing_dict=slicing_dict,
|
|
463
|
-
)
|
|
464
|
-
return build_numpy_setter(
|
|
465
|
-
zarr_array=zarr_array,
|
|
466
|
-
dimensions=dimensions,
|
|
467
|
-
axes_order=axes_order,
|
|
468
|
-
transforms=transforms,
|
|
469
|
-
slicing_dict=input_slice_kwargs,
|
|
470
|
-
remove_channel_selection=remove_channel_selection,
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
def build_roi_dask_getter(
|
|
475
|
-
zarr_array: zarr.Array,
|
|
476
|
-
dimensions: Dimensions,
|
|
477
|
-
roi: Roi | RoiPixels,
|
|
478
|
-
pixel_size: PixelSize | None = None,
|
|
479
|
-
axes_order: Sequence[str] | None = None,
|
|
480
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
481
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
482
|
-
remove_channel_selection: bool = False,
|
|
483
|
-
) -> Callable[[], da.Array]:
|
|
484
|
-
"""Prepare slice kwargs for getting an array."""
|
|
485
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
486
|
-
roi=roi,
|
|
487
|
-
dimensions=dimensions,
|
|
488
|
-
pixel_size=pixel_size,
|
|
489
|
-
slicing_dict=slicing_dict,
|
|
490
|
-
)
|
|
491
|
-
return build_dask_getter(
|
|
492
|
-
zarr_array=zarr_array,
|
|
493
|
-
dimensions=dimensions,
|
|
494
|
-
axes_order=axes_order,
|
|
495
|
-
transforms=transforms,
|
|
496
|
-
slicing_dict=input_slice_kwargs,
|
|
497
|
-
remove_channel_selection=remove_channel_selection,
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
def build_roi_dask_setter(
|
|
502
|
-
zarr_array: zarr.Array,
|
|
503
|
-
dimensions: Dimensions,
|
|
504
|
-
roi: Roi | RoiPixels,
|
|
505
|
-
pixel_size: PixelSize | None = None,
|
|
506
|
-
axes_order: Sequence[str] | None = None,
|
|
507
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
508
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
509
|
-
remove_channel_selection: bool = False,
|
|
510
|
-
) -> Callable[[da.Array], None]:
|
|
511
|
-
"""Prepare slice kwargs for setting an array."""
|
|
512
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
513
|
-
roi=roi,
|
|
514
|
-
dimensions=dimensions,
|
|
515
|
-
pixel_size=pixel_size,
|
|
516
|
-
slicing_dict=slicing_dict,
|
|
517
|
-
)
|
|
518
|
-
return build_dask_setter(
|
|
519
|
-
zarr_array=zarr_array,
|
|
520
|
-
dimensions=dimensions,
|
|
521
|
-
axes_order=axes_order,
|
|
522
|
-
transforms=transforms,
|
|
523
|
-
slicing_dict=input_slice_kwargs,
|
|
524
|
-
remove_channel_selection=remove_channel_selection,
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
################################################################
|
|
529
|
-
#
|
|
530
|
-
# Masked ROIs array pipes
|
|
531
|
-
#
|
|
532
|
-
################################################################
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
def build_roi_masked_numpy_getter(
|
|
536
|
-
*,
|
|
537
|
-
roi: Roi | RoiPixels,
|
|
538
|
-
zarr_array: zarr.Array,
|
|
539
|
-
dimensions: Dimensions,
|
|
540
|
-
pixel_size: PixelSize | None = None,
|
|
541
|
-
label_zarr_array: zarr.Array,
|
|
542
|
-
label_dimensions: Dimensions,
|
|
543
|
-
label_pixel_size: PixelSize | None = None,
|
|
544
|
-
axes_order: Sequence[str] | None = None,
|
|
545
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
546
|
-
label_transforms: Sequence[TransformProtocol] | None = None,
|
|
547
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
548
|
-
label_slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
549
|
-
fill_value: int | float = 0,
|
|
550
|
-
allow_scaling: bool = True,
|
|
551
|
-
remove_channel_selection: bool = False,
|
|
552
|
-
) -> Callable[[], np.ndarray]:
|
|
553
|
-
"""Prepare slice kwargs for getting a masked array."""
|
|
554
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
555
|
-
roi=roi,
|
|
556
|
-
dimensions=dimensions,
|
|
557
|
-
pixel_size=pixel_size,
|
|
558
|
-
slicing_dict=slicing_dict,
|
|
559
|
-
)
|
|
560
|
-
label_slice_kwargs = roi_to_slicing_dict(
|
|
561
|
-
roi=roi,
|
|
562
|
-
dimensions=label_dimensions,
|
|
563
|
-
pixel_size=label_pixel_size,
|
|
564
|
-
slicing_dict=label_slicing_dict,
|
|
565
|
-
)
|
|
566
|
-
return build_masked_numpy_getter(
|
|
567
|
-
zarr_array=zarr_array,
|
|
568
|
-
dimensions=dimensions,
|
|
569
|
-
label_zarr_array=label_zarr_array,
|
|
570
|
-
label_dimensions=label_dimensions,
|
|
571
|
-
label_id=roi.label,
|
|
572
|
-
axes_order=axes_order,
|
|
573
|
-
transforms=transforms,
|
|
574
|
-
label_transforms=label_transforms,
|
|
575
|
-
slicing_dict=input_slice_kwargs,
|
|
576
|
-
label_slicing_dict=label_slice_kwargs,
|
|
577
|
-
fill_value=fill_value,
|
|
578
|
-
allow_scaling=allow_scaling,
|
|
579
|
-
remove_channel_selection=remove_channel_selection,
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
def build_roi_masked_numpy_setter(
|
|
584
|
-
*,
|
|
585
|
-
roi: Roi | RoiPixels,
|
|
586
|
-
zarr_array: zarr.Array,
|
|
587
|
-
dimensions: Dimensions,
|
|
588
|
-
pixel_size: PixelSize | None = None,
|
|
589
|
-
label_zarr_array: zarr.Array,
|
|
590
|
-
label_dimensions: Dimensions,
|
|
591
|
-
label_pixel_size: PixelSize | None = None,
|
|
592
|
-
axes_order: Sequence[str] | None = None,
|
|
593
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
594
|
-
label_transforms: Sequence[TransformProtocol] | None = None,
|
|
595
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
596
|
-
label_slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
597
|
-
allow_scaling: bool = True,
|
|
598
|
-
remove_channel_selection: bool = False,
|
|
599
|
-
) -> Callable[[np.ndarray], None]:
|
|
600
|
-
"""Prepare slice kwargs for setting a masked array."""
|
|
601
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
602
|
-
roi=roi,
|
|
603
|
-
dimensions=dimensions,
|
|
604
|
-
pixel_size=pixel_size,
|
|
605
|
-
slicing_dict=slicing_dict,
|
|
606
|
-
)
|
|
607
|
-
label_slice_kwargs = roi_to_slicing_dict(
|
|
608
|
-
roi=roi,
|
|
609
|
-
dimensions=label_dimensions,
|
|
610
|
-
pixel_size=label_pixel_size,
|
|
611
|
-
slicing_dict=label_slicing_dict,
|
|
612
|
-
)
|
|
613
|
-
return build_masked_numpy_setter(
|
|
614
|
-
zarr_array=zarr_array,
|
|
615
|
-
dimensions=dimensions,
|
|
616
|
-
label_zarr_array=label_zarr_array,
|
|
617
|
-
label_dimensions=label_dimensions,
|
|
618
|
-
label_id=roi.label,
|
|
619
|
-
axes_order=axes_order,
|
|
620
|
-
transforms=transforms,
|
|
621
|
-
label_transforms=label_transforms,
|
|
622
|
-
slicing_dict=input_slice_kwargs,
|
|
623
|
-
label_slicing_dict=label_slice_kwargs,
|
|
624
|
-
allow_scaling=allow_scaling,
|
|
625
|
-
remove_channel_selection=remove_channel_selection,
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
def build_roi_masked_dask_getter(
|
|
630
|
-
*,
|
|
631
|
-
roi: Roi | RoiPixels,
|
|
632
|
-
zarr_array: zarr.Array,
|
|
633
|
-
dimensions: Dimensions,
|
|
634
|
-
pixel_size: PixelSize | None = None,
|
|
635
|
-
label_zarr_array: zarr.Array,
|
|
636
|
-
label_dimensions: Dimensions,
|
|
637
|
-
label_pixel_size: PixelSize | None = None,
|
|
638
|
-
axes_order: Sequence[str] | None = None,
|
|
639
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
640
|
-
label_transforms: Sequence[TransformProtocol] | None = None,
|
|
641
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
642
|
-
label_slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
643
|
-
allow_scaling: bool = True,
|
|
644
|
-
remove_channel_selection: bool = False,
|
|
645
|
-
) -> Callable[[], da.Array]:
|
|
646
|
-
"""Prepare slice kwargs for getting a masked array."""
|
|
647
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
648
|
-
roi=roi,
|
|
649
|
-
dimensions=dimensions,
|
|
650
|
-
pixel_size=pixel_size,
|
|
651
|
-
slicing_dict=slicing_dict,
|
|
652
|
-
)
|
|
653
|
-
label_slice_kwargs = roi_to_slicing_dict(
|
|
654
|
-
roi=roi,
|
|
655
|
-
dimensions=label_dimensions,
|
|
656
|
-
pixel_size=label_pixel_size,
|
|
657
|
-
slicing_dict=label_slicing_dict,
|
|
658
|
-
)
|
|
659
|
-
return build_masked_dask_getter(
|
|
660
|
-
zarr_array=zarr_array,
|
|
661
|
-
dimensions=dimensions,
|
|
662
|
-
label_zarr_array=label_zarr_array,
|
|
663
|
-
label_dimensions=label_dimensions,
|
|
664
|
-
label_id=roi.label,
|
|
665
|
-
axes_order=axes_order,
|
|
666
|
-
transforms=transforms,
|
|
667
|
-
label_transforms=label_transforms,
|
|
668
|
-
slicing_dict=input_slice_kwargs,
|
|
669
|
-
label_slicing_dict=label_slice_kwargs,
|
|
670
|
-
allow_scaling=allow_scaling,
|
|
671
|
-
remove_channel_selection=remove_channel_selection,
|
|
672
|
-
)
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
def build_roi_masked_dask_setter(
|
|
676
|
-
*,
|
|
677
|
-
roi: Roi | RoiPixels,
|
|
678
|
-
zarr_array: zarr.Array,
|
|
679
|
-
dimensions: Dimensions,
|
|
680
|
-
pixel_size: PixelSize | None = None,
|
|
681
|
-
label_zarr_array: zarr.Array,
|
|
682
|
-
label_dimensions: Dimensions,
|
|
683
|
-
label_pixel_size: PixelSize | None = None,
|
|
684
|
-
axes_order: Sequence[str] | None = None,
|
|
685
|
-
transforms: Sequence[TransformProtocol] | None = None,
|
|
686
|
-
label_transforms: Sequence[TransformProtocol] | None = None,
|
|
687
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
688
|
-
label_slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
689
|
-
allow_scaling: bool = True,
|
|
690
|
-
remove_channel_selection: bool = False,
|
|
691
|
-
) -> Callable[[da.Array], None]:
|
|
692
|
-
"""Prepare slice kwargs for setting a masked array."""
|
|
693
|
-
input_slice_kwargs = roi_to_slicing_dict(
|
|
694
|
-
roi=roi,
|
|
695
|
-
dimensions=dimensions,
|
|
696
|
-
pixel_size=pixel_size,
|
|
697
|
-
slicing_dict=slicing_dict,
|
|
698
|
-
)
|
|
699
|
-
label_slice_kwargs = roi_to_slicing_dict(
|
|
700
|
-
roi=roi,
|
|
701
|
-
dimensions=label_dimensions,
|
|
702
|
-
pixel_size=label_pixel_size,
|
|
703
|
-
slicing_dict=label_slicing_dict,
|
|
704
|
-
)
|
|
705
|
-
return build_masked_dask_setter(
|
|
706
|
-
zarr_array=zarr_array,
|
|
707
|
-
dimensions=dimensions,
|
|
708
|
-
label_zarr_array=label_zarr_array,
|
|
709
|
-
label_dimensions=label_dimensions,
|
|
710
|
-
label_id=roi.label,
|
|
711
|
-
axes_order=axes_order,
|
|
712
|
-
transforms=transforms,
|
|
713
|
-
label_transforms=label_transforms,
|
|
714
|
-
slicing_dict=input_slice_kwargs,
|
|
715
|
-
label_slicing_dict=label_slice_kwargs,
|
|
716
|
-
allow_scaling=allow_scaling,
|
|
717
|
-
remove_channel_selection=remove_channel_selection,
|
|
718
|
-
)
|
ngio/common/_zoom.py
CHANGED
|
@@ -7,6 +7,19 @@ from scipy.ndimage import zoom as scipy_zoom
|
|
|
7
7
|
|
|
8
8
|
from ngio.utils import NgioValueError
|
|
9
9
|
|
|
10
|
+
InterpolationOrder = Literal["nearest", "linear", "cubic"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def order_to_int(order: InterpolationOrder | Literal[0, 1, 2]) -> Literal[0, 1, 2]:
|
|
14
|
+
if order == "nearest" or order == 0:
|
|
15
|
+
return 0
|
|
16
|
+
elif order == "linear" or order == 1:
|
|
17
|
+
return 1
|
|
18
|
+
elif order == "cubic" or order == 2:
|
|
19
|
+
return 2
|
|
20
|
+
else:
|
|
21
|
+
raise NgioValueError(f"Invalid order: {order}")
|
|
22
|
+
|
|
10
23
|
|
|
11
24
|
def _stacked_zoom(x, zoom_y, zoom_x, order=1, mode="grid-constant", grid_mode=True):
|
|
12
25
|
*rest, yshape, xshape = x.shape
|
|
@@ -32,6 +45,10 @@ def fast_zoom(x, zoom, order=1, mode="grid-constant", grid_mode=True, auto_stack
|
|
|
32
45
|
it stacks the first dimensions to call zoom only on the last two.
|
|
33
46
|
"""
|
|
34
47
|
mask = np.isclose(x.shape, 1)
|
|
48
|
+
# Always keep the last two dimensions
|
|
49
|
+
# To avoid issues with singleton x or y dimensions
|
|
50
|
+
mask[-1] = False
|
|
51
|
+
mask[-2] = False
|
|
35
52
|
zoom = np.array(zoom)
|
|
36
53
|
singletons = tuple(np.where(mask)[0])
|
|
37
54
|
xs = np.squeeze(x, axis=singletons)
|
|
@@ -71,7 +88,7 @@ def _zoom_inputs_check(
|
|
|
71
88
|
_target_shape = target_shape
|
|
72
89
|
else:
|
|
73
90
|
_scale = np.array(scale)
|
|
74
|
-
_target_shape = tuple(np.array(source_array.shape) * scale)
|
|
91
|
+
_target_shape = tuple(map(int, np.round(np.array(source_array.shape) * scale)))
|
|
75
92
|
|
|
76
93
|
if len(_scale) != source_array.ndim:
|
|
77
94
|
raise NgioValueError(
|
|
@@ -86,7 +103,7 @@ def dask_zoom(
|
|
|
86
103
|
source_array: da.Array,
|
|
87
104
|
scale: tuple[float | int, ...] | None = None,
|
|
88
105
|
target_shape: tuple[int, ...] | None = None,
|
|
89
|
-
order:
|
|
106
|
+
order: InterpolationOrder = "linear",
|
|
90
107
|
) -> da.Array:
|
|
91
108
|
"""Dask implementation of zooming an array.
|
|
92
109
|
|
|
@@ -96,7 +113,8 @@ def dask_zoom(
|
|
|
96
113
|
source_array (da.Array): The source array to zoom.
|
|
97
114
|
scale (tuple[int, ...] | None): The scale factor to zoom by.
|
|
98
115
|
target_shape (tuple[int, ...], None): The target shape to zoom to.
|
|
99
|
-
order (Literal[
|
|
116
|
+
order (Literal["nearest", "linear", "cubic"]): The order of interpolation.
|
|
117
|
+
Defaults to "linear".
|
|
100
118
|
|
|
101
119
|
Returns:
|
|
102
120
|
da.Array: The zoomed array.
|
|
@@ -105,7 +123,6 @@ def dask_zoom(
|
|
|
105
123
|
# https://github.com/ome/ome-zarr-py/blob/master/ome_zarr/dask_utils.py
|
|
106
124
|
# The module was contributed by Andreas Eisenbarth @aeisenbarth
|
|
107
125
|
# See https://github.com/toloudis/ome-zarr-py/pull/
|
|
108
|
-
|
|
109
126
|
_scale, _target_shape = _zoom_inputs_check(
|
|
110
127
|
source_array=source_array, scale=scale, target_shape=target_shape
|
|
111
128
|
)
|
|
@@ -120,7 +137,11 @@ def dask_zoom(
|
|
|
120
137
|
block_output_shape = tuple(np.ceil(better_source_chunks * _scale).astype(int))
|
|
121
138
|
|
|
122
139
|
zoom_wrapper = partial(
|
|
123
|
-
fast_zoom,
|
|
140
|
+
fast_zoom,
|
|
141
|
+
zoom=_scale,
|
|
142
|
+
order=order_to_int(order),
|
|
143
|
+
mode="grid-constant",
|
|
144
|
+
grid_mode=True,
|
|
124
145
|
)
|
|
125
146
|
|
|
126
147
|
out_array = da.map_blocks(
|
|
@@ -137,7 +158,7 @@ def numpy_zoom(
|
|
|
137
158
|
source_array: np.ndarray,
|
|
138
159
|
scale: tuple[int | float, ...] | None = None,
|
|
139
160
|
target_shape: tuple[int, ...] | None = None,
|
|
140
|
-
order:
|
|
161
|
+
order: InterpolationOrder = "linear",
|
|
141
162
|
) -> np.ndarray:
|
|
142
163
|
"""Numpy implementation of zooming an array.
|
|
143
164
|
|
|
@@ -157,7 +178,11 @@ def numpy_zoom(
|
|
|
157
178
|
)
|
|
158
179
|
|
|
159
180
|
out_array = fast_zoom(
|
|
160
|
-
source_array,
|
|
181
|
+
source_array,
|
|
182
|
+
zoom=_scale,
|
|
183
|
+
order=order_to_int(order),
|
|
184
|
+
mode="grid-constant",
|
|
185
|
+
grid_mode=True,
|
|
161
186
|
)
|
|
162
187
|
assert isinstance(out_array, np.ndarray)
|
|
163
188
|
return out_array
|
|
@@ -156,7 +156,7 @@ class AbstractIteratorBuilder(ABC):
|
|
|
156
156
|
def map_as_numpy(self, func: Callable) -> None:
|
|
157
157
|
"""Apply a transformation function to the ROI pixels."""
|
|
158
158
|
for roi in self.rois:
|
|
159
|
-
data = self.build_numpy_getter(roi)
|
|
159
|
+
data = self.build_numpy_getter(roi)()
|
|
160
160
|
data = func(data)
|
|
161
161
|
self.build_numpy_setter(roi)
|
|
162
162
|
self.post_consolidate()
|
|
@@ -164,7 +164,7 @@ class AbstractIteratorBuilder(ABC):
|
|
|
164
164
|
def map_as_dask(self, func: Callable) -> None:
|
|
165
165
|
"""Apply a transformation function to the ROI pixels."""
|
|
166
166
|
for roi in self.rois:
|
|
167
|
-
data = self.build_dask_getter(roi)
|
|
167
|
+
data = self.build_dask_getter(roi)()
|
|
168
168
|
data = func(data)
|
|
169
169
|
self.build_dask_setter(roi)
|
|
170
170
|
self.post_consolidate()
|