def test_hrv_frequency(): # Test frequency domain ecg1 = nk.ecg_simulate(duration=60, sampling_rate=2000, heart_rate=70, random_state=42) _, peaks1 = nk.ecg_process(ecg1, sampling_rate=2000) hrv1 = nk.hrv_frequency(peaks1, sampling_rate=2000) ecg2 = nk.signal_resample(ecg1, sampling_rate=2000, desired_sampling_rate=500) _, peaks2 = nk.ecg_process(ecg2, sampling_rate=500) hrv2 = nk.hrv_frequency(peaks2, sampling_rate=500) assert np.allclose(hrv1["HRV_HF"] - hrv2["HRV_HF"], 0, atol=1.5) assert np.isnan(hrv1["HRV_LF"][0]) assert np.isnan(hrv2["HRV_LF"][0]) assert np.isnan(hrv1["HRV_VLF"][0]) assert np.isnan(hrv2["HRV_LF"][0]) # Test warning on too short duration with pytest.warns(nk.misc.NeuroKitWarning, match=r"The duration of recording is too short.*"): ecg3 = nk.ecg_simulate(duration=10, sampling_rate=2000, heart_rate=70, random_state=42) _, peaks3 = nk.ecg_process(ecg3, sampling_rate=2000) nk.hrv_frequency(peaks3, sampling_rate=2000, silent=False)
def nothing(): # Quality check algorithms # qcqrs = run_qc(signal=data.filtered, epoch_len=15, fs=fs, algorithm="averageQRS", show_plot=False) # Removing invalid data based on QC thresholdling #t = threshold_averageqrs_data(signal=data.filtered, qc_signal=qc, epoch_len=10, fs=fs, pad_val=0, # thresh=.95, method='exclusive', plot_data=False) p, pc = find_peaks(signal=data.filtered, fs=fs, show_plot=True, peak_method="pantompkins1985", clean_method='neurokit') hrv = nk.hrv_time(peaks=pc, sampling_rate=data.sample_rate, show=False) freq = nk.hrv_frequency(peaks=pc, sampling_rate=data.sample_rate, show=True) # df_events, info = test_ecg_process_func(signal=data.filtered[15000:15000+1250], start=0, n_samples=int(10*125), fs=fs, plot_builtin=True, plot_events=False) # heartbeats = segment_ecg(signal=d, fs=fs) waves, sigs = nk.ecg_delineate(ecg_cleaned=data.filtered, rpeaks=pc, sampling_rate=data.sample_rate, method='dwt', show=False) intervals, ecg_rate, ecg_hrv = nk.ecg_intervalrelated() # TODO # Organizing # Signal quality in organized section --> able to pick which algorithm
def test_hrv_frequency(): # Test frequency domain ecg1 = nk.ecg_simulate(duration=60, sampling_rate=2000, heart_rate=70, random_state=42) _, peaks1 = nk.ecg_process(ecg1, sampling_rate=2000) hrv1 = nk.hrv_frequency(peaks1, sampling_rate=2000) ecg2 = nk.signal_resample(ecg1, sampling_rate=2000, desired_sampling_rate=500) _, peaks2 = nk.ecg_process(ecg2, sampling_rate=500) hrv2 = nk.hrv_frequency(peaks2, sampling_rate=500) assert np.allclose(hrv1["HRV_HF"] - hrv2["HRV_HF"], 0, atol=1.5) assert np.isnan(hrv1["HRV_LF"][0]) assert np.isnan(hrv2["HRV_LF"][0]) assert np.isnan(hrv1["HRV_VLF"][0]) assert np.isnan(hrv2["HRV_LF"][0])
def my_hrv(peaks, sampling_rate): result = [] result.append(nk.hrv_time(peaks, sampling_rate=sampling_rate)) result.append( nk.hrv_frequency(peaks, sampling_rate=sampling_rate, vlf=(0.01, 0.04), lf=(0.04, 0.15), hf=(0.15, 0.4), vhf=(0.4, 1))) return pd.concat(result, axis=1)
def process_bvp(bvp, show_fig=False): """ Compute BVP signal features (more info: https://neurokit2.readthedocs.io/en/latest/functions.html#module-neurokit2.ppg). Compute HRV indices (more info: https://neurokit2.readthedocs.io/en/latest/functions.html#module-neurokit2.hrv) Parameters ---------- bvp : dict [timestamp : value] EDA signal. Returns ------- bvp_signals : DataFrame bvp_info : dict """ bvp_signals, bvp_info = nk.ppg_process(bvp['value'], sampling_rate=64) # First 5 seconds of the signal. # Find peaks peaks = bvp_signals['PPG_Peaks'][:320] # Compute HRV indices time_hrv = nk.hrv_time(peaks, sampling_rate=64, show=show_fig) hrv_base = time_hrv hrv_base['type'] = 'base' # The rest part of the signal. # Find peaks peaks = bvp_signals['PPG_Peaks'][320:] # Compute HRV indices phase_hrv = nk.hrv_frequency(peaks, sampling_rate=64, show=show_fig) time_hrv = nk.hrv_time(peaks, sampling_rate=64, show=show_fig) nonlinear_hrv = nk.hrv_nonlinear(peaks, sampling_rate=64, show=show_fig) hrv_indices = pd.concat([phase_hrv, time_hrv, nonlinear_hrv], axis=1) hrv_indices['type'] = 'stimul' hrv_indices = pd.concat([hrv_indices, hrv_base]) return bvp_signals, bvp_info, hrv_indices
def signals_analysis(signals, is_out=False, is_show=False, out_path="figs"): """ 数据的分析 主要是特征参数的获取 :param signals: 初步处理过的数据 :param is_out: :param is_show: :param out_path: :return: """ sampling_rate = signals["Sampling Rate"] feature_points = signals["Feature Points"] cleaned_pulses = feature_points["Cleaned Pulses"] peaks = feature_points["Peaks"] title = "Time Analysis" hrv_time = nk.hrv_time(peaks, sampling_rate, show=is_show) if is_out: no = datetime.datetime.now().strftime("%Y%m%d%H%M%S") out_name = os.path.join("outputs", str(out_path) + "___" + no + title + ".png") plt.savefig(out_name, dpi=300) if is_show: plt.show() title = "Frequency Analysis" hrv_freq = nk.hrv_frequency(peaks, sampling_rate, show=is_show) if is_out: no = datetime.datetime.now().strftime("%Y%m%d%H%M%S") out_name = os.path.join("outputs", str(out_path) + "___" + no + title + ".png") plt.savefig(out_name, dpi=300) if is_show: plt.show() title = "Nonlinear Analysis" hrv_nonlinear = nk.hrv_nonlinear(peaks, sampling_rate, show=is_show) if is_out: no = datetime.datetime.now().strftime("%Y%m%d%H%M%S") out_name = os.path.join("outputs", str(out_path) + "___" + no + title + ".png") plt.savefig(out_name, dpi=300) if is_show: plt.show() # 傅里叶分析 对应给的文章 xFFT = np.abs(np.fft.rfft(cleaned_pulses) / len(cleaned_pulses)) xFFT = xFFT[:600] xFreqs = np.linspace(0, sampling_rate // 2, len(cleaned_pulses) // 2 + 1) xFreqs = xFreqs[:600] # 滤波处理 平滑去噪 只处理前200个信号即可 # TODO 去噪方法可以调节 cleaned_xFFT = nk.signal_smooth(xFFT, method="loess") # 计算特征值 # F1值 cleaned_FFT = cleaned_xFFT.copy() locmax, props = spsg.find_peaks(cleaned_xFFT) hr_hz_index = np.argmax(cleaned_xFFT) f1 = np.argmax(xFFT) fmax = np.argmax(cleaned_xFFT[locmax]) cleaned_xFFT[locmax[fmax]] = np.min(cleaned_xFFT) f2s = np.argmax(cleaned_xFFT[locmax]) if f2s - fmax != 1: hr_hz_index = locmax[0] + int(np.sqrt(locmax[1] - locmax[0])) # F2值 f2 = locmax[np.argmax(cleaned_xFFT[locmax])] F1 = np.round(xFreqs[f1], 2) F2 = np.round(xFreqs[f2], 2) # 相位差 F2_F1 = F2 - F1 # 心率 HR_FFT = xFreqs[hr_hz_index] * 60 print(HR_FFT) if is_show: plt.plot(xFreqs, cleaned_FFT) plt.scatter(xFreqs[f1], cleaned_FFT[f1], color="red", label="F1 = " + str(F1) + "HZ") plt.scatter(xFreqs[f2], cleaned_FFT[f2], color="orange", label="F2 = " + str(F2) + "HZ") plt.legend(loc="upper right") plt.ylabel("Power") plt.xlabel("Freq(Hz)") title = "FFT analysis" plt.title(title) if is_out: no = datetime.datetime.now().strftime("%Y%m%d%H%M%S") out_name = os.path.join( "outputs", str(out_path) + "___" + no + title + ".png") plt.savefig(out_name, dpi=300) plt.show() hrv_new = { "Power": xFFT, "Freq": cleaned_FFT, "X": xFreqs, "F1": F1, "F2": F2, "F2_F1": F2_F1, "HR_FFT": HR_FFT } return { "HRV New": hrv_new, "HRV Time": hrv_time, "HRV Frequency": hrv_freq, "HRV Nonlinear": hrv_nonlinear }
def process_epochs_neurokit(signal, window_len=300, jump_len=300): t = datetime.now() indexes = np.arange(0, len(signal), int(jump_len * data.sample_rate)) print( f"\nRunning NeuroKit analysis in {window_len}-second windows with jump interval of {jump_len} seconds" f"({len(indexes)} iterations)...") # Within-epoch processing using NeuroKit to match Cardioscope output df_nk = pd.DataFrame([[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]).transpose() df_nk.columns = [ "Timestamp", "Index", "Quality", "HR", "meanRR", "sdRR", "meanNN", "SDNN", "pNN20", 'pNN50', "VLF", "LF", "HF", "LF/HF", "LFn", "HFn" ] print("\nProcessing data in epochs with NeuroKit2...") for start in indexes: print(f"{round(100*start/len(signal), 1)}%") try: s, i = nk.ecg_process(signal[start:start + int(data.sample_rate * window_len)], sampling_rate=data.sample_rate) s = s.loc[s["ECG_R_Peaks"] == 1] inds = [i for i in s.index] hrv = nk.hrv_time(peaks=inds, sampling_rate=data.sample_rate, show=False) freq = nk.hrv_frequency(peaks=inds, sampling_rate=data.sample_rate, show=False) rr_ints = [(d2 - d1) / data.sample_rate for d1, d2 in zip(inds[:], inds[1:])] mean_rr = 1000 * np.mean(rr_ints) sd_rr = 1000 * np.std(rr_ints) out = [ data.timestamps[start], start, 100 * s["ECG_Quality"].mean(), s["ECG_Rate"].mean().__round__(3), mean_rr, sd_rr, hrv["HRV_MeanNN"].iloc[0], hrv["HRV_SDNN"].iloc[0], hrv["HRV_pNN20"].iloc[0], hrv["HRV_pNN50"].iloc[0], freq["HRV_VLF"].iloc[0], freq["HRV_LF"].iloc[0], freq["HRV_HF"].iloc[0], freq["HRV_LFHF"].iloc[0], freq["HRV_LFn"].iloc[0], freq["HRV_HFn"].iloc[0] ] df_out = pd.DataFrame(out).transpose() df_out.columns = df_nk.columns df_nk = df_nk.append(df_out, ignore_index=True) except (ValueError, IndexError): out = [ data.timestamps[start], start, None, None, None, None, None, None, None, None, None, None, None, None, None ] df_out = pd.DataFrame(out).transpose() df_out.columns = df_nk.columns df_nk = df_nk.append(df_out, ignore_index=True) t1 = datetime.now() td = (t1 - t).total_seconds() print(f"100% ({round(td, 1)} seconds)") df_nk["Timestamp"] = pd.date_range(start=bf["Timestamp"].iloc[0], freq=f"{jump_len}S", periods=df_nk.shape[0]) return df_nk
def extract_bvp_features(bvp_data, sampling_rate): # Extract Heart Rate, RR Interval, and Heart Rate Variability features from PPG signals # bvp_data = MinMaxScaler().fit_transform(np.array(bvp_data).reshape(-1, 1)).ravel() ppg_signals, info = nk.ppg_process(bvp_data, sampling_rate=sampling_rate) hr = ppg_signals['PPG_Rate'] # hr = MinMaxScaler().fit_transform(np.array(hr).reshape(-1, 1)).ravel() peaks = info['PPG_Peaks'] # Sanitize input peaks = _hrv_sanitize_input(peaks) if isinstance(peaks, tuple): # Detect actual sampling rate peaks, sampling_rate = peaks[0], peaks[1] rri = _hrv_get_rri(peaks, sampling_rate=sampling_rate, interpolate=False) diff_rri = np.diff(rri) hrv_features = nk.hrv( peaks, sampling_rate=sampling_rate ) # Ignore NeuroKitWarning: The duration of recording is too short to support a sufficiently long window for high frequency resolution as we used another frequency for hrv_frequency hrv_frequency = nk.hrv_frequency( peaks, sampling_rate=sampling_rate, ulf=(0.01, 0.04), lf=(0.04, 0.15), hf=(0.15, 0.4) ) # the parameters of ULF, LF, HF follows the original paper of WESAD dataset # Philip Schmidt, Attila Reiss, Robert Duerichen, Claus Marberger, and Kristof Van Laerhoven. 2018. Introducing WESAD, a Multimodal Dataset for Wearable Stress and Affect Detection. # In Proceedings of the 20th ACM International Conference on Multimodal Interaction (ICMI '18). Association for Computing Machinery, New York, NY, USA, 400–408. DOI:https://doi.org/10.1145/3242969.3242985 # Not including: f_x_HRV of ULF and HLF, rel_f_x, sum f_x_HRV mean_HR, std_HR = np.mean(hr), np.std(hr) mean_HRV, std_HRV = hrv_features['HRV_MeanNN'], hrv_features['HRV_SDNN'] HRV_ULF, HRV_LF, HRV_HF, HRV_LFHF, HRV_LFnorm, HRV_HFnorm = hrv_frequency[ 'HRV_ULF'], hrv_frequency['HRV_LF'], hrv_frequency[ 'HRV_HF'], hrv_frequency['HRV_LFHF'], hrv_frequency[ 'HRV_LFn'], hrv_frequency['HRV_HFn'] rms = np.sqrt(np.nanmean(rri**2)) nn50 = np.sum(np.abs(diff_rri) > 50) HRV_TINN, HRV_pNN50, HRV_RMSSD = hrv_features['HRV_TINN'], hrv_features[ 'HRV_pNN50'], hrv_features['HRV_RMSSD'] # Nkurikiyeyezu, K., Yokokubo, A., & Lopez, G. (2019). The Influence of Person-Specific Biometrics in Improving Generic Stress Predictive Models. # ArXiv, abs/1910.01770. kurtosis_HRV, skewness_HRV = kurtosis(rri), skew(rri) HRV_VLF = hrv_frequency['HRV_VLF'] HRV_SD1, HRV_SD2 = hrv_features['HRV_SD1'], hrv_features['HRV_SD2'] HRV_SDSD = hrv_features['HRV_SDSD'] HRV_SDSD_RMSSD = HRV_SDSD / HRV_RMSSD adj_sum_rri = diff_rri + 2 * rri[:-1] HRV_pNN25 = np.sum(np.abs(diff_rri) > 25) / len(rri) * 100 relative_RRI = 2 * diff_rri / adj_sum_rri mean_relativeRRI, median_relativeRRI, std_relativeRRI, RMSSD_relativeRRI, kurtosis_relativeRRI, skew_relativeRRI = np.mean( relative_RRI), np.median(relative_RRI), np.std(relative_RRI), np.sqrt( np.mean(np.diff(relative_RRI)**2)), kurtosis(relative_RRI), skew( relative_RRI) # Combining the extracted features features = [ mean_HR, std_HR, mean_HRV, std_HRV, kurtosis_HRV, skewness_HRV, rms, nn50, HRV_pNN50, HRV_pNN25, HRV_TINN, HRV_RMSSD, HRV_LF, HRV_HF, HRV_LFHF, HRV_LFnorm, HRV_HFnorm, HRV_SD1, HRV_SD2, HRV_SDSD, HRV_SDSD_RMSSD, mean_relativeRRI, median_relativeRRI, std_relativeRRI, RMSSD_relativeRRI, kurtosis_relativeRRI, skew_relativeRRI ] features = np.array(list(map(float, features))) return features