Python中小波工具(pywt)分析EEG數據

小波作爲一種信號處理的工具在腦波分析中應用很多,常用的有連續小波變換、小波包分析等等。小波涉及的相關介紹和公式推導有很多資料,文章末尾推薦了幾個鏈接。本文主要介紹連續小波變換,小波包分解重構,對應頻段能量計算這3種應用在Python中的實現。

文中的數據和代碼參考https://download.csdn.net/download/zhoudapeng01/12566856

數據來源爲BCI競賽公開數據集中的部分數據,剔除了無效數據。有關數據的描述見鏈接:

https://blog.csdn.net/zhoudapeng01/article/details/103822321

https://blog.csdn.net/zhoudapeng01/article/details/103893014

1、連續小波變換(主要用於時頻域分析)

這裏使用連續小波變換進行時頻域分析,數據只是示例,代碼中的參數在實際應用的時候需要根據實際情況進行調整。代碼中有關小波尺度的計算很有意思,這裏單獨拿出來詳細說明下。

一般用小波的尺度來衡量小波的頻率,兩者之間的轉換關係爲:

scale*f=Fs*wcf

其中Fs爲採樣頻率,wcf爲小波的中心頻率。

我們以下文代碼中的參數爲例,當一共想要分析totalscal個頻率的時候,第i個頻率fi對應的是\frac{Fs}{2}*\frac{i}{total},帶入上面的等式中scale = \frac{Fs*wcf}{f}=\frac{FS*wcf}{\frac{Fs*i}{2*total}}=\frac{2*wcf*total}{i}

該部分對應的代碼如下:

import numpy as np
import matplotlib.pyplot as plt
import pywt
import mne
mne.set_log_level(False)
######################################################連續小波變換##########
# totalscal小波的尺度,對應頻譜分析結果也就是分析幾個(totalscal-1)頻譜
def TimeFrequencyCWT(data,fs,totalscal,wavelet='cgau8'):
    # 採樣數據的時間維度
    t = np.arange(data.shape[0])/fs
    # 中心頻率
    wcf = pywt.central_frequency(wavelet=wavelet)
    # 計算對應頻率的小波尺度
    cparam = 2 * wcf * totalscal
    scales = cparam/np.arange(totalscal, 1, -1)
    # 連續小波變換
    [cwtmatr, frequencies] = pywt.cwt(data, scales, wavelet, 1.0/fs)
    # 繪圖
    plt.figure(figsize=(8, 4))
    plt.subplot(211)
    plt.plot(t, data)
    plt.xlabel(u"time(s)")
    plt.title(u"Time spectrum")
    plt.subplot(212)
    plt.contourf(t, frequencies, abs(cwtmatr))
    plt.ylabel(u"freq(Hz)")
    plt.xlabel(u"time(s)")
    plt.subplots_adjust(hspace=0.4)
    plt.show()


if __name__ == '__main__':
    # 讀取篩選好的epoch數據
    epochsCom = mne.read_epochs(r'F:\BaiduNetdiskDownload\BCICompetition\BCICIV_2a_gdf\Train\Fif\A02T_epo.fif')
    dataCom = epochsCom[10].get_data()[0][0]
    TimeFrequencyCWT(dataCom, fs=250, totalscal=10, wavelet='cgau8')

對應結果如下,這裏只是示例,並不進行分析。

2、小波包分解重構

小波包分解可以同時分析低頻和高頻部分的數據,分析結果的頻率分辨率和小波樹的深度有關,在使用的時候建議輸入的數據個數最好爲2的次方(如果不是2的次方重構後數據點數可能會和原始數據不同)。小波樹節點的排序非常重要需要注意,在默認情況下並非由低頻向高頻排列。以深度爲3的小波樹爲例,默認的節點排序爲:['aaa', 'aad', 'ada', 'add', 'daa', 'dad', 'dda', 'ddd'],但是其對應頻率由低到高的排序應爲:['aaa', 'aad', 'add', 'ada', 'dda', 'ddd', 'dad', 'daa']。程序中使用了一種判斷方法,可以判斷分析目標頻率對應的小波參數,不足之處就是如果分析的目標頻率的範圍較低,受小波分析的分辨率限制,小波樹的深度就要加深。具體小波深度的選取要結合採樣頻率,數據點數,分析目標頻率的範圍等因素綜合考慮來確定。

對應的示例代碼如下:

import numpy as np
import matplotlib.pyplot as plt
import pywt
import mne

# 需要分析的四個頻段
iter_freqs = [
    {'name': 'Delta', 'fmin': 0, 'fmax': 4},
    {'name': 'Theta', 'fmin': 4, 'fmax': 8},
    {'name': 'Alpha', 'fmin': 8, 'fmax': 13},
    {'name': 'Beta', 'fmin': 13, 'fmax': 35},
]

plt.rcParams['font.sans-serif'] = ['SimHei'] #用來正常顯示中文標籤
plt.rcParams['axes.unicode_minus'] = False #用來正常顯示負號
mne.set_log_level(False)
########################################小波包變換-重構造分析不同頻段的特徵(注意maxlevel,如果太小可能會導致)#########################
def TimeFrequencyWP(data, fs, wavelet, maxlevel = 8):
    # 小波包變換這裏的採樣頻率爲250,如果maxlevel太小部分波段分析不到
    wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
    # 頻譜由低到高的對應關係,這裏需要注意小波變換的頻帶排列默認並不是順序排列,所以這裏需要使用’freq‘排序。
    freqTree = [node.path for node in wp.get_level(maxlevel, 'freq')]
    # 計算maxlevel最小頻段的帶寬
    freqBand = fs/(2**maxlevel)
    #######################根據實際情況計算頻譜對應關係,這裏要注意係數的順序
    # 繪圖顯示
    fig, axes = plt.subplots(len(iter_freqs)+1, 1, figsize=(10, 7), sharex=True, sharey=False)
    # 繪製原始數據
    axes[0].plot(data)
    axes[0].set_title('原始數據')
    for iter in range(len(iter_freqs)):
        # 構造空的小波包
        new_wp = pywt.WaveletPacket(data=None, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
        for i in range(len(freqTree)):
            # 第i個頻段的最小頻率
            bandMin = i * freqBand
            # 第i個頻段的最大頻率
            bandMax = bandMin + freqBand
            # 判斷第i個頻段是否在要分析的範圍內
            if (iter_freqs[iter]['fmin']<=bandMin and iter_freqs[iter]['fmax']>= bandMax):
                # 給新構造的小波包參數賦值
                new_wp[freqTree[i]] = wp[freqTree[i]].data
        # 繪製對應頻率的數據
        axes[iter+1].plot(new_wp.reconstruct(update=True))
        # 設置圖名
        axes[iter+1].set_title(iter_freqs[iter]['name'])
    plt.show()


if __name__ == '__main__':
    # 讀取篩選好的epoch數據
    epochsCom = mne.read_epochs(r'F:\BaiduNetdiskDownload\BCICompetition\BCICIV_2a_gdf\Train\Fif\A02T_epo.fif')
    dataCom = epochsCom[10].get_data()[0][0][0:1024]
    TimeFrequencyWP(dataCom,250,wavelet='db4', maxlevel=8)

重構後的腦波數據如下:看着還像那麼回事,實際上如果你輸入的數據長度再長一些,看起來效果會好點。

3、基於小波包分解計算不同頻段的能量和

計算能量或者說是功率的方法有很多,比如之前寫過的PSD:https://blog.csdn.net/zhoudapeng01/article/details/106906593

這裏利用小波包的方法計算區間能量的累加和。

對應的代碼如下:

import numpy as np
import matplotlib.pyplot as plt
import pywt
import mne

# 需要分析的四個頻段
iter_freqs = [
    {'name': 'Delta', 'fmin': 0, 'fmax': 4},
    {'name': 'Theta', 'fmin': 4, 'fmax': 8},
    {'name': 'Alpha', 'fmin': 8, 'fmax': 13},
    {'name': 'Beta', 'fmin': 13, 'fmax': 35},
]

plt.rcParams['font.sans-serif'] = ['SimHei'] #用來正常顯示中文標籤
plt.rcParams['axes.unicode_minus'] = False #用來正常顯示負號
mne.set_log_level(False)
#############################################小波包計算四個頻段的能量分佈
def WPEnergy(data, fs, wavelet, maxlevel=6):
    # 小波包分解
    wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode='symmetric', maxlevel=maxlevel)
    # 頻譜由低到高的對應關係,這裏需要注意小波變換的頻帶排列默認並不是順序排列,所以這裏需要使用’freq‘排序。
    freqTree = [node.path for node in wp.get_level(maxlevel, 'freq')]
    # 計算maxlevel最小頻段的帶寬
    freqBand = fs / (2 ** maxlevel)
    # 定義能量數組
    energy = []
    # 循環遍歷計算四個頻段對應的能量
    for iter in range(len(iter_freqs)):
        iterEnergy = 0.0
        for i in range(len(freqTree)):
            # 第i個頻段的最小頻率
            bandMin = i * freqBand
            # 第i個頻段的最大頻率
            bandMax = bandMin + freqBand
            # 判斷第i個頻段是否在要分析的範圍內
            if (iter_freqs[iter]['fmin'] <= bandMin and iter_freqs[iter]['fmax'] >= bandMax):
                # 計算對應頻段的累加和
                iterEnergy += pow(np.linalg.norm(wp[freqTree[i]].data, ord=None), 2)
        # 保存四個頻段對應的能量和
        energy.append(iterEnergy)
    # 繪製能量分佈圖
    plt.plot([xLabel['name'] for xLabel in iter_freqs], energy, lw=0, marker='o')
    plt.title('能量分佈')
    plt.show()


if __name__ == '__main__':
    # 讀取篩選好的epoch數據
    epochsCom = mne.read_epochs(r'F:\BaiduNetdiskDownload\BCICompetition\BCICIV_2a_gdf\Train\Fif\A02T_epo.fif')

    dataCom = epochsCom[10].get_data()[0][0]
    WPEnergy(dataCom, fs=250, wavelet='db4', maxlevel=6)

計算結果:

 

參考資料

文中對應的數據和代碼:

https://download.csdn.net/download/zhoudapeng01/12566856

小波變換:

https://www.cnblogs.com/jfdwd/p/9249850.html

https://blog.csdn.net/weixin_42943114/article/details/89603208

https://my.oschina.net/u/4335157/blog/3893295

小波包變換:

http://blog.sina.com.cn/s/blog_8fc890a20101ecn7.html

https://blog.csdn.net/zds13257177985/article/details/102896041

小波與小波包的區別:

https://blog.csdn.net/cqfdcw/article/details/84995904?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章