prtools 1.3.1__tar.gz → 1.3.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 (31) hide show
  1. {prtools-1.3.1/prtools.egg-info → prtools-1.3.2}/PKG-INFO +1 -1
  2. {prtools-1.3.1 → prtools-1.3.2}/prtools/__init__.py +4 -2
  3. {prtools-1.3.1 → prtools-1.3.2}/prtools/array.py +5 -4
  4. prtools-1.3.1/prtools/_backend.py → prtools-1.3.2/prtools/backend/__init__.py +42 -10
  5. {prtools-1.3.1 → prtools-1.3.2}/prtools/backend/_base.py +1 -1
  6. prtools-1.3.2/prtools/backend/_jax.py +49 -0
  7. prtools-1.3.1/prtools/backend/numpy.py → prtools-1.3.2/prtools/backend/_numpy.py +3 -3
  8. {prtools-1.3.1 → prtools-1.3.2}/prtools/cost.py +1 -1
  9. {prtools-1.3.1 → prtools-1.3.2}/prtools/fourier.py +1 -4
  10. prtools-1.3.2/prtools/jax.py +114 -0
  11. {prtools-1.3.1 → prtools-1.3.2}/prtools/misc.py +2 -2
  12. {prtools-1.3.1 → prtools-1.3.2}/prtools/segmented.py +1 -1
  13. {prtools-1.3.1 → prtools-1.3.2}/prtools/shape.py +1 -1
  14. {prtools-1.3.1 → prtools-1.3.2}/prtools/stats.py +1 -1
  15. {prtools-1.3.1 → prtools-1.3.2}/prtools/zernike.py +1 -1
  16. {prtools-1.3.1 → prtools-1.3.2/prtools.egg-info}/PKG-INFO +1 -1
  17. {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/SOURCES.txt +5 -4
  18. prtools-1.3.2/tests/test_array.py +67 -0
  19. prtools-1.3.2/tests/test_fourier.py +95 -0
  20. prtools-1.3.1/prtools/backend/__init__.py +0 -19
  21. prtools-1.3.1/prtools/backend/jax.py +0 -130
  22. prtools-1.3.1/tests/test_array.py +0 -45
  23. {prtools-1.3.1 → prtools-1.3.2}/LICENSE +0 -0
  24. {prtools-1.3.1 → prtools-1.3.2}/NOTICE +0 -0
  25. {prtools-1.3.1 → prtools-1.3.2}/README.rst +0 -0
  26. {prtools-1.3.1 → prtools-1.3.2}/prtools/sparse.py +0 -0
  27. {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/dependency_links.txt +0 -0
  28. {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/requires.txt +0 -0
  29. {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/top_level.txt +0 -0
  30. {prtools-1.3.1 → prtools-1.3.2}/pyproject.toml +0 -0
  31. {prtools-1.3.1 → prtools-1.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prtools
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Utility functions for image-based phase retrieval
5
5
  Author: Andy Kee
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
- __version__ = '1.3.1'
1
+ __version__ = '1.3.2'
2
2
 
3
- from prtools._backend import __backend__, use
3
+ from prtools.backend import __backend__, use
4
4
 
5
5
  from prtools.array import (
6
6
  centroid,
@@ -19,6 +19,8 @@ from prtools.cost import sserror
19
19
 
20
20
  from prtools.fourier import dft2, idft2
21
21
 
22
+ import prtools.jax
23
+
22
24
  from prtools.misc import (
23
25
  calcpsf,
24
26
  translation_defocus,
@@ -1,8 +1,8 @@
1
1
  import warnings
2
2
 
3
3
  from prtools import __backend__
4
- from prtools._backend import numpy as np
5
- from prtools._backend import scipy
4
+ from prtools.backend import numpy as np
5
+ from prtools.backend import scipy
6
6
 
7
7
 
8
8
  def centroid(a, where=None, kind='absolute', indexing='ij'):
@@ -265,8 +265,9 @@ def boundary(x, threshold=0):
265
265
  rows = np.any(x, axis=1)
266
266
  cols = np.any(x, axis=0)
267
267
 
268
- rmin, rmax = np.where(rows)[0][[0, -1]]
269
- cmin, cmax = np.where(cols)[0][[0, -1]]
268
+ idx = np.array((0, -1)) # https://github.com/andykee/prtools/issues/6
269
+ rmin, rmax = np.where(rows)[0][idx]
270
+ cmin, cmax = np.where(cols)[0][idx]
270
271
 
271
272
  return rmin, rmax, cmin, cmax
272
273
 
@@ -1,12 +1,22 @@
1
+ import importlib
1
2
  import sys
2
3
  import types
3
4
 
4
- from prtools.backend import registry
5
- from prtools.backend._base import _BackendLibrary
5
+ from ._base import BackendLibrary
6
6
 
7
7
 
8
- class _BackendName:
9
- """Helper class for storing the backend name - needed since strings are
8
+ def backend_available(name):
9
+ """Return True if backend library is available"""
10
+ try:
11
+ importlib.import_module(name)
12
+ except ImportError:
13
+ return False
14
+ else:
15
+ return True
16
+
17
+
18
+ class BackendName:
19
+ """Helper class for changing the backend name - needed since strings are
10
20
  immutable.
11
21
  """
12
22
 
@@ -23,10 +33,32 @@ class _BackendName:
23
33
  return self.name == other
24
34
 
25
35
 
26
- class _Backend(types.ModuleType):
27
- __backend__ = _BackendName(None)
28
- numpy = _BackendLibrary(None)
29
- scipy = _BackendLibrary(None)
36
+ class BackendRegistry:
37
+
38
+ _BUILTIN_BACKEND = ['numpy', 'jax']
39
+
40
+ def _get_backend_module(self, backend):
41
+ return importlib.import_module(f'._{backend}', 'prtools.backend')
42
+
43
+ def load_numpy(self, backend):
44
+ backend = self._get_backend_module(backend)
45
+ return backend.Numpy()
46
+
47
+ def load_scipy(self, backend):
48
+ return self._get_backend_module(backend).Scipy()
49
+
50
+
51
+ registry = BackendRegistry()
52
+
53
+ class Backend(types.ModuleType):
54
+
55
+ numpy = BackendLibrary(None)
56
+ scipy = BackendLibrary(None)
57
+
58
+ __backend__ = BackendName(None)
59
+
60
+ JAX_AVAILABLE = backend_available('jax') & backend_available('optax')
61
+ NUMPY_AVAILABLE = backend_available('numpy')
30
62
 
31
63
  @classmethod
32
64
  def use(cls, backend):
@@ -67,7 +99,7 @@ class _Backend(types.ModuleType):
67
99
 
68
100
 
69
101
  # use NumPy by default
70
- _Backend.use('numpy')
102
+ Backend.use('numpy')
71
103
 
72
104
  # https://stackoverflow.com/a/72911884
73
- sys.modules[__name__].__class__ = _Backend
105
+ sys.modules[__name__].__class__ = Backend
@@ -1,5 +1,5 @@
1
1
 
2
- class _BackendLibrary:
2
+ class BackendLibrary:
3
3
  def __init__(self, module):
4
4
  self.module = module
5
5
 
@@ -0,0 +1,49 @@
1
+ import importlib
2
+
3
+ from ._base import BackendLibrary
4
+
5
+
6
+ class Numpy(BackendLibrary):
7
+ def __init__(self):
8
+ super().__init__(importlib.import_module('jax.numpy'))
9
+
10
+ def broadcast_to(self, array, shape):
11
+ # jax numpy.broadcast_to expects an array input
12
+ array = self.module.asarray(array)
13
+ return self.module.broadcast_to(array, shape)
14
+
15
+ def divide(self, a, b, out=None):
16
+ # jax.numpy.divide doesn't support the `out` parameter so we
17
+ # ignore it
18
+ return self.module.divide(a, b)
19
+
20
+ def dot(self, a, b, out=None):
21
+ # jax.numpy.dot doesn't support the `out` parameter so we ignore it
22
+ return self.module.dot(a, b)
23
+
24
+ def floor(self, x, *args, **kwargs):
25
+ # jax numpy.floor expects a scalar or array input. It also doesn't
26
+ # support the `out` parameter
27
+ kwargs.pop('out', None)
28
+ x = self.module.asarray(x)
29
+ return self.module.floor(x, *args, **kwargs)
30
+
31
+ def max(self, a, *args, **kwargs):
32
+ # jax numpy.max expects an array input
33
+ array = self.module.asarray(a)
34
+ return self.module.max(array, *args, **kwargs)
35
+
36
+ def multiply(self, a, b, out=None):
37
+ # jax.numpy.multiply doesn't support the `out` parameter so we
38
+ # ignore it
39
+ return self.module.multiply(a, b)
40
+
41
+ def sum(self, a, *args, **kwargs):
42
+ kwargs.pop('out', None)
43
+ a = self.module.asarray(a)
44
+ return self.module.sum(a, *args, **kwargs)
45
+
46
+
47
+ class Scipy(BackendLibrary):
48
+ def __init__(self):
49
+ super().__init__(importlib.import_module('jax.scipy'))
@@ -1,13 +1,13 @@
1
- from ._base import _BackendLibrary
1
+ from ._base import BackendLibrary
2
2
 
3
3
 
4
- class Numpy(_BackendLibrary):
4
+ class Numpy(BackendLibrary):
5
5
  def __init__(self):
6
6
  import numpy
7
7
  super().__init__(numpy)
8
8
 
9
9
 
10
- class Scipy(_BackendLibrary):
10
+ class Scipy(BackendLibrary):
11
11
  def __init__(self):
12
12
  import scipy
13
13
  super().__init__(scipy)
@@ -1,4 +1,4 @@
1
- from prtools._backend import numpy as np
1
+ from prtools.backend import numpy as np
2
2
 
3
3
 
4
4
  def sserror(data, est, mask=None, gain_bias_invariant=False, ghat=None):
@@ -1,7 +1,5 @@
1
- import functools
2
-
3
1
  from prtools import __backend__
4
- from prtools._backend import numpy as np
2
+ from prtools.backend import numpy as np
5
3
 
6
4
 
7
5
  def dft2(f, alpha, shape=None, shift=(0, 0), offset=(0, 0), unitary=True,
@@ -137,7 +135,6 @@ def _idft2_matrices(m, n, M, N, alphar, alphac, shiftr, shiftc, offsetr, offsetc
137
135
  return E1, E2
138
136
 
139
137
 
140
- @functools.lru_cache(maxsize=32)
141
138
  def _dft2_coords(m, n, M, N):
142
139
  # R and S are (r,c) coordinates in the (m x n) input plane f
143
140
  # V and U are (r,c) coordinates in the (M x N) output plane F
@@ -0,0 +1,114 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from prtools import __backend__
5
+ from prtools.backend import JAX_AVAILABLE
6
+
7
+ if JAX_AVAILABLE:
8
+ import jax
9
+ import optax
10
+ import optax.tree_utils as otu
11
+
12
+
13
+ def register_dataclass(cls):
14
+ if JAX_AVAILABLE:
15
+ data_fields = ['x', 'n', 'grad', 'value', 'state']
16
+ meta_fields = []
17
+ cls = jax.tree_util.register_dataclass(cls,
18
+ data_fields=data_fields,
19
+ meta_fields=meta_fields)
20
+ return cls
21
+
22
+
23
+ @register_dataclass
24
+ @dataclass
25
+ class JaxOptimizeResult:
26
+ """Represents the optimization result."""
27
+ x: Any #: The solution of the optimization
28
+ n: Any #: Number of iterations performed by the optimizer
29
+ value: Any #: Value of objective function at x
30
+ grad: Any #: Value of objective function gradient at x
31
+ state: Any #: Optimizer state
32
+
33
+
34
+ def lbfgs(fun, x0, gtol=None, maxiter=None, callback=None):
35
+ """Minimize a scalar function of one or more variables using the L-BFGS
36
+ algorithm
37
+
38
+ Parameters
39
+ ----------
40
+ fun : callable
41
+ The objective function to be minimied
42
+ x0 : jax.Array
43
+ Initial guess
44
+ gtol : float
45
+ Iteration stops when ``l2_norm(grad) <= gtol``
46
+ maxiter : int
47
+ Maximum number of iterations
48
+ callback : callable, optional
49
+ A callable called after each iteration with the signature
50
+
51
+ .. code:: python
52
+
53
+ callback(intermediate_result: JaxOptimizeResult)
54
+
55
+ where ``intermediate_result`` is a :class:`JaxOptimizeResult`.
56
+
57
+ Returns
58
+ -------
59
+ res: JaxOptimizeResult
60
+ The optimization result. See :class:`JaxOptimizeResult` for a
61
+ description of attributes.
62
+
63
+ """
64
+ if not JAX_AVAILABLE:
65
+ message = "jax and optax must be installed to use method `lbfgs`."
66
+ raise ModuleNotFoundError(message)
67
+
68
+ if __backend__ != 'jax':
69
+ raise RuntimeError('JAX backend must be selected')
70
+
71
+ if not any((gtol, maxiter)):
72
+ raise ValueError('At least one termination tolerance must be specified.')
73
+
74
+ opt = optax.lbfgs()
75
+ value_and_grad_fun = optax.value_and_grad_from_state(fun)
76
+
77
+ def step(carry):
78
+ params, state = carry
79
+ value, grad = value_and_grad_fun(params, state=state)
80
+ updates, state = opt.update(
81
+ grad, state, params, value=value, grad=grad, value_fn=fun)
82
+ if callback:
83
+ res = JaxOptimizeResult(
84
+ n=otu.tree_get(state, 'count')-1,
85
+ x=params,
86
+ grad=grad,
87
+ value=value,
88
+ state=state)
89
+ jax.debug.callback(callback, res)
90
+ params = optax.apply_updates(params, updates)
91
+ return params, state
92
+
93
+ def continuing_criterion(carry):
94
+ _, state = carry
95
+ iter_num = otu.tree_get(state, 'count')
96
+ grad = otu.tree_get(state, 'grad')
97
+ grad_norm = otu.tree_l2_norm(grad)
98
+ return (iter_num == 0) | ((iter_num < maxiter) & (grad_norm >= gtol))
99
+
100
+ init_carry = (x0, opt.init(x0))
101
+ final_params, final_state = jax.lax.while_loop(
102
+ continuing_criterion, step, init_carry)
103
+
104
+ res = JaxOptimizeResult(
105
+ n=otu.tree_get(final_state, 'count'),
106
+ x=final_params,
107
+ grad=otu.tree_get(final_state, 'grad'),
108
+ value=otu.tree_get(final_state, 'value'),
109
+ state=final_state)
110
+
111
+ if callback:
112
+ jax.debug.callback(callback, res)
113
+
114
+ return res
@@ -1,5 +1,5 @@
1
1
  import prtools
2
- from prtools._backend import numpy as np
2
+ from prtools.backend import numpy as np
3
3
 
4
4
 
5
5
  def calcpsf(amp, opd, wavelength, sampling, shape, oversample=2,
@@ -18,7 +18,7 @@ def calcpsf(amp, opd, wavelength, sampling, shape, oversample=2,
18
18
  Propagation sampling term defined as
19
19
 
20
20
  .. math::
21
- \mbox{sampling} = \\frac{dx \ du}{z}
21
+ \mbox{sampling} = \frac{dx \ du}{z}
22
22
 
23
23
  where *dx* is the pupil plane spatial sampling, *du* is the image
24
24
  plane spatial sampling, and *z* is the propagation distance. If a
@@ -1,7 +1,7 @@
1
1
  import collections
2
2
 
3
3
  import prtools
4
- from prtools._backend import numpy as np
4
+ from prtools.backend import numpy as np
5
5
 
6
6
 
7
7
  def hex_segments(rings, seg_radius, seg_gap, rotate=False, antialias=True,
@@ -1,5 +1,5 @@
1
1
  from prtools import __backend__
2
- from prtools._backend import numpy as np
2
+ from prtools.backend import numpy as np
3
3
 
4
4
 
5
5
  def mesh(shape, shift=(0, 0), angle=0, indexing='ij'):
@@ -1,5 +1,5 @@
1
1
  import prtools
2
- from prtools._backend import numpy as np
2
+ from prtools.backend import numpy as np
3
3
 
4
4
 
5
5
  def ee(a, energy=0.8, center=None):
@@ -1,7 +1,7 @@
1
1
  from math import factorial
2
2
 
3
3
  import prtools
4
- from prtools._backend import numpy as np
4
+ from prtools.backend import numpy as np
5
5
 
6
6
 
7
7
  def zernike(mask, index, normalize=True, rho=None, theta=None):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prtools
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Utility functions for image-based phase retrieval
5
5
  Author: Andy Kee
6
6
  License: MIT License
@@ -3,10 +3,10 @@ NOTICE
3
3
  README.rst
4
4
  pyproject.toml
5
5
  prtools/__init__.py
6
- prtools/_backend.py
7
6
  prtools/array.py
8
7
  prtools/cost.py
9
8
  prtools/fourier.py
9
+ prtools/jax.py
10
10
  prtools/misc.py
11
11
  prtools/segmented.py
12
12
  prtools/shape.py
@@ -20,6 +20,7 @@ prtools.egg-info/requires.txt
20
20
  prtools.egg-info/top_level.txt
21
21
  prtools/backend/__init__.py
22
22
  prtools/backend/_base.py
23
- prtools/backend/jax.py
24
- prtools/backend/numpy.py
25
- tests/test_array.py
23
+ prtools/backend/_jax.py
24
+ prtools/backend/_numpy.py
25
+ tests/test_array.py
26
+ tests/test_fourier.py
@@ -0,0 +1,67 @@
1
+ import warnings
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ import prtools
7
+ from .import BACKENDS
8
+
9
+
10
+ @pytest.mark.parametrize('backend', BACKENDS)
11
+ def test_centroid(backend):
12
+ prtools.use(backend)
13
+ x = np.zeros((5, 5))
14
+ x[2, 2] = 1
15
+ assert np.array_equal(prtools.centroid(x), [2, 2])
16
+
17
+
18
+ @pytest.mark.parametrize('backend', BACKENDS)
19
+ def test_centroid_where(backend):
20
+ prtools.use(backend)
21
+ x = np.zeros((5, 5))
22
+ x[2, 2] = 1
23
+ x[1, 1] = 1
24
+ mask = np.ones_like(x)
25
+ mask[1, 1] = 0
26
+ assert np.array_equal(prtools.centroid(x, where=mask), [2, 2])
27
+
28
+
29
+ @pytest.mark.parametrize('backend', BACKENDS)
30
+ def test_centroid_nan(backend):
31
+ prtools.use(backend)
32
+ x = np.zeros((5, 5))
33
+ x[2, 2] = 1
34
+ x[2, 3] = np.nan
35
+ assert np.array_equal(prtools.centroid(x), [2, 2])
36
+
37
+
38
+ x, _ = np.meshgrid(range(10), range(10))
39
+ x[2, 2] = 100
40
+
41
+
42
+ @pytest.mark.parametrize('backend', BACKENDS)
43
+ def test_medfix(backend):
44
+ prtools.use(backend)
45
+ m = np.zeros_like(x)
46
+ m[2, 2] = 1
47
+ y = prtools.medfix(x, mask=m, kernel=(3, 3))
48
+ assert y[2, 2] == 2
49
+
50
+
51
+ @pytest.mark.parametrize('backend', BACKENDS)
52
+ def test_medfix_bigmask(backend):
53
+ prtools.use(backend)
54
+ m = np.zeros_like(x)
55
+ m[2:6, 2:6] = 1
56
+ with warnings.catch_warnings():
57
+ warnings.simplefilter('error')
58
+ y = prtools.medfix(x, mask=m, kernel=(3, 3))
59
+ assert np.all(np.isnan(y[3:5, 3:5]))
60
+
61
+
62
+ @pytest.mark.parametrize('backend', BACKENDS)
63
+ def test_boundary(backend):
64
+ prtools.use(backend)
65
+ x = np.zeros((10, 10))
66
+ x[3:7, 2:8] = 1
67
+ assert np.array_equal(prtools.boundary(x), (3, 6, 2, 7))
@@ -0,0 +1,95 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ import prtools
5
+ from .import BACKENDS
6
+
7
+
8
+ @pytest.mark.parametrize('backend', BACKENDS)
9
+ def test_dft2_even(backend):
10
+ prtools.use(backend)
11
+ n = 10
12
+ f = np.random.rand(n, n) + 1j * np.random.rand(n, n)
13
+
14
+ F_dft = prtools.dft2(f, 1/n, unitary=False)
15
+ F_fft = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(f)))
16
+
17
+ assert np.allclose(F_dft, F_fft, atol=1e-5)
18
+
19
+
20
+ @pytest.mark.parametrize('backend', BACKENDS)
21
+ def test_idft2_even(backend):
22
+ prtools.use(backend)
23
+ n = 10
24
+ F = np.random.rand(n, n) + 1j * np.random.rand(n, n)
25
+
26
+ f_dft = prtools.idft2(F, 1/n, unitary=False)
27
+ f_fft = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(F)))
28
+
29
+ assert np.allclose(f_dft, f_fft, atol=1e-5)
30
+
31
+
32
+ @pytest.mark.parametrize('backend', BACKENDS)
33
+ def test_dft2_odd(backend):
34
+ prtools.use(backend)
35
+ n = 11
36
+ f = np.random.rand(n, n) + 1j * np.random.rand(n, n)
37
+
38
+ F_dft = prtools.dft2(f, 1/n, unitary=False)
39
+ F_fft = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(f)))
40
+
41
+ print(F_dft.dtype)
42
+ print(F_fft.dtype)
43
+ assert np.allclose(F_dft, F_fft, atol=1e-5)
44
+
45
+
46
+ @pytest.mark.parametrize('backend', BACKENDS)
47
+ def test_idft2_odd(backend):
48
+ prtools.use(backend)
49
+ n = 11
50
+ F = np.random.rand(n, n) + 1j * np.random.rand(n, n)
51
+
52
+ f_dft = prtools.idft2(F, 1/n, unitary=False)
53
+ f_fft = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(F)))
54
+
55
+ assert np.allclose(f_dft, f_fft, atol=1e-5)
56
+
57
+
58
+ @pytest.mark.parametrize('backend', BACKENDS)
59
+ def test_dft2_unitary(backend):
60
+ prtools.use(backend)
61
+ n = 10
62
+ f = np.random.rand(n, n) + 1j * np.random.rand(n, n)
63
+ f_power = np.sum(np.abs(f)**2)
64
+
65
+ F = prtools.dft2(f, 1/n, unitary=True)
66
+ F_power = np.sum(np.abs(F)**2)
67
+
68
+ assert np.allclose(f_power, F_power)
69
+
70
+
71
+ @pytest.mark.parametrize('backend', BACKENDS)
72
+ def test_dft2_shift(backend):
73
+ prtools.use(backend)
74
+ n = 100
75
+ shift = np.round(np.random.uniform(low=-25, high=25, size=2))
76
+ f = np.ones((n, n))
77
+
78
+ F = prtools.dft2(f, 1/n, shift=shift)
79
+
80
+ (xc, yc) = (np.floor(n/2), np.floor(n/2))
81
+ (x, y) = prtools.centroid(np.abs(F)**2)
82
+ observed_shift = (x-xc, y-yc)
83
+ assert np.array_equal(shift, observed_shift)
84
+
85
+
86
+ @pytest.mark.parametrize('backend', BACKENDS)
87
+ def test_dft2_rectangle(backend):
88
+ prtools.use(backend)
89
+ m, n = 10, 11
90
+ f = np.random.rand(m, n) + 1j * np.random.rand(m, n)
91
+
92
+ F_dft = prtools.dft2(f, [1/m, 1/n], unitary=False)
93
+ F_fft = np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(f)))
94
+
95
+ assert np.allclose(F_dft, F_fft, atol=1e-5)
@@ -1,19 +0,0 @@
1
- import importlib
2
-
3
-
4
- class BackendRegistry:
5
-
6
- _BUILTIN_BACKEND = ['numpy', 'jax']
7
-
8
- def _get_backend_module(self, backend):
9
- return importlib.import_module(f'.{backend}', 'prtools.backend')
10
-
11
- def load_numpy(self, backend):
12
- backend = self._get_backend_module(backend)
13
- return backend.Numpy()
14
-
15
- def load_scipy(self, backend):
16
- return self._get_backend_module(backend).Scipy()
17
-
18
-
19
- registry = BackendRegistry()
@@ -1,130 +0,0 @@
1
- import importlib
2
- from typing import NamedTuple
3
-
4
- from ._base import _BackendLibrary
5
- from prtools import __backend__
6
- from prtools._backend import numpy as np
7
-
8
-
9
- class Numpy(_BackendLibrary):
10
- def __init__(self):
11
- super().__init__(importlib.import_module('jax.numpy'))
12
-
13
- def broadcast_to(self, array, shape):
14
- # jax broadcast_to expects an array input
15
- array = self.module.asarray(array)
16
- return self.module.broadcast_to(array, shape)
17
-
18
- def dot(self, a, b, out=None):
19
- # jax.numpy.dot doesn't support the `out` parameter so we ignore it
20
- return self.module.dot(a, b)
21
-
22
- def max(self, a, *args, **kwargs):
23
- # jax max expects an array input
24
- array = self.module.asarray(a)
25
- return self.module.max(array, *args, **kwargs)
26
-
27
- def multiply(self, a, b, out=None):
28
- # jax.numpy.multiply doesn't support the `out` parameter so we
29
- # ignore it
30
- return self.module.multiply(a, b)
31
-
32
- def divide(self, a, b, out=None):
33
- # jax.numpy.divide doesn't support the `out` parameter so we
34
- # ignore it
35
- return self.module.divide(a, b)
36
-
37
-
38
- class Scipy(_BackendLibrary):
39
- def __init__(self):
40
- super().__init__(importlib.import_module('jax.scipy'))
41
-
42
-
43
- class OptimizeResult(dict):
44
- """Represents the optimization result.
45
-
46
- Attributes
47
- ----------
48
- x : ndarray
49
- The solution of the optimization.
50
-
51
- """
52
-
53
- def __init__(self):
54
- super().__init__()
55
-
56
- #def __getattr__(self, name):
57
- # try:
58
- # return self[name]
59
- # except KeyError as e:
60
- # raise AttributeError(name) from e
61
-
62
- #__setattr__ = dict.__setitem__
63
- #__delattr__ = dict.__delitem__
64
-
65
-
66
- def lbfgs(fun, x0, tol, maxiter, callback=None):
67
- """Minimize a scalar function of one or more variables using the L-BFGS
68
- algorithm
69
-
70
- Parameters
71
- ----------
72
- fun : callable
73
- The objective function to be minimied
74
- x0 : array_like
75
- Initial starting guess
76
- tol : float
77
- Termination tolerance
78
- maxiter : int
79
- Maximum number of iterations
80
- callback : callable, optional
81
- Not currently implemented
82
-
83
- Returns
84
- -------
85
- final_params :
86
-
87
- final_state :
88
-
89
- """
90
- if __backend__ != 'jax':
91
- raise RuntimeError('JAX backend must be selected')
92
-
93
- try:
94
- import jax
95
- import optax
96
- import optax.tree_utils as otu
97
- except ImportError as error:
98
- raise ImportError('lbfgs requires jax and optax') from error
99
-
100
- opt = optax.lbfgs()
101
- value_and_grad_fun = optax.value_and_grad_from_state(fun)
102
-
103
- def step(carry):
104
- params, state = carry
105
- value, grad = value_and_grad_fun(params, state=state)
106
- updates, state = opt.update(
107
- grad, state, params, value=value, grad=grad, value_fn=fun,
108
- )
109
- if callback:
110
- res = {}
111
- res['x'] = params
112
- res['grad'] = grad
113
- res['fun'] = value
114
- res['nit'] = otu.tree_get(state, 'count')
115
- jax.debug.callback(callback, res)
116
- params = optax.apply_updates(params, updates)
117
- return params, state
118
-
119
- def continuing_criterion(carry):
120
- _, state = carry
121
- iter_num = otu.tree_get(state, 'count')
122
- grad = otu.tree_get(state, 'grad')
123
- err = otu.tree_l2_norm(grad)
124
- return (iter_num == 0) | ((iter_num < maxiter) & (err >= tol))
125
-
126
- init_carry = (x0, opt.init(x0))
127
- final_params, final_state = jax.lax.while_loop(
128
- continuing_criterion, step, init_carry
129
- )
130
- return final_params, final_state
@@ -1,45 +0,0 @@
1
- import warnings
2
-
3
- import numpy as np
4
-
5
- import prtools
6
-
7
-
8
- def test_centroid():
9
- x = np.zeros((5,5))
10
- x[2,2] = 1
11
- assert(np.array_equal(prtools.centroid(x), [2,2]))
12
-
13
-
14
- def test_centroid_where():
15
- x = np.zeros((5,5))
16
- x[2,2] = 1
17
- x[1,1] = 1
18
- mask = np.ones_like(x)
19
- mask[1,1] = 0
20
- assert(np.array_equal(prtools.centroid(x, where=mask), [2,2]))
21
-
22
-
23
- def test_centroid_nan():
24
- x = np.zeros((5,5))
25
- x[2,2] = 1
26
- x[2,3] = np.nan
27
- assert(np.array_equal(prtools.centroid(x), [2,2]))
28
-
29
-
30
- x, _ = np.meshgrid(range(10), range(10))
31
- x[2,2] = 100
32
-
33
- def test_medfix():
34
- m = np.zeros_like(x)
35
- m[2,2] = 1
36
- y = prtools.medfix(x, mask=m, kernel=(3,3))
37
- assert(y[2,2] == 2)
38
-
39
- def test_medfix_bigmask():
40
- m = np.zeros_like(x)
41
- m[2:6, 2:6] = 1
42
- with warnings.catch_warnings():
43
- warnings.simplefilter('error')
44
- y = prtools.medfix(x, mask=m, kernel=(3,3))
45
- assert(np.all(np.isnan(y[3:5,3:5])))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes