Judging a sorting: against a reference, and unit by unit
A sorting is a list of units, each a spike train. Two questions about it:
- Does it agree with a reference? Here dspk’s Kilosort4 (
dspk sort kilosort4) against Kilosort4’s own results on the same recording: units are paired 1-to-1 by spike-time agreement (spikes within 0.4 ms),compare_sortings. - Is each unit one neuron? A neuron cannot fire twice within ~1.5 ms (refractory period), so inter-spike-interval violations reveal mixed units; firing over the whole recording (presence) and a full amplitude distribution (amplitude cutoff) reveal units that drift away or are cut by the threshold. The autocorrelogram of a clean unit has a hole at zero lag.
Needs the recording (00_fetch_kilosort_data.py) and the two sortings next to it:
<recording>.kilosort4.sorting.zarr (from dspk sort kilosort4 <recording> -o …) and
Kilosort4’s saved_results/ (its Phy folder).
from pathlib import Path
import numpy as np
from dspk.synapse import io, metrics
DATA = Path(__file__).resolve().parents[1] / "datasets" / "kilosort4"OURS = DATA / "ZFM-02370_mini.imec0.ap.short.kilosort4.sorting.zarr"REFERENCE = DATA / "saved_results"for path in (OURS, REFERENCE): if not path.exists(): raise SystemExit(f"{path} is missing (see this chapter's README)")
ours, reference = io.Sorting.load(OURS), io.Sorting.load(REFERENCE)print("dspk: ", ours)print("Kilosort4:", reference)
# Agreement: each reference unit's best match among ourscmp = metrics.compare_sortings(reference, ours)best = cmp["agreement"].max(axis=1)print(f"{len(cmp['matched'])} units paired; {len(cmp['well_detected'])} of {len(reference)} reference units found at ≥ {metrics.DEFAULT_AGREEMENT_THRESHOLD:.0%} agreement")
# Quality of each of our units (computed when the sorting was saved / loaded)units = ours.units()rate, isi, presence = np.asarray(units["firing_rate_hz"]), np.asarray(units["isi_violation_ratio"]), np.asarray(units["presence_ratio"])single = np.asarray(units["label"]) == "SingleUnit"print(f"{single.sum()} of {single.size} units labelled single units")
# A clean, busy unit, and its autocorrelogramids = np.asarray(units["unit_id"])clean = ids[single][np.argmax(np.asarray(units["spikes"])[single])]acg = metrics.autocorrelogram(ours.spike_train(int(clean)), ours.sample_rate, bin_ms=0.5, window_ms=50.0)
The whole script is playground/09_sorting/02_sorting_quality.py. Run it with uv run python playground/09_sorting/02_sorting_quality.py.