Skip to content

Commit 2eaa282

Browse files
committed
Add BaseRasterWidget
1 parent c5d3055 commit 2eaa282

1 file changed

Lines changed: 263 additions & 0 deletions

File tree

src/spikeinterface/widgets/rasters.py

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,269 @@
44
from warnings import warn
55

66
from .base import BaseWidget, to_attr, default_backend_kwargs
7+
from . import get_some_colors
8+
9+
10+
class BaseRasterWidget(BaseWidget):
11+
"""
12+
Make a raster plot with spike times on the x axis and arbritary data on the y axis.
13+
Can customise plot with histograms, title, labels, ticks etc.
14+
15+
16+
Parameters
17+
----------
18+
spike_train_data : dict
19+
A dict of spike trains, indexed by the unit_id
20+
y_axis_data : dict
21+
A dict of the y-axis data, indexed by the unit_id
22+
unit_ids : array-like | None, default: None
23+
List of unit_ids to plot
24+
total_duration : int | None, default: None
25+
Duration of spike_train_data in seconds.
26+
plot_histograms : bool, default: False
27+
Plot histogram of y-axis data in another subplot
28+
bins : int | None, default: None
29+
Number of bins to use in histogram. If None, use 1/30 of spike train sample length.
30+
scatter_decimate : int | None, default: None
31+
If > 1, the scatter points are decimated.
32+
unit_colors : dict | None, default: None
33+
Dict of colors with key : unit, value : color. If None, uses `get_some_colors` to
34+
generate colors.
35+
color_kwargs : dict | None, default: None
36+
More color control for e.g. colouring spikes by property. Passed to `maplotlib.scatter`.
37+
plot_legend : bool, default: False
38+
If True, the legend is plotted
39+
x_lim : tuple or None, default: None
40+
The min and max width to display, if None use (0, total_duration)
41+
y_lim : tuple or None, default: None
42+
The min and max depth to display, if None use the min and max of y_axis_data.
43+
title : str | None, default: None
44+
Title of plot. If None, no title is displayed.
45+
y_label : str | None, default: None
46+
Label of y-axis. If None, no label is displayed.
47+
y_ticks : dict | None, default: None
48+
Ticks on y-axis, passed to `set_yticks`. If None, default ticks are used.
49+
hide_unit_selector : bool, default: False
50+
For sortingview backend, if True the unit selector is not displayed
51+
backend : str | None, default None
52+
Which plotting backend to use e.g. 'matplotlib', 'ipywidgets'. If None, uses
53+
default from `get_default_plotter_backend`.
54+
"""
55+
56+
def __init__(
57+
self,
58+
spike_train_data: dict,
59+
y_axis_data: dict,
60+
unit_ids: list | None = None,
61+
total_duration: int | None = None,
62+
plot_histograms: bool = False,
63+
bins: int | None = None,
64+
scatter_decimate: int = 1,
65+
unit_colors: dict | None = None,
66+
color_kwargs: dict | None = None,
67+
plot_legend: bool | None = False,
68+
y_lim: tuple[float, float] | None = None,
69+
x_lim: tuple[float, float] | None = None,
70+
title: str | None = None,
71+
y_label: str | None = None,
72+
y_ticks: bool = False,
73+
hide_unit_selector: bool = True,
74+
backend: str | None = None,
75+
**backend_kwargs,
76+
):
77+
78+
plot_data = dict(
79+
spike_train_data=spike_train_data,
80+
y_axis_data=y_axis_data,
81+
unit_ids=unit_ids,
82+
plot_histograms=plot_histograms,
83+
y_lim=y_lim,
84+
x_lim=x_lim,
85+
scatter_decimate=scatter_decimate,
86+
color_kwargs=color_kwargs,
87+
unit_colors=unit_colors,
88+
y_label=y_label,
89+
title=title,
90+
total_duration=total_duration,
91+
plot_legend=plot_legend,
92+
bins=bins,
93+
y_ticks=y_ticks,
94+
hide_unit_selector=hide_unit_selector,
95+
)
96+
97+
BaseWidget.__init__(self, plot_data, backend=backend, **backend_kwargs)
98+
99+
def plot_matplotlib(self, data_plot, **backend_kwargs):
100+
import matplotlib.pyplot as plt
101+
from matplotlib.colors import Normalize
102+
from .utils_matplotlib import make_mpl_figure
103+
104+
dp = to_attr(data_plot)
105+
106+
if dp.unit_colors is None and dp.color_kwargs is None:
107+
unit_colors = get_some_colors(dp.spike_train_data.keys())
108+
else:
109+
unit_colors = dp.unit_colors
110+
111+
if backend_kwargs["axes"] is not None:
112+
axes = backend_kwargs["axes"]
113+
if dp.plot_histograms:
114+
assert np.asarray(axes).size == 2
115+
else:
116+
assert np.asarray(axes).size == 1
117+
elif backend_kwargs["ax"] is not None:
118+
assert not dp.plot_histograms
119+
else:
120+
if dp.plot_histograms:
121+
backend_kwargs["num_axes"] = 2
122+
backend_kwargs["ncols"] = 2
123+
else:
124+
backend_kwargs["num_axes"] = None
125+
126+
unit_ids = dp.unit_ids
127+
if dp.unit_ids is None:
128+
unit_ids = dp.spike_train_data.keys()
129+
130+
self.figure, self.axes, self.ax = make_mpl_figure(**backend_kwargs)
131+
scatter_ax = self.axes.flatten()[0]
132+
133+
spike_train_data = dp.spike_train_data
134+
y_axis_data = dp.y_axis_data
135+
136+
for unit_id in unit_ids:
137+
138+
unit_spike_train = spike_train_data[unit_id][:: dp.scatter_decimate]
139+
unit_y_data = y_axis_data[unit_id][:: dp.scatter_decimate]
140+
141+
if dp.color_kwargs is None:
142+
scatter_ax.scatter(unit_spike_train, unit_y_data, s=1, label=unit_id, color=unit_colors[unit_id])
143+
else:
144+
if dp.scatter_decimate != 1:
145+
color_kwargs = dp.color_kwargs
146+
color_kwargs["c"] = dp.color_kwargs["c"][:: dp.scatter_decimate]
147+
scatter_ax.scatter(unit_spike_train, unit_y_data, s=1, label=unit_id, **color_kwargs)
148+
149+
if dp.plot_histograms:
150+
if dp.bins is None:
151+
bins = int(len(unit_spike_train) / 30)
152+
else:
153+
bins = dp.bins
154+
ax_hist = self.axes.flatten()[1]
155+
count, bins = np.histogram(unit_y_data, bins=bins)
156+
ax_hist.plot(count, bins[:-1], color=unit_colors[unit_id], alpha=0.8)
157+
158+
if dp.plot_histograms:
159+
ax_hist = self.axes.flatten()[1]
160+
ax_hist.set_ylim(scatter_ax.get_ylim())
161+
ax_hist.axis("off")
162+
163+
if dp.plot_legend:
164+
if hasattr(self, "legend") and self.legend is not None:
165+
self.legend.remove()
166+
self.legend = self.figure.legend(
167+
loc="upper center", bbox_to_anchor=(0.5, 1.0), ncol=5, fancybox=True, shadow=True
168+
)
169+
170+
if dp.y_lim is not None:
171+
scatter_ax.set_ylim(*dp.y_lim)
172+
x_lim = dp.x_lim
173+
if x_lim is None:
174+
x_lim = [0, dp.total_duration]
175+
scatter_ax.set_xlim(x_lim)
176+
177+
if dp.y_ticks:
178+
scatter_ax.set_yticks(**dp.y_ticks)
179+
180+
scatter_ax.set_title(dp.title)
181+
scatter_ax.set_xlabel("Times [s]")
182+
scatter_ax.set_ylabel(dp.y_label)
183+
scatter_ax.spines["top"].set_visible(False)
184+
scatter_ax.spines["right"].set_visible(False)
185+
186+
def plot_ipywidgets(self, data_plot, **backend_kwargs):
187+
import matplotlib.pyplot as plt
188+
189+
import ipywidgets.widgets as W
190+
from IPython.display import display
191+
from spikeinterface.widgets.utils_ipywidgets import check_ipywidget_backend, UnitSelector
192+
193+
check_ipywidget_backend()
194+
195+
self.next_data_plot = data_plot.copy()
196+
197+
cm = 1 / 2.54
198+
199+
width_cm = backend_kwargs["width_cm"]
200+
height_cm = backend_kwargs["height_cm"]
201+
202+
ratios = [0.15, 0.85]
203+
204+
with plt.ioff():
205+
output = W.Output()
206+
with output:
207+
self.figure = plt.figure(figsize=((ratios[1] * width_cm) * cm, height_cm * cm))
208+
plt.show()
209+
210+
self.unit_selector = UnitSelector(list(data_plot["spike_train_data"].keys()))
211+
self.unit_selector.value = list(data_plot["spike_train_data"].keys())[:1]
212+
213+
children = [self.unit_selector]
214+
215+
if data_plot["plot_histograms"] is not None:
216+
self.checkbox_histograms = W.Checkbox(
217+
value=data_plot["plot_histograms"],
218+
description="hist",
219+
)
220+
children.append(self.checkbox_histograms)
221+
222+
left_sidebar = W.VBox(
223+
children=children,
224+
layout=W.Layout(align_items="center", width="100%", height="100%"),
225+
)
226+
227+
self.widget = W.AppLayout(
228+
center=self.figure.canvas,
229+
left_sidebar=left_sidebar,
230+
pane_widths=ratios + [0],
231+
)
232+
233+
# a first update
234+
self._full_update_plot()
235+
236+
self.unit_selector.observe(self._update_plot, names="value", type="change")
237+
if data_plot["plot_histograms"] is not None:
238+
self.checkbox_histograms.observe(self._full_update_plot, names="value", type="change")
239+
240+
if backend_kwargs["display"]:
241+
display(self.widget)
242+
243+
def _full_update_plot(self, change=None):
244+
self.figure.clear()
245+
data_plot = self.next_data_plot
246+
data_plot["unit_ids"] = self.unit_selector.value
247+
if data_plot["plot_histograms"] is not None:
248+
data_plot["plot_histograms"] = self.checkbox_histograms.value
249+
data_plot["plot_legend"] = False
250+
251+
backend_kwargs = dict(figure=self.figure, axes=None, ax=None)
252+
self.plot_matplotlib(data_plot, **backend_kwargs)
253+
self._update_plot()
254+
255+
def _update_plot(self, change=None):
256+
for ax in self.axes.flatten():
257+
ax.clear()
258+
259+
data_plot = self.next_data_plot
260+
data_plot["unit_ids"] = self.unit_selector.value
261+
if data_plot["plot_histograms"] is not None:
262+
data_plot["plot_histograms"] = self.checkbox_histograms.value
263+
data_plot["plot_legend"] = False
264+
265+
backend_kwargs = dict(figure=None, axes=self.axes, ax=None)
266+
self.plot_matplotlib(data_plot, **backend_kwargs)
267+
268+
self.figure.canvas.draw()
269+
self.figure.canvas.flush_events()
7270

8271

9272
class RasterWidget(BaseWidget):

0 commit comments

Comments
 (0)