impy-array 2.4.4__py3-none-any.whl → 2.4.6__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.
impy/__init__.py CHANGED
@@ -1,19 +1,19 @@
1
- __version__ = "2.4.4"
1
+ __version__ = "2.4.6"
2
2
  __author__ = "Hanjin Liu"
3
3
  __email__ = "liuhanjin-sc@g.ecc.u-tokyo.ac.jp"
4
4
 
5
5
  import logging
6
+ from typing import TYPE_CHECKING
6
7
 
7
- from ._const import Const, SetConst, use # noqa
8
+ from impy._const import Const, SetConst, use # noqa
8
9
 
9
- from .collections import DataList, DataDict # noqa
10
- from .core import * # noqa
11
- from .binder import bind # noqa
12
- from .viewer import gui # noqa
13
- from .correlation import * # noqa
14
- from .arrays import ImgArray, LazyImgArray, BigImgArray, Label # noqa
15
- from . import random, io, lazy # noqa
16
- from .axes import slicer # noqa
10
+ from impy.collections import DataList, DataDict # noqa
11
+ from impy.core import * # noqa
12
+ from impy.binder import bind # noqa
13
+ from impy.correlation import * # noqa
14
+ from impy.arrays import ImgArray, LazyImgArray, BigImgArray, Label # noqa
15
+ from impy import random, io, lazy # noqa
16
+ from impy.axes import slicer # noqa
17
17
 
18
18
  # Inheritance
19
19
  # -----------
@@ -41,3 +41,23 @@ from numpy import ( # noqa
41
41
  complex64, complex128,
42
42
  bool_,
43
43
  )
44
+
45
+ # lazy loading
46
+
47
+ _VIEWER_CACHE = None
48
+ if TYPE_CHECKING:
49
+ from impy.viewer import napariViewers
50
+
51
+ gui: "napariViewers"
52
+
53
+ def __getattr__(key):
54
+ global _VIEWER_CACHE
55
+
56
+ if key == "gui":
57
+ from impy.viewer import napariViewers
58
+
59
+ if _VIEWER_CACHE is None:
60
+ _VIEWER_CACHE = napariViewers()
61
+ return _VIEWER_CACHE
62
+ else:
63
+ raise AttributeError(f"module 'impy' has no attribute '{key}'")
@@ -41,6 +41,9 @@ def make_rotated_axis(src, dst):
41
41
  n = int(round(d))
42
42
  return np.linspace(src, src + dr / d * (n - 1), n)
43
43
 
44
+ def make_rotated_axis_by_vec(src, unit_vec, n: int):
45
+ return np.linspace(src, src + unit_vec * (n - 1), n)
46
+
44
47
  def make_pad(pad_width, dims, all_axes, **kwargs):
45
48
  """More flexible padding than `np.pad`."""
46
49
  pad_width_ = []
impy/arrays/big.py CHANGED
@@ -31,7 +31,6 @@ class BigImgArray(LazyImgArray):
31
31
  fft = wrap_method(LazyImgArray.fft)
32
32
  ifft = wrap_method(LazyImgArray.ifft)
33
33
  power_spectra = wrap_method(LazyImgArray.power_spectra)
34
- tiled_lowpass_filter = wrap_method(LazyImgArray.tiled_lowpass_filter)
35
34
  proj = wrap_method(LazyImgArray.proj)
36
35
  binning = wrap_method(LazyImgArray.binning)
37
36
  track_drift = wrap_method(LazyImgArray.track_drift)
impy/arrays/imgarray.py CHANGED
@@ -217,13 +217,13 @@ class ImgArray(LabeledArray):
217
217
  c_axes=complement_axes(dims, self.axes),
218
218
  kwargs=dict(matrix=mx, order=order, mode=mode, cval=cval),
219
219
  )
220
-
220
+
221
221
  @_docs.write_docs
222
222
  @dims_to_spatial_axes
223
223
  @same_dtype(asfloat=True)
224
224
  @check_input_and_output
225
225
  def shift(
226
- self, translation, order: int = 3, mode: PaddingMode = "constant",
226
+ self, translation, order: int = 3, mode: PaddingMode = "constant",
227
227
  cval: float = 0, prefilter: bool | None = None, dims: Dims = None,
228
228
  update: bool = False,
229
229
  ) -> ImgArray:
@@ -329,7 +329,9 @@ class ImgArray(LabeledArray):
329
329
  out.set_scale({a: self.scale[a]/scale for a, scale in zip(self.axes, zoom_)})
330
330
  return out
331
331
 
332
- rescale = zoom # backward compatibility
332
+ def log(self, eps: float = 1e-8) -> ImgArray:
333
+ """Calculate natural logarithm of the image, with a small epsilon."""
334
+ return np.log(self.as_float() + eps)
333
335
 
334
336
  @_docs.write_docs
335
337
  @dims_to_spatial_axes
@@ -902,6 +904,51 @@ class ImgArray(LabeledArray):
902
904
  return xp.asnumpy(out)
903
905
 
904
906
 
907
+ @_docs.write_docs
908
+ @dims_to_spatial_axes
909
+ @same_dtype(asfloat=True)
910
+ @check_input_and_output
911
+ def bandpass_filter(
912
+ self,
913
+ cuton: nDFloat = 0.02,
914
+ cutoff: nDFloat = 0.2,
915
+ order: float = 2,
916
+ *,
917
+ dims: Dims = None,
918
+ update: bool = False
919
+ ):
920
+ """
921
+ Butterworth band-pass filter.
922
+
923
+ Parameters
924
+ ----------
925
+ cuton, cutoff : float or array-like
926
+ Cuton and Cutoff frequency. Fequency domain between these two values will be
927
+ passed.
928
+ order : float, default is 2
929
+ Steepness of cutoff.
930
+ {dims}{update}
931
+
932
+ Returns
933
+ -------
934
+ ImgArray
935
+ Filtered image
936
+ """
937
+ from ._utils._skimage import _get_ND_butterworth_filter
938
+ cuton = check_nd(cuton, len(dims))
939
+ cutoff = check_nd(cutoff, len(dims))
940
+ spatial_shape = self.sizesof(dims)
941
+ spatial_axes = [self.axisof(a) for a in dims]
942
+ weight_on = _get_ND_butterworth_filter(spatial_shape, cuton, order, True, True)
943
+ weight_off = _get_ND_butterworth_filter(spatial_shape, cutoff, order, False, True)
944
+ weight = weight_off * weight_on
945
+ input = xp.asarray(self.value)
946
+ if len(dims) < self.ndim:
947
+ weight = add_axes(self.axes, self.shape, weight, dims)
948
+ out = xp.fft.irfftn(xp.asarray(weight)*xp.fft.rfftn(input, axes=spatial_axes),
949
+ s=spatial_shape, axes=spatial_axes)
950
+ return xp.asnumpy(out)
951
+
905
952
  @_docs.write_docs
906
953
  @dims_to_spatial_axes
907
954
  @same_dtype(asfloat=True)
@@ -848,8 +848,8 @@ class LabeledArray(MetaArray):
848
848
  Parameters
849
849
  ----------
850
850
  pixel : int or array-like, default is 1
851
- Number of pixels to remove. If an array is given, each axis will be cropped with different pixels,
852
- using each value respectively.
851
+ Number of pixels to remove. If an array is given, each axis will be cropped
852
+ with different pixels, using each value respectively.
853
853
  {dims}
854
854
 
855
855
  Returns
@@ -869,16 +869,65 @@ class LabeledArray(MetaArray):
869
869
  out = self[fmt[sl]]
870
870
  return out
871
871
 
872
+ def crop_subregion(
873
+ self,
874
+ centers: Coords,
875
+ shape: tuple[int, int],
876
+ angles: float | Sequence[float] = 0,
877
+ *,
878
+ order=3,
879
+ ) -> Self:
880
+ """
881
+ Crop a subragion of an image.
882
+
883
+ Parameters
884
+ ----------
885
+ centers : sequence of (float, float)
886
+ Center coordinates in pixels.
887
+ shape : (int, int)
888
+ Shape of the subregion.
889
+ angles : float or sequence of float, default is 0
890
+ Clockwise angle of the subregion.
891
+ {dims}
892
+ """
893
+ if order > 1:
894
+ self_filt = self.spline_filter(order).value
895
+ else:
896
+ self_filt = self.value
897
+ centers = np.asarray(centers)
898
+ if np.isscalar(angles):
899
+ angles = [angles] * len(centers)
900
+ else:
901
+ if len(centers) != len(angles):
902
+ raise ValueError("Length of centers and angles must be the same.")
903
+ cropped_imgs = []
904
+ for center, angle in zip(centers, angles, strict=True):
905
+ center = np.asarray(center)
906
+ rad = np.deg2rad(angle)
907
+ ey = np.array([np.sin(rad), np.cos(rad)])
908
+ ex = np.array([np.cos(rad), -np.sin(rad)])
909
+ ny, nx = shape
910
+ origin = center - ey * (nx - 1) / 2 - ex * (ny - 1) / 2
911
+ ax0 = _misc.make_rotated_axis_by_vec(origin, ex, nx)
912
+ ax1 = _misc.make_rotated_axis_by_vec(origin, ey, ny)
913
+ all_coords = ax0[:, np.newaxis] + ax1[np.newaxis] - origin
914
+ all_coords = np.moveaxis(all_coords, -1, 0)
915
+ cropped_img = ndi.map_coordinates(self_filt, all_coords, prefilter=False, order=order)
916
+ cropped_imgs.append(cropped_img)
917
+ cropped_imgs = np.stack(cropped_imgs, axis=0).view(self.__class__)
918
+ return cropped_imgs._set_info(self, new_axes="p" + self.axes)
919
+
872
920
  @_docs.write_docs
873
921
  @check_input_and_output
874
922
  @dims_to_spatial_axes
875
- def rotated_crop(self, origin, dst1, dst2, dims=2) -> Self:
923
+ def rotated_crop(self, origin, dst1, dst2, *, crop_labels: bool = True, dims=2) -> Self:
876
924
  """
877
- Crop the image at four courners of an rotated rectangle. Currently only supports rotation within
878
- yx-plane. An rotated rectangle is specified with positions of a origin and two destinations `dst1`
879
- and `dst2`, i.e., vectors (dst1-origin) and (dst2-origin) represent a rotated rectangle. Let
880
- origin be the origin of a xy-plane, the rotation direction from dst1 to dst2 must be counter-
881
- clockwise, or the cropped image will be reversed.
925
+ Crop the image at four courners of an rotated rectangle. Currently only supports
926
+ rotation within yx-plane. An rotated rectangle is specified with positions of a
927
+ origin and two destinations `dst1` and `dst2`, i.e., vectors (dst1-origin) and
928
+ (dst2-origin) represent a rotated rectangle. Let origin be the origin of a
929
+ xy-plane, the rotation direction from dst1 to dst2 must be counter-clockwise,
930
+ or the cropped image will be reversed.
882
931
 
883
932
  Parameters
884
933
  ----------
@@ -907,7 +956,7 @@ class LabeledArray(MetaArray):
907
956
  )
908
957
  cropped_img = cropped_img.view(self.__class__)
909
958
  cropped_img.axes = self.axes
910
- if self.labels is not None:
959
+ if crop_labels and self.labels is not None:
911
960
  try:
912
961
  lbl = self.labels
913
962
  cropped_labels = np.empty(lbl.shape[:-2] + all_coords.shape[1:], dtype=lbl.dtype)
impy/arrays/lazy.py CHANGED
@@ -1041,19 +1041,6 @@ class LazyImgArray(AxesMixin):
1041
1041
 
1042
1042
  return out
1043
1043
 
1044
- @dims_to_spatial_axes
1045
- @check_input_and_output_lazy
1046
- def tiled_lowpass_filter(self, cutoff: float = 0.2, order: int = 2, overlap: int = 16, *,
1047
- dims: Dims = None, update: bool = False) -> LazyImgArray:
1048
- warnings.warn(
1049
- "`tiled_lowpass_filter` is deprecated. Please use "
1050
- f"`img.tiled({overlap=}).lowpass_filter({cutoff=}, {order=})` instead.",
1051
- DeprecationWarning,
1052
- )
1053
- return self.tiled(
1054
- chunks=self.chunksizesof(dims), overlap=overlap, dims=dims,
1055
- ).lowpass_filter(cutoff, order)
1056
-
1057
1044
  @_docs.copy_docs(ImgArray.proj)
1058
1045
  @same_dtype
1059
1046
  def proj(self, axis: str = None, method: str = "mean") -> LazyImgArray:
impy/arrays/tiled.py CHANGED
@@ -24,10 +24,10 @@ class TiledAccessor(Generic[_T]):
24
24
  class _PartialTiledImage(Generic[_T]):
25
25
  def __init__(self, img: _T):
26
26
  self._img = weakref.ref(img)
27
-
27
+
28
28
  def __call__(
29
- self,
30
- chunks: tuple[int, ...] | Literal["auto"] = "auto",
29
+ self,
30
+ chunks: tuple[int, ...] | Literal["auto"] = "auto",
31
31
  overlap: int | tuple[int, ...] = 32,
32
32
  boundary: Boundary | list[Boundary] = "reflect",
33
33
  dims: Dims = None,
@@ -78,13 +78,13 @@ class TiledImage(Generic[_T]):
78
78
  else:
79
79
  img_repr = repr(img)
80
80
  return f"TiledImage<chunks={self.chunks}, depth={self.depth}, boundary={self.boundary}> of \n{img_repr}"
81
-
81
+
82
82
  def _deref_image(self) -> _T:
83
83
  img = self._img()
84
84
  if img is None:
85
85
  raise RuntimeError("Image has been deleted")
86
86
  return img
87
-
87
+
88
88
  def _map_overlap(self, func: Callable[[np.ndarray], np.ndarray], *args, **kwargs) -> np.ndarray:
89
89
  from .imgarray import ImgArray
90
90
  from .lazy import LazyImgArray
@@ -92,14 +92,14 @@ class TiledImage(Generic[_T]):
92
92
  img = self._deref_image()
93
93
  if isinstance(img, ImgArray):
94
94
  from dask import array as da
95
-
95
+
96
96
  input = da.from_array(img.value, chunks=self._chunks)
97
-
97
+
98
98
  def _func(arr: np.ndarray, *args, **kwargs) -> np.ndarray:
99
99
  return func(arr, *args, **kwargs).astype(arr.dtype, copy=False)
100
100
  out: np.ndarray = xp.asnumpy(
101
101
  da.map_overlap(
102
- _func,
102
+ _func,
103
103
  input,
104
104
  *args,
105
105
  depth=self.depth,
@@ -116,7 +116,7 @@ class TiledImage(Generic[_T]):
116
116
  func,
117
117
  c_axes="",
118
118
  depth=self.depth,
119
- boundary=self.boundary,
119
+ boundary=self.boundary,
120
120
  dtype=img.dtype,
121
121
  args=args,
122
122
  kwargs=kwargs,
@@ -138,7 +138,33 @@ class TiledImage(Generic[_T]):
138
138
  Steepness of cutoff.
139
139
  """
140
140
  return self._map_overlap(_lowpass, cutoff=cutoff, order=order)
141
-
141
+
142
+ def highpass_filter(self, cutoff: float = 0.2, order: int = 2) -> _T:
143
+ """
144
+ Tile-wise butterworth highpass filter.
145
+
146
+ Parameters
147
+ ----------
148
+ cutoff : float or array-like, default is 0.2
149
+ Cutoff frequency.
150
+ order : float, default is 2
151
+ Steepness of cutoff.
152
+ """
153
+ return self._map_overlap(_highpass, cutoff=cutoff, order=order)
154
+
155
+ def bandpass_filter(self, cuton: float, cutoff: float, order: int = 2) -> _T:
156
+ """
157
+ Tile-wise butterworth bandpass filter.
158
+
159
+ Parameters
160
+ ----------
161
+ cuton, cutoff : float or array-like
162
+ Cuton and cutoff frequency.
163
+ order : float, default is 2
164
+ Steepness of cutoff.
165
+ """
166
+ return self._map_overlap(_bandpass, low_cutoff=cuton, high_cutoff=cutoff, order=order)
167
+
142
168
  def lucy(
143
169
  self,
144
170
  psf: np.ndarray | Callable[[tuple[int, ...]], np.ndarray],
@@ -147,7 +173,7 @@ class TiledImage(Generic[_T]):
147
173
  ) -> _T:
148
174
  """
149
175
  Deconvolution of N-dimensional image, using Richardson-Lucy's algorithm.
150
-
176
+
151
177
  Parameters
152
178
  ----------
153
179
  psf : ndarray or callable
@@ -156,30 +182,30 @@ class TiledImage(Generic[_T]):
156
182
  niter : int, default is 50.
157
183
  Number of iterations.
158
184
  eps : float, default is 1e-5
159
- During deconvolution, division by small values in the convolve image
160
- of estimation and PSF may cause divergence. Therefore, division by
185
+ During deconvolution, division by small values in the convolve image
186
+ of estimation and PSF may cause divergence. Therefore, division by
161
187
  values under `eps` is substituted to zero.
162
188
  """
163
189
  img = self._deref_image()
164
190
  scale = tuple(img.scale.values())
165
-
191
+
166
192
  def func(arr: np.ndarray):
167
193
  psf_ft, psf_ft_conj = _deconv.check_psf(arr.shape, scale, psf)
168
194
  return _deconv.richardson_lucy(arr, psf_ft, psf_ft_conj, niter, eps)
169
-
195
+
170
196
  return self._map_overlap(func)
171
-
197
+
172
198
  def gaussian_filter(self, sigma: float = 1.0, fourier: bool = False) -> _T:
173
199
  """
174
200
  Run Gaussian filter (Gaussian blur).
175
-
201
+
176
202
  Parameters
177
203
  ----------
178
204
  {sigma}{fourier}
179
205
  """
180
206
  filter_func = _filters.gaussian_filter_fourier if fourier else _filters.gaussian_filter
181
207
  return self._map_overlap(filter_func, sigma=sigma)
182
-
208
+
183
209
  def dog_filter(
184
210
  self,
185
211
  low_sigma: float = 1.0,
@@ -189,7 +215,7 @@ class TiledImage(Generic[_T]):
189
215
  """
190
216
  Run Difference of Gaussian filter. This function does not support `update`
191
217
  argument because intensity can be negative.
192
-
218
+
193
219
  Parameters
194
220
  ----------
195
221
  low_sigma : scalar or array of scalars, default is 1.
@@ -197,7 +223,7 @@ class TiledImage(Generic[_T]):
197
223
  high_sigma : scalar or array of scalars, default is x1.6 of low_sigma.
198
224
  higher standard deviation(s) of Gaussian.
199
225
  {fourier}
200
- """
226
+ """
201
227
  if high_sigma is None:
202
228
  high_sigma = low_sigma * 1.6
203
229
  filter_func = _filters.dog_filter_fourier if fourier else _filters.dog_filter
@@ -222,3 +248,24 @@ def _lowpass(arr, cutoff, order=2):
222
248
  ft = xp.asarray(weight) * xp.fft.rfftn(arr)
223
249
  ift = xp.fft.irfftn(ft, s=shape)
224
250
  return xp.asnumpy(ift)
251
+
252
+ def _highpass(arr, cutoff, order=2):
253
+ arr = xp.asarray(arr)
254
+ shape = arr.shape
255
+ _cutoff = check_nd(cutoff, len(shape))
256
+ weight = _get_ND_butterworth_filter(shape, _cutoff, order, True, True)
257
+ ft = xp.asarray(weight) * xp.fft.rfftn(arr)
258
+ ift = xp.fft.irfftn(ft, s=shape)
259
+ return xp.asnumpy(ift)
260
+
261
+ def _bandpass(arr, low_cutoff, high_cutoff, order=2):
262
+ arr = xp.asarray(arr)
263
+ shape = arr.shape
264
+ _low_cutoff = check_nd(low_cutoff, len(shape))
265
+ _high_cutoff = check_nd(high_cutoff, len(shape))
266
+ low_weight = _get_ND_butterworth_filter(shape, _low_cutoff, order, False, True)
267
+ high_weight = _get_ND_butterworth_filter(shape, _high_cutoff, order, True, True)
268
+ weight = low_weight * high_weight
269
+ ft = xp.asarray(weight) * xp.fft.rfftn(arr)
270
+ ift = xp.fft.irfftn(ft, s=shape)
271
+ return xp.asnumpy(ift)