Eeg Viewer#

Download this notebook from GitHub (right-click to download).


This example demonstrates advanced visualization techniques using HoloViews with the Bokeh plotting backend. You’ll learn how to:

  1. Display multiple timeseries in a single plot using subcoordinate_y.

  2. Create and link a minimap to the main plot with RangeToolLink.

Specifically, we’ll simulate Electroencephalography (EEG) data, plot it, and then create a minimap based on the z-score of the data for easier navigation.

import numpy as np
import holoviews as hv
from bokeh.models import HoverTool
from holoviews.plotting.links import RangeToolLink
from scipy.stats import zscore

hv.extension('bokeh')

Generating EEG data#

Let’s start by simulating some EEG data. We’ll create a timeseries for each channel using sine waves with varying frequencies.

N_CHANNELS = 10
N_SECONDS = 5
SAMPLING_RATE = 200
INIT_FREQ = 2  # Initial frequency in Hz
FREQ_INC = 5  # Frequency increment
AMPLITUDE = 1

# Generate time and channel labels
total_samples = N_SECONDS * SAMPLING_RATE
time = np.linspace(0, N_SECONDS, total_samples)
channels = [f'EEG {i}' for i in range(N_CHANNELS)]

# Generate sine wave data
data = np.array([AMPLITUDE * np.sin(2 * np.pi * (INIT_FREQ + i * FREQ_INC) * time)
                     for i in range(N_CHANNELS)])

Visualizing EEG Data#

Next, let’s dive into visualizing the EEG data. We construct each timeseries using a Curve element, assigning it a label and setting subcoordinate_y=True. All these curves are then aggregated into a list, which serves as the input for an Overlay element. Rendering this Overlay produces a plot where the timeseries are stacked vertically.

Additionally, we’ll enhance user interaction by implementing a custom hover tool. This will display key information—channel, time, and amplitude—when you hover over any of the curves.

hover = HoverTool(tooltips=[
    ("Channel", "@channel"),
    ("Time", "$x s"),
    ("Amplitude", "$y µV")
])

channel_curves = []
for channel, channel_data in zip(channels, data):
    ds = hv.Dataset((time, channel_data, channel), ["Time", "Amplitude", "channel"])
    curve = hv.Curve(ds, "Time", ["Amplitude", "channel"], label=channel)
    curve.opts(
        subcoordinate_y=True, color="black", line_width=1, tools=[hover],
    )
    channel_curves.append(curve)

eeg = hv.Overlay(channel_curves, kdims="Channel").opts(
    xlabel="Time (s)", ylabel="Channel", show_legend=False, aspect=3, responsive=True,
)
eeg

Creating the Minimap#

A minimap can provide a quick overview of the data and help you navigate through it. We’ll compute the z-score for each channel and represent it as an image; the z-score will normalize the data and bring out the patterns more clearly. To enable linking in the next step between the EEG Overlay and the minimap Image, we ensure they share the same y-axis range.

y_positions = range(N_CHANNELS)
yticks = [(i , ich) for i, ich in enumerate(channels)]

z_data = zscore(data, axis=1)

minimap = hv.Image((time, y_positions , z_data), ["Time (s)", "Channel"], "Amplitude (uV)")
minimap = minimap.opts(
    cmap="RdBu_r", xlabel='Time (s)', alpha=.5, yticks=[yticks[0], yticks[-1]],
    height=150, responsive=True, default_tools=[], clim=(-z_data.std(), z_data.std())
)
minimap

Building the dashboard#

Finally, we use RangeToolLink to connect the minimap Image and the EEG Overlay, setting bounds for the initial viewable area. Once the plots are linked and assembled into a unified dashboard, you can interact with it. Experiment by dragging the selection box on the minimap or resizing it by clicking and dragging its edges.

RangeToolLink(
    minimap, eeg, axes=["x", "y"],
    boundsx=(None, 2), boundsy=(None, 6.5)
)

dashboard = (eeg + minimap).opts(merge_tools=False).cols(1)
dashboard
This web page was generated from a Jupyter notebook and not all interactivity will work on this website. Right click to download and run locally for full Python-backed interactivity.

Download this notebook from GitHub (right-click to download).