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 <: AbstractSMLMConfigConfiguration for drift correction, holding all algorithm parameters. Constructed with keyword arguments; all fields have sensible defaults.
Fields
| Field | Default | Description |
|---|---|---|
quality | :singlepass | Quality tier: :fft, :singlepass, or :iterative |
degree | 2 | Legendre polynomial degree for intra-dataset drift |
dataset_mode | :registered | Inter-dataset algorithm: :registered (CC-primary) or :continuous (consecutive-chunk CC); ignored by quality=:fft |
chunk_frames | 0 | Split datasets into chunks of N frames (0 = no chunking) |
n_chunks | 0 | Alternative: number of chunks per dataset (0 = use chunk_frames) |
maxn | 100 | Maximum neighbors for entropy calculation |
max_iterations | 10 | Iterations per attempt for :iterative mode (a run that has not converged is retried from its current model up to 3 times) |
convergence_tol | 0.001 | Convergence tolerance in μm for :iterative mode |
warm_start | nothing | Previous info.model for warm starting optimization |
verbose | 0 | Verbosity level: 0=quiet, 1=info, 2=debug |
auto_roi | false | Use dense ROI subset for faster estimation |
σ_loc | 0.010 | Typical localization precision (μm) for ROI sizing |
σ_target | 0.001 | Target drift precision (μm) for ROI sizing |
roi_safety_factor | 4.0 | Safety multiplier for required localizations |
shift_scale | 1.0 | No 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.001Quality 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 ignoresdataset_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 belowconvergence_tolor the entropy cost plateaus (relative improvement < 1e-4, both dataset modes).max_iterationsis 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 <: AbstractSMLMInfoMetadata 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 secondsbackend::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 bylength(smld.emitters)for a size-independent numberhistory::Vector{Float64}: Entropy after the singlepass start and after each iteration (:iterativeonly; empty for:fftand: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 (default0)n_chunks::Int: Chunks per dataset requested for continuous mode (default0)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 averageintra_per_dataset: vector of(dataset, n_locs, corr_x, corr_y, corr_z)— flag bad datasetsinter:(corr_x, corr_y, corr_z)— cross-dataset alignment quality
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)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 <: AbstractSMLMConfigConfiguration for rigid-shift alignment of independent SMLDs.
Fields
| Field | Default | Description |
|---|---|---|
method | :entropy | Alignment method: :entropy (CC + entropy refinement) or :fft (CC only) |
transform | :shift | Transform model: :shift (translation only) or :affine (rotation + scale + translation) |
maxn | 100 | Maximum neighbors for entropy calculation |
histbinsize | 0.05 | Histogram bin size (μm) for cross-correlation |
verbose | 0 | Verbosity level: 0=quiet, 1=info |
Output: AlignInfo
SMLMDriftCorrection.AlignInfo — Type
AlignInfo <: AbstractSMLMInfoResult 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). Fortransform=:affinethis is a similarity-best-fit approximation of the general affine that was actually applied — the exact coefficients live indiagnostic.elapsed_s::Float64: Wall time in secondsmethod::Symbol: Method used (:entropyor:fft)transform::Symbol: Transform model used (:shiftor:affine)backend::Symbol: Computation backend (:cpu)diagnostic::Union{Nothing, NamedTuple}: Extra per-dataset information.nothingfor pure shift alignment. Fortransform=:affine, a NamedTuple with:affine_coeffs::Vector{NTuple{6,Float64}}: per-dataset(a, b, c, d, e, f)such that the applied correction wasx -> 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).
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.