Skip to content

Overview

A microsim simulation follows the light from the sample to the detector. This page describes the parts of a simulation and the order in which they are used. The following pages cover each part in detail.

The Simulation object

All parameters live on one Simulation object. Its fields map onto parts of a real experiment:

Field What it describes Guide
truth_space the grid on which the sample and optics are computed Spaces
output_space the pixel grid of the final image Spaces
sample where the fluorophores are, and which fluorophores they are Samples
channels light sources and filters, one entry per channel Channels
tracks which channels are acquired at the same time Channels
objective_lens numerical aperture, refractive indices, coverslip Modalities
modality how the image is formed: widefield, confocal, ... Modalities
detector camera or point detector, and its noise Detectors
exposure_ms camera exposure, or pixel dwell time for point scanners Detectors
settings compute backend, precision, random seed, caching Settings
output_path where to save the result Saving and loading

Only truth_space and sample are required.

The stages

Simulation.run() computes four things in order. Each is also available as a method, so you can stop at any stage, inspect the intermediate result, or change a parameter and repeat only the later stages.

flowchart TB
    subgraph S1 ["<span class='ms-stage'>1</span> ground_truth()"]
      direction LR
      sp[truth_space] --> gt["fluorophores per voxel<br/><code>(f, z, y, x)</code>"]
      sa[sample] --> gt
    end
    subgraph S2 ["<span class='ms-stage'>2</span> filtered_emission_rates()"]
      direction LR
      fl[fluorophore spectra] --> em["photons/s per fluorophore<br/><code>(c, f, w)</code>"]
      ch["channels, tracks"] --> em
      ob1[objective NA, specimen RI] --> em
      dq[detector QE] --> em
    end
    subgraph S3 ["<span class='ms-stage'>3</span> optical_image()"]
      direction LR
      mo[modality + objective → PSF] --> oi["photons/s per voxel<br/><code>(c, z, y, x)</code>"]
    end
    subgraph S4 ["<span class='ms-stage'>4</span> digital_image()"]
      direction LR
      os[output_space] --> di["gray values<br/><code>(c, z, y, x)</code>"]
      de[detector + exposure] --> di
    end
    S1 --> S3
    S2 --> S3
    S3 --> S4

1 Ground truth. Each label in the sample is drawn into the truth space. The result counts fluorophores per voxel, with one layer per label (f). See Samples.

2 Emission rates. For every pair of fluorophore and channel, microsim computes how many photons per second one fluorophore emits and how many reach the detector, as a function of wavelength (w). This includes the light source, excitation and emission filters, the fluorophore's absorption and emission spectra, extinction coefficient and quantum yield, the fraction of light the objective collects, and the detector's quantum efficiency. It has no spatial dimensions. Because every fluorophore is computed in every channel, bleedthrough and cross-excitation follow automatically. Channels acquired at the same time (tracks) excite the sample together. See Channels and Photon budget.

3 Optical image. The ground truth of each fluorophore is multiplied by its emission rate and convolved with the microscope's point spread function (PSF), then the fluorophores are summed within each channel. The result is the noise-free light distribution at the detector, in photons per second, still on the truth-space grid. See Modalities.

4 Digital image. The optical image is binned to the output space, multiplied by the exposure time, and passed through the detector model, which adds shot noise, dark current, gain, read noise, and digitization. See Detectors.

The three spatial stages, for a two-color simulation of a HeLa cell

Ground truth (left), optical image (center), and digital image (right) for endoplasmic reticulum labeled with EGFP (green) and mitochondria labeled with mCherry (magenta). The ground truth is from OpenOrganelle; see the two-color tutorial.

There are two more methods for analysis. optical_image_per_fluor() returns the optical image before fluorophores are summed, as (c, f, z, y, x), which shows how much of each channel comes from each fluorophore. emission_flux() returns the emitted photons per voxel before blurring.

To simulate many samples with the same optics, run_batch() runs the simulation once per sample and yields each digital image. PSFs and emission rates are computed once and reused. Each sample gets its own random seed, derived from settings.random_seed, and if output_path is set, result i is written to <stem>_<i>.<suffix>.

Units

microsim uses one unit per kind of quantity:

Quantity Unit
lengths (space scale, extent) µm
wavelengths nm
irradiance (power) W/cm²
exposure and dwell time ms
extinction coefficient M⁻¹ cm⁻¹
fluorescence lifetime ns

Outputs are labeled arrays

Every stage returns an xarray.DataArray. Its dimensions have names, and z, y, x carry coordinates in µm, so you can select and reduce by name:

digital = sim.run()
digital.sel(c="FITC")  # select a channel by name
digital.isel(z=8).plot()  # plot a plane (needs matplotlib)
sim.ground_truth().max("z")  # maximum projection

Use digital.values (or numpy.asarray(digital)) for a plain NumPy array. With a GPU backend, digital.data is a GPU array; see Settings.

What is (and isn't) modeled

microsim aims to make each modeled step physically correct, and to state the approximations. The guide pages list the details for each part. In summary, these are not modeled at present:

  • photobleaching, blinking, and other time-dependent fluorophore behavior (emission is a steady-state rate)
  • triplet and other dark states, and stimulated emission
  • scattering, absorption, and aberrations from the sample (other than the refractive index mismatch handled by the PSF model)
  • out-of-focus background other than what the PSF produces, and autofluorescence
  • pixel-to-pixel variation of sCMOS cameras
  • the camera's physical pixel size and magnification (the output pixel size is set by output_space)