This notebook illustrates how empirical mode decomposition (EMD) decomposes a signal into multiple rhythmic components.
Outline
A future notebook will demonstrate how well EMD works at decomposing oscillations in a few different neural data sets. A future notebook will propose a method for choosing the criterion for an IMF (a key free parameter in the algorithm).
Some background
Advantages of EMD
Disadvantages of EMD
Load libraries
In [1]:
%config InlineBackend.figure_format = 'retina'
%matplotlib inline
import seaborn as sns
sns.set_style('white')
import numpy as np
import scipy as sp
from scipy import signal
import matplotlib.pyplot as plt
EMD functions
In [2]:
def emd(x, nIMF = 3, stoplim = .001):
"""Perform empirical mode decomposition to extract 'niMF' components out of the signal 'x'."""
r = x
t = np.arange(len(r))
imfs = np.zeros(nIMF,dtype=object)
for i in range(nIMF):
r_t = r
is_imf = False
while is_imf == False:
# Identify peaks and troughs
pks = sp.signal.argrelmax(r_t)[0]
trs = sp.signal.argrelmin(r_t)[0]
# Interpolate extrema
pks_r = r_t[pks]
fip = sp.interpolate.InterpolatedUnivariateSpline(pks,pks_r,k=3)
pks_t = fip(t)
trs_r = r_t[trs]
fitr = sp.interpolate.InterpolatedUnivariateSpline(trs,trs_r,k=3)
trs_t = fitr(t)
# Calculate mean
mean_t = (pks_t + trs_t) / 2
mean_t = _emd_complim(mean_t, pks, trs)
# Assess if this is an IMF (only look in time between peaks and troughs)
sdk = _emd_comperror(r_t, mean_t, pks, trs)
# if not imf, update r_t and is_imf
if sdk < stoplim:
is_imf = True
else:
r_t = r_t - mean_t
imfs[i] = r_t
r = r - imfs[i]
return imfs
def _emd_comperror(h, mean, pks, trs):
"""Calculate the normalized error of the current component"""
samp_start = np.max((np.min(pks),np.min(trs)))
samp_end = np.min((np.max(pks),np.max(trs))) + 1
return np.sum(np.abs(mean[samp_start:samp_end]**2)) / np.sum(np.abs(h[samp_start:samp_end]**2))
def _emd_complim(mean_t, pks, trs):
samp_start = np.max((np.min(pks),np.min(trs)))
samp_end = np.min((np.max(pks),np.max(trs))) + 1
mean_t[:samp_start] = mean_t[samp_start]
mean_t[samp_end:] = mean_t[samp_end]
return mean_t
Import data
In [3]:
minN = 7000
maxN = 9000
x = np.load('./exampledata.npy')
x = x[minN:maxN+1]
t = np.arange(0,len(x)*.001,.001)
Calculate and plot components
In [4]:
imfs = emd(x, nIMF = 5)
In [5]:
plt.figure(figsize=(12,12))
for i in range(len(imfs)):
plt.subplot(len(imfs),1,i+1)
plt.plot(t,x,color='0.6')
plt.plot(t,imfs[i],'k')
plt.ylim([-1000,1000])
plt.ylabel('IMF '+np.str(i+1))
if i == len(imfs)-1:
plt.xlabel('Time (s)')
EMD is an interative algorithm. Begin with a signal and repetitively modify it until it satisfies a certain criterion. Modify the original signal, and repeat the procedure to obtain each component.
The next section outlines the different stages of the algorithm:
Define decomposition parameters
In [6]:
nIMF = 3 # Number of components in which to decompose the signal
stoplim = .001 # Criterion to stop iteration and declare a signal to be a component
In [7]:
x_temp = x
pks = signal.argrelmax(x_temp)[0]
trs = signal.argrelmin(x_temp)[0]
In [8]:
# Plot
plt.figure(figsize=(15,3))
plt.plot(t,x_temp,'k')
plt.plot(t[pks],x_temp[pks],'r.')
plt.plot(t[trs],x_temp[trs],'b.')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (uV)')
Out[8]:
In [9]:
x_pks = x[pks]
fip = sp.interpolate.InterpolatedUnivariateSpline(pks,x_pks,k=3)
pks_t = fip(range(len(x)))
x_trs = x_temp[trs]
fitr = sp.interpolate.InterpolatedUnivariateSpline(trs,x_trs,k=3)
trs_t = fitr(range(len(x)))
In [10]:
# Plot
plt.figure(figsize=(15,3))
plt.plot(t,x,'k')
plt.plot(t[pks],x_temp[pks],'r.')
plt.plot(t[trs],x_temp[trs],'b.')
plt.plot(t,pks_t,'r')
plt.plot(t,trs_t,'b')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (uV)')
plt.ylim([-1000,1000])
Out[10]:
In [11]:
mean_t = (pks_t + trs_t) / 2
In [12]:
# Plot
plt.figure(figsize=(10,6))
plt.subplot(2,1,1)
plt.plot(t,x,'k',label='raw')
plt.plot(t,pks_t,'b',label='extrema envelope')
plt.plot(t,trs_t,'b')
plt.plot(t,mean_t,'r',label='mean of extrema envelopes')
plt.ylabel('Voltage (uV)')
plt.ylim([-1000,1000])
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(t,x,'k',label='raw')
plt.plot(t,x-mean_t,'r',label='potential IMF')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (uV)')
plt.ylim([-1000,1000])
plt.legend(loc='best')
Out[12]:
Notice the high frequency components of the signal are most preserved
The result of iteration step 4 is declared as an IMF if the mean of its extrema envelope is sufficiently close to 0
In [13]:
samp_start = np.max((np.min(pks),np.min(trs)))
samp_end = np.min((np.max(pks),np.max(trs))) + 1
sdk = np.sum(np.abs(mean_t[samp_start:samp_end]**2)) / np.sum(np.abs(x_temp[samp_start:samp_end]**2))
if sdk < stoplim:
is_imf = True
else:
x_temp = x_temp - mean_t
is_imf = False
If the signal satisfies the criterion:
If the signal does not satisfy the criterion:
In [14]:
nIMF = 3
imfs = np.zeros(nIMF,dtype=object)
In [17]:
def findnextcomponent(x,orig,t):
"""Find the next IMF of the input signal 'x' and return it as 'r'.
The plots are compared to the original signal, 'orig'."""
r_t = x
is_imf = False
while is_imf == False:
# Identify peaks and troughs
pks = sp.signal.argrelmax(r_t)[0]
trs = sp.signal.argrelmin(r_t)[0]
# Interpolate extrema
pks_r = r_t[pks]
fip = sp.interpolate.InterpolatedUnivariateSpline(pks,pks_r,k=3)
pks_t = fip(range(len(r_t)))
trs_r = r_t[trs]
fitr = sp.interpolate.InterpolatedUnivariateSpline(trs,trs_r,k=3)
trs_t = fitr(range(len(r_t)))
# Calculate mean
mean_t = (pks_t + trs_t) / 2
mean_t = _emd_complim(mean_t, pks, trs)
# Assess if this is an IMF
sdk = _emd_comperror(r_t, mean_t,pks,trs)
# debug: make sure everything looks right, because getting weird interp error after a few iterations; error not converging?
plt.figure(figsize=(10,1))
plt.plot(orig,color='0.8')
plt.plot(r_t - mean_t,'k')
plt.ylim((-1000,1000))
# if not imf, update r_t and is_imf
if sdk < stoplim:
is_imf = True
print 'IMF found!'
else:
r_t = r_t - mean_t
return x - r_t
IMF 1
In [18]:
r = findnextcomponent(x,x,t)
IMF 2
In [19]:
r = findnextcomponent(r,x,t)
IMF 3
In [20]:
r = findnextcomponent(r,x,t)
I hope this helped give you an intuition behind EMD! Keep up to date with my website for the next notebook about EMD application.