Note
Go to the end to download the full example code.
Quality Metrics Tutorial
After spike sorting, you might want to validate the ‘goodness’ of the sorted units. This can be done using the
qualitymetrics
submodule, which computes several quality metrics of the sorted units.
import spikeinterface.core as si
from spikeinterface.qualitymetrics import (
compute_snrs,
compute_firing_rates,
compute_isi_violations,
compute_quality_metrics,
)
First, let’s generate a simulated recording and sorting
recording, sorting = si.generate_ground_truth_recording()
print(recording)
print(sorting)
GroundTruthRecording (InjectTemplatesRecording): 4 channels - 25.0kHz - 1 segments
250,000 samples - 10.00s - float32 dtype - 3.81 MiB
GroundTruthSorting (NumpySorting): 10 units - 1 segments - 25.0kHz
Create SortingAnalyzer
For quality metrics we need first to create a SortingAnalyzer
.
analyzer = si.create_sorting_analyzer(sorting=sorting, recording=recording, format="memory")
print(analyzer)
estimate_sparsity (no parallelization): 0%| | 0/10 [00:00<?, ?it/s]
estimate_sparsity (no parallelization): 100%|██████████| 10/10 [00:00<00:00, 464.78it/s]
SortingAnalyzer: 4 channels - 10 units - 1 segments - memory - sparse - has recording
Loaded 0 extensions
Depending on which metrics we want to compute we will need first to compute some necessary extensions. (if not computed an error message will be raised)
analyzer.compute("random_spikes", method="uniform", max_spikes_per_unit=600, seed=2205)
analyzer.compute("waveforms", ms_before=1.3, ms_after=2.6, n_jobs=2)
analyzer.compute("templates", operators=["average", "median", "std"])
analyzer.compute("noise_levels")
print(analyzer)
compute_waveforms (workers: 2 processes): 0%| | 0/10 [00:00<?, ?it/s]
compute_waveforms (workers: 2 processes): 80%|████████ | 8/10 [00:00<00:00, 73.71it/s]
compute_waveforms (workers: 2 processes): 100%|██████████| 10/10 [00:00<00:00, 86.98it/s]
noise_level (no parallelization): 0%| | 0/20 [00:00<?, ?it/s]
noise_level (no parallelization): 100%|██████████| 20/20 [00:00<00:00, 279.05it/s]
SortingAnalyzer: 4 channels - 10 units - 1 segments - memory - sparse - has recording
Loaded 4 extensions: random_spikes, waveforms, templates, noise_levels
The spikeinterface.qualitymetrics
submodule has a set of functions that allow users to compute
metrics in a compact and easy way. To compute a single metric, one can simply run one of the
quality metric functions as shown below. Each function has a variety of adjustable parameters that can be tuned.
firing_rates = compute_firing_rates(analyzer)
print(firing_rates)
isi_violation_ratio, isi_violations_count = compute_isi_violations(analyzer)
print(isi_violation_ratio)
snrs = compute_snrs(analyzer)
print(snrs)
{np.str_('0'): 15.9, np.str_('1'): 17.4, np.str_('2'): 14.3, np.str_('3'): 15.4, np.str_('4'): 15.6, np.str_('5'): 16.3, np.str_('6'): 14.6, np.str_('7'): 15.2, np.str_('8'): 16.3, np.str_('9'): 15.7}
{np.str_('0'): np.float64(0.0), np.str_('1'): np.float64(0.0), np.str_('2'): np.float64(0.0), np.str_('3'): np.float64(0.0), np.str_('4'): np.float64(0.0), np.str_('5'): np.float64(0.0), np.str_('6'): np.float64(0.0), np.str_('7'): np.float64(0.0), np.str_('8'): np.float64(0.0), np.str_('9'): np.float64(0.0)}
{np.str_('0'): np.float64(0.8115581261096024), np.str_('1'): np.float64(12.148417738280905), np.str_('2'): np.float64(2.937305291301113), np.str_('3'): np.float64(15.222005999580237), np.str_('4'): np.float64(4.905677941163871), np.str_('5'): np.float64(22.62981036736856), np.str_('6'): np.float64(34.107214182773816), np.str_('7'): np.float64(2.000744910270261), np.str_('8'): np.float64(13.100944551144812), np.str_('9'): np.float64(26.655169806779853)}
To compute more than one metric at once, we can use the compute_quality_metrics
function and indicate
which metrics we want to compute. This will return a pandas dataframe:
metrics = compute_quality_metrics(analyzer, metric_names=["firing_rate", "snr", "amplitude_cutoff"])
print(metrics)
firing_rate snr amplitude_cutoff
0 15.9 0.811558 NaN
1 17.4 12.148418 NaN
2 14.3 2.937305 NaN
3 15.4 15.222006 NaN
4 15.6 4.905678 NaN
5 16.3 22.629810 NaN
6 14.6 34.107214 NaN
7 15.2 2.000745 NaN
8 16.3 13.100945 NaN
9 15.7 26.655170 NaN
Some metrics are based on the principal component scores, so the exwtension must be computed before. For instance:
analyzer.compute("principal_components", n_components=3, mode="by_channel_global", whiten=True)
metrics = compute_quality_metrics(
analyzer,
metric_names=[
"isolation_distance",
"d_prime",
],
)
print(metrics)
Fitting PCA: 0%| | 0/10 [00:00<?, ?it/s]
Fitting PCA: 100%|██████████| 10/10 [00:00<00:00, 177.31it/s]
Projecting waveforms: 0%| | 0/10 [00:00<?, ?it/s]
Projecting waveforms: 100%|██████████| 10/10 [00:00<00:00, 2388.83it/s]
calculate pc_metrics: 0%| | 0/10 [00:00<?, ?it/s]
calculate pc_metrics: 100%|██████████| 10/10 [00:00<00:00, 531.37it/s]
isolation_distance d_prime firing_rate amplitude_cutoff snr
0 5.732070 1.575294 15.9 NaN 0.811558
1 64.558223 3.444697 17.4 NaN 12.148418
2 13.797184 1.327231 14.3 NaN 2.937305
3 108.959216 5.329499 15.4 NaN 15.222006
4 34.651664 3.085616 15.6 NaN 4.905678
5 332.565139 8.813182 16.3 NaN 22.629810
6 718.273351 12.875703 14.6 NaN 34.107214
7 5.261477 1.371375 15.2 NaN 2.000745
8 168.832398 4.488255 16.3 NaN 13.100945
9 1225.517084 11.425180 15.7 NaN 26.655170
Total running time of the script: (0 minutes 0.371 seconds)