impy-array 2.2.0__py3-none-any.whl → 2.2.2__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 +12 -14
- impy/array_api.py +4 -0
- impy/arrays/__init__.py +10 -0
- impy/arrays/_utils/_deconv.py +18 -13
- impy/arrays/_utils/_linalg.py +13 -11
- impy/arrays/_utils/_transform.py +7 -5
- impy/arrays/axesmixin.py +3 -0
- impy/arrays/bases/metaarray.py +2 -2
- impy/arrays/bases/overload.py +58 -0
- impy/arrays/imgarray.py +103 -50
- impy/arrays/labeledarray.py +37 -8
- impy/arrays/lazy.py +2 -2
- impy/arrays/phasearray.py +1 -1
- impy/axes/_axes.py +3 -0
- impy/axes/_axis.py +1 -1
- impy/core.py +46 -22
- impy/correlation.py +68 -5
- impy/io.py +196 -69
- {impy_array-2.2.0.dist-info → impy_array-2.2.2.dist-info}/METADATA +51 -24
- {impy_array-2.2.0.dist-info → impy_array-2.2.2.dist-info}/RECORD +25 -24
- {impy_array-2.2.0.dist-info → impy_array-2.2.2.dist-info}/WHEEL +1 -2
- rst/conf.py +64 -0
- setup.py +37 -0
- impy_array-2.2.0.dist-info/top_level.txt +0 -1
- {impy_array-2.2.0.dist-info → impy_array-2.2.2.dist-info}/entry_points.txt +0 -0
- {impy_array-2.2.0.dist-info → impy_array-2.2.2.dist-info/licenses}/LICENSE +0 -0
impy/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "2.2.
|
|
1
|
+
__version__ = "2.2.2"
|
|
2
2
|
__author__ = "Hanjin Liu"
|
|
3
3
|
__email__ = "liuhanjin-sc@g.ecc.u-tokyo.ac.jp"
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
|
|
7
7
|
from ._const import Const, SetConst, use
|
|
8
8
|
|
|
9
|
-
from .collections import
|
|
9
|
+
from .collections import DataList, DataDict
|
|
10
10
|
from .core import *
|
|
11
11
|
from .binder import bind
|
|
12
12
|
from .viewer import gui
|
|
@@ -15,19 +15,17 @@ from .arrays import ImgArray, LazyImgArray, BigImgArray, Label # for typing
|
|
|
15
15
|
from . import random, io
|
|
16
16
|
from .axes import slicer
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
# Inheritance
|
|
19
|
+
# -----------
|
|
20
|
+
#
|
|
21
|
+
# AxesMixin -----> LazyImgArray ------> BigImgArray
|
|
22
|
+
# |
|
|
23
|
+
# +-----> MetaArray --+--> LabeledArray --+--> ImgArray
|
|
24
|
+
# | | |
|
|
25
|
+
# np.ndarray +--> Label +--> PhaseArray
|
|
26
|
+
# |
|
|
27
|
+
# +--> PropArray
|
|
21
28
|
|
|
22
|
-
AxesMixin -----> LazyImgArray ------> BigImgArray
|
|
23
|
-
|
|
|
24
|
-
+-----> MetaArray --+--> LabeledArray --+--> ImgArray
|
|
25
|
-
| | |
|
|
26
|
-
np.ndarray +--> Label +--> PhaseArray
|
|
27
|
-
|
|
|
28
|
-
+--> PropArray
|
|
29
|
-
|
|
30
|
-
"""
|
|
31
29
|
|
|
32
30
|
logging.getLogger("skimage").setLevel(logging.ERROR)
|
|
33
31
|
logging.getLogger("tifffile").setLevel(logging.ERROR)
|
impy/array_api.py
CHANGED
|
@@ -72,11 +72,13 @@ class XP:
|
|
|
72
72
|
self.round = np.round
|
|
73
73
|
self.gradient = np.gradient
|
|
74
74
|
self.tensordot = np.tensordot
|
|
75
|
+
self.concatenate = np.concatenate
|
|
75
76
|
self.stack = np.stack
|
|
76
77
|
self.unravel_index = np.unravel_index
|
|
77
78
|
self.argmax = np.argmax
|
|
78
79
|
self.argmin = np.argmin
|
|
79
80
|
self.pad = np.pad
|
|
81
|
+
self.isnan = np.isnan
|
|
80
82
|
|
|
81
83
|
self.state = "numpy"
|
|
82
84
|
from ._const import Const
|
|
@@ -141,11 +143,13 @@ class XP:
|
|
|
141
143
|
except AttributeError:
|
|
142
144
|
self.gradient = _gradient
|
|
143
145
|
self.tensordot = cp.tensordot
|
|
146
|
+
self.concatenate = cp.concatenate
|
|
144
147
|
self.stack = cp.stack
|
|
145
148
|
self.unravel_index = cp.unravel_index
|
|
146
149
|
self.argmax = cp.argmax
|
|
147
150
|
self.argmin = cp.argmin
|
|
148
151
|
self.pad = cp.pad
|
|
152
|
+
self.isnan = cp.isnan
|
|
149
153
|
self.state = "cupy"
|
|
150
154
|
|
|
151
155
|
from ._const import Const
|
impy/arrays/__init__.py
CHANGED
impy/arrays/_utils/_deconv.py
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
1
4
|
from functools import partial
|
|
2
5
|
import numpy as np
|
|
3
|
-
from
|
|
6
|
+
from impy.array_api import xp
|
|
4
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from impy.arrays import ImgArray
|
|
10
|
+
|
|
5
11
|
try:
|
|
6
12
|
gradient = xp.gradient
|
|
7
13
|
except AttributeError:
|
|
@@ -11,11 +17,7 @@ except AttributeError:
|
|
|
11
17
|
out = numpy.gradient(a.get(), axis=axis)
|
|
12
18
|
return xp.asarray(out)
|
|
13
19
|
|
|
14
|
-
__all__ = ["wiener",
|
|
15
|
-
"lucy",
|
|
16
|
-
"lucy_tv",
|
|
17
|
-
"check_psf",
|
|
18
|
-
]
|
|
20
|
+
__all__ = ["wiener", "lucy", "lucy_tv", "check_psf"]
|
|
19
21
|
|
|
20
22
|
def wiener(obs, psf_ft, psf_ft_conj, lmd):
|
|
21
23
|
obs = xp.asarray(obs)
|
|
@@ -67,20 +69,23 @@ def richardson_lucy_tv(obs, psf_ft, psf_ft_conj, max_iter, lmd, tol, eps):
|
|
|
67
69
|
return xp.fft.fftshift(est_new)
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
def _safe_div(a, b, eps=1e-8):
|
|
72
|
+
def _safe_div(a: np.ndarray, b: np.ndarray, eps: float = 1e-8):
|
|
71
73
|
out = xp.zeros(a.shape, dtype=np.float32)
|
|
72
74
|
mask = b > eps
|
|
73
|
-
out[mask] = a[mask]/b[mask]
|
|
75
|
+
out[mask] = a[mask] / b[mask]
|
|
74
76
|
return out
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def check_psf(
|
|
79
|
+
def check_psf(img_shape: tuple[int, ...], img_scale: tuple[float, ...], psf):
|
|
80
|
+
if callable(psf):
|
|
81
|
+
psf = psf(img_shape, img_scale)
|
|
78
82
|
psf = xp.asarray(psf, dtype=np.float32)
|
|
79
83
|
psf /= xp.sum(psf)
|
|
80
84
|
|
|
81
|
-
if
|
|
82
|
-
raise ValueError(
|
|
83
|
-
|
|
85
|
+
if img_shape != psf.shape:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"observation and PSF have different shape: {img_shape} and {psf.shape}"
|
|
88
|
+
)
|
|
84
89
|
psf_ft = xp.fft.rfftn(psf)
|
|
85
90
|
psf_ft_conj = xp.conjugate(psf_ft)
|
|
86
|
-
return psf_ft, psf_ft_conj
|
|
91
|
+
return psf_ft, psf_ft_conj
|
impy/arrays/_utils/_linalg.py
CHANGED
|
@@ -3,11 +3,11 @@ import numpy as np
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
from skimage.feature.corner import _symmetric_image
|
|
5
5
|
from ._skimage import skfeat
|
|
6
|
-
from
|
|
6
|
+
from impy.array_api import xp
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from
|
|
10
|
-
from
|
|
9
|
+
from impy.arrays.imgarray import ImgArray
|
|
10
|
+
from impy.axes import Axes
|
|
11
11
|
|
|
12
12
|
def eigh(a: np.ndarray):
|
|
13
13
|
a = xp.asarray(a, dtype=a.dtype)
|
|
@@ -20,33 +20,35 @@ def eigvalsh(a: np.ndarray):
|
|
|
20
20
|
return val
|
|
21
21
|
|
|
22
22
|
def structure_tensor_eigval(img: np.ndarray, sigma: float, pxsize: float):
|
|
23
|
-
tensor_elements = skfeat.structure_tensor(
|
|
24
|
-
|
|
23
|
+
tensor_elements = skfeat.structure_tensor(
|
|
24
|
+
img, sigma, mode="reflect"
|
|
25
|
+
)
|
|
25
26
|
return _solve_hermitian_eigval(tensor_elements, pxsize)
|
|
26
27
|
|
|
27
28
|
def structure_tensor_eigh(img: np.ndarray, sigma: float, pxsize: float):
|
|
28
|
-
tensor_elements = skfeat.structure_tensor(
|
|
29
|
-
|
|
29
|
+
tensor_elements = skfeat.structure_tensor(
|
|
30
|
+
img, sigma, mode="reflect"
|
|
31
|
+
)
|
|
30
32
|
return _solve_hermitian_eigs(tensor_elements, pxsize)
|
|
31
33
|
|
|
32
34
|
def hessian_eigval(img: np.ndarray, sigma: float, pxsize: float):
|
|
33
35
|
hessian_elements = skfeat.hessian_matrix(
|
|
34
|
-
img, sigma=sigma,
|
|
36
|
+
img, sigma=sigma, mode="reflect", use_gaussian_derivatives=False,
|
|
35
37
|
)
|
|
36
38
|
return _solve_hermitian_eigval(hessian_elements, pxsize)
|
|
37
39
|
|
|
38
40
|
def hessian_eigh(img: np.ndarray, sigma: float, pxsize: float):
|
|
39
41
|
hessian_elements = skfeat.hessian_matrix(
|
|
40
|
-
img, sigma=sigma,
|
|
42
|
+
img, sigma=sigma, mode="reflect", use_gaussian_derivatives=False,
|
|
41
43
|
)
|
|
42
44
|
return _solve_hermitian_eigs(hessian_elements, pxsize)
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def _solve_hermitian_eigval(elems, pxsize):
|
|
47
|
+
def _solve_hermitian_eigval(elems, pxsize) -> np.ndarray:
|
|
46
48
|
# Correct for scale
|
|
47
49
|
pxsize = np.asarray(pxsize)
|
|
48
50
|
hessian = _symmetric_image(elems)
|
|
49
|
-
hessian *= (pxsize.reshape(-1,1) * pxsize.reshape(1
|
|
51
|
+
hessian *= (pxsize.reshape(-1, 1) * pxsize.reshape(1, -1))
|
|
50
52
|
eigval = eigvalsh(hessian)
|
|
51
53
|
return eigval
|
|
52
54
|
|
impy/arrays/_utils/_transform.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import Iterable, NamedTuple, Sequence, TYPE_CHECKING
|
|
4
|
+
import math
|
|
4
5
|
import numpy as np
|
|
5
6
|
|
|
6
7
|
from ._skimage import sktrans
|
|
@@ -149,7 +150,7 @@ def check_matrix(matrices: list[np.ndarray | float]):
|
|
|
149
150
|
def polar2d(
|
|
150
151
|
img: xp.ndarray,
|
|
151
152
|
rmax: int,
|
|
152
|
-
dtheta: float = 0.1,
|
|
153
|
+
dtheta: float = 0.1, # radian
|
|
153
154
|
order: int = 1,
|
|
154
155
|
mode: str = "constant",
|
|
155
156
|
cval: float = 0,
|
|
@@ -307,8 +308,8 @@ def iradon(
|
|
|
307
308
|
# Reconstruct image by interpolation
|
|
308
309
|
reconstructed = np.zeros(output_shape, dtype=dtype)
|
|
309
310
|
xpr, ypr = np.indices(output_shape)
|
|
310
|
-
xpr
|
|
311
|
-
ypr
|
|
311
|
+
xpr = xpr - output_shape[0] // 2
|
|
312
|
+
ypr = ypr - output_shape[1] // 2
|
|
312
313
|
|
|
313
314
|
from scipy.interpolate import interp1d
|
|
314
315
|
x = np.arange(img_shape) - img_shape // 2 # NOTE: use CPU!
|
|
@@ -322,12 +323,13 @@ def iradon(
|
|
|
322
323
|
# This function is almost ported from `skimage.transform`.
|
|
323
324
|
def get_fourier_filter(size: int, filter_name: str):
|
|
324
325
|
n = xp.concatenate(
|
|
325
|
-
[xp.arange(1, size
|
|
326
|
-
xp.arange(size
|
|
326
|
+
[xp.arange(1, size // 2 + 1, 2, dtype=np.float32),
|
|
327
|
+
xp.arange(size // 2 - 1, 0, -2, dtype=np.float32)]
|
|
327
328
|
)
|
|
328
329
|
f = xp.zeros(size)
|
|
329
330
|
f[0] = 0.25
|
|
330
331
|
f[1::2] = -1 / (np.pi * n[:len(f[1::2])]) ** 2
|
|
332
|
+
|
|
331
333
|
fourier_filter = 2 * xp.real(xp.fft.fft(f)) # ramp filter
|
|
332
334
|
if filter_name == "ramp":
|
|
333
335
|
pass
|
impy/arrays/axesmixin.py
CHANGED
|
@@ -200,6 +200,9 @@ class AxesMixin:
|
|
|
200
200
|
{a: s for a, s in other.scale.items() if a in self.axes} # type: ignore
|
|
201
201
|
)
|
|
202
202
|
|
|
203
|
+
elif isinstance(other, (list, tuple)):
|
|
204
|
+
for a, s in zip(self.axes, other):
|
|
205
|
+
a.scale = s
|
|
203
206
|
else:
|
|
204
207
|
raise TypeError(
|
|
205
208
|
f"'other' must be str or axes supported object, but got {type(other)}"
|
impy/arrays/bases/metaarray.py
CHANGED
|
@@ -27,7 +27,7 @@ SupportSlicing = Union[
|
|
|
27
27
|
Mapping[str, SupportOneSlicing],
|
|
28
28
|
]
|
|
29
29
|
|
|
30
|
-
class MetaArray(AxesMixin, np.ndarray):
|
|
30
|
+
class MetaArray(AxesMixin, np.ndarray[Any, np.dtype[np.number]]):
|
|
31
31
|
additional_props = ["_source", "_metadata", "_name"]
|
|
32
32
|
NP_DISPATCH = {}
|
|
33
33
|
_name: str
|
|
@@ -747,7 +747,7 @@ class MetaArray(AxesMixin, np.ndarray):
|
|
|
747
747
|
except ImageAxesError:
|
|
748
748
|
self.axes = None
|
|
749
749
|
|
|
750
|
-
return
|
|
750
|
+
return self
|
|
751
751
|
|
|
752
752
|
def _process_output(self, func, args, kwargs):
|
|
753
753
|
# find the largest MetaArray. Largest because of broadcasting.
|
impy/arrays/bases/overload.py
CHANGED
|
@@ -181,6 +181,29 @@ def _(img: MetaArray, axis1: int | AxisLike, axis2: int | AxisLike):
|
|
|
181
181
|
out._set_info(img, new_axes=axes_list)
|
|
182
182
|
return out
|
|
183
183
|
|
|
184
|
+
# argmax does not support axis as tuple
|
|
185
|
+
@MetaArray.implements(np.argmax)
|
|
186
|
+
def _(img: MetaArray, axis: int | AxisLike | None = None):
|
|
187
|
+
if isinstance(axis, (str, Axis)):
|
|
188
|
+
axis = img.axisof(axis)
|
|
189
|
+
new_axes = img.axes.drop(axis)
|
|
190
|
+
out = np.argmax(img.value, axis=axis)
|
|
191
|
+
if isinstance(out, np.ndarray):
|
|
192
|
+
out = out.view(img.__class__)
|
|
193
|
+
out._set_info(img, new_axes=new_axes)
|
|
194
|
+
return out
|
|
195
|
+
|
|
196
|
+
@MetaArray.implements(np.argmin)
|
|
197
|
+
def _(img: MetaArray, axis: int | AxisLike | None = None):
|
|
198
|
+
if isinstance(axis, (str, Axis)):
|
|
199
|
+
axis = img.axisof(axis)
|
|
200
|
+
new_axes = img.axes.drop(axis)
|
|
201
|
+
out = np.argmin(img.value, axis=axis)
|
|
202
|
+
if isinstance(out, np.ndarray):
|
|
203
|
+
out = out.view(img.__class__)
|
|
204
|
+
out._set_info(img, new_axes=new_axes)
|
|
205
|
+
return out
|
|
206
|
+
|
|
184
207
|
@MetaArray.implements(np.cross)
|
|
185
208
|
def _(
|
|
186
209
|
img: MetaArray,
|
|
@@ -206,6 +229,41 @@ def _(
|
|
|
206
229
|
out._set_info(img, new_axes=img.axes)
|
|
207
230
|
return out
|
|
208
231
|
|
|
232
|
+
@MetaArray.implements(np.diff)
|
|
233
|
+
def _(
|
|
234
|
+
img: MetaArray,
|
|
235
|
+
n: int = 1,
|
|
236
|
+
axis: int | AxisLike = -1,
|
|
237
|
+
prepend=None,
|
|
238
|
+
append=None,
|
|
239
|
+
):
|
|
240
|
+
if isinstance(axis, (str, Axis)):
|
|
241
|
+
axis = img.axisof(axis)
|
|
242
|
+
kwargs = dict(n=n, axis=axis)
|
|
243
|
+
if prepend is not None:
|
|
244
|
+
kwargs["prepend"] = prepend
|
|
245
|
+
if append is not None:
|
|
246
|
+
kwargs["append"] = append
|
|
247
|
+
out = np.diff(img.value, **kwargs)
|
|
248
|
+
out = out.view(img.__class__)
|
|
249
|
+
out._set_info(img, new_axes=img.axes)
|
|
250
|
+
return out
|
|
251
|
+
|
|
252
|
+
@MetaArray.implements(np.gradient)
|
|
253
|
+
def _(img: MetaArray, *varargs, axis=None, edge_order=1):
|
|
254
|
+
if isinstance(axis, (str, Axis)):
|
|
255
|
+
axis = img.axisof(axis)
|
|
256
|
+
elif isinstance(axis, Axes):
|
|
257
|
+
axis = tuple(img.axisof(ax) for ax in axis)
|
|
258
|
+
out = np.gradient(img.value, *varargs, axis=axis, edge_order=edge_order)
|
|
259
|
+
if isinstance(out, np.ndarray):
|
|
260
|
+
out = out.view(img.__class__)._set_info(img, new_axes=img.axes)
|
|
261
|
+
else:
|
|
262
|
+
out = [arr.view(img.__class__)._set_info(img, new_axes=img.axes) for arr in out]
|
|
263
|
+
out = DataList(out)
|
|
264
|
+
return out
|
|
265
|
+
|
|
266
|
+
|
|
209
267
|
# This function is ported from numpy.core.numeric.normalize_axis_tuple
|
|
210
268
|
def np_normalize_axis_tuple(axis, ndim, argname=None, allow_duplicate=False):
|
|
211
269
|
# Optimization to speed-up the most common cases.
|
impy/arrays/imgarray.py
CHANGED
|
@@ -13,25 +13,24 @@ from .specials import PropArray
|
|
|
13
13
|
from ._utils._skimage import skexp, skfeat, skfil, skimage, skmes, skres, skseg, sktrans
|
|
14
14
|
from ._utils import _filters, _linalg, _deconv, _misc, _glcm, _docs, _transform, _structures, _corr
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
16
|
+
from impy.utils.axesop import add_axes, switch_slice, complement_axes, find_first_appeared
|
|
17
|
+
from impy.utils.deco import check_input_and_output, dims_to_spatial_axes, same_dtype
|
|
18
|
+
from impy.utils.gauss import GaussianBackground, GaussianParticle
|
|
19
|
+
from impy.utils.misc import check_nd, largest_zeros
|
|
20
|
+
from impy.utils.slicer import solve_slicer
|
|
21
|
+
|
|
22
|
+
from impy.collections import DataDict
|
|
23
|
+
from impy.axes import AxisLike, slicer, Axes, Axis
|
|
24
|
+
from impy._types import nDInt, nDFloat, Dims, Coords, AxesTargetedSlicer, PaddingMode
|
|
25
|
+
from impy._const import Const
|
|
26
|
+
from impy.array_api import xp, cupy_dispatcher
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
from ..frame import MarkerFrame
|
|
30
30
|
from typing import Literal, Union
|
|
31
|
-
ThreasholdMethod =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Literal["triangle"], Literal["yen"]
|
|
31
|
+
ThreasholdMethod = Literal[
|
|
32
|
+
"isodata", "li", "local", "mean", "min", "minimum", "niblack", "otsu", "sauvola",
|
|
33
|
+
"triangle", "yen"
|
|
35
34
|
]
|
|
36
35
|
FftShape = Literal["same", "square"]
|
|
37
36
|
|
|
@@ -220,7 +219,7 @@ class ImgArray(LabeledArray):
|
|
|
220
219
|
@same_dtype(asfloat=True)
|
|
221
220
|
def rescale(
|
|
222
221
|
self,
|
|
223
|
-
scale:
|
|
222
|
+
scale: nDFloat = 1/16,
|
|
224
223
|
*,
|
|
225
224
|
order: int = None,
|
|
226
225
|
mode: PaddingMode = "reflect",
|
|
@@ -228,31 +227,45 @@ class ImgArray(LabeledArray):
|
|
|
228
227
|
dims: Dims = None,
|
|
229
228
|
) -> ImgArray:
|
|
230
229
|
"""
|
|
231
|
-
Rescale image.
|
|
230
|
+
Rescale image by applying the diagonal component of Affine matrix.
|
|
232
231
|
|
|
233
232
|
Parameters
|
|
234
233
|
----------
|
|
235
234
|
scale : float, optional
|
|
236
|
-
scale of the new image.
|
|
235
|
+
Relative scale of the new image. scale 1/2 means that the shape of
|
|
236
|
+
the output image will be (N/2, ...).
|
|
237
237
|
{order}{mode}{cval}{dims}
|
|
238
238
|
|
|
239
239
|
Returns
|
|
240
240
|
-------
|
|
241
241
|
ImgArray
|
|
242
242
|
Rescaled image.
|
|
243
|
-
"""
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
gb =
|
|
243
|
+
"""
|
|
244
|
+
scale_is_seq = hasattr(scale, "__iter__")
|
|
245
|
+
|
|
246
|
+
# Check if output is too large.
|
|
247
|
+
gb = -1
|
|
248
|
+
if not scale_is_seq and scale > 1:
|
|
249
|
+
gb = np.prod(self.shape) * (scale ** len(dims)) / 1e9
|
|
250
|
+
elif scale_is_seq and np.prod(list(scale)) > 1:
|
|
251
|
+
gb = np.prod(self.shape) * np.prod(list(scale)) / 1e9
|
|
248
252
|
if gb > Const["MAX_GB"]:
|
|
249
|
-
raise MemoryError(f"
|
|
250
|
-
|
|
253
|
+
raise MemoryError(f"Output image is too large: {gb} GB")
|
|
254
|
+
|
|
255
|
+
if scale_is_seq:
|
|
256
|
+
scale = list(scale)
|
|
257
|
+
if len(scale) != len(dims):
|
|
258
|
+
raise ValueError(
|
|
259
|
+
"scale must have the same length as the spatial dimensions."
|
|
260
|
+
)
|
|
261
|
+
it = iter(scale)
|
|
262
|
+
scale_ = [next(it) if a in dims else 1 for a in self.axes]
|
|
263
|
+
else:
|
|
264
|
+
scale_ = [scale if a in dims else 1 for a in self.axes]
|
|
251
265
|
out = sktrans.rescale(
|
|
252
266
|
self.value, scale_, order=order, mode=mode, cval=cval, anti_aliasing=False
|
|
253
267
|
)
|
|
254
|
-
out
|
|
255
|
-
out._set_info(self)
|
|
268
|
+
out = out.view(self.__class__)._set_info(self)
|
|
256
269
|
out.axes = self.axes.copy()
|
|
257
270
|
out.set_scale({a: self.scale[a]/scale for a, scale in zip(self.axes, scale_)})
|
|
258
271
|
return out
|
|
@@ -303,11 +316,11 @@ class ImgArray(LabeledArray):
|
|
|
303
316
|
)
|
|
304
317
|
|
|
305
318
|
reshaped_img = img_to_reshape.reshape(shape)
|
|
306
|
-
axes_to_reduce = tuple(i*2+1 for i in range(self.ndim))
|
|
319
|
+
axes_to_reduce = tuple(i * 2 + 1 for i in range(self.ndim))
|
|
307
320
|
out: np.ndarray = binfunc(reshaped_img, axis=axes_to_reduce)
|
|
308
321
|
out: ImgArray = out.view(self.__class__)
|
|
309
322
|
out._set_info(self)
|
|
310
|
-
out.axes =
|
|
323
|
+
out.axes = self.axes.copy() # _set_info does not pass copy so new axes must be defined here.
|
|
311
324
|
out.set_scale(
|
|
312
325
|
{a: self.scale[a]/scale for a, scale in zip(self.axes, scale_)}
|
|
313
326
|
)
|
|
@@ -318,7 +331,7 @@ class ImgArray(LabeledArray):
|
|
|
318
331
|
def radial_profile(
|
|
319
332
|
self,
|
|
320
333
|
nbin: int = 32,
|
|
321
|
-
center: Iterable[float] = None,
|
|
334
|
+
center: Iterable[float] | None = None,
|
|
322
335
|
r_max: float = None,
|
|
323
336
|
*,
|
|
324
337
|
method: str = "mean",
|
|
@@ -3111,8 +3124,7 @@ class ImgArray(LabeledArray):
|
|
|
3111
3124
|
if out.ndim == 3:
|
|
3112
3125
|
out = np.moveaxis(out, 0, 1)
|
|
3113
3126
|
out = out[::-1]
|
|
3114
|
-
out._set_info(self, new_axes)
|
|
3115
|
-
return out
|
|
3127
|
+
return out._set_info(self, new_axes)
|
|
3116
3128
|
|
|
3117
3129
|
@check_input_and_output
|
|
3118
3130
|
def threshold(
|
|
@@ -4195,7 +4207,7 @@ class ImgArray(LabeledArray):
|
|
|
4195
4207
|
@check_input_and_output
|
|
4196
4208
|
def wiener(
|
|
4197
4209
|
self,
|
|
4198
|
-
psf: np.ndarray,
|
|
4210
|
+
psf: np.ndarray | Callable[[tuple[int, ...]], np.ndarray],
|
|
4199
4211
|
lmd: float = 0.1,
|
|
4200
4212
|
*,
|
|
4201
4213
|
dims: Dims = None,
|
|
@@ -4213,8 +4225,9 @@ class ImgArray(LabeledArray):
|
|
|
4213
4225
|
|
|
4214
4226
|
Parameters
|
|
4215
4227
|
----------
|
|
4216
|
-
psf :
|
|
4217
|
-
Point spread function.
|
|
4228
|
+
psf : ndarray or callable
|
|
4229
|
+
Point spread function. If a function is given, `psf(shape)` will be
|
|
4230
|
+
called to generate the PSF.
|
|
4218
4231
|
lmd : float, default is 0.1
|
|
4219
4232
|
Constant value used in the deconvolution. See Formulation below.
|
|
4220
4233
|
{dims}{update}
|
|
@@ -4233,22 +4246,23 @@ class ImgArray(LabeledArray):
|
|
|
4233
4246
|
if lmd <= 0:
|
|
4234
4247
|
raise ValueError(f"lmd must be positive, but got: {lmd}")
|
|
4235
4248
|
|
|
4236
|
-
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4249
|
+
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4250
|
+
self.sizesof(dims), tuple(self.scale[axis] for axis in dims), psf,
|
|
4251
|
+
)
|
|
4237
4252
|
|
|
4238
4253
|
return self._apply_dask(
|
|
4239
4254
|
_deconv.wiener,
|
|
4240
4255
|
c_axes=complement_axes(dims, self.axes),
|
|
4241
4256
|
args=(psf_ft, psf_ft_conj, lmd)
|
|
4242
|
-
)
|
|
4243
|
-
|
|
4244
|
-
|
|
4257
|
+
)
|
|
4258
|
+
|
|
4245
4259
|
@_docs.write_docs
|
|
4246
4260
|
@dims_to_spatial_axes
|
|
4247
4261
|
@same_dtype(asfloat=True)
|
|
4248
4262
|
@check_input_and_output
|
|
4249
4263
|
def lucy(
|
|
4250
4264
|
self,
|
|
4251
|
-
psf: np.ndarray,
|
|
4265
|
+
psf: np.ndarray | Callable[[tuple[int, ...]], np.ndarray],
|
|
4252
4266
|
niter: int = 50,
|
|
4253
4267
|
eps: float = 1e-5,
|
|
4254
4268
|
*,
|
|
@@ -4260,14 +4274,15 @@ class ImgArray(LabeledArray):
|
|
|
4260
4274
|
|
|
4261
4275
|
Parameters
|
|
4262
4276
|
----------
|
|
4263
|
-
psf :
|
|
4264
|
-
Point spread function.
|
|
4277
|
+
psf : ndarray or callable
|
|
4278
|
+
Point spread function. If a function is given, `psf(shape)` will be
|
|
4279
|
+
called to generate the PSF.
|
|
4265
4280
|
niter : int, default is 50.
|
|
4266
4281
|
Number of iterations.
|
|
4267
4282
|
eps : float, default is 1e-5
|
|
4268
|
-
During deconvolution, division by small values in the convolve image
|
|
4269
|
-
PSF may cause divergence. Therefore, division by
|
|
4270
|
-
to zero.
|
|
4283
|
+
During deconvolution, division by small values in the convolve image
|
|
4284
|
+
of estimation and PSF may cause divergence. Therefore, division by
|
|
4285
|
+
values under `eps` is substituted to zero.
|
|
4271
4286
|
{dims}{update}
|
|
4272
4287
|
|
|
4273
4288
|
Returns
|
|
@@ -4281,7 +4296,9 @@ class ImgArray(LabeledArray):
|
|
|
4281
4296
|
wiener
|
|
4282
4297
|
"""
|
|
4283
4298
|
|
|
4284
|
-
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4299
|
+
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4300
|
+
self.sizesof(dims), tuple(self.scale[axis] for axis in dims), psf,
|
|
4301
|
+
)
|
|
4285
4302
|
|
|
4286
4303
|
return self._apply_dask(
|
|
4287
4304
|
_deconv.richardson_lucy,
|
|
@@ -4295,7 +4312,7 @@ class ImgArray(LabeledArray):
|
|
|
4295
4312
|
@check_input_and_output
|
|
4296
4313
|
def lucy_tv(
|
|
4297
4314
|
self,
|
|
4298
|
-
psf: np.ndarray,
|
|
4315
|
+
psf: np.ndarray | Callable[[tuple[int, ...]], np.ndarray],
|
|
4299
4316
|
max_iter: int = 50,
|
|
4300
4317
|
lmd: float = 1e-3,
|
|
4301
4318
|
tol: float = 1e-3,
|
|
@@ -4319,8 +4336,9 @@ class ImgArray(LabeledArray):
|
|
|
4319
4336
|
|
|
4320
4337
|
Parameters
|
|
4321
4338
|
----------
|
|
4322
|
-
psf :
|
|
4323
|
-
Point spread function.
|
|
4339
|
+
psf : ndarray or callable
|
|
4340
|
+
Point spread function. If a function is given, `psf(shape)` will be
|
|
4341
|
+
called to generate the PSF.
|
|
4324
4342
|
max_iter : int, default is 50.
|
|
4325
4343
|
Maximum number of iterations.
|
|
4326
4344
|
lmd : float, default is 1e-3
|
|
@@ -4361,7 +4379,9 @@ class ImgArray(LabeledArray):
|
|
|
4361
4379
|
"In Richadson-Lucy with total-variance-regularization, "
|
|
4362
4380
|
"parameter `lmd` must be positive."
|
|
4363
4381
|
)
|
|
4364
|
-
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4382
|
+
psf_ft, psf_ft_conj = _deconv.check_psf(
|
|
4383
|
+
self.sizesof(dims), tuple(self.scale[axis] for axis in dims), psf
|
|
4384
|
+
)
|
|
4365
4385
|
|
|
4366
4386
|
return self._apply_dask(
|
|
4367
4387
|
_deconv.richardson_lucy_tv,
|
|
@@ -4369,6 +4389,39 @@ class ImgArray(LabeledArray):
|
|
|
4369
4389
|
args=(psf_ft, psf_ft_conj, max_iter, lmd, tol, eps)
|
|
4370
4390
|
)
|
|
4371
4391
|
|
|
4392
|
+
@_docs.write_docs
|
|
4393
|
+
@dims_to_spatial_axes
|
|
4394
|
+
@same_dtype(asfloat=True)
|
|
4395
|
+
@check_input_and_output
|
|
4396
|
+
def tiled_lucy(
|
|
4397
|
+
self,
|
|
4398
|
+
psf: np.ndarray | Callable[[tuple[int, ...]], np.ndarray],
|
|
4399
|
+
niter: int = 50,
|
|
4400
|
+
eps: float = 1e-5,
|
|
4401
|
+
chunks = "auto",
|
|
4402
|
+
overlap: int = 32,
|
|
4403
|
+
*,
|
|
4404
|
+
dims: Dims = None,
|
|
4405
|
+
update: bool = False
|
|
4406
|
+
) -> ImgArray:
|
|
4407
|
+
from dask import array as da
|
|
4408
|
+
|
|
4409
|
+
input = da.from_array(self.value, chunks=chunks)
|
|
4410
|
+
depth = switch_slice(dims, self.axes, overlap, 0)
|
|
4411
|
+
scale = tuple(self.scale[axis] for axis in dims)
|
|
4412
|
+
|
|
4413
|
+
def func(arr: np.ndarray):
|
|
4414
|
+
psf_ft, psf_ft_conj = _deconv.check_psf(arr.shape, scale, psf)
|
|
4415
|
+
return _deconv.richardson_lucy(arr, psf_ft, psf_ft_conj, niter, eps)
|
|
4416
|
+
|
|
4417
|
+
out = da.map_overlap(
|
|
4418
|
+
func,
|
|
4419
|
+
input,
|
|
4420
|
+
depth=depth,
|
|
4421
|
+
boundary="reflect",
|
|
4422
|
+
dtype=self.dtype,
|
|
4423
|
+
).compute()
|
|
4424
|
+
return out
|
|
4372
4425
|
|
|
4373
4426
|
def _check_coordinates(coords, img: ImgArray, dims: Dims = None):
|
|
4374
4427
|
from impy.frame import MarkerFrame
|