Event Detection - Muscular Activations (EMG)
Difficulty Level:
Tags detect☁emg☁tkeo

Skeletal muscle activation is, in normal conditions, a voluntary process triggered by a nervous impulse that propagates along motor neurons until the desired muscle.

When the nervous impulse reaches sarcolemma (muscle fiber membrane) the depolarisation/repolarisation continues and the changes in membrane potential can be monitored with specialised sensors placed at skin surface.

For contracting a muscle, a large set of motor units needs to be activated, so that the acquired EMG signal is the sum of their elementary potential changes. Because of this "summation" process, EMG seems to be a little "anarchic", and the essence of EMG signal processing is in study the activation zones (bursts).

So, burst detection is an important processing step, which can be achieved by single or double threshold algorithm, generally preceded by a smoothing phase.

In this Jupyter Notebook it will be presented a single threshold algorithm, that includes the Teager-Kaiser Energy Operator (TKEO) in his implementation.


1 - Importation of the needed packages and definition of auxiliary functions

In [1]:
# biosignalsnotebooks python package
import biosignalsnotebooks as bsnb

# Numpy package is dedicated to simplify the work (operations between) with arrays/lists
from numpy import cumsum, concatenate, zeros, linspace, average, power, absolute, mean, std, max, array, diff, where

# Scientific packages
from scipy.signal import butter, lfilter
from scipy.stats import linregress
In [2]:
# Base packages used in OpenSignals Tools Notebooks for ploting data
from bokeh.plotting import output_file, show
from bokeh.io import output_notebook
from bokeh.layouts import gridplot
output_notebook(hide_banner=True)

2 - Load of acquired EMG data

In [3]:
# Load of data.
data, header = bsnb.load_signal("emg_bursts", get_header=True)

3 - Identification of mac address of the device and the channel used during acquisition

In [4]:
channel = list(data.keys())[0]
In [5]:
print ("\033[1mChannel: \033[0m " + str(channel))
Channel:  CH3

4 - Storage of sampling rate and acquired data inside variables

In [6]:
# Sampling rate and acquired data
sr = header["sampling rate"]
device = header["device"]

# Signal Samples
signal = data[channel]
time = bsnb.generate_time(signal)

5 - Binarisation of EMG signal
5.1 - Preprocessing Steps

In [7]:
# [Baseline Removal]
pre_pro_signal = signal - average(signal)

# [Signal Filtering]
low_cutoff = 10 # Hz
high_cutoff = 300 # Hz

# Application of the signal to the filter.
pre_pro_signal = bsnb.aux_functions._butter_bandpass_filter(pre_pro_signal, low_cutoff, high_cutoff, sr)

5.2 - Application of TKEO operator

\begin{equation} TKEO[i] = \begin{cases} EMG_{original}[i], & \mbox{if } i=0 \mbox{ or } i=N-1 \\ EMG_{original}[i]^2 - (EMG_{original}[i + 1] \times EMG_{original}[i - 1]), & \mbox{otherwise}\end{cases} \end{equation} ... being $N$ the number of acquired samples.

In [8]:
# [Application of TKEO Operator]
tkeo = []
for i in range(0, len(pre_pro_signal)):
    if i == 0 or i == len(pre_pro_signal) - 1:
        tkeo.append(pre_pro_signal[i])
    else:
        tkeo.append(power(pre_pro_signal[i], 2) - (pre_pro_signal[i + 1] * pre_pro_signal[i - 1]))
In [9]:
bsnb.plot([list(time), list(time)], [list(signal), list(tkeo)], legend=["Original EMG", "TKEO Signal"], grid_plot=True, grid_lines=1, grid_columns=2, opensignals_style=True, x_axis_label="Time (s)", y_axis_label=["Raw Data", "Raw Data"])