Skip to content
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

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f09a64f
enh: add scipy fft interface
Aug 26, 2024
41b77e4
tests: correct path for ci use
Aug 26, 2024
97e7c5c
tests: fft interface checks and lint code
Aug 26, 2024
93618fb
docs: update docstrings and docs example output image
Aug 26, 2024
b19a817
enh, tests: add cupy3D class and basic tests for debugging
Sep 17, 2024
8525abd
enh: making the code 3d-proof
Sep 18, 2024
839214e
test: compare 2D and 3D mean value consistency
Nov 7, 2024
7d1ca55
enh: allow fourier base to subtract mean from 3D data arrays
Nov 7, 2024
4f01683
enh: create utility functions for padding and subt_mean
Nov 7, 2024
63b0c4d
tests: add tests for new util funcs and comparing 2D and 3D Fourier p…
Nov 7, 2024
5054745
tests: remove use of matplotlib from tests
Nov 7, 2024
5319043
docs: create speed comparison example for Cupy 3d
Nov 7, 2024
35975f3
test: reorg the cupy tests for clarity
Nov 8, 2024
23d367f
ci: ignore cupy tests for now during the cicd pipeline
Nov 8, 2024
b812c07
docs: update figure labels; ci: correct github actions syntax
Nov 8, 2024
2361769
fix: correct define FFTFilters upon import
Nov 8, 2024
32cf2ce
test: check ci tests to see if Pyfftw is the issue
Nov 8, 2024
59f67ec
docs: lint examples
Nov 8, 2024
2468569
docs: update README
Nov 8, 2024
7d9f1ae
ref: use negative indexing for np array shape
Nov 8, 2024
d2e2f61
enh: add use of 3D array for the FFTFilter init, not incl padding
Nov 8, 2024
e427cc5
enh: ensure ifft with padding works with 3D stack
Nov 8, 2024
d7d84f5
enh: ensure all data is converted to 3D image stack
Nov 20, 2024
2da1582
enh: add data format conversion functions
Nov 20, 2024
57947e6
ref: use the data format conversion convenience fnuctions to handle f…
Nov 20, 2024
1ac98ff
test: refactor relevant oah tests to expect new format and shape
Nov 20, 2024
fe33bf0
enh: add rgb warning for user
Nov 20, 2024
568d511
test: ensure the users provided data format is returned
Nov 20, 2024
7d88bb9
enh: add 3d array usage to qsli
Nov 20, 2024
d7457e1
ref: align the 2d qlsi code to 3d
Nov 21, 2024
528a038
fix: match qlsi 2d with new 3d implementation
Dec 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ['3.10']
python-version: ['3.10', '3.11']
os: [macos-latest, ubuntu-latest, windows-latest]

steps:
Expand Down Expand Up @@ -39,7 +39,8 @@ jobs:
pip freeze
- name: Test with pytest
run: |
coverage run --source=qpretrieve -m pytest tests
# ignore the cupy imports, as we don't have a gpu-enabled pipeline setup
coverage run --source=qpretrieve -m pytest tests --ignore=tests/test_cupy_gpu
- name: Lint with flake8
run: |
flake8 .
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ To install the requirements for building the documentation, run

To compile the documentation, run

cd docs
sphinx-build . _build


Expand Down
7 changes: 3 additions & 4 deletions docs/requirements.txt
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
17 changes: 15 additions & 2 deletions docs/sec_code_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,34 @@ Fourier transform methods
=========================

.. _sec_code_fourier_numpy:

Numpy
-----
.. automodule:: qpretrieve.fourier.ff_numpy
:members:
:inherited-members:

.. _sec_code_fourier_pyfftw:

PyFFTW
------
.. automodule:: qpretrieve.fourier.ff_pyfftw
:members:
:inherited-members:

.. _sec_code_fourier_scipy:
Scipy
------
.. automodule:: qpretrieve.fourier.ff_scipy
:members:
:inherited-members:

.. _sec_code_fourier_cupy:
Cupy
----
.. automodule:: qpretrieve.fourier.ff_cupy
:members:
:inherited-members:


.. _sec_code_ifer:

Interference image analysis
Expand Down
4 changes: 4 additions & 0 deletions docs/sec_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ Examples
.. fancy_include:: filter_visualization.py

.. fancy_include:: fourier_scale.py

.. fancy_include:: fft_options.py

.. fancy_include:: fft_cupy3d_speed.py
Binary file added examples/fft_cupy3d_speed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions examples/fft_cupy3d_speed.py
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)
Binary file added examples/fft_options.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
109 changes: 109 additions & 0 deletions examples/fft_options.py
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 = {}
Copy link
Author

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

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)
1 change: 1 addition & 0 deletions examples/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
matplotlib
10 changes: 6 additions & 4 deletions qpretrieve/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scipy's new versions imports tukey from signal.window, not signal

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
Expand Down
27 changes: 27 additions & 0 deletions qpretrieve/fourier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Author

Choose a reason for hiding this comment

The 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

Expand Down
Loading
Loading