Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
98de1f9
template metrics from bombcell - use scipy findpeaks() to detect peak…
Julie-Fabre Jan 7, 2026
71d35a4
template denoising - SVD option and bombcell baseline flatness metric
Julie-Fabre Jan 7, 2026
788e8be
woops remove kilosort4_output folder
Julie-Fabre Jan 7, 2026
4b79f55
woops remove kilosort4_output folder
Julie-Fabre Jan 7, 2026
b391456
remove SVD option - was not performing well - and add sane tested def…
Julie-Fabre Jan 7, 2026
c9306df
bombcell unit type classification logic and output plots - waveform o…
Julie-Fabre Jan 7, 2026
44d8192
bombcell unit type classification logic and output plots - waveform o…
Julie-Fabre Jan 7, 2026
a29d3e1
bombcell snr
Julie-Fabre Jan 7, 2026
4514a51
fix: use peak_valley code to get duration, rename to peak_to_trough_d…
Julie-Fabre Jan 7, 2026
5b4cafb
upset plots
Julie-Fabre Jan 8, 2026
515ed36
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
8467177
cleanup
Julie-Fabre Jan 8, 2026
41fde99
Merge branch 'bombcell' of https://github.com/Julie-Fabre/spikeinterf…
Julie-Fabre Jan 8, 2026
2e8d6ea
cleanup
Julie-Fabre Jan 8, 2026
ed770bb
cleanup
Julie-Fabre Jan 8, 2026
c81898f
cleanup old template metric functions and ensure backward compaiblity…
Julie-Fabre Jan 8, 2026
71063dc
move bombcell functions to curation and rename bombcell ones to bombc…
Julie-Fabre Jan 8, 2026
5a2416e
remove upset plot warnings for now
Julie-Fabre Jan 8, 2026
afe4e1b
bombcell plot wrapper
Julie-Fabre Jan 8, 2026
6fb5b13
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
52a58b2
users can input bombcell parameters as JSON
Julie-Fabre Jan 8, 2026
103ba7a
Merge branch 'bombcell' of https://github.com/Julie-Fabre/spikeinterf…
Julie-Fabre Jan 8, 2026
aa35ac8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
eb130e9
optionally save plots and metrics, explicit inputs to functions to ha…
Julie-Fabre Jan 8, 2026
3b609b5
Merge branch 'bombcell' of https://github.com/Julie-Fabre/spikeinterf…
Julie-Fabre Jan 8, 2026
01480b3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 8, 2026
af36259
example jupyter notebook
Julie-Fabre Jan 8, 2026
7045c43
example jupyter notebook
Julie-Fabre Jan 8, 2026
91a333e
Merge branch 'bombcell' of https://github.com/Julie-Fabre/spikeinterf…
Julie-Fabre Jan 8, 2026
adac68e
labelling -> labeling and unit_labeling.py -> bombcell_curation.py
Julie-Fabre Jan 18, 2026
291bca4
WAVEFORM_METRICS -> NOISE_METRICS
Julie-Fabre Jan 18, 2026
6b21830
use sorting analyzer rather than inputing template_metrics and qualty…
Julie-Fabre Jan 18, 2026
8673ebc
remove unused apply_thresholds() function
Julie-Fabre Jan 18, 2026
e55cde7
get_labeling_summary -> get_bombcell_labeling_summary
Julie-Fabre Jan 18, 2026
b04ff26
Removed save_thresholds and load_thresholds. can now use standard jso…
Julie-Fabre Jan 18, 2026
5383728
get_labeling_results -> get_bombcell_results
Julie-Fabre Jan 18, 2026
7491a6a
woops, that was tracked and committed too early - reverting
Julie-Fabre Jan 18, 2026
3b1d819
removed debugging plots
Julie-Fabre Jan 18, 2026
02c9a53
imports to individual functions
Julie-Fabre Jan 18, 2026
96d8bb6
remove more debugging plots
Julie-Fabre Jan 18, 2026
0e7fb40
rename waveform_duration
Julie-Fabre Jan 18, 2026
a5ab4a6
more combine -> sorting_analyzer
Julie-Fabre Jan 18, 2026
a345e42
converted all durations (and defualt thresholds) to seconds
Julie-Fabre Jan 18, 2026
1b18775
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 18, 2026
e596493
Use external metrics and remove json
alejoe91 Jan 21, 2026
90b5a51
clean up imports
alejoe91 Jan 21, 2026
7d4747d
Add polarity inversion for old peak to trough
alejoe91 Jan 21, 2026
81dc7d6
ported general label widhet to unit labels and backward comp for unit…
alejoe91 Jan 21, 2026
702f5c7
labels to lower case
alejoe91 Jan 22, 2026
2983204
Use peak_sign='both' as default for SNR
alejoe91 Jan 22, 2026
42aa4ff
auto_label_units -> model_base_label_units + unitrefine function
alejoe91 Jan 22, 2026
bbd0201
Use lightweight model by default
alejoe91 Jan 22, 2026
03d7077
examples!
alejoe91 Jan 22, 2026
bbab79a
Merge conflicts and use SNR in bombcell by default
alejoe91 Jan 22, 2026
2d8c126
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 22, 2026
7e8e644
Lazy import of pandas and basic tests
alejoe91 Jan 22, 2026
241a6cb
Merge branch 'bombcell' of github.com:Julie-Fabre/spikeinterface into…
alejoe91 Jan 22, 2026
6bfdb8b
Update src/spikeinterface/curation/unitrefine_curation.py
alejoe91 Jan 22, 2026
fc0ed03
Merge branch 'main' into unitrefine-actor
alejoe91 Jan 22, 2026
48cd1c5
Deprecate auto_label_units and set default models to None
alejoe91 Jan 23, 2026
6af9617
fix docs and tests
alejoe91 Jan 23, 2026
0f5cab5
Preserve order when retrieving metric columns/descriptions
alejoe91 Jan 26, 2026
d5df76f
Fix curation tests
alejoe91 Jan 26, 2026
0083d56
Merge branch 'main' into bombcell
alejoe91 Jan 29, 2026
bbb4a3b
Suggestions from Chris
alejoe91 Jan 29, 2026
6c2932f
last comment addressed
alejoe91 Jan 29, 2026
67fef72
Update src/spikeinterface/curation/unitrefine_curation.py
alejoe91 Jan 29, 2026
1004748
Merge branch 'main' of github.com:SpikeInterface/spikeinterface into …
alejoe91 Feb 2, 2026
d91befd
Extend unitrefine tests
alejoe91 Feb 2, 2026
7785454
Update examples/tutorials/curation/plot_1_automated_curation.py
alejoe91 Feb 2, 2026
67d6443
bombcell curation accept JSON files and extend tests
alejoe91 Feb 2, 2026
d89a1e3
Add warnings if wrong labels and extend to local models
alejoe91 Feb 2, 2026
d5d0482
Merge branch 'unitrefine-actor' of github.com:alejoe91/spikeinterface…
alejoe91 Feb 2, 2026
f9600e7
Update src/spikeinterface/curation/__init__.py
alejoe91 Feb 2, 2026
7de1b4e
Merge branch 'main' of github.com:SpikeInterface/spikeinterface into …
alejoe91 Feb 2, 2026
87bffa6
wip merging
alejoe91 Feb 2, 2026
3b3edaa
Solve conflicts
alejoe91 Feb 2, 2026
a2550d2
Fix how_to/README
alejoe91 Feb 2, 2026
c797a49
Add qualitymetrics_based curation
alejoe91 Feb 3, 2026
b50d1a5
Add qualitymetrics curation
alejoe91 Feb 3, 2026
5116b08
Merge branch 'main' into qualitymetrics_curation
alejoe91 Feb 3, 2026
d395c43
Add to __init__
alejoe91 Feb 3, 2026
9c4f745
Solve conflicts
alejoe91 Feb 3, 2026
57616d0
rename function
alejoe91 Feb 3, 2026
42d9945
Merge branch 'qualitymetrics_curation' into bombcell
alejoe91 Feb 3, 2026
4c9d32d
bombcell returns dataframe and docs
alejoe91 Feb 3, 2026
1d9dfb8
Use rst note
alejoe91 Feb 3, 2026
8c553c1
Clean up BaseMetricExtension
alejoe91 Feb 3, 2026
9d115c4
Use smooth_window_ms, rename variable unambiguously, add trough_half_…
alejoe91 Feb 3, 2026
e7bf88a
Update and clarify template_metrics documentation and docstring
alejoe91 Feb 3, 2026
78f732f
Rename threshold_metrics_label_units
alejoe91 Feb 3, 2026
a37186f
Add new template metrics figure
alejoe91 Feb 3, 2026
467420b
Generalize over any metric
alejoe91 Feb 3, 2026
b8cb1fc
add file...
alejoe91 Feb 3, 2026
3b37364
Fix tests
alejoe91 Feb 4, 2026
55c1f9e
Fix curation tests
alejoe91 Feb 4, 2026
5ebf852
Solve conflicts
alejoe91 Feb 4, 2026
6a59a0a
Get rid of unit types in bombcell
alejoe91 Feb 4, 2026
b7aebc7
Merge branch 'main' of github.com:SpikeInterface/spikeinterface into …
alejoe91 Feb 4, 2026
2958d76
Allow passing external labels and accept analyzer or dataframe
alejoe91 Feb 4, 2026
d810b5e
Extend threshold_metrics curation and refactor bombcell
alejoe91 Feb 4, 2026
2a3d17c
Refactor and simplify template_metrics and peaks_info
alejoe91 Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ examples/modules_gallery/**/*.zarr

# Files and folders generated during tests
test_folder/
*trained_pipeline/

# Mac OS
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ spikeinterface.curation
.. autofunction:: remove_redundant_units
.. autofunction:: remove_duplicated_spikes
.. autofunction:: remove_excess_spikes
.. autofunction:: threshold_metrics_label_units
.. autofunction:: model_based_label_units
.. autofunction:: load_model
.. autofunction:: train_model
Expand Down
274 changes: 274 additions & 0 deletions doc/how_to/auto_label_units.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
Automatic labeling units after spike sorting
============================================

This example shows how to automatically label units after spike sorting,
using three different approaches:

1. Simple filter based on quality metrics
2. Bombcell: heuristic approach to label units based on quality and
template metrics [Fabre]_
3. UnitRefine: pre-trained classifiers to label units as noise or
SUA/MUA [Jain]_

.. code:: ipython3

import numpy as np

import spikeinterface as si
import spikeinterface.curation as sc
import spikeinterface.widgets as sw

from pprint import pprint

.. code:: ipython3

%matplotlib inline

.. code:: ipython3

analyzer_path = "/ssd980/working/analyzer_np2_single_shank.zarr"

.. code:: ipython3

sorting_analyzer = si.load(analyzer_path)


.. code:: ipython3

sorting_analyzer


.. parsed-literal::

SortingAnalyzer: 96 channels - 142 units - 1 segments - zarr - sparse - has recording
Loaded 14 extensions: amplitude_scalings, correlograms, isi_histograms, noise_levels, principal_components, quality_metrics, random_spikes, spike_amplitudes, spike_locations, template_metrics, template_similarity, templates, unit_locations, waveforms



The ``SortingAnalyzer`` includes several metrics that we can use for
curation:

.. code:: ipython3

sorting_analyzer.get_metrics_extension_data().columns




.. parsed-literal::

Index(['amplitude_cutoff', 'amplitude_cv_median', 'amplitude_cv_range',
'amplitude_median', 'd_prime', 'drift_mad', 'drift_ptp', 'drift_std',
'firing_range', 'firing_rate', 'isi_violations_count',
'isi_violations_ratio', 'isolation_distance', 'l_ratio', 'nn_hit_rate',
'nn_miss_rate', 'noise_cutoff', 'noise_ratio', 'num_spikes',
'presence_ratio', 'rp_contamination', 'rp_violations', 'sd_ratio',
'silhouette', 'sliding_rp_violation', 'snr', 'sync_spike_2',
'sync_spike_4', 'sync_spike_8', 'exp_decay', 'half_width',
'main_peak_to_trough_ratio', 'main_to_next_extremum_duration',
'num_negative_peaks', 'num_positive_peaks',
'peak_after_to_trough_ratio', 'peak_after_width',
'peak_before_to_peak_after_ratio', 'peak_before_to_trough_ratio',
'peak_before_width', 'peak_to_trough_duration', 'recovery_slope',
'repolarization_slope', 'spread', 'trough_width', 'velocity_above',
'velocity_below', 'waveform_baseline_flatness'],
dtype='object')



1. Quality-metrics based curation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A simple solution is to use a filter based on quality metrics. To do so,
we can use the ``spikeinterface.curation.threshold_metrics_label_units``
function and provide a set of thresholds.

.. code:: ipython3

qm_thresholds = {
"snr": {"min": 5},
"firing_rate": {"min": 0.1, "max": 200},
"rp_contamination": {"max": 0.5}
}

.. code:: ipython3

qm_labels = sc.threshold_metrics_label_units(sorting_analyzer, thresholds=qm_thresholds)

.. code:: ipython3

qm_labels["label"].value_counts()




.. parsed-literal::

label
noise 115
good 27
Name: count, dtype: int64



.. code:: ipython3

w = sw.plot_unit_labels(sorting_analyzer, qm_labels["label"], ylims=(-300, 100))
w.figure.suptitle("Quality-metrics labeling")



.. image:: auto_label_units_files/auto_label_units_12_1.png


Only 27 units are labeled as *good*, and we can see from the plots that
some “noisy” waveforms are not properly flagged and some visually good
waveforms are labeled as noise. Let’s take a look at more powerful
methods.

1. Bombcell
-----------

**Bombcell** ([Fabre]_) is another threshold-based method that also uses
quality metrics and template metrics, but in a much more refined way! It
can label units as ``noise``, ``mua``, and ``good`` and further detect
``non-soma`` units. It comes with some default thresholds, but
user-defined thresholds can be provided from a dictionary or a JSON
file.

.. code:: ipython3

bombcell_default_thresholds = sc.bombcell_get_default_thresholds()
pprint(bombcell_default_thresholds)


.. parsed-literal::

{'amplitude_cutoff': {'max': 0.2, 'min': None},
'amplitude_median': {'max': None, 'min': 40},
'drift_ptp': {'max': 100, 'min': None},
'exp_decay': {'max': 0.1, 'min': 0.01},
'main_peak_to_trough_ratio': {'max': 0.8, 'min': None},
'num_negative_peaks': {'max': 1, 'min': None},
'num_positive_peaks': {'max': 2, 'min': None},
'num_spikes': {'max': None, 'min': 300},
'peak_after_to_trough_ratio': {'max': 0.8, 'min': None},
'peak_before_to_peak_after_ratio': {'max': 3, 'min': None},
'peak_before_to_trough_ratio': {'max': 3, 'min': None},
'peak_before_width': {'max': None, 'min': 0.00015},
'peak_to_trough_duration': {'max': 0.00115, 'min': 0.0001},
'presence_ratio': {'max': None, 'min': 0.7},
'rp_contamination': {'max': 0.1, 'min': None},
'snr_baseline': {'max': None, 'min': 5},
'trough_width': {'max': None, 'min': 0.0002},
'waveform_baseline_flatness': {'max': 0.5, 'min': None}}


.. code:: ipython3

bombcell_labels = sc.bombcell_label_units(sorting_analyzer, thresholds=bombcell_default_thresholds)

.. code:: ipython3

bombcell_labels["label"].value_counts()




.. parsed-literal::

label
good 58
noise 50
mua 33
non_soma 1
Name: count, dtype: int64



.. code:: ipython3

w = sw.plot_unit_labels(sorting_analyzer, bombcell_labels["label"], ylims=(-300, 100))
w.figure.suptitle("Bombcell labeling")



.. image:: auto_label_units_files/auto_label_units_18_1.png


UnitRefine
----------

**UnitRefine** ([Jain]_) also uses quality and template metrics, but in
a different way. It uses pre-trained classifiers to trained on
hand-curated data. By default, the classification is performed in two
steps: first a *noise*/*neural* classifier is applied, followed by a
*sua*/*mua* classifier. Several models are available on the
`SpikeInterface HuggingFace
page <https://huggingface.co/SpikeInterface>`__.

.. code:: ipython3

unitrefine_labels = sc.unitrefine_label_units(
sorting_analyzer,
noise_neural_classifier="SpikeInterface/UnitRefine_noise_neural_classifier",
sua_mua_classifier="SpikeInterface/UnitRefine_sua_mua_classifier",
)

.. code:: ipython3

unitrefine_labels["label"].value_counts()




.. parsed-literal::

label
sua 62
noise 47
mua 33
Name: count, dtype: int64



.. code:: ipython3

w = sw.plot_unit_labels(sorting_analyzer, unitrefine_labels["label"], ylims=(-300, 100))
w.figure.suptitle("UnitRefine labeling")



.. image:: auto_label_units_files/auto_label_units_22_1.png


.. note::

If you want to train your own models, see the `UnitRefine
repo <%60https://github.com/anoushkajain/UnitRefine%60>`__ for
instructions!

This “How To” demonstrated how to automatically label units after spike
sorting with different strategies. We recommend running **Bombcell** and
**UnitRefine** as part of your pipeline. These methods will facilitate
further curation and make downstream analysis cleaner.

To remove units from your ``SortingAnalyzer``, you can simply use the
``select_units`` function:

.. code:: ipython3

non_noisy_units = bombcell_labels["label"] != "noise"
sorting_analyzer_clean = sorting_analyzer.select_units(sorting_analyzer.unit_ids[non_noisy_units])

.. code:: ipython3

sorting_analyzer_clean




.. parsed-literal::

SortingAnalyzer: 96 channels - 92 units - 1 segments - memory - sparse - has recording
Loaded 14 extensions: random_spikes, waveforms, templates, amplitude_scalings, correlograms, isi_histograms, noise_levels, principal_components, spike_locations, spike_amplitudes, quality_metrics, template_metrics, template_similarity, unit_locations
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/how_to/import_kilosort_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ If you'd like to store the information you've computed, you can save the analyze
)
You now have a fully functional ``SortingAnalyzer`` - congrats! You can now use `spikeinterface-gui <https://github.com/SpikeInterface/spikeinterface-gui/>`__. to view the results
interactively, or start manually labelling your units to `create an automated curation model <https://spikeinterface.readthedocs.io/en/stable/tutorials_custom_index.html#automated-curation-tutorials>`__.
interactively, or start manually labeling your units to `create an automated curation model <https://spikeinterface.readthedocs.io/en/stable/tutorials_custom_index.html#automated-curation-tutorials>`__.

Note that if you have access to the raw recording, you can attach it to the analyzer, and re-compute extensions from the raw data. E.g.

Expand Down
1 change: 1 addition & 0 deletions doc/how_to/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Guides on how to solve specific, short problems in SpikeInterface. Learn how to.
load_matlab_data
load_your_data_into_sorting
benchmark_with_hybrid_recordings
auto_label_units
auto_curation_training
auto_curation_prediction
import_kilosort_data
Binary file added doc/images/template_metrics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 35 additions & 8 deletions doc/modules/metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,32 @@ metric information. For example, you can get the list of available metrics using
.. code-block::

Available metric columns:
['peak_to_valley', 'peak_trough_ratio', 'half_width', 'repolarization_slope',
'recovery_slope', 'num_positive_peaks', 'num_negative_peaks', 'velocity_above',
'velocity_below', 'exp_decay', 'spread']
[
'peak_to_trough_duration',
'half_width',
'repolarization_slope',
'recovery_slope',
'num_positive_peaks',
'num_negative_peaks',
'main_to_next_peak_duration',
'peak_before_to_trough_ratio',
'peak_after_to_trough_ratio',
'peak_before_to_peak_after_ratio',
'main_peak_to_trough_ratio',
'trough_width',
'peak_before_width',
'peak_after_width',
'waveform_baseline_flatness',
'velocity_above',
'velocity_below',
'exp_decay',
'spread'
]


.. code-block:: python

metric_descriptions = ComputeTemplateMetrics.get_metric_descriptions()
metric_descriptions = ComputeTemplateMetrics.get_metric_column_descriptions()
print("Metric descriptions: ")
print(metric_descriptions)

Expand All @@ -44,21 +62,30 @@ metric information. For example, you can get the list of available metrics using

Metric descriptions:
{
'peak_to_valley': 'Duration in s between the trough (minimum) and the peak (maximum) of the spike waveform.',
'peak_trough_ratio': 'Ratio of the amplitude of the peak (maximum) to the trough (minimum) of the spike waveform.',
'peak_to_trough_duration': 'Duration in seconds between the trough (minimum) and the peak (maximum) of the spike waveform.',
'half_width': 'Duration in s at half the amplitude of the trough (minimum) of the spike waveform.',
'repolarization_slope': 'Slope of the repolarization phase of the spike waveform, between the trough (minimum) and return to baseline in uV/s.',
'recovery_slope': 'Slope of the recovery phase of the spike waveform, after the peak (maximum) returning to baseline in uV/s.',
'num_positive_peaks': 'Number of positive peaks in the template',
'num_negative_peaks': 'Number of negative peaks in the template',
'num_negative_peaks': 'Number of negative peaks (troughs) in the template',
'main_to_next_peak_duration': 'Duration in seconds from main extremum to next extremum.',
'peak_before_to_trough_ratio': 'Ratio of peak before amplitude to trough amplitude',
'peak_after_to_trough_ratio': 'Ratio of peak after amplitude to trough amplitude',
'peak_before_to_peak_after_ratio': 'Ratio of peak before amplitude to peak after amplitude',
'main_peak_to_trough_ratio': 'Ratio of main peak amplitude to trough amplitude',
'trough_width': 'Width of the main trough in seconds',
'peak_before_width': 'Width of the main peak before trough in seconds',
'peak_after_width': 'Width of the main peak after trough in seconds',
'waveform_baseline_flatness': 'Ratio of max baseline amplitude to max waveform amplitude. Lower = flatter baseline.',
'velocity_above': 'Velocity of the spike propagation above the max channel in um/ms',
'velocity_below': 'Velocity of the spike propagation below the max channel in um/ms',
'exp_decay': 'Exponential decay of the template amplitude over distance from the extremum channel (1/um).',
'exp_decay': 'Spatial decay of the template amplitude over distance from the extremum channel (1/um). Uses exponential or linear fit based on linear_fit parameter.',
'spread': 'Spread of the template amplitude in um, calculated as the distance between channels whose templates exceed the spread_threshold.'
}




.. toctree::
:caption: Metrics submodules
:maxdepth: 1
Expand Down
Loading
Loading