impy-array 2.3.3__py3-none-any.whl → 2.4.1__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.3.3"
1
+ __version__ = "2.4.1"
2
2
  __author__ = "Hanjin Liu"
3
3
  __email__ = "liuhanjin-sc@g.ecc.u-tokyo.ac.jp"
4
4
 
5
5
  import logging
6
6
 
7
- from ._const import Const, SetConst, use
7
+ from ._const import Const, SetConst, use # noqa
8
8
 
9
- from .collections import DataList, DataDict
10
- from .core import *
11
- from .binder import bind
12
- from .viewer import gui
13
- from .correlation import *
14
- from .arrays import ImgArray, LazyImgArray, BigImgArray, Label # for typing
15
- from . import random, io, lazy
16
- from .axes import slicer
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
17
17
 
18
18
  # Inheritance
19
19
  # -----------
@@ -34,7 +34,7 @@ logging.getLogger("tifffile").setLevel(logging.ERROR)
34
34
  del logging
35
35
 
36
36
  # dtypes
37
- from numpy import (
37
+ from numpy import ( # noqa
38
38
  uint8, uint16, uint32, uint64,
39
39
  int8, int16, int32, int64,
40
40
  float16, float32, float64,
@@ -1,13 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
3
  from functools import partial
5
4
  import numpy as np
6
5
  from impy.array_api import xp
7
6
 
8
- if TYPE_CHECKING:
9
- from impy.arrays import ImgArray
10
-
11
7
  try:
12
8
  gradient = xp.gradient
13
9
  except AttributeError:
@@ -17,18 +13,18 @@ except AttributeError:
17
13
  out = numpy.gradient(a.get(), axis=axis)
18
14
  return xp.asarray(out)
19
15
 
20
- __all__ = ["wiener", "lucy", "lucy_tv", "check_psf"]
16
+ __all__ = ["wiener", "richardson_lucy", "richardson_lucy_tv", "check_psf"]
21
17
 
22
18
  def wiener(obs, psf_ft, psf_ft_conj, lmd):
23
19
  obs = xp.asarray(obs)
24
20
  fft = xp.fft.rfftn
25
21
  ifft = partial(xp.fft.irfftn, s=obs.shape)
26
-
22
+
27
23
  img_ft = fft(obs)
28
-
24
+
29
25
  estimated = xp.real(ifft(img_ft*psf_ft_conj / (psf_ft*psf_ft_conj + lmd)))
30
26
  return xp.fft.fftshift(estimated)
31
-
27
+
32
28
  def richardson_lucy(obs, psf_ft, psf_ft_conj, niter, eps):
33
29
  # Identical to the algorithm in Deconvolution.jl of Julia.
34
30
  obs = xp.asarray(obs)
@@ -36,12 +32,12 @@ def richardson_lucy(obs, psf_ft, psf_ft_conj, niter, eps):
36
32
  ifft = partial(xp.fft.irfftn, s=obs.shape)
37
33
  conv = factor = xp.empty(obs.shape, dtype=xp.float32) # placeholder
38
34
  estimated = xp.real(ifft(fft(obs) * psf_ft)) # initialization
39
-
35
+
40
36
  for _ in range(niter):
41
37
  conv[:] = ifft(fft(estimated) * psf_ft).real
42
38
  factor[:] = ifft(fft(_safe_div(obs, conv, eps=eps)) * psf_ft_conj).real
43
39
  estimated *= factor
44
-
40
+
45
41
  return xp.fft.fftshift(estimated)
46
42
 
47
43
  def richardson_lucy_tv(obs, psf_ft, psf_ft_conj, max_iter, lmd, tol, eps):
@@ -51,21 +47,21 @@ def richardson_lucy_tv(obs, psf_ft, psf_ft_conj, max_iter, lmd, tol, eps):
51
47
  est_old = ifft(fft(obs) * psf_ft).real
52
48
  est_new = xp.empty(obs.shape, dtype=xp.float32)
53
49
  conv = factor = norm = gg = xp.empty(obs.shape, dtype=np.float32) # placeholder
54
-
50
+
55
51
  for _ in range(max_iter):
56
52
  conv[:] = ifft(fft(est_old) * psf_ft).real
57
53
  factor[:] = ifft(fft(_safe_div(obs, conv, eps=eps)) * psf_ft_conj).real
58
54
  est_new[:] = est_old * factor
59
55
  grad = gradient(est_old)
60
56
  norm[:] = xp.sqrt(sum(g**2 for g in grad))
61
- gg[:] = sum(gradient(_safe_div(grad[i], norm, eps=1e-8), axis=i)
57
+ gg[:] = sum(gradient(_safe_div(grad[i], norm, eps=1e-8), axis=i)
62
58
  for i in range(obs.ndim))
63
59
  est_new /= (1 - lmd * gg)
64
60
  gain = xp.sum(xp.abs(est_new - est_old))/xp.sum(xp.abs(est_old))
65
61
  if gain < tol:
66
62
  break
67
63
  est_old[:] = est_new
68
-
64
+
69
65
  return xp.fft.fftshift(est_new)
70
66
 
71
67
 
@@ -81,7 +77,7 @@ def check_psf(img_shape: tuple[int, ...], img_scale: tuple[float, ...], psf):
81
77
  psf = psf(img_shape, img_scale)
82
78
  psf = xp.asarray(psf, dtype=np.float32)
83
79
  psf /= xp.sum(psf)
84
-
80
+
85
81
  if img_shape != psf.shape:
86
82
  raise ValueError(
87
83
  f"observation and PSF have different shape: {img_shape} and {psf.shape}"
@@ -6,7 +6,7 @@ from scipy import ndimage as scipy_ndi
6
6
 
7
7
 
8
8
  __all__ = ["binary_erosion",
9
- "erosion"
9
+ "erosion",
10
10
  "binary_dilation",
11
11
  "dilation",
12
12
  "binary_opening",
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
- from ...collections import DataDict
3
2
  import scipy
3
+ from impy.collections import DataDict
4
4
 
5
5
  __all__ = ["glcm_props_", "check_glcm"]
6
6
 
@@ -15,7 +15,7 @@ def asm_(glcm, ref, nei):
15
15
 
16
16
  def idm_(glcm, ref, nei):
17
17
  return np.sum(glcm/(1+(ref-nei)**2), axis=(0,1))
18
-
18
+
19
19
  def energy_(glcm, ref, nei):
20
20
  return np.sqrt(np.sum(glcm**2, axis=(0,1)))
21
21
 
@@ -29,11 +29,11 @@ def entropy_(glcm, ref, nei):
29
29
  def correlation_(glcm, ref, nei):
30
30
  diffy = ref - np.sum(glcm * ref, axis=(0,1))
31
31
  diffx = nei - np.sum(glcm * nei, axis=(0,1))
32
-
32
+
33
33
  stdy = np.sqrt(np.sum(glcm*diffy**2, axis=(0,1)))
34
34
  stdx = np.sqrt(np.sum(glcm*diffx**2, axis=(0,1)))
35
35
  cov = np.sum(glcm*diffx*diffy, axis=(0,1))
36
-
36
+
37
37
  out = np.empty(glcm.shape[2:], dtype=np.float32)
38
38
  mask_0 = np.logical_or(stdx<1e-15, stdy<1e-15)
39
39
  mask_1 = ~mask_0
@@ -73,7 +73,7 @@ propdict = {"contrast": contrast_,
73
73
  def glcm_props_(data, distances, angles, levels, radius, properties):
74
74
  outshape = (len(distances), len(angles)) + \
75
75
  (data.shape[0]-2*radius, data.shape[1]-2*radius)
76
-
76
+
77
77
  propout = DataDict()
78
78
  for prop in properties:
79
79
  if isinstance(prop, str):
@@ -82,13 +82,13 @@ def glcm_props_(data, distances, angles, levels, radius, properties):
82
82
  propout[prop.__name__] = np.empty(outshape, dtype=np.float32)
83
83
  # placeholder
84
84
  glcm = np.empty((levels, levels, len(distances), len(angles)), dtype=np.float32)
85
-
85
+
86
86
  ref, nei = np.indices((levels, levels), dtype=np.float32)
87
87
  ref = ref[:, :, np.newaxis, np.newaxis]
88
88
  nei = nei[:, :, np.newaxis, np.newaxis]
89
-
89
+
90
90
  from ._process_numba import _calc_glcm
91
-
91
+
92
92
  for x in range(outshape[3]):
93
93
  for y in range(outshape[2]):
94
94
  # calc glcm
@@ -101,7 +101,7 @@ def glcm_props_(data, distances, angles, levels, radius, properties):
101
101
  else:
102
102
  # only callable
103
103
  propout[prop.__name__][:,:,y,x] = prop(glcm, ref, nei)
104
-
104
+
105
105
  return propout
106
106
 
107
107
  def check_glcm(self, bins, rescale_max):
@@ -112,22 +112,22 @@ def check_glcm(self, bins, rescale_max):
112
112
  bins = 256
113
113
  elif bins > 256:
114
114
  raise ValueError("`bins` must be smaller than 256.")
115
-
115
+
116
116
  if self.dtype == np.uint16:
117
117
  self = self.as_uint8()
118
118
  elif self.dtype == np.uint8:
119
119
  pass
120
120
  else:
121
121
  raise TypeError(f"Cannot calculate comatrix of {self.dtype} image.")
122
-
122
+
123
123
  imax = np.iinfo(self.dtype).max
124
-
124
+
125
125
  if rescale_max:
126
126
  scale = int(imax/self.max())
127
127
  self *= scale
128
-
128
+
129
129
  if (imax+1) % bins != 0 or bins > imax+1:
130
130
  raise ValueError(f"`bins` must be a divisor of {imax+1} (max value of {self.dtype}).")
131
131
  self = self // ((imax+1) // bins)
132
-
133
- return self, bins, rescale_max
132
+
133
+ return self, bins, rescale_max
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import numpy as np
3
- from ...array_api import xp
3
+ from impy.array_api import xp
4
4
 
5
5
  def adjust_bin(
6
6
  img: np.ndarray,
@@ -44,12 +44,12 @@ def make_rotated_axis(src, dst):
44
44
  def make_pad(pad_width, dims, all_axes, **kwargs):
45
45
  """More flexible padding than `np.pad`."""
46
46
  pad_width_ = []
47
-
47
+
48
48
  # for consistency with scipy-format
49
49
  if "cval" in kwargs.keys():
50
50
  kwargs["constant_values"] = kwargs["cval"]
51
51
  kwargs.pop("cval")
52
-
52
+
53
53
  if hasattr(pad_width, "__iter__") and len(pad_width) == len(dims):
54
54
  pad_iter = iter(pad_width)
55
55
  for a in all_axes:
@@ -57,7 +57,7 @@ def make_pad(pad_width, dims, all_axes, **kwargs):
57
57
  pad_width_.append(next(pad_iter))
58
58
  else:
59
59
  pad_width_.append((0, 0))
60
-
60
+
61
61
  elif isinstance(pad_width, int):
62
62
  for a in all_axes:
63
63
  if a in dims:
@@ -66,7 +66,7 @@ def make_pad(pad_width, dims, all_axes, **kwargs):
66
66
  pad_width_.append((0, 0))
67
67
  else:
68
68
  raise TypeError(f"pad_width must be iterable or int, but got {type(pad_width)}")
69
-
69
+
70
70
  return pad_width_
71
71
 
72
72
  def dft(img: xp.ndarray, exps: list[xp.ndarray] = None):
@@ -1,5 +1,5 @@
1
1
  import numpy as np
2
- from ...array_api import xp
2
+ from impy.array_api import xp
3
3
 
4
4
  def circle(radius, shape, dtype="bool"):
5
5
  x = xp.arange(-(shape[0] - 1) / 2, (shape[0] - 1) / 2 + 1)
@@ -10,7 +10,7 @@ def circle(radius, shape, dtype="bool"):
10
10
  def ball_like(radius, ndim:int):
11
11
  half_int = int(2*radius)/2
12
12
  L = xp.arange(-half_int, half_int + 1)
13
-
13
+
14
14
  if ndim == 1:
15
15
  return xp.ones(int(radius)*2+1, dtype=np.uint8)
16
16
  elif ndim == 2:
@@ -32,9 +32,9 @@ def ball_like_odd(radius, ndim):
32
32
  [1, 1, 1, 1],
33
33
  [0, 1, 1, 0]]
34
34
  This is not suitable for specify().
35
- """
35
+ """
36
36
  xc = int(radius)
37
37
  l = xc*2 + 1
38
38
  coords = xp.indices((l,)*ndim)
39
39
  s = xp.sum((a-xc)**2 for a in coords)
40
- return xp.array(s <= radius*radius, dtype=bool)
40
+ return xp.array(s <= radius*radius, dtype=bool)
@@ -10,9 +10,9 @@ if TYPE_CHECKING:
10
10
  from impy import ImgArray
11
11
 
12
12
  __all__ = [
13
- "compose_affine_matrix",
13
+ "compose_affine_matrix",
14
14
  "decompose_affine_matrix",
15
- "affinefit",
15
+ "affinefit",
16
16
  "check_matrix",
17
17
  "warp",
18
18
  ]
@@ -24,8 +24,8 @@ class AffineTransformationParameters(NamedTuple):
24
24
  shear: np.ndarray
25
25
 
26
26
  def warp(
27
- img: xp.ndarray,
28
- matrix: xp.ndarray,
27
+ img: np.ndarray,
28
+ matrix: np.ndarray,
29
29
  mode="constant",
30
30
  cval=0,
31
31
  output_shape=None,
@@ -35,13 +35,13 @@ def warp(
35
35
  ):
36
36
  img = xp.asarray(img, dtype=img.dtype)
37
37
  matrix = xp.asarray(matrix)
38
- out = xp.ndi.affine_transform(img, matrix, cval=cval, mode=mode, output_shape=output_shape,
38
+ out = xp.ndi.affine_transform(img, matrix, cval=cval, mode=mode, output_shape=output_shape,
39
39
  offset=offset, order=order, prefilter=prefilter)
40
40
  return out
41
41
 
42
- def shift(img: xp.ndarray, shift: xp.ndarray, cval=0, mode="constant", order=1, prefilter=False):
42
+ def shift(img: np.ndarray, shift: np.ndarray, cval=0, mode="constant", order=1, prefilter=False):
43
43
  img = xp.asarray(img, dtype=img.dtype)
44
- out = xp.ndi.shift(img, shift, cval=cval, mode=mode,
44
+ out = xp.ndi.shift(img, shift, cval=cval, mode=mode,
45
45
  order=order, prefilter=prefilter)
46
46
  return out
47
47
 
@@ -71,10 +71,10 @@ def compose_affine_matrix(
71
71
  translation = [translation] * ndim
72
72
  if rotation is not None:
73
73
  rotation = np.rad2deg(rotation)
74
-
74
+
75
75
  af = Affine(scale=scale, translate=translation, rotate=rotation, shear=shear)
76
76
  mx = af.affine_matrix
77
-
77
+
78
78
  return mx
79
79
 
80
80
 
@@ -83,12 +83,12 @@ def decompose_affine_matrix(matrix: np.ndarray):
83
83
  if ndim == 2:
84
84
  from skimage.transform import AffineTransform
85
85
  af = AffineTransform(matrix=matrix)
86
- out = AffineTransformationParameters(translation=af.translation, rotation=af.rotation,
86
+ out = AffineTransformationParameters(translation=af.translation, rotation=af.rotation,
87
87
  scale=af.scale, shear=af.shear)
88
88
  else:
89
89
  from napari.utils.transforms import Affine
90
90
  af = Affine(affine_matrix=matrix)
91
- out = AffineTransformationParameters(translation=af.translate, rotation=af.rotate,
91
+ out = AffineTransformationParameters(translation=af.translate, rotation=af.rotate,
92
92
  scale=af.scale, shear=af.shear)
93
93
  return out
94
94
 
@@ -104,8 +104,10 @@ def affinefit(img, imgref, bins=256, order=1):
104
104
  from scipy.stats import entropy
105
105
  from scipy.optimize import minimize
106
106
  from skimage.transform import warp
107
-
108
- as_3x3_matrix = lambda mtx: np.vstack((mtx.reshape(2,3), [0., 0., 1.]))
107
+
108
+ def as_3x3_matrix(mtx: np.ndarray):
109
+ return np.vstack((mtx.reshape(2,3), [0., 0., 1.]))
110
+
109
111
  def normalized_mutual_information(img, imgref):
110
112
  """
111
113
  Y(A,B) = (H(A)+H(B))/H(A,B)
@@ -117,15 +119,15 @@ def affinefit(img, imgref, bins=256, order=1):
117
119
  e2 = entropy(np.sum(hist, axis=1))
118
120
  e12 = entropy(np.ravel(hist)) # mutual entropy
119
121
  return (e1 + e2)/e12
120
-
122
+
121
123
  def cost_nmi(mtx, img, imgref):
122
124
  mtx = as_3x3_matrix(mtx)
123
125
  img_transformed = warp(img, mtx, order=order)
124
126
  return -normalized_mutual_information(img_transformed, imgref)
125
-
127
+
126
128
  mtx0 = np.array([[1., 0., 0.],
127
129
  [0., 1., 0.]]) # aberration makes little difference
128
-
130
+
129
131
  result = minimize(
130
132
  cost_nmi, mtx0, args=(np.asarray(img), np.asarray(imgref)), method="Powell"
131
133
  )
@@ -134,24 +136,24 @@ def affinefit(img, imgref, bins=256, order=1):
134
136
 
135
137
 
136
138
  def check_matrix(matrices: list[np.ndarray | float]):
137
- """Check Affine transformation matrix."""
139
+ """Check Affine transformation matrix."""
138
140
  mtx = []
139
141
  for m in matrices:
140
- if np.isscalar(m):
142
+ if np.isscalar(m):
141
143
  if m == 1:
142
144
  mtx.append(m)
143
145
  else:
144
146
  raise ValueError(f"Only `1` is ok, but got {m}")
145
-
147
+
146
148
  elif m.shape != (3, 3) or not np.allclose(m[2,:2], 0):
147
149
  raise ValueError(f"Wrong Affine transformation matrix:\n{m}")
148
-
150
+
149
151
  else:
150
152
  mtx.append(m)
151
153
  return mtx
152
154
 
153
155
  def polar2d(
154
- img: xp.ndarray,
156
+ img: np.ndarray,
155
157
  rmax: int,
156
158
  dtheta: float = 0.1, # radian
157
159
  order: int = 1,
@@ -170,14 +172,14 @@ def polar2d(
170
172
  return out
171
173
 
172
174
  def normalize_radon_input(
173
- self: ImgArray,
174
- dims: Axes,
175
- central_axis: AxisLike,
175
+ self: ImgArray,
176
+ dims: Axes,
177
+ central_axis: AxisLike,
176
178
  degrees: Sequence[float] | float,
177
- ):
179
+ ):
178
180
  ndim = len(dims)
179
181
  squeeze = not hasattr(degrees, "__iter__")
180
-
182
+
181
183
  if squeeze:
182
184
  degrees = [degrees]
183
185
  if ndim != self.ndim:
@@ -213,7 +215,7 @@ def normalize_radon_input(
213
215
  central_axis: np.ndarray
214
216
  if central_axis.shape != (ndim,):
215
217
  raise ValueError(f"Image is {ndim}D but central_axis is {central_axis.ndim}D.")
216
-
218
+
217
219
  # construct Affine transform matrices
218
220
  height = int(np.ceil(np.linalg.norm(self.shape)))
219
221
  output_shape = (height, self.shape[1], self.shape[2])
@@ -222,11 +224,11 @@ def normalize_radon_input(
222
224
  )
223
225
  else:
224
226
  raise ValueError("Only 2D or 3D input is supported.")
225
-
227
+
226
228
  return params, output_shape, squeeze
227
229
 
228
230
  def normalize_iradon_input(
229
- self: ImgArray,
231
+ self: ImgArray,
230
232
  central_axis: AxisLike,
231
233
  height_axis: AxisLike,
232
234
  degree_axis: AxisLike,
@@ -250,7 +252,7 @@ def normalize_iradon_input(
250
252
  output_shape = (height, self.shape[-1])
251
253
  return central_axis, degree_axis, output_shape, new_axes
252
254
 
253
- def radon_single(img: xp.ndarray, mtx: np.ndarray, order: int = 3, output_shape=None):
255
+ def radon_single(img: np.ndarray, mtx: np.ndarray, order: int = 3, output_shape=None):
254
256
  """Radon transform of 2D image."""
255
257
  img_rot = warp(img, mtx, order=order, output_shape=output_shape, prefilter=False)
256
258
  return xp.sum(img_rot, axis=0)
@@ -290,13 +292,13 @@ def _get_rotation_matrices_for_radon_3d(
290
292
  return np.einsum("ij,njk,kl->nil", tr_0, rotation, tr_1)
291
293
 
292
294
  # This function is mostly ported from `skimage.transform`.
293
- # The most important difference is that this implementation support arbitrary
295
+ # The most important difference is that this implementation support arbitrary
294
296
  # output shape.
295
297
  def iradon(
296
- img: xp.ndarray,
297
- degrees: xp.ndarray,
298
+ img: np.ndarray,
299
+ degrees: np.ndarray,
298
300
  output_shape: tuple[int, int],
299
- filter_func: xp.ndarray,
301
+ filter_func: np.ndarray,
300
302
  interp: Callable[[np.ndarray, np.ndarray], np.ndarray],
301
303
  ):
302
304
  angles_count = len(degrees)
@@ -322,7 +324,7 @@ def iradon(
322
324
 
323
325
  return reconstructed * np.pi / (2 * angles_count)
324
326
 
325
- # This function is almost ported from `skimage.transform`, but create a ramp
327
+ # This function is almost ported from `skimage.transform`, but create a ramp
326
328
  # filter in a simpler way.
327
329
  def get_fourier_filter(size: int, filter_name: str):
328
330
  # ramp filter