# 希爾伯特黃轉換(Hilbert Huang Transform) 這邊需要用到 `PyEMD` 套件,以及翻譯自黃院士的 matlab libary 的 `nspplota` 首先來設定一下執行整套 HHT 需要的計算資源 ```python import mne import matplotlib.pyplot as plt import pandas as pd import numpy as np from mne.time_frequency import tfr_stockwell # HHT tools from PyEMD import EEMD as py_eemd from scipy.signal import hilbert, medfilt #from eemd_faphi import * from nspplota import nspplota from scipy.ndimage import gaussian_filter, uniform_filter EEMD_class = py_eemd(trials = 40, noise_width = 0.4, max_imf = 6) EEMD_class.EMD.FIXE = 10 # number of sifting # temp_: 2D MEG/EEG data, n_trial x times # ntrl: integer, number of trial # nimf: integer, number of IMFs # ntime: integer, number of time frames def DoEEMD(temp_, ntrl, nimf, ntime, EEMD_class): print('Do EEMD....') data_imfs = np.zeros((ntime, nimf + 1, ntrl)) for trl in range(ntrl): imf_ = EEMD_class(temp_[trl,:], max_imf = nimf).T data_imfs[:,:,trl] = imf_ return data_imfs def calc_inst_fa_hilbert(sig, dt): """"Extracts instantaneous frequency through the Hilbert Transform.""" analytic_signal = hilbert(sig) # Apply Hilbert transform to each row inst_a = np.abs(analytic_signal) inst_phase = np.unwrap(np.angle(analytic_signal)) # Compute angle between img and real inst_f1 = np.diff(inst_phase)/(2*np.pi*dt) inst_f = np.hstack((inst_f1, np.tile(inst_f1[:, [-1]], 1))) # Duplicate last points to make dimensions equal # medien filter for n in range(inst_f.shape[0]): inst_f[n] = medfilt(inst_f[n], 5) pass inst_a = np.abs(analytic_signal) return inst_f, inst_a, inst_phase data_path = '/Users/kevinhsu/Documents/D/000_experiment/project_cospro/PG_meg_data/001' sid = 1 epochs_file = data_path + '/%.3d_raw_epo.fif' % sid epochs = mne.read_epochs(epochs_file) epochs ``` ------------ ## Part 1. 這裡面用到 `mne` 的 `tfr_stockwell`,純粹是製作一個儲存 time-frequency 資料的容器: ```python freqs = np.arange(3., 60., 2.) fmin, fmax = freqs[[0, -1]] width = 0.9 power1kHz = tfr_stockwell(epochs['Tone1kHz'], fmin=fmin, fmax=fmax, width=width) powerAM40hz = tfr_stockwell(epochs['AM40Hz'], fmin=fmin, fmax=fmax, width=width) print(type(power1kHz)) ``` <class 'mne.time_frequency.tfr.AverageTFR'> 這邊建立了兩個 `AverageTFR` class 的物件,`power1kHz` 以及`powerAM40hz`。和 `raw`, `epochs`, `evoked` 這三個 class 一樣,`AverageTFR` 底下有一個 numpy ndarray 的資料。 以下的步驟是先做基準線校正,以事件發生之前 (-0.1~0s) 的區間的 power 平均值當作分母,去對比每一個時間點的 power, 取得相對值之後,在做 log 轉換。 最後結果如果是正值,表示事件發生之後的訊號大於baseline的訊號 (event-related synchronization ERS) 如果是負值,表示事件發生之後的訊號小於baseline的訊號 (event-related desynchronization ERD) ```python power1kHz.apply_baseline((-0.1, 0), mode = 'logratio') powerAM40hz.apply_baseline((-0.1, 0), mode = 'logratio') ``` 然後從兩個物件裡面取出資料並且相減,以凸顯 40Hz 活動在兩組情境之下的差異: ```python powerDiff_amp = powerAM40hz.data - power1kHz.data ``` 這時候 `powerDiff_amp` 只是兩個 numpy ndarray 相減的結果,本身也是 numpy ndarray, 接著把`powerDiff_amp` 變成 `AverageTFR` class 實際上的做法是,利用`mne.time_frequency.AverageTFR`這個指令,產生一個 `AverageTFR` class 的物件,並且把 `powerDiff_amp` 放到這個新的物件裡面。根據網頁說明,一個 `AverageTFR` 至少需要下面這些資訊: ?mne.time_frequency.AverageTFR ``` info : The measurement info. data : ndarray, shape (n_channels, n_freqs, n_times) --The data. times : ndarray, shape (n_times,) --The time values in seconds. freqs : ndarray, shape (n_freqs,) --The frequencies in Hz. nave : int --The number of averaged TFRs. ``` 基本上除了第二項要用`powerDiff_amp`,其他四項可以直接繼承自 `power1kHz` 或 `powerAM40hz` ```python powerDiff = mne.time_frequency.AverageTFR(powerAM40hz.info, powerDiff_amp, powerAM40hz.times, powerAM40hz.freqs, powerAM40hz.nave) ``` 然後就能繪圖: ```python powerDiff.plot_topo() ``` ------------ ## Part 2. 到此為止,沒有用到任何 HHT 的概念。我們接下來要做的是: 1. 從`epochs` 取出 data array,維度是 (193, 157, 700), events x channels x times 2. 針對每一段訊號 (event by event, channel by channel)逐一執行 HHT 的第一步驟,EEMD。假設每一個訊號分解成 7 個 IMFs,資料的維度變成 (193, 157, 700, 7) 3. 針對每一組 IMFs (總共 193 x 157 x 700) 組,取出前面 6 項 IMF, 然後計算瞬時頻率、瞬時震幅。這兩個資料的大小和 IMFs 的量一樣,都是 (193, 157, 700, 7) 4. 每一組 IMFs 會有一對 瞬時頻率、瞬時震幅。將這兩項資訊投射到 time-frequency 空間。頻率至個維度取 50 個 bins。然後將 events 這個維度平均再一起。最後的結果是 (157, 50, 700) 的三維矩陣。 ### 2.1. **EEMD** 第一個步驟要用的就是 `epochs.get_data()`,這邊先只挑一種事件來作HHT。這邊要注意一個重點,mne裡面的資料是用沒有轉換小數點的格式,MEG是T (tesla), EEG是V (voltage) 由於 MEG sensor 的解析度是到fT,所以一開始用`get_data()`取得的數值會很小,取出來的資料需要轉換成fT ```python MEG_data = epochs['AM40Hz'].get_data() * 1e+15 MEG_data = MEG_data.transpose((1,0,2)) MEG_data.shape ``` (157, 95, 700) 為了快速示範,這邊只挑一個 channel (MEG 103) ```python MEG_data_select = MEG_data[102, :, :] # T >> fT MEG_data_select.shape ``` (95, 700) 現在取得95段event的訊號,每一段的長度是700個取樣點。以下針對這95段訊號執行EEMD ```python ntrl, ntimes = MEG_data_select.shape # ntrl: number of trials; ntimes: number of time frames nimf = 6 # number of IMFs #data_imfs = DoEEMD( MEG_data_select, 193, 6, 700, EEMD_class) data_imfs = DoEEMD( MEG_data_select, ntrl, nimf, ntimes, EEMD_class) ``` 這邊要注意一下,`nimf` 是設定IMF的數量。PyEMD會再額外加上一個IMF不能解釋的殘差當作做後一項資料,所以做後輸出的IMF的維度是 `nimf + 1`: ```python data_imfs.shape ``` (700, 7, 95) >> times x imf x events ### 2.2. **Hilbert transform** 接著,使用 IMF 計算瞬時頻率、瞬時震幅。這邊先放棄瞬時相位...Orz 等之後我補上院士在2002年使用的 `direct quadrature` 再來玩 phase ```python dt = 1.0 / epochs.info['sfreq'] # data_imfs[:,0:-1,0].T.shape >> (6, 700) inst_f, inst_a, inst_phase = calc_inst_fa_hilbert(data_imfs[:,0:-1,0].T, dt) ``` 可以用這個指令看一下瞬時震幅與IMF之間的關聯: ```python fig, axes = plt.subplots(6,1) temp_imf = data_imfs[:,0:-1,0] for num in range(6): ax = axes[num] ax.plot(epochs.times, temp_imf[:, num], 'b') ax.plot(epochs.times, inst_a[num, :], 'r') #ax.plot(epochs.times, inst_f[num, :], 'b') ax.set_ylabel("IMF " + str(num+1)) ``` 接下來就是有一點噁心的步驟了。上面的 `calc_inst_fa_hilbert` 只有抓一段 event 訊號的 6 個 IMFs。下面要做的是,把95段event各自的6個IMF取出來執行`calc_inst_fa_hilbert`... ```python inst_f_full = np.zeros((data_imfs.shape[2], data_imfs.shape[1]-1, data_imfs.shape[0])) inst_a_full = np.zeros((data_imfs.shape[2], data_imfs.shape[1]-1, data_imfs.shape[0])) for num in range(data_imfs.shape[2]): print(num) inst_f, inst_a, inst_phase = calc_inst_fa_hilbert(data_imfs[:,0:-1,num].T, dt) inst_f_full[num] = inst_f inst_a_full[num] = inst_a pass ``` 這邊的`inst_f_full` 和 `inst_a_full` 都是 events x imf x times 的矩陣。把event 放在第一個維度主要是方便後續的分析步驟 ### 2.1. **make a beautiful picture** 到此為止,前面說的1-4個步驟裡,我們用一個 channel 完成了1~3 接下來要做第四步。先用一段MEG訊號的instantaneous frequency、instantaneous amplitude來示範怎麼使用`nspplota`: ```python # 定義nspplota的參數 t0 = epochs.times[0] # 時間序列訊號的起始時間,等於epochs物件底下的 times 的第一個數值 (s) t1 = epochs.times[-1] # 時間序列訊號的起始時間,用epochs物件底下的 times 的最後一個數值 (s) fres = 50 # 頻率維度的 bin 數量 tres = 100 # 時間維度的 bin 數量 fw0 = 0 # 頻率維度的最小值 (Hz) fw1 = 100 # 頻率維度的最大值 (Hz) tw0 = t0 # 同 t0 tw1 = t1 # 同 t1 # 用第一段event的instantaneous frequency、instantaneous amplitude。請注意輸入變數的順序要放對 nt, fscale, tscale, nt_imf_ms = nspplota(inst_f_full[0], inst_a_full[0], dt, t0, t1, fres, tres, fw0, fw1, tw0, tw1) # nt 的大小等於 fres x tres, 在這邊的結果是 50 x 100 # 第一個維度對應 frequency, 第二個維度則是 times # 依照院士在 1998 年的報告中,HHT spectra 需要做 兩次 2D # smoothing 才能凸現 time-frequency 的特徵 nt2 = gaussian_filter(nt, sigma = 0.6) nt2 = uniform_filter(nt2, size = 5) nt2 = np.sqrt(nt2) # 左圖是沒有 smoothing 的 HHT spectrum, 右圖是 fig, axes = plt.subplots(1,2) ax = axes[0] ax.imshow(nt, extent=[tscale[0],tscale[-1],fscale[0],fscale[-1]], interpolation='none', aspect='auto', origin = 'lower') ax = axes[1] ax.imshow(nt2, extent=[tscale[0],tscale[-1],fscale[0],fscale[-1]], interpolation='none', aspect='auto', origin = 'lower') ``` 好了,那再來跑一遍令人頭暈的分析,把同一個MEG channel的95段event 的HHT spectra計算出來,然後平均在一起,得到一個平均之後的 spectra。 ```python t0 = epochs.times[0] t1 = epochs.times[-1] fres = 50 tres = 100 fw0 = 0 fw1 = 100 tw0 = t0 tw1 = t1 nt_full = np.zeros((inst_f_full.shape[0], fres, tres)) nt_imf_ms_full = np.zeros((inst_f_full.shape[0], nimf, fres)) for num in range(inst_f_full.shape[0]): print(num) nt, fscale, tscale, nt_imf_ms = nspplota(inst_f_full[num], inst_a_full[num], dt, t0, t1, fres, tres, fw0, fw1, tw0, tw1) nt2 = gaussian_filter(nt, sigma = 0.6) nt2 = uniform_filter(nt2, size = 5) nt2 = np.sqrt(nt2) nt_full[num] = np.nan_to_num(nt2) nt_imf_ms_full[num] = nt_imf_ms pass nt_mean = np.mean(nt_full, axis = 0) plt.imshow(nt_mean, extent=[tscale[0],tscale[-1],fscale[0],fscale[-1]], interpolation='none', aspect='auto', origin = 'lower', ) ``` 到這邊為止,我們把 `MEG 103` 號 channel 上的資料做了下面的處理: 1. 95 段event 的訊號 (95x700), 轉換成 IMF (700x7x95) 2. 每一段event 的 IMF (700x7) 變成 instantaneous frequency、instantaneous amplitude。所以這個步驟產生兩個同樣是 95x7x700 的矩陣 3. 每一段event 的instantaneous frequency、instantaneous amplitude投射到由 fres (50) 和 tres (100) 定義的 HHT spectra空間上。這邊會形成一個 95x50x100 的矩陣 4. 最後,把前一個步驟的矩陣沿著第一個維度 (event) 平均在一起,產生一個 50x100 的 time-frequency spectra 恭喜你!完成人生中的第一個 event-related HHT spectra!!!! 那麼.... # My wish list: 1. 我們總共有 157 個 channels,請把上面的步驟變成一個大大的 loop,最後產生一個 157 x freq x tres 的 numpy ndarray 3. 然後請用 part 1 的方式,把這些 time-frequency spectra 放入 `AverageTFR` class 裡面 (人生中的第一個 全頭式 MEG 的 ER-HHT spectra!!!!)。 放入 `AverageTFR` class 裡面的步驟大致上會像這樣: ```python dummy_power_ndarray = np.ones((157, fres, tres)) dummy_power_ndarray[102] = nt_mean dummy_power = mne.time_frequency.AverageTFR(epochs.info, dummy_power_ndarray, tscale, fscale, data_imfs.shape[2]) ```