Microscope Image Generation
This page explains how to generate realistic microscope images from simulated SMLM data and the available noise options.
Overview
SMLMSim can convert SMLD objects containing emitter coordinates into realistic microscope image stacks using point spread function (PSF) models. This functionality enables:
- Creating synthetic microscope data for algorithm testing
- Visualizing simulated molecules
- Generating training data for deep learning models
- Testing localization algorithms with realistic inputs
Basic Image Generation
The primary functions for creating images are:
gen_images: Generate multiple frames (3D stack)gen_image: Generate a single frame (2D image)
Creating a Full Image Stack
To generate a complete image stack from an SMLD object:
using SMLMSim
using MicroscopePSFs
# Create or load an SMLD object
# (This could be from either static or diffusion simulation)
params = StaticSMLMParams(density=1.0, σ_psf=0.13, nframes=1000)
camera = IdealCamera(128, 128, 0.1) # 128×128 pixels, 100nm pixels
pattern = Nmer2D(n=6, d=0.2) # Hexamer with 200nm diameter
molecule = GenericFluor(1e4, [-50.0 50.0; 1e-2 -1e-2]) # Blinking model
# Run simulation
smld_true, smld_model, smld_noisy = simulate(params, pattern=pattern, molecule=molecule, camera=camera)
# Create a PSF model (Gaussian with 150nm width)
psf = GaussianPSF(0.15) # 150nm PSF width
# Generate image stack from emitter data
images = gen_images(smld_model, psf;
bg=5.0, # background photons per pixel
poisson_noise=true # add realistic photon counting noise
)The resulting images is a 3D array with dimensions [height, width, frames] that can be used for visualization or algorithm development.
Creating a Single Frame
To generate an image for a specific frame:
# Generate image for frame 10
frame_image = gen_image(smld_model, psf, 10;
bg=5.0,
poisson_noise=true
)Function Parameters
The gen_images function has the following signature:
gen_images(smld::SMLD, psf::AbstractPSF; kwargs...) -> Array{T, 3} where T<:RealRequired Parameters
smld::SMLD: Single molecule localization data containerpsf::AbstractPSF: Point spread function model from MicroscopePSFs.jl
Optional Keyword Arguments
# Complete example with all available options
images = gen_images(smld_model, psf;
dataset::Int=1, # Dataset number to use from SMLD
frames=nothing, # Specific frames to generate (default: all frames)
support=Inf, # PSF support region size in μm (Inf, scalar, or tuple)
sampling=2, # Supersampling factor for PSF integration
threaded=true, # Enable multithreading for faster computation
bg=0.0, # Background signal level (photons per pixel)
poisson_noise=false, # Apply Poisson noise only (simple shot noise)
camera_noise=false # Apply full camera noise model (requires SCMOSCamera)
)Noise Options
Realistic microscope images include various noise sources. SMLMSim supports the following options:
Photon Shot Noise
Photon shot noise follows a Poisson distribution and is the most fundamental noise source in fluorescence microscopy:
# Enable Poisson noise (default: false)
images = gen_images(smld_model, psf, poisson_noise=true)When enabled, each pixel's intensity is drawn from a Poisson distribution with λ equal to the expected photon count.
Background Signal
Background noise can be added as a constant offset to all pixels:
# Add background signal (photons per pixel)
images = gen_images(smld_model, psf, bg=10.0)When combined with Poisson noise, the background is included in the Poisson sampling process for a realistic noise model.
sCMOS Camera Noise
For realistic camera noise modeling, SMLMSim supports sCMOS cameras with per-pixel calibration parameters. This applies the full detection chain from photons to ADU (analog-to-digital units):
using SMLMSim
using MicroscopePSFs
# Create an sCMOS camera with realistic parameters
# Parameters: width, height, pixel_size (μm), readnoise (e⁻ RMS)
camera_scmos = SCMOSCamera(128, 128, 0.1, 1.6) # 1.6 e⁻ RMS read noise
# Run simulation with sCMOS camera
params = StaticSMLMParams(density=1.0, σ_psf=0.13)
smld_true, smld_model, smld_noisy = simulate(
params,
pattern=Nmer2D(n=8, d=0.1),
camera=camera_scmos
)
# Generate images with full sCMOS noise model
psf = GaussianPSF(0.15)
images = gen_images(smld_model, psf, bg=10.0, camera_noise=true)The sCMOS noise model applies these transformations in order:
- Quantum Efficiency (QE): Converts photons to photoelectrons (typically 70-95%)
- Poisson Noise: Shot noise on the photoelectron count
- Read Noise: Gaussian noise per pixel (amplifier/ADC noise)
- Gain: Converts electrons to ADU (e.g., 0.5 e⁻/ADU)
- Offset: Adds dark level baseline (e.g., 100 ADU)
Advanced sCMOS Configuration
You can specify all calibration parameters explicitly:
camera_scmos = SCMOSCamera(
128, 128, 0.1;
offset=100.0, # 100 ADU dark level
gain=0.5, # 0.5 e⁻/ADU conversion
readnoise=1.6, # 1.6 e⁻ RMS read noise
qe=0.95 # 95% quantum efficiency
)Each parameter can be either:
- A scalar value (uniform across all pixels)
- A matrix (spatially varying, per-pixel calibration)
IdealCamera vs SCMOSCamera
# IdealCamera: Use with poisson_noise for simple shot noise
camera_ideal = IdealCamera(128, 128, 0.1)
smld = BasicSMLD(emitters, camera_ideal, n_frames, n_datasets)
images_ideal = gen_images(smld, psf, bg=10.0, poisson_noise=true)
# SCMOSCamera: Use with camera_noise for realistic noise
camera_scmos = SCMOSCamera(128, 128, 0.1, 1.6)
smld = BasicSMLD(emitters, camera_scmos, n_frames, n_datasets)
images_scmos = gen_images(smld, psf, bg=10.0, camera_noise=true)The camera_noise=true parameter only works with SCMOSCamera. Using it with IdealCamera will show a warning and be ignored. For IdealCamera, use poisson_noise=true instead.
Spatially-Varying Calibration Maps
Real sCMOS sensors have per-pixel variation in offset, gain, and readnoise. You can model this with calibration maps:
using SMLMSim
using MicroscopePSFs
# Create per-pixel calibration maps (128×128 sensor)
n_pixels = 128
# Readnoise: mean 1.6 e⁻ with ±0.3 e⁻ variation
readnoise_map = 1.6 .+ 0.3 .* randn(n_pixels, n_pixels)
readnoise_map = max.(readnoise_map, 0.5) # Ensure positive values
# Offset: mean 100 ADU with ±5 ADU variation plus gradient
offset_map = zeros(n_pixels, n_pixels)
for i in 1:n_pixels, j in 1:n_pixels
gradient = 10.0 * (i / n_pixels) # Vertical gradient
noise = 5.0 * randn()
offset_map[i, j] = 100.0 + gradient + noise
end
# Gain: mostly uniform with slight pattern
gain_map = 0.5 .+ 0.05 .* sin.(2π .* (1:n_pixels) ./ 20) * cos.(2π .* (1:n_pixels)' ./ 20)
# Create sCMOS camera with spatially-varying calibration
pixel_size = 0.1 # 100 nm
camera_scmos = SCMOSCamera(
collect(0:pixel_size:(n_pixels*pixel_size)),
collect(0:pixel_size:(n_pixels*pixel_size)),
offset_map,
gain_map,
readnoise_map,
0.95 # Uniform quantum efficiency (could also be a matrix)
)
# Generate images with realistic spatial artifacts
# (offset gradient, readnoise stripes, etc.)
images = gen_images(smld, psf, bg=10.0, camera_noise=true)This creates realistic spatial artifacts visible in the images, including:
- Offset gradients: Fixed pattern across sensor
- Readnoise variation: Some pixels noisier than others
- Gain non-uniformity: Affects signal scaling
For demonstration of extreme artifacts, see dev/scmos_quick_demo.jl in the repository.
PSF Support Region
The PSF support region controls how much of the PSF is calculated for each emitter, affecting both accuracy and performance. This parameter is specified in microns (μm):
# Define a small support region (faster but may truncate PSF wings)
images = gen_images(smld_model, psf, support=3.0) # 3.0 μm radius around each emitter
# Define a rectangular support region
images = gen_images(smld_model, psf, support=(3.0, 3.0, 1.0, 1.0)) # (left, right, bottom, top) in μm
# Use infinite support (most accurate but slowest)
images = gen_images(smld_model, psf, support=Inf)Reducing the support region can significantly improve performance for large datasets at the cost of some accuracy. For most PSFs, a support radius of 3-5 times the PSF width (σ) is sufficient (typically 0.5-1.0 μm for a standard SMLM PSF).
Working with Image Stacks
The generated images can be visualized using various Julia plotting libraries:
using CairoMakie # or GLMakie, etc.
# Display a single frame
fig = Figure()
ax = Axis(fig[1, 1], yreversed=true)
heatmap!(ax, images[:, :, 10]', colormap = :inferno)
display(fig)
# Create and save a movie from all frames
fig = Figure()
ax = Axis(fig[1, 1], yreversed=true)
# record will clear and redraw each frame for you
record(fig, "smlm_simulation.mp4", 1:size(images, 3); framerate = 10) do i
heatmap!(ax, images[:, :, i]', colormap = :inferno)
end