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.
- {prtools-1.3.1/prtools.egg-info → prtools-1.3.2}/PKG-INFO +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools/__init__.py +4 -2
- {prtools-1.3.1 → prtools-1.3.2}/prtools/array.py +5 -4
- prtools-1.3.1/prtools/_backend.py → prtools-1.3.2/prtools/backend/__init__.py +42 -10
- {prtools-1.3.1 → prtools-1.3.2}/prtools/backend/_base.py +1 -1
- prtools-1.3.2/prtools/backend/_jax.py +49 -0
- prtools-1.3.1/prtools/backend/numpy.py → prtools-1.3.2/prtools/backend/_numpy.py +3 -3
- {prtools-1.3.1 → prtools-1.3.2}/prtools/cost.py +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools/fourier.py +1 -4
- prtools-1.3.2/prtools/jax.py +114 -0
- {prtools-1.3.1 → prtools-1.3.2}/prtools/misc.py +2 -2
- {prtools-1.3.1 → prtools-1.3.2}/prtools/segmented.py +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools/shape.py +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools/stats.py +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools/zernike.py +1 -1
- {prtools-1.3.1 → prtools-1.3.2/prtools.egg-info}/PKG-INFO +1 -1
- {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/SOURCES.txt +5 -4
- prtools-1.3.2/tests/test_array.py +67 -0
- prtools-1.3.2/tests/test_fourier.py +95 -0
- prtools-1.3.1/prtools/backend/__init__.py +0 -19
- prtools-1.3.1/prtools/backend/jax.py +0 -130
- prtools-1.3.1/tests/test_array.py +0 -45
- {prtools-1.3.1 → prtools-1.3.2}/LICENSE +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/NOTICE +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/README.rst +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/prtools/sparse.py +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/dependency_links.txt +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/requires.txt +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/prtools.egg-info/top_level.txt +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/pyproject.toml +0 -0
- {prtools-1.3.1 → prtools-1.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
__version__ = '1.3.
|
|
1
|
+
__version__ = '1.3.2'
|
|
2
2
|
|
|
3
|
-
from prtools.
|
|
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.
|
|
5
|
-
from prtools.
|
|
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
|
-
|
|
269
|
-
|
|
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
|
|
5
|
-
from prtools.backend._base import _BackendLibrary
|
|
5
|
+
from ._base import BackendLibrary
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
"""
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
102
|
+
Backend.use('numpy')
|
|
71
103
|
|
|
72
104
|
# https://stackoverflow.com/a/72911884
|
|
73
|
-
sys.modules[__name__].__class__ =
|
|
105
|
+
sys.modules[__name__].__class__ = Backend
|
|
@@ -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
|
|
1
|
+
from ._base import BackendLibrary
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Numpy(
|
|
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(
|
|
10
|
+
class Scipy(BackendLibrary):
|
|
11
11
|
def __init__(self):
|
|
12
12
|
import scipy
|
|
13
13
|
super().__init__(scipy)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
|
|
3
1
|
from prtools import __backend__
|
|
4
|
-
from prtools.
|
|
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.
|
|
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} =
|
|
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
|
|
@@ -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/
|
|
24
|
-
prtools/backend/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|