Skip to content

Commit 253efc0

Browse files
authored
Merge pull request #3717 from yger/new_sparsity
New sparsity (to add Ks clustering in components)
2 parents 966fb08 + a7aacf5 commit 253efc0

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

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)

0 commit comments

Comments
 (0)