Skip to content

Commit d5847f7

Browse files
authored
Merge branch 'main' into fix_chris_bug
2 parents e470715 + 16b71c9 commit d5847f7

21 files changed

Lines changed: 723 additions & 305 deletions

src/spikeinterface/core/node_pipeline.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ def __init__(
376376
parents: Optional[list[PipelineNode]] = None,
377377
return_output: bool = False,
378378
radius_um: float = 100.0,
379+
sparsity_mask: np.ndarray = None,
379380
):
380381
"""
381382
Extract sparse waveforms from a recording. The strategy in this specific node is to reshape the waveforms
@@ -400,6 +401,11 @@ def __init__(
400401
Pass parents nodes to perform a previous computation
401402
return_output : bool, default: False
402403
Whether or not the output of the node is returned by the pipeline
404+
radius_um : float, default: 100.0
405+
The radius to determine the neighborhood of channels to extract waveforms from.
406+
sparsity_mask : np.ndarray, default: None
407+
Optional mask to specify the sparsity of the waveforms. If provided, it should be a boolean array of shape
408+
(num_channels, num_channels) where True indicates that the channel is active in the neighborhood.
403409
"""
404410
WaveformsNode.__init__(
405411
self,
@@ -410,10 +416,15 @@ def __init__(
410416
return_output=return_output,
411417
)
412418

413-
self.radius_um = radius_um
414419
self.contact_locations = recording.get_channel_locations()
415420
self.channel_distance = get_channel_distances(recording)
416-
self.neighbours_mask = self.channel_distance <= radius_um
421+
422+
if sparsity_mask is not None:
423+
self.neighbours_mask = sparsity_mask
424+
self.radius_um = None
425+
else:
426+
self.radius_um = radius_um
427+
self.neighbours_mask = self.channel_distance <= radius_um
417428
self.max_num_chans = np.max(np.sum(self.neighbours_mask, axis=1))
418429

419430
def get_trace_margin(self):

src/spikeinterface/postprocessing/spike_amplitudes.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ class ComputeSpikeAmplitudes(AnalyzerExtension):
3131
spike_retriver_kwargs : dict
3232
A dictionary to control the behavior for getting the maximum channel for each spike
3333
This dictionary contains:
34-
3534
* channel_from_template: bool, default: True
3635
For each spike is the maximum channel computed from template or re estimated at every spikes
3736
channel_from_template = True is old behavior but less acurate

src/spikeinterface/sortingcomponents/clustering/method_list.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@
2424
"circus": CircusClustering,
2525
"tdc_clustering": TdcClustering,
2626
}
27+
28+
try:
29+
# Kilosort licence (GPL 3) is forcing us to make and use an external package
30+
from spikeinterface_kilosort_components import KiloSortClustering
31+
32+
clustering_methods["kilosort_clustering"] = KiloSortClustering
33+
except ImportError:
34+
pass

src/spikeinterface/widgets/all_amplitudes_distributions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ class AllAmplitudesDistributionsWidget(BaseWidget):
1919
The SortingAnalyzer
2020
unit_ids : list
2121
List of unit ids, default None
22-
unit_colors : None or dict
23-
Dict of colors with key : unit, value : color, default None
22+
unit_colors : dict | None, default: None
23+
Dict of colors with unit ids as keys and colors as values. Colors can be any type accepted
24+
by matplotlib. If None, default colors are chosen using the `get_some_colors` function.
2425
"""
2526

2627
def __init__(

src/spikeinterface/widgets/amplitudes.py

Lines changed: 30 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import numpy as np
44
from warnings import warn
55

6+
from .rasters import BaseRasterWidget
67
from .base import BaseWidget, to_attr
78
from .utils import get_some_colors
89

910
from ..core.sortinganalyzer import SortingAnalyzer
1011

12+
from spikeinterface.core import SortingAnalyzer
1113

12-
class AmplitudesWidget(BaseWidget):
14+
15+
class AmplitudesWidget(BaseRasterWidget):
1316
"""
1417
Plots spike amplitudes
1518
@@ -19,10 +22,17 @@ class AmplitudesWidget(BaseWidget):
1922
The input waveform extractor
2023
unit_ids : list or None, default: None
2124
List of unit ids
25+
unit_colors : dict | None, default: None
26+
Dict of colors with unit ids as keys and colors as values. Colors can be any type accepted
27+
by matplotlib. If None, default colors are chosen using the `get_some_colors` function.
2228
segment_index : int or None, default: None
2329
The segment index (or None if mono-segment)
2430
max_spikes_per_unit : int or None, default: None
2531
Number of max spikes per unit to display. Use None for all spikes
32+
y_lim : tuple or None, default: None
33+
The min and max depth to display, if None (min and max of the amplitudes).
34+
scatter_decimate : int, default: 1
35+
If equal to n, each nth spike is kept for plotting.
2636
hide_unit_selector : bool, default: False
2737
If True the unit selector is not displayed
2838
(sortingview backend)
@@ -31,7 +41,7 @@ class AmplitudesWidget(BaseWidget):
3141
(matplotlib backend)
3242
bins : int or None, default: None
3343
If plot_histogram is True, the number of bins for the amplitude histogram.
34-
If None this is automatically adjusted
44+
If None, uses 100 bins.
3545
plot_legend : bool, default: True
3646
True includes legend in plot
3747
"""
@@ -43,6 +53,8 @@ def __init__(
4353
unit_colors=None,
4454
segment_index=None,
4555
max_spikes_per_unit=None,
56+
y_lim=None,
57+
scatter_decimate=1,
4658
hide_unit_selector=False,
4759
plot_histograms=False,
4860
bins=None,
@@ -61,25 +73,21 @@ def __init__(
6173
if unit_ids is None:
6274
unit_ids = sorting.unit_ids
6375

64-
if unit_colors is None:
65-
unit_colors = get_some_colors(sorting.unit_ids)
66-
6776
if sorting.get_num_segments() > 1:
6877
if segment_index is None:
69-
warn("More than one segment available! Using segment_index 0")
78+
warn("More than one segment available! Using `segment_index = 0`.")
7079
segment_index = 0
7180
else:
7281
segment_index = 0
82+
7383
amplitudes_segment = amplitudes[segment_index]
7484
total_duration = sorting_analyzer.get_num_samples(segment_index) / sorting_analyzer.sampling_frequency
7585

76-
spiketrains_segment = {}
77-
for i, unit_id in enumerate(sorting.unit_ids):
78-
times = sorting.get_unit_spike_train(unit_id, segment_index=segment_index)
79-
times = times / sorting.get_sampling_frequency()
80-
spiketrains_segment[unit_id] = times
86+
all_spiketrains = {
87+
unit_id: sorting.get_unit_spike_train(unit_id, segment_index=segment_index, return_times=True)
88+
for unit_id in sorting.unit_ids
89+
}
8190

82-
all_spiketrains = spiketrains_segment
8391
all_amplitudes = amplitudes_segment
8492
if max_spikes_per_unit is not None:
8593
spiketrains_to_plot = dict()
@@ -101,163 +109,21 @@ def __init__(
101109
bins = 100
102110

103111
plot_data = dict(
104-
sorting_analyzer=sorting_analyzer,
105-
amplitudes=amplitudes_to_plot,
106-
unit_ids=unit_ids,
112+
spike_train_data=spiketrains_to_plot,
113+
y_axis_data=amplitudes_to_plot,
107114
unit_colors=unit_colors,
108-
spiketrains=spiketrains_to_plot,
109-
total_duration=total_duration,
110115
plot_histograms=plot_histograms,
111116
bins=bins,
117+
total_duration=total_duration,
118+
unit_ids=unit_ids,
112119
hide_unit_selector=hide_unit_selector,
113120
plot_legend=plot_legend,
121+
y_label="Amplitude",
122+
y_lim=y_lim,
123+
scatter_decimate=scatter_decimate,
114124
)
115125

116-
BaseWidget.__init__(self, plot_data, backend=backend, **backend_kwargs)
117-
118-
def plot_matplotlib(self, data_plot, **backend_kwargs):
119-
import matplotlib.pyplot as plt
120-
from .utils_matplotlib import make_mpl_figure
121-
122-
dp = to_attr(data_plot)
123-
124-
if backend_kwargs["axes"] is not None:
125-
axes = backend_kwargs["axes"]
126-
if dp.plot_histograms:
127-
assert np.asarray(axes).size == 2
128-
else:
129-
assert np.asarray(axes).size == 1
130-
elif backend_kwargs["ax"] is not None:
131-
assert not dp.plot_histograms
132-
else:
133-
if dp.plot_histograms:
134-
backend_kwargs["num_axes"] = 2
135-
backend_kwargs["ncols"] = 2
136-
else:
137-
backend_kwargs["num_axes"] = None
138-
139-
self.figure, self.axes, self.ax = make_mpl_figure(**backend_kwargs)
140-
141-
scatter_ax = self.axes.flatten()[0]
142-
143-
for unit_id in dp.unit_ids:
144-
spiketrains = dp.spiketrains[unit_id]
145-
amps = dp.amplitudes[unit_id]
146-
scatter_ax.scatter(spiketrains, amps, color=dp.unit_colors[unit_id], s=3, alpha=1, label=unit_id)
147-
148-
if dp.plot_histograms:
149-
if dp.bins is None:
150-
bins = int(len(spiketrains) / 30)
151-
else:
152-
bins = dp.bins
153-
ax_hist = self.axes.flatten()[1]
154-
# this is super slow, using plot and np.histogram is really much faster (and nicer!)
155-
# ax_hist.hist(amps, bins=bins, orientation="horizontal", color=dp.unit_colors[unit_id], alpha=0.8)
156-
count, bins = np.histogram(amps, bins=bins)
157-
ax_hist.plot(count, bins[:-1], color=dp.unit_colors[unit_id], alpha=0.8)
158-
159-
if dp.plot_histograms:
160-
ax_hist = self.axes.flatten()[1]
161-
ax_hist.set_ylim(scatter_ax.get_ylim())
162-
ax_hist.axis("off")
163-
# self.figure.tight_layout()
164-
165-
if dp.plot_legend:
166-
if hasattr(self, "legend") and self.legend is not None:
167-
self.legend.remove()
168-
self.legend = self.figure.legend(
169-
loc="upper center", bbox_to_anchor=(0.5, 1.0), ncol=5, fancybox=True, shadow=True
170-
)
171-
172-
scatter_ax.set_xlim(0, dp.total_duration)
173-
scatter_ax.set_xlabel("Times [s]")
174-
scatter_ax.set_ylabel(f"Amplitude")
175-
scatter_ax.spines["top"].set_visible(False)
176-
scatter_ax.spines["right"].set_visible(False)
177-
self.figure.subplots_adjust(bottom=0.1, top=0.9, left=0.1)
178-
179-
def plot_ipywidgets(self, data_plot, **backend_kwargs):
180-
import matplotlib.pyplot as plt
181-
182-
# import ipywidgets.widgets as widgets
183-
import ipywidgets.widgets as W
184-
from IPython.display import display
185-
from .utils_ipywidgets import check_ipywidget_backend, UnitSelector
186-
187-
check_ipywidget_backend()
188-
189-
self.next_data_plot = data_plot.copy()
190-
191-
cm = 1 / 2.54
192-
analyzer = data_plot["sorting_analyzer"]
193-
194-
width_cm = backend_kwargs["width_cm"]
195-
height_cm = backend_kwargs["height_cm"]
196-
197-
ratios = [0.15, 0.85]
198-
199-
with plt.ioff():
200-
output = W.Output()
201-
with output:
202-
self.figure = plt.figure(figsize=((ratios[1] * width_cm) * cm, height_cm * cm))
203-
plt.show()
204-
205-
self.unit_selector = UnitSelector(analyzer.unit_ids)
206-
self.unit_selector.value = list(analyzer.unit_ids)[:1]
207-
208-
self.checkbox_histograms = W.Checkbox(
209-
value=data_plot["plot_histograms"],
210-
description="hist",
211-
)
212-
213-
left_sidebar = W.VBox(
214-
children=[
215-
self.unit_selector,
216-
self.checkbox_histograms,
217-
],
218-
layout=W.Layout(align_items="center", width="100%", height="100%"),
219-
)
220-
221-
self.widget = W.AppLayout(
222-
center=self.figure.canvas,
223-
left_sidebar=left_sidebar,
224-
pane_widths=ratios + [0],
225-
)
226-
227-
# a first update
228-
self._full_update_plot()
229-
230-
self.unit_selector.observe(self._update_plot, names="value", type="change")
231-
self.checkbox_histograms.observe(self._full_update_plot, names="value", type="change")
232-
233-
if backend_kwargs["display"]:
234-
display(self.widget)
235-
236-
def _full_update_plot(self, change=None):
237-
self.figure.clear()
238-
data_plot = self.next_data_plot
239-
data_plot["unit_ids"] = self.unit_selector.value
240-
data_plot["plot_histograms"] = self.checkbox_histograms.value
241-
data_plot["plot_legend"] = False
242-
243-
backend_kwargs = dict(figure=self.figure, axes=None, ax=None)
244-
self.plot_matplotlib(data_plot, **backend_kwargs)
245-
self._update_plot()
246-
247-
def _update_plot(self, change=None):
248-
for ax in self.axes.flatten():
249-
ax.clear()
250-
251-
data_plot = self.next_data_plot
252-
data_plot["unit_ids"] = self.unit_selector.value
253-
data_plot["plot_histograms"] = self.checkbox_histograms.value
254-
data_plot["plot_legend"] = False
255-
256-
backend_kwargs = dict(figure=None, axes=self.axes, ax=None)
257-
self.plot_matplotlib(data_plot, **backend_kwargs)
258-
259-
self.figure.canvas.draw()
260-
self.figure.canvas.flush_events()
126+
BaseRasterWidget.__init__(self, **plot_data, backend=backend, **backend_kwargs)
261127

262128
def plot_sortingview(self, data_plot, **backend_kwargs):
263129
import sortingview.views as vv
@@ -270,8 +136,8 @@ def plot_sortingview(self, data_plot, **backend_kwargs):
270136
sa_items = [
271137
vv.SpikeAmplitudesItem(
272138
unit_id=u,
273-
spike_times_sec=dp.spiketrains[u].astype("float32"),
274-
spike_amplitudes=dp.amplitudes[u].astype("float32"),
139+
spike_times_sec=dp.spike_train_data[u].astype("float32"),
140+
spike_amplitudes=dp.y_axis_data[u].astype("float32"),
275141
)
276142
for u in unit_ids
277143
]

src/spikeinterface/widgets/crosscorrelograms.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ class CrossCorrelogramsWidget(BaseWidget):
3131
this argument is ignored
3232
hide_unit_selector : bool, default: False
3333
For sortingview backend, if True the unit selector is not displayed
34-
unit_colors : dict or None, default: None
35-
If given, a dictionary with unit ids as keys and colors as values
34+
unit_colors : dict | None, default: None
35+
Dict of colors with unit ids as keys and colors as values. Colors can be any type accepted
36+
by matplotlib. If None, default colors are chosen using the `get_some_colors` function.
3637
"""
3738

3839
def __init__(

src/spikeinterface/widgets/metrics.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class MetricsBaseWidget(BaseWidget):
2424
If given, a list of quality metrics to skip, default: None
2525
include_metrics: list or None, default: None
2626
If given, a list of quality metrics to include, default: None
27-
unit_colors : dict or None, default: None
28-
If given, a dictionary with unit ids as keys and colors as values
27+
unit_colors : dict | None, default: None
28+
Dict of colors with unit ids as keys and colors as values. Colors can be any type accepted
29+
by matplotlib. If None, default colors are chosen using the `get_some_colors` function.
2930
hide_unit_selector : bool, default: False
3031
For sortingview backend, if True the unit selector is not displayed
3132
include_metrics_data : bool, default: True

0 commit comments

Comments
 (0)