Spec and steps#
The Spec is the one object you build to describe a
recording: an acquisition, a seed, an ordered list of steps, and output options.
It validates the whole pipeline on construction.
Spec#
- pydantic model minisim.Spec[source]#
Bases:
_BaseA complete, reproducible recording specification.
acquisition+steps(the ordered pipe) +seed+outputfully determine a recording. The cross-field validators below catch genuinely invalid configs (raise) and flag unusual-but-legal ones (SpecWarning).- Fields:
acquisition (minisim.spec.Acquisition)output (minisim.spec.Output)seed (int)steps (list[minisim.spec.PlaceNeurons | minisim.spec.CellActivity | minisim.spec.CellOptics | minisim.spec.Composite | minisim.spec.Neuropil | minisim.spec.Vasculature | minisim.spec.Bleaching | minisim.spec.BrainMotion | minisim.spec.IlluminationProfile | minisim.spec.Vignette | minisim.spec.Leakage | minisim.spec.Sensor])
- field acquisition: Acquisition [Optional]#
Acquisition and physical models#
The acquisition owns all unit conversions between the physical world (µm, seconds) and the sampled world (pixels, frames), and composes the three physical models below.
- pydantic model minisim.Acquisition[source]#
Bases:
_BaseThe physical acquisition: optics, image sensor, tissue, and sampling.
Owns all unit conversions between the physical world (µm, seconds) and the sampled world (pixels, frames). Pixel size is the joint optics×sensor quantity
image_sensor.pixel_pitch_um / optics.magnification; FOV is then derived from the sensor’s pixel count - any two of {FOV, pixel size, pixel count} fix the third.- Fields:
duration_s (float)focal_depth_in_tissue_um (float | Literal['auto'])fps (float)front_working_distance_um (float | None)image_sensor (minisim.spec.ImageSensor)optics (minisim.spec.Optics)tissue (minisim.spec.Tissue)
- field image_sensor: ImageSensor [Optional]#
- field focal_depth_in_tissue_um: float | Literal['auto'] = 'auto'#
Depth of the focal plane below the tissue surface, µm (0 = surface), in the same coordinate as each cell’s depth z. Cells above or below it defocus; ‘auto’ resolves to the median realized cell depth at the optics step.
- field front_working_distance_um: float | None = None#
Front working distance (lens front → focal point), µm - Miniscope V4 ≈ 700. Informational only: it does NOT affect the simulation (the optics math uses focal_depth_in_tissue_um), but it’s a physically relevant number for surgery/implant planning, so it’s recorded here.
- pydantic model minisim.Optics[source]#
Bases:
_BaseObjective optics - the measurable lens properties of a 1-photon scope.
Layer-2 phenomenological quantities (diffraction sigma, defocus blur) are derived from these fields by the helper methods below. Pixel size is a joint optics×sensor quantity (sensor pitch / magnification) and so lives on
Acquisition, not here.Typical 1-photon miniscope ranges: NA 0.3–0.6, magnification ~5–10×, GCaMP emission ~510–540 nm.
- Fields:
depth_of_field_um (float | Literal['auto'])emission_nm (float)field_curvature_radius_um (float | None)magnification (float)na (float)
- field magnification: float = 8.0#
Optical magnification (sensor side / object side).
- Constraints:
gt = 0
- field emission_nm: float = 525.0#
Fluorophore emission wavelength, nm (GCaMP ≈ 525).
- Constraints:
gt = 0
- pydantic model minisim.ImageSensor[source]#
Bases:
_BasePhysical and noise properties of the bare image sensor (the detector).
Named image sensor, not camera, on purpose: a camera bundles optics on top of a sensor, whereas this is only the photosensitive array and its readout chain. The optics live separately on
Optics. Together with the exposure scale on thesensorstep, the fields here fully specify the photons→counts conversion.Typical CMOS miniscope sensors: ~2–6 µm pixel pitch, QE 0.6–0.9, read noise 1–5 e⁻ RMS, 8–12-bit ADC.
- Fields:
bit_depth (int)gain_adu_per_e (float)n_px_height (int)n_px_width (int)pixel_pitch_um (float)quantum_efficiency (float)read_noise_e (float)
- pydantic model minisim.Tissue[source]#
Bases:
_BaseLight-scattering properties of the imaged tissue, as a function of depth.
The fields parametrize Layer-2 helpers (
attenuation,scatter_sigma). Scattering has two separable consequences on a cell’s image, modelled by two knobs: it dims the sharp signal (light scattered out of the collection cone is lost →attenuation(), thescatter_mfp_*fields) and it blurs the footprint (forward-scattered light,g ≈ 0.88, is recollected as a growing halo →scatter_sigma_um(),scatter_blur_per_um).Round-trip scattering, asymmetric. The signal makes two passes through tissue, but they attenuate very differently:
Excitation in (≈470 nm) is delivered by widefield illumination, so it reaches a cell as a diffuse fluence, not a ballistic beam. Diffuse light penetrates far (transport length ≈ 800 µm) and its fluence actually peaks a few hundred µm deep before falling (Ma et al. 2020, Neurophotonics 7:031208), so over the depths a 1-photon scope images, excitation barely dims cells - a long effective MFP (
scatter_mfp_excitation_um).Emission out (≈525 nm) is the image-forming sharp signal, which decays at roughly the scattering MFP (
scatter_mfp_emission_um). This leg dominates the depth-dimming.
Modelling excitation as ballistic (a short, symmetric leg) double-counts the loss and makes deep cells unrealistically dim; the asymmetric split above is both more honest and what keeps the round trip from over-attenuating.
Literature anchors (mouse cortex / gray matter). The ballistic scattering mean free path at blue/green is ≈ 40–50 µm (μ_s ≈ 200 cm⁻¹, g ≈ 0.86–0.89): ≈ 47 µm at 473 nm (Al-Juboori et al. 2013, PLoS ONE 8:e67626) and ≈ 38 µm at 515 nm (Azimipour et al. 2014, Biomed. Opt. Express). That ballistic length is what sets the blur rate. The light an objective actually collects decays more slowly, because the strong forward scattering (g ≈ 0.88) is largely recollected - so the emission leg uses the high end of the scattering-MFP literature (~100 µm), and the diffuse excitation leg is longer still; their round trip gives an effective ≈ 85 µm (see
scatter_mfp_um).- Fields:
scatter_blur_per_um (float)scatter_mfp_emission_um (float)scatter_mfp_excitation_um (float)
- field scatter_mfp_excitation_um: float = 600.0#
Effective attenuation MFP for the excitation leg (≈470 nm, in) - long, diffuse fluence, µm.
- Constraints:
gt = 0
Output#
- pydantic model minisim.Output[source]#
Bases:
_BaseFinal-array formatting - formatting only, never rescaling (honest radiometry).
- Fields:
save_intermediates (bool)store_dtype (Literal['float32', 'float64'])
- field save_intermediates: bool = False#
Retain a snapshot after every step (test oracle + teaching visuals). Default False to keep memory flat for the programmatic and sweep paths; the two notebook presets opt in explicitly. When False, only observed + ground_truth are kept and stage() raises.
- field store_dtype: Literal['float32', 'float64'] = 'float32'#
Float container for the integer-valued sensor counts.
Steps#
Spec.steps is the forward chain. Each step below is a
StepSpec (the base class lives in minisim.spec);
AnyStep is the discriminated union of them and is the type you
annotate a step list with. Each kind may appear at most once in a spec.
- pydantic model minisim.spec.StepSpec[source]#
Bases:
_BaseBase class for a single pipeline step’s configuration.
A concrete step spec carries its physical parameters and a literal
kinddiscriminator, and declares itsdomain(a class attribute).build()turns the spec into the executable step that mutates aScene, resolvingkindthrough theminisim.steps.STEP_FOR_KINDtable.requiresdeclares the step kinds whose output this step consumes through the sharedScene(e.g.compositereads the footprintsplace_neuronsmakes and the tracescell_activitymakes). It is about presence-order, not completeness: a present requirement is placed before this step by the canonical ordering (_PIPELINE_ORDER), but it may be absent entirely. Partial pipelines are first-class - a spec of[place_neurons, cell_activity, composite]with no sensor is valid, so targeted test data for a downstream calcium pipeline can exercise just a few stages.- Fields:
The forward chain#
- pydantic model minisim.PlaceNeurons[source]#
Bases:
StepSpec,NeuronPopulationPlace generic neurons in a 3-D µm volume, soma-only or with dendrites.
‘Place’ is the verb - this positions neurons in space (anchored at the cell body); it is unrelated to hippocampal place cells. v1 models one generic excitable cell type (an irregular soma blob) with two GCaMP targeting variants via
morphology:"soma"(soma-targeted, body only) or"cytosolic"(standard GCaMP, the soma plus a few tapering proximal dendrites). There is no further cell-type distinction and no spatial/behavioral tuning. Footprints are 2-D masks carrying a scalar depthz; out-of-focus neurons that become background emerge for free downstream fromz+optics.The step is a single
NeuronPopulation: its inherited fields (morphology,soma_radius_um,depth_range_um, …) describe that one group. To place several distinct groups together - a thin soma-targeted layer over a deep cytosolic volume, say - setpopulationsto a list ofNeuronPopulationinstead; the step then samples each in turn (its own step-level population fields are ignored, and mixing the two raises).- Fields:
kind (Literal['place_neurons'])populations (list[NeuronPopulation] | None)
- field kind: Literal['place_neurons'] = 'place_neurons'#
- field populations: list[NeuronPopulation] | None = None#
Distinct neuron populations to place together (e.g. a thin layer + a deep volume). None (default) = the step is a single population described by its own fields; a list = sample each entry in turn and ignore the step-level population fields.
A PlaceNeurons step is itself a single NeuronPopulation
(its fields describe one group of cells). To place several distinct groups at
once - a thin soma-targeted layer over a deep cytosolic volume, say - set
populations to a list of NeuronPopulation instead. A population can be
density-sampled (density_per_mm3 over a depth_range_um) or placed at exact
positions_um centers (z, y, x); the two kinds can be mixed in one spec.
- pydantic model minisim.NeuronPopulation[source]#
Bases:
_BaseOne homogeneous group of neurons to place: a morphology + a 3-D distribution.
A population is the unit
place_neuronsactually samples - one cell shape (soma-only or cytosolic) at one soma size, scattered at one volumetric density across one depth range. A single population is the common case; list several onPlaceNeurons.populationsto build layered anatomy - e.g. a thin soma-targeted band over a deep cytosolic volume. The cell count is derived volumetrically fromdensity_per_mm3and the depth thickness (seesample_neurons()); brightness is biology and is drawn later incell_activity, never here.- Fields:
dendrite_length_um (float)dendrite_width_um (float)density_per_mm3 (float)depth_range_um (tuple[float, float])irregularity (float)min_distance_um (float)morphology (Literal['soma', 'cytosolic'])positions_um (list[tuple[float, float, float]] | None)soma_radius_um (float)
- field density_per_mm3: float = 25000.0#
Cell volumetric density (cells/mm³); count = density × FOV area × depth thickness, the thickness floored at one soma diameter so a thin or planar layer still yields cells.
- Constraints:
gt = 0
- field soma_radius_um: float = 7.0#
Soma radius, µm (typical cortical neuron ≈ 5–10).
- Constraints:
gt = 0
- field irregularity: float = 0.3#
0 = smooth disk; higher = lumpier soma (low-pass-noise threshold).
- Constraints:
ge = 0
le = 1
- field morphology: Literal['soma', 'cytosolic'] = 'soma'#
GCaMP targeting variant: ‘soma’ = soma-targeted (lumpy disk only); ‘cytosolic’ = standard GCaMP (soma + branched proximal dendrites).
- field dendrite_length_um: float = 24.0#
Nominal proximal-dendrite reach, µm (cytosolic only); kept proximal so the arbor shapes the blurred footprint without blowing up the sparse-patch bounding box. The per-cell count is drawn randomly (not a spec input, so cells differ), and each dendrite’s length jitters around this value and may branch.
- Constraints:
gt = 0
- field dendrite_width_um: float = 3.0#
Proximal-dendrite base width (diameter), µm; tapers to a ~1 px thread at the tip (cytosolic only).
- Constraints:
gt = 0
- field min_distance_um: float = 0.0#
3-D center-to-center minimum (Poisson-disk if > 0).
- Constraints:
ge = 0
- field positions_um: list[tuple[float, float, float]] | None = None#
Explicit soma centers as (z, y, x) µm tuples - depth first, matching Cell.center_um (not x,y,z). z is depth below the tissue surface (0 = surface); y, x are lateral in the optical-center frame: the optical axis is (0, 0), +y down and +x right (image convention), so a cell at (z, 0, 0) sits dead-center regardless of the motion margin, and these are the same coordinates GroundTruth.centers_um reports back. When given, these exact positions are placed instead of density-sampling, so the distribution fields (density_per_mm3, depth_range_um, min_distance_um) are ignored; the shape fields (soma_radius_um, irregularity, morphology, dendrites) still apply to each placed cell.
- pydantic model minisim.CellActivity[source]#
Bases:
StepSpecCalcium activity: 2-state Markov gate → Poisson spikes → double-exp kernel.
Modeled on the CaLab web simulator: spikes are generated on a high-resolution grid (
spike_sim_hz, ~300 Hz), convolved with the double-exponential kernelk(t) = exp(-t/τ_d) − exp(-t/τ_r)at that rate, then bin-averaged down to the camera frame rate (exposure integration). One spike per fine bin respects the ~3 ms refractory period. The ground-truthSis the per-frame spike count (the fine train is binned away - nothing recovers spikes faster than the frame rate). Indicator saturation and per-cell τ jitter are deferred to v1.1.Amplitude is biology and lives here as a single per-cell gain:
brightness_cvis the cell-to-cell spread of an overall expression/response gain that scales each cell’s whole trace (baseline and transients together). The emitted trace is the clean ground truthC; measurement noise is deliberately not added here. Photon shot noise and read noise enter at thesensor, background fluctuations atneuropil- so any SNR is an emergent property of the physical chain, computable downstream, never an input.- Fields:
active_rate_hz (float)brightness_cv (float)f0 (float)kind (Literal['cell_activity'])p_active_to_quiescent (float)p_quiescent_to_active (float)quiescent_rate_hz (float)spike_sim_hz (float)tau_decay_s (float)tau_rise_s (float)trace_noise (float)
- field kind: Literal['cell_activity'] = 'cell_activity'#
- field spike_sim_hz: float = 300.0#
High-res spike-simulation rate, Hz (~300 = a ~3 ms refractory); binned to the frame rate.
- Constraints:
gt = 0
- field p_quiescent_to_active: float = 0.005#
Per-frame quiescent→active transition prob.
- Constraints:
gt = 0
- field p_active_to_quiescent: float = 0.3#
Per-frame active→quiescent transition prob.
- Constraints:
gt = 0
- field active_rate_hz: float = 150.0#
Instantaneous firing rate while active, Hz (the in-burst rate).
- Constraints:
gt = 0
- field quiescent_rate_hz: float = 0.6#
Instantaneous firing rate while quiescent, Hz (the intrinsic background).
- Constraints:
ge = 0
- pydantic model minisim.CellOptics[source]#
Bases:
StepSpecPer-cell diffraction + defocus
|z − z_f|+ scatter(z) blur & attenuation.No tunable fields: blur and attenuation are fully determined by each cell’s
zplus the physicalOptics/Tissueconstants onAcquisition. Writes the observed (degraded) footprint alongside the planted (sharp) one, sets the geometricin_focusflag, and stores the per-celloptical_brightnesspeak scalar.detectableis not set here - it is a whole-pipeline flag (optics × illumination vs the sensor noise floor) assembled infinalize().- Fields:
kind (Literal['optics'])
- field kind: Literal['optics'] = 'optics'#
- pydantic model minisim.Composite[source]#
Bases:
StepSpecComposite
Σ_i degraded_footprint_i × trace_iinto the movie.The built step’s snapshot name is
"cells_only". The planted (sharp)A/Cremain the ideal, optics-free target in ground truth.- Fields:
kind (Literal['composite'])
- field kind: Literal['composite'] = 'composite'#
- pydantic model minisim.Neuropil[source]#
Bases:
StepSpecAdditive diffuse background from the dendritic/axonal felt around the cells.
A smooth spatial field (mesh-density variation on
spatial_sigma_um) modulated by a temporal envelope that is biologically driven: the haze is the aggregate calcium of the surrounding neural processes, so its time course is the local population activity, lagged and smoothed by the felt’s integration (population_tau_s, short). Each component’s envelope mixes that population driver with an independent slow drift (the unmodeled out-of-FOV/out-of-plane tissue,temporal_tau_s, slow) atpopulation_coupling. This is the modeled diffuse mesh only - out-of-focus somata are a separate background that emerges for free fromplace_neurons+optics.- Fields:
amplitude (float)kind (Literal['neuropil'])n_components (int)population_coupling (float)population_tau_s (float)spatial_sigma_um (float)temporal_tau_s (float)
- field kind: Literal['neuropil'] = 'neuropil'#
- field temporal_tau_s: float = 10.0#
OU correlation time of the independent slow-drift leg, s (slow).
- Constraints:
gt = 0
- pydantic model minisim.Vasculature[source]#
Bases:
StepSpecDark, spatially-static absorbing vessels - a multiplicative shadow on the movie.
Grows the
layersof branching vessel trees, composites their Beer-Lambert transmission masks multiplicatively, and multiplies the result into the brain-frame movie (movie *= M). Because it is a tissue-domain effect applied beforebrain_motion, the vessel pattern is fixed in the brain frame and rides the motion crop exactly with the cells - the high-contrast, temporally static landmark that motion-correction leans on (unlike the cells, whose brightness flickers with activity). It is also a tunable confound: a vessel crossing a soma corrupts its footprint and trace, so dialing vasculature up stress-tests footprint/dynamics extraction. That occlusion is scored, not hidden: each cell’s footprint-weighted vessel burden is recorded asGroundTruth.vessel_overlap_fraction, and a vessel over a soma dims its peak in thedetectabletest - but the footprints (A_observed) stay vessel-free, the single-cell optical truth the confound is measured against.v1 simplification: a single multiplicative attenuation of the already composited movie. Excitation and emission absorption are lumped into one static transmission, and light generated in front of the vessel is not exempted from the shadow (real out-of-plane / in-front fluorescence fills it in); the
opacityfloor stands in for that fill rather than modeling it. Good enough for a landmark and an occlusion confound; not a radiative-transfer model.Off by default:
enabled=Falseand an emptylayersboth make the step a no-op, so it must be explicitly turned on with at least oneVesselLayer. The static absorbing mask is recorded toGroundTruth.vasculature_mask(cropped to the FOV) so the confound is scoreable. Temporal pulsation (cardiac / vasomotion) is deliberately deferred - the vessels are static here; only the brightness, not the position, would ever breathe, so the landmark property is unaffected either way.- Fields:
enabled (bool)kind (Literal['vasculature'])layers (list[VesselLayer])
- field kind: Literal['vasculature'] = 'vasculature'#
- field layers: list[VesselLayer] [Optional]#
Vessel layers to grow, each at its own depth/caliber. Empty = no vessels.
- pydantic model minisim.VesselLayer[source]#
Bases:
_BaseOne depth’s worth of randomly-grown blood vessels - a dark absorbing tree.
A vessel absorbs both the excitation going in and the emission coming out (haemoglobin), so a layer is a shadow cast on the tissue behind it, not a light source. Each layer grows
n_rootsbranching trees (seegrow_vessel_tree()) entering from the field edges, tapering fromroot_radius_umtrunks down tomin_radius_umcapillaries by Murray’s law, then rasterizes them to a Beer-Lambert transmission mask and blurs it by the defocus + scatter at this layer’sdepth_um(reusing the optics model): a vessel near the focal plane is a crisp dark thread, one far from focus a broad soft shadow. List several layers onVasculatureto stack scales and depths (e.g. a shallow large-caliber cortical layer above the cells, plus a deeper fine-capillary bed).All lengths are µm;
depth_umshares the cell-z/ focal-plane coordinate (0 = surface), so a layer can sit above the cells (shallower) or, less commonly, below them.- Fields:
absorption_per_um (float)branch_angle_deg (float)branch_area_main (float)branch_prob (float)depth_um (float)min_radius_um (float)n_roots (int)opacity (float)root_radius_um (float)step_per_radius (float)tortuosity_deg (float)
- field depth_um: float = 30.0#
Depth of this vessel layer below the surface, µm (same coord as cell z / focal plane).
- Constraints:
ge = 0
- field n_roots: int = 4#
Number of vessel trees entering this layer from the field edges.
- Constraints:
ge = 1
- field min_radius_um: float = 2.0#
Capillary floor: a branch terminates once Murray-law tapering drops below this, µm.
- Constraints:
gt = 0
- field opacity: float = 0.85#
Peak absorption of a thick trunk in (0, 1]; transmission floors at 1 − opacity (real vessels are never fully black).
- Constraints:
gt = 0
le = 1
- field absorption_per_um: float = 0.04#
Beer-Lambert absorption per µm of blood path length: sets the capillary-vs-trunk contrast.
- Constraints:
gt = 0
- field branch_prob: float = 0.2#
Per-step bifurcation probability (a side branch peels off, the main vessel continues).
- Constraints:
ge = 0
lt = 1
- field tortuosity_deg: float = 8.0#
Std of the per-step heading perturbation, degrees (0 = ruler-straight vessels).
- Constraints:
ge = 0
- field branch_angle_deg: float = 30.0#
Base heading deviation at a bifurcation, degrees; the thinner child turns more.
- Constraints:
ge = 0
- pydantic model minisim.Bleaching[source]#
Bases:
StepSpecPer-cell, activity-driven photobleaching, opposed by protein turnover.
Photobleaching is a per-photon hazard, so each cell loses intact fluorophore in proportion to how much it emits (its calcium activity × excitation intensity), while turnover replenishes it toward full expression. The realized envelope is a cell-domain effect computed before
composite(seeBleachingStep), not a global movie multiply: busy or brightly-lit cells fade faster and to a lower floor, and with the light off the pool recovers, so the same model spans single recordings and repeated sessions. Defaults are calibrated to measured CA1 GCaMP6f bleaching curves across a wide range of excitation powers (bleaching linear in excitation; effective recovery ≈5.5 h, so darkness restores the pool within a couple of days).- Fields:
bleach_susceptibility (float)excitation_intensity (float)kind (Literal['bleaching'])turnover_tau_s (float)
- field kind: Literal['bleaching'] = 'bleaching'#
- field bleach_susceptibility: float = 6.3e-06#
Bleach rate per second at unit excitation and baseline emission (the per-photon hazard); 0 disables bleaching. Calibrated to CA1 GCaMP6f.
- Constraints:
ge = 0
- pydantic model minisim.BrainMotion[source]#
Bases:
StepSpecRigid x,y translation of the whole tissue frame - the tissue→sensor boundary.
The built step shifts the brain-frame canvas per frame and crops the sensor FOV from its center; it therefore requires a scene whose tissue canvas carries a margin ≥ the maximum shift (
Scene.zeros(acq, margin_px=…), sized automatically bysimulate()), so real off-FOV tissue moves into view instead of a fabricated fill. Ground truth records the per-frame(dy, dx)displacement in pixels.Three sources of the trajectory, selected by
model:"physical"(default): a 2-D damped harmonic oscillator. The brain is a damped mass elastically tethered to the (rigid) skull, driven on the dominantlocomotion_axisby an always-on locomotion rhythm atlocomotion_freq_hz(mice/rats run at ~6-8 Hz) and on both axes by broadband sloshing noise. The restoring force bounds the motion physically;motion_amplitude_umsets the typical excursion andmax_shift_umis the hard safety clamp (and the margin size). This is the realistic model the teaching notebook uses."walk": a bounded random walk (walk_step_umper frame, clamped to themax_shift_umdisk). Cheap and rhythm-free; kept for simple tests/fixtures.an explicit
trajectory_umoverrides both, regardless ofmodel.
Axial focus-drift motion is a deferred placeholder.
- Fields:
damping_ratio (float)kind (Literal['brain_motion'])locomotion_axis (Literal['y', 'x'])locomotion_fraction (float)locomotion_freq_hz (float)max_shift_um (float)model (Literal['physical', 'walk'])motion_amplitude_um (float)resonance_freq_hz (float)trajectory_um (list[tuple[float, float]] | None)walk_step_um (float)
- field kind: Literal['brain_motion'] = 'brain_motion'#
- field model: Literal['physical', 'walk'] = 'physical'#
Trajectory generator: ‘physical’ (driven damped oscillator) or ‘walk’ (bounded random walk). An explicit trajectory_um overrides both.
- field trajectory_um: list[tuple[float, float]] | None = None#
Explicit per-frame (dy, dx) in µm; overrides model.
- field max_shift_um: float = 15.0#
Hard safety clamp on cumulative shift magnitude, µm (also sizes the tissue margin).
- Constraints:
gt = 0
- field locomotion_freq_hz: float = 7.0#
Locomotion (stride) drive frequency, Hz; mice/rats run at ~6-8 Hz.
- Constraints:
gt = 0
- field motion_amplitude_um: float = 10.0#
Extreme excursion (99th-percentile displacement radius), µm; most frames move less.
- Constraints:
gt = 0
- field locomotion_axis: Literal['y', 'x'] = 'y'#
Dominant motion axis the locomotion rhythm drives (y = height; the cross axis gets noise only).
- field resonance_freq_hz: float = 6.0#
Natural frequency of the brain-on-skull oscillator, Hz.
- Constraints:
gt = 0
- field damping_ratio: float = 0.5#
Damping ratio ζ of the oscillator (<1 under-damped, sloshy; ≥1 over-damped).
- Constraints:
gt = 0
- pydantic model minisim.IlluminationProfile[source]#
Bases:
StepSpecStatic excitation-illumination falloff - the LED lights the FOV unevenly.
A single excitation LED illuminates the tissue brightest at the center and dimmer toward the edges, so peripheral cells fluoresce less to begin with. Modeled as a multiplicative radial falloff (
1at the bright center, dropping tofalloffat the farthest corner,exponentshaping the rolloff) - fixed to the scope, so it does not move with the brain. Typically a gentle, broad rolloff (vs the sharper emissionVignette). Being on the excitation side, this falloff also drives photobleaching faster at the bright center: that coupling is wired inBleaching(which evaluates this field at each cell’s rest position), the one way it differs from the collection-side vignette.- Fields:
center_offset_um (tuple[float, float])exponent (float)falloff (float)kind (Literal['illumination_profile'])
- field kind: Literal['illumination_profile'] = 'illumination_profile'#
- pydantic model minisim.Vignette[source]#
Bases:
StepSpecStatic radial vignette on the emission / return path (collection light loss).
The physical return path trims light rays toward the field edges (aperture and relay clipping, compounded by poorer off-axis optical performance), so corners read dimmer regardless of how brightly the tissue was lit. Same multiplicative radial-falloff shape as the
IlluminationProfilebut on the collection side - so it does not drive bleaching - and typically a sharper edge rolloff. Also fixed to the scope (does not move with the brain). Off-axis blur is a separate concern, deferred to a future optical-aberration step.- Fields:
center_offset_um (tuple[float, float])exponent (float)falloff (float)kind (Literal['vignette'])
- field kind: Literal['vignette'] = 'vignette'#
- pydantic model minisim.Leakage[source]#
Bases:
StepSpecStatic additive baseline (stray excitation light on the detector).
One additive contributor to the smooth, low-frequency background that minian’s ‘glow removal’ estimates and subtracts - not its sole target: that removal also strips the multiplicative illumination falloff and vignette (see
IlluminationProfile/Vignette), since all three are smooth and static while the cells are sharp and moving.- Fields:
kind (Literal['leakage'])level (float)profile (Literal['uniform', 'gaussian'])sigma_um (float | None)
- field kind: Literal['leakage'] = 'leakage'#
- field profile: Literal['uniform', 'gaussian'] = 'gaussian'#
Spatial baseline shape.
- pydantic model minisim.Sensor[source]#
Bases:
StepSpecPhotons → e⁻ → Poisson shot + read noise → ×gain → quantize → clip.
The only step that produces integer-valued counts. The sensor hardware (QE, read noise, gain, bit depth, pixel pitch) lives on
Acquisition.image_sensorand is read from there. The single field below is the exposure/flux scale - a scene property, not sensor hardware - which is why it stays on the step rather than the image-sensor spec.- Fields:
kind (Literal['sensor'])photons_per_unit (float)
- field kind: Literal['sensor'] = 'sensor'#
- field photons_per_unit: float = 100.0#
Photons per fluorescence intensity unit (exposure/flux scale); sets the shot-noise regime, and scales every light source in the frame (cells, neuropil, vasculature, leakage). A scene/illumination property, not sensor hardware (it lumps excitation power x integration time x collection efficiency), which is why it stays on the step rather than the image-sensor spec. Raise it (or the sensor gain) for a brighter recording; raising it also lifts the shot-noise-limited SNR.
- Constraints:
gt = 0
Warnings#
- class minisim.SpecWarning[source]#
Bases:
UserWarningAdvisory warning for unusual-but-legal spec configurations.
The simulator distinguishes invalid configs (which raise) from unusual ones (which warn but still run) - e.g. a focal plane outside the cell depth range, or motion larger than the configured FOV margin.