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.
Files changed (29) hide show
  1. {prtools-2.0.1/prtools.egg-info → prtools-2.0.2}/PKG-INFO +1 -1
  2. {prtools-2.0.1 → prtools-2.0.2}/prtools/__init__.py +2 -1
  3. {prtools-2.0.1 → prtools-2.0.2}/prtools/array.py +76 -0
  4. {prtools-2.0.1 → prtools-2.0.2}/prtools/convolve.py +26 -10
  5. {prtools-2.0.1 → prtools-2.0.2/prtools.egg-info}/PKG-INFO +1 -1
  6. {prtools-2.0.1 → prtools-2.0.2}/tests/test_array.py +42 -1
  7. {prtools-2.0.1 → prtools-2.0.2}/LICENSE +0 -0
  8. {prtools-2.0.1 → prtools-2.0.2}/NOTICE +0 -0
  9. {prtools-2.0.1 → prtools-2.0.2}/README.rst +0 -0
  10. {prtools-2.0.1 → prtools-2.0.2}/prtools/_array_api.py +0 -0
  11. {prtools-2.0.1 → prtools-2.0.2}/prtools/cost.py +0 -0
  12. {prtools-2.0.1 → prtools-2.0.2}/prtools/fourier.py +0 -0
  13. {prtools-2.0.1 → prtools-2.0.2}/prtools/jax.py +0 -0
  14. {prtools-2.0.1 → prtools-2.0.2}/prtools/misc.py +0 -0
  15. {prtools-2.0.1 → prtools-2.0.2}/prtools/morphology.py +0 -0
  16. {prtools-2.0.1 → prtools-2.0.2}/prtools/segmented.py +0 -0
  17. {prtools-2.0.1 → prtools-2.0.2}/prtools/shape.py +0 -0
  18. {prtools-2.0.1 → prtools-2.0.2}/prtools/sparse.py +0 -0
  19. {prtools-2.0.1 → prtools-2.0.2}/prtools/stats.py +0 -0
  20. {prtools-2.0.1 → prtools-2.0.2}/prtools/zernike.py +0 -0
  21. {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/SOURCES.txt +0 -0
  22. {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/dependency_links.txt +0 -0
  23. {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/requires.txt +0 -0
  24. {prtools-2.0.1 → prtools-2.0.2}/prtools.egg-info/top_level.txt +0 -0
  25. {prtools-2.0.1 → prtools-2.0.2}/pyproject.toml +0 -0
  26. {prtools-2.0.1 → prtools-2.0.2}/setup.cfg +0 -0
  27. {prtools-2.0.1 → prtools-2.0.2}/tests/test_convolve.py +0 -0
  28. {prtools-2.0.1 → prtools-2.0.2}/tests/test_fourier.py +0 -0
  29. {prtools-2.0.1 → prtools-2.0.2}/tests/test_jax.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prtools
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Utility functions for image-based phase retrieval
5
5
  Author: Andy Kee
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- __version__ = '2.0.1'
1
+ __version__ = '2.0.2'
2
2
 
3
3
 
4
4
  from prtools._array_api import use
@@ -8,6 +8,7 @@ from prtools.array import (
8
8
  pad,
9
9
  crop,
10
10
  boundary,
11
+ sample,
11
12
  rebin,
12
13
  rescale,
13
14
  normpow,
@@ -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 colvolution is computed as
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 = xp.abs(sinc(x1, x2, indexing='ij', xp=xp))
380
+ k = sinc(x1, x2, indexing='ij', xp=xp)
365
381
 
366
382
  if fftshift:
367
383
  return xp.fft.fftshift(k)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prtools
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Utility functions for image-based phase retrieval
5
5
  Author: Andy Kee
6
6
  License: MIT License
@@ -109,4 +109,45 @@ def test_register(xp):
109
109
 
110
110
  s = prtools.register(x2, x1, oversample=2)
111
111
 
112
- assert(xp.allclose(xp.asarray(s), xp.asarray((-1,-1))))
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