API Reference

This page provides a comprehensive reference of the types and functions in MicroscopePSFs.jl.

Public API

These types and functions form the core public API of MicroscopePSFs.jl.

PSF Types

2D PSF Models

MicroscopePSFs.GaussianPSF — Type
GaussianPSF{T<:AbstractFloat} <: Abstract2DPSF{T}

Isotropic 2D Gaussian PSF. The width σ must be finite and strictly positive.

Fields

  • σ: Standard deviation in physical units (typically microns)
source
MicroscopePSFs.AiryPSF — Type
AiryPSF{T<:AbstractFloat} <: Abstract2DPSF{T}

2D Airy pattern PSF using paraxial, scalar model.

Field amplitude for a circular aperture is given by: A(r) = ν/√(4π) * (2J₁(νr)/(νr)) where: ν = 2π*nₐ/λ J₁ is the Bessel function of first kind, order 1 r is the radial distance from optical axis

Fields

  • nₐ: Numerical aperture
  • λ: Wavelength in microns
  • ν: Optical parameter = 2π*nₐ/λ
source

3D PSF Models

MicroscopePSFs.ScalarPSF — Type
ScalarPSF{T} <: Abstract3DPSF{T}

Scalar 3D PSF using explicit pupil function representation.

Fields

  • nₐ::T: Numerical aperture
  • λ::T: Wavelength in microns
  • n::T: Refractive index
  • pupil::PupilFunction{T}: Complex pupil function
  • zernike_coeffs::Union{Nothing, ZernikeCoefficients{T}}: Zernike coefficients used to create this PSF (if applicable)

Notes

Can be initialized with either a PupilFunction or ZernikeCoefficients using the ScalarPSF factory function.

source
MicroscopePSFs.VectorPSF — Type
VectorPSF{T<:AbstractFloat} <: Abstract3DPSF{T}

Vector PSF model using explicit pupil function representation. Allows direct manipulation of pupil function for custom aberrations.

Fields

  • nₐ::T: Numerical aperture
  • λ::T: Wavelength in microns
  • n_medium::T: Sample medium refractive index
  • n_coverslip::T: Cover slip refractive index
  • n_immersion::T: Immersion medium refractive index
  • dipole::DipoleVector{T}: Dipole orientation
  • z_stage::T: Distance the sample stage was moved away from the nominal focal plane at the coverslip (μm)
  • vector_pupils::Vector{VectorPupilFunction{T}}: Vector of pupil functions containing field components. For a single dipole, this contains one pupil. For a rotating dipole, it contains three pupils for x, y, and z orientations.
  • base_pupil::Union{Nothing, PupilFunction{T}}: Base pupil function representing system aberrations
  • zernike_coeffs::Union{Nothing, ZernikeCoefficients{T}}: Zernike coefficients used to create this PSF (if applicable)
source

Multi-channel Models

MicroscopePSFs.PlanePSF — Type
PlanePSF(psf::AbstractPSF; dz::Real)

Create a detection plane focused dz microns away.

Arguments

  • psf: A 3D PSF, including a 3D SplinePSF.
  • dz: Required emitter-side focus shift in microns.

Returns

A 3D PSF whose intensity, amplitude, and dipole fields evaluate psf at z + dz. This is emitter-side high-NA defocus, which is not the same phase as moving a camera in low-NA image space. A learned pupil absorbs the difference.

Examples

plane = PlanePSF(ScalarPSF(1.2, 0.68, 1.33); dz=0.2)
intensity = plane(0.0, 0.0, 0.1)
source
MicroscopePSFs.AnalyzerPSF — Type
AnalyzerPSF(psf::AbstractPSF; component::Integer)

Pass one camera-frame polarization component of a vector field.

Arguments

  • psf: VectorPSF, or a PlanePSF, FourPiPSF, or AnalyzerPSF over one.
  • component: Required component: 1 passes Ex (H), 2 passes Ey (V).

Returns

A 3D PSF with the rejected component set to zero in every dipole field. It is not renormalized: H and V intensities sum to the input intensity. amplitude returns [Ex, Ey] for one dipole and errors for several dipoles.

Examples

psf = VectorPSF(1.2, 0.68)
h = AnalyzerPSF(psf; component=1)
v = AnalyzerPSF(psf; component=2)
intensity = h(0.1, 0.0, 0.0) + v(0.1, 0.0, 0.0)
source
MicroscopePSFs.FourPiPSF — Type
FourPiPSF(top::AbstractPSF, bottom::AbstractPSF; phase::Real=0)

Interfere two opposed objectives about a shared focal plane.

Arguments

  • top, bottom: Complex-field arms with equal dipole and component counts and the same wavelength within relative tolerance sqrt(eps(T)) of the coarser arm type. A fixed bottom dipole must mirror the top: DipoleVector(px, py, -pz). Vector arms require n_medium ≥ NA to avoid evanescent growth at negative z.
  • phase: Cavity phase in radians.

Returns

A 3D PSF evaluating the top at z and bottom at -z, measured from the shared focal plane. The images overlap with the same orientation: one arm has one more mirror than the other, so the camera sees both arms' x and y components in a common frame. For each dipole, fields combine as (Et + cis(phase) * s * Eb) / sqrt(2). The mirror signs are (1, 1, -1) for rotating x, y, z dipoles and (1,) for fixed mirrored dipoles or scalar arms. Dipole intensities add incoherently. amplitude returns a bare scalar for scalar arms, [Ex, Ey] for one vector dipole, and errors for rotating dipoles.

Arms of unit energy give a channel of unit energy averaged over the fringe. p(φ) + p(φ + π) == top(x,y,z) + bottom(x,y,-z) always holds (up to rounding). Polarization-dependent cavity phases use one AnalyzerPSF(FourPiPSF(top, bottom; phase=φ); component=c) per channel.

Examples

arm = ScalarPSF(1.2, 0.68, 1.33)
p = FourPiPSF(arm, arm; phase=π/2)
intensity = p(0.0, 0.0, 0.1)
source
MicroscopePSFs.MultiChannelPSF — Type
MultiChannelPSF(; channels, registration=nothing, transmission=nothing,
                z_offset=nothing, reference=1)

Construct a collection of complete PSFs with a shared emitter coordinate frame. This container is deliberately not an AbstractPSF; one-channel consumers must handle its channels explicitly.

Arguments

  • channels: Nonempty collection of AbstractPSFs, each normalized alone.
  • registration: One 2×3 matrix [A t] per channel, mapping absolute reference camera coordinates in microns to channel coordinates: [xᵢ,yᵢ] = A*[x,y]+t. Defaults to identity; absolute coordinates keep registration valid after crops.
  • transmission: Nonnegative fractions of the emitter's total photons; their sum must not exceed one beyond summation rounding. Defaults to equal splitting. A 4Pi port uses its fringe-average share: 0.5 for each of two complementary ports.
  • z_offset: Per-channel emitter z shifts in microns, with the PlanePSF.dz sign convention. Defaults to zero.
  • reference: Reference channel index (default 1), whose registration and z shift must be identity and zero within sqrt(eps(T)).

Returns

A MultiChannelPSF{T,P} with channel element type narrowed when homogeneous. T is the promoted float type of supplied numeric arrays, restricted to Float32 or Float64; integer and rational inputs promote to Float64, as do all-default inputs. All numeric entries must be finite. Each channel integrates to one alone (4Pi phase channels on average over the fringe); this cannot be checked by the constructor. Raw AnalyzerPSF channels and wrappers carrying their photon split are refused: tabulate them into a SplinePSF scaled to unit integral and put the split in transmission. See channel_position for the forward law.

For 4Pi channels, fitted photons are the fringe-average total. A single 4Pi channel at transmission 1 can predict up to twice that count at the fringe peak. If transmissions sum below 1, photons mean photons before those losses. Learned containers carry axial offsets in z_offset; PlanePSF is for building model channels. Apply each offset once, so it is never applied twice.

Examples

p = ScalarPSF(1.2, 0.68, 1.33)
mc = MultiChannelPSF(; channels=[p, PlanePSF(p; dz=0.2)])
position = channel_position(mc, 2, 1.0, 1.0, 0.1)
source
MicroscopePSFs.channel_position — Function
channel_position(mc::MultiChannelPSF, i, x, y, z) -> (xᵢ, yᵢ, zᵢ)

Map an emitter from the reference camera frame into channel i.

Arguments

  • mc: Multi-channel PSF container.
  • i: Channel index.
  • x, y, z: Reference-frame emitter position in microns, using absolute camera coordinates as in SMLMData pixel edges (not pixel indices).

Returns

(xᵢ, yᵢ, zᵢ) with [xᵢ,yᵢ] = A*[x,y]+t from registration[i] and zᵢ = z + z_offset[i]. The defining forward law is

expected image i = N * mc.transmission[i] *
    integrate_pixels(mc.channels[i], camera_i, emitter at channel_position)

where N is the emitter's total photons and the integrated emitter has unit photons. Each channel must integrate to one alone, with 4Pi phase channels normalized on average over the fringe. The constructor cannot check arbitrary channel normalization; it refuses raw AnalyzerPSF channels and wrappers carrying their photon split. Tabulate these into a SplinePSF scaled to unit integral and put the split in transmission.

For a 4Pi port, transmission is its fringe-average share, 0.5 for each of two complementary ports. Fitted photons are the fringe-average total; a single 4Pi channel at transmission 1 predicts up to twice that count at the fringe peak. When transmissions sum below 1, photons mean photons before those losses. Learned containers carry axial offsets in z_offset. Use PlanePSF for building model channels, and never apply the same offset twice.

Examples

p = ScalarPSF(1.2, 0.68, 1.33)
mc = MultiChannelPSF(; channels=[p, p], z_offset=[0.0, 0.2])
channel_position(mc, 2, 1.0, 2.0, 0.1)  # (1.0, 2.0, 0.3), up to rounding
source

PSF Acceleration

MicroscopePSFs.SplinePSF — Type
SplinePSF{T<:AbstractFloat, IT<:AbstractInterpolation} <: AbstractPSF

A point spread function (PSF) represented as a B-spline interpolation.

Fields

  • spline: The B-spline interpolation object
  • x_range: Range of x-coordinates used for uniform grid interpolation
  • y_range: Range of y-coordinates used for uniform grid interpolation
  • z_range: Range of z-coordinates for 3D PSFs, or nothing for 2D PSFs
  • x_min, x_max, y_min, y_max, z_min, z_max: Cached bounds for fast evaluation
  • original_grid: Original grid data used to create the interpolation
  • interp_order: Interpolation order used (0=constant, 1=linear, 3=cubic)

Notes

  • Coordinates and ranges are in physical units (typically microns)
  • PSF values are preserved from the original PSF that was sampled
  • Bounds are cached as plain Float64 to avoid type-instability in hot loops
  • Full implementation is in spline_psf.jl
source

Core Interface Functions

MicroscopePSFs.amplitude — Function
amplitude(p::PupilFunction, x::Real, y::Real, z::Real)

Calculate complex amplitude from pupil function integration.

Arguments

  • p::PupilFunction: Pupil function
  • x::Real: X position in μm
  • y::Real: Y position in μm
  • z::Real: Z position in μm
source
amplitude(psf::AbstractPSF, x::Real, y::Real)

Evaluate complex field amplitude at position (x,y) relative to PSF center.

Arguments

  • x, y: Position in microns relative to PSF center

Returns

  • Complex amplitude normalized such that |amplitude|² gives normalized intensity

Coordinates

Input coordinates (x,y) are in physical units (microns) relative to PSF center.

Examples

psf = AiryPSF(1.4, 0.532)
amp = amplitude(psf, 0.1, 0.2)
intensity = abs2(amp)  # Convert to intensity
source
amplitude(psf::AbstractPSF, x::Real, y::Real, z::Real)

Evaluate complex field amplitude in 3D. z is axial distance from focus in microns.

source
amplitude(psf::ScalarPSF{T}, x::Real, y::Real, z::Real) where {T}

Calculate complex amplitude at a 3D position using scalar diffraction theory.

Arguments

  • psf: ScalarPSF instance
  • x, y, z: Position in microns relative to PSF center

Returns

  • Complex amplitude at the specified position

Notes

  • Uses Fourier optics to propagate from pupil to image space
  • Accounts for defocus and aberrations encoded in the pupil function
source
amplitude(psf::VectorPSF, x::Real, y::Real, z::Real)

Compute complex vector amplitude at given position.

Arguments

  • psf: Vector PSF instance
  • x, y: Lateral position in microns relative to PSF center
  • z: Axial position in microns representing depth above the coverslip

Returns

  • Vector [Ex, Ey] of complex field amplitudes

Notes

  • z coordinate represents the depth above the coverslip
  • z_stage in the PSF indicates the distance the stage was moved away from the nominal focal plane
  • Includes both UAF and SAF contributions automatically
  • Not meaningful for rotating dipoles (throws an error if PSF has multiple pupils)
source
amplitude(psf::SplinePSF, x::Real, y::Real, z::Real)

Calculate the complex amplitude of the 3D PSF at position (x, y, z).

Arguments

  • psf: SplinePSF instance
  • x, y, z: Coordinates in microns relative to PSF center

Returns

  • Complex amplitude = sqrt(intensity) with zero phase

Notes

  • Returns sqrt(intensity) as Complex to match the interface of other PSFs
  • The SplinePSF does not model phase information
source
amplitude(psf::SplinePSF, x::Real, y::Real)

Calculate the complex amplitude of the PSF at position (x, y) with z=0.

Arguments

  • psf: SplinePSF instance
  • x, y: Coordinates in microns relative to PSF center

Returns

  • Complex amplitude = sqrt(intensity) with zero phase
source
MicroscopePSFs.dipole_fields — Function
dipole_fields(psf::AbstractPSF, x, y, z)

Return the complex field components for each mutually incoherent dipole.

Arguments

  • psf: A PSF with a complex field, such as ScalarPSF or VectorPSF.
  • x, y, z: Position relative to focus, in microns.

Returns

A vector of component vectors: one [E] for a scalar field, one [Ex, Ey] for a fixed vector dipole, or three [Ex, Ey] vectors in x, y, z dipole order for a rotating dipole. Intensity is sum(sum(abs2, E) for E in dipole_fields(...)). Intensity-only PSFs, including SplinePSF, throw ArgumentError: their square-root amplitude has no phase and cannot enter an interference sum.

Examples

psf = ScalarPSF(1.2, 0.68, 1.33)
fields = dipole_fields(psf, 0.1, 0.0, 0.2)
intensity = sum(sum(abs2, E) for E in fields)
source
MicroscopePSFs.integrate_pixels — Function
integrate_pixels(
    psf::AbstractPSF, 
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter; 
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF intensity over camera pixels with optional support region optimization.

For each pixel in the specified region, numerically integrates the PSF intensity using the specified sampling density. Physical coordinates are relative to camera with (0,0) at top-left corner. Automatically handles z-coordinate if both PSF and emitter support it.

Arguments

  • psf::AbstractPSF: Point spread function to integrate
  • pixel_edges_x::AbstractVector: X-coordinate edges of pixels in microns
  • pixel_edges_y::AbstractVector: Y-coordinate edges of pixels in microns
  • emitter::AbstractEmitter: Emitter with position in microns relative to camera
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling::Integer=2: Number of samples per pixel in each dimension
  • threaded::Bool=true: Whether to use multi-threading for integration Set to false when using with automatic differentiation frameworks

Returns

  • Matrix{T}: Integrated intensities where T matches emitter.photons type
  • Array is indexed as [y,x] with [1,1] at top-left pixel
  • Values represent actual photon counts in each pixel based on emitter's photon value

Examples

# Create pixel edges for a 20x20 camera with 100nm pixels
pixel_edges_x = pixel_edges_y = 0:0.1:2.0
emitter = Emitter2D(1.0, 1.0, 1000.0)  # Emitter at (1μm, 1μm) with 1000 photons
psf = GaussianPSF(0.15)  # σ = 150nm

# Calculate over full image
pixels = integrate_pixels(psf, pixel_edges_x, pixel_edges_y, emitter)

# Calculate only within a 0.5μm radius of the emitter
pixels_roi = integrate_pixels(psf, pixel_edges_x, pixel_edges_y, emitter, support=0.5)

See also: integrate_pixels_amplitude, AbstractPSF

source
integrate_pixels(
    psf::AbstractPSF, 
    camera::AbstractCamera, 
    emitter::AbstractEmitter;
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF intensity over camera pixels with optional support region optimization.

This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf::AbstractPSF: Point spread function to integrate
  • camera::AbstractCamera: Camera geometry defining pixel edges in microns
  • emitter::AbstractEmitter: Emitter with position in microns relative to camera
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling::Integer=2: Number of samples per pixel in each dimension
  • threaded::Bool=true: Whether to use multi-threading for integration

Returns

  • Array of pixel values with dimensions [y, x] (matrix indexed [row,col])
  • Values are normalized to sum to 1
  • Array indices start at [1,1] for top-left pixel

See also: integrate_pixels_amplitude, AbstractPSF

source
integrate_pixels(
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF intensity over camera pixels for multiple emitters with optional support region optimization.

Arguments

  • psf: Point spread function to integrate
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
    • If Real: radius in microns around each emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns (fixed region for all emitters)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true) Set to false when using with automatic differentiation frameworks

Returns

  • Array of integrated PSF intensities with dimensions matching the camera
  • Values represent actual photon counts from all emitters

Notes

  • Results are the sum of individual emitter contributions (incoherent addition)
  • For coherent addition, use integrate_pixels_amplitude and sum complex amplitudes
source
integrate_pixels(
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF intensity over camera pixels for multiple emitters with optional support region optimization. This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated PSF intensities with dimensions matching the camera
  • Values represent actual photon counts from all emitters
source
integrate_pixels(
    psf::SplinePSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF over camera pixels using interpolation.

Arguments

  • psf: SplinePSF instance
  • camera: Camera geometry
  • emitter: Emitter with position information
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling: Subpixel sampling density for integration accuracy
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated PSF intensities with dimensions [ny, nx]
  • Values represent actual photon counts based on emitter's photon value

Notes

  • For 3D SplinePSFs (when z_range is defined), requires an emitter with a z-coordinate
source
MicroscopePSFs.integrate_pixels_amplitude — Function
integrate_pixels_amplitude(
    psf::AbstractPSF, 
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter; 
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels with optional support region optimization.

For each pixel in the specified region, numerically integrates the PSF amplitude using the specified sampling density. Returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity. Automatically handles z-coordinate if both PSF and emitter support it.

Arguments

  • psf::AbstractPSF: Point spread function to integrate
  • pixel_edges_x::AbstractVector: X-coordinate edges of pixels in microns
  • pixel_edges_y::AbstractVector: Y-coordinate edges of pixels in microns
  • emitter::AbstractEmitter: Emitter with position in microns relative to camera
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling::Integer=2: Number of samples per pixel in each dimension
  • threaded::Bool=true: Whether to use multi-threading for integration

Returns

  • Matrix{Complex{T}}: Integrated complex amplitudes where T matches emitter.photons type
  • Array is indexed as [y,x] with [1,1] at top-left pixel
  • Values are not normalized to preserve complex amplitude relationships

Notes

  • For coherent calculations, use this function instead of integrate_pixels
  • Return type is complex to support PSFs with phase information
  • For pixel-integrated intensity, use integrate_pixels.

See also: integrate_pixels, AbstractPSF

source
integrate_pixels_amplitude(
    psf::AbstractPSF, 
    camera::AbstractCamera, 
    emitter::AbstractEmitter;
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels with optional support region optimization.

This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf::AbstractPSF: Point spread function to integrate
  • camera::AbstractCamera: Camera geometry defining pixel edges in microns
  • emitter::AbstractEmitter: Emitter with position in microns relative to camera
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling::Integer=2: Number of samples per pixel in each dimension
  • threaded::Bool=true: Whether to use multi-threading for integration

Returns

  • Matrix of complex amplitudes
  • Array indices start at [1,1] for top-left pixel

See also: integrate_pixels, AbstractPSF

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
integrate_pixels_amplitude(
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels for multiple emitters with optional support region optimization.

Arguments

  • psf: Point spread function to integrate
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated complex amplitudes
  • Values represent coherently summed field contributions

Notes

  • Results are the coherent sum of individual emitter field contributions
  • Returns a unit-photon field for each emitter; emitter photons are ignored.
  • Squaring a pixel-integrated field is not pixel-integrated intensity.
  • For incoherent photon counts, use integrate_pixels.
source
integrate_pixels_amplitude(
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels for multiple emitters with optional support region optimization. This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated complex amplitudes
  • Values represent coherently summed field contributions

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
integrate_pixels_amplitude(
    psf::VectorPSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
)

Specialized version of amplitude integration for VectorPSF that preserves polarization components.

Arguments

  • psf: VectorPSF instance
  • camera: Camera geometry defining pixel edges
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • 3D array of integrated complex amplitudes with dimensions [y, x, pol] where pol index 1 = Ex and pol index 2 = Ey

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
integrate_pixels_amplitude(
    psf::VectorPSF,
    camera::AbstractCamera,
    emitters::Vector{<:AbstractEmitter};
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels for multiple emitters. Special version for VectorPSF that preserves polarization components.

Arguments

  • psf: VectorPSF instance
  • camera: Camera geometry defining pixel edges
  • emitters: Vector of emitters with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • 3D array of integrated complex amplitudes with dimensions [y, x, pol] where pol index 1 = Ex and pol index 2 = Ey

Notes

  • Results are the coherent sum of individual emitter field contributions

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
integrate_pixels_amplitude(
    psf::SplinePSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF amplitude (complex) over camera pixels.

Arguments

  • psf: SplinePSF instance
  • camera: Camera geometry
  • emitter: Emitter with position information
  • support: Region to calculate (default: Inf = full image)
    • If Real: radius in microns around emitter
    • If Tuple: explicit (xmin, xmax, ymin, ymax) in microns
  • sampling: Subpixel sampling density for integration accuracy
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated PSF complex amplitudes with dimensions [ny, nx]

Notes

  • For 3D SplinePSFs (when z_range is defined), requires an emitter with a z-coordinate

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
integrate_pixels_amplitude(psf::Union{PlanePSF,AnalyzerPSF,FourPiPSF}, camera, emitter;
                           support=Inf, sampling=2, threaded=true)
integrate_pixels_amplitude(psf::Union{PlanePSF,AnalyzerPSF,FourPiPSF}, camera, emitters;
                           support=Inf, sampling=2, threaded=true)

Integrate wrapper field amplitudes while preserving vector polarization components.

Arguments

  • psf: A plane, analyzer, or 4Pi PSF with a single coherent dipole.
  • camera: Camera geometry defining pixel edges in microns.
  • emitter or emitters: One emitter or a vector of emitters, summed coherently.
  • support: Scalar wrappers retain the generic support options. Vector wrappers require Inf, as for VectorPSF integration without a support keyword.
  • sampling: Subpixel samples per dimension (default 2).
  • threaded: Enable threaded integration (default true).

Returns

A complex [y, x, pol] array for vector fields, with components Ex and Ey. Scalar wrappers retain the generic complex matrix. Several incoherent dipoles throw the same error as VectorPSF amplitude integration. These are unit-photon fields: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

Examples

p = PlanePSF(VectorPSF(1.2, 0.68, DipoleVector(1.0, 0.0, 0.0)); dz=0.1)
camera = IdealCamera(4, 4, 0.1)
fields = integrate_pixels_amplitude(p, camera, Emitter3D(0.2, 0.2, 0.0, 1.0))
source

Multi-Emitter Integration

The following functions support integrating PSFs for multiple emitters:

MicroscopePSFs.integrate_pixels — Method
integrate_pixels(
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF intensity over camera pixels for multiple emitters with optional support region optimization. This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated PSF intensities with dimensions matching the camera
  • Values represent actual photon counts from all emitters
source
MicroscopePSFs.integrate_pixels_amplitude — Method
integrate_pixels_amplitude(
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitters::Vector{<:AbstractEmitter};
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}} = Inf,
    sampling::Integer=2,
    threaded::Bool=true
)

Integrate PSF complex amplitude over camera pixels for multiple emitters with optional support region optimization. This version takes a camera object instead of explicit pixel edges.

Arguments

  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitters: Vector of emitters with position information
  • support: Region to calculate for each emitter (default: Inf = full image)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array of integrated complex amplitudes
  • Values represent coherently summed field contributions

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source

Pupil Functions

MicroscopePSFs.PupilFunction — Type
PupilFunction{T}

Represents a pupil function with physical parameters.

Fields

  • nₐ: Numerical aperture
  • λ: Wavelength in μm
  • n: Refractive index
  • field: Square complex field array with at least two samples per axis
source
MicroscopePSFs.VectorPupilFunction — Type
VectorPupilFunction{T}

Vector pupil function storing Ex,Ey field components as PupilFunctions.

Fields

  • nₐ::T: Numerical aperture
  • λ::T: Wavelength in μm
  • n_medium::T: Refractive index of the sample medium
  • n_coverslip::T: Refractive index of the coverslip
  • n_immersion::T: Refractive index of the immersion medium
  • Ex::PupilFunction{T}: x-component of electric field
  • Ey::PupilFunction{T}: y-component of electric field
source

Zernike Module

MicroscopePSFs.Zernike.ZernikeCoefficients — Type
ZernikeCoefficients{T<:Real}

Mutable structure to hold Zernike coefficients for both magnitude and phase of a pupil function. Uses Noll indexing convention starting at index 1.

Fields

  • mag::Vector{T}: Coefficients for magnitude (1-indexed per Noll convention)
  • phase::Vector{T}: Coefficients for phase (1-indexed per Noll convention)

Notes

  • First coefficient (index 1) typically represents piston
  • Magnitude coefficients are typically normalized with mag[1] = 1
  • Phase coefficients represent phase in radians
  • RMS normalization (Noll convention) is used so coefficients directly represent RMS wavefront error
source

Zernike Polynomial Functions

MicroscopePSFs.Zernike.zernikepolynomial — Function
zernikepolynomial(n::Integer, l::Integer, ρ::Real, ϕ::Real) -> Real

Compute the complete Zernike polynomial Z_n^l(ρ,ϕ) with Noll normalization (RMS=1).

Arguments

  • n: Radial order
  • l: Azimuthal frequency (signed)
  • ρ: Radial coordinate (0 ≤ ρ ≤ 1)
  • ϕ: Azimuthal angle in radians

Notes

  • Uses Noll normalization where RMS=1 over unit circle
  • Combines radial polynomial with appropriate trigonometric function
source
zernikepolynomial(j::Integer, ρ::Real, ϕ::Real) -> Real

Compute Zernike polynomial using Noll index.

Arguments

  • j: Polynomial index using Noll convention
  • ρ: Radial coordinate
  • ϕ: Azimuthal angle
source
MicroscopePSFs.Zernike.radialpolynomial — Function
radialpolynomial(n::Integer, m::Integer, ρ::Real) -> Real

Compute the unnormalized radial component R_n^m(ρ) of the Zernike polynomial.

Arguments

  • n: Radial order
  • m: Azimuthal order (absolute value of azimuthal frequency)
  • ρ: Radial coordinate (0 ≤ ρ ≤ 1)

Notes

  • Returns 0 for ρ > 1
  • No normalization applied - returns the standard mathematical form
source
MicroscopePSFs.Zernike.max_radial_order — Function
max_radial_order(num_coeffs::Integer)

Calculate maximum radial order N given number of coefficients L using Noll indexing. Returns the maximum radial order where at least one Zernike term can be represented (even if not all terms of that order can fit in the coefficient vector).

Arguments

  • num_coeffs: Maximum Noll index available

Returns

  • Maximum radial order n where at least the first term of that order has index ≤ num_coeffs
source
MicroscopePSFs.Zernike.evaluate_pupil — Function
evaluate_pupil(coeffs::ZernikeCoefficients, grid_size::Integer) -> Matrix{Complex{Float64}}

Generate complex pupil function from Zernike coefficients.

Arguments

  • coeffs: ZernikeCoefficients containing magnitude and phase coefficients
  • grid_size: Size of the output grid (gridsize × gridsize)

Returns

  • Complex-valued matrix representing the pupil function

Notes

  • Output grid is normalized to unit circle
  • Points outside unit circle are set to zero
  • Amplitude is the sum of magnitude coefficients times their Zernike modes. The default mag[1] = 1 supplies the unit piston; no offset is added.
  • Phase is applied as exp(iϕ)
  • Uses Noll indexing throughout
source

Zernike Analysis Functions

MicroscopePSFs.Zernike.rms — Function
rms(coeffs::ZernikeCoefficients) -> Tuple{Float64,Float64}

Calculate RMS values for magnitude and phase coefficients (excluding piston).

source
MicroscopePSFs.Zernike.significant_terms — Function
significant_terms(coeffs::ZernikeCoefficients, 
                 threshold::Real=0.01) -> Vector{Tuple{Int,Float64,Float64}}

Return list of significant terms: (index, magnitude, phase) above threshold.

source

Index Conversion

MicroscopePSFs.Zernike.nl2osa — Function
nl2osa(n::Integer, l::Integer) -> Int

Convert from (n,l) indices to OSA/ANSI single index.

Arguments

  • n: Radial order (≥ 0)
  • l: Azimuthal frequency, must satisfy:
    • |l| ≤ n
    • n - |l| must be even

Returns

  • OSA/ANSI single index j = (n(n+2) + l)/2

Examples

julia> nl2osa(4, 2)
15
source
MicroscopePSFs.Zernike.osa2nl — Function
osa2nl(j::Integer) -> Tuple{Int,Int}

Convert from OSA/ANSI single index to (n,l) indices.

Arguments

  • j: OSA/ANSI index (≥ 0)

Returns

  • Tuple of (n,l) indices
source
MicroscopePSFs.Zernike.nl2noll — Function
nl2noll(n::Integer, l::Integer) -> Int

Convert from (n,l) indices to Noll single index.

Arguments

  • n: Radial order (≥ 0)
  • l: Azimuthal frequency, must satisfy:
    • |l| ≤ n
    • n - |l| must be even

Returns

  • Noll single index
source
MicroscopePSFs.Zernike.noll2nl — Function
noll2nl(j::Integer) -> Tuple{Int,Int}

Convert from Noll single index to (n,l) indices.

Arguments

  • j: Noll index (> 0)

Returns

  • Tuple of (n,l) indices
source

Emitters

MicroscopePSFs.DipoleVector — Type
DipoleVector{T<:Real}

A normalized 3D dipole orientation. Every constructor normalizes nonzero inputs. All input components must be finite. Normalization uses a working type promoted from the floating-point types of every input and the requested component type. Conversion to the requested type must preserve a finite, nonzero direction. The zero vector is allowed for rotating-PSF metadata; fixed dipoles must be nonzero. The untyped factory promotes integers and rationals to floating point. DipoleVector{T}(px, py, pz) retains T. Integer and rational types require exactly representable normalized components; otherwise use a floating-point type or the untyped DipoleVector(px, py, pz). Integer types allow unit axes and zero.

Fields

  • px::T: x component of the dipole vector
  • py::T: y component of the dipole vector
  • pz::T: z component of the dipole vector
source
MicroscopePSFs.DipoleEmitter3D — Type
DipoleEmitter3D{T} <: AbstractEmitter

3D dipole emitter with position, orientation and optical properties.

DipoleEmitter3D{T}(x, y, z, photons, dipole) retains T when its normalized orientation and scalar arguments are representable in T. Otherwise use a floating-point type or the untyped DipoleEmitter3D(x, y, z, photons, dipole), which promotes integer and rational inputs to floating point.

Fields

  • x::T: x-coordinate in microns
  • y::T: y-coordinate in microns
  • z::T: z-coordinate in microns
  • photons::T: number of photons
  • dipole::DipoleVector{T}: dipole orientation vector
source

I/O Functions

MicroscopePSFs.save_psf — Function
save_psf(filename::String, object; metadata::Dict=Dict())

Save a PSF or related object (e.g., ZernikeCoefficients, PupilFunction) to an HDF5 file.

Arguments

  • filename: Path where the PSF will be saved
  • object: Object to save (PSF, ZernikeCoefficients, PupilFunction, etc.)
  • metadata: Optional dictionary of additional metadata to include

Returns

  • filename for chaining
source
MicroscopePSFs.load_psf — Function
load_psf(filename::String)

Load a PSF or related object (e.g., ZernikeCoefficients, PupilFunction) from an HDF5 file.

Arguments

  • filename: Path to the HDF5 file containing a saved PSF object

Returns

  • The loaded object with its original type (PSF, ZernikeCoefficients, PupilFunction, etc.)

Examples

# Load a previously saved PSF
psf = load_psf("my_airy_psf.h5")

# Use the loaded PSF normally
intensity = psf(0.1, 0.2)

# Load Zernike coefficients and use them
zc = load_psf("zernike_coeffs.h5")
psf = ScalarPSF(1.4, 0.532, 1.518; zernike_coeffs=zc)

See also: save_psf

source

Complete API (All Documented Functions)

This section lists additional internal functions and types that are documented but not part of the public API.

Base.copy — Method
copy(p::PupilFunction)

Return a deep copy of p with an independent field array.

source
Base.copy — Method
copy(vp::VectorPupilFunction)

Return a deep copy of vp with independent Ex and Ey field arrays.

source
MicroscopePSFs._calculate_field_amplitude — Method
_calculate_field_amplitude(
    pupil::VectorPupilFunction,
    nₐ::Real, λ::Real, n_medium::Real, n_coverslip::Real, n_immersion::Real, z_stage::Real,
    x::Real, y::Real, z::Real)

Internal helper function to compute complex vector amplitude at given position.

Arguments

  • pupil: Vector pupil function containing Ex and Ey fields
  • nₐ, λ, n_medium, n_coverslip, n_immersion: Optical parameters
  • z_stage: Distance the sample stage was moved away from the nominal focal plane
  • x, y, z: Position in microns

Returns

  • Vector [Ex, Ey] of complex field amplitudes

Notes

  • This is a helper function used by both amplitude() and the PSF evaluation function
  • Implements the core field calculation for a single pupil
source
MicroscopePSFs._integrate_pixels_core! — Method
_integrate_pixels_core!(
    result::AbstractArray,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    func::Function,
    emitter_x::Real,
    emitter_y::Real;
    sampling::Integer=2,
    threaded::Bool=true
)

Internal function implementing the core pixel integration logic. Supports both scalar and vector/tensor return types from the function.

Arguments

  • result: Pre-allocated array where integration results will be stored
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • func: Function to evaluate at PSF coordinates - can return scalar, vector, or tensor
  • emitter_x, emitter_y: Emitter coordinates in same units as pixel edges
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true) Set to false when using with automatic differentiation frameworks

Returns

  • result array filled with integration values
source
MicroscopePSFs._integrate_pixels_generic! — Method
_integrate_pixels_generic!(
    result::AbstractArray,
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter,
    f::Function;
    sampling::Integer=2,
    threaded::Bool=true
)

Generic wrapper for PSF integration that handles coordinate systems and validation. Automatically handles z-coordinates when both PSF and emitter support them. Supports functions that return either scalars or vectors/tensors.

Arguments

  • result: Pre-allocated array where results will be stored
  • psf: Point spread function to integrate
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitter: Emitter with position information
  • f: Function to evaluate (e.g., (p,x,y) -> p(x,y) for intensity)
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • result array filled with integration values
source
MicroscopePSFs._integrate_pixels_generic — Method
_integrate_pixels_generic(
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter,
    f::Function,
    ::Type{T},
    output_dims=();
    sampling::Integer=2,
    threaded::Bool=true
) where T

Internal generic integration routine used by both intensity and amplitude integration. Creates a new result array and delegates to the in-place version. Supports both scalar and vector/tensor return types.

Arguments

  • output_dims: Additional dimensions for the result array beyond the basic [y,x] dimensions. For vector returns (e.g., [Ex, Ey]), set output_dims=(2,) for a [y,x,2] result array.
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • Array with dimensions [y,x] or [y,x,output_dims...] depending on the function's return type
source
MicroscopePSFs._io_load_zernike_coeffs — Method
_io_load_zernike_coeffs(file; group_name="zernike_coefficients")

Load ZernikeCoefficients from an HDF5 file from a specified group. Returns nothing if the group doesn't exist.

source
MicroscopePSFs._io_save_zernike_coeffs — Method
_io_save_zernike_coeffs(file, coeffs; group_name="zernike_coefficients")

Save ZernikeCoefficients to an HDF5 file in a specified group. Does nothing if coeffs is nothing.

source
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{SplinePSF})

Load a SplinePSF from an HDF5 file, reconstructing it using the standard constructor.

source
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{VectorPSF})

Load a VectorPSF from an HDF5 file, reconstructing all components. Stored pupil fields are preserved exactly.

Arguments

  • file: Open HDF5 file handle
  • ::Type{VectorPSF}: Type to load

Returns

  • VectorPSF reconstructed from stored data
source
MicroscopePSFs._resolve_psf_type — Method
_resolve_psf_type(type_str::AbstractString) -> Type

Map a stored psf_type attribute to a MicroscopePSFs type. Accepts the bare name ("SplinePSF") and the MicroscopePSFs.-qualified name found in some saved files. Any other name throws an ArgumentError.

source
MicroscopePSFs._sample_psf_2d — Method
_sample_psf_2d(psf::AbstractPSF, x_range::AbstractRange, y_range::AbstractRange)

Internal helper function to sample a 2D PSF on a regular grid. Returns an array with dimensions [y, x] containing raw PSF values.

source
MicroscopePSFs._sample_psf_3d — Method
_sample_psf_3d(psf::AbstractPSF, x_range::AbstractRange, y_range::AbstractRange, z_range::AbstractRange)

Internal helper function to sample a 3D PSF on a regular grid. Returns an array with dimensions [y, x, z] containing raw PSF values.

source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, pupil::PupilFunction)

Save a PupilFunction to an HDF5 file, including physical parameters and complex field.

source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, psf::ScalarPSF)

Save a ScalarPSF to an HDF5 file, including pupil function and any Zernike coefficients.

source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, psf::SplinePSF)

Save a SplinePSF to an HDF5 file, including the original grid data used to create the spline interpolation.

source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, psf::VectorPSF)

Save a VectorPSF to an HDF5 file, including all optical parameters, dipole orientation, and vector pupil fields.

Arguments

  • file: Open HDF5 file handle
  • psf: VectorPSF to save
source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, pupil::VectorPupilFunction)

Save a VectorPupilFunction to an HDF5 file, including all physical parameters and both Ex and Ey field components.

source
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, zc::ZernikeCoefficients)

Save ZernikeCoefficients to an HDF5 file, storing magnitude and phase arrays.

source
MicroscopePSFs.api — Method

MicroscopePSFs.jl API Overview

This document provides a concise overview of the MicroscopePSFs.jl package API, designed for both human users and AI assistants.

Key Concepts

MicroscopePSFs.jl models point spread functions (PSFs) for microscopy applications, particularly single-molecule localization microscopy (SMLM). The package provides:

  • 2D and 3D PSF models from simple Gaussian to complex vectorial diffraction
  • Aberration modeling using Zernike polynomials
  • Dipole orientation support for modeling fluorophore emission patterns
  • Camera integration for simulating realistic microscope images
  • Performance optimization through spline interpolation and multi-threading

Units Convention: All distances are in microns (μm). Wavelengths should be specified in microns (e.g., 0.532 for 532nm green light).

Type Hierarchy

AbstractPSF{T}
├── Abstract2DPSF{T}
│   ├── GaussianPSF{T}         # Isotropic 2D Gaussian
│   └── AiryPSF{T}             # 2D Airy diffraction pattern
├── Abstract3DPSF{T}
│   ├── ScalarPSF{T}           # 3D scalar diffraction
│   └── VectorPSF{T}           # 3D vectorial with dipole
└── SplinePSF{T,IT}            # Spline interpolation (2D/3D)

PupilFunction{T}               # Complex pupil for scalar models
VectorPupilFunction{T}         # Vectorial pupil with polarization

Essential Types

2D PSF Models

# Gaussian PSF - simplest model
struct GaussianPSF{T} <: Abstract2DPSF{T}
    σ::T        # Standard deviation in microns
end

# Airy PSF - diffraction-limited 2D
struct AiryPSF{T} <: Abstract2DPSF{T}
    nₐ::T       # Numerical aperture
    λ::T        # Wavelength in microns
end

3D PSF Models

# Scalar diffraction PSF - fast 3D model
struct ScalarPSF{T} <: Abstract3DPSF{T}
    nₐ::T                           # Numerical aperture
    λ::T                            # Wavelength in microns
    n::T                            # Refractive index of immersion medium
    pupil::PupilFunction{T}         # Complex pupil function
    # ... (internal fields)
end

# Vectorial PSF - accurate model with dipole orientation
struct VectorPSF{T} <: Abstract3DPSF{T}
    nₐ::T                           # Numerical aperture
    λ::T                            # Wavelength in microns
    dipole::DipoleVector{T}         # Dipole orientation
    pupil::VectorPupilFunction{T}   # Vector pupil function
    z_stage::T                      # Stage position offset
    # ... (internal fields)
end

Support Types

# Dipole orientation (normalized vector)
struct DipoleVector{T}
    px::T
    py::T
    pz::T
end

# Zernike aberration coefficients
struct ZernikeCoefficients{T}
    phase::Vector{T}      # Phase coefficients (radians)
    mag::Vector{T}        # Magnitude coefficients
end

Constructor Examples

Basic PSF Construction

using MicroscopePSFs

# 2D Gaussian PSF with σ = 150nm
psf_gauss = GaussianPSF(0.15)

# 2D Airy PSF: NA=1.4, λ=532nm
psf_airy = AiryPSF(1.4, 0.532)

# 3D Scalar PSF: NA=1.4, λ=680nm, n=1.52 (oil immersion)
psf_scalar = ScalarPSF(1.4, 0.680, 1.52)

# Vector PSF with x-oriented dipole
dipole = DipoleVector(1.0, 0.0, 0.0)  # x-dipole
psf_vector = VectorPSF(1.4, 0.690, dipole; z_stage=0.0)

PSF with Aberrations

# Create Zernike coefficients (max radial order 15)
zc = ZernikeCoefficients(15)

# Add specific aberrations (Noll indexing)
zc.phase[5] = 0.3    # Defocus
zc.phase[6] = 0.5    # Vertical astigmatism
zc.phase[7] = -0.2   # Oblique astigmatism

# Create aberrated PSF
psf_aberrated = ScalarPSF(1.4, 0.532, 1.52; zernike_coeffs=zc)

Spline Interpolation

# Convert any PSF to spline for fast evaluation
x_range = -2.0:0.05:2.0  # microns
y_range = -2.0:0.05:2.0
z_range = -1.0:0.05:1.0

psf_spline = SplinePSF(psf_scalar, x_range, y_range, z_range)

Core Functions

PSF Evaluation

# 2D PSF evaluation
intensity = psf(x, y)                    # Single point
intensity = psf(x_vec, y_vec)            # Vectorized

# 3D PSF evaluation  
intensity = psf(x, y, z)                 # Single point
intensity = psf(x_vec, y_vec, z_vec)     # Vectorized

# Complex amplitude (before squaring)
amp = amplitude(psf, x, y)              # 2D
amp = amplitude(psf, x, y, z)           # 3D

Image Generation

# Setup camera
camera = IdealCamera(nx, ny, pixel_size)  # nx, ny pixels, size in microns

# Create emitter
emitter = Emitter2D(x, y, photons)       # 2D
emitter = Emitter3D(x, y, z, photons)    # 3D

# Generate image
image = integrate_pixels(psf, camera, emitter)

# With support region (faster computation)
image = integrate_pixels(psf, camera, emitter; support=0.5)

# Multiple emitters
emitters = [Emitter3D(x[i], y[i], z[i], photons[i]) for i in 1:n]
image = integrate_pixels(psf, camera, emitters)

Amplitude Integration

# Integrate complex amplitude (preserves phase)
complex_image = integrate_pixels_amplitude(psf, camera, emitter)

# Useful for coherent imaging or interferometry

Common Workflows

1. Simple 2D Imaging

using MicroscopePSFs, SMLMData

# Create PSF
psf = GaussianPSF(0.15)  # 150nm standard deviation

# Setup camera: 100x100 pixels, 100nm pixel size
camera = IdealCamera(100, 100, 0.1)

# Create emitter at (5μm, 5μm) with 1000 photons
emitter = Emitter2D(5.0, 5.0, 1000.0)

# Generate image
image = integrate_pixels(psf, camera, emitter)

2. 3D Localization with Astigmatism

# Create PSF with astigmatism for 3D
zc = ZernikeCoefficients(5)
zc.phase[6] = 1.0  # Add astigmatism

psf = ScalarPSF(1.4, 0.680, 1.52; zernike_coeffs=zc)

# Emitter at different z positions
for z in [-0.5, 0.0, 0.5]  # microns
    emitter = Emitter3D(5.0, 5.0, z, 1000.0)
    image = integrate_pixels(psf, camera, emitter)
    # PSF shape changes with z due to astigmatism
end

3. Dipole Orientation Imaging

# Create dipoles with different orientations
dipole_x = DipoleVector(1.0, 0.0, 0.0)
dipole_z = DipoleVector(0.0, 0.0, 1.0)

psf_x = VectorPSF(1.4, 0.690, dipole_x)
psf_z = VectorPSF(1.4, 0.690, dipole_z)

# Compare PSF patterns
emitter = DipoleEmitter3D(5.0, 5.0, 0.0, 1000.0, dipole_x)
image_x = integrate_pixels(psf_x, camera, emitter)

emitter_z = DipoleEmitter3D(5.0, 5.0, 0.0, 1000.0, dipole_z)
image_z = integrate_pixels(psf_z, camera, emitter)

4. Performance Optimization with Splines

# Original PSF (slow for many evaluations)
psf = ScalarPSF(1.4, 0.680, 1.52)

# Convert to spline (fast evaluation)
psf_spline = SplinePSF(psf, -2:0.05:2, -2:0.05:2, -1:0.05:1)

# Use spline for faster multi-emitter simulations
emitters = [Emitter3D(rand()*10, rand()*10, randn()*0.5, 1000.0) 
            for _ in 1:100]
            
@time image_slow = integrate_pixels(psf, camera, emitters)
@time image_fast = integrate_pixels(psf_spline, camera, emitters)
# Spline version is typically 10-100x faster

Complete Examples

Example 1: Simulating STORM/PALM Data

using MicroscopePSFs, SMLMData, Random

# Microscope parameters
NA = 1.4                    # Oil objective
wavelength = 0.647          # 647nm in microns
n_immersion = 1.515         # Oil refractive index

# Create PSF
psf = ScalarPSF(NA, wavelength, n_immersion)

# Camera setup: 256x256 pixels, 160nm pixel size
camera = IdealCamera(256, 256, 0.160)

# Generate random emitters
Random.seed!(42)
n_emitters = 50
emitters = [
    Emitter3D(
        rand() * 40.0,      # x in [0, 40] microns
        rand() * 40.0,      # y in [0, 40] microns  
        randn() * 0.3,      # z in [-0.9, 0.9] microns
        500 + rand() * 2000 # 500-2500 photons
    )
    for _ in 1:n_emitters
]

# Generate image
image = integrate_pixels(psf, camera, emitters; support=0.7)

# Add noise (Poisson + Gaussian read noise)
# Note: Use your preferred noise simulation method
# For example: noisy_image = image + sqrt.(image) .* randn(size(image))  # Simple Poisson approximation
noisy_image = image .+ 0.01 * randn(size(image))  # Gaussian read noise

Example 2: PSF Engineering for 3D Imaging

using MicroscopePSFs

# Create tetrapod PSF using phase mask
zc = ZernikeCoefficients(15)

# Tetrapod phase mask coefficients (example values)
zc.phase[11] = 1.5   # Primary spherical
zc.phase[22] = 0.8   # Secondary spherical
zc.phase[5] = 0.3    # Defocus

# Create engineered PSF
psf_tetrapod = ScalarPSF(1.4, 0.680, 1.52; zernike_coeffs=zc)

# Evaluate PSF at different z positions
z_positions = -1.5:0.1:1.5
camera = IdealCamera(64, 64, 0.1)

for (i, z) in enumerate(z_positions)
    emitter = Emitter3D(3.2, 3.2, z, 1000.0)
    image = integrate_pixels(psf_tetrapod, camera, emitter)
    # PSF rotates with z, enabling 3D localization
end

Example 3: Analyzing PSF Properties

using MicroscopePSFs

# Create PSF
psf = ScalarPSF(1.4, 0.532, 1.52)

# Compute FWHM at focus
x = -1.0:0.01:1.0
y = 0.0
z = 0.0
intensity = [psf(xi, y, z) for xi in x]
intensity_norm = intensity ./ maximum(intensity)

# Find FWHM
half_max_indices = findall(intensity_norm .>= 0.5)
fwhm = x[half_max_indices[end]] - x[half_max_indices[1]]
println("Lateral FWHM: $(fwhm * 1000) nm")

# Axial profile
z_range = -2.0:0.01:2.0
axial_intensity = [psf(0.0, 0.0, zi) for zi in z_range]

# 3D OTF (via FFT of PSF)
# ... (implementation depends on specific analysis needs)

Important Notes

  1. Memory efficiency: Use support parameter in integrate_pixels to limit computation region
  2. Threading: Multi-emitter integration is automatically parallelized
  3. Automatic differentiation: Compatible with Enzyme.jl for gradient computations
  4. Normalization: PSFs are normalized so total integrated intensity equals photon count
  5. Coordinate system: (0,0) is at the corner of the first pixel, not the center

Common Pitfalls

  • Remember all distances are in microns, not nanometers
  • Zernike coefficients use Noll indexing (starting from 1)
  • For VectorPSF, the dipole vector is automatically normalized
  • Support regions should be chosen carefully - too small truncates the PSF, too large wastes computation
  • When using splines, ensure the interpolation range covers all possible emitter positions

Multi-channel PSFs

These types use the standard PSF interface. All distances are microns. Each wrapper below is a complete 3D PSF; MultiChannelPSF is a separate container and deliberately does not subtype AbstractPSF.

using MicroscopePSFs

s = ScalarPSF(1.2, 0.68, 1.33)
v = VectorPSF(1.2, 0.68; n_medium=1.33, n_coverslip=1.33, n_immersion=1.33)

# One complex component vector per incoherent dipole, in x/y/z order if rotating.
fields = dipole_fields(v, 0.1, 0.0, 0.2)
intensity = sum(sum(abs2, E) for E in fields)

# Emitter-side high-NA defocus: evaluates s at z + dz.
plane = PlanePSF(s; dz=0.2)

# Camera polarization: 1 passes Ex/H, 2 passes Ey/V. Not renormalized.
h = AnalyzerPSF(v; component=1)

# Opposed objectives, bottom at -z, axial rotating-dipole mirror sign included.
# Fixed vector arms must instead use bottom DipoleVector(px, py, -pz).
fourpi = FourPiPSF(s, s; phase=0.3)

# Each channel normalized alone; transmission splits the emitter's total photons.
mc = MultiChannelPSF(; channels=[s, plane], transmission=[0.5, 0.5])

# Absolute camera coordinates, not pixels; registration is forward [A t].
pos = channel_position(mc, 2, 1.0, 2.0, 0.1)

The forward law is expected image i = N * mc.transmission[i] * integrate_pixels(mc.channels[i], camera_i, unit-photon emitter at channel_position(mc, i, x, y, z)). Registration is in absolute camera microns, so it survives crops; z_offset[i] adds to z with the same sign as PlanePSF.dz. The reference transform is identity with zero axial offset.

Channel PSFs must integrate to one alone, with 4Pi phase channels normalized on average over the fringe. Raw AnalyzerPSF channels already carry the polarization split: normalize them alone before assigning transmission. Vector 4Pi arms require n_medium ≥ NA. phase is the cavity phase; polarization-specific phases use AnalyzerPSF(FourPiPSF(top, bottom; phase=φ); component=c) per channel. Emitter z and dipole z share one frame, with +z pointing into the sample, away from the objective. The opposed-objective mirror rule applies. Splines hold intensity only and cannot enter coherent sums. All wrappers and the container support recursive HDF5 I/O.


api() returns this documentation as a plain String.

source
MicroscopePSFs.apply_defocus! — Function
apply_defocus!(vp::VectorPupilFunction, z::Real, z_stage::Real=zero(z))

Apply the vectorial axial defocus phase to a VectorPupilFunction in place.

The phase exp(im·2π·(k_{z,medium}·z − k_{z,immersion}·z_stage)) is multiplied into both the Ex and Ey field components at every in-aperture sample, using the optical parameters (nₐ, λ, n_medium, n_coverslip, n_immersion) stored in vp. Samples outside the aperture are left unchanged (they are already zero).

This reuses the same k-space grid and phase convention (calculate_wave_vectors, calculate_axial_phase) as VectorPSF evaluation, so the result is the pupil whose Fourier transform yields the field at emitter depth z and stage position z_stage.

Arguments

  • vp: Vector pupil function to modify
  • z: Emitter depth above the coverslip (μm)
  • z_stage: Distance the stage was moved from the nominal focal plane (μm, default 0)

Returns

  • The modified vp
source
MicroscopePSFs.fill_vector_pupils! — Function
fill_vector_pupils!(vpupil::VectorPupilFunction, dipole::DipoleVector,
                  base_pupil::Union{Nothing, PupilFunction}=nothing;
                  normalize::Bool=true)

Fill vector pupil function with field components including dipole orientation, base aberrations, and proper apodization.

This pre-calculates all position-independent factors of the pupil function. When n_medium == n_coverslip, use nₐ <= 0.995 * n_medium: apodization diverges at grazing angles and grid sampling fails near 90 degrees. Real objectives stay below this bound. For a higher-index coverslip, the finite medium-grazing limit is evaluated by combining Fresnel transmission and apodization.

Arguments

  • vpupil: Vector pupil function to fill
  • dipole: Dipole orientation vector
  • base_pupil: Optional base aberration pupil function

Keyword Arguments

  • normalize: Whether to normalize the pupil function (default: true)

Returns

  • Filled vector pupil function (normalized if normalize=true)
source
MicroscopePSFs.get_pixel_indices — Method
get_pixel_indices(
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter,
    support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}}
) -> Tuple{UnitRange{Int},UnitRange{Int}}

Get the pixel indices that overlap with the support region.

Arguments

  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitter: Emitter with position information
  • support: Region of interest, either a radius or explicit bounds (xmin, xmax, ymin, ymax)

Returns

  • Tuple of (irange, jrange) with pixel indices that cover the support region If the support region doesn't overlap with the camera, returns empty ranges
source
MicroscopePSFs.integrate_pixels! — Method
integrate_pixels!(
    result::AbstractMatrix{T},
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
) where T <: Real

Integrate PSF intensity over camera pixels using camera object, storing the result in a pre-allocated matrix.

Arguments

  • result: Pre-allocated array where results will be stored
  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • The result array, filled with integrated intensities
source
MicroscopePSFs.integrate_pixels! — Method
integrate_pixels!(
    result::AbstractMatrix{T},
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
) where T <: Real

Integrate PSF intensity over camera pixels, storing the result in a pre-allocated matrix. Automatically uses z-coordinate if both PSF and emitter support it.

Arguments

  • result: Pre-allocated array where results will be stored
  • psf: Point spread function to integrate
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true) Set to false when using with automatic differentiation frameworks

Returns

  • The result array, filled with integrated intensities scaled by emitter.photons

Notes

  • Array is indexed as [y,x] with [1,1] at top-left pixel
source
MicroscopePSFs.integrate_pixels_amplitude! — Method
integrate_pixels_amplitude!(
    result::AbstractArray{Complex{T},3},
    psf::VectorPSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
) where T <: Real

Special version of amplitude integration for VectorPSF that preserves polarization components. Uses the flexible core integration function that handles vector returns.

Arguments

  • result: Pre-allocated 3D complex array where results will be stored, dimensions [y, x, pol]
  • psf: VectorPSF instance
  • camera: Camera geometry defining pixel edges
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • The result array, filled with integrated complex amplitudes for each polarization

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
MicroscopePSFs.integrate_pixels_amplitude! — Method
integrate_pixels_amplitude!(
    result::AbstractArray{Complex{T}},
    psf::AbstractPSF,
    camera::AbstractCamera,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
) where T <: Real

Integrate PSF complex amplitude over camera pixels using camera object, storing the result in a pre-allocated matrix.

Arguments

  • result: Pre-allocated complex array where results will be stored
  • psf: Point spread function to integrate
  • camera: Camera geometry defining pixel edges
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • The result array, filled with integrated complex amplitudes

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
MicroscopePSFs.integrate_pixels_amplitude! — Method
integrate_pixels_amplitude!(
    result::AbstractArray{Complex{T}},
    psf::AbstractPSF,
    pixel_edges_x::AbstractVector,
    pixel_edges_y::AbstractVector,
    emitter::AbstractEmitter;
    sampling::Integer=2,
    threaded::Bool=true
) where T <: Real

Integrate PSF complex amplitude over camera pixels, storing the result in a pre-allocated matrix. Automatically uses z-coordinate if both PSF and emitter support it.

Arguments

  • result: Pre-allocated complex array where results will be stored
  • psf: Point spread function to integrate
  • pixel_edges_x, pixel_edges_y: Arrays defining pixel edge coordinates
  • emitter: Emitter with position information
  • sampling: Subpixel sampling density (default: 2)
  • threaded: Whether to use multi-threading for integration (default: true)

Returns

  • The result array, filled with integrated complex amplitudes

Notes

  • Array is indexed as [y,x] with [1,1] at top-left pixel

This returns a unit-photon field: emitter photons are ignored. Squaring a pixel-integrated field is not pixel-integrated intensity; use integrate_pixels for intensity images.

source
MicroscopePSFs.normalize! — Method
normalize!(p::PupilFunction)

Normalize to unit energy inside the strict circular aperture used by amplitude. The field must have finite positive energy there; zero fields may be stored but cannot be normalized. Samples outside the aperture do not contribute.

source
MicroscopePSFs.normalize! — Method
normalize!(p::VectorPupilFunction)

Normalize the electric field components of the vector pupil function. Ensures total energy across both components equals 1. Energy includes exactly the circular aperture samples used by vector evaluation.

Arguments

  • p: Vector pupil function to normalize

Returns

  • Normalized vector pupil function
source
MicroscopePSFs.update_pupils! — Method
update_pupils!(psf::VectorPSF) -> VectorPSF

Update the vector pupil functions based on stored Zernike coefficients and/or base pupil. This is useful after modifying aberrations to regenerate the pupil fields.

Arguments

  • psf: VectorPSF to update

Returns

  • Updated VectorPSF

Notes

  • Requires either stored Zernike coefficients or a base pupil
  • For rotating dipoles (multiple pupils), updates each pupil with the appropriate dipole orientation
  • Returns the updated PSF for method chaining
source

Zernike Module Internal API

Base.setindex! — Method
setindex!(zc::ZernikeCoefficients, value::Real, i::Integer, field::Symbol)

Assign a Noll-indexed coefficient with zc[i, :phase] = value or zc[i, :mag] = value. Phase coefficients are in radians. Returns zc.

source