Plotting of Acquired Data using Bokeh
Difficulty Level:
Tags visualise☁plot☁single

Digital sensors, like the ones used in PLUX acquisition systems, establish an interface between physical and digital environment.

The human sensorial system is analogous to this description, being the visual component particularly important for a researcher, in order to extract knowledge from the acquired data. Plotting data brings another perspective to the researcher, stimulating this sensorial component, which is ideal for identifying patterns and communicate.

This Jupyter Notebook is intended to explain how the user can plot data in a simple and attractive way.


1 - Importation of the needed packages

In [1]:
# Base packages used in OpenSignals Tools Notebooks for plotting data
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.layouts import gridplot
from bokeh.models.tools import *
output_notebook(hide_banner=True)

# Package for ensuring operations in arrays
from numpy import average, linspace, random

# Own opensignalstools package needed here for applying the default OpenSignals styles and for loading data from an acquisition 
# file.
import biosignalsnotebooks as bsnb

2 - Load of data using load function of biosignalsnotebooks package

In [2]:
# Load of data from a remote source
data, header = bsnb.load("/signal_samples/ecg_sample.h5", get_header=True)
In [3]:
print ("\033[1mHeader:\n\033[0m" + str(header) + "\n\033[1mData:\033[0m\n" + str(data))
Header:
{'channels': array([1]), 'comments': '', 'date': '2017-1-17', 'device': 'biosignalsplux', 'device connection': b'BTH00:07:80:3B:46:61', 'device name': b'00:07:80:3B:46:61', 'digital IO': array([0, 1]), 'firmware version': 772, 'resolution': array([16]), 'sampling rate': 200, 'sync interval': 2, 'time': '14:50:32.316', 'sensor': [b'ECG'], 'column labels': {1: 'channel_1'}}
Data:
{'CH1': array([32452, 32394, 32448, ..., 33120, 33164, 33192], dtype=uint16)}

3 - Storage of CH1 data in a dedicated variable and creation of a time axis linked with the acquired data

In [4]:
signal = data["CH1"]
time = bsnb.generate_time(signal, header["sampling rate"])

4 - Creation of an auxiliary signal by adding noise to the original one

In [5]:
# Noise samples and change of the baseline level
baseline = average(signal)
baseline_shift = 0.50 * baseline
data_noise = signal + random.normal(0, 1000, len(signal)) + baseline_shift

5 - Creation of a figure to plot the acquired data

In [6]:
# New Bokeh figure.
bokeh_figure = figure(x_axis_label='Time (s)', y_axis_label='Raw Data')

6 - Plotting of data in the last created figure (adding the respective legend of the axes with the "legend_label" argument)

In [7]:
# Original Data.
bokeh_figure.line(time, signal, legend_label="Original Data")

# Noisy Data.
bokeh_figure.line(time, data_noise, legend_label="Noisy Data")
Out[7]:
GlyphRenderer (
id = '1050', …)

Bokeh offers lots of configurable options. When invoking line function arguments can be specified for customising the plot, such as the next ones:

Argument Input type
line_width numeric
line_color RGB hexadecimal values;
bokeh.colors.RGB objects;
CSS-format RGB/RGBA strings
line_dash line style such as dotted or dashed

7 - Show figure

Bokeh offers the user interactive tools in right panel, for panning or zooming the representation

In [8]:
show(bokeh_figure)

Sometimes may be interesting that plots be represented in separate axes. This can be achieved by creating multiple figures. When a figure is created hits handler needs to be stored in a variable, in order to plot all the desired data by invoking the figure.

When all figures have been "filled" a Grid Layout is defined, as described in the following steps.


E.1 - Creation of two separate figures

In [9]:
# Top Figure.
top_figure = figure(x_axis_label='Time (s)', y_axis_label='Raw Data')

# Bottom Figure.
bottom_figure = figure(x_axis_label='Time (s)', y_axis_label='Raw Data')

E.2 - Plotting of data in each figure

In [10]:
# Plot of data in top_figure.
top_figure.line(time, signal, legend_label="Original Data")

# Plot of data in bottom_figure.
bottom_figure.line(time, data_noise, legend_label="Noisy Data")
Out[10]:
GlyphRenderer (
id = '1222', …)

E.3 - Generation of the Grid Plot structure

In this case we want a grid with 2x1 shape (2 lines and 1 column), so the gridplot input will be [[top_figure], [bottom_figure]].

The generic structure of the input for NxM shape is [[<line_of_figures_1>], [<line_of_figures_2>], ... [<line_of_figures_i>], ... [<line_of_figures_N>]], where [<line_of_figures_i>] is a list of M figures [<column_of_figures_1>, <column_of_figures_2>, ... <column_of_figures_M>]

In [11]:
grid_plot = gridplot([[top_figure], [bottom_figure]])

E.4 - Show the final Grid Plot

In [12]:
show(grid_plot)

Some of these visualisation functionalities can be done in a more immediate way by plot function of biosignalsnotebooks package.
For example to produce a similar result of steps 1 to 7 the command should be:

In [13]:
bsnb.plot([time, time], [signal, data_noise], legend_label=["Original Data", "Noisy Data"], y_axis_label=["Raw Data", "Raw Data"], x_axis_label="Time (s)")

To produce the result of steps E.1 to E.3:

In [14]:
bsnb.plot([time, time], [signal, data_noise], legend_label=["Original Data", "Noisy Data"], y_axis_label=["Raw Data", "Raw Data"], grid_plot=True, grid_lines=2, grid_columns=1, x_axis_label="Time (s)")

In a scientific research, graphical representations of collected data or time-series describing the evolution of extracted parameters is essential to achieve a better knowledge of the phenomenon under analysis, considering that a graphical plot is much more intuitive than raw numerical data.

As you can see, generating interactive and attractive plots is easy and now you have an additional tools to explore with the Bokeh package.

We hope that you have enjoyed this guide. biosignalsnotebooks is an environment in continuous expansion, so don"t stop your journey and learn more with the remaining Notebooks !

In [15]:
from biosignalsnotebooks.__notebook_support__ import css_style_apply
css_style_apply()
.................... CSS Style Applied to Jupyter Notebook .........................
Out[15]: