Skip to content

Commit 1eb05c7

Browse files
authored
Merge branch 'main' into sa-by-dict
2 parents 78af5b6 + 9e94ccf commit 1eb05c7

49 files changed

Lines changed: 1792 additions & 249 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,16 @@ spikeinterface.preprocessing
176176
.. autofunction:: clip
177177
.. autofunction:: common_reference
178178
.. autofunction:: correct_lsb
179+
.. autofunction:: compute_motion
179180
.. autofunction:: correct_motion
180181
.. autofunction:: get_motion_presets
181182
.. autofunction:: get_motion_parameters_preset
182183
.. autofunction:: load_motion_info
183184
.. autofunction:: save_motion_info
184185
.. autofunction:: depth_order
185186
.. autofunction:: detect_bad_channels
187+
.. autofunction:: detect_and_interpolate_bad_channels
188+
.. autofunction:: detect_and_remove_bad_channels
186189
.. autofunction:: directional_derivative
187190
.. autofunction:: filter
188191
.. autofunction:: gaussian_filter

doc/how_to/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ Guides on how to solve specific, short problems in SpikeInterface. Learn how to.
1818
auto_curation_training
1919
auto_curation_prediction
2020
physical_units
21+
unsigned_to_signed
2122
customize_a_plot

doc/how_to/physical_units.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _physical_units:
2+
13
Working with physical units in SpikeInterface recordings
24
========================================================
35

doc/how_to/unsigned_to_signed.rst

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
Unsigned to Signed Data types
2+
=============================
3+
4+
As of version 0.103.0 SpikeInterface has changed one of its defaults for interacting with
5+
:code:`Recording` objects. We no longer autocast unsigned dtypes to signed implicitly. This
6+
means that some users of SpikeInterface will need to add one additional line of code to their scripts
7+
to explicitly handle this conversion.
8+
9+
10+
Why this matters?
11+
-----------------
12+
13+
For those that want a deeper understanding of dtypes `NumPy provides a great explanation <https://numpy.org/doc/stable/reference/arrays.dtypes.html>`_.
14+
For our purposes it is important to know that many pieces of recording equipment opt to store their electrophysiological data as unsigned integers
15+
(e.g., Intan, Maxwell Biosystems, 3Brain Biocam).
16+
Similarly to signed integers, in order to convert to real units these file formats only need to store a :code:`gain`
17+
and an :code:`offset`. Our :code:`RecordingExtractor`'s maintain the dtype that the file format utilizes, which means that some of our
18+
:code:`RecordingExtractor`'s will have unsigned dtypes.
19+
20+
The problem with using unsigned dtypes is that many types of functions (including the ones we use from :code:`SciPy`) perform poorly with unsigned integers.
21+
This is made worse by the fact that these failures are silent (i.e. no error is triggered but the operation leads to nonsensical data). So the
22+
solution required is to convernt unsigned integers into signed integers. Previously we did this under the hood, automatically for users that had
23+
a :code:`Recording` object with an unsigned dtype.
24+
25+
We decided, however, that implicitly performing this action was not the best course of action, since:
26+
27+
1) *explicit* is always better than *implicit*
28+
2) some functions would *magically* change the dtype of the :code:`Recording` object, which can cause confusion
29+
30+
So from version 0.103.0, users will now explicitly have to perform this transformation of their data. This will help users better understand how they are
31+
processing their data during an analysis pipeline as well as better understand the provenance of their pipeline.
32+
33+
34+
Using :code:`unsigned_to_signed`
35+
--------------------------------
36+
37+
For users that receive an error because their :code:`Recording` is unsigned, their is one additional step that must be done:
38+
39+
.. code:: python
40+
41+
import spikeinterface.extractors as se
42+
import spikeinterface.preprocessing as spre
43+
44+
# Intan is an example of unsigned data
45+
recording = se.read_intan('path/to/my/file.rhd', stream_id='0')
46+
# to get a signed version of our Recording we use the following function
47+
recording_signed = spre.unsigned_to_signed(recording)
48+
# we can now apply any preprocessing functions like normal, e.g.
49+
recording_filtered = spre.bandpass_filter(recording_signed)
50+
51+
52+
Now with the signed dtype of the :code:`Recording` one can use a SpikeInterface pipeline as usual.
53+
54+
55+
If you are curious if your :code:`Recording` is unsigned you can simply check the repr or use :code:`get_dtype()`
56+
57+
.. code:: python
58+
59+
# the repr automatically displays the dtype
60+
print(recording)
61+
# use method on the Recording object
62+
print(recording.get_dtype())
63+
64+
In either case, if the dtype displayed has a :code:`u` at the beginning (e.g. :code:`uint16`) then your recording is
65+
unsigned. If it doesn't have the :code:`u` (e.g. :code:`int16`) then it is signed and would not need this preprocessing step.
66+
67+
68+
Bit depth
69+
---------
70+
71+
One final important piece of information for some users is the concept of bit depth, which is the number of bits used to
72+
sample the data. The :code:`bit_depth` argument that can be fed into the :code:`unsigned_to_signed` function.
73+
This should be used in cases where the ADC bit depth does not match the bit depth of the data type (e.g., if the data is
74+
stored as :code:`uint16` but the ADC is 12 bits).
75+
Let's make a concrete example: the Biocam acquisition system from 3Brain uses a 12-bit ADC and stores the data as
76+
:code:`uint16`. This means that the data is stored in a 16-bit unsigned integer format, but the actual data
77+
only covers a 12-bit range. Therefore, that the "zero" of the data is not at 0, nor at half of the :code:`uint16` range (i.e. 2^15),
78+
but rather at 2048 (i.e., 2^12).
79+
In this case, setting the :code:`bit_depth` argument to 12 will allow the :code:`unsigned_to_signed` function to
80+
correctly convert the unsigned data to signed data and offset the data to be centered around 0, by subtracting 2048
81+
while converting the data from unsigned to signed.
82+
83+
.. code:: python
84+
85+
recording_unsigned = se.read_biocam('path/to/my/file.brw')
86+
# we can now convert to signed with the correct bit depth
87+
recording_signed = spre.unsigned_to_signed(recording_unsigned, bit_depth=12)
88+
89+
90+
Additional Notes
91+
----------------
92+
93+
1) Some sorters make use of SpikeInterface preprocessing either
94+
within their wrappers or within their own code base. So remember to use the "signed" version of
95+
your recording for the rest of your pipeline.
96+
97+
2) Using :code:`unsigned_to_signed` in versions less than 0.103.0 does not hurt your scripts. This
98+
option was available previously along with the implicit option. Adding this into scripts with old
99+
versions of SpikeInterface will still work and will "future-proof" your scripts for when you
100+
update to a version greater than or equal to 0.103.0.
101+
102+
3) For additional information on units and scaling in SpikeInterface see :ref:`physical_units`.
27.3 KB
Loading
Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,128 @@
1-
Noise cutoff (not currently implemented)
2-
========================================
1+
Noise cutoff (:code:`noise_cutoff`)
2+
===================================
33

44
Calculation
55
-----------
66

77

8-
Metric describing whether an amplitude distribution is cut off, similar to _amp_cutoff :ref:`amplitude cutoff <amp_cutoff>` but without a Gaussian assumption.
9-
A histogram of amplitudes is created and quantifies the distance between the low tail, mean number of spikes and high tail in terms of standard deviations.
8+
Metric describing whether an amplitude distribution is cut off as it approaches zero, similar to :ref:`amplitude cutoff <amp_cutoff>` but without a Gaussian assumption.
109

11-
A SpikeInterface implementation is not yet available.
10+
The **noise cutoff** metric assesses whether a unit’s spike‐amplitude distribution is truncated
11+
at the low-end, which may be due to the high amplitude detection threshold in the deconvolution step,
12+
i.e., if low‐amplitude spikes were missed. It does not assume a Gaussian shape;
13+
instead, it directly compares counts in the low‐amplitude bins to counts in high‐amplitude bins.
14+
15+
1. **Build a histogram**
16+
17+
For each unit, divide all amplitudes into ``n_bins`` equally spaced bins over the range of the amplitude.
18+
If the number of spikes is large, you may consider using a larger ``n_bins``. For a small number of spikes, consider a smaller ``n_bins``.
19+
Let :math:`n_i` denote the count in the :math:`i`-th bin.
20+
21+
2. **Identify the “low” region**
22+
- Compute the amplitude value at the specified ``low_quantile`` (for example, 0.10 = 10th percentile), denoted as :math:`\text{amp}_{low}`.
23+
- Find all histogram bins whose upper edge is below that quantile value. These bins form the "low‐quantile region".
24+
- Compute
25+
26+
.. math::
27+
L_{\mathrm{bins}} = \bigl\{i : \text{upper_edge}_i \le \text{amp}_{low}\bigr\}, \quad
28+
\mu_{\mathrm{low}} = \frac{1}{|L_{\mathrm{bins}}|}\sum_{i\in L_{\mathrm{bins}}} n_i.
29+
30+
3. **Identify the “high” region**
31+
32+
- Compute the amplitude value at the specified ``high_quantile`` (for example, 0.25 = top 25th percentile), denoted as :math:`\text{amp}_{high}`.
33+
- Find all histogram bins whose lower edge is greater than that quantile value. These bins form the "high‐quantile region".
34+
- Compute
35+
36+
.. math::
37+
H_{\mathrm{bins}} &= \bigl\{i : \text{lower_edge}_i \ge \text{amp}_{high}\bigr\}, \\
38+
\mu_{\mathrm{high}} &= \frac{1}{|H_{\mathrm{bins}}|}\sum_{i\in H_{\mathrm{bins}}} n_i, \quad
39+
\sigma_{\mathrm{high}} = \sqrt{\frac{1}{|H_{\mathrm{bins}}|-1}\sum_{i\in H_{\mathrm{bins}}}\bigl(n_i-\mu_{\mathrm{high}} \bigr)^2}.
40+
41+
4. **Compute cutoff**
42+
43+
The *cutoff* is given by how many standard deviations away the low-amplitude bins are from the high-amplitude bins, defined as
44+
45+
.. math::
46+
\mathrm{cutoff} = \frac{\mu_{\mathrm{low}} - \mu_{\mathrm{high}}}{\sigma_{\mathrm{high}}}.
47+
48+
49+
- If no low‐quantile bins exist, a warning is issued and ``cutoff = NaN``.
50+
- If no high‐quantile bins exist or :math:`\sigma_{\mathrm{high}} = 0`, a warning is issued and ``cutoff = NaN``.
51+
52+
5. **Compute the low-to-peak ratio**
53+
54+
- Let :math:`M = \max_i\,n_i` be the height of the largest bin in the histogram.
55+
- Define
56+
57+
.. math::
58+
\mathrm{ratio} = \frac{\mu_{\mathrm{low}}}{M}.
59+
60+
61+
- If there are no low bins, :math:`\mathrm{ratio} = NaN`.
62+
63+
64+
Together, ``(cutoff, ratio)`` quantify how suppressed the low‐end of the amplitude distribution is relative to the top quantile and to the peak.
1265

1366
Expectation and use
1467
-------------------
1568

1669
Noise cutoff attempts to describe whether an amplitude distribution is cut off.
17-
The metric is loosely based on [Hill]_'s amplitude cutoff, but is here adapted (originally by [IBL]_) to avoid making the Gaussianity assumption on spike distributions.
18-
Noise cutoff provides an estimate of false negative rate, so a lower value indicates fewer missed spikes (a more complete unit).
70+
Larger values of ``cutoff`` and ``ratio`` suggest that the distribution is cut-off.
71+
IBL uses the default value of 1 (equivalent to e.g. ``low_quantile=0.01, n_bins=100``) to choose the number of
72+
lower bins, with a suggested threshold of 5 for ``cutoff`` to determine whether a unit is cut off or not.
73+
In practice, the IBL threshold is quite conservative, and a lower threshold might work better for your data.
74+
We suggest plotting the data using the :py:func:`~spikeinterface.widgets.plot_amplitudes` widget to view your data when choosing your threshold.
75+
It is suggested to use this metric when the amplitude histogram is **unimodal**.
76+
77+
The metric is loosely based on [Hill]_'s amplitude cutoff, but is here adapted (originally by [IBL2024]_) to avoid making the Gaussian assumption on spike distributions.
78+
79+
Example code
80+
------------
81+
82+
.. code-block:: python
83+
84+
import numpy as np
85+
import matplotlib.pyplot as plt
86+
from spikeinterface.full as si
87+
88+
# Suppose `sorting_analyzer` has been computed with spike amplitudes:
89+
# Select units you are interested in visualizing
90+
unit_ids = ...
91+
92+
# Compute noise_cutoff:
93+
summary_dict = compute_noise_cutoff(
94+
sorting_analyzer=sorting_analyzer
95+
high_quantile=0.25,
96+
low_quantile=0.10,
97+
n_bins=100,
98+
unit_ids=unit_ids
99+
)
100+
101+
Reference
102+
---------
103+
104+
.. autofunction:: spikeinterface.qualitymetrics.misc_metrics.compute_noise_cutoff
105+
106+
Examples with plots
107+
-------------------
108+
109+
Here is shown the histogram of two units, with the vertical lines separating low- and high-amplitude regions.
110+
111+
- On the left, we have a unit with no truncation at the left end, and the cutoff and ratio are small.
112+
- On the right, we have a unit with truncation at -1, and the cutoff and ratio are much larger.
19113

114+
.. image:: example_cutoff.png
115+
:width: 600
20116

21117
Links to original implementations
22118
---------------------------------
23119

24120
* From `IBL implementation <https://github.com/int-brain-lab/ibllib/blob/2e1f91c622ba8dbd04fc53946c185c99451ce5d6/brainbox/metrics/single_units.py>`_
25121

122+
Note: Compared to the original implementation, we have added a comparison between the low-amplitude bins to the largest bin (``noise_ratio``).
123+
The selection of low-amplitude bins is based on the ``low_quantile`` rather than the number of bins.
26124

27125
Literature
28126
----------
29127

30-
Metric introduced by [IBL]_ (adapted from [Hill]_'s amplitude cutoff metric).
128+
Metric introduced by [IBL2024]_.

doc/references.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ References
123123
124124
.. [IBL] `Spike sorting pipeline for the International Brain Laboratory. 2022. <https://figshare.com/articles/online_resource/Spike_sorting_pipeline_for_the_International_Brain_Laboratory/19705522/3>`_
125125
126+
.. [IBL2024] `Spike sorting pipeline for the International Brain Laboratory - Version 2. 2024. <https://figshare.com/articles/online_resource/Spike_sorting_pipeline_for_the_International_Brain_Laboratory/19705522?file=49783080>`_
127+
126128
.. [Jackson] `Quantitative assessment of extracellular multichannel recording quality using measures of cluster separation. Society of Neuroscience Abstract. 2005. <https://www.sciencedirect.com/science/article/abs/pii/S0306452204008425>`_
127129
128130
.. [Jain] `UnitRefine: A Community Toolbox for Automated Spike Sorting Curation. 2025 <https://www.biorxiv.org/content/10.1101/2025.03.30.645770v1>`_

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,7 @@ docs = [
202202
"networkx",
203203
"skops", # For automated curation
204204
"scikit-learn", # For automated curation
205-
# Download data
206-
"pooch>=1.8.2",
207-
"datalad>=1.0.2",
205+
"huggingface_hub", # For automated curation
208206

209207
# for release we need pypi, so this needs to be commented
210208
"probeinterface @ git+https://github.com/SpikeInterface/probeinterface.git", # We always build from the latest version

src/spikeinterface/benchmark/tests/common_benchmark_testing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def compute_gt_templates(recording, gt_sorting, ms_before=2.0, ms_after=3.0, ret
7474
channel_ids=recording.channel_ids,
7575
unit_ids=gt_sorting.unit_ids,
7676
probe=recording.get_probe(),
77-
is_scaled=return_scaled,
77+
is_in_uV=return_scaled,
7878
)
7979
return gt_templates
8080

src/spikeinterface/core/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@
109109
get_chunk_with_margin,
110110
order_channels_by_depth,
111111
)
112-
from .sorting_tools import spike_vector_to_spike_trains, random_spikes_selection, apply_merges_to_sorting
112+
from .sorting_tools import (
113+
spike_vector_to_spike_trains,
114+
random_spikes_selection,
115+
apply_merges_to_sorting,
116+
apply_splits_to_sorting,
117+
)
113118

114119
from .waveform_tools import extract_waveforms_to_buffers, estimate_templates, estimate_templates_with_accumulator
115120
from .snippets_tools import snippets_from_sorting

0 commit comments

Comments
 (0)