First commit.
This commit is contained in:
parent
c54669da25
commit
634b0eb93a
189
.gitignore
vendored
189
.gitignore
vendored
@ -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
1226
MainWindow.py
Normal file
File diff suppressed because it is too large
Load Diff
1927
MainWindow.ui
Normal file
1927
MainWindow.ui
Normal file
File diff suppressed because it is too large
Load Diff
16
README.md
16
README.md
@ -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
948
ecg_label_check.py
Normal 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_())
|
Loading…
Reference in New Issue
Block a user