Skip to content

Commit dda2697

Browse files
committed
Merge branch 'develop' into denoiser_test
2 parents 2c68610 + 551aa32 commit dda2697

File tree

17 files changed

+129
-182
lines changed

17 files changed

+129
-182
lines changed

.travis.yml

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
1-
sudo: required
21
language: python
3-
matrix:
2+
3+
# manylinux2010 wheels are not found on xenial
4+
dist: bionic
5+
6+
os:
7+
- linux
8+
9+
arch:
10+
- amd64
11+
12+
stages:
13+
- check
14+
- test
15+
- deploy
16+
17+
jobs:
418
include:
519
- python: 3.6
6-
env:
7-
- TOXENV=clean,py36-stable,coveralls,report
8-
- TOXENV=py36-dev
20+
env: TOXENV=clean,py36-stable,coveralls,report
21+
- python: 3.6
22+
env: TOXENV=py36-dev
923
- python: 3.7
10-
env:
11-
- TOXENV=py37-stable
12-
- TOXENV=py37-dev
24+
env: TOXENV=py37-stable
25+
- python: 3.7
26+
env: TOXENV=py37-dev
27+
- python: 3.8
28+
env: TOXENV=py38-stable
1329
- python: 3.8
14-
env:
15-
- TOXENV=py38-stable
16-
- TOXENV=py38-dev
17-
- env: TOXENV=check
18-
- env: TOXENV=docs
30+
env: TOXENV=py38-dev
31+
- python: 3.6
32+
env: TOXENV=check
33+
- python: 3.6
34+
env: TOXENV=docs
35+
1936
install:
2037
- pip install -U tox tox-travis
2138

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def read(fname):
2424
author_email="[email protected]",
2525
install_requires=[
2626
"click",
27-
"finufftpy",
27+
"finufft",
2828
"importlib_resources>=1.0.2",
2929
"joblib",
3030
"jupyter",

src/aspire/basis/ffb_2d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def _precomp(self):
9191
np.sin(np.arange(n_theta, dtype=self.dtype) * 2 * pi / (2 * n_theta)),
9292
(1, n_theta),
9393
)
94-
freqs = np.vstack((freqs_x[np.newaxis, ...], freqs_y[np.newaxis, ...]))
94+
freqs = np.vstack((freqs_y[np.newaxis, ...], freqs_x[np.newaxis, ...]))
9595

9696
return {"gl_nodes": r, "gl_weights": w, "radial": radial, "freqs": freqs}
9797

src/aspire/basis/ffb_3d.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ def _precomp(self):
138138
* pi
139139
* np.vstack(
140140
(
141-
fourier_x[np.newaxis, ...],
142-
fourier_y[np.newaxis, ...],
143141
fourier_z[np.newaxis, ...],
142+
fourier_y[np.newaxis, ...],
143+
fourier_x[np.newaxis, ...],
144144
)
145145
)
146146
)
@@ -269,7 +269,7 @@ def evaluate(self, v):
269269
pf = m_reshape(pf, (n_theta * n_phi * n_r, n_data))
270270

271271
# perform inverse non-uniformly FFT transformation back to 3D rectangular coordinates
272-
freqs = m_reshape(self._precomp["fourier_pts"], (3, n_r * n_theta * n_phi, -1))
272+
freqs = m_reshape(self._precomp["fourier_pts"], (3, n_r * n_theta * n_phi))
273273
x = np.zeros((n_data, self.sz[0], self.sz[1], self.sz[2]), dtype=v.dtype)
274274
for isample in range(0, n_data):
275275
x[isample] = np.real(anufft(pf[:, isample], freqs, self.sz))

src/aspire/basis/fpswf_2d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def _precomp(self):
8787
self.num_angular_pts = f
8888

8989
# pre computing variables for forward
90-
us_fft_pts = np.column_stack((self.quad_rule_pts_x, self.quad_rule_pts_y))
90+
us_fft_pts = np.column_stack((self.quad_rule_pts_y, self.quad_rule_pts_x))
9191
us_fft_pts = self.bandlimit / (self.rcut * np.pi * 2) * us_fft_pts # for pynfft
9292
(
9393
blk_r,

src/aspire/basis/polar_2d.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ def _precomp(self):
6464
for i in range(self.ntheta // 2):
6565
freqs[0, i * self.nrad : (i + 1) * self.nrad] = np.arange(
6666
self.nrad
67-
) * np.sin(i * dtheta)
67+
) * np.cos(i * dtheta)
6868
freqs[1, i * self.nrad : (i + 1) * self.nrad] = np.arange(
6969
self.nrad
70-
) * np.cos(i * dtheta)
70+
) * np.sin(i * dtheta)
7171

7272
freqs *= omega0
7373
return freqs

src/aspire/nufft/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def _try_backend(backend):
6262

6363
elif backend == "finufft":
6464
try:
65-
from finufftpy import nufft1d1 # noqa: F401
65+
from finufft import Plan # noqa: F401
6666

6767
from aspire.nufft.finufft import FinufftPlan
6868

src/aspire/nufft/cufinufft.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,8 @@ def __init__(self, sz, fourier_pts, epsilon=1e-15, ntransforms=1, **kwargs):
8282
# is tied to instance, instead of this method.
8383
self.fourier_pts_gpu = gpuarray.to_gpu(self.fourier_pts)
8484

85-
# Note, cufinufft expects column major signal data,
86-
# but we don't want to transpose large arrays.
87-
# We instead reverse the references to the points axes.
88-
# This is identical to what is done for FINUFFT.
89-
self._transform_plan.set_pts(self.num_pts, *self.fourier_pts_gpu[::-1])
90-
self._adjoint_plan.set_pts(self.num_pts, *self.fourier_pts_gpu[::-1])
85+
self._transform_plan.set_pts(self.num_pts, *self.fourier_pts_gpu)
86+
self._adjoint_plan.set_pts(self.num_pts, *self.fourier_pts_gpu)
9187

9288
def transform(self, signal):
9389
"""

src/aspire/nufft/finufft.py

Lines changed: 50 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
import finufftpy
3+
import finufft
44
import numpy as np
55

66
from aspire.nufft import Plan
@@ -10,84 +10,73 @@
1010

1111

1212
class FinufftPlan(Plan):
13-
def __init__(self, sz, fourier_pts, epsilon=1e-15, ntransforms=1, **kwargs):
13+
def __init__(self, sz, fourier_pts, epsilon=1e-8, ntransforms=1, **kwargs):
1414
"""
1515
A plan for non-uniform FFT in 2D or 3D.
1616
17-
:param sz: A tuple indicating the geometry of the signal
18-
:param fourier_pts: The points in Fourier space where the Fourier transform is to be calculated,
19-
arranged as a dimension-by-K array. These need to be in the range [-pi, pi] in each dimension.
20-
:param epsilon: The desired precision of the NUFFT
21-
:param ntransforms: Optional integer indicating if you would like to compute a batch of `ntransforms`
22-
transforms. Implies vol_f.shape is (..., `ntransforms`). Defaults to 0 which disables batching.
17+
:param sz: A tuple indicating the geometry of the signal.
18+
:param fourier_pts: The points in Fourier space where the Fourier
19+
transform is to be calculated, arranged as a dimension-by-K array.
20+
These need to be in the range [-pi, pi] in each dimension.
21+
:param epsilon: The desired precision of the NUFFT.
22+
:param ntransforms: Optional integer indicating if you would like
23+
to compute a batch of `ntransforms`.
24+
transforms. Implies vol_f.shape is (`ntransforms`, ...).
2325
"""
2426

2527
self.ntransforms = ntransforms
26-
manystr = ""
27-
if self.ntransforms > 1:
28-
manystr = "many"
2928

3029
self.sz = sz
3130
self.dim = len(sz)
3231

3332
self.dtype = fourier_pts.dtype
3433

35-
# TODO: Currently/historically finufftpy is hardcoded as doubles only (inside the binding layer).
36-
# This has been changed in their GuruV2 work,
37-
# and an updated package should be released and integrated very soon.
38-
# The following casting business is only to facilitate the transition.
39-
# I don't want to send one precision in and get a different one out.
40-
# We have enough code that does that already.
41-
# (Potentially anything that used this, for example).
42-
# I would error, but I know a bunch of code ASPIRE wants to work,
43-
# the cov2d tutorial for example, would fail and require hacks to run
44-
# if I was strict about dtypes rn.
45-
# I would rather deal with that in other, more targeted PRs.
46-
# This preserves the legacy behavior of admitting singles,
47-
# but I will correct it slightly, to return the precision given as input.
48-
# This approach should contain the hacks here to a single place on the edge,
49-
# instead of spread through the code. ASPIRE code should focus on being
50-
# internally consistent.
51-
# Admittedly not ideal, but ignoring these problems wasn't sustainable.
52-
53-
self.cast_output = False
54-
if self.dtype != np.float64:
55-
logger.debug(
56-
"This version of finufftpy is hardcoded to doubles internally"
57-
" casting input to doubles, results cast back to singles."
58-
)
59-
self.cast_output = True
60-
self.dtype = np.float64
61-
6234
self.complex_dtype = complex_type(self.dtype)
6335

64-
# TODO: Things get messed up unless we ensure a 'C' ordering here - investigate why
65-
self.fourier_pts = np.asarray(
66-
np.mod(fourier_pts + np.pi, 2 * np.pi) - np.pi, order="C", dtype=self.dtype
36+
self.fourier_pts = np.ascontiguousarray(
37+
np.mod(fourier_pts + np.pi, 2 * np.pi) - np.pi
6738
)
39+
6840
self.num_pts = fourier_pts.shape[1]
69-
self.epsilon = epsilon
7041

71-
# Get a handle on the appropriate 1d/2d/3d forward transform function in finufftpy
72-
self.transform_function = getattr(finufftpy, f"nufft{self.dim}d2{manystr}")
42+
self.epsilon = max(epsilon, np.finfo(self.dtype).eps)
43+
if self.epsilon != epsilon:
44+
logger.debug(
45+
f"FinufftPlan adjusted eps={self.epsilon}" f" from requested {epsilon}."
46+
)
47+
48+
self._transform_plan = finufft.Plan(
49+
nufft_type=2,
50+
n_modes_or_dim=self.sz,
51+
eps=self.epsilon,
52+
n_trans=self.ntransforms,
53+
dtype=self.dtype,
54+
)
55+
56+
self._adjoint_plan = finufft.Plan(
57+
nufft_type=1,
58+
n_modes_or_dim=self.sz,
59+
eps=self.epsilon,
60+
n_trans=self.ntransforms,
61+
dtype=self.dtype,
62+
)
7363

74-
# Get a handle on the appropriate 1d/2d/3d adjoint function in finufftpy
75-
self.adjoint_function = getattr(finufftpy, f"nufft{self.dim}d1{manystr}")
64+
self._transform_plan.setpts(*self.fourier_pts)
65+
self._adjoint_plan.setpts(*self.fourier_pts)
7666

7767
def transform(self, signal):
7868
"""
7969
Compute the NUFFT transform using this plan instance.
8070
8171
:param signal: Signal to be transformed. For a single transform,
8272
this should be a a 1, 2, or 3D array matching the plan `sz`.
83-
For a batch, signal should have shape `(*sz, ntransforms)`.
73+
For a batch, signal should have shape `(ntransforms, *sz)`.
8474
8575
:returns: Transformed signal of shape `num_pts` or
8676
`(ntransforms, num_pts)`.
8777
"""
8878

89-
sig_shape = signal.shape
90-
res_shape = self.num_pts
79+
sig_frame_shape = signal.shape
9180
# Note, there is a corner case for ntransforms == 1.
9281
if self.ntransforms > 1 or (
9382
self.ntransforms == 1 and len(signal.shape) == self.dim + 1
@@ -103,37 +92,18 @@ def transform(self, signal):
10392
f" should match ntransforms {self.ntransforms}.",
10493
)
10594

106-
sig_shape = signal.shape[1:]
107-
res_shape = (self.ntransforms, self.num_pts)
95+
sig_frame_shape = signal.shape[1:]
96+
97+
# finufft expects signal.ndim == dim for ntransforms = 1.
98+
if self.ntransforms == 1:
99+
signal = signal.reshape(self.sz)
108100

109101
ensure(
110-
sig_shape == self.sz,
102+
sig_frame_shape == self.sz,
111103
f"Signal frame to be transformed must have shape {self.sz}",
112104
)
113105

114-
epsilon = max(self.epsilon, np.finfo(signal.dtype).eps)
115-
116-
# Forward transform functions in finufftpy have signatures of the form:
117-
# (x, y, z, c, isign, eps, f, ...)
118-
# (x, y c, isign, eps, f, ...)
119-
# (x, c, isign, eps, f, ...)
120-
# Where f is a Fortran-order ndarray of the appropriate dimensions
121-
# We form these function signatures here by tuple-unpacking
122-
123-
result = np.zeros(res_shape, dtype=self.complex_dtype)
124-
125-
result_code = self.transform_function(
126-
*self.fourier_pts,
127-
result,
128-
-1,
129-
epsilon,
130-
signal.T, # RCOPT, currently F ordered, should change in gv2
131-
)
132-
133-
if result_code != 0:
134-
raise RuntimeError(f"FINufft transform failed. Result code {result_code}")
135-
if self.cast_output:
136-
result = result.astype(np.complex64)
106+
result = self._transform_plan.execute(signal)
137107

138108
return result
139109

@@ -145,19 +115,9 @@ def adjoint(self, signal):
145115
this should be a a 1D array of len `num_pts`.
146116
For a batch, signal should have shape `(ntransforms, num_pts)`.
147117
148-
:returns: Transformed signal `(sz)` or `(sz, ntransforms)`.
118+
:returns: Transformed signal `(sz)` or `(ntransforms, sz)`.
149119
"""
150120

151-
epsilon = max(self.epsilon, np.finfo(signal.dtype).eps)
152-
153-
# Adjoint functions in finufftpy have signatures of the form:
154-
# (x, y, z, c, isign, eps, ms, mt, mu, f, ...)
155-
# (x, y c, isign, eps, ms, mt f, ...)
156-
# (x, c, isign, eps, ms, f, ...)
157-
# Where f is a Fortran-order ndarray of the appropriate dimensions
158-
# We form these function signatures here by tuple-unpacking
159-
160-
res_shape = self.sz
161121
# Note, there is a corner case for ntransforms == 1.
162122
if self.ntransforms > 1 or (self.ntransforms == 1 and len(signal.shape) == 2):
163123
ensure(
@@ -170,31 +130,11 @@ def adjoint(self, signal):
170130
"For multiple transforms, signal stack length"
171131
f" should match ntransforms {self.ntransforms}.",
172132
)
173-
res_shape = (
174-
self.ntransforms,
175-
*self.sz,
176-
)
177133

178-
result = np.zeros(res_shape, dtype=self.complex_dtype)
179-
180-
# FINUFFT is F order at this time. The bindings
181-
# will pickup the fact `signal` is C_Contiguous,
182-
# and transpose the data; we just need to transpose
183-
# the indices. I think the next release addresses this.
184-
# Note in the 2020 hackathon this was changed directly in FFB,
185-
# which worked because GPU arrays just need the pointer anyway...
186-
# This is a quirk of this version of FINUFFT, and
187-
# so probably belongs here at the edge,
188-
# away from other implementations.
189-
signal = signal.reshape(signal.shape[::-1])
190-
191-
result_code = self.adjoint_function(
192-
*self.fourier_pts, signal, 1, epsilon, *self.sz, result
193-
)
194-
if result_code != 0:
195-
raise RuntimeError(f"FINufft adjoint failed. Result code {result_code}")
134+
# finufft is expecting flat array for 1D case.
135+
if self.ntransforms == 1:
136+
signal = signal.reshape(self.num_pts)
196137

197-
if self.cast_output:
198-
result = result.astype(np.complex64)
138+
result = self._adjoint_plan.execute(signal)
199139

200140
return result

src/aspire/utils/types.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,6 @@ def complex_type(realtype):
5757
return complextype
5858

5959

60-
def dtype_legacy_string(dtype):
61-
"""
62-
Get legacy dtype string from corresponding numpy dtype.
63-
64-
:param dtype: Numpy dtype
65-
:return: string
66-
"""
67-
68-
dtype_to_string_map = {
69-
"float32": "single",
70-
"float64": "double",
71-
"complex128": "complex",
72-
}
73-
74-
dtype_str = dtype_to_string_map.get(str(dtype))
75-
76-
if not dtype_str:
77-
msg = f"Corresponding dtype {str(dtype)} is not defined."
78-
logger.error(msg)
79-
raise TypeError(msg)
80-
81-
return dtype_str
82-
83-
8460
def utest_tolerance(dtype):
8561
"""
8662
Return ASPIRE tolerance for unit tests based on `dtype`.

0 commit comments

Comments
 (0)