impy-array 2.4.6__tar.gz → 2.4.7__tar.gz

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 (71) hide show
  1. {impy_array-2.4.6 → impy_array-2.4.7}/PKG-INFO +2 -2
  2. {impy_array-2.4.6 → impy_array-2.4.7}/impy/__init__.py +1 -1
  3. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_corr.py +3 -1
  4. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_skimage.py +14 -3
  5. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/imgarray.py +16 -6
  6. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/lazy.py +241 -39
  7. {impy_array-2.4.6 → impy_array-2.4.7}/impy/axes/_axis.py +40 -40
  8. {impy_array-2.4.6 → impy_array-2.4.7}/impy/core.py +5 -5
  9. {impy_array-2.4.6 → impy_array-2.4.7}/pyproject.toml +1 -1
  10. {impy_array-2.4.6 → impy_array-2.4.7}/.gitignore +0 -0
  11. {impy_array-2.4.6 → impy_array-2.4.7}/LICENSE +0 -0
  12. {impy_array-2.4.6 → impy_array-2.4.7}/README.md +0 -0
  13. {impy_array-2.4.6 → impy_array-2.4.7}/impy/__main__.py +0 -0
  14. {impy_array-2.4.6 → impy_array-2.4.7}/impy/_const.py +0 -0
  15. {impy_array-2.4.6 → impy_array-2.4.7}/impy/_types.py +0 -0
  16. {impy_array-2.4.6 → impy_array-2.4.7}/impy/array_api.py +0 -0
  17. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/__init__.py +0 -0
  18. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_imshow.py +0 -0
  19. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/__init__.py +0 -0
  20. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_deconv.py +0 -0
  21. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_docs.py +0 -0
  22. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_filters.py +0 -0
  23. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_glcm.py +0 -0
  24. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_linalg.py +0 -0
  25. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_misc.py +0 -0
  26. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_plot.py +0 -0
  27. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_process_numba.py +0 -0
  28. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_structures.py +0 -0
  29. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/_utils/_transform.py +0 -0
  30. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/axesmixin.py +0 -0
  31. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/bases/__init__.py +0 -0
  32. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/bases/metaarray.py +0 -0
  33. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/bases/overload.py +0 -0
  34. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/big.py +0 -0
  35. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/label.py +0 -0
  36. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/labeledarray.py +0 -0
  37. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/phasearray.py +0 -0
  38. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/specials.py +0 -0
  39. {impy_array-2.4.6 → impy_array-2.4.7}/impy/arrays/tiled.py +0 -0
  40. {impy_array-2.4.6 → impy_array-2.4.7}/impy/axes/__init__.py +0 -0
  41. {impy_array-2.4.6 → impy_array-2.4.7}/impy/axes/_axes.py +0 -0
  42. {impy_array-2.4.6 → impy_array-2.4.7}/impy/axes/_axes_tuple.py +0 -0
  43. {impy_array-2.4.6 → impy_array-2.4.7}/impy/axes/_slicer.py +0 -0
  44. {impy_array-2.4.6 → impy_array-2.4.7}/impy/binder.py +0 -0
  45. {impy_array-2.4.6 → impy_array-2.4.7}/impy/collections.py +0 -0
  46. {impy_array-2.4.6 → impy_array-2.4.7}/impy/correlation.py +0 -0
  47. {impy_array-2.4.6 → impy_array-2.4.7}/impy/frame/__init__.py +0 -0
  48. {impy_array-2.4.6 → impy_array-2.4.7}/impy/frame/frames.py +0 -0
  49. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/__init__.py +0 -0
  50. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_mrc.py +0 -0
  51. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_nd2.py +0 -0
  52. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_npy.py +0 -0
  53. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_registry.py +0 -0
  54. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_tiff.py +0 -0
  55. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_utils.py +0 -0
  56. {impy_array-2.4.6 → impy_array-2.4.7}/impy/io/_zarr.py +0 -0
  57. {impy_array-2.4.6 → impy_array-2.4.7}/impy/lazy/__init__.py +0 -0
  58. {impy_array-2.4.6 → impy_array-2.4.7}/impy/lazy/core.py +0 -0
  59. {impy_array-2.4.6 → impy_array-2.4.7}/impy/lazy/random.py +0 -0
  60. {impy_array-2.4.6 → impy_array-2.4.7}/impy/random.py +0 -0
  61. {impy_array-2.4.6 → impy_array-2.4.7}/impy/roi.py +0 -0
  62. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/__init__.py +0 -0
  63. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/axesop.py +0 -0
  64. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/deco.py +0 -0
  65. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/gauss.py +0 -0
  66. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/misc.py +0 -0
  67. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/slicer.py +0 -0
  68. {impy_array-2.4.6 → impy_array-2.4.7}/impy/utils/utilcls.py +0 -0
  69. {impy_array-2.4.6 → impy_array-2.4.7}/impy/viewer/__init__.py +0 -0
  70. {impy_array-2.4.6 → impy_array-2.4.7}/impy/viewer/utils.py +0 -0
  71. {impy_array-2.4.6 → impy_array-2.4.7}/impy/viewer/viewer.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: impy-array
3
- Version: 2.4.6
3
+ Version: 2.4.7
4
4
  Summary: Speed up coding/extending image analysis in Python.
5
5
  Project-URL: Download, https://github.com/hanjinliu/impy
6
6
  Author-email: Hanjin Liu <liuhanjin-sc@g.ecc.u-tokyo.ac.jp>
@@ -1,4 +1,4 @@
1
- __version__ = "2.4.6"
1
+ __version__ = "2.4.7"
2
2
  __author__ = "Hanjin Liu"
3
3
  __email__ = "liuhanjin-sc@g.ecc.u-tokyo.ac.jp"
4
4
 
@@ -34,7 +34,7 @@ def subpixel_pcc(
34
34
  f0: np.ndarray,
35
35
  f1: np.ndarray,
36
36
  upsample_factor: int,
37
- max_shifts: tuple[float, ...] | None = None,
37
+ max_shifts: float | tuple[float, ...] | None = None,
38
38
  ):
39
39
  power, product = _pcc_and_power_spec(f0, f1, max_shifts)
40
40
  maxima = xp.unravel_index(xp.argmax(power), power.shape)
@@ -60,6 +60,8 @@ def subpixel_pcc(
60
60
  power = abs2(cross_correlation)
61
61
 
62
62
  if max_shifts is not None:
63
+ if np.isscalar(max_shifts):
64
+ max_shifts = (max_shifts,) * f0.ndim
63
65
  max_shifts = xp.asarray(max_shifts)
64
66
  _upsampled_left_shifts = (shifts + max_shifts) * upsample_factor
65
67
  _upsampled_right_shifts = (max_shifts - shifts) * upsample_factor
@@ -6,8 +6,19 @@ from functools import reduce, lru_cache
6
6
 
7
7
  # same as the function in skimage.filters._fft_based (available in scikit-image >= 0.19)
8
8
  @lru_cache(maxsize=4)
9
- def _get_ND_butterworth_filter(shape: tuple[int, ...], cutoff: float, order: int,
10
- high_pass: bool, real: bool):
9
+ def _get_ND_butterworth_filter(
10
+ shape: tuple[int, ...],
11
+ cutoff: float,
12
+ order: int,
13
+ high_pass: bool,
14
+ real: bool,
15
+ ):
16
+ if cutoff == 0:
17
+ if high_pass:
18
+ wfilt = np.ones(shape, dtype=np.float32)
19
+ else:
20
+ wfilt = np.zeros(shape, dtype=np.float32)
21
+ return wfilt
11
22
  ranges = []
12
23
  for d, fc in zip(shape, cutoff):
13
24
  axis = np.arange(-(d - 1) // 2, (d - 1) // 2 + 1, dtype=np.float32) / (d*fc)
@@ -19,4 +30,4 @@ def _get_ND_butterworth_filter(shape: tuple[int, ...], cutoff: float, order: int
19
30
  wfilt = 1 / (1 + q2**order)
20
31
  if high_pass:
21
32
  wfilt = 1 - wfilt
22
- return wfilt
33
+ return wfilt
@@ -247,12 +247,11 @@ class ImgArray(LabeledArray):
247
247
  cval = cval(self.value)
248
248
 
249
249
  prefilter = prefilter or order > 1
250
- mtx = _transform.compose_affine_matrix(translation=translation)
251
250
 
252
251
  return self._apply_dask(
253
- _transform.warp,
252
+ _transform.shift,
254
253
  c_axes=complement_axes(dims, self.axes),
255
- kwargs=dict(matrix=mtx, order=order, mode=mode, cval=cval,
254
+ kwargs=dict(shift=translation, order=order, mode=mode, cval=cval,
256
255
  prefilter=prefilter)
257
256
  )
258
257
 
@@ -3986,6 +3985,7 @@ class ImgArray(LabeledArray):
3986
3985
  along: AxisLike | None = None,
3987
3986
  show_drift: bool = False,
3988
3987
  upsample_factor: int = 10,
3988
+ max_shift: nDFloat | None = None,
3989
3989
  ) -> MarkerFrame:
3990
3990
  """
3991
3991
  Calculate yx-directional drift using the method equivalent to
@@ -3999,6 +3999,8 @@ class ImgArray(LabeledArray):
3999
3999
  If True, plot the result.
4000
4000
  upsample_factor : int, default is 10
4001
4001
  Up-sampling factor when calculating phase cross correlation.
4002
+ max_shift : tuple of float, optional
4003
+ Maximum shift in spatial directions.
4002
4004
 
4003
4005
  Returns
4004
4006
  -------
@@ -4013,7 +4015,9 @@ class ImgArray(LabeledArray):
4013
4015
  raise ValueError("`along` must be single character.")
4014
4016
  if not isinstance(upsample_factor, int):
4015
4017
  raise TypeError(f"upsample-factor must be integer but got {type(upsample_factor)}")
4016
-
4018
+ if max_shift is not None:
4019
+ if np.isscalar(max_shift):
4020
+ max_shift = np.full(self.ndim - 1, max_shift)
4017
4021
  result = np.zeros((self.sizeof(along), self.ndim-1), dtype=np.float32)
4018
4022
  c_axes = complement_axes(along, self.axes)
4019
4023
  last_img = None
@@ -4022,7 +4026,12 @@ class ImgArray(LabeledArray):
4022
4026
  img = xp.asarray(img)
4023
4027
  if last_img is not None:
4024
4028
  result[i] = xp.asnumpy(
4025
- _corr.subpixel_pcc(last_img, img, upsample_factor=upsample_factor)[0]
4029
+ _corr.subpixel_pcc(
4030
+ last_img,
4031
+ img,
4032
+ max_shifts=max_shift,
4033
+ upsample_factor=upsample_factor,
4034
+ )[0]
4026
4035
  )
4027
4036
  last_img = img
4028
4037
  else:
@@ -4047,6 +4056,7 @@ class ImgArray(LabeledArray):
4047
4056
  *,
4048
4057
  zero_ave: bool = True,
4049
4058
  along: AxisLike | None = None,
4059
+ max_shift: nDFloat | None = None,
4050
4060
  order: int = 1,
4051
4061
  mode: str = "constant",
4052
4062
  cval: float = 0,
@@ -4109,7 +4119,7 @@ class ImgArray(LabeledArray):
4109
4119
  )
4110
4120
  return out
4111
4121
 
4112
- shift = ref.track_drift(along=along).values
4122
+ shift = ref.track_drift(along=along, max_shift=max_shift).values
4113
4123
 
4114
4124
  else:
4115
4125
  shift = np.asarray(shift, dtype=np.float32)
@@ -2,10 +2,9 @@ from __future__ import annotations
2
2
  from functools import lru_cache, wraps
3
3
  import itertools
4
4
  from pathlib import Path
5
- from typing import Any, Callable, TYPE_CHECKING, Sequence
6
- import warnings
5
+ from typing import Any, Callable, TYPE_CHECKING, Literal, Sequence
7
6
  import numpy as np
8
- from numpy.typing import ArrayLike, DTypeLike
7
+ from numpy.typing import DTypeLike
9
8
  from warnings import warn
10
9
  import tempfile
11
10
  import operator
@@ -300,8 +299,8 @@ class LazyImgArray(AxesMixin):
300
299
 
301
300
  def compute(self, ignore_limit: bool = False) -> ImgArray:
302
301
  """
303
- Compute all the task and convert the result into ImgArray. If image size overwhelms MAX_GB
304
- then MemoryError is raised.
302
+ Compute all the task and convert the result into ImgArray. If image size
303
+ overwhelms MAX_GB then MemoryError is raised.
305
304
  """
306
305
  if self.gb > Const["MAX_GB"] and not ignore_limit:
307
306
  raise MemoryError(f"Too large: {self.gb:.2f} GB")
@@ -316,8 +315,8 @@ class LazyImgArray(AxesMixin):
316
315
 
317
316
  def release(self, update: bool = True) -> LazyImgArray:
318
317
  """
319
- Compute all the tasks and store the data in memory map, and read it as a dask array
320
- again.
318
+ Compute all the tasks and store the data in memory map, and read it as a dask
319
+ array again.
321
320
  """
322
321
  from dask import array as da
323
322
 
@@ -528,25 +527,21 @@ class LazyImgArray(AxesMixin):
528
527
  kwargs: dict[str, Any] = None
529
528
  ) -> DaskArray:
530
529
  from dask import array as da
530
+
531
531
  if args is None:
532
532
  args = ()
533
533
  if kwargs is None:
534
534
  kwargs = {}
535
535
  all_axes = str(self.axes)
536
- def _func(input: ArrayLike, *args, **kwargs):
537
- out = xp.empty(input.shape, input.dtype)
538
- for sl in iter_slice(input.shape, c_axes, all_axes):
539
- out[sl] = func(input[sl], *args, **kwargs)
540
- return out
541
-
536
+ _func = _make_map_blocks_func(func, self.dtype, c_axes, all_axes)
542
537
  return da.map_blocks(_func, self.value, dtype=self.dtype, *args, **kwargs)
543
538
 
544
539
  def _apply_map_overlap(
545
540
  self,
546
541
  func: Callable,
547
- c_axes: str = None,
542
+ c_axes: str | None = None,
548
543
  depth: int = 16,
549
- boundary="reflect",
544
+ boundary="none",
550
545
  dtype: DTypeLike = None,
551
546
  args: tuple | None = None,
552
547
  kwargs: dict[str, Any] | None = None
@@ -560,16 +555,9 @@ class LazyImgArray(AxesMixin):
560
555
  if dtype is None:
561
556
  dtype = self.dtype
562
557
  all_axes = str(self.axes)
563
- def _func(input: np.ndarray, *args, **kwargs):
564
- out = xp.empty(input.shape, dtype=dtype)
565
- for sl in iter_slice(input.shape, c_axes, all_axes):
566
- out[sl] = func(input[sl], *args, **kwargs).astype(dtype, copy=False)
567
- return out
568
- depth = switch_slice(c_axes, self.axes, 0, depth)
569
- return da.map_overlap(
570
- _func, self.value, *args, depth=depth, boundary=boundary, dtype=dtype,
571
- **kwargs
572
- )
558
+ _func = _make_map_overlap_func(func, dtype, c_axes, all_axes)
559
+ depths = switch_slice(c_axes, self.axes, 0, depth)
560
+ return da.map_overlap(_func, self.value, *args, depth=depths, dtype=dtype, **kwargs, boundary=boundary, align_arrays=False)
573
561
 
574
562
  def _apply_dask_filter(
575
563
  self,
@@ -589,6 +577,61 @@ class LazyImgArray(AxesMixin):
589
577
  out[sl] = func(img, *args, **kwargs)
590
578
  return out
591
579
 
580
+ @_docs.copy_docs(ImgArray.shift)
581
+ @dims_to_spatial_axes
582
+ @check_input_and_output_lazy
583
+ def shift(
584
+ self,
585
+ translation: Coords,
586
+ *,
587
+ order: int = 3,
588
+ mode: PaddingMode = "constant",
589
+ cval: float = 0,
590
+ prefilter: bool | None = None,
591
+ dims: Dims = 2,
592
+ update: bool = False
593
+ ) -> LazyImgArray:
594
+
595
+ prefilter = prefilter or order > 1
596
+
597
+ return self._apply_map_blocks(
598
+ _transform.shift,
599
+ c_axes=complement_axes(dims, self.axes),
600
+ kwargs=dict(shift=translation, order=order, mode=mode, cval=cval,
601
+ prefilter=prefilter)
602
+ )
603
+
604
+ @_docs.copy_docs(ImgArray.rotate)
605
+ @dims_to_spatial_axes
606
+ @check_input_and_output_lazy
607
+ def rotate(
608
+ self,
609
+ degree: float,
610
+ center: Sequence[float] | Literal["center"] = "center",
611
+ *,
612
+ order: int = 3,
613
+ mode: PaddingMode = "constant",
614
+ cval: float = 0,
615
+ dims: Dims = 2,
616
+ update: bool = False
617
+ ) -> LazyImgArray:
618
+ if center == "center":
619
+ center = np.array(self.sizesof(dims))/2. - 0.5
620
+ else:
621
+ center = np.asarray(center)
622
+
623
+ translation_0 = _transform.compose_affine_matrix(translation=center)
624
+ rotation = _transform.compose_affine_matrix(rotation=np.deg2rad(degree))
625
+ translation_1 = _transform.compose_affine_matrix(translation=-center)
626
+
627
+ mx = translation_0 @ rotation @ translation_1
628
+ mx[-1, :] = [0] * len(dims) + [1]
629
+ return self._apply_map_blocks(
630
+ _transform.warp,
631
+ c_axes=complement_axes(dims, self.axes),
632
+ kwargs=dict(matrix=mx, order=order, mode=mode, cval=cval),
633
+ )
634
+
592
635
  # TODO: This should wait for dask-image implement map_coordinates
593
636
  # @_docs.copy_docs(LabeledArray.rotated_crop)
594
637
  # @dims_to_spatial_axes
@@ -702,6 +745,28 @@ class LazyImgArray(AxesMixin):
702
745
  kwargs=dict(footprint=disk)
703
746
  )
704
747
 
748
+
749
+ @_docs.copy_docs(ImgArray.tophat)
750
+ @dims_to_spatial_axes
751
+ @check_input_and_output_lazy
752
+ def tophat(
753
+ self,
754
+ radius: float = 1,
755
+ *,
756
+ mode: PaddingMode = "reflect",
757
+ cval: float = 0.0,
758
+ dims: Dims = None,
759
+ update: bool = False
760
+ ) -> LazyImgArray:
761
+ disk = _structures.ball_like(radius, len(dims))
762
+ return self._apply_map_overlap(
763
+ _filters.white_tophat,
764
+ c_axes=complement_axes(dims, self.axes),
765
+ dtype=self.dtype,
766
+ kwargs=dict(footprint=disk, mode=mode, cval=cval)
767
+ )
768
+
769
+
705
770
  @_docs.copy_docs(ImgArray.gaussian_filter)
706
771
  @dims_to_spatial_axes
707
772
  @same_dtype(asfloat=True)
@@ -722,6 +787,64 @@ class LazyImgArray(AxesMixin):
722
787
  kwargs=dict(sigma=sigma),
723
788
  )
724
789
 
790
+ @_docs.copy_docs(ImgArray.dog_filter)
791
+ @dims_to_spatial_axes
792
+ @check_input_and_output_lazy
793
+ def dog_filter(
794
+ self,
795
+ low_sigma: nDFloat = 1.0,
796
+ high_sigma: nDFloat = 2.0,
797
+ *,
798
+ dims: Dims = None,
799
+ update: bool = False
800
+ ) -> LazyImgArray:
801
+ c_axes = complement_axes(dims, self.axes)
802
+ depth = _ceilint(high_sigma*4)
803
+ return self._apply_map_overlap(
804
+ _filters.dog_filter,
805
+ c_axes=c_axes,
806
+ depth=depth,
807
+ kwargs=dict(low_sigma=low_sigma, high_sigma=high_sigma),
808
+ )
809
+
810
+ @_docs.copy_docs(ImgArray.doh_filter)
811
+ @dims_to_spatial_axes
812
+ @check_input_and_output_lazy
813
+ def doh_filter(
814
+ self,
815
+ sigma: nDFloat = 1.0,
816
+ *,
817
+ dims: Dims = None,
818
+ update: bool = False
819
+ ) -> LazyImgArray:
820
+ c_axes = complement_axes(dims, self.axes)
821
+ depth = _ceilint(sigma*4)
822
+ return self._apply_map_overlap(
823
+ _filters.doh_filter,
824
+ c_axes=c_axes,
825
+ depth=depth,
826
+ kwargs=dict(sigma=sigma),
827
+ )
828
+
829
+ @_docs.copy_docs(ImgArray.log_filter)
830
+ @dims_to_spatial_axes
831
+ @check_input_and_output_lazy
832
+ def log_filter(
833
+ self,
834
+ sigma: nDFloat = 1.0,
835
+ *,
836
+ dims: Dims = None,
837
+ update: bool = False
838
+ ) -> LazyImgArray:
839
+ c_axes = complement_axes(dims, self.axes)
840
+ depth = _ceilint(sigma*4)
841
+ return -self._apply_map_overlap(
842
+ _filters.gaussian_laplace,
843
+ c_axes=c_axes,
844
+ depth=depth,
845
+ kwargs=dict(sigma=sigma),
846
+ )
847
+
725
848
  @_docs.copy_docs(ImgArray.spline_filter)
726
849
  @dims_to_spatial_axes
727
850
  @same_dtype(asfloat=True)
@@ -754,6 +877,8 @@ class LazyImgArray(AxesMixin):
754
877
  self,
755
878
  radius: float = 1,
756
879
  *,
880
+ mode: PaddingMode = "reflect",
881
+ cval: float = 0,
757
882
  dims: Dims = None,
758
883
  update: bool = False
759
884
  ) -> LazyImgArray:
@@ -762,8 +887,51 @@ class LazyImgArray(AxesMixin):
762
887
  xp.ndi.median_filter,
763
888
  depth=_ceilint(radius),
764
889
  c_axes=complement_axes(dims, self.axes),
765
- kwargs=dict(footprint=disk)
766
- )
890
+ kwargs=dict(footprint=disk, mode=mode, cval=cval),
891
+ )
892
+
893
+
894
+ @_docs.copy_docs(ImgArray.std_filter)
895
+ @dims_to_spatial_axes
896
+ @same_dtype
897
+ @check_input_and_output_lazy
898
+ def std_filter(
899
+ self,
900
+ radius: float = 1,
901
+ *,
902
+ mode: PaddingMode = "reflect",
903
+ cval: float = 0,
904
+ dims: Dims = None,
905
+ update: bool = False
906
+ ) -> LazyImgArray:
907
+ disk = _structures.ball_like(radius, len(dims))
908
+ return self._apply_map_overlap(
909
+ _filters.std_filter,
910
+ depth=_ceilint(radius),
911
+ c_axes=complement_axes(dims, self.axes),
912
+ kwargs=dict(selem=disk, mode=mode, cval=cval),
913
+ )
914
+
915
+ @_docs.copy_docs(ImgArray.coef_filter)
916
+ @dims_to_spatial_axes
917
+ @same_dtype
918
+ @check_input_and_output_lazy
919
+ def coef_filter(
920
+ self,
921
+ radius: float = 1,
922
+ *,
923
+ mode: PaddingMode = "reflect",
924
+ cval: float = 0,
925
+ dims: Dims = None,
926
+ update: bool = False
927
+ ) -> LazyImgArray:
928
+ disk = _structures.ball_like(radius, len(dims))
929
+ return self._apply_map_overlap(
930
+ _filters.coef_filter,
931
+ depth=_ceilint(radius),
932
+ c_axes=complement_axes(dims, self.axes),
933
+ kwargs=dict(selem=disk, mode=mode, cval=cval),
934
+ )
767
935
 
768
936
  @_docs.copy_docs(ImgArray.mean_filter)
769
937
  @same_dtype(asfloat=True)
@@ -773,17 +941,19 @@ class LazyImgArray(AxesMixin):
773
941
  self,
774
942
  radius: float = 1,
775
943
  *,
944
+ mode: PaddingMode = "reflect",
945
+ cval: float = 0,
776
946
  dims: Dims = None,
777
947
  update: bool = False
778
948
  ) -> LazyImgArray:
779
949
  disk = _structures.ball_like(radius, len(dims))
780
- kernel = (disk/np.sum(disk)).astype(np.float32)
950
+ kernel = (disk / np.sum(disk)).astype(np.float32)
781
951
  return self._apply_map_overlap(
782
952
  xp.ndi.convolve,
783
953
  depth=_ceilint(radius),
784
954
  c_axes=complement_axes(dims, self.axes),
785
- kwargs=dict(weights=kernel),
786
- )
955
+ kwargs=dict(weights=kernel, mode=mode, cval=cval),
956
+ )
787
957
 
788
958
  @_docs.copy_docs(ImgArray.convolve)
789
959
  @dims_to_spatial_axes
@@ -793,7 +963,7 @@ class LazyImgArray(AxesMixin):
793
963
  self,
794
964
  kernel,
795
965
  *,
796
- mode: str = "reflect",
966
+ mode: PaddingMode = "reflect",
797
967
  cval: float = 0,
798
968
  dims: Dims = None,
799
969
  update: bool = False
@@ -915,7 +1085,7 @@ class LazyImgArray(AxesMixin):
915
1085
  thr: float | str,
916
1086
  *,
917
1087
  along: AxisLike | None = None,
918
- ) -> ImgArray:
1088
+ ) -> LazyImgArray:
919
1089
  if self.dtype == bool:
920
1090
  return self
921
1091
 
@@ -943,7 +1113,7 @@ class LazyImgArray(AxesMixin):
943
1113
  @dims_to_spatial_axes
944
1114
  @check_input_and_output_lazy
945
1115
  def fft(self, *, shape: int | Iterable[int] | str = "same", shift: bool = True,
946
- dims: Dims = None) -> LazyImgArray:
1116
+ double_precision: bool = False, dims: Dims = None) -> LazyImgArray:
947
1117
  from dask import array as da
948
1118
 
949
1119
  axes = [self.axisof(a) for a in dims]
@@ -955,7 +1125,8 @@ class LazyImgArray(AxesMixin):
955
1125
  else:
956
1126
  shape = check_nd(shape, len(dims))
957
1127
  fft = _get_fft_func(Const["RESOURCE"])
958
- freq = fft(self.value.astype(np.float32), s=shape, axes=axes)
1128
+ dtype = np.float64 if double_precision else np.float32
1129
+ freq = fft(self.value.astype(dtype), s=shape, axes=axes)
959
1130
  if shift:
960
1131
  freq[:] = da.fft.fftshift(freq, axes=axes)
961
1132
  return freq
@@ -963,16 +1134,24 @@ class LazyImgArray(AxesMixin):
963
1134
  @_docs.copy_docs(ImgArray.ifft)
964
1135
  @dims_to_spatial_axes
965
1136
  @check_input_and_output_lazy
966
- def ifft(self, real: bool = True, *, shift: bool = True, dims: Dims = None) -> LazyImgArray:
1137
+ def ifft(
1138
+ self,
1139
+ real: bool = True,
1140
+ *,
1141
+ shift: bool = True,
1142
+ double_precision: bool = False,
1143
+ dims: Dims = None,
1144
+ ) -> LazyImgArray:
967
1145
  from dask import array as da
968
1146
 
1147
+ dtype = np.complex128 if double_precision else np.complex64
969
1148
  axes = [self.axisof(a) for a in dims]
970
1149
  if shift:
971
1150
  freq = da.fft.ifftshift(self.value, axes=axes)
972
1151
  else:
973
1152
  freq = self.value
974
1153
  ifft = _get_ifft_func(Const["RESOURCE"])
975
- out = ifft(freq, axes=axes)
1154
+ out = ifft(freq.astype(dtype), axes=axes)
976
1155
 
977
1156
  if real:
978
1157
  out = da.real(out)
@@ -982,9 +1161,16 @@ class LazyImgArray(AxesMixin):
982
1161
  @_docs.copy_docs(ImgArray.power_spectra)
983
1162
  @dims_to_spatial_axes
984
1163
  @check_input_and_output_lazy
985
- def power_spectra(self, shape = "same", norm: bool = False, zero_norm: bool = False, *,
986
- dims: Dims = None) -> LazyImgArray:
987
- freq = self.fft(dims=dims, shape=shape)
1164
+ def power_spectra(
1165
+ self,
1166
+ shape = "same",
1167
+ norm: bool = False,
1168
+ zero_norm: bool = False,
1169
+ *,
1170
+ double_precision: bool = False,
1171
+ dims: Dims = None,
1172
+ ) -> LazyImgArray:
1173
+ freq = self.fft(dims=dims, shape=shape, double_precision=double_precision)
988
1174
  pw = freq.value.real ** 2 + freq.value.imag ** 2
989
1175
  if norm:
990
1176
  pw /= pw.max()
@@ -1572,3 +1758,19 @@ def _get_ifft_func(resource) -> Callable[[DaskArray], DaskArray]:
1572
1758
  from dask import array as da
1573
1759
 
1574
1760
  return da.fft.fft_wrap(scipy_fft.ifftn)
1761
+
1762
+ def _make_map_blocks_func(func, dtype, c_axes, all_axes):
1763
+ def _func(input: np.ndarray, *args, **kwargs):
1764
+ out = xp.empty(input.shape, dtype)
1765
+ for sl in iter_slice(input.shape, c_axes, all_axes):
1766
+ out[sl] = func(input[sl], *args, **kwargs)
1767
+ return out
1768
+ return _func
1769
+
1770
+ def _make_map_overlap_func(func, dtype, c_axes, all_axes):
1771
+ def _func(input: np.ndarray, *args, **kwargs):
1772
+ out = xp.empty(input.shape, dtype=dtype)
1773
+ for sl in iter_slice(input.shape, c_axes, all_axes):
1774
+ out[sl] = func(input[sl], *args, **kwargs).astype(dtype, copy=False)
1775
+ return out
1776
+ return _func
@@ -21,20 +21,20 @@ class RangeLabels(LabelBase[_T]):
21
21
  self._start = start
22
22
  self._stop = stop
23
23
  self._step = step
24
-
24
+
25
25
  def __getitem__(self, key):
26
26
  val = self._start + key * self._step
27
27
  if val > self._stop:
28
28
  raise IndexError("Index out of range.")
29
29
  return val
30
-
30
+
31
31
  def __len__(self) -> int:
32
32
  return (self._stop - self._start) // self._step
33
33
 
34
34
 
35
35
  class Labels(LabelBase):
36
36
  """A tuple-like object storing label specifiers."""
37
-
37
+
38
38
  def __init__(self, seq: Iterable[_T]):
39
39
  self._labels = tuple(seq)
40
40
  self._hash_map: dict[_T, int] = {}
@@ -44,29 +44,29 @@ class Labels(LabelBase):
44
44
  def __repr__(self) -> str:
45
45
  clsname = type(self).__name__
46
46
  return f"{clsname}{self._labels!r}"
47
-
47
+
48
48
  def __len__(self) -> int:
49
49
  """Length of labels"""
50
50
  return len(self._labels)
51
-
51
+
52
52
  def __getitem__(self, key):
53
53
  out = self._labels[key]
54
54
  if isinstance(key, slice):
55
55
  return Labels(out)
56
56
  return out
57
-
57
+
58
58
  def __eq__(self, other: Sequence[_T]) -> bool:
59
59
  return self._labels == other
60
-
60
+
61
61
  @property
62
62
  def has_duplicate(self) -> bool:
63
63
  """True if self has duplicated labels."""
64
64
  return len(self._labels) != len(self._hash_map)
65
-
65
+
66
66
  def get_item(self, key: _T | slice):
67
67
  idx = self.get_slice(key)
68
68
  return self._labels[idx]
69
-
69
+
70
70
  def get_slice(self, key: _T | slice) -> int | slice:
71
71
  if self.has_duplicate:
72
72
  raise ValueError("Labels have duplicate.")
@@ -95,67 +95,67 @@ class Labels(LabelBase):
95
95
  class Axis:
96
96
  """
97
97
  An axis object.
98
-
98
+
99
99
  This object behaves like a length-1 string as much as possible.
100
-
101
- Parameters
100
+
101
+ Parameters
102
102
  ----------
103
103
  name : str
104
104
  Name of axis.
105
105
  """
106
-
106
+
107
107
  def __init__(self, name: str, metadata: dict[str, Any] | None = None):
108
108
  self._name = str(name)
109
109
  self._metadata = metadata or {}
110
-
110
+
111
111
  def __str__(self) -> str:
112
112
  """String representation of the axis."""
113
113
  return self._name
114
-
114
+
115
115
  def __repr__(self) -> str:
116
116
  return f"{self.__class__.__name__}[{self._name!r}]"
117
-
117
+
118
118
  def __hash__(self) -> int:
119
119
  """Hash as a string."""
120
120
  return hash(str(self))
121
-
121
+
122
122
  def __eq__(self, other) -> bool:
123
123
  """Check equality as a string."""
124
124
  return str(self) == other
125
-
125
+
126
126
  def __add__(self, other: str) -> str:
127
127
  """Add as a string ans returns a string."""
128
128
  return self._name + other
129
-
129
+
130
130
  def __radd__(self, other: str) -> str:
131
131
  """Add as a string ans returns a string."""
132
132
  return other + self._name
133
-
133
+
134
134
  def __lt__(self, other) -> bool:
135
135
  """To support alphabetic ordering."""
136
136
  return str(self) < str(other)
137
-
137
+
138
138
  def __len__(self) -> int:
139
139
  """Length of the string representation."""
140
140
  return len(str(self))
141
-
141
+
142
142
  def __iter__(self) -> Iterable[str]:
143
143
  """Iterate as a string."""
144
144
  return iter(str(self))
145
-
145
+
146
146
  def __copy__(self) -> Self:
147
147
  return self.__class__(self._name, self._metadata.copy())
148
-
148
+
149
149
  @property
150
150
  def metadata(self) -> dict[str, Any]:
151
151
  """Metadata dictionary."""
152
152
  return self._metadata
153
-
153
+
154
154
  @property
155
155
  def scale(self) -> float:
156
156
  """Physical scale of axis."""
157
157
  return self.metadata.get(_SCALE, 1.0)
158
-
158
+
159
159
  @scale.setter
160
160
  def scale(self, value: float) -> None:
161
161
  """Set physical scale to the axis."""
@@ -163,37 +163,37 @@ class Axis:
163
163
  if value <= 0:
164
164
  raise ValueError(f"Cannot set negative scale: {value!r}.")
165
165
  self.metadata[_SCALE] = value
166
-
166
+
167
167
  @property
168
168
  def unit(self) -> str:
169
169
  """Physical scale unit of axis."""
170
- return self.metadata.get(_UNIT, "px")
171
-
170
+ return self.metadata.get(_UNIT, "")
171
+
172
172
  @unit.setter
173
173
  def unit(self, value: str | None):
174
174
  """Set physical unit to the axis."""
175
175
  if value is None:
176
- value = "px"
176
+ value = ""
177
177
  elif value.startswith("\\u00B5") or value.startswith("\\u03BC"):
178
178
  value = "μ" + value[6:]
179
-
179
+
180
180
  self.metadata[_UNIT] = value
181
-
181
+
182
182
  @property
183
183
  def labels(self) -> Labels | None:
184
184
  """Axis labels."""
185
185
  return self.metadata.get(_LABELS, None)
186
-
186
+
187
187
  @labels.setter
188
188
  def labels(self, value: Iterable[Hashable]) -> None:
189
189
  """Set axis labels."""
190
190
  self.metadata[_LABELS] = Labels(value)
191
-
191
+
192
192
  @labels.deleter
193
193
  def labels(self) -> None:
194
194
  """Set axis labels."""
195
195
  del self.metadata[_LABELS]
196
-
196
+
197
197
  def isin(self, values: Iterable[Hashable]) -> np.ndarray:
198
198
  """Check if labels are in values."""
199
199
  if self.labels is None:
@@ -223,23 +223,23 @@ class Axis:
223
223
  metadata.update(labels=Labels([labels[i] for i in sl]))
224
224
  return self.__class__(self._name, metadata=metadata)
225
225
 
226
-
226
+
227
227
 
228
228
  AxisLike = Union[str, Axis]
229
229
 
230
230
 
231
231
  class UndefAxis(Axis):
232
232
  """Undefined axis object."""
233
-
233
+
234
234
  def __init__(self, *args, **kwargs):
235
235
  super().__init__("#")
236
-
236
+
237
237
  def __repr__(self) -> str:
238
238
  return "#undef"
239
-
239
+
240
240
  def __hash__(self) -> str:
241
241
  return id(self)
242
-
242
+
243
243
  def __eq__(self, other) -> bool:
244
244
  return isinstance(other, str) and other == "#"
245
245
 
@@ -764,18 +764,18 @@ def _imread_stack(
764
764
  def imread_collection(
765
765
  path: str | list[str],
766
766
  filt: Callable[[np.ndarray], bool] | None = None,
767
- ) -> DataList:
767
+ ) -> DataList[ImgArray]:
768
768
  """
769
769
  Open images as ImgArray and store them in DataList.
770
770
 
771
771
  Parameters
772
772
  ----------
773
773
  path : str or list of str
774
- Path than can be passed to ``glob.glob``. If a list of path is given, all the matched
775
- images will be read and concatenated into a DataList.
774
+ Path than can be passed to ``glob.glob``. If a list of path is given, all the
775
+ matched images will be read and concatenated into a DataList.
776
776
  filt : callable, optional
777
- If specified, only images that satisfies filt(img)==True will be stored in the returned
778
- DataList.
777
+ If specified, only images that satisfies filt(img)==True will be stored in the
778
+ returned DataList.
779
779
 
780
780
  Returns
781
781
  -------
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling>=1.26.1"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes