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 +11 -11
- impy/arrays/_utils/_deconv.py +10 -14
- impy/arrays/_utils/_filters.py +1 -1
- impy/arrays/_utils/_glcm.py +15 -15
- impy/arrays/_utils/_misc.py +5 -5
- impy/arrays/_utils/_structures.py +4 -4
- impy/arrays/_utils/_transform.py +37 -35
- impy/arrays/imgarray.py +785 -726
- impy/io.py +107 -16
- impy/lazy/__init__.py +2 -2
- impy/lazy/core.py +42 -34
- impy/random.py +26 -27
- impy/utils/gauss.py +27 -22
- {impy_array-2.3.3.dist-info → impy_array-2.4.1.dist-info}/METADATA +30 -12
- {impy_array-2.3.3.dist-info → impy_array-2.4.1.dist-info}/RECORD +18 -18
- {impy_array-2.3.3.dist-info → impy_array-2.4.1.dist-info}/WHEEL +0 -0
- {impy_array-2.3.3.dist-info → impy_array-2.4.1.dist-info}/entry_points.txt +0 -0
- {impy_array-2.3.3.dist-info → impy_array-2.4.1.dist-info}/licenses/LICENSE +0 -0
impy/__init__.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
__version__ = "2.
|
|
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 #
|
|
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,
|
impy/arrays/_utils/_deconv.py
CHANGED
|
@@ -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", "
|
|
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}"
|
impy/arrays/_utils/_filters.py
CHANGED
impy/arrays/_utils/_glcm.py
CHANGED
|
@@ -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
|
impy/arrays/_utils/_misc.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import numpy as np
|
|
3
|
-
from
|
|
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
|
|
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)
|
impy/arrays/_utils/_transform.py
CHANGED
|
@@ -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:
|
|
28
|
-
matrix:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
297
|
-
degrees:
|
|
298
|
+
img: np.ndarray,
|
|
299
|
+
degrees: np.ndarray,
|
|
298
300
|
output_shape: tuple[int, int],
|
|
299
|
-
filter_func:
|
|
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
|