Skip to content

Commit 3c8222f

Browse files
authored
Merge branch 'main' into fix-openephys-warnings
2 parents e7d3153 + 191a047 commit 3c8222f

File tree

80 files changed

+1317
-575
lines changed

Some content is hidden

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

80 files changed

+1317
-575
lines changed

.github/scripts/test_kilosort4_ci.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@
112112
PARAMS_TO_TEST_DICT.update({"cluster_neighbors": 11})
113113
PARAMETERS_NOT_AFFECTING_RESULTS.append("cluster_neighbors")
114114

115+
if parse(kilosort.__version__) >= parse("4.0.37"):
116+
PARAMS_TO_TEST_DICT.update({"max_cluster_subset": 20})
117+
PARAMETERS_NOT_AFFECTING_RESULTS.append("max_cluster_subset")
118+
115119

116120
PARAMS_TO_TEST = list(PARAMS_TO_TEST_DICT.keys())
117121

@@ -254,6 +258,8 @@ def test_initialize_ops_arguments(self):
254258
"device",
255259
"save_preprocessed_copy",
256260
]
261+
if parse(kilosort.__version__) >= parse("4.0.37"):
262+
expected_arguments += ["gui_mode"]
257263

258264
self._check_arguments(
259265
initialize_ops,

doc/how_to/combine_recordings.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Combine recordings in SpikeInterface
44
In this tutorial we will walk through combining multiple recording objects. Sometimes this occurs due to hardware
55
settings (e.g. Intan software has a default setting of new files every 1 minute) or the experimenter decides to
66
split their recording into multiple files for different experimental conditions. If the probe has not been moved,
7-
however, then during sorting it would likely make sense to combine these individual reocrding objects into one
7+
however, then during sorting it would likely make sense to combine these individual recording objects into one
88
recording object.
99

1010
**Why Combine?**

doc/how_to/handle_drift.rst

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,38 +1455,32 @@ Plot the results
14551455
We load back the results and use the widgets module to explore the
14561456
estimated drift motion.
14571457

1458-
For all methods we have 4 plots: \* top left: time vs estimated peak
1459-
depth \* top right: time vs peak depth after motion correction \* bottom
1460-
left: the average motion vector across depths and all motion across
1461-
spatial depths (for non-rigid estimation) \* bottom right: if motion
1462-
correction is non rigid, the motion vector across depths is plotted as a
1463-
map, with the color code representing the motion in micrometers.
1464-
1465-
A few comments on the figures: \* the preset **‘rigid_fast’** has only
1466-
one motion vector for the entire probe because it is a “rigid” case. The
1467-
motion amplitude is globally underestimated because it averages across
1468-
depths. However, the corrected peaks are flatter than the non-corrected
1469-
ones, so the job is partially done. The big jump at=600s when the probe
1470-
start moving is recovered quite well. \* The preset **kilosort_like**
1471-
gives better results because it is a non-rigid case. The motion vector
1472-
is computed for different depths. The corrected peak locations are
1473-
flatter than the rigid case. The motion vector map is still be a bit
1474-
noisy at some depths (e.g around 1000um). \* The preset **dredge** is
1475-
offcial DREDge re-implementation in spikeinterface. It give the best
1476-
result : very fast and smooth motion estimation. Very few noise. This
1477-
method also capture very well the non rigid motion gradient along the
1478-
probe. The best method on the market at the moement. An enormous thanks
1479-
to the dream team : Charlie Windolf, Julien Boussard, Erdem Varol, Liam
1480-
Paninski. Note that in the first part of the recording before the
1481-
imposed motion (0-600s) we clearly have a non-rigid motion: the upper
1482-
part of the probe (2000-3000um) experience some drifts, but the lower
1483-
part (0-1000um) is relatively stable. The method defined by this preset
1484-
is able to capture this. \* The preset **nonrigid_accurate** this is the
1485-
ancestor of “dredge” before it was published. It seems to give the good
1486-
results on this recording but with bit more noise. \* The preset
1487-
**dredge_fast** similar than dredge but faster (using grid_convolution).
1488-
\* The preset **nonrigid_fast_and_accurate** a variant of
1489-
nonrigid_accurate but faster (using grid_convolution).
1458+
For all methods we have 4 plots:
1459+
* top left: time vs estimated peak
1460+
* top right: time vs peak depth after motion correction
1461+
* bottom left: the average motion vector across depths and all motion across spatial depths (for non-rigid estimation)
1462+
* bottom right: if motion correction is non rigid, the motion vector across depths is plotted as a map,
1463+
with the color code representing the motion in micrometers.
1464+
1465+
A few comments on the figures:
1466+
* the preset **‘rigid_fast’** has only one motion vector for the entire probe because it is a “rigid” case. The
1467+
motion amplitude is globally underestimated because it averages across depths. However, the corrected peaks
1468+
are flatter than the non-corrected ones, so the job is partially done. The big jump at=600s when the probe start
1469+
moving is recovered quite well.
1470+
* The preset **kilosort_like** gives better results because it is a non-rigid case. The motion vector is computed
1471+
for different depths. The corrected peak locations are flatter than the rigid case. The motion vector map is still
1472+
be a bit noisy at some depths (e.g around 1000um).
1473+
* The preset **dredge** is official DREDge re-implementation in spikeinterface. It give the best result : very fast
1474+
and smooth motion estimation. Very few noise. This method also capture very well the non rigid motion gradient along
1475+
the probe. The best method on the market at the moement. An enormous thanks to the dream team : Charlie Windolf,
1476+
Julien Boussard, Erdem Varol, Liam Paninski. Note that in the first part of the recording before the imposed motion
1477+
(0-600s) we clearly have a non-rigid motion: the upper part of the probe (2000-3000um) experience some drifts, but the
1478+
lower part (0-1000um) is relatively stable. The method defined by this preset is able to capture this.
1479+
* The preset **nonrigid_accurate** this is the ancestor of “dredge” before it was published. It seems to give good results
1480+
on this recording but with bit more noise.
1481+
* The preset **dredge_fast** similar than dredge but faster (using grid_convolution).
1482+
* The preset **nonrigid_fast_and_accurate** a variant of
1483+
nonrigid_accurate but faster (using grid_convolution).
14901484

14911485
.. code:: ipython3
14921486

doc/how_to/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ Guides on how to solve specific, short problems in SpikeInterface. Learn how to.
1717
drift_with_lfp
1818
auto_curation_training
1919
auto_curation_prediction
20+
physical_units
2021
customize_a_plot

doc/how_to/physical_units.rst

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
Working with physical units in SpikeInterface recordings
2+
========================================================
3+
4+
In neurophysiology recordings, data is often stored in raw ADC (Analog-to-Digital Converter) integer values but needs to be analyzed in physical units.
5+
For extracellular recordings, this is typically microvolts (µV), but some recording devices may use different physical units.
6+
SpikeInterface provides tools to handle both situations.
7+
8+
It's important to note that **most spike sorters work fine on raw digital (ADC) units** and scaling is not needed. Going a step further, some sorters, such as Kilosort 3, require their input to be in raw ADC units.
9+
The specific behavior however depends on the spike sorter, so it's important to understand the specific input requirements on a case per case basis.
10+
11+
Many preprocessing tools are also linear transformations, and if the ADC is implemented as a linear transformation which is fairly common, then the overall effect can be preserved.
12+
That is, **preprocessing steps can often be applied either before or after unit conversion without affecting the outcome.**. That being said, there are rough edges to this approach.
13+
preprocessing algorithms like filtering, whitening, centering, interpolation and common reference require casting to float within the pipeline. We advise users to experiment
14+
with different approaches to find the best one for their specific use case.
15+
16+
17+
Therefore, **it is usually safe to work in raw ADC integer values unless a specific tool or analysis requires physical units**.
18+
If you are interested in visualizations, comparability across devices, or outputs with interpretable physical scales (e.g., microvolts), converting to physical units is recommended.
19+
Otherwise, remaining in raw units can simplify processing and preserve performance.
20+
21+
Understanding Physical Units
22+
----------------------------
23+
24+
Most recording devices store data in ADC units (integers) to save space and preserve the raw data.
25+
To convert these values to physical units, two parameters are needed:
26+
27+
* **gain**: A multiplicative factor to scale the raw values
28+
* **offset**: An additive factor to shift the values
29+
30+
The conversion formula is:
31+
32+
.. code-block:: text
33+
34+
physical_value = raw_value * gain + offset
35+
36+
37+
Converting to Physical Units
38+
----------------------------
39+
40+
SpikeInterface provides two preprocessing classes for converting recordings to physical units. Both wrap the
41+
``RecordingExtractor`` class and ensures that the data is returned in physical units when calling `get_traces <https://spikeinterface.readthedocs.io/en/stable/api.html#spikeinterface.core.BaseRecording.get_traces>`_
42+
43+
1. ``scale_to_uV``: The primary function for extracellular recordings. SpikeInterface is centered around
44+
extracellular recordings, and this function is designed to convert the data to microvolts (µV).
45+
2. ``scale_to_physical_units``: A general function for any physical unit conversion. This will allow you to extract the data in any
46+
physical unit, not just microvolts. This is useful for other types of recordings, such as force measurements in Newtons but should be
47+
handled with care.
48+
49+
For most users working with extracellular recordings, ``scale_to_uV`` is the recommended choice if they want to work in physical units:
50+
51+
.. code-block:: python
52+
53+
from spikeinterface.extractors import read_intan
54+
from spikeinterface.preprocessing import scale_to_uV
55+
56+
# Load recording (data is in ADC units)
57+
recording = read_intan("path/to/file.rhs")
58+
59+
# Convert to microvolts
60+
recording_uv = scale_to_uV(recording)
61+
62+
For recordings with non-standard units (e.g., force measurements in Newtons), use ``scale_to_physical_units``:
63+
64+
.. code-block:: python
65+
66+
from spikeinterface.preprocessing import scale_to_physical_units
67+
68+
# Convert to physical units (whatever they may be)
69+
recording_physical = scale_to_physical_units(recording)
70+
71+
Both preprocessors automatically:
72+
73+
1. Detect the appropriate gain and offset from the recording properties
74+
2. Apply the conversion to all channels
75+
3. Update the recording properties to reflect that data is now in physical units
76+
77+
Setting Custom Physical Units
78+
-----------------------------
79+
80+
While most extractors automatically set the appropriate ``gain_to_uV`` and ``offset_to_uV`` values,
81+
there might be cases where you want to set custom physical units. In these cases, you can set
82+
the following properties:
83+
84+
* ``physical_unit``: The target physical unit (e.g., 'uV', 'mV', 'N')
85+
* ``gain_to_unit``: The gain to convert from raw values to the target unit
86+
* ``offset_to_unit``: The offset to convert from raw values to the target unit
87+
88+
You need to set these properties for every channel, which allows for the case when there are different gains and offsets on different channels. Here's an example:
89+
90+
.. code-block:: python
91+
92+
# Set custom physical units
93+
num_channels = recording.get_num_channels()
94+
values = ["volts"] * num_channels
95+
recording.set_property(key='physical_unit', values=values)
96+
97+
gain_values = [0.001] * num_channels # Convert from ADC to volts
98+
recording.set_property(key='gain_to_unit', values=gain_values) # Convert to volts
99+
100+
offset_values = [0] * num_channels # No offset
101+
recording.set_property(key='offset_to_unit', values=offset_values) # No offset
102+
103+
# Apply the conversion using scale_to_physical_units
104+
recording_physical = scale_to_physical_units(recording)
105+
106+
This approach gives you full control over the unit conversion process while maintaining
107+
compatibility with SpikeInterface's preprocessing pipeline.

examples/how_to/handle_drift.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -133,32 +133,33 @@ def preprocess_chain(rec):
133133
# We load back the results and use the widgets module to explore the estimated drift motion.
134134
#
135135
# For all methods we have 4 plots:
136+
#
136137
# * top left: time vs estimated peak depth
137138
# * top right: time vs peak depth after motion correction
138139
# * bottom left: the average motion vector across depths and all motion across spatial depths (for non-rigid estimation)
139140
# * bottom right: if motion correction is non rigid, the motion vector across depths is plotted as a map, with the color code representing the motion in micrometers.
140141
#
141142
# A few comments on the figures:
142-
# * the preset **'rigid_fast'** has only one motion vector for the entire probe because it is a "rigid" case.
143-
# The motion amplitude is globally underestimated because it averages across depths.
144-
# However, the corrected peaks are flatter than the non-corrected ones, so the job is partially done.
145-
# The big jump at=600s when the probe start moving is recovered quite well.
146-
# * The preset **kilosort_like** gives better results because it is a non-rigid case.
147-
# The motion vector is computed for different depths.
148-
# The corrected peak locations are flatter than the rigid case.
149-
# The motion vector map is still be a bit noisy at some depths (e.g around 1000um).
150-
# * The preset **dredge** is offcial DREDge re-implementation in spikeinterface.
151-
# It give the best result : very fast and smooth motion estimation. Very few noise.
152-
# This method also capture very well the non rigid motion gradient along the probe.
153-
# The best method on the market at the moement.
154-
# An enormous thanks to the dream team : Charlie Windolf, Julien Boussard, Erdem Varol, Liam Paninski.
155-
# Note that in the first part of the recording before the imposed motion (0-600s) we clearly have a non-rigid motion:
156-
# the upper part of the probe (2000-3000um) experience some drifts, but the lower part (0-1000um) is relatively stable.
157-
# The method defined by this preset is able to capture this.
158-
# * The preset **nonrigid_accurate** this is the ancestor of "dredge" before it was published.
159-
# It seems to give the good results on this recording but with bit more noise.
160-
# * The preset **dredge_fast** similar than dredge but faster (using grid_convolution).
161-
# * The preset **nonrigid_fast_and_accurate** a variant of nonrigid_accurate but faster (using grid_convolution).
143+
# * the preset **'rigid_fast'** has only one motion vector for the entire probe because it is a "rigid" case.
144+
# The motion amplitude is globally underestimated because it averages across depths.
145+
# However, the corrected peaks are flatter than the non-corrected ones, so the job is partially done.
146+
# The big jump at=600s when the probe start moving is recovered quite well.
147+
# * The preset **kilosort_like** gives better results because it is a non-rigid case.
148+
# The motion vector is computed for different depths.
149+
# The corrected peak locations are flatter than the rigid case.
150+
# The motion vector map is still be a bit noisy at some depths (e.g around 1000um).
151+
# * The preset **dredge** is offcial DREDge re-implementation in spikeinterface.
152+
# It give the best result : very fast and smooth motion estimation. Very few noise.
153+
# This method also capture very well the non rigid motion gradient along the probe.
154+
# The best method on the market at the moement.
155+
# An enormous thanks to the dream team : Charlie Windolf, Julien Boussard, Erdem Varol, Liam Paninski.
156+
# Note that in the first part of the recording before the imposed motion (0-600s) we clearly have a non-rigid motion:
157+
# the upper part of the probe (2000-3000um) experience some drifts, but the lower part (0-1000um) is relatively stable.
158+
# The method defined by this preset is able to capture this.
159+
# * The preset **nonrigid_accurate** this is the ancestor of "dredge" before it was published.
160+
# It seems to give the good results on this recording but with bit more noise.
161+
# * The preset **dredge_fast** similar than dredge but faster (using grid_convolution).
162+
# * The preset **nonrigid_fast_and_accurate** a variant of nonrigid_accurate but faster (using grid_convolution).
162163
#
163164
#
164165

@@ -205,6 +206,7 @@ def preprocess_chain(rec):
205206
# We can see here that some clusters seem to be more compact on the 'y' axis, especially for the preset "nonrigid_accurate".
206207
#
207208
# Be aware that there are two ways to correct for the motion:
209+
#
208210
# 1. Interpolate traces and detect/localize peaks again (`interpolate_recording()`)
209211
# 2. Compensate for drifts directly on peak locations (`correct_motion_on_peaks()`)
210212
#

examples/tutorials/core/plot_4_sorting_analyzer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@
4343
##############################################################################
4444
# Let's now instantiate the recording and sorting objects:
4545

46-
recording = se.MEArecRecordingExtractor(local_path)
46+
recording, sorting = se.read_mearec(local_path)
4747
print(recording)
48-
sorting = se.MEArecSortingExtractor(local_path)
4948
print(sorting)
5049

5150
###############################################################################

examples/tutorials/extractors/plot_1_read_various_formats.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
# :py:class:`~spikeinterface.extractors.Spike2RecordingExtractor` object:
6262
#
6363

64-
recording = se.Spike2RecordingExtractor(spike2_file_path, stream_id="0")
64+
recording = se.read_spike2(spike2_file_path, stream_id="0")
6565
print(recording)
6666

6767
##############################################################################
@@ -75,11 +75,6 @@
7575
print(sorting)
7676
print(type(sorting))
7777

78-
##############################################################################
79-
# The :py:func:`~spikeinterface.extractors.read_mearec` function is equivalent to:
80-
81-
recording = se.MEArecRecordingExtractor(mearec_folder_path)
82-
sorting = se.MEArecSortingExtractor(mearec_folder_path)
8378

8479
##############################################################################
8580
# SI objects (:py:class:`~spikeinterface.core.BaseRecording` and :py:class:`~spikeinterface.core.BaseSorting`)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ qualitymetrics = [
120120
]
121121

122122
test_core = [
123-
"pytest",
123+
"pytest<8.4.0",
124124
"pytest-dependency",
125125
"psutil",
126126

@@ -146,7 +146,7 @@ test_preprocessing = [
146146

147147

148148
test = [
149-
"pytest",
149+
"pytest<8.4.0",
150150
"pytest-dependency",
151151
"pytest-cov",
152152
"psutil",

src/spikeinterface/benchmark/benchmark_base.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ def create(cls, study_folder, datasets={}, cases={}, levels=None):
134134
else:
135135
analyzer = data
136136

137-
rec, gt_sorting = analyzer.recording, analyzer.sorting
138-
139137
analyzers_path[key] = str(analyzer.folder.resolve())
140138

141139
# recordings are pickled
@@ -180,7 +178,11 @@ def scan_folder(self):
180178
self.analyzers[key] = analyzer
181179
# the sorting is in memory here we take the saved one because comparisons need to pickle it later
182180
sorting = load(analyzer.folder / "sorting")
183-
self.datasets[key] = analyzer.recording, sorting
181+
if analyzer.has_recording():
182+
recording = analyzer.recording
183+
else:
184+
recording = None
185+
self.datasets[key] = recording, sorting
184186

185187
with open(self.folder / "cases.pickle", "rb") as f:
186188
self.cases = pickle.load(f)
@@ -594,15 +596,22 @@ def load_folder(cls, folder):
594596
elif format == "sorting":
595597
from spikeinterface.core import load_extractor
596598

597-
result[k] = load(folder / k)
599+
sorting_folder = folder / k
600+
if sorting_folder.exists():
601+
result[k] = load(sorting_folder)
598602
elif format == "Motion":
599603
from spikeinterface.core.motion import Motion
600604

601-
result[k] = Motion.load(folder / k)
605+
motion_folder = folder / k
606+
if motion_folder.exists():
607+
result[k] = Motion.load(motion_folder)
602608
elif format == "zarr_templates":
603609
from spikeinterface.core.template import Templates
604610

605-
result[k] = Templates.from_zarr(folder / k)
611+
zarr_folder = folder / k
612+
if zarr_folder.exists():
613+
614+
result[k] = Templates.from_zarr(zarr_folder)
606615

607616
return result
608617

0 commit comments

Comments
 (0)