Skip to content

Dyes and light path

Since issue 16 every structure carries a label: a dye of the library in one of four modes, imaged through a light path of lasers, a dichroic, an emission filter and the camera's QE curve. What a dye emits, at which wavelength (and so with which PSF), and how fast it switches all follow from the dye's data and the light path; nothing is a free "photons per second" number any more. Only the microtubules carry a label today. The switching models themselves are on Photophysics.

The dye library

data/dyes/library.json holds the dyes and fluorescent proteins: AF647, AF532, Cy5, ATTO 655, ATTO 542, mEos3.2, Dendra2, PAmCherry2, mEGFP, mScarlet, DAPI, Hoechst 33342 and a Custom dye with Gaussian spectra. Spectra, extinction coefficients and quantum yields come from FPbase [lambert2019] (CC BY-SA 4.0); per-mode kinetics come from the literature named in each entry (dSTORM from [dempsey2011], DNA-PAINT from [jungmann2010, schnitzbauer2017], PALM photoactivation efficiencies from [durisic2014], primed conversion from [dempsey2015, turkowyd2017]). Values without a measurement behind them are marked estimate in the data. References lists every source.

A dye has a default mode and a block per mode it supports. The fluorescent fraction (e.g. the share of a PALM protein that ever photoactivates) thins the labelled sites with its own address-based draw, nested in the labelled set.

Three dye slots (dye1..3.source, MM Fluorophores.Dye{1,2,3}_Source) hold edited copies of library dyes; a structure can use a slot (mt-dye=Dye1). Any dye field can be overridden per structure (mt-dye.on-sec=0.03) or per slot (dye1.qy=0.5). Micro-Manager shows the slots as Fluorophores.Dye<N>_* (Expert), loaded from the library when their source changes and editable after; a structure there takes a slot (CellField.Microtubules_Label = Dye1) instead of per-structure overrides.

Label modes

Mode Emitters Rendered as
dSTORM an initial ON phase (all dyes on, Exp(initial-on-sec)), then blinks until bleached blinks; the initial ON as a continuous population
PALM optional pre-converted state (e.g. mEos3.2 green) until activation, then blinks blinks; the pre state as a continuous population
DNA-PAINT persistent binding sites: imager binds at \(k_{on} c\), never bleaches (\(k_{on}\) and the bound time are the target's: photophysics) blinks, plus the free imager's background
WideField every dye emits from \(t=0\) and bleaches by its photon budget a continuous population

Changing the mode sets the labelled share of the binding sites to the mode's suggestion (DNA-PAINT 70 %, dSTORM 3 %, PALM 25 %, WideField 70 %; estimates of typical densities), as the viewer does.

The four modes on one field:

dSTORM
Alexa Fluor 647, 3 %
PALM
mEos3.2, 25 %
DNA-PAINT
ATTO 655, 70 %
WideField
mEGFP, 70 %

One camera frame of the same 9.6 µm field (96 px of 100 nm, 50 ms, t = 60 s) with each mode's typical label, labelled share and light preset, each with its own contrast. The blinking modes show a few emitters per frame and the structure only over many frames; WideField shows it in every frame.

How this figure was made (4 cli runs, 5.66 s)

Generated by tools/build_physics_figures.py (figure light-modes) 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 dSTORM.movie.tif --setup-json dSTORM.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --mt-mode dSTORM --mt-dye -1 --light-preset auto --npc-dye None    # 977 ms [served]
insiliscope_cli --out PALM.movie.tif --setup-json PALM.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --mt-mode PALM --mt-dye -1 --light-preset auto --npc-dye None    # 1.10 s [served]
insiliscope_cli --out DNA-PAINT.movie.tif --setup-json DNA-PAINT.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --npc-dye None    # 2.30 s [served]
insiliscope_cli --out WideField.movie.tif --setup-json WideField.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --npc-dye None    # 1.28 s [served]

Excitation, emission and detection

For each dye state (main, or a PALM pre state) and the light path:

\[ k_{exc} = \sum_{\text{lasers}} \sigma(\lambda)\; \Phi(\lambda)\;\bigl(1 - T_{D}(\lambda)\bigr), \qquad \sigma(\lambda) = \frac{\ln 10\;10^{3}\,\varepsilon\,E(\lambda)}{N_A} \]

with \(\varepsilon\) the extinction coefficient, \(E\) the normalised excitation spectrum, \(\Phi\) the laser's photon flux (from its intensity in kW/cm\(^2\)) and \(T_D\) the dichroic's transmission (a long-pass reflects the laser onto the sample). \(\sigma\) is the Beer-Lambert cross-section: \(10^{-\varepsilon c l} = e^{-\sigma n l}\) with \(n = 10^{-3} c N_A\) molecules per cm\(^3\) for \(c\) in mol/L. The emission rate is \(k_{em} = \mathrm{QY}\,k_{exc}\). The detected fraction of the emission spectrum \(S\) is

\[ F = \frac{\sum S\,T_D\,T_F\,\mathrm{QE}}{\sum S}, \]

through the dichroic, the emission filter \(T_F\) and the camera's QE curve, and the detected photons per second while emitting are \(k_{em}\,\eta\,F\) with the objective's collection efficiency \(\eta = \tfrac12\bigl(1 - \sqrt{1 - (\mathrm{NA}/n)^2}\bigr)\), the share of isotropic emission inside the objective's cone (\(\sin\theta = \mathrm{NA}/n\)). The PSF of each state is computed at the detected-spectrum-weighted wavelength, rounded to 2 nm (one kernel per distinct wavelength). MM shows the microtubules' main state as read-only properties: Fluorophores.Microtubules_DetectedPct (\(100F\), without \(\eta\)), EffectiveEmissionNm and PhotonsPerSecOn (\(k_{em}\eta F\)).

The four typical labels through their light presets, as a movie resolves them:

Dye spectra through the light path Dye spectra through the light path
The typical microtubule label of each mode with its light preset, as a movie resolves it (the cli's setup output). The detected part of the emission passes the dichroic, the emission filter and the camera's QE; F is its fraction. mEos3.2's green pre state (before photoconversion) is mostly blocked by the PALM filter. Photons per second: a dye while it emits, after the objective's collection efficiency.
How this figure was made (4 cli runs, 45 ms)

Generated by tools/build_physics_figures.py (figure light-spectra) 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 --setup-json dSTORM.setup.json --mt-mode dSTORM --mt-dye -1 --light-preset auto --npc-dye None    # 15 ms [served]
insiliscope_cli --setup-json PALM.setup.json --mt-mode PALM --mt-dye -1 --light-preset auto --npc-dye None    # 11 ms [served]
insiliscope_cli --setup-json DNA-PAINT.setup.json --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --npc-dye None    # 8.6 ms [served]
insiliscope_cli --setup-json WideField.setup.json --mt-mode WideField --mt-dye -1 --light-preset auto --npc-dye None    # 11 ms [served]

Because the QE is in \(F\), the camera noise chain runs at QE 1 for fluorescence; the flat background-per-sec is multiplied by the QE at the filter's centre. BrightField uses the curve's QE at its lamp wavelength.

Activation and switching rates follow the lasers too: dSTORM switches on at \(1/\tau_{off} + a_{405} I_{405}\), and its ON, spontaneous-dark and initial-ON times scale as \(k_{exc,ref}/k_{exc}\), so the photons per blink and the duty cycle stay as measured at Dempsey et al.'s intensities (488 nm 1.2, 561 nm 2.2, 647 nm 0.8 kW/cm\(^2\)) [dempsey2011] (\(a_{405}\) set from the 405 nm sensitivity grades of its Table 2). PALM activates at the spontaneous rate plus \(a_{405} I_{405}\) plus, for primed conversion, a term in the product of the 470-510 nm and 690-780 nm intensities [dempsey2015, turkowyd2017] (the PALM rate coefficients are estimates). DNA-PAINT blinks at \(k_{on} c\) [jungmann2010] (\(k_{on}\) of the order of \(10^6\) /M/s, an estimate), with \(c\) the imager concentration (mt-imager-nm, MM CellField.Microtubules_ImagerNanoMol).

Light-path presets

data/dyes/light_path.json defines the lasers (405, 488, 561, 640, 730 nm and a custom line), dichroics, emission filters and presets: dSTORM-640/561/488 (Dempsey's conditions [dempsey2011]), PAINT-640/561/488, PALM-561, PALM-primed, WF-405/488/561/640, and the samples' own: Beads-561 (the bead sample) and Slide-405/488/561/640 (the fluorescent slide: a line at 0.02 kW/cm² with the WF preset's filters). Intensities without a source are estimates. Every dye mode names its preset. light-preset=auto (the default, cli/viewer; MM Lasers.Preset Auto) combines the presets of the labelled targets of the mounted sample: each laser at its largest power among them, the dichroic and filter where they all agree, else a multi-band set (Chroma ZT405/488/561/640rpc dichroic and ZET405/488/561/640m emission filter, FPbase curves [lambert2019]), so one image shows every target with its real cross-talk. A dye or mode change re-applies it (viewer, MM). With one labelled target that is its own preset, e.g. PAINT-640 (640 nm at 1 kW/cm\(^2\), an estimate; LP650, 676/37) for the microtubules' ATTO 655 DNA-PAINT label alone. The default cell field labels two targets: the microtubules (ATTO 655, PAINT-640) and the nuclear pores (Cy3B, PAINT-561), so 561 and 640 nm through the multi-band set. Camera presets (cameras.json: Kinetix22, iXon Ultra 897, ...) set the noise values and the QE curve.

Excitation (laser clean-up) filters

An excitation filter sits between the lasers and the dichroic (ex-filter, MM ExcitationFilter wheel or the FilterCube). Each laser line's intensity is multiplied by the filter's transmission at that line, so a 640 nm clean-up blocks the 561 nm line, as on a real scope. The library has None (the default, and every preset's), ideal laser-line band passes of +/- 5 nm (BP405-10, BP488-10, BP561-10, BP640-10), an ideal quad (Quad), measured curves from FPbase [lambert2019] of Chroma ZET405/20x, ZET488/10x, ZET561/10x, ZET642/20x and the quad ZET405/488/561/640xv2, and Semrock FF01-405/10, FF01-488/10, FF01-561/14 and FF01-640/14, and a Custom band pass (ex-lo-nm, ex-hi-nm). Semrock's MaxDiode/MaxLine clean-ups (LD01-405/10, LL02-561, ...) have no FPbase curve; the FF01 band passes stand in for them.

Which light: the shutters

The lasers (epi) and the transmitted lamp each have a shutter: light-epi and light-trans (1 open, 0 closed; -1, the default, follows modality: Fluorescence opens the lasers, BrightField the lamp). In Micro-Manager they are the Lasers and TransmittedLamp shutter devices. Lasers alone give the fluorescence image, the lamp alone the BrightField image. Both open add up on one camera: the fluorescence photons plus the lamp's photons times the camera's QE at the lamp wavelength, then one noise chain (so the lamp's shot noise sits on the fluorescence, as it would on a real scope). None open gives dark frames: offset, read noise and dark current only.

Lasers (epi) Lamp (transmitted) Both None
Frame
Median (ADU) 375 1050 1325 100

The two shutters (cli light-epi, light-trans; Micro-Manager's Lasers and TransmittedLamp) with the default label (ATTO 655 DNA-PAINT) and the lamp turned down to 5000 photons/px/s so both lights show. Both open: the fluorescence photons plus the lamp's, through one noise chain. None: offset, read noise and dark current only. Each image has its own contrast.

How this figure was made (4 cli runs, 1.82 s)

Generated by tools/build_physics_figures.py (figure light-shutters) 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 lasers.movie.tif --x 6.58 --y -5.61 --size 96 --frames 1 --light-epi 1 --light-trans 0 --bf-photons-per-px-per-sec 5000 --npc-dye None    # 1.62 s [served]
insiliscope_cli --out lamp.movie.tif --x 6.58 --y -5.61 --size 96 --frames 1 --light-epi 0 --light-trans 1 --bf-photons-per-px-per-sec 5000 --npc-dye None    # 153 ms [served]
insiliscope_cli --out both.movie.tif --x 6.58 --y -5.61 --size 96 --frames 1 --light-epi 1 --light-trans 1 --bf-photons-per-px-per-sec 5000 --npc-dye None    # 41 ms [served]
insiliscope_cli --out none.movie.tif --x 6.58 --y -5.61 --size 96 --frames 1 --light-epi 0 --light-trans 0 --bf-photons-per-px-per-sec 5000 --npc-dye None    # 3.8 ms [served]

Illumination geometry

One number moves the laser spot across the objective's back focal plane (illum-bfp, MM Lasers.BfpPosition, the shipped configs' Illumination group: Epi / HILO / TIRF): 0 is the centre (epi: the beam goes straight up), 1 the critical angle, between them HILO (a highly inclined sheet, [tokunaga2008]) and above it TIRF (an evanescent layer over the coverslip, [axelrod1981], [axelrod2001]), up to the pupil edge NA / n of the medium (1.047 for the 1.40 objective in water, n = bf-n-medium, MM SampleHolder.MediumIndex, 1.337); beyond it no light reaches the sample (MM Lasers.IlluminationRegime reads Clipped). The position is linear in the BFP radius, so 0.5 is half-way to the critical angle.

The beam's cross-section is illum-profile (Flat, Gaussian, the default, or FlatTop: a disc with a soft edge) with illum-fwhm (% of the FOV width, default 100: the default Gaussian gives the middle of each edge half the centre's light and the corners a quarter). Each dye is excited by the laser power times a factor at its place:

  • epi: the profile, the same at every height;
  • HILO: the profile tilted into a sheet through the focal plane, about W / (tan θs cos θi) thick on the axis (W the beam's FWHM; at 0.95 about 0.6 W: 15 µm for the default beam over a 25.6 µm field, so narrow the beam, illum-fwhm, for a thin sheet; MM Lasers.IlluminationDepthNm), dyes above or below it unlit;
  • TIRF: the profile at the coverslip times \(e^{-z/d}\), \(d = \lambda / (4\pi n \sqrt{u^2-1})\) (about 150 nm at 1.03 and 640 nm), with the interface's field enhancement (up to ~4x near the critical angle).

The factor changes the photons, and through the local light the kinetics: dSTORM switching, PALM activation and bleaching run at the local excitation, each dye at the dose its own place and height has had (Illumination history; a cli or viewer movie's past is start-sec at its own light), so a TIRF field bleaches its bottom layer and leaves the dyes higher up fresh. The DNA-PAINT light presets set HILO (0.95), the nanorulers TIRF (1.03), the other presets epi (all estimates); with several labelled targets (light-preset auto) the position their presets agree on, else epi. Supercritical-angle emission and the beam's angular spread in the BFP are not modelled. The physics in full: spec/ALGORITHM.md, "Excitation geometry".

The excitation factor a movie applies, in a section through the axis and on the axis:

The excitation factor in epi, HILO and TIRF The excitation factor in epi, HILO and TIRF
The excitation factor a movie applies (laser power x this; the cli's setup output, 640 nm, the default Gaussian beam over a 25.6 µm field, focus 0.5 µm: dotted). Top: a section through the axis along the incidence plane, one colour scale: epi lights every height alike, HILO an inclined sheet through the focus (dyes above and below it unlit), TIRF only a layer of about d over the coverslip, with the interface's field enhancement. Bottom: the factor on the axis against height for BFP positions around the critical angle (u = 1): the sheet's factor rises towards it, TIRF's depth falls past it; beyond the pupil edge (1.047) no light reaches the sample.
How this figure was made (9 cli runs, 148 ms)

Generated by tools/build_physics_figures.py (figure light-geometry) 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 --setup-json bfp0.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 0 --npc-dye None    # 24 ms [served]
insiliscope_cli --setup-json bfp0.95.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 0.95 --npc-dye None    # 21 ms [served]
insiliscope_cli --setup-json bfp1.03.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 1.03 --npc-dye None    # 28 ms [served]
insiliscope_cli --setup-json bfp0.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 0 --npc-dye None    # 23 ms [served]
insiliscope_cli --setup-json bfp0.9.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 0.9 --npc-dye None    # 10 ms [served]
insiliscope_cli --setup-json bfp0.99.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 0.99 --npc-dye None    # 10 ms [served]
insiliscope_cli --setup-json bfp1.01.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 1.01 --npc-dye None    # 12 ms [served]
insiliscope_cli --setup-json bfp1.03.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 1.03 --npc-dye None    # 12 ms [served]
insiliscope_cli --setup-json bfp1.045.setup.json --x 6.58 --y -5.61 --size 256 --frames 1 --illum-bfp 1.045 --npc-dye None    # 7.7 ms [served]

What it does to a frame, with the free DNA-PAINT imager's background:

Epi (0) HILO (0.95) TIRF (1.03)
Nanorulers
Median (photons/px) 1056 858 94.0
Cell field
Median (photons/px) 67.9 68.8 5.00

One frame (50 ms, 9.6 µm field, t = 60 s, each its own contrast) of the DNA-origami nanorulers (strands 13.5 nm over the coverslip, focus on them) and of the default cell field (focus 0.5 µm), both in DNA-PAINT, at three BFP positions. The median is the photon image before the camera, mostly the free imager: TIRF lights only the layer over the coverslip, so the nanorulers' background drops about ninefold. HILO's sheet is the beam's width over tan θs cos θi thick, here (a 9.6 µm beam) about 6 µm: about the column epi collects from a beam this narrow, so it changes little; its gain is against a wide epi beam. In the cell field TIRF leaves the microtubules above the evanescent layer dark.

How this figure was made (6 cli runs, 1.30 s)

Generated by tools/build_physics_figures.py (figure light-geometry-images) 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 Nanorulers-Epi.movie.tif --photons-out Nanorulers-Epi.photons.tif --setup-json Nanorulers-Epi.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --specimen 1 --illum-bfp 0    # 538 ms [served]
insiliscope_cli --out Nanorulers-HILO.movie.tif --photons-out Nanorulers-HILO.photons.tif --setup-json Nanorulers-HILO.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --specimen 1 --illum-bfp 0.95    # 29 ms [served]
insiliscope_cli --out Nanorulers-TIRF.movie.tif --photons-out Nanorulers-TIRF.photons.tif --setup-json Nanorulers-TIRF.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --specimen 1 --illum-bfp 1.03    # 30 ms [served]
insiliscope_cli --out Cell-Epi.movie.tif --photons-out Cell-Epi.photons.tif --setup-json Cell-Epi.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --illum-bfp 0 --npc-dye None    # 606 ms [served]
insiliscope_cli --out Cell-HILO.movie.tif --photons-out Cell-HILO.photons.tif --setup-json Cell-HILO.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --illum-bfp 0.95 --npc-dye None    # 60 ms [served]
insiliscope_cli --out Cell-TIRF.movie.tif --photons-out Cell-TIRF.photons.tif --setup-json Cell-TIRF.setup.json --x 6.58 --y -5.61 --size 96 --frames 1 --illum-bfp 1.03 --npc-dye None    # 33 ms [served]

Structured illumination (SIM)

The light can also be several coherent beams, each a spot in the objective's back focal plane, interfering in the sample to a fringe pattern that steps with the frame (sim-type, MM Lasers.Pattern; the physics in full: spec/ALGORITHM.md, "Structured illumination"):

type beams frames per cycle after
SIM2D two at ±r 3 phases × 3 orientations (60° apart) [gustafsson2000]
SIM3D two at ±r and the centre 5 phases × 3 orientations [gustafsson2008]
LatticeSIM2D ±x and ±y 9 phases, one orientation a four-beam lattice
LatticeSIM3D ±x, ±y and the centre 13 phases (the x and y pairs stepping 2 : 3) the five-beam lattice of Zeiss's Lattice SIM
Custom the beam matrix alone sim-phases × sim-angles
The light entering the objective's back focal plane and the pattern it makes, from the viewer's engine (the same sketch as the viewer's Preview illumination button): pick a pattern type, the BFP position, the polarization and the beam width, step through the frame program and move the section's height. From above: the pupil, each beam's spot ringed with its polarization (a bar for linear, a circular arrow for circular; evanescent beams in blue); from below: the excitation on a plane at the chosen height (the focus to start); from the side: an x-z section through the axis, the section's height dashed.

The beams sit at radius r = illum-bfp in the pupil: the BFP slider is the beams' radius, so past 1 the beams are evanescent and the pattern is TIRF-SIM [kner2009]; a type pick sets it to the type's default (2D 0.95, 3D and the lattices 0.9; estimates). Two beams at ±u make a fringe of period λ / (2 n u) in the medium (180 nm at u = 0.95 and 640 nm): near the resolution limit, so the raw frames show little of the fringe itself and mostly its moiré with the sample, which is what a reconstruction uses. Every beam's field is the single beam's physics above at the beam's own angle (the Fresnel amplitudes below the critical angle, Axelrod's evanescent field components with their phases above it [axelrod1984]), the pattern their coherent sum squared [frohn2001]; the polarization (sim-polarization, Advanced) is S for every type unless changed (each beam linearly polarized across its plane of incidence, a beam and its opposite with the same field direction as one grating's +1 and -1 orders have: full fringe contrast, and the centre-beam fringes of SIM3D at their maximum in the in-phase plane; for the lattice the x and y pairs then do not interfere, and LatticeSIM3D's centre beam is linear diagonal under S and P, interfering with both pairs alike and in phase, so both first-order fringes peak in the focal plane together; a circular centre beam, set through the matrix, would put them a quarter axial period apart), or P, Circular, LinearX or Unpolarized. The beams are in phase at the focal plane (sim-anchor Focus, Advanced): the grating or SLM that makes them is conjugate to it, so the pattern's axial structure moves with the focus and the sample moves through it, whether the stage or the objective moves; Coverslip fixes the fringes to the sample instead (an illumination path that does not move with the focus). The evanescent decay of a TIRF beam is from the coverslip either way.

The SIM types: the beams in the pupil, the pattern on the focal plane and in an x-z section The SIM types: the beams in the pupil, the pattern on the focal plane and in an x-z section
The four pattern types at frame 0, the beams at 0.5 in the pupil (below the types' defaults, so the fringes are resolved here; 488 nm, the widefield microtubules' line). Top: the pupil from above, the critical circle dotted, each beam's spot (the default beam's 29 µm radius) with its polarization (S: a bar across its plane of incidence; the lattice's diagonal centre beam a bar at 45 degrees). Middle: the excitation on the focal plane over the centre 6 µm (period 303 nm for a pair at 0.5). Bottom: an x-z section through the axis: 2D SIM's stripes reach every height alike, the centre beam of 3D SIM and the 3D lattice adds an axial modulation.
How this figure was made (4 cli runs, 74 ms)

Generated by tools/build_physics_figures.py (figure sim-patterns) 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 --setup-json SIM2D.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --sim-type SIM2D --npc-dye None    # 16 ms [served]
insiliscope_cli --setup-json SIM3D.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --sim-type SIM3D --npc-dye None    # 16 ms [served]
insiliscope_cli --setup-json LatticeSIM2D.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --sim-type LatticeSIM2D --npc-dye None    # 19 ms [served]
insiliscope_cli --setup-json LatticeSIM3D.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --sim-type LatticeSIM3D --npc-dye None    # 23 ms [served]

Each beam is a spot in the pupil: a Gaussian spot of radius wb is exactly a Gaussian beam of 1/e² radius λ f / (π wb) in the sample (f the objective's focal length, tube-length-mm / magnification), so a spot size sets the envelope's width (and illum-fwhm is the same knob the other way round: the readouts give the equivalent spot radius), and a bundle entering the pupil at an angle lands its envelope off-axis by f tan(tilt). The Expert beam matrix (MM Lasers.Beams, cli --sim-beams) holds every beam of the illumination path in every mode, one row per beam, ux uy spot tiltX tiltY amp phase pol ellip phaseStep nm: position in u units, the spot's radius in µm (0: from illum-fwhm), the entry tilt in degrees (two axes), the field amplitude, the phase in turns, the polarization angle in degrees (0 s, 90 p) and the ellipticity phase in turns (0.25 circular), the phase step per phase index in 1/sim-phases turns, and the laser line the beam carries (0: every line). A row over the type's beam j replaces the columns given; a row beyond them adds a beam; Custom is the matrix alone. For example the 2D SIM pair at 0.95 is 0.95 0 0 0 0 1 0 0 0 1 0; -0.95 0 0 0 0 1 0 0 0 -1 0, and a lattice whose centre beam the fibre bundle delivers 0.5° off is its five rows with tiltX 0.5 in the first.

The spot size and tilt in the pupil against the envelope in the sample The spot size and tilt in the pupil against the envelope in the sample
Left: a Gaussian spot of 1/e² radius wb in the pupil is a Gaussian beam of 1/e² radius λ f / (π wb) in the sample (f = 2 mm, the tube length over the magnification); the dotted line is the default beam (a FWHM of the whole 12.8 µm field: a 37 µm spot at 640 nm). Middle: the beam matrix row 0 0 100 0 0 1 0 0 0 0 0 -- a 100 µm spot -- lights only a 4.1 µm beam in the centre of the field. Right: the default spot entering the pupil 0.1° off axis (tiltX 0.1) lands its envelope f tan(0.1°) = 3.5 µm off centre.
How this figure was made (3 cli runs, 27 ms)

Generated by tools/build_physics_figures.py (figure sim-spot) 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 --setup-json spot0.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0 --npc-dye None    # 11 ms [served]
insiliscope_cli --setup-json spot100.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0 --sim-beams 0 0 100 0 0 1 0 0 0 0 0 --npc-dye None    # 8.0 ms [served]
insiliscope_cli --setup-json tilt.setup.json --x 6.58 --y -5.61 --size 128 --frames 1 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0 --sim-beams 0 0 0 0.1 0 1 0 0 0 0 0 --npc-dye None    # 7.6 ms [served]

The frame program. Frame f of the sample's frame sequence takes the phase index f mod nPhases and the orientation index f / nPhases (every phase of an orientation, then the next; sim-order PhaseMajor swaps them); the orientation rotates every beam by 180° / sim-angles, the phase adds phaseStep × phase / nPhases turns to each beam. A cli stack starts at frame 0 and writes the state per frame to <out>.pattern.csv; the viewer's live sample counts its frames (every frame, dark ones too; a fresh sample starts at 0). A phase drift (Expert) makes each beam's phase walk per frame (sim-phase-drift-turns-per-sqrt-sec) or slide (sim-phase-drift-turns-per-sec).

frame 0 (angle 0, phase 0) frame 1 (phase 1) frame 2 (phase 2) frame 3 (angle 1) the 9 frames summed 2 x 9 x one beam

The photon images (before the camera) of a 2D SIM cycle on the widefield microtubules (mEGFP, 488 nm, the beams at 0.5): the fringe moves by a third of its period per frame and turns by 60° every three frames. Summed over the cycle the fringes cancel: the sum equals twice nine frames under the single beam at the same power (the pair carries two beams), per pixel 0.274 ± 0.081 over the lit field -- the cycle mean the dyes' kinetics run on.

How this figure was made (2 cli runs, 4.40 s)

Generated by tools/build_physics_figures.py (figure sim-cycle) 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 sim2d.movie.tif --photons-out sim2d.photons.tif --setup-json sim2d.setup.json --x 6.58 --y -5.61 --size 96 --frames 9 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --sim-type SIM2D --npc-dye None    # 4.20 s [served]
insiliscope_cli --out off.movie.tif --photons-out off.photons.tif --setup-json off.setup.json --x 6.58 --y -5.61 --size 96 --frames 9 --mt-mode WideField --mt-dye -1 --light-preset auto --illum-bfp 0.5 --npc-dye None    # 198 ms [served]

What the dyes see. The emission of every frame uses that frame's exact pattern: each blink's photons at its site, the mean-field image under the pattern (one scene per pattern state), the background at the coverslip. One exception, for speed: the blink modes' always-glowing dyes (dSTORM's initial ON, PALM's pre state) glow under the cycle mean, one image as without a pattern (live dSTORM-SIM ~6x faster; over a full cycle the same photons, 1 % per pixel apart in a 9-frame SIM2D test); Renderer.Quality Exhaustive (Renderer.PatternOnContinuousDyes On, Expert; --sim-frame-pattern-continuous 1) gives them the frame's pattern too. The blinks and WideField dyes always take it. The kinetics (dSTORM switching, PALM activation, bleaching, the background fade) run on the cycle mean of the pattern, the beams' incoherent sum, which the pattern summed over a full phase cycle equals exactly: a dye's dose within a cycle is off by at most one frame's modulation, and the light history keeps one chapter per beam set (one component per beam of the cycle) instead of a fringe it could not resolve. The DNA-PAINT imager background uses the cycle mean too (the defocused column blurs the fringes). A fiducial bead takes the pattern averaged over its volume (the ball's form factor per fringe term: a 200 nm bead on a 200 nm fringe keeps about a third of the modulation, and is never dark), and the fluorescent slide shows the frame's fringes imaged through the PSF at every height's defocus: in a thick layer only the heights near focus keep their fringe, so its contrast is a fraction of the pattern's; a thin layer or TIRF-SIM shows it in full.

The DNA-PAINT imager background

Unbound imager in the illuminated volume adds an offset per pixel and second of

\[ c\,N_A\,A_{px}\;k_{em}\eta F \int_0^H \int K_{|z - z_f|}(\mathbf r)\, e(\mathbf x + \mathbf r, z)\,d^2r\,dz, \]

with \(c\) the imager concentration, \(H\) the chamber height (chamber-height-um, default 100 µm, an estimate: tape and flow chambers), \(A_{px}\) the pixel area in the sample, \(e\) the excitation factor of the illumination geometry (illum-bfp) and \(K\) the spread of a dye's light at a distance \(|z - z_f|\) from the focal plane: the light of the imager high above the focus lands on the camera defocused, spread over a disc of radius \(|z - z_f| \tan\theta\), so a pixel collects the lit imager within that reach of it, not the column above it alone. \(K\) is geometric optics in one medium: an isotropic emitter (a freely rotating imager), its emission angles weighted by the Fresnel power transmittance into the coverslip (Born & Wolf 1999, section 1.5.3; s and p averaged), collected with equal power per solid angle by an aplanatic objective (Richards & Wolf 1959) up to its NA. Without the weighting the focal-plane irradiance of a dye at defocus \(\delta\) is \(\delta / 2\pi(\delta^2 + r^2)^{3/2}\), and on the axis of a lit disc of radius \(a\) the column is \(\int_0^H 1 - \delta/\sqrt{a^2 + \delta^2}\,d\delta \approx a\) for \(H \gg a\); the Fresnel weighting favours the steep rays, so the effective column is a little deeper.

So in epi the background is set by the beam's width, not the chamber's height: about 15 µm on the axis of the default beam, a Gaussian of 25.6 µm FWHM (14 µm with a FlatTop that wide; 100 µm before this model), and about half that at the beam's edge. In TIRF only the evanescent layer is lit (about the field factor times the decay depth, ~0.5 µm), in HILO about the inclined sheet. That is why DNA-PAINT is imaged in HILO or TIRF: the DNA-PAINT light presets set HILO (illum-bfp 0.95), the nanorulers TIRF (1.03), both estimates; TIRF cuts the nanorulers' imager background about 13-fold against epi. The renderers evaluate it on a node grid (a third of the beam's edge width apart) and interpolate: within 0.2 % of a dense quadrature in epi and TIRF, 2 % in HILO. The map depends on the focus (the defocus of each height; HILO's sheet pivots at the focus), so it is made at foci 0.25 µm apart and blended linearly between the two around the focus: a focus move reuses them, and Micro-Manager makes the neighbouring ones ahead on a worker thread. A Flat beam (laterally infinite) lights every plane alike, so the spread changes nothing: there the column is the plain integral, \(H\) in epi.

Left out: the index mismatch between the medium and the glass (the defocus is taken in one medium), the dye's emission dipole (a free imager rotates: isotropic), the near-field (supercritical-angle) emission of dyes within a wavelength of the coverslip, imager depletion and its exclusion from the cells: the background is the same over cells and medium and does not drop as imager binds.

The background against the imager concentration, the model next to the rendered photons:

Imager 0.3 nM 1 nM 3 nM 10 nM
Frame
Background, model (photons/px/frame) 17.9 59.7 179 597
Median, rendered (photons/px/frame) 22.0 74.4 232 798

The DNA-PAINT imager concentration sets the binding rate (konc: more blinks) and the free imager's flat background c NA H Apx kemηF (model row, from the cli's setup output), here under a laterally infinite flat epi beam and a 5 um chamber (H), where the column is the chamber's height; under a shaped beam the collected column is about the beam's radius (above). The rendered row is the median of the photon images before the camera, which also holds the blinks' halos. Frames: one camera frame (ADU, each its own contrast). Default 1 nM; frames 50 ms.

How this figure was made (4 cli runs, 2.65 s)

Generated by tools/build_physics_figures.py (figure light-imager) 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 c0.3.movie.tif --photons-out c0.3.photons.tif --setup-json c0.3.setup.json --x 6.58 --y -5.61 --size 96 --frames 5 --mt-imager-nm 0.3 --illum-profile Flat --illum-bfp 0 --chamber-height-um 5 --npc-dye None    # 1.18 s [served]
insiliscope_cli --out c1.movie.tif --photons-out c1.photons.tif --setup-json c1.setup.json --x 6.58 --y -5.61 --size 96 --frames 5 --mt-imager-nm 1 --illum-profile Flat --illum-bfp 0 --chamber-height-um 5 --npc-dye None    # 273 ms [served]
insiliscope_cli --out c3.movie.tif --photons-out c3.photons.tif --setup-json c3.setup.json --x 6.58 --y -5.61 --size 96 --frames 5 --mt-imager-nm 3 --illum-profile Flat --illum-bfp 0 --chamber-height-um 5 --npc-dye None    # 533 ms [served]
insiliscope_cli --out c10.movie.tif --photons-out c10.photons.tif --setup-json c10.setup.json --x 6.58 --y -5.61 --size 96 --frames 5 --mt-imager-nm 10 --illum-profile Flat --illum-bfp 0 --chamber-height-um 5 --npc-dye None    # 668 ms [served]

Continuous populations: mean field or per dye

Pre states, the dSTORM initial ON and WideField-mode dyes emit continuously. Such a population renders either

  • mean field: the structure's dye density binned on world-anchored z planes (wf-plane-nm) on a grid (wf-upscale cells per pixel, spanning the FOV plus a 2 µm margin), convolved with the state's PSF, times the exact mean photons per dye of the frame, \(r\,(e^{-\lambda t_0} - e^{-\lambda t_1})/\lambda\) (under a shaped beam with each place's own dose and rate); or
  • per dye: each dye's window (its own address-based end: bleached at aux \(\times\) budget / \(k_{em}\), activated, or the initial ON's end) splatted as a unit image into a running image that changes only where windows start or end.

The switch is per frame: mean field while the expected emitters exceed mean-field-density-per-um2 (default 20) in the mean-field-slab-nm (500 nm) slab around focus, or mean-field-max-emitters (5000) in the z range; per dye below (MM Renderer.MeanField*). Both give the same mean (checked within 1 % in total, a few % per pixel).

One population rendered both ways:

Per dye Mean field Mean field - per dye
Photons
How Each dye's window splatted Dye density on z planes × PSF ±43 photons = 31 % of the mean
Total photons 5.704×105 5.697×105 -0.13 %
rms difference per pixel 4.5 % of the mean
Setup / frame 649 ms / 407 ms 420 ms / 70 µs

A continuous population (mEGFP WideField, 10 % of the sites labelled, 64 px, 50 ms frames; the images are the first frame) rendered both ways, forced by the switch's thresholds; by default the renderer picks mean field above 20 emitting dyes per µm2 of the 500 nm focal slab (mean-field-density-per-um2). Times on the build machine (4 threads): the setup in a fresh process, the frame time averaged over 20 frames.

How this figure was made (2 cli runs, 9.29 s)

Generated by tools/build_physics_figures.py (figure light-meanfield) from the code it documents at commit 72b1b0f, on Linux with 4 threads, 2026-11-01. Commands, in order, with their wall time:

insiliscope_cli --photons-out meanfield.photons.tif --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode WideField --mt-dye -1 --light-preset auto --mt-label-pct 10 --mean-field-density-per-um2 0 --npc-dye None    # 451 ms
insiliscope_cli --photons-out perdye.photons.tif --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode WideField --mt-dye -1 --light-preset auto --mt-label-pct 10 --mean-field-density-per-um2 1000000000000 --mean-field-max-emitters 1000000000000 --npc-dye None    # 8.84 s

The blinks of dSTORM, PALM and DNA-PAINT render in one of three regimes, chosen per label and per frame:

Regime What is drawn Exact Lost
SMLM (splat) every blink, its PSF splatted at its position everything --
Binned (approximate SMLM) the same blinks, each snapped to a grid of blink-binned-upscale cells per pixel, one density per PSF z plane, FFT-convolved with that plane's kernel which dyes blink, their photons and z plane, the frame-to-frame fluctuation the position within a cell (±half a cell, ±25 nm at the default 2 cells per 100 nm pixel)
Mean field no blinks: each dye's expected ON time in the frame times the dye density, convolved once the mean image the blinking itself
  • Splat or binned. The splat costs per blink, the binned FFT per field-of-view area. A frame renders binned while more than blink-binned-density-per-um2 (default 12.5; MM Renderer.BlinkBinnedDensityPerUm2) blinks are ON per µm² of the field of view, or blink-binned-max-emitters (1e9) in all; splatted below. The default is an estimate, measured on this implementation (the splat/binned break-even is ~10 per µm²; 12.5 sits just above it): 256 px × 100 frames of DNA-PAINT took 2.9 / 22.9 / 240 s splatted at 1 / 10 / 100 nM imager and 13–20 s binned at any density (upscale 2; 3–4 s at upscale 1). Binned against splat, same blinks: the photons agree within 0.1 %, the mean image within 0.5–2 % (relative L2).
  • Mean field is decided before any blink is drawn, from the expected ON emitters: above blink-mean-field-density-per-um2 per µm² of the mean-field-slab-nm slab, or blink-mean-field-max-emitters in the z range. A movie that is mean-field throughout draws no blinks at all. The expected ON time is closed form: dSTORM and PALM follow the dye's chain (initial ON → first dark → ON → bleached | OFF → ON) as a matrix exponential through each piece of the light history; DNA-PAINT integrates its 1 s binding bins exactly. It matches the core's own blinks to within 0.2 % (some 10^5 to 10^7 dyes).
  • Mean field is off by default (both thresholds 1e9), on purpose. For continuous emitters the mean image is exact up to shot noise. For blinks it is not: the blink fluctuation has a variance of about (photons per blink) times the shot-noise variance, and that fluctuation is the SMLM signal. A blink-mean-field movie has the right total signal (within 0.1 %), but its frame-to-frame noise is shot noise only (std 41 against 126 ADU in the check). Use it as a fast picture of where a dense label sits, not as SMLM data.
  • The regime can change both ways within a movie (a power step, a 405 nm pulse). The progress line says which one drew a frame: SMLM: N blinks (splat), (binned FFT), or blinks: mean-field (FFT).

The three regimes at a low, medium and high imager concentration:

Splat (SMLM) Binned (approximate SMLM) Mean field Binned − splat (frame 0)
Low: 1 nM imager
1.4 ON / µm2 (all z)
0.28 ON / µm2 in the focal slab
default: splat
4.4 ms / frame
photons +0.0 %
frame-to-frame std 37 % of the mean
17 ms / frame
photons +0.1 %
frame-to-frame std 37 % of the mean
255 µs / frame
photons -2.2 %
frame-to-frame std 0 % of the mean
±131 photons = 15 % of the splat frame's peak
rms 8.8 % of its mean
Medium: 10 nM imager
13 ON / µm2 (all z)
2.8 ON / µm2 in the focal slab
default: binned
39 ms / frame
photons +0.0 %
frame-to-frame std 14 % of the mean
20 ms / frame
photons +0.1 %
frame-to-frame std 14 % of the mean
165 µs / frame
photons -0.7 %
frame-to-frame std 0 % of the mean
±194 photons = 7.7 % of the splat frame's peak
rms 2.5 % of its mean
High: 100 nM imager
133 ON / µm2 (all z)
28 ON / µm2 in the focal slab
default: binned
308 ms / frame
photons +0.0 %
frame-to-frame std 4.6 % of the mean
21 ms / frame
photons +0.1 %
frame-to-frame std 4.6 % of the mean
165 µs / frame
photons +0.0 %
frame-to-frame std 0 % of the mean
±314 photons = 2.9 % of the splat frame's peak
rms 0.76 % of its mean

DNA-PAINT (ATTO 655 imager, 64 px, 20 frames of 50 ms) at three imager concentrations, each rendered in the three blink regimes, forced by their thresholds (one movie per cell, camera ADU on one scale per row). Splat and binned draw the same blinks; binned snaps each to a cell of half a pixel (±25 nm), and the last column is what that moves: the photon images (before the camera's noise) of frame 0, binned minus splat, red where binned has more light, blue where less, on ± the largest difference. Mean field draws no blinks, only each dye's expected ON time, so its frames hold still apart from shot noise.

ON / µm2: the ON emitters per frame (each blink weighted by the part of the frame it is ON) per µm2 of the field of view, over all depths and within the 500 nm focal slab (mean-field-slab-nm), averaged over the movie. default is the regime the renderer picks by itself: binned while more than 12.5 are ON per µm2 of the field of view (blink-binned-density-per-um2; the rule counts the blinks the movie queries, which reach a little beyond the field of view), splat below. Mean field is never picked by default: it takes over only when you set blink-mean-field-density-per-um2 below the expected ON density in the focal slab (or blink-mean-field-max-emitters below the expected ON count over all depths); both are 1e9 by default, because mean field loses the blinking. Photons: the movie's total against the splat's; frame-to-frame std: of the photon images, per pixel, averaged (0 for mean field, whose expected image stays put). Times on the build machine (4 threads), per frame.

How this figure was made (15 cli runs, 24.7 s)

Generated by tools/build_physics_figures.py (figure light-blink-regimes) 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 1nM-splat.movie.tif --photons-out 1nM-splat.photons.tif --setup-json 1nM-splat.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 1 --blink-binned-density-per-um2 1000000000 --npc-dye None    # 1.66 s [served]
insiliscope_cli --out 1nM-binned.movie.tif --photons-out 1nM-binned.photons.tif --setup-json 1nM-binned.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 1 --blink-binned-max-emitters 0 --npc-dye None    # 781 ms [served]
insiliscope_cli --out 1nM-mean.movie.tif --photons-out 1nM-mean.photons.tif --setup-json 1nM-mean.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 1 --blink-mean-field-max-emitters 0 --npc-dye None    # 227 ms [served]
insiliscope_cli --dyes-json 1nM-dyes.dyes.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 1 --dyes-t 60,61 --npc-dye None    # 1.39 s [served]
insiliscope_cli --photons-out 1nM-default.photons.tif --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 1 --npc-dye None    # 85 ms [served]
insiliscope_cli --out 10nM-splat.movie.tif --photons-out 10nM-splat.photons.tif --setup-json 10nM-splat.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 10 --blink-binned-density-per-um2 1000000000 --npc-dye None    # 1.66 s [served]
insiliscope_cli --out 10nM-binned.movie.tif --photons-out 10nM-binned.photons.tif --setup-json 10nM-binned.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 10 --blink-binned-max-emitters 0 --npc-dye None    # 816 ms [served]
insiliscope_cli --out 10nM-mean.movie.tif --photons-out 10nM-mean.photons.tif --setup-json 10nM-mean.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 10 --blink-mean-field-max-emitters 0 --npc-dye None    # 160 ms [served]
insiliscope_cli --dyes-json 10nM-dyes.dyes.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 10 --dyes-t 60,61 --npc-dye None    # 1.38 s [served]
insiliscope_cli --photons-out 10nM-default.photons.tif --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 10 --npc-dye None    # 405 ms [served]
insiliscope_cli --out 100nM-splat.movie.tif --photons-out 100nM-splat.photons.tif --setup-json 100nM-splat.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 100 --blink-binned-density-per-um2 1000000000 --npc-dye None    # 12.9 s [served]
insiliscope_cli --out 100nM-binned.movie.tif --photons-out 100nM-binned.photons.tif --setup-json 100nM-binned.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 100 --blink-binned-max-emitters 0 --npc-dye None    # 915 ms [served]
insiliscope_cli --out 100nM-mean.movie.tif --photons-out 100nM-mean.photons.tif --setup-json 100nM-mean.setup.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 100 --blink-mean-field-max-emitters 0 --npc-dye None    # 159 ms [served]
insiliscope_cli --dyes-json 100nM-dyes.dyes.json --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 100 --dyes-t 60,61 --npc-dye None    # 1.68 s [served]
insiliscope_cli --photons-out 100nM-default.photons.tif --x 6.58 --y -5.61 --size 64 --frames 20 --mt-mode DNA-PAINT --mt-dye -1 --light-preset auto --mt-imager-nm 100 --npc-dye None    # 482 ms [served]

Where it lives

adapter/inSiliScope/Simulation/Spectra.*, LightPath.*, DyeLibrary.* (data generated into DyeLibraryData.inc by tools/gen_dye_library.mjs), ScopeMovie.* (FluorescenceMovie), BinnedBlinks.* and BlinkExpectation.*. The core's label model (ABI 10) is in core/src/dyes.*; spec/PORT.md section 16 has the details.