-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: Add Scipy and Cupy as fft interfaces #8
base: main
Are you sure you want to change the base?
Changes from 22 commits
f09a64f
41b77e4
97e7c5c
93618fb
b19a817
8525abd
839214e
7d1ca55
4f01683
63b0c4d
5054745
5319043
35975f3
23d367f
b812c07
2361769
32cf2ce
59f67ec
2468569
7d9f1ae
d2e2f61
e427cc5
d7d84f5
2da1582
57947e6
1ac98ff
fe33bf0
568d511
7d88bb9
d7457e1
528a038
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
sphinx==4.3.0 | ||
sphinxcontrib.bibtex>=2.0 | ||
sphinx_rtd_theme==1.0 | ||
|
||
sphinx | ||
sphinxcontrib.bibtex | ||
sphinx_rtd_theme |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
"""Fourier Transform speeds for the Cupy 3D interface | ||
|
||
This example visualizes the speed for different batch sizes for | ||
the `FFTFilterCupy3D` FFT Filter. The y-axis shows the speed of a single | ||
FFT for the corresponding batch size. | ||
|
||
- Optimum batch size is between 64 and 256 for 256x256pix imgs (incl padding). | ||
- Here, batch size is the size of the 3D stack in z. | ||
|
||
""" | ||
import time | ||
import matplotlib.pylab as plt | ||
import numpy as np | ||
import qpretrieve | ||
|
||
# load the experimental data | ||
edata = np.load("./data/hologram_cell.npz") | ||
|
||
n_transforms_list = [8, 16, 32, 64, 128, 256, 512] | ||
subtract_mean = True | ||
padding = True | ||
fft_interface = qpretrieve.fourier.FFTFilterCupy3D | ||
|
||
results = {} | ||
for n_transforms in n_transforms_list: | ||
print(f"Running {n_transforms} transforms...") | ||
|
||
data_2d = edata["data"].copy() | ||
data_2d_bg = edata["bg_data"].copy() | ||
data_3d = np.repeat( | ||
edata["data"].copy()[np.newaxis, ...], | ||
repeats=n_transforms, axis=0) | ||
data_3d_bg = np.repeat( | ||
edata["bg_data"].copy()[np.newaxis, ...], | ||
repeats=n_transforms, axis=0) | ||
assert data_3d.shape == data_3d_bg.shape == (n_transforms, | ||
edata["data"].shape[0], | ||
edata["data"].shape[1]) | ||
|
||
t0 = time.time() | ||
|
||
holo = qpretrieve.OffAxisHologram(data=data_3d, | ||
fft_interface=fft_interface, | ||
subtract_mean=subtract_mean, | ||
padding=padding) | ||
holo.run_pipeline(filter_name="disk", filter_size=1 / 2) | ||
bg = qpretrieve.OffAxisHologram(data=data_3d_bg) | ||
bg.process_like(holo) | ||
|
||
t1 = time.time() | ||
results[n_transforms] = t1 - t0 | ||
|
||
speed_norm = [v / k for k, v in results.items()] | ||
|
||
fig, axes = plt.subplots(1, 1, figsize=(8, 5)) | ||
ax1 = axes | ||
|
||
ax1.bar(range(len(n_transforms_list)), height=speed_norm, color='darkmagenta') | ||
ax1.set_xticks(range(len(n_transforms_list)), labels=n_transforms_list) | ||
ax1.set_xlabel("Fourier transform batch size") | ||
ax1.set_ylabel("Speed / batch size (s)") | ||
|
||
plt.suptitle("Speed of FFT Interface CuPy3D") | ||
plt.tight_layout() | ||
plt.show() | ||
# plt.savefig("fft_cupy3d_speed.png", dpi=150) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"""Fourier Transform interfaces available | ||
|
||
This example visualizes the different backends and packages available to the | ||
user for performing Fourier transforms. | ||
|
||
- PyFFTW is initially slow, but over many FFTs is very quick. | ||
- CuPy 3D should not be used for direct comparison here, | ||
as it is not doing post-processing padding. | ||
|
||
""" | ||
import time | ||
import matplotlib.pylab as plt | ||
import numpy as np | ||
import qpretrieve | ||
|
||
# load the experimental data | ||
edata = np.load("./data/hologram_cell.npz") | ||
|
||
# get the available fft interfaces | ||
interfaces_available = qpretrieve.fourier.get_available_interfaces() | ||
|
||
n_transforms = 200 | ||
subtract_mean = True | ||
padding = True | ||
|
||
print("Running single transform...") | ||
# one transform | ||
results_1 = {} | ||
for fft_interface in interfaces_available: | ||
t0 = time.time() | ||
holo = qpretrieve.OffAxisHologram(data=edata["data"], | ||
fft_interface=fft_interface, | ||
subtract_mean=subtract_mean, | ||
padding=padding) | ||
holo.run_pipeline(filter_name="disk", filter_size=1 / 2) | ||
bg = qpretrieve.OffAxisHologram(data=edata["bg_data"]) | ||
bg.process_like(holo) | ||
t1 = time.time() | ||
results_1[fft_interface.__name__] = t1 - t0 | ||
num_interfaces = len(results_1) | ||
|
||
# multiple transforms (should see speed increase for PyFFTW) | ||
print(f"Running {n_transforms} transforms...") | ||
results = {} | ||
for fft_interface in interfaces_available: | ||
|
||
data_2d = edata["data"].copy() | ||
data_2d_bg = edata["bg_data"].copy() | ||
|
||
data_3d = np.repeat( | ||
edata["data"].copy()[np.newaxis, ...], | ||
repeats=n_transforms, axis=0) | ||
data_3d_bg = np.repeat( | ||
edata["bg_data"].copy()[np.newaxis, ...], | ||
repeats=n_transforms, axis=0) | ||
assert data_3d.shape == data_3d_bg.shape == (n_transforms, | ||
edata["data"].shape[0], | ||
edata["data"].shape[1]) | ||
|
||
t0 = time.time() | ||
|
||
if fft_interface.__name__ == "FFTFilterCupy3D": | ||
holo = qpretrieve.OffAxisHologram(data=data_3d, | ||
fft_interface=fft_interface, | ||
subtract_mean=subtract_mean, | ||
padding=padding) | ||
holo.run_pipeline(filter_name="disk", filter_size=1 / 2) | ||
bg = qpretrieve.OffAxisHologram(data=data_3d_bg) | ||
bg.process_like(holo) | ||
else: | ||
# 2d | ||
for _ in range(n_transforms): | ||
holo = qpretrieve.OffAxisHologram( | ||
data=data_2d, | ||
fft_interface=fft_interface, | ||
subtract_mean=subtract_mean, padding=padding) | ||
holo.run_pipeline(filter_name="disk", filter_size=1 / 2) | ||
bg = qpretrieve.OffAxisHologram(data=edata["bg_data"]) | ||
bg.process_like(holo) | ||
|
||
t1 = time.time() | ||
results[fft_interface.__name__] = t1 - t0 | ||
num_interfaces = len(results) | ||
|
||
fft_interfaces = list(results.keys()) | ||
speed_1 = list(results_1.values()) | ||
speed = list(results.values()) | ||
|
||
fig, axes = plt.subplots(1, 2, figsize=(8, 5)) | ||
ax1, ax2 = axes | ||
labels = [fftstr[9:] for fftstr in fft_interfaces] | ||
|
||
ax1.bar(range(num_interfaces), height=speed_1, color='lightseagreen') | ||
ax1.set_xticks(range(num_interfaces), labels=labels, | ||
rotation=45) | ||
ax1.set_ylabel("Speed (s)") | ||
ax1.set_title("1 Transform") | ||
|
||
ax2.bar(range(num_interfaces), height=speed, color='lightseagreen') | ||
ax2.set_xticks(range(num_interfaces), labels=labels, | ||
rotation=45) | ||
ax2.set_ylabel("Speed (s)") | ||
# todo: fix code, then this title | ||
ax2.set_title(f"{n_transforms} Transforms\n(**Cupy comparison not valid)") | ||
|
||
plt.suptitle("Speed of FFT Interfaces") | ||
plt.tight_layout() | ||
# plt.show() | ||
plt.savefig("fft_options.png", dpi=150) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
matplotlib |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,9 +38,9 @@ def get_filter_array(filter_name, filter_size, freq_pos, fft_shape): | |
and must be between 0 and `max(fft_shape)/2` | ||
freq_pos: tuple of floats | ||
The position of the filter in frequency coordinates as | ||
returned by :func:`nunpy.fft.fftfreq`. | ||
returned by :func:`numpy.fft.fftfreq`. | ||
fft_shape: tuple of int | ||
The shape of the Fourier transformed image for which the | ||
The shape of the Fourier transformed image (2d) for which the | ||
filter will be applied. The shape must be squared (two | ||
identical integers). | ||
|
||
|
@@ -104,8 +104,10 @@ def get_filter_array(filter_name, filter_size, freq_pos, fft_shape): | |
# TODO: avoid the np.roll, instead use the indices directly | ||
alpha = 0.1 | ||
rsize = int(min(fx.size, fy.size) * filter_size) * 2 | ||
tukey_window_x = signal.tukey(rsize, alpha=alpha).reshape(-1, 1) | ||
tukey_window_y = signal.tukey(rsize, alpha=alpha).reshape(1, -1) | ||
tukey_window_x = signal.windows.tukey( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scipy's new versions imports tukey from |
||
rsize, alpha=alpha).reshape(-1, 1) | ||
tukey_window_y = signal.windows.tukey( | ||
rsize, alpha=alpha).reshape(1, -1) | ||
tukey = tukey_window_x * tukey_window_y | ||
base = np.zeros(fft_shape) | ||
s1 = (np.array(fft_shape) - rsize) // 2 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,42 @@ | |
import warnings | ||
|
||
from .ff_numpy import FFTFilterNumpy | ||
from .ff_scipy import FFTFilterScipy | ||
|
||
try: | ||
from .ff_pyfftw import FFTFilterPyFFTW | ||
except ImportError: | ||
FFTFilterPyFFTW = None | ||
|
||
try: | ||
from .ff_cupy import FFTFilterCupy | ||
except ImportError: | ||
FFTFilterCupy = None | ||
|
||
try: | ||
from .ff_cupy3D import FFTFilterCupy3D | ||
except ImportError: | ||
FFTFilterCupy3D = None | ||
|
||
PREFERRED_INTERFACE = None | ||
|
||
|
||
def get_available_interfaces(): | ||
"""Return a list of available FFT algorithms""" | ||
interfaces = [ | ||
FFTFilterPyFFTW, | ||
FFTFilterNumpy, | ||
FFTFilterScipy, | ||
FFTFilterCupy, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessarily the "perferred order" we want, but I'd like to keep it as is due to old default pipelines. |
||
FFTFilterCupy3D, | ||
] | ||
interfaces_available = [] | ||
for interface in interfaces: | ||
if interface is not None and interface.is_available: | ||
interfaces_available.append(interface) | ||
return interfaces_available | ||
|
||
|
||
def get_best_interface(): | ||
"""Return the fastest refocusing interface available | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to clean this script up a bit to make it simpler