Skip to content

Commit 72cff8f

Browse files
committed
Merge branch 'main' of github.com:SpikeInterface/spikeinterface into rtd-icons
2 parents 4d52f77 + ad467e3 commit 72cff8f

9 files changed

Lines changed: 89 additions & 12 deletions

File tree

doc/api.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,6 @@ spikeinterface.comparison
277277

278278
.. autoclass:: CollisionGTComparison
279279
.. autoclass:: CorrelogramGTComparison
280-
.. autoclass:: CollisionGTStudy
281-
.. autoclass:: CorrelogramGTStudy
282280

283281

284282

src/spikeinterface/comparison/basecomparison.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,10 @@ class MixinSpikeTrainComparison:
283283
* n_jobs
284284
"""
285285

286-
def __init__(self, delta_time=0.4, n_jobs=-1):
286+
def __init__(self, delta_time=0.4, agreement_method="count", n_jobs=-1):
287287
self.delta_time = delta_time
288288
self.n_jobs = n_jobs
289+
self.agreement_method = agreement_method
289290
self.sampling_frequency = None
290291
self.delta_frames = None
291292

src/spikeinterface/comparison/multicomparisons.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class MultiSortingComparison(BaseMultiComparison, MixinSpikeTrainComparison):
3535
Minimum agreement score to match units
3636
chance_score : float, default: 0.1
3737
Minimum agreement score to for a possible match
38+
agreement_method : "count" | "distance", default: "count"
39+
The method to compute agreement scores. The "count" method computes agreement scores from spike counts.
40+
The "distance" method computes agreement scores from spike time distance functions.
3841
n_jobs : int, default: -1
3942
Number of cores to use in parallel. Uses all available if -1
4043
spiketrain_mode : "union" | "intersection", default: "union"
@@ -60,6 +63,7 @@ def __init__(
6063
delta_time=0.4, # sampling_frequency=None,
6164
match_score=0.5,
6265
chance_score=0.1,
66+
agreement_method="count",
6367
n_jobs=-1,
6468
spiketrain_mode="union",
6569
verbose=False,
@@ -75,7 +79,9 @@ def __init__(
7579
chance_score=chance_score,
7680
verbose=verbose,
7781
)
78-
MixinSpikeTrainComparison.__init__(self, delta_time=delta_time, n_jobs=n_jobs)
82+
MixinSpikeTrainComparison.__init__(
83+
self, delta_time=delta_time, agreement_method=agreement_method, n_jobs=n_jobs
84+
)
7985
self.set_frames_and_frequency(self.object_list)
8086
self._spiketrain_mode = spiketrain_mode
8187
self._spiketrains = None
@@ -93,6 +99,8 @@ def _compare_ij(self, i, j):
9399
sorting2_name=self.name_list[j],
94100
delta_time=self.delta_time,
95101
match_score=self.match_score,
102+
chance_score=self.chance_score,
103+
agreement_method=self.agreement_method,
96104
n_jobs=self.n_jobs,
97105
verbose=False,
98106
)

src/spikeinterface/comparison/tests/test_multisortingcomparison.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ def test_compare_multiple_sorters(setup_module):
4141
)
4242
msc = compare_multiple_sorters([sorting1, sorting2, sorting3], verbose=True)
4343
msc_shuffle = compare_multiple_sorters([sorting3, sorting1, sorting2])
44+
msc_dist = compare_multiple_sorters([sorting3, sorting1, sorting2], agreement_method="distance")
4445

4546
agr = msc._do_agreement_matrix()
4647
agr_shuffle = msc_shuffle._do_agreement_matrix()
48+
agr_dist = msc_dist._do_agreement_matrix()
4749

4850
print(agr)
4951
print(agr_shuffle)
52+
print(agr_dist)
5053

5154
assert len(msc.get_agreement_sorting(minimum_agreement_count=3).get_unit_ids()) == 3
5255
assert len(msc.get_agreement_sorting(minimum_agreement_count=2).get_unit_ids()) == 5
@@ -57,7 +60,14 @@ def test_compare_multiple_sorters(setup_module):
5760
assert len(msc.get_agreement_sorting(minimum_agreement_count=2).get_unit_ids()) == len(
5861
msc_shuffle.get_agreement_sorting(minimum_agreement_count=2).get_unit_ids()
5962
)
63+
assert len(msc.get_agreement_sorting(minimum_agreement_count=3).get_unit_ids()) == len(
64+
msc_dist.get_agreement_sorting(minimum_agreement_count=3).get_unit_ids()
65+
)
66+
assert len(msc.get_agreement_sorting(minimum_agreement_count=2).get_unit_ids()) == len(
67+
msc_dist.get_agreement_sorting(minimum_agreement_count=2).get_unit_ids()
68+
)
6069
assert len(msc.get_agreement_sorting().get_unit_ids()) == len(msc_shuffle.get_agreement_sorting().get_unit_ids())
70+
assert len(msc.get_agreement_sorting().get_unit_ids()) == len(msc_dist.get_agreement_sorting().get_unit_ids())
6171
agreement_2 = msc.get_agreement_sorting(minimum_agreement_count=2, minimum_agreement_count_only=True)
6272
assert np.all([agreement_2.get_unit_property(u, "agreement_number")] == 2 for u in agreement_2.get_unit_ids())
6373

src/spikeinterface/core/baserecordingsnippets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ def split_by(self, property="group", outputs="dict"):
560560
recordings = []
561561
elif outputs == "dict":
562562
recordings = {}
563-
for value in np.unique(values):
563+
for value in np.unique(values).tolist():
564564
(inds,) = np.nonzero(values == value)
565565
new_channel_ids = self.get_channel_ids()[inds]
566566
subrec = self.select_channels(new_channel_ids)

src/spikeinterface/core/sparsity.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
method : str
1616
* "best_channels" : N best channels with the largest amplitude. Use the "num_channels" argument to specify the
1717
number of channels.
18+
* "closest_channels" : N closest channels according to the distance. Use the "num_channels" argument to specify the
19+
number of channels.
1820
* "radius" : radius around the best channel. Use the "radius_um" argument to specify the radius in um.
1921
* "snr" : threshold based on template signal-to-noise ratio. Use the "threshold" argument
2022
to specify the SNR threshold (in units of noise levels) and the "amplitude_mode" argument
@@ -320,6 +322,39 @@ def from_best_channels(
320322
mask[unit_ind, chan_inds] = True
321323
return cls(mask, templates_or_sorting_analyzer.unit_ids, templates_or_sorting_analyzer.channel_ids)
322324

325+
## Some convinient function to compute sparsity from several strategy
326+
@classmethod
327+
def from_closest_channels(cls, templates_or_sorting_analyzer, num_channels):
328+
"""
329+
Construct sparsity from N closest channels
330+
Use the "num_channels" argument to specify the number of channels.
331+
332+
Parameters
333+
----------
334+
templates_or_sorting_analyzer : Templates | SortingAnalyzer
335+
A Templates or a SortingAnalyzer object.
336+
num_channels : int
337+
Number of channels for "best_channels" method.
338+
339+
Returns
340+
-------
341+
sparsity : ChannelSparsity
342+
The estimated sparsity
343+
"""
344+
from .template_tools import get_template_amplitudes
345+
346+
mask = np.zeros(
347+
(templates_or_sorting_analyzer.unit_ids.size, templates_or_sorting_analyzer.channel_ids.size), dtype="bool"
348+
)
349+
channel_locations = templates_or_sorting_analyzer.get_channel_locations()
350+
distances = np.linalg.norm(channel_locations[:, np.newaxis] - channel_locations[np.newaxis, :], axis=2)
351+
352+
for unit_ind, unit_id in enumerate(templates_or_sorting_analyzer.unit_ids):
353+
chan_inds = np.argsort(distances[unit_ind])
354+
chan_inds = chan_inds[:num_channels]
355+
mask[unit_ind, chan_inds] = True
356+
return cls(mask, templates_or_sorting_analyzer.unit_ids, templates_or_sorting_analyzer.channel_ids)
357+
323358
@classmethod
324359
def from_radius(cls, templates_or_sorting_analyzer, radius_um, peak_sign="neg"):
325360
"""
@@ -600,7 +635,9 @@ def create_dense(cls, sorting_analyzer):
600635
def compute_sparsity(
601636
templates_or_sorting_analyzer: "Templates | SortingAnalyzer",
602637
noise_levels: np.ndarray | None = None,
603-
method: "radius" | "best_channels" | "snr" | "amplitude" | "energy" | "by_property" | "ptp" = "radius",
638+
method: (
639+
"radius" | "best_channels" | "closest_channels" | "snr" | "amplitude" | "energy" | "by_property" | "ptp"
640+
) = "radius",
604641
peak_sign: "neg" | "pos" | "both" = "neg",
605642
num_channels: int | None = 5,
606643
radius_um: float | None = 100.0,
@@ -635,7 +672,7 @@ def compute_sparsity(
635672
# to keep backward compatibility
636673
templates_or_sorting_analyzer = templates_or_sorting_analyzer.sorting_analyzer
637674

638-
if method in ("best_channels", "radius", "snr", "amplitude", "ptp"):
675+
if method in ("best_channels", "closest_channels", "radius", "snr", "amplitude", "ptp"):
639676
assert isinstance(
640677
templates_or_sorting_analyzer, (Templates, SortingAnalyzer)
641678
), f"compute_sparsity(method='{method}') need Templates or SortingAnalyzer"
@@ -647,6 +684,9 @@ def compute_sparsity(
647684
if method == "best_channels":
648685
assert num_channels is not None, "For the 'best_channels' method, 'num_channels' needs to be given"
649686
sparsity = ChannelSparsity.from_best_channels(templates_or_sorting_analyzer, num_channels, peak_sign=peak_sign)
687+
elif method == "closest_channels":
688+
assert num_channels is not None, "For the 'closest_channels' method, 'num_channels' needs to be given"
689+
sparsity = ChannelSparsity.from_closest_channels(templates_or_sorting_analyzer, num_channels)
650690
elif method == "radius":
651691
assert radius_um is not None, "For the 'radius' method, 'radius_um' needs to be given"
652692
sparsity = ChannelSparsity.from_radius(templates_or_sorting_analyzer, radius_um, peak_sign=peak_sign)
@@ -698,7 +738,7 @@ def estimate_sparsity(
698738
num_spikes_for_sparsity: int = 100,
699739
ms_before: float = 1.0,
700740
ms_after: float = 2.5,
701-
method: "radius" | "best_channels" | "amplitude" | "snr" | "by_property" | "ptp" = "radius",
741+
method: "radius" | "best_channels" | "closest_channels" | "amplitude" | "snr" | "by_property" | "ptp" = "radius",
702742
peak_sign: "neg" | "pos" | "both" = "neg",
703743
radius_um: float = 100.0,
704744
num_channels: int = 5,
@@ -747,7 +787,7 @@ def estimate_sparsity(
747787
# Can't be done at module because this is a cyclic import, too bad
748788
from .template import Templates
749789

750-
assert method in ("radius", "best_channels", "snr", "amplitude", "by_property", "ptp"), (
790+
assert method in ("radius", "best_channels", "closest_channels", "snr", "amplitude", "by_property", "ptp"), (
751791
f"method={method} is not available for `estimate_sparsity()`. "
752792
"Available methods are 'radius', 'best_channels', 'snr', 'amplitude', 'by_property', 'ptp' (deprecated)"
753793
)
@@ -802,6 +842,9 @@ def estimate_sparsity(
802842
sparsity = ChannelSparsity.from_best_channels(
803843
templates, num_channels, peak_sign=peak_sign, amplitude_mode=amplitude_mode
804844
)
845+
elif method == "closest_channels":
846+
assert num_channels is not None, "For the 'closest_channels' method, 'num_channels' needs to be given"
847+
sparsity = ChannelSparsity.from_closest_channels(templates, num_channels)
805848
elif method == "radius":
806849
assert radius_um is not None, "For the 'radius' method, 'radius_um' needs to be given"
807850
sparsity = ChannelSparsity.from_radius(templates, radius_um, peak_sign=peak_sign)

src/spikeinterface/core/tests/test_sparsity.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,21 @@ def test_estimate_sparsity():
195195
)
196196
assert np.array_equal(np.sum(sparsity.mask, axis=1), np.ones(num_units) * 3)
197197

198+
# closest_channels : the mask should exactly 3 channels per units
199+
sparsity = estimate_sparsity(
200+
sorting,
201+
recording,
202+
num_spikes_for_sparsity=50,
203+
ms_before=1.0,
204+
ms_after=2.0,
205+
method="closest_channels",
206+
num_channels=3,
207+
chunk_duration="1s",
208+
progress_bar=True,
209+
n_jobs=1,
210+
)
211+
assert np.array_equal(np.sum(sparsity.mask, axis=1), np.ones(num_units) * 3)
212+
198213
# by_property
199214
sparsity = estimate_sparsity(
200215
sorting,
@@ -287,6 +302,7 @@ def test_compute_sparsity():
287302
# using object SortingAnalyzer
288303
sparsity = compute_sparsity(sorting_analyzer, method="best_channels", num_channels=2, peak_sign="neg")
289304
sparsity = compute_sparsity(sorting_analyzer, method="radius", radius_um=50.0, peak_sign="neg")
305+
sparsity = compute_sparsity(sorting_analyzer, method="closest_channels", num_channels=2)
290306
sparsity = compute_sparsity(sorting_analyzer, method="snr", threshold=5, peak_sign="neg")
291307
sparsity = compute_sparsity(
292308
sorting_analyzer, method="snr", threshold=5, peak_sign="neg", amplitude_mode="peak_to_peak"
@@ -304,6 +320,7 @@ def test_compute_sparsity():
304320
sparsity = compute_sparsity(templates, method="radius", radius_um=50.0, peak_sign="neg")
305321
sparsity = compute_sparsity(templates, method="snr", noise_levels=noise_levels, threshold=5, peak_sign="neg")
306322
sparsity = compute_sparsity(templates, method="amplitude", threshold=5, amplitude_mode="peak_to_peak")
323+
sparsity = compute_sparsity(templates, method="closest_channels", num_channels=2)
307324

308325
with pytest.warns(DeprecationWarning):
309326
sparsity = compute_sparsity(templates, method="ptp", noise_levels=noise_levels, threshold=5)

src/spikeinterface/extractors/neoextractors/openephys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def __init__(
164164
**neo_kwargs,
165165
)
166166
# get streams to find correct probe
167-
stream_names, stream_ids = self.get_streams(folder_path, experiment_names)
167+
stream_names, stream_ids = self.get_streams(folder_path, load_sync_channel, experiment_names)
168168
if stream_name is None and stream_id is None:
169169
stream_name = stream_names[0]
170170
elif stream_name is None:

src/spikeinterface/postprocessing/localization_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def solve_monopolar_triangulation(wf_data, local_contact_locations, max_distance
363363
output = scipy.optimize.least_squares(estimate_distance_error, x0=x0, bounds=bounds, args=args)
364364
return tuple(output["x"])
365365
except Exception as e:
366-
print(f"scipy.optimize.least_squares error: {e}")
366+
warnings.warn(f"scipy.optimize.least_squares error: {e}")
367367
return (np.nan, np.nan, np.nan, np.nan)
368368

369369
if optimizer == "minimize_with_log_penality":
@@ -378,7 +378,7 @@ def solve_monopolar_triangulation(wf_data, local_contact_locations, max_distance
378378
alpha = (wf_data * q).sum() / np.square(q).sum()
379379
return (*output["x"], alpha)
380380
except Exception as e:
381-
print(f"scipy.optimize.minimize error: {e}")
381+
warnings.warn(f"scipy.optimize.minimize error: {e}")
382382
return (np.nan, np.nan, np.nan, np.nan)
383383

384384

0 commit comments

Comments
 (0)