Source code for pyxu.util.misc
1import collections.abc as cabc
2import importlib
3import inspect
4import types
5
6import pyxu.info.deps as pxd
7import pyxu.info.ptype as pxt
8
9__all__ = [
10 "copy_if_unsafe",
11 "import_module",
12 "parse_params",
13 "read_only",
14]
15
16
[docs]
17def peaks(x: pxt.NDArray, y: pxt.NDArray) -> pxt.NDArray:
18 r"""
19 Matlab 2D peaks function.
20
21 Peaks is a function of two variables, obtained by translating and scaling Gaussian distributions. (See `Matlab's
22 peaks function <https://www.mathworks.com/help/matlab/ref/peaks.html>`_.)
23
24 This function is useful for testing purposes.
25
26 Parameters
27 ----------
28 x: NDArray
29 X coordinates.
30 y: NDArray
31 Y coordinates.
32
33 Returns
34 -------
35 z: NDArray
36 Values of the 2D function ``peaks`` at the points specified by the entries of `x` and `y`.
37
38 Examples
39 --------
40
41 .. plot::
42
43 import numpy as np
44 import matplotlib.pyplot as plt
45 from pyxu.util.misc import peaks
46
47 x = np.linspace(-3, 3, 1000)
48 xx, yy = np.meshgrid(x, x)
49 z = peaks(xx, yy)
50 plt.figure()
51 plt.imshow(z)
52 """
53 ndi = pxd.NDArrayInfo.from_obj(x)
54 xp = ndi.module()
55
56 a = 3 * ((1 - x) ** 2) * xp.exp(-(x**2) - (y + 1) ** 2)
57 b = -10 * ((x / 5) - x**3 - y**5) * xp.exp(-(x**2) - (y**2))
58 c = -xp.exp(-((x + 1) ** 2) - (y**2)) / 3
59 z = a + b + c
60 return z
61
62
[docs]
63def star_like_sample(
64 N: pxt.Integer,
65 w: pxt.Integer,
66 s: pxt.Real,
67 po: pxt.Integer,
68 x0: pxt.Real,
69 ndi: pxd.NDArrayInfo = pxd.NDArrayInfo.NUMPY,
70) -> pxt.NDArray:
71 r"""
72 Star-like test image.
73
74 Generates a (N, N) square image of a star-like object normalized between 0 and 1. Based on `GlobalBioIm's
75 StarLikeSample function
76 <https://github.com/Biomedical-Imaging-Group/GlobalBioIm/blob/master/Util/StarLikeSample.m>`_. This function is
77 useful for testing purposes as it contains high-frequency information.
78
79 Parameters
80 ----------
81 N: Integer
82 Size of the image (must be an even number).
83 w: Integer
84 The number of branches of the sample will be 4*w.
85 s: Real
86 Slope of the sigmoid function :math:`\frac1{1+\exp[s (x-x_{0})]}` attenuating the boundaries.
87 po: Integer
88 Power-raising factor for the final image (to have smoother edges).
89 x0: Real
90 Radial shift of the sigmoid function :math:`\frac1{1+\exp[s (x-x_{0})]}`.
91 ndi: NDArrayInfo
92 Desired array module for the output.
93
94 Returns
95 -------
96 image: NDArray
97 (N, N) image of star-like sample.
98
99 Examples
100 --------
101 .. plot::
102
103 import numpy as np
104 import matplotlib.pyplot as plt
105 from pyxu.util.misc import star_like_sample
106
107 star = star_like_sample(N=256, w=8, s=20, po=3, x0=0.7)
108 plt.figure()
109 plt.imshow(star)
110 """
111 assert N % 2 == 0
112 xp = ndi.module()
113
114 grid = xp.linspace(-1, 1, N)
115 x, y = xp.meshgrid(grid, grid)
116 theta = xp.arctan2(y, x)
117 image = 1 + xp.cos(4 * w * theta)
118 image /= 1 + xp.exp(s * (xp.sqrt(x**2 + y**2) - x0))
119 image = xp.maximum(image, 0) / 2
120 image **= po
121 image /= xp.amax(image)
122 return image
123
124
[docs]
125def parse_params(func, *args, **kwargs) -> cabc.Mapping:
126 """
127 Get function parameterization.
128
129 Returns
130 -------
131 params: ~collections.abc.Mapping
132 (key, value) params as seen in body of `func` when called via `func(*args, **kwargs)`.
133 """
134 sig = inspect.Signature.from_callable(func)
135 f_args = sig.bind(*args, **kwargs)
136 f_args.apply_defaults()
137
138 params = dict(
139 zip(f_args.arguments.keys(), f_args.args), # positional arguments
140 **f_args.kwargs,
141 )
142 return params
143
144
[docs]
145def import_module(name: str, fail_on_error: bool = True) -> types.ModuleType:
146 """
147 Load a Python module dynamically.
148 """
149 try:
150 pkg = importlib.import_module(name)
151 except ModuleNotFoundError:
152 if fail_on_error:
153 raise
154 else:
155 pkg = None
156 return pkg
157
158
[docs]
159def copy_if_unsafe(x: pxt.NDArray) -> pxt.NDArray:
160 """
161 Copy array if it is unsafe to do in-place updates on it.
162
163 In-place updates are unsafe if:
164
165 * the array is read-only, OR
166 * the array is a view onto another array.
167
168 Parameters
169 ----------
170 x: NDArray
171
172 Returns
173 -------
174 y: NDArray
175 """
176 N = pxd.NDArrayInfo
177 ndi = N.from_obj(x)
178 if ndi == N.DASK:
179 # Dask operations span a graph -> always safe.
180 y = x
181 elif ndi == N.NUMPY:
182 read_only = not x.flags.writeable
183 reference = not x.flags.owndata
184 y = x.copy() if (read_only or reference) else x
185 elif ndi == N.CUPY:
186 read_only = False # https://github.com/cupy/cupy/issues/2616
187 reference = not x.flags.owndata
188 y = x.copy() if (read_only or reference) else x
189 else:
190 msg = f"copy_if_unsafe() not yet defined for {ndi}."
191 raise NotImplementedError(msg)
192 return y
193
194
[docs]
195def read_only(x: pxt.NDArray) -> pxt.NDArray:
196 """
197 Make an array read-only.
198
199 Parameters
200 ----------
201 x: NDArray
202
203 Returns
204 -------
205 y: NDArray
206 """
207 N = pxd.NDArrayInfo
208 ndi = N.from_obj(x)
209 if ndi == N.DASK:
210 # Dask operations are read-only since graph-backed.
211 y = x
212 elif ndi == N.NUMPY:
213 y = x.view()
214 y.flags.writeable = False
215 elif ndi == N.CUPY:
216 y = x.view()
217 # y.flags.writeable = False # https://github.com/cupy/cupy/issues/2616
218 else:
219 msg = f"read_only() not yet defined for {ndi}."
220 raise NotImplementedError(msg)
221 return y