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