1030 lines
54 KiB
Python
1030 lines
54 KiB
Python
"""
|
||
@author:Yosa
|
||
@file:ecg_label_check.py
|
||
@email:2023025086@m.scnu.edu.cn
|
||
@time:2025/2/5
|
||
"""
|
||
|
||
import sys
|
||
from logging import NOTSET, getLogger, FileHandler, Formatter, StreamHandler, info, error, debug
|
||
from time import time, strftime, localtime
|
||
|
||
import pandas as pd
|
||
import numpy as np
|
||
from PyQt5 import QtGui
|
||
from PyQt5.QtGui import QFont, QDoubleValidator, QIntValidator
|
||
from matplotlib.ticker import FuncFormatter
|
||
from numpy import append
|
||
from matplotlib import use
|
||
from matplotlib import pyplot as plt, gridspec
|
||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from PyQt5.QtCore import QCoreApplication, QTimer
|
||
from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox, QButtonGroup, QApplication, QTableWidgetItem, \
|
||
QLineEdit, QAction, QTableWidget, QDialog, QVBoxLayout, QLabel, QDialogButtonBox
|
||
from scipy.signal import butter, filtfilt, find_peaks
|
||
|
||
import append
|
||
from MainWindow import Ui_MainWindow
|
||
|
||
use("Qt5Agg") # 声明使用QT5
|
||
|
||
# 设置日志
|
||
logger = getLogger()
|
||
logger.setLevel(NOTSET)
|
||
realtime = strftime('%Y%m%d', localtime(time()))
|
||
if not Path("logs").exists():
|
||
Path("logs").mkdir(exist_ok=True)
|
||
fh = FileHandler(Path("logs") / (realtime + ".log"), mode='a')
|
||
fh.setLevel(NOTSET)
|
||
fh.setFormatter(Formatter("%(asctime)s: %(message)s"))
|
||
logger.addHandler(fh)
|
||
|
||
ch = StreamHandler()
|
||
ch.setLevel(NOTSET)
|
||
ch.setFormatter(Formatter("%(asctime)s: %(message)s"))
|
||
logger.addHandler(ch)
|
||
getLogger('matplotlib.font_manager').disabled = True
|
||
info("------------------------------------")
|
||
info("---------ecg_label_check.py---------")
|
||
|
||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||
|
||
# 全局变量初始化
|
||
data1 = np.array([])
|
||
data1 = data1.astype(np.float64)
|
||
data2 = np.array([])
|
||
data2 = data2.astype(np.float64)
|
||
label1 = np.array([])
|
||
label1 = label1.astype(np.int64)
|
||
label2 = np.array([])
|
||
label2 = label2.astype(np.int64)
|
||
|
||
# 程序初始化操作
|
||
def __init__(self):
|
||
super(MainWindow, self).__init__()
|
||
self.setupUi(self)
|
||
|
||
# 设置画框
|
||
self.figure = plt.figure(figsize=(12, 6), dpi=150)
|
||
self.canvas = FigureCanvasQTAgg(self.figure)
|
||
self.figToolbar = CustomNavigationToolbar(self.canvas, self)
|
||
self.verticalLayout_canvas.addWidget(self.canvas)
|
||
self.verticalLayout_canvas.addWidget(self.figToolbar)
|
||
self.figToolbar._actions['home'].triggered.connect(self.toggle_home)
|
||
self.figToolbar.action_Label_Single.triggered.connect(self.toggle_changeLabel_Single_mode)
|
||
self.figToolbar.action_Label_Multiple.triggered.connect(self.toggle_changeLabel_Multiple_mode)
|
||
|
||
# 画框子图初始化
|
||
self.gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1])
|
||
self.figure.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
|
||
plt.margins(0, 0)
|
||
plt.tight_layout()
|
||
plt.xticks([])
|
||
plt.yticks([])
|
||
|
||
# 单选框组件分组
|
||
self.buttonGroup_inputMode = QButtonGroup()
|
||
self.buttonGroup_inputMode.addButton(self.radioButton_inputMode_auto)
|
||
self.buttonGroup_inputMode.addButton(self.radioButton_inputMode_manual)
|
||
self.buttonGroup_data1_preprocess = QButtonGroup()
|
||
self.buttonGroup_data1_preprocess.addButton(self.radioButton_data1_fillterMode)
|
||
self.buttonGroup_data2_preprocess = QButtonGroup()
|
||
self.buttonGroup_data2_preprocess.addButton(self.radioButton_data2_fillterMode)
|
||
self.buttonGroup_move_preset = QButtonGroup()
|
||
self.buttonGroup_move_preset.addButton(self.radioButton_move_preset_1)
|
||
self.buttonGroup_move_preset.addButton(self.radioButton_move_preset_2)
|
||
self.buttonGroup_move_preset.addButton(self.radioButton_move_preset_3)
|
||
self.buttonGroup_move_preset.addButton(self.radioButton_move_custom)
|
||
|
||
# 设置表格属性
|
||
self.tableWidget_label1.setHorizontalHeaderLabels(['label1'])
|
||
self.tableWidget_label2.setHorizontalHeaderLabels(['label2'])
|
||
self.tableWidget_label1.setEditTriggers(QTableWidget.NoEditTriggers)
|
||
self.tableWidget_label2.setEditTriggers(QTableWidget.NoEditTriggers)
|
||
self.tableWidget_label1.horizontalHeader().setStretchLastSection(True)
|
||
self.tableWidget_label1.horizontalHeader().setSectionResizeMode(1)
|
||
self.tableWidget_label2.horizontalHeader().setStretchLastSection(True)
|
||
self.tableWidget_label2.horizontalHeader().setSectionResizeMode(1)
|
||
|
||
# 槽函数连接控件初始化
|
||
self.pushButton_savepath_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_rootpath_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_data1path_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_data2path_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_label1path_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_label2path_open.clicked.connect(self.slot_btn_selectPath)
|
||
self.pushButton_dataInput.clicked.connect(self.slot_btn_dataInput)
|
||
self.pushButton_append.clicked.connect(self.slot_btn_append)
|
||
self.pushButton_outputLabel.clicked.connect(self.slot_btn_outputLabel)
|
||
self.comboBox_choose.currentTextChanged.connect(self.slot_comboBox_textChanged)
|
||
self.radioButton_inputMode_auto.clicked.connect(self.slot_radioBtn_inputMode_auto)
|
||
self.radioButton_inputMode_manual.clicked.connect(self.slot_radioBtn_inputMode_manual)
|
||
self.pushButton_left_move.clicked.connect(self.slot_btn_left_move)
|
||
self.pushButton_pause.clicked.connect(self.slot_btn_pause)
|
||
self.pushButton_right_move.clicked.connect(self.slot_btn_right_move)
|
||
self.radioButton_move_preset_1.clicked.connect(self.slot_radioBtn_move_preset_1)
|
||
self.radioButton_move_preset_2.clicked.connect(self.slot_radioBtn_move_preset_2)
|
||
self.radioButton_move_preset_3.clicked.connect(self.slot_radioBtn_move_preset_3)
|
||
self.radioButton_move_custom.clicked.connect(self.slot_radioBtn_move_custom)
|
||
self.tableWidget_label1.cellDoubleClicked.connect(self.slot_tableWidget_1_on_cell_double_clicked)
|
||
self.tableWidget_label2.cellDoubleClicked.connect(self.slot_tableWidget_2_on_cell_double_clicked)
|
||
|
||
# 初始化按钮快捷键
|
||
self.pushButton_left_move.setShortcut(QCoreApplication.translate("MainWindow", "A"))
|
||
self.pushButton_pause.setShortcut(QCoreApplication.translate("MainWindow", "S"))
|
||
self.pushButton_right_move.setShortcut(QCoreApplication.translate("MainWindow", "D"))
|
||
|
||
# 界面控件状态初始化
|
||
self.lineEdit_rootpath.setEnabled(False)
|
||
self.lineEdit_savepath.setEnabled(False)
|
||
self.lineEdit_data1path.setEnabled(False)
|
||
self.lineEdit_data2path.setEnabled(False)
|
||
self.lineEdit_label1path.setEnabled(False)
|
||
self.lineEdit_label2path.setEnabled(False)
|
||
self.label_choose.setEnabled(False)
|
||
self.comboBox_choose.setEnabled(False)
|
||
self.groupBox_save.setEnabled(False)
|
||
self.groupBox_data1.setEnabled(False)
|
||
self.groupBox_data2.setEnabled(False)
|
||
self.groupBox_label1.setEnabled(False)
|
||
self.groupBox_label2.setEnabled(False)
|
||
self.groupBox_autoplay.setEnabled(False)
|
||
self.groupBox_labelDisplay.setEnabled(False)
|
||
self.radioButton_inputMode_auto.setChecked(True)
|
||
self.radioButton_data1_fillterMode.setChecked(True)
|
||
self.radioButton_data2_fillterMode.setChecked(True)
|
||
self.radioButton_move_preset_1.setChecked(True)
|
||
self.pushButton_outputLabel.setEnabled(False)
|
||
self.figToolbar.action_Label_Single.setEnabled(False)
|
||
self.figToolbar.action_Label_Multiple.setEnabled(False)
|
||
for action in self.figToolbar._actions.values():
|
||
action.setEnabled(False)
|
||
|
||
# 消息弹窗初始化
|
||
self.msgBox = QMessageBox()
|
||
self.msgBox.setWindowTitle("消息")
|
||
|
||
# 自动播放参数初始化
|
||
self.autoplay_mode = "pause"
|
||
self.autoplay_moveLength = int(self.label_moveLength_preset_1.text())
|
||
self.autoplay_maxRange = int(self.label_maxRange_preset_1.text())
|
||
self.autoplay_moveSpeed = int(self.label_moveSpeed_preset_1.text())
|
||
|
||
# 初始化自动播放定时器
|
||
self.timer_autoplay = QTimer()
|
||
self.timer_autoplay.timeout.connect(self.autoplay_move_xlim)
|
||
|
||
# 绘图工具初始化
|
||
self.points_y = np.array([])
|
||
self.temp_point = None
|
||
self.is_left_button_pressed = False
|
||
self.is_right_button_pressed = False
|
||
|
||
# 定义验证器,用于规范lineEdit的输入内容
|
||
validator_double = QDoubleValidator(-1e100, 1e100, 10)
|
||
validator_integer = QIntValidator(-2**31, 2**31 - 1)
|
||
self.lineEdit_data1_fillterNum.setValidator(validator_integer)
|
||
self.lineEdit_data2_fillterNum.setValidator(validator_integer)
|
||
self.lineEdit_data1_fillterFrequency_min.setValidator(validator_double)
|
||
self.lineEdit_data1_fillterFrequency_max.setValidator(validator_double)
|
||
self.lineEdit_data2_fillterFrequency_min.setValidator(validator_double)
|
||
self.lineEdit_data2_fillterFrequency_max.setValidator(validator_double)
|
||
self.lineEdit_frequency.setValidator(validator_double)
|
||
self.lineEdit_moveLength.setValidator(validator_integer)
|
||
self.lineEdit_maxRange.setValidator(validator_double)
|
||
self.lineEdit_moveSpeed.setValidator(validator_integer)
|
||
self.lineEdit_findpeaks_min_interval.setValidator(validator_integer)
|
||
self.lineEdit_findpeaks_min_height.setValidator(validator_double)
|
||
|
||
self.textBrowser_update("程序启动成功,导入数据以开始任务")
|
||
info("Program Started.")
|
||
|
||
def slot_btn_selectPath(self):
|
||
fileDialog = QFileDialog()
|
||
if self.sender() == self.pushButton_rootpath_open:
|
||
fileDialog.setFileMode(QFileDialog.Directory)
|
||
fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
|
||
if fileDialog.exec_() == QFileDialog.Accepted:
|
||
file_path = fileDialog.selectedFiles()[0]
|
||
if not file_path:
|
||
error("Root Path not Exist...")
|
||
self.textBrowser_update("操作:根目录路径输入错误")
|
||
self.msgBox.setText("根目录路径输入错误")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
|
||
self.lineEdit_rootpath.setText(file_path)
|
||
file_path = Path(file_path)
|
||
info("Loading Root Path...")
|
||
self.data_and_label_Path = file_path / "label"
|
||
|
||
if self.data_and_label_Path.exists():
|
||
# 检查路径下的hecg.txt和hRpeak.txt文件是否一一对应
|
||
data_files = sorted(self.data_and_label_Path.glob('*ecg.txt'))
|
||
label_files = sorted(self.data_and_label_Path.glob('*Rpeak.txt'))
|
||
if len(data_files) != len(label_files):
|
||
error("Data Quantity not Match...")
|
||
self.textBrowser_update(f"操作:数据导入错误,ecg.txt和Rpeak.txt文件数量不匹配,分别是{str(len(data_files))}和{str(len(label_files))}")
|
||
self.msgBox.setText(f"数据导入错误,ecg.txt和Rpeak.txt文件数量不匹配,分别是{str(len(data_files))}和{str(len(label_files))}")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
for data_file, label_file in zip(data_files, label_files):
|
||
if data_file.stem.replace('ecg', '') != label_file.stem.replace('Rpeak', ''):
|
||
self.textBrowser_update(f"操作:数据导入错误,文件名称存在不匹配,分别是{str(data_file.stem)}和{str(label_file.stem)}")
|
||
self.msgBox.setText(f"操作:数据导入错误,文件名称存在不匹配,分别是{str(data_file.stem)}和{str(label_file.stem)}")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
nums_for_comboBox = np.array([])
|
||
for data_file in data_files:
|
||
prefix = data_file.stem.replace('ecg', '')
|
||
nums_for_comboBox = np.append(nums_for_comboBox, prefix)
|
||
nums_for_comboBox = nums_for_comboBox.astype(np.int8)
|
||
nums_for_comboBox.sort()
|
||
nums_for_comboBox = nums_for_comboBox.astype(str)
|
||
self.comboBox_choose.clear()
|
||
self.comboBox_choose.addItems(nums_for_comboBox)
|
||
self.label_choose.setEnabled(True)
|
||
self.comboBox_choose.setEnabled(True)
|
||
self.groupBox_save.setEnabled(True)
|
||
self.groupBox_data1.setEnabled(True)
|
||
self.groupBox_data2.setEnabled(True)
|
||
self.groupBox_label1.setEnabled(True)
|
||
self.groupBox_label2.setEnabled(True)
|
||
|
||
# 更新数据路径文本框
|
||
self.lineEdit_savepath.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "location_J.txt")
|
||
self.lineEdit_data1path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "ecg.txt")
|
||
self.lineEdit_data2path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "ecg.txt")
|
||
self.lineEdit_label1path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "Rpeak.txt")
|
||
self.lineEdit_label2path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "Rpeak.txt")
|
||
|
||
info("Successfully Loaded Root Path.")
|
||
self.textBrowser_update("操作:根目录路径选择成功")
|
||
else:
|
||
info("Failed to Load Root Path.")
|
||
self.textBrowser_update("操作:根目录路径选择错误,缺乏必要数据文件夹")
|
||
self.msgBox.setText("根目录路径选择错误,缺乏必要数据文件夹")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
return
|
||
else:
|
||
info("Canceled Loading Root Path.")
|
||
self.textBrowser_update("提示:根目录路径选择取消")
|
||
self.msgBox.setText("根目录路径选择取消")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
elif self.sender() == self.pushButton_savepath_open:
|
||
fileDialog.setFileMode(QFileDialog.Directory)
|
||
fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
|
||
if fileDialog.exec_() == QFileDialog.Accepted:
|
||
file_path = fileDialog.selectedFiles()[0]
|
||
self.lineEdit_savepath.setText(str(Path(file_path) / "newlocation_J.txt"))
|
||
info("Successfully Loaded Save Path.")
|
||
self.textBrowser_update("操作:保存路径选择成功")
|
||
else:
|
||
info("Canceled Loading Save Path.")
|
||
self.textBrowser_update("提示:保存路径选择取消")
|
||
self.msgBox.setText("保存路径选择取消")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
else:
|
||
fileDialog.setFileMode(QFileDialog.ExistingFile)
|
||
fileDialog.setOption(QFileDialog.ReadOnly, True)
|
||
fileDialog.setNameFilter("Text Files (*.txt)")
|
||
if fileDialog.exec_() == QFileDialog.Accepted:
|
||
file_path = fileDialog.selectedFiles()[0]
|
||
if self.sender() == self.pushButton_data1path_open:
|
||
self.lineEdit_data1path.setText(file_path)
|
||
elif self.sender() == self.pushButton_data2path_open:
|
||
self.lineEdit_data2path.setText(file_path)
|
||
elif self.sender() == self.pushButton_label1path_open:
|
||
self.lineEdit_label1path.setText(file_path)
|
||
elif self.sender() == self.pushButton_label2path_open:
|
||
self.lineEdit_label2path.setText(file_path)
|
||
info("Successfully Loaded Data Path.")
|
||
self.textBrowser_update("操作:数据路径选择成功")
|
||
else:
|
||
info("Canceled Loading Data Path.")
|
||
self.textBrowser_update("提示:数据路径选择取消")
|
||
self.msgBox.setText("数据路径选择取消")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
|
||
def slot_btn_dataInput(self):
|
||
if self.lineEdit_data1path.text() != "" or self.lineEdit_data2path.text() != "" or self.lineEdit_label1path.text() != "" or self.lineEdit_label2path.text() != "" or self.lineEdit_savepath.text() != "":
|
||
info("Inputing Data...")
|
||
self.textBrowser_update("提示:开始导入数据")
|
||
|
||
# 导入数据
|
||
self.label1 = np.array([])
|
||
self.label2 = np.array([])
|
||
file = open(str(self.lineEdit_data1path.text()), 'r')
|
||
self.data1 = file.readlines()
|
||
self.data1 = list(map(float, self.data1))
|
||
self.data2 = self.data1
|
||
self.data1 = self.data_preprocess(self.data1, int(self.lineEdit_data1_fillterNum.text()),
|
||
float(self.lineEdit_data1_fillterFrequency_min.text()),
|
||
float(self.lineEdit_data1_fillterFrequency_max.text()),
|
||
float(self.lineEdit_frequency.text()))
|
||
self.data2 = self.data_preprocess(self.data2, int(self.lineEdit_data2_fillterNum.text()),
|
||
float(self.lineEdit_data2_fillterFrequency_min.text()),
|
||
float(self.lineEdit_data2_fillterFrequency_max.text()),
|
||
float(self.lineEdit_frequency.text()))
|
||
file = open(str(self.lineEdit_label1path.text()), 'r')
|
||
self.label1 = np.array(file.readlines())
|
||
self.label2 = self.label1
|
||
self.data1 = self.data1.astype(np.float64)
|
||
self.data2 = self.data2.astype(np.float64)
|
||
self.label1 = self.label1.astype(np.int64)
|
||
self.label2 = self.label2.astype(np.int64)
|
||
self.points_y_1 = [self.data1[x] for x in self.label1]
|
||
self.points_y_2 = [self.data2[x] for x in self.label2]
|
||
|
||
# 更新tableWidget
|
||
self.tableWidget_label1.setRowCount(len(self.label1))
|
||
for row, value in enumerate(self.label1):
|
||
item = QTableWidgetItem(str(value).strip())
|
||
self.tableWidget_label1.setItem(row, 0, item)
|
||
self.tableWidget_label2.setRowCount(self.label2.__len__())
|
||
for row, value in enumerate(self.label2):
|
||
item = QTableWidgetItem(str(value).strip())
|
||
self.tableWidget_label2.setItem(row, 0, item)
|
||
self.groupBox_labelDisplay.setEnabled(True)
|
||
|
||
# 更新界面
|
||
self.groupBox_inputSetting.setEnabled(False)
|
||
self.groupBox_autoplay.setEnabled(True)
|
||
self.pushButton_outputLabel.setEnabled(True)
|
||
self.figToolbar.action_Label_Single.setEnabled(True)
|
||
self.figToolbar.action_Label_Multiple.setEnabled(True)
|
||
MainWindow.setWindowTitle(self, QCoreApplication.translate("MainWindow",
|
||
"ECG_Label_Check - Data Path: " + self.lineEdit_rootpath.text()))
|
||
for action in self.figToolbar._actions.values():
|
||
action.setEnabled(True)
|
||
|
||
# 更新信息
|
||
self.label_data1_length.setText(str(len(self.data1)))
|
||
self.label_data2_length.setText(str(len(self.data2)))
|
||
self.label_label1_length.setText(str(len(self.label1)))
|
||
self.label_label2_length.setText(str(len(self.label2)))
|
||
|
||
# 更新画框
|
||
self.figure.clear()
|
||
self.plot_data_and_label()
|
||
|
||
# 连接画框中的槽函数
|
||
# Connect mouse events
|
||
self.canvas.mpl_connect('motion_notify_event', self.on_motion)
|
||
|
||
# 保存路径文件是否存在的检查
|
||
if not Path(self.lineEdit_savepath.text()).exists():
|
||
Path(self.lineEdit_savepath.text()).touch()
|
||
np.savetxt(Path(self.lineEdit_savepath.text()), self.label2, fmt='%d', newline='\n')
|
||
|
||
info("Finished Input Data.")
|
||
self.textBrowser_update("提示:导入数据完成")
|
||
else:
|
||
info("Failed to Input Data!")
|
||
self.textBrowser_update("操作:导入数据失败")
|
||
self.msgBox.setText("导入失败,请正确输入路径")
|
||
self.msgBox.setIcon(QMessageBox.Critical)
|
||
self.msgBox.exec()
|
||
|
||
def slot_radioBtn_inputMode_auto(self):
|
||
self.pushButton_rootpath_open.setEnabled(True)
|
||
self.label_choose.setEnabled(False)
|
||
self.comboBox_choose.setEnabled(False)
|
||
self.groupBox_save.setEnabled(False)
|
||
self.groupBox_data1.setEnabled(False)
|
||
self.groupBox_data2.setEnabled(False)
|
||
self.groupBox_label1.setEnabled(False)
|
||
self.groupBox_label2.setEnabled(False)
|
||
self.lineEdit_savepath.setText("")
|
||
self.lineEdit_rootpath.setText("")
|
||
self.lineEdit_data1path.setText("")
|
||
self.lineEdit_data2path.setText("")
|
||
self.lineEdit_label1path.setText("")
|
||
self.lineEdit_label2path.setText("")
|
||
self.comboBox_choose.clear()
|
||
info("Switched to inputMode_auto.")
|
||
self.textBrowser_update("操作:切换到输入模式-自动识别")
|
||
|
||
def slot_radioBtn_inputMode_manual(self):
|
||
self.pushButton_rootpath_open.setEnabled(False)
|
||
self.label_choose.setEnabled(False)
|
||
self.comboBox_choose.setEnabled(False)
|
||
self.groupBox_save.setEnabled(True)
|
||
self.groupBox_data1.setEnabled(True)
|
||
self.groupBox_data2.setEnabled(True)
|
||
self.groupBox_label1.setEnabled(True)
|
||
self.groupBox_label2.setEnabled(True)
|
||
self.lineEdit_rootpath.setText("")
|
||
self.comboBox_choose.clear()
|
||
info("Switched to inputMode_manual.")
|
||
self.textBrowser_update("操作:切换到输入模式-手动输入")
|
||
|
||
def slot_comboBox_textChanged(self):
|
||
# 更新数据路径文本框
|
||
self.lineEdit_savepath.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "location_J.txt")
|
||
self.lineEdit_data1path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "ecg.txt")
|
||
self.lineEdit_data2path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "ecg.txt")
|
||
self.lineEdit_label1path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "Rpeak.txt")
|
||
self.lineEdit_label2path.setText(
|
||
str(self.data_and_label_Path) + "\\" + self.comboBox_choose.currentText() + "Rpeak.txt")
|
||
|
||
def slot_btn_left_move(self):
|
||
self.autoplay_mode = "left"
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[1])
|
||
if self.autoplay_xlim_end > self.data2.__len__():
|
||
self.autoplay_xlim_start = int(self.data2.__len__() - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.data2.__len__())
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Started Autoplay left_mode.")
|
||
self.textBrowser_update("操作:开始自动播放-向左")
|
||
|
||
def slot_btn_pause(self):
|
||
self.autoplay_mode = "pause"
|
||
self.timer_autoplay.stop()
|
||
info("Paused Autoplay.")
|
||
self.textBrowser_update("操作:暂停自动播放")
|
||
|
||
def slot_btn_right_move(self):
|
||
self.autoplay_mode = "right"
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[0])
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + self.autoplay_maxRange)
|
||
if self.autoplay_xlim_start < 0:
|
||
self.autoplay_xlim_start = 0
|
||
self.autoplay_xlim_end = 0 + self.autoplay_maxRange
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Started Autoplay right_mode.")
|
||
self.textBrowser_update("操作:开始自动播放-向右")
|
||
|
||
def slot_radioBtn_move_preset_1(self):
|
||
self.autoplay_moveLength = int(self.label_moveLength_preset_1.text())
|
||
self.autoplay_maxRange = int(self.label_maxRange_preset_1.text())
|
||
self.autoplay_moveSpeed = int(self.label_moveSpeed_preset_1.text())
|
||
if self.autoplay_mode != "pause":
|
||
if self.autoplay_mode == "right":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[0])
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + self.autoplay_maxRange)
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
elif self.autoplay_mode == "left":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[1])
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Switched to Autoplay preset_1.")
|
||
self.textBrowser_update("操作:切换到自动播放-预设1")
|
||
|
||
def slot_radioBtn_move_preset_2(self):
|
||
self.autoplay_moveLength = int(self.label_moveLength_preset_2.text())
|
||
self.autoplay_maxRange = int(self.label_maxRange_preset_2.text())
|
||
self.autoplay_moveSpeed = int(self.label_moveSpeed_preset_2.text())
|
||
if self.autoplay_mode != "pause":
|
||
if self.autoplay_mode == "right":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[0])
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + self.autoplay_maxRange)
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
elif self.autoplay_mode == "left":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[1])
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Switched to Autoplay preset_2.")
|
||
self.textBrowser_update("操作:切换到自动播放-预设2")
|
||
|
||
def slot_radioBtn_move_preset_3(self):
|
||
self.autoplay_moveLength = int(self.label_moveLength_preset_3.text())
|
||
self.autoplay_maxRange = int(self.label_maxRange_preset_3.text())
|
||
self.autoplay_moveSpeed = int(self.label_moveSpeed_preset_3.text())
|
||
if self.autoplay_mode != "pause":
|
||
if self.autoplay_mode == "right":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[0])
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + self.autoplay_maxRange)
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
elif self.autoplay_mode == "left":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[1])
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Switched to Autoplay preset_3.")
|
||
self.textBrowser_update("操作:切换到自动播放-预设3")
|
||
|
||
def slot_radioBtn_move_custom(self):
|
||
self.autoplay_moveLength = int(self.lineEdit_moveLength.text())
|
||
self.autoplay_maxRange = int(self.lineEdit_maxRange.text())
|
||
self.autoplay_moveSpeed = int(self.lineEdit_moveSpeed.text())
|
||
if self.autoplay_mode != "pause":
|
||
if self.autoplay_mode == "right":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[0])
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[0] + self.autoplay_maxRange)
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
elif self.autoplay_mode == "left":
|
||
self.autoplay_xlim_start = int(self.ax0.get_xlim()[1] - self.autoplay_maxRange)
|
||
self.autoplay_xlim_end = int(self.ax0.get_xlim()[1])
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
self.timer_autoplay.start(self.autoplay_moveSpeed)
|
||
info("Switched to Autoplay custom.")
|
||
self.textBrowser_update("操作:切换到自动播放-自定义")
|
||
self.msgBox.setText("自定义的输入参数未做任何检查,请斟酌输入参数,否则可能会导致程序异常")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
|
||
def slot_tableWidget_1_on_cell_double_clicked(self, row, column):
|
||
x = float(self.tableWidget_label1.item(row, column).text())
|
||
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='black', shrink=0.1))
|
||
self.canvas.draw()
|
||
info(f"Jumped to x_axis: {str(int(x))}.")
|
||
self.textBrowser_update(f"操作:跳转到x坐标: {str(int(x))}")
|
||
|
||
def slot_tableWidget_2_on_cell_double_clicked(self, row, column):
|
||
x = float(self.tableWidget_label2.item(row, column).text())
|
||
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='black', shrink=0.1))
|
||
self.canvas.draw()
|
||
info(f"Jumped to x_axis: {str(int(x))}.")
|
||
self.textBrowser_update(f"操作:跳转到x坐标: {str(int(x))}")
|
||
|
||
def slot_btn_append(self):
|
||
dialog = CustomMessageBox(self)
|
||
reply = dialog.exec_()
|
||
if reply == QDialog.Accepted:
|
||
if self.lineEdit_rootpath.text() == "" or len(list((Path(self.lineEdit_rootpath.text()) / "label").rglob("*location_J.txt"))) == 0:
|
||
info(f"*location_J.txt Files not Exist in the Directory.")
|
||
self.textBrowser_update(f"错误:无法进行<片段合并>")
|
||
self.msgBox.setText(f"目录下不存在*location_J.txt文件")
|
||
self.msgBox.setIcon(QMessageBox.Warning)
|
||
self.msgBox.exec()
|
||
return
|
||
final_Rpeak = append.Rpeak_Detection(self.lineEdit_rootpath.text(), int(dialog.lineEdit_fs.text()), int(dialog.lineEdit_th1.text()))
|
||
pd.DataFrame(final_Rpeak).to_csv(Path(self.lineEdit_rootpath.text()) / "final_Rpeak.txt", index=False, header=None)
|
||
info(f"Saved final_Rpeak to Directory {self.lineEdit_rootpath.text()}.")
|
||
self.textBrowser_update(f"提示:保存final_Rpeak.txt成功至文件夹{self.lineEdit_rootpath.text()}")
|
||
self.msgBox.setText(f"保存成功至{self.lineEdit_rootpath.text()}")
|
||
self.msgBox.setIcon(QMessageBox.Information)
|
||
self.msgBox.exec()
|
||
else:
|
||
self.textBrowser_update("提示:操作取消")
|
||
|
||
def slot_btn_outputLabel(self):
|
||
np.savetxt(Path(self.lineEdit_savepath.text()), self.label2, fmt='%d', newline='\n')
|
||
info(f"Manually Saved Data to: {str(Path(self.lineEdit_savepath.text()))}.")
|
||
self.textBrowser_update(f"操作:手动保存数据至{str(Path(self.lineEdit_savepath.text()))}")
|
||
|
||
def data_preprocess(self, data, n, f1, f2, fs):
|
||
f1 = f1 / (fs / 2.0)
|
||
f2 = f2 / (fs / 2.0)
|
||
b, a = butter(n, [f1, f2], btype = 'bandpass')
|
||
data = np.array(filtfilt(b, a, data))
|
||
return data
|
||
|
||
def update_tableWidget_and_info(self):
|
||
self.tableWidget_label2.setRowCount(self.label2.__len__())
|
||
for row, value in enumerate(self.label2):
|
||
item = QTableWidgetItem(str(value).strip())
|
||
self.tableWidget_label2.setItem(row, 0, item)
|
||
self.groupBox_labelDisplay.setEnabled(True)
|
||
self.label_label2_length.setText(str(len(self.label2)))
|
||
|
||
def textBrowser_update(self, message):
|
||
self.textBrowser_infoOutput.append(str(datetime.now().strftime("%H:%M:%S")) + ": " + message)
|
||
self.textBrowser_infoOutput.verticalScrollBar().setValue(self.textBrowser_infoOutput.verticalScrollBar().maximum())
|
||
|
||
def autoplay_move_xlim(self):
|
||
if self.autoplay_mode == "left" and self.autoplay_xlim_start < 0:
|
||
self.autoplay_mode = "pause"
|
||
self.timer_autoplay.stop()
|
||
info("Paused Autoplay.")
|
||
self.textBrowser_update("操作:暂停自动播放")
|
||
elif self.autoplay_mode == "right" and self.autoplay_xlim_end > self.data2.__len__():
|
||
self.autoplay_mode = "pause"
|
||
self.timer_autoplay.stop()
|
||
info("Paused Autoplay.")
|
||
self.textBrowser_update("操作:暂停自动播放")
|
||
else:
|
||
if self.autoplay_mode == "right":
|
||
self.autoplay_xlim_start += self.autoplay_moveLength
|
||
self.autoplay_xlim_end += self.autoplay_moveLength
|
||
elif self.autoplay_mode == "left":
|
||
self.autoplay_xlim_start -= self.autoplay_moveLength
|
||
self.autoplay_xlim_end -= self.autoplay_moveLength
|
||
self.ax0.set_xlim(self.autoplay_xlim_start, self.autoplay_xlim_end)
|
||
self.canvas.draw()
|
||
|
||
def plot_data_and_label(self, ax_top=True, ax_bottom=True, ax_left=True, ax_right=True):
|
||
self.ax0 = self.figure.add_subplot(self.gs[0])
|
||
self.ax0.plot(self.data1, label="data1", color='b')
|
||
self.ax0.plot(self.label1, self.points_y_1, 'ro', label='label1')
|
||
self.ax0 = plt.gca()
|
||
self.ax0.grid(True)
|
||
self.ax0.spines["top"].set_visible(ax_top)
|
||
self.ax0.spines["right"].set_visible(ax_right)
|
||
self.ax0.spines["bottom"].set_visible(ax_bottom)
|
||
self.ax0.spines["left"].set_visible(ax_left)
|
||
self.ax0.tick_params(axis='x', colors='white')
|
||
self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}"))
|
||
|
||
self.ax1 = self.figure.add_subplot(self.gs[1], sharex=self.ax0, sharey=self.ax0)
|
||
self.line_data2, = self.ax1.plot(self.data2, label="data2", color='b')
|
||
self.point_label2, = self.ax1.plot(self.label2, self.points_y_2, 'ro', label='label2')
|
||
self.ax1 = plt.gca()
|
||
self.ax1.grid(True)
|
||
self.ax1.spines["top"].set_visible(ax_top)
|
||
self.ax1.spines["right"].set_visible(ax_right)
|
||
self.ax1.spines["bottom"].set_visible(ax_bottom)
|
||
self.ax1.spines["left"].set_visible(ax_left)
|
||
|
||
self.ax0.autoscale(False)
|
||
self.ax0.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax))
|
||
|
||
def toggle_home(self):
|
||
if self.timer_autoplay.isActive() == True:
|
||
self.autoplay_mode = "pause"
|
||
self.timer_autoplay.stop()
|
||
info("Paused Autoplay.")
|
||
self.textBrowser_update("操作:暂停自动播放")
|
||
self.ax0.autoscale(True)
|
||
self.ax0.relim()
|
||
self.ax0.autoscale_view()
|
||
self.canvas.draw()
|
||
self.ax0.autoscale(False)
|
||
self.textBrowser_update("操作:尺度恢复")
|
||
|
||
def toggle_changeLabel_Single_mode(self, state):
|
||
if state:
|
||
self.deactivate_figToolbar_buttons()
|
||
self.figToolbar.action_Label_Single.setChecked(True)
|
||
self.figToolbar.action_Label_Multiple.setChecked(False)
|
||
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)
|
||
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
|
||
|
||
def toggle_changeLabel_Multiple_mode(self, state):
|
||
if state:
|
||
self.deactivate_figToolbar_buttons()
|
||
self.figToolbar.action_Label_Single.setChecked(False)
|
||
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_Single.isChecked():
|
||
if event.button == 1:
|
||
self.is_left_button_pressed = True
|
||
self.add_temp_point(event)
|
||
elif event.button == 3:
|
||
self.is_right_button_pressed = True
|
||
self.remove_temp_point(event)
|
||
elif self.figToolbar.action_Label_Multiple.isChecked():
|
||
if event.button == 1 or event.button == 3: # 左键或右键
|
||
if event.button == 1:
|
||
self.is_left_button_pressed = True
|
||
elif event.button == 3:
|
||
self.is_right_button_pressed = True
|
||
self.figToolbar.rect_start_x = event.xdata
|
||
# 如果矩形patch已存在,先移除
|
||
if self.figToolbar.rect_patch is not None:
|
||
self.figToolbar.rect_patch.remove()
|
||
self.figToolbar.rect_patch = None
|
||
self.canvas.draw()
|
||
|
||
def on_release(self, event):
|
||
if self.figToolbar.action_Label_Single.isChecked():
|
||
if event.button == 1 and self.is_left_button_pressed:
|
||
self.is_left_button_pressed = False
|
||
if self.temp_point:
|
||
self.label2 = np.append(self.label2, self.temp_point[0])
|
||
self.points_y_2 = np.append(self.points_y_2, self.temp_point[1])
|
||
info(f"Added point: ({str(self.temp_point[0])},{str(self.temp_point[1])}).")
|
||
self.textBrowser_update(f"操作:新增点:({str(self.temp_point[0])},{str(self.temp_point[1])})")
|
||
self.temp_point = None
|
||
self.redraw_plot()
|
||
elif event.button == 3 and self.is_right_button_pressed:
|
||
self.is_right_button_pressed = False
|
||
if self.temp_point:
|
||
distances = np.abs(np.array(self.label2) - self.temp_point[0])
|
||
if len(distances) > 0:
|
||
idx = distances.argmin()
|
||
info(f"Removed point {idx + 1}: ({str(self.label2[idx])},{str(self.points_y_2[idx])}).")
|
||
self.textBrowser_update(f"操作:删除第{idx + 1}点:({str(self.label2[idx])},{str(self.points_y_2[idx])})")
|
||
self.label2 = np.delete(self.label2, idx)
|
||
self.points_y_2 = np.delete(self.points_y_2, idx)
|
||
self.temp_point = None
|
||
self.redraw_plot()
|
||
self.label2.sort()
|
||
self.points_y_2 = [self.data2[x] for x in self.label2]
|
||
self.update_tableWidget_and_info()
|
||
np.savetxt(Path(self.lineEdit_savepath.text()), self.label2, fmt='%d', newline='\n')
|
||
elif self.figToolbar.action_Label_Multiple.isChecked():
|
||
if self.figToolbar.rect_start_x is not None:
|
||
self.figToolbar.rect_end_x = event.xdata
|
||
if self.figToolbar.rect_start_x is not None and self.figToolbar.rect_end_x is not None:
|
||
if self.figToolbar.rect_start_x < self.figToolbar.rect_end_x:
|
||
rect_left = self.figToolbar.rect_start_x
|
||
rect_right = self.figToolbar.rect_end_x
|
||
elif self.figToolbar.rect_start_x > self.figToolbar.rect_end_x:
|
||
rect_left = self.figToolbar.rect_end_x
|
||
rect_right = self.figToolbar.rect_start_x
|
||
else:
|
||
rect_left = self.figToolbar.rect_start_x
|
||
rect_right = self.figToolbar.rect_start_x
|
||
else:
|
||
rect_left = self.figToolbar.rect_start_x
|
||
rect_right = self.figToolbar.rect_start_x
|
||
if event.button == 1 and self.is_left_button_pressed:
|
||
self.is_left_button_pressed = False
|
||
if rect_left < 0:
|
||
rect_left = 0
|
||
if rect_right >= len(self.data2):
|
||
rect_right = len(self.data2) - 1
|
||
selected_area_for_add_points = self.data2[int(rect_left):int(rect_right)]
|
||
peaks_idx, _ = find_peaks(selected_area_for_add_points, height=float(self.lineEdit_findpeaks_min_height.text()), distance=float(self.lineEdit_findpeaks_min_interval.text()))
|
||
peaks_idx = peaks_idx + int(rect_left)
|
||
if len(peaks_idx) != 0:
|
||
info(f"Added {len(peaks_idx)} points.")
|
||
self.textBrowser_update(f"操作:新增{len(peaks_idx)}个点")
|
||
else:
|
||
info(f"No point added in selected area.")
|
||
self.textBrowser_update(f"操作:所选区间内无新增点")
|
||
self.label2 = np.append(self.label2, peaks_idx)
|
||
self.points_y_2 = np.append(self.points_y_2, self.data2[peaks_idx])
|
||
self.redraw_plot()
|
||
elif event.button == 3 and self.is_right_button_pressed:
|
||
self.is_right_button_pressed = False
|
||
left_label2_to_delete = self.label2 - rect_left
|
||
right_label2_to_delete = self.label2 - rect_right
|
||
self.left_label2_to_delete_idx = len(left_label2_to_delete[left_label2_to_delete < 0])
|
||
self.right_label2_to_delete_idx = len(right_label2_to_delete[right_label2_to_delete < 0])
|
||
if self.left_label2_to_delete_idx != self.right_label2_to_delete_idx:
|
||
info(f"Removed points from {self.left_label2_to_delete_idx + 1} to {self.right_label2_to_delete_idx}.")
|
||
self.textBrowser_update(f"操作:删除第{self.left_label2_to_delete_idx + 1}到{self.right_label2_to_delete_idx}点")
|
||
else:
|
||
info(f"No point to delete in selected area.")
|
||
self.textBrowser_update(f"操作:所选区间内无删除点")
|
||
self.label2 = np.delete(self.label2, np.arange(self.left_label2_to_delete_idx, self.right_label2_to_delete_idx))
|
||
self.points_y_2 = np.delete(self.points_y_2, np.arange(self.left_label2_to_delete_idx, self.right_label2_to_delete_idx))
|
||
self.redraw_plot()
|
||
self.figToolbar.rect_start_x = None
|
||
self.figToolbar.rect_end_x = None
|
||
self.label2.sort()
|
||
self.points_y_2 = [self.data2[x] for x in self.label2]
|
||
self.update_tableWidget_and_info()
|
||
np.savetxt(Path(self.lineEdit_savepath.text()), self.label2, fmt='%d', newline='\n')
|
||
# 移除矩形patch
|
||
if self.figToolbar.rect_patch is not None:
|
||
self.figToolbar.rect_patch.remove()
|
||
self.figToolbar.rect_patch = None
|
||
self.canvas.draw()
|
||
|
||
def on_hold(self, event):
|
||
if self.figToolbar.rect_start_x is not None and event.xdata is not None:
|
||
self.figToolbar.rect_end_x = event.xdata
|
||
|
||
# 如果矩形patch不存在,则创建一个新的
|
||
if self.figToolbar.rect_patch is None:
|
||
if self.is_left_button_pressed:
|
||
self.figToolbar.rect_patch = plt.Rectangle((0, 0), 1, 1, fill=True, alpha=0.2, color='#ff00ff')
|
||
elif self.is_right_button_pressed:
|
||
self.figToolbar.rect_patch = plt.Rectangle((0, 0), 1, 1, fill=True, alpha=0.2, color='r')
|
||
self.ax1.add_patch(self.figToolbar.rect_patch)
|
||
|
||
# 更新矩形patch的位置和大小
|
||
x_start = self.figToolbar.rect_start_x
|
||
x_end = self.figToolbar.rect_end_x
|
||
y_min, y_max = self.ax1.get_ylim()
|
||
self.figToolbar.rect_patch.set_xy((min(x_start, x_end), y_min))
|
||
self.figToolbar.rect_patch.set_width(abs(x_end - x_start))
|
||
self.figToolbar.rect_patch.set_height(y_max - y_min)
|
||
|
||
self.canvas.draw()
|
||
|
||
def on_motion(self, event):
|
||
if event.inaxes:
|
||
# Clear previous reference lines and temporary points
|
||
for line in self.ax0.lines[1:]:
|
||
if line.get_label() == 'vline' or line.get_label() == 'hline' or line.get_label() == 'temp_point':
|
||
line.remove()
|
||
for line in self.ax1.lines[1:]:
|
||
if line.get_label() == 'vline' or line.get_label() == 'hline' or line.get_label() == 'temp_point':
|
||
line.remove()
|
||
|
||
# Draw vertical and horizontal reference lines
|
||
self.ax0.axvline(event.xdata, color='gray', linestyle='--', label='vline')
|
||
self.ax0.axhline(event.ydata, color='gray', linestyle='--', label='hline')
|
||
self.ax1.axvline(event.xdata, color='gray', linestyle='--', label='vline')
|
||
self.ax1.axhline(event.ydata, color='gray', linestyle='--', label='hline')
|
||
|
||
# Draw temporary point if left button is pressed
|
||
if self.is_left_button_pressed:
|
||
self.add_temp_point(event)
|
||
elif self.is_right_button_pressed:
|
||
self.remove_temp_point(event)
|
||
|
||
self.canvas.draw()
|
||
|
||
def add_temp_point(self, event):
|
||
if self.figToolbar.action_Label_Single.isChecked():
|
||
# Find the closest x value on the curve
|
||
idx = np.abs(np.arange(len(self.data2)) - event.xdata).argmin()
|
||
x_point = np.arange(len(self.data2))[idx]
|
||
y_point = self.data2[idx]
|
||
|
||
# Store the temporary point
|
||
self.temp_point = (x_point, y_point)
|
||
|
||
# Plot the temporary point
|
||
self.ax1.plot(x_point, y_point, marker='o', color='#ff00ff', label='temp_point')
|
||
self.ax1.plot(x_point, y_point, marker='x', color='#ff00ff', markersize=30, label='temp_point')
|
||
|
||
def remove_temp_point(self, event):
|
||
if self.figToolbar.action_Label_Single.isChecked():
|
||
if self.label2.any():
|
||
# Find the closest point to the current x coordinate
|
||
distances = np.abs(np.array(self.label2) - event.xdata)
|
||
idx = distances.argmin()
|
||
x_point = self.label2[idx]
|
||
y_point = self.points_y_2[idx]
|
||
|
||
# Store the temporary point to be removed
|
||
self.temp_point = (x_point, y_point)
|
||
|
||
# Plot the temporary point to be removed
|
||
self.ax1.plot(x_point, y_point, marker='x', color='r', markersize=30, label='temp_point')
|
||
|
||
def redraw_plot(self):
|
||
self.point_label2.remove()
|
||
self.point_label2, = self.ax1.plot(self.label2, self.points_y_2, 'ro', label='label2')
|
||
self.canvas.draw()
|
||
|
||
def on_xlim_change(self, event_ax):
|
||
try:
|
||
if self.annotation_tableWidget:
|
||
self.annotation_tableWidget.remove()
|
||
except AttributeError:
|
||
pass
|
||
self.annotation_tableWidget = None
|
||
self.canvas.draw()
|
||
|
||
class CustomNavigationToolbar(NavigationToolbar2QT):
|
||
def __init__(self, canvas, parent):
|
||
super().__init__(canvas, parent)
|
||
# 初始化画框工具栏
|
||
self.action_Label_Single = QAction('逐一更改标签(Z)', self)
|
||
self.action_Label_Single.setFont(QFont("黑体", 14))
|
||
self.action_Label_Single.setCheckable(True)
|
||
self.action_Label_Single.setShortcut(QCoreApplication.translate("MainWindow", "Z"))
|
||
self.action_Label_Multiple = QAction('批量更改标签(X)', self)
|
||
self.action_Label_Multiple.setFont(QFont("黑体", 14))
|
||
self.action_Label_Multiple.setCheckable(True)
|
||
self.action_Label_Multiple.setShortcut(QCoreApplication.translate("MainWindow", "X"))
|
||
self.insertAction(self._actions['pan'], self.action_Label_Single)
|
||
self.insertAction(self._actions['pan'], self.action_Label_Multiple)
|
||
|
||
self._actions['pan'].setShortcut(QCoreApplication.translate("MainWindow", "C"))
|
||
|
||
# 用于存储事件连接ID
|
||
self.cid_mouse_press = None
|
||
self.cid_mouse_release = None
|
||
self.cid_mouse_hold = None
|
||
|
||
# 初始化矩形选择区域
|
||
self.rect_start_x = None
|
||
self.rect_end_x = None
|
||
self.rect_patch = 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_Single.isChecked():
|
||
self.action_Label_Single.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
|
||
elif 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
|
||
|
||
class CustomMessageBox(QDialog):
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
|
||
self.resize(300, 300)
|
||
self.setWindowTitle("警告:确认操作")
|
||
layout = QVBoxLayout()
|
||
self.label = QLabel("是否执行<片段合并>?请在执行前确保此份数据的所有片段都已被打标!请输入相应参数后执行任务。")
|
||
self.label1 = QLabel(" ")
|
||
self.label_fs = QLabel("信号采样率(Hz):")
|
||
self.label_th1 = QLabel("寻峰阈值(个):")
|
||
self.lineEdit_fs = QLineEdit()
|
||
self.lineEdit_th1 = QLineEdit()
|
||
|
||
font = QtGui.QFont()
|
||
font.setFamily("黑体")
|
||
font.setPointSize(14)
|
||
self.label.setFont(font)
|
||
self.label1.setFont(font)
|
||
self.label_fs.setFont(font)
|
||
self.label_th1.setFont(font)
|
||
self.lineEdit_fs.setFont(font)
|
||
self.lineEdit_th1.setFont(font)
|
||
|
||
self.label.setWordWrap(True)
|
||
self.label.setFixedWidth(300)
|
||
self.lineEdit_fs.setText("1000")
|
||
self.lineEdit_th1.setText("130")
|
||
|
||
layout.addWidget(self.label)
|
||
layout.addWidget(self.label1)
|
||
layout.addWidget(self.label_fs)
|
||
layout.addWidget(self.lineEdit_fs)
|
||
layout.addWidget(self.label_th1)
|
||
layout.addWidget(self.lineEdit_th1)
|
||
|
||
self.button_box = QDialogButtonBox(QDialogButtonBox.Yes | QDialogButtonBox.No)
|
||
self.button_box.setFont(font)
|
||
self.button_box.accepted.connect(self.accept)
|
||
self.button_box.rejected.connect(self.reject)
|
||
layout.addWidget(self.button_box)
|
||
self.setLayout(layout)
|
||
|
||
# 主函数
|
||
if __name__ == '__main__':
|
||
app = QApplication(sys.argv)
|
||
mainWindow = MainWindow()
|
||
mainWindow.show()
|
||
sys.exit(app.exec_()) |