Computing SNR for Slow Signals
Difficulty Level:
Tags pre-process☁snr☁ecg☁eda

A very important parameter to consider when analysing a signal is the Signal to Noise Ratio (SNR) - a metric that classifies objectively the quality of the acquisition, and like the name suggests, the relation between the intensity of the signal and the undesired noise in the acquired data, which is defined by: $ \\SNR = \frac{V_{pp}^{signal}}{V_{pp}^{noise}}\\ $, being $V_{pp}^{signal}$ and $V_{pp}^{noise}$ the peak-to-peak amplitude of the signal and noise segment, respectively.

To obtain this parameter, there is a big difference in the procedure when applying it to slow and rapid signals. Slow signals, as expected, have slow oscillations. To find the noise signal, you just have to subtract the filtered signal to the raw one. However, when it comes to rapid signals, you would not have a correct noise signal this way.

In this Jupyter Notebook you will learn how to compute the SNR values for slow varying signals, using samples that are available in our website.


1 - Importation of the required packages

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

#Importing numpy package methods
from numpy import ptp, zeros

#Package to calculate SNR with log
from math import log10

2 - Opening files and storing data in variables

If this step is challenging for you, try this Jupyter Notebooks on Opening a .txt file and Working with file headers .

In [2]:
#EDA file - Slow Signal
data_eda, header_eda = bsnb.load("/signal_samples/eda_slow_signal.txt", get_header=True)

# The loaded data is in the form of Python dictionary, where the first entry is the mac address 
# of the first device of the acquisition. This line, extracts that mac address.
mac_address_eda = list(header_eda.keys())[0]

# For this particular acquisition, the EDA data corresponds to the data in channel 3.
signal_eda = data_eda['CH3']

3 - EDA - Slow Signal Analysis
Electrodermal Activity (EDA) is a physical property that measures skin conductance, varying with its resistance. For a more thorough revision, we recommend the reading of this revision paper by Hugo F. Posada-Quintero.

3.1 - Conversion of the raw signal to its physical meaning

The device used for the acquisition measures signals and provides the raw data for post analyses. Thus, before starting the interpretation and processing, we will convert it to physical units, that in the case of conductance corresponds to Siemens or microSiemens (S or μS) using the transfer function that is already implemented in the biosignalsnotebooks Python package . This transfer function may be accessed in the sensor datasheet .

In [3]:
# Get the sampling rate of the acquisition
sampling_rate_eda = header_eda['sampling rate']

# Get the resolution of the sensor
resolution = header_eda['resolution'][0]

# Converting signal to uS (microSiemens) unit
signal_us = bsnb.raw_to_phy('EDA', 'biosignalsplux', signal_eda, resolution, option='uS')

#Time axis generation
time_eda= bsnb.generate_time(signal_eda, sampling_rate_eda)

3.2 - Graphic representation of raw signal

The following plot shows the EDA signal we will analyse henceforth. Notice that this signal varies slowly over time.

In [4]:
#Creating the graphic
bsnb.plot(time_eda, signal_us, title="Raw EDA", y_axis_label = "Signal (μS)", x_axis_label = "Time (s)")

3.3 - Filtering the signal

Given that the signal varies slowly, if we apply a low-pass filter attenuating the components of higher frequency relative to the one we choose, we will be able to remove the influence of undesired high-frequency noise and get a more trustworthy representation of the measured physical property. According to the aforementioned paper, the typical cut-off frequency is of 1 Hz. In this case, we chose to apply a cut-off frequency of 5 Hz to make sure that the signal itself is not attenuated at all.

Note : If there was noise on lower frequencies and if we knew the frequencies that describe the signal, we could instead apply a bandpass filter, attenuating the lower and higher frequency components of the measured signal.

In [5]:
# Cut-off frequency value (Hz)
low_cutoff = 5

# Filtering EDA signal
eda_filtered = bsnb.lowpass(signal_us, low_cutoff, fs=sampling_rate_eda, use_filtfilt=True)

The next plot shows the resulting signal. Since the noise influence is not clearly noticeable, zoom in on the next and the previous plots to clearly understand the differences.

In [6]:
#Ploting the graphic representation of filtered signal
bsnb.plot(time_eda, eda_filtered, title="Filtered EDA", y_axis_label = "Filtered Signal (μS)", x_axis_label = "Time (s)")

3.4 - Calculating V pp signal | Peak-to-peak amplitude of the signal component

Since the V pp signal corresponds to the difference between the maximum and minimum values of the signal, we first must find them in order to proceed to the respective subtraction. Fortunately, Python and numpy offer functions to simplify this process.

In [7]:
# Finding the maximum and minimum values of the EDA signal
max_value = max(eda_filtered)
min_value = min(eda_filtered)

# Calculating the amplitude of the signal
vpp_signal_eda = max_value - min_value

# Notice that this procedure is condensed in a single function in the numpy Python package:
vpp_signal_eda = ptp(eda_filtered)
In [8]:
print("Amplitude of the signal: {} μS".format(vpp_signal_eda))
Amplitude of the signal: 3.9425913525303926 μS

The next plot is a visual representation of this procedure. The V pp value is given by the absolute difference between the lines that encompass the EDA signal.

In [9]:
max_line = zeros(len(eda_filtered)) + max(eda_filtered)
min_line = zeros(len(eda_filtered)) + min(eda_filtered)

bsnb.plot([time_eda, time_eda,time_eda],[eda_filtered, min_line, max_line], color=['#009EE3', '#E84D0E', '#E84D0E'],
         title="Filtered EDA Amplitude", y_axis_label = "Filtered Signal (μS)", x_axis_label = "Time (s)")

3.5 - Noise signal and measuring V pp noise | Peak-to-peak amplitude of the noise component

The noise component of the signal can be calculated by subtracting the unfiltered signal to the filtered signal. Given that the difference corresponds to the high-frequency components of the signal, which we considered to be noise , the result is the noise of the measured signal.

In [10]:
# Calculating the noise in the signal
eda_noise = signal_us - eda_filtered

# Calculating the peak to peak amplitude of the noise in the EDA signal
vpp_noise_eda = ptp(eda_noise)
In [11]:
print("Amplitude of the noise: {} μS".format(vpp_noise_eda))
Amplitude of the noise: 0.05510369998200915 μS

The next plot is a visual representation of the noise and the procedure, which is analogous to the procedure applied to the EDA signal.

In [12]:
#Another visual help to guide you
eda_noise_length = len(eda_noise)
max_line = zeros(eda_noise_length) + max(eda_noise)
min_line = zeros(eda_noise_length) + min(eda_noise)

bsnb.plot([time_eda, time_eda,time_eda],[eda_noise, min_line, max_line], color=['#009EE3', '#E84D0E', '#E84D0E'],
         title="EDA Noise Amplitude", y_axis_label = "Noise Signal (μS)", x_axis_label = "Time (s)")

3.6 - Computing SNR (dB)

Though the formula for the signal to noise ratio shown before is valid, it is more usual to represent this quantity in decibels (dB). This conversion is given by: $ \\SNR= 20 \times log_{10}{\frac{V_{pp}^{signal}}{V_{pp}^{noise}}}\\ $. Both quantities will be calculated.

In [13]:
# Unitless SNR determination
snr_eda = vpp_signal_eda/vpp_noise_eda

# The multiplication by 20 is because the signals are in the unit of (micro)Siemes
snr_eda_db = 20 * log10(snr_eda)
In [14]:
print("SNR for EDA signal: {:0.2f}.".format(snr_eda))
print("SNR for EDA signal in dB: {:0.2f} dB.".format(snr_eda_db))
SNR for EDA signal: 71.55.
SNR for EDA signal in dB: 37.09 dB.

SNR values are proportional to the quality of the acquisition. Thus, the higher the value, the better the acquisition, because the higher the influence of the signal relative to the noise. Specifically, the signal is higher than the noise if the SNR is higher than 1 (in dB higher than 0) and inversely, if it is below 1 (in dB below 0) the influence of the noise is higher than the influence of the signal and, thus, it might be impossible to recover the signal.

Signal to noise ratio is important for every type of signal, however it is particularly simple to calculate for slow varying signals. In this case, we concluded that the noise influence was low on the overall signal, once the SNR is much higher than 1 (or 0 dB).

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]: