diff --git a/func/Module_SA_label.py b/func/Module_SA_label.py index 7941f95..0668599 100644 --- a/func/Module_SA_label.py +++ b/func/Module_SA_label.py @@ -3,30 +3,1214 @@ from pathlib import Path from traceback import format_exc import matplotlib.pyplot as plt -from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication +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.preprocess_input_setting import Ui_MainWindow_preprocess_input_setting +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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/func/Module_cut_PSG.py b/func/Module_cut_PSG.py index c790782..890cc3c 100644 --- a/func/Module_cut_PSG.py +++ b/func/Module_cut_PSG.py @@ -64,7 +64,7 @@ class MainWindow_cut_PSG(QMainWindow): "Path": { "InputFolder": str(Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID))), "SaveFolder": str(Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_ALIGNED / Path(str(self.sampID))), - "InputAlignInfo": str(Path(self.root_path) / ConfigParams.PUBLIC_PATH_LABEL / Path(str(self.sampID)) / (ConfigParams.CUT_PSG_SAVE_ECG_ALIGNINFO_FILENAME + ConfigParams.ENDSWITH_TXT)) + "InputAlignInfo": str(Path(self.root_path) / ConfigParams.PUBLIC_PATH_LABEL / Path(str(self.sampID))) } }) @@ -108,7 +108,7 @@ class MainWindow_cut_PSG(QMainWindow): def __slot_btn_execute__(self): PublicFunc.__disableAllButton__(self, ButtonState) - self.data = Data() + self.data = Data(self.root_path, self.sampID) Config["ECGFreq"] = self.ui.spinBox_ECGFreq.value() # 检查文件是否存在并获取其数据采样率 @@ -185,7 +185,7 @@ class MainWindow_cut_PSG(QMainWindow): class Data: - def __init__(self): + def __init__(self, root_path, sampID): self.alignInfo = None self.raw = {key: array([]) for key in Config["ChannelInput"]} @@ -194,6 +194,9 @@ class Data: self.SALabel = None self.startTime = None + self.root_path = root_path + self.sampID = sampID + def get_file_and_freq(self): try: for file_path in Path(Config["Path"]["InputFolder"]).glob('*'): @@ -206,22 +209,41 @@ class Data: freq = int(freq_str) self.freq[key] = freq except ValueError: - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Filename_Format_not_Correct"]) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["Filename_Format_not_Correct"]) for value in self.freq.values(): if value == 0: - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Filename_Format_not_Correct"]) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["Filename_Format_not_Correct"]) if not any((Config["LabelInput"]["SA Label"] + Config["EndWith"]["SA Label"]) in str(file) for file in Path(Config["Path"]["InputFolder"]).glob('*')): - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_FAILURE_REASON["File_not_Exist"]) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["File_not_Exist"]) if not any((Config["StartTime"] + Config["EndWith"]["StartTime"]) in str(file) for file in Path(Config["Path"]["InputFolder"]).glob('*')): - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_FAILURE_REASON["File_not_Exist"]) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["File_not_Exist"]) if not Path(Config["Path"]["InputAlignInfo"]).exists(): - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_FAILURE_REASON["File_not_Exist"]) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["File_not_Exist"]) except Exception as e: - return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE["Get_File_and_Freq_Excepetion"] + "\n" + format_exc()) + return Result().failure(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FAILURE + + Constants.FAILURE_REASON["Get_File_and_Freq_Excepetion"] + "\n" + format_exc()) return Result().success(info=Constants.CUT_PSG_GET_FILE_AND_FREQ_FINISHED) def open_file(self): + path = str(Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID))) + for value in Config["ChannelInput"].values(): + result = PublicFunc.examine_file(path, value) + if not result.status: + return result + + if Path(Config["Path"]["InputAlignInfo"]).is_file(): + Config["Path"]["InputAlignInfo"] = str(Path(Config["Path"]["InputAlignInfo"]).parent) + + Config["Path"]["InputAlignInfo"] = str( + Path(Config["Path"]["InputAlignInfo"]) / Path( + ConfigParams.PRECISELY_ALIGN_INFO + ConfigParams.ENDSWITH_TXT)) + try: for key in Config["ChannelInput"].keys(): self.raw[key] = read_csv(Path(Config["Path"]["InputFolder"]) / Path((Config["ChannelInput"][key] + str(self.freq[key]) + Config["EndWith"][key])), @@ -237,7 +259,8 @@ class Data: header=None).to_numpy().reshape(-1) self.alignInfo = literal_eval(self.alignInfo[0]) except Exception as e: - return Result().failure(info=Constants.INPUT_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Read_Data_Exception"] + "\n" + format_exc()) + return Result().failure(info=Constants.INPUT_FAILURE + + Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.INPUT_FINISHED) @@ -255,9 +278,11 @@ class Data: # 切割信号 self.raw[key] = self.raw[key][start_index_cut:end_index_cut] except Exception: - return Result().failure(info=Constants.CUT_PSG_CUT_DATA_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Cut_Data_Length_not_Correct"]) + return Result().failure(info=Constants.CUT_PSG_CUT_DATA_FAILURE + + Constants.FAILURE_REASON["Cut_Data_Length_not_Correct"]) except Exception as e: - return Result().failure(info=Constants.CUT_PSG_CUT_DATA_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Cut_Data_Exception"] + "\n" + format_exc()) + return Result().failure(info=Constants.CUT_PSG_CUT_DATA_FAILURE + + Constants.FAILURE_REASON["Cut_Data_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.CUT_PSG_CUT_DATA_FINISHED) @@ -269,7 +294,8 @@ class Data: self.SALabel["Duration"] = self.SALabel["Duration"].astype(str) self.SALabel["Duration"] = self.SALabel["Duration"].str.replace(r' \(.*?\)', '', regex=True) except Exception: - return Result().failure(info=Constants.CUT_PSG_ALIGN_LABEL_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Align_Label_SALabel_Format_not_Correct"]) + return Result().failure(info=Constants.CUT_PSG_ALIGN_LABEL_FAILURE + + Constants.FAILURE_REASON["Align_Label_SALabel_Format_not_Correct"]) try: # 获取记录开始时间 @@ -290,14 +316,16 @@ class Data: self.SALabel = self.SALabel[self.SALabel["Start"] < ECG_length] self.SALabel.loc[self.SALabel["End"] >= ECG_length, "End"] = ECG_length - 1 except Exception as e: - return Result().failure(info=Constants.CUT_PSG_ALIGN_LABEL_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Align_Label_Exception"] + "\n" + format_exc()) + return Result().failure(info=Constants.CUT_PSG_ALIGN_LABEL_FAILURE + + Constants.FAILURE_REASON["Align_Label_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.CUT_PSG_ALIGN_LABEL_FINISHED) def save(self): for raw in self.raw.values(): if len(raw) == 0: - return Result().failure(info=Constants.SAVING_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Save_Data_not_Exist"]) + return Result().failure(info=Constants.SAVING_FAILURE + + Constants.FAILURE_REASON["Data_not_Exist"]) try: for key, raw in self.raw.items(): @@ -307,7 +335,8 @@ class Data: index=False, encoding="gbk") except Exception as e: - return Result().failure(info=Constants.SAVING_FAILURE + Constants.CUT_PSG_FAILURE_REASON["Save_Exception"] + "\n" + format_exc()) + return Result().failure(info=Constants.SAVING_FAILURE + + Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.SAVING_FINISHED) diff --git a/func/Module_detect_Rpeak.py b/func/Module_detect_Rpeak.py index 03308e9..c2bcd02 100644 --- a/func/Module_detect_Rpeak.py +++ b/func/Module_detect_Rpeak.py @@ -415,7 +415,7 @@ class Data: header=None).to_numpy().reshape(-1) except Exception as e: return Result().failure(info=Constants.INPUT_FAILURE + - Constants.FAILURE_REASON["Read_Data_Exception"] + "\n" + format_exc()) + Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.INPUT_FINISHED) diff --git a/func/Module_mainwindow.py b/func/Module_mainwindow.py index b552eb5..d89b0bc 100644 --- a/func/Module_mainwindow.py +++ b/func/Module_mainwindow.py @@ -209,8 +209,6 @@ class MainWindow(QMainWindow, Ui_Signal_Label): if not self.check_sampID(): return self.cut_PSG.show(root_path, int(sampID)) - # 默认最大化显示而非固定分辨率 - self.cut_PSG.showMaximized() def __slot_btn_artifact_label__(self): self.artifact_label = MainWindow_artifact_label() diff --git a/func/utils/ConfigParams.py b/func/utils/ConfigParams.py index 76dfe25..c7179fe 100644 --- a/func/utils/ConfigParams.py +++ b/func/utils/ConfigParams.py @@ -35,6 +35,9 @@ class ConfigParams: ABD_RAW: str = "Effort Abd_Raw_" FLOWT_RAW: str = "Flow T_Raw_" FLOWP_RAW: str = "Flow P_Raw_" + SNORE_RAW: str = "Snore_Raw_" + SPO2_RAW: str = "SpO2_Raw_" + FIVE_CLASS_RAW: str = "5_class_Raw_" BCG_FILTER: str = "BCG_Filter_" ECG_FILTER: str = "ECG_Filter_" JPEAK_REVISE: str = "Jpeak_revise_" @@ -49,6 +52,9 @@ class ConfigParams: ABD_SYNC: str = "Effort Abd_Sync_" FLOWT_SYNC: str = "Flow T_Sync_" FLOWP_SYNC: str = "Flow P_Sync_" + SNORE_SYNC: str = "Snore_Sync_" + SPO2_SYNC: str = "SpO2_Sync_" + FIVE_CLASS_SYNC: str = "5_class_Sync_" BCG_SYNC: str = "BCG_Sync_" ECG_SYNC: str = "ECG_Sync_" JPEAK_SYNC: str = "Jpeak_Sync_" @@ -100,10 +106,6 @@ class ConfigParams: "ECGBandPassHigh": 25 } } - PREPROCESS_INPUT_BCG_FILENAME: str = "OrgBCG_Raw_" - PREPROCESS_INPUT_ECG_FILENAME: str = "ECG II_Raw_" - PREPROCESS_SAVE_BCG_FILENAME: str = "BCG_Filter_" - PREPROCESS_SAVE_ECG_FILENAME: str = "ECG_Filter_" PREPROCESS_SAVE_CHUNK_SIZE: int = 1000000 # BCG的J峰算法定位 @@ -124,8 +126,6 @@ class ConfigParams: "UseCPU": False, "DetectMethod": "" } - DETECT_JPEAK_INPUT_BCG_FILENAME: str = "BCG_Filter_" - DETECT_JPEAK_SAVE_FILENAME: str = "JPeak_revise" DETECT_JPEAK_SAVE_CHUNK_SIZE: int = 100 # ECG的R峰算法定位 @@ -141,8 +141,6 @@ class ConfigParams: "PeaksValue": 200, "DetectMethod": "" } - DETECT_RPEAK_INPUT_ECG_FILENAME: str = "ECG_Filter_" - DETECT_RPEAK_SAVE_FILENAME: str = "Rpeak_final" DETECT_RPEAK_SAVE_CHUNK_SIZE: int = 100 # 人工纠正 @@ -169,13 +167,6 @@ class ConfigParams: "MoveSpeed": 1000 } } - LABEL_CHECK_INPUT_BCG_FILENAME: str = "BCG_Filter_" - LABEL_CHECK_INPUT_JPEAK_FILENAME: str = "JPeak_revise" - LABEL_CHECK_SAVE_JPEAK_FILENAME: str = "JPeak_revise_corrected" - LABEL_CHECK_INPUT_ECG_FILENAME: str = "ECG_Filter_" - LABEL_CHECK_INPUT_RPEAK_FILENAME: str = "Rpeak_final" - LABEL_CHECK_SAVE_RPEAK_FILENAME: str = "Rpeak_final_corrected" - LABEL_CHECK_APPROXIMATELY_ALIGNINFO_FILENAME: str = "Approximately_Align_Info" LABEL_CHECK_SAVE_CHUNK_SIZE: int = 100 LABEL_CHECK_LABEL_TRANSPARENCY: float = 0.2 LABEL_CHECK_ACTION_LABEL_MULTIPLE_SHORTCUT_KEY: str = "Z" @@ -190,18 +181,6 @@ class ConfigParams: "UseFreq": 1000 } } - PRECISELY_ALIGN_INPUT_ORGBCG_FILENAME: str = "OrgBCG_Raw_" - PRECISELY_ALIGN_INPUT_BCG_FILENAME: str = "BCG_Filter_" - PRECISELY_ALIGN_INPUT_JPEAK_FILENAME: str = "JPeak_revise_corrected" - PRECISELY_ALIGN_SAVE_ALIGNINFO_FILENAME: str = "Align_info" - PRECISELY_ALIGN_INPUT_ECG_FILENAME: str = "ECG_Filter_" - PRECISELY_ALIGN_INPUT_RPEAK_FILENAME: str = "Rpeak_final_corrected" - PRECISELY_ALIGN_SAVE_ORGBCG_FILENAME: str = "OrgBCG_Sync_" - PRECISELY_ALIGN_SAVE_BCG_FILENAME: str = "BCG_Sync_" - PRECISELY_ALIGN_SAVE_ECG_FILENAME: str = "ECG_Sync_" - PRECISELY_ALIGN_SAVE_JPEAK_FILENAME: str = "JPeak_Sync" - PRECISELY_ALIGN_SAVE_RPEAK_FILENAME: str = "Rpeak_Sync" - PRECISELY_ALIGN_APPROXIMATELY_ALIGNINFO_FILENAME: str = "Approximately_Align_Info" PRECISELY_ALIGN_ACTION_GET_RANGE_SHORTCUT_KEY: str = "Z" PRECISELY_ALIGN_SAVE_CHUNK_SIZE: int = 1000000 PRECISELY_ALIGN_SAVE_PEAK_CHUNK_SIZE: int = 100 @@ -212,26 +191,26 @@ class ConfigParams: CUT_PSG_CONFIG_NEW_CONTENT: dict = { "ECGFreq": 1000, "ChannelInput": { - "Effort Tho": "Effort Tho_Raw_", - "Effort Abd": "Effort Abd_Raw_", - "Flow T": "Flow T_Raw_", - "Flow P": "Flow P_Raw_", - "Snore": "Snore_Raw_", - "SpO2": "SpO2_Raw_", - "5_class": "5_class_Raw_" + "Effort Tho": THO_RAW, + "Effort Abd": ABD_RAW, + "Flow T": FLOWT_RAW, + "Flow P": FLOWP_RAW, + "Snore": SNORE_RAW, + "SpO2": SPO2_RAW, + "5_class": FIVE_CLASS_RAW }, "LabelInput": { "SA Label": "SA Label_Raw" }, "StartTime": "StartTime_Raw", "ChannelSave": { - "Effort Tho": "Effort Tho_Sync_", - "Effort Abd": "Effort Abd_Sync_", - "Flow T": "Flow T_Sync_", - "Flow P": "Flow P_Sync_", - "Snore": "Snore_Sync_", - "SpO2": "SpO2_Sync_", - "5_class": "5_class_Sync_" + "Effort Tho": THO_SYNC, + "Effort Abd": ABD_SYNC, + "Flow T": FLOWT_SYNC, + "Flow P": FLOWP_SYNC, + "Snore": SNORE_SYNC, + "SpO2": SPO2_SYNC, + "5_class": FIVE_CLASS_SYNC }, "LabelSave": { "SA Label": "SA Label_Sync" @@ -249,7 +228,6 @@ class ConfigParams: "StartTime": ENDSWITH_TXT }, } - CUT_PSG_SAVE_ECG_ALIGNINFO_FILENAME: str = "Align_info" CUT_PSG_SALABEL_EVENT: list = ["Hypopnea", "Central apnea", "Obstructive apnea", "Mixed apnea"] diff --git a/func/utils/Constants.py b/func/utils/Constants.py index 5574608..3d5dd74 100644 --- a/func/utils/Constants.py +++ b/func/utils/Constants.py @@ -94,6 +94,10 @@ class Constants: "Frequency_Not_In_Filename": "(数据频率不在文件名中)", "Data_Not_Exist": "(数据不存在)", "Model_File_Not_Exist": "(模型文件不存在)", + "Cut_Data_Length_not_Correct": "(切割数据时长度不正确)", + "Align_Label_SALabel_Format_not_Correct": "(映射标签时SA Label中的文件格式不正确)", + "Filename_Format_not_Correct": "(文件名格式不正确)", + "Method_Not_Exist": "(检测方法不存在)", "Open_Data_Exception": "(打开数据异常)", "Process_Exception": "(处理异常)", @@ -106,7 +110,9 @@ class Constants: "Calculate_Correlation_Exception": "(计算相关性异常)", "Correlation_Align_Exception": "(处理相关对齐异常)", "PostProcess_Align_Exception": "(数据后处理异常)", - + "Cut_Data_Exception": "(切割数据异常)", + "Align_Label_Exception": "(映射标签异常)", + "Get_File_and_Freq_Excepetion": "(检查文件是否存在并获取其数据采样率异常)", "res_orgBcg_Not_Exist": "(切割后orgBcg不存在)", "res_BCG_Not_Exist": "(切割后BCG不存在)", @@ -145,19 +151,6 @@ class Constants: DETECT_RPEAK_PREDICT_FINISHED: str = "预测完成" DETECT_RPEAK_PREDICT_FAILURE: str = "预测失败" - DETECT_RPEAK_FAILURE_REASON = { - "Data_Path_Not_Exist": "(数据路径不存在)", - "Read_Data_Exception": "(读取数据异常)", - "Method_Not_Exist": "(检测方法不存在)", - "Read_Method_Exception": "(读取方法异常)", - "Predict_Exception": "(峰值预测异常)", - "Raw_Data_Not_Exist": "(原始数据不存在)", - "Filter_Exception": "(滤波器异常)", - "Processed_Data_Not_Exist": "(处理后数据不存在)", - "Peak_Not_Exist": "(预测的峰值不存在)", - "Save_Exception": "(保存异常)" - } - DETECT_RPEAK_DATA_LENGTH_POINTS: str = "数据长度(点数):" DETECT_RPEAK_DURATION_MIN: str = "数据时长(分钟):" DETECT_RPEAK_PEAK_AMOUNT: str = "R峰个数:" @@ -167,16 +160,6 @@ class Constants: DETECT_RPEAK_PLOT_LABEL_INTERVAL: str = "Interval" # 人工纠正 - LABEL_CHECK_FAILURE_REASON = { - "Data_Path_Not_Exist": "(数据路径不存在)", - "Read_Data_Exception": "(读取数据异常)", - "Raw_Data_Not_Exist": "(原始数据不存在)", - "Filter_Exception": "(滤波器异常)", - "Processed_Data_Not_Exist": "(处理后数据不存在)", - "Peak_Not_Exist": "(峰值不存在)", - "Save_Exception": "(保存异常)" - } - LABEL_CHECK_PLOT_LABEL_SIGNAL: str = "Data_Processed" LABEL_CHECK_PLOT_LABEL_PEAK_ORIGINAL: str = "Peaks_Original" LABEL_CHECK_PLOT_LABEL_PEAK_CORRECTED: str = "Peaks_Corrected" @@ -250,6 +233,21 @@ class Constants: PRECISELY_ALIGN_RECOVER_SCALE: str = "尺度恢复" PRECISELY_ALIGN_ACTION_GET_RANGE_NAME: str = f"设置范围({ConfigParams.PRECISELY_ALIGN_ACTION_GET_RANGE_SHORTCUT_KEY})" + # 冗余数据切割和标签映射 + CUT_PSG_GETTING_FILE_AND_FREQ: str = "正在获取文件及其采样率" + CUT_PSG_GET_FILE_AND_FREQ_FINISHED: str = "获取文件及其采样率完成" + CUT_PSG_GET_FILE_AND_FREQ_FAILURE: str = "获取文件及其采样率失败" + + CUT_PSG_CUTTING_DATA: str = "正在切割数据" + CUT_PSG_CUT_DATA_FINISHED: str = "切割数据完成" + CUT_PSG_CUT_DATA_FAILURE: str = "切割数据失败" + + CUT_PSG_ALIGNING_LABEL: str = "正在映射标签" + CUT_PSG_ALIGN_LABEL_FINISHED: str = "映射标签完成" + CUT_PSG_ALIGN_LABEL_FAILURE: str = "映射标签失败" + + + @@ -315,32 +313,6 @@ class Constants: "Save_Exception": "(保存异常)" } - # 冗余数据切割和标签映射 - CUT_PSG_GETTING_FILE_AND_FREQ: str = "正在获取文件及其采样率" - CUT_PSG_GET_FILE_AND_FREQ_FINISHED: str = "获取文件及其采样率完成" - CUT_PSG_GET_FILE_AND_FREQ_FAILURE: str = "获取文件及其采样率失败" - - CUT_PSG_CUTTING_DATA: str = "正在切割数据" - CUT_PSG_CUT_DATA_FINISHED: str = "切割数据完成" - CUT_PSG_CUT_DATA_FAILURE: str = "切割数据失败" - - CUT_PSG_ALIGNING_LABEL: str = "正在映射标签" - CUT_PSG_ALIGN_LABEL_FINISHED: str = "映射标签完成" - CUT_PSG_ALIGN_LABEL_FAILURE: str = "映射标签失败" - - CUT_PSG_FAILURE_REASON: str = { - "Filename_Format_not_Correct": "(文件名格式不正确)", - "File_not_Exist": "(需要处理的文件不存在)", - "Get_File_and_Freq_Excepetion": "(检查文件是否存在并获取其数据采样率异常)", - "Read_Data_Exception": "(读取数据异常)", - "Cut_Data_Length_not_Correct": "(切割数据时长度不正确)", - "Cut_Data_Exception": "(切割数据异常)", - "Align_Label_SALabel_Format_not_Correct": "(映射标签时SA Label中的文件格式不正确)", - "Align_Label_Exception": "(映射标签异常)", - "Save_Data_not_Exist": "(需要保存的数据不存在)", - "Save_Exception": "(保存异常)" - } - # 体动标注 ARTIFACT_LABEL_LOADING_ARCHIVE: str = "正在获取历史存档" ARTIFACT_LABEL_ARCHIVE_EXIST: str = "找到历史存档,成功读取"