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.
Files changed (50) hide show
  1. ngio/__init__.py +1 -2
  2. ngio/common/__init__.py +2 -51
  3. ngio/common/_dimensions.py +223 -64
  4. ngio/common/_pyramid.py +42 -23
  5. ngio/common/_roi.py +47 -411
  6. ngio/common/_zoom.py +32 -7
  7. ngio/experimental/iterators/_abstract_iterator.py +2 -2
  8. ngio/experimental/iterators/_feature.py +9 -14
  9. ngio/experimental/iterators/_image_processing.py +17 -27
  10. ngio/experimental/iterators/_rois_utils.py +4 -4
  11. ngio/experimental/iterators/_segmentation.py +37 -53
  12. ngio/images/_abstract_image.py +135 -93
  13. ngio/images/_create.py +16 -0
  14. ngio/images/_create_synt_container.py +10 -0
  15. ngio/images/_image.py +33 -9
  16. ngio/images/_label.py +24 -3
  17. ngio/images/_masked_image.py +60 -81
  18. ngio/images/_ome_zarr_container.py +33 -0
  19. ngio/io_pipes/__init__.py +49 -0
  20. ngio/io_pipes/_io_pipes.py +286 -0
  21. ngio/io_pipes/_io_pipes_masked.py +481 -0
  22. ngio/io_pipes/_io_pipes_roi.py +143 -0
  23. ngio/io_pipes/_io_pipes_utils.py +299 -0
  24. ngio/io_pipes/_match_shape.py +376 -0
  25. ngio/io_pipes/_ops_axes.py +146 -0
  26. ngio/io_pipes/_ops_slices.py +218 -0
  27. ngio/io_pipes/_ops_transforms.py +104 -0
  28. ngio/io_pipes/_zoom_transform.py +175 -0
  29. ngio/ome_zarr_meta/__init__.py +6 -2
  30. ngio/ome_zarr_meta/ngio_specs/__init__.py +6 -4
  31. ngio/ome_zarr_meta/ngio_specs/_axes.py +182 -70
  32. ngio/ome_zarr_meta/ngio_specs/_dataset.py +47 -121
  33. ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +30 -22
  34. ngio/ome_zarr_meta/ngio_specs/_pixel_size.py +17 -1
  35. ngio/ome_zarr_meta/v04/_v04_spec_utils.py +33 -30
  36. ngio/resources/20200812-CardiomyocyteDifferentiation14-Cycle1_B03/nuclei.png +0 -0
  37. ngio/resources/__init__.py +1 -0
  38. ngio/resources/resource_model.py +1 -0
  39. ngio/{common/transforms → transforms}/__init__.py +1 -1
  40. ngio/transforms/_zoom.py +19 -0
  41. ngio/utils/_zarr_utils.py +5 -1
  42. {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/METADATA +1 -1
  43. ngio-0.4.0a4.dist-info/RECORD +83 -0
  44. ngio/common/_array_io_pipes.py +0 -554
  45. ngio/common/_array_io_utils.py +0 -508
  46. ngio/common/transforms/_label.py +0 -12
  47. ngio/common/transforms/_zoom.py +0 -109
  48. ngio-0.4.0a3.dist-info/RECORD +0 -76
  49. {ngio-0.4.0a3.dist-info → ngio-0.4.0a4.dist-info}/WHEEL +0 -0
  50. {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 collections.abc import Callable, Sequence
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, pixel_size: float, max_shape: int | None) -> int:
33
- """Convert to raster coordinates."""
34
- round_value = int(np.round(value / pixel_size))
35
- # Ensure the value is within the image shape boundaries
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 _to_world(value: int, pixel_size: float) -> float:
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, Generic[T]):
40
+ class GenericRoi(BaseModel):
50
41
  """A generic Region of Interest (ROI) model."""
51
42
 
52
43
  name: str | None
53
- x: T
54
- y: T
55
- z: T | None = None
56
- t: T | None = None
57
- x_length: T
58
- y_length: T
59
- z_length: T | None = None
60
- t_length: T | None = None
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[T]") -> "GenericRoi[T] | None":
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[float]):
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
- if dimensions is None:
213
- # No check for dimensions
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
- z = _to_raster(self.z, pixel_size.z, dim_z)
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
- t_length = _to_raster(self.t_length, pixel_size.t, dim_t)
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, dimensions=dimensions)
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[int]):
255
+ class RoiPixels(GenericRoi):
290
256
  """Region of interest (ROI) in pixel coordinates."""
291
257
 
292
- x: int = 0
293
- y: int = 0
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, SlicingInputType]:
340
- x_slice = slice(self.x, self.x + self.x_length)
341
- y_slice = slice(self.y, self.y + self.y_length)
342
- if self.z is not None and self.z_length is not None:
343
- z_slice = slice(self.z, self.z + self.z_length)
344
- else:
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: Literal[0, 1, 2] = 1,
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[0, 1, 2]): The order of interpolation. Defaults to 1.
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, zoom=_scale, order=order, mode="grid-constant", grid_mode=True
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: Literal[0, 1, 2] = 1,
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, zoom=_scale, order=order, mode="grid-constant", grid_mode=True
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()