-
Notifications
You must be signed in to change notification settings - Fork 0
03. Holography Methods
HoloGen supports two fundamental approaches to digital holography: inline and off-axis. Each method has distinct optical configurations, reconstruction algorithms, and use cases. This document explains the principles behind each method, their advantages and limitations, and how to use them effectively in your synthetic dataset generation.
Digital holography is a technique for recording and reconstructing optical wavefronts using digital sensors. Unlike conventional imaging, holography captures both the amplitude and phase of light, enabling three-dimensional information recovery and quantitative phase measurements.
Holography involves three main steps:
- Recording: An object wave interferes with a reference wave at the sensor plane, creating an interference pattern (hologram)
- Propagation: Light waves travel from the object plane to the sensor plane, governed by wave optics
- Reconstruction: The object wave is numerically recovered from the hologram using computational algorithms
Different holography configurations offer different trade-offs:
- Inline holography: Simple optical setup, but reconstruction suffers from twin-image problem
- Off-axis holography: More complex setup with carrier frequency, but enables clean separation of diffraction orders
The choice depends on your application requirements, optical constraints, and reconstruction quality needs.
Optical configuration: Object and reference waves propagate along the same optical axis
Key characteristics:
- Simplest optical setup (no beam splitter required)
- Reference wave is the unscattered illumination
- Hologram contains overlapping diffraction orders
- Reconstruction affected by twin-image artifact
- Suitable for sparse or weakly scattering objects
Mathematical representation:
Hologram intensity: I = |O + R|² = |O|² + |R|² + O*R + OR*
↑ ↑ ↑ ↑
object DC +1 order -1 order
term term (signal) (twin)
Optical configuration: Reference wave arrives at an angle, introducing a spatial carrier frequency
Key characteristics:
- Requires beam splitter and angled reference
- Carrier frequency separates diffraction orders in Fourier space
- Clean reconstruction without twin-image
- Requires higher spatial resolution
- Suitable for dense or strongly scattering objects
Mathematical representation:
Hologram intensity: I = |O + R·exp(i·k·r)|²
= |O|² + |R|² + O*R·exp(i·k·r) + OR*·exp(-i·k·r)
↑ ↑
+1 order (signal) -1 order (twin)
shifted by +k shifted by -k
Inline holography, also known as Gabor holography, uses the simplest optical configuration. The object is illuminated by a plane wave, and the transmitted (or scattered) light interferes with the unscattered portion of the illumination at the sensor plane.
Optical setup:
Plane wave → Object → Propagation → Sensor
(O) (distance z) (I)
The object wave O and reference wave R (unscattered illumination) propagate together along the optical axis and interfere at the sensor.
Interference pattern:
I(x,y) = |O(x,y) + R|²
= |O|² + |R|² + O*R + OR*
Where:
-
|O|²: Object intensity (DC term) -
|R|²: Reference intensity (DC term) -
O*R: First-order diffraction term (desired signal) -
OR*: Conjugate term (twin image)
- Simple optical setup: No beam splitter, mirrors, or alignment required
- Compact configuration: Minimal optical components
- High light efficiency: No light loss from beam splitting
- Easy to implement: Straightforward experimental realization
- Suitable for sparse objects: Works well when object occupies small portion of field
- Twin-image problem: Conjugate term creates out-of-focus artifact in reconstruction
- DC terms: Zero-order terms reduce contrast and dynamic range
- Limited to weak scattering: Strong scattering objects create severe artifacts
- Overlapping orders: All diffraction orders overlap spatially
- Reconstruction ambiguity: Cannot cleanly separate signal from artifacts
from hologen.types import GridSpec, OpticalConfig, HolographyConfig, HolographyMethod
# Grid specification
grid = GridSpec(
height=512, # Sensor height in pixels
width=512, # Sensor width in pixels
pixel_pitch=6.4e-6 # Pixel size in meters (6.4 μm)
)
# Optical parameters
optics = OpticalConfig(
wavelength=532e-9, # Illumination wavelength in meters (532 nm, green)
propagation_distance=0.05 # Object-to-sensor distance in meters (50 mm)
)
# Holography configuration
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.INLINE,
carrier=None # No carrier for inline holography
)Parameter guidelines:
- wavelength: Typical values 400-700 nm (visible light), 1064 nm (IR laser)
-
propagation_distance:
- Too small (< 10 mm): Minimal diffraction, poor hologram formation
- Optimal (10-100 mm): Good fringe visibility and reconstruction
- Too large (> 500 mm): Requires very high resolution, sampling issues
- pixel_pitch: Must satisfy Nyquist criterion (see Angular Spectrum Propagation section)
Best suited for:
- Sparse objects (particles, cells, small features)
- Weakly scattering samples
- Compact optical setups
- Real-time holography applications
- Educational demonstrations
- Preliminary experiments
Not recommended for:
- Dense objects covering large field of view
- Strongly scattering samples
- Applications requiring high reconstruction quality
- Quantitative phase imaging of complex samples
from hologen.holography.inline import InlineHolographyStrategy
from hologen.types import HolographyConfig, GridSpec, OpticalConfig, HolographyMethod
import numpy as np
# Create configuration
grid = GridSpec(height=512, width=512, pixel_pitch=6.4e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
config = HolographyConfig(grid=grid, optics=optics, method=HolographyMethod.INLINE)
# Create strategy
strategy = InlineHolographyStrategy()
# Generate hologram from object field
object_field = np.ones((512, 512), dtype=np.complex128) # Example object
hologram = strategy.create_hologram(object_field, config)
# Reconstruct object from hologram
reconstruction = strategy.reconstruct(hologram, config)
print(f"Hologram shape: {hologram.shape}")
print(f"Hologram dtype: {hologram.dtype}")
print(f"Reconstruction shape: {reconstruction.shape}")Off-axis holography introduces a spatial carrier frequency by angling the reference wave relative to the object wave. This angular offset shifts the diffraction orders in Fourier space, enabling clean separation during reconstruction.
Optical setup:
┌─ Object → Propagation ─┐
Beam Splitter ──────┤ ├─→ Sensor
└─ Reference (angled) ────┘
The reference wave arrives at an angle θ, creating a spatial carrier:
R(x,y) = exp(i·k·r) = exp(i·2π·(fx·x + fy·y))
Where:
-
k = (kx, ky): Carrier wave vector -
fx, fy: Spatial carrier frequencies (cycles per meter) - Carrier frequency magnitude:
f = (2/λ)·sin(θ/2)
Interference pattern:
I(x,y) = |O(x,y) + R·exp(i·k·r)|²
= |O|² + |R|² + O*R·exp(i·k·r) + OR*·exp(-i·k·r)
In Fourier space, the three terms are spatially separated:
- DC terms: Centered at origin (0, 0)
- +1 order: Shifted to (+fx, +fy) - desired signal
- -1 order: Shifted to (-fx, -fy) - twin image
- Twin-image elimination: Spatial separation enables clean reconstruction
- High quality: No overlapping artifacts in reconstruction
- Quantitative phase: Accurate phase measurements possible
- Dense objects: Works well for complex, strongly scattering samples
- Fourier filtering: Simple bandpass filter isolates signal
- Complex optical setup: Requires beam splitter, mirrors, precise alignment
- Higher resolution required: Carrier frequency demands finer sampling
- Reduced field of view: Carrier consumes spatial bandwidth
- Alignment sensitivity: Misalignment affects reconstruction quality
- Lower light efficiency: Beam splitting reduces signal
from hologen.types import (
GridSpec, OpticalConfig, HolographyConfig,
HolographyMethod, OffAxisCarrier
)
# Grid specification (higher resolution than inline)
grid = GridSpec(
height=512,
width=512,
pixel_pitch=3.2e-6 # Smaller pixels for carrier sampling
)
# Optical parameters
optics = OpticalConfig(
wavelength=532e-9,
propagation_distance=0.05
)
# Carrier configuration
carrier = OffAxisCarrier(
frequency_x=1e6, # Carrier frequency in x (cycles/m)
frequency_y=0, # Carrier frequency in y (cycles/m)
gaussian_width=2e5 # Filter width (cycles/m)
)
# Holography configuration
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.OFF_AXIS,
carrier=carrier
)Parameter guidelines:
-
frequency_x, frequency_y: Carrier spatial frequencies
- Must satisfy:
sqrt(fx² + fy²) < 1/(2·pixel_pitch)(Nyquist) - Typical: 10-50% of Nyquist frequency
- Example: For pixel_pitch=3.2μm, max frequency ≈ 1.56e5 cycles/m
- Common choice:
fx = 1e6 cycles/m(well below Nyquist)
- Must satisfy:
-
gaussian_width: Fourier filter bandwidth
- Too narrow: Signal loss, reduced resolution
- Too wide: Includes DC or twin-image contamination
- Typical: 20-40% of carrier frequency
- Example: For
fx=1e6, usegaussian_width=2e5 to 4e5
The carrier frequency determines the spatial separation in Fourier space. Choose based on:
- Object bandwidth: Carrier must exceed object's spatial frequency content
- Sampling constraint: Carrier + object bandwidth must fit within Nyquist limit
- Separation margin: Sufficient gap between orders to avoid overlap
Rule of thumb:
carrier_frequency ≥ 2 × max_object_frequency
carrier_frequency + max_object_frequency < Nyquist_frequency
Example calculation:
import numpy as np
# System parameters
pixel_pitch = 3.2e-6 # meters
wavelength = 532e-9 # meters
grid_size = 512 # pixels
# Nyquist frequency
nyquist_freq = 1 / (2 * pixel_pitch) # 1.56e5 cycles/m
# Object bandwidth (estimate from object size)
object_size = 50e-6 # 50 μm object
max_object_freq = 1 / object_size # 2e4 cycles/m
# Carrier frequency (2x object bandwidth + margin)
carrier_freq = 2.5 * max_object_freq # 5e4 cycles/m
# Verify constraint
assert carrier_freq + max_object_freq < nyquist_freq
print(f"Carrier frequency: {carrier_freq:.2e} cycles/m")
print(f"Nyquist frequency: {nyquist_freq:.2e} cycles/m")
print(f"Margin: {(nyquist_freq - carrier_freq - max_object_freq)/nyquist_freq * 100:.1f}%")Reconstruction uses Gaussian filtering in Fourier space to isolate the +1 diffraction order:
# Fourier transform of hologram intensity
spectrum = np.fft.fft2(hologram_intensity)
# Create Gaussian filter centered at carrier frequency
filter_mask = exp(-((fx - carrier_x)² + (fy - carrier_y)²) / (2·σ²))
# Apply filter and inverse transform
filtered_spectrum = spectrum * filter_mask
object_field = np.fft.ifft2(filtered_spectrum)The Gaussian width σ controls the filter bandwidth:
- Narrow filter: High selectivity, but may clip object spectrum
- Wide filter: Preserves object spectrum, but may include noise
Best suited for:
- Dense objects covering significant field of view
- Strongly scattering samples
- Quantitative phase imaging applications
- High-quality reconstruction requirements
- Research-grade holographic microscopy
- Applications where optical complexity is acceptable
Not recommended for:
- Compact or portable systems
- Real-time applications with limited computation
- Low-resolution sensors
- Applications requiring maximum field of view
from hologen.holography.off_axis import OffAxisHolographyStrategy
from hologen.types import (
HolographyConfig, GridSpec, OpticalConfig,
HolographyMethod, OffAxisCarrier
)
import numpy as np
# Create configuration with carrier
grid = GridSpec(height=512, width=512, pixel_pitch=3.2e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
carrier = OffAxisCarrier(
frequency_x=1e6,
frequency_y=0,
gaussian_width=2e5
)
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.OFF_AXIS,
carrier=carrier
)
# Create strategy
strategy = OffAxisHolographyStrategy()
# Generate hologram from object field
object_field = np.ones((512, 512), dtype=np.complex128) # Example object
hologram = strategy.create_hologram(object_field, config)
# Reconstruct object from hologram
reconstruction = strategy.reconstruct(hologram, config)
print(f"Hologram shape: {hologram.shape}")
print(f"Carrier frequency: ({carrier.frequency_x:.2e}, {carrier.frequency_y:.2e}) cycles/m")
print(f"Reconstruction shape: {reconstruction.shape}")| Feature | Inline Holography | Off-Axis Holography |
|---|---|---|
| Optical Setup | Simple (no beam splitter) | Complex (beam splitter + alignment) |
| Reference Wave | Unscattered illumination | Angled plane wave |
| Carrier Frequency | None | Required |
| Twin Image | Present (overlapping) | Separated (removable) |
| Reconstruction Quality | Lower (artifacts) | Higher (clean) |
| Resolution Requirement | Standard | Higher (for carrier) |
| Field of View | Maximum | Reduced (carrier bandwidth) |
| Computation | Simple back-propagation | Fourier filtering + back-propagation |
| Best For | Sparse objects | Dense objects |
| Typical Applications | Particle tracking, simple imaging | Quantitative phase imaging, microscopy |
| Light Efficiency | High (no splitting) | Lower (beam splitting) |
| Alignment Sensitivity | Low | High |
| Real-time Capability | Easier | More challenging |
Choose inline holography when:
- Optical setup must be simple and compact
- Objects are sparse or weakly scattering
- Twin-image artifacts are acceptable
- Maximum field of view is needed
- Real-time processing is required
- Educational or demonstration purposes
- Preliminary experiments or prototyping
Choose off-axis holography when:
- High reconstruction quality is critical
- Objects are dense or strongly scattering
- Quantitative phase measurements are needed
- Twin-image artifacts are unacceptable
- Optical complexity is acceptable
- Sufficient spatial resolution is available
- Research-grade results are required
- Publication-quality images are needed
Some applications benefit from combining both methods:
- Inline for screening, off-axis for detailed analysis: Use inline for rapid screening, then off-axis for selected samples
- Dual-mode systems: Switchable configuration supporting both methods
- Computational methods: Use inline acquisition with computational twin-image removal
Both inline and off-axis holography use the angular spectrum method for wave propagation. This section explains the underlying physics and implementation.
The angular spectrum method decomposes an optical field into plane waves, propagates each plane wave independently, and recombines them at the observation plane.
Mathematical formulation:
-
Decompose field into plane waves (Fourier transform):
Ã(fx, fy, z=0) = FFT[A(x, y, z=0)] -
Propagate each plane wave by distance z:
Ã(fx, fy, z) = Ã(fx, fy, 0) · H(fx, fy, z)Where the transfer function is:
H(fx, fy, z) = exp(i·kz·z)And the longitudinal wave vector component is:
kz = k·sqrt(1 - (λ·fx)² - (λ·fy)²) for propagating waves kz = i·k·sqrt((λ·fx)² + (λ·fy)² - 1) for evanescent waves -
Recompose field in spatial domain (inverse Fourier transform):
A(x, y, z) = IFFT[Ã(fx, fy, z)]
The angular spectrum distinguishes two types of plane waves:
Propagating waves (real kz):
- Condition:
(λ·fx)² + (λ·fy)² < 1 - Behavior: Oscillate with phase
exp(i·kz·z) - Carry energy to observation plane
- Contribute to far-field pattern
Evanescent waves (imaginary kz):
- Condition:
(λ·fx)² + (λ·fy)² ≥ 1 - Behavior: Decay exponentially with distance
- Do not propagate to far field
- Contain sub-wavelength information
The angular spectrum method requires proper spatial sampling to avoid aliasing:
Sampling constraint:
pixel_pitch < λ / 2
More precisely, the maximum representable spatial frequency must be less than the physical limit:
fmax = 1 / (2·pixel_pitch) < 1 / λ
Example validation:
wavelength = 532e-9 # 532 nm
pixel_pitch = 6.4e-6 # 6.4 μm
max_spatial_freq = 1 / (2 * pixel_pitch) # 7.8e4 cycles/m
physical_limit = 1 / wavelength # 1.88e6 cycles/m
normalized_freq = max_spatial_freq * wavelength # 0.042
assert normalized_freq < 1.0, "Nyquist criterion violated!"
print(f"Sampling is valid: {normalized_freq:.3f} < 1.0")HoloGen's angular_spectrum_propagate() function handles:
- Validation: Checks field shape matches grid dimensions
- Short-circuit: Returns input for zero distance
- Nyquist check: Validates sampling criterion
- Frequency grid: Computes spatial frequency coordinates
- Transfer function: Calculates propagation kernel
- Evanescent handling: Applies exponential decay for evanescent components
- FFT propagation: Applies kernel in Fourier domain
- Type preservation: Returns complex128 array
Function signature:
def angular_spectrum_propagate(
field: ArrayComplex,
grid: GridSpec,
optics: OpticalConfig,
distance: float,
) -> ArrayComplex:
"""Propagate complex optical field using angular spectrum method.
Args:
field: Complex field at source plane, shape (grid.height, grid.width)
grid: Spatial sampling specification
optics: Optical parameters (wavelength)
distance: Propagation distance in meters (positive = forward, negative = backward)
Returns:
Complex field at observation plane
Raises:
ValueError: If field shape doesn't match grid or Nyquist criterion violated
"""The angular spectrum method supports bidirectional propagation:
Forward propagation (positive distance):
- Object plane → Sensor plane
- Used in hologram generation
- Distance:
z = propagation_distance
Backward propagation (negative distance):
- Sensor plane → Object plane
- Used in reconstruction
- Distance:
z = -propagation_distance
Example:
from hologen.holography.propagation import angular_spectrum_propagate
from hologen.types import GridSpec, OpticalConfig
import numpy as np
# Configuration
grid = GridSpec(height=512, width=512, pixel_pitch=6.4e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
# Object field
object_field = np.ones((512, 512), dtype=np.complex128)
# Forward propagation (object → sensor)
sensor_field = angular_spectrum_propagate(
field=object_field,
grid=grid,
optics=optics,
distance=optics.propagation_distance # +0.05 m
)
# Backward propagation (sensor → object)
reconstructed_field = angular_spectrum_propagate(
field=sensor_field,
grid=grid,
optics=optics,
distance=-optics.propagation_distance # -0.05 m
)
# Verify round-trip
error = np.abs(reconstructed_field - object_field).max()
print(f"Round-trip error: {error:.2e}") # Should be very smallToo short (< 1 mm):
- Minimal diffraction
- Hologram looks like object
- Poor fringe formation
- Limited depth information
Optimal (10-100 mm):
- Good fringe visibility
- Clear diffraction patterns
- Reasonable sampling requirements
- Practical optical setup
Too long (> 500 mm):
- Very fine fringes
- High resolution required
- Sampling challenges
- Large optical setup
Fresnel number as guide:
F = a² / (λ·z)
Where:
-
a: Object characteristic size -
λ: Wavelength -
z: Propagation distance -
F >> 1: Near field (geometric optics) -
F ≈ 1: Fresnel region (optimal for holography) -
F << 1: Far field (Fraunhofer diffraction)
The HolographyStrategy protocol defines the interface for holography implementations.
from typing import Protocol
from hologen.types import ArrayComplex, HolographyConfig
class HolographyStrategy(Protocol):
"""Protocol for holography strategy implementations.
Implementations must provide methods for hologram generation
and reconstruction using specific holography techniques.
"""
def create_hologram(
self,
object_field: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Generate hologram from object-domain complex field.
Args:
object_field: Complex field at object plane, shape (H, W)
config: Holography configuration including grid, optics, method
Returns:
Complex field at sensor plane (hologram)
Raises:
ValueError: If configuration is invalid for this strategy
"""
...
def reconstruct(
self,
hologram: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Reconstruct object-domain field from hologram.
Args:
hologram: Complex field at sensor plane
config: Holography configuration (must match creation config)
Returns:
Complex field at object plane (reconstruction)
Raises:
ValueError: If configuration is invalid for this strategy
"""
...Implementation of inline (Gabor) holography.
from hologen.holography.inline import InlineHolographyStrategy
class InlineHolographyStrategy:
"""Inline holography implementation.
Generates holograms by propagating the object field to the sensor plane
without additional reference wave modulation. Reconstruction uses
back-propagation to the object plane.
This method produces holograms with overlapping diffraction orders,
resulting in twin-image artifacts in reconstruction.
"""
def create_hologram(
self,
object_field: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Generate inline hologram.
Process:
1. Propagate object field to sensor plane using angular spectrum
2. Return complex field (interference with unscattered wave implicit)
Args:
object_field: Complex object field, shape (H, W)
config: Configuration with grid, optics, method=INLINE
Returns:
Complex hologram field at sensor plane
Example:
>>> strategy = InlineHolographyStrategy()
>>> hologram = strategy.create_hologram(object_field, config)
"""
def reconstruct(
self,
hologram: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Reconstruct object from inline hologram.
Process:
1. Back-propagate hologram field to object plane
2. Return reconstructed complex field
Note: Reconstruction contains twin-image artifacts due to
overlapping diffraction orders.
Args:
hologram: Complex hologram field, shape (H, W)
config: Configuration (must match creation config)
Returns:
Complex reconstructed field at object plane
Example:
>>> reconstruction = strategy.reconstruct(hologram, config)
"""Usage example:
from hologen.holography.inline import InlineHolographyStrategy
from hologen.types import GridSpec, OpticalConfig, HolographyConfig, HolographyMethod
import numpy as np
# Setup
grid = GridSpec(height=512, width=512, pixel_pitch=6.4e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
config = HolographyConfig(grid=grid, optics=optics, method=HolographyMethod.INLINE)
# Create strategy
strategy = InlineHolographyStrategy()
# Generate and reconstruct
object_field = np.ones((512, 512), dtype=np.complex128)
hologram = strategy.create_hologram(object_field, config)
reconstruction = strategy.reconstruct(hologram, config)Implementation of off-axis holography with spatial carrier.
from hologen.holography.off_axis import OffAxisHolographyStrategy
class OffAxisHolographyStrategy:
"""Off-axis holography implementation.
Generates holograms by adding a tilted reference wave to the propagated
object field. Reconstruction uses Fourier filtering to isolate the
first-order diffraction term, followed by back-propagation.
This method produces clean reconstructions without twin-image artifacts.
"""
def create_hologram(
self,
object_field: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Generate off-axis hologram.
Process:
1. Propagate object field to sensor plane
2. Generate tilted reference wave with carrier frequency
3. Add reference to propagated object field
4. Return complex interference pattern
Args:
object_field: Complex object field, shape (H, W)
config: Configuration with carrier parameters
Returns:
Complex hologram field with carrier modulation
Raises:
ValueError: If config.carrier is None
Example:
>>> strategy = OffAxisHolographyStrategy()
>>> hologram = strategy.create_hologram(object_field, config)
"""
def reconstruct(
self,
hologram: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Reconstruct object from off-axis hologram.
Process:
1. Convert hologram to intensity
2. Fourier transform to frequency domain
3. Apply Gaussian filter centered at carrier frequency
4. Inverse Fourier transform
5. Back-propagate to object plane
Args:
hologram: Complex hologram field, shape (H, W)
config: Configuration with carrier parameters
Returns:
Complex reconstructed field (clean, no twin image)
Raises:
ValueError: If config.carrier is None
Example:
>>> reconstruction = strategy.reconstruct(hologram, config)
"""Usage example:
from hologen.holography.off_axis import OffAxisHolographyStrategy
from hologen.types import (
GridSpec, OpticalConfig, HolographyConfig,
HolographyMethod, OffAxisCarrier
)
import numpy as np
# Setup with carrier
grid = GridSpec(height=512, width=512, pixel_pitch=3.2e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
carrier = OffAxisCarrier(frequency_x=1e6, frequency_y=0, gaussian_width=2e5)
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.OFF_AXIS,
carrier=carrier
)
# Create strategy
strategy = OffAxisHolographyStrategy()
# Generate and reconstruct
object_field = np.ones((512, 512), dtype=np.complex128)
hologram = strategy.create_hologram(object_field, config)
reconstruction = strategy.reconstruct(hologram, config)Core propagation function used by both strategies.
from hologen.holography.propagation import angular_spectrum_propagate
def angular_spectrum_propagate(
field: ArrayComplex,
grid: GridSpec,
optics: OpticalConfig,
distance: float,
) -> ArrayComplex:
"""Propagate complex optical field using angular spectrum method.
Decomposes field into plane waves, applies appropriate phase shifts
(or evanescent decay) for given propagation distance, and recomposes
the field at the observation plane.
Args:
field: Complex field at source plane, shape (grid.height, grid.width)
grid: Spatial sampling specification (height, width, pixel_pitch)
optics: Optical parameters (wavelength)
distance: Propagation distance in meters
Positive: forward propagation (object → sensor)
Negative: backward propagation (sensor → object)
Returns:
Complex field at observation plane, dtype complex128
Raises:
ValueError: If field shape doesn't match grid dimensions
ValueError: If Nyquist criterion violated (pixel_pitch too large)
Notes:
- Assumes monochromatic illumination
- Handles both propagating and evanescent waves
- Validates sampling criterion: pixel_pitch < λ/2
- Returns immediately for distance=0 (no-op)
Example:
>>> from hologen.holography.propagation import angular_spectrum_propagate
>>> from hologen.types import GridSpec, OpticalConfig
>>> import numpy as np
>>>
>>> grid = GridSpec(height=512, width=512, pixel_pitch=6.4e-6)
>>> optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
>>> field = np.ones((512, 512), dtype=np.complex128)
>>>
>>> # Forward propagation
>>> propagated = angular_spectrum_propagate(field, grid, optics, 0.05)
>>>
>>> # Backward propagation
>>> reconstructed = angular_spectrum_propagate(propagated, grid, optics, -0.05)
"""@dataclass(slots=True)
class HolographyConfig:
"""Configuration bundle for holography strategies.
Attributes:
grid: Spatial sampling specification
optics: Optical parameters (wavelength, distance)
method: Holography method (INLINE or OFF_AXIS)
carrier: Carrier configuration (required for OFF_AXIS, None for INLINE)
"""
grid: GridSpec
optics: OpticalConfig
method: HolographyMethod
carrier: OffAxisCarrier | None = None@dataclass(slots=True)
class OffAxisCarrier:
"""Carrier modulation parameters for off-axis holography.
Attributes:
frequency_x: Spatial carrier frequency along x-axis (cycles/m)
frequency_y: Spatial carrier frequency along y-axis (cycles/m)
gaussian_width: Standard deviation of Gaussian filter (cycles/m)
Constraints:
- sqrt(frequency_x² + frequency_y²) < Nyquist frequency
- gaussian_width typically 20-40% of carrier frequency magnitude
"""
frequency_x: float
frequency_y: float
gaussian_width: floatComplete example generating inline holograms with phase-only objects:
from hologen.converters import ObjectDomainProducer, ObjectToHologramConverter, HologramDatasetGenerator
from hologen.holography.inline import InlineHolographyStrategy
from hologen.shapes import CircleGenerator
from hologen.types import (
GridSpec, OpticalConfig, HolographyConfig, HolographyMethod,
FieldRepresentation, OutputConfig
)
from hologen.utils.io import ComplexFieldWriter
from pathlib import Path
import numpy as np
# 1. Configure system
grid = GridSpec(height=512, width=512, pixel_pitch=6.4e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.INLINE,
carrier=None # No carrier for inline
)
# 2. Configure output
output_config = OutputConfig(
object_representation=FieldRepresentation.PHASE,
hologram_representation=FieldRepresentation.COMPLEX,
reconstruction_representation=FieldRepresentation.COMPLEX
)
# 3. Create pipeline components
shape_generator = CircleGenerator(radius_range=(20e-6, 50e-6))
object_producer = ObjectDomainProducer(
generator=shape_generator,
phase_shift=np.pi/2,
mode="phase"
)
strategy = InlineHolographyStrategy()
converter = ObjectToHologramConverter(
strategy_mapping={HolographyMethod.INLINE: strategy},
output_config=output_config
)
dataset_generator = HologramDatasetGenerator(
object_producer=object_producer,
converter=converter,
config=config
)
# 4. Generate dataset
rng = np.random.default_rng(42)
samples = dataset_generator.generate(count=100, rng=rng)
# 5. Write to disk
writer = ComplexFieldWriter(save_preview=True, phase_colormap="twilight")
writer.save(samples, output_dir=Path("inline_dataset"))
print("Inline holography dataset generated successfully!")Complete example generating off-axis holograms:
from hologen.converters import ObjectDomainProducer, ObjectToHologramConverter, HologramDatasetGenerator
from hologen.holography.off_axis import OffAxisHolographyStrategy
from hologen.shapes import RectangleGenerator
from hologen.types import (
GridSpec, OpticalConfig, HolographyConfig, HolographyMethod,
OffAxisCarrier, FieldRepresentation, OutputConfig
)
from hologen.utils.io import ComplexFieldWriter
from pathlib import Path
import numpy as np
# 1. Configure system with carrier
grid = GridSpec(height=512, width=512, pixel_pitch=3.2e-6) # Smaller pixels
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
carrier = OffAxisCarrier(
frequency_x=1e6,
frequency_y=0,
gaussian_width=2e5
)
config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.OFF_AXIS,
carrier=carrier
)
# 2. Configure output
output_config = OutputConfig(
object_representation=FieldRepresentation.AMPLITUDE,
hologram_representation=FieldRepresentation.COMPLEX,
reconstruction_representation=FieldRepresentation.COMPLEX
)
# 3. Create pipeline components
shape_generator = RectangleGenerator(
width_range=(30e-6, 60e-6),
height_range=(30e-6, 60e-6)
)
object_producer = ObjectDomainProducer(
generator=shape_generator,
mode="amplitude"
)
strategy = OffAxisHolographyStrategy()
converter = ObjectToHologramConverter(
strategy_mapping={HolographyMethod.OFF_AXIS: strategy},
output_config=output_config
)
dataset_generator = HologramDatasetGenerator(
object_producer=object_producer,
converter=converter,
config=config
)
# 4. Generate dataset
rng = np.random.default_rng(42)
samples = dataset_generator.generate(count=100, rng=rng)
# 5. Write to disk
writer = ComplexFieldWriter(save_preview=True, phase_colormap="twilight")
writer.save(samples, output_dir=Path("offaxis_dataset"))
print("Off-axis holography dataset generated successfully!")Generate datasets with both methods for comparison:
from hologen.converters import ObjectDomainProducer, ObjectToHologramConverter, HologramDatasetGenerator
from hologen.holography.inline import InlineHolographyStrategy
from hologen.holography.off_axis import OffAxisHolographyStrategy
from hologen.shapes import CircleGenerator
from hologen.types import (
GridSpec, OpticalConfig, HolographyConfig, HolographyMethod,
OffAxisCarrier, FieldRepresentation, OutputConfig
)
from hologen.utils.io import ComplexFieldWriter
from pathlib import Path
import numpy as np
# Shared configuration
grid = GridSpec(height=512, width=512, pixel_pitch=3.2e-6)
optics = OpticalConfig(wavelength=532e-9, propagation_distance=0.05)
shape_generator = CircleGenerator(radius_range=(20e-6, 50e-6))
rng = np.random.default_rng(42)
output_config = OutputConfig(
object_representation=FieldRepresentation.PHASE,
hologram_representation=FieldRepresentation.COMPLEX,
reconstruction_representation=FieldRepresentation.COMPLEX
)
# Inline configuration
inline_config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.INLINE
)
# Off-axis configuration
carrier = OffAxisCarrier(frequency_x=1e6, frequency_y=0, gaussian_width=2e5)
offaxis_config = HolographyConfig(
grid=grid,
optics=optics,
method=HolographyMethod.OFF_AXIS,
carrier=carrier
)
# Generate inline dataset
object_producer = ObjectDomainProducer(
generator=shape_generator,
phase_shift=np.pi/2,
mode="phase"
)
inline_strategy = InlineHolographyStrategy()
inline_converter = ObjectToHologramConverter(
strategy_mapping={HolographyMethod.INLINE: inline_strategy},
output_config=output_config
)
inline_generator = HologramDatasetGenerator(
object_producer=object_producer,
converter=inline_converter,
config=inline_config
)
inline_samples = inline_generator.generate(count=50, rng=rng)
writer = ComplexFieldWriter(save_preview=True)
writer.save(inline_samples, output_dir=Path("comparison/inline"))
# Generate off-axis dataset (same objects, different method)
rng = np.random.default_rng(42) # Reset for same objects
offaxis_strategy = OffAxisHolographyStrategy()
offaxis_converter = ObjectToHologramConverter(
strategy_mapping={HolographyMethod.OFF_AXIS: offaxis_strategy},
output_config=output_config
)
offaxis_generator = HologramDatasetGenerator(
object_producer=object_producer,
converter=offaxis_converter,
config=offaxis_config
)
offaxis_samples = offaxis_generator.generate(count=50, rng=rng)
writer.save(offaxis_samples, output_dir=Path("comparison/offaxis"))
print("Comparison datasets generated!")
print("Compare reconstruction quality in comparison/inline vs comparison/offaxis")Implement a custom holography strategy:
from hologen.types import ArrayComplex, HolographyConfig
from hologen.holography.propagation import angular_spectrum_propagate
import numpy as np
class CustomHolographyStrategy:
"""Custom holography strategy with modified reconstruction."""
def create_hologram(
self,
object_field: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Standard inline hologram generation."""
propagated = angular_spectrum_propagate(
field=object_field,
grid=config.grid,
optics=config.optics,
distance=config.optics.propagation_distance
)
return propagated
def reconstruct(
self,
hologram: ArrayComplex,
config: HolographyConfig
) -> ArrayComplex:
"""Custom reconstruction with additional processing."""
# Standard back-propagation
reconstructed = angular_spectrum_propagate(
field=hologram,
grid=config.grid,
optics=config.optics,
distance=-config.optics.propagation_distance
)
# Custom post-processing (example: phase unwrapping, filtering, etc.)
# Add your custom processing here
return reconstructed
# Use custom strategy
from hologen.converters import ObjectToHologramConverter
from hologen.types import HolographyMethod
custom_strategy = CustomHolographyStrategy()
converter = ObjectToHologramConverter(
strategy_mapping={HolographyMethod.INLINE: custom_strategy},
output_config=output_config
)Generate datasets using different holography methods from the command line.
Basic inline hologram generation:
python scripts/generate_dataset.py \
--samples 100 \
--output ./inline_dataset \
--method inline \
--height 512 \
--width 512 \
--pixel-pitch 6.4e-6 \
--wavelength 532e-9 \
--distance 0.05Inline with phase-only objects:
python scripts/generate_dataset.py \
--samples 100 \
--output ./inline_phase \
--method inline \
--object-type phase \
--output-domain complex \
--phase-shift 1.5708 \
--height 512 \
--width 512 \
--pixel-pitch 6.4e-6 \
--wavelength 532e-9 \
--distance 0.05Basic off-axis hologram generation:
python scripts/generate_dataset.py \
--samples 100 \
--output ./offaxis_dataset \
--method off_axis \
--carrier-frequency-x 1e6 \
--carrier-frequency-y 0 \
--gaussian-width 2e5 \
--height 512 \
--width 512 \
--pixel-pitch 3.2e-6 \
--wavelength 532e-9 \
--distance 0.05Off-axis with phase objects:
python scripts/generate_dataset.py \
--samples 100 \
--output ./offaxis_phase \
--method off_axis \
--carrier-frequency-x 1e6 \
--carrier-frequency-y 0 \
--gaussian-width 2e5 \
--object-type phase \
--output-domain complex \
--phase-shift 1.5708 \
--height 512 \
--width 512 \
--pixel-pitch 3.2e-6 \
--wavelength 532e-9 \
--distance 0.05Generate both inline and off-axis datasets for comparison:
# Inline dataset
python scripts/generate_dataset.py \
--samples 50 \
--output ./comparison/inline \
--method inline \
--seed 42 \
--height 512 \
--width 512 \
--pixel-pitch 3.2e-6 \
--wavelength 532e-9 \
--distance 0.05
# Off-axis dataset (same seed for same objects)
python scripts/generate_dataset.py \
--samples 50 \
--output ./comparison/offaxis \
--method off_axis \
--carrier-frequency-x 1e6 \
--carrier-frequency-y 0 \
--gaussian-width 2e5 \
--seed 42 \
--height 512 \
--width 512 \
--pixel-pitch 3.2e-6 \
--wavelength 532e-9 \
--distance 0.05For inline holography:
- Use standard pixel pitch (6.4 μm typical)
- No carrier parameters needed
- Suitable for sparse objects
- Faster generation
For off-axis holography:
- Use smaller pixel pitch (3.2 μm or less) to sample carrier
- Must specify carrier-frequency-x, carrier-frequency-y
- Must specify gaussian-width for filtering
- Suitable for dense objects
- Slower generation (Fourier filtering overhead)
Inline, intensity output (default):
python scripts/generate_dataset.py --method inlineInline, complex output:
python scripts/generate_dataset.py \
--method inline \
--output-domain complexOff-axis, complex output:
python scripts/generate_dataset.py \
--method off_axis \
--carrier-frequency-x 1e6 \
--carrier-frequency-y 0 \
--gaussian-width 2e5 \
--output-domain complexOff-axis with noise:
python scripts/generate_dataset.py \
--method off_axis \
--carrier-frequency-x 1e6 \
--carrier-frequency-y 0 \
--gaussian-width 2e5 \
--sensor-read-noise 3.0 \
--sensor-shot-noise \
--speckle-contrast 0.8-
Start with Fresnel number ≈ 1:
# For object size a = 50 μm, wavelength λ = 532 nm a = 50e-6 wavelength = 532e-9 z_optimal = a**2 / wavelength # ≈ 4.7 mm
-
Verify fringe visibility: Generate test hologram and check for clear interference patterns
-
Adjust based on application:
- Particle tracking: 10-50 mm
- Microscopy: 1-10 mm
- Large objects: 50-200 mm
For off-axis holography:
-
Estimate object bandwidth:
object_size = 50e-6 # meters max_object_freq = 1 / object_size # 2e4 cycles/m
-
Choose carrier frequency:
carrier_freq = 2.5 * max_object_freq # 5e4 cycles/m
-
Verify Nyquist constraint:
nyquist_freq = 1 / (2 * pixel_pitch) assert carrier_freq + max_object_freq < nyquist_freq
-
Set filter width:
gaussian_width = 0.3 * carrier_freq # 30% of carrier
- Inline holography: Standard sampling (pixel_pitch ≈ 6.4 μm)
- Off-axis holography: Finer sampling (pixel_pitch ≈ 3.2 μm or less)
-
Always verify:
pixel_pitch < wavelength / 2
For inline holography:
- Accept twin-image artifacts for sparse objects
- Consider computational twin-image removal for critical applications
- Use multiple propagation distances for depth information
For off-axis holography:
- Optimize carrier frequency for clean separation
- Adjust Gaussian filter width to balance resolution and noise
- Verify no overlap between diffraction orders in Fourier space
- Batch generation: Generate multiple samples in parallel
- Memory management: Use generators for large datasets
- FFT optimization: Ensure grid dimensions are powers of 2 for faster FFT
- Caching: Reuse propagation kernels when possible
Before generating large datasets:
- Verify Nyquist criterion satisfied
- Check Fresnel number in reasonable range (0.1 - 10)
- Generate test sample and inspect visually
- Verify reconstruction quality acceptable
- Check file sizes and storage requirements
- Validate parameter ranges for your application
- Test loading data in your ML framework
- Pixel pitch too large: Violates Nyquist criterion, causes aliasing
- Carrier frequency too high: Exceeds Nyquist limit, creates artifacts
- Gaussian width too narrow: Clips object spectrum, reduces resolution
- Gaussian width too wide: Includes DC or twin-image contamination
- Propagation distance too short: Poor hologram formation
- Propagation distance too long: Requires impractical resolution
Problem: Hologram looks like object (no fringes)
- Solution: Increase propagation distance
Problem: Reconstruction is blurry
- Solution: Check Nyquist criterion, reduce pixel pitch
Problem: Off-axis reconstruction has artifacts
- Solution: Adjust carrier frequency or Gaussian width
Problem: ValueError: Nyquist criterion violated
- Solution: Reduce pixel pitch or increase wavelength
Problem: Reconstruction has twin-image (inline)
- Solution: This is expected; use off-axis or computational removal
Problem: Off-axis reconstruction is empty
- Solution: Check carrier frequency matches hologram generation
- Complex Field Support: Learn about field representations (intensity, amplitude, phase, complex) and how to generate phase-only objects
- Noise Simulation: Add realistic sensor noise, speckle, and aberrations to holograms
- Shape Generators: Explore available object-domain shape generators
- Quickstart Guide: Get started quickly with basic examples
- API Reference: Complete API documentation for all classes and functions
-
Angular Spectrum Method: See
hologen.holography.propagationmodule -
Dataset Generation Pipeline: See
hologen.convertersmodule -
I/O and File Formats: See
hologen.utils.iomodule -
Configuration Types: See
hologen.typesmodule
Digital holography fundamentals:
- Schnars, U., & Jüptner, W. (2005). Digital Holography: Digital Hologram Recording, Numerical Reconstruction, and Related Techniques. Springer.
- Kreis, T. (2005). Handbook of Holographic Interferometry: Optical and Digital Methods. Wiley-VCH.
Angular spectrum method:
- Goodman, J. W. (2005). Introduction to Fourier Optics (3rd ed.). Roberts and Company Publishers.
- Matsushima, K., & Shimobaba, T. (2009). Band-limited angular spectrum method for numerical simulation of free-space propagation in far and near fields. Optics Express, 17(22), 19662-19673.
Off-axis holography:
- Cuche, E., Marquet, P., & Depeursinge, C. (1999). Simultaneous amplitude-contrast and quantitative phase-contrast microscopy by numerical reconstruction of Fresnel off-axis holograms. Applied Optics, 38(34), 6994-7001.
Inline holography and twin-image problem:
- Gabor, D. (1948). A new microscopic principle. Nature, 161(4098), 777-778.
- Latychevskaia, T., & Fink, H. W. (2015). Solution to the twin image problem in holography. Physical Review Letters, 98(23), 233901.
HoloGen includes example scripts for generating comparison datasets:
# Generate visual examples
python scripts/generate_visual_examples.py
# Generate shape examples
python scripts/generate_shape_examples.py
# Generate full dataset
python scripts/generate_dataset.py --samples 1000Check the docs/examples/ directory for pre-generated visual examples demonstrating different holography methods.