Note
Go to the end to download the full example code.
Curation Tutorial
After spike sorting and computing quality metrics, you can automatically curate the spike sorting output using the quality metrics that you have calculated.
Import the modules and/or functions necessary from spikeinterface
import spikeinterface.core as si
from spikeinterface.qualitymetrics import compute_quality_metrics
Let’s generate a simulated dataset, and imagine that the ground-truth sorting is in fact the output of a sorter.
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 this example, we will need a SortingAnalyzer
and some extensions
to be computed first
analyzer = si.create_sorting_analyzer(sorting=sorting, recording=recording, format="memory")
analyzer.compute(["random_spikes", "waveforms", "templates", "noise_levels"])
analyzer.compute("principal_components", n_components=3, mode="by_channel_local")
print(analyzer)
estimate_sparsity (no parallelization): 0%| | 0/10 [00:00<?, ?it/s]
estimate_sparsity (no parallelization): 100%|██████████| 10/10 [00:00<00:00, 417.36it/s]
compute_waveforms (no parallelization): 0%| | 0/10 [00:00<?, ?it/s]
compute_waveforms (no parallelization): 100%|██████████| 10/10 [00:00<00:00, 324.70it/s]
noise_level (no parallelization): 0%| | 0/20 [00:00<?, ?it/s]
noise_level (no parallelization): 100%|██████████| 20/20 [00:00<00:00, 263.04it/s]
Fitting PCA: 0%| | 0/10 [00:00<?, ?it/s]
Fitting PCA: 100%|██████████| 10/10 [00:00<00:00, 180.81it/s]
Projecting waveforms: 0%| | 0/10 [00:00<?, ?it/s]
Projecting waveforms: 100%|██████████| 10/10 [00:00<00:00, 2619.64it/s]
SortingAnalyzer: 4 channels - 10 units - 1 segments - memory - sparse - has recording
Loaded 5 extensions: random_spikes, waveforms, templates, noise_levels, principal_components
Then we compute some quality metrics:
metrics = compute_quality_metrics(analyzer, metric_names=["snr", "isi_violation", "nearest_neighbor"])
print(metrics)
calculate pc_metrics: 0%| | 0/10 [00:00<?, ?it/s]
calculate pc_metrics: 60%|██████ | 6/10 [00:00<00:00, 51.29it/s]
calculate pc_metrics: 100%|██████████| 10/10 [00:00<00:00, 51.43it/s]
snr isi_violations_ratio ... nn_hit_rate nn_miss_rate
0 2.081982 0.0 ... 0.495536 0.068199
1 1.897748 0.0 ... 0.496711 0.067587
2 0.603453 0.0 ... 0.513793 0.039226
3 27.721287 0.0 ... 0.975904 0.004222
4 2.954370 0.0 ... 0.493421 0.054506
5 14.908217 0.0 ... 0.916176 0.008468
6 9.532658 0.0 ... 0.924837 0.005636
7 3.572693 0.0 ... 0.743056 0.028360
8 1.941346 0.0 ... 0.365809 0.061243
9 30.585269 0.0 ... 0.963028 0.002165
[10 rows x 5 columns]
We can now threshold each quality metric and select units based on some rules.
The easiest and most intuitive way is to use boolean masking with a dataframe.
Then create a list of unit ids that we want to keep
keep_mask = (metrics["snr"] > 7.5) & (metrics["isi_violations_ratio"] < 0.2) & (metrics["nn_hit_rate"] > 0.90)
print(keep_mask)
keep_unit_ids = keep_mask[keep_mask].index.values
keep_unit_ids = [unit_id for unit_id in keep_unit_ids]
print(keep_unit_ids)
0 False
1 False
2 False
3 True
4 False
5 True
6 True
7 False
8 False
9 True
dtype: bool
['3', '5', '6', '9']
And now let’s create a sorting that contains only curated units and save it.
curated_sorting = sorting.select_units(keep_unit_ids)
print(curated_sorting)
curated_sorting.save(folder="curated_sorting")
GroundTruthSorting (UnitsSelectionSorting): 4 units - 1 segments - 25.0kHz
We can also save the analyzer with only theses units
clean_analyzer = analyzer.select_units(unit_ids=keep_unit_ids, format="zarr", folder="clean_analyzer")
print(clean_analyzer)
SortingAnalyzer: 4 channels - 4 units - 1 segments - zarr - sparse - has recording
Loaded 6 extensions: random_spikes, waveforms, templates, noise_levels, principal_components, quality_metrics
Total running time of the script: (0 minutes 0.527 seconds)