1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| import numpy as np from scipy.signal import welch
class RatioBasedThresholding: """ 比值阈值标定(RBT) 论文核心方法: 1. 计算清醒基线θ/β比值 2. 动态计算当前θ/β比值 3. 基于基线偏差生成疲劳标签 """ def __init__(self, fs: int = 200, theta_band=(4, 8), beta_band=(13, 30)): self.fs = fs self.theta_band = theta_band self.beta_band = beta_band def compute_theta_beta_ratio(self, eeg: np.ndarray) -> float: """ 计算θ/β功率比值 Args: eeg: [n_channels, n_samples] EEG信号 Returns: ratio: θ/β功率比值 """ eeg = eeg - eeg.mean(axis=0) freqs, psd = welch(eeg, fs=self.fs, nperseg=512) theta_mask = (freqs >= self.theta_band[0]) & \ (freqs < self.theta_band[1]) beta_mask = (freqs >= self.beta_band[0]) & \ (freqs < self.beta_band[1]) theta_power = np.sum(psd[:, theta_mask], axis=1).mean() beta_power = np.sum(psd[:, beta_mask], axis=1).mean() return theta_power / (beta_power + 1e-8) def calibrate_baseline(self, alert_eeg: np.ndarray, window_sec: int = 10) -> float: """ 从清醒状态EEG标定个体基线 Args: alert_eeg: 清醒状态EEG数据 window_sec: 窗口长度 Returns: baseline_ratio: 基线θ/β比值 """ window_samples = window_sec * self.fs n_windows = len(alert_eeg[0]) // window_samples ratios = [] for i in range(n_windows): window = alert_eeg[:, i*window_samples:(i+1)*window_samples] ratios.append(self.compute_theta_beta_ratio(window)) baseline = np.mean(ratios) baseline_std = np.std(ratios) return baseline, baseline_std def generate_labels(self, eeg: np.ndarray, baseline: float, baseline_std: float, threshold_std: float = 1.5) -> np.ndarray: """ 基于RBT生成疲劳标签 Args: eeg: [n_channels, n_samples] baseline: 基线θ/β比值 baseline_std: 基线标准差 threshold_std: 阈值标准差倍数 Returns: labels: [n_windows] 0=清醒, 1=疲劳 """ window_samples = 10 * self.fs n_windows = len(eeg[0]) // window_samples labels = np.zeros(n_windows) ratios = [] for i in range(n_windows): window = eeg[:, i*window_samples:(i+1)*window_samples] ratio = self.compute_theta_beta_ratio(window) ratios.append(ratio) if ratio > baseline + threshold_std * baseline_std: labels[i] = 1 return labels, ratios
if __name__ == "__main__": rbt = RatioBasedThresholding(fs=200) np.random.seed(42) alert_eeg = np.random.randn(4, 200 * 60) alert_eeg += 2.0 * np.sin(2 * np.pi * 20 * np.arange(200*60) / 200) baseline, baseline_std = rbt.calibrate_baseline(alert_eeg) print(f"基线θ/β比值: {baseline:.4f} ± {baseline_std:.4f}") fatigue_eeg = np.random.randn(4, 200 * 60) fatigue_eeg += 3.0 * np.sin(2 * np.pi * 6 * np.arange(200*60) / 200) labels, ratios = rbt.generate_labels( fatigue_eeg, baseline, baseline_std ) print(f"疲劳窗口比例: {labels.mean():.1%}") print(f"平均θ/β比值: {np.mean(ratios):.4f}") print(f"基线+1.5σ阈值: {baseline + 1.5 * baseline_std:.4f}")
|