API Reference
This page provides a comprehensive reference for the types and functions in SMLMSim.
SMLMSim.SMLMSim — Module
SMLMSimMain module for the SMLMSim.jl package.
API Overview
For a comprehensive overview of the API, use the help mode on api:
?apiOr access the complete API documentation programmatically:
docs = SMLMSim.api()This package provides tools for simulating Single Molecule Localization Microscopy (SMLM) data. It includes modules for:
- Core: Fundamental types (molecules, patterns) and photophysics simulation (CTMC, blinking).
- StaticSMLM: Simulating static emitters with blinking and localization noise.
- InteractionDiffusion: Simulating diffusing and interacting emitters (e.g., dimerization) using Smoluchowski dynamics.
- CameraImages: Generating simulated camera images from emitter data, including noise models.
The main SMLMSim module re-exports key types and functions from these submodules to provide a unified user interface.
Usage
using SMLMSim
# Example: Static simulation
params_static = StaticSMLMConfig(density=1.0, σ_psf=0.13)
smld_noisy, info = simulate(params_static)
# Access intermediate results: info.smld_true, info.smld_model
# Example: Diffusion simulation
params_diff = DiffusionSMLMConfig(density=0.5, diff_monomer=0.1)
smld, info = simulate(params_diff)
# Example: Generate images
psf = GaussianPSF(0.15)
images, img_info = gen_images(smld_noisy, psf)SMLMSim.ImageInfo — Type
ImageInfoMetadata from image generation functions.
Fields
elapsed_s::Float64: Wall-clock time for image generation in secondsbackend::Symbol: Computation backend (:cpu)device_id::Int: Device identifier (-1 for CPU)frames_generated::Int: Number of frames generatedn_photons_total::Float64: Total photon count across all framesoutput_size::Tuple{Int,Int,Int}: Image dimensions (height, width, n_frames)
Example
images, info = gen_images(smld, psf)
println("Generated $(info.frames_generated) frames in $(info.elapsed_s) seconds")
println("Total photons: $(info.n_photons_total)")SMLMSim.SimInfo — Type
SimInfoMetadata and intermediate results from simulation functions.
Fields
elapsed_s::Float64: Wall-clock time for simulation in secondsbackend::Symbol: Computation backend (:cpu)device_id::Int: Device identifier (-1 for CPU)seed::Union{UInt64, Nothing}: RNG seed for reproducibility (if applicable)smld_true::Union{BasicSMLD, Nothing}: Ground truth positions (static simulation only)smld_model::Union{BasicSMLD, Nothing}: After kinetic model (static simulation only)n_patterns::Int: Number of spatial patterns generatedn_emitters::Int: Total emitters simulatedn_localizations::Int: Total localizations generatedn_frames::Int: Number of frames in simulation
Example
smld_noisy, info = simulate(params)
println("Simulation took $(info.elapsed_s) seconds")
println("Generated $(info.n_localizations) localizations from $(info.n_emitters) emitters")
# Access intermediate results for analysis
if info.smld_true !== nothing
# Compare true vs noisy positions
endSMLMSim.api — Method
SMLMSim.jl API Overview
SMLMSim is a Julia package for simulating Single Molecule Localization Microscopy (SMLM) data with realistic physical properties. It provides tools for generating static SMLM simulations, diffusion-interaction simulations, and microscope image generation.
Key Concepts
Physical Units
All simulations use consistent physical units:
- Spatial dimensions: microns (μm)
- Time: seconds (s)
- Diffusion coefficients: μm²/s
- Rate constants: s⁻¹
Core Components
- Patterns: Spatial arrangements of molecules (e.g., oligomers, lines)
- Molecules: Photophysical models of fluorophores with state transitions
- Simulation: Parameters and functions for different simulation types
- Noise: Realistic localization uncertainty based on photon statistics
- Camera Images: Generation of microscope images from simulation data
Type Hierarchy
AbstractSMLMConfig: Base type for all config types (from SMLMData)SMLMSimParams: Base type for simulation parametersStaticSMLMConfig: Parameters for static SMLM simulationDiffusionSMLMConfig: Parameters for diffusion simulation
AbstractSMLMInfo: Base type for info structs (from SMLMData)SimInfo: Metadata and intermediate results from simulation functionsImageInfo: Metadata from image generation functions
Pattern: Base type for all molecular patternsPattern2D: Base type for 2D patternsNmer2D: N molecules arranged in a circleLine2D: Molecules arranged along a lineNanoruler2D: N collinear, uniformly-spaced marks (DNA-PAINT nanoruler)
Pattern3D: Base type for 3D patternsNmer3D: N molecules arranged in a circle in 3DLine3D: Molecules arranged along a 3D lineNanoruler3D: N collinear, uniformly-spaced marks in 3D (DNA-PAINT nanoruler)
AbstractLabeling: Base type for labeling strategiesFixedLabeling: Deterministic number of fluorophores per sitePoissonLabeling: Poisson-distributed number of fluorophores per siteBinomialLabeling: Binomial-distributed number of fluorophores per site
Molecule: Base type for all photophysical modelsGenericFluor: General fluorophore with kinetic state model
AbstractDiffusingEmitter: Base type for diffusing emittersDiffusingEmitter2D: 2D emitter with diffusion stateDiffusingEmitter3D: 3D emitter with diffusion state
Essential Types
StaticSMLMConfig
Parameters for static SMLM simulation with fixed molecular patterns.
Base.@kwdef mutable struct StaticSMLMConfig <: SMLMSimParams
density::Float64 = 1.0 # density in particles per square micron
σ_psf::Float64 = 0.13 # PSF width in microns
minphotons::Int = 50 # minimum photons for detection
ndatasets::Int = 10 # number of datasets to simulate
nframes::Int = 1000 # number of frames per dataset
framerate::Float64 = 50.0 # frames per second
ndims::Int = 2 # dimensionality (2 or 3)
zrange::Vector{Float64} = [-1.0, 1.0] # axial range for 3D simulations [min_z, max_z]
endDiffusionSMLMConfig
Parameters for diffusion-based SMLM simulation using Smoluchowski dynamics.
Base.@kwdef mutable struct DiffusionSMLMConfig <: SMLMSimParams
density::Float64 = 1.0 # number density (molecules/μm²)
box_size::Float64 = 10.0 # simulation box size (μm)
diff_monomer::Float64 = 0.1 # monomer diffusion coefficient (μm²/s)
diff_dimer::Float64 = 0.05 # dimer diffusion coefficient (μm²/s)
diff_dimer_rot::Float64 = 0.5 # dimer rotational diffusion coefficient (rad²/s)
k_off::Float64 = 0.2 # dimer dissociation rate (s⁻¹)
r_react::Float64 = 0.01 # reaction radius (μm)
d_dimer::Float64 = 0.05 # monomer separation in dimer (μm)
dt::Float64 = 0.01 # physics step (s); also sets sub-steps per frame (motion blur)
t_max::Float64 = 10.0 # total simulation time (s)
ndims::Int = 2 # number of dimensions (2 or 3)
boundary::String = "periodic" # boundary condition type ("periodic" or "reflecting")
camera_framerate::Float64 = 10.0 # camera frames per second (Hz)
camera_exposure::Float64 = 0.1 # camera exposure time per frame (s)
monomer_mobility::Vector{Tuple{Float64,Float64}} = Tuple{Float64,Float64}[] # optional (fraction, D) mixture
endcameraexposure and 1/cameraframerate should be integer multiples of dt (otherwise simulate rounds to the nearest step count and warns). The γ passed to simulate is the emission rate in photons/s; each of the nsub = cameraexposure/dt records in a frame carries γ·dt. The deprecated photons keyword (removed in 0.8.0) is photons per record, equal to γ = photons/dt; without either, records carry 1000 photons.
Molecular Patterns
Nmer2D
N molecules symmetrically organized around a circle with diameter d.
mutable struct Nmer2D <: Pattern2D
n::Int # Number of molecules in the pattern
d::Float64 # Diameter of the circle in microns
x::Vector{Float64} # X positions of molecules in microns
y::Vector{Float64} # Y positions of molecules in microns
endLine2D
Points with uniform random distribution between two endpoints.
mutable struct Line2D <: Pattern2D
n::Int # Number of molecules in the pattern
x::Vector{Float64} # X positions of molecules in microns
y::Vector{Float64} # Y positions of molecules in microns
λ::Float64 # Linear molecule density (molecules per micron)
endpoints::Vector{Tuple{Float64,Float64}} # Vector of endpoint coordinates
endNanoruler2D
n collinear marks with uniform spacing, modeling DNA-PAINT calibration nanorulers (e.g. GATTAquant). Nanoruler3D is the 3D analog (adds a z field).
mutable struct Nanoruler2D <: Pattern2D
n::Int # Number of marks in the pattern (default 3)
spacing::Float64 # Distance between adjacent marks in microns
x::Vector{Float64} # X positions of marks in microns
y::Vector{Float64} # Y positions of marks in microns
endInfo Types
SimInfo
Metadata and intermediate results from simulation functions. Returned as the second element of the tuple from simulate().
struct SimInfo <: AbstractSMLMInfo
elapsed_s::Float64 # Wall-clock time in seconds
backend::Symbol # Computation backend (:cpu)
device_id::Int # Device identifier (-1 for CPU)
seed::Union{UInt64, Nothing} # RNG seed for reproducibility
smld_true::Any # Ground truth positions (static only)
smld_model::Any # After kinetic model (static only)
n_patterns::Int # Number of spatial patterns
n_emitters::Int # Total emitters simulated
n_localizations::Int # Total localizations generated
n_frames::Int # Number of frames
endImageInfo
Metadata from image generation functions. Returned as the second element of the tuple from gen_images() and gen_image().
struct ImageInfo <: AbstractSMLMInfo
elapsed_s::Float64 # Wall-clock time in seconds
backend::Symbol # Computation backend (:cpu)
device_id::Int # Device identifier (-1 for CPU)
frames_generated::Int # Number of frames generated
n_photons_total::Float64 # Total photon count
output_size::Tuple{Int,Int,Int} # Image dimensions (H, W, T)
endFluorophore Models
GenericFluor
Defines a fluorophore with photophysical properties.
struct GenericFluor <: Molecule
γ::AbstractFloat # Photon emission rate in Hz
q::Array{<:AbstractFloat} # Rate matrix for state transitions
endLabeling Strategies
Labeling is separate from photophysics (Molecule). These types control how many fluorophores attach to each binding site, while Molecule handles blinking kinetics of each fluorophore.
AbstractLabeling
Abstract type for labeling strategies. All implementations support an efficiency parameter controlling the probability that a binding site gets labeled at all.
FixedLabeling
Deterministic labeling with exactly n fluorophores per site.
struct FixedLabeling <: AbstractLabeling
n::Int # Number of fluorophores per site
efficiency::Float64 # Probability that a site gets labeled (0 to 1)
endPoissonLabeling
Poisson-distributed number of fluorophores per site.
struct PoissonLabeling <: AbstractLabeling
mean::Float64 # Mean number of fluorophores per site (λ for Poisson)
efficiency::Float64 # Probability that a site gets labeled (0 to 1)
endNote: With Poisson statistics, some sites may receive 0 fluorophores even when efficiency=1.0 (especially for small mean values).
BinomialLabeling
Binomial-distributed number of fluorophores per site. Models scenarios where each binding site has n potential attachment points, each occupied with probability p.
struct BinomialLabeling <: AbstractLabeling
n::Int # Number of potential attachment points per site
p::Float64 # Probability each attachment point is occupied (0 to 1)
efficiency::Float64 # Probability that a site gets labeled (0 to 1)
endConstructor Examples
Creating Simulation Parameters
# Static SMLM with default parameters
params_static = StaticSMLMConfig()
# Custom parameters for static simulation
params_static = StaticSMLMConfig(
density = 2.0, # 2 patterns per μm²
σ_psf = 0.15, # 150nm PSF width
nframes = 2000, # 2000 frames
framerate = 20.0 # 20 fps
)
# Diffusion simulation with default parameters
params_diff = DiffusionSMLMConfig()
# Custom parameters for diffusion simulation
params_diff = DiffusionSMLMConfig(
density = 0.5, # molecules per μm²
box_size = 15.0, # 15μm box size
diff_monomer = 0.2, # 0.2 μm²/s diffusion coefficient
k_off = 0.1, # 0.1 s⁻¹ dissociation rate
t_max = 20.0 # 20s simulation time
)Creating Patterns
# Create an 8-molecule pattern with 100nm diameter (default)
nmer = Nmer2D()
# Create a custom pattern with 6 molecules and 200nm diameter
hexamer = Nmer2D(n=6, d=0.2) # d is in microns
# Create a 3D pattern
octamer3d = Nmer3D(n=8, d=0.15)
# Create a line pattern
line = Line2D(λ=5.0, endpoints=[(-2.0, 0.0), (2.0, 0.0)]) # 5 molecules/μm
# Create a 3D line pattern
line3d = Line3D(λ=8.0, endpoints=[(-1.0, 0.0, -0.5), (1.0, 0.0, 0.5)])
# Create a DNA-PAINT nanoruler: 3 collinear marks, 20nm spacing (GATTAquant-style)
ruler = Nanoruler2D(spacing=0.02)
ruler3d = Nanoruler3D(n=3, spacing=0.04)Creating Fluorophore Models
# Create a fluorophore with default parameters
fluor = GenericFluor()
# Create a fluorophore with custom parameters
# γ=100,000 photons/s, k_off=10 Hz, k_on=0.1 Hz
fluor = GenericFluor(1e5, [-10.0 10.0; 0.1 -0.1])
# Create a fluorophore using the 2-state keyword constructor
fluor = GenericFluor(photons=1e5, k_off=50.0, k_on=1e-2)Creating Labeling Strategies
# Perfect labeling - exactly 1 fluorophore per site (default)
labeling = FixedLabeling()
# 2 fluorophores per site, 90% of sites labeled
labeling = FixedLabeling(2; efficiency=0.9)
# Poisson labeling - average 1.5 fluorophores per site
labeling = PoissonLabeling(1.5)
# Poisson with 80% labeling efficiency
labeling = PoissonLabeling(2.0; efficiency=0.8)
# Binomial labeling - antibody with 4 dye attachment points, 80% occupancy
labeling = BinomialLabeling(4, 0.8)
# Same with 90% of sites receiving an antibody
labeling = BinomialLabeling(4, 0.8; efficiency=0.9)Creating a Camera
# IdealCamera: Poisson noise only
camera = IdealCamera(128, 128, 0.1) # 128×128 pixels, 100nm pixels
# Specify field of view with an array of pixel edges
pixel_edges_x = 0.0:0.1:12.8 # 0 to 12.8μm in 0.1μm steps
pixel_edges_y = 0.0:0.1:12.8
camera = IdealCamera(pixel_edges_x, pixel_edges_y)
# SCMOSCamera: Realistic noise model with per-pixel calibration (SMLMData 0.4+)
# Parameters: width, height, pixel_size, readnoise
camera_scmos = SCMOSCamera(128, 128, 0.1, 1.6) # 1.6 e⁻ RMS read noise
# Advanced: Specify all calibration parameters
# offset (ADU), gain (e⁻/ADU), readnoise (e⁻ RMS), quantum efficiency (0-1)
camera_scmos = SCMOSCamera(
128, 128, 0.1;
offset=100.0, # 100 ADU dark level
gain=0.5, # 0.5 e⁻/ADU
readnoise=1.6, # 1.6 e⁻ RMS
qe=0.95 # 95% quantum efficiency
)Core Functions
Simulation
simulate
The main simulation function with multiple methods for different simulation types. All simulate() methods return a tuple: (primary_output, info).
# Static SMLM simulation
# First create simulation parameters
params = StaticSMLMConfig()
# Then run simulation - returns (smld_noisy, SimInfo)
smld_noisy, info = simulate(
params;
pattern=Nmer2D(),
labeling=FixedLabeling(), # Default: 1 fluorophore per site
molecule=GenericFluor(),
camera=IdealCamera(128, 128, 0.1)
)
# Access intermediate results from info
smld_true = info.smld_true # Ground truth positions
smld_model = info.smld_model # After kinetic model
# With Poisson labeling (variable fluorophores per site)
smld_noisy, info = simulate(
params;
pattern=Nmer2D(n=6, d=0.2),
labeling=PoissonLabeling(1.5), # Average 1.5 fluorophores per site
molecule=GenericFluor()
)
# Diffusion simulation
# First create simulation parameters
params_diff = DiffusionSMLMConfig()
# Then run simulation - returns (smld, SimInfo)
# γ = emission rate, photons/s; each of the n_sub records of a frame carries γ·dt,
# so a frame holds γ·camera_exposure photons (1e4/s * 0.1 s = 1000).
# Without γ each record carries 1000 photons (0.7's default).
smld, info = simulate(
params_diff;
γ=1e4
)
# Check timing
println("Simulation took $(info.elapsed_s) seconds")kinetic_model
Generate kinetic blinking model from existing localization data.
# Example of how to call kinetic_model
# First create or obtain the required inputs
smld_true = ... # Some BasicSMLD with true positions
fluor = GenericFluor(1e5, [-10.0 10.0; 0.5 -0.5]) # Fluorophore model
nframes = 1000 # Number of frames
framerate = 50.0 # Frames per second
# Call the function
smld_model = kinetic_model(
smld_true, # BasicSMLD with emitter positions
fluor, # Molecule with kinetic rates
nframes, # Number of frames
framerate; # Frames per second
ndatasets=1, # Number of independent datasets
minphotons=50.0, # Minimum photons for detection
state1=:equilibrium # Initial state sampling
)apply_noise
Add localization uncertainty to emitter positions based on photon counts.
# Example usage of apply_noise
# First, you need smld_model from a previous step
# For example, from the output of kinetic_model()
# For 2D emitters - add noise with 130nm PSF width
smld_noisy = apply_noise(smld_model, 0.13) # 0.13 μm = 130nm PSF width
# For 3D emitters - specify PSF width in each dimension
smld_noisy_3d = apply_noise(smld_model_3d, [0.13, 0.13, 0.39]) # [x, y, z] widths in μmImage Generation
gen_images
Generate camera images from SMLD data using the specified PSF model. Returns a tuple: (images, ImageInfo).
images, info = gen_images(
smld::SMLD,
psf::AbstractPSF;
dataset::Int=1, # Dataset number to use from SMLD
frames=nothing, # Specific frames to generate (default: all frames)
support=Inf, # PSF support region size (see details below)
sampling=2, # Supersampling factor for PSF integration
threaded=true, # Enable multithreading
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)
# - For SCMOSCamera: QE, Poisson, read noise, gain, offset
# - For IdealCamera: ignored (use poisson_noise instead)
)
# Access image metadata
println("Generated $(info.frames_generated) frames in $(info.elapsed_s) seconds")
println("Total photons: $(info.n_photons_total)")
# The support parameter controls PSF computation region:
# 1. Inf (default): Compute PSF over entire image (most accurate but slowest)
support=Inf
# 2. Real number: Use circular region with given radius around each emitter
# Typically 3-5× the PSF width is sufficient for accuracy with better performance
support=1.0 # 1.0 µm radius around each emitter
# 3. Tuple (xmin, xmax, ymin, ymax): Explicit region in microns
support=(4.0, 6.0, 4.0, 6.0) # Only compute PSF within this region
# Example: sCMOS camera with realistic noise
camera_scmos = SCMOSCamera(128, 128, 0.1, 1.6)
smld = BasicSMLD(emitters, camera_scmos, n_frames, n_datasets)
images_scmos, info = gen_images(smld, psf, bg=10.0, camera_noise=true)
# Applies: QE → Poisson → read noise → gain → offset
# Example: IdealCamera with Poisson noise only
camera_ideal = IdealCamera(128, 128, 0.1)
smld = BasicSMLD(emitters, camera_ideal, n_frames, n_datasets)
images_poisson, info = gen_images(smld, psf, bg=10.0, poisson_noise=true)gen_image
Generate a single frame camera image. Returns a tuple: (image, ImageInfo).
# Example of generating a single frame image
# First, define variables
smld = ... # Your SMLD data
psf = GaussianPSF(0.15) # PSF model with 150nm width
frame_number = 10 # The frame you want to generate
# Generate image for a specific frame
single_frame, info = gen_image(
smld, # SMLD data
psf, # PSF model
frame_number; # Frame to generate
support=1.0, # Same keyword arguments as gen_images
bg=5.0,
poisson_noise=true
)Analysis Functions
Diffusion Analysis
# Example usage of diffusion analysis functions
# First, run a diffusion simulation
params = DiffusionSMLMConfig()
smld, info = simulate(params)
# Extract dimers from diffusion simulation
dimer_smld = get_dimers(smld)
# Extract monomers
monomer_smld = get_monomers(smld)
# Analyze dimer formation over time
frames, fractions = analyze_dimer_fraction(smld)
# Analyze average dimer lifetime
lifetime = analyze_dimer_lifetime(smld)
# Per-frame dimer ground truth: rows of (frame, track_id, partner_id, bound_fraction, t_form, t_break)
truth = frame_dimer_truth(smld)
# Continue a run from its exact end state (each track keeps its D)
smld2, info2 = simulate(params; starting_conditions=smld) # resumes at extract_end_state(smld)
# Track state changes over time
state_history = track_state_changes(smld)Track Utilities
# Example usage of track utilities
# First, run a simulation
params = StaticSMLMConfig()
smld_noisy, info = simulate(params)
# Specify a track ID to extract
track_id = 1 # ID of the track to extract
# Get a specific track by ID
track_smld = get_track(smld_noisy, track_id)
# Get number of unique tracks
n_tracks = get_num_tracks(smld_noisy)
# Get all tracks as separate SMLDs
track_smlds = get_tracks(smld_noisy)Pattern Manipulation
# Example usage of pattern manipulation
# Create patterns
pattern2d = Nmer2D(n=6, d=0.2)
pattern3d = Nmer3D(n=8, d=0.15)
# Rotate a 2D pattern by 45 degrees
rotate!(pattern2d, π/4)
# Rotate a 3D pattern with Euler angles (ZYZ convention)
rotate!(pattern3d, π/4, π/6, π/3) # α, β, γ angles
# Rotate a 3D pattern with a rotation matrix
θ = π/2 # 90 degrees
R = [cos(θ) -sin(θ) 0; sin(θ) cos(θ) 0; 0 0 1] # Z-axis rotation
rotate!(pattern3d, R)Labeling Functions
apply_labeling
Apply labeling strategy to binding site coordinates, expanding each site to the appropriate number of fluorophore positions while preserving pattern IDs.
# Generate binding site coordinates with pattern IDs from pattern distribution
x, y, pattern_ids = uniform2D(1.0, Nmer2D(), 10.0, 10.0)
# Apply labeling with pattern ID tracking (returns coords tuple and new IDs)
(x_labeled, y_labeled), new_ids = apply_labeling((x, y), pattern_ids, PoissonLabeling(1.5))
# Backward-compatible: without pattern IDs (returns only coords)
x_labeled, y_labeled = apply_labeling((x, y), FixedLabeling(1; efficiency=0.8))
# Works with 3D coordinates too
x, y, z, pattern_ids = uniform3D(1.0, Nmer3D(), 10.0, 10.0)
(x_labeled, y_labeled, z_labeled), new_ids = apply_labeling((x, y, z), pattern_ids, BinomialLabeling(4, 0.8))n_fluorophores
Sample the number of fluorophores to place at a single binding site. Called internally by apply_labeling, but can be used directly for custom workflows.
labeling = PoissonLabeling(1.5)
# Sample number of fluorophores for one site
n = n_fluorophores(labeling) # Returns Int (can be 0)Pattern Distribution
Generate random pattern distributions in a field. Each function returns coordinates plus pattern instance IDs for tracking which emitters belong to the same pattern.
# Create patterns
pattern2d = Nmer2D(n=6, d=0.2)
pattern3d = Nmer3D(n=8, d=0.15)
# Generate random pattern distribution in a field
field_x = 10.0 # μm
field_y = 10.0 # μm
density = 1.0 # patterns per μm²
# Get coordinates for 2D distribution (returns pattern IDs)
x, y, pattern_ids = uniform2D(density, pattern2d, field_x, field_y)
# pattern_ids[i] indicates which pattern instance point i belongs to
# Get coordinates for 3D distribution (returns pattern IDs)
x, y, z, pattern_ids = uniform3D(density, pattern3d, field_x, field_y, zrange=[-2.0, 2.0])Common Workflows
Static SMLM Simulation Workflow
- Define simulation parameters
- Create a pattern (or use default)
- Choose a labeling strategy (or use default FixedLabeling)
- Define a fluorophore model (or use default)
- Run simulation to get noisy localizations and info (with intermediate results)
- Generate microscope images or analyze the data
# 1. Define parameters
params = StaticSMLMConfig(
density = 1.0,
σ_psf = 0.13,
nframes = 1000
)
# 2. Create a pattern
pattern = Nmer2D(n=6, d=0.2) # hexamer with 200nm diameter
# 3. Choose labeling strategy
labeling = PoissonLabeling(1.5) # Average 1.5 fluorophores per binding site
# 4. Define fluorophore model
fluor = GenericFluor(photons=1e5, k_off=50.0, k_on=1e-2)
# 5. Run simulation - returns (smld_noisy, SimInfo)
smld_noisy, info = simulate(
params;
pattern=pattern,
labeling=labeling,
molecule=fluor
)
# Access intermediate results from info
smld_true = info.smld_true # Ground truth positions
smld_model = info.smld_model # After kinetic model
# 6. Create microscope images with efficient PSF support
psf = GaussianPSF(0.15) # 150nm PSF width
images, img_info = gen_images(smld_model, psf;
support=1.0, # 1.0 μm radius around each emitter
poisson_noise=true # Add realistic photon counting noise
)Diffusion Simulation Workflow
- Define diffusion parameters
- Run simulation to get emitter trajectories
- Analyze the diffusion and interaction dynamics
- Generate microscope images
# 1. Define parameters
params = DiffusionSMLMConfig(
density = 0.5, # molecules per μm²
box_size = 10.0, # μm
diff_monomer = 0.1, # μm²/s
diff_dimer = 0.05, # μm²/s
k_off = 0.2, # s⁻¹
t_max = 10.0 # s
)
# 2. Run simulation - returns (smld, SimInfo)
smld, info = simulate(params)
# 3. Analyze the results
dimer_smld = get_dimers(smld)
frames, fractions = analyze_dimer_fraction(smld)
# 4. Generate microscope images with realistic sCMOS noise
# Create sCMOS camera matching simulation box
n_pixels = Int(ceil(params.box_size / 0.1)) # 0.1 μm pixels
camera_scmos = SCMOSCamera(n_pixels, n_pixels, 0.1, 1.6)
smld_cam = BasicSMLD(smld.emitters, camera_scmos, smld.n_frames, 1)
psf = GaussianPSF(0.15) # 150nm PSF width
images, img_info = gen_images(smld_cam, psf;
support=1.0, # 1.0 μm PSF support radius (faster)
bg=5.0, # Background photons per pixel
camera_noise=true # Full sCMOS noise model (QE, Poisson, read noise, gain, offset)
)Complete Examples
Static SMLM with Image Generation
using SMLMSim
using MicroscopePSFs
# Define a camera and simulation parameters
camera = IdealCamera(128, 128, 0.1) # 128×128 pixels, 100nm pixels
params = StaticSMLMConfig(density=1.0, σ_psf=0.13, nframes=1000)
# Run simulation for an 8-molecule ring pattern
smld_noisy, info = simulate(
params;
pattern=Nmer2D(n=8, d=0.1), # 100nm diameter ring
molecule=GenericFluor(1e5, [-10.0 10.0; 0.5 -0.5]), # γ=100,000, k_off=10, k_on=0.5
camera=camera
)
# Access intermediate results from info
smld_model = info.smld_model
# Create a PSF model
psf = GaussianPSF(0.15) # 150nm PSF width
# Generate microscope images with finite PSF support
images, img_info = gen_images(smld_model, psf;
support=1.0, # 1.0 μm PSF support radius (faster than Inf)
bg=5.0, # 5 background photons per pixel
poisson_noise=true # Add realistic photon counting noise
)
println("Generated $(length(smld_noisy.emitters)) localizations and $(img_info.frames_generated) images.")
println("Simulation took $(info.elapsed_s) seconds")Diffusion with Dimer Analysis
using SMLMSim
using MicroscopePSFs
# Set diffusion simulation parameters
params = DiffusionSMLMConfig(
density = 0.5, # molecules per μm²
box_size = 10.0, # μm
diff_monomer = 0.1, # μm²/s
diff_dimer = 0.05, # μm²/s
k_off = 0.2, # s⁻¹
t_max = 10.0, # s
boundary = "reflecting" # Use reflecting boundaries
)
# Run diffusion simulation
smld, info = simulate(params)
# Analyze dimer formation
frames, dimer_fraction = analyze_dimer_fraction(smld)
avg_lifetime = analyze_dimer_lifetime(smld)
# Generate microscope images with finite PSF support
psf = GaussianPSF(0.15) # 150nm PSF width
images, img_info = gen_images(smld, psf;
support=1.0, # 1.0 μm PSF support radius (faster)
bg=2.0, # Background photons per pixel
poisson_noise=true # Add realistic photon counting noise
)
println("Simulation complete with $(length(smld.emitters)) emitters")
println("Average dimer fraction: $(mean(dimer_fraction))")
println("Average dimer lifetime: $(avg_lifetime) seconds")Custom Pattern with 3D Simulation
using SMLMSim
using MicroscopePSFs
# Define a custom 3D pattern: two rings at different z-positions
mutable struct DoubleRing3D <: Pattern3D
n::Int
d1::Float64
d2::Float64
z1::Float64
z2::Float64
x::Vector{Float64}
y::Vector{Float64}
z::Vector{Float64}
end
function DoubleRing3D(; n=8, d1=0.1, d2=0.2, z1=-0.2, z2=0.2)
total_n = 2*n
x = zeros(total_n)
y = zeros(total_n)
z = zeros(total_n)
# First ring (bottom)
for i = 1:n
θ = 2π * (i-1) / n
x[i] = d1/2 * cos(θ)
y[i] = d1/2 * sin(θ)
z[i] = z1
end
# Second ring (top)
for i = 1:n
θ = 2π * (i-1) / n + π/n # Offset angle for second ring
x[n+i] = d2/2 * cos(θ)
y[n+i] = d2/2 * sin(θ)
z[n+i] = z2
end
return DoubleRing3D(n, d1, d2, z1, z2, x, y, z)
end
# Create camera and parameters
camera = IdealCamera(128, 128, 0.1)
params = StaticSMLMConfig(
density = 0.5,
σ_psf = 0.13,
nframes = 2000,
ndims = 3,
zrange = [-1.0, 1.0]
)
# Create custom pattern
double_ring = DoubleRing3D(n=6, d1=0.15, d2=0.3, z1=-0.3, z2=0.3)
# Run simulation
smld_noisy, info = simulate(
params;
pattern=double_ring,
camera=camera
)
# Access intermediate results
smld_model = info.smld_model
# Generate images with a 3D astigmatic PSF and finite support
# Create a PSF with astigmatism using Zernike coefficients
using MicroscopePSFs
zc = ZernikeCoefficients(15)
zc.phase[6] = 0.5 # Add vertical astigmatism
psf_scalar = ScalarPSF(1.4, 0.532, 1.52; zernike_coeffs=zc)
# Create SplinePSF for speed
xy_sampling, z_sampling = 0.05, 0.1
x_range = y_range = -1.0:xy_sampling:1.0
z_range = -1.0:z_sampling:1.0
psf_spline = SplinePSF(psf_scalar, x_range, y_range, z_range)
# Generate images using the spline PSF with finite support
images, img_info = gen_images(smld_model, psf_spline;
support=0.5, # 0.5 μm PSF support radius for performance
bg=5.0, # Background photons per pixel
poisson_noise=true # Add realistic photon counting noise
)
println("Generated $(length(smld_noisy.emitters)) localizations in 3D")api() returns this documentation as a plain String.
SMLMSim.simulate — Method
simulate(sim::SMLMSimParams; kwargs...)Generic interface for all simulation types. Dispatches to the appropriate method based on the concrete simulation type.
Arguments
sim::SMLMSimParams: The simulation configuration objectkwargs...: Additional keyword arguments specific to the simulation type
Returns
- The result of the specific simulation method
Example
# Create a static SMLM simulation configuration
params = StaticSMLMConfig(
density = 1.0, # Changed from ρ to density
σ_psf = 0.13
)
# Run the simulation
results = simulate(params)SMLMSim.Core — Module
CoreCore module with shared utilities for SMLM simulation.
This module contains fundamental components that can be used across different simulation types (static, diffusion, etc.) including:
- Abstract types for simulation
- Molecule and pattern definitions
- CTMC (Continuous Time Markov Chain) for stochastic state transitions
- Photophysics modeling for blinking kinetics and detection
Usage
using SMLMSim.CoreSMLMSim.Core.AbstractLabeling — Type
AbstractLabelingAbstract type for labeling strategies that determine how many fluorophores attach to each binding site.
Labeling is separate from photophysics (Molecule) - this type hierarchy handles the statistics of fluorophore attachment, while Molecule handles blinking kinetics.
Implementations
FixedLabeling: Deterministic number of fluorophores per sitePoissonLabeling: Poisson-distributed number of fluorophores per siteBinomialLabeling: Binomial-distributed number of fluorophores per site
SMLMSim.Core.BinomialLabeling — Type
BinomialLabeling <: AbstractLabelingBinomial-distributed number of fluorophores per site. Models scenarios where each binding site has n potential attachment points, each occupied with probability p.
Fields
n::Int: Number of potential attachment points per sitep::Float64: Probability each attachment point is occupied (0 to 1)efficiency::Float64: Probability that a site gets labeled at all (0 to 1)
Examples
# Antibody with 4 dye attachment points, each 80% likely to be occupied
labeling = BinomialLabeling(4, 0.8)
# Same, but only 90% of binding sites get an antibody
labeling = BinomialLabeling(4, 0.8; efficiency=0.9)SMLMSim.Core.CTMC — Type
CTMC{T<:AbstractFloat, U<:Int}A Continuous Time Markov Chain representation storing the full trajectory of state transitions.
Fields
simulation_time::T: Total simulation time spantransition_times::Vector{T}: Time points at which state changes occurred, starting at 0.0states::Vector{U}: Sequence of states entered at each transition time, starting with initial state
Type Parameters
T: Floating point type for time valuesU: Integer type for state indices
Note
The states and transition_times vectors have the same length, with each entry in states[i] representing the state entered at time transition_times[i]. The system remains in states[i] until time transition_times[i+1].
SMLMSim.Core.CTMC — Method
CTMC(q::Array{T}, simulation_time::T, state1::Int) where {T<:AbstractFloat}Construct a Continuous Time Markov Chain simulation from a rate matrix.
Arguments
q::Array{T}: Rate matrix where q[i,j] for i≠j is the transition rate from state i to j, and q[i,i] is the negative exit rate from state isimulation_time::T: Total time to simulatestate1::Int: Initial state
Returns
CTMC{T,Int}: Simulated CTMC with transition times and states
Details
Simulates a CTMC using the Gillespie algorithm:
- Start in state1 at time 0
- For current state i:
- Calculate total exit rate ktot = Σj q[i,j]
- Sample time until next transition from Exp(k_tot)
- Sample next state j with probability q[i,j]/k_tot
- Repeat until exceeding simulation_time
SMLMSim.Core.FixedLabeling — Type
FixedLabeling <: AbstractLabelingDeterministic labeling with exactly n fluorophores per site.
Fields
n::Int: Number of fluorophores per siteefficiency::Float64: Probability that a site gets labeled at all (0 to 1)
Examples
# Perfect labeling - exactly 1 fluorophore per site (default/current behavior)
labeling = FixedLabeling()
# 2 fluorophores per site, 90% of sites labeled
labeling = FixedLabeling(2; efficiency=0.9)SMLMSim.Core.GenericFluor — Type
GenericFluor <: MoleculeDefines a fluorophore with photophysical properties.
Fields
γ::AbstractFloat: Photon emission rate in Hz. Default: 1e5q::Array{<:AbstractFloat}: Rate matrix where q[i,j] for i≠j is the transition rate from state i to j, and q[i,i] is the negative exit rate from state i. Default: standard 2-state model with on->off rate of 50Hz and off->on rate of 1e-2Hz
Examples
# Create a fluorophore with default parameters (using the 2-state keyword constructor)
fluor = GenericFluor()
# Create a fluorophore with custom parameters using the positional constructor
fluor = GenericFluor(1e5, [-50.0 50.0; 1e-2 -1e-2])
# Create a fluorophore using the 2-state keyword constructor
fluor = GenericFluor(; photons=1e5, k_off=10.0, k_on=1e-1)SMLMSim.Core.GenericFluor — Method
GenericFluor(; photons::AbstractFloat=1e5, k_off::AbstractFloat=50.0, k_on::AbstractFloat=1e-2)Create a simple two-state (on/off) fluorophore with specified parameters.
Arguments
photons::AbstractFloat: Photon emission rate in Hzk_off::AbstractFloat: Off-switching rate (on→off) in Hzk_on::AbstractFloat: On-switching rate (off→on) in Hz
Details
Creates a fluorophore with a 2-state model and the specified rates. State 1 is the on (bright) state, and state 2 is the off (dark) state. The rate matrix is constructed as: q = [-koff koff; kon -kon]
Note: kon and koff are transition rates (1/s), not duty cycle fractions. The duty cycle (fraction of time in ON state) is kon/(kon + koff). For typical dSTORM, kon << k_off gives low duty cycle (mostly dark, brief blinks).
SMLMSim.Core.Line2D — Type
Line2D <: Pattern2DPoints with uniform random distribution between two endpoints.
Fields
λ::Float64: Linear molecule density (molecules per micron)endpoints::Vector{Tuple{Float64,Float64}}: Vector of endpoint coordinatesn::Int: Number of molecules in the patternx::Vector{Float64}: X positions of molecules in micronsy::Vector{Float64}: Y positions of molecules in microns
Examples
# Create a line with default parameters
line = Line2D()
# Create a custom line
line = Line2D(; λ=5.0, endpoints=[(-2.0, 0.0), (2.0, 0.0)])SMLMSim.Core.Line3D — Type
Line3D <: Pattern3DPoints with uniform random distribution between two 3D endpoints.
Fields
λ::Float64: Linear molecule density (molecules per micron)endpoints::Vector{Tuple{Float64,Float64,Float64}}: Vector of 3D endpoint coordinatesn::Int: Number of molecules in the patternx::Vector{Float64}: X positions of molecules in micronsy::Vector{Float64}: Y positions of molecules in micronsz::Vector{Float64}: Z positions of molecules in microns
Examples
# Create a line with default parameters
line = Line3D()
# Create a custom 3D line
line = Line3D(; λ=5.0, endpoints=[(-1.0, 0.0, -0.5), (1.0, 0.0, 0.5)])SMLMSim.Core.Molecule — Type
MoleculeAbstract type for representing photophysical properties of a molecule.
This is the most general type of luminescent or scattering single molecule. Inherited types will define the properties of specific classes of molecules.
SMLMSim.Core.Nanoruler2D — Type
Nanoruler2D <: Pattern2Dn fluorophore marks arranged collinearly with uniform spacing, modeling DNA-PAINT calibration nanorulers (e.g. GATTAquant). Marks lie on the x-axis, centered on the origin; uniform2D then applies random placement and rotation.
Fields
n::Int: Number of marks in the patternspacing::Float64: Distance between adjacent marks in micronsx::Vector{Float64}: X positions of marks in micronsy::Vector{Float64}: Y positions of marks in microns
Examples
# Default 3-mark nanoruler with 40 nm spacing
ruler = Nanoruler2D()
# GATTAquant-style 3-mark ruler with 20 nm spacing (marks at -20, 0, +20 nm)
ruler = Nanoruler2D(; spacing=0.02)
# 5-mark ruler with 50 nm spacing
ruler = Nanoruler2D(; n=5, spacing=0.05)SMLMSim.Core.Nanoruler3D — Type
Nanoruler3D <: Pattern3D3D equivalent of Nanoruler2D: n fluorophore marks arranged collinearly with uniform spacing along the x-axis at z=0, centered on the origin. uniform3D then applies random 3D placement and rotation, giving the rod-like ruler a random orientation in space.
Fields
n::Int: Number of marks in the patternspacing::Float64: Distance between adjacent marks in micronsx::Vector{Float64}: X positions of marks in micronsy::Vector{Float64}: Y positions of marks in micronsz::Vector{Float64}: Z positions of marks in microns
Examples
# Default 3-mark nanoruler with 40 nm spacing
ruler = Nanoruler3D()
# GATTAquant-style 3-mark ruler with 20 nm spacing
ruler = Nanoruler3D(; spacing=0.02)
# 5-mark ruler with 50 nm spacing
ruler = Nanoruler3D(; n=5, spacing=0.05)SMLMSim.Core.Nmer2D — Type
Nmer2D <: Pattern2DN molecules symmetrically organized around a circle with diameter d.
Fields
n::Int: Number of molecules in the patternd::Float64: Diameter of the circle in micronsx::Vector{Float64}: X positions of molecules in micronsy::Vector{Float64}: Y positions of molecules in microns
Examples
# Create an 8-molecule pattern with 100nm diameter
nmer = Nmer2D()
# Create a custom pattern with 6 molecules and 200nm diameter
nmer = Nmer2D(; n=6, d=0.2)SMLMSim.Core.Nmer3D — Type
Nmer3D <: Pattern3DN molecules symmetrically organized around a circle with diameter d at z=0.
Fields
n::Int: Number of molecules in the patternd::Float64: Diameter of the circle in micronsx::Vector{Float64}: X positions of molecules in micronsy::Vector{Float64}: Y positions of molecules in micronsz::Vector{Float64}: Z positions of molecules in microns
Examples
# Create an 8-molecule pattern with 100nm diameter
nmer = Nmer3D()
# Create a custom pattern with 6 molecules and 200nm diameter
nmer = Nmer3D(; n=6, d=0.2)SMLMSim.Core.Pattern — Type
PatternAbstract type for all molecular spatial patterns.
SMLMSim.Core.Pattern2D — Type
Pattern2D <: PatternAbstract type for 2D molecular spatial patterns.
SMLMSim.Core.Pattern3D — Type
Pattern3D <: PatternAbstract type for 3D molecular spatial patterns.
SMLMSim.Core.PoissonLabeling — Type
PoissonLabeling <: AbstractLabelingPoisson-distributed number of fluorophores per site.
Fields
mean::Float64: Mean number of fluorophores per site (λ for Poisson)efficiency::Float64: Probability that a site gets labeled at all (0 to 1)
Examples
# Average 1.5 fluorophores per site
labeling = PoissonLabeling(1.5)
# Average 2 fluorophores per site, but only 80% of sites get labeled
labeling = PoissonLabeling(2.0; efficiency=0.8)Note
With Poisson statistics, some sites may receive 0 fluorophores even when efficiency=1.0 (especially for small mean values). The efficiency parameter controls a separate "does this site get labeled at all" step.
SMLMSim.Core.SMLMSimParams — Type
SMLMSimParams <: AbstractSMLMConfigAbstract type for all SMLM simulation parameter types. Inherits from SMLMData.AbstractSMLMConfig to participate in the ecosystem-wide (Config, Info, Data) tuple pattern.
SMLMSim.Core.apply_labeling — Method
apply_labeling(coords, labeling::AbstractLabeling)Apply labeling strategy to binding site coordinates without pattern tracking.
This is a convenience method that returns only the expanded coordinates (for backward compatibility).
Arguments
coords: Tuple of coordinate vectors(x, y)for 2D or(x, y, z)for 3Dlabeling::AbstractLabeling: Labeling strategy to apply
Returns
- Tuple of coordinate vectors with fluorophore positions
Example
# Apply labeling without pattern tracking
x, y = [1.0, 2.0], [3.0, 4.0]
new_x, new_y = apply_labeling((x, y), FixedLabeling(2))SMLMSim.Core.apply_labeling — Method
apply_labeling(coords, pattern_ids, labeling::AbstractLabeling)Apply labeling strategy to binding site coordinates, expanding each site to the appropriate number of fluorophore positions while preserving pattern IDs.
Arguments
coords: Tuple of coordinate vectors(x, y)for 2D or(x, y, z)for 3Dpattern_ids::Vector{Int}: Pattern instance ID for each binding sitelabeling::AbstractLabeling: Labeling strategy to apply
Returns
Tuple: (coords, newpatternids) where coords is a tuple of coordinate vectors and newpatternids preserves pattern membership for each fluorophore
Example
# Original: binding sites with pattern IDs
x, y, pattern_ids = uniform2D(1.0, Nmer2D(), 10.0, 10.0)
# After Poisson labeling: variable number of fluorophores, pattern IDs preserved
(x_labeled, y_labeled), new_ids = apply_labeling((x, y), pattern_ids, PoissonLabeling(1.5))SMLMSim.Core.compute_equilibrium_distribution — Method
compute_equilibrium_distribution(q::Matrix{<:AbstractFloat})Calculate the equilibrium probability distribution for a CTMC rate matrix.
Arguments
q::Matrix{<:AbstractFloat}: Rate matrix where q[i,j] for i≠j is the transition rate from state i to j, and q[i,i] is the negative exit rate from state i
Returns
Vector{Float64}: Equilibrium probabilities for each state
Details
For a rate matrix Q, the equilibrium distribution π satisfies π·Q = 0 subject to Σπ = 1. This function solves the linear system directly to find the equilibrium distribution.
SMLMSim.Core.get_next — Method
get_next(ctmc::CTMC, t::AbstractFloat)Get the next state transition after a specific time point.
Arguments
ctmc::CTMC: The CTMC to queryt::AbstractFloat: Current time point
Returns
Tuple{Int,AbstractFloat}: (nextstate, transitiontime)
Note
Returns the next state that will be entered and when it will be entered, searching from the current time point forward.
SMLMSim.Core.get_num_tracks — Method
get_num_tracks(smld::BasicSMLD)Return the number of unique tracks (based on track_id) in the SMLD.
Arguments
smld::BasicSMLD: The SMLD to analyze
Returns
Int: Number of unique track IDs
Example
# Get the number of tracks
n_tracks = get_num_tracks(smld)SMLMSim.Core.get_state — Method
get_state(ctmc::CTMC, t::AbstractFloat)Get the state of the CTMC at a specific time point.
Arguments
ctmc::CTMC: The CTMC to queryt::AbstractFloat: Time point of interest
Returns
Int: State of the chain at time t
Note
Searches through transition times to find the state active at time t. Returns the state that was entered at the last transition before t.
SMLMSim.Core.get_track — Method
get_track(smld::BasicSMLD, id::Int)Return a new SMLD containing only emitters with the specified track_id.
Arguments
smld::BasicSMLD: The original SMLDid::Int: Track ID to filter by
Returns
BasicSMLD: New SMLD containing only emitters from the specified track
Example
# Get all emitters belonging to track 5
track_smld = get_track(smld, 5)SMLMSim.Core.get_tracks — Method
get_tracks(smld::BasicSMLD)Return a vector of SMLD objects, one for each unique track (based on track_id).
Arguments
smld::BasicSMLD: The original SMLD
Returns
Vector{BasicSMLD}: Vector of SMLD objects, one per track
Example
# Get all tracks as separate SMLD objects
track_smlds = get_tracks(smld)
# Access the first track
first_track = track_smlds[1]SMLMSim.Core.intensity_trace — Method
intensity_trace(f::GenericFluor, nframes::Int, framerate::Real; state1=1, burn_in=0.0)Calculate a fluorescence intensity trace by integrating emission during fluorescent state occupancy.
Arguments
f::GenericFluor: Fluorophore model containing transition rates (q) and emission rate (γ)nframes::Int: Number of frames to simulateframerate::Real: Frame rate in Hzstate1::Int=1: Initial state (default: 1 for fluorescent state)burn_in::Real=0.0: Pre-illumination time in seconds before recording starts. The CTMC runs for this duration first, allowing the system to reach a pseudo-equilibrium (e.g., some molecules bleach before data collection begins).
Returns
Vector{Float64}: Integrated photon counts for each frame
Details
For each frame:
- Determines state occupancy using CTMC (which starts at time 0, but recording starts at burn_in)
- Integrates emission (rate f.γ) during fluorescent state periods
- Accumulates photons within frame exposure time (1/framerate)
Example
fluor = GenericFluor(; γ=10000.0, q=[-10.0 10.0; 1e-1 -1e-1])
photons = intensity_trace(fluor, 1000, 10.0)
# With 5 second burn-in (pre-illumination before recording)
photons = intensity_trace(fluor, 1000, 10.0; burn_in=5.0)Note
- State 1 is assumed to be the fluorescent state
- Emission only occurs in state 1 with rate f.γ
- Frame exposure is assumed to be 1/framerate (100% duty cycle)
- burn_in is useful for models with photobleaching to simulate pre-illumination
SMLMSim.Core.kinetic_model — Method
kinetic_model(smld::BasicSMLD, f::Molecule, nframes::Int, framerate::Real;
ndatasets::Int=1, minphotons=50.0, state1::Union{Int, Symbol}=:equilibrium,
burn_in::Real=0.0)Generate kinetic blinking model from existing localization data.
Arguments
smld::BasicSMLD: Input SMLD containing true emitter positionsf::Molecule: Fluorophore model with kinetic ratesnframes::Int: Number of frames to simulateframerate::Real: Frame rate in Hzndatasets::Int=1: Number of independent datasets to generateminphotons::Float64=50.0: Minimum photons for detectionstate1::Union{Int, Symbol}=:equilibrium: Initial state specification:::Int: Specific state to start in (1=on, 2=off typically):equilibrium: Sample from equilibrium distribution (default)
burn_in::Real=0.0: Pre-illumination time in seconds before recording starts. Simulates the common experimental protocol where high laser power is applied for several seconds before data collection begins, allowing some molecules to bleach and others to reach a pseudo-equilibrium blinking state.
Returns
BasicSMLD: New SMLD with simulated blinking kinetics
Details
For each unique position in the input SMLD:
- Simulates fluorophore blinking using the kinetic model
- Creates emitters for frames where photon count exceeds threshold
- Preserves track_id for linking emitters from same position
- Maintains camera and extends metadata from input SMLD
Example
camera = IdealCamera(1:128, 1:128, 0.1)
pattern = Nmer2D()
smld_true, _, _ = simulate(pattern=pattern, camera=camera)
# Add blinking kinetics
fluor = GenericFluor(; γ=10000.0, q=[-10.0 10.0; 1e-1 -1e-1])
smld_model = kinetic_model(smld_true, fluor, 1000, 10.0)
# With 5 second burn-in to simulate pre-illumination
smld_model = kinetic_model(smld_true, fluor, 1000, 10.0; burn_in=5.0)Note
The emitter type (2D/3D) is automatically determined from the input SMLD. Position uncertainties are initialized to 0 and can be set using the apply_noise() function. For models with photobleaching (absorbing states), use state1=1 instead of :equilibrium since absorbing states have no true equilibrium.
SMLMSim.Core.n_fluorophores — Method
n_fluorophores(labeling::AbstractLabeling) -> IntSample the number of fluorophores to place at a single binding site.
This function is called once per binding site during labeling. It first checks labeling efficiency (probability site gets labeled at all), then samples from the appropriate distribution.
Returns
Int: Number of fluorophores (can be 0)
SMLMSim.Core.rotate! — Method
rotate!(p::Pattern2D, θ::Float64)Rotate a 2D pattern by angle θ (in radians).
Arguments
p::Pattern2D: Pattern to rotateθ::Float64: Rotation angle in radians
Example
nmer = Nmer2D()
rotate!(nmer, π/4) # Rotate 45 degreesSMLMSim.Core.rotate! — Method
rotate!(p::Pattern3D, R::Matrix{Float64})Rotate a 3D pattern by rotation matrix R.
Arguments
p::Pattern3D: Pattern to rotateR::Matrix{Float64}: 3x3 rotation matrix
Example
nmer = Nmer3D()
# Create a rotation matrix for 90 degrees around z-axis
θ = π/2
R = [cos(θ) -sin(θ) 0; sin(θ) cos(θ) 0; 0 0 1]
rotate!(nmer, R)SMLMSim.Core.rotate! — Method
rotate!(p::Pattern3D, α::Float64, β::Float64, γ::Float64)Rotate a 3D pattern by Euler angles α, β, γ (in radians). Uses ZYZ convention.
Arguments
p::Pattern3D: Pattern to rotateα::Float64: First rotation angle (around Z axis)β::Float64: Second rotation angle (around Y' axis)γ::Float64: Third rotation angle (around Z'' axis)
Example
nmer = Nmer3D()
rotate!(nmer, π/4, π/6, π/3)SMLMSim.Core.sample_discrete — Method
sample_discrete(p::Vector{<:AbstractFloat})Sample from a discrete probability distribution.
Arguments
p::Vector{<:AbstractFloat}: Probability distribution
Returns
Int: Sampled state index
Details
Samples a state index i with probability p[i] using the inverse CDF method.
SMLMSim.Core.sample_discrete_with_probs — Method
sample_discrete_with_probs(indices, probs)Sample a discrete value from indices with corresponding probabilities.
Arguments
indices::Vector{Int}: Vector of possible indices to sampleprobs::Vector{<:AbstractFloat}: Corresponding probabilities
Returns
Int: Sampled index
SMLMSim.Core.uniform2D — Method
uniform2D(ρ::Float64, p::Pattern2D, field_x::Float64, field_y::Float64)Create coordinate arrays for randomly placed and rotated 2D patterns.
Arguments
ρ::Float64: Pattern density (patterns per square micron)p::Pattern2D: Pattern to replicatefield_x::Float64: Field width in micronsfield_y::Float64: Field height in microns
Returns
Tuple{Vector{Float64}, Vector{Float64}, Vector{Int}}: (x, y, patternids) where patternids indicates which pattern instance each point belongs to
Example
# Generate coordinates for randomly placed Nmer2D patterns
nmer = Nmer2D(; n=6, d=0.2)
x, y, pattern_ids = uniform2D(1.0, nmer, 10.0, 10.0)SMLMSim.Core.uniform3D — Method
uniform3D(ρ::Float64, p::Pattern3D, field_x::Float64, field_y::Float64;
zrange::Vector{Float64}=[-1.0, 1.0])Create coordinate arrays for randomly placed and rotated 3D patterns.
Arguments
ρ::Float64: Pattern density (patterns per square micron)p::Pattern3D: Pattern to replicatefield_x::Float64: Field width in micronsfield_y::Float64: Field height in micronszrange::Vector{Float64}=[-1.0, 1.0]: [minz, maxz] range in microns
Returns
Tuple{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Int}}: (x, y, z, patternids) where patternids indicates which pattern instance each point belongs to
Example
# Generate coordinates for randomly placed Nmer3D patterns
nmer = Nmer3D(; n=6, d=0.2)
x, y, z, pattern_ids = uniform3D(1.0, nmer, 10.0, 10.0; zrange=[-2.0, 2.0])SMLMSim.StaticSMLM — Module
StaticSMLMModule for simulating static (non-diffusing) SMLM data with blinking kinetics.
This module provides functionality for:
- Generating spatial distributions of emitters based on patterns
- Simulating fluorophore blinking based on stochastic kinetic models
- Adding realistic localization uncertainties
- Creating complete SMLM datasets
Usage
using SMLMSim.StaticSMLMSMLMSim.StaticSMLM.StaticSMLMConfig — Type
StaticSMLMConfig <: SMLMSimParamsParameters for static SMLM simulation.
Fields
density::Float64: density in particles per square micronσ_psf::Float64: PSF width in micronsminphotons::Int: minimum photons for detectionndatasets::Int: number of datasets to simulatenframes::Int: number of frames per datasetframerate::Float64: frames per secondndims::Int: dimensionality (2 or 3)zrange::Vector{Float64}: axial range for 3D simulations [minz, maxz]
Examples
# Default parameters
params = StaticSMLMConfig()
# Custom parameters
params = StaticSMLMConfig(
density = 2.0, # 2 particles per μm²
σ_psf = 0.15, # 150nm PSF width
minphotons = 100, # minimum photons for detection
ndatasets = 5, # 5 independent datasets
nframes = 2000, # 2000 frames per dataset
framerate = 100.0, # 100 frames per second
ndims = 3, # 3D simulation
zrange = [-2.0, 2.0] # 4μm axial range
)SMLMSim.StaticSMLM.add_coordinate_noise — Method
add_coordinate_noise(emitter, σ)Helper function to add position noise to an emitter with appropriate uncertainty. Returns new coordinate values and uncertainty values.
For 2D emitters, σ is a scalar. For 3D emitters, σ is a 3-element vector.
SMLMSim.StaticSMLM.apply_noise — Method
apply_noise(smld::BasicSMLD, σ_psf::AbstractFloat)Add localization uncertainty to 2D emitter positions based on photon counts.
Arguments
smld::BasicSMLD: Input SMLD containing 2D emittersσ_psf::AbstractFloat: PSF width in microns
Returns
BasicSMLD: New SMLD with noisy positions and updated uncertainties
Note
For symmetric (axis-aligned) PSFs, σ_xy covariance is set to 0.0. This is the physically correct value, not a placeholder. Rotated/tilted PSFs would require additional parameters to compute non-zero covariance.
Example
# Then add localization noise with specific PSF width
smld_noisy = apply_noise(smld_model, 0.13) # 130nm PSF widthSMLMSim.StaticSMLM.apply_noise — Method
apply_noise(smld::BasicSMLD, σ_psf::Vector{<:AbstractFloat})Add localization uncertainty to 3D emitter positions based on photon counts.
Arguments
smld::BasicSMLD: Input SMLD containing 3D emittersσ_psf::Vector{<:AbstractFloat}: PSF widths [σx, σy, σz] in microns
Returns
BasicSMLD: New SMLD with noisy positions and updated uncertainties
Note
For symmetric (axis-aligned) PSFs, σ_xy covariance is set to 0.0. This is the physically correct value, not a placeholder. Rotated/tilted PSFs would require additional parameters to compute non-zero covariance.
Example
# Then add localization noise with specific PSF widths
σ_psf = [0.13, 0.13, 0.39] # 130nm lateral, 390nm axial
smld_noisy = apply_noise(smld_model, σ_psf)SMLMSim.simulate — Method
simulate(params::StaticSMLMConfig;
starting_conditions::Union{Nothing, SMLD, Vector{<:AbstractEmitter}}=nothing,
pattern::Pattern=nothing,
labeling::AbstractLabeling=FixedLabeling(),
molecule::Molecule=GenericFluor(photons=1e4, k_off=50.0, k_on=1e-2),
camera::AbstractCamera=IdealCamera(1:128, 1:128, 0.1),
state1::Union{Int, Symbol}=:equilibrium,
burn_in::Real=0.0)Generate simulated static SMLM data with realistic blinking kinetics and localization uncertainty.
Arguments
params::StaticSMLMConfig: Simulation parametersstarting_conditions::Union{Nothing, SMLD, Vector{<:AbstractEmitter}}: Optional starting conditions instead of generating patternspattern::Pattern: Pattern to use (default depends on params.ndims)labeling::AbstractLabeling: Labeling strategy for fluorophore attachment (default: FixedLabeling() = 1 per site)molecule::Molecule: Fluorophore model for blinking simulationcamera::AbstractCamera: Camera model for detection simulationstate1::Union{Int, Symbol}=:equilibrium: Initial fluorophore state:::Int: Specific state (1=on, 2=off typically):equilibrium: Sample from equilibrium distribution (default)
state1=1.burn_in::Real=0.0: Pre-illumination time in seconds before recording starts. Simulates experimental protocol where laser is on before data collection, allowing some molecules to bleach and reach pseudo-equilibrium.
Returns
Tuple{BasicSMLD, SimInfo}: (noisy_data, info)- noisy_data: Positions with blinking and localization uncertainty
- info: SimInfo containing smldtrue, smldmodel, timing, and counts
Example
# Create parameters
params = StaticSMLMConfig(
density = 2.0, # 2 patterns per μm²
σ_psf = 0.15, # 150nm PSF width
minphotons = 100, # 100 photons for detection
ndatasets = 5, # 5 independent datasets
nframes = 2000, # 2000 frames
framerate = 100.0 # 100 frames per second
)
# Run simulation with Nmer pattern
pattern = Nmer3D(n=6, d=0.2)
smld_noisy, info = simulate(params; pattern=pattern)
# Access intermediate results
smld_true = info.smld_true
smld_model = info.smld_model
# Run with Poisson labeling (average 1.5 fluorophores per binding site)
smld_noisy, info = simulate(params;
pattern=pattern,
labeling=PoissonLabeling(1.5)
)
# Run with partial labeling efficiency (80% of sites labeled)
smld_noisy, info = simulate(params;
pattern=pattern,
labeling=PoissonLabeling(1.0; efficiency=0.8)
)
# Run with photobleaching model and 5s burn-in
k_off, k_on, k_bleach = 10.0, 1.0, 0.1
Q = [-(k_off+k_bleach) k_off k_bleach; k_on -k_on 0.0; 0.0 0.0 0.0]
fluor = GenericFluor(1e4, Q)
smld_noisy, info = simulate(params;
molecule=fluor,
state1=1, # Start in ON state (required for absorbing states)
burn_in=5.0 # 5 seconds pre-illumination
)
# Run with custom starting conditions (labeling not applied)
custom_emitters = [
Emitter2DFit{Float64}(x, y, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0; σ_xy=0.0, track_id=i)
for (i, (x, y)) in enumerate(zip(rand(10), rand(10)))
]
smld_noisy, info = simulate(params; starting_conditions=custom_emitters)Note
- The
params.σ_psfvalue is used directly for lateral uncertainty (σx, σy) in both 2D and 3D. - For 3D simulations, the axial uncertainty (σz) is scaled by a factor of 3 (i.e., σz = 3 * σ_psf).
- If
starting_conditionsis provided, it will be used instead of generating patterns, and labeling is not applied. - Labeling and molecule are separate concepts: labeling controls how many fluorophores per binding site, molecule controls the photophysics (blinking) of each fluorophore.
SMLMSim.InteractionDiffusion — Module
InteractionDiffusionThis module provides simulation tools for diffusion and interaction between particles.
Overview
Simulates diffusion and interaction dynamics between particles in 2D/3D space. Includes functionality for generating microscope images, analyzing dimers, and visualizing particle dynamics.
Components
- Abstract and concrete emitter types (AbstractDiffusingEmitter, DiffusingEmitter2D, DiffusingEmitter3D)
- Smoluchowski dynamics simulation
- Analysis tools for dimers and state transitions
- SMLD conversion utilities
Examples
# Set up simulation parameters
params = DiffusionSMLMConfig(
density = 0.5, # molecules per μm²
box_size = 10.0, # μm
diff_monomer = 0.1, # μm²/s
diff_dimer = 0.05, # μm²/s
k_off = 0.2, # s⁻¹
r_react = 0.01, # μm
d_dimer = 0.05, # μm
dt = 0.01, # s
t_max = 10.0, # s
camera_framerate = 20.0, # fps
camera_exposure = 0.04 # s
)
# Run simulation - returns a single SMLD with all emitters
smld = simulate(params)
# Generate images for microscopy
psf = GaussianPSF(0.15) # 150nm PSF width
images = gen_images(psf, smld)
# Analyze results
dimer_smld = get_dimers(smld)
frames, dimer_fractions = analyze_dimer_fraction(smld)SMLMSim.InteractionDiffusion.AbstractDiffusingEmitter — Type
AbstractDiffusingEmitter <: AbstractEmitterAbstract type for all diffusing emitters to enable dispatch-based operations. This provides a common parent for 2D and 3D diffusing emitters.
SMLMSim.InteractionDiffusion.DiffusingEmitter2D — Type
DiffusingEmitter2D{T<:AbstractFloat} <: AbstractDiffusingEmitterA 2D emitter type for diffusion simulations that contains both spatial and temporal information, plus molecular state information.
Fields
x::T: x-coordinate in micronsy::T: y-coordinate in micronsphotons::T: photons in this record (γ·dt for diffusion records)timestamp::T: actual simulation time in secondsframe::Int: camera frame number based on framerate and exposuredataset::Int: dataset identifiertrack_id::Int: unique molecule identifierstate::Symbol: molecular state (:monomer or :dimer)partner_id::Union{Int,Nothing}: ID of linked molecule (for dimers), or nothing for monomers
SMLMSim.InteractionDiffusion.DiffusingEmitter3D — Type
DiffusingEmitter3D{T<:AbstractFloat} <: AbstractDiffusingEmitterA 3D emitter type for diffusion simulations that contains both spatial and temporal information, plus molecular state information.
Fields
x::T: x-coordinate in micronsy::T: y-coordinate in micronsz::T: z-coordinate in micronsphotons::T: photons in this record (γ·dt for diffusion records)timestamp::T: actual simulation time in secondsframe::Int: camera frame number based on framerate and exposuredataset::Int: dataset identifiertrack_id::Int: unique molecule identifierstate::Symbol: molecular state (:monomer or :dimer)partner_id::Union{Int,Nothing}: ID of linked molecule (for dimers), or nothing for monomers
SMLMSim.InteractionDiffusion.DiffusionSMLMConfig — Type
DiffusionSMLMConfig <: SMLMSimParamsParameters for diffusion-based SMLM simulation using Smoluchowski dynamics.
Fields
density::Float64: number density (molecules/μm²)box_size::Float64: simulation box size (μm)diff_monomer::Float64: monomer diffusion coefficient (μm²/s)diff_dimer::Float64: dimer diffusion coefficient (μm²/s)diff_dimer_rot::Float64: dimer rotational diffusion coefficient (rad²/s)k_off::Float64: dimer dissociation rate (s⁻¹)r_react::Float64: reaction radius (μm): two monomers closer than this form a pair, by their distance in Float64 from the stored coordinates, to the minimum image under periodic boundaries (0.7.1 took the plain difference, so no pair formed across a periodic edge)d_dimer::Float64: monomer separation in dimer (μm) On dissociation the partners are placed at leastr_reactapart along the pair axis (the minimum-image distance under periodic boundaries), so a pair does not re-form at the next step only becaused_dimer < r_react. Underpair_mobility = :minan immobile partner stays put and the other is placed from it, mirrored across it on each axis it would leave a reflecting box (if both are immobile, the lowertrack_idstays and the higher one moves); otherwise, including every pair under:fixed, both move apart about their midpoint, shifted inward just enough to fit a reflecting box or wrapped under periodic boundaries. When that cannot fit along the pair's axis (a box of about2·r_reactor less), the partners stay where they are, andsimulatewarns once per run. Partners alreadyr_reactor more apart (by minimum image, computed in Float64 from their stored coordinates) keep their positions: every pair bound atd_dimer, whend_dimerexceedsr_reactby more than rounding and the box is at least2·d_dimer. In a smaller box a pair can be closer thand_dimer(a reflecting anchored formation that kept its separation, or a periodic bond whose minimum image is shorter), and one closer thanr_reactmoves at the split. The rules: docs/src/diffusion/rules.md. A mobile partner can still diffuse back withinr_reactin its free step and re-form: that is geminate re-encounter in the contact model, not a re-capture bug.dt::Float64: physics step (s); also sets the sub-steps per frame (motion blur): cameraexposure and 1/cameraframerate should be integer multiples of dt (otherwisesimulaterounds to the nearest step count and warns)t_max::Float64: total simulation time (s); rounded down to whole frame periods (t_maxshorter than one frame period simulates one frame, with a warning)ndims::Int: number of dimensions (2 or 3)boundary::String: boundary condition type ("periodic" or "reflecting")camera_framerate::Float64: camera frames per second (Hz)camera_exposure::Float64: camera exposure time per frame (s)monomer_mobility::Vector{Tuple{Float64,Float64}}: optional mixture of monomer mobility populations, each entry(fraction, D)with fractions summing to 1 andDin μm²/s. Each molecule draws its population once, at initialization (or when it enters throughstarting_conditions), and keeps its ownDas a monomer, including after dissociation. Empty (default) means every monomer usesdiff_monomer. Underpair_mobility = :fixeddimers always usediff_dimer. The drawn values are stored insmld.metadata["monomer_D"](trackid => D; a track at `diffmonomerhas no entry, so read it withget(smld.metadata["monomerD"], id, diffmonomer)), and each molecule's population index insmld.metadata["monomerclass"]` (trackid => Int; 1 when empty, unless an SMLDstarting_conditionscarries the class).pair_mobility::Symbol: how a bound pair diffuses.:fixed(default, the 0.7 behaviour): every pair diffuses withdiff_dimerand rotates withdiff_dimer_rot.:min: a pair moves atmin(D1, D2) × diff_dimer/diff_monomer, withD1,D2the partners' monomer D (frommonomer_mobility, ordiff_monomer), so two partners atdiff_monomermove atdiff_dimer(as under:fixed) and a pair with an immobile partner does not move; the rotation scales the same way (diff_dimer_rot × D_pair/diff_dimer; withdiff_dimer = 0a:minpair does not rotate, while a:fixedpair still rotates withdiff_dimer_rot). On formation an immobile partner keeps its position and the other is placedd_dimerfrom it (in a reflecting box, mirrored across the immobile partner on each axis it would leave, so the bond staysd_dimerlong; if some axis fits neither way, possible only whenbox_size < 2·d_dimer, it keeps its position); if both are immobile the lowertrack_idstays.:minrequiresdiff_monomer > 0. Stored insmld.metadata["pair_mobility"]. Under either setting a mobile pair in a reflecting box is rigid: at formation, if an end is outside the box, both partners move tod_dimer/2either side of their midpoint, shifted inward just enough to fit, and while bound the pair's center folds off the walls moved in by each end's half-extent as often as it crosses them, so both partners stay inside atd_dimerapart (whenbox_size < d_dimereach partner is reflected on its own, as in 0.7.1). Under periodic boundaries, the default, a bound pair moves from its partner's minimum image, so a pair straddling the boundary moves by one step, and a pair forms from its partner's minimum image too (across the edge as anywhere else), with each partner wrapped into the box on the formation step. Under:fixedforming a pair places both partnersd_dimerapart about their midpoint and a bound pair moves and rotates, even when a member is immobile (monomer D = 0), and on dissociation a pair closer thanr_reactis spread about its midpoint;simulatewarns once per run when a step moves an immobile member, recorded or not;:minkeeps such a pair in place.
Photons: simulate takes γ, the emission rate in photons/s. Each of the n_sub records of a frame (substeps_per_frame) carries γ·dt, so a frame holds γ·n_sub·dt photons: γ·camera_exposure when camera_exposure is an integer multiple of dt and not longer than the frame period 1/camera_framerate. There is no field for γ; it is a keyword of simulate.
Examples
# Default parameters
params = DiffusionSMLMConfig()
# Custom parameters
params = DiffusionSMLMConfig(
density = 1.0, # 1 molecule per μm²
box_size = 20.0, # 20μm × 20μm box
diff_monomer = 0.2, # 0.2 μm²/s
diff_dimer = 0.1, # 0.1 μm²/s
diff_dimer_rot = 0.8, # 0.8 rad²/s
k_off = 0.1, # 0.1 s⁻¹
r_react = 0.02, # 20nm reaction radius
d_dimer = 0.06, # 60nm dimer separation
dt = 0.005, # 5ms time step
t_max = 20.0, # 20s simulation
ndims = 3, # 3D simulation
boundary = "reflecting", # reflecting boundaries
camera_framerate = 20.0, # 20 frames per second
camera_exposure = 0.04 # 40ms exposure per frame
)
# Mixed mobility: 85% immobile, 5% slow, 10% mobile monomers
params = DiffusionSMLMConfig(
monomer_mobility = [(0.85, 0.0), (0.05, 0.08), (0.10, 0.38)]
)SMLMSim.InteractionDiffusion._anchor — Method
_anchor(params, e1, e2, D1, D2) -> Union{Nothing,Int}The track_id of the pair member that keeps its position, or nothing when the pair is not anchored. A pair is anchored only when params.pair_mobility == :min and one member is immobile (D == 0); that member is the anchor, and if both are immobile the one with the smaller track_id.
SMLMSim.InteractionDiffusion._pair_motion — Method
_pair_motion(params::DiffusionSMLMConfig, D1::Real, D2::Real) -> (D, D_rot)Translational and rotational diffusion coefficients of a bound pair whose partners have monomer coefficients D1 and D2. :fixed gives (diff_dimer, diff_dimer_rot). :min gives (0, 0) when either partner is immobile (the pair is anchored), otherwise D = min(D1, D2) * diff_dimer / diff_monomer and D_rot = diff_dimer_rot * D / diff_dimer (0 when diff_dimer == 0): two partners at diff_monomer give (diff_dimer, diff_dimer_rot), as under :fixed. Internal.
SMLMSim.InteractionDiffusion._record_frame! — Method
_record_frame!(camera_emitters, emitters, time, frame_num)Record the current emitters as one sub-step record of camera frame frame_num. Each record carries the live emitter's photons (γ·dt for a rate γ), so the n_sub records of a frame sum to γ·n_sub·dt. simulate decides by integer step which sub-steps to record. Internal.
SMLMSim.InteractionDiffusion._unbind — Method
_unbind(m1, m2, params, D1, D2, unfit=nothing)Place two freshly dissociated monomers at least params.r_react apart along their pair axis (the minimum-image distance under periodic boundaries), so that the formation check (can_dimerize) cannot re-capture them at the next step only because d_dimer < r_react. The distance is formation's (_sep, in Float64 from the stored coordinates, each axis rounded once) with the minimum image under periodic boundaries; partners r_react or more apart by it are returned unchanged. Under pair_mobility = :min an immobile member (both immobile: the lower track_id, see _anchor) keeps its position and the other is placed from it (mirrored across it on each axis it would leave a reflecting box); otherwise, including every pair under :fixed, both move apart about their midpoint, shifted inward just far enough that both are inside a reflecting box, or wrapped under periodic boundaries. Every placed coordinate is inside the box in the coordinate type, and the separation carries a margin in that precision (_unbind_spacing), so Float32 partners are also beyond r_react after conversion. When that placement cannot fit along the pair's axis (docs/src/diffusion/rules.md, Dissociation), the partners are left where dissociate put them (0.7.1's behaviour) and unfit[] is set, from which simulate warns once per run. Draws no random numbers and changes positions only.
Arguments
m1, m2: The monomers returned bydissociateparams: Simulation parameters (r_react,pair_mobility,box_size,boundary)D1, D2::Float64: Monomer diffusion coefficients ofm1andm2unfit::Union{Nothing,Base.RefValue{Bool}}=nothing: Set totruewhen the placement cannot fit
Returns
Tuple: The two monomers, moved apart if they were closer thanr_react
SMLMSim.InteractionDiffusion.add_camera_frame_emitters! — Method
add_camera_frame_emitters!(camera_emitters, emitters, time, frame_num, params)Deprecated, removed in 0.8.0 (internal). Add emitters to camera frames when time falls within the exposure window of frame frame_num; simulate no longer calls it.
Arguments
camera_emitters::Vector{<:AbstractDiffusingEmitter}: Collection of emitters for camera framesemitters::Vector{<:AbstractDiffusingEmitter}: Current emitters from simulationtime::Float64: Current simulation timeframe_num::Int: Current frame numberparams::DiffusionSMLMConfig: Simulation parameters
Returns
Nothing
SMLMSim.InteractionDiffusion.analyze_dimer_fraction — Method
analyze_dimer_fraction(smld::BasicSMLD)Calculate the fraction of dimers per frame.
Arguments
smld::BasicSMLD: SMLD containing all emitters
Returns
Tuple{Vector{Int}, Vector{Float64}}: Frame numbers and dimer fractions
Example
# Calculate dimer fraction over time
frames, fractions = analyze_dimer_fraction(smld)
plot(frames, fractions, xlabel="Frame", ylabel="Dimer Fraction")SMLMSim.InteractionDiffusion.analyze_dimer_lifetime — Method
analyze_dimer_lifetime(smld::BasicSMLD)Calculate the average lifetime of dimers.
Arguments
smld::BasicSMLD: SMLD containing all emitters
Returns
Float64: Average dimer lifetime in seconds
SMLMSim.InteractionDiffusion.apply_boundary — Method
apply_boundary(e::AbstractDiffusingEmitter, box_size::Float64, boundary::String)Apply boundary conditions to an emitter (generic fallback).
Arguments
e::AbstractDiffusingEmitter: Emitter to apply boundary tobox_size::Float64: Simulation box size in micronsboundary::String: Boundary condition type ("periodic" or "reflecting")
Returns
AbstractDiffusingEmitter: Emitter with boundary conditions applied
SMLMSim.InteractionDiffusion.apply_boundary — Method
apply_boundary(e::DiffusingEmitter2D, box_size::Float64, boundary::String)Apply boundary conditions to a 2D emitter.
Arguments
e::DiffusingEmitter2D: Emitter to apply boundary tobox_size::Float64: Simulation box size in micronsboundary::String: Boundary condition type ("periodic" or "reflecting")
Returns
DiffusingEmitter2D: New emitter with position constrained to the box
SMLMSim.InteractionDiffusion.apply_boundary — Method
apply_boundary(e::DiffusingEmitter3D, box_size::Float64, boundary::String)Apply boundary conditions to a 3D emitter.
Arguments
e::DiffusingEmitter3D: Emitter to apply boundary tobox_size::Float64: Simulation box size in micronsboundary::String: Boundary condition type ("periodic" or "reflecting")
Returns
DiffusingEmitter3D: New emitter with position constrained to the box
SMLMSim.InteractionDiffusion.can_dimerize — Function
can_dimerize(e1::AbstractDiffusingEmitter, e2::AbstractDiffusingEmitter, r_react::Float64, period=nothing)Check if two emitters can form a dimer: both are monomers and their distance is below r_react. The distance is measured in Float64 from the stored coordinates, each axis's difference rounded once, to the minimum image when period is the box size of periodic boundaries (docs/src/diffusion/rules.md, Distance); dissociation measures the same distance, so a pair it leaves in place cannot pass this check, unless its placement cannot fit (the partners then stay where they are).
Arguments
e1::AbstractDiffusingEmitter: First emittere2::AbstractDiffusingEmitter: Second emitterr_react::Float64: Reaction radius in micronsperiod::Union{Nothing,Float64}=nothing: The box size under periodic boundaries (_period(params)), ornothingfor the plain difference
Returns
Bool: True if emitters can form a dimer
SMLMSim.InteractionDiffusion.convert_to_diffusing_emitters — Function
convert_to_diffusing_emitters(emitters::Vector{<:AbstractEmitter}, photons::Float64=1000.0, state::Symbol=:monomer)Convert regular emitters to diffusing emitters for use as starting conditions.
Arguments
emitters::Vector{<:AbstractEmitter}: Vector of static emitters to convertphotons::Float64=1000.0: Number of photons to assign (kept as given bysimulateunlessγis passed)state::Symbol=:monomer: Initial state (:monomer or :dimer)
Returns
Vector{<:AbstractDiffusingEmitter}: Vector of diffusing emitters
Example
# Convert static emitters to diffusing emitters
static_emitters = smld_static.emitters
diffusing_emitters = convert_to_diffusing_emitters(static_emitters)
# Use as starting conditions for a diffusion simulation
params = DiffusionSMLMConfig(t_max=10.0)
smld = simulate(params; starting_conditions=diffusing_emitters)SMLMSim.InteractionDiffusion.create_smld — Method
create_smld(emitters::Vector{<:AbstractDiffusingEmitter}, camera::AbstractCamera, params::DiffusionSMLMConfig)Convert a collection of diffusing emitters to a BasicSMLD object.
Arguments
emitters::Vector{<:AbstractDiffusingEmitter}: Collection of emitters from simulationcamera::AbstractCamera: Camera model for imagingparams::DiffusionSMLMConfig: Simulation parameters, stored asmetadata["simulation_parameters"]; itsdtis also stored asmetadata["dt"], a snapshot that later edits of the mutable config do not changetrack_D::Dict{Int,Float64}: Per-track monomer diffusion coefficients, stored asmetadata["monomer_D"]; the config's mixture is stored asmetadata["monomer_mobility"], a copy that later edits of the config do not changetrack_class::Dict{Int,Int}: Per-track mobility class (index intomonomer_mobility), stored asmetadata["monomer_class"]γ::Union{Nothing,Real}=nothing: Emission rate, photons/s, stored asmetadata["γ"]; no key whennothingrate_source::Union{Nothing,String}=nothing: How the rate was set ("γ","photons"or"default"), stored asmetadata["rate_source"]; no key whennothingn_frames::Union{Nothing,Int}=nothing: Frame count of the movie; the largest frame present whennothing
Returns
BasicSMLD: SMLD containing all emitters for further analysis or visualization
SMLMSim.InteractionDiffusion.diffuse — Method
diffuse(e::DiffusingEmitter2D, diff_coef::Float64, dt::Float64)Create a new emitter with updated position based on Brownian motion.
Arguments
e::DiffusingEmitter2D: Emitter to updatediff_coef::Float64: Diffusion coefficient (μm²/s)dt::Float64: Time step (s)
Returns
DiffusingEmitter2D: New emitter with updated position
SMLMSim.InteractionDiffusion.diffuse — Method
diffuse(e::DiffusingEmitter3D, diff_coef::Float64, dt::Float64)Create a new 3D emitter with updated position based on Brownian motion.
Arguments
e::DiffusingEmitter3D: Emitter to updatediff_coef::Float64: Diffusion coefficient (μm²/s)dt::Float64: Time step (s)
Returns
DiffusingEmitter3D: New emitter with updated position
SMLMSim.InteractionDiffusion.diffuse_dimer — Method
diffuse_dimer(e1::DiffusingEmitter2D, e2::DiffusingEmitter2D, diff_trans::Float64, diff_rot::Float64, d_dimer::Float64, dt::Float64)Diffuse a dimer with both translational and rotational components.
Arguments
e1::DiffusingEmitter2D: First emitter in dimere2::DiffusingEmitter2D: Second emitter in dimerdiff_trans::Float64: Translational diffusion coefficient (μm²/s)diff_rot::Float64: Rotational diffusion coefficient (rad²/s)d_dimer::Float64: Dimer separation distance (μm)dt::Float64: Time step (s)
Returns
Tuple{DiffusingEmitter2D, DiffusingEmitter2D}: Two new emitters with updated positions
SMLMSim.InteractionDiffusion.diffuse_dimer — Method
diffuse_dimer(e1::DiffusingEmitter3D, e2::DiffusingEmitter3D, diff_trans::Float64, diff_rot::Float64, d_dimer::Float64, dt::Float64)Diffuse a 3D dimer with both translational and rotational components.
Arguments
e1::DiffusingEmitter3D: First emitter in dimere2::DiffusingEmitter3D: Second emitter in dimerdiff_trans::Float64: Translational diffusion coefficient (μm²/s)diff_rot::Float64: Rotational diffusion coefficient (rad²/s)d_dimer::Float64: Dimer separation distance (μm)dt::Float64: Time step (s)
Returns
Tuple{DiffusingEmitter3D, DiffusingEmitter3D}: Two new emitters with updated positions
SMLMSim.InteractionDiffusion.dimerize — Method
dimerize(e1::DiffusingEmitter2D, e2::DiffusingEmitter2D, d_dimer::Float64; anchor::Union{Nothing,Int}=nothing)Create two new emitters in dimer state from two monomers.
Arguments
e1::DiffusingEmitter2D: First emittere2::DiffusingEmitter2D: Second emitterd_dimer::Float64: Dimer separation distance in micronsanchor::Union{Nothing,Int}=nothing:track_idof the emitter that keeps its position; the other is placedd_dimerfrom it along the axis between them.nothingsnaps both to the midpoint ±d_dimer/2.
The pair is oriented along the Float64 difference of their coordinates, and along +x when they coincide.
Returns
Tuple{DiffusingEmitter2D, DiffusingEmitter2D}: Two new emitters in dimer state
SMLMSim.InteractionDiffusion.dimerize — Method
dimerize(e1::DiffusingEmitter3D, e2::DiffusingEmitter3D, d_dimer::Float64; anchor::Union{Nothing,Int}=nothing)Create two new emitters in dimer state from two monomers in 3D.
Arguments
e1::DiffusingEmitter3D: First emittere2::DiffusingEmitter3D: Second emitterd_dimer::Float64: Dimer separation distance in micronsanchor::Union{Nothing,Int}=nothing:track_idof the emitter that keeps its position; the other is placedd_dimerfrom it along the axis between them.nothingsnaps both to the midpoint ±d_dimer/2.
The pair is oriented along the Float64 difference of their coordinates, and along +x when they coincide.
Returns
Tuple{DiffusingEmitter3D, DiffusingEmitter3D}: Two new emitters in dimer state
SMLMSim.InteractionDiffusion.dissociate — Method
dissociate(e::DiffusingEmitter2D, emitters::Vector{<:AbstractDiffusingEmitter})Create two new monomers from a dimer.
Arguments
e::DiffusingEmitter2D: Emitter part of a dimeremitters::Vector{<:AbstractDiffusingEmitter}: All emitters in the system
Returns
Tuple{DiffusingEmitter2D, DiffusingEmitter2D}: Two new emitters in monomer state
SMLMSim.InteractionDiffusion.dissociate — Method
dissociate(e::DiffusingEmitter3D, emitters::Vector{<:AbstractDiffusingEmitter})Create two new monomers from a 3D dimer.
Arguments
e::DiffusingEmitter3D: Emitter part of a dimeremitters::Vector{<:AbstractDiffusingEmitter}: All emitters in the system
Returns
Tuple{DiffusingEmitter3D, DiffusingEmitter3D}: Two new emitters in monomer state
SMLMSim.InteractionDiffusion.draw_monomer_class — Method
draw_monomer_class(params::DiffusionSMLMConfig) -> IntDraw the index of the params.monomer_mobility component a monomer belongs to (1 when the mixture is empty, with no random draw). Internal.
SMLMSim.InteractionDiffusion.extract_end_state — Method
extract_end_state(smld::BasicSMLD{T,E}) where {T, E<:AbstractDiffusingEmitter}Reduce a diffusion simulation to its exact end state, for use as starting_conditions. Returns a BasicSMLD with one emitter per track and photons unchanged.
simulate stores the exact end state (the live emitters at the start of the frame after the last) in smld.metadata["final_state"], and that is returned when the SMLD's last frame is unchanged from that run (the latest record per track of the last frame matches metadata["last_frame_latest"]). Otherwise (filtered, edited or re-wrapped without metadata) the record with the largest timestamp per track in the last frame present is used, with its photons as they are; tracks absent from that frame are not resumed. The result carries copies of "γ", "rate_source", "dt", "monomer_mobility", "monomer_D" and "monomer_class" when present. A concatenation or merge of runs has unknown provenance: metadata "concatenated_from" or "merged_from" (written by SMLMData's cat_smld and merge_smld), or, as a backstop, a last frame holding two records of one track at the same timestamp, which no single run produces. Then the latest record per track (the first of tied ones) is resumed, "γ", "rate_source", "monomer_D" and "monomer_class" are left out, and one warning is given. simulate then applies the continuation rule: a γ-set rate is restamped to γ·dt only if every molecule carries γ·dt at the saved dt, and otherwise photons per record are kept; a kept D stays, other tracks take the current setting. Extracting twice gives the same result. Continuation assumes the SMLD comes from one simulation run, or a filtered subset of one; continuing a concatenation of different runs is unsupported, and the γ check is a consistency check that cannot detect a molecule from another run that happens to carry γ·dt.
Arguments
smld::BasicSMLD: SMLD of diffusing emitters fromsimulate
Returns
BasicSMLD: One emitter per track,n_frames = 1, every emitter atframe = 1
Example
params = DiffusionSMLMConfig(t_max=5.0)
smld, info = simulate(params)
# Continue with new parameters
params_new = DiffusionSMLMConfig(t_max=10.0, diff_monomer=0.2)
smld_continued, info = simulate(params_new; starting_conditions=extract_end_state(smld))SMLMSim.InteractionDiffusion.extract_final_state — Method
extract_final_state(smld::SMLD)Deprecated, removed in 0.8.0: use extract_end_state, which returns the exact end state as an SMLD and carries each track's D.
Returns a Vector. For an SMLD of diffusing emitters, one emitter per track: the record with the largest timestamp in the last frame, photons unchanged, sorted by track_id. For any other SMLD (for example fitted emitters, which have no timestamp), every record in the largest frame, as in 0.7.1.
SMLMSim.InteractionDiffusion.filter_by_state — Method
filter_by_state(smld::BasicSMLD, state::Symbol)Filter emitters by their state (monomer or dimer).
Arguments
smld::BasicSMLD: The original SMLD with diffusing emittersstate::Symbol: State to filter by (:monomer or :dimer)
Returns
BasicSMLD: New SMLD containing only emitters with the specified state
Example
# Get only monomers
monomer_smld = filter_by_state(smld, :monomer)
# Get only dimers
dimer_smld = filter_by_state(smld, :dimer)SMLMSim.InteractionDiffusion.frame_dimer_truth — Method
frame_dimer_truth(smld::BasicSMLD)Per-frame dimer ground truth for every molecule in a diffusion simulation.
Returns a Vector of NamedTuple{(:frame, :track_id, :partner_id, :bound_fraction, :t_form, :t_break, :mixed)}, one row per (frame, trackid) present, sorted by (frame, trackid).
bound_fraction: fraction of the track's records in that frame withstate == :dimerpartner_id::Int: partner of the last bound record in the frame, or0if none was boundt_form::Float64: timestamp of the first record in the frame that is:dimerwhile the track's previous record (in time, across frames) was:monomer;NaNif nonet_break::Float64: the same for:monomerafter:dimermixed::Bool:truewhen the row'spartner_idis nonzero and exactly one of the track and its partner is immobile (monomer D == 0), read fromsmld.metadata["monomer_D"], or for an SMLD without a track there from its"monomer_class"and themonomer_mobilityof"simulation_parameters", else that config'sdiff_monomer(the D a track without a saved D moves with);falsewhen unbound or when a D is not known. Two mobile partners with different D, or two immobile ones, are not mixed.
This is sub-step resolution. With exposure shorter than the frame period, a change that happens during the gap between exposures shows at the next frame's first record.
SMLMSim.InteractionDiffusion.get_dimers — Method
get_dimers(smld::BasicSMLD)Extract a new BasicSMLD containing only emitters in dimer state.
Arguments
smld::BasicSMLD: Original SMLD with all emitters
Returns
BasicSMLD: New SMLD containing only dimers
Example
# Extract only dimers from simulation results
smld = simulate(params)
dimer_smld = get_dimers(smld)SMLMSim.InteractionDiffusion.get_frame — Method
get_frame(smld::BasicSMLD, frame_num::Int)Extract emitters from a specific frame.
Arguments
smld::BasicSMLD: SMLD containing all emittersframe_num::Int: Frame number to extract
Returns
BasicSMLD: New SMLD containing only emitters from the specified frame
SMLMSim.InteractionDiffusion.get_monomers — Method
get_monomers(smld::BasicSMLD)Extract a new BasicSMLD containing only emitters in monomer state.
Arguments
smld::BasicSMLD: Original SMLD with all emitters
Returns
BasicSMLD: New SMLD containing only monomers
SMLMSim.InteractionDiffusion.initialize_emitters — Function
initialize_emitters(params::DiffusionSMLMConfig, photons::Union{Nothing,Real}=nothing;
γ=nothing, override_count::Union{Nothing, Int}=nothing)Create initial emitter positions for the simulation.
Arguments
params::DiffusionSMLMConfig: Simulation parametersphotons: Deprecated (removed in 0.8.0). Photons per emitter record, as in 0.7. Passγinstead.
Keyword Arguments
γ: Emission rate, photons/s; each emitter carriesγ·dtphotons (the photons of one sub-step), so a frame holds γ·camera_exposure photons. Passing bothphotonsandγthrowsArgumentError. With neither, each emitter carries 1000.0 photons.override_count::Union{Nothing, Int}=nothing: Optional override for the number of molecules
Returns
Vector{<:AbstractDiffusingEmitter}: Vector of initialized emitters
SMLMSim.InteractionDiffusion.restamp — Method
restamp(e::AbstractDiffusingEmitter; photons, timestamp, frame, state, partner_id)Copy of a diffusing emitter with the given fields replaced. Internal.
SMLMSim.InteractionDiffusion.should_dissociate — Method
should_dissociate(e::AbstractDiffusingEmitter, k_off::Float64, dt::Float64)Check if a dimer should dissociate based on stochastic rate.
Arguments
e::AbstractDiffusingEmitter: Emitter to checkk_off::Float64: Dissociation rate (s⁻¹)dt::Float64: Time step (s)
Returns
Bool: True if dimer should dissociate
SMLMSim.InteractionDiffusion.substeps_per_frame — Method
substeps_per_frame(params::DiffusionSMLMConfig) -> (n_sub, steps_per_frame)Number of physics steps dt inside one exposure (n_sub) and in one frame period (steps_per_frame). Internal. Timing that is not an integer multiple of dt is rounded to the nearest step count, and an exposure longer than the frame period is capped at the frame period; each case warns (once) with the rule applied.
SMLMSim.InteractionDiffusion.track_state_changes — Method
track_state_changes(smld::BasicSMLD)Track state changes of molecules over time.
Arguments
smld::BasicSMLD: SMLD containing all emitters
Returns
Dict{Int, Vector{Tuple{Int, Symbol}}}: Dictionary mapping molecule IDs to vectors of (frame, state) pairs
Example
# Track state changes of molecules
state_history = track_state_changes(smld)
# Plot state history for molecule 1
history = state_history[1]
frames = [h[1] for h in history]
states = [h[2] for h in history]SMLMSim.InteractionDiffusion.update_system — Method
update_system(emitters::Vector{<:AbstractDiffusingEmitter}, params::DiffusionSMLMConfig, dt::Float64;
track_D=nothing, moved_immobile=nothing, split_unfit=nothing)Update all emitters based on Smoluchowski diffusion dynamics. Monomers diffuse with track_D[track_id] when the track has an entry, otherwise with params.diff_monomer.
Arguments
emitters::Vector{<:AbstractDiffusingEmitter}: Current emitters stateparams::DiffusionSMLMConfig: Simulation parametersdt::Float64: Time steptrack_D::Union{Nothing,Dict{Int,Float64}}=nothing: Per-track monomer diffusion coefficientsmoved_immobile::Union{Nothing,Base.RefValue{Bool}}=nothing: Set totruewhen, underpair_mobility = :fixed, a formation, a bound step or a dissociation moves a member whose monomer D is 0 (simulatewarns once per run from it)split_unfit::Union{Nothing,Base.RefValue{Bool}}=nothing: Set totruewhen a dissociated pair closer thanr_reactcannot be placedr_reactapart along its axis and stays where it is (simulatewarns once per run from it)
Returns
Vector{<:AbstractDiffusingEmitter}: Updated emitters
SMLMSim.simulate — Method
simulate(params::DiffusionSMLMConfig;
starting_conditions::Union{Nothing, SMLD, Vector{<:AbstractDiffusingEmitter}}=nothing,
γ::Union{Nothing, Real}=nothing,
photons::Union{Nothing, Real}=nothing,
override_count::Union{Nothing, Int}=nothing,
kwargs...)Run a Smoluchowski diffusion simulation and return a BasicSMLD object with emitters that have both frame number and timestamp information.
Arguments
params::DiffusionSMLMConfig: Simulation parameters
Keyword Arguments
starting_conditions::Union{Nothing, SMLD, Vector{<:AbstractDiffusingEmitter}}=nothing: Optional starting emitters. An SMLD resumes at its exact end state (extract_end_state) and carries each track's D and γ forward. A Vector keeps each emitter's ownphotons, gets fresh D draws, and is deduplicated to the latest record per trackid (with a warning) if trackids repeat.γ::Union{Nothing, Real}=nothing: emission rate, photons/s (finite, ≥ 0); each of the nsub records in a frame carries γ·dt, so a frame holds γ·nsub·dt photons (γ·cameraexposure when the exposure is a whole number of steps and not capped at the frame period). An explicit γ restamps starting emitters to γ·dt. Default for new emitters: 1000 photons per record (γ = 1000/dt, 0.7's default; 0.8.0 will change the default to a fixed rate). `smld.metadata["ratesource"]records how the rate was set:"γ","photons"or"default". Without γ, an SMLDstarting_conditionswhose rate was set with γ continues at that rate (every molecule restamped to γ·dt at thisdt); any other source (default,photons`, a 0.7.1 SMLD, a Vector) keeps each molecule's photons per record, as in 0.7.1.photons::Union{Nothing, Real}=nothing: deprecated, removed in 0.8.0. Photons per record, as in 0.7; for new emittersphotons = pisγ = p/dtwith identical output. It is ignored withstarting_conditions, as in 0.7, whereγrestamps every molecule. Passing bothphotonsandγthrowsArgumentError.override_count::Union{Nothing, Int}=nothing: Optional override for the number of moleculescamera::Union{Nothing, AbstractCamera}=nothing: Camera model (default: IdealCamera with 100nm pixels)- If
nothing, creates IdealCamera with dimensions matching box_size - Can specify SCMOSCamera for realistic noise modeling
- If
- Any additional parameters are ignored (allows unified interface with other simulate methods)
Warnings
Each warns once and then runs with the stated rule: camera_exposure or the frame period not an integer multiple of dt (rounded to the nearest step count), camera_exposure longer than the frame period (capped at the frame period), t_max under one frame period (one frame is simulated), Vector starting_conditions with repeated track_id (latest record per track), and dimers without a matching partner (converted to monomers).
Returns
Tuple{BasicSMLD, SimInfo}: (smld, info)- smld: SMLD object containing all emitters across all frames
- info: SimInfo containing timing and simulation statistics
Example
# Set up parameters with camera settings
params = DiffusionSMLMConfig(
density = 0.5, # molecules per μm²
box_size = 10.0, # μm
camera_framerate = 20.0, # 20 fps
camera_exposure = 0.04 # 40ms exposure
)
# Run basic simulation
smld, info = simulate(params)
# Run simulation with exactly 2 particles
smld, info = simulate(params; override_count=2)
# Use previous simulation state as starting conditions for a new simulation
smld_continued, info = simulate(params; starting_conditions=smld)SMLMSim.CameraImages — Module
CameraImagesModule for generating simulated camera images from SMLM data.
This module provides functions to:
- Generate ideal camera images by integrating emitter photons over a PSF.
- Add realistic camera noise (e.g., Poisson noise).
Usage
using SMLMSim.CameraImagesSMLMSim.CameraImages.gen_image — Method
gen_image(smld::SMLD, psf::AbstractPSF, frame::Int; kwargs...) -> Tuple{Matrix{T}, ImageInfo} where T<:RealGenerate a single camera image for a specific frame from SMLD data. See gen_images for full documentation of parameters.
Returns
Tuple{Matrix{T}, ImageInfo}: (image, info)- image: 2D camera image as Matrix{T}
- info: ImageInfo containing timing and image statistics
SMLMSim.CameraImages.gen_images — Method
gen_images(smld::SMLD, psf::AbstractPSF; kwargs...) -> Tuple{Array{T, 3}, ImageInfo} where T<:RealGenerate camera images from SMLD data using the specified PSF model.
Arguments
smld::SMLD: Single molecule localization data containerpsf::AbstractPSF: Point spread function model
Keyword arguments
dataset::Int=1: Dataset number to use from SMLDframes=nothing: Specific frames to generate (default: all frames in smld.n_frames)support::Union{Real,Tuple{<:Real,<:Real,<:Real,<:Real}}=Inf: PSF support region size:Inf(default): Calculate PSF over the entire image (most accurate but slowest)Real: Circular region with specified radius (in microns) around each emitterTuple{<:Real,<:Real,<:Real,<:Real}: Explicit region as (xmin, xmax, ymin, ymax) in microns
sampling::Int=2: Supersampling factor for PSF integrationthreaded::Bool=true: Enable multithreading for faster computationbg::Float64=0.0: Background signal level (photons per pixel)poisson_noise::Bool=false: Apply Poisson noise only (for simple shot noise)camera_noise::Bool=false: Apply full camera noise model (requires SCMOSCamera)- For SCMOSCamera: applies QE, Poisson, read noise, gain, and offset
- For IdealCamera: ignored (use poisson_noise instead)
Returns
Tuple{Array{T,3}, ImageInfo}: (images, info)- images: 3D array of camera images with dimensions [height, width, num_frames]
- info: ImageInfo containing timing and image statistics
Performance Note
For the support parameter, using a finite radius (typically 3-5× the PSF width) provides a good balance between accuracy and performance. For example, with a PSF width of 0.15μm, a support radius of 0.5-1.0μm is usually sufficient.
SMLMSim.CameraImages.poisson_noise! — Method
poisson_noise!(image::AbstractArray{T}) where T<:Real -> nothingApply Poisson noise to an image or image stack in-place.
Arguments
image::AbstractArray{T}: Input image or image stack with values representing photon counts
Returns
nothing: The input array is modified in-place
Details
Same as poisson_noise, but modifies the input array directly instead of creating a new one. This can be more memory-efficient for large images or when processing multiple frames.
Example
# Add Poisson noise to an image in-place
image = ones(100, 100) * 100.0 # 100 expected photons per pixel
poisson_noise!(image) # image is modified in-placeSMLMSim.CameraImages.poisson_noise — Method
poisson_noise(image::AbstractArray{T}) where T<:Real -> Array{Float64}Apply Poisson noise to an image or image stack.
Arguments
image::AbstractArray{T}: Input image or image stack with values representing photon counts
Returns
- Array with same dimensions as input, with Poisson noise applied to each pixel
Details
This function creates a copy of the input array and applies Poisson noise to each pixel using the in-place poisson_noise! function.
Non-integer and negative values are handled specially:
- Non-integer values are accepted (treating them as expected photon counts)
- Negative values are clipped to zero before applying noise
- Zero values remain zero (as Poisson(0) always returns 0)
Example
# Add Poisson noise to a clean image
clean_image = ones(100, 100) * 100.0 # 100 expected photons per pixel
noisy_image = poisson_noise(clean_image)SMLMSim.CameraImages.scmos_noise! — Method
scmos_noise!(image::AbstractMatrix{T}, camera::SCMOSCamera) where T<:Real -> nothingApply realistic sCMOS camera noise model to an image in-place.
Arguments
image::AbstractMatrix{T}: Input image with values representing photon counts (modified in-place)camera::SCMOSCamera: sCMOS camera with calibration parameters
Returns
nothing: The input array is modified in-place
Details
Same as scmos_noise, but modifies the input array directly instead of creating a new one.
Example
camera = SCMOSCamera(128, 128, 0.1, 1.6)
image = ones(128, 128) * 100.0
scmos_noise!(image, camera) # image is modified in-placeSMLMSim.CameraImages.scmos_noise — Method
scmos_noise(image::AbstractMatrix{T}, camera::SCMOSCamera) where T<:Real -> Matrix{Float64}Apply realistic sCMOS camera noise model to an image.
Arguments
image::AbstractMatrix{T}: Input image with values representing photon countscamera::SCMOSCamera: sCMOS camera with calibration parameters (offset, gain, readnoise, qe)
Returns
- Matrix with same dimensions as input, with full sCMOS noise model applied
Details
The sCMOS noise model applies the following transformations in order:
- Quantum efficiency: Convert photons to photoelectrons
- Poisson noise: Shot noise on photoelectrons
- Read noise: Gaussian noise per pixel
- Gain: Convert electrons to ADU (analog-to-digital units)
- Offset: Add dark level
The process simulates the physical detection chain:
- QE: Not all photons generate photoelectrons
- Poisson: Fundamental shot noise
- Read noise: Electronic noise from amplifier/ADC
- Gain & Offset: Conversion to digital units
Example
# Create an sCMOS camera with 1.6 e⁻ read noise
camera = SCMOSCamera(128, 128, 0.1, 1.6)
# Apply realistic noise to a clean image
clean_image = ones(128, 128) * 100.0 # 100 photons per pixel
noisy_image = scmos_noise(clean_image, camera)