SpikeInterface — Unified Extracellular Electrophysiology Framework
Overview
SpikeInterface provides a common Python API to read extracellular recordings from 20+ file formats, preprocess raw voltage traces, run 10+ spike sorters, postprocess and quality-control sorted units, and export results — all without format-specific code. Its modular design lets users swap sorters, formats, and preprocessing steps without rewriting pipelines. SpikeInterface is built around lazy, chainable objects: a Recording holds raw data, a Sorting holds spike times, and a SortingAnalyzer ties them together for waveform and metric computation.
When to Use
- Loading recordings from multiple acquisition systems (SpikeGLX, OpenEphys, Intan, NWB, Maxwell MEA, Blackrock) with a unified API rather than format-specific parsers
- Running the same preprocessing and sorting pipeline across experiments recorded on different hardware
- Comparing two or more spike sorters on the same recording to assess agreement and choose the best output
- Running containerized sorters (Kilosort, IronClust, MountainSort5) via Docker or Singularity without local installation
- Computing standard quality metrics (SNR, ISI violations, firing rate, presence ratio, amplitude cutoff) and applying threshold-based curation
- Validating spike-sorting accuracy against synthetic or hybrid ground-truth recordings
- Exporting sorted results to NWB for data sharing or to Phy for manual curation
- Use
neuropixels-analysisinstead for a complete Neuropixels-specific Kilosort4 workflow including PSTH computation, tuning curves, and population decoding - For EEG, ECG, or other biosignal processing (not spike sorting), use
neurokit2instead
Prerequisites
- Python packages:
spikeinterface[full]>=0.101,probeinterface,numpy,matplotlib - Optional sorter deps:
kilosort(pip), or Docker/Singularity for containerized sorters - Data requirements: raw binary recording files plus probe geometry (
.prb,.json, or auto-detected from format) - Hardware: GPU required for Kilosort4; all other sorters run on CPU
pip install "spikeinterface[full]>=0.101" probeinterface
# Optional: Kilosort4 Python package
pip install kilosort
# Optional: Phy for manual curation
pip install phy
Quick Start
import spikeinterface.full as si
import spikeinterface.preprocessing as spre
import spikeinterface.sorters as ss
import spikeinterface.qualitymetrics as sqm
# Load, preprocess, sort, and inspect quality metrics in 10 lines
recording = si.read_openephys("/data/session_001", stream_name="Signals CH")
recording_pp = spre.bandpass_filter(
spre.common_reference(recording, reference="global", operator="median"),
freq_min=300, freq_max=6000,
)
sorting = ss.run_sorter("spykingcircus2", recording_pp, output_folder="./sc2_out")
analyzer = si.create_sorting_analyzer(sorting, recording_pp, folder="./analyzer")
analyzer.compute(["random_spikes", "waveforms", "templates", "noise_levels"])
metrics = sqm.compute_quality_metrics(analyzer, metric_names=["snr", "firing_rate", "isi_violation"])
print(metrics.describe())
Core API
Module 1: Recording I/O
SpikeInterface wraps every acquisition format behind a common BaseRecording interface. Once loaded, all objects expose the same methods regardless of origin format.
import spikeinterface.full as si
# SpikeGLX (.bin + .meta)
recording_sglx = si.read_spikeglx("/data/session_001", stream_name="imec0.ap")
# OpenEphys (binary or classic)
recording_oe = si.read_openephys("/data/oe_session", stream_name="Signals CH")
# NWB file
recording_nwb = si.read_nwb_recording("/data/recording.nwb",
electrical_series_name="ElectricalSeries")
# Intan RHD/RHS
recording_intan = si.read_intan("/data/session.rhd", stream_name="RHn")
# Inspect any recording with the same API
print(f"Format: {type(recording_sglx).__name__}")
print(f"Channels: {recording_sglx.get_num_channels()}")
print(f"Sampling rate:{recording_sglx.get_sampling_frequency()} Hz")
print(f"Duration: {recording_sglx.get_total_duration():.1f} s")
print(f"Probe: {recording_sglx.get_probe().name}")
# List available streams before loading (useful when a file has multiple streams)
streams = si.get_neo_streams("spikeglx", "/data/session_001")
print("Available streams:", streams)
# e.g. ['imec0.ap', 'imec0.lf', 'nidq']
# Select a time slice (lazy, no data loaded until get_traces() is called)
recording_slice = recording_sglx.frame_slice(
start_frame=0,
end_frame=int(60 * recording_sglx.get_sampling_frequency()), # first 60 s
)
print(f"Sliced duration: {recording_slice.get_total_duration():.1f} s")
Module 2: Preprocessing
Preprocessing functions return new Recording objects wrapping the original; the chain is applied lazily when data is read. This keeps memory usage low even for multi-hour recordings.
import spikeinterface.preprocessing as spre
# 1. Common median reference — removes shared noise across all channels
recording_cmr = spre.common_reference(recording_sglx,
reference="global",
operator="median")
# 2. Bandpass filter for action potentials (300–6000 Hz typical)
recording_filt = spre.bandpass_filter(recording_cmr,
freq_min=300,
freq_max=6000)
# 3. Remove bad channels automatically (coherence-based detection)
recording_clean, removed_ids = spre.remove_bad_channels(recording_filt,
method="coherence+psd")
print(f"Removed {len(removed_ids)} bad channels: {removed_ids}")
print(f"Clean channels: {recording_clean.get_num_channels()}")
# Whitening — decorrelates channels; recommended before template-matching sorters
recording_white = spre.whiten(recording_clean, mode="local")
# Phase shift correction for Neuropixels (samples acquired with small time offsets)
recording_shifted = spre.phase_shift(recording_clean)
# Inspect a short snippet of preprocessed data
traces = recording_white.get_traces(start_frame=0, end_frame=3000, segment_index=0)
print(f"Trace snippet shape: {traces.shape}") # (3000, n_channels)
print(f"Trace range: [{traces.min():.2f}, {traces.max():.2f}] µV")
Module 3: Spike Sorting
ss.run_sorter() wraps every supported sorter behind a uniform call signature. Sorter-specific parameters are passed as keyword arguments; all other pipeline steps are identical.
import spikeinterface.sorters as ss
from pathlib import Path
# List all sorters available in the current environment
available = ss.available_sorters()
print("Available sorters:", available)
# List sorters that can run without local installation (via container)
installed = ss.installed_sorters()
print("Installed locally:", installed)
# Run SpykingCircus2 (CPU, no external deps)
sorting_sc2 = ss.run_sorter(
"spykingcircus2",
recording_clean,
output_folder=Path("./sorter_output/sc2"),
remove_existing_folder=True,
verbose=True,
)
print(f"SpykingCircus2 units: {len(sorting_sc2.get_unit_ids())}")
# Run Kilosort4 via Docker container (no local GPU/MATLAB required)
sorting_ks4 = ss.run_sorter(
"kilosort4",
recording_clean,
output_folder=Path("./sorter_output/ks4"),
singularity_image=False, # use Docker; set True for Singularity
docker_image=True,
remove_existing_folder=True,
# Kilosort4-specific parameters
nblocks=5,
Th_learned=8,
do_correction=True,
)
print(f"Kilosort4 units: {len(sorting_ks4.get_unit_ids())}")
# Run MountainSort5 (CPU, fast, good for tetrode/low-channel-count probes)
sorting_ms5 = ss.run_sorter(
"mountainsort5",
recording_clean,
output_folder=Path("./sorter_output/ms5"),
scheme="2", # scheme 2 is recommended for high-density