from gc import collect from pathlib import Path from traceback import format_exc import matplotlib.pyplot as plt from PySide6.QtCore import QTimer, QCoreApplication from PySide6.QtGui import QAction, QFont from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication, QTableWidgetItem, QTableWidget from matplotlib import gridspec, patches from matplotlib.backends.backend_qt import NavigationToolbar2QT from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from numpy import append, delete, arange, setdiff1d from overrides import overrides from pandas import read_csv, DataFrame from scipy.signal import find_peaks from yaml import dump, load, FullLoader from func.utils.ConfigParams import Filename, Params from func.utils.PublicFunc import PublicFunc from func.utils.Constants import Constants from func.Filters.Preprocessing import data_preprocess_for_label_check from func.utils.Result import Result from ui.MainWindow.MainWindow_label_check import Ui_MainWindow_label_check from ui.setting.label_check_input_setting import Ui_MainWindow_label_check_input_setting Config = { } ButtonState = { "Default": { "pushButton_input_setting": True, "pushButton_input": True, "pushButton_prev_move": False, "pushButton_pause": False, "pushButton_next_move": False }, "Current": { "pushButton_input_setting": True, "pushButton_input": True, "pushButton_prev_move": False, "pushButton_pause": False, "pushButton_next_move": False } } class SettingWindow(QMainWindow): def __init__(self, mode, root_path, sampID): super(SettingWindow, self).__init__() self.ui = Ui_MainWindow_label_check_input_setting() self.ui.setupUi(self) self.mode = mode self.root_path = root_path self.sampID = sampID self.config = None self.__read_config__() self.ui.spinBox_input_freq_signal.valueChanged.connect(self.__update_ui__) self.ui.spinBox_bandPassOrder.valueChanged.connect(self.__update_ui__) self.ui.doubleSpinBox_bandPassLow.valueChanged.connect(self.__update_ui__) self.ui.doubleSpinBox_bandPassHigh.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(Params.LABEL_CHECK_CONFIG_FILE_PATH).exists(): with open(Params.LABEL_CHECK_CONFIG_FILE_PATH, "w") as f: dump(Params.LABEL_CHECK_CONFIG_NEW_CONTENT, f) with open(Params.LABEL_CHECK_CONFIG_FILE_PATH, "r") as f: file_config = load(f.read(), Loader=FullLoader) Config.update(file_config) self.config = file_config if self.mode == "BCG": Config.update({ "Path": { "Input_Signal": str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)))), "Input_Peak": str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)))), "Input_Approximately_Align": str((Path(self.root_path) / Filename.PATH_LABEL / Path(str(self.sampID)))), "Save": str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)))) }, "Mode": self.mode }) elif self.mode == "ECG": Config.update({ "Path": { "Input_Signal": str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)))), "Input_Peak": str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)))), "Input_Approximately_Align": str((Path(self.root_path) / Filename.PATH_LABEL / Path(str(self.sampID)))), "Save": str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)))) }, "Mode": self.mode }) else: raise ValueError("模式不存在") # 数据回显 self.ui.spinBox_input_freq_signal.setValue(Config["InputConfig"]["Freq"]) self.ui.plainTextEdit_file_path_input_signal.setPlainText(Config["Path"]["Input_Signal"]) self.ui.plainTextEdit_file_path_input_peak.setPlainText(Config["Path"]["Input_Peak"]) self.ui.plainTextEdit_file_path_input_approximately_align.setPlainText(Config["Path"]["Input_Approximately_Align"]) self.ui.plainTextEdit_file_path_save.setPlainText(Config["Path"]["Save"]) if Config["Mode"] == "BCG": self.ui.spinBox_bandPassOrder.setValue(Config["Filter"]["BCGBandPassOrder"]) self.ui.doubleSpinBox_bandPassLow.setValue(Config["Filter"]["BCGBandPassLow"]) self.ui.doubleSpinBox_bandPassHigh.setValue(Config["Filter"]["BCGBandPassHigh"]) elif Config["Mode"] == "ECG": self.ui.spinBox_bandPassOrder.setValue(Config["Filter"]["ECGBandPassOrder"]) self.ui.doubleSpinBox_bandPassLow.setValue(Config["Filter"]["ECGBandPassLow"]) self.ui.doubleSpinBox_bandPassHigh.setValue(Config["Filter"]["ECGBandPassHigh"]) else: raise ValueError("模式不存在") def __write_config__(self): # 从界面写入配置 Config["InputConfig"]["Freq"] = self.ui.spinBox_input_freq_signal.value() Config["Path"]["Input_Signal"] = self.ui.plainTextEdit_file_path_input_signal.toPlainText() Config["Path"]["Input_Peak"] = self.ui.plainTextEdit_file_path_input_peak.toPlainText() Config["Path"]["Input_Approximately_Align"] = self.ui.plainTextEdit_file_path_input_approximately_align.toPlainText() Config["Path"]["Save"] = self.ui.plainTextEdit_file_path_save.toPlainText() if Config["Mode"] == "BCG": Config["Filter"]["BCGBandPassOrder"] = self.ui.spinBox_bandPassOrder.value() Config["Filter"]["BCGBandPassLow"] = self.ui.doubleSpinBox_bandPassLow.value() Config["Filter"]["BCGBandPassHigh"] = self.ui.doubleSpinBox_bandPassHigh.value() elif Config["Mode"] == "ECG": Config["Filter"]["ECGBandPassOrder"] = self.ui.spinBox_bandPassOrder.value() Config["Filter"]["ECGBandPassLow"] = self.ui.doubleSpinBox_bandPassLow.value() Config["Filter"]["ECGBandPassHigh"] = self.ui.doubleSpinBox_bandPassHigh.value() else: raise ValueError("模式不存在") # 保存配置到文件 self.config["InputConfig"]["Freq"] = self.ui.spinBox_input_freq_signal.value() with open(Params.LABEL_CHECK_CONFIG_FILE_PATH, "w") as f: dump(self.config, f) self.close() def __rollback_config__(self): self.__read_config__() def __update_ui__(self): if self.mode == "BCG": self.ui.plainTextEdit_file_path_input_signal.setPlainText( str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(Filename.BCG_FILTER + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) self.ui.plainTextEdit_file_path_input_peak.setPlainText( str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(Filename.JPEAK_REVISE + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) self.ui.plainTextEdit_file_path_save.setPlainText( str((Path(self.root_path) / Filename.PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(Filename.JPEAK_REVISE_CORRECTED + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) elif self.mode == "ECG": self.ui.plainTextEdit_file_path_input_signal.setPlainText( str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)) / Path(Filename.ECG_FILTER + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) self.ui.plainTextEdit_file_path_input_peak.setPlainText( str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)) / Path(Filename.RPEAK_FINAL + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) self.ui.plainTextEdit_file_path_save.setPlainText( str((Path(self.root_path) / Filename.PATH_PSG_TEXT / Path(str(self.sampID)) / Path(Filename.RPEAK_FINAL_CORRECTED + str(self.ui.spinBox_input_freq_signal.value()) + Params.ENDSWITH_TXT)))) else: raise ValueError("模式不存在") class MainWindow_label_check(QMainWindow): def __init__(self): super(MainWindow_label_check, self).__init__() self.ui = Ui_MainWindow_label_check() self.ui.setupUi(self) self.mode = None self.root_path = None self.sampID = None self.data = None self.setting = None # 初始化进度条 self.progressbar = None PublicFunc.add_progressbar(self) #初始化画框 self.fig = None self.canvas = None self.figToolbar = None self.gs = None self.ax0 = None self.ax1 = None self.is_left_button_pressed = None self.is_right_button_pressed = None self.point_peak_original = None self.point_peak_corrected = None self.annotation_tableWidget = None # 初始化自动播放定时器 self.autoplay_xlim_start = None self.autoplay_xlim_end = None self.timer_autoplay = QTimer() self.timer_autoplay.timeout.connect(self.autoplay_move_xlim) Config.update({ "AutoplayArgs": { "AutoplayMode": "pause", "MoveLength": int(self.ui.label_moveLength_preset_1.text()), "MaxRange": int(self.ui.label_maxRange_preset_1.text()), "MoveSpeed": int(self.ui.label_moveSpeed_preset_1.text()) } }) self.msgBox = QMessageBox() self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE) @overrides def show(self, mode, root_path, sampID): super().show() self.mode = mode self.root_path = root_path self.sampID = sampID self.setting = SettingWindow(mode, 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_Label_Multiple.setEnabled(False) for action in self.figToolbar._actions.values(): action.setEnabled(False) for action in self.figToolbar.actions(): if action.text() == "Subplots" or action.text() == "Customize": self.figToolbar.removeAction(action) self.figToolbar._actions['home'].triggered.connect(self.toggle_home) self.figToolbar.action_Label_Multiple.triggered.connect(self.toggle_changeLabel) self.ui.verticalLayout_canvas.addWidget(self.canvas) self.ui.verticalLayout_canvas.addWidget(self.figToolbar) self.gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1]) self.fig.subplots_adjust(top=0.98, bottom=0.05, right=0.98, left=0.1, hspace=0, wspace=0) self.ax0 = self.fig.add_subplot(self.gs[0]) self.ax0.grid(True) self.ax0.xaxis.set_major_formatter(Params.FORMATTER) self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE) self.ax1 = self.fig.add_subplot(self.gs[1], sharex=self.ax0, sharey=self.ax0) self.ax1.grid(True) self.ax1.xaxis.set_major_formatter(Params.FORMATTER) PublicFunc.__resetAllButton__(self, ButtonState) self.ui.label_mode.setText(self.mode) self.ui.doubleSpinBox_findpeaks_min_interval.setValue(Config["FindPeaks"]["MinInterval"]) self.ui.doubleSpinBox_findpeaks_min_height.setValue(Config["FindPeaks"]["MinHeight"]) self.ui.spinBox_moveLength.setValue(Config["CustomAutoplayArgs"]["MoveLength"]) self.ui.spinBox_maxRange.setValue(Config["CustomAutoplayArgs"]["MaxRange"]) self.ui.spinBox_moveSpeed.setValue(Config["CustomAutoplayArgs"]["MoveSpeed"]) self.ui.tableWidget_peak_original.setHorizontalHeaderLabels(['Original']) self.ui.tableWidget_peak_corrected.setHorizontalHeaderLabels(['Corrected']) self.ui.tableWidget_peak_original.setEditTriggers(QTableWidget.NoEditTriggers) self.ui.tableWidget_peak_corrected.setEditTriggers(QTableWidget.NoEditTriggers) self.ui.tableWidget_peak_original.horizontalHeader().setStretchLastSection(True) self.ui.tableWidget_peak_corrected.horizontalHeader().setStretchLastSection(True) self.ui.pushButton_input.clicked.connect(self.__slot_btn_input__) self.ui.pushButton_input_setting.clicked.connect(self.setting.show) self.ui.pushButton_prev_move.clicked.connect(self.__slot_btn_move__) self.ui.pushButton_pause.clicked.connect(self.__slot_btn_move__) self.ui.pushButton_next_move.clicked.connect(self.__slot_btn_move__) self.ui.radioButton_move_preset_1.toggled.connect(self.__change_autoplay_args__) self.ui.radioButton_move_preset_2.toggled.connect(self.__change_autoplay_args__) self.ui.radioButton_move_preset_3.toggled.connect(self.__change_autoplay_args__) self.ui.radioButton_move_custom.toggled.connect(self.__change_autoplay_args__) self.ui.tableWidget_peak_original.cellDoubleClicked.connect( self.__slot_tableWidget_on_cell_double_clicked__) self.ui.tableWidget_peak_corrected.cellDoubleClicked.connect( self.__slot_tableWidget_on_cell_double_clicked__) self.ui.doubleSpinBox_findpeaks_min_interval.editingFinished.connect(self.__update_config__) self.ui.doubleSpinBox_findpeaks_min_height.editingFinished.connect(self.__update_config__) self.ui.spinBox_moveLength.editingFinished.connect(self.__update_config__) self.ui.spinBox_maxRange.editingFinished.connect(self.__update_config__) self.ui.spinBox_moveSpeed.editingFinished.connect(self.__update_config__) @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() # 清空画框 del self.point_peak_original del self.point_peak_corrected del self.annotation_tableWidget if self.ax0 is not None: self.ax0.clear() if self.ax1 is not None: self.ax1.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): # 清空画框 if self.point_peak_original is not None: self.point_peak_original.remove() self.point_peak_original = None if self.point_peak_corrected is not None: self.point_peak_corrected.remove() self.point_peak_corrected = None if self.annotation_tableWidget is not None: self.annotation_tableWidget.remove() self.annotation_tableWidget = None self.reset_axes() sender = self.sender() if sender == self.ui.pushButton_input: self.ui.spinBox_data_length.setValue(len(self.data.processed_data)) self.ax0.plot(self.data.processed_data, label=Constants.LABEL_CHECK_PLOT_LABEL_SIGNAL, color=Constants.PLOT_COLOR_BLUE) self.ax1.plot(self.data.processed_data, label=Constants.LABEL_CHECK_PLOT_LABEL_SIGNAL, color=Constants.PLOT_COLOR_BLUE) self.ax0.axvline(x=self.data.approximately_align_pos, color=Constants.PLOT_COLOR_BLACK, linestyle="--", label="Start Line") self.ax1.axvline(x=self.data.approximately_align_pos, color=Constants.PLOT_COLOR_BLACK, linestyle="--", label="Start Line") self.ax0.legend(loc=Constants.PLOT_UPPER_RIGHT) self.ax1.legend(loc=Constants.PLOT_UPPER_RIGHT) self.canvas.draw() self.ax0.autoscale(False) self.ax1.autoscale(False) return Result().success(info=Constants.DRAW_FINISHED) else: self.canvas.draw() self.ax0.autoscale(False) self.ax1.autoscale(False) return Result().failure(info=Constants.DRAW_FAILURE) def __plot_peaks__(self): try: self.point_peak_original, = self.ax0.plot(self.data.original_peak, self.data.original_peak_y, 'ro', label=Constants.LABEL_CHECK_PLOT_LABEL_PEAK_ORIGINAL) self.point_peak_corrected, = self.ax1.plot(self.data.corrected_peak, self.data.corrected_peak_y, 'ro', label=Constants.LABEL_CHECK_PLOT_LABEL_PEAK_CORRECTED) self.ax1.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax)) self.ax0.legend(loc=Constants.PLOT_UPPER_RIGHT) self.ax1.legend(loc=Constants.PLOT_UPPER_RIGHT) except Exception as e: return Result().failure(info=Constants.DRAW_FAILURE + "\n" + format_exc()) self.canvas.draw() return Result().success(info=Constants.DRAW_FINISHED) def __redraw_peaks__(self): self.point_peak_corrected.remove() self.point_peak_corrected, = self.ax1.plot(self.data.corrected_peak, self.data.corrected_peak_y, 'ro', label=Constants.LABEL_CHECK_PLOT_LABEL_PEAK_CORRECTED) self.canvas.draw() def __update_tableWidget_and_info__(self): if self.data.original_peak is None or self.data.corrected_peak is None: return False, Constants.FAILURE_REASON["Data_Not_Exist"] try: self.ui.tableWidget_peak_original.setRowCount(len(self.data.original_peak)) self.ui.spinBox_peak_length_original.setValue(len(self.data.original_peak)) for row, value in enumerate(self.data.original_peak): item = QTableWidgetItem(str(value).strip()) self.ui.tableWidget_peak_original.setItem(row, 0, item) self.ui.tableWidget_peak_corrected.setRowCount(len(self.data.corrected_peak)) self.ui.spinBox_peak_length_corrected.setValue(len(self.data.corrected_peak)) for row, value in enumerate(self.data.corrected_peak): item = QTableWidgetItem(str(value).strip()) self.ui.tableWidget_peak_corrected.setItem(row, 0, item) return Result().success(info=Constants.UPDATE_FINISHED) except Exception as e: return Result().failure(info=Constants.UPDATE_FAILURE + "\n" + format_exc()) def __update_config__(self): Config["FindPeaks"]["MinInterval"] = self.ui.doubleSpinBox_findpeaks_min_interval.value() Config["FindPeaks"]["MinHeight"] = self.ui.doubleSpinBox_findpeaks_min_height.value() Config["CustomAutoplayArgs"]["MoveLength"] = self.ui.spinBox_moveLength.value() Config["CustomAutoplayArgs"]["MaxRange"] = self.ui.spinBox_maxRange.value() Config["CustomAutoplayArgs"]["MoveSpeed"] = self.ui.spinBox_moveSpeed.value() def __slot_btn_input__(self): PublicFunc.__disableAllButton__(self, ButtonState) # 清空画框 if self.point_peak_original is not None: self.point_peak_original.remove() self.point_peak_original = None if self.point_peak_corrected is not None: self.point_peak_corrected.remove() self.point_peak_corrected = None if self.annotation_tableWidget is not None: self.annotation_tableWidget.remove() self.annotation_tableWidget = None 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.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.SAVING_DATA, 25) result = self.data.save() 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.PREPROCESSING_DATA, 30) result = self.data.preprocess() 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.UPDATING_TABLEWIDGET_AND_INFO, 50) result = self.__update_tableWidget_and_info__() 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.DRAWING_DATA, 60) result = self.__plot__() 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.DRAWING_DATA, 80) result = self.__plot_peaks__() 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.canvas.mpl_connect("motion_notify_event", self.on_motion) self.figToolbar.action_Label_Multiple.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_prev_move"] = True ButtonState["Current"]["pushButton_next_move"] = True ButtonState["Current"]["pushButton_pause"] = True PublicFunc.finish_operation(self, ButtonState) def __slot_btn_move__(self): if self.data is None: return sender = self.sender() if sender == self.ui.pushButton_prev_move: Config["AutoplayArgs"]["AutoplayMode"] = "prev" self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - Config["AutoplayArgs"]["MaxRange"]) self.autoplay_xlim_end = int(self.ax0.get_xlim()[1]) if self.autoplay_xlim_end > len(self.data.processed_data): self.autoplay_xlim_start = int(len(self.data.processed_data) - Config["AutoplayArgs"]["MaxRange"]) self.autoplay_xlim_end = int(len(self.data.processed_data)) self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end) self.canvas.draw() self.timer_autoplay.start(Config["AutoplayArgs"]["MoveSpeed"]) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_PREV_MOVE, Constants.TIPS_TYPE_INFO) elif sender == self.ui.pushButton_next_move: Config["AutoplayArgs"]["AutoplayMode"] = "next" self.autoplay_xlim_start = int(self.ax0.get_xlim()[0]) self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + Config["AutoplayArgs"]["MaxRange"]) if self.autoplay_xlim_start < 0: self.autoplay_xlim_start = 0 self.autoplay_xlim_end = 0 + Config["AutoplayArgs"]["MaxRange"] self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end) self.canvas.draw() self.timer_autoplay.start(Config["AutoplayArgs"]["MoveSpeed"]) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_NEXT_MOVE, Constants.TIPS_TYPE_INFO) elif sender == self.ui.pushButton_pause: Config["AutoplayArgs"]["AutoplayMode"] = "pause" self.timer_autoplay.stop() PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_PAUSE, Constants.TIPS_TYPE_INFO) def __change_autoplay_args__(self): sender = self.sender() if sender == self.ui.radioButton_move_preset_1 and self.ui.radioButton_move_preset_1.isChecked(): Config["AutoplayArgs"]["MoveLength"] = int(self.ui.label_moveLength_preset_1.text()) Config["AutoplayArgs"]["MaxRange"] = int(self.ui.label_maxRange_preset_1.text()) Config["AutoplayArgs"]["MoveSpeed"] = int(self.ui.label_moveSpeed_preset_1.text()) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_SWITCH_PRESET_1, Constants.TIPS_TYPE_INFO) elif sender == self.ui.radioButton_move_preset_2 and self.ui.radioButton_move_preset_2.isChecked(): Config["AutoplayArgs"]["MoveLength"] = int(self.ui.label_moveLength_preset_2.text()) Config["AutoplayArgs"]["MaxRange"] = int(self.ui.label_maxRange_preset_2.text()) Config["AutoplayArgs"]["MoveSpeed"] = int(self.ui.label_moveSpeed_preset_2.text()) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_SWITCH_PRESET_2, Constants.TIPS_TYPE_INFO) elif sender == self.ui.radioButton_move_preset_3 and self.ui.radioButton_move_preset_3.isChecked(): Config["AutoplayArgs"]["MoveLength"] = int(self.ui.label_moveLength_preset_3.text()) Config["AutoplayArgs"]["MaxRange"] = int(self.ui.label_maxRange_preset_3.text()) Config["AutoplayArgs"]["MoveSpeed"] = int(self.ui.label_moveSpeed_preset_3.text()) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_SWITCH_PRESET_3, Constants.TIPS_TYPE_INFO) elif sender == self.ui.radioButton_move_custom and self.ui.radioButton_move_custom.isChecked(): Config["AutoplayArgs"]["MoveLength"] = int(self.ui.spinBox_moveLength.value()) Config["AutoplayArgs"]["MaxRange"] = int(self.ui.spinBox_maxRange.value()) Config["AutoplayArgs"]["MoveSpeed"] = int(self.ui.spinBox_moveSpeed.value()) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_SWITCH_CUSTOM, Constants.TIPS_TYPE_INFO) if Config["AutoplayArgs"]["AutoplayMode"] == "next": self.autoplay_xlim_start = int(self.ax0.get_xlim()[0]) self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + Config["AutoplayArgs"]["MaxRange"]) if self.autoplay_xlim_start < 0: self.autoplay_xlim_start = 0 self.autoplay_xlim_end = 0 + Config["AutoplayArgs"]["MaxRange"] self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end) self.timer_autoplay.start(Config["AutoplayArgs"]["MoveSpeed"]) elif Config["AutoplayArgs"]["AutoplayMode"] == "prev": self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - Config["AutoplayArgs"]["MaxRange"]) self.autoplay_xlim_end = int(self.ax0.get_xlim()[1]) if self.autoplay_xlim_end > len(self.data.processed_data): self.autoplay_xlim_start = int(self.data.processed_data) - Config["AutoplayArgs"]["MaxRange"] self.autoplay_xlim_end = int(self.data.processed_data) self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end) self.timer_autoplay.start(Config["AutoplayArgs"]["MoveSpeed"]) elif Config["AutoplayArgs"]["AutoplayMode"] == "pause": self.timer_autoplay.stop() def __slot_tableWidget_on_cell_double_clicked__(self, row, col): if Config["AutoplayArgs"]["AutoplayMode"] != "pause": self.ui.pushButton_pause.click() sender = self.sender() if sender == self.ui.tableWidget_peak_original: x = float(self.ui.tableWidget_peak_original.item(row, col).text()) elif sender == self.ui.tableWidget_peak_corrected: x = float(self.ui.tableWidget_peak_corrected.item(row, col).text()) else: raise ValueError("表格跳转参数不存在") self.ax0.set_xlim(x - 5000, x + 5000) self.annotation_tableWidget = self.ax0.annotate(f'x={int(x)}', xy=(int(x), self.ax0.get_ylim()[0]), xytext=(int(x), self.ax0.get_ylim()[0] + (self.ax0.get_ylim()[1] - self.ax0.get_ylim()[0]) * 0.1), arrowprops=dict(facecolor=Constants.PLOT_COLOR_BLACK, shrink=0.1)) self.canvas.draw() PublicFunc.text_output(self.ui, f"{Constants.LABEL_CHECK_JUMP_X_INDEX}{str(int(x))}", Constants.TIPS_TYPE_INFO) def reset_axes(self): if self.ax0 is not None: self.ax0.clear() self.ax0.grid(True) self.ax0.xaxis.set_major_formatter(Params.FORMATTER) self.ax0.tick_params(axis='x', colors=Constants.PLOT_COLOR_WHITE) if self.ax1 is not None: self.ax1.clear() self.ax1.grid(True) self.ax1.xaxis.set_major_formatter(Params.FORMATTER) def on_xlim_change(self, event_ax): try: if self.annotation_tableWidget is not None: self.annotation_tableWidget.remove() self.annotation_tableWidget = None except AttributeError: pass def autoplay_move_xlim(self): if Config["AutoplayArgs"]["AutoplayMode"] == "prev" and self.autoplay_xlim_start < 0: Config["AutoplayArgs"]["AutoplayMode"] = "pause" self.timer_autoplay.stop() elif Config["AutoplayArgs"]["AutoplayMode"] == "next" and self.autoplay_xlim_end > len(self.data.processed_data): Config["AutoplayArgs"]["AutoplayMode"] = "pause" self.timer_autoplay.stop() else: if Config["AutoplayArgs"]["AutoplayMode"] == "next": self.autoplay_xlim_start += Config["AutoplayArgs"]["MoveLength"] self.autoplay_xlim_end += Config["AutoplayArgs"]["MoveLength"] elif Config["AutoplayArgs"]["AutoplayMode"] == "prev": self.autoplay_xlim_start -= Config["AutoplayArgs"]["MoveLength"] self.autoplay_xlim_end -= Config["AutoplayArgs"]["MoveLength"] self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end) self.canvas.draw() def on_motion(self, event): if event.inaxes and self.ui.checkBox_show_reference_line.isChecked(): # Clear previous reference lines and temporary points for line in self.ax0.lines[1:]: if (line.get_label() == "vline" or line.get_label() == "hline"): line.remove() for line in self.ax1.lines[1:]: if (line.get_label() == "vline" or line.get_label() == "hline"): line.remove() # Draw vertical and horizontal reference lines self.ax0.axvline(event.xdata, color=Constants.PLOT_COLOR_GRAY, linestyle='--', label="vline") self.ax0.axhline(event.ydata, color=Constants.PLOT_COLOR_GRAY, linestyle='--', label="hline") self.ax1.axvline(event.xdata, color=Constants.PLOT_COLOR_GRAY, linestyle='--', label="vline") self.ax1.axhline(event.ydata, color=Constants.PLOT_COLOR_GRAY, linestyle='--', label="hline") self.canvas.draw() def toggle_home(self): if Config["AutoplayArgs"]["AutoplayMode"] != "pause": self.ui.pushButton_pause.click() self.ax0.autoscale(True) self.ax1.autoscale(True) self.ax1.relim() self.ax1.autoscale_view() self.canvas.draw() self.ax0.autoscale(False) self.ax1.autoscale(False) PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_RECOVER_SCALE, Constants.TIPS_TYPE_INFO) def toggle_changeLabel(self, state): if state: self.deactivate_figToolbar_buttons() self.figToolbar.action_Label_Multiple.setChecked(True) self.figToolbar.cid_mouse_press = self.canvas.mpl_connect( "button_press_event", self.on_click) self.figToolbar.cid_mouse_release = self.canvas.mpl_connect( "button_release_event", self.on_release) self.figToolbar.cid_mouse_hold = self.canvas.mpl_connect( "motion_notify_event", self.on_hold) else: if self.figToolbar.cid_mouse_press is not None: self.canvas.mpl_disconnect(self.figToolbar.cid_mouse_press) self.figToolbar.cid_mouse_press = None if self.figToolbar.cid_mouse_release is not None: self.canvas.mpl_disconnect(self.figToolbar.cid_mouse_release) self.figToolbar.cid_mouse_release = None if self.figToolbar.cid_mouse_hold: self.canvas.mpl_disconnect(self.figToolbar.cid_mouse_hold) self.figToolbar.cid_mouse_hold = None def deactivate_figToolbar_buttons(self): for action in self.figToolbar._actions.values(): if action.isChecked() == True: if action == self.figToolbar._actions['pan']: self.figToolbar.pan() if action == self.figToolbar._actions['zoom']: self.figToolbar.zoom() def on_click(self, event): if self.figToolbar.action_Label_Multiple.isChecked(): if event.button == 1 or event.button == 3: # 左键或右键 if event.button == 1: self.is_left_button_pressed = True elif event.button == 3: self.is_right_button_pressed = True self.figToolbar.rect_start_x = event.xdata # 如果矩形patch已存在,先移除 if self.figToolbar.rect_patch_ax0 is not None and self.figToolbar.rect_patch_ax1 is not None: self.figToolbar.rect_patch_ax0.remove() self.figToolbar.rect_patch_ax0 = None self.figToolbar.rect_patch_ax1.remove() self.figToolbar.rect_patch_ax1 = None self.canvas.draw() def on_release(self, event): if self.figToolbar.action_Label_Multiple.isChecked(): if self.figToolbar.rect_start_x is not None: self.figToolbar.rect_end_x = event.xdata if self.figToolbar.rect_start_x is not None and self.figToolbar.rect_end_x is not None: if self.figToolbar.rect_start_x < self.figToolbar.rect_end_x: rect_left = self.figToolbar.rect_start_x rect_right = self.figToolbar.rect_end_x elif self.figToolbar.rect_start_x > self.figToolbar.rect_end_x: rect_left = self.figToolbar.rect_end_x rect_right = self.figToolbar.rect_start_x else: rect_left = self.figToolbar.rect_start_x rect_right = self.figToolbar.rect_start_x else: rect_left = self.figToolbar.rect_start_x rect_right = self.figToolbar.rect_start_x if event.button == 1 and self.is_left_button_pressed: self.is_left_button_pressed = False if rect_left < 0: rect_left = 0 elif rect_left >= len(self.data.processed_data): rect_left = 0 rect_right = 0 if rect_right >= len(self.data.processed_data): rect_right = len(self.data.processed_data) - 1 elif rect_right < 0: rect_left = 0 rect_right = 0 selected_area_for_add_points = self.data.processed_data[int(rect_left):int(rect_right)] peaks_idx, _ = find_peaks(selected_area_for_add_points, height=Config["FindPeaks"]["MinHeight"], distance=Config["FindPeaks"]["MinInterval"]) peaks_idx = peaks_idx + int(rect_left) peaks_idx = setdiff1d(peaks_idx, self.data.corrected_peak) if len(peaks_idx) != 0: PublicFunc.text_output(self.ui, f"{Constants.LABEL_CHECK_ADD_POINTS_SUCCESSFULLY}{peaks_idx}", Constants.TIPS_TYPE_INFO) else: PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_NO_POINT_IN_THE_INTERVAL, Constants.TIPS_TYPE_INFO) self.data.corrected_peak = append(self.data.corrected_peak, peaks_idx) self.data.corrected_peak_y = append(self.data.corrected_peak_y, self.data.processed_data[peaks_idx]) self.__redraw_peaks__() elif event.button == 3 and self.is_right_button_pressed: self.is_right_button_pressed = False left_label2_to_delete = self.data.corrected_peak - rect_left right_label2_to_delete = self.data.corrected_peak - rect_right left_label2_to_delete_idx = len(left_label2_to_delete[left_label2_to_delete < 0]) right_label2_to_delete_idx = len(right_label2_to_delete[right_label2_to_delete < 0]) if left_label2_to_delete_idx != right_label2_to_delete_idx: PublicFunc.text_output(self.ui, f"{Constants.LABEL_CHECK_REMOVE_POINTS_SUCCESSFULLY}{self.data.corrected_peak[left_label2_to_delete_idx:right_label2_to_delete_idx]}", Constants.TIPS_TYPE_INFO) else: PublicFunc.text_output(self.ui, Constants.LABEL_CHECK_NO_POINT_IN_THE_INTERVAL, Constants.TIPS_TYPE_INFO) self.data.corrected_peak = delete(self.data.corrected_peak, arange(left_label2_to_delete_idx, right_label2_to_delete_idx)) self.data.corrected_peak_y = delete(self.data.corrected_peak_y, arange(left_label2_to_delete_idx, right_label2_to_delete_idx)) self.__redraw_peaks__() self.figToolbar.rect_start_x = None self.figToolbar.rect_end_x = None self.data.corrected_peak.sort() self.data.corrected_peak_y = [self.data.processed_data[x] for x in self.data.corrected_peak] self.__update_tableWidget_and_info__() result = self.data.save() if not result.status: PublicFunc.text_output(self.ui, 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, result.info, Constants.TIPS_TYPE_INFO) # 移除矩形patch if self.figToolbar.rect_patch_ax0 is not None and self.figToolbar.rect_patch_ax1 is not None: self.figToolbar.rect_patch_ax0.remove() self.figToolbar.rect_patch_ax0 = None self.figToolbar.rect_patch_ax1.remove() self.figToolbar.rect_patch_ax1 = None self.canvas.draw() def on_hold(self, event): if self.figToolbar.rect_start_x is not None and event.xdata is not None: self.figToolbar.rect_end_x = event.xdata # 如果矩形patch不存在,则创建一个新的 if self.figToolbar.rect_patch_ax0 is None: if self.is_left_button_pressed: self.figToolbar.rect_patch_ax0 = patches.Rectangle((0, 0), 1, 1, fill=True, alpha=Params.LABEL_CHECK_LABEL_TRANSPARENCY, color=Constants.PLOT_COLOR_PINK) elif self.is_right_button_pressed: self.figToolbar.rect_patch_ax0 = patches.Rectangle((0, 0), 1, 1, fill=True, alpha=Params.LABEL_CHECK_LABEL_TRANSPARENCY, color=Constants.PLOT_COLOR_RED) self.ax0.add_patch(self.figToolbar.rect_patch_ax0) # 如果矩形patch不存在,则创建一个新的 if self.figToolbar.rect_patch_ax1 is None: if self.is_left_button_pressed: self.figToolbar.rect_patch_ax1 = patches.Rectangle((0, 0), 1, 1, fill=True, alpha=Params.LABEL_CHECK_LABEL_TRANSPARENCY, color=Constants.PLOT_COLOR_PINK) elif self.is_right_button_pressed: self.figToolbar.rect_patch_ax1 = patches.Rectangle((0, 0), 1, 1, fill=True, alpha=Params.LABEL_CHECK_LABEL_TRANSPARENCY, color=Constants.PLOT_COLOR_RED) self.ax1.add_patch(self.figToolbar.rect_patch_ax1) # 更新矩形patch的位置和大小 x_start = self.figToolbar.rect_start_x x_end = self.figToolbar.rect_end_x rect_down = min(self.ax0.get_ylim()[0], self.ax1.get_ylim()[0]) - 1000 rect_up = max(self.ax0.get_ylim()[1], self.ax1.get_ylim()[1]) + 1000 self.figToolbar.rect_patch_ax0.set_xy((min(x_start, x_end), rect_down)) self.figToolbar.rect_patch_ax0.set_width(abs(x_end - x_start)) self.figToolbar.rect_patch_ax0.set_height(rect_up - rect_down) self.figToolbar.rect_patch_ax1.set_xy((min(x_start, x_end), rect_down)) self.figToolbar.rect_patch_ax1.set_width(abs(x_end - x_start)) self.figToolbar.rect_patch_ax1.set_height(rect_up - rect_down) self.canvas.draw() class Data: def __init__(self): self.raw_data = None self.processed_data = None self.original_peak = None self.original_peak_y = None self.corrected_peak = None self.corrected_peak_y = None self.approximately_align_pos = None def open_file(self): if Config["Mode"] == "BCG": signal = Filename.BCG_FILTER peak = Filename.JPEAK_REVISE save = Filename.JPEAK_REVISE_CORRECTED elif Config["Mode"] == "ECG": signal = Filename.ECG_FILTER peak = Filename.RPEAK_FINAL save = Filename.RPEAK_FINAL_CORRECTED else: raise ValueError("模式不存在") if Path(Config["Path"]["Input_Signal"]).is_file(): Config["Path"]["Input_Signal"] = str(Path(Config["Path"]["Input_Signal"]).parent) if Path(Config["Path"]["Input_Peak"]).is_file(): Config["Path"]["Input_Peak"] = str(Path(Config["Path"]["Input_Peak"]).parent) if Path(Config["Path"]["Input_Approximately_Align"]).is_file(): Config["Path"]["Input_Approximately_Align"] = str(Path(Config["Path"]["Input_Approximately_Align"]).parent) if Path(Config["Path"]["Save"]).is_file(): Config["Path"]["Save"] = str(Path(Config["Path"]["Save"]).parent) result = PublicFunc.examine_file(Config["Path"]["Input_Signal"], signal, Params.ENDSWITH_TXT) if result.status: Config["Path"]["Input_Signal"] = result.data["path"] Config["InputConfig"]["Freq"] = result.data["freq"] else: return result Config["Path"]["Input_Peak"] = str( Path(Config["Path"]["Input_Peak"]) / Path(peak + str(Config["InputConfig"]["Freq"]) + Params.ENDSWITH_TXT)) Config["Path"]["Input_Approximately_Align"] = str( Path(Config["Path"]["Input_Approximately_Align"]) / Path( Filename.APPROXIMATELY_ALIGN_INFO + Params.ENDSWITH_CSV)) Config["Path"]["Save"] = str( Path(Config["Path"]["Save"]) / Path(save + str(Config["InputConfig"]["Freq"]) + Params.ENDSWITH_TXT)) if not Path(Config["Path"]["Input_Peak"]).exists(): return Result().failure(info=Constants.INPUT_FAILURE + "\n" + peak + ":" + Config["Path"]["Input_Peak"] + Constants.FAILURE_REASON["Path_Not_Exist"]) if not Path(Config["Path"]["Input_Approximately_Align"]).exists(): return Result().failure(info=Constants.INPUT_FAILURE + "\n" + Filename.APPROXIMATELY_ALIGN_INFO + ":" + Config["Path"]["Input_Approximately_Align"] + Constants.FAILURE_REASON["Path_Not_Exist"]) try: self.raw_data = read_csv(Config["Path"]["Input_Signal"], encoding=Params.UTF8_ENCODING, header=None).to_numpy().reshape(-1) self.original_peak = read_csv(Config["Path"]["Input_Peak"], encoding=Params.UTF8_ENCODING, header=None).to_numpy().reshape(-1) except Exception as e: return Result().failure(info=Constants.INPUT_FAILURE + Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc()) try: df = read_csv(Config["Path"]["Input_Approximately_Align"]) pos = df["pos"].values[-1] ApplyFrequency = df["ApplyFrequency"].values[-1] self.approximately_align_pos = int(pos * (Config["InputConfig"]["Freq"] / ApplyFrequency)) if Config["Mode"] == "BCG": if self.approximately_align_pos > 0: self.approximately_align_pos = 0 else: self.approximately_align_pos = - self.approximately_align_pos elif Config["Mode"] == "ECG": if self.approximately_align_pos < 0: self.approximately_align_pos = 0 else: raise ValueError("模式不存在") except Exception: self.approximately_align_pos = 0 return Result().success(info=Constants.INPUT_FINISHED) def get_archive(self): if not Path(Config["Path"]["Save"]).exists(): self.corrected_peak = self.original_peak return Result().success(info=Constants.ARCHIVE_NOT_EXIST) else: self.corrected_peak = read_csv(Config["Path"]["Save"], encoding=Params.UTF8_ENCODING, header=None).to_numpy().reshape(-1) return Result().success(info=Constants.ARCHIVE_EXIST) def preprocess(self): if self.raw_data is None: return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"]) try: if Config["Mode"] == "BCG": if Config["Filter"]["BCGBandPassOrder"] == 0: self.processed_data = self.raw_data else: if ((Config["Filter"]["BCGBandPassLow"] >= Config["Filter"]["BCGBandPassHigh"]) or (Config["Filter"]["BCGBandPassLow"] <= 0) or (Config["Filter"]["BCGBandPassHigh"] <= 0)): return Result().failure( info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Filter_Args_Not_Correct"]) self.processed_data = data_preprocess_for_label_check(self.raw_data, Config["Filter"]["BCGBandPassOrder"], Config["Filter"]["BCGBandPassLow"], Config["Filter"]["BCGBandPassHigh"], Config["InputConfig"]["Freq"]) elif Config["Mode"] == "ECG": if Config["Filter"]["ECGBandPassOrder"] == 0: self.processed_data = self.raw_data else: if ((Config["Filter"]["ECGBandPassLow"] >= Config["Filter"]["ECGBandPassHigh"]) or (Config["Filter"]["ECGBandPassLow"] <= 0) or (Config["Filter"]["ECGBandPassHigh"] <= 0)): return Result().failure( info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Filter_Args_Not_Correct"]) self.processed_data = data_preprocess_for_label_check(self.raw_data, Config["Filter"]["ECGBandPassOrder"], Config["Filter"]["ECGBandPassLow"], Config["Filter"]["ECGBandPassHigh"], Config["InputConfig"]["Freq"]) else: raise ValueError("模式不存在") self.original_peak_y = [self.processed_data[x] for x in self.original_peak] self.corrected_peak_y = [self.processed_data[x] for x in self.corrected_peak] except Exception as e: return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Preprocess_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.PREPROCESS_FINISHED) def save(self): if self.corrected_peak is None: return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"]) try: DataFrame(self.corrected_peak).to_csv(Config["Path"]["Save"], index=False, header=False) except Exception as e: return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.SAVE_FINISHED) class CustomNavigationToolbar(NavigationToolbar2QT): def __init__(self, canvas, parent): super().__init__(canvas, parent) # 初始化画框工具栏 self.action_Label_Multiple = QAction(Constants.LABEL_CHECK_ACTION_LABEL_MULTIPLE_NAME, self) self.action_Label_Multiple.setFont(QFont(Params.FONT, 14)) self.action_Label_Multiple.setCheckable(True) self.action_Label_Multiple.setShortcut(QCoreApplication.translate( "MainWindow", Params.LABEL_CHECK_ACTION_LABEL_MULTIPLE_SHORTCUT_KEY)) self.insertAction(self._actions['pan'], self.action_Label_Multiple) self._actions['pan'].setShortcut(QCoreApplication.translate( "MainWindow", Params.ACTION_PAN_SHORTCUT_KEY)) self._actions['zoom'].setShortcut(QCoreApplication.translate( "MainWindow", Params.ACTION_ZOOM_SHORTCUT_KEY)) # 用于存储事件连接ID self.cid_mouse_press = None self.cid_mouse_release = None self.cid_mouse_hold = None # 初始化矩形选择区域 self.rect_start_x = None self.rect_end_x = None self.rect_patch_ax0 = None # 用于绘制矩形的patch self.rect_patch_ax1 = None # 用于绘制矩形的patch def home(self, *args): pass def zoom(self, *args): super().zoom(*args) self.deactivate_figToorbar_changeLabel_mode() def pan(self, *args): super().pan(*args) self.deactivate_figToorbar_changeLabel_mode() def deactivate_figToorbar_changeLabel_mode(self): if self.action_Label_Multiple.isChecked(): self.action_Label_Multiple.setChecked(False) if self.cid_mouse_press is not None: self.canvas.mpl_disconnect(self.cid_mouse_press) self.cid_mouse_press = None if self.cid_mouse_release is not None: self.canvas.mpl_disconnect(self.cid_mouse_release) self.cid_mouse_release = None if self.cid_mouse_hold is not None: self.canvas.mpl_disconnect(self.cid_mouse_hold) self.cid_mouse_hold = None