Testing helpers#

Convenience helpers for using minisim as test fixtures for an analysis pipeline. Unlike the rest of the reference, these live in the minisim.testing submodule (from minisim.testing import make_recording, score). See the how-to guide Use minisim in your test suite for the recommended pytest wiring.

make_recording#

minisim.testing.make_recording(*, n_cells=6, n_px=128, pixel_size_um=1.0, duration_s=2.0, fps=20.0, seed=0, depth_um=50.0, focal_depth_um='auto', soma_radius_um=5.0, morphology='soma', population=None, motion=False, activity=None, sensor=None, extra_steps=(), save_intermediates=False)[source]#

A small, fast, deterministic recording with ground truth, for CI tests.

Places n_cells well-separated somata on a jittered grid at a single depth_um and runs the minimal forward chain (place_neurons cell_activity optics composite sensor) on a square n_px × n_px sensor at pixel_size_um per pixel. Cell count, positions, and every pixel are fully determined by the arguments, so the same seed (and the same arguments) always yields the same recording - the property a fixture needs.

Defaults are tuned for CI: a 128 px FOV at 1 µm/px (128 µm), six cells at 50 µm depth, two seconds at 20 fps, with a lively default activity and a fixed, well-chosen exposure (the focal plane is "auto"), so every placed cell reliably fires, is in focus, and is brightly but non-saturatingly exposed - hence detectable - with no manual tuning. Shrink n_px / duration_s for an even faster fixture, or raise n_cells for a denser one.

Parameters:
  • n_cells (int) – Number of somata to place (exactly this many; grid-arranged and separated).

  • n_px (int) – Square sensor side, pixels (both height and width).

  • pixel_size_um (float) – Object-space size of one pixel, µm. Realized via a sensor pitch of 8.0 µm and a matching magnification, so the FOV is n_px · pixel_size_um.

  • duration_s (float) – Sampling and the master RNG seed.

  • fps (float) – Sampling and the master RNG seed.

  • seed (int) – Sampling and the master RNG seed.

  • depth_um (float) – Common depth of every soma below the tissue surface, µm. With the default focal_depth_um="auto" the focal plane resolves here and the cells are in focus. Ignored when population is given (the population carries its own depth distribution).

  • focal_depth_um (float | Literal['auto']) – Focal plane depth in tissue, µm, or "auto" (the default) to let the simulator pick the most-recoverable plane. Pin it to a fixed depth (e.g. 0.0, the surface) to hold the plane still while sweeping cell depth - the setup a recall-vs-depth study needs, since auto-focus refocuses on each band and would otherwise keep every cell in focus and detectable.

  • soma_radius_um (float) – Cell shape: "soma" (body only, fast) or "cytosolic" (soma plus proximal dendrites). Ignored when population is given.

  • morphology (Literal['soma', 'cytosolic']) – Cell shape: "soma" (body only, fast) or "cytosolic" (soma plus proximal dendrites). Ignored when population is given.

  • population (NeuronPopulation | None) – A NeuronPopulation to place verbatim, bypassing the well-separated grid. Use minisim’s native volumetric placement - a density_per_mm3 over a depth_range_um, optionally Poisson-disk-spaced by min_distance_um - to get a realistic, variable layout where footprints overlap and a depth spread leaves some cells off the focal plane (so not every planted cell is detectable). None (the default) keeps the exact-count grid; when given, n_cells, depth_um, soma_radius_um, and morphology are ignored (the population sets them).

  • motion (bool) – When True, append a default BrainMotion step, so ground_truth.shifts is populated (lets you exercise motion correction).

  • activity (CellActivity | None) – Override the CellActivity model or the Sensor exposure step; None uses defaults (the default sensor uses a fixed, well-exposed photons_per_unit). Pass an explicit Sensor(photons_per_unit=...) for a dimmer or brighter recording.

  • sensor (Sensor | None) – Override the CellActivity model or the Sensor exposure step; None uses defaults (the default sensor uses a fixed, well-exposed photons_per_unit). Pass an explicit Sensor(photons_per_unit=...) for a dimmer or brighter recording.

  • extra_steps (Sequence[Annotated[PlaceNeurons | CellActivity | CellOptics | Composite | Neuropil | Vasculature | Bleaching | BrainMotion | IlluminationProfile | Vignette | Leakage | Sensor, Discriminator(discriminator=kind, custom_error_type=None, custom_error_message=None, custom_error_context=None)]]) – Additional steps to append (Neuropil, Vignette, Leakage, …); the spec re-sorts into canonical order, so order here is free.

  • save_intermediates (bool) – Persist per-stage snapshots (see Output).

Returns:

rec.observed is the movie, rec.ground_truth the exact truth.

Return type:

Recording

score#

minisim.testing.score(estimate, ground_truth, *, match_metric='iou', match_threshold=0.5, restrict_to_detectable=True, footprint_shift='auto')[source]#

Score a pipeline’s Estimate against the ground truth, in one call.

Runs the standard recovery pipeline: match estimated footprints to true ones by spatial overlap (hungarian_match() against A_observed, the recoverable target), then score the temporal recovery of the matched pairs. The conventions the a-la-carte recipe asks you to remember are applied here:

  • matches against A_observed (not the optics-free A_planted);

  • scores recall over the detectable cells by default (the fair denominator; see detectable_subset()). The returned Report always carries n_requested (cells planted) and n_detectable alongside n_true (the denominator used), so a high recall over a shrunken denominator can never be mistaken for “recovered everything”;

  • absorbs a global footprint offset before matching, so a uniform shift from the pipeline’s motion-correction frame is not scored as a miss (see footprint_shift);

  • reduces per-pair trace correlations with a nan-safe median;

  • scores the deconvolved activity without binarizing and up to an unknown scale (activity_similarity()), reducing each quantity with a nan-safe median;

  • treats an estimated motion trajectory as a correction (negated) and aligns away a constant origin offset when comparing to GroundTruth.shifts.

Parameters:
  • estimate (Estimate) – The pipeline output. Only Estimate.A is required.

  • ground_truth (GroundTruth) – The recording’s ground_truth.

  • match_metric (str) – Footprint similarity for matching (see SIMILARITY_METRICS): "iou" (binary, default), "cosine", or "weighted_jaccard" (the weighted metrics let pixel weights, not just lit pixels, drive the match).

  • match_threshold (float) – Minimum similarity for a matched pair to count as a true positive.

  • restrict_to_detectable (bool) – Score against detectable_subset() (default). Set False to score against every planted cell, detectable or not.

  • footprint_shift (tuple[float, float] | str | None) – How to handle a global translational offset between the estimated (motion-corrected) footprints and the true ones. "auto" (default) reads the offset off the motion trajectories when both estimate.shifts and ground_truth.shifts are present (exact), and otherwise estimates it by aligning the footprint centroids. Pass a (dy, dx) to force a known shift, or None to disable. The applied integer shift is recorded on the Report.

Returns:

The scorecard. Unsupplied estimate fields score nan / None.

Return type:

Report

Estimate#

class minisim.testing.Estimate(A=<object object>, C=None, S=None, shifts=None, *, footprints=<object object>, traces=None, activity=None)[source]#

Bases: object

What a pipeline recovered, as the input to score().

The footprints are the only required field. Traces / activity / shifts are optional; when omitted, the matching score in the Report is nan (or None for motion). Arrays may be numpy or xarray (minian’s CNMF returns xr.DataArray); both are accepted.

Each field has two interchangeable spellings - the terse CNMF/minian symbol a pipeline already emits, and a spelled-out alias for anyone who does not speak that dialect. Both work as keyword arguments, and both are readable attributes:

Estimate(A=A, C=C, S=S)                            # CNMF names
Estimate(footprints=A, traces=C, activity=S)       # spelled out
  • A / footprints - spatial footprints, (n_units, height, width).

  • C / traces - calcium traces, (n_units, frame).

  • S / activity - the deconvolved estimate, (n_units, frame). Not a spike train: it is a non-negative estimate of neural activity rate, scaled by an unknown factor mapping activity to calcium-kernel amplitude. score() compares it without binarizing and up to that unknown scale (see activity_similarity()).

  • shifts - estimated per-frame (dy, dx) motion, (frame, 2). A motion correction trajectory (the negation of the applied shift); score() negates it to compare against GroundTruth.shifts and to recover the global footprint offset.

A frozen dataclass (not a NamedTuple) on purpose: keyword construction with no positional contract, so a future field can be added without breaking callers that pin the current ones - the property a long-lived public scoring contract needs.

Parameters:
property footprints: object#

Spelled-out alias for A (spatial footprints).

property traces: object | None#

Spelled-out alias for C (calcium traces).

property activity: object | None#

Spelled-out alias for S (the deconvolved activity estimate).

Report#

class minisim.testing.Report(n_true, n_est, n_matched, recall, precision, f1, mean_overlap, trace_corr, activity_corr, activity_variance_explained, activity_scale, shift_rmse, n_requested, n_detectable, match_metric='iou', footprint_shift=(0, 0))[source]#

Bases: object

The recovery scorecard score() returns.

Cell counts and spatial scores are always present; temporal scores are nan when the matching estimate field was not supplied, and shift_rmse is None when motion truth or a motion estimate is absent.

The recall denominator is reported explicitly, because it is not obvious: by default score() scores against the detectable cells, so recall is over n_true (= n_detectable), which can be fewer than the cells that were planted. The three counts make that legible at a glance - recall = 1.0 with n_detectable=4 < n_requested=6 means “recovered every detectable cell, but two planted cells were too dim to detect”, not “recovered everything”. See score()’s restrict_to_detectable.

Parameters:
summary()[source]#

A compact one-line-per-metric string, handy for a test failure message.

Return type:

str