Skip to content

Commit ec1d10f

Browse files
authored
Merge branch 'main' into feat/vectorize-per-channel-pca
2 parents ab4dedf + 89381fe commit ec1d10f

28 files changed

Lines changed: 95 additions & 70 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ SpikeInterface is a Python package designed to unify preexisting spike sorting t
5252

5353
- Bombcell [Bombcell: automated curation and cell classification of spike-sorted electrophysiology data](https://doi.org/10.5281/zenodo.8172822>) ([docs](https://spikeinterface.readthedocs.io/en/latest/how_to/auto_label_units.html#bombcell))
5454
- SLAy. [SLAy-ing oversplitting errors in high-density electrophysiology spike sorting](https://www.biorxiv.org/content/10.1101/2025.06.20.660590v2) ([docs](https://spikeinterface.readthedocs.io/en/latest/modules/curation.html#auto-merging-units))
55-
- Lupin, Spykingcicus2 and Tridesclous2. [Opening the black box: a modular approach to spike sorting](https://www.biorxiv.org/content/10.64898/2026.01.23.701239v1) ([docs](https://spikeinterface.readthedocs.io/en/stable/modules/sorters.html#supported-spike-sorters))
55+
- Lupin, Spykingcircus2 and Tridesclous2. [Opening the black box: a modular approach to spike sorting](https://www.biorxiv.org/content/10.64898/2026.01.23.701239v1) ([docs](https://spikeinterface.readthedocs.io/en/stable/modules/sorters.html#supported-spike-sorters))
5656
- rtsort. [RT-Sort: An action potential propagation-based algorithm for real time spike detection and sorting with millisecond latencies](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0312438) ([docs](https://spikeinterface.readthedocs.io/en/stable/modules/sorters.html#supported-spike-sorters))
5757
- MEDiCINe. [MEDiCINe: Motion Correction for Neural Electrophysiology Recordings](https://www.eneuro.org/content/12/3/ENEURO.0529-24.2025) ([docs](https://spikeinterface.readthedocs.io/en/latest/how_to/handle_drift.html))
5858
- UnitRefine. [UnitRefine: A Community Toolbox for Automated Spike Sorting Curation](https://www.biorxiv.org/content/10.1101/2025.03.30.645770v2) ([docs](https://spikeinterface.readthedocs.io/en/latest/tutorials_custom_index.html#automated-curation-tutorials))
@@ -73,7 +73,7 @@ With SpikeInterface, users can:
7373
- visualize recordings and spike sorting outputs in several ways (matplotlib, sortingview, jupyter, ephyviewer)
7474
- export a report and/or export to phy
7575
- curate your sorting with several strategies (ml-based, metrics based, manual, ...)
76-
- offer a powerful Qt-based or we-based viewer in a separate package [spikeinterface-gui](https://github.com/SpikeInterface/spikeinterface-gui) for manual curation that replace phy.
76+
- offer a powerful Qt-based or web-based viewer in a separate package [spikeinterface-gui](https://github.com/SpikeInterface/spikeinterface-gui) for manual curation that replaces phy.
7777
- have powerful sorting components to build your own sorter.
7878
- have a full motion/drift correction framework
7979

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ dev = [
232232
]
233233

234234
[tool.pytest.ini_options]
235+
testpaths = ["src"]
235236
markers = [
236237
"core",
237238
"generation",

src/spikeinterface/benchmark/tests/common_benchmark_testing.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
estimate_templates,
1717
Templates,
1818
create_sorting_analyzer,
19+
ms_to_samples,
1920
)
2021
from spikeinterface.generation import generate_drifting_recording
2122

@@ -54,8 +55,8 @@ def make_dataset(job_kwargs={}):
5455
def compute_gt_templates(recording, gt_sorting, ms_before=2.0, ms_after=3.0, return_in_uV=False, **job_kwargs):
5556
spikes = gt_sorting.to_spike_vector() # [spike_indices]
5657
fs = recording.sampling_frequency
57-
nbefore = int(ms_before * fs / 1000)
58-
nafter = int(ms_after * fs / 1000)
58+
nbefore = ms_to_samples(ms_before, fs)
59+
nafter = ms_to_samples(ms_after, fs)
5960
templates_array = estimate_templates(
6061
recording,
6162
spikes,

src/spikeinterface/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
read_python,
9191
write_python,
9292
normal_pdf,
93+
ms_to_samples,
9394
)
9495
from .job_tools import (
9596
get_best_job_kwargs,

src/spikeinterface/core/analyzer_extension_core.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .template import Templates
2222
from .sorting_tools import random_spikes_selection, select_sorting_periods_mask, spike_vector_to_indices
2323
from .job_tools import fix_job_kwargs, split_job_kwargs
24+
from .core_tools import ms_to_samples
2425

2526

2627
class ComputeRandomSpikes(AnalyzerExtension):
@@ -170,11 +171,11 @@ class ComputeWaveforms(AnalyzerExtension):
170171

171172
@property
172173
def nbefore(self):
173-
return int(self.params["ms_before"] * self.sorting_analyzer.sampling_frequency / 1000.0)
174+
return ms_to_samples(self.params["ms_before"], self.sorting_analyzer.sampling_frequency)
174175

175176
@property
176177
def nafter(self):
177-
return int(self.params["ms_after"] * self.sorting_analyzer.sampling_frequency / 1000.0)
178+
return ms_to_samples(self.params["ms_after"], self.sorting_analyzer.sampling_frequency)
178179

179180
def _run(self, verbose=False, **job_kwargs):
180181
self.data.clear()
@@ -540,12 +541,12 @@ def _compute_and_append_from_waveforms(self, operators):
540541

541542
@property
542543
def nbefore(self):
543-
nbefore = int(self.params["ms_before"] * self.sorting_analyzer.sampling_frequency / 1000.0)
544+
nbefore = ms_to_samples(self.params["ms_before"], self.sorting_analyzer.sampling_frequency)
544545
return nbefore
545546

546547
@property
547548
def nafter(self):
548-
nafter = int(self.params["ms_after"] * self.sorting_analyzer.sampling_frequency / 1000.0)
549+
nafter = ms_to_samples(self.params["ms_after"], self.sorting_analyzer.sampling_frequency)
549550
return nafter
550551

551552
def _select_extension_data(self, unit_ids):

src/spikeinterface/core/core_tools.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,3 +757,8 @@ def is_path_remote(path: str | Path) -> bool:
757757
Whether the path is a remote path.
758758
"""
759759
return "s3://" in str(path) or "gcs://" in str(path)
760+
761+
762+
def ms_to_samples(ms: float, sampling_frequency: float) -> int:
763+
"""Convert a duration in milliseconds to the nearest number of samples."""
764+
return round(ms * sampling_frequency / 1000.0)

src/spikeinterface/core/generate.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from spikeinterface.core import BaseRecording, BaseRecordingSegment, BaseSorting
1414
from .snippets_tools import snippets_from_sorting
15-
from .core_tools import define_function_from_class
15+
from .core_tools import define_function_from_class, ms_to_samples
1616

1717

1818
def _ensure_seed(seed):
@@ -1598,8 +1598,8 @@ def generate_single_fake_waveform(
15981598
assert ms_after > depolarization_ms + repolarization_ms
15991599
assert ms_before > depolarization_ms
16001600

1601-
nbefore = int(sampling_frequency * ms_before / 1000.0)
1602-
nafter = int(sampling_frequency * ms_after / 1000.0)
1601+
nbefore = ms_to_samples(ms_before, sampling_frequency)
1602+
nafter = ms_to_samples(ms_after, sampling_frequency)
16031603
width = nbefore + nafter
16041604
wf = np.zeros(width, dtype=dtype)
16051605

@@ -1776,8 +1776,8 @@ def generate_templates(
17761776

17771777
num_units = units_locations.shape[0]
17781778
num_channels = channel_locations.shape[0]
1779-
nbefore = int(sampling_frequency * ms_before / 1000.0)
1780-
nafter = int(sampling_frequency * ms_after / 1000.0)
1779+
nbefore = ms_to_samples(ms_before, sampling_frequency)
1780+
nafter = ms_to_samples(ms_after, sampling_frequency)
17811781
width = nbefore + nafter
17821782

17831783
if upsample_factor is not None:
@@ -2451,8 +2451,8 @@ def generate_ground_truth_recording(
24512451
upsample_factor = templates.shape[3]
24522452
upsample_vector = rng.integers(0, upsample_factor, size=num_spikes)
24532453

2454-
nbefore = int(ms_before * sampling_frequency / 1000.0)
2455-
nafter = int(ms_after * sampling_frequency / 1000.0)
2454+
nbefore = ms_to_samples(ms_before, sampling_frequency)
2455+
nafter = ms_to_samples(ms_after, sampling_frequency)
24562456
assert (nbefore + nafter) == templates.shape[1]
24572457

24582458
# construct recording

src/spikeinterface/core/node_pipeline.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from spikeinterface.core import BaseRecording, get_chunk_with_margin
1515
from spikeinterface.core.job_tools import ChunkRecordingExecutor, fix_job_kwargs, _shared_job_kwargs_doc
1616
from spikeinterface.core import get_channel_distances
17+
from spikeinterface.core.core_tools import ms_to_samples
1718

1819

1920
class PipelineNode:
@@ -314,8 +315,8 @@ def __init__(
314315
PipelineNode.__init__(self, recording=recording, parents=parents, return_output=return_output)
315316
self.ms_before = ms_before
316317
self.ms_after = ms_after
317-
self.nbefore = int(ms_before * recording.get_sampling_frequency() / 1000.0)
318-
self.nafter = int(ms_after * recording.get_sampling_frequency() / 1000.0)
318+
self.nbefore = ms_to_samples(ms_before, recording.get_sampling_frequency())
319+
self.nafter = ms_to_samples(ms_after, recording.get_sampling_frequency())
319320
self.neighbours_mask = None
320321

321322

src/spikeinterface/core/sparsity.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .sorting_tools import random_spikes_selection
88
from .job_tools import _shared_job_kwargs_doc
99
from .waveform_tools import estimate_templates_with_accumulator
10+
from .core_tools import ms_to_samples
1011

1112
_sparsity_doc = """
1213
method : str
@@ -784,8 +785,8 @@ def estimate_sparsity(
784785
probe = recording.create_dummy_probe_from_locations(chan_locs)
785786

786787
if method != "by_property":
787-
nbefore = int(ms_before * recording.sampling_frequency / 1000.0)
788-
nafter = int(ms_after * recording.sampling_frequency / 1000.0)
788+
nbefore = ms_to_samples(ms_before, recording.sampling_frequency)
789+
nafter = ms_to_samples(ms_after, recording.sampling_frequency)
789790

790791
num_samples = [recording.get_num_samples(seg_index) for seg_index in range(recording.get_num_segments())]
791792
random_spikes_indices = random_spikes_selection(

src/spikeinterface/core/tests/test_loading.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
generate_ground_truth_recording,
66
create_sorting_analyzer,
77
load,
8+
ms_to_samples,
89
SortingAnalyzer,
910
Templates,
1011
aggregate_channels,
@@ -71,7 +72,7 @@ def generate_templates_object():
7172
templates = Templates(
7273
templates_array=templates_arr,
7374
sampling_frequency=sampling_frequency,
74-
nbefore=int(ms_before * sampling_frequency / 1000),
75+
nbefore=ms_to_samples(ms_before, sampling_frequency),
7576
probe=probe,
7677
)
7778
return templates

0 commit comments

Comments
 (0)