Skip to content

Camera and background

Noise chain

Per pixel, in this order:

  1. quantum efficiency (Camera.QuantumEfficiency, default 0.85, an estimate; the Kinetix22's peak QE is 95 % [kinetix-datasheet]),
  2. plus dark current (Camera.DarkCurrentElectronsPerSec, 1.03 e-/s [kinetix-datasheet]),
  3. Poisson shot noise,
  4. Gaussian read noise (Camera.ReadNoiseElectrons, 1.2 e- [kinetix-datasheet]), optionally with a per-pixel spread (Camera.sCMOS_ReadNoiseStdPctPerPixel, 20%, an estimate),
  5. gain (Camera.GainElectronsPerADU, 0.25 e⁻/ADU [kinetix-datasheet]; per-pixel spread Camera.sCMOS_GainStdPctPerPixel, 0.5%, an estimate),
  6. a static per-pixel offset (Camera.OffsetADU 100, std Camera.OffsetStdADU 0.5; both estimates),
  7. 16-bit clamp.

Read noise, dark current and gain follow the Photometrics Kinetix22 sCMOS (Sensitivity mode) datasheet [kinetix-datasheet]; the QE, the offset and the per-pixel spreads are estimates because the datasheet does not give them. Every sCMOS pixel has its own offset, read noise and gain [huang2013]. The gain spread (PRNU, a static pattern proportional to the signal) is 0.5% (an estimate; a published sCMOS PRNU is 0.06 % rms at 15 000 e- and 0.3 % rms at 700 e- [orcaflash4v3-technote]); it was 5% until 2026-10-01, which hid little in single-molecule frames but swamped brightfield contrast. An EMCCD path (Camera.CameraType) adds EM gain and clock-induced charge but keeps this project's dark current. The per-pixel gain and read-noise spreads are an sCMOS's (an amplifier per pixel): an EMCCD reads every pixel through one amplifier, so it ignores them (since 2026-10-06; the per-pixel offset applies to both).

The chain switched on step by step, ending with both camera presets:

Expected photons Shot noise only + Dark current, read noise Kinetix22 (default) iXon Ultra 897 (EMCCD)
One row through a blink at each step One row through a blink at each step
The camera's noise chain switched on step by step, on one frame of the default movie (ATTO 655 DNA-PAINT, 64 px). Every panel in photoelectrons on one grey scale ((ADU - offset) x gain): the expected photons (the photon image before the camera), Poisson shot noise only, plus dark current and read noise (with its per-pixel spread), the full Kinetix22 (gain, offset, per-pixel gain spread, 16 bits) and the iXon Ultra 897 EMCCD (EM gain with its excess noise, clock-induced charge). Below: the row through the brightest pixel.
How this figure was made (5 cli runs, 923 ms)

Generated by tools/build_physics_figures.py (figure cam-chain) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time (those marked [served] ran one after the other in one insiliscope_cli --serve process, which keeps the world and the PSF kernel between commands; their outputs are the same as separate runs'):

insiliscope_cli --photons-out photons.photons.tif --x 6.58 --y -5.61 --size 64 --frames 1 --npc-dye None    # 832 ms [served]
insiliscope_cli --out step1.movie.tif --setup-json step1.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --gain 1 --read-noise 0 --read-noise-std-pct 0 --gain-std-pct 0 --offset 0 --offset-std 0 --dark-per-sec 0 --npc-dye None    # 22 ms [served]
insiliscope_cli --out step2.movie.tif --setup-json step2.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --gain 1 --read-noise 1.2 --read-noise-std-pct 20 --gain-std-pct 0 --offset 0 --offset-std 0 --dark-per-sec 1.03 --npc-dye None    # 24 ms [served]
insiliscope_cli --out step3.movie.tif --setup-json step3.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --npc-dye None    # 19 ms [served]
insiliscope_cli --out step4.movie.tif --setup-json step4.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --camera-preset iXonUltra897 --npc-dye None    # 26 ms [served]

The gain spread on a BrightField image, where it matters:

No noise (photons) Gain spread 0.5 % (default)
One frame
Mean of 20 frames
Cells' contrast (rms, % of the mean) 0.65
Empty field: pixel spread, one frame (%) 1.7
... in the mean of 20 frames (%) 0.62

The sCMOS per-pixel gain spread (PRNU; Camera.sCMOS_GainStdPctPerPixel, cli gain-std-pct) is a fixed pattern proportional to the signal. BrightField cells are weak phase objects: here (focus 1 µm above the coverslip, the default lamp of 4000 photons per pixel and frame) their contrast is below one frame's shot noise (the empty field's spread in one frame). Averaging frames removes the shot noise but not the pattern, so the gain spread sets how far averaging helps: compare the empty field's spread in the mean of 20 frames with the cells' contrast. The default 0.5 % is an estimate, above a published sCMOS PRNU of 0.06-0.3 % rms [orcaflash4v3-technote]; it was 5 % before 2026-10-01, a pattern far above the cells' contrast. One grey scale for all (intensity / its mean).

How this figure was made (3 cli runs, 505 ms)

Generated by tools/build_physics_figures.py (figure cam-prnu) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time (those marked [served] ran one after the other in one insiliscope_cli --serve process, which keeps the world and the PSF kernel between commands; their outputs are the same as separate runs'):

insiliscope_cli --photons-out photons.photons.tif --x 6.58 --y -5.61 --size 96 --z 1 --modality BrightField --frames 1 --npc-dye None    # 454 ms [served]
insiliscope_cli --out g0.5.movie.tif --setup-json g0.5.setup.json --x 6.58 --y -5.61 --size 96 --z 1 --modality BrightField --frames 20 --gain-std-pct 0.5 --npc-dye None    # 27 ms [served]
insiliscope_cli --out flat0.5.movie.tif --x 6.58 --y -5.61 --size 96 --z 1 --modality BrightField --frames 20 --occupancy 0 --gain-std-pct 0.5 --npc-dye None    # 24 ms [served]

The gain (Camera.GainElectronsPerADU, e⁻/ADU) is the whole conversion from photoelectrons to counts for both sensor types. The EM gain does not scale the signal: the multiplication is drawn as Gamma(shape = n, scale = 1) on the n photoelectrons, so its mean stays n and it adds the \(\sqrt{2}\) excess noise factor [hirsch2013]; the EM gain divides the read noise (50 e⁻ at an EM gain of 150 is 0.33 e⁻ effective; the iXon preset's 50 e⁻ is an estimate, the 897's specification gives 37-89 e⁻ at 5-17 MHz [ixon897-datasheet]). The EM gain is not set on its own: it is the camera preset's pre-amplifier sensitivity (e⁻/ADU after the EM register; 1 e⁻/ADU for the iXon, an estimate, and when a preset has none) divided by the gain (Micro-Manager's Camera.EMCCD_EmGain is read-only; the cli's em-gain can override it). The iXon Ultra 897 preset sets the gain per photoelectron: 0.0066 e⁻/ADU (about 150 ADU per photoelectron, EM gain about 150) for dSTORM, PALM and DNA-PAINT, 0.1 e⁻/ADU (EM gain 10) for WideField and BrightField, where 0.0066 would saturate the 16-bit output (estimates). A mode or modality change re-applies it (viewer and Micro-Manager); the cli picks it from the spec's mode and modality unless gain is given.

The per-pixel maps (offset, gain and read-noise spreads) are a pure function of the seed and the camera settings, so the same camera keeps the same fixed pattern in live mode and in a stack, whatever else changes (since 2026-10-08; live mode redrew them on every property change before).

The gain and the EMCCD's excess noise, measured as photon transfer curves on the cli's frames:

Photon transfer curves Photon transfer curves
Photon transfer of the two camera presets, measured on the cli's frames: an empty field (occupancy 0) under a flat background of increasing intensity, the variance from the difference of two frames (the per-pixel offset and gain patterns cancel). Shot noise makes the variance grow with the mean at 1/gain (gain in electrons per ADU); the EMCCD's multiplication doubles that (an excess noise factor of √2 [hirsch2013]); at low signal the read-noise floor shows. The fit is the slope through the three brightest levels; levels that reach the 16-bit ceiling are left out. The lowest points sit on the free imager's background (the default DNA-PAINT label).
How this figure was made (14 cli runs, 644 ms)

Generated by tools/build_physics_figures.py (figure cam-ptc) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time (those marked [served] ran one after the other in one insiliscope_cli --serve process, which keeps the world and the PSF kernel between commands; their outputs are the same as separate runs'):

insiliscope_cli --out Kinetix22-10.movie.tif --setup-json Kinetix22-10.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 200 --npc-dye None    # 56 ms [served]
insiliscope_cli --out Kinetix22-30.movie.tif --setup-json Kinetix22-30.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 600 --npc-dye None    # 36 ms [served]
insiliscope_cli --out Kinetix22-100.movie.tif --setup-json Kinetix22-100.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 2000 --npc-dye None    # 49 ms [served]
insiliscope_cli --out Kinetix22-300.movie.tif --setup-json Kinetix22-300.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 6000 --npc-dye None    # 38 ms [served]
insiliscope_cli --out Kinetix22-1000.movie.tif --setup-json Kinetix22-1000.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 20000 --npc-dye None    # 61 ms [served]
insiliscope_cli --out Kinetix22-3000.movie.tif --setup-json Kinetix22-3000.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 60000 --npc-dye None    # 55 ms [served]
insiliscope_cli --out Kinetix22-10000.movie.tif --setup-json Kinetix22-10000.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 200000 --npc-dye None    # 60 ms [served]
insiliscope_cli --out Kinetix22-30000.movie.tif --setup-json Kinetix22-30000.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset Kinetix22 --background-per-sec 600000 --npc-dye None    # 53 ms [served]
insiliscope_cli --out iXonUltra897-1.movie.tif --setup-json iXonUltra897-1.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 20 --npc-dye None    # 73 ms [served]
insiliscope_cli --out iXonUltra897-3.movie.tif --setup-json iXonUltra897-3.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 60 --npc-dye None    # 56 ms [served]
insiliscope_cli --out iXonUltra897-10.movie.tif --setup-json iXonUltra897-10.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 200 --npc-dye None    # 39 ms [served]
insiliscope_cli --out iXonUltra897-30.movie.tif --setup-json iXonUltra897-30.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 600 --npc-dye None    # 21 ms [served]
insiliscope_cli --out iXonUltra897-100.movie.tif --setup-json iXonUltra897-100.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 2000 --npc-dye None    # 24 ms [served]
insiliscope_cli --out iXonUltra897-200.movie.tif --setup-json iXonUltra897-200.setup.json --size 64 --frames 2 --occupancy 0 --psf-model 0 --camera-preset iXonUltra897 --background-per-sec 4000 --npc-dye None    # 23 ms [served]

The two presets and an ideal camera on one frame:

Kinetix22 (sCMOS, default) iXon Ultra 897 (EMCCD) Ideal (QE 1, shot noise only)
Frame (ADU)
Detected fraction F (holds the QE) 0.42 0.44 0.46
Read noise 1.2 e- 50 e-, EM gain 152 0 e-
Gain (e-/ADU) 0.25 0.0066 1.0
Brightest blink / noise 223 97 231

The camera presets (Camera.CameraPreset, cli camera-preset) on one frame of the default movie (ATTO 655 DNA-PAINT, 64 px), and an ideal camera to compare. Each image its own contrast. The QE curve enters through the label's detected fraction F. 'Brightest blink / noise': a rough signal-to-noise measure on the frame itself, the brightest blink's 3 × 3 px sum over the noise of the dimmer half of the pixels (which holds the imager background's shot noise too).

How this figure was made (3 cli runs, 911 ms)

Generated by tools/build_physics_figures.py (figure cam-presets) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time (those marked [served] ran one after the other in one insiliscope_cli --serve process, which keeps the world and the PSF kernel between commands; their outputs are the same as separate runs'):

insiliscope_cli --out cam0.movie.tif --setup-json cam0.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --npc-dye None    # 819 ms [served]
insiliscope_cli --out cam1.movie.tif --setup-json cam1.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --camera-preset iXonUltra897 --npc-dye None    # 43 ms [served]
insiliscope_cli --out cam2.movie.tif --setup-json cam2.setup.json --x 6.58 --y -5.61 --size 64 --frames 1 --gain 1 --read-noise 0 --read-noise-std-pct 0 --gain-std-pct 0 --offset 0 --offset-std 0 --dark-per-sec 0 --camera-preset Custom --qe-curve Custom --qe 1 --npc-dye None    # 50 ms [served]

Noise draws are counter-based (pcg4d), so frames are independent and the CPU and GPU paths agree except for float32 rounding (at least 99.8% of pixels identical; the rest differ by one electron in a Poisson draw). Two draws from one sequential stream are never put in one C++ expression (evaluation order is unspecified); this once made the sCMOS noise non-reproducible across rebuilds.

Background

A flat background (SampleHolder.BackgroundPhotonsPerSec) multiplies an illumination field and fades with time:

  • fade: \(0.3 + 0.7\,e^{-t/\tau}\) (SampleHolder.BackgroundDecaySec; the form and its constants are estimates); \(t\) is the movie's time, in Micro-Manager the dose at the FOV centre, in seconds of the first light (Illumination history: one value per frame, the fade does not vary across the FOV);
  • illumination: the excitation's lateral footprint at the coverslip (illum-profile, illum-fwhm; MM Lasers.IlluminationProfile, Lasers.IlluminationFwhmPct; default a Gaussian with a FWHM of the FOV's width) multiplies the background (Dyes and light path).

For fluorescence the background is in detected photons: it is multiplied by the camera's QE at the emission filter's centre, and the noise chain runs at QE 1 because each dye's detected fraction already holds the QE curve (Dyes and light path). Camera presets (camera-preset, MM Camera.CameraPreset: Kinetix22, iXon Ultra 897, Custom) set the noise values and the QE curve (qe-curve, MM Camera.QeCurve; Custom = flat at qe). BrightField uses the curve's QE at its lamp wavelength.

Out-of-focus light needs no extra population: every dye of the cell field sits at its own depth and is drawn with the defocused PSF.

Drift

The sample drift has two parts, set separately for xy and z: a directed part (a slow, mostly steady movement in one direction, as from thermal expansion or a creeping stage) and a random walk on top.

Preset. SampleHolder.DriftPreset (a Basic property; the viewer's Drift select, the Drift group of the shipped configurations) sets both speeds and both random walks at once; the directions and wanders keep their values. The tiers are estimates (Ma et al. 2024 simulate random walks of 5, 10 and 20 nm/\(\sqrt{\text{s}}\) [ma2024], from High to four times High):

Preset \(V_{xy}\), \(V_z\) (nm/s) \(\sigma_{xy}\), \(\sigma_z\) (nm/\(\sqrt{\text{s}}\))
Off (default) 0 0
Low 2 0.4
Medium 5 1
High 25 5
Extreme 250 50

Setting any of the four by hand makes the preset Custom. The speeds go up to 1000 nm/s, the walks to 200 nm/\(\sqrt{\text{s}}\) (4x Extreme).

The Extreme preset as a movie, and every preset's drift over 30 s:

Extreme drift
The Extreme drift preset (250 nm/s directed, 50 nm/√s random walk, xy and z) on mEGFP WideField, 96 px (9.6 µm), 15 s of 50 ms frames, every 3rd frame, in real time: the field slides and the focus wanders into the cell. One grey scale for all frames.
Drift trajectories per preset Drift trajectories per preset
SampleHolder.DriftPreset (viewer: Drift; cli: the speeds and walks it sets, from the cli's preset table): how far the sample has moved after each 50 ms frame over 30 s (the cli's .drift.csv, the true drift; seed 42, the direction and its wander drawn per seed). On log axes the random walk grows as √t at first and the directed part as t later.
How this figure was made (7 cli runs, 50.7 s)

Generated by tools/build_physics_figures.py (figure drift-presets) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time (those marked [served] ran one after the other in one insiliscope_cli --serve process, which keeps the world and the PSF kernel between commands; their outputs are the same as separate runs'):

insiliscope_cli --presets-json presets.presets.json --npc-dye None    # 2.1 ms [served]
insiliscope_cli --out Off.movie.tif --x 6.58 --y -5.61 --size 16 --frames 600 --drift-xy-speed-nm-per-sec 0 --drift-z-speed-nm-per-sec 0 --drift-xy-nm-per-sqrt-sec 0 --drift-z-nm-per-sqrt-sec 0 --npc-dye None    # 5.59 s [served]
insiliscope_cli --out Low.movie.tif --x 6.58 --y -5.61 --size 16 --frames 600 --drift-xy-speed-nm-per-sec 2 --drift-z-speed-nm-per-sec 2 --drift-xy-nm-per-sqrt-sec 0.4 --drift-z-nm-per-sqrt-sec 0.4 --npc-dye None    # 4.86 s [served]
insiliscope_cli --out Medium.movie.tif --x 6.58 --y -5.61 --size 16 --frames 600 --drift-xy-speed-nm-per-sec 5 --drift-z-speed-nm-per-sec 5 --drift-xy-nm-per-sqrt-sec 1 --drift-z-nm-per-sqrt-sec 1 --npc-dye None    # 4.92 s [served]
insiliscope_cli --out High.movie.tif --x 6.58 --y -5.61 --size 16 --frames 600 --drift-xy-speed-nm-per-sec 25 --drift-z-speed-nm-per-sec 25 --drift-xy-nm-per-sqrt-sec 5 --drift-z-nm-per-sqrt-sec 5 --npc-dye None    # 4.98 s [served]
insiliscope_cli --out Extreme.movie.tif --x 6.58 --y -5.61 --size 16 --frames 600 --drift-xy-speed-nm-per-sec 250 --drift-z-speed-nm-per-sec 250 --drift-xy-nm-per-sqrt-sec 50 --drift-z-nm-per-sqrt-sec 50 --npc-dye None    # 5.38 s [served]
insiliscope_cli --out extreme-movie.movie.tif --x 6.58 --y -5.61 --size 96 --frames 300 --mt-mode WideField --mt-dye -1 --light-preset auto --drift-xy-speed-nm-per-sec 250 --drift-z-speed-nm-per-sec 250 --drift-xy-nm-per-sqrt-sec 50 --drift-z-nm-per-sqrt-sec 50 --npc-dye None    # 25.0 s [served]

Directed part. A mean velocity: xy speed \(V_{xy}\) (SampleHolder.DriftXySpeedNmPerSec, cli/viewer drift-xy-speed-nm-per-sec) in a direction \(\theta_0\) (SampleHolder.DriftXyAngleDeg, degrees from +x; −1, the default, draws it once per seed), and a z speed \(V_z \ge 0\) (SampleHolder.DriftZSpeedNmPerSec) in a direction \(u = \pm 1\) (SampleHolder.DriftZDirection: Up = away from the coverslip, Down, or Random, the default, drawn once per seed; cli/viewer drift-z-direction 1 / −1 / 0). Direction and strength wander slowly, each within bounds:

\[ v_{xy} = V_{xy}\,\max(0, 1 + w\,s_{xy})\;(\cos(\theta_0 + \alpha\,S(\phi)),\ \sin(\theta_0 + \alpha\,S(\phi))), \qquad v_z = u\,V_z\,\max(0, 1 + w\,s_z)\,\cos(\beta\,S(\psi)), \]

where \(\phi, \psi, s_{xy}, s_z\) are independent unit-variance Ornstein–Uhlenbeck processes with correlation time \(\tau\) (SampleHolder.DriftWanderTimeSec, default 60 s, an estimate) and \(S(x) = \operatorname{erf}(x/\sqrt 2)\) maps each to a uniform value in (−1, 1) (A&S 7.1.26 [abramowitz1964]), so the swings stay within their bounds:

  • \(\alpha\) (SampleHolder.DriftXyAngleWanderDeg, default 180, an estimate): the xy direction swings within \(\pm\alpha\) of \(\theta_0\), RMS \(\alpha/\sqrt 3\); 180 = it can turn to any direction.
  • \(\beta\) (SampleHolder.DriftZAngleWanderDeg, default 90, an estimate, at most 180): the z drift is the speed times the cosine of a swing within \(\pm\beta\). At 90 it moves between full speed in its direction and standing still, never back; at 180 it also reverses for a while; 0 = steady.
  • \(w\) (SampleHolder.DriftSpeedWanderPct): the strengths' fluctuation, % RMS of the mean.

With \(\alpha = \beta = w = 0\) the velocity is constant. Each frame moves the sample by \(v\,\Delta t\), with \(v\) at the frame's start. The preset (or the two speeds) is the everyday setting; directions, wanders and \(\tau\) are advanced (the viewer shows them under Advanced, MM at Detail Expert).

Random walk. Every frame adds an independent normal step per axis, and the steps add up (the "cumulative normal distribution" of Cnossen et al. 2021 [cnossen2021], used by Ma et al. 2024 at RMS drifts of 5, 10 and 20 nm/s [ma2024]). The step of a frame of length \(\Delta t\) has variance \(\sigma^2 \Delta t\) per axis, so

\[ d(0) = 0, \qquad d(f) = d(f-1) + \sigma \sqrt{\Delta t}\; g_f, \qquad \langle d(t)^2 \rangle = \sigma^2 t , \]

and \(\sigma\) is the RMS displacement per axis after 1 s whatever the frame rate (strictly nm/\(\sqrt{\text{s}}\); "nm/s" in the papers' wording; advanced, "jitter" in the viewer). x and y each get \(\sigma_{xy}\) (SampleHolder.DriftXyNmPerSqrtSec, cli/viewer drift-xy-nm-per-sqrt-sec), z gets \(\sigma_z\) (SampleHolder.DriftZNmPerSqrtSec, drift-z-nm-per-sqrt-sec); both default to 0. A stable setup (optical table, active isolation, constant temperature) drifts several nm/s [ma2024].

  • The draws are counter-based per (seed, frame) on a stream of their own, so a precomputed stack, live mode, the cli, the viewer and webSMLM's CellField.driftTrajectory follow the same path for one seed, and no other draw moves. A movie's (and a precomputed stack's) drift starts at zero at its first frame and is constant within a frame (motion during the exposure is ignored).
  • In Micro-Manager's live mode the drift is the sample's: it starts at zero when the world is made (device load, seed or cell parameters) and continues across Live/MDA stops and starts (since 2026-10-08; it restarted at zero before). The first acquisition on a fresh sample follows the seed's path step by step. Between acquisitions SampleHolder.TimeWhileIdle decides: Running (default) keeps the sample drifting at the wall-clock rate (steps of at most 10 s), Paused holds it still. Switching the drift off keeps the sample where it is. Camera.Test_DriftNm (Test tier) reports the drift of the last frame taken.
  • The drift is the sample's displacement in camera axes, +z away from the coverslip: the focal plane sits \(d_z\) lower in the sample.
  • Blinks and per-dye emitters (dSTORM, PALM, DNA-PAINT; WideField labels drawn per dye): every emitter is drawn at its position plus the drift, with the focal plane moved by \(-d_z\).
  • Mean-field images (WideField labels, PALM pre states, the dSTORM initial ON) and BrightField: under uniform illumination the image of a moved sample is the image moved. A frame is the scene's full-grid image spectrum (periodic and band-limited: grid pitch \(\le \lambda/4\mathrm{NA}\) resp. \(\lambda/4n\)) times a phase ramp, cropped and binned, so a sub-pixel drift is exact rather than re-binned. The illuminated square and the dye grid (mean field) or the grid margin (BrightField) grow by the xy drift. The z drift uses images at foci 10 nm apart, linearly interpolated (mean field: 2e-5 rms, BrightField: 2e-4 of the contrast against the exact focus). In Micro-Manager's live mode both shift the image of a scene anchored within 1 µm of the drifted sample (rebuilt when it moves further; under a shaped beam the beam's offset from that scene is taken in 0.25 µm steps), and the drift's z enters a live frame's focus on a 10 nm grid.
  • The flat background and the DNA-PAINT imager background are unaffected.
  • The cli writes the true drift per frame next to the movie (<name>.drift.csv: frame, dx, dy, dz in nm), ground truth for testing drift correction.

References (cnossen2021, ma2024): J. Cnossen, T. J. Cui, C. Joo, C. Smith, "Drift correction in localization microscopy using entropy minimization", Opt. Express 29(18), 27961-27974 (2021), doi:10.1364/OE.426620. H. Ma, M. Chen, P. Nguyen, Y. Liu, "Toward drift-free high-throughput nanoscopy through adaptive intersection maximization", Sci. Adv. 10(21), eadm7765 (2024), doi:10.1126/sciadv.adm7765.