First commit.

This commit is contained in:
Yorusora 2025-02-10 19:14:14 +08:00
parent c54669da25
commit 634b0eb93a
5 changed files with 4212 additions and 94 deletions

189
.gitignore vendored
View File

@ -1,96 +1,3 @@
# ---> JupyterNotebooks
# gitignore template for Jupyter Notebooks
# website: http://jupyter.org/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
# IPython
profile_default/
ipython_config.py
# Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
@ -253,3 +160,99 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> JupyterNotebooks
# gitignore template for Jupyter Notebooks
# website: http://jupyter.org/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
# IPython
profile_default/
ipython_config.py
# Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/
Label_Check/
logs/*
.idea/*
!./logs

1226
MainWindow.py Normal file

File diff suppressed because it is too large Load Diff

1927
MainWindow.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,16 @@
# ecg_label_check # ecg_label_check——人工纠正R峰标签程序
```
项目作者信息
@author:Yosa
@email:2023025086@m.scnu.edu.cn
@time:2025/2/5
```
本程序提供符合用户直接的操作界面用于人工纠正ECG信号所对应的R峰坐标可逐个或批量对标签进行增加和删除操作。
## TODO
编写说明文档
将批量增删标签中的长按选择区域修改为伴有纵坐标判断选择区域的矩形

948
ecg_label_check.py Normal file
View File

@ -0,0 +1,948 @@
"""
@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 numpy as np
from PyQt5.QtGui import QFont, QDoubleValidator, QIntValidator
from matplotlib.ticker import FuncFormatter
from numpy import load, nan, zeros, append, linspace, place
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
from scipy.signal import butter, filtfilt, find_peaks
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.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_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")
MainWindow.setWindowTitle(self, QCoreApplication.translate("MainWindow",
"Label_Check - Data Path: " + str(file_path)))
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):
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)
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()
info("Finished Input Data.")
self.textBrowser_update("提示:导入数据完成")
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_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)
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
# 主函数
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())