from gc import collect
from pathlib import Path
from traceback import format_exc

import matplotlib.pyplot as plt
from PySide6.QtCore import QCoreApplication
from PySide6.QtGui import QAction, QFont
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication, QTableWidget, QTableWidgetItem
from matplotlib import gridspec, patches
from matplotlib.backends.backend_qt import NavigationToolbar2QT
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from numpy import array, full, int64, append, zeros, where, arange, float64, nan, place, delete, setdiff1d
from overrides import overrides
from pandas import read_csv, DataFrame
from pandas.errors import EmptyDataError
from scipy.signal import find_peaks, resample
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.utils.Result import Result
from func.utils.resp_quality_label import pre_process, get_slice, evaluate_quality
from func.utils.resp_quality_label_filter import get_bandpass_bcgsignal

from ui.MainWindow.MainWindow_resp_quality_label import Ui_MainWindow_resp_quality_label
from ui.setting.resp_quality_label_input_setting import Ui_MainWindow_resp_quality_label_input_setting


Config = {

}

ButtonState = {
    "Default": {
        "pushButton_input_setting": True,
        "pushButton_input_and_calculate_peaks": True,
        "pushButton_calculate_peaks": False,
        "pushButton_calculate_peaks_save": False,
        "pushButton_input_and_label": True,
        "pushButton_autoqualitylabel_recalculate": False,
        "pushButton_refilter_orgBcg": False,
        "pushButton_valid": False,
        "pushButton_invalid": False,
        "pushButton_reset": False,
        "pushButton_prev": False,
        "pushButton_next": False,
        "pushButton_save": False
    },
    "Current": {
        "pushButton_input_setting": True,
        "pushButton_input_and_calculate_peaks": True,
        "pushButton_calculate_peaks": False,
        "pushButton_calculate_peaks_save": False,
        "pushButton_input_and_label": True,
        "pushButton_autoqualitylabel_recalculate": False,
        "pushButton_refilter_orgBcg": False,
        "pushButton_valid": False,
        "pushButton_invalid": False,
        "pushButton_reset": False,
        "pushButton_prev": False,
        "pushButton_next": False,
        "pushButton_save": False
    }
}


class SettingWindow(QMainWindow):

    def __init__(self, root_path, sampID):
        super(SettingWindow, self).__init__()
        self.ui = Ui_MainWindow_resp_quality_label_input_setting()
        self.ui.setupUi(self)

        self.root_path = root_path
        self.sampID = sampID

        self.msgBox = QMessageBox()
        self.msgBox.setWindowTitle(Constants.MAINWINDOW_MSGBOX_TITLE)

        self.config = None
        self.__read_config__()
        self.__examine_freq__()

        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.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.RESP_QUALITY_LABEL_CONFIG_FILE_PATH).exists():
            with open(Params.RESP_QUALITY_LABEL_CONFIG_FILE_PATH, "w") as f:
                dump(Params.RESP_QUALITY_LABEL_CONFIG_NEW_CONTENT, f)

        with open(Params.RESP_QUALITY_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) / Filename.PATH_ORGBCG_ALIGNED /
                                     Path(str(self.sampID)))),
                "Input_Tho": str((Path(self.root_path) / Filename.PATH_PSG_ALIGNED /
                                  Path(str(self.sampID)))),
                "Input_Artifact": str((Path(self.root_path) / Filename.PATH_LABEL /
                                       Path(str(self.sampID)))),
                "Save_Resp_quality_label": str((Path(self.root_path) / Filename.PATH_LABEL /
                                                Path(str(self.sampID)))),
                "Save_Tho_peak": str((Path(self.root_path) / Filename.PATH_LABEL /
                                      Path(str(self.sampID))))
            },
            "CurrentPartNum": 1,
            "DataPartNum": 0,
            "CurrentOrgBCGIndex": 0,
            "CurrentThoIndex": 0
        })

        # 数据回显
        self.ui.spinBox_input_freq_signal_OrgBCG.setValue(Config["InputConfig"]["OrgBCGFreq"])
        self.ui.spinBox_input_freq_signal_Tho.setValue(Config["InputConfig"]["ThoFreq"])
        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_artifact.setPlainText(Config["Path"]["Input_Artifact"])
        self.ui.plainTextEdit_file_path_save_Resp_quality_label.setPlainText(Config["Path"]["Save_Resp_quality_label"])
        self.ui.plainTextEdit_file_path_save_Tho_peak.setPlainText(Config["Path"]["Save_Tho_peak"])

    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["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_Artifact"] = self.ui.plainTextEdit_file_path_input_artifact.toPlainText()
        Config["Path"]["Save_Resp_quality_label"] = self.ui.plainTextEdit_file_path_save_Resp_quality_label.toPlainText()
        Config["Path"]["Save_Tho_peak"] = self.ui.plainTextEdit_file_path_save_Tho_peak.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()

        with open(Params.RESP_QUALITY_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) /
                 Filename.PATH_ORGBCG_ALIGNED /
                 Path(str(self.sampID)) /
                 Path(Filename.ORGBCG_SYNC +
                      str(self.ui.spinBox_input_freq_signal_OrgBCG.value()) +
                      Params.ENDSWITH_TXT))))
        self.ui.plainTextEdit_file_path_input_signal_Tho.setPlainText(
            str((Path(self.root_path) /
                 Filename.PATH_PSG_ALIGNED /
                 Path(str(self.sampID)) /
                 Path(Filename.THO_SYNC +
                      str(self.ui.spinBox_input_freq_signal_Tho.value()) +
                      Params.ENDSWITH_TXT))))

    def __examine_freq__(self):
        if Path(Config["Path"]["Input_OrgBCG"]).is_file():
            Config["Path"]["Input_OrgBCG"] = str(Path(Config["Path"]["Input_OrgBCG"]).parent)
        if Path(Config["Path"]["Input_Tho"]).is_file():
            Config["Path"]["Input_Tho"] = str(Path(Config["Path"]["Input_Tho"]).parent)

        result = PublicFunc.examine_file(Config["Path"]["Input_OrgBCG"], Filename.ORGBCG_SYNC, Params.ENDSWITH_TXT)
        if result.status:
            Config["InputConfig"]["OrgBCGFreq"] = result.data["freq"]
        else:
            PublicFunc.msgbox_output(self, Filename.ORGBCG_SYNC + Constants.FAILURE_REASON["Get_Freq_Not_Correct"], Constants.MSGBOX_TYPE_ERROR)
        result = PublicFunc.examine_file(Config["Path"]["Input_Tho"], Filename.THO_SYNC, Params.ENDSWITH_TXT)
        if result.status:
            Config["InputConfig"]["ThoFreq"] = result.data["freq"]
        else:
            PublicFunc.msgbox_output(self, Filename.THO_SYNC + Constants.FAILURE_REASON["Get_Freq_Not_Correct"], Constants.MSGBOX_TYPE_ERROR)

        # 数据回显
        self.ui.spinBox_input_freq_signal_OrgBCG.setValue(Config["InputConfig"]["OrgBCGFreq"])
        self.ui.spinBox_input_freq_signal_Tho.setValue(Config["InputConfig"]["ThoFreq"])


class MainWindow_resp_quality_label(QMainWindow):

    def __init__(self):
        super(MainWindow_resp_quality_label, self).__init__()
        self.ui = Ui_MainWindow_resp_quality_label()
        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.fig_spectrum = None
        self.canvas_spectrum = None
        self.figToolbar_spectrum = None
        self.gs_spectrum = None
        self.ax0_spectrum = None
        self.ax1_spectrum = None
        self.is_left_button_pressed = None
        self.is_right_button_pressed = None

        self.tho_peak_point = None

        self.cid1 = None
        self.cid2 = None

        self.ui.textBrowser_info.setStyleSheet("QTextBrowser { background-color: rgb(255, 255, 200); }")
        PublicFunc.__styleAllButton__(self, ButtonState)

        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_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_Multiple_mode)
        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.1, wspace=0)
        self.ax0 = self.fig.add_subplot(self.gs[0])
        self.ax0.grid(True)
        self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
        self.ax1 = self.fig.add_subplot(self.gs[1])
        self.ax1.grid(True)
        self.ax1.xaxis.set_major_formatter(Params.FORMATTER)

        # 初始化频谱画框
        self.fig_spectrum = plt.figure(figsize=(12, 9), dpi=100)
        self.canvas_spectrum = FigureCanvasQTAgg(self.fig_spectrum)
        self.figToolbar_spectrum = NavigationToolbar2QT(self.canvas_spectrum)
        self.figToolbar_spectrum.setStyleSheet(Constants.RESP_QUALITY_LABEL_FIGTOOLBAR_SPECTRUM_STYLESHEET)
        for action in self.figToolbar_spectrum._actions.values():
            action.setEnabled(False)
        for action in self.figToolbar_spectrum.actions():
            if action.text() == "Subplots" or action.text() == "Customize":
                self.figToolbar_spectrum.removeAction(action)
        self.ui.verticalLayout_spectrum_display.addWidget(self.canvas_spectrum)
        self.ui.verticalLayout_spectrum_display.addWidget(self.figToolbar_spectrum)
        self.gs_spectrum = gridspec.GridSpec(2, 1, height_ratios=[1, 1])
        self.fig_spectrum.subplots_adjust(top=0.95, bottom=0.05, right=0.98, left=0.1, hspace=0.2, wspace=0)
        self.ax0_spectrum = self.fig_spectrum.add_subplot(self.gs_spectrum[0])
        self.ax0_spectrum.grid(True)
        self.ax0_spectrum.set_title(Constants.RESP_QUALITY_LABEL_SPECTRUM_BDR_TITLE, fontsize=8)
        self.ax1_spectrum = self.fig_spectrum.add_subplot(self.gs_spectrum[1])
        self.ax1_spectrum.grid(True)
        self.ax1_spectrum.set_title(Constants.RESP_QUALITY_LABEL_SPECTRUM_THO_TITLE, fontsize=8)

        PublicFunc.__resetAllButton__(self, ButtonState)

        self.ui.doubleSpinBox_quality_threshold1.setValue(Config["Threshold"]["Low"])
        self.ui.doubleSpinBox_quality_threshold2.setValue(Config["Threshold"]["High"])
        self.ui.doubleSpinBox_findpeaks_min_interval.setValue(Config["FindPeaks"]["MinInterval"])
        self.ui.doubleSpinBox_findpeaks_min_height.setValue(Config["FindPeaks"]["MinHeight"])
        self.ui.doubleSpinBox_fillterMode_custom_low.setValue(Config["Filter"]["BandPassLow"])
        self.ui.doubleSpinBox_fillterMode_custom_high.setValue(Config["Filter"]["BandPassHigh"])

        self.ui.tableWidget_labeled.setHorizontalHeaderLabels(['信号片段', '质量类型'])
        self.ui.tableWidget_tobelabeled.setHorizontalHeaderLabels(['信号片段', '质量类型'])
        self.ui.tableWidget_labeled.setEditTriggers(QTableWidget.NoEditTriggers)
        self.ui.tableWidget_tobelabeled.setEditTriggers(QTableWidget.NoEditTriggers)
        self.ui.tableWidget_labeled.horizontalHeader().setStretchLastSection(True)
        self.ui.tableWidget_tobelabeled.horizontalHeader().setStretchLastSection(True)

        self.ui.pushButton_input_setting.clicked.connect(self.setting.show)
        self.ui.pushButton_input_and_calculate_peaks.clicked.connect(self.__slot_btn_input_and_calculate_peaks__)
        self.ui.pushButton_calculate_peaks.clicked.connect(self.__slot_btn_calculate_peaks__)
        self.ui.pushButton_calculate_peaks_save.clicked.connect(self.__slot_btn_save_calculate_peak__)
        self.ui.pushButton_input_and_label.clicked.connect(self.__slot_btn_input_and_label__)
        self.ui.pushButton_autoqualitylabel_recalculate.clicked.connect(self.__slot_btn_autoqualitylabel_recalculate__)
        self.ui.pushButton_refilter_orgBcg.clicked.connect(self.__slot_btn_refilter_orgBcg__)
        self.ui.pushButton_save.clicked.connect(self.__slot_btn_save__)
        self.ui.pushButton_prev.clicked.connect(self.__slot_btn_move__)
        self.ui.pushButton_next.clicked.connect(self.__slot_btn_move__)
        self.ui.tableWidget_labeled.cellDoubleClicked.connect(self.__slot_tableWidget_on_cell_double_clicked__)
        self.ui.tableWidget_tobelabeled.cellDoubleClicked.connect(self.__slot_tableWidget_on_cell_double_clicked__)
        self.ui.pushButton_valid.clicked.connect(self.__slot_btn_label__)
        self.ui.pushButton_invalid.clicked.connect(self.__slot_btn_label__)
        self.ui.pushButton_reset.clicked.connect(self.__slot_btn_label__)
        self.ui.lineEdit_filter_labeled.textChanged.connect(self.__slot_lineEdit_filter__)
        self.ui.lineEdit_filter_tobelabeled.textChanged.connect(self.__slot_lineEdit_filter__)
        self.ui.checkBox_allin.clicked.connect(self.__slot_checkBox__)
        self.ui.checkBox_type1.clicked.connect(self.__slot_checkBox__)
        self.ui.checkBox_type2.clicked.connect(self.__slot_checkBox__)
        self.ui.checkBox_type3.clicked.connect(self.__slot_checkBox__)
        self.ui.checkBox_type4.clicked.connect(self.__slot_checkBox__)
        self.ui.checkBox_type5.clicked.connect(self.__slot_checkBox__)
        self.ui.doubleSpinBox_quality_threshold1.valueChanged.connect(self.update_config)
        self.ui.doubleSpinBox_quality_threshold2.valueChanged.connect(self.update_config)
        self.ui.doubleSpinBox_findpeaks_min_interval.valueChanged.connect(self.update_config)
        self.ui.doubleSpinBox_findpeaks_min_height.valueChanged.connect(self.update_config)
        self.ui.doubleSpinBox_fillterMode_custom_low.valueChanged.connect(self.update_config)
        self.ui.doubleSpinBox_fillterMode_custom_high.valueChanged.connect(self.update_config)

        self.ui.pushButton_prev.setShortcut(QCoreApplication.translate("MainWindow", Params.RESP_QUALITY_LABEL_BTN_PREV_SHORTCUT_KEY))
        self.ui.pushButton_next.setShortcut(QCoreApplication.translate("MainWindow", Params.RESP_QUALITY_LABEL_BTN_NEXT_SHORTCUT_KEY))
        self.ui.pushButton_valid.setShortcut(QCoreApplication.translate("MainWindow", Params.RESP_QUALITY_LABEL_BTN_VALID_SHORTCUT_KEY))
        self.ui.pushButton_invalid.setShortcut(QCoreApplication.translate("MainWindow", Params.RESP_QUALITY_LABEL_BTN_INVALID_SHORTCUT_KEY))
        self.ui.pushButton_reset.setShortcut(QCoreApplication.translate("MainWindow", Params.RESP_QUALITY_LABEL_BTN_RESET_SHORTCUT_KEY))


    @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.tho_peak_point
            if self.ax0 is not None:
                self.ax0.clear()
            if self.ax1 is not None:
                self.ax1.clear()
            if self.ax0_spectrum is not None:
                self.ax0_spectrum.clear()
            if self.ax1_spectrum is not None:
                self.ax1_spectrum.clear()

            # 释放资源
            self.setting.close()
            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_calculate_peaks:
            try:
                self.ax1.plot(self.data.Tho_Processed,
                              label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_THO, color=Constants.PLOT_COLOR_BLUE)
                self.tho_peak_point, = self.ax1.plot(self.data.Tho_peak, self.data.Tho_peak_y, 'ro',
                                                     label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_THO_PEAKS)
                self.ax1.legend(loc=Constants.PLOT_UPPER_RIGHT)
                self.canvas.draw()
            except Exception as e:
                return Result().failure(info=Constants.DRAW_FAILURE + "\n" + format_exc())
            return Result().success(info=Constants.DRAW_FINISHED)
        elif (sender == self.ui.pushButton_input_and_label or
              sender == self.ui.pushButton_refilter_orgBcg or
              sender == self.ui.pushButton_prev or
              sender == self.ui.pushButton_next or
              sender == self.ui.tableWidget_labeled or
              sender == self.ui.tableWidget_tobelabeled or
              sender == self.ui.checkBox_allin or
              sender == self.ui.checkBox_type1 or
              sender == self.ui.checkBox_type2 or
              sender == self.ui.checkBox_type3 or
              sender == self.ui.checkBox_type4 or
              sender == self.ui.checkBox_type5):
            try:
                if Config["CurrentPartNum"] != Config["DataPartNum"]:
                    begin_OrgBCG = Config["CurrentOrgBCGIndex"]
                    end_OrgBCG = (Config["CurrentOrgBCGIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                  int(Config["InputConfig"]["OrgBCGUseFreq"]))
                    begin_Tho = Config["CurrentThoIndex"]
                    end_Tho = (Config["CurrentThoIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                               int(Config["InputConfig"]["ThoUseFreq"]))
                else:
                    begin_OrgBCG = Config["CurrentOrgBCGIndex"]
                    end_OrgBCG = len(self.data.OrgBCG_Processed)
                    begin_Tho = Config["CurrentThoIndex"]
                    end_Tho = len(self.data.Tho_Processed)

                if self.ui.radioButton_orgBcg_fillterMode_preset.isChecked():
                    mode = "preset"
                elif self.ui.radioButton_orgBcg_fillterMode_custom.isChecked():
                    mode = "custom"
                else:
                    raise ValueError("模式不存在")
                BDR, band_low, band_high, bcg_spectrum, bcg_freq, tho_spectrum, tho_freq = (
                    self.data.preprocess_getBDR(self.data.OrgBCG_Processed[begin_OrgBCG:end_OrgBCG],
                                                self.data.Tho_Processed[begin_Tho:end_Tho], mode))
                # 绘制数据
                self.ax0.plot(arange(begin_OrgBCG, end_OrgBCG), BDR,
                                               label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_ORGBCG,
                                               color=Constants.PLOT_COLOR_BLUE)
                self.ax1.plot(arange(begin_Tho, end_Tho), self.data.Tho_Processed[begin_Tho:end_Tho],
                                               label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_THO,
                                               color=Constants.PLOT_COLOR_BLUE)
                self.tho_peak_point, = self.ax1.plot(
                    [value for value in self.data.Tho_peak if begin_Tho <= value <= end_Tho],
                    [self.data.Tho_peak_y[x] for x in
                     [index for index, value in enumerate(self.data.Tho_peak) if begin_Tho <= value <= end_Tho]], 'ro',
                    label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_THO_PEAKS)

                # 绘制体动
                artifact_type_seq = array([])
                artifact_type_seq = artifact_type_seq.astype(int64)
                if self.ui.checkBox_type1.isChecked():
                    artifact_type_seq = append(artifact_type_seq, 1)
                if self.ui.checkBox_type2.isChecked():
                    artifact_type_seq = append(artifact_type_seq, 2)
                if self.ui.checkBox_type3.isChecked():
                    artifact_type_seq = append(artifact_type_seq, 3)
                if self.ui.checkBox_type4.isChecked():
                    artifact_type_seq = append(artifact_type_seq, 4)
                if self.ui.checkBox_type5.isChecked():
                    artifact_type_seq = append(artifact_type_seq, 5)
                length = end_OrgBCG - begin_OrgBCG
                mask = array([arange(length), arange(length), arange(length), arange(length), arange(length), arange(length)])
                mask = mask.astype(float64)
                for i in artifact_type_seq:
                    mask[i] = self.data.artifact_mask[begin_OrgBCG:end_OrgBCG] == i
                    mask[i] = (BDR * mask[i]).astype(float64)
                    place(mask[i], mask[i] == 0, nan)
                    self.ax0.plot(arange(begin_OrgBCG, end_OrgBCG), mask[i],
                        label=f"{Constants.RESP_QUALITY_LABEL_PLOT_LABEL_ARTIFACT}{i}", color=Constants.PLOT_COLOR_RED,
                        linestyle="-")

                self.ax0.legend(loc=Constants.PLOT_UPPER_RIGHT)
                self.ax1.legend(loc=Constants.PLOT_UPPER_RIGHT)

                # 绘制频谱
                self.ax0_spectrum.plot(bcg_freq, bcg_spectrum,
                                       label=Constants.RESP_QUALITY_LABEL_SPECTRUM_ORGBCG_LABEL,
                                       color=Constants.PLOT_COLOR_BLUE)
                self.ax0_spectrum.axvline(x=band_low,
                                          label=Constants.RESP_QUALITY_LABEL_SPECTRUM_BDR_LABEL,
                                          color=Constants.PLOT_COLOR_ORANGE,
                                          linestyle='--')
                self.ax0_spectrum.axvline(x=band_high,
                                          color=Constants.PLOT_COLOR_ORANGE,
                                          linestyle='--')
                self.ax1_spectrum.plot(tho_freq, tho_spectrum,
                                       label=Constants.RESP_QUALITY_LABEL_SPECTRUM_THO_LABEL,
                                       color=Constants.PLOT_COLOR_BLUE)
                self.ax0_spectrum.legend(loc=Constants.PLOT_UPPER_RIGHT)
                self.ax1_spectrum.legend(loc=Constants.PLOT_UPPER_RIGHT)

                self.canvas.draw()
                self.canvas_spectrum.draw()
                self.cid1 = self.ax0.callbacks.connect('xlim_changed', self.on_xlim_changed)
                self.cid2 = self.ax1.callbacks.connect('xlim_changed', self.on_xlim_changed)
            except Exception as e:
                return Result().failure(info=Constants.DRAW_FAILURE + "\n" + format_exc())
            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 __redraw_peaks__(self):
        try:
            if Config["CurrentPartNum"] != Config["DataPartNum"]:
                begin_Tho = Config["CurrentThoIndex"]
                end_Tho = (Config["CurrentThoIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                           int(Config["InputConfig"]["ThoUseFreq"]))
            elif Config["CurrentPartNum"] == Config["DataPartNum"]:
                begin_Tho = Config["CurrentThoIndex"]
                end_Tho = len(self.data.Tho_Processed)

            self.tho_peak_point.remove()
            self.tho_peak_point, = self.ax1.plot(
                [value for value in self.data.Tho_peak if begin_Tho <= value <= end_Tho],
                [self.data.Tho_peak_y[x] for x in
                 [index for index, value in enumerate(self.data.Tho_peak) if begin_Tho <= value <= end_Tho]], 'ro',
                label=Constants.RESP_QUALITY_LABEL_PLOT_LABEL_THO_PEAKS)

            self.ax1.autoscale(False)
            self.canvas.draw()
            return Result().success(info=Constants.DRAW_FINISHED)
        except Exception as e:
            self.canvas.draw()
            self.ax0.autoscale(False)
            self.ax1.autoscale(False)
            return Result().failure(info=Constants.DRAW_FAILURE)

    def update_tableWidget(self):
        label_list = {
            "已标注":
                where((self.data.resp_quality_label == Constants.RESP_QUALITY_LABEL_A_QUALITY) |
                      (self.data.resp_quality_label == Constants.RESP_QUALITY_LABEL_B_QUALITY))[0] + 1,
            "未标注":
                where(self.data.resp_quality_label == Constants.RESP_QUALITY_LABEL_C_QUALITY)[0] + 1
        }
        self.ui.tableWidget_labeled.setRowCount(
            label_list["已标注"].__len__())
        self.ui.tableWidget_tobelabeled.setRowCount(
            label_list["未标注"].__len__())
        for label_name, label in label_list.items():
            if label_name == "已标注":
                tableWidget = self.ui.tableWidget_labeled
            elif label_name == "未标注":
                tableWidget = self.ui.tableWidget_tobelabeled
            else:
                raise ValueError("label_name不存在")
            for row, value in enumerate(label):
                item = QTableWidgetItem(str(value).strip())
                tableWidget.setItem(row, 0, item)
                item = QTableWidgetItem(Constants.RESP_QUALITY_LABEL_KEY_VALUE[self.data.resp_quality_label[value - 1]])
                tableWidget.setItem(row, 1, item)
        self.ui.tableWidget_labeled.verticalScrollBar().setValue(self.ui.tableWidget_labeled.verticalScrollBar().maximum())
        self.ui.tableWidget_tobelabeled.verticalScrollBar().setValue(self.ui.tableWidget_tobelabeled.verticalScrollBar().maximum())

    def update_info(self):
        try:
            self.ui.lineEdit_current_part_num.setText("{} / {}".format(Config["CurrentPartNum"], Config["DataPartNum"]))
            self.ui.lineEdit_current_part_label.setText(
                str(Constants.RESP_QUALITY_LABEL_KEY_VALUE[self.data.resp_quality_label[Config["CurrentPartNum"] - 1]]))
        except Exception as e:
            return Result().failure(info=Constants.UPDATE_FAILURE +
                                         Constants.FAILURE_REASON["Update_Info_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.UPDATE_FINISHED)

    def __slot_btn_input_and_calculate_peaks__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.reset_axes()
        self.canvas.draw()

        self.data = Data()

        # 导入数据
        PublicFunc.progressbar_update(self, 1, 3, Constants.INPUTTING_DATA, 0)
        result = self.data.open_file_calculate_peaks()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/3)" + 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/3)" + result.info, Constants.TIPS_TYPE_INFO)

        # 重采样
        PublicFunc.progressbar_update(self, 2, 3, Constants.RESAMPLING_DATA, 40)
        result = self.data.resample_tho()
        if not result.status:
            PublicFunc.text_output(self.ui, "(2/3)" + 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/3)" + result.info, Constants.TIPS_TYPE_INFO)

        # 数据预处理
        PublicFunc.progressbar_update(self, 3, 3, Constants.PREPROCESSING_DATA, 70)
        result = self.data.preprocess_tho()
        if not result.status:
            PublicFunc.text_output(self.ui, "(3/3)" + 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/3)" + result.info, Constants.TIPS_TYPE_INFO)

        self.setting.close()
        ButtonState["Current"]["pushButton_input_setting"] = False
        ButtonState["Current"]["pushButton_input_and_calculate_peaks"] = False
        ButtonState["Current"]["pushButton_calculate_peaks"] = True
        ButtonState["Current"]["pushButton_calculate_peaks_save"] = True
        ButtonState["Current"]["pushButton_input_and_label"] = False
        ButtonState["Current"]["pushButton_autoqualitylabel_recalculate"] = False
        ButtonState["Current"]["pushButton_refilter_orgBcg"]: False
        ButtonState["Current"]["pushButton_valid"] = False
        ButtonState["Current"]["pushButton_invalid"] = False
        ButtonState["Current"]["pushButton_reset"] = False
        ButtonState["Current"]["pushButton_prev"] = False
        ButtonState["Current"]["pushButton_next"] = False
        ButtonState["Current"]["pushButton_save"] = False
        for action in self.figToolbar._actions.values():
            action.setEnabled(True)
        self.figToolbar.action_Label_Multiple.setEnabled(False)

        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_calculate_peaks__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.reset_axes()
        self.canvas.draw()

        # 计算峰值
        PublicFunc.progressbar_update(self, 1, 2, Constants.RESP_QUALITY_LABEL_CALCULATING_PEAK, 0)
        result = self.data.calculate_tho_peak()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/2)" + 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/2)" + result.info, Constants.TIPS_TYPE_INFO)

        # 绘图
        PublicFunc.progressbar_update(self, 2, 2, Constants.DRAWING_DATA, 50)
        result = self.__plot__()
        if not result.status:
            PublicFunc.text_output(self.ui, "(2/2)" + 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/2)" + result.info, Constants.TIPS_TYPE_INFO)

        ButtonState["Current"]["pushButton_input_setting"] = False
        ButtonState["Current"]["pushButton_input_and_calculate_peaks"] = False
        ButtonState["Current"]["pushButton_calculate_peaks"] = True
        ButtonState["Current"]["pushButton_calculate_peaks_save"] = True
        ButtonState["Current"]["pushButton_input_and_label"] = False
        ButtonState["Current"]["pushButton_autoqualitylabel_recalculate"] = False
        ButtonState["Current"]["pushButton_refilter_orgBcg"]: False
        ButtonState["Current"]["pushButton_valid"] = False
        ButtonState["Current"]["pushButton_invalid"] = False
        ButtonState["Current"]["pushButton_reset"] = False
        ButtonState["Current"]["pushButton_prev"] = False
        ButtonState["Current"]["pushButton_next"] = False
        ButtonState["Current"]["pushButton_save"] = False

        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_save_calculate_peak__(self):
        reply = QMessageBox.question(self, Constants.QUESTION_TITLE,
                                     Constants.QUESTION_CONTENT + Config["Path"]["Save_Tho_peak"],
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            PublicFunc.__disableAllButton__(self, ButtonState)

            # 保存
            PublicFunc.progressbar_update(self, 1, 1, Constants.SAVING_DATA, 0)
            result = self.data.save_tho_peak()
            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_input_and_label__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        self.reset_axes()
        self.canvas.draw()

        self.data = Data()

        # 导入数据
        PublicFunc.progressbar_update(self, 1, 6, Constants.INPUTTING_DATA, 0)
        result = self.data.open_file_label()
        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.LOADING_ARCHIVE, 30)
        result = self.data.get_archive_resp_quality()
        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.LOADING_ARCHIVE, 30)
        result = self.data.get_archive_tho_peak()
        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.RESAMPLING_DATA, 40)
        result = self.data.resample_tho_and_OrgBCG()
        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.PREPROCESSING_DATA, 70)
        result = self.data.preprocess_tho_and_OrgBCG()
        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, 90)
        self.ax0.autoscale(True)
        self.ax1.autoscale(True)
        self.ax1.relim()
        self.ax1.autoscale_view()
        result = self.__plot__()
        self.ax0.autoscale(False)
        self.ax1.autoscale(False)
        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)

        Config["CurrentPartNum"] = 1
        self.update_info()
        self.update_tableWidget()
        self.setting.close()
        ButtonState["Current"]["pushButton_input_setting"] = False
        ButtonState["Current"]["pushButton_input_and_calculate_peaks"] = False
        ButtonState["Current"]["pushButton_calculate_peaks"] = False
        ButtonState["Current"]["pushButton_calculate_peaks_save"] = False
        ButtonState["Current"]["pushButton_input_and_label"] = False
        ButtonState["Current"]["pushButton_autoqualitylabel_recalculate"] = True
        ButtonState["Current"]["pushButton_refilter_orgBcg"] = True
        ButtonState["Current"]["pushButton_valid"] = True
        ButtonState["Current"]["pushButton_invalid"] = True
        ButtonState["Current"]["pushButton_reset"] = True
        ButtonState["Current"]["pushButton_prev"] = True
        ButtonState["Current"]["pushButton_next"] = True
        ButtonState["Current"]["pushButton_save"] = True
        for action in self.figToolbar._actions.values():
            action.setEnabled(True)
        for action in self.figToolbar_spectrum._actions.values():
            action.setEnabled(True)
        self.figToolbar.action_Label_Multiple.setEnabled(True)

        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_autoqualitylabel_recalculate__(self):
        reply = QMessageBox.question(self, '确认', Constants.RESP_QUALITY_LABEL_CHECK_ARGS_QUESTION_CONTENT,
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.check_autolabel_args() is False:
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_AUTOLABEL_ARGS_ERROR, Constants.MSGBOX_TYPE_ERROR)
                return

            try:
                # 确定带体动的片段
                artifact_indices = []
                for i in range(0, len(self.data.Artifact_a)):
                    if i + 3 < len(self.data.Artifact_a):  # 防止索引越界
                        index0 = self.data.Artifact_a[i + 2] // (Config["InputConfig"]["OrgBCGUseFreq"] * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC)  # 第三行（索引+2）
                        index1 = self.data.Artifact_a[i + 3] // (Config["InputConfig"]["OrgBCGUseFreq"] * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC)  # 第四行（索引+3）
                        if index0 == index1:
                            artifact_indices.append(index0)
                        else:
                            artifact_indices.append(index0)
                            artifact_indices.append(index1)
                artifact_indices = list(dict.fromkeys(artifact_indices))
                for seg_idx in range(1, Config["DataPartNum"] - 1):
                    # 数据切片预处理
                    BCG = pre_process(
                        get_slice(
                            self.data.OrgBCG_Processed, seg_idx, Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC, Config["InputConfig"]["OrgBCGUseFreq"]), Config["InputConfig"]["OrgBCGUseFreq"], Params.RESP_QUALITY_LABEL_PREPROCESS_FC)
                    THO = pre_process(
                        get_slice(
                            self.data.Tho_Processed, seg_idx, Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC, Config["InputConfig"]["ThoUseFreq"]), Config["InputConfig"]["ThoUseFreq"], Params.RESP_QUALITY_LABEL_PREPROCESS_FC)

                    # 质量评估
                    # 有体动：1 无体动：0
                    artifact_flag = 1 if (seg_idx - 1) in artifact_indices else 0

                    quality = evaluate_quality(
                        BCG, THO, Config["InputConfig"]["OrgBCGUseFreq"], Config["InputConfig"]["ThoUseFreq"]
                        , artifact_flag, [float(Config["Threshold"]["Low"]), float(Config["Threshold"]["High"])]
                    )
                    self.data.resp_quality_label[seg_idx - 1] = quality

                self.update_tableWidget()
                result = self.update_info()
                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)
                PublicFunc.text_output(self.ui, Constants.OPERATION_FINISHED, Constants.TIPS_TYPE_INFO)
                PublicFunc.msgbox_output(self, Constants.OPERATION_FINISHED, Constants.MSGBOX_TYPE_INFO)
            except Exception as e:
                PublicFunc.text_output(self.ui, Constants.OPERATION_FAILURE, Constants.TIPS_TYPE_ERROR)
                PublicFunc.msgbox_output(self, Constants.OPERATION_FAILURE, Constants.MSGBOX_TYPE_ERROR)

    def __slot_btn_refilter_orgBcg__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)
        if len(self.data.OrgBCG_Processed) == 0:
            return Result().failure(info=Constants.RESAMPLE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        if self.ui.radioButton_orgBcg_fillterMode_custom.isChecked():
            if self.check_filter_args() is False:
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_CUSTOM_FILTER_ARGS_ERROR, Constants.MSGBOX_TYPE_ERROR)
                PublicFunc.finish_operation(self, ButtonState)
                return
        result = self.__plot__()
        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)

        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_save__(self):
        PublicFunc.__disableAllButton__(self, ButtonState)

        # 保存
        PublicFunc.progressbar_update(self, 1, 2, Constants.SAVING_DATA, 0)
        result = self.data.save_resp_quality_label()
        if not result.status:
            PublicFunc.text_output(self.ui, "(1/2)" + 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/2)" + result.info, Constants.TIPS_TYPE_INFO)

        # 保存
        PublicFunc.progressbar_update(self, 2, 2, Constants.SAVING_DATA, 50)
        result = self.data.save_tho_peak()
        if not result.status:
            PublicFunc.text_output(self.ui, "(2/2)" + 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/2)" + result.info, Constants.TIPS_TYPE_INFO)

        info = f"{Filename.RESP_QUALITY_LABEL}和{Filename.THO_PEAK}保存成功"
        # PublicFunc.msgbox_output(self, info, Constants.TIPS_TYPE_INFO)
        PublicFunc.finish_operation(self, ButtonState)

    def __slot_btn_move__(self):
        sender = self.sender()

        if sender == self.ui.pushButton_prev:
            if self.check_filter_args() is False:
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_CUSTOM_FILTER_ARGS_ERROR, Constants.MSGBOX_TYPE_ERROR)
                return
            if Config["CurrentPartNum"] == 1:
                PublicFunc.text_output(self.ui, Constants.RESP_QUALITY_LABEL_VIEWING_THE_FIRST_PART, Constants.MSGBOX_TYPE_INFO)
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_VIEWING_THE_FIRST_PART, Constants.MSGBOX_TYPE_INFO)
                return
            Config["CurrentPartNum"] = Config["CurrentPartNum"] - 1
            Config["CurrentOrgBCGIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                            int(Config["InputConfig"]["OrgBCGUseFreq"]))
            Config["CurrentThoIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                         int(Config["InputConfig"]["ThoUseFreq"]))
            result = self.__plot__()
            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)
            result = self.update_info()
            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)
            PublicFunc.text_output(self.ui, Constants.RESP_QUALITY_LABEL_PREV_PART + str(Config["CurrentPartNum"]),
                                   Constants.TIPS_TYPE_INFO)
        elif sender == self.ui.pushButton_next:
            if self.check_filter_args() is False:
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_CUSTOM_FILTER_ARGS_ERROR, Constants.MSGBOX_TYPE_ERROR)
                return
            if Config["CurrentPartNum"] == Config["DataPartNum"]:
                PublicFunc.text_output(self.ui, Constants.RESP_QUALITY_LABEL_VIEWING_THE_LAST_PART, Constants.MSGBOX_TYPE_INFO)
                PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_VIEWING_THE_LAST_PART, Constants.MSGBOX_TYPE_INFO)
                return
            Config["CurrentPartNum"] = Config["CurrentPartNum"] + 1
            Config["CurrentOrgBCGIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                            int(Config["InputConfig"]["OrgBCGUseFreq"]))
            Config["CurrentThoIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                         int(Config["InputConfig"]["ThoUseFreq"]))
            result = self.__plot__()
            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)
            result = self.update_info()
            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)
            PublicFunc.text_output(self.ui, Constants.RESP_QUALITY_LABEL_NEXT_PART + str(Config["CurrentPartNum"]),
                                   Constants.TIPS_TYPE_INFO)
        else:
            raise ValueError("发射信号不存在")

    def __slot_tableWidget_on_cell_double_clicked__(self, row, column):
        sender = self.sender()

        if self.check_filter_args() is False:
            PublicFunc.msgbox_output(self, Constants.RESP_QUALITY_LABEL_CUSTOM_FILTER_ARGS_ERROR, Constants.MSGBOX_TYPE_ERROR)
            return
        if sender == self.ui.tableWidget_labeled:
            Config["CurrentPartNum"] = int(
                self.ui.tableWidget_labeled.item(row, 0).text())
            Config["CurrentOrgBCGIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                            int(Config["InputConfig"]["OrgBCGUseFreq"]))
            Config["CurrentThoIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                         int(Config["InputConfig"]["ThoUseFreq"]))
        elif sender == self.ui.tableWidget_tobelabeled:
            Config["CurrentPartNum"] = int(
                self.ui.tableWidget_tobelabeled.item(row, 0).text())
            Config["CurrentOrgBCGIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                            int(Config["InputConfig"]["OrgBCGUseFreq"]))
            Config["CurrentThoIndex"] = ((Config["CurrentPartNum"] - 1) * Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                         int(Config["InputConfig"]["ThoUseFreq"]))
        else:
            raise ValueError("发射信号不存在")
        result = self.__plot__()
        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)
        result = self.update_info()
        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)
        PublicFunc.text_output(self.ui, Constants.RESP_QUALITY_LABEL_JUMP + str(Config["CurrentPartNum"]),
                               Constants.TIPS_TYPE_INFO)

    def __slot_btn_label__(self):
        sender = self.sender()

        if sender == self.ui.pushButton_valid:
            self.data.resp_quality_label[Config["CurrentPartNum"] - 1] = 1
        elif sender == self.ui.pushButton_invalid:
            self.data.resp_quality_label[Config["CurrentPartNum"] - 1] = 0
        elif sender == self.ui.pushButton_reset:
            self.data.resp_quality_label[Config["CurrentPartNum"] - 1] = -1
        else:
            raise ValueError("发射信号不存在")
        PublicFunc.text_output(self.ui, str(Config["CurrentPartNum"]) +
                               Constants.RESP_QUALITY_LABEL_LABEL_SUCCESSFULLY +
                               "，" + Constants.RESP_QUALITY_LABEL_LABEL_TYPE +
                               Constants.RESP_QUALITY_LABEL_KEY_VALUE[
                                   self.data.resp_quality_label[Config["CurrentPartNum"] - 1]],
                               Constants.TIPS_TYPE_INFO)
        result = self.data.save_resp_quality_label()
        self.update_tableWidget()
        self.update_info()
        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)
            if self.sender() == self.ui.pushButton_valid or self.sender() == self.ui.pushButton_invalid:
                if self.ui.checkBox_auto_next.isChecked():
                    self.ui.pushButton_next.click()
            self.__slot_btn_save__()

    def __slot_lineEdit_filter__(self, filter_text):
        sender = self.sender()

        if sender == self.ui.lineEdit_filter_labeled:
            for row in range(self.ui.tableWidget_labeled.rowCount()):
                match = False
                for col in range(self.ui.tableWidget_labeled.columnCount()):
                    item = self.ui.tableWidget_labeled.item(row, col)
                    if filter_text.lower() in item.text().lower():
                        match = True
                        break
                self.ui.tableWidget_labeled.setRowHidden(row, not match)
        elif sender == self.ui.lineEdit_filter_tobelabeled:
            for row in range(self.ui.tableWidget_tobelabeled.rowCount()):
                match = False
                for col in range(self.ui.tableWidget_tobelabeled.columnCount()):
                    item = self.ui.tableWidget_tobelabeled.item(row, col)
                    if filter_text.lower() in item.text().lower():
                        match = True
                        break
                self.ui.tableWidget_tobelabeled.setRowHidden(row, not match)
        else:
            raise ValueError("发生信号不存在")

    def __slot_checkBox__(self):
        sender = self.sender()

        if sender == self.ui.checkBox_allin:
            if self.ui.checkBox_allin.isChecked():
                self.ui.checkBox_type1.setChecked(True)
                self.ui.checkBox_type2.setChecked(True)
                self.ui.checkBox_type3.setChecked(True)
                self.ui.checkBox_type4.setChecked(True)
                self.ui.checkBox_type5.setChecked(True)
            else:
                self.ui.checkBox_type1.setChecked(False)
                self.ui.checkBox_type2.setChecked(False)
                self.ui.checkBox_type3.setChecked(False)
                self.ui.checkBox_type4.setChecked(False)
                self.ui.checkBox_type5.setChecked(False)
        elif (sender == self.ui.checkBox_type1 or
            sender == self.ui.checkBox_type2 or
            sender == self.ui.checkBox_type3 or
            sender == self.ui.checkBox_type4 or
            sender == self.ui.checkBox_type5):
            if (self.ui.checkBox_type1.isChecked() and
                self.ui.checkBox_type2.isChecked() and
                self.ui.checkBox_type3.isChecked() and
                self.ui.checkBox_type4.isChecked() and
                self.ui.checkBox_type5.isChecked()):
                self.ui.checkBox_allin.setChecked(True)
            else:
                self.ui.checkBox_allin.setChecked(False)
        else:
            raise ValueError("发射信号不存在")

        self.__plot__()

    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)
        if self.ax1 is not None:
            self.ax1.clear()
            self.ax1.grid(True)
            self.ax1.xaxis.set_major_formatter(Params.FORMATTER)
        if self.ax0_spectrum is not None:
            self.ax0_spectrum.clear()
            self.ax0_spectrum.grid(True)
            self.ax0_spectrum.set_title(Constants.RESP_QUALITY_LABEL_SPECTRUM_BDR_TITLE, fontsize=8)
        if self.ax1_spectrum is not None:
            self.ax1_spectrum.clear()
            self.ax1_spectrum.grid(True)
            self.ax1_spectrum.set_title(Constants.RESP_QUALITY_LABEL_SPECTRUM_THO_TITLE, fontsize=8)

    def check_filter_args(self):
        if ((float(Config["Filter"]["BandPassLow"]) >= float(Config["Filter"]["BandPassHigh"])) or
                (float(Config["Filter"]["BandPassLow"]) <= 0) or (float(Config["Filter"]["BandPassHigh"]) <= 0)):
            return False
        return True

    def check_autolabel_args(self):
        if (float(Config["Threshold"]["Low"]) >= float(Config["Threshold"]["High"])):
            return False
        return True

    def on_xlim_changed(self, ax):
        # 获取触发事件的轴
        if ax == self.ax0:
            # 获取第一个子图的X轴范围
            xlim1 = self.ax0.get_xlim()
            # 计算对应的第二个子图的X轴范围
            ratio = (int(Config["InputConfig"]["ThoUseFreq"]) /
                     int(Config["InputConfig"]["OrgBCGUseFreq"]))  # 信号B的样本点数与信号A的样本点数的比例
            xlim2 = (xlim1[0] * ratio, xlim1[1] * ratio)

            # 禁用事件监听
            self.ax0.callbacks.disconnect(self.cid2)
            # 设置第二个子图的X轴范围
            self.ax1.set_xlim(xlim2)
            # 重新启用事件监听
            self.cid2 = self.ax1.callbacks.connect('xlim_changed', self.on_xlim_changed)

        elif ax == self.ax1:
            # 获取第二个子图的X轴范围
            xlim2 = self.ax1.get_xlim()
            # 计算对应的第一个子图的X轴范围
            ratio = (int(Config["InputConfig"]["OrgBCGUseFreq"]) /
                     int(Config["InputConfig"]["ThoUseFreq"]))  # 信号A的样本点数与信号B的样本点数的比例
            xlim1 = (xlim2[0] * ratio, xlim2[1] * ratio)

            # 禁用事件监听
            self.ax0.callbacks.disconnect(self.cid1)
            # 设置第一个子图的X轴范围
            self.ax0.set_xlim(xlim1)
            # 重新启用事件监听
            self.cid1 = self.ax0.callbacks.connect('xlim_changed', self.on_xlim_changed)

    def update_config(self):
        Config["Threshold"]["Low"] = self.ui.doubleSpinBox_quality_threshold1.value()
        Config["Threshold"]["High"] = self.ui.doubleSpinBox_quality_threshold2.value()
        Config["FindPeaks"]["MinInterval"] = self.ui.doubleSpinBox_findpeaks_min_interval.value()
        Config["FindPeaks"]["MinHeight"] = self.ui.doubleSpinBox_findpeaks_min_height.value()
        Config["Filter"]["BandPassLow"] = self.ui.doubleSpinBox_fillterMode_custom_low.value()
        Config["Filter"]["BandPassHigh"] = self.ui.doubleSpinBox_fillterMode_custom_high.value()

    def toggle_home(self):
        if Config["CurrentPartNum"] is not None:
            self.ax1.autoscale(True)
            self.ax1.relim()
            self.ax1.autoscale_view()
            self.canvas.draw()
            self.ax1.autoscale(False)

    def toggle_changeLabel_Multiple_mode(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
                self.figToolbar.rect_start_y = event.ydata
                # 如果矩形patch已存在，先移除
                if self.figToolbar.rect_patch_ax1 is not 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 and self.figToolbar.rect_start_y is not None:
                self.figToolbar.rect_end_x = event.xdata
                self.figToolbar.rect_end_y = event.ydata
                if ((self.figToolbar.rect_start_x is not None and self.figToolbar.rect_end_x is not None) and
                        (self.figToolbar.rect_start_y is not None and self.figToolbar.rect_end_y 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
                    if self.figToolbar.rect_start_y < self.figToolbar.rect_end_y:
                        rect_bottom = self.figToolbar.rect_start_y
                        rect_top = self.figToolbar.rect_end_y
                    elif self.figToolbar.rect_start_y > self.figToolbar.rect_end_y:
                        rect_bottom = self.figToolbar.rect_end_y
                        rect_top = self.figToolbar.rect_start_y
                    else:
                        rect_bottom = self.figToolbar.rect_start_y
                        rect_top = self.figToolbar.rect_start_y
                else:
                    rect_left = self.figToolbar.rect_start_x
                    rect_right = self.figToolbar.rect_start_x
                    rect_bottom = self.figToolbar.rect_start_y
                    rect_top = self.figToolbar.rect_start_y
                if event.button == 1 and self.is_left_button_pressed:
                    self.is_left_button_pressed = False
                    if rect_left < Config["CurrentThoIndex"]:
                        rect_left = Config["CurrentThoIndex"]
                    elif rect_left >= (Config["CurrentThoIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                       int(Config["InputConfig"]["ThoUseFreq"])):
                        rect_left = 0
                        rect_right = 0
                    if (rect_right >= Config["CurrentThoIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                            int(Config["InputConfig"]["ThoUseFreq"])):
                        rect_right = (Config["CurrentThoIndex"] + Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                      int(Config["InputConfig"]["ThoUseFreq"]) - 1)
                    elif rect_right < Config["CurrentThoIndex"]:
                        rect_left = 0
                        rect_right = 0
                    selected_area_for_add_points = self.data.Tho_Processed[int(rect_left):int(rect_right)]
                    # 找到子列表的最小值
                    min_value = min(selected_area_for_add_points)
                    # 遍历子列表，将不满足条件的元素替换为最小值
                    selected_area_for_add_points = [
                        y if rect_bottom < y < rect_top else min_value
                        for y in selected_area_for_add_points
                    ]
                    peaks_idx, _ = find_peaks(selected_area_for_add_points,
                                                   height=float(Config["FindPeaks"]["MinHeight"]),
                                                   distance=float(Config["FindPeaks"]["MinInterval"]))
                    peaks_idx = peaks_idx + int(rect_left)
                    peaks_idx = setdiff1d(peaks_idx, self.data.Tho_peak)
                    if len(peaks_idx) != 0:
                        PublicFunc.text_output(self.ui,
                                               f"{Constants.RESP_QUALITY_LABEL_ADD_POINTS_SUCCESSFULLY}{peaks_idx}",
                                               Constants.TIPS_TYPE_INFO)
                    else:
                        PublicFunc.text_output(self.ui,
                                               Constants.RESP_QUALITY_LABEL_NO_POINT_IN_THE_INTERVAL,
                                               Constants.TIPS_TYPE_INFO)
                    self.data.Tho_peak = append(self.data.Tho_peak, peaks_idx)
                    self.data.Tho_peak_y = append(self.data.Tho_peak_y, self.data.Tho_Processed[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.Tho_peak - rect_left
                    right_label2_to_delete = self.data.Tho_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.RESP_QUALITY_LABEL_REMOVE_POINTS_SUCCESSFULLY}{self.data.Tho_peak[left_label2_to_delete_idx:right_label2_to_delete_idx]}",
                                               Constants.TIPS_TYPE_INFO)
                    else:
                        PublicFunc.text_output(self.ui,
                                               Constants.RESP_QUALITY_LABEL_NO_POINT_IN_THE_INTERVAL,
                                               Constants.TIPS_TYPE_INFO)
                    self.data.Tho_peak = delete(self.data.Tho_peak,
                                                  arange(left_label2_to_delete_idx, right_label2_to_delete_idx))
                    self.data.Tho_peak_y = delete(self.data.Tho_peak_y, arange(left_label2_to_delete_idx, right_label2_to_delete_idx))
                    self.__redraw_peaks__()
                self.data.Tho_peak.sort()
                self.data.Tho_peak_y = [self.data.Tho_Processed[x] for x in self.data.Tho_peak]
                self.figToolbar.rect_start_x = None
                self.figToolbar.rect_end_x = None
                self.figToolbar.rect_start_y = None
                self.figToolbar.rect_end_y = None

                result = self.data.save_tho_peak()
                if not result.status:
                    info = f"未成功保存，错误提示：{result.info}，结果已暂存到缓存中，请正确操作后重试。"
                    PublicFunc.text_output(self.ui, info, Constants.TIPS_TYPE_ERROR)
                    PublicFunc.msgbox_output(self, info, Constants.MSGBOX_TYPE_ERROR)
                else:
                    info = result.info
                    PublicFunc.text_output(self.ui, info, Constants.TIPS_TYPE_INFO)

                # 移除矩形patch
                if self.figToolbar.rect_patch_ax1 is not 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
        if self.figToolbar.rect_start_y is not None and event.ydata is not None:
            self.figToolbar.rect_end_y = event.ydata

            # 如果矩形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.RESP_QUALITY_LABEL_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.RESP_QUALITY_LABEL_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

            if self.is_left_button_pressed:
                y_start = self.figToolbar.rect_start_y
                y_end = self.figToolbar.rect_end_y
                self.figToolbar.rect_patch_ax1.set_xy((min(x_start, x_end), min(y_start, y_end)))
                self.figToolbar.rect_patch_ax1.set_height(abs(y_end - y_start))
            elif self.is_right_button_pressed:
                y_start = min(self.ax0.get_ylim()[0], self.ax1.get_ylim()[0]) - 1000
                y_end = max(self.ax0.get_ylim()[1], self.ax1.get_ylim()[1]) + 1000
                self.figToolbar.rect_patch_ax1.set_xy((min(x_start, x_end), y_start))
                self.figToolbar.rect_patch_ax1.set_height(abs(y_end - y_start))

            self.figToolbar.rect_patch_ax1.set_width(abs(x_end - x_start))

            self.canvas.draw()


class Data():

    def __init__(self):
        self.OrgBCG = None
        self.Tho = None
        self.OrgBCG_Processed = None
        self.Tho_Processed = None
        self.Tho_peak = None
        self.resp_quality_label = None
        self.Artifact_a = None
        self.Tho_peak_y = None

        self.artifact_number = array([]).astype(int64)
        self.artifact_type = array([]).astype(int64)
        self.artifact_mask = array([]).astype(int64)

    def open_file_calculate_peaks(self):
        if Path(Config["Path"]["Input_OrgBCG"]).is_file():
            Config["Path"]["Input_OrgBCG"] = str(Path(Config["Path"]["Input_OrgBCG"]).parent)
        if Path(Config["Path"]["Input_Tho"]).is_file():
            Config["Path"]["Input_Tho"] = str(Path(Config["Path"]["Input_Tho"]).parent)
        if Path(Config["Path"]["Input_Artifact"]).is_file():
            Config["Path"]["Input_Artifact"] = str(Path(Config["Path"]["Input_Artifact"]).parent)

        result = PublicFunc.examine_file(Config["Path"]["Input_Tho"], Filename.THO_SYNC, Params.ENDSWITH_TXT)
        if result.status:
            Config["Path"]["Input_Tho"] = result.data["path"]
            Config["InputConfig"]["ThoFreq"] = result.data["freq"]
        else:
            return result

        Config["Path"]["Save_Tho_peak"] = str(
            Path(Config["Path"]["Save_Tho_peak"]) / Path(Filename.THO_PEAK + str(Config["InputConfig"]["ThoUseFreq"]) + Params.ENDSWITH_TXT))

        try:
            self.Tho = read_csv(Config["Path"]["Input_Tho"],
                                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())

        return Result().success(info=Constants.INPUT_FINISHED)

    def resample_tho(self):
        if self.Tho is None:
            return Result().failure(info=Constants.RESAMPLE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            if Config["InputConfig"]["ThoFreq"] != Config["InputConfig"]["ThoUseFreq"]:
                self.Tho = resample(self.Tho,
                                    int(len(self.Tho) *
                                        (Config["InputConfig"]["ThoUseFreq"] / Config["InputConfig"]["ThoFreq"])))
            else:
                return Result().success(info=Constants.RESAMPLE_NO_NEED)
        except Exception as e:
            return Result().failure(info=Constants.RESAMPLE_FAILURE +
                                         Constants.FAILURE_REASON["Resample_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.RESAMPLE_FINISHED)

    def preprocess_tho(self):
        if self.Tho is None:
            return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            self.Tho_Processed = pre_process(self.Tho, Config["InputConfig"]["ThoUseFreq"], Params.RESP_QUALITY_LABEL_PREPROCESS_FC)
        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 calculate_tho_peak(self):
        if self.Tho_Processed is None:
            return Result().failure(info=Constants.RESP_QUALITY_LABEL_CALCULATE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            self.Tho_peak, _ = find_peaks(self.Tho_Processed,
                                          height=float(Config["FindPeaks"]["MinHeight"]),
                                          distance=float(Config["FindPeaks"]["MinInterval"]))
            self.Tho_peak_y = [self.Tho_Processed[x] for x in (self.Tho_peak)]
        except Exception as e:
            return Result().failure(info=Constants.RESP_QUALITY_LABEL_CALCULATE_FAILURE +
                                         Constants.FAILURE_REASON["Calculate_Peak_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.RESP_QUALITY_LABEL_CALCULATE_FINISHED)

    def save_tho_peak(self):
        if (not Path(Config["Path"]["Save_Tho_peak"]).parent.exists()) or (not Path(Config["Path"]["Save_Tho_peak"]).parent.is_dir()):
            Path(Config["Path"]["Save_Tho_peak"]).parent.mkdir(parents=True, exist_ok=True)

        if self.Tho_peak is None:

            return Result().failure(info=Filename.THO_PEAK + Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            DataFrame(self.Tho_peak).to_csv(Path(Config["Path"]["Save_Tho_peak"]), mode='w', index=False, header=False)
        except PermissionError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_Permission_Denied"])
        except FileNotFoundError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_File_Not_Found"])
        except Exception as e:
            return Result().failure(info=Filename.THO_PEAK + Constants.SAVE_FAILURE +
                                         Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc())

        return Result().success(info=Filename.THO_PEAK + Constants.SAVE_FINISHED)

    def open_file_label(self):
        if Path(Config["Path"]["Input_OrgBCG"]).is_file():
            Config["Path"]["Input_OrgBCG"] = str(Path(Config["Path"]["Input_OrgBCG"]).parent)
        if Path(Config["Path"]["Input_Tho"]).is_file():
            Config["Path"]["Input_Tho"] = str(Path(Config["Path"]["Input_Tho"]).parent)
        if Path(Config["Path"]["Input_Artifact"]).is_file():
            Config["Path"]["Input_Artifact"] = str(Path(Config["Path"]["Input_Artifact"]).parent)

        result = PublicFunc.examine_file(Config["Path"]["Input_OrgBCG"], Filename.ORGBCG_SYNC, Params.ENDSWITH_TXT)
        if result.status:
            Config["Path"]["Input_OrgBCG"] = result.data["path"]
            Config["InputConfig"]["OrgBCGFreq"] = result.data["freq"]
        else:
            return result

        result = PublicFunc.examine_file(Config["Path"]["Input_Tho"], Filename.THO_SYNC, Params.ENDSWITH_TXT)
        if result.status:
            Config["Path"]["Input_Tho"] = result.data["path"]
            Config["InputConfig"]["ThoFreq"] = result.data["freq"]
        else:
            return result

        result = PublicFunc.examine_file(Config["Path"]["Input_Artifact"], Filename.ARTIFACT_A, Params.ENDSWITH_TXT)
        if result.status:
            Config["Path"]["Input_Artifact"] = result.data["path"]
        else:
            return result

        Config["Path"]["Save_Resp_quality_label"] = str(
            Path(Config["Path"]["Save_Resp_quality_label"]) / Path(
                Filename.RESP_QUALITY_LABEL + Params.ENDSWITH_TXT))
        Config["Path"]["Save_Tho_peak"] = str(
            Path(Config["Path"]["Save_Tho_peak"]) / Path(
                Filename.THO_PEAK + str(Config["InputConfig"]["ThoUseFreq"]) + Params.ENDSWITH_TXT))

        try:
            self.OrgBCG = read_csv(Config["Path"]["Input_OrgBCG"],
                                   encoding=Params.UTF8_ENCODING,
                                   header=None).to_numpy().reshape(-1)
            self.Tho = read_csv(Config["Path"]["Input_Tho"],
                                encoding=Params.UTF8_ENCODING,
                                header=None).to_numpy().reshape(-1)
            self.Artifact_a = read_csv(Config["Path"]["Input_Artifact"],
                                       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:
            Config["DataPartNum"] = len(self.OrgBCG) // (Params.RESP_QUALITY_LABEL_PARTS_TIME_SEC *
                                                         Config["InputConfig"]["OrgBCGFreq"]) + 1
            if Config["DataPartNum"] == 0:
                return Result().failure(info=Constants.INPUT_FAILURE + Constants.FAILURE_REASON["Data_Length_Not_Correct"])
        except Exception as e:
            return Result().failure(info=Constants.INPUT_FAILURE + Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc())

        try:
            # 检查体动标签正确性，长度
            PublicFunc.examine_artifact(self.Artifact_a)
            # 定义绘制体动所需要的数组
            artifact_start = array([])
            artifact_start = artifact_start.astype(int64)
            artifact_end = array([])
            artifact_end = artifact_end.astype(int64)
            for i in range(0, len(self.Artifact_a), 4):
                self.artifact_number = append(self.artifact_number, self.Artifact_a[i])
            for i in range(1, len(self.Artifact_a), 4):
                self.artifact_type = append(self.artifact_type, self.Artifact_a[i])
            for i in range(2, len(self.Artifact_a), 4):
                artifact_start = append(artifact_start, self.Artifact_a[i])
            for i in range(3, len(self.Artifact_a), 4):
                artifact_end = append(artifact_end, self.Artifact_a[i])
            self.artifact_mask = zeros(len(self.OrgBCG))
            for i in range(0, len(self.artifact_number)):
                if self.artifact_type[i] == 1:
                    self.artifact_mask[artifact_start[i]: artifact_end[i] + 1] = 1
                elif self.artifact_type[i] == 2:
                    self.artifact_mask[artifact_start[i]: artifact_end[i] + 1] = 2
                elif self.artifact_type[i] == 3:
                    self.artifact_mask[artifact_start[i]: artifact_end[i] + 1] = 3
                elif self.artifact_type[i] == 4:
                    self.artifact_mask[artifact_start[i]: artifact_end[i] + 1] = 4
                elif self.artifact_type[i] == 5:
                    self.artifact_mask[artifact_start[i]: artifact_end[i] + 1] = 5
        except Exception as e:
            return Result().failure(info=Constants.INPUT_FAILURE +
                                         Constants.FAILURE_REASON["Get_Artifact_Format_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.INPUT_FINISHED)

    def get_archive_resp_quality(self):
        if not Path(Config["Path"]["Save_Resp_quality_label"]).exists():
            self.resp_quality_label = full(Config["DataPartNum"], -1)
            return Result().success(info=Filename.RESP_QUALITY_LABEL + "：" + Constants.ARCHIVE_NOT_EXIST)
        else:
            self.resp_quality_label = read_csv(Config["Path"]["Save_Resp_quality_label"],
                                               encoding=Params.UTF8_ENCODING,
                                               header=None).to_numpy().reshape(-1)
            return Result().success(info=Filename.RESP_QUALITY_LABEL + "：" + Constants.ARCHIVE_EXIST)

    def get_archive_tho_peak(self):
        try:
            if not Path(Config["Path"]["Save_Tho_peak"]).exists():
                self.Tho_peak = array([]).astype(int)
                self.Tho_peak_y = array([]).astype(int)
                return Result().success(info=Filename.RESP_QUALITY_LABEL + "：" + Constants.ARCHIVE_NOT_EXIST)
            else:
                self.Tho_peak = read_csv(Config["Path"]["Save_Tho_peak"],
                                         encoding=Params.UTF8_ENCODING,
                                         header=None).to_numpy().reshape(-1)

                return Result().success(info=Filename.THO_PEAK + "：" + Constants.ARCHIVE_EXIST)
        except EmptyDataError as e:
            self.Tho_peak = array([]).astype(int)
            self.Tho_peak_y = array([]).astype(int)
            return Result().success(info=Filename.RESP_QUALITY_LABEL + "：" + Constants.ARCHIVE_EMPTY)

    def resample_tho_and_OrgBCG(self):
        if (self.OrgBCG is None) or (self.Tho is None):
            return Result().failure(info=Constants.RESAMPLE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            if ((Config["InputConfig"]["OrgBCGFreq"] != Config["InputConfig"]["OrgBCGUseFreq"])
                or (Config["InputConfig"]["ThoFreq"] != Config["InputConfig"]["ThoUseFreq"])):

                if Config["InputConfig"]["OrgBCGFreq"] != Config["InputConfig"]["OrgBCGUseFreq"]:
                    self.OrgBCG = resample(self.OrgBCG,
                                           int(len(self.OrgBCG) *
                                               (Config["InputConfig"]["OrgBCGUseFreq"] / Config["InputConfig"]["OrgBCGFreq"])))
                if Config["InputConfig"]["ThoFreq"] != Config["InputConfig"]["ThoUseFreq"]:
                    self.Tho = resample(self.Tho,
                                        int(len(self.Tho) *
                                            (Config["InputConfig"]["ThoUseFreq"] / Config["InputConfig"]["ThoFreq"])))
            else:
                return Result().success(info=Constants.RESAMPLE_NO_NEED)
        except Exception as e:
            return Result().failure(info=Constants.RESAMPLE_FAILURE +
                                         Constants.FAILURE_REASON["Resample_Exception"] + "\n" + format_exc())

        return Result().success(info=Constants.RESAMPLE_FINISHED)

    def preprocess_tho_and_OrgBCG(self):
        if (self.OrgBCG is None) or (self.Tho is None):
            return Result().failure(info=Constants.PREPROCESS_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            self.OrgBCG_Processed = self.OrgBCG.copy()
            self.Tho_Processed = pre_process(self.Tho, Config["InputConfig"]["ThoUseFreq"], Params.RESP_QUALITY_LABEL_PREPROCESS_FC)
            self.Tho_peak_y = [self.Tho_Processed[x] for x in (self.Tho_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 preprocess_getBDR(self, orgBcg_slice, THO_slice, mode):
        orgBcg = pre_process(orgBcg_slice, int(Config["InputConfig"]["OrgBCGUseFreq"]),
                             Params.RESP_QUALITY_LABEL_PREPROCESS_FC)
        if mode == "preset":
            BDR, band_low, band_high, bcg_spectrum, bcg_freq, tho_spectrum, tho_freq = (
                get_bandpass_bcgsignal(orgBcg, THO_slice, int(Config["InputConfig"]["OrgBCGUseFreq"]),
                                       int(Config["InputConfig"]["ThoUseFreq"]), use_custom_band=False))
        elif mode == "custom":
            BDR, band_low, band_high, bcg_spectrum, bcg_freq, tho_spectrum, tho_freq = (
                get_bandpass_bcgsignal(orgBcg, THO_slice, int(Config["InputConfig"]["OrgBCGUseFreq"]),
                                       int(Config["InputConfig"]["ThoUseFreq"]),
                                       use_custom_band=True,
                                       low_cutoff=float(Config["Filter"]["BandPassLow"]),
                                       high_cutoff=float(Config["Filter"]["BandPassHigh"])))
        else:
            raise ValueError("模式不存在")
        return BDR, band_low, band_high, bcg_spectrum, bcg_freq, tho_spectrum, tho_freq

    def save_resp_quality_label(self):
        if self.resp_quality_label is None:

            return Result().failure(info=Filename.RESP_QUALITY_LABEL + Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Data_Not_Exist"])

        try:
            DataFrame(self.resp_quality_label).to_csv(Path(Config["Path"]["Save_Resp_quality_label"]), mode='w', index=False, header=False)
        except PermissionError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_Permission_Denied"])
        except FileNotFoundError as e:
            return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON["Save_File_Not_Found"])
        except Exception as e:
            return Result().failure(info=Filename.RESP_QUALITY_LABEL + Constants.SAVE_FAILURE +
                                         Constants.FAILURE_REASON["Save_Exception"] + "\n" + format_exc())

        return Result().success(info=Filename.RESP_QUALITY_LABEL + Constants.SAVE_FINISHED)


class CustomNavigationToolbar(NavigationToolbar2QT):

    def __init__(self, canvas, parent):
        super().__init__(canvas, parent)
        # 初始化画框工具栏
        self.action_Label_Multiple = QAction(Constants.RESP_QUALITY_LABEL_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.RESP_QUALITY_LABEL_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_start_y = None
        self.rect_end_y = None
        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