How to visualize ECoG data
All interactive components live in cogpy.plot.hv and require a Bokeh backend.
Call hv.extension("bokeh") once at the top of your notebook.
import holoviews as hv
import panel as pn
hv.extension("bokeh")
pn.extension()
Stacked multichannel traces
multichannel_view accepts (time, ch) or (time, AP, ML) signals and
renders stacked traces with a linked minimap.
from cogpy.plot.hv import multichannel_view
# Works with flat (time, ch) or grid (time, AP, ML) signals
view = multichannel_view(sig)
view
Control the initial visible window with boundsx:
view = multichannel_view(sig, boundsx=(0.0, 2.0), title="LFP")
The minimap below the traces is a z-scored image; drag the range box to pan, resize it to zoom.
Spatial grid movie
grid_movie animates an (AP, ML) image across remaining dimensions (e.g.,
time or freq). HoloViews generates a slider for each extra dimension.
from cogpy.plot.hv import grid_movie
# sig_grid has dims (time, AP, ML)
movie = grid_movie(sig_grid, cmap="RdBu_r", symmetric=True)
movie
For a spectrogram (freq, AP, ML):
movie = grid_movie(spectrogram, x_dim="ML", y_dim="AP", cmap="viridis", symmetric=False)
Grid movie with linked time cursor
grid_movie_with_time_curve combines a spatial frame (top) with a 1D summary
curve (bottom). Clicking the curve moves the time cursor and updates the image.
from cogpy.plot.hv import grid_movie_with_time_curve
layout = grid_movie_with_time_curve(sig_grid, time_dim="time")
layout
To react to the selected time programmatically:
layout, ctrl = grid_movie_with_time_curve(sig_grid, return_controller=True)
ctrl.param.watch(lambda e: print("t =", e.new), "t")
layout
For a 4D spectrogram (time, freq, AP, ML), fix the frequency slice with
indexers:
layout = grid_movie_with_time_curve(
spec4d, time_dim="time", indexers={"freq": 80.0}
)
Per-electrode scalar heatmap (TopoMap)
TopoMap renders a single scalar per electrode as an AP×ML heatmap using
Bokeh directly (no HoloViews required). Typical inputs: RMS power, z-score,
bad-channel flag.
from cogpy.plot.hv.topomap import TopoMap
import numpy as np
# values: shape (n_ap, n_ml)
values = sig.std(dim="time").values # e.g. per-electrode RMS
tmap = TopoMap(
values,
ap_coords=sig.coords["AP"].values,
ml_coords=sig.coords["ML"].values,
colormap="viridis",
title="RMS power",
)
pn.panel(tmap.figure).servable()
Use symmetric=True for diverging quantities (e.g. z-score):
TopoMap(zscores, symmetric=True, colormap="coolwarm")
Interactive orthoslicer (time × frequency × space)
OrthoSlicerRanger provides a linked 3-panel view for 4D data
(time, freq, AP, ML): a time–frequency spectrogram (TZ), a spatial
AP×ML frame (XY), and an optional 1D time summary curve.
from cogpy.plot.hv.orthoslicer import OrthoSlicerRanger
import holoviews as hv
# Map xarray dim names to labeled HoloViews Dimensions
dx = ("ML", hv.Dimension("x", label="Medial-Lateral", unit="mm"))
dy = ("AP", hv.Dimension("y", label="Anterior-Posterior", unit="mm"))
dt = ("time", hv.Dimension("t", label="Time", unit="s"))
dz = ("freq", hv.Dimension("z", label="Frequency", unit="Hz"))
# Optional 1D signal drawn under the TZ view
summary = spec4d.mean(dim=("freq", "AP", "ML"))
slicer = OrthoSlicerRanger(
spec4d,
rangeslider_sig=summary,
dt=dt, dz=dz, dy=dy, dx=dx,
)
slicer.panel_app().servable()
Click the TZ panel to select a time; click XY to select a spatial location. The summary curve at the bottom controls the visible time window.
Serving as a Panel app
Any layout can be served as a standalone dashboard:
pn.panel(layout).show() # opens browser tab (blocking)
pn.panel(layout).servable() # use inside `panel serve notebook.ipynb`
See also
Architecture And Scope — compute vs visualization boundary
cogpy.burst — low-level burst detection for overlay data