Fourier ptychography tooling for simulation and reconstruction
Project description
fpm-py
A Python library for Fourier Ptychographic Microscopy (FPM) simulation and reconstruction with GPU acceleration.
Overview
This library implements both the forward pass (reconstruction from captures) and backward pass (simulation of captures from an object) for Fourier ptychography. It uses PyTorch for efficient GPU-accelerated computation and provides a clean, type-safe API for research and development.
Fourier ptychography synthesizes a high-resolution, wide-field image by computationally combining multiple low-resolution images captured under varying illumination angles. This creates a synthetic aperture larger than the physical objective NA, enabling resolution enhancement while maintaining a large field of view.
Installation
pip install fpm-py
Or with uv (preferred):
uv add fpm-py
Requirements: 3.12 ≥ Python ≥ 3.9, PyTorch ≥ 2.8.0
Quick Start
import fpm_py as fpm
from fpm_py.utils.data_utils import image_to_tensor
import torch
# Load a test object
obj = image_to_tensor("path/to/image.png", to_complex=True)
# Define k-vectors (illumination angles) - 5×5 grid
grid_size = 5
spacing = 0.2
k_range = torch.linspace(-(grid_size // 2), grid_size // 2, grid_size) * spacing
kx, ky = torch.meshgrid(k_range, k_range, indexing="ij")
k_vectors = torch.stack((kx.flatten(), ky.flatten()), dim=1)
# Simulate FPM captures (backward pass)
dataset = fpm.kvectors_to_image_series(
obj=obj,
k_vectors=k_vectors,
pupil_radius=100,
wavelength=550e-9, # 550 nm (green light)
pixel_size=1e-6, # 1 μm pixels
magnification=10.0 # 10× objective
)
# Reconstruct high-resolution image (forward pass)
reconstruction = fpm.reconstruct(dataset, output_scale_factor=4, max_iters=10)
# View magnitude
import matplotlib.pyplot as plt
plt.imshow(reconstruction.abs().cpu().numpy(), cmap='gray')
plt.show()
For a more detailed walkthrough, see main.py.
Core Concepts
Data Structures
ImageCapture
Represents a single FPM capture with its associated k-vector:
@dataclass(frozen=True)
class ImageCapture:
image: torch.Tensor # (H, W) intensity image
k_vector: torch.Tensor # (2,) illumination angle in k-space
ImageSeries
Container for multiple captures with acquisition settings:
@dataclass
class ImageSeries:
captures: Sequence[ImageCapture]
settings: AcquisitionSettings # du, wavelength, pixel_size
device: torch.device
The du parameter is the k-space lattice spacing — it relates pixel coordinates in Fourier space to physical spatial frequencies. Automatically computed as:
du = wavelength / (N × effective_pixel_size)
where effective_pixel_size = pixel_size / magnification.
Backward Pass: Simulation
Generate synthetic FPM captures from a known object (inverse of reconstruction):
series = fpm.kvectors_to_image_series(
obj=complex_field, # Complex object (amplitude + phase)
k_vectors=k_vectors, # (N, 2) illumination directions
pupil_radius=100, # Pupil radius in pixels (related to NA)
wavelength=550e-9, # Illumination wavelength [m]
pixel_size=1e-6, # Camera pixel size [m]
magnification=10.0, # Optical magnification
pupil=None # Optional custom pupil function
)
Physics: For each k-vector (kx, ky), the backward pass:
- Applies a tilted plane wave:
obj × exp(i(kx·x + ky·y)) - Computes Fourier transform and applies pupil mask (optical transfer function)
- Inverse transforms back to spatial domain
- Records intensity:
I = |wave|²
This simulates the effect of different illuminations in a physical FPM system, given a 'gold standard' image (ie. target reconstruction).
Forward Pass: Reconstruction
Recover the high-resolution complex field from captures (the primary FPM algorithm):
reconstructed = fpm.reconstruct(
series=image_series,
output_scale_factor=4, # Output is 4× larger than input captures
max_iters=10, # Number of iterative refinement passes
pupil_0=None # Optional initial pupil estimate
)
Returns: Complex-valued tensor in spatial domain with shape (H_out, W_out) where the output size is either:
output_scale_factor × input_size(if specified)- Auto-calculated minimum size for full k-space coverage
- Explicitly set via
output_image_size=(H, W)
Reconstruction Algorithm
The implementation uses a quasi-Newton phase retrieval approach based on Tian et al. (2014):
- Initialization: First capture initializes the high-res spectrum
- Iterative Update Loop: For each capture at k-vector
(kx, ky):- Extract relevant Fourier patch from global object estimate
- Apply pupil function and transform to spatial domain
- Intensity constraint: Replace estimated magnitude with √(measured intensity)
- Back-propagate: Transform corrected wave to Fourier domain
- Joint optimization: Update both object spectrum and pupil using quasi-Newton step
- Support constraint: Enforce binary pupil support
The quasi-Newton update (Tian's method) balances updates between object and pupil:
# Object update
obj_update = pupil* · Δwave / (|pupil|² + α)
# Pupil update
pupil_update = obj* · Δwave / (|obj|² + β)
where Δwave = wave_corrected - wave_estimated and α, β are regularization parameters.
Key parameters:
alpha=1.0: Object update regularization (prevents division by zero)beta=1000.0: Pupil update regularization (pupil converges slower than object)
Converting Physical LED Positions
If you have physical LED array coordinates, convert them to k-vectors:
import numpy as np
# LED positions in 3D space (meters), with (0,0,0) at optical axis
led_positions = np.array([
[0.01, 0.0, 0.05], # x, y, z for each LED
[0.0, 0.01, 0.05],
# ... more LEDs
])
k_vectors = fpm.spatial_to_kvectors(
led_positions=led_positions,
wavelength=550e-9,
pixel_size=1e-6,
sample_to_lens_distance=0.05, # 50 mm
lens_to_sensor_distance=0.50 # 500 mm (10× magnification)
)
# Now use k_vectors with kvectors_to_image_series()
Evaluation & Visualization
Compare reconstruction quality:
from fpm_py import plot_comparison_with_histograms
# Create comparison plots with histograms and Fourier spectra
fig, stats = plot_comparison_with_histograms(
images=[target, recon_1iter, recon_10iter],
titles=["Ground Truth", "1 Iteration", "10 Iterations"],
reference_idx=0 # Use first image as reference for stat calculations
)
plt.show()
# Access quantitative metrics
print(f"SSIM: {stats['ssim']}")
print(f"PSNR: {stats['psnr']}")
The stats dictionary includes:
- SSIM: Structural similarity index
- PSNR: Peak signal-to-noise ratio
- MSE/RMSE: Mean squared error metrics
- NCC: Normalized cross-correlation
- Plus histogram statistics (mean, std, skewness, kurtosis, entropy)
Implementation Details
Device Management
The library automatically selects the best available device (CUDA > MPS > CPU):
from fpm_py import best_device
device = best_device() # Auto-selects GPU if available
series = series.to(device) # Move data to device
FFT Conventions
All Fourier transforms use centered FFTs via fftshift/ifftshift:
def ft(x):
return torch.fft.fftshift(torch.fft.fft2(torch.fft.ifftshift(x)))
def ift(x):
return torch.fft.ifftshift(torch.fft.ifft2(torch.fft.fftshift(x)))
This ensures DC component is at the array center, matching the physics convention where k=0 is the optical axis.
Memory Optimization
- All computations use
torch.no_grad()(no autograd overhead) - Float32 precision throughout (GPU-optimized)
- In-place operations where possible (
add_,mul_,div_) - Output dimensions forced to even numbers for FFT efficiency
Coordinate System
k-space: The k-vector (kx, ky) represents the transverse component of the wavevector, scaled by effective pixel size:
k_scaled = (2π/λ) sin(θ) × (pixel_size / magnification)
where θ is the illumination angle from the optical axis.
Fourier patch extraction: For k-vector (kx, ky), the crop location in the global spectrum is:
x = center_x + int(kx / du)
y = center_y + int(ky / du)
This shift-and-multiply theorem maps illumination angles to Fourier space shifts.
Architecture
src/fpm_py/
├── core/
│ ├── structs.py # Data structures (ImageCapture, ImageSeries)
│ ├── forward.py # Reconstruction algorithm (forward pass)
│ └── backward.py # Simulation (backward pass)
├── utils/
│ ├── math_utils.py # FFT helpers, geometry, overlap operations
│ └── data_utils.py # Image loading, device selection
└── analysis/
└── metrics.py # SSIM, PSNR, visualization
Note: The naming convention:
forward.py= reconstruction (captures → high-res image) - the forward passbackward.py= simulation (high-res image → captures) - the backward pass (inverse of reconstruction)
References
This implementation is based on:
- Tian et al. (2014) - "Multiplexed coded illumination for Fourier Ptychography with an LED array microscope" - Quasi-Newton optimization
- Zheng et al. (2013) - "Wide-field, high-resolution Fourier ptychographic microscopy" - Original FPM paper
- Ou et al. (2015) - "Embedded pupil function recovery for Fourier ptychographic microscopy" - Pupil recovery method
For the mathematical foundations of Fourier ptychography, see: https://en.wikipedia.org/wiki/Fourier_ptychography
Example: Full Pipeline
See main.py for a complete working example that:
- Loads a test image (USAF resolution target)
- Generates a 5×5 grid of k-vectors
- Simulates FPM captures
- Reconstructs with 1 and 10 iterations
- Visualizes results with comparison metrics
License
MIT License - See LICENSE file for details.
Contributing
Issues and pull requests welcome at: https://github.com/rspcunningham/fpm-py
Version: 2.0.0 | Author: Robin Cunningham
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fpm_py-2.0.1.tar.gz.
File metadata
- Download URL: fpm_py-2.0.1.tar.gz
- Upload date:
- Size: 23.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8d55e4b45392fd5b9b9610bcae7fa167e8057a88d528f8919ba6c090a1a83a2
|
|
| MD5 |
180ea8910bead7a1782cb3e5db28ca08
|
|
| BLAKE2b-256 |
66b3eb70eae208493cbb337e4eb68430a97d3e5fa342d4c3f90c4d5c07674443
|
Provenance
The following attestation bundles were made for fpm_py-2.0.1.tar.gz:
Publisher:
publish.yml on rspcunningham/fpm-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fpm_py-2.0.1.tar.gz -
Subject digest:
c8d55e4b45392fd5b9b9610bcae7fa167e8057a88d528f8919ba6c090a1a83a2 - Sigstore transparency entry: 591451855
- Sigstore integration time:
-
Permalink:
rspcunningham/fpm-py@a673dd0fb22e3dfcbda2ead291e2a6acf44e05fc -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/rspcunningham
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a673dd0fb22e3dfcbda2ead291e2a6acf44e05fc -
Trigger Event:
release
-
Statement type:
File details
Details for the file fpm_py-2.0.1-py3-none-any.whl.
File metadata
- Download URL: fpm_py-2.0.1-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a2708de8c48951b5e4d340046a1ca2237ba06264fa3e94a185d9562829e688d
|
|
| MD5 |
514df436841f648c4927f4e16021fd80
|
|
| BLAKE2b-256 |
09f19ef59f2427bae9b1d53c5318e3a6d0236f4d81d6e552a66d7d807094df58
|
Provenance
The following attestation bundles were made for fpm_py-2.0.1-py3-none-any.whl:
Publisher:
publish.yml on rspcunningham/fpm-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fpm_py-2.0.1-py3-none-any.whl -
Subject digest:
6a2708de8c48951b5e4d340046a1ca2237ba06264fa3e94a185d9562829e688d - Sigstore transparency entry: 591451868
- Sigstore integration time:
-
Permalink:
rspcunningham/fpm-py@a673dd0fb22e3dfcbda2ead291e2a6acf44e05fc -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/rspcunningham
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a673dd0fb22e3dfcbda2ead291e2a6acf44e05fc -
Trigger Event:
release
-
Statement type: