阶段二:记借助KIMI速通pyqt后--EEG代码+虚拟数据运行
Q1:好了,现在我想让EEG这份代码加一个虚拟数据让代码跑起来,并且看到滤波与不滤波的效果我可以怎么做呢
好!让 EEG 代码跑起来,看到滤波 vs 不滤波效果 🎯
方案:添加虚拟数据模式
修改 SerialThread,让它在没有硬件时生成带噪声的正弦波。
第一步:修改 SerialThread 的 __init__
class SerialThread(QtCore.QThread):
data_received = QtCore.pyqtSignal(str)
def __init__(self, com_port: str, log_queue: queue.Queue, virtual_mode=False):
super().__init__()
self.com_port = com_port
self.log_queue = log_queue
self.virtual_mode = virtual_mode # ← 新增:虚拟模式标志
# 虚拟数据用
self.virtual_t = 0
self.virtual_noise = 50 # 噪声幅度(μV)
# 原有代码...
self.buffer = ChannelBuffer(capacity=DATA_LENGTH)
# ...
第二步:修改 run 方法
def run(self):
self.running = True
if self.virtual_mode:
# ===== 虚拟模式:生成正弦波 + 噪声 =====
self._run_virtual()
else:
# ===== 真实串口模式(原有代码)=====
self._run_serial()
第三步:添加虚拟数据生成
def _run_virtual(self):
"""虚拟模式:生成8通道正弦波,带50Hz干扰"""
import numpy as np
while self.running:
# 生成8通道数据
values = []
for ch in range(8):
# 基础信号:10Hz正弦波(模拟脑电α波)
base = np.sin(2 * np.pi * 10 * self.virtual_t + ch) * 100
# 添加50Hz工频干扰(模拟电源噪声)
interference = np.sin(2 * np.pi * 50 * self.virtual_t) * self.virtual_noise
# 添加随机噪声
noise = np.random.randn() * 20
value = base + interference + noise
values.append(value)
# 格式化字符串(模拟串口输出)
raw = f"通道数据(μV): {','.join(f'{v:.2f}' for v in values)}"
# 走原有处理流程
self._process_data(raw)
# 模拟500Hz采样率(2ms间隔)
self.virtual_t += 0.002
time.sleep(0.002)
def _process_data(self, raw: str):
"""共用数据处理(日志 + 解析 + 滤波)"""
# 日志
try:
self.log_queue.put_nowait(f"{datetime.now()},{raw}")
except queue.Full:
pass
# 解析并处理
self._process_serial_data(raw)
# 状态栏更新
self.data_received.emit(raw)
第四步:修改主程序启动
# ADCPlotter.__init__ 中
class ADCPlotter(QtWidgets.QMainWindow):
def __init__(self, parent=None, virtual_mode=True): # ← 默认虚拟模式
# ...
# 检测串口或强制虚拟
if virtual_mode:
com_port = "VIRTUAL"
InfoBar.info(title='模式', content='虚拟数据模式(带50Hz干扰)', ...)
else:
com_port = self._detect_com_port()
self.serial_thread = SerialThread(com_port, self.log_queue, virtual_mode=virtual_mode)
第五步:添加滤波开关对比
在控制栏添加按钮,实时切换:
# _setup_ui 中添加
self.virtual_noise_btn = PushButton("噪声:开")
self.virtual_noise_btn.clicked.connect(self._toggle_noise)
def _toggle_noise(self):
"""切换虚拟噪声"""
if self.serial_thread.virtual_noise > 0:
self.serial_thread.virtual_noise = 0 # 关噪声
self.virtual_noise_btn.setText("噪声:关")
self._info("已关闭50Hz干扰")
else:
self.serial_thread.virtual_noise = 50 # 开噪声
self.virtual_noise_btn.setText("噪声:开")
self._info("已开启50Hz干扰")
运行效果
| 操作 | 效果 |
|---|---|
| 启动 | 8通道正弦波,带50Hz干扰 |
| 勾选"滤波开启" | 50Hz干扰被陷波滤波去除,波形变干净 |
| 点击"噪声:关" | 关闭干扰源,看原始信号 |
| 点击"噪声:开" | 开启干扰,看滤波效果 |
快速验证滤波效果
# 在 _process_serial_data 中添加打印
def _process_serial_data(self, raw: str):
values = self._parse_line(raw)
if values:
ch0_raw = values[0]
if self.filter_enabled:
# 滤波后
ch0_filtered = self._apply_filter(ch0_raw, channel=0)
print(f"CH0 原始:{ch0_raw:8.2f} 滤波后:{ch0_filtered:8.2f}")
else:
print(f"CH0 原始:{ch0_raw:8.2f} (未滤波)")
你现在要做的
- 备份原文件
FIFO_plot.py - 修改
SerialThread添加虚拟模式 - 运行看效果:对比勾选/取消"滤波开启"的波形
需要我给出完整修改后的代码文件,还是你先试着改,遇到问题问我?💪
Q2
我按照您的提示修改了代码,但是运行结果跟源代码是一样的,是我哪里修改的不对吗
我看到问题了!你修改了代码,但**主程序入口没有启用虚拟模式**,而且`ADCPlotter.__init__`里有**重复的串口初始化代码**覆盖了虚拟模式。
问题定位
问题1:主程序入口没传参数
# 你的代码第900行
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ADCPlotter() # ← 没传 virtual_mode=True!
window.resize(1200, 800)
window.show()
sys.exit(app.exec_())
问题2:__init__里有重复的串口代码
# 第410-430行:你添加了虚拟模式判断
if virtual_mode:
com_port = "VIRTUAL"
InfoBar.info(...)
else:
com_port = self._detect_com_port()
self.serial_thread = SerialThread(com_port, self.log_queue, virtual_mode=virtual_mode)
# 第432-450行:原来的代码还在,又创建了一次!
com_port = self._detect_com_port() # ← 这里覆盖了!
...
self.serial_thread = SerialThread(com_port, self.log_queue) # ← 又创建一次,没传virtual_mode
结果:第二次创建覆盖了第一次,始终走真实串口模式。
修复方案
修复1:删除重复的串口代码
# 找到这段(约432-450行),全部删除!
###############################################################################
com_port = self._detect_com_port()
if not com_port:
available_ports = [p.device for p in serial.tools.list_ports.comports()]
if not available_ports:
QtWidgets.QMessageBox.critical(self, "错误", "未检测到任何串口")
self.log_thread.stop()
sys.exit(1)
com_port, ok = QtWidgets.QInputDialog.getItem(self, "选择串口", "请选择串口设备:", available_ports, 0,
False)
if not ok or not com_port:
sys.exit(1)
self.serial_thread = SerialThread(com_port, self.log_queue)
self.serial_thread.data_received.connect(self._on_serial_line)
self.serial_thread.start()
###############################################################################
# 保留你添加的这段(约410-430行):
if virtual_mode:
com_port = "VIRTUAL"
InfoBar.info(title='模式', content='虚拟数据模式(带50Hz干扰)')
else:
com_port = self._detect_com_port()
self.serial_thread = SerialThread(com_port, self.log_queue, virtual_mode=virtual_mode)
self.serial_thread.data_received.connect(self._on_serial_line) # 加上这行信号连接
self.serial_thread.start() # 加上启动
修复2:主程序启用虚拟模式
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ADCPlotter(virtual_mode=True) # ← 关键:传参数!
window.resize(1200, 800)
window.show()
sys.exit(app.exec_())
快速验证
修改后运行,应该看到:
1. 弹出提示:"虚拟数据模式(带50Hz干扰)"
2. 波形显示8通道正弦波
3. 点击"滤波开启"取消勾选,50Hz干扰出现(波形变粗/抖动)
4. 勾选"滤波开启",干扰被去除(波形变平滑)
或者:简化方案,强制虚拟模式
如果不想改主程序,在__init__里强制:
def __init__(self, parent=None, virtual_mode=True): # ← 默认True
...
# 删除所有真实串口代码,只保留:
com_port = "VIRTUAL"
InfoBar.info(title='模式', content='虚拟数据模式(带50Hz干扰)')
self.serial_thread = SerialThread(com_port, self.log_queue, virtual_mode=True)
self.serial_thread.data_received.connect(self._on_serial_line)
self.serial_thread.start()
你现在要做的
- 删除
###包围的重复串口代码(约432-450行) - 确认 只保留一段
SerialThread创建代码 - 修改 主程序
ADCPlotter(virtual_mode=True) - 运行,看是否有"虚拟数据模式"提示
改完运行,告诉我结果! 💪
成功显示图像时刻!Amazing!
真的非常非常感谢AI感谢kimi
Q3:天啊,运行成功了!但是画面中的数据十分杂乱。可以修改成只显示单通道吗,然后把画面的更新频率改低一些
太好了!运行成功!🎉 画面杂乱是因为8通道叠加+高频率更新,我来帮你优化。
修改目标
| 问题 | 解决方案 |
|---|---|
| 8通道太乱 | 只显示单通道(CH1) |
| 更新太快 | 降低频率或降低采样率 |
| 波形太密 | 减少PLOT_LENGTH显示点数 |
快速修改(3处)
修改1:只显示CH1(约第480行)
def _update_time_plot(self):
x, data = self.serial_thread.get_plot_data(PLOT_LENGTH)
if data.size == 0:
return
# 只显示CH1,忽略勾选框
# active = [i for i, cb in enumerate(self.ch_boxes) if cb.isChecked()]
# if not active:
# return
# for i in active:
# self.time_curves[i].setData(x, data[i])
# 只画CH1
self.time_curves[0].setData(x, data[0])
# 隐藏其他通道
for i in range(1, 8):
self.time_curves[i].setData([], [])
修改2:降低更新频率(第20行)
UPDATE_INTERVAL = 200 # 60 → 200,每200ms刷新一次(5fps,更慢更省CPU)
修改3:减少显示点数(第23行)
PLOT_LENGTH = 500 # 1000 → 500,显示500个点(画面更宽松)
额外优化:降低虚拟数据采样率
虚拟模式生成数据太快,也降一点(第280行):
# 模拟200Hz采样率(5ms间隔),而不是500Hz
self.virtual_t += 0.005 # 0.002 → 0.005
time.sleep(0.005) # 0.002 → 0.005
修改后效果
| 修改前 | 修改后 |
|---|---|
| 8通道叠加,混乱 | 单通道清晰 |
| 60ms刷新(16fps) | 200ms刷新(5fps),更慢更稳 |
| 显示1000点,密集 | 显示500点,宽松 |
| 500Hz虚拟采样 | 200Hz虚拟采样,波形更平滑 |
完整替换 _update_time_plot 函数
def _update_time_plot(self):
x, data = self.serial_thread.get_plot_data(PLOT_LENGTH)
if data.size == 0:
return
# 只显示CH1,单通道清晰
self.time_curves[0].setData(x, data[0])
# 清空其他通道
for i in range(1, 8):
self.time_curves[i].setData([], [])
# 自动滚动X轴
if len(x) > 0:
self.time_plot.setXRange(x[0], x[-1])
# 自动Y轴
if self.auto_y_box.isChecked():
ch0_data = data[0]
mask = ~np.isnan(ch0_data)
if np.any(mask):
valid_data = ch0_data[mask]
y_min, y_max = np.min(valid_data), np.max(valid_data)
if y_min != y_max:
margin = (y_max - y_min) * 0.1 or 1.0
self.time_plot.setYRange(y_min - margin, y_max + margin)
else:
self.time_plot.setYRange(y_min - 1, y_max + 1)
现在修改,运行看效果!
改完应该看到:一条蓝色正弦波,缓慢滚动,清晰平滑。
成功了吗?💪
Q4:为什么我把界面上面的CH2.3.4.5取消掉了,但是上位机界面并没有把这几个波形取消掉呢
因为你只改了界面勾选框的状态,但代码还在画所有勾选的通道!
问题所在
# 你的代码(约第480行)
active = [i for i, cb in enumerate(self.ch_boxes) if cb.isChecked()]
# 这里根据勾选框状态决定画哪些通道
for i in active:
self.time_curves[i].setData(x, data[i])
# 所以取消勾选 → active列表里没有 → 不画
但你说取消后还在画,说明可能是:
| 可能原因 | 检查 |
|---|---|
| 改完代码没保存 | Ctrl+S 保存 |
| 改完没重启程序 | 关掉重新运行 |
| 改错地方了 | 确认改的是_update_time_plot |
| 有缓存 | 删掉__pycache__文件夹 |
快速验证修改是否生效
在_update_time_plot里加打印:
def _update_time_plot(self):
x, data = self.serial_thread.get_plot_data(PLOT_LENGTH)
# 加这行!看控制台输出
active = [i for i, cb in enumerate(self.ch_boxes) if cb.isChecked()]
print(f"当前勾选的通道: {active}") # ← 加这行
if not active:
return
# ...
运行后看控制台:
- 只勾CH1 → 应该显示
[0] - 勾CH1+CH2 → 应该显示
[0, 1]
如果显示不对,说明界面和代码没连上。
彻底解决方案:强制只画CH1(不管勾选)
def _update_time_plot(self):
x, data = self.serial_thread.get_plot_data(PLOT_LENGTH)
if data.size == 0:
return
# ===== 强制只画CH1 =====
self.time_curves[0].setData(x, data[0])
# 其他通道强制清空
for i in range(1, 8):
self.time_curves[i].setData([], [])
# 自动Y轴只看CH1
if self.auto_y_box.isChecked():
ch0 = data[0]
mask = ~np.isnan(ch0)
if np.any(mask):
y_min, y_max = np.min(ch0[mask]), np.max(ch0[mask])
margin = (y_max - y_min) * 0.1 or 1.0
self.time_plot.setYRange(y_min - margin, y_max + margin)
检查步骤
- 保存代码(Ctrl+S)
- 完全关闭程序(点X)
- 重新运行
- 看是否只有蓝色一条线
还是不行的话,把现在的_update_time_plot完整代码发给我,我帮你检查!💪
Q5:现在可以看到单独1条线了,我竟然有点不知道下一步应该做什么
太好了!单通道成功!🎉 你现在处于关键转折点——基础功能都通了,接下来要选方向深入。
你现在掌握的技能
| 技能 | 成果 |
|---|---|
| PyQt5窗口 | 创建、控件、布局 |
| 信号槽 | 跨线程通信、定时刷新 |
| QThread | 后台数据生成、暂停/继续 |
| pyqtgraph | 实时曲线、滚动显示 |
| 虚拟数据 | 模拟硬件、测试滤波 |
下一步选择(3个方向)
方向A:深入理解滤波器(推荐,最核心)
目标:真正看懂iirnotch和firwin的作用
任务:
- 在虚拟数据里单独控制50Hz干扰的幅度
- 对比勾选/取消"滤波开启"的波形差异
- 修改
NOTCH_FREQ = 50为其他值(如30),看滤波效果变化
代码修改(第265行):
# 添加滑块控制干扰幅度
self.interference_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.interference_slider.setRange(0, 100)
self.interference_slider.setValue(50)
self.interference_slider.valueChanged.connect(self._change_interference)
def _change_interference(self, value):
self.serial_thread.virtual_noise = value # 实时改噪声幅度
方向B:完整还原8通道EEG
目标:理解原代码的8通道架构
任务:
- 恢复8通道显示,但分层排列(不重叠)
- 每个通道Y轴偏移,像真实脑电图那样
效果:
CH1: ~~~~~~ (Y轴中心 +300)
CH2: ~~~~~~ (Y轴中心 +200)
CH3: ~~~~~~ (Y轴中心 +100)
...
方向C:数据保存与分析
目标:把虚拟数据存下来,用Python离线分析
任务:
- 点击"保存"按钮,把当前1秒数据存为CSV
- 用
matplotlib画静态图 - 计算FFT频谱,对比滤波前后
我的建议
选A!理由:
- 滤波是这份代码的核心技术点
- 你现在能看到效果,是学习的最佳时机
- 搞懂后,你能向他人解释"为什么用50Hz陷波"
立即开始(A方向)
第一步:添加噪声控制滑块
在_setup_ui里添加(第395行附近):
# 噪声幅度滑块
self.noise_label = QtWidgets.QLabel("50Hz干扰:50")
ctrl.addWidget(self.noise_label)
self.noise_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.noise_slider.setRange(0, 100)
self.noise_slider.setValue(50)
self.noise_slider.valueChanged.connect(self._update_noise)
ctrl.addWidget(self.noise_slider)
添加回调函数:
def _update_noise(self, value):
self.serial_thread.virtual_noise = value
self.noise_label.setText(f"50Hz干扰:{value}")
运行后:拖动滑块,实时改变干扰强度,观察滤波效果!
选哪个方向?
或者你有其他想法(比如加FFT频谱显示、改颜色主题)?
告诉我,帮你规划!💪
至此,我真的觉得AI帮助了我完成过去靠自己根本不可能完成的艰难任务。
全球科技通史的作者说:技术的进步可以叠加的。
我又想起博弈论中的正和游戏,技术的进步就很像正和游戏。太神奇了
更多推荐



所有评论(0)