1197 lines
62 KiB
Python
1197 lines
62 KiB
Python
"""
|
||
@author:Yosa, Marques
|
||
@file:Main_Quality_Relabel_GUI.py
|
||
@email:2023025086@m.scnu.edu.cn, 2021022362@m.scnu.edu.cn
|
||
@time:2025/1/4
|
||
"""
|
||
|
||
import sys
|
||
from logging import NOTSET, getLogger, FileHandler, Formatter, StreamHandler, info, error, debug
|
||
from time import time, strftime, localtime
|
||
from numpy import load, nan, zeros, append, linspace, place
|
||
from pandas import DataFrame, read_csv, read_excel, Series, concat
|
||
from matplotlib import use
|
||
from matplotlib import pyplot as plt, gridspec
|
||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
|
||
from pyedflib import EdfReader
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from PyQt5.QtCore import QCoreApplication, QThread, pyqtSignal
|
||
from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox, QButtonGroup, QApplication
|
||
|
||
from MainWindow import Ui_MainWindow
|
||
from utils.Preprocessing import Butterworth
|
||
|
||
|
||
use("Qt5Agg") # 声明使用QT5
|
||
|
||
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
|
||
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
|
||
|
||
# ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2',
|
||
# 'EEG O2-A1', 'EOG Right', 'EOG Left', 'EMG Chin', 'ECG I', 'RR',
|
||
# 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'Effort Abd',
|
||
# 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2',
|
||
# 'EEG A1-A2', 'Imp']
|
||
|
||
# 设置日志
|
||
logger = getLogger()
|
||
logger.setLevel(NOTSET)
|
||
realtime = strftime('%Y%m%d', localtime(time()))
|
||
if not Path("logs").exists():
|
||
Path("logs").mkdir(exist_ok=True)
|
||
fh = FileHandler(Path("logs") / (realtime + ".log"), mode='a')
|
||
fh.setLevel(NOTSET)
|
||
fh.setFormatter(Formatter("%(asctime)s: %(message)s"))
|
||
logger.addHandler(fh)
|
||
|
||
ch = StreamHandler()
|
||
ch.setLevel(NOTSET)
|
||
ch.setFormatter(Formatter("%(asctime)s: %(message)s"))
|
||
logger.addHandler(ch)
|
||
getLogger('matplotlib.font_manager').disabled = True
|
||
info("------------------------------------")
|
||
info("-----Main_Quality_Relabel_GUI.py----")
|
||
|
||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||
# 可选择的通道
|
||
base_channel = ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2', 'EEG O2-A1', 'EOG Right',
|
||
'EOG Left', 'EMG Chin', 'ECG I', 'RR', 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'HR',
|
||
'Effort Abd', 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2', 'EEG A1-A2', 'Imp']
|
||
|
||
# 显示事件
|
||
base_event = ["Hypopnea", "Central apnea", "Obstructive apnea", "Mixed apnea", "Desaturation"]
|
||
|
||
# 设定事件和其对应颜色
|
||
# event_code color event
|
||
# 0 黑色 背景
|
||
# 1 粉色 低通气
|
||
# 2 蓝色 中枢性
|
||
# 3 红色 阻塞型
|
||
# 4 灰色 混合型
|
||
# 5 绿色 血氧饱和度下降
|
||
# 6 橙色 大体动
|
||
# 7 橙色 小体动
|
||
# 8 橙色 深呼吸
|
||
# 9 橙色 脉冲体动
|
||
# 10 橙色 无效片段
|
||
color_cycle = ["black", "pink", "blue", "red", "silver", "green", "orange", "orange", "orange", "orange", "orange"]
|
||
|
||
# 程序初始化操作
|
||
def __init__(self):
|
||
super(MainWindow, self).__init__()
|
||
self.setupUi(self)
|
||
self.figure = plt.figure(figsize=(12, 6), dpi=150)
|
||
self.canvas = FigureCanvasQTAgg(self.figure)
|
||
self.figToolbar = CustomNavigationToolbar(self.canvas, self, self.slot_btn_resetOriginalView)
|
||
self.verticalLayout_canvas.addWidget(self.canvas)
|
||
self.verticalLayout_canvas.addWidget(self.figToolbar)
|
||
self.msgBox = QMessageBox()
|
||
self.msgBox.setWindowTitle("消息")
|
||
self.pushButton_left.clicked.connect(self.slot_btn_left)
|
||
self.pushButton_right.clicked.connect(self.slot_btn_right)
|
||
self.pushButton_confirmLabel.clicked.connect(self.slot_btn_run)
|
||
self.pushButton_confirmLabel.setText("请选择数据路径")
|
||
self.pushButton_previous10s.setEnabled(False)
|
||
self.pushButton_previous30s.setEnabled(False)
|
||
self.pushButton_previous60s.setEnabled(False)
|
||
self.pushButton_next10s.setEnabled(False)
|
||
self.pushButton_next30s.setEnabled(False)
|
||
self.pushButton_next60s.setEnabled(False)
|
||
self.time_move_count = int(0)
|
||
self.pushButton_previous10s.clicked.connect(self.slot_btn_previous10s)
|
||
self.pushButton_previous30s.clicked.connect(self.slot_btn_previous30s)
|
||
self.pushButton_previous60s.clicked.connect(self.slot_btn_previous60s)
|
||
self.pushButton_next10s.clicked.connect(self.slot_btn_next10s)
|
||
self.pushButton_next30s.clicked.connect(self.slot_btn_next30s)
|
||
self.pushButton_next60s.clicked.connect(self.slot_btn_next60s)
|
||
self.action_selectPath.triggered.connect(self.slot_btn_selectPath)
|
||
self.pushButton_left.setEnabled(False)
|
||
self.pushButton_right.setEnabled(False)
|
||
self.pushButton_confirmLabel.setEnabled(False)
|
||
self.pushButton_quick_remark_input_waitingForTalk.setEnabled(False)
|
||
self.pushButton_quick_remark_input_waitingForTalk.clicked.connect(
|
||
self.slot_btn_quick_remark_input_waitingForTalk)
|
||
self.pushButton_quick_remark_input_maybeDesaturation.setEnabled(False)
|
||
self.pushButton_quick_remark_input_maybeDesaturation.clicked.connect(
|
||
self.slot_btn_quick_remark_input_maybeDesaturation)
|
||
self.pushButton_quick_remark_input_maybeWrongLabeled.setEnabled(False)
|
||
self.pushButton_quick_remark_input_maybeWrongLabeled.clicked.connect(
|
||
self.slot_btn_quick_remark_input_maybeWrongLabeled)
|
||
self.pushButton_quick_remark_input_durationNoEnough.setEnabled(False)
|
||
self.pushButton_quick_remark_input_durationNoEnough.clicked.connect(
|
||
self.slot_btn_quick_remark_input_durationNoEnough)
|
||
self.pushButton_quick_remark_input_littleChange.setEnabled(False)
|
||
self.pushButton_quick_remark_input_littleChange.clicked.connect(
|
||
self.slot_btn_quick_remark_input_littleChange)
|
||
self.pushButton_quick_remark_input_noNormalRespBetweenArtifact.setEnabled(False)
|
||
self.pushButton_quick_remark_input_noNormalRespBetweenArtifact.clicked.connect(
|
||
self.slot_btn_quick_remark_input_noNormalRespBetweenArtifact)
|
||
self.pushButton_quick_remark_input_lowSignalNoiseRatio.setEnabled(False)
|
||
self.pushButton_quick_remark_input_lowSignalNoiseRatio.clicked.connect(
|
||
self.slot_btn_quick_remark_input_lowSignalNoiseRatio)
|
||
self.pushButton_quick_remark_input_changeOnMiddle.setEnabled(False)
|
||
self.pushButton_quick_remark_input_changeOnMiddle.clicked.connect(
|
||
self.slot_btn_quick_remark_input_changeOnMiddle)
|
||
self.checkBox_examineBySecond.setEnabled(False)
|
||
self.checkBox_examineBySecond.clicked.connect(self.enable_checkBox_examineBySecond)
|
||
self.lineEdit_start_bcg_index.setEnabled(False)
|
||
self.lineEdit_frequency.setEnabled(False)
|
||
self.lineEdit_bcg_frequency.setEnabled(False)
|
||
self.lineEdit_front_add_second.setEnabled(False)
|
||
self.lineEdit_back_add_second.setEnabled(False)
|
||
self.radioButton_1_class.setEnabled(False)
|
||
self.radioButton_2_class.setEnabled(False)
|
||
self.radioButton_3_class.setEnabled(False)
|
||
self.buttonGroup_1 = QButtonGroup()
|
||
self.buttonGroup_1.addButton(self.radioButton_1_class)
|
||
self.buttonGroup_1.addButton(self.radioButton_2_class)
|
||
self.buttonGroup_1.addButton(self.radioButton_3_class)
|
||
self.lineEdit_remark.setEnabled(False)
|
||
self.lineEdit_correctStart.setEnabled(False)
|
||
self.lineEdit_correctEnd.setEnabled(False)
|
||
self.comboBox_sampID.setEnabled(False)
|
||
self.checkBox_OSA.setEnabled(False)
|
||
self.checkBox_CSA.setEnabled(False)
|
||
self.checkBox_MSA.setEnabled(False)
|
||
self.checkBox_HPY.setEnabled(False)
|
||
self.checkBox_examineLabeled.setEnabled(False)
|
||
self.checkBox_examineLabeled.clicked.connect(self.enable_checkBox_examineLabeled)
|
||
self.radioButton_OSA.setEnabled(False)
|
||
self.radioButton_CSA.setEnabled(False)
|
||
self.radioButton_MSA.setEnabled(False)
|
||
self.radioButton_HPY.setEnabled(False)
|
||
self.buttonGroup_2 = QButtonGroup()
|
||
self.buttonGroup_2.addButton(self.radioButton_OSA)
|
||
self.buttonGroup_2.addButton(self.radioButton_CSA)
|
||
self.buttonGroup_2.addButton(self.radioButton_MSA)
|
||
self.buttonGroup_2.addButton(self.radioButton_HPY)
|
||
self.thread_textbrowserUpdate = Thread_textbrowserUpdate()
|
||
self.thread_textbrowserUpdate.trigger.connect(self.textBrowser_update)
|
||
self.textBrowser_update("提示:请点击左上角“打开”菜单来开始任务")
|
||
|
||
# 选择路径按钮的槽函数
|
||
def slot_btn_selectPath(self):
|
||
fileDialog = QFileDialog()
|
||
fileDialog.setFileMode(QFileDialog.Directory)
|
||
fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
|
||
if fileDialog.exec_() == QFileDialog.Accepted:
|
||
self.dir_path = fileDialog.selectedFiles()[0]
|
||
if not self.dir_path:
|
||
info("Data Path not Exist...")
|
||
self.textBrowser_update("操作:载入存档错误,存档不存在")
|
||
self.msgBox.setText("载入存档错误,存档不存在")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
|
||
self.dir_path = Path(self.dir_path)
|
||
info("Loading Data Path...")
|
||
self.PSG_Data_Path = self.dir_path / "PSG"
|
||
self.BCG_Data_Path = self.dir_path / "BCG"
|
||
self.BCG_Label_Path = self.dir_path / "BCG_label"
|
||
self.Artifact_Label_Path = self.dir_path / "Artifact_label"
|
||
if self.PSG_Data_Path.exists() and self.BCG_Data_Path.exists() and self.BCG_Label_Path.exists() and self.Artifact_Label_Path.exists() and self.Artifact_Label_Path.exists():
|
||
sampIDs = self.BCG_Data_Path.glob('*.*')
|
||
sampID_for_comboBox = []
|
||
for sampID in sampIDs:
|
||
sampID = sampID.name.replace("samp.npy", "")
|
||
sampID_for_comboBox.append(sampID)
|
||
bcg_path = self.BCG_Data_Path / f"{sampID}samp.npy"
|
||
ecg_path = self.PSG_Data_Path / f"A{str(sampID).rjust(7, '0')}.edf"
|
||
bcg_label_path = self.BCG_Label_Path / f"export{sampID}_all.csv"
|
||
if not bcg_path.exists():
|
||
error(f"Can't find {bcg_path}!")
|
||
self.msgBox.setText(f"找不到数据{bcg_path}")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
if not ecg_path.exists():
|
||
error(f"Can't find {ecg_path}!")
|
||
self.msgBox.setText(f"找不到数据{ecg_path}")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
if not bcg_label_path.exists():
|
||
error(f"Can't find {bcg_label_path}!")
|
||
self.msgBox.setText(f"找不到数据{bcg_label_path}")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
self.comboBox_sampID.setEnabled(True)
|
||
self.lineEdit_start_bcg_index.setEnabled(True)
|
||
self.comboBox_sampID.addItems(sampID_for_comboBox)
|
||
self.lineEdit_start_bcg_index.setEnabled(True)
|
||
self.lineEdit_frequency.setEnabled(True)
|
||
self.lineEdit_bcg_frequency.setEnabled(True)
|
||
self.lineEdit_front_add_second.setEnabled(True)
|
||
self.lineEdit_back_add_second.setEnabled(True)
|
||
self.checkBox_OSA.setEnabled(True)
|
||
self.checkBox_CSA.setEnabled(True)
|
||
self.checkBox_MSA.setEnabled(True)
|
||
self.checkBox_HPY.setEnabled(True)
|
||
self.action_selectPath.setEnabled(False)
|
||
self.pushButton_confirmLabel.setEnabled(True)
|
||
self.pushButton_confirmLabel.setText("开始打标")
|
||
MainWindow.setWindowTitle(self, QCoreApplication.translate("MainWindow",
|
||
"Main_Quality_Relabel_GUI - Data Path: " + str(self.dir_path)))
|
||
info("Successfully Loaded Data Path.")
|
||
self.textBrowser_update("操作:数据路径选择成功")
|
||
else:
|
||
info("Failed to Load Data Path.")
|
||
self.textBrowser_update("操作:数据路径选择错误,缺乏必要数据文件夹")
|
||
self.msgBox.setText("数据路径选择错误,缺乏必要数据文件夹")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
|
||
else:
|
||
info("Canceled Loading Data Path.")
|
||
self.textBrowser_update("提示:数据路径选择取消")
|
||
self.msgBox.setText("数据路径选择取消")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
|
||
# 开始打标按钮的槽函数
|
||
def slot_btn_run(self):
|
||
self.init_variable()
|
||
self.check_channel()
|
||
self.read_data()
|
||
self.read_event()
|
||
self.read_artifact_label()
|
||
if self.plotEventIndex >= len(self.bcg_event_label_index_list) or self.plotEventIndex < 0:
|
||
self.msgBox.setText("输入起始心晓事件序列过大或过小导致错误")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.pushButton_left.setEnabled(False)
|
||
self.pushButton_right.setEnabled(True)
|
||
self.pushButton_confirmLabel.setText("确定打标参数(S)")
|
||
self.pushButton_confirmLabel.setShortcut(QCoreApplication.translate("MainWindow", "S"))
|
||
self.pushButton_confirmLabel.clicked.disconnect(self.slot_btn_run)
|
||
self.pushButton_confirmLabel.clicked.connect(self.slot_btn_confirmLabel)
|
||
self.pushButton_quick_remark_input_waitingForTalk.setEnabled(True)
|
||
self.pushButton_quick_remark_input_maybeDesaturation.setEnabled(True)
|
||
self.pushButton_quick_remark_input_maybeWrongLabeled.setEnabled(True)
|
||
self.pushButton_quick_remark_input_durationNoEnough.setEnabled(True)
|
||
self.pushButton_quick_remark_input_littleChange.setEnabled(True)
|
||
self.pushButton_quick_remark_input_noNormalRespBetweenArtifact.setEnabled(True)
|
||
self.pushButton_quick_remark_input_lowSignalNoiseRatio.setEnabled(True)
|
||
self.pushButton_quick_remark_input_changeOnMiddle.setEnabled(True)
|
||
self.comboBox_sampID.setEnabled(False)
|
||
self.lineEdit_start_bcg_index.setEnabled(False)
|
||
self.lineEdit_frequency.setEnabled(False)
|
||
self.lineEdit_bcg_frequency.setEnabled(False)
|
||
self.lineEdit_front_add_second.setEnabled(False)
|
||
self.lineEdit_back_add_second.setEnabled(False)
|
||
self.checkBox_OSA.setEnabled(False)
|
||
self.checkBox_CSA.setEnabled(False)
|
||
self.checkBox_MSA.setEnabled(False)
|
||
self.checkBox_HPY.setEnabled(False)
|
||
self.radioButton_1_class.setEnabled(True)
|
||
self.radioButton_2_class.setEnabled(True)
|
||
self.radioButton_3_class.setEnabled(True)
|
||
self.checkBox_examineLabeled.setEnabled(True)
|
||
self.checkBox_examineBySecond.setEnabled(True)
|
||
self.radioButton_OSA.setEnabled(True)
|
||
self.radioButton_CSA.setEnabled(True)
|
||
self.radioButton_MSA.setEnabled(True)
|
||
self.radioButton_HPY.setEnabled(True)
|
||
self.lineEdit_remark.setEnabled(True)
|
||
self.lineEdit_correctStart.setEnabled(True)
|
||
self.lineEdit_correctEnd.setEnabled(True)
|
||
self.df_saLabel = read_csv(
|
||
self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_all.csv",
|
||
encoding="gbk")
|
||
csv_headers = self.df_saLabel.columns.tolist()
|
||
if not (("score" in csv_headers) and ("remark" in csv_headers) and ("correct_Start" in csv_headers) and (
|
||
"correct_End") in csv_headers and ("isLabeled" in csv_headers)):
|
||
self.df_saLabel["score"] = "-1"
|
||
self.df_saLabel["remark"] = ""
|
||
self.df_saLabel["correct_Start"] = "-1"
|
||
self.df_saLabel["correct_End"] = "-1"
|
||
self.df_saLabel["correct_EventsType"] = ""
|
||
self.df_saLabel["isLabeled"] = "-1"
|
||
self.df_saLabel["score"] = self.df_saLabel["score"].astype(int)
|
||
self.df_saLabel["remark"] = self.df_saLabel["remark"].astype(str)
|
||
self.df_saLabel["correct_Start"] = self.df_saLabel["correct_Start"].astype(int)
|
||
self.df_saLabel["correct_End"] = self.df_saLabel["correct_End"].astype(int)
|
||
self.df_saLabel["correct_EventsType"] = self.df_saLabel["correct_EventsType"].astype(str)
|
||
self.df_saLabel["isLabeled"] = self.df_saLabel["isLabeled"].astype(int)
|
||
self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex])
|
||
if not Path(self.BCG_Label_Path /
|
||
f"export{self.comboBox_sampID.currentText()}_addNew.csv").exists():
|
||
self.pd_examineBySecond = DataFrame(columns=self.df_saLabel.columns)
|
||
self.pd_examineBySecond["score"] = self.pd_examineBySecond["score"].astype(int)
|
||
self.pd_examineBySecond["remark"] = self.pd_examineBySecond["remark"].astype(str)
|
||
self.pd_examineBySecond["correct_Start"] = self.pd_examineBySecond["correct_Start"].astype(int)
|
||
self.pd_examineBySecond["correct_End"] = self.pd_examineBySecond["correct_End"].astype(int)
|
||
self.pd_examineBySecond["correct_EventsType"] = self.pd_examineBySecond["correct_EventsType"].astype(
|
||
str)
|
||
self.pd_examineBySecond["isLabeled"] = self.pd_examineBySecond["isLabeled"].astype(int)
|
||
self.pd_examineBySecond.to_csv(
|
||
self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_addNew.csv",
|
||
index=False, encoding="gbk")
|
||
else:
|
||
self.pd_examineBySecond = read_csv(
|
||
self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_addNew.csv",
|
||
encoding="gbk")
|
||
if (self.df_saLabel["isLabeled"] == 1).all():
|
||
self.checkBox_examineLabeled.setChecked(False)
|
||
self.msgBox.setText("该份数据打标已全部完成")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
self.df_saLabel.to_csv(self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_all.csv",
|
||
mode='w', index=None, encoding="gbk")
|
||
self.show_one_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second)
|
||
if not str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan":
|
||
self.lineEdit_remark.setText(
|
||
str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]))
|
||
else:
|
||
self.lineEdit_remark.setText("")
|
||
if str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1":
|
||
self.radioButton_1_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2":
|
||
self.radioButton_2_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3":
|
||
self.radioButton_3_class.setChecked(True)
|
||
else:
|
||
self.radioButton_2_class.setChecked(True)
|
||
self.textBrowser_update("操作:开始打标")
|
||
|
||
# 上一个事件按钮的槽函数
|
||
def slot_btn_left(self):
|
||
self.figure.clear()
|
||
self.plotEventIndex = self.plotEventIndex - 1
|
||
if self.checkBox_examineLabeled.isChecked() == True:
|
||
while self.df_saLabel.at[self.bcg_event_label_index_list[
|
||
self.plotEventIndex], "isLabeled"] == 1 and self.plotEventIndex > self.start_bcg_index:
|
||
self.plotEventIndex = self.plotEventIndex - 1
|
||
self.show_one_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second)
|
||
if not str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan":
|
||
self.lineEdit_remark.setText(
|
||
str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]))
|
||
else:
|
||
self.lineEdit_remark.setText("")
|
||
self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex])
|
||
if str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1":
|
||
self.radioButton_1_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2":
|
||
self.radioButton_2_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3":
|
||
self.radioButton_3_class.setChecked(True)
|
||
else:
|
||
self.radioButton_2_class.setChecked(True)
|
||
self.textBrowser_update("操作:↑上一个事件,index" + str(self.plotEventIndex + 1))
|
||
if self.plotEventIndex <= self.start_bcg_index:
|
||
self.pushButton_left.setEnabled(False)
|
||
self.pushButton_right.setEnabled(True)
|
||
self.msgBox.setText("你正在查看第一个事件")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.pushButton_left.setEnabled(True)
|
||
self.pushButton_right.setEnabled(True)
|
||
|
||
# 下一个事件按钮的槽函数
|
||
def slot_btn_right(self):
|
||
self.figure.clear()
|
||
self.plotEventIndex = self.plotEventIndex + 1
|
||
if self.checkBox_examineLabeled.isChecked() == True:
|
||
while self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] == 1 and self.plotEventIndex < len(
|
||
self.bcg_event_label_filtered_df) - 1:
|
||
self.plotEventIndex = self.plotEventIndex + 1
|
||
self.show_one_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second)
|
||
if not str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan":
|
||
self.lineEdit_remark.setText(
|
||
str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]))
|
||
else:
|
||
self.lineEdit_remark.setText("")
|
||
self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex])
|
||
if str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1":
|
||
self.radioButton_1_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2":
|
||
self.radioButton_2_class.setChecked(True)
|
||
elif str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3":
|
||
self.radioButton_3_class.setChecked(True)
|
||
else:
|
||
self.radioButton_2_class.setChecked(True)
|
||
self.textBrowser_update("操作:↓下一个事件,index" + str(self.plotEventIndex + 1))
|
||
if self.plotEventIndex >= len(self.bcg_event_label_filtered_df) - 1:
|
||
self.pushButton_left.setEnabled(True)
|
||
self.pushButton_right.setEnabled(False)
|
||
self.msgBox.setText("你正在查看最后一个事件")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.pushButton_left.setEnabled(True)
|
||
self.pushButton_right.setEnabled(True)
|
||
|
||
# 确定打标参数按钮的槽函数
|
||
def slot_btn_confirmLabel(self):
|
||
if int(self.lineEdit_correctEnd.text()) - int(self.lineEdit_correctStart.text()) < 10:
|
||
self.msgBox.setText("当前标注的事件的持续时间小于10秒")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
if self.checkBox_examineBySecond.isChecked() == True:
|
||
if int(self.lineEdit_correctStart.text()) < int(self.lineEdit_correctEnd.text()):
|
||
if self.radioButton_1_class.isChecked() == True:
|
||
score = int(1)
|
||
elif self.radioButton_2_class.isChecked() == True:
|
||
score = int(2)
|
||
elif self.radioButton_3_class.isChecked() == True:
|
||
score = int(3)
|
||
remark = self.lineEdit_remark.text()
|
||
correct_Start = int(self.lineEdit_correctStart.text())
|
||
correct_End = int(self.lineEdit_correctEnd.text())
|
||
if self.radioButton_OSA.isChecked() == True:
|
||
correct_EventsType = "Obstructive apnea"
|
||
elif self.radioButton_CSA.isChecked() == True:
|
||
correct_EventsType = "Central apnea"
|
||
elif self.radioButton_MSA.isChecked() == True:
|
||
correct_EventsType = "Mixed apnea"
|
||
elif self.radioButton_HPY.isChecked() == True:
|
||
correct_EventsType = "Hypopnea"
|
||
isLabeled = int(1)
|
||
self.pd_examineBySecond = self.pd_add_new_row(self.pd_examineBySecond, score, remark, correct_Start,
|
||
correct_End, correct_EventsType, isLabeled)
|
||
self.pd_examineBySecond.to_csv(
|
||
self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_addNew.csv",
|
||
mode='w', index=None, encoding="gbk")
|
||
self.textBrowser_update(
|
||
"操作:保存新事件打标结果到csv。" + "score:" + str(score) + ",correct_Start:" + str(
|
||
correct_Start) + ",correct_End:" + str(correct_End) + ",correct_EventsType:" + str(
|
||
correct_EventsType))
|
||
else:
|
||
self.msgBox.setText("起始时间和终止时间输入错误")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
elif not self.checkBox_examineBySecond.isChecked():
|
||
if int(self.lineEdit_correctStart.text()) < int(self.lineEdit_correctEnd.text()):
|
||
if self.radioButton_1_class.isChecked():
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(1)
|
||
elif self.radioButton_2_class.isChecked():
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(2)
|
||
elif self.radioButton_3_class.isChecked():
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(3)
|
||
self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "remark"] = self.lineEdit_remark.text()
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"] = int(
|
||
self.lineEdit_correctStart.text())
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"] = int(
|
||
self.lineEdit_correctEnd.text())
|
||
if self.radioButton_OSA.isChecked():
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[
|
||
self.plotEventIndex], "correct_EventsType"] = "Obstructive apnea"
|
||
elif self.radioButton_CSA.isChecked():
|
||
self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Central apnea"
|
||
elif self.radioButton_MSA.isChecked():
|
||
self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Mixed apnea"
|
||
elif self.radioButton_HPY.isChecked():
|
||
self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Hypopnea"
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] = int(1)
|
||
self.df_saLabel.to_csv(self.BCG_Label_Path / f"export{self.comboBox_sampID.currentText()}_all.csv",
|
||
mode='w', index=None, encoding="gbk")
|
||
self.textBrowser_update(
|
||
"操作:保存index" + str(self.plotEventIndex + 1) + "打标结果到csv。" + "score:" + str(
|
||
self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[
|
||
self.plotEventIndex], "score"]) + ",correct_Start:" + str(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[
|
||
self.plotEventIndex], "correct_Start"]) + ",correct_End:" + str(self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[
|
||
self.plotEventIndex], "correct_End"]) + ",correct_EventsType:" + str(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"]))
|
||
if (self.df_saLabel.loc[self.bcg_event_label_index_list]["isLabeled"] == 1).all():
|
||
self.msgBox.setText("该份数据打标已全部完成")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.msgBox.setText("起始时间和终止时间输入错误")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
|
||
# 快速备注输入中按钮的槽函数
|
||
def slot_btn_quick_remark_input_waitingForTalk(self):
|
||
self.lineEdit_remark.setText("待讨论")
|
||
|
||
def slot_btn_quick_remark_input_maybeDesaturation(self):
|
||
self.lineEdit_remark.setText("形似潮式呼吸")
|
||
self.radioButton_1_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_maybeWrongLabeled(self):
|
||
self.lineEdit_remark.setText("疑似医生误标")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_durationNoEnough(self):
|
||
self.lineEdit_remark.setText("时长不足")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_littleChange(self):
|
||
self.lineEdit_remark.setText("起伏变化不大")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_noNormalRespBetweenArtifact(self):
|
||
self.lineEdit_remark.setText("体动间无正常呼吸")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_lowSignalNoiseRatio(self):
|
||
self.lineEdit_remark.setText("信噪比低")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
def slot_btn_quick_remark_input_changeOnMiddle(self):
|
||
self.lineEdit_remark.setText("中间起伏")
|
||
self.radioButton_2_class.setChecked(True)
|
||
|
||
# -10s的槽函数
|
||
def slot_btn_previous10s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向前10秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count - 10
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# -30s的槽函数
|
||
def slot_btn_previous30s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向前30秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count - 30
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# -60s的槽函数
|
||
def slot_btn_previous60s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向前60秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count - 60
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# +10s的槽函数
|
||
def slot_btn_next10s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向后10秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count + 10
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# +30s的槽函数
|
||
def slot_btn_next30s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向后30秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count + 30
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# +30s的槽函数
|
||
def slot_btn_next60s(self):
|
||
self.figure.clear()
|
||
self.lineEdit_remark.setText("")
|
||
self.textBrowser_update("操作:向后60秒")
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.time_move_count = self.time_move_count + 60
|
||
self.show_new_event(self.plotEventIndex, front_add_second=self.front_add_second,
|
||
back_add_second=self.back_add_second, time_move_count=self.time_move_count)
|
||
|
||
# +60s的槽函数
|
||
def enable_checkBox_examineLabeled(self):
|
||
if self.checkBox_examineLabeled.isChecked():
|
||
if (self.df_saLabel.loc[self.bcg_event_label_index_list]["isLabeled"] == 1).all():
|
||
self.checkBox_examineLabeled.setChecked(False)
|
||
self.msgBox.setText("该份数据打标已全部完成")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
|
||
# 点击了启用逐帧检查模式复选框的槽函数
|
||
def enable_checkBox_examineBySecond(self):
|
||
if self.checkBox_examineBySecond.isChecked() == True:
|
||
self.pushButton_left.setEnabled(False)
|
||
self.pushButton_right.setEnabled(False)
|
||
self.checkBox_examineLabeled.setEnabled(False)
|
||
self.radioButton_OSA.setChecked(True)
|
||
self.lineEdit_remark.setText("")
|
||
self.pushButton_previous10s.setEnabled(True)
|
||
self.pushButton_next10s.setEnabled(True)
|
||
self.pushButton_previous30s.setEnabled(True)
|
||
self.pushButton_next30s.setEnabled(True)
|
||
self.pushButton_previous60s.setEnabled(True)
|
||
self.pushButton_next60s.setEnabled(True)
|
||
self.radioButton_2_class.setChecked(True)
|
||
self.radioButton_3_class.setEnabled(False)
|
||
elif self.checkBox_examineBySecond.isChecked() == False:
|
||
self.checkBox_examineLabeled.setEnabled(True)
|
||
self.lineEdit_remark.setText("")
|
||
self.pushButton_previous10s.setEnabled(False)
|
||
self.pushButton_next10s.setEnabled(False)
|
||
self.pushButton_previous30s.setEnabled(False)
|
||
self.pushButton_next30s.setEnabled(False)
|
||
self.pushButton_previous60s.setEnabled(False)
|
||
self.pushButton_next60s.setEnabled(False)
|
||
self.radioButton_3_class.setEnabled(True)
|
||
if self.plotEventIndex <= self.start_bcg_index:
|
||
self.pushButton_left.setEnabled(False)
|
||
self.pushButton_right.setEnabled(True)
|
||
self.msgBox.setText("你正在查看第一个事件")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
elif self.plotEventIndex >= len(self.bcg_event_label_filtered_df) - 1:
|
||
self.pushButton_left.setEnabled(True)
|
||
self.pushButton_right.setEnabled(False)
|
||
self.msgBox.setText("你正在查看最后一个事件")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.pushButton_left.setEnabled(True)
|
||
self.pushButton_right.setEnabled(True)
|
||
|
||
# matplot中更改x轴范围后执行的函数
|
||
def on_xlim_change(self, event_ax):
|
||
# 获取当前x轴范围
|
||
left, right = event_ax.get_xlim()
|
||
self.lineEdit_correctStart.setText(str(round(left)))
|
||
self.lineEdit_correctEnd.setText(str(round(right)))
|
||
|
||
# 点击了matplot中的HOME键后执行的槽函数
|
||
def slot_btn_resetOriginalView(self):
|
||
self.lineEdit_correctStart.setText(str(self.bcg_SP))
|
||
self.lineEdit_correctEnd.setText(str(self.bcg_EP))
|
||
|
||
# 用于更新信息输出中的textBrowser的函数
|
||
def textBrowser_update(self, message):
|
||
self.textBrowser_infoOutput.append(str(datetime.now().strftime("%H:%M:%S")) + ": " + message)
|
||
|
||
# 修改了事件类型单选框后执行的函数
|
||
def change_radioButton_events(self, index):
|
||
if not (self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] == -1):
|
||
if self.df_saLabel.at[index, "correct_EventsType"] == "Obstructive apnea":
|
||
self.radioButton_OSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "correct_EventsType"] == "Central apnea":
|
||
self.radioButton_CSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "correct_EventsType"] == "Mixed apnea":
|
||
self.radioButton_MSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "correct_EventsType"] == "Hypopnea":
|
||
self.radioButton_HPY.setChecked(True)
|
||
else:
|
||
if self.df_saLabel.at[index, "Event type"] == "Obstructive apnea":
|
||
self.radioButton_OSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "Event type"] == "Central apnea":
|
||
self.radioButton_CSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "Event type"] == "Mixed apnea":
|
||
self.radioButton_MSA.setChecked(True)
|
||
elif self.df_saLabel.at[index, "Event type"] == "Hypopnea":
|
||
self.radioButton_HPY.setChecked(True)
|
||
|
||
# 点击开始打标按钮后的初始化变量函数
|
||
def init_variable(self):
|
||
self.sampNo = int(self.comboBox_sampID.currentText())
|
||
self.channel_list = ['Effort Tho', 'Effort Abd', 'SpO2', 'Flow Patient', 'Flow Patient']
|
||
self.focus_event_list = self.set_focus_event_list()
|
||
self.frequency = int(self.lineEdit_frequency.text())
|
||
self.bcg_frequency = int(self.lineEdit_bcg_frequency.text())
|
||
self.front_add_second = int(self.lineEdit_front_add_second.text())
|
||
self.back_add_second = int(self.lineEdit_back_add_second.text())
|
||
self.extend_second = int(self.front_add_second + self.back_add_second)
|
||
self.start_bcg_index = int(self.lineEdit_start_bcg_index.text())
|
||
self.plotEventIndex = self.start_bcg_index
|
||
|
||
# 用来显示颜色时按点匹配事件
|
||
self.bcg_event_label = None
|
||
self.artifact_event_label = None
|
||
|
||
# 仅包含关注暂停事件的列表
|
||
self.bcg_event_label_filtered_df = None
|
||
|
||
# 所有事件列表
|
||
self.bcg_event_label_df = None
|
||
|
||
# 各通道信号
|
||
self.signal_select = {}
|
||
|
||
def check_channel(self):
|
||
for i in self.channel_list:
|
||
if i not in self.base_channel:
|
||
debug(f"{i} 不存在于常见通道名中")
|
||
print(f"常见通道名:{self.channel_list}")
|
||
|
||
def set_focus_event_list(self):
|
||
focus_event_list = []
|
||
if self.checkBox_HPY.isChecked() == True:
|
||
focus_event_list.append("Hypopnea")
|
||
if self.checkBox_CSA.isChecked() == True:
|
||
focus_event_list.append("Central apnea")
|
||
if self.checkBox_OSA.isChecked() == True:
|
||
focus_event_list.append("Obstructive apnea")
|
||
if self.checkBox_MSA.isChecked() == True:
|
||
focus_event_list.append("Mixed apnea")
|
||
return focus_event_list
|
||
|
||
def read_data(self):
|
||
bcg_path = self.BCG_Data_Path / f"{self.sampNo}samp.npy"
|
||
ecg_path = self.PSG_Data_Path / f"A{str(self.sampNo).rjust(7, '0')}.edf"
|
||
|
||
if not bcg_path.exists():
|
||
error(f"Can't find {bcg_path} !")
|
||
raise FileNotFoundError(f"Can't find {bcg_path} !")
|
||
|
||
if not ecg_path.exists():
|
||
error(f"Can't find {ecg_path} !")
|
||
raise FileNotFoundError(f"Can't find {ecg_path} !")
|
||
|
||
with EdfReader(str(ecg_path.resolve())) as file:
|
||
signal_num = file.signals_in_file
|
||
debug(f"{self.sampNo} EDF file signal number is {signal_num}")
|
||
|
||
signal_label = file.getSignalLabels()
|
||
debug(f"{self.sampNo} EDF file signal label : {signal_label}")
|
||
|
||
# 打印PSG信息
|
||
file.file_info_long()
|
||
|
||
# sub_index 用于区分两个flow patient
|
||
sub_index = 1
|
||
|
||
info("Loading PSG signal...")
|
||
self.textBrowser_update("提示:正在加载PSG信号")
|
||
for i, index in enumerate(signal_label):
|
||
# 仅加载选中的通道
|
||
if index in self.channel_list:
|
||
# 重命名flow patient通道
|
||
if index == 'Flow Patient':
|
||
if sub_index == 1:
|
||
index = "Flow T"
|
||
else:
|
||
index = "Flow P"
|
||
sub_index += 1
|
||
signal = file.readSignal(i)
|
||
sample_frequency = file.getSampleFrequency(i)
|
||
# 读取采样率 进行重采样
|
||
if sample_frequency < self.frequency:
|
||
signal = signal.repeat(self.frequency / sample_frequency)
|
||
elif sample_frequency > self.frequency:
|
||
signal = signal[::int(sample_frequency / self.frequency)]
|
||
self.signal_select[index] = signal
|
||
info(f"Finished load PSG: {index}")
|
||
self.textBrowser_update("提示:完成加载PSG信号")
|
||
|
||
# 加载心晓信号
|
||
info("Loading XinXiao signal...")
|
||
self.textBrowser_update("提示:正在加载心晓信号")
|
||
signal = load(bcg_path)
|
||
# 20Hz低通去噪
|
||
signal1 = Butterworth(signal, self.bcg_frequency, 'lowpass', low_cut=20, order=3)
|
||
# 0.7Hz 低通提取呼吸
|
||
signal2 = Butterworth(signal, self.bcg_frequency, 'lowpass', low_cut=0.7, order=3)
|
||
|
||
# 进行降采样
|
||
signal1 = signal1[::int(self.bcg_frequency / self.frequency)]
|
||
signal2 = signal2[::int(self.bcg_frequency / self.frequency)]
|
||
|
||
# 根据心晓事件长度生成事件记录
|
||
self.bcg_event_label = zeros(len(signal) + self.extend_second * self.frequency)
|
||
self.signal_select['orgdata'] = signal1
|
||
self.signal_select['0.7lowpass_resp'] = signal2
|
||
|
||
for signal_key in self.signal_select.keys():
|
||
self.signal_select[signal_key] = append(self.signal_select[signal_key],
|
||
self.signal_select[signal_key].mean().astype(int).repeat(
|
||
self.extend_second * self.frequency))
|
||
info("Finished load XinXiao signal")
|
||
self.textBrowser_update("提示:完成加载心晓信号")
|
||
|
||
def read_event(self):
|
||
bcg_label_path = self.BCG_Label_Path / f"export{self.sampNo}_all.csv"
|
||
|
||
if not bcg_label_path.exists():
|
||
error(f"Can't find {bcg_label_path} !")
|
||
raise FileNotFoundError(f"Can't find {bcg_label_path} !")
|
||
|
||
# 读取心晓事件
|
||
df = read_csv(bcg_label_path, encoding="gbk")
|
||
df["Start"] = df["Start"].astype("int")
|
||
df["End"] = df["End"].astype("int")
|
||
self.bcg_event_label_df = df
|
||
|
||
# 过滤不关注事件
|
||
df2 = df[df["Event type"].isin(self.focus_event_list)]
|
||
df2 = df2.sort_values(by='Epoch')
|
||
self.bcg_event_label_filtered_df = df2
|
||
self.bcg_event_label_index_list = df2.index.tolist()
|
||
info("Traversaling XinXiao events...")
|
||
self.textBrowser_update("提示:正在遍历心晓事件")
|
||
for one_data in df.index:
|
||
one_data = df.loc[one_data]
|
||
SP = one_data["Start"] * self.frequency
|
||
EP = one_data["End"] * self.frequency
|
||
|
||
if one_data["Event type"] == "Hypopnea":
|
||
self.bcg_event_label[SP:EP] = 1
|
||
elif one_data["Event type"] == "Central apnea":
|
||
self.bcg_event_label[SP:EP] = 2
|
||
elif one_data["Event type"] == "Obstructive apnea":
|
||
self.bcg_event_label[SP:EP] = 3
|
||
elif one_data["Event type"] == "Mixed apnea":
|
||
self.bcg_event_label[SP:EP] = 4
|
||
info("Finished Traversal XinXiao events")
|
||
self.textBrowser_update("提示:完成遍历心晓事件")
|
||
|
||
def read_artifact_label(self):
|
||
artifact_label_path = self.Artifact_Label_Path / f"Artifact_{self.sampNo}.txt"
|
||
artifact_label = read_csv(artifact_label_path, header=None).to_numpy().reshape(-1, 4)
|
||
|
||
self.artifact_event_label = zeros(len(self.bcg_event_label) + self.extend_second * self.frequency)
|
||
|
||
for i, artifact_type, SP, EP in artifact_label:
|
||
SP = int(SP) // (1000 // self.frequency)
|
||
EP = int(EP) // (1000 // self.frequency)
|
||
artifact_type = int(artifact_type) + 5
|
||
|
||
SP = 0 if SP < 0 else SP
|
||
if EP < 0:
|
||
continue
|
||
|
||
if SP > len(self.bcg_event_label):
|
||
continue
|
||
|
||
self.artifact_event_label[SP:EP] = artifact_type
|
||
|
||
def show_one_event(self, bcg_index: int, front_add_second: int, back_add_second: int):
|
||
"""
|
||
:param bcg_index: 心晓事件在csv中行号
|
||
:param front_add_second: 向前延伸时间
|
||
:param back_add_second: 向后延伸时间
|
||
:return:
|
||
"""
|
||
# 获取事件实际在csv文件中的序号
|
||
bcg_real_index = self.bcg_event_label_filtered_df.index[bcg_index],
|
||
one_bcg_data = self.bcg_event_label_df.loc[bcg_real_index]
|
||
|
||
# 获取BCG事件开始与结束时间
|
||
bcg_SP = one_bcg_data["Start"]
|
||
bcg_EP = one_bcg_data["End"]
|
||
self.bcg_SP = bcg_SP
|
||
self.bcg_EP = bcg_EP
|
||
bcg_duration = bcg_EP - bcg_SP
|
||
|
||
info(f"sampNo:{self.sampNo} "
|
||
f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] "
|
||
f"ecg:[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}]")
|
||
|
||
# 进行向两边延展
|
||
bcg_SP = bcg_SP - front_add_second
|
||
bcg_EP = bcg_EP + back_add_second
|
||
|
||
# 各个子图之间的比例
|
||
gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2])
|
||
self.figure.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
|
||
plt.margins(0, 0)
|
||
plt.tight_layout()
|
||
plt.xticks([])
|
||
plt.yticks([])
|
||
|
||
if not (self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] == -1):
|
||
bcg_duration_new = self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"] - \
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"]
|
||
self.lineEdit_correctStart.setText(
|
||
str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"]))
|
||
self.lineEdit_correctEnd.setText(
|
||
str(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"]))
|
||
self.label_BCG_event.setText(
|
||
f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} "
|
||
f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}s New Duration:{bcg_duration_new}s")
|
||
else:
|
||
bcg_duration_new = -1
|
||
self.lineEdit_correctStart.setText(str(self.bcg_SP))
|
||
self.lineEdit_correctEnd.setText(str(self.bcg_EP))
|
||
self.label_BCG_event.setText(
|
||
f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} "
|
||
f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}s")
|
||
|
||
# 绘制 Flow1
|
||
self.ax0 = self.figure.add_subplot(gs[0])
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow T", bcg_duration_new=bcg_duration_new,
|
||
show_mode="one")
|
||
|
||
# 绘制 Flow2
|
||
self.ax1 = self.figure.add_subplot(gs[1], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow P", bcg_duration_new=bcg_duration_new,
|
||
show_mode="one")
|
||
|
||
self.ax2 = self.figure.add_subplot(gs[2], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Tho", bcg_duration_new=bcg_duration_new,
|
||
show_mode="one")
|
||
|
||
self.ax3 = self.figure.add_subplot(gs[3], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Abd", bcg_duration_new=bcg_duration_new,
|
||
show_mode="one")
|
||
|
||
self.ax4 = self.figure.add_subplot(gs[4], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="SpO2", event_code=[5],
|
||
bcg_duration_new=bcg_duration_new, show_mode="one")
|
||
|
||
self.ax5 = self.figure.add_subplot(gs[5], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="orgdata",
|
||
event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4],
|
||
event_show_under=False, bcg_duration_new=bcg_duration_new, show_mode="one")
|
||
|
||
self.ax6 = self.figure.add_subplot(gs[6], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="0.7lowpass_resp",
|
||
event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4],
|
||
event_show_under=False,
|
||
title=f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)}",
|
||
bcg_duration_new=bcg_duration_new, show_mode="one")
|
||
|
||
# figManager = plt.get_current_fig_manager()
|
||
# figManager.window.showMaximized()
|
||
self.canvas.draw()
|
||
self.ax0.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax))
|
||
|
||
def plt_channel(self, plt_, SP, EP, channel, event_code=[1, 2, 3, 4], event_show_under=False,
|
||
ax_top=True, ax_bottom=True, ax_left=True, ax_right=True, title=None, bcg_duration_new=-1,
|
||
show_mode=None):
|
||
"""
|
||
:param plt_:
|
||
:param SP: 显示开始秒数
|
||
:param EP: 显示结束秒数
|
||
:param channel: 通道名称
|
||
:param event_code: 要上色的事件编号
|
||
:param event_show_under: 事件颜色显示在信号下面
|
||
:param ax_top: 显示上框线
|
||
:param ax_bottom: 显示下框线
|
||
:param ax_left: 显示左框线
|
||
:param ax_right: 显示右框线
|
||
:return:
|
||
"""
|
||
linestyle = "-"
|
||
SP = 0 if SP < 0 else SP
|
||
plt_.plot(linspace(SP, EP, (EP - SP) * self.frequency),
|
||
self.signal_select[channel][SP * self.frequency:EP * self.frequency], label=channel,
|
||
color=self.color_cycle[0])
|
||
|
||
for j in event_code:
|
||
if channel == "SpO2":
|
||
mask = self.bcg_event_label[SP * self.frequency:EP * self.frequency] == j
|
||
elif channel == "orgdata" or channel == "0.7lowpass_resp":
|
||
if j <= 5:
|
||
mask = self.bcg_event_label[SP * self.frequency:EP * self.frequency] == j
|
||
else:
|
||
mask = self.artifact_event_label[SP * self.frequency:EP * self.frequency] == j
|
||
linestyle = "-"
|
||
else:
|
||
mask = self.bcg_event_label[SP * self.frequency:EP * self.frequency] == j
|
||
|
||
if event_show_under:
|
||
min_point = self.signal_select[channel][SP * self.frequency:EP * self.frequency].min()
|
||
len_segment = EP * self.frequency - SP * self.frequency
|
||
y = (min_point.repeat(len_segment) * mask).astype('float')
|
||
place(y, y == 0, nan)
|
||
else:
|
||
y = (self.signal_select[channel][SP * self.frequency:EP * self.frequency] * mask).astype('float')
|
||
|
||
place(y, y == 0, nan)
|
||
plt_.plot(linspace(SP, EP, (EP - SP) * self.frequency), y, color=self.color_cycle[j],
|
||
linestyle=linestyle)
|
||
plt_.legend(fontsize=8, loc=1)
|
||
if title is not None:
|
||
plt_.title(title)
|
||
|
||
ax = plt.gca()
|
||
ax.grid(True)
|
||
ax.spines["top"].set_visible(ax_top)
|
||
ax.spines["right"].set_visible(ax_right)
|
||
ax.spines["bottom"].set_visible(ax_bottom)
|
||
ax.spines["left"].set_visible(ax_left)
|
||
|
||
# 绘制半透明矩形框
|
||
if show_mode == "one":
|
||
if not (bcg_duration_new == -1):
|
||
if self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] == "Obstructive apnea":
|
||
ax.axvspan(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"],
|
||
color='red',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] == "Central apnea":
|
||
ax.axvspan(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"],
|
||
color='blue',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] == "Mixed apnea":
|
||
ax.axvspan(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"],
|
||
color='gray',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] == "Hypopnea":
|
||
ax.axvspan(
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"],
|
||
color='pink',
|
||
alpha=0.3)
|
||
else:
|
||
if self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "Event type"] == "Obstructive apnea":
|
||
ax.axvspan(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "End"],
|
||
color='red',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "Event type"] == "Central apnea":
|
||
ax.axvspan(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "End"],
|
||
color='blue',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "Event type"] == "Mixed apnea":
|
||
ax.axvspan(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "End"],
|
||
color='gray',
|
||
alpha=0.3)
|
||
elif self.df_saLabel.at[
|
||
self.bcg_event_label_index_list[self.plotEventIndex], "Event type"] == "Hypopnea":
|
||
ax.axvspan(self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "Start"],
|
||
self.df_saLabel.at[self.bcg_event_label_index_list[self.plotEventIndex], "End"],
|
||
color='pink',
|
||
alpha=0.3)
|
||
# xticks = [[]] if xticks else [range(SP, EP, 5), [str(i) for i in range(0, (EP - SP), 5)]]
|
||
# print(xticks)
|
||
# plt_.xticks(*xticks) # 去掉x轴
|
||
|
||
def show_new_event(self, bcg_index: int, front_add_second: int, back_add_second: int, time_move_count: int):
|
||
"""
|
||
:param bcg_index: 心晓事件在csv中行号
|
||
:param front_add_second: 向前延伸时间
|
||
:param back_add_second: 向后延伸时间
|
||
:param time_move_count: 时间轴移动的时间
|
||
:return:
|
||
"""
|
||
# 获取事件实际在csv文件中的序号
|
||
bcg_real_index = self.bcg_event_label_filtered_df.index[bcg_index],
|
||
one_bcg_data = self.bcg_event_label_df.loc[bcg_real_index]
|
||
|
||
# 获取BCG事件开始与结束时间
|
||
bcg_SP = one_bcg_data["Start"]
|
||
bcg_EP = one_bcg_data["End"]
|
||
bcg_duration = bcg_EP - bcg_SP
|
||
|
||
info(f"sampNo:{self.sampNo} "
|
||
f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] "
|
||
f"ecg:[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}]")
|
||
|
||
# 进行向两边延展
|
||
bcg_SP = bcg_SP - front_add_second + time_move_count
|
||
bcg_EP = bcg_EP + back_add_second + time_move_count
|
||
|
||
self.bcg_SP = bcg_SP
|
||
self.bcg_EP = bcg_EP
|
||
self.lineEdit_correctStart.setText(str(self.bcg_SP))
|
||
self.lineEdit_correctEnd.setText(str(self.bcg_EP))
|
||
|
||
# 各个子图之间的比例
|
||
gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2])
|
||
self.figure.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
|
||
plt.margins(0, 0)
|
||
plt.tight_layout()
|
||
plt.xticks([])
|
||
plt.yticks([])
|
||
|
||
# 绘制 Flow1
|
||
self.ax0 = self.figure.add_subplot(gs[0])
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow T", show_mode="new")
|
||
|
||
# 绘制 Flow2
|
||
self.ax1 = self.figure.add_subplot(gs[1], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow P", show_mode="new")
|
||
|
||
self.ax2 = self.figure.add_subplot(gs[2], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Tho", show_mode="new")
|
||
|
||
self.ax3 = self.figure.add_subplot(gs[3], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Abd", show_mode="new")
|
||
|
||
self.ax4 = self.figure.add_subplot(gs[4], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="SpO2", event_code=[5], show_mode="new")
|
||
|
||
self.ax5 = self.figure.add_subplot(gs[5], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="orgdata",
|
||
event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4],
|
||
event_show_under=False, show_mode="new")
|
||
|
||
self.ax6 = self.figure.add_subplot(gs[6], sharex=self.ax0)
|
||
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="0.7lowpass_resp",
|
||
event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4],
|
||
event_show_under=False,
|
||
title=f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)}",
|
||
show_mode="new")
|
||
|
||
self.label_BCG_event.setText(
|
||
f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} "
|
||
f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}s")
|
||
|
||
# figManager = plt.get_current_fig_manager()
|
||
# figManager.window.showMaximized()
|
||
self.canvas.draw()
|
||
self.ax0.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax))
|
||
|
||
def pd_add_new_row(self, df, score: int, remark: str, correct_Start: int, correct_End: int, correct_EventsType: str,
|
||
isLabeled: int):
|
||
new_row = Series(index=df.columns, data=nan)
|
||
new_row["score"] = score
|
||
new_row["remark"] = remark
|
||
new_row["correct_Start"] = correct_Start
|
||
new_row["correct_End"] = correct_End
|
||
new_row["correct_EventsType"] = correct_EventsType
|
||
new_row["isLabeled"] = isLabeled
|
||
return concat([df, DataFrame([new_row])], ignore_index=True)
|
||
|
||
|
||
# 此线程类用于更新textBrowser的内容
|
||
class Thread_textbrowserUpdate(QThread):
|
||
trigger = pyqtSignal(str)
|
||
|
||
def __init__(self):
|
||
super(Thread_textbrowserUpdate, self).__init__()
|
||
|
||
def run_(self, message):
|
||
self.trigger.emit(message)
|
||
|
||
|
||
# 此类用于继承matplot的NavigationToolbar类,然后写回调函数
|
||
class CustomNavigationToolbar(NavigationToolbar2QT):
|
||
def __init__(self, canvas, parent, callback):
|
||
super().__init__(canvas, parent)
|
||
self.callback = callback
|
||
|
||
def home(self, *args):
|
||
super().home(*args) # 执行原始功能
|
||
if self.callback:
|
||
self.callback() # 执行自定义回调
|
||
|
||
|
||
# 主函数
|
||
if __name__ == '__main__':
|
||
app = QApplication(sys.argv)
|
||
mainWindow = MainWindow()
|
||
mainWindow.show()
|
||
sys.exit(app.exec_())
|