Examples
Field Retrieval & Unwrapping
Field retrieval (qpretrieve) and phase unwrapping (xpunwrap) on GPU.
gpu_field_retr_phase_unwrapping.py
1import matplotlib.pyplot as plt
2import numpy as np
3import qpretrieve
4import xpunwrap
5
6# Force GPU backend for both libraries.
7qpretrieve.set_ndarray_backend("cupy")
8xpunwrap.set_ndarray_backend("cupy")
9xp = xpunwrap.get_ndarray_backend()
10
11fft_interface = qpretrieve.fourier.FFTFilterCupy
12edata = np.load("./data/hologram_cell.npz")
13holo = qpretrieve.OffAxisHologram(edata["data"], fft_interface)
14bg = qpretrieve.OffAxisHologram(edata["bg_data"], fft_interface)
15
16holo.run_pipeline(filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
17bg.process_like(holo)
18phase_wrapped = xp.asarray(holo.phase - bg.phase).astype(xp.float32)
19
20# Unwrap the phase with all available algorithms
21outputs = {}
22for algo_name, algo in xpunwrap.algos_available().items():
23 outputs[algo_name] = algo(phase_wrapped)
24skimage_out = outputs.get("algo_skimage_unwrap", None)
25if skimage_out is not None:
26 outputs_no_skimage = {k: v for k, v in outputs.items() if k != "algo_skimage_unwrap"}
27else:
28 outputs_no_skimage = outputs
29
30# plot the wrapped and unwrapped phases
31plt.style.use("dark_background")
32fig, axes = plt.subplots(2, 3, figsize=(8, 6))
33fig.suptitle("Field Retrieval + Phase Unwrapping (GPU)", fontsize=18)
34axes = axes.flatten(order="F")
35
36axes[0].imshow(phase_wrapped.get()[0])
37axes[0].set_title("Wrapped Phase")
38
39# With column-major flattening, bottom-right is index 5.
40skimage_ax = axes[5]
41
42# Fill remaining non-skimage algorithms first (slots 1..4).
43plot_slots = [2, 3, 4]
44for slot, (algo_name, arr) in zip(plot_slots, outputs_no_skimage.items()):
45 ax = axes[slot]
46 ax.imshow(arr.get()[0])
47 ax.set_title(f"Unwrapped\n{algo_name}")
48
49if skimage_out is not None:
50 skimage_ax.imshow(skimage_out.get()[0])
51 skimage_ax.set_title("Unwrapped (CPU-only)\nalgo_skimage_unwrap")
52else:
53 skimage_ax.text(0.5, 0.5, "skimage missing", ha="center", va="center")
54 skimage_ax.set_title("Unwrapped\nalgo_skimage_unwrap")
55
56for ax in axes:
57 ax.set_axis_off()
58
59plt.tight_layout(w_pad=4.5)
60plt.savefig("gpu_field_retr_phase_unwrapping.png")
61# plt.show()
CPU vs GPU phase unwrapping speed comparison
Compares four pipelines on hologram_cell data:
CPU skimage + NumPy (qpretrieve NumPy FFT + skimage unwrap)
CPU skimage + pyFFTW (qpretrieve pyFFTW FFT + skimage unwrap)
CPU ls_poisson + pyFFTW (qpretrieve pyFFTW FFT + xpunwrap Poisson)
GPU ls_poisson + CuPy (full CuPy pipeline)
Run:
python examples/compare_cpu_gpu.py
compare_cpu_gpu.py
1from __future__ import annotations
2
3import time
4from pathlib import Path
5
6import matplotlib.pyplot as plt
7import numpy as np
8
9import qpretrieve
10import xpunwrap
11import xpunwrap.fourier as xp_fourier
12
13DATA_PATH = Path(__file__).parent.parent / "tests" / "data" / "hologram_cell.npz"
14OUTPUT_PNG = Path(__file__).parent / "compare_cpu_gpu.png"
15STACK_DEPTH = 256
16TILE_SIZE = 256
17
18
19def _load_raw_numpy() -> dict[str, np.ndarray]:
20 edata = np.load(DATA_PATH)
21 data = np.asarray(edata["data"], dtype=np.float32)
22 bg = np.asarray(edata["bg_data"], dtype=np.float32)
23 tile_h = int(np.ceil(TILE_SIZE / data.shape[-2]))
24 tile_w = int(np.ceil(TILE_SIZE / data.shape[-1]))
25 data = np.tile(data, (tile_h, tile_w))[:TILE_SIZE, :TILE_SIZE]
26 bg = np.tile(bg, (tile_h, tile_w))[:TILE_SIZE, :TILE_SIZE]
27 return {"data": data, "bg": bg}
28
29
30def _stack_phase(arr: np.ndarray) -> np.ndarray:
31 return np.repeat(arr[None, ...], repeats=STACK_DEPTH, axis=0)
32
33
34def _time_skimage_pipeline(
35 raw: dict[str, np.ndarray],
36 fft_interface,
37 repeats: int = 3,
38):
39 """Shared timing loop for skimage-based pipelines."""
40 qpretrieve.set_ndarray_backend("numpy")
41 try:
42 algo = xpunwrap.algos_available()["algo_skimage_unwrap"]
43 except Exception:
44 return None
45
46 def _run():
47 holo = qpretrieve.OffAxisHologram(raw["data"], fft_interface)
48 bg = qpretrieve.OffAxisHologram(raw["bg"], fft_interface)
49 holo.run_pipeline(
50 filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
51 bg.process_like(holo)
52 algo(np.asarray(holo.phase - bg.phase, dtype=np.float32))
53
54 _run() # warmup (important for pyFFTW plan compilation)
55 times = []
56 for _ in range(repeats):
57 start = time.perf_counter()
58 _run()
59 times.append((time.perf_counter() - start) * 1000.0)
60 return float(np.mean(times)), float(np.std(times))
61
62
63def time_cpu_skimage_numpy(raw: dict[str, np.ndarray], repeats: int = 3):
64 """CPU pipeline: qpretrieve (NumPy FFT) + skimage unwrap."""
65 return _time_skimage_pipeline(
66 raw, qpretrieve.fourier.FFTFilterNumpy, repeats)
67
68
69def time_cpu_skimage_pyfftw(raw: dict[str, np.ndarray], repeats: int = 3):
70 """CPU pipeline: qpretrieve (pyFFTW) + skimage unwrap."""
71 return _time_skimage_pipeline(
72 raw, qpretrieve.fourier.FFTFilterPyFFTW, repeats)
73
74
75def time_cpu_ls_poisson_pyfftw(raw: dict[str, np.ndarray], repeats: int = 3):
76 """CPU pipeline: qpretrieve (pyFFTW) + xpunwrap ls_poisson (pyFFTW)."""
77 from xpunwrap.fourier import FFTEnginePyFFTW
78 if FFTEnginePyFFTW is None:
79 return None
80
81 qpretrieve.set_ndarray_backend("numpy")
82 xpunwrap.set_ndarray_backend("numpy")
83 fft_interface = qpretrieve.fourier.FFTFilterPyFFTW
84 xp_fourier.PREFERRED_ENGINE = "FFTEnginePyFFTW"
85 algo = xpunwrap.algos_available()["algo_ls_poisson"]
86
87 try:
88 # Warmup
89 holo = qpretrieve.OffAxisHologram(raw["data"], fft_interface)
90 bg = qpretrieve.OffAxisHologram(raw["bg"], fft_interface)
91 holo.run_pipeline(
92 filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
93 bg.process_like(holo)
94 algo(np.asarray(holo.phase - bg.phase, dtype=np.float32))
95
96 times = []
97 for _ in range(repeats):
98 start = time.perf_counter()
99 holo = qpretrieve.OffAxisHologram(raw["data"], fft_interface)
100 bg = qpretrieve.OffAxisHologram(raw["bg"], fft_interface)
101 holo.run_pipeline(
102 filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
103 bg.process_like(holo)
104 algo(np.asarray(holo.phase - bg.phase, dtype=np.float32))
105 times.append((time.perf_counter() - start) * 1000.0)
106 finally:
107 xp_fourier.PREFERRED_ENGINE = None
108
109 return float(np.mean(times)), float(np.std(times))
110
111
112def time_gpu_pipeline(raw: dict[str, np.ndarray], repeats: int = 3):
113 """GPU pipeline: qpretrieve (CuPy FFT) + xpunwrap ls_poisson (CuPy FFT)."""
114 try:
115 import cupy as cp # noqa: F401
116 except Exception:
117 return None
118
119 qpretrieve.set_ndarray_backend("cupy")
120 fft_interface = qpretrieve.fourier.FFTFilterCupy
121 xpunwrap.set_ndarray_backend("cupy")
122 xp = xpunwrap.get_ndarray_backend()
123 algo = xpunwrap.algos_available()["algo_ls_poisson"]
124
125 def _sync():
126 if hasattr(xp, "cuda"):
127 xp.cuda.Stream.null.synchronize()
128
129 # Warmup
130 _sync()
131 holo_gpu = xp.asarray(raw["data"])
132 bg_gpu = xp.asarray(raw["bg"])
133 holo_obj = qpretrieve.OffAxisHologram(holo_gpu, fft_interface)
134 bg_obj = qpretrieve.OffAxisHologram(bg_gpu, fft_interface)
135 holo_obj.run_pipeline(
136 filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
137 bg_obj.process_like(holo_obj)
138 algo(xp.asarray(holo_obj.phase - bg_obj.phase, dtype=xp.float32))
139 _sync()
140
141 times = []
142 for _ in range(repeats):
143 _sync()
144 start = time.perf_counter()
145 holo_gpu = xp.asarray(raw["data"])
146 bg_gpu = xp.asarray(raw["bg"])
147 holo_obj = qpretrieve.OffAxisHologram(holo_gpu, fft_interface)
148 bg_obj = qpretrieve.OffAxisHologram(bg_gpu, fft_interface)
149 holo_obj.run_pipeline(
150 filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
151 bg_obj.process_like(holo_obj)
152 algo(xp.asarray(holo_obj.phase - bg_obj.phase, dtype=xp.float32))
153 _sync()
154 times.append((time.perf_counter() - start) * 1000.0)
155 return float(np.mean(times)), float(np.std(times))
156
157
158def main():
159 raw = _load_raw_numpy()
160
161 skimage_numpy = time_cpu_skimage_numpy(raw)
162 skimage_pyfftw = time_cpu_skimage_pyfftw(raw)
163 ls_pyfftw = time_cpu_ls_poisson_pyfftw(raw)
164 ls_cupy = time_gpu_pipeline(raw)
165
166 # Wong (2011) / Nature Methods colorblind-safe palette
167 clr_blue, clr_green = "#56B4E9", "#009E73"
168 clr_orange, clr_purple = "#D55E00", "#CC79A7"
169 entries = [
170 ("skimage\n+ NumPy\n(CPU)", skimage_numpy, clr_blue),
171 ("skimage\n+ pyFFTW\n(CPU)", skimage_pyfftw, clr_green),
172 ("ls_poisson\n+ pyFFTW\n(CPU)", ls_pyfftw, clr_orange),
173 ("ls_poisson\n+ CuPy\n(GPU)", ls_cupy, clr_purple),
174 ]
175 entries = [(lbl, res, col) for lbl, res, col in entries if res is not None]
176
177 labels = [e[0] for e in entries]
178 means = [e[1][0] for e in entries]
179 stds = [e[1][1] for e in entries]
180 colors = [e[2] for e in entries]
181
182 plt.style.use("dark_background")
183 fig, ax = plt.subplots(figsize=(6, 4), facecolor="#181C24")
184 x = np.arange(len(labels))
185 ax.bar(x, means, yerr=stds, color=colors, edgecolor="white",
186 error_kw={"ecolor": "white", "elinewidth": 1.5,
187 "capsize": 5, "capthick": 1.5})
188 ax.set_xticks(x)
189 ax.set_xticklabels(labels, ha="center")
190 ax.set_ylabel("Time (ms)")
191 ax.set_title("Phase unwrapping speed comparison")
192 ax.grid(axis="y", linestyle="--", alpha=0.3)
193 ax.set_facecolor("#181C24")
194
195 for xi, mean, std in zip(x, means, stds):
196 ax.text(xi, mean + std, f"{mean:.1f} ms",
197 ha="center", va="bottom", fontsize=8)
198
199 fig.tight_layout()
200 fig.savefig(OUTPUT_PNG, dpi=200)
201 print(f"Saved {OUTPUT_PNG}")
202
203
204if __name__ == "__main__":
205 main()
Poisson LS Algorithm Walkthrough
Step-by-step walkthrough of the Least-squares Poisson Periodic Gradient algorithm.
The aim here is to visualise the steps of the algorithm with matplotlib with example datasets, showing edge cases where phase unwrapping fails. Where possible the scikit-image 2D phase unwrapping algorithm will be used for comparison.
The example data is the data used by M. Gdeisat and F. Lilley in their excellent “Two-Dimensional Phase Unwrapping Problem” guide.
walkthrough-ls_poisson_pg.py
1import numpy as np
2import matplotlib.pyplot as plt
3from typing import Tuple
4
5import xpunwrap
6from xpunwrap.algorithms.ls_poisson_pg import (
7 wrapped_gradients_stack,
8 enforce_periodic_gradients_stack,
9 divergence_stack,
10 poisson_solve_fft_stack,
11)
12from xpunwrap.algorithms._plane_utils import restore_mean_plane
13
14# use numpy for this example for simplicity
15xpunwrap.set_ndarray_backend('numpy')
16xp = xpunwrap.get_ndarray_backend()
17
18
19# load/create the example data
20def _generate_phase(nrx: int = 512, nry: int = 512) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
21 """Create the continuous phase image f(x, y) = 20*exp(-0.25*(x^2+y^2)) + 2x + y."""
22 tx = np.linspace(-3.0, 3.0, nrx)
23 ty = np.linspace(-3.0, 3.0, nry)
24 x, y = np.meshgrid(tx, ty)
25 image = 20.0 * np.exp(-0.25 * (x ** 2 + y ** 2)) + 2.0 * x + y
26 return x, y, image
27
28
29def _wrap_phase(image: np.ndarray) -> np.ndarray:
30 """Wrap phase to [-pi, pi] using angle of complex exponential."""
31 return np.angle(np.exp(1j * image))
32
33
34x, y, image = _generate_phase()
35arr = _wrap_phase(image)
36phase_wrapped = np.repeat(arr[None, ...], repeats=10, axis=0)
37
38# the `algo_ls_poisson_pg` is broken up into the following steps:
39# initial dimensionality check
40# gx, gy = wrapped_gradients_stack(phase_wrapped)
41# gx, gy = enforce_periodic_gradients_stack(gx, gy)
42# rhs = divergence_stack(gx, gy)
43# phi = poisson_solve_fft_stack(rhs)
44# phase inversion
45# restore_mean_plane
46
47# Compute forward finite differences of the wrapped phase.
48# Then wrap those gradients back into [-pi, pi) so they remain a valid
49# representation of wrapped phase changes.
50gx1, gy1 = wrapped_gradients_stack(phase_wrapped)
51# Enforce periodic boundary conditions on the wrapped gradients so that the
52# edges match up. This makes the subsequent FFT-based Poisson solve consistent
53# with a periodic domain.
54gx2, gy2 = enforce_periodic_gradients_stack(gx1, gy1)
55# Take the divergence of those periodic wrapped gradients; this produces the
56# right-hand side for the Poisson equation whose solution is the unwrapped phase.
57rhs = divergence_stack(gx2, gy2)
58# Solve the Poisson equation in the Fourier domain (batched over the stack);
59# this yields an unwrapped phase up to a linear ramp.
60phi = poisson_solve_fft_stack(rhs)
61# Negate to match the sign convention used by the wrapped-gradient computation
62# so that gradients of the solution align with the original wrapped gradients.
63phi *= -1
64phi_restored = restore_mean_plane(phi, phase_wrapped)
65
66# plotting
67fontsize = 12
68nrows, ncols = 4, 2
69fig = plt.figure(figsize=(8, 12))
70
71ax1 = fig.add_subplot(nrows, ncols, 1)
72ax1.set_title("Wrapped Phase Example", fontsize=fontsize)
73im1 = ax1.imshow(phase_wrapped[1])
74fig.colorbar(im1, ax=ax1, fraction=0.046, pad=0.04)
75
76ax2 = fig.add_subplot(nrows, ncols, 2, projection='3d')
77ax2.set_title("Wrapped Phase Example 3D proj", fontsize=fontsize)
78surf2 = ax2.plot_surface(x, y, phase_wrapped[1], cmap="viridis", linewidth=0, antialiased=True)
79fig.colorbar(surf2, ax=ax2, fraction=0.046, pad=0.04, shrink=0.6)
80
81ax3 = fig.add_subplot(nrows, ncols, 3)
82ax3.set_title("Wrapped Gradients + Per. Grad. (x)", fontsize=fontsize)
83im3 = ax3.imshow(gx2[1])
84fig.colorbar(im3, ax=ax3, fraction=0.046, pad=0.04)
85
86ax4 = fig.add_subplot(nrows, ncols, 4)
87ax4.set_title("Wrapped Gradients + Per. Grad. (y)", fontsize=fontsize)
88im4 = ax4.imshow(gy2[1])
89fig.colorbar(im4, ax=ax4, fraction=0.046, pad=0.04)
90
91ax5 = fig.add_subplot(nrows, ncols, 5)
92ax5.set_title("Poisson RHS", fontsize=fontsize)
93im5 = ax5.imshow(rhs[1])
94fig.colorbar(im5, ax=ax5, fraction=0.046, pad=0.04)
95
96ax6 = fig.add_subplot(nrows, ncols, 6)
97ax6.set_title("Unwrapped phase (with inversion)", fontsize=fontsize)
98im6 = ax6.imshow(phi[1])
99fig.colorbar(im6, ax=ax6, fraction=0.046, pad=0.04)
100
101ax7 = fig.add_subplot(nrows, ncols, 7)
102ax7.set_title("Unwrapped phase (restore)", fontsize=fontsize)
103im7 = ax7.imshow(phi_restored[1])
104fig.colorbar(im7, ax=ax7, fraction=0.046, pad=0.04)
105
106ax8 = fig.add_subplot(nrows, ncols, 8, projection='3d')
107ax8.set_title("Unwrapped phase (restore) 3D proj", fontsize=fontsize)
108surf8 = ax8.plot_surface(x, y, phi_restored[1], cmap="viridis", linewidth=0, antialiased=True)
109fig.colorbar(surf8, ax=ax8, fraction=0.046, pad=0.04, shrink=0.6)
110
111for ax in [ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8]:
112 ax.set_xticks([])
113 ax.set_yticks([])
114
115plt.tight_layout()
116plt.savefig("walkthrough-ls_poisson_pg.png")
117plt.show()
Download base simulation script:
unwrapping_simulation.py
LS-Poisson variants
Generate only the LS-Poisson variants figure from unwrapping_simulation. Outputs: unwrapping_comparison.png
unwrapping_simulation_poisson.py
1from unwrapping_simulation import main
2
3
4if __name__ == "__main__":
5 main(plot_itoh=False, plot_poisson=True, plot_2d_comparison=False)
Itoh and Skimage unwrapping
Generate only the Itoh/skimage comparison figure from unwrapping_simulation. This graph is a reproduction of the graph in “Two-Dimensional Phase Unwrapping Problem By Dr. Munther Gdeisat and Dr. Francis Lilley”.
Outputs: unwrapping_itoh.png
unwrapping_simulation_itoh.py
1from unwrapping_simulation import main
2
3
4if __name__ == "__main__":
5 main(plot_itoh=True, plot_poisson=False, plot_2d_comparison=False)
Phase Unwrapping Algorithm comparison
Generate only the 2D unwrapped comparison/difference maps vs skimage. Outputs: unwrapping_2d_comparison.png
unwrapping_simulation_comparison.py
1from unwrapping_simulation import main
2
3
4if __name__ == "__main__":
5 main(plot_itoh=False, plot_poisson=False, plot_2d_comparison=True)
NDArray backend and FFT Engine Selection
Demonstrates how to choose between the NumPy (CPU) and CuPy (GPU) array backends and how the FFT engine is selected automatically to match.
The FFT engine is only relevant for the least-squares Poisson solvers
(algo_ls_poisson and algo_ls_poisson_pg); the weighted solver
(algo_ls_weighted) is Jacobi-based and uses no FFT.
Run:
python examples/using_backends_and_fft-engines.py
using_backends_and_fft-engines.py
1from __future__ import annotations
2
3import numpy as np
4
5import xpunwrap
6import xpunwrap.fourier as fourier
7
8# 1. Dumm wrapped phase data (N=1, H=64, W=64)
9rng = np.random.default_rng(0)
10_tx = np.linspace(-3.0, 3.0, 64)
11_x, _y = np.meshgrid(_tx, _tx)
12_phase = (20.0 * np.exp(-0.25 * (_x ** 2 + _y ** 2)) +
13 rng.normal(0, 0.3, (64, 64)))
14wrapped_np = np.angle(np.exp(1j * _phase))[None, ...].astype(np.float32)
15
16# 2. NumPy array backend → FFT engine auto-selects pyFFTW (if installed)
17xpunwrap.set_ndarray_backend("numpy")
18xp = xpunwrap.get_ndarray_backend()
19
20fft_cls = fourier.get_best_engine()
21print(f"Array backend : {xp.__name__} (CPU)")
22print(f"FFT engine : {fft_cls.__name__}")
23
24wrapped = xp.asarray(wrapped_np)
25result_auto = xpunwrap.algo_ls_poisson(wrapped, restore_plane=False)
26print(f"Result shape : {result_auto.shape}, dtype: {result_auto.dtype}")
27
28# 3. Force the NumPy FFT engine via PREFERRED_ENGINE
29fourier.PREFERRED_ENGINE = "FFTEngineNumpy"
30fft_cls_forced = fourier.get_best_engine()
31print(f"\nArray backend : {xp.__name__} (CPU)")
32print(f"Preferred FFT : {fourier.PREFERRED_ENGINE}")
33print(f"FFT engine : {fft_cls_forced.__name__}")
34
35result_numpy_fft = xpunwrap.algo_ls_poisson(wrapped, restore_plane=False)
36print(f"Result shape : {result_numpy_fft.shape}, "
37 f"dtype: {result_numpy_fft.dtype}")
38
39# Both FFT engines produce the same answer (within float32 rounding).
40max_diff = float(np.max(np.abs(
41 np.asarray(result_auto) - np.asarray(result_numpy_fft)
42)))
43print(f"Max |auto - numpy| : {max_diff:.2e} (should be near zero)")
44
45# Reset so the rest of this script uses auto-selection again.
46fourier.PREFERRED_ENGINE = None
47
48# 4. CuPy array backend (skipped when CuPy is not installed)
49try:
50 import cupy # noqa: F401
51
52 _cupy_available = True
53except ImportError:
54 _cupy_available = False
55
56if _cupy_available:
57 xpunwrap.set_ndarray_backend("cupy")
58 xp_gpu = xpunwrap.get_ndarray_backend()
59 fft_cls_gpu = fourier.get_best_engine()
60 print(f"\nArray backend : {xp_gpu.__name__} (GPU)")
61 print(f"FFT engine : {fft_cls_gpu.__name__}")
62
63 wrapped_gpu = xp_gpu.asarray(wrapped_np)
64 result_gpu = xpunwrap.algo_ls_poisson(wrapped_gpu, restore_plane=False)
65 print(f"Result shape : {result_gpu.shape}, dtype: {result_gpu.dtype}")
66
67 # Compare GPU result against CPU result.
68 max_diff_gpu = float(np.max(np.abs(
69 np.asarray(result_auto) - result_gpu.get()
70 )))
71 print(f"Max |cpu - gpu| : {max_diff_gpu:.2e}")
72else:
73 print("\nCuPy not installed — GPU section skipped.")
74
75# 5. Use xp directly for array operations alongside the unwrapper
76xpunwrap.set_ndarray_backend("numpy")
77xp = xpunwrap.get_ndarray_backend()
78
79wrapped = xp.asarray(wrapped_np)
80result = xpunwrap.algo_ls_poisson(wrapped, restore_plane=False)
81
82# All array ops go through xp, so this code is backend-agnostic.
83residual = xp.angle(xp.exp(1j * result)) - xp.angle(xp.exp(1j * wrapped))
84print(f"\nMean abs residual (wrapped): {float(
85 xp.mean(xp.abs(residual))):.4f} rad")
86
87"""
88Example output:
89
90Array backend : numpy (CPU)
91FFT engine : FFTEnginePyFFTW
92Result shape : (1, 64, 64), dtype: float32
93
94Array backend : numpy (CPU)
95Preferred FFT : FFTEngineNumpy
96FFT engine : FFTEngineNumpy
97Result shape : (1, 64, 64), dtype: float32
98Max |auto - numpy| : 4.77e-06 (should be near zero)
99
100Array backend : cupy (GPU)
101FFT engine : FFTEngineCupy
102Result shape : (1, 64, 64), dtype: float32
103Max |cpu - gpu| : 4.29e-06
104
105Mean abs residual (wrapped): 0.1254 rad
106"""
Processing Pipeline with GPU and Zarr
Example processing pipeline for phase data on the GPU: - Load with Zarr in GPU - Field retrieval - Phase unwrapping - Write to Zarr
processing_pipeline_gpu.py
1import zarr
2import qpretrieve
3import xpunwrap
4
5# set cupy backend
6xpunwrap.set_ndarray_backend("cupy")
7qpretrieve.set_ndarray_backend("cupy")
8xp = xpunwrap.get_ndarray_backend()
9
10# load data with zarr via GPU memory
11zarr.config.enable_gpu()
12
13# load to cpu (mightn't need this)
14edata = xp.load("./data/hologram_cell.npz")
15
16# load to gpu
17# caveat: Zarr loads the data into GPU, but encoding and decoding happens
18# on the CPU. It is a work in progress.
19# One can use `kvikio` to directly do GPU loading, but
20# `pip install kvikio-cu12` only works on linux, or use conda/mamba to install
21# on Windows, I assume. See https://docs.rapids.ai/api/kvikio/stable/zarr/
22store_holo = zarr.storage.MemoryStore()
23store_bg = zarr.storage.MemoryStore()
24data_holo = zarr.create_array(store=store_holo, data=edata["data"])
25data_bg = zarr.create_array(store=store_bg, data=edata["bg_data"])
26
27# field retrieval on the GPU
28fft_interface = qpretrieve.fourier.FFTFilterCupy
29holo = qpretrieve.OffAxisHologram(data_holo[:], fft_interface)
30bg = qpretrieve.OffAxisHologram(data_bg[:], fft_interface)
31holo.run_pipeline(filter_name="disk", filter_size=1 / 2, scale_to_filter=True)
32bg.process_like(holo)
33phase_wrapped = xp.asarray(holo.phase - bg.phase).astype(xp.float32)
34
35# unwrap the phase on the GPU
36phase_unwrapped = xpunwrap.algo_ls_poisson_pg(phase_wrapped)
37
38# write from GPU to Zarr file
39path = "temp_unwrapped_cell_phase.zip"
40with zarr.storage.ZipStore(path, mode='w') as disk:
41 data_phase = zarr.create_array(store=disk, data=phase_unwrapped)
42
43# reopen the file to ensure it is correct
44with zarr.storage.ZipStore(path, mode='r') as disk_reload:
45 data_reload = zarr.open_array(store=disk_reload, mode='r')
46 assert data_phase.shape == data_reload.shape
47 assert xp.allclose(phase_unwrapped, data_reload[:])