from gc import collect from pathlib import Path 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 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.PublicFunc import PublicFunc from func.utils.Constants import Constants, ConfigParams 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, "pushButton_save": False }, "Current": { "pushButton_input_setting": True, "pushButton_input": True, "pushButton_prev_move": False, "pushButton_pause": False, "pushButton_next_move": False, "pushButton_save": 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(ConfigParams.LABEL_CHECK_CONFIG_FILE_PATH).exists(): with open(ConfigParams.LABEL_CHECK_CONFIG_FILE_PATH, "w") as f: dump(ConfigParams.LABEL_CHECK_CONFIG_NEW_CONTENT, f) with open(ConfigParams.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) / ConfigParams.PUBLIC_PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_BCG_FILENAME + str(Config["InputConfig"]["Freq"]) + ConfigParams.ENDSWITH_TXT))), "Input_Peak": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_JPEAK_FILENAME + ConfigParams.ENDSWITH_TXT))), "Input_Approximately_Align": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_APPROXIMATELY_ALIGNINFO_FILENAME + ConfigParams.ENDSWITH_CSV))), "Save": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_SAVE_JPEAK_FILENAME + ConfigParams.ENDSWITH_TXT))) }, "Mode": self.mode }) elif self.mode == "ECG": Config.update({ "Path": { "Input_Signal": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_ECG_FILENAME + str(Config["InputConfig"]["Freq"]) + ConfigParams.ENDSWITH_TXT))), "Input_Peak": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_RPEAK_FILENAME + ConfigParams.ENDSWITH_TXT))), "Input_Approximately_Align": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_APPROXIMATELY_ALIGNINFO_FILENAME + ConfigParams.ENDSWITH_CSV))), "Save": str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_SAVE_RPEAK_FILENAME + ConfigParams.ENDSWITH_TXT))) }, "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(ConfigParams.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) / ConfigParams.PUBLIC_PATH_ORGBCG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_BCG_FILENAME + str(self.ui.spinBox_input_freq_signal.value()) + ConfigParams.ENDSWITH_TXT)))) elif self.mode == "ECG": self.ui.plainTextEdit_file_path_input_signal.setPlainText( str((Path(self.root_path) / ConfigParams.PUBLIC_PATH_PSG_TEXT / Path(str(self.sampID)) / Path(ConfigParams.LABEL_CHECK_INPUT_ECG_FILENAME + str(self.ui.spinBox_input_freq_signal.value()) + ConfigParams.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(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, sharey=self.ax0) self.ax1.grid(True) self.ax1.xaxis.set_major_formatter(ConfigParams.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_save.clicked.connect(self.__slot_btn_save__) 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 self.ax0.clear() 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.DRAWING_FINISHED) else: self.canvas.draw() self.ax0.autoscale(False) self.ax1.autoscale(False) return Result().failure(info=Constants.DRAWING_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: return Result().failure(info=Constants.DRAWING_FAILURE) self.canvas.draw() return Result().success(info=Constants.DRAWING_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.LABEL_CHECK_FAILURE_REASON["Peak_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.UPDATING_FINISHED) except Exception: return Result().failure(info=Constants.UPDATING_FAILURE) 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, 6, Constants.INPUTTING_DATA, 0) result = self.data.open_file() if not result.status: PublicFunc.text_output(self.ui, "(1/6)" + 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/6)" + result.info, Constants.TIPS_TYPE_INFO) # 获取存档 PublicFunc.progressbar_update(self, 2, 6, Constants.LABEL_CHECK_LOADING_ARCHIVE, 20) result = self.data.get_archive() if not result.status: PublicFunc.text_output(self.ui, "(2/6)" + 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/6)" + result.info, Constants.TIPS_TYPE_INFO) # 数据预处理 PublicFunc.progressbar_update(self, 3, 6, Constants.LABEL_CHECK_PROCESSING_DATA, 30) result = self.data.preprocess() if not result.status: PublicFunc.text_output(self.ui, "(3/6)" + 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/6)" + result.info, Constants.TIPS_TYPE_INFO) # 更新表格 PublicFunc.progressbar_update(self, 4, 6, Constants.UPDATING_TABLEWIDGET_AND_INFO, 50) result = self.__update_tableWidget_and_info__() if not result.status: PublicFunc.text_output(self.ui, "(4/6)" + 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/6)" + result.info, Constants.TIPS_TYPE_INFO) # 绘图 PublicFunc.progressbar_update(self, 5, 6, Constants.DRAWING_DATA, 60) result = self.__plot__() if not result.status: PublicFunc.text_output(self.ui, "(5/6)" + 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/6)" + result.info, Constants.TIPS_TYPE_INFO) # 绘点 PublicFunc.progressbar_update(self, 6, 6, Constants.DRAWING_DATA, 80) result = self.__plot_peaks__() if not result.status: PublicFunc.text_output(self.ui, "(6/6)" + 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/6)" + 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 ButtonState["Current"]["pushButton_save"] = True PublicFunc.finish_operation(self, ButtonState) def __slot_btn_save__(self): reply = QMessageBox.question(self, Constants.QUESTION_TITLE, Constants.QUESTION_CONTENT + Config["Path"]["Save"], QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: PublicFunc.__disableAllButton__(self, ButtonState) # 保存 PublicFunc.progressbar_update(self, 1, 1, Constants.SAVING_DATA, 0) total_rows = len(DataFrame(self.data.corrected_peak.reshape(-1))) chunk_size = ConfigParams.LABEL_CHECK_SAVE_CHUNK_SIZE with open(Config["Path"]["Save"], 'w') as f: for start in range(0, total_rows, chunk_size): end = min(start + chunk_size, total_rows) chunk = DataFrame(self.data.corrected_peak.reshape(-1)).iloc[start:end] result = self.data.save(chunk) progress = int((end / total_rows) * 100) self.progressbar.setValue(progress) QApplication.processEvents() if not result.status: PublicFunc.text_output(self.ui, "(1/1)" + 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/1)" + result.info, Constants.TIPS_TYPE_INFO) PublicFunc.msgbox_output(self, result.info, Constants.TIPS_TYPE_INFO) 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): self.ax0.clear() self.ax1.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) 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) 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__() DataFrame(self.data.corrected_peak).to_csv(Config["Path"]["Save"], index=False, header=False) # 移除矩形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=ConfigParams.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=ConfigParams.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=ConfigParams.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=ConfigParams.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 (not Path(Config["Path"]["Input_Signal"]).exists()) or (not Path(Config["Path"]["Input_Peak"]).exists()): return Result().failure(info=Constants.INPUT_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Data_Path_Not_Exist"]) try: self.raw_data = read_csv(Config["Path"]["Input_Signal"], encoding=ConfigParams.UTF8_ENCODING, header=None).to_numpy().reshape(-1) self.original_peak = read_csv(Config["Path"]["Input_Peak"], encoding=ConfigParams.UTF8_ENCODING, header=None).to_numpy().reshape(-1) except Exception: return Result().failure(info=Constants.INPUT_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Read_Data_Exception"]) 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.LABEL_CHECK_ARCHIVE_NOT_EXIST) else: self.corrected_peak = read_csv(Config["Path"]["Save"], encoding=ConfigParams.UTF8_ENCODING, header=None).to_numpy().reshape(-1) return Result().success(info=Constants.LABEL_CHECK_ARCHIVE_EXIST) def preprocess(self): if self.raw_data is None: return Result().failure(info=Constants.LABEL_CHECK_PROCESS_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Raw_Data_Not_Exist"]) try: if Config["Mode"] == "BCG": 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": 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: return Result().failure(info=Constants.LABEL_CHECK_PROCESS_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Filter_Exception"]) return Result().success(info=Constants.LABEL_CHECK_PROCESS_FINISHED) def save(self, chunk): if self.corrected_peak is None: return Result().failure(info=Constants.SAVING_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Peak_Not_Exist"]) try: chunk.to_csv(Config["Path"]["Save"], mode='a', index=False, header=False) except Exception: return Result().failure(info=Constants.SAVING_FAILURE + Constants.LABEL_CHECK_FAILURE_REASON["Save_Exception"]) return Result().success(info=Constants.SAVING_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(ConfigParams.FONT, 14)) self.action_Label_Multiple.setCheckable(True) self.action_Label_Multiple.setShortcut(QCoreApplication.translate( "MainWindow", ConfigParams.LABEL_CHECK_ACTION_LABEL_MULTIPLE_SHORTCUT_KEY)) self.insertAction(self._actions['pan'], self.action_Label_Multiple) self._actions['pan'].setShortcut(QCoreApplication.translate( "MainWindow", ConfigParams.ACTION_PAN_SHORTCUT_KEY)) self._actions['zoom'].setShortcut(QCoreApplication.translate( "MainWindow", ConfigParams.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