Files
Signal_Label_Reborn/func/Module_SA_label.py
Yorusora db07be0ca7 优化<冗余数据切割和标签映射>的代码
删去了<冗余数据切割和标签映射>的自动最大化
2025-05-20 21:02:03 +08:00

1216 lines
68 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from gc import collect
from pathlib import Path
from traceback import format_exc
import matplotlib.pyplot as plt
from PySide6.QtGui import QAction, QFont, QColor
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication, QButtonGroup, QTableWidget, QTableWidgetItem
from matplotlib import gridspec
from matplotlib.backends.backend_qt import NavigationToolbar2QT
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from numpy import array, zeros, append, linspace, place, nan
from overrides import overrides
from pandas import read_csv, DataFrame
from scipy.signal import resample
from yaml import dump, load, FullLoader
from func.Filters.Preprocessing import Butterworth_for_ECG_PreProcess
from func.utils.PublicFunc import PublicFunc
from func.utils.Constants import Constants, ConfigParams
from func.utils.Result import Result
from ui.MainWindow.MainWindow_SA_label import Ui_MainWindow_SA_label
from ui.setting.SA_label_input_setting import Ui_MainWindow_SA_label_input_setting
Config = {
}
ButtonState = {
"Default": {
"pushButton_input_setting": True,
"pushButton_input": True,
"pushButton_quick_remark_input_waitingForTalk": False,
"pushButton_quick_remark_input_maybeDesaturation": False,
"pushButton_quick_remark_input_maybeWrongLabeled": False,
"pushButton_quick_remark_input_durationNoEnough": False,
"pushButton_quick_remark_input_littleChange": False,
"pushButton_quick_remark_input_noNormalRespBetweenArtifact": False,
"pushButton_quick_remark_input_lowSignalNoiseRatio": False,
"pushButton_quick_remark_input_changeOnMiddle": False,
"pushButton_prev": False,
"pushButton_next": False,
"pushButton_confirmLabel": False,
"pushButton_previous10s": False,
"pushButton_previous30s": False,
"pushButton_previous60s": False,
"pushButton_next10s": False,
"pushButton_next30s": False,
"pushButton_next60s": False
},
"Current": {
"pushButton_input_setting": True,
"pushButton_input": True,
"pushButton_quick_remark_input_waitingForTalk": False,
"pushButton_quick_remark_input_maybeDesaturation": False,
"pushButton_quick_remark_input_maybeWrongLabeled": False,
"pushButton_quick_remark_input_durationNoEnough": False,
"pushButton_quick_remark_input_littleChange": False,
"pushButton_quick_remark_input_noNormalRespBetweenArtifact": False,
"pushButton_quick_remark_input_lowSignalNoiseRatio": False,
"pushButton_quick_remark_input_changeOnMiddle": False,
"pushButton_prev": False,
"pushButton_next": False,
"pushButton_confirmLabel": False,
"pushButton_previous10s": False,
"pushButton_previous30s": False,
"pushButton_previous60s": False,
"pushButton_next10s": False,
"pushButton_next30s": False,
"pushButton_next60s": False
}
}
class SettingWindow(QMainWindow):
def __init__(self, root_path, sampID):
super(SettingWindow, self).__init__()
self.ui = Ui_MainWindow_SA_label_input_setting()
self.ui.setupUi(self)
self.root_path = root_path
self.sampID = sampID
self.config = None
self.__read_config__()
self.ui.spinBox_input_freq_signal_OrgBCG.valueChanged.connect(self.__update_ui__)
self.ui.spinBox_input_freq_signal_Tho.valueChanged.connect(self.__update_ui__)
self.ui.spinBox_input_freq_signal_Abd.valueChanged.connect(self.__update_ui__)
self.ui.spinBox_input_freq_signal_FlowT.valueChanged.connect(self.__update_ui__)
self.ui.spinBox_input_freq_signal_FlowP.valueChanged.connect(self.__update_ui__)
self.ui.spinBox_input_freq_signal_SpO2.valueChanged.connect(self.__update_ui__)
self.ui.pushButton_confirm.clicked.connect(self.__write_config__)
self.ui.pushButton_cancel.clicked.connect(self.__rollback_config__)
self.ui.pushButton_cancel.clicked.connect(self.close)
def __read_config__(self):
if not Path(ConfigParams.SA_LABEL_CONFIG_FILE_PATH).exists():
with open(ConfigParams.SA_LABEL_CONFIG_FILE_PATH, "w") as f:
dump(ConfigParams.SA_LABEL_CONFIG_NEW_CONTENT, f)
with open(ConfigParams.SA_LABEL_CONFIG_FILE_PATH, "r") as f:
file_config = load(f.read(), Loader=FullLoader)
Config.update(file_config)
self.config = file_config
Config.update({
"Path": {
"Input_OrgBCG": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_ORGBCG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_ORGBCG_FILENAME +
str(Config["InputConfig"]["OrgBCGFreq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_Tho": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_THO_FILENAME +
str(Config["InputConfig"]["ThoFreq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_Abd": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_ABD_FILENAME +
str(Config["InputConfig"]["AbdFreq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_FlowT": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_FLOWT_FILENAME +
str(Config["InputConfig"]["FlowTFreq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_FlowP": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_FLOWP_FILENAME +
str(Config["InputConfig"]["FlowPFreq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_SpO2": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_SPO2_FILENAME +
str(Config["InputConfig"]["SpO2Freq"]) +
ConfigParams.ENDSWITH_TXT))),
"Input_Artifact": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_LABEL /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_ARTIFACT_FILENAME +
ConfigParams.ENDSWITH_TXT))),
"Input_Label": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_INPUT_LABEL_FILENAME +
ConfigParams.ENDSWITH_CSV))),
"Save": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_LABEL /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_SAVE_FILENAME +
ConfigParams.ENDSWITH_CSV))),
"Save_2": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_LABEL /
Path(str(self.sampID)) / Path(ConfigParams.SA_LABEL_SAVE2_FILENAME +
ConfigParams.ENDSWITH_CSV)))
},
"PlotEventIndex": 0,
"TimeMoveCount": 0,
"BCG_SP": 0,
"BCG_EP": 0,
"EventLabelIndexList": []
})
# 数据回显
self.ui.spinBox_input_freq_signal_OrgBCG.setValue(Config["InputConfig"]["OrgBCGFreq"])
self.ui.spinBox_input_freq_signal_Tho.setValue(Config["InputConfig"]["ThoFreq"])
self.ui.spinBox_input_freq_signal_Abd.setValue(Config["InputConfig"]["AbdFreq"])
self.ui.spinBox_input_freq_signal_FlowT.setValue(Config["InputConfig"]["FlowTFreq"])
self.ui.spinBox_input_freq_signal_FlowP.setValue(Config["InputConfig"]["FlowPFreq"])
self.ui.spinBox_input_freq_signal_SpO2.setValue(Config["InputConfig"]["SpO2Freq"])
self.ui.plainTextEdit_file_path_input_signal_OrgBCG.setPlainText(Config["Path"]["Input_OrgBCG"])
self.ui.plainTextEdit_file_path_input_signal_Tho.setPlainText(Config["Path"]["Input_Tho"])
self.ui.plainTextEdit_file_path_input_signal_Abd.setPlainText(Config["Path"]["Input_Abd"])
self.ui.plainTextEdit_file_path_input_signal_FlowT.setPlainText(Config["Path"]["Input_FlowT"])
self.ui.plainTextEdit_file_path_input_signal_FlowP.setPlainText(Config["Path"]["Input_FlowP"])
self.ui.plainTextEdit_file_path_input_signal_SpO2.setPlainText(Config["Path"]["Input_SpO2"])
self.ui.plainTextEdit_file_path_input_artifact.setPlainText(Config["Path"]["Input_Artifact"])
self.ui.plainTextEdit_file_path_input_label.setPlainText(Config["Path"]["Input_Label"])
self.ui.plainTextEdit_file_path_save.setPlainText(Config["Path"]["Save"])
self.ui.plainTextEdit_file_path_save_2.setPlainText(Config["Path"]["Save_2"])
def __write_config__(self):
# 从界面写入配置
Config["InputConfig"]["OrgBCGFreq"] = self.ui.spinBox_input_freq_signal_OrgBCG.value()
Config["InputConfig"]["ThoFreq"] = self.ui.spinBox_input_freq_signal_Tho.value()
Config["InputConfig"]["AbdFreq"] = self.ui.spinBox_input_freq_signal_Abd.value()
Config["InputConfig"]["FlowTFreq"] = self.ui.spinBox_input_freq_signal_FlowT.value()
Config["InputConfig"]["FlowPFreq"] = self.ui.spinBox_input_freq_signal_FlowP.value()
Config["InputConfig"]["SpO2Freq"] = self.ui.spinBox_input_freq_signal_SpO2.value()
Config["Path"]["Input_OrgBCG"] = self.ui.plainTextEdit_file_path_input_signal_OrgBCG.toPlainText()
Config["Path"]["Input_Tho"] = self.ui.plainTextEdit_file_path_input_signal_Tho.toPlainText()
Config["Path"]["Input_Abd"] = self.ui.plainTextEdit_file_path_input_signal_Abd.toPlainText()
Config["Path"]["Input_FlowT"] = self.ui.plainTextEdit_file_path_input_signal_FlowT.toPlainText()
Config["Path"]["Input_FlowP"] = self.ui.plainTextEdit_file_path_input_signal_FlowP.toPlainText()
Config["Path"]["Input_SpO2"] = self.ui.plainTextEdit_file_path_input_signal_SpO2.toPlainText()
Config["Path"]["Input_Artifact"] = self.ui.plainTextEdit_file_path_input_artifact.toPlainText()
Config["Path"]["Input_Label"] = self.ui.plainTextEdit_file_path_input_label.toPlainText()
Config["Path"]["Save"] = self.ui.plainTextEdit_file_path_save.toPlainText()
Config["Path"]["Save_2"] = self.ui.plainTextEdit_file_path_save_2.toPlainText()
# 保存配置到文件
self.config["InputConfig"]["OrgBCGFreq"] = self.ui.spinBox_input_freq_signal_OrgBCG.value()
self.config["InputConfig"]["ThoFreq"] = self.ui.spinBox_input_freq_signal_Tho.value()
self.config["InputConfig"]["AbdFreq"] = self.ui.spinBox_input_freq_signal_Abd.value()
self.config["InputConfig"]["FlowTFreq"] = self.ui.spinBox_input_freq_signal_FlowT.value()
self.config["InputConfig"]["FlowPFreq"] = self.ui.spinBox_input_freq_signal_FlowP.value()
self.config["InputConfig"]["SpO2Freq"] = self.ui.spinBox_input_freq_signal_SpO2.value()
with open(ConfigParams.SA_LABEL_CONFIG_FILE_PATH, "w") as f:
dump(self.config, f)
self.close()
def __rollback_config__(self):
self.__read_config__()
def __update_ui__(self):
self.ui.plainTextEdit_file_path_input_signal_OrgBCG.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_ORGBCG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_ORGBCG_FILENAME +
str(self.ui.spinBox_input_freq_signal_OrgBCG.value()) +
ConfigParams.ENDSWITH_TXT))))
self.ui.plainTextEdit_file_path_input_signal_Tho.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_THO_FILENAME +
str(self.ui.spinBox_input_freq_signal_Tho.value()) +
ConfigParams.ENDSWITH_TXT))))
self.ui.plainTextEdit_file_path_input_signal_Abd.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_ABD_FILENAME +
str(self.ui.spinBox_input_freq_signal_Abd.value()) +
ConfigParams.ENDSWITH_TXT))))
self.ui.plainTextEdit_file_path_input_signal_FlowT.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_FLOWT_FILENAME +
str(self.ui.spinBox_input_freq_signal_FlowT.value()) +
ConfigParams.ENDSWITH_TXT))))
self.ui.plainTextEdit_file_path_input_signal_FlowP.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_FLOWP_FILENAME +
str(self.ui.spinBox_input_freq_signal_FlowP.value()) +
ConfigParams.ENDSWITH_TXT))))
self.ui.plainTextEdit_file_path_input_signal_SpO2.setPlainText(
str((Path(self.root_path) /
ConfigParams.PUBLIC_PATH_PSG_ALIGNED /
Path(str(self.sampID)) /
Path(ConfigParams.SA_LABEL_INPUT_SPO2_FILENAME +
str(self.ui.spinBox_input_freq_signal_SpO2.value()) +
ConfigParams.ENDSWITH_TXT))))
class MainWindow_SA_label(QMainWindow):
def __init__(self):
super(MainWindow_SA_label, self).__init__()
self.ui = Ui_MainWindow_SA_label()
self.ui.setupUi(self)
self.root_path = None
self.sampID = None
self.data = None
self.setting = None
self.buttonGroup_event = None
self.buttonGroup_class = None
# 初始化进度条
self.progressbar = None
PublicFunc.add_progressbar(self)
# 初始化画框
self.fig = None
self.canvas = None
self.figToolbar = None
self.gs = None
# 设定事件和其对应颜色
# event_code color event
# 0 黑色 背景
# 1 粉色 低通气
# 2 蓝色 中枢性
# 3 红色 阻塞型
# 4 灰色 混合型
# 5 绿色 血氧饱和度下降
# 6 橙色 大体动
# 7 橙色 小体动
# 8 橙色 深呼吸
# 9 橙色 脉冲体动
# 10 橙色 无效片段
self.color_cycle = ["black", "pink", "blue", "red", "silver", "green", "orange", "orange", "orange", "orange", "orange"]
self.msgBox = QMessageBox()
self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE)
@overrides
def show(self, root_path, sampID):
super().show()
self.root_path = root_path
self.sampID = sampID
self.setting = SettingWindow(root_path, sampID)
# 初始化画框
self.fig = plt.figure(figsize=(12, 9), dpi=100)
self.canvas = FigureCanvasQTAgg(self.fig)
self.figToolbar = CustomNavigationToolbar(self.canvas, self)
self.figToolbar.action_Reset_Signal_and_Time.setEnabled(False)
for action in self.figToolbar._actions.values():
action.setEnabled(False)
for action in self.figToolbar.actions():
if action.text() == "Home" or action.text() == "Subplots" or action.text() == "Customize":
self.figToolbar.removeAction(action)
self.figToolbar.action_Reset_Signal_and_Time.triggered.connect(self.toggle_resetOriginalView)
self.ui.verticalLayout_canvas.addWidget(self.canvas)
self.ui.verticalLayout_canvas.addWidget(self.figToolbar)
self.gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2])
self.fig.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
self.ax0 = self.fig.add_subplot(self.gs[0])
self.ax0.grid(True)
self.ax0.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax1 = self.fig.add_subplot(self.gs[1], sharex=self.ax0)
self.ax1.grid(True)
self.ax1.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax1.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax2 = self.fig.add_subplot(self.gs[2], sharex=self.ax0)
self.ax2.grid(True)
self.ax2.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax2.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax3 = self.fig.add_subplot(self.gs[3], sharex=self.ax0)
self.ax3.grid(True)
self.ax3.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax3.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax4 = self.fig.add_subplot(self.gs[4], sharex=self.ax0)
self.ax4.grid(True)
self.ax4.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax4.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax5 = self.fig.add_subplot(self.gs[5], sharex=self.ax0)
self.ax5.grid(True)
self.ax5.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax5.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax6 = self.fig.add_subplot(self.gs[6], sharex=self.ax0)
self.ax6.grid(True)
self.ax6.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax6.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
PublicFunc.__resetAllButton__(self, ButtonState)
self.ui.tableWidget_label.setHorizontalHeaderLabels(['原事件类型', '事件类型', '标签类型', '起始时间(s)', '终止时间(s)'])
self.ui.tableWidget_label_add.setHorizontalHeaderLabels(['事件类型', '标签类型', '起始时间(s)', '终止时间(s)'])
self.ui.tableWidget_label.setEditTriggers(QTableWidget.NoEditTriggers)
self.ui.tableWidget_label_add.setEditTriggers(QTableWidget.NoEditTriggers)
self.ui.tableWidget_label.horizontalHeader().setStretchLastSection(True)
self.ui.tableWidget_label_add.horizontalHeader().setStretchLastSection(True)
self.buttonGroup_event = QButtonGroup(self)
self.buttonGroup_class = QButtonGroup(self)
self.buttonGroup_event.addButton(self.ui.radioButton_OSA)
self.buttonGroup_event.addButton(self.ui.radioButton_CSA)
self.buttonGroup_event.addButton(self.ui.radioButton_MSA)
self.buttonGroup_event.addButton(self.ui.radioButton_HPY)
self.buttonGroup_class.addButton(self.ui.radioButton_1_class)
self.buttonGroup_class.addButton(self.ui.radioButton_2_class)
self.buttonGroup_class.addButton(self.ui.radioButton_3_class)
self.ui.radioButton_OSA.setChecked(True)
self.ui.radioButton_2_class.setChecked(True)
self.ui.pushButton_input.clicked.connect(self.__slot_btn_input__)
self.ui.pushButton_input_setting.clicked.connect(self.setting.show)
self.ui.tableWidget_label.cellDoubleClicked.connect(self.slot_tableWidget_label_on_cell_double_clicked)
self.ui.lineEdit_filter_label.textChanged.connect(self.slot_lineEdit_filter_label)
self.ui.lineEdit_filter_label_add.textChanged.connect(self.slot_lineEdit_filter_label)
@overrides
def closeEvent(self, event):
reply = QMessageBox.question(self, '确认', '确认退出吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
PublicFunc.__disableAllButton__(self, ButtonState)
PublicFunc.statusbar_show_msg(self, PublicFunc.format_status_msg(Constants.SHUTTING_DOWN))
QApplication.processEvents()
# 清空画框
if self.ax0 is not None:
self.ax0.clear()
if self.ax1 is not None:
self.ax1.clear()
if self.ax2 is not None:
self.ax2.clear()
if self.ax3 is not None:
self.ax3.clear()
if self.ax4 is not None:
self.ax4.clear()
if self.ax5 is not None:
self.ax5.clear()
if self.ax6 is not None:
self.ax6.clear()
# 释放资源
del self.data
self.fig.clf()
plt.close(self.fig)
self.deleteLater()
collect()
self.canvas = None
event.accept()
else:
event.ignore()
def __reset__(self):
ButtonState["Current"].update(ButtonState["Default"].copy())
def __plot__(self):
# 清空画框
self.reset_axes()
sender = self.sender()
if sender == self.ui.pushButton_input:
try:
self.draw_one_event()
self.canvas.draw()
except Exception as e:
return Result().failure(info=Constants.DRAWING_FAILURE + "\n" + format_exc())
return Result().success(info=Constants.DRAWING_FINISHED)
def update_UI_Args(self):
try:
if self.data is not None and self.data.df_corrected is not None:
if not str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "remark"]) == "nan":
self.ui.lineEdit_remark.setText(
str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "remark"]))
else:
self.ui.lineEdit_remark.setText("")
if not (self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "isLabeled"] == -1):
if self.data.df_corrected.at[Config["PlotEventIndex"], "correct_EventsType"] == "Obstructive apnea":
self.ui.radioButton_OSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "correct_EventsType"] == "Central apnea":
self.ui.radioButton_CSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "correct_EventsType"] == "Mixed apnea":
self.ui.radioButton_MSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "correct_EventsType"] == "Hypopnea":
self.ui.radioButton_HPY.setChecked(True)
else:
if self.data.df_corrected.at[Config["PlotEventIndex"], "Event type"] == "Obstructive apnea":
self.ui.radioButton_OSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "Event type"] == "Central apnea":
self.ui.radioButton_CSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "Event type"] == "Mixed apnea":
self.ui.radioButton_MSA.setChecked(True)
elif self.data.df_corrected.at[Config["PlotEventIndex"], "Event type"] == "Hypopnea":
self.ui.radioButton_HPY.setChecked(True)
if str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "score"]) == "1":
self.ui.radioButton_1_class.setChecked(True)
elif str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "score"]) == "2":
self.ui.radioButton_2_class.setChecked(True)
elif str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "score"]) == "3":
self.ui.radioButton_3_class.setChecked(True)
else:
self.ui.radioButton_2_class.setChecked(True)
except Exception as e:
return Result().failure(info=Constants.SA_LABEL_UPDATE_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Update_Info_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.SA_LABEL_UPDATE_FINISHED)
def update_tableWidget(self):
try:
if self.data.df_corrected is not None:
self.ui.tableWidget_label.setRowCount(len(self.data.df_corrected))
for index, row in self.data.df_corrected.iterrows():
Event_type = row.get("Event type", None)
if Event_type == "Obstructive apnea":
item = QTableWidgetItem("OSA_org")
elif Event_type == "Central apnea":
item = QTableWidgetItem("CSA_org")
elif Event_type == "Mixed apnea":
item = QTableWidgetItem("MSA_org")
elif Event_type == "Hypopnea":
item = QTableWidgetItem("HPY_org")
else:
item = QTableWidgetItem("None")
self.ui.tableWidget_label.setItem(index, 0, item)
correct_EventsType = row.get("correct_EventsType", None)
if correct_EventsType == "Obstructive apnea":
item = QTableWidgetItem("OSA")
elif correct_EventsType == "Central apnea":
item = QTableWidgetItem("CSA")
elif correct_EventsType == "Mixed apnea":
item = QTableWidgetItem("MSA")
elif correct_EventsType == "Hypopnea":
item = QTableWidgetItem("HPY")
else:
item = QTableWidgetItem("None")
self.ui.tableWidget_label.setItem(index, 1, item)
score = row.get("score", None)
if score == 1:
item = QTableWidgetItem("一类")
elif score == 2:
item = QTableWidgetItem("二类")
elif score == 3:
item = QTableWidgetItem("删除")
else:
item = QTableWidgetItem("None")
self.ui.tableWidget_label.setItem(index, 2, item)
correct_Start = row.get("correct_Start", None)
if correct_Start < 0 or correct_Start * Config["InputConfig"]["OrgBCGFreq"] > len(self.data.OrgBCG):
item = QTableWidgetItem("None")
else:
item = QTableWidgetItem(str(correct_Start))
self.ui.tableWidget_label.setItem(index, 3, item)
correct_End = row.get("correct_End", None)
if correct_End < 0 or correct_End * Config["InputConfig"]["OrgBCGFreq"] > len(self.data.OrgBCG):
item = QTableWidgetItem("None")
else:
item = QTableWidgetItem(str(correct_End))
self.ui.tableWidget_label.setItem(index, 4, item)
remark = row.get("remark", None)
if str(remark) != "" and str(remark) != "nan" and row.get("isLabeled", None) == 1:
for col in range(self.ui.tableWidget_label.columnCount()):
item = self.ui.tableWidget_label.item(index, col)
item.setBackground(QColor(255, 200, 200))
else:
for col in range(self.ui.tableWidget_label.columnCount()):
item = self.ui.tableWidget_label.item(index, col)
item.setBackground(QColor(255, 255, 255))
if self.data.df_addNew is not None:
self.ui.tableWidget_label_add.setRowCount(len(self.data.df_addNew))
for index, row in self.data.df_addNew.iterrows():
correct_EventsType = row.get("correct_EventsType", None)
if correct_EventsType == "Obstructive apnea":
item = QTableWidgetItem("OSA")
elif correct_EventsType == "Central apnea":
item = QTableWidgetItem("CSA")
elif correct_EventsType == "Mixed apnea":
item = QTableWidgetItem("MSA")
elif correct_EventsType == "Hypopnea":
item = QTableWidgetItem("HPY")
else:
item = QTableWidgetItem("None")
self.ui.tableWidget_label_add.setItem(index, 0, item)
score = row.get("score", None)
if score == 1:
item = QTableWidgetItem("一类")
elif score == 2:
item = QTableWidgetItem("二类")
elif score == 3:
item = QTableWidgetItem("删除")
else:
item = QTableWidgetItem("None")
self.ui.tableWidget_label_add.setItem(index, 1, item)
correct_Start = row.get("correct_Start", None)
if correct_Start < 0 or correct_Start * Config["InputConfig"]["OrgBCGFreq"] > len(self.data.OrgBCG):
item = QTableWidgetItem("None")
else:
item = QTableWidgetItem(str(correct_Start))
self.ui.tableWidget_label_add.setItem(index, 2, item)
correct_End = row.get("correct_End", None)
if correct_End < 0 or correct_End * Config["InputConfig"]["OrgBCGFreq"] > len(self.data.OrgBCG):
item = QTableWidgetItem("None")
else:
item = QTableWidgetItem(str(correct_End))
self.ui.tableWidget_label_add.setItem(index, 3, item)
remark = row.get("remark", None)
if str(remark) != "" and str(remark) != "nan" and row.get("isLabeled", None) == 1:
for col in range(self.ui.tableWidget_label_add.columnCount()):
item = self.ui.tableWidget_label_add.item(index, col)
item.setBackground(QColor(255, 200, 200))
else:
for col in range(self.ui.tableWidget_label_add.columnCount()):
item = self.ui.tableWidget_label_add.item(index, col)
item.setBackground(QColor(255, 255, 255))
except Exception as e:
return Result().failure(info=Constants.SA_LABEL_UPDATE_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Update_tableWidget_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.SA_LABEL_UPDATE_FINISHED)
def __slot_btn_input__(self):
PublicFunc.__disableAllButton__(self, ButtonState)
self.reset_axes()
self.canvas.draw()
self.data = Data()
# 导入数据
PublicFunc.progressbar_update(self, 1, 7, Constants.INPUTTING_DATA, 0)
result = self.data.open_file()
if not result.status:
PublicFunc.text_output(self.ui, "(1/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(1/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 获取存档
PublicFunc.progressbar_update(self, 2, 7, Constants.SA_LABEL_LOADING_ARCHIVE, 20)
result = self.data.get_archive()
if not result.status:
PublicFunc.text_output(self.ui, "(2/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(2/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 数据预处理
PublicFunc.progressbar_update(self, 3, 7, Constants.SA_LABEL_PROCESSING_DATA, 30)
result = self.data.preprocess()
if not result.status:
PublicFunc.text_output(self.ui, "(3/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(3/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 重采样
PublicFunc.progressbar_update(self, 4, 7, Constants.RESAMPLING_DATA, 50)
result = self.data.resample()
if not result.status:
PublicFunc.text_output(self.ui, "(4/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(4/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 绘图
PublicFunc.progressbar_update(self, 5, 7, Constants.DRAWING_DATA, 70)
result = self.__plot__()
if not result.status:
PublicFunc.text_output(self.ui, "(5/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(5/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 更新表格
PublicFunc.progressbar_update(self, 6, 7, Constants.SA_LABEL_UPDATING_TABLE, 90)
result = self.update_tableWidget()
if not result.status:
PublicFunc.text_output(self.ui, "(6/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(6/7)" + result.info, Constants.TIPS_TYPE_INFO)
# 更新信息
PublicFunc.progressbar_update(self, 7, 7, Constants.SA_LABEL_UPDATING_INFO, 95)
result = self.update_UI_Args()
if not result.status:
PublicFunc.text_output(self.ui, "(7/7)" + result.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(7/7)" + result.info, Constants.TIPS_TYPE_INFO)
self.__reset__()
self.figToolbar.action_Reset_Signal_and_Time.setEnabled(True)
for action in self.figToolbar._actions.values():
action.setEnabled(True)
ButtonState["Current"]["pushButton_input"] = False
ButtonState["Current"]["pushButton_input_setting"] = False
ButtonState["Current"]["pushButton_quick_remark_input_waitingForTalk"] = True
ButtonState["Current"]["pushButton_quick_remark_input_maybeDesaturation"] = True
ButtonState["Current"]["pushButton_quick_remark_input_maybeWrongLabeled"] = True
ButtonState["Current"]["pushButton_quick_remark_input_durationNoEnough"] = True
ButtonState["Current"]["pushButton_quick_remark_input_littleChange"] = True
ButtonState["Current"]["pushButton_quick_remark_input_noNormalRespBetweenArtifact"] = True
ButtonState["Current"]["pushButton_quick_remark_input_lowSignalNoiseRatio"] = True
ButtonState["Current"]["pushButton_quick_remark_input_changeOnMiddle"] = True
ButtonState["Current"]["pushButton_prev"] = True
ButtonState["Current"]["pushButton_next"] = True
ButtonState["Current"]["pushButton_confirmLabel"] = True
ButtonState["Current"]["pushButton_previous10s"] = True
ButtonState["Current"]["pushButton_previous30s"] = True
ButtonState["Current"]["pushButton_previous60s"] = True
ButtonState["Current"]["pushButton_next10s"] = True
ButtonState["Current"]["pushButton_next30s"] = True
ButtonState["Current"]["pushButton_next60s"] = True
PublicFunc.finish_operation(self, ButtonState)
def slot_tableWidget_label_on_cell_double_clicked(self, row, column):
self.reset_axes()
Config["PlotEventIndex"] = int(row)
PublicFunc.text_output(self.ui, "{}index".format(Constants.SA_LABEL_JUMP, Config["PlotEventIndex"]), Constants.TIPS_TYPE_INFO)
self.draw_one_event()
self.update_UI_Args()
def slot_lineEdit_filter_label(self, filter_text):
sender = self.sender()
if sender == self.ui.lineEdit_filter_label:
for row in range(self.ui.tableWidget_label.rowCount()):
match = False
for col in range(self.ui.tableWidget_label.columnCount()):
item = self.ui.tableWidget_label.item(row, col)
if filter_text.lower() in item.text().lower():
match = True
break
self.ui.tableWidget_label.setRowHidden(row, not match)
elif sender == self.ui.lineEdit_filter_label_add:
for row in range(self.ui.tableWidget_label_add.rowCount()):
match = False
for col in range(self.ui.tableWidget_label_add.columnCount()):
item = self.ui.tableWidget_label_add.item(row, col)
if filter_text.lower() in item.text().lower():
match = True
break
self.ui.tableWidget_label_add.setRowHidden(row, not match)
else:
raise ValueError("信号发生器错误")
def toggle_resetOriginalView(self):
self.figToolbar.home()
self.figToolbar.action_Reset_Signal_and_Time.setChecked(False)
self.ui.spinBox_correctStart.setValue(Config["BCG_SP"])
self.ui.spinBox_correctEnd.setValue(Config["BCG_EP"])
def reset_axes(self):
self.ax0.clear()
self.ax1.clear()
self.ax2.clear()
self.ax3.clear()
self.ax4.clear()
self.ax5.clear()
self.ax6.clear()
self.ax0.grid(True)
self.ax0.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax1.grid(True)
self.ax1.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax1.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax2.grid(True)
self.ax2.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax2.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax3.grid(True)
self.ax3.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax3.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax4.grid(True)
self.ax4.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax4.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax5.grid(True)
self.ax5.xaxis.set_major_formatter(ConfigParams.FORMATTER)
self.ax5.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE)
self.ax6.grid(True)
self.ax6.xaxis.set_major_formatter(ConfigParams.FORMATTER)
def on_xlim_change(self, event_ax):
# 获取当前x轴范围
left, right = event_ax.get_xlim()
self.ui.spinBox_correctStart.setValue(round(left))
self.ui.spinBox_correctEnd.setValue(round(right))
def draw_one_event(self):
one_bcg_data = self.data.df_corrected.loc[Config["PlotEventIndex"]]
# 获取BCG事件开始与结束时间
bcg_SP = one_bcg_data["Start"]
bcg_EP = one_bcg_data["End"]
Config["BCG_SP"] = bcg_SP
Config["BCG_EP"] = bcg_EP
bcg_duration = bcg_EP - bcg_SP
# "index:{} epoch:{} event:{}".format(Config["PlotEventIndex"], one_bcg_data['Epoch'], one_bcg_data['Event type'])
# 进行向两边延展
bcg_SP = bcg_SP - Config["AddSecond"]["Front"]
bcg_EP = bcg_EP + Config["AddSecond"]["Back"]
if not (self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "isLabeled"] == -1):
bcg_duration_new = self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "correct_End"] - \
self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "correct_Start"]
self.ui.spinBox_correctStart.setValue(
str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "correct_Start"]))
self.ui.spinBox_correctEnd.setValue(
str(self.data.df_corrected.at[Config["EventLabelIndexList"][Config["PlotEventIndex"]], "correct_End"]))
self.ui.label_BCG_event.setText("Index:{}/{}".format((Config["PlotEventIndex"] + 1), len(self.data.df_corrected)))
self.ui.label_BCG_event_2.setText("Epoch:{} Duration:{}s New Duration:{}s".format(one_bcg_data["Epoch"], bcg_duration, bcg_duration_new))
else:
bcg_duration_new = -1
self.ui.spinBox_correctStart.setValue(Config["BCG_SP"])
self.ui.spinBox_correctEnd.setValue(Config["BCG_EP"])
self.ui.label_BCG_event.setText("Index:{}/{}".format((Config["PlotEventIndex"] + 1), len(self.data.df_corrected)))
self.ui.label_BCG_event_2.setText("Epoch:{} Duration:{}s".format(one_bcg_data["Epoch"], bcg_duration))
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow T", bcg_duration_new=bcg_duration_new,
show_mode="one")
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Flow P", bcg_duration_new=bcg_duration_new,
show_mode="one")
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Tho", bcg_duration_new=bcg_duration_new,
show_mode="one")
self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="Effort Abd", bcg_duration_new=bcg_duration_new,
show_mode="one")
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.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.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="Index:{}/{}".format((Config["PlotEventIndex"] + 1), len(self.data.df_corrected)),
bcg_duration_new=bcg_duration_new, show_mode="one")
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,
title=None, bcg_duration_new=-1,
show_mode=None):
SP = 0 if SP < 0 else SP
plt_.plot(linspace(SP, EP, (EP - SP) * Config["InputConfig"]["PlotFreq"]),
self.data.channel[channel][SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]], label=channel,
color=self.color_cycle[0])
for j in event_code:
if channel == "SpO2":
mask = self.data.event_label[SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]] == j
elif channel == "orgdata" or channel == "0.7lowpass_resp":
if j <= 5:
mask = self.data.event_label[SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]] == j
else:
mask = self.data.artifact_label[SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]] == j
else:
mask = self.data.event_label[SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]] == j
if event_show_under:
min_point = self.data.channel[channel][SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]].min()
len_segment = EP * Config["InputConfig"]["PlotFreq"] - SP * Config["InputConfig"]["PlotFreq"]
y = (min_point.repeat(len_segment) * mask).astype('float')
place(y, y == 0, nan)
else:
y = (self.data.channel[channel][SP * Config["InputConfig"]["PlotFreq"]:EP * Config["InputConfig"]["PlotFreq"]] * mask).astype('float')
place(y, y == 0, nan)
plt_.plot(linspace(SP, EP, (EP - SP) * Config["InputConfig"]["PlotFreq"]), y, color=self.color_cycle[j],
linestyle="-")
plt_.legend(fontsize=8, loc=1)
if title is not None:
plt_.title(title)
ax = plt.gca()
ax.grid(True)
# 绘制半透明矩形框
if show_mode == "one":
if not (bcg_duration_new == -1):
if self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "correct_EventsType"] == "Obstructive apnea":
ax.axvspan(
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_End"],
color='red',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "correct_EventsType"] == "Central apnea":
ax.axvspan(
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_End"],
color='blue',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "correct_EventsType"] == "Mixed apnea":
ax.axvspan(
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_End"],
color='gray',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "correct_EventsType"] == "Hypopnea":
ax.axvspan(
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "correct_End"],
color='pink',
alpha=0.3)
else:
if self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "Event type"] == "Obstructive apnea":
ax.axvspan(self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "End"],
color='red',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "Event type"] == "Central apnea":
ax.axvspan(self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "End"],
color='blue',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "Event type"] == "Mixed apnea":
ax.axvspan(self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "End"],
color='gray',
alpha=0.3)
elif self.data.df_corrected.at[
self.data.df_corrected[Config["PlotEventIndex"]], "Event type"] == "Hypopnea":
ax.axvspan(self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "Start"],
self.data.df_corrected.at[self.data.df_corrected[Config["PlotEventIndex"]], "End"],
color='pink',
alpha=0.3)
class Data:
def __init__(self):
self.OrgBCG = None
self.Tho = None
self.Abd = None
self.FlowT = None
self.FlowP = None
self.SpO2 = None
self.lowPass20Hz = None
self.lowPassResp = None
self.OrgBCG_Resampled = None
self.Tho_Resampled = None
self.Abd_Resampled = None
self.FlowT_Resampled = None
self.FlowP_Resampled = None
self.SpO2_Resampled = None
self.lowPass20Hz_Resampled = None
self.lowPassResp_Resampled = None
self.channel = {
"orgdata": self.lowPass20Hz_Resampled,
"0.7lowpass_resp": self.lowPassResp_Resampled,
"Effort Tho": self.Tho_Resampled,
"Effort Abd": self.Abd_Resampled,
"Flow T": self.FlowT_Resampled,
"Flow P": self.FlowP_Resampled,
"SpO2": self.SpO2_Resampled
}
self.event_label = None
self.artifact_label = None
self.Artifact = None
self.df_corrected = None
self.df_addNew = None
def open_file(self):
if ((not Path(Config["Path"]["Input_OrgBCG"]).exists())
or (not Path(Config["Path"]["Input_Tho"]).exists())
or (not Path(Config["Path"]["Input_Abd"]).exists())
or (not Path(Config["Path"]["Input_FlowT"]).exists())
or (not Path(Config["Path"]["Input_FlowP"]).exists())
or (not Path(Config["Path"]["Input_SpO2"]).exists())
or (not Path(Config["Path"]["Input_Artifact"]).exists())):
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Data_Path_Not_Exist"])
try:
self.OrgBCG = read_csv(Config["Path"]["Input_OrgBCG"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.Tho = read_csv(Config["Path"]["Input_Tho"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.Abd = read_csv(Config["Path"]["Input_Abd"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.FlowT = read_csv(Config["Path"]["Input_FlowT"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.FlowP = read_csv(Config["Path"]["Input_FlowP"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.SpO2 = read_csv(Config["Path"]["Input_SpO2"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.Artifact = read_csv(Config["Path"]["Input_Artifact"],
encoding=ConfigParams.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.event_label = zeros(len(self.OrgBCG) + (int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"])) * Config["InputConfig"]["PlotFreq"]))
except Exception as e:
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Read_Data_Exception"] + "\n" + format_exc())
try:
# 检查体动标签正确性,长度
if len(self.Artifact) % 4 != 0:
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Artifact_Format_Not_Correct"])
for i in range(0, len(self.Artifact), 4):
unit_data = self.Artifact[i:i + 4]
if len(unit_data) < 4:
break
self.Artifact = self.Artifact.reshape(-1, 4)
except Exception as e:
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Get_Artifact_Format_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.INPUT_FINISHED)
def get_archive(self):
if (not Path(Config["Path"]["Save"]).exists()) or (not Path(Config["Path"]["Save_2"]).exists()):
self.df_corrected = read_csv(Config["Path"]["Input_Label"], encoding="gbk")
self.df_corrected["Start"] = self.df_corrected["Start"].astype(int)
self.df_corrected["End"] = self.df_corrected["End"].astype(int)
self.df_corrected["score"] = "-1"
self.df_corrected["remark"] = ""
self.df_corrected["correct_Start"] = "-1"
self.df_corrected["correct_End"] = "-1"
self.df_corrected["correct_EventsType"] = ""
self.df_corrected["isLabeled"] = "-1"
self.df_corrected["score"] = self.df_corrected["score"].astype(int)
self.df_corrected["remark"] = self.df_corrected["remark"].astype(str)
self.df_corrected["correct_Start"] = self.df_corrected["correct_Start"].astype(int)
self.df_corrected["correct_End"] = self.df_corrected["correct_End"].astype(int)
self.df_corrected["correct_EventsType"] = self.df_corrected["correct_EventsType"].astype(str)
self.df_corrected["isLabeled"] = self.df_corrected["isLabeled"].astype(int)
self.df_corrected = self.df_corrected.sort_values(by='Epoch')
for one_data in self.df_corrected.index:
one_data = self.df_corrected.loc[one_data]
SP = one_data["Start"] * Config["InputConfig"]["PlotFreq"]
EP = one_data["End"] * Config["InputConfig"]["PlotFreq"]
if one_data["Event type"] == "Hypopnea":
self.event_label[SP:EP] = 1
elif one_data["Event type"] == "Central apnea":
self.event_label[SP:EP] = 2
elif one_data["Event type"] == "Obstructive apnea":
self.event_label[SP:EP] = 3
elif one_data["Event type"] == "Mixed apnea":
self.event_label[SP:EP] = 4
self.df_addNew = DataFrame(columns=self.df_corrected.columns)
self.df_addNew["score"] = self.df_addNew["score"].astype(int)
self.df_addNew["remark"] = self.df_addNew["remark"].astype(str)
self.df_addNew["correct_Start"] = self.df_addNew["correct_Start"].astype(int)
self.df_addNew["correct_End"] = self.df_addNew["correct_End"].astype(int)
self.df_addNew["correct_EventsType"] = self.df_addNew["correct_EventsType"].astype(
str)
self.df_addNew["isLabeled"] = self.df_addNew["isLabeled"].astype(int)
self.df_addNew.to_csv(Config["Path"]["Save_2"], index=False, encoding="gbk")
Config["EventLabelIndexList"] = self.df_corrected.index.tolist()
return Result().success(info=Constants.SA_LABEL_ARCHIVE_NOT_EXIST)
else:
try:
self.df_corrected = read_csv(Config["Path"]["Save"], encoding="gbk")
self.df_addNew = read_csv(Config["Path"]["Save_2"], encoding="gbk")
csv_headers = self.df_corrected.columns.tolist()
if not (("score" in csv_headers) or ("remark" in csv_headers) or ("correct_Start" in csv_headers) or (
"correct_End") in csv_headers or ("isLabeled" in csv_headers)):
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON[
"Label_Format_Exception"])
csv_headers = self.df_addNew.columns.tolist()
if not (("score" in csv_headers) or ("remark" in csv_headers) or ("correct_Start" in csv_headers) or (
"correct_End") in csv_headers or ("isLabeled" in csv_headers)):
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON[
"Label_Format_Exception"])
Config["EventLabelIndexList"] = self.df_corrected.index.tolist()
for one_data in self.df_corrected.index:
one_data = self.df_corrected.loc[one_data]
SP = one_data["Start"] * Config["InputConfig"]["PlotFreq"]
EP = one_data["End"] * Config["InputConfig"]["PlotFreq"]
if one_data["Event type"] == "Hypopnea":
self.event_label[SP:EP] = 1
elif one_data["Event type"] == "Central apnea":
self.event_label[SP:EP] = 2
elif one_data["Event type"] == "Obstructive apnea":
self.event_label[SP:EP] = 3
elif one_data["Event type"] == "Mixed apnea":
self.event_label[SP:EP] = 4
except Exception as e:
return Result().failure(info=Constants.INPUT_FAILURE + Constants.SA_LABEL_FAILURE_REASON[
"Label_Format_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.SA_LABEL_ARCHIVE_EXIST)
def preprocess(self):
if self.OrgBCG is None:
return Result().failure(info=Constants.SA_LABEL_PROCESS_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Raw_Data_Not_Exist"])
try:
self.lowPass20Hz = Butterworth_for_ECG_PreProcess(self.OrgBCG, Config["InputConfig"]["OrgBCGFreq"],
'lowpass', low_cut=20, order=3)
self.lowPassResp = Butterworth_for_ECG_PreProcess(self.OrgBCG, Config["InputConfig"]["OrgBCGFreq"],
'lowpass', low_cut=0.7, order=3)
self.artifact_label = zeros(len(self.event_label) + (int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"])) * Config["InputConfig"]["PlotFreq"]))
for i, artifact_type, SP, EP in self.Artifact:
SP = int(SP) // (Config["InputConfig"]["OrgBCGFreq"] // Config["InputConfig"]["PlotFreq"])
EP = int(EP) // (Config["InputConfig"]["OrgBCGFreq"] // Config["InputConfig"]["PlotFreq"])
artifact_type = int(artifact_type) + 5
SP = 0 if SP < 0 else SP
if EP < 0:
continue
if SP > len(self.event_label):
continue
self.artifact_label[SP:EP] = artifact_type
except Exception as e:
return Result().failure(info=Constants.SA_LABEL_PROCESS_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Filter_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.SA_LABEL_PROCESS_FINISHED)
def resample(self):
if self.Tho is None or self.Abd is None or self.FlowT is None or self.FlowP is None or self.SpO2 is None or self.lowPass20Hz is None or self.lowPassResp is None:
return Result().failure(info=Constants.RESAMPLE_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Raw_Data_Not_Exist"])
try:
if Config["InputConfig"]["OrgBCGFreq"] < Config["InputConfig"]["PlotFreq"]:
self.lowPass20Hz_Resampled = self.lowPass20Hz.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["OrgBCGFreq"])
self.lowPassResp_Resampled = self.lowPassResp.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["OrgBCGFreq"])
elif Config["InputConfig"]["OrgBCGFreq"] > Config["InputConfig"]["PlotFreq"]:
self.lowPass20Hz_Resampled = self.lowPass20Hz[::int(
Config["InputConfig"]["OrgBCGFreq"] / Config["InputConfig"]["PlotFreq"])]
self.lowPassResp_Resampled = self.lowPassResp[::int(
Config["InputConfig"]["OrgBCGFreq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.lowPass20Hz_Resampled = self.lowPass20Hz
self.lowPassResp_Resampled = self.lowPassResp
self.lowPass20Hz_Resampled = append(self.lowPass20Hz_Resampled,
self.lowPass20Hz_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
self.lowPassResp_Resampled = append(self.lowPassResp_Resampled,
self.lowPassResp_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
if Config["InputConfig"]["ThoFreq"] < Config["InputConfig"]["PlotFreq"]:
self.Tho_Resampled = self.Tho.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["ThoFreq"])
elif Config["InputConfig"]["ThoFreq"] > Config["InputConfig"]["PlotFreq"]:
self.Tho_Resampled = self.Tho[::int(
Config["InputConfig"]["ThoFreq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.Tho_Resampled = self.Tho
self.Tho_Resampled = append(self.Tho_Resampled,
self.Tho_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
if Config["InputConfig"]["AbdFreq"] < Config["InputConfig"]["PlotFreq"]:
self.Abd_Resampled = self.Abd.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["AbdFreq"])
elif Config["InputConfig"]["AbdFreq"] > Config["InputConfig"]["PlotFreq"]:
self.Abd_Resampled = self.Abd[::int(
Config["InputConfig"]["AbdFreq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.Abd_Resampled = self.Abd
self.Abd_Resampled = append(self.Abd_Resampled,
self.Abd_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
if Config["InputConfig"]["FlowTFreq"] < Config["InputConfig"]["PlotFreq"]:
self.FlowT_Resampled = self.FlowT.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["FlowTFreq"])
elif Config["InputConfig"]["FlowTFreq"] > Config["InputConfig"]["PlotFreq"]:
self.FlowT_Resampled = self.FlowT[::int(
Config["InputConfig"]["FlowTFreq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.FlowT_Resampled = self.FlowT
self.FlowT_Resampled = append(self.FlowT_Resampled,
self.FlowT_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
if Config["InputConfig"]["FlowPFreq"] < Config["InputConfig"]["PlotFreq"]:
self.FlowP_Resampled = self.FlowP.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["FlowPFreq"])
elif Config["InputConfig"]["FlowPFreq"] > Config["InputConfig"]["PlotFreq"]:
self.FlowP_Resampled = self.FlowP[::int(
Config["InputConfig"]["FlowPFreq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.FlowP_Resampled = self.FlowP
self.FlowP_Resampled = append(self.FlowP_Resampled,
self.FlowP_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
if Config["InputConfig"]["SpO2Freq"] < Config["InputConfig"]["PlotFreq"]:
self.SpO2_Resampled = self.SpO2.repeat(
Config["InputConfig"]["PlotFreq"] / Config["InputConfig"]["SpO2Freq"])
elif Config["InputConfig"]["SpO2Freq"] > Config["InputConfig"]["PlotFreq"]:
self.SpO2_Resampled = self.SpO2[::int(
Config["InputConfig"]["SpO2Freq"] / Config["InputConfig"]["PlotFreq"])]
else:
self.SpO2_Resampled = self.SpO2
self.SpO2_Resampled = append(self.SpO2_Resampled,
self.SpO2_Resampled.mean().astype(int).repeat(
(int(Config["AddSecond"]["Front"] + int(Config["AddSecond"]["Back"]))
* Config["InputConfig"]["PlotFreq"])))
self.channel["orgdata"] = self.lowPass20Hz_Resampled,
self.channel["7lowpass_resp"] = self.lowPassResp_Resampled,
self.channel["Effort Tho"] = self.Tho_Resampled,
self.channel["Effort Abd"] = self.Abd_Resampled,
self.channel["Flow T"] = self.FlowT_Resampled,
self.channel["Flow P"] = self.FlowP_Resampled,
self.channel["SpO2"] = self.SpO2_Resampled,
except Exception as e:
return Result().failure(info=Constants.RESAMPLE_FAILURE + Constants.SA_LABEL_FAILURE_REASON["Resample_Exception"] + "\n" + format_exc())
return Result().success(info=Constants.RESAMPLE_FINISHED)
class CustomNavigationToolbar(NavigationToolbar2QT):
def __init__(self, canvas, parent):
super().__init__(canvas, parent)
self.action_Reset_Signal_and_Time = QAction('复原视图和时间', self)
self.action_Reset_Signal_and_Time.setFont(QFont(ConfigParams.FONT, 14))
self.action_Reset_Signal_and_Time.setCheckable(True)
self.insertAction(self._actions['home'], self.action_Reset_Signal_and_Time)