Configuration

The primary interface follows the JuliaSMLM tuple pattern:

(smld_corrected, info) = driftcorrect(smld, config)

where config is a DriftConfig and info is a DriftInfo.

Input: DriftConfig

SMLMDriftCorrection.DriftConfig — Type
DriftConfig <: AbstractSMLMConfig

Configuration for drift correction, holding all algorithm parameters. Constructed with keyword arguments; all fields have sensible defaults.

Fields

FieldDefaultDescription
quality:singlepassQuality tier: :fft, :singlepass, or :iterative
degree2Legendre polynomial degree for intra-dataset drift
dataset_mode:registeredInter-dataset algorithm: :registered (CC-primary) or :continuous (consecutive-chunk CC); ignored by quality=:fft
chunk_frames0Split datasets into chunks of N frames (0 = no chunking)
n_chunks0Alternative: number of chunks per dataset (0 = use chunk_frames)
maxn100Maximum neighbors for entropy calculation
max_iterations10Iterations per attempt for :iterative mode (a run that has not converged is retried from its current model up to 3 times)
convergence_tol0.001Convergence tolerance in μm for :iterative mode
warm_startnothingPrevious info.model for warm starting optimization
verbose0Verbosity level: 0=quiet, 1=info, 2=debug
auto_roifalseUse dense ROI subset for faster estimation
σ_loc0.010Typical localization precision (μm) for ROI sizing
σ_target0.001Target drift precision (μm) for ROI sizing
roi_safety_factor4.0Safety multiplier for required localizations
shift_scale1.0No effect: read by no dataset mode since 0.2.7 (kept for compatibility; a non-default value logs a warning)

Example

julia> config = DriftConfig(quality=:iterative, degree=3);

julia> config.quality
:iterative

julia> config.degree
3

julia> config.convergence_tol
0.001
source

Quality Tiers

The quality parameter selects the algorithm complexity:

  • :fft – Fast cross-correlation of histogram images. No intra-dataset correction. Best for quick previews or very large datasets. It ignores dataset_mode.
  • :singlepass (default) – One pass of entropy-based intra-dataset correction followed by cross-correlation-seeded, entropy-refined inter-dataset alignment. Good balance of speed and accuracy.
  • :iterative – Iterates intra and inter correction until both parameter changes fall below convergence_tol or the entropy cost plateaus (relative improvement < 1e-4, both dataset modes). max_iterations is the number of iterations per attempt; a run that has not converged is retried from its current model up to 3 times.

Dataset Modes

The dataset_mode parameter selects the inter-dataset algorithm. In :iterative, continuous mode also adds boundary priors to the intra fit. quality=:fft ignores dataset_mode and uses the registered-style CC passes:

  • :registered (default) – Datasets are independent acquisitions of the same field of view (e.g., SeqSRM). With two datasets only the second is aligned, against the first. With three or more, every dataset including the reference is aligned against the others; moves within 30 nm of the current shift are damped by (N-1)/N (N = number of datasets) to remove the overcount from updating all datasets at once. Larger moves into another basin are applied in full.
  • :continuous – One long acquisition split into multiple files or chunks. Each inter-chunk shift is measured by cross-correlating consecutive chunks (with endpoint-chaining kept only as a per-boundary fallback), then entropy-refined with continuity regularization.

No tier, :fft included, applies a cross-correlation shift with a component over 5 μm. Registered :singlepass and :iterative, and :fft regardless of dataset_mode, warn once per dataset per call when a dropped CC overlaps clearly better than the shift the dataset ends with, indicating a likely collection failure (the stage moved between acquisitions). Continuous :singlepass and :iterative drop such a boundary CC and fall back to endpoint chaining at that boundary without a warning. See Theory and Workflow for the alignment rules.

Chunking (Continuous Mode)

For long continuous acquisitions, a single polynomial may not capture complex drift. Use chunk_frames or n_chunks to split each dataset into temporal segments, each modeled independently:

# Split into ~4000-frame chunks
config = DriftConfig(dataset_mode=:continuous, chunk_frames=4000)

# Or specify number of chunks
config = DriftConfig(dataset_mode=:continuous, n_chunks=3)

Each chunk gets its own polynomial; consecutive chunks are aligned by cross-correlation (endpoint-chaining only as a fallback), which keeps a single mis-fit boundary from propagating down the chain.

Auto-ROI

When auto_roi=true, the algorithm selects a dense rectangular subregion of the field of view for drift estimation, then applies the fitted model to all localizations. This can significantly speed up processing for large datasets while trading some accuracy (~1.4 nm vs ~0.5 nm RMSD in testing).

The ROI size is determined by σ_loc, σ_target, and roi_safety_factor:

config = DriftConfig(auto_roi=true, σ_loc=0.010, σ_target=0.001)

The estimated ROI indices are stored in info.roi_indices.

Warm Starting

Pass a previously fitted model to initialize optimization:

# From a previous correction
(smld1, info1) = driftcorrect(smld1, DriftConfig(degree=2))

# Use as starting point for new data
config2 = DriftConfig(warm_start=info1.model)
(smld2, info2) = driftcorrect(smld2, config2)

Output: DriftInfo

SMLMDriftCorrection.DriftInfo — Type
DriftInfo <: AbstractSMLMInfo

Metadata from drift correction, returned as second element of tuple. Supports warm start via info.model.

Fields

  • model::LegendrePolynomial: Fitted drift model (intra + inter)
  • elapsed_s::Float64: Wall time in seconds
  • backend::Symbol: Computation backend (:cpu)
  • iterations::Int: Number of iterations completed (0 for :fft, 1 for :singlepass, N for :iterative)
  • converged::Bool: Whether convergence criterion was met (always true for :fft/:singlepass)
  • entropy::Float64: Final entropy value after correction. A sum over localizations, so it scales with their number; divide by length(smld.emitters) for a size-independent number
  • history::Vector{Float64}: Entropy after the singlepass start and after each iteration (:iterative only; empty for :fft and :singlepass)
  • roi_indices::Union{Nothing, Vector{Int}}: Indices used for ROI subsampling (nothing if not used)
  • dataset_mode::Symbol: Dataset mode used for fitting (default :registered)
  • chunk_frames::Int: Frames per chunk requested for continuous mode (default 0)
  • n_chunks::Int: Chunks per dataset requested for continuous mode (default 0)
  • residual_correlation::NamedTuple: Position-frame correlation diagnostic on corrected data.
    • intra_summary: (mean_abs_corr_x, mean_abs_corr_y, mean_abs_corr_z) — per-dataset average
    • intra_per_dataset: vector of (dataset, n_locs, corr_x, corr_y, corr_z) — flag bad datasets
    • inter: (corr_x, corr_y, corr_z) — cross-dataset alignment quality
    Low values (~0.01) indicate drift is well-corrected; high values indicate residual drift.

Usage

(smld_corrected, info) = driftcorrect(smld)
info.converged    # check convergence
info.entropy      # final value
info.elapsed_s    # timing
info.history      # entropy after the singlepass start and each iteration (:iterative only; empty otherwise)
info.roi_indices  # ROI used for estimation (nothing if auto_roi=false)
info.residual_correlation.intra_summary.mean_abs_corr_y  # overall residual
info.residual_correlation.intra_per_dataset[5].corr_y    # dataset 5 residual
info.residual_correlation.inter.corr_y                   # inter-dataset alignment

# Warm start from previous result
(smld2, info2) = driftcorrect(smld2; warm_start=info.model)
source

Accessing Results

(smld_corrected, info) = driftcorrect(smld, DriftConfig())

# Diagnostics
info.converged     # true if convergence criterion met
info.entropy       # final entropy value (sum over localizations; divide by length(smld.emitters) for a size-independent number)
info.elapsed_s     # wall time in seconds
info.iterations    # iterations completed (0 for :fft, 1 for :singlepass)
info.residual_correlation  # position-frame correlation diagnostic (intra_summary, intra_per_dataset, inter)
info.history       # entropy after the singlepass start and each iteration (:iterative only; empty otherwise)
info.roi_indices   # indices used for ROI subset (nothing if auto_roi=false)

Drift Trajectory

Extract the fitted drift model for plotting:

traj = drift_trajectory(info.model)
# traj.frames, traj.x, traj.y (and traj.z for 3D)

# For continuous mode, chain datasets end-to-end
traj = drift_trajectory(info.model; cumulative=true)

Continuation

Pass DriftInfo directly to continue iterating from a previous result:

(smld1, info1) = driftcorrect(smld, DriftConfig(quality=:singlepass))

# Continue with more iterations
(smld2, info2) = driftcorrect(smld, info1; max_iterations=5)

Pass the original, uncorrected smld with its info1. Continuation reuses the original run's dataset_mode and chunking, so it also works on chunked continuous results.

SMLD Alignment

For aligning independent SMLD structures (e.g., multi-color channels or separate acquisitions of the same FOV), use align_smld with AlignConfig.

Input: AlignConfig

SMLMDriftCorrection.AlignConfig — Type
AlignConfig <: AbstractSMLMConfig

Configuration for rigid-shift alignment of independent SMLDs.

Fields

FieldDefaultDescription
method:entropyAlignment method: :entropy (CC + entropy refinement) or :fft (CC only)
transform:shiftTransform model: :shift (translation only) or :affine (rotation + scale + translation)
maxn100Maximum neighbors for entropy calculation
histbinsize0.05Histogram bin size (μm) for cross-correlation
verbose0Verbosity level: 0=quiet, 1=info
source

Output: AlignInfo

SMLMDriftCorrection.AlignInfo — Type
AlignInfo <: AbstractSMLMInfo

Result metadata from align_smld.

Fields

  • shifts::Vector{Vector{Float64}}: Recovered shift for each SMLD (shifts[1] = zeros)
  • transforms::Vector{<:AbstractAlignTransform}: Full transform for each SMLD (ShiftTransform for :shift, AffineTransform2D/3D for :affine). For transform=:affine this is a similarity-best-fit approximation of the general affine that was actually applied — the exact coefficients live in diagnostic.
  • elapsed_s::Float64: Wall time in seconds
  • method::Symbol: Method used (:entropy or :fft)
  • transform::Symbol: Transform model used (:shift or :affine)
  • backend::Symbol: Computation backend (:cpu)
  • diagnostic::Union{Nothing, NamedTuple}: Extra per-dataset information. nothing for pure shift alignment. For transform=:affine, a NamedTuple with:
    • affine_coeffs::Vector{NTuple{6,Float64}}: per-dataset (a, b, c, d, e, f) such that the applied correction was x -> x - (a·x + b·y + c), y -> y - (d·x + e·y + f) (summed over the two refinement passes).
    • global_shifts::Vector{Vector{Float64}}: per-dataset global shift applied before the affine correction (summed over both passes).
source

Usage

# Entropy method (default) -- CC initial guess + entropy refinement
(aligned, info) = align_smld([smld_ch1, smld_ch2])
info.shifts  # [zeros(2), [dx, dy]]

# FFT method -- cross-correlation only
(aligned, info) = align_smld(smlds; method=:fft)

# Config struct form
config = AlignConfig(method=:entropy, maxn=100)
(aligned, info) = align_smld(smlds, config)

The entropy method regularizes toward the cross-correlation initial guess to prevent optimizer divergence. Shift alignment works on both 2D and 3D data, threaded across datasets. Use transform=:affine for a 2D affine correction; see align_smld for requirements. info.transforms then holds similarity approximations, while info.diagnostic holds the exact affine coefficients and global shifts.