|
4 | 4 | from warnings import warn |
5 | 5 |
|
6 | 6 | 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() |
7 | 270 |
|
8 | 271 |
|
9 | 272 | class RasterWidget(BaseWidget): |
|
0 commit comments