prtools 2.0.1__tar.gz → 2.0.2__tar.gz
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.
- {prtools-2.0.1/prtools.egg-info → prtools-2.0.2}/PKG-INFO +1 -1
- {prtools-2.0.1 → prtools-2.0.2}/prtools/__init__.py +2 -1
- {prtools-2.0.1 → prtools-2.0.2}/prtools/array.py +76 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/convolve.py +26 -10
- {prtools-2.0.1 → prtools-2.0.2/prtools.egg-info}/PKG-INFO +1 -1
- {prtools-2.0.1 → prtools-2.0.2}/tests/test_array.py +42 -1
- {prtools-2.0.1 → prtools-2.0.2}/LICENSE +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/NOTICE +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/README.rst +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/_array_api.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/cost.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/fourier.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/jax.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/misc.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/morphology.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/segmented.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/shape.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/sparse.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/stats.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools/zernike.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/SOURCES.txt +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/dependency_links.txt +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/requires.txt +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/top_level.txt +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/pyproject.toml +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/setup.cfg +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/tests/test_convolve.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/tests/test_fourier.py +0 -0
- {prtools-2.0.1 → prtools-2.0.2}/tests/test_jax.py +0 -0
|
@@ -326,6 +326,82 @@ def boundary(x, threshold=0):
|
|
|
326
326
|
return rmin, rmax, cmin, cmax
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
def sample(x, oversample, offset=0, axes=(-2, -1), indexing='ij'):
|
|
330
|
+
"""Extract samples from an oversampled array.
|
|
331
|
+
|
|
332
|
+
The sampled axes are tiled into contiguous blocks of ``oversample``
|
|
333
|
+
samples, with one sample being taken from each block. No averaging or
|
|
334
|
+
filtering is performed.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
x : array_like
|
|
339
|
+
Array to sample
|
|
340
|
+
oversample : int or tuple of ints
|
|
341
|
+
Decimation factor. Providing two values defines asymmetric
|
|
342
|
+
oversampling with ordering interpreted according to ``indexing``.
|
|
343
|
+
offset : int or tuple of ints, optional
|
|
344
|
+
Position of the sample with each block, measured from
|
|
345
|
+
``oversample//2``. The sub-sampled position reachable by ``offset`` is
|
|
346
|
+
quantized to ``1/oversample``. Default is 0. If two values are
|
|
347
|
+
provided, their ordering is interpreted according to ``indexing``.
|
|
348
|
+
axes : (2,) sequence of ints, optional
|
|
349
|
+
Axes over which to sample. If not provided, the last two axes are
|
|
350
|
+
used.
|
|
351
|
+
indexing : {'ij', 'xy'}, optional
|
|
352
|
+
Matrix (‘ij’, default) or cartesian (‘xy’) indexing of ``x`` grid.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
x : ndarray
|
|
357
|
+
Each sampled axis has length ``x.shape[axis]//oversample``. If axis
|
|
358
|
+
length is not a multiple of ``oversample``, samples taken beyond the
|
|
359
|
+
last whole block are ignored.
|
|
360
|
+
|
|
361
|
+
Examples
|
|
362
|
+
--------
|
|
363
|
+
.. code-block:: python
|
|
364
|
+
|
|
365
|
+
>>> psf = prtools.calcpsf(amp, opd, wave, sampling, (32, 32),
|
|
366
|
+
... oversample=8)
|
|
367
|
+
>>> k = prtools.pixel_kernel(psf.shape, oversample=8)
|
|
368
|
+
>>> native = prtools.sample(prtools.fftconv(psf, k), 8)
|
|
369
|
+
>>> native.shape
|
|
370
|
+
(32, 32)
|
|
371
|
+
|
|
372
|
+
"""
|
|
373
|
+
xp = array_namespace(x)
|
|
374
|
+
x = xp.asarray(x)
|
|
375
|
+
|
|
376
|
+
if indexing not in ('ij', 'xy'):
|
|
377
|
+
raise ValueError("Valid values for indexing are 'xy' and 'ij'.")
|
|
378
|
+
|
|
379
|
+
oversample = tuple(int(v) for v in np.broadcast_to(oversample, (2,)))
|
|
380
|
+
offset = tuple(int(v) for v in np.broadcast_to(offset, (2,)))
|
|
381
|
+
axes = tuple(int(a) % x.ndim for a in axes)
|
|
382
|
+
|
|
383
|
+
if indexing == 'xy':
|
|
384
|
+
offset = (-offset[1], offset[0])
|
|
385
|
+
oversample = (oversample[1], oversample[0])
|
|
386
|
+
|
|
387
|
+
idx = [slice(None)] * x.ndim
|
|
388
|
+
|
|
389
|
+
for ax, m, o in zip(axes, oversample, offset):
|
|
390
|
+
|
|
391
|
+
lo, hi = -(m // 2), m - 1 - m // 2
|
|
392
|
+
if not lo <= o <= hi:
|
|
393
|
+
raise ValueError(f'offset {o} lies outside one pixel for oversample '
|
|
394
|
+
f'{m} on axis {ax}; require {lo} <= offset <= {hi}')
|
|
395
|
+
|
|
396
|
+
n = x.shape[ax] // m
|
|
397
|
+
start = m // 2 + o
|
|
398
|
+
stop = start + m * n
|
|
399
|
+
|
|
400
|
+
idx[ax] = slice(start, stop, m)
|
|
401
|
+
|
|
402
|
+
return x[tuple(idx)]
|
|
403
|
+
|
|
404
|
+
|
|
329
405
|
def rebin(x, factor):
|
|
330
406
|
"""Rebin an array by an integer factor.
|
|
331
407
|
|
|
@@ -7,7 +7,7 @@ def fftconv(array, kernel, axes=(-2, -1), normalize_kernel=True,
|
|
|
7
7
|
fft_array=True, fft_kernel=False, fftshift_kernel=False):
|
|
8
8
|
r"""Convolve an array with a kernel using the FFT.
|
|
9
9
|
|
|
10
|
-
The
|
|
10
|
+
The convolution is computed as
|
|
11
11
|
|
|
12
12
|
.. math::
|
|
13
13
|
|
|
@@ -18,12 +18,13 @@ def fftconv(array, kernel, axes=(-2, -1), normalize_kernel=True,
|
|
|
18
18
|
array : array_like
|
|
19
19
|
Array to be convolved with ``kernel``.
|
|
20
20
|
kernel : array_like
|
|
21
|
-
Convolution kernel. Should have the same shape as ``array``.
|
|
21
|
+
Convolution kernel. Should have the same shape as ``array``. May be
|
|
22
|
+
given in either the frequency or spatial domain, as indicated by
|
|
23
|
+
``fft_kernel``. The kernel is applied exactly as supplied. See Notes
|
|
24
|
+
for normalization details.
|
|
22
25
|
axes : sequence of ints, optional
|
|
23
26
|
Axes over which to apply the convolution. If not given, the last two
|
|
24
27
|
axes are used.
|
|
25
|
-
normalize_kernel : bool, optional
|
|
26
|
-
If True (default), kernel is normalized so that ``sum(kernel) == 1``.
|
|
27
28
|
fft_array : bool, optional
|
|
28
29
|
If True (default), the array is assumed to be provided in the spatial
|
|
29
30
|
domain and its FFT will be computed by this function. If False, the
|
|
@@ -46,6 +47,24 @@ def fftconv(array, kernel, axes=(-2, -1), normalize_kernel=True,
|
|
|
46
47
|
--------
|
|
47
48
|
:func:`~prtools.gauss_blur`
|
|
48
49
|
:func:`~prtools.pixelate`
|
|
50
|
+
|
|
51
|
+
Notes
|
|
52
|
+
-----
|
|
53
|
+
**Kernel normalization** This function does not normalize or scale the
|
|
54
|
+
supplied kernel in any way.
|
|
55
|
+
|
|
56
|
+
The sum of ``array`` is conserved when the kernel's transfer function is
|
|
57
|
+
unity at zero frequency. Depending on which domain the kernel is supplied
|
|
58
|
+
in, the kernel normaliztion approach is different:
|
|
59
|
+
|
|
60
|
+
* A frequency-domain kernel (``fft_kernel=False``) should be scaled such
|
|
61
|
+
that ``kernel[0,0] == 1``. Note that ``kernel[0,0]`` is the
|
|
62
|
+
zero-frequency term only for a kernel in standard (unshifted) FFT layout.
|
|
63
|
+
If the kernel origin lies at the center of the array
|
|
64
|
+
(``fftshift_kernel=True``), the DC term is at ``kernel[nr//2, nc//2]``.
|
|
65
|
+
|
|
66
|
+
* A spatial-domain kernal(``fft_kernel=True``) should have
|
|
67
|
+
``sum(kernel) == 1``.
|
|
49
68
|
"""
|
|
50
69
|
xp = array_namespace(array)
|
|
51
70
|
|
|
@@ -53,9 +72,6 @@ def fftconv(array, kernel, axes=(-2, -1), normalize_kernel=True,
|
|
|
53
72
|
k = xp.fft.fftshift(kernel) if fftshift_kernel else kernel
|
|
54
73
|
k = xp.fft.fft2(k) if fft_kernel else k
|
|
55
74
|
|
|
56
|
-
if normalize_kernel:
|
|
57
|
-
k = k / xp.sum(k)
|
|
58
|
-
|
|
59
75
|
return xp.fft.ifft2(a*k, axes=axes).real
|
|
60
76
|
|
|
61
77
|
|
|
@@ -312,10 +328,10 @@ def gauss_kernel(shape, sigma, oversample=1, pixelscale=1.0, fftshift=False,
|
|
|
312
328
|
return k
|
|
313
329
|
|
|
314
330
|
|
|
315
|
-
def pixel_kernel(shape, oversample=1, pixelscale=1.0, fftshift=False,
|
|
331
|
+
def pixel_kernel(shape, oversample=1, pixelscale=1.0, fftshift=False,
|
|
316
332
|
xp=None):
|
|
317
333
|
r"""2D pixel MTF filter kernel
|
|
318
|
-
|
|
334
|
+
|
|
319
335
|
This function returns a normalized 2D sinc function sized to represent
|
|
320
336
|
the transfer function of an idealized square pixel.
|
|
321
337
|
|
|
@@ -361,7 +377,7 @@ def pixel_kernel(shape, oversample=1, pixelscale=1.0, fftshift=False,
|
|
|
361
377
|
x1 = xp.fft.fftfreq(shape[0], d=pixelscale[0]) * oversample
|
|
362
378
|
x2 = xp.fft.fftfreq(shape[1], d=pixelscale[1]) * oversample
|
|
363
379
|
|
|
364
|
-
k =
|
|
380
|
+
k = sinc(x1, x2, indexing='ij', xp=xp)
|
|
365
381
|
|
|
366
382
|
if fftshift:
|
|
367
383
|
return xp.fft.fftshift(k)
|
|
@@ -109,4 +109,45 @@ def test_register(xp):
|
|
|
109
109
|
|
|
110
110
|
s = prtools.register(x2, x1, oversample=2)
|
|
111
111
|
|
|
112
|
-
assert
|
|
112
|
+
assert xp.allclose(xp.asarray(s), xp.asarray((-1, -1)))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize('xp', BACKENDS)
|
|
116
|
+
@pytest.mark.parametrize('shape, oversample, offset, result', [
|
|
117
|
+
((2, 2), 1, 0, [[0, 1], [2, 3]]),
|
|
118
|
+
((2, 2), 2, 0, [3]),
|
|
119
|
+
((2, 2), 2, (-1, 0), [1]),
|
|
120
|
+
((2, 2), 2, (0, -1), [2]),
|
|
121
|
+
((2, 2), 2, (-1, -1), [0]),
|
|
122
|
+
((3, 3), 1, 0, [[0, 1, 2], [3, 4, 5], [6, 7, 8]]),
|
|
123
|
+
((3, 3), 3, 0, [4]),
|
|
124
|
+
((3, 3), 3, (-1, 0), [1]),
|
|
125
|
+
((3, 3), 3, (1, 0), [7]),
|
|
126
|
+
((3, 3), 3, (0, -1), [3]),
|
|
127
|
+
((3, 3), 3, (0, 1), [5]),
|
|
128
|
+
((3, 3), 3, (-1, -1), [0]),
|
|
129
|
+
((3, 3), 3, (-1, 1), [2]),
|
|
130
|
+
((3, 3), 3, (1, -1), [6]),
|
|
131
|
+
((3, 3), 3, (1, 1), [8]),
|
|
132
|
+
((4, 4), 1, 0, [[0, 1, 2, 3], [4, 5, 6, 7],
|
|
133
|
+
[8, 9, 10, 11], [12, 13, 14, 15]]),
|
|
134
|
+
((4, 4), 2, 0, [[5, 7], [13, 15]]),
|
|
135
|
+
])
|
|
136
|
+
def test_sample(xp, shape, oversample, offset, result):
|
|
137
|
+
x = np.arange(np.prod(shape)).reshape(shape)
|
|
138
|
+
assert np.all(prtools.sample(x, oversample, offset) == result)
|
|
139
|
+
|
|
140
|
+
@pytest.mark.parametrize('xp', BACKENDS)
|
|
141
|
+
@pytest.mark.parametrize('shape, oversample, offset', [
|
|
142
|
+
((2, 2), 1, 1),
|
|
143
|
+
((2, 2), 1, -2),
|
|
144
|
+
((2, 2), 2, 1),
|
|
145
|
+
((3, 3), 3, -2),
|
|
146
|
+
((3, 3), 3, 2),
|
|
147
|
+
((4, 4), 2, 1),
|
|
148
|
+
((4, 4), 2, -2)
|
|
149
|
+
])
|
|
150
|
+
def test_sample_value_error(xp, shape, oversample, offset):
|
|
151
|
+
x = np.arange(np.prod(shape)).reshape(shape)
|
|
152
|
+
with pytest.raises(ValueError):
|
|
153
|
+
prtools.sample(x, oversample, offset)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|