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
MicroscopePSFs.AbstractPSF — Type
AbstractPSFAbstract base type for all Point Spread Functions
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)
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ₐ/λ
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 micronsn::T: Refractive indexpupil::PupilFunction{T}: Complex pupil functionzernike_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.
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 micronsn_medium::T: Sample medium refractive indexn_coverslip::T: Cover slip refractive indexn_immersion::T: Immersion medium refractive indexdipole::DipoleVector{T}: Dipole orientationz_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 aberrationszernike_coeffs::Union{Nothing, ZernikeCoefficients{T}}: Zernike coefficients used to create this PSF (if applicable)
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 3DSplinePSF.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)MicroscopePSFs.AnalyzerPSF — Type
AnalyzerPSF(psf::AbstractPSF; component::Integer)Pass one camera-frame polarization component of a vector field.
Arguments
psf:VectorPSF, or aPlanePSF,FourPiPSF, orAnalyzerPSFover one.component: Required component: 1 passesEx(H), 2 passesEy(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)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 tolerancesqrt(eps(T))of the coarser arm type. A fixed bottom dipole must mirror the top:DipoleVector(px, py, -pz). Vector arms requiren_medium ≥ NAto 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)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 ofAbstractPSFs, 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 thePlanePSF.dzsign convention. Defaults to zero.reference: Reference channel index (default 1), whose registration and z shift must be identity and zero withinsqrt(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)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 inSMLMDatapixel 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 roundingPSF Acceleration
MicroscopePSFs.SplinePSF — Type
SplinePSF{T<:AbstractFloat, IT<:AbstractInterpolation} <: AbstractPSFA point spread function (PSF) represented as a B-spline interpolation.
Fields
spline: The B-spline interpolation objectx_range: Range of x-coordinates used for uniform grid interpolationy_range: Range of y-coordinates used for uniform grid interpolationz_range: Range of z-coordinates for 3D PSFs, ornothingfor 2D PSFsx_min,x_max,y_min,y_max,z_min,z_max: Cached bounds for fast evaluationoriginal_grid: Original grid data used to create the interpolationinterp_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
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 functionx::Real: X position in μmy::Real: Y position in μmz::Real: Z position in μm
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 intensityamplitude(psf::AbstractPSF, x::Real, y::Real, z::Real)Evaluate complex field amplitude in 3D. z is axial distance from focus in microns.
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 instancex, 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
amplitude(psf::VectorPSF, x::Real, y::Real, z::Real)Compute complex vector amplitude at given position.
Arguments
psf: Vector PSF instancex, y: Lateral position in microns relative to PSF centerz: 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)
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 instancex, 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
amplitude(psf::SplinePSF, x::Real, y::Real)Calculate the complex amplitude of the PSF at position (x, y) with z=0.
Arguments
psf: SplinePSF instancex, y: Coordinates in microns relative to PSF center
Returns
- Complex amplitude = sqrt(intensity) with zero phase
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 asScalarPSForVectorPSF.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)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 integratepixel_edges_x::AbstractVector: X-coordinate edges of pixels in micronspixel_edges_y::AbstractVector: Y-coordinate edges of pixels in micronsemitter::AbstractEmitter: Emitter with position in microns relative to camerasupport: 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 dimensionthreaded::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
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 integratecamera::AbstractCamera: Camera geometry defining pixel edges in micronsemitter::AbstractEmitter: Emitter with position in microns relative to camerasupport: 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 dimensionthreaded::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
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 integratepixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesemitters: Vector of emitters with position informationsupport: 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_amplitudeand sum complex amplitudes
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 integratecamera: Camera geometry defining pixel edgesemitters: Vector of emitters with position informationsupport: 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
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 instancecamera: Camera geometryemitter: Emitter with position informationsupport: 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 accuracythreaded: 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
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 integratepixel_edges_x::AbstractVector: X-coordinate edges of pixels in micronspixel_edges_y::AbstractVector: Y-coordinate edges of pixels in micronsemitter::AbstractEmitter: Emitter with position in microns relative to camerasupport: 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 dimensionthreaded::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
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 integratecamera::AbstractCamera: Camera geometry defining pixel edges in micronsemitter::AbstractEmitter: Emitter with position in microns relative to camerasupport: 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 dimensionthreaded::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.
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 integratepixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesemitters: Vector of emitters with position informationsupport: 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.
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 integratecamera: Camera geometry defining pixel edgesemitters: Vector of emitters with position informationsupport: 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.
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 instancecamera: Camera geometry defining pixel edgesemitter: Emitter with position informationsampling: 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.
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 instancecamera: Camera geometry defining pixel edgesemitters: Vector of emitters with position informationsampling: 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.
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 instancecamera: Camera geometryemitter: Emitter with position informationsupport: 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 accuracythreaded: 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.
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.emitteroremitters: One emitter or a vector of emitters, summed coherently.support: Scalar wrappers retain the generic support options. Vector wrappers requireInf, as forVectorPSFintegration 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))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 integratecamera: Camera geometry defining pixel edgesemitters: Vector of emitters with position informationsupport: 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
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 integratecamera: Camera geometry defining pixel edgesemitters: Vector of emitters with position informationsupport: 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.
Pupil Functions
MicroscopePSFs.PupilFunction — Type
PupilFunction{T}Represents a pupil function with physical parameters.
Fields
nₐ: Numerical apertureλ: Wavelength in μmn: Refractive indexfield: Square complex field array with at least two samples per axis
MicroscopePSFs.VectorPupilFunction — Type
VectorPupilFunction{T}Vector pupil function storing Ex,Ey field components as PupilFunctions.
Fields
nₐ::T: Numerical apertureλ::T: Wavelength in μmn_medium::T: Refractive index of the sample mediumn_coverslip::T: Refractive index of the coverslipn_immersion::T: Refractive index of the immersion mediumEx::PupilFunction{T}: x-component of electric fieldEy::PupilFunction{T}: y-component of electric field
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
Zernike Polynomial Functions
MicroscopePSFs.Zernike.zernikepolynomial — Function
zernikepolynomial(n::Integer, l::Integer, ρ::Real, ϕ::Real) -> RealCompute the complete Zernike polynomial Z_n^l(ρ,ϕ) with Noll normalization (RMS=1).
Arguments
n: Radial orderl: 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
zernikepolynomial(j::Integer, ρ::Real, ϕ::Real) -> RealCompute Zernike polynomial using Noll index.
Arguments
j: Polynomial index using Noll conventionρ: Radial coordinateϕ: Azimuthal angle
MicroscopePSFs.Zernike.radialpolynomial — Function
radialpolynomial(n::Integer, m::Integer, ρ::Real) -> RealCompute the unnormalized radial component R_n^m(ρ) of the Zernike polynomial.
Arguments
n: Radial orderm: Azimuthal order (absolute value of azimuthal frequency)ρ: Radial coordinate (0 ≤ ρ ≤ 1)
Notes
- Returns 0 for ρ > 1
- No normalization applied - returns the standard mathematical form
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
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 coefficientsgrid_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] = 1supplies the unit piston; no offset is added. - Phase is applied as exp(iϕ)
- Uses Noll indexing throughout
Zernike Analysis Functions
MicroscopePSFs.Zernike.rms — Function
rms(coeffs::ZernikeCoefficients) -> Tuple{Float64,Float64}Calculate RMS values for magnitude and phase coefficients (excluding piston).
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.
Index Conversion
MicroscopePSFs.Zernike.nl2osa — Function
nl2osa(n::Integer, l::Integer) -> IntConvert 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)
15MicroscopePSFs.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
MicroscopePSFs.Zernike.nl2noll — Function
nl2noll(n::Integer, l::Integer) -> IntConvert 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
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
MicroscopePSFs.Zernike.osa2noll — Function
osa2noll(j::Integer) -> IntConvert from OSA/ANSI to Noll index.
MicroscopePSFs.Zernike.noll2osa — Function
noll2osa(j::Integer) -> IntConvert from Noll to OSA/ANSI index.
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 vectorpy::T: y component of the dipole vectorpz::T: z component of the dipole vector
MicroscopePSFs.DipoleEmitter3D — Type
DipoleEmitter3D{T} <: AbstractEmitter3D 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 micronsy::T: y-coordinate in micronsz::T: z-coordinate in micronsphotons::T: number of photonsdipole::DipoleVector{T}: dipole orientation vector
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 savedobject: Object to save (PSF, ZernikeCoefficients, PupilFunction, etc.)metadata: Optional dictionary of additional metadata to include
Returns
filenamefor chaining
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
Complete API (All Documented Functions)
This section lists additional internal functions and types that are documented but not part of the public API.
MicroscopePSFs.Abstract2DPSF — Type
Abstract2DPSF{T<:AbstractFloat}Abstract type for all 2D point spread functions. Parameterized by numeric type T.
MicroscopePSFs.Abstract3DPSF — Type
Abstract3DPSF{T<:AbstractFloat}Abstract type for all 3D point spread functions.
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 fieldsnₐ,λ,n_medium,n_coverslip,n_immersion: Optical parametersz_stage: Distance the sample stage was moved away from the nominal focal planex, 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
MicroscopePSFs._check_normalization — Method
_check_normalization(values; tol=1e-6)Internal function to verify array sums to 1 within tolerance.
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 storedpixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesfunc: Function to evaluate at PSF coordinates - can return scalar, vector, or tensoremitter_x,emitter_y: Emitter coordinates in same units as pixel edgessampling: 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
resultarray filled with integration values
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 storedpsf: Point spread function to integratepixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesemitter: Emitter with position informationf: 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
resultarray filled with integration values
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 TInternal 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
MicroscopePSFs._io_check_required_fields — Method
_io_check_required_fields(group, fields)Check if all required fields exist in an HDF5 group. Throws an error if any required field is missing.
MicroscopePSFs._io_load_complex_array — Method
_io_load_complex_array(group, name)Load a complex array from an HDF5 group by combining real and imaginary parts.
MicroscopePSFs._io_load_range — Method
_io_load_range(group, name)Load a range from an HDF5 group by reconstructing from start, step, and length.
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.
MicroscopePSFs._io_save_complex_array — Method
_io_save_complex_array(group, name, array)Save a complex array to an HDF5 group by splitting into real and imaginary parts.
MicroscopePSFs._io_save_pupil_params — Method
_io_save_pupil_params(params, pupil)Save common pupil parameters to an HDF5 group.
MicroscopePSFs._io_save_range — Method
_io_save_range(group, name, range)Save a range to an HDF5 group by storing start, step, and length.
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.
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{AiryPSF})Load an AiryPSF from an HDF5 file.
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{GaussianPSF})Load a GaussianPSF from an HDF5 file.
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{PupilFunction})Load a PupilFunction from an HDF5 file.
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{ScalarPSF})Load a ScalarPSF from an HDF5 file.
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.
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
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{VectorPupilFunction})Load a VectorPupilFunction from an HDF5 file.
MicroscopePSFs._load_psf_impl — Method
_load_psf_impl(file::_H5Parent, ::Type{ZernikeCoefficients})Load ZernikeCoefficients from an HDF5 file.
MicroscopePSFs._resolve_psf_type — Method
_resolve_psf_type(type_str::AbstractString) -> TypeMap 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.
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.
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.
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, psf::AiryPSF)Save an AiryPSF to an HDF5 file.
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, psf::GaussianPSF)Save a GaussianPSF to an HDF5 file.
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.
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.
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.
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 handlepsf: VectorPSF to save
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.
MicroscopePSFs._save_psf_impl — Method
_save_psf_impl(file::_H5Parent, zc::ZernikeCoefficients)Save ZernikeCoefficients to an HDF5 file, storing magnitude and phase arrays.
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 polarizationEssential 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
end3D 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)
endSupport 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
endConstructor 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) # 3DImage 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 interferometryCommon 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
end3. 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 fasterComplete 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 noiseExample 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
endExample 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
- Memory efficiency: Use
supportparameter inintegrate_pixelsto limit computation region - Threading: Multi-emitter integration is automatically parallelized
- Automatic differentiation: Compatible with Enzyme.jl for gradient computations
- Normalization: PSFs are normalized so total integrated intensity equals photon count
- 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.
MicroscopePSFs.apply_aperture! — Function
apply_aperture!(p::PupilFunction, radius::Real=1.0)Apply circular aperture to pupil function. Radius is relative to NA.
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 modifyz: 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
MicroscopePSFs.apply_defocus! — Method
apply_defocus!(p::PupilFunction, z::Real)Apply defocus phase to pupil function for propagation distance z.
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 filldipole: Dipole orientation vectorbase_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)
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 coordinatesemitter: Emitter with position informationsupport: 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
MicroscopePSFs.integrate_pixels! — Method
integrate_pixels!(
result::AbstractMatrix{T},
psf::AbstractPSF,
camera::AbstractCamera,
emitter::AbstractEmitter;
sampling::Integer=2,
threaded::Bool=true
) where T <: RealIntegrate 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 storedpsf: Point spread function to integratecamera: Camera geometry defining pixel edgesemitter: Emitter with position informationsampling: Subpixel sampling density (default: 2)threaded: Whether to use multi-threading for integration (default: true)
Returns
- The
resultarray, filled with integrated intensities
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 <: RealIntegrate 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 storedpsf: Point spread function to integratepixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesemitter: Emitter with position informationsampling: 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
resultarray, filled with integrated intensities scaled by emitter.photons
Notes
- Array is indexed as [y,x] with [1,1] at top-left pixel
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 <: RealSpecial 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 instancecamera: Camera geometry defining pixel edgesemitter: Emitter with position informationsampling: Subpixel sampling density (default: 2)threaded: Whether to use multi-threading for integration (default: true)
Returns
- The
resultarray, 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.
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 <: RealIntegrate 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 storedpsf: Point spread function to integratecamera: Camera geometry defining pixel edgesemitter: Emitter with position informationsampling: Subpixel sampling density (default: 2)threaded: Whether to use multi-threading for integration (default: true)
Returns
- The
resultarray, 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.
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 <: RealIntegrate 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 storedpsf: Point spread function to integratepixel_edges_x,pixel_edges_y: Arrays defining pixel edge coordinatesemitter: Emitter with position informationsampling: Subpixel sampling density (default: 2)threaded: Whether to use multi-threading for integration (default: true)
Returns
- The
resultarray, 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.
MicroscopePSFs.kmax — Method
kmax(p::PupilFunction)Get maximum spatial frequency in μm⁻¹
MicroscopePSFs.kmax — Method
Get maximum spatial frequency in μm⁻¹
MicroscopePSFs.kpixelsize — Method
kpixelsize(p::PupilFunction)Get pupil plane sampling in μm⁻¹
MicroscopePSFs.kpixelsize — Method
Get pupil plane sampling in μm⁻¹
MicroscopePSFs.k₀ — Method
k₀(p::PupilFunction)Get central wavevector magnitude in μm⁻¹
MicroscopePSFs.k₀ — Method
Get central wavevector magnitude in μm⁻¹
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.
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
MicroscopePSFs.supports_3d — Method
supports_3d(psf_type::Type{<:AbstractPSF}) -> BoolCheck if a PSF type supports 3D evaluation via psf(x, y, z).
MicroscopePSFs.update_pupils! — Method
update_pupils!(psf::VectorPSF) -> VectorPSFUpdate 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
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.
MicroscopePSFs.Zernike.get_nl — Method
get_nl(j::Integer) -> Tuple{Int,Int}Get (n,l) indices from Noll index.