API Reference

This page provides a comprehensive reference for the types and functions in SMLMSim.

SMLMSim.SMLMSim — Module
SMLMSim

Main module for the SMLMSim.jl package.

API Overview

For a comprehensive overview of the API, use the help mode on api:

?api

Or 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
using MicroscopePSFs  # for GaussianPSF

# 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)
source
SMLMSim.ImageInfo — Type
ImageInfo

Metadata from image generation functions.

Fields

  • elapsed_s::Float64: Wall-clock time for image generation 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 across all frames
  • output_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)")
source
SMLMSim.SimInfo — Type
SimInfo

Metadata and intermediate results from simulation functions.

Fields

  • elapsed_s::Float64: Wall-clock time for simulation in seconds
  • backend::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 generated
  • n_emitters::Int: Total emitters simulated
  • n_localizations::Int: Total localizations generated
  • n_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
end
source
SMLMSim.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 parameters
      • StaticSMLMConfig: Parameters for static SMLM simulation
      • DiffusionSMLMConfig: Parameters for diffusion simulation
  • AbstractSMLMInfo: Base type for info structs (from SMLMData)

    • SimInfo: Metadata and intermediate results from simulation functions
    • ImageInfo: Metadata from image generation functions
  • Pattern: Base type for all molecular patterns

    • Pattern2D: Base type for 2D patterns
      • Nmer2D: N molecules arranged in a circle
      • Line2D: Molecules arranged along a line
      • Nanoruler2D: N collinear, uniformly-spaced marks (DNA-PAINT nanoruler)
    • Pattern3D: Base type for 3D patterns
      • Nmer3D: N molecules arranged in a circle in 3D
      • Line3D: Molecules arranged along a 3D line
      • Nanoruler3D: N collinear, uniformly-spaced marks in 3D (DNA-PAINT nanoruler)
  • AbstractLabeling: Base type for labeling strategies

    • FixedLabeling: Deterministic number of fluorophores per site
    • PoissonLabeling: Poisson-distributed number of fluorophores per site
    • BinomialLabeling: Binomial-distributed number of fluorophores per site
  • Molecule: Base type for all photophysical models

    • GenericFluor: General fluorophore with kinetic state model
  • AbstractDiffusingEmitter: Base type for diffusing emitters

    • DiffusingEmitter2D: 2D emitter with diffusion state
    • DiffusingEmitter3D: 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]
end

DiffusionSMLMConfig

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
end

cameraexposure 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
end

Line2D

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
end

Nanoruler2D

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
end

Info 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
end

ImageInfo

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)
end

Fluorophore 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
end

Labeling 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)
end

PoissonLabeling

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)
end

Note: 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)
end

Constructor 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 = collect(0.0:0.1:12.8)  # 0 to 12.8μm in 0.1μm steps
pixel_edges_y = collect(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, 1.6;   # 1.6 e⁻ RMS read noise (positional)
    offset=100.0,      # 100 ADU dark level
    gain=0.5,          # 0.5 e⁻/ADU
    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
_, sim_info = simulate(StaticSMLMConfig(); pattern=Nmer2D(), camera=IdealCamera(128, 128, 0.1))
smld_true = sim_info.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 μm

Image Generation

gen_images

Generate camera images from SMLD data using the specified PSF model. Returns a tuple: (images, ImageInfo).

images, info = gen_images(
    smld,
    psf;
    dataset=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
using MicroscopePSFs

# First, define variables
smld, _ = simulate(DiffusionSMLMConfig(box_size=5.0, t_max=5.0))  # 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 = SMLMSim.InteractionDiffusion.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

  1. Define simulation parameters
  2. Create a pattern (or use default)
  3. Choose a labeling strategy (or use default FixedLabeling)
  4. Define a fluorophore model (or use default)
  5. Run simulation to get noisy localizations and info (with intermediate results)
  6. Generate microscope images or analyze the data
using MicroscopePSFs

# 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

  1. Define diffusion parameters
  2. Run simulation to get emitter trajectories
  3. Analyze the diffusion and interaction dynamics
  4. Generate microscope images
using MicroscopePSFs

# 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
using Statistics

# 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.

source
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 object
  • kwargs...: 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)
source
SMLMSim.Core — Module
Core

Core module with shared utilities for SMLM simulation.

This module contains fundamental components that can be used across different simulation types (static, diffusion, etc.) including:

  1. Abstract types for simulation
  2. Molecule and pattern definitions
  3. CTMC (Continuous Time Markov Chain) for stochastic state transitions
  4. Photophysics modeling for blinking kinetics and detection

Usage

using SMLMSim.Core
source
SMLMSim.Core.AbstractLabeling — Type
AbstractLabeling

Abstract 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 site
  • PoissonLabeling: Poisson-distributed number of fluorophores per site
  • BinomialLabeling: Binomial-distributed number of fluorophores per site
source
SMLMSim.Core.BinomialLabeling — Type
BinomialLabeling <: AbstractLabeling

Binomial-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 site
  • p::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)
source
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 span
  • transition_times::Vector{T}: Time points at which state changes occurred, starting at 0.0
  • states::Vector{U}: Sequence of states entered at each transition time, starting with initial state

Type Parameters

  • T: Floating point type for time values
  • U: 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].

source
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 i
  • simulation_time::T: Total time to simulate
  • state1::Int: Initial state

Returns

  • CTMC{T,Int}: Simulated CTMC with transition times and states

Details

Simulates a CTMC using the Gillespie algorithm:

  1. Start in state1 at time 0
  2. 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
  3. Repeat until exceeding simulation_time
source
SMLMSim.Core.FixedLabeling — Type
FixedLabeling <: AbstractLabeling

Deterministic labeling with exactly n fluorophores per site.

Fields

  • n::Int: Number of fluorophores per site
  • efficiency::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)
source
SMLMSim.Core.GenericFluor — Type
GenericFluor <: Molecule

Defines a fluorophore with photophysical properties.

Fields

  • γ::AbstractFloat: Photon emission rate in Hz. Default: 1e5
  • q::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)
source
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 Hz
  • k_off::AbstractFloat: Off-switching rate (on→off) in Hz
  • k_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).

source
SMLMSim.Core.Line2D — Type
Line2D <: Pattern2D

Points with uniform random distribution between two endpoints.

Fields

  • λ::Float64: Linear molecule density (molecules per micron)
  • endpoints::Vector{Tuple{Float64,Float64}}: Vector of endpoint coordinates
  • 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

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)])
source
SMLMSim.Core.Line3D — Type
Line3D <: Pattern3D

Points 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 coordinates
  • 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
  • z::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)])
source
SMLMSim.Core.Molecule — Type
Molecule

Abstract 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.

source
SMLMSim.Core.Nanoruler2D — Type
Nanoruler2D <: Pattern2D

n 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 pattern
  • 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

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)
source
SMLMSim.Core.Nanoruler3D — Type
Nanoruler3D <: Pattern3D

3D 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 pattern
  • 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
  • z::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)
source
SMLMSim.Core.Nmer2D — Type
Nmer2D <: Pattern2D

N molecules symmetrically organized around a circle with diameter d.

Fields

  • 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

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)
source
SMLMSim.Core.Nmer3D — Type
Nmer3D <: Pattern3D

N molecules symmetrically organized around a circle with diameter d at z=0.

Fields

  • 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
  • z::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)
source
SMLMSim.Core.PoissonLabeling — Type
PoissonLabeling <: AbstractLabeling

Poisson-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.

source
SMLMSim.Core.SMLMSimParams — Type
SMLMSimParams <: AbstractSMLMConfig

Abstract type for all SMLM simulation parameter types. Inherits from SMLMData.AbstractSMLMConfig to participate in the ecosystem-wide (Config, Info, Data) tuple pattern.

source
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 3D
  • labeling::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))
source
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 3D
  • pattern_ids::Vector{Int}: Pattern instance ID for each binding site
  • labeling::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))
source
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.

source
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 query
  • t::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.

source
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)
source
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 query
  • t::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.

source
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 SMLD
  • id::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)
source
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]
source
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 simulate
  • framerate::Real: Frame rate in Hz
  • state1::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:

  1. Determines state occupancy using CTMC (which starts at time 0, but recording starts at burn_in)
  2. Integrates emission (rate f.γ) during fluorescent state periods
  3. Accumulates photons within frame exposure time (1/framerate)

Example

fluor = GenericFluor(10000.0, [-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
source
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 positions
  • f::Molecule: Fluorophore model with kinetic rates
  • nframes::Int: Number of frames to simulate
  • framerate::Real: Frame rate in Hz
  • ndatasets::Int=1: Number of independent datasets to generate
  • minphotons::Float64=50.0: Minimum photons for detection
  • state1::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:

  1. Simulates fluorophore blinking using the kinetic model
  2. Creates emitters for frames where photon count exceeds threshold
  3. Preserves track_id for linking emitters from same position
  4. Maintains camera and extends metadata from input SMLD

Example

camera = IdealCamera(1:128, 1:128, 0.1)
pattern = Nmer2D()
_, info = simulate(StaticSMLMConfig(); pattern=pattern, camera=camera)
smld_true = info.smld_true

# Add blinking kinetics
fluor = GenericFluor(10000.0, [-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.

source
SMLMSim.Core.n_fluorophores — Method
n_fluorophores(labeling::AbstractLabeling) -> Int

Sample 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)
source
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 degrees
source
SMLMSim.Core.rotate! — Method
rotate!(p::Pattern3D, R::Matrix{Float64})

Rotate a 3D pattern by rotation matrix R.

Arguments

  • p::Pattern3D: Pattern to rotate
  • R::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)
source
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)
source
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.

source
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 sample
  • probs::Vector{<:AbstractFloat}: Corresponding probabilities

Returns

  • Int: Sampled index
source
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 replicate
  • field_x::Float64: Field width in microns
  • field_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)
source
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 replicate
  • field_x::Float64: Field width in microns
  • field_y::Float64: Field height in microns
  • zrange::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])
source
SMLMSim.StaticSMLM — Module
StaticSMLM

Module for simulating static (non-diffusing) SMLM data with blinking kinetics.

This module provides functionality for:

  1. Generating spatial distributions of emitters based on patterns
  2. Simulating fluorophore blinking based on stochastic kinetic models
  3. Adding realistic localization uncertainties
  4. Creating complete SMLM datasets

Usage

using SMLMSim.StaticSMLM
source
SMLMSim.StaticSMLM.StaticSMLMConfig — Type
StaticSMLMConfig <: SMLMSimParams

Parameters for static SMLM simulation.

Fields

  • density::Float64: density in particles per square micron
  • σ_psf::Float64: PSF width in microns
  • minphotons::Int: minimum photons for detection
  • ndatasets::Int: number of datasets to simulate
  • nframes::Int: number of frames per dataset
  • framerate::Float64: frames per second
  • ndims::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
)
source
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.

source
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 width
source
SMLMSim.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

# smld_model: a 3D SMLD, e.g. info.smld_model from simulate(StaticSMLMConfig(ndims=3))
# 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)
source
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 parameters
  • starting_conditions::Union{Nothing, SMLD, Vector{<:AbstractEmitter}}: Optional starting conditions instead of generating patterns
  • pattern::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 simulation
  • camera::AbstractCamera: Camera model for detection simulation
  • state1::Union{Int, Symbol}=:equilibrium: Initial fluorophore state:
    • ::Int: Specific state (1=on, 2=off typically)
    • :equilibrium: Sample from equilibrium distribution (default)
    For models with photobleaching (absorbing states), use 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.σ_psf value 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_conditions is 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.
source
SMLMSim.InteractionDiffusion — Module
InteractionDiffusion

This 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

using MicroscopePSFs  # for GaussianPSF

# 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, and a SimInfo
smld, info = simulate(params)

# Generate images for microscopy
psf = GaussianPSF(0.15)  # 150nm PSF width
images, img_info = gen_images(smld, psf)

# Analyze results
dimer_smld = get_dimers(smld)
frames, dimer_fractions = analyze_dimer_fraction(smld)
source
SMLMSim.InteractionDiffusion.DiffusingEmitter2D — Type
DiffusingEmitter2D{T<:AbstractFloat} <: AbstractDiffusingEmitter

A 2D emitter type for diffusion simulations that contains both spatial and temporal information, plus molecular state information.

Fields

  • x::T: x-coordinate in microns
  • y::T: y-coordinate in microns
  • photons::T: photons in this record (γ·dt for diffusion records)
  • timestamp::T: actual simulation time in seconds
  • frame::Int: camera frame number based on framerate and exposure
  • dataset::Int: dataset identifier
  • track_id::Int: unique molecule identifier
  • state::Symbol: molecular state (:monomer or :dimer)
  • partner_id::Union{Int,Nothing}: ID of linked molecule (for dimers), or nothing for monomers
source
SMLMSim.InteractionDiffusion.DiffusingEmitter3D — Type
DiffusingEmitter3D{T<:AbstractFloat} <: AbstractDiffusingEmitter

A 3D emitter type for diffusion simulations that contains both spatial and temporal information, plus molecular state information.

Fields

  • x::T: x-coordinate in microns
  • y::T: y-coordinate in microns
  • z::T: z-coordinate in microns
  • photons::T: photons in this record (γ·dt for diffusion records)
  • timestamp::T: actual simulation time in seconds
  • frame::Int: camera frame number based on framerate and exposure
  • dataset::Int: dataset identifier
  • track_id::Int: unique molecule identifier
  • state::Symbol: molecular state (:monomer or :dimer)
  • partner_id::Union{Int,Nothing}: ID of linked molecule (for dimers), or nothing for monomers
source
SMLMSim.InteractionDiffusion.DiffusionSMLMConfig — Type
DiffusionSMLMConfig <: SMLMSimParams

Parameters 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 least r_react apart along the pair axis (the minimum-image distance under periodic boundaries), so a pair does not re-form at the next step only because d_dimer < r_react. Under pair_mobility = :min an 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 lower track_id stays 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 about 2·r_react or less), the partners stay where they are, and simulate warns once per run. Partners already r_react or more apart (by minimum image, computed in Float64 from their stored coordinates) keep their positions: every pair bound at d_dimer, when d_dimer exceeds r_react by more than rounding and the box is at least 2·d_dimer. In a smaller box a pair can be closer than d_dimer (a reflecting anchored formation that kept its separation, or a periodic bond whose minimum image is shorter), and one closer than r_react moves at the split. The rules: docs/src/diffusion/rules.md. A mobile partner can still diffuse back within r_react in 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 (otherwise simulate rounds to the nearest step count and warns)
  • t_max::Float64: total simulation time (s); rounded down to whole frame periods (t_max shorter 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 and D in μm²/s. Each molecule draws its population once, at initialization (or when it enters through starting_conditions), and keeps its own D as a monomer, including after dissociation. Empty (default) means every monomer uses diff_monomer. Under pair_mobility = :fixed dimers always use diff_dimer. The drawn values are stored in smld.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 SMLD starting_conditions carries the class).
  • pair_mobility::Symbol: how a bound pair diffuses. :fixed (default, the 0.7 behaviour): every pair diffuses with diff_dimer and rotates with diff_dimer_rot. :min: a pair moves at min(D1, D2) × diff_dimer/diff_monomer, with D1, D2 the partners' monomer D (from monomer_mobility, or diff_monomer), so two partners at diff_monomer move at diff_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; with diff_dimer = 0 a :min pair does not rotate, while a :fixed pair still rotates with diff_dimer_rot). On formation an immobile partner keeps its position and the other is placed d_dimer from it (in a reflecting box, mirrored across the immobile partner on each axis it would leave, so the bond stays d_dimer long; if some axis fits neither way, possible only when box_size < 2·d_dimer, it keeps its position); if both are immobile the lower track_id stays. :min requires diff_monomer > 0. Stored in smld.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 to d_dimer/2 either 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 at d_dimer apart (when box_size < d_dimer each 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 :fixed forming a pair places both partners d_dimer apart 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 than r_react is spread about its midpoint; simulate warns once per run when a step moves an immobile member, recorded or not; :min keeps 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)]
)
source
Base.copy — Method
Base.copy(e::DiffusingEmitter2D)

Create a copy of a 2D diffusing emitter.

source
Base.copy — Method
Base.copy(e::DiffusingEmitter3D)

Create a copy of a 3D diffusing emitter.

source
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.

source
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.

source
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.

source
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 by dissociate
  • params: Simulation parameters (r_react, pair_mobility, box_size, boundary)
  • D1, D2::Float64: Monomer diffusion coefficients of m1 and m2
  • unfit::Union{Nothing,Base.RefValue{Bool}}=nothing: Set to true when the placement cannot fit

Returns

  • Tuple: The two monomers, moved apart if they were closer than r_react
source
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 frames
  • emitters::Vector{<:AbstractDiffusingEmitter}: Current emitters from simulation
  • time::Float64: Current simulation time
  • frame_num::Int: Current frame number
  • params::DiffusionSMLMConfig: Simulation parameters

Returns

  • Nothing
source
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 against fractions with a plotting package of your choice
source
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 to
  • box_size::Float64: Simulation box size in microns
  • boundary::String: Boundary condition type ("periodic" or "reflecting")

Returns

  • AbstractDiffusingEmitter: Emitter with boundary conditions applied
source
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 to
  • box_size::Float64: Simulation box size in microns
  • boundary::String: Boundary condition type ("periodic" or "reflecting")

Returns

  • DiffusingEmitter2D: New emitter with position constrained to the box
source
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 to
  • box_size::Float64: Simulation box size in microns
  • boundary::String: Boundary condition type ("periodic" or "reflecting")

Returns

  • DiffusingEmitter3D: New emitter with position constrained to the box
source
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 emitter
  • e2::AbstractDiffusingEmitter: Second emitter
  • r_react::Float64: Reaction radius in microns
  • period::Union{Nothing,Float64}=nothing: The box size under periodic boundaries (_period(params)), or nothing for the plain difference

Returns

  • Bool: True if emitters can form a dimer
source
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 convert
  • photons::Float64=1000.0: Number of photons to assign (kept as given by simulate unless γ 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 = SMLMSim.InteractionDiffusion.convert_to_diffusing_emitters(static_emitters)

# Use as starting conditions for a diffusion simulation
params = DiffusionSMLMConfig(t_max=10.0)
smld, info = simulate(params; starting_conditions=diffusing_emitters)
source
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 simulation
  • camera::AbstractCamera: Camera model for imaging
  • params::DiffusionSMLMConfig: Simulation parameters, stored as metadata["simulation_parameters"]; its dt is also stored as metadata["dt"], a snapshot that later edits of the mutable config do not change
  • track_D::Dict{Int,Float64}: Per-track monomer diffusion coefficients, stored as metadata["monomer_D"]; the config's mixture is stored as metadata["monomer_mobility"], a copy that later edits of the config do not change
  • track_class::Dict{Int,Int}: Per-track mobility class (index into monomer_mobility), stored as metadata["monomer_class"]
  • γ::Union{Nothing,Real}=nothing: Emission rate, photons/s, stored as metadata["γ"]; no key when nothing
  • rate_source::Union{Nothing,String}=nothing: How the rate was set ("γ", "photons" or "default"), stored as metadata["rate_source"]; no key when nothing
  • n_frames::Union{Nothing,Int}=nothing: Frame count of the movie; the largest frame present when nothing

Returns

  • BasicSMLD: SMLD containing all emitters for further analysis or visualization
source
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 update
  • diff_coef::Float64: Diffusion coefficient (μm²/s)
  • dt::Float64: Time step (s)

Returns

  • DiffusingEmitter2D: New emitter with updated position
source
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 update
  • diff_coef::Float64: Diffusion coefficient (μm²/s)
  • dt::Float64: Time step (s)

Returns

  • DiffusingEmitter3D: New emitter with updated position
source
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 dimer
  • e2::DiffusingEmitter2D: Second emitter in dimer
  • diff_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
source
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 dimer
  • e2::DiffusingEmitter3D: Second emitter in dimer
  • diff_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
source
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 emitter
  • e2::DiffusingEmitter2D: Second emitter
  • d_dimer::Float64: Dimer separation distance in microns
  • anchor::Union{Nothing,Int}=nothing: track_id of the emitter that keeps its position; the other is placed d_dimer from it along the axis between them. nothing snaps 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
source
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 emitter
  • e2::DiffusingEmitter3D: Second emitter
  • d_dimer::Float64: Dimer separation distance in microns
  • anchor::Union{Nothing,Int}=nothing: track_id of the emitter that keeps its position; the other is placed d_dimer from it along the axis between them. nothing snaps 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
source
SMLMSim.InteractionDiffusion.dissociate — Method
dissociate(e::DiffusingEmitter2D, emitters::Vector{<:AbstractDiffusingEmitter})

Create two new monomers from a dimer.

Arguments

  • e::DiffusingEmitter2D: Emitter part of a dimer
  • emitters::Vector{<:AbstractDiffusingEmitter}: All emitters in the system

Returns

  • Tuple{DiffusingEmitter2D, DiffusingEmitter2D}: Two new emitters in monomer state
source
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 dimer
  • emitters::Vector{<:AbstractDiffusingEmitter}: All emitters in the system

Returns

  • Tuple{DiffusingEmitter3D, DiffusingEmitter3D}: Two new emitters in monomer state
source
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 from simulate

Returns

  • BasicSMLD: One emitter per track, n_frames = 1, every emitter at frame = 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))
source
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.

source
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 emitters
  • state::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 = SMLMSim.InteractionDiffusion.filter_by_state(smld, :monomer)

# Get only dimers
dimer_smld = SMLMSim.InteractionDiffusion.filter_by_state(smld, :dimer)
source
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 with state == :dimer
  • partner_id::Int: partner of the last bound record in the frame, or 0 if none was bound
  • t_form::Float64: timestamp of the first record in the frame that is :dimer while the track's previous record (in time, across frames) was :monomer; NaN if none
  • t_break::Float64: the same for :monomer after :dimer
  • mixed::Bool: true when the row's partner_id is nonzero and exactly one of the track and its partner is immobile (monomer D == 0), read from smld.metadata["monomer_D"], or for an SMLD without a track there from its "monomer_class" and the monomer_mobility of "simulation_parameters", else that config's diff_monomer (the D a track without a saved D moves with); false when 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.

source
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, info = simulate(params)
dimer_smld = get_dimers(smld)
source
SMLMSim.InteractionDiffusion.get_frame — Method
get_frame(smld::BasicSMLD, frame_num::Int)

Extract emitters from a specific frame.

Arguments

  • smld::BasicSMLD: SMLD containing all emitters
  • frame_num::Int: Frame number to extract

Returns

  • BasicSMLD: New SMLD containing only emitters from the specified frame
source
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
source
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 parameters
  • photons: 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 γ·dt photons (the photons of one sub-step), so a frame holds γ·camera_exposure photons. Passing both photons and γ throws ArgumentError. 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
source
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 check
  • k_off::Float64: Dissociation rate (s⁻¹)
  • dt::Float64: Time step (s)

Returns

  • Bool: True if dimer should dissociate
source
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.

source
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 = SMLMSim.InteractionDiffusion.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]
source
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 state
  • params::DiffusionSMLMConfig: Simulation parameters
  • dt::Float64: Time step
  • track_D::Union{Nothing,Dict{Int,Float64}}=nothing: Per-track monomer diffusion coefficients
  • moved_immobile::Union{Nothing,Base.RefValue{Bool}}=nothing: Set to true when, under pair_mobility = :fixed, a formation, a bound step or a dissociation moves a member whose monomer D is 0 (simulate warns once per run from it)
  • split_unfit::Union{Nothing,Base.RefValue{Bool}}=nothing: Set to true when a dissociated pair closer than r_react cannot be placed r_react apart along its axis and stays where it is (simulate warns once per run from it)

Returns

  • Vector{<:AbstractDiffusingEmitter}: Updated emitters
source
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 own photons, 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 emitters photons = p is γ = p/dt with identical output. It is ignored with starting_conditions, as in 0.7, where γ restamps every molecule. Passing both photons and γ throws ArgumentError.
  • override_count::Union{Nothing, Int}=nothing: Optional override for the number of molecules
  • camera::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
  • 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)
source
SMLMSim.CameraImages — Module
CameraImages

Module for generating simulated camera images from SMLM data.

This module provides functions to:

  1. Generate ideal camera images by integrating emitter photons over a PSF.
  2. Add realistic camera noise (e.g., Poisson noise).

Usage

using SMLMSim.CameraImages
source
SMLMSim.CameraImages.gen_image — Method
gen_image(smld::SMLD, psf::AbstractPSF, frame::Int; kwargs...) -> Tuple{Matrix{T}, ImageInfo} where T<:Real

Generate 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
source
SMLMSim.CameraImages.gen_images — Method
gen_images(smld::SMLD, psf::AbstractPSF; kwargs...) -> Tuple{Array{T, 3}, ImageInfo} where T<:Real

Generate camera images from SMLD data using the specified PSF model.

Arguments

  • smld::SMLD: Single molecule localization data container
  • psf::AbstractPSF: Point spread function model

Keyword arguments

  • dataset::Int=1: Dataset number to use from SMLD
  • frames=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 emitter
    • Tuple{<:Real,<:Real,<:Real,<:Real}: Explicit region as (xmin, xmax, ymin, ymax) in microns
  • sampling::Int=2: Supersampling factor for PSF integration
  • threaded::Bool=true: Enable multithreading for faster computation
  • bg::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.

source
SMLMSim.CameraImages.poisson_noise! — Method
poisson_noise!(image::AbstractArray{T}) where T<:Real -> nothing

Apply 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-place
source
SMLMSim.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)
source
SMLMSim.CameraImages.scmos_noise! — Method
scmos_noise!(image::AbstractMatrix{T}, camera::SCMOSCamera) where T<:Real -> nothing

Apply 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-place
source
SMLMSim.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 counts
  • camera::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:

  1. Quantum efficiency: Convert photons to photoelectrons
  2. Poisson noise: Shot noise on photoelectrons
  3. Read noise: Gaussian noise per pixel
  4. Gain: Convert electrons to ADU (analog-to-digital units)
  5. 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)
source