commit 54f8cc79f6718657a365068b0dc071a1a10f19e4 Author: Yorusora <2944763079@qq.com> Date: Sat Jan 4 17:33:28 2025 +0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6025788 --- /dev/null +++ b/.gitignore @@ -0,0 +1,259 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.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/ +!data +!history +data/* +history/* +.idea/* \ No newline at end of file diff --git a/MainWindow.py b/MainWindow.py new file mode 100644 index 0000000..68053cc --- /dev/null +++ b/MainWindow.py @@ -0,0 +1,781 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'MainWindow.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(1920, 1080) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout.setObjectName("gridLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout_left = QtWidgets.QVBoxLayout() + self.verticalLayout_left.setObjectName("verticalLayout_left") + self.groupBox_1 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_1.sizePolicy().hasHeightForWidth()) + self.groupBox_1.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_1.setFont(font) + self.groupBox_1.setObjectName("groupBox_1") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_1) + self.gridLayout_4.setObjectName("gridLayout_4") + self.comboBox_sampID = QtWidgets.QComboBox(self.groupBox_1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.comboBox_sampID.sizePolicy().hasHeightForWidth()) + self.comboBox_sampID.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.comboBox_sampID.setFont(font) + self.comboBox_sampID.setObjectName("comboBox_sampID") + self.gridLayout_4.addWidget(self.comboBox_sampID, 2, 1, 1, 1) + self.lineEdit_start_bcg_index = QtWidgets.QLineEdit(self.groupBox_1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_start_bcg_index.sizePolicy().hasHeightForWidth()) + self.lineEdit_start_bcg_index.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_start_bcg_index.setFont(font) + self.lineEdit_start_bcg_index.setObjectName("lineEdit_start_bcg_index") + self.gridLayout_4.addWidget(self.lineEdit_start_bcg_index, 2, 3, 1, 1) + self.label_start_bcg_index = QtWidgets.QLabel(self.groupBox_1) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_start_bcg_index.setFont(font) + self.label_start_bcg_index.setObjectName("label_start_bcg_index") + self.gridLayout_4.addWidget(self.label_start_bcg_index, 2, 2, 1, 1) + self.label_sampID = QtWidgets.QLabel(self.groupBox_1) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_sampID.setFont(font) + self.label_sampID.setObjectName("label_sampID") + self.gridLayout_4.addWidget(self.label_sampID, 2, 0, 1, 1) + self.verticalLayout_left.addWidget(self.groupBox_1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_left.addItem(spacerItem) + self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_2.setFont(font) + self.groupBox_2.setObjectName("groupBox_2") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_2) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.checkBox_OSA = QtWidgets.QCheckBox(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_OSA.sizePolicy().hasHeightForWidth()) + self.checkBox_OSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.checkBox_OSA.setFont(font) + self.checkBox_OSA.setChecked(True) + self.checkBox_OSA.setObjectName("checkBox_OSA") + self.horizontalLayout_2.addWidget(self.checkBox_OSA) + self.checkBox_CSA = QtWidgets.QCheckBox(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_CSA.sizePolicy().hasHeightForWidth()) + self.checkBox_CSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.checkBox_CSA.setFont(font) + self.checkBox_CSA.setChecked(True) + self.checkBox_CSA.setObjectName("checkBox_CSA") + self.horizontalLayout_2.addWidget(self.checkBox_CSA) + self.checkBox_MSA = QtWidgets.QCheckBox(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_MSA.sizePolicy().hasHeightForWidth()) + self.checkBox_MSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.checkBox_MSA.setFont(font) + self.checkBox_MSA.setChecked(True) + self.checkBox_MSA.setObjectName("checkBox_MSA") + self.horizontalLayout_2.addWidget(self.checkBox_MSA) + self.checkBox_HPY = QtWidgets.QCheckBox(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_HPY.sizePolicy().hasHeightForWidth()) + self.checkBox_HPY.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.checkBox_HPY.setFont(font) + self.checkBox_HPY.setChecked(True) + self.checkBox_HPY.setObjectName("checkBox_HPY") + self.horizontalLayout_2.addWidget(self.checkBox_HPY) + self.verticalLayout_left.addWidget(self.groupBox_2) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_left.addItem(spacerItem1) + self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_3.setFont(font) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_frequency = QtWidgets.QLabel(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_frequency.sizePolicy().hasHeightForWidth()) + self.label_frequency.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_frequency.setFont(font) + self.label_frequency.setObjectName("label_frequency") + self.gridLayout_5.addWidget(self.label_frequency, 0, 0, 1, 1) + self.lineEdit_frequency = QtWidgets.QLineEdit(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_frequency.sizePolicy().hasHeightForWidth()) + self.lineEdit_frequency.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_frequency.setFont(font) + self.lineEdit_frequency.setObjectName("lineEdit_frequency") + self.gridLayout_5.addWidget(self.lineEdit_frequency, 0, 1, 1, 1) + self.label_bcg_frequency = QtWidgets.QLabel(self.groupBox_3) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_bcg_frequency.setFont(font) + self.label_bcg_frequency.setObjectName("label_bcg_frequency") + self.gridLayout_5.addWidget(self.label_bcg_frequency, 1, 0, 1, 1) + self.lineEdit_bcg_frequency = QtWidgets.QLineEdit(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_bcg_frequency.sizePolicy().hasHeightForWidth()) + self.lineEdit_bcg_frequency.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_bcg_frequency.setFont(font) + self.lineEdit_bcg_frequency.setObjectName("lineEdit_bcg_frequency") + self.gridLayout_5.addWidget(self.lineEdit_bcg_frequency, 1, 1, 1, 1) + self.label_front_add_second = QtWidgets.QLabel(self.groupBox_3) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_front_add_second.setFont(font) + self.label_front_add_second.setObjectName("label_front_add_second") + self.gridLayout_5.addWidget(self.label_front_add_second, 2, 0, 1, 1) + self.lineEdit_front_add_second = QtWidgets.QLineEdit(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_front_add_second.sizePolicy().hasHeightForWidth()) + self.lineEdit_front_add_second.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_front_add_second.setFont(font) + self.lineEdit_front_add_second.setObjectName("lineEdit_front_add_second") + self.gridLayout_5.addWidget(self.lineEdit_front_add_second, 2, 1, 1, 1) + self.label_back_add_second = QtWidgets.QLabel(self.groupBox_3) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_back_add_second.setFont(font) + self.label_back_add_second.setObjectName("label_back_add_second") + self.gridLayout_5.addWidget(self.label_back_add_second, 3, 0, 1, 1) + self.lineEdit_back_add_second = QtWidgets.QLineEdit(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_back_add_second.sizePolicy().hasHeightForWidth()) + self.lineEdit_back_add_second.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_back_add_second.setFont(font) + self.lineEdit_back_add_second.setObjectName("lineEdit_back_add_second") + self.gridLayout_5.addWidget(self.lineEdit_back_add_second, 3, 1, 1, 1) + self.verticalLayout_left.addWidget(self.groupBox_3) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_left.addItem(spacerItem2) + self.groupBox_4 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_4.sizePolicy().hasHeightForWidth()) + self.groupBox_4.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_4.setFont(font) + self.groupBox_4.setObjectName("groupBox_4") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_4) + self.gridLayout_2.setObjectName("gridLayout_2") + self.lineEdit_remark = QtWidgets.QLineEdit(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_remark.sizePolicy().hasHeightForWidth()) + self.lineEdit_remark.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_remark.setFont(font) + self.lineEdit_remark.setObjectName("lineEdit_remark") + self.gridLayout_2.addWidget(self.lineEdit_remark, 5, 2, 1, 3) + self.pushButton_left = QtWidgets.QPushButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_left.sizePolicy().hasHeightForWidth()) + self.pushButton_left.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.pushButton_left.setFont(font) + self.pushButton_left.setObjectName("pushButton_left") + self.gridLayout_2.addWidget(self.pushButton_left, 15, 0, 1, 1) + self.lineEdit_correctStart = QtWidgets.QLineEdit(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_correctStart.sizePolicy().hasHeightForWidth()) + self.lineEdit_correctStart.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_correctStart.setFont(font) + self.lineEdit_correctStart.setObjectName("lineEdit_correctStart") + self.gridLayout_2.addWidget(self.lineEdit_correctStart, 7, 2, 1, 3) + self.label_correctStart = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_correctStart.sizePolicy().hasHeightForWidth()) + self.label_correctStart.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_correctStart.setFont(font) + self.label_correctStart.setObjectName("label_correctStart") + self.gridLayout_2.addWidget(self.label_correctStart, 7, 0, 1, 1) + self.checkBox_examineLabeled = QtWidgets.QCheckBox(self.groupBox_4) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.checkBox_examineLabeled.setFont(font) + self.checkBox_examineLabeled.setObjectName("checkBox_examineLabeled") + self.gridLayout_2.addWidget(self.checkBox_examineLabeled, 0, 0, 1, 4) + self.label_quick_remark_input = QtWidgets.QLabel(self.groupBox_4) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_quick_remark_input.setFont(font) + self.label_quick_remark_input.setObjectName("label_quick_remark_input") + self.gridLayout_2.addWidget(self.label_quick_remark_input, 6, 0, 1, 1) + self.lineEdit_correctEnd = QtWidgets.QLineEdit(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_correctEnd.sizePolicy().hasHeightForWidth()) + self.lineEdit_correctEnd.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_correctEnd.setFont(font) + self.lineEdit_correctEnd.setObjectName("lineEdit_correctEnd") + self.gridLayout_2.addWidget(self.lineEdit_correctEnd, 9, 2, 1, 3) + self.label_correctEnd = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_correctEnd.sizePolicy().hasHeightForWidth()) + self.label_correctEnd.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_correctEnd.setFont(font) + self.label_correctEnd.setObjectName("label_correctEnd") + self.gridLayout_2.addWidget(self.label_correctEnd, 9, 0, 1, 1) + self.label_BCG_event = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_BCG_event.sizePolicy().hasHeightForWidth()) + self.label_BCG_event.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(12) + font.setBold(False) + font.setWeight(50) + self.label_BCG_event.setFont(font) + self.label_BCG_event.setText("") + self.label_BCG_event.setAlignment(QtCore.Qt.AlignCenter) + self.label_BCG_event.setObjectName("label_BCG_event") + self.gridLayout_2.addWidget(self.label_BCG_event, 2, 0, 1, 5) + self.pushButton_quick_remark_input_waitingForTalk = QtWidgets.QPushButton(self.groupBox_4) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.pushButton_quick_remark_input_waitingForTalk.setFont(font) + self.pushButton_quick_remark_input_waitingForTalk.setObjectName("pushButton_quick_remark_input_waitingForTalk") + self.gridLayout_2.addWidget(self.pushButton_quick_remark_input_waitingForTalk, 6, 2, 1, 3) + self.horizontalLayout_radioButton = QtWidgets.QHBoxLayout() + self.horizontalLayout_radioButton.setObjectName("horizontalLayout_radioButton") + self.label_events = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_events.sizePolicy().hasHeightForWidth()) + self.label_events.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_events.setFont(font) + self.label_events.setObjectName("label_events") + self.horizontalLayout_radioButton.addWidget(self.label_events) + self.radioButton_OSA = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_OSA.sizePolicy().hasHeightForWidth()) + self.radioButton_OSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_OSA.setFont(font) + self.radioButton_OSA.setObjectName("radioButton_OSA") + self.horizontalLayout_radioButton.addWidget(self.radioButton_OSA) + self.radioButton_CSA = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_CSA.sizePolicy().hasHeightForWidth()) + self.radioButton_CSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_CSA.setFont(font) + self.radioButton_CSA.setObjectName("radioButton_CSA") + self.horizontalLayout_radioButton.addWidget(self.radioButton_CSA) + self.radioButton_MSA = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_MSA.sizePolicy().hasHeightForWidth()) + self.radioButton_MSA.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_MSA.setFont(font) + self.radioButton_MSA.setObjectName("radioButton_MSA") + self.horizontalLayout_radioButton.addWidget(self.radioButton_MSA) + self.radioButton_HPY = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_HPY.sizePolicy().hasHeightForWidth()) + self.radioButton_HPY.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_HPY.setFont(font) + self.radioButton_HPY.setObjectName("radioButton_HPY") + self.horizontalLayout_radioButton.addWidget(self.radioButton_HPY) + self.gridLayout_2.addLayout(self.horizontalLayout_radioButton, 3, 0, 1, 5) + self.label_PSG_event = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_PSG_event.sizePolicy().hasHeightForWidth()) + self.label_PSG_event.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(12) + font.setBold(False) + font.setWeight(50) + self.label_PSG_event.setFont(font) + self.label_PSG_event.setText("") + self.label_PSG_event.setAlignment(QtCore.Qt.AlignCenter) + self.label_PSG_event.setObjectName("label_PSG_event") + self.gridLayout_2.addWidget(self.label_PSG_event, 1, 0, 1, 5) + self.label_remark = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_remark.sizePolicy().hasHeightForWidth()) + self.label_remark.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_remark.setFont(font) + self.label_remark.setObjectName("label_remark") + self.gridLayout_2.addWidget(self.label_remark, 5, 0, 1, 1) + self.pushButton_right = QtWidgets.QPushButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_right.sizePolicy().hasHeightForWidth()) + self.pushButton_right.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.pushButton_right.setFont(font) + self.pushButton_right.setObjectName("pushButton_right") + self.gridLayout_2.addWidget(self.pushButton_right, 16, 0, 1, 1) + self.horizontalLayout_12 = QtWidgets.QHBoxLayout() + self.horizontalLayout_12.setObjectName("horizontalLayout_12") + self.label_score = QtWidgets.QLabel(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_score.sizePolicy().hasHeightForWidth()) + self.label_score.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.label_score.setFont(font) + self.label_score.setObjectName("label_score") + self.horizontalLayout_12.addWidget(self.label_score) + self.radioButton_1_class = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_1_class.sizePolicy().hasHeightForWidth()) + self.radioButton_1_class.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.radioButton_1_class.setFont(font) + self.radioButton_1_class.setObjectName("radioButton_1_class") + self.horizontalLayout_12.addWidget(self.radioButton_1_class) + self.radioButton_2_class = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_2_class.sizePolicy().hasHeightForWidth()) + self.radioButton_2_class.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.radioButton_2_class.setFont(font) + self.radioButton_2_class.setObjectName("radioButton_2_class") + self.horizontalLayout_12.addWidget(self.radioButton_2_class) + self.radioButton_3_class = QtWidgets.QRadioButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.radioButton_3_class.sizePolicy().hasHeightForWidth()) + self.radioButton_3_class.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.radioButton_3_class.setFont(font) + self.radioButton_3_class.setObjectName("radioButton_3_class") + self.horizontalLayout_12.addWidget(self.radioButton_3_class) + self.gridLayout_2.addLayout(self.horizontalLayout_12, 4, 0, 1, 5) + self.pushButton_confirmLabel = QtWidgets.QPushButton(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_confirmLabel.sizePolicy().hasHeightForWidth()) + self.pushButton_confirmLabel.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_confirmLabel.setFont(font) + self.pushButton_confirmLabel.setObjectName("pushButton_confirmLabel") + self.gridLayout_2.addWidget(self.pushButton_confirmLabel, 15, 2, 2, 3) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem3, 16, 1, 1, 1) + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem4, 15, 1, 1, 1) + self.verticalLayout_left.addWidget(self.groupBox_4) + spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_left.addItem(spacerItem5) + self.groupBox_5 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) + self.groupBox_5.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_5.setFont(font) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_3.setObjectName("gridLayout_3") + self.pushButton_next10s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_next10s.sizePolicy().hasHeightForWidth()) + self.pushButton_next10s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_next10s.setFont(font) + self.pushButton_next10s.setObjectName("pushButton_next10s") + self.gridLayout_3.addWidget(self.pushButton_next10s, 1, 2, 1, 1) + self.pushButton_previous10s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_previous10s.sizePolicy().hasHeightForWidth()) + self.pushButton_previous10s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_previous10s.setFont(font) + self.pushButton_previous10s.setObjectName("pushButton_previous10s") + self.gridLayout_3.addWidget(self.pushButton_previous10s, 1, 0, 1, 1) + self.pushButton_next30s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_next30s.sizePolicy().hasHeightForWidth()) + self.pushButton_next30s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_next30s.setFont(font) + self.pushButton_next30s.setObjectName("pushButton_next30s") + self.gridLayout_3.addWidget(self.pushButton_next30s, 2, 2, 1, 1) + self.pushButton_next60s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_next60s.sizePolicy().hasHeightForWidth()) + self.pushButton_next60s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_next60s.setFont(font) + self.pushButton_next60s.setObjectName("pushButton_next60s") + self.gridLayout_3.addWidget(self.pushButton_next60s, 3, 2, 1, 1) + self.pushButton_previous60s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_previous60s.sizePolicy().hasHeightForWidth()) + self.pushButton_previous60s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_previous60s.setFont(font) + self.pushButton_previous60s.setObjectName("pushButton_previous60s") + self.gridLayout_3.addWidget(self.pushButton_previous60s, 3, 0, 1, 1) + self.pushButton_previous30s = QtWidgets.QPushButton(self.groupBox_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_previous30s.sizePolicy().hasHeightForWidth()) + self.pushButton_previous30s.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_previous30s.setFont(font) + self.pushButton_previous30s.setObjectName("pushButton_previous30s") + self.gridLayout_3.addWidget(self.pushButton_previous30s, 2, 0, 1, 1) + spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem6, 1, 1, 1, 1) + spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem7, 2, 1, 1, 1) + spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem8, 3, 1, 1, 1) + self.checkBox_examineBySecond = QtWidgets.QCheckBox(self.groupBox_5) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.checkBox_examineBySecond.setFont(font) + self.checkBox_examineBySecond.setObjectName("checkBox_examineBySecond") + self.gridLayout_3.addWidget(self.checkBox_examineBySecond, 0, 0, 1, 2) + self.gridLayout_3.setColumnStretch(0, 2) + self.gridLayout_3.setColumnStretch(1, 1) + self.gridLayout_3.setColumnStretch(2, 2) + self.verticalLayout_left.addWidget(self.groupBox_5) + spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_left.addItem(spacerItem9) + self.groupBox_6 = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) + self.groupBox_6.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_6.setFont(font) + self.groupBox_6.setObjectName("groupBox_6") + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_6) + self.gridLayout_6.setObjectName("gridLayout_6") + self.textBrowser_infoOutput = QtWidgets.QTextBrowser(self.groupBox_6) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.textBrowser_infoOutput.setFont(font) + self.textBrowser_infoOutput.setObjectName("textBrowser_infoOutput") + self.gridLayout_6.addWidget(self.textBrowser_infoOutput, 0, 0, 1, 1) + self.verticalLayout_left.addWidget(self.groupBox_6) + self.verticalLayout_left.setStretch(0, 3) + self.verticalLayout_left.setStretch(1, 1) + self.verticalLayout_left.setStretch(2, 3) + self.verticalLayout_left.setStretch(3, 1) + self.verticalLayout_left.setStretch(4, 8) + self.verticalLayout_left.setStretch(5, 1) + self.verticalLayout_left.setStretch(6, 30) + self.verticalLayout_left.setStretch(7, 1) + self.verticalLayout_left.setStretch(8, 12) + self.verticalLayout_left.setStretch(9, 1) + self.verticalLayout_left.setStretch(10, 12) + self.horizontalLayout.addLayout(self.verticalLayout_left) + self.verticalLayout_canvas = QtWidgets.QVBoxLayout() + self.verticalLayout_canvas.setObjectName("verticalLayout_canvas") + self.horizontalLayout.addLayout(self.verticalLayout_canvas) + self.horizontalLayout.setStretch(0, 3) + self.horizontalLayout.setStretch(1, 20) + self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 3, 2) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1920, 23)) + self.menubar.setObjectName("menubar") + self.menu = QtWidgets.QMenu(self.menubar) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.menu.setFont(font) + self.menu.setObjectName("menu") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.action_selectPath = QtWidgets.QAction(MainWindow) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.action_selectPath.setFont(font) + self.action_selectPath.setObjectName("action_selectPath") + self.action = QtWidgets.QAction(MainWindow) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.action.setFont(font) + self.action.setObjectName("action") + self.menu.addAction(self.action_selectPath) + self.menubar.addAction(self.menu.menuAction()) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Main_Quality_Relabel_GUI")) + self.groupBox_1.setTitle(_translate("MainWindow", "样本ID和起始心晓事件")) + self.lineEdit_start_bcg_index.setText(_translate("MainWindow", "0")) + self.lineEdit_start_bcg_index.setPlaceholderText(_translate("MainWindow", "从第几个心晓事件数量开始")) + self.label_start_bcg_index.setText(_translate("MainWindow", "起始心晓事件")) + self.label_sampID.setText(_translate("MainWindow", "样本ID")) + self.groupBox_2.setTitle(_translate("MainWindow", "事件类型选择")) + self.checkBox_OSA.setText(_translate("MainWindow", "OSA")) + self.checkBox_CSA.setText(_translate("MainWindow", "CSA")) + self.checkBox_MSA.setText(_translate("MainWindow", "MSA")) + self.checkBox_HPY.setText(_translate("MainWindow", "HPY")) + self.groupBox_3.setTitle(_translate("MainWindow", "参数设置(一般无需修改)")) + self.label_frequency.setText(_translate("MainWindow", "绘图采样率(Hz)")) + self.lineEdit_frequency.setText(_translate("MainWindow", "100")) + self.lineEdit_frequency.setPlaceholderText(_translate("MainWindow", "绘图时的采样率")) + self.label_bcg_frequency.setText(_translate("MainWindow", "心晓信号采样率(Hz)")) + self.lineEdit_bcg_frequency.setText(_translate("MainWindow", "1000")) + self.lineEdit_bcg_frequency.setPlaceholderText(_translate("MainWindow", "心晓数据采样率")) + self.label_front_add_second.setText(_translate("MainWindow", "信号向前扩展(s)")) + self.lineEdit_front_add_second.setText(_translate("MainWindow", "60")) + self.lineEdit_front_add_second.setPlaceholderText(_translate("MainWindow", "信号显示事件前多少秒")) + self.label_back_add_second.setText(_translate("MainWindow", "信号向后扩展(s)")) + self.lineEdit_back_add_second.setText(_translate("MainWindow", "60")) + self.lineEdit_back_add_second.setPlaceholderText(_translate("MainWindow", "信号显示事件后多少秒")) + self.groupBox_4.setTitle(_translate("MainWindow", "打标操作")) + self.pushButton_left.setText(_translate("MainWindow", "上一个事件(A)")) + self.pushButton_left.setShortcut(_translate("MainWindow", "A")) + self.label_correctStart.setText(_translate("MainWindow", "修正后起始时间(s)")) + self.checkBox_examineLabeled.setText(_translate("MainWindow", "仅检查未确定打标参数的事件")) + self.label_quick_remark_input.setText(_translate("MainWindow", "快速备注输入")) + self.label_correctEnd.setText(_translate("MainWindow", "修正后终止时间(s)")) + self.pushButton_quick_remark_input_waitingForTalk.setText(_translate("MainWindow", "待讨论(J)")) + self.pushButton_quick_remark_input_waitingForTalk.setShortcut(_translate("MainWindow", "J")) + self.label_events.setText(_translate("MainWindow", "事件类型")) + self.radioButton_OSA.setText(_translate("MainWindow", "OSA")) + self.radioButton_OSA.setShortcut(_translate("MainWindow", "1")) + self.radioButton_CSA.setText(_translate("MainWindow", "CSA")) + self.radioButton_CSA.setShortcut(_translate("MainWindow", "2")) + self.radioButton_MSA.setText(_translate("MainWindow", "MSA")) + self.radioButton_MSA.setShortcut(_translate("MainWindow", "3")) + self.radioButton_HPY.setText(_translate("MainWindow", "HPY")) + self.radioButton_HPY.setShortcut(_translate("MainWindow", "4")) + self.label_remark.setText(_translate("MainWindow", "备注")) + self.pushButton_right.setText(_translate("MainWindow", "下一个事件(D)")) + self.pushButton_right.setShortcut(_translate("MainWindow", "D")) + self.label_score.setText(_translate("MainWindow", "标签类型")) + self.radioButton_1_class.setText(_translate("MainWindow", "一类(U)")) + self.radioButton_1_class.setShortcut(_translate("MainWindow", "U")) + self.radioButton_2_class.setText(_translate("MainWindow", "二类(I)")) + self.radioButton_2_class.setShortcut(_translate("MainWindow", "I")) + self.radioButton_3_class.setText(_translate("MainWindow", "删除(O)")) + self.radioButton_3_class.setShortcut(_translate("MainWindow", "O")) + self.pushButton_confirmLabel.setText(_translate("MainWindow", "确定打标参数(S)")) + self.groupBox_5.setTitle(_translate("MainWindow", "逐帧检查")) + self.pushButton_next10s.setText(_translate("MainWindow", "+10s")) + self.pushButton_previous10s.setText(_translate("MainWindow", "-10s")) + self.pushButton_next30s.setText(_translate("MainWindow", "+30s")) + self.pushButton_next60s.setText(_translate("MainWindow", "+60s")) + self.pushButton_previous60s.setText(_translate("MainWindow", "-60s")) + self.pushButton_previous30s.setText(_translate("MainWindow", "-30s")) + self.checkBox_examineBySecond.setText(_translate("MainWindow", "启用逐帧检查模式")) + self.groupBox_6.setTitle(_translate("MainWindow", "信息输出")) + self.menu.setTitle(_translate("MainWindow", "打开")) + self.action_selectPath.setText(_translate("MainWindow", "数据路径选择")) + self.action.setText(_translate("MainWindow", "加载存档")) diff --git a/MainWindow.ui b/MainWindow.ui new file mode 100644 index 0000000..d33fce4 --- /dev/null +++ b/MainWindow.ui @@ -0,0 +1,1229 @@ + + + MainWindow + + + + 0 + 0 + 1920 + 1080 + + + + Main_Quality_Relabel_GUI + + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 样本ID和起始心晓事件 + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 0 + + + 从第几个心晓事件数量开始 + + + + + + + + Times New Roman + 14 + + + + 起始心晓事件 + + + + + + + + Times New Roman + 14 + + + + 样本ID + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 事件类型选择 + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + OSA + + + true + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + CSA + + + true + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + MSA + + + true + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + HPY + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 参数设置(一般无需修改) + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 绘图采样率(Hz) + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 100 + + + 绘图时的采样率 + + + + + + + + Times New Roman + 14 + + + + 心晓信号采样率(Hz) + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 1000 + + + 心晓数据采样率 + + + + + + + + Times New Roman + 14 + + + + 信号向前扩展(s) + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 60 + + + 信号显示事件前多少秒 + + + + + + + + Times New Roman + 14 + + + + 信号向后扩展(s) + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 60 + + + 信号显示事件后多少秒 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 打标操作 + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 上一个事件(A) + + + A + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 修正后起始时间(s) + + + + + + + + Times New Roman + 14 + + + + 仅检查未确定打标参数的事件 + + + + + + + + Times New Roman + 14 + + + + 快速备注输入 + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 修正后终止时间(s) + + + + + + + + 0 + 0 + + + + + Times New Roman + 12 + 50 + false + + + + + + + Qt::AlignCenter + + + + + + + + Times New Roman + 14 + + + + 待讨论(J) + + + J + + + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 事件类型 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + OSA + + + 1 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + CSA + + + 2 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + MSA + + + 3 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + HPY + + + 4 + + + + + + + + + + 0 + 0 + + + + + Times New Roman + 12 + 50 + false + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 备注 + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 下一个事件(D) + + + D + + + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 标签类型 + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 一类(U) + + + U + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 二类(I) + + + I + + + + + + + + 0 + 0 + + + + + Times New Roman + 14 + + + + 删除(O) + + + O + + + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 确定打标参数(S) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 逐帧检查 + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + +10s + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + -10s + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + +30s + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + +60s + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + -60s + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + -30s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 黑体 + 14 + + + + 启用逐帧检查模式 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 信息输出 + + + + + + + 黑体 + 10 + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1920 + 23 + + + + + + 黑体 + 14 + + + + 打开 + + + + + + + + + 数据路径选择 + + + + 黑体 + 14 + + + + + + 加载存档 + + + + 黑体 + 14 + + + + + + + diff --git a/Main_Quality_Relabel_GUI.py b/Main_Quality_Relabel_GUI.py new file mode 100644 index 0000000..a2617b5 --- /dev/null +++ b/Main_Quality_Relabel_GUI.py @@ -0,0 +1,1107 @@ +""" +@author:Yosa, Marques +@file:Main_Quality_Relabel_GUI.py +@email:2023025086@m.scnu.edu.cn, 2021022362@m.scnu.edu.cn +@time:2025/1/4 +""" + +import os +import sys +import logging +import time +import numpy as np +import pandas as pd +import matplotlib +import pyedflib +import traceback +from datetime import datetime +from tqdm import tqdm +from pathlib import Path +from PyQt5 import QtCore, QtWidgets +from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox, QButtonGroup +from matplotlib import pyplot as plt, gridspec +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT +from MainWindow import Ui_MainWindow +from utils.Preprocessing import BCG_Operation + +matplotlib.use("Qt5Agg") # 声明使用QT5 + +plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 +plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 + +# ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2', +# 'EEG O2-A1', 'EOG Right', 'EOG Left', 'EMG Chin', 'ECG I', 'RR', +# 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'Effort Abd', +# 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2', +# 'EEG A1-A2', 'Imp'] + +# 设置日志 +logger = logging.getLogger() +logger.setLevel(logging.NOTSET) +realtime = time.strftime('%Y%m%d', time.localtime(time.time())) +fh = logging.FileHandler(Path("history") / (realtime + ".log"), mode='a') +fh.setLevel(logging.NOTSET) +fh.setFormatter(logging.Formatter("%(asctime)s: %(message)s")) +logger.addHandler(fh) + +ch = logging.StreamHandler() +ch.setLevel(logging.NOTSET) +ch.setFormatter(logging.Formatter("%(asctime)s: %(message)s")) +logger.addHandler(ch) +logging.getLogger('matplotlib.font_manager').disabled = True +logging.info("------------------------------------") + +class MainWindow(QMainWindow, Ui_MainWindow): + + # 可选择的通道 + base_channel = ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2', 'EEG O2-A1', 'EOG Right', + 'EOG Left', 'EMG Chin', 'ECG I', 'RR', 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'HR', + 'Effort Abd', 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2', 'EEG A1-A2', 'Imp'] + + # 显示事件 + base_event = ["Hypopnea", "Central apnea", "Obstructive apnea", "Mixed apnea", "Desaturation"] + + # 设定事件和其对应颜色 + # event_code color event + # 0 黑色 背景 + # 1 粉色 低通气 + # 2 蓝色 中枢性 + # 3 红色 阻塞型 + # 4 灰色 混合型 + # 5 绿色 血氧饱和度下降 + # 6 橙色 大体动 + # 7 橙色 小体动 + # 8 橙色 深呼吸 + # 9 橙色 脉冲体动 + # 10 橙色 无效片段 + color_cycle = ["black", "pink", "blue", "red", "silver", "green", "orange", "orange", "orange", "orange", "orange"] + + # 程序初始化操作 + 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.slot_btn_resetOriginalView) + self.verticalLayout_canvas.addWidget(self.canvas) + self.verticalLayout_canvas.addWidget(self.figToolbar) + self.msgBox = QMessageBox() + self.msgBox.setWindowTitle("消息") + self.pushButton_left.clicked.connect(self.slot_btn_left) + self.pushButton_right.clicked.connect(self.slot_btn_right) + self.pushButton_confirmLabel.clicked.connect(self.slot_btn_run) + self.pushButton_confirmLabel.setText("请选择数据路径") + self.pushButton_previous10s.setEnabled(False) + self.pushButton_previous30s.setEnabled(False) + self.pushButton_previous60s.setEnabled(False) + self.pushButton_next10s.setEnabled(False) + self.pushButton_next30s.setEnabled(False) + self.pushButton_next60s.setEnabled(False) + self.time_move_count = int(0) + self.pushButton_previous10s.clicked.connect(self.slot_btn_previous10s) + self.pushButton_previous30s.clicked.connect(self.slot_btn_previous30s) + self.pushButton_previous60s.clicked.connect(self.slot_btn_previous60s) + self.pushButton_next10s.clicked.connect(self.slot_btn_next10s) + self.pushButton_next30s.clicked.connect(self.slot_btn_next30s) + self.pushButton_next60s.clicked.connect(self.slot_btn_next60s) + self.action_selectPath.triggered.connect(self.slot_btn_selectPath) + self.pushButton_left.setEnabled(False) + self.pushButton_right.setEnabled(False) + self.pushButton_confirmLabel.setEnabled(False) + self.pushButton_quick_remark_input_waitingForTalk.setEnabled(False) + self.pushButton_quick_remark_input_waitingForTalk.clicked.connect(self.slot_btn_quick_remark_input_waitingForTalk) + self.checkBox_examineBySecond.setEnabled(False) + self.checkBox_examineBySecond.clicked.connect(self.enable_checkBox_examineBySecond) + self.lineEdit_start_bcg_index.setEnabled(False) + self.lineEdit_frequency.setEnabled(False) + self.lineEdit_bcg_frequency.setEnabled(False) + self.lineEdit_front_add_second.setEnabled(False) + self.lineEdit_back_add_second.setEnabled(False) + self.radioButton_1_class.setEnabled(False) + self.radioButton_2_class.setEnabled(False) + self.radioButton_3_class.setEnabled(False) + self.buttonGroup_1 = QButtonGroup() + self.buttonGroup_1.addButton(self.radioButton_1_class) + self.buttonGroup_1.addButton(self.radioButton_2_class) + self.buttonGroup_1.addButton(self.radioButton_3_class) + self.lineEdit_remark.setEnabled(False) + self.lineEdit_correctStart.setEnabled(False) + self.lineEdit_correctEnd.setEnabled(False) + self.comboBox_sampID.setEnabled(False) + self.checkBox_OSA.setEnabled(False) + self.checkBox_CSA.setEnabled(False) + self.checkBox_MSA.setEnabled(False) + self.checkBox_HPY.setEnabled(False) + self.checkBox_examineLabeled.setEnabled(False) + self.checkBox_examineLabeled.clicked.connect(self.enable_checkBox_examineLabeled) + self.radioButton_OSA.setEnabled(False) + self.radioButton_CSA.setEnabled(False) + self.radioButton_MSA.setEnabled(False) + self.radioButton_HPY.setEnabled(False) + self.buttonGroup_2 = QButtonGroup() + self.buttonGroup_2.addButton(self.radioButton_OSA) + self.buttonGroup_2.addButton(self.radioButton_CSA) + self.buttonGroup_2.addButton(self.radioButton_MSA) + self.buttonGroup_2.addButton(self.radioButton_HPY) + self.thread_textbrowserUpdate = Thread_textbrowserUpdate() + self.thread_textbrowserUpdate.trigger.connect(self.textBrowser_update) + self.textBrowser_update("提示:请点击左上角“打开”菜单来开始任务") + + # 选择路径按钮的槽函数 + def slot_btn_selectPath(self): + fileDialog = QFileDialog() + fileDialog.setFileMode(QFileDialog.Directory) + fileDialog.setOption(QFileDialog.ShowDirsOnly, True) + if fileDialog.exec_() == QFileDialog.Accepted: + self.dir_path = fileDialog.selectedFiles()[0] + if self.dir_path: + logging.info("Loading Data Path...") + self.PSG_Data_Path = Path(os.path.join(self.dir_path, "PSG")) + self.PSG_Label_Path = Path(os.path.join(self.dir_path, "PSG_label")) + self.BCG_Data_Path = Path(os.path.join(self.dir_path, "BCG")) + self.BCG_Label_Path = Path(os.path.join(self.dir_path, "BCG_label")) + self.Artifact_Label_Path = Path(os.path.join(self.dir_path, "Artifact_label")) + self.Artifact_Offset_Path = Path(os.path.join(self.dir_path, "Artifact_label", "20220421Artifact_offset_value.xlsx")) + if self.PSG_Data_Path.exists() and self.PSG_Label_Path.exists() and self.BCG_Data_Path.exists() and self.BCG_Label_Path.exists() and self.Artifact_Label_Path.exists() and self.Artifact_Label_Path.exists(): + sampIDs = os.listdir(self.BCG_Data_Path) + sampID_for_comboBox = [] + for sampID in sampIDs: + sampID = sampID.replace("samp.npy", "") + sampID_for_comboBox.append(sampID) + bcg_path = self.BCG_Data_Path / f"{sampID}samp.npy" + ecg_path = self.PSG_Data_Path / f"A{str(sampID).rjust(7, '0')}.edf" + bcg_label_path = self.BCG_Label_Path / f"export{sampID}_all.csv" + ecg_label_path = self.PSG_Label_Path / f"export{sampID}.csv" + if not bcg_path.exists(): + logging.error(f"Can't find {bcg_path}!") + self.msgBox.setText(f"找不到数据{bcg_path}") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + if not ecg_path.exists(): + logging.error(f"Can't find {ecg_path}!") + self.msgBox.setText(f"找不到数据{ecg_path}") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + if not bcg_label_path.exists(): + logging.error(f"Can't find {bcg_label_path}!") + self.msgBox.setText(f"找不到数据{bcg_label_path}") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + if not ecg_label_path.exists(): + logging.error(f"Can't find {ecg_label_path}!") + self.msgBox.setText(f"找不到数据{ecg_label_path}") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + self.comboBox_sampID.setEnabled(True) + self.lineEdit_start_bcg_index.setEnabled(True) + self.comboBox_sampID.addItems(sampID_for_comboBox) + self.lineEdit_start_bcg_index.setEnabled(True) + self.lineEdit_frequency.setEnabled(True) + self.lineEdit_bcg_frequency.setEnabled(True) + self.lineEdit_front_add_second.setEnabled(True) + self.lineEdit_back_add_second.setEnabled(True) + self.checkBox_OSA.setEnabled(True) + self.checkBox_CSA.setEnabled(True) + self.checkBox_MSA.setEnabled(True) + self.checkBox_HPY.setEnabled(True) + self.action_selectPath.setEnabled(False) + self.pushButton_confirmLabel.setEnabled(True) + self.pushButton_confirmLabel.setText("开始打标") + MainWindow.setWindowTitle(self, QtCore.QCoreApplication.translate("MainWindow", "Main_Quality_Relabel_GUI - Data Path: " + self.dir_path)) + logging.info("Successfully Loaded Data Path.") + self.textBrowser_update("操作:数据路径选择成功") + else: + logging.info("Failed to Load Data Path.") + self.textBrowser_update("操作:数据路径选择错误,缺乏必要数据文件夹") + self.msgBox.setText("数据路径选择错误,缺乏必要数据文件夹") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + else: + logging.info("Data Path not Exist...") + self.textBrowser_update("操作:载入存档错误,存档不存在") + self.msgBox.setText("载入存档错误,存档不存在") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + else: + logging.info("Canceled Loading Data Path.") + self.textBrowser_update("提示:数据路径选择取消") + self.msgBox.setText("数据路径选择取消") + self.msgBox.setIcon(QMessageBox.Warning) + self.msgBox.exec() + + # 开始打标按钮的槽函数 + def slot_btn_run(self): + self.init_variable() + self.check_channel() + self.read_data() + self.read_event() + self.read_artifact_label() + if self.plotEventIndex >= len(self.bcg_event_label_index_list) or self.plotEventIndex < 0: + self.msgBox.setText("输入起始心晓事件序列过大或过小导致错误") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + else: + self.pushButton_left.setEnabled(False) + self.pushButton_right.setEnabled(True) + self.pushButton_confirmLabel.setText("确定打标参数(S)") + self.pushButton_confirmLabel.setShortcut(QtCore.QCoreApplication.translate("MainWindow", "S")) + self.pushButton_confirmLabel.clicked.disconnect(self.slot_btn_run) + self.pushButton_confirmLabel.clicked.connect(self.slot_btn_confirmLabel) + self.pushButton_quick_remark_input_waitingForTalk.setEnabled(True) + self.comboBox_sampID.setEnabled(False) + self.lineEdit_start_bcg_index.setEnabled(False) + self.lineEdit_frequency.setEnabled(False) + self.lineEdit_bcg_frequency.setEnabled(False) + self.lineEdit_front_add_second.setEnabled(False) + self.lineEdit_back_add_second.setEnabled(False) + self.checkBox_OSA.setEnabled(False) + self.checkBox_CSA.setEnabled(False) + self.checkBox_MSA.setEnabled(False) + self.checkBox_HPY.setEnabled(False) + self.radioButton_1_class.setEnabled(True) + self.radioButton_2_class.setEnabled(True) + self.radioButton_3_class.setEnabled(True) + self.checkBox_examineLabeled.setEnabled(True) + self.checkBox_examineBySecond.setEnabled(True) + self.radioButton_OSA.setEnabled(True) + self.radioButton_CSA.setEnabled(True) + self.radioButton_MSA.setEnabled(True) + self.radioButton_HPY.setEnabled(True) + self.lineEdit_remark.setEnabled(True) + self.lineEdit_correctStart.setEnabled(True) + self.lineEdit_correctEnd.setEnabled(True) + self.pd = pd.read_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_all.csv")), encoding="gbk") + csv_headers = self.pd.columns.tolist() + if not (("score" in csv_headers) and ("remark" in csv_headers) and ("correct_Start" in csv_headers) and ("correct_End") in csv_headers and ("isLabeled" in csv_headers)): + self.pd["score"] = "-1" + self.pd["remark"] = "" + self.pd["correct_Start"] = "-1" + self.pd["correct_End"] = "-1" + self.pd["correct_EventsType"] = "" + self.pd["isLabeled"] = "-1" + self.pd["score"] = self.pd["score"].astype(int) + self.pd["remark"] = self.pd["remark"].astype(str) + self.pd["correct_Start"] = self.pd["correct_Start"].astype(int) + self.pd["correct_End"] = self.pd["correct_End"].astype(int) + self.pd["correct_EventsType"] = self.pd["correct_EventsType"].astype(str) + self.pd["isLabeled"] = self.pd["isLabeled"].astype(int) + self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex]) + if not Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_addNew.csv")).exists(): + self.pd_examineBySecond = pd.DataFrame(columns=self.pd.columns) + self.pd_examineBySecond["score"] = self.pd_examineBySecond["score"].astype(int) + self.pd_examineBySecond["remark"] = self.pd_examineBySecond["remark"].astype(str) + self.pd_examineBySecond["correct_Start"] = self.pd_examineBySecond["correct_Start"].astype(int) + self.pd_examineBySecond["correct_End"] = self.pd_examineBySecond["correct_End"].astype(int) + self.pd_examineBySecond["correct_EventsType"] = self.pd_examineBySecond["correct_EventsType"].astype(str) + self.pd_examineBySecond["isLabeled"] = self.pd_examineBySecond["isLabeled"].astype(int) + self.pd_examineBySecond.to_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_addNew.csv")), index=False, encoding="gbk") + else: + self.pd_examineBySecond = pd.read_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_addNew.csv")), encoding="gbk") + if (self.pd["isLabeled"] == 1).all(): + self.checkBox_examineLabeled.setChecked(False) + self.msgBox.setText("该份数据打标已全部完成") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + self.pd.to_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_all.csv")), mode='w', index=None, encoding="gbk") + self.show_one_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, back_add_second=self.back_add_second) + if not str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan": + self.lineEdit_remark.setText(str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"])) + else: + self.lineEdit_remark.setText("") + if str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1": + self.radioButton_1_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2": + self.radioButton_2_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3": + self.radioButton_3_class.setChecked(True) + else: + self.radioButton_2_class.setChecked(True) + self.textBrowser_update("操作:开始打标") + + # 上一个事件按钮的槽函数 + def slot_btn_left(self): + self.figure.clear() + self.plotEventIndex = self.plotEventIndex - 1 + if self.checkBox_examineLabeled.isChecked() == True: + while self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] == 1 and self.plotEventIndex > self.start_bcg_index: + self.plotEventIndex = self.plotEventIndex - 1 + self.show_one_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second) + if not str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan": + self.lineEdit_remark.setText(str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"])) + else: + self.lineEdit_remark.setText("") + self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex]) + if str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1": + self.radioButton_1_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2": + self.radioButton_2_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3": + self.radioButton_3_class.setChecked(True) + else: + self.radioButton_2_class.setChecked(True) + self.textBrowser_update("操作:↑上一个事件,index" + str(self.plotEventIndex + 1)) + if self.plotEventIndex <= self.start_bcg_index: + self.pushButton_left.setEnabled(False) + self.pushButton_right.setEnabled(True) + self.msgBox.setText("你正在查看第一个事件") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.pushButton_left.setEnabled(True) + self.pushButton_right.setEnabled(True) + + # 下一个事件按钮的槽函数 + def slot_btn_right(self): + self.figure.clear() + self.plotEventIndex = self.plotEventIndex + 1 + if self.checkBox_examineLabeled.isChecked() == True: + while self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] == 1 and self.plotEventIndex < len(self.bcg_event_label_filtered_df) - 1: + self.plotEventIndex = self.plotEventIndex + 1 + self.show_one_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second) + if not str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"]) == "nan": + self.lineEdit_remark.setText(str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"])) + else: + self.lineEdit_remark.setText("") + self.change_radioButton_events(self.bcg_event_label_index_list[self.plotEventIndex]) + if str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "1": + self.radioButton_1_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "2": + self.radioButton_2_class.setChecked(True) + elif str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) == "3": + self.radioButton_3_class.setChecked(True) + else: + self.radioButton_2_class.setChecked(True) + self.textBrowser_update("操作:↓下一个事件,index" + str(self.plotEventIndex + 1)) + if self.plotEventIndex >= len(self.bcg_event_label_filtered_df) - 1: + self.pushButton_left.setEnabled(True) + self.pushButton_right.setEnabled(False) + self.msgBox.setText("你正在查看最后一个事件") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.pushButton_left.setEnabled(True) + self.pushButton_right.setEnabled(True) + + # 确定打标参数按钮的槽函数 + def slot_btn_confirmLabel(self): + if self.checkBox_examineBySecond.isChecked() == True: + if int(self.lineEdit_correctStart.text()) < int(self.lineEdit_correctEnd.text()): + if self.radioButton_1_class.isChecked() == True: + score = int(1) + elif self.radioButton_2_class.isChecked() == True: + score = int(2) + elif self.radioButton_3_class.isChecked() == True: + score = int(3) + remark = self.lineEdit_remark.text() + correct_Start = int(self.lineEdit_correctStart.text()) + correct_End = int(self.lineEdit_correctEnd.text()) + if self.radioButton_OSA.isChecked() == True: + correct_EventsType = "Obstructive apnea" + elif self.radioButton_CSA.isChecked() == True: + correct_EventsType = "Central apnea" + elif self.radioButton_MSA.isChecked() == True: + correct_EventsType = "Mixed apnea" + elif self.radioButton_HPY.isChecked() == True: + correct_EventsType = "Hypopnea" + isLabeled = int(1) + self.pd_examineBySecond = self.pd_add_new_row(self.pd_examineBySecond, score, remark, correct_Start, correct_End, correct_EventsType, isLabeled) + self.pd_examineBySecond.to_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_addNew.csv")), mode='w', index=None, encoding="gbk") + self.textBrowser_update("操作:保存新事件打标结果到csv。" + "score:" + str(score) + ",correct_Start:" + str(correct_Start) + ",correct_End:" + str(correct_End) + ",correct_EventsType:" + str(correct_EventsType)) + else: + self.msgBox.setText("起始时间和终止时间输入错误") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + elif self.checkBox_examineBySecond.isChecked() == False: + if int(self.lineEdit_correctStart.text()) < int(self.lineEdit_correctEnd.text()): + if self.radioButton_1_class.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(1) + elif self.radioButton_2_class.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(2) + elif self.radioButton_3_class.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"] = int(3) + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "remark"] = self.lineEdit_remark.text() + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"] = int(self.lineEdit_correctStart.text()) + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"] = int(self.lineEdit_correctEnd.text()) + if self.radioButton_OSA.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Obstructive apnea" + elif self.radioButton_CSA.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Central apnea" + elif self.radioButton_MSA.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Mixed apnea" + elif self.radioButton_HPY.isChecked() == True: + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"] = "Hypopnea" + self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "isLabeled"] = int(1) + self.pd.to_csv(Path(os.path.join(self.BCG_Label_Path, f"export{self.comboBox_sampID.currentText()}_all.csv")), mode='w', index=None, encoding="gbk") + self.textBrowser_update("操作:保存index" + str(self.plotEventIndex + 1) + "打标结果到csv。" + "score:" + str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "score"]) + ",correct_Start:" + str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_Start"]) + ",correct_End:" + str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_End"]) + ",correct_EventsType:" + str(self.pd.at[self.bcg_event_label_index_list[self.plotEventIndex], "correct_EventsType"])) + if (self.pd.loc[self.bcg_event_label_index_list]["isLabeled"] == 1).all(): + self.msgBox.setText("该份数据打标已全部完成") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.msgBox.setText("起始时间和终止时间输入错误") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + + # 快速备注输入中待讨论按钮的槽函数 + def slot_btn_quick_remark_input_waitingForTalk(self): + self.lineEdit_remark.setText("待讨论") + + # -10s的槽函数 + def slot_btn_previous10s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向前10秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count - 10 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # -30s的槽函数 + def slot_btn_previous30s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向前30秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count - 30 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # -60s的槽函数 + def slot_btn_previous60s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向前60秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count - 60 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # +10s的槽函数 + def slot_btn_next10s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向后10秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count + 10 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # +30s的槽函数 + def slot_btn_next30s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向后30秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count + 30 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # +30s的槽函数 + def slot_btn_next60s(self): + self.figure.clear() + self.lineEdit_remark.setText("") + self.textBrowser_update("操作:向后60秒") + self.radioButton_OSA.setChecked(True) + self.time_move_count = self.time_move_count + 60 + self.show_new_event(self.plotEventIndex, self.plotEventIndex, front_add_second=self.front_add_second, + back_add_second=self.back_add_second, time_move_count = self.time_move_count) + + # +60s的槽函数 + def enable_checkBox_examineLabeled(self): + if self.checkBox_examineLabeled.isChecked(): + if (self.pd.loc[self.bcg_event_label_index_list]["isLabeled"] == 1).all(): + self.checkBox_examineLabeled.setChecked(False) + self.msgBox.setText("该份数据打标已全部完成") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + + # 点击了启用逐帧检查模式复选框的槽函数 + def enable_checkBox_examineBySecond(self): + if self.checkBox_examineBySecond.isChecked() == True: + self.pushButton_left.setEnabled(False) + self.pushButton_right.setEnabled(False) + self.checkBox_examineLabeled.setEnabled(False) + self.radioButton_OSA.setChecked(True) + self.lineEdit_remark.setText("") + self.pushButton_previous10s.setEnabled(True) + self.pushButton_next10s.setEnabled(True) + self.pushButton_previous30s.setEnabled(True) + self.pushButton_next30s.setEnabled(True) + self.pushButton_previous60s.setEnabled(True) + self.pushButton_next60s.setEnabled(True) + self.radioButton_2_class.setChecked(True) + self.radioButton_3_class.setEnabled(False) + elif self.checkBox_examineBySecond.isChecked() == False: + self.checkBox_examineLabeled.setEnabled(True) + self.lineEdit_remark.setText("") + self.pushButton_previous10s.setEnabled(False) + self.pushButton_next10s.setEnabled(False) + self.pushButton_previous30s.setEnabled(False) + self.pushButton_next30s.setEnabled(False) + self.pushButton_previous60s.setEnabled(False) + self.pushButton_next60s.setEnabled(False) + self.radioButton_3_class.setEnabled(True) + if self.plotEventIndex <= self.start_bcg_index: + self.pushButton_left.setEnabled(False) + self.pushButton_right.setEnabled(True) + self.msgBox.setText("你正在查看第一个事件") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + elif self.plotEventIndex >= len(self.bcg_event_label_filtered_df) - 1: + self.pushButton_left.setEnabled(True) + self.pushButton_right.setEnabled(False) + self.msgBox.setText("你正在查看最后一个事件") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.pushButton_left.setEnabled(True) + self.pushButton_right.setEnabled(True) + + # matplot中更改x轴范围后执行的函数 + def on_xlim_change(self, event_ax): + # 获取当前x轴范围 + left, right = event_ax.get_xlim() + self.lineEdit_correctStart.setText(str(round(left))) + self.lineEdit_correctEnd.setText(str(round(right))) + + # 点击了matplot中的HOME键后执行的槽函数 + def slot_btn_resetOriginalView(self): + self.lineEdit_correctStart.setText(str(self.bcg_SP)) + self.lineEdit_correctEnd.setText(str(self.bcg_EP)) + + # 用于更新信息输出中的textBrowser的函数 + def textBrowser_update(self, message): + self.textBrowser_infoOutput.append(str(datetime.now().strftime("%H:%M:%S")) + ": " + message) + + # 修改了事件类型单选框后执行的函数 + def change_radioButton_events(self, index): + if self.pd.at[index, "Event type"] == "Obstructive apnea": + self.radioButton_OSA.setChecked(True) + elif self.pd.at[index, "Event type"] == "Central apnea": + self.radioButton_CSA.setChecked(True) + elif self.pd.at[index, "Event type"] == "Mixed apnea": + self.radioButton_MSA.setChecked(True) + elif self.pd.at[index, "Event type"] == "Hypopnea": + self.radioButton_HPY.setChecked(True) + + # 点击开始打标按钮后的初始化变量函数 + def init_variable(self): + self.sampNo = int(self.comboBox_sampID.currentText()) + self.channel_list = ['Effort Tho', 'Effort Abd', 'SpO2', 'Flow Patient', 'Flow Patient'] + self.focus_event_list = self.set_focus_event_list() + self.frequency = int(self.lineEdit_frequency.text()) + self.bcg_frequency = int(self.lineEdit_bcg_frequency.text()) + self.front_add_second = int(self.lineEdit_front_add_second.text()) + self.back_add_second = int(self.lineEdit_back_add_second.text()) + self.extend_second = int(self.front_add_second + self.back_add_second) + self.start_bcg_index = int(self.lineEdit_start_bcg_index.text()) + self.plotEventIndex = self.start_bcg_index + + self.ecg_start_time = None + + # 用来显示颜色时按点匹配事件 + self.ecg_event_label = None + self.bcg_event_label = None + self.spo2_event_label = None + self.artifact_event_label = None + + # 仅包含关注暂停事件的列表 + self.ecg_event_label_filtered_df = None + self.bcg_event_label_filtered_df = None + + # 所有事件列表 + self.ecg_event_label_df = None + self.bcg_event_label_df = None + + # 各通道信号 + self.signal_select = {} + + def check_channel(self): + for i in self.channel_list: + if i not in self.base_channel: + logging.debug(f"{i} 不存在于常见通道名中") + print(f"常见通道名:{self.channel_list}") + + def set_focus_event_list(self): + focus_event_list = [] + if self.checkBox_HPY.isChecked() == True: + focus_event_list.append("Hypopnea") + if self.checkBox_CSA.isChecked() == True: + focus_event_list.append("Central apnea") + if self.checkBox_OSA.isChecked() == True: + focus_event_list.append("Obstructive apnea") + if self.checkBox_MSA.isChecked() == True: + focus_event_list.append("Mixed apnea") + return focus_event_list + + def read_data(self): + bcg_path = self.BCG_Data_Path / f"{self.sampNo}samp.npy" + ecg_path = self.PSG_Data_Path / f"A{str(self.sampNo).rjust(7, '0')}.edf" + + if not bcg_path.exists(): + logging.error(f"Can't find {bcg_path} !") + raise FileNotFoundError(f"Can't find {bcg_path} !") + + if not ecg_path.exists(): + logging.error(f"Can't find {ecg_path} !") + raise FileNotFoundError(f"Can't find {ecg_path} !") + + with pyedflib.EdfReader(str(ecg_path.resolve())) as file: + signal_num = file.signals_in_file + logging.debug(f"{self.sampNo} EDF file signal number is {signal_num}") + + signal_label = file.getSignalLabels() + logging.debug(f"{self.sampNo} EDF file signal label : {signal_label}") + + self.ecg_start_time = file.getStartdatetime() + + # 根据PSG记录长度生成事件表 + self.ecg_event_label = np.zeros( + int(file.getFileDuration()) * self.frequency + self.extend_second * self.frequency) + self.spo2_event_label = np.zeros( + int(file.getFileDuration()) * self.frequency + self.extend_second * self.frequency) + # 打印PSG信息 + file.file_info_long() + + # sub_index 用于区分两个flow patient + sub_index = 1 + + logging.info("Loading PSG signal...") + self.textBrowser_update("提示:正在加载PSG信号") + for i, index in enumerate(signal_label): + # 仅加载选中的通道 + if index in self.channel_list: + # 重命名flow patient通道 + if index == 'Flow Patient': + if sub_index == 1: + index = "Flow T" + else: + index = "Flow P" + sub_index += 1 + signal = file.readSignal(i) + sample_frequency = file.getSampleFrequency(i) + # 读取采样率 进行重采样 + if sample_frequency < self.frequency: + signal = signal.repeat(self.frequency / sample_frequency) + elif sample_frequency > self.frequency: + signal = signal[::int(sample_frequency / self.frequency)] + self.signal_select[index] = signal + logging.info(f"Finished load PSG: {index}") + self.textBrowser_update("提示:完成加载PSG信号") + + # 加载心晓信号 + logging.info("Loading XinXiao signal...") + self.textBrowser_update("提示:正在加载心晓信号") + signal = np.load(bcg_path) + preprocessing = BCG_Operation(sample_rate=self.bcg_frequency) + # 20Hz低通去噪 + signal1 = preprocessing.Butterworth(signal, 'lowpass', low_cut=20, order=3) + # 0.7Hz 低通提取呼吸 + signal2 = preprocessing.Butterworth(signal, 'lowpass', low_cut=0.7, order=3) + + # 进行降采样 + signal1 = signal1[::int(self.bcg_frequency / self.frequency)] + signal2 = signal2[::int(self.bcg_frequency / self.frequency)] + + # 根据心晓事件长度生成事件记录 + self.bcg_event_label = np.zeros(len(signal) + self.extend_second * self.frequency) + self.signal_select['orgdata'] = signal1 + self.signal_select['0.7lowpass_resp'] = signal2 + + for signal_key in self.signal_select.keys(): + self.signal_select[signal_key] = np.append(self.signal_select[signal_key], + self.signal_select[signal_key].mean().astype(int).repeat( + self.extend_second * self.frequency)) + logging.info("Finished load XinXiao signal") + self.textBrowser_update("提示:完成加载心晓信号") + + def read_event(self): + bcg_label_path = self.BCG_Label_Path / f"export{self.sampNo}_all.csv" + ecg_label_path = self.PSG_Label_Path / f"export{self.sampNo}.csv" + + if not bcg_label_path.exists(): + logging.error(f"Can't find {bcg_label_path} !") + raise FileNotFoundError(f"Can't find {bcg_label_path} !") + + if not ecg_label_path.exists(): + logging.error(f"Can't find {ecg_label_path} !") + raise FileNotFoundError(f"Can't find {ecg_label_path} !") + + df = pd.read_csv(ecg_label_path, encoding="gbk") + self.ecg_event_label_df = df + + # 过滤不关注的事件 + df2 = df[df["Event type"].isin(self.focus_event_list)] + # 根据epoch进行排列方便索引 + df2 = df2.sort_values(by='Epoch') + self.ecg_event_label_filtered_df = df2 + + logging.info("Traversaling PSG events...") + self.textBrowser_update("提示:正在遍历PSG事件") + for one_data in tqdm(df.index, ncols=80): + one_data = df.loc[one_data] + + # 通过开始时间推算事件起始点与结束点 + event_start_time = datetime.strptime(one_data["Date"] + " " + one_data["Time"], '%Y/%m/%d %H:%M:%S') + SP = (event_start_time - self.ecg_start_time).seconds + # 对括号进行切分,避免Duration 20 (20) 这种带括号的问题 + EP = int(SP + float(one_data["Duration"].split("(")[0])) + SP *= self.frequency + EP *= self.frequency + + # 对事件重新编码并存到事件记录表中 + if one_data["Event type"] == "Hypopnea": + self.ecg_event_label[SP:EP] = 1 + elif one_data["Event type"] == "Central apnea": + self.ecg_event_label[SP:EP] = 2 + elif one_data["Event type"] == "Obstructive apnea": + self.ecg_event_label[SP:EP] = 3 + elif one_data["Event type"] == "Mixed apnea": + self.ecg_event_label[SP:EP] = 4 + elif one_data["Event type"] == "Desaturation": + self.spo2_event_label[SP:EP] = 5 + logging.info("Finished Traversal PSG events") + self.textBrowser_update("提示:完成遍历PSG事件") + + # 读取心晓事件 + df = pd.read_csv(bcg_label_path, encoding="gbk") + df["new_start"] = df["new_start"].astype("int") + df["new_end"] = df["new_end"].astype("int") + self.bcg_event_label_df = df + + # 过滤不关注事件 + df2 = df[df["Event type"].isin(self.focus_event_list)] + df2 = df2.sort_values(by='Epoch') + self.bcg_event_label_filtered_df = df2 + self.bcg_event_label_index_list = df2.index.tolist() + logging.info("Traversaling XinXiao events...") + self.textBrowser_update("提示:正在遍历心晓事件") + for one_data in tqdm(df.index): + one_data = df.loc[one_data] + SP = one_data["new_start"] * self.frequency + EP = one_data["new_end"] * self.frequency + + if one_data["Event type"] == "Hypopnea": + self.bcg_event_label[SP:EP] = 1 + elif one_data["Event type"] == "Central apnea": + self.bcg_event_label[SP:EP] = 2 + elif one_data["Event type"] == "Obstructive apnea": + self.bcg_event_label[SP:EP] = 3 + elif one_data["Event type"] == "Mixed apnea": + self.bcg_event_label[SP:EP] = 4 + logging.info("Finished Traversal XinXiao events") + self.textBrowser_update("提示:完成遍历心晓事件") + + def read_artifact_label(self): + all_offset_length = pd.read_excel(self.Artifact_Offset_Path) + offset_length = all_offset_length[all_offset_length['数据编号'] == self.sampNo]['from_code'].values[0] + artifact_label_path = self.Artifact_Label_Path / f"Artifact_{self.sampNo}.txt" + artifact_label = pd.read_csv(artifact_label_path, header=None).to_numpy().reshape(-1, 4) + + self.artifact_event_label = np.zeros(len(self.bcg_event_label) + self.extend_second * self.frequency) + + for i, artifact_type, SP, EP in artifact_label: + SP = (int(SP) + offset_length) // (1000 // self.frequency) + EP = (int(EP) + offset_length) // (1000 // self.frequency) + artifact_type = int(artifact_type) + 5 + + SP = 0 if SP < 0 else SP + if EP < 0: + continue + + if SP > len(self.bcg_event_label): + continue + + self.artifact_event_label[SP:EP] = artifact_type + + # assert len(self.ecg_event_label_filtered_df) == len(self.bcg_event_label_filtered_df), \ + # f"PSG与心晓事件数量不一致, PSG事件数量{len(self.ecg_event_label_filtered_df)}, + # 心晓事件数量{len(self.bcg_event_label_filtered_df)}" + + def show_one_event(self, bcg_index: int, ecg_index: int, front_add_second: int, back_add_second: int): + """ + :param bcg_index: 心晓事件在csv中行号 + :param ecg_index: PSG事件在csv中序号 + :param front_add_second: 向前延伸时间 + :param back_add_second: 向后延伸时间 + :return: + """ + # 获取事件实际在csv文件中的序号 + bcg_real_index = self.bcg_event_label_filtered_df.index[bcg_index], + ecg_real_index = self.ecg_event_label_filtered_df.index[ecg_index], + one_bcg_data = self.bcg_event_label_df.loc[bcg_real_index] + one_ecg_data = self.ecg_event_label_df.loc[ecg_real_index] + + # 获取ECG事件开始与结束时间 + event_start_time = datetime.strptime(one_ecg_data["Date"] + " " + one_ecg_data["Time"], '%Y/%m/%d %H:%M:%S') + ecg_SP = (event_start_time - self.ecg_start_time).seconds + ecg_duration = int(float(str(one_ecg_data["Duration"]).split("(")[0]) + 0.5) + ecg_EP = ecg_SP + ecg_duration + + # 获取BCG事件开始与结束时间 + bcg_SP = one_bcg_data["new_start"] + bcg_EP = one_bcg_data["new_end"] + self.bcg_SP = bcg_SP + self.bcg_EP = bcg_EP + bcg_duration = bcg_EP - bcg_SP + + logging.info(f"sampNo:{self.sampNo} " + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + + if one_bcg_data['Event type'] != one_ecg_data['Event type']: + logging.error(f"sampNo:{self.sampNo} PSG事件与心晓时间不一致,请排查" + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + raise ValueError() + + self.lineEdit_correctStart.setText(str(self.bcg_SP)) + self.lineEdit_correctEnd.setText(str(self.bcg_EP)) + + # 进行向两边延展 + ecg_SP = ecg_SP - front_add_second + ecg_EP = ecg_EP + back_add_second + bcg_SP = bcg_SP - front_add_second + bcg_EP = bcg_EP + back_add_second + + # 各个子图之间的比例 + gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2]) + 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([]) + + # 绘制 Flow1 + self.ax0 = self.figure.add_subplot(gs[0]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow T") + + # 绘制 Flow2 + self.ax1 = self.figure.add_subplot(gs[1], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow P") + + self.ax2 = self.figure.add_subplot(gs[2], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Tho") + + self.ax3 = self.figure.add_subplot(gs[3], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Abd") + + self.ax4 = self.figure.add_subplot(gs[4], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="SpO2", event_code=[5]) + + self.ax5 = self.figure.add_subplot(gs[5], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="orgdata", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False) + + self.ax6 = self.figure.add_subplot(gs[6], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="0.7lowpass_resp", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False, + title=f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)}") + + self.label_PSG_event.setText(f"PSG sampNo:{self.sampNo} Index:{ecg_index + 1}/{len(self.ecg_event_label_filtered_df)} " + f"Epoch:{one_ecg_data['Epoch']} Duration:{ecg_duration}s") + self.label_BCG_event.setText(f"心晓 sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} " + f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}s") + + # figManager = plt.get_current_fig_manager() + # figManager.window.showMaximized() + self.canvas.draw() + self.ax0.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax)) + + def plt_channel(self, plt_, SP, EP, channel, event_code=[1, 2, 3, 4], event_show_under=False, + ax_top=True, ax_bottom=True, ax_left=True, ax_right=True, title=None): + """ + :param plt_: + :param SP: 显示开始秒数 + :param EP: 显示结束秒数 + :param channel: 通道名称 + :param event_code: 要上色的事件编号 + :param event_show_under: 事件颜色显示在信号下面 + :param ax_top: 显示上框线 + :param ax_bottom: 显示下框线 + :param ax_left: 显示左框线 + :param ax_right: 显示右框线 + :return: + """ + linestyle = "-" + SP = 0 if SP < 0 else SP + plt_.plot(np.linspace(SP, EP, (EP - SP) * self.frequency), + self.signal_select[channel][SP * self.frequency:EP * self.frequency], label=channel, + color=self.color_cycle[0]) + + for j in event_code: + if channel == "SpO2": + mask = self.spo2_event_label[SP * self.frequency:EP * self.frequency] == j + elif channel == "orgdata" or channel == "0.7lowpass_resp": + if j <= 5: + mask = self.bcg_event_label[SP * self.frequency:EP * self.frequency] == j + else: + mask = self.artifact_event_label[SP * self.frequency:EP * self.frequency] == j + linestyle = "-" + else: + mask = self.ecg_event_label[SP * self.frequency:EP * self.frequency] == j + + if event_show_under: + min_point = self.signal_select[channel][SP * self.frequency:EP * self.frequency].min() + len_segment = EP * self.frequency - SP * self.frequency + y = (min_point.repeat(len_segment) * mask).astype('float') + np.place(y, y == 0, np.nan) + else: + y = (self.signal_select[channel][SP * self.frequency:EP * self.frequency] * mask).astype('float') + + np.place(y, y == 0, np.nan) + plt_.plot(np.linspace(SP, EP, (EP - SP) * self.frequency), y, color=self.color_cycle[j], + linestyle=linestyle) + plt_.legend(fontsize=8, loc=1) + if title is not None: + plt_.title(title) + ax = plt.gca() + ax.grid(True) + ax.spines["top"].set_visible(ax_top) + ax.spines["right"].set_visible(ax_right) + ax.spines["bottom"].set_visible(ax_bottom) + ax.spines["left"].set_visible(ax_left) + # xticks = [[]] if xticks else [range(SP, EP, 5), [str(i) for i in range(0, (EP - SP), 5)]] + # print(xticks) + # plt_.xticks(*xticks) # 去掉x轴 + + def show_new_event(self, bcg_index: int, ecg_index: int, front_add_second: int, back_add_second: int, time_move_count: int): + """ + :param bcg_index: 心晓事件在csv中行号 + :param ecg_index: PSG事件在csv中序号 + :param front_add_second: 向前延伸时间 + :param back_add_second: 向后延伸时间 + :param time_move_count: 时间轴移动的时间 + :return: + """ + # 获取事件实际在csv文件中的序号 + bcg_real_index = self.bcg_event_label_filtered_df.index[bcg_index], + ecg_real_index = self.ecg_event_label_filtered_df.index[ecg_index], + one_bcg_data = self.bcg_event_label_df.loc[bcg_real_index] + one_ecg_data = self.ecg_event_label_df.loc[ecg_real_index] + + # 获取ECG事件开始与结束时间 + event_start_time = datetime.strptime(one_ecg_data["Date"] + " " + one_ecg_data["Time"], '%Y/%m/%d %H:%M:%S') + ecg_SP = (event_start_time - self.ecg_start_time).seconds + ecg_duration = int(float(str(one_ecg_data["Duration"]).split("(")[0]) + 0.5) + ecg_EP = ecg_SP + ecg_duration + + # 获取BCG事件开始与结束时间 + bcg_SP = one_bcg_data["new_start"] + bcg_EP = one_bcg_data["new_end"] + bcg_duration = bcg_EP - bcg_SP + + logging.info(f"sampNo:{self.sampNo} " + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + + if one_bcg_data['Event type'] != one_ecg_data['Event type']: + logging.error(f"sampNo:{self.sampNo} PSG事件与心晓时间不一致,请排查" + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + raise ValueError() + + # 进行向两边延展 + ecg_SP = ecg_SP - front_add_second + time_move_count + ecg_EP = ecg_EP + back_add_second + time_move_count + bcg_SP = bcg_SP - front_add_second + time_move_count + bcg_EP = bcg_EP + back_add_second + time_move_count + + self.bcg_SP = bcg_SP + self.bcg_EP = bcg_EP + self.lineEdit_correctStart.setText(str(self.bcg_SP)) + self.lineEdit_correctEnd.setText(str(self.bcg_EP)) + + # 各个子图之间的比例 + gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 2]) + 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([]) + + # 绘制 Flow1 + self.ax0 = self.figure.add_subplot(gs[0]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow T") + + # 绘制 Flow2 + self.ax1 = self.figure.add_subplot(gs[1], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow P") + + self.ax2 = self.figure.add_subplot(gs[2], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Tho") + + self.ax3 = self.figure.add_subplot(gs[3], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Abd") + + self.ax4 = self.figure.add_subplot(gs[4], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="SpO2", event_code=[5]) + + self.ax5 = self.figure.add_subplot(gs[5], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="orgdata", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False) + + self.ax6 = self.figure.add_subplot(gs[6], sharex=self.ax0) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="0.7lowpass_resp", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False, + title=f"sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)}") + + self.label_PSG_event.setText( + f"PSG sampNo:{self.sampNo} Index:{ecg_index + 1}/{len(self.ecg_event_label_filtered_df)} " + f"Epoch:{one_ecg_data['Epoch']} Duration:{ecg_duration}s") + self.label_BCG_event.setText( + f"心晓 sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} " + f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}s") + + # figManager = plt.get_current_fig_manager() + # figManager.window.showMaximized() + self.canvas.draw() + self.ax0.callbacks.connect('xlim_changed', lambda ax: self.on_xlim_change(ax)) + + def pd_add_new_row(self, df, score: int, remark: str, correct_Start: int, correct_End: int, correct_EventsType: str, isLabeled: int): + new_row = pd.Series(index=df.columns, data=np.nan) + new_row["score"] = score + new_row["remark"] = remark + new_row["correct_Start"] = correct_Start + new_row["correct_End"] = correct_End + new_row["correct_EventsType"] = correct_EventsType + new_row["isLabeled"] = isLabeled + return pd.concat([df, pd.DataFrame([new_row])], ignore_index=True) + +# 此线程类用于更新textBrowser的内容 +class Thread_textbrowserUpdate(QtCore.QThread): + trigger = QtCore.pyqtSignal(str) + + def __init__(self): + super(Thread_textbrowserUpdate, self).__init__() + + def run_(self, message): + self.trigger.emit(message) + +# 此类用于继承matplot的NavigationToolbar类,然后写回调函数 +class CustomNavigationToolbar(NavigationToolbar2QT): + def __init__(self, canvas, parent, callback): + super().__init__(canvas, parent) + self.callback = callback + + def home(self, *args): + super().home(*args) # 执行原始功能 + if self.callback: + self.callback() # 执行自定义回调 + +# 主函数 +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + mainWindow = MainWindow() + mainWindow.show() + sys.exit(app.exec_()) diff --git a/utils/Preprocessing.py b/utils/Preprocessing.py new file mode 100644 index 0000000..2085357 --- /dev/null +++ b/utils/Preprocessing.py @@ -0,0 +1,179 @@ +# encoding:utf-8 + +""" +@ date: 2020-09-16 +@ author: jingxian +@ illustration: Pre-processing +""" + +import sys +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + +import pywt +from scipy import signal +from scipy import fftpack + + +def Dilate(x, N, g, M): + returndata = np.array([]) + for num in range(N - M + 1): + returndata = np.append(returndata, np.min(np.array(x[num:num + M]) - np.array(g))) + return returndata + + +def Eorde(x, N, g, M): + returndata = np.array([]) + for num in range(N - M + 1): + returndata = np.append(returndata, np.max(np.array(x[num:num + M]) - np.array(g))) + return returndata + + +def fin_turn(data, peak): + if len(data) == 0 or len(peak) == 0: return peak + return_peak = [] + for p in peak: + minx, maxx = max(0, p - 100), min(len(data), p + 100) + return_peak.append(minx + np.argmax(data[minx: maxx])) + return return_peak + + +class BCG_Operation(): + def __init__(self, sample_rate=1000): + self.sample_rate = sample_rate + + def down_sample(self, data=None, down_radio=10): + if data is None: + raise ValueError("data is None, please given an real value!") + data = data[:len(data) // down_radio * down_radio].reshape(-1, down_radio)[:, 0] + self.sample_rate = self.sample_rate / down_radio + return data + + def Splitwin(self, data=None, len_win=None, coverage=1.0, calculate_to_end=False): + """ + 分窗 + :param len_win: length of window + :return: signal windows + """ + if (len_win is None) or (data is None): + raise ValueError("length of window or data is None, please given an real value!") + else: + length = len_win * self.sample_rate # number point of a window + # step of split windows + step = length * coverage + start = 0 + Splitdata = [] + while (len(data) - start >= length): + Splitdata.append(data[int(start):int(start + length)]) + start += step + if calculate_to_end and (len(data) - start > 2000): + remain = len(data) - start + start = start - step + step = int(remain / 2000) + start = start + step * 2000 + Splitdata.append(data[int(start):int(start + length)]) + return np.array(Splitdata), step + elif calculate_to_end: + return np.array(Splitdata), 0 + else: + return np.array(Splitdata) + + def Butterworth(self, data, type, low_cut=0.0, high_cut=0.0, order=10): + """ + :param type: Type of Butter. filter, lowpass, bandpass, ... + :param lowcut: Low cutoff frequency + :param highcut: High cutoff frequency + :param order: Order of filter + :return: Signal after filtering + """ + if type == "lowpass": # 低通滤波处理 + b, a = signal.butter(order, low_cut / (self.sample_rate * 0.5), btype='lowpass') + return signal.filtfilt(b, a, np.array(data)) + elif type == "bandpass": # 带通滤波处理 + low = low_cut / (self.sample_rate * 0.5) + high = high_cut / (self.sample_rate * 0.5) + b, a = signal.butter(order, [low, high], btype='bandpass') + return signal.filtfilt(b, a, np.array(data)) + elif type == "highpass": # 高通滤波处理 + b, a = signal.butter(order, high_cut / (self.sample_rate * 0.5), btype='highpass') + return signal.filtfilt(b, a, np.array(data)) + else: # 警告,滤波器类型必须有 + raise ValueError("Please choose a type of fliter") + + def MorphologicalFilter(self, data=None, M=200, get_bre=False): + """ + :param data: Input signal + :param M: Length of structural element + :return: Signal after filter + """ + if not data.any(): + raise ValueError("The input data is None, please given real value data") + g = np.ones(M) + Data_pre = np.insert(data, 0, np.zeros(M)) + Data_pre = np.insert(Data_pre, -1, np.zeros(M)) + # Opening: 腐蚀 + 膨胀 + out1 = Eorde(Data_pre, len(Data_pre), g, M) + out2 = Dilate(out1, len(out1), g, M) + out2 = np.insert(out2, 0, np.zeros(M - 2)) + # Closing: 膨胀 + 腐蚀 + out5 = Dilate(Data_pre, len(Data_pre), g, M) + out6 = Eorde(out5, len(out5), g, M) + out6 = np.insert(out6, 0, np.zeros(M - 2)) + + baseline = (out2 + out6) / 2 + # -------------------------保留剩余价值------------------------ + data_filtered = Data_pre[:len(baseline)] - baseline + data_filtered = data_filtered[M: M + len(data)] + baseline = baseline[M:] + data_filtered[-1] = data_filtered[-2] = data_filtered[-3] + baseline[-1] = baseline[-2] = baseline[-3] + if get_bre: + return data_filtered, baseline + else: + return data_filtered + + def Iirnotch(self, data=None, cut_fre=50, quality=3): + """陷波器""" + b, a = signal.iirnotch(cut_fre / (self.sample_rate * 0.5), quality) + return signal.filtfilt(b, a, np.array(data)) + + def ChebyFilter(self, data, rp=1, type=None, low_cut=0, high_cut=0, order=10): + """ + 切比雪夫滤波器 + :param data: Input signal + :param rp: The maximum ripple allowed + :param type: 'lowpass', 'bandpass, 'highpass' + :param low_cut: Low cut-off fre + :param high_cut: High cut-off fre + :param order: The order of filter + :return: Signal after filter + """ + if type == 'lowpass': + b, a = signal.cheby1(order, rp, low_cut, btype='lowpass', fs=self.sample_rate) + return signal.filtfilt(b, a, np.array(data)) + elif type == 'bandpass': + b, a = signal.cheby1(order, rp, [low_cut, high_cut], btype='bandpass', fs=self.sample_rate) + return signal.filtfilt(b, a, np.array(data)) + elif type == 'highpass': + b, a = signal.cheby1(order, rp, high_cut, btype='highpass', fs=self.sample_rate) + return signal.filtfilt(b, a, np.array(data)) + else: + raise ValueError("The type of filter is None, please given the real value!") + + def Envelope(self, data): + """取信号包络""" + if len(data) <= 1: raise ValueError("Wrong input data") + hx = fftpack.hilbert(data) + return np.sqrt(hx ** 2, data ** 2) + + def wavelet_trans(self, data, c_level=['aaa', 'aad'], wavelet='db4', mode='symmetric', maxlevel=10): + wp = pywt.WaveletPacket(data=data, wavelet=wavelet, mode=mode, maxlevel=maxlevel) + new_wp = pywt.WaveletPacket(data=None, wavelet=wavelet, mode=mode) + for c in c_level: + new_wp[c] = wp[c] + return new_wp.reconstruct() + + # def em_decomposition(self, data): + # from pyhht.emd import EMD + # return EMD(data).decompose() diff --git a/utils/Quality_Relabel.py b/utils/Quality_Relabel.py new file mode 100644 index 0000000..0b64b93 --- /dev/null +++ b/utils/Quality_Relabel.py @@ -0,0 +1,470 @@ +# -*- coding: cp936 -*- +# ʹgbkܶǩ +""" +@author:Marques +@file:Prepare_Data.py +@email:admin@marques22.com +@email:2021022362@m.scnu.edu.cn +@time:2022/03/26 +""" +import time +from typing import List +import logging +import pyedflib +from pathlib import Path +import numpy as np +import pandas as pd +import matplotlib +from matplotlib import pyplot as plt, gridspec +from utils.Preprocessing import BCG_Operation +from tqdm import tqdm +from datetime import datetime + +matplotlib.use("Qt5Agg") + +plt.rcParams['font.sans-serif'] = ['SimHei'] # ʾıǩ +plt.rcParams['axes.unicode_minus'] = False # ʾ + +# ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2', +# 'EEG O2-A1', 'EOG Right', 'EOG Left', 'EMG Chin', 'ECG I', 'RR', +# 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'Effort Abd', +# 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2', +# 'EEG A1-A2', 'Imp'] + + +# ־ +logger = logging.getLogger() +logger.setLevel(logging.NOTSET) +realtime = time.strftime('%Y%m%d', time.localtime(time.time())) +fh = logging.FileHandler(Path("history") / (realtime + ".log"), mode='a') +fh.setLevel(logging.NOTSET) +fh.setFormatter(logging.Formatter("%(asctime)s: %(message)s")) +logger.addHandler(fh) + +ch = logging.StreamHandler() +ch.setLevel(logging.NOTSET) +ch.setFormatter(logging.Formatter("%(asctime)s: %(message)s")) +logger.addHandler(ch) +logging.getLogger('matplotlib.font_manager').disabled = True +logging.info("------------------------------------") + + +class Quality_Relabel: + # ѡͨ + base_channel = ['EEG F3-A2', 'EEG F4-A1', 'EEG C3-A2', 'EEG C4-A1', 'EEG O1-A2', 'EEG O2-A1', 'EOG Right', + 'EOG Left', 'EMG Chin', 'ECG I', 'RR', 'ECG II', 'Effort Tho', 'Flow Patient', 'Flow Patient', 'HR', + 'Effort Abd', 'SpO2', 'Pleth', 'Snore', 'Body', 'Pulse', 'Leg LEG1', 'Leg LEG2', 'EEG A1-A2', 'Imp'] + + # ʾ¼ + base_event = ["Hypopnea", "Central apnea", "Obstructive apnea", "Mixed apnea", "Desaturation"] + + # 趨¼Ӧɫ + # event_code color event + # 0 ɫ + # 1 ɫ ͨ + # 2 ɫ + # 3 ɫ + # 4 ɫ + # 5 ɫ ѪͶ½ + # 6 ɫ 嶯 + # 7 ɫ С嶯 + # 8 ɫ + # 9 ɫ 嶯 + # 10 ɫ ЧƬ + color_cycle = ["black", "pink", "blue", "red", "silver", "green", "orange", "orange", "orange", "orange", "orange"] + + # assert len(color_cycle) == len(base_event) + 1, "¼ɫһ" + + def __init__(self, all_path: List, sampNo: int, frequency: int = 100, bcg_frequency: int = 1000, + extend_second: int = 0, + channel_list: List[str] = ['Effort Tho', 'Effort Abd', 'SpO2', 'Flow Patient', 'Flow Patient'], + focus_event_list: List[str] = ["Obstructive apnea"]): + """ + + :param sampNo: ѡ + :param frequency: ʾ + :param bcg_frequency: BCGźŲ + :param channel_list: ʾͨ + :param focus_event_list: עͣ¼ + """ + self.PSG_Data_Path = all_path[0] + self.PSG_Label_Path = all_path[1] + self.BCG_Data_Path = all_path[2] + self.BCG_Label_Path = all_path[3] + self.Artifact_Label_Path = all_path[4] + self.Artifact_Offset_Path = all_path[5] + + self.sampNo = sampNo + self.channel_list = channel_list + self.focus_event_list = focus_event_list + self.frequency = frequency + self.bcg_frequency = bcg_frequency + self.extend_second = extend_second + + self.ecg_start_time = None + + # ʾɫʱƥ¼ + self.ecg_event_label = None + self.bcg_event_label = None + self.spo2_event_label = None + self.artifact_event_label = None + + # עͣ¼б + self.ecg_event_label_filtered_df = None + self.bcg_event_label_filtered_df = None + + # ¼б + self.ecg_event_label_df = None + self.bcg_event_label_df = None + + # ͨź + self.signal_select = {} + + self.check_channel() + self.read_data(frequency, bcg_frequency) + self.read_event() + self.read_artifact_label() + + def check_channel(self): + for i in self.channel_list: + if i not in self.base_channel: + logging.debug(f"{i} ڳͨ") + print(f"ͨ{self.channel_list}") + + def read_data(self, frequency: int = 100, bcg_frequency: int = 1000): + bcg_path = self.BCG_Data_Path / f"{self.sampNo}samp.npy" + ecg_path = self.PSG_Data_Path / f"A{str(self.sampNo).rjust(7, '0')}.edf" + + if not bcg_path.exists(): + logging.error(f"{bcg_path} ڣ") + raise FileNotFoundError(f"{bcg_path} ڣ") + + if not ecg_path.exists(): + logging.error(f"{ecg_path} ڣ") + raise FileNotFoundError(f"{ecg_path} ڣ") + + with pyedflib.EdfReader(str(ecg_path.resolve())) as file: + signal_num = file.signals_in_file + logging.debug(f"{self.sampNo} EDF file signal number is {signal_num}") + + signal_label = file.getSignalLabels() + logging.debug(f"{self.sampNo} EDF file signal label : {signal_label}") + + self.ecg_start_time = file.getStartdatetime() + + # PSG¼¼ + self.ecg_event_label = np.zeros( + int(file.getFileDuration()) * self.frequency + self.extend_second * self.frequency) + self.spo2_event_label = np.zeros( + int(file.getFileDuration()) * self.frequency + self.extend_second * self.frequency) + # ӡPSGϢ + file.file_info_long() + + # sub_index flow patient + sub_index = 1 + + logging.info("ȡPSGźš") + for i, index in enumerate(signal_label): + # ѡеͨ + if index in self.channel_list: + # flow patientͨ + if index == 'Flow Patient': + if sub_index == 1: + index = "Flow T" + else: + index = "Flow P" + sub_index += 1 + signal = file.readSignal(i) + sample_frequency = file.getSampleFrequency(i) + # ȡ ز + if sample_frequency < frequency: + signal = signal.repeat(frequency / sample_frequency) + elif sample_frequency > frequency: + signal = signal[::int(sample_frequency / frequency)] + self.signal_select[index] = signal + logging.info(f"ɶȡPSG: {index}") + + # ź + logging.info("ȡźš") + signal = np.load(bcg_path) + preprocessing = BCG_Operation(sample_rate=bcg_frequency) + # 20Hzͨȥ + signal1 = preprocessing.Butterworth(signal, 'lowpass', low_cut=20, order=3) + # 0.7Hz ͨȡ + signal2 = preprocessing.Butterworth(signal, 'lowpass', low_cut=0.7, order=3) + + # н + signal1 = signal1[::int(bcg_frequency / frequency)] + signal2 = signal2[::int(bcg_frequency / frequency)] + + # ¼¼¼ + self.bcg_event_label = np.zeros(len(signal) + self.extend_second * self.frequency) + self.signal_select['orgdata'] = signal1 + self.signal_select['0.7lowpass_resp'] = signal2 + + for signal_key in self.signal_select.keys(): + self.signal_select[signal_key] = np.append(self.signal_select[signal_key], + self.signal_select[signal_key].mean().astype(int).repeat( + self.extend_second * self.frequency)) + + def read_event(self): + bcg_label_path = self.BCG_Label_Path / f"export{self.sampNo}_all.csv" + ecg_label_path = self.PSG_Label_Path / f"export{self.sampNo}.csv" + + if not bcg_label_path.exists(): + logging.error(f"{bcg_label_path} ڣ") + raise FileNotFoundError(f"{bcg_label_path} ڣ") + + if not ecg_label_path.exists(): + logging.error(f"{ecg_label_path} ڣ") + raise FileNotFoundError(f"{ecg_label_path} ڣ") + + df = pd.read_csv(ecg_label_path, encoding='gbk') + self.ecg_event_label_df = df + + # ˲ע¼ + df2 = df[df["Event type"].isin(self.focus_event_list)] + # epochз + df2 = df2.sort_values(by='Epoch') + self.ecg_event_label_filtered_df = df2 + + logging.info("PSG¼") + for one_data in tqdm(df.index, ncols=80): + one_data = df.loc[one_data] + + # ͨʼʱ¼ʼ + event_start_time = datetime.strptime(one_data["Date"] + " " + one_data["Time"], '%Y/%m/%d %H:%M:%S') + SP = (event_start_time - self.ecg_start_time).seconds + # Žз֣Duration 20 (20) ִŵ + EP = int(SP + float(one_data["Duration"].split("(")[0])) + SP *= self.frequency + EP *= self.frequency + + # ¼±벢浽¼¼ + if one_data["Event type"] == "Hypopnea": + self.ecg_event_label[SP:EP] = 1 + elif one_data["Event type"] == "Central apnea": + self.ecg_event_label[SP:EP] = 2 + elif one_data["Event type"] == "Obstructive apnea": + self.ecg_event_label[SP:EP] = 3 + elif one_data["Event type"] == "Mixed apnea": + self.ecg_event_label[SP:EP] = 4 + elif one_data["Event type"] == "Desaturation": + self.spo2_event_label[SP:EP] = 5 + + # ȡ¼ + df = pd.read_csv(bcg_label_path, encoding='gbk') + df["new_start"] = df["new_start"].astype("int") + df["new_end"] = df["new_end"].astype("int") + self.bcg_event_label_df = df + + # ˲ע¼ + df2 = df[df["Event type"].isin(self.focus_event_list)] + df2 = df2.sort_values(by='Epoch') + self.bcg_event_label_filtered_df = df2 + logging.info("¼") + for one_data in tqdm(df.index): + one_data = df.loc[one_data] + SP = one_data["new_start"] * self.frequency + EP = one_data["new_end"] * self.frequency + + if one_data["Event type"] == "Hypopnea": + self.bcg_event_label[SP:EP] = 1 + elif one_data["Event type"] == "Central apnea": + self.bcg_event_label[SP:EP] = 2 + elif one_data["Event type"] == "Obstructive apnea": + self.bcg_event_label[SP:EP] = 3 + elif one_data["Event type"] == "Mixed apnea": + self.bcg_event_label[SP:EP] = 4 + + def read_artifact_label(self): + all_offset_length = pd.read_excel(self.Artifact_Offset_Path) + offset_length = all_offset_length[all_offset_length['ݱ'] == self.sampNo]['from_code'].values[0] + artifact_label_path = self.Artifact_Label_Path / f"Artifact_{self.sampNo}.txt" + artifact_label = pd.read_csv(artifact_label_path, header=None).to_numpy().reshape(-1, 4) + + self.artifact_event_label = np.zeros(len(self.bcg_event_label) + self.extend_second * self.frequency) + + for i, artifact_type, SP, EP in artifact_label: + SP = (int(SP) + offset_length) // (1000 // self.frequency) + EP = (int(EP) + offset_length) // (1000 // self.frequency) + artifact_type = int(artifact_type) + 5 + + SP = 0 if SP < 0 else SP + if EP < 0: + continue + + if SP > len(self.bcg_event_label): + continue + + self.artifact_event_label[SP:EP] = artifact_type + + # assert len(self.ecg_event_label_filtered_df) == len(self.bcg_event_label_filtered_df), \ + # f"PSG¼һ, PSG¼{len(self.ecg_event_label_filtered_df)}, + # ¼{len(self.bcg_event_label_filtered_df)}" + + def show_one_event(self, bcg_index: int, ecg_index: int, front_add_second: int = 60, + back_add_second: int = 60): + """ + :param bcg_index: ¼csvк + :param ecg_index: PSG¼csv + :param front_add_second: ǰʱ + :param back_add_second: ʱ + :return: + """ + # ȡ¼ʵcsvļе + bcg_real_index = self.bcg_event_label_filtered_df.index[bcg_index], + ecg_real_index = self.ecg_event_label_filtered_df.index[ecg_index], + one_bcg_data = self.bcg_event_label_df.loc[bcg_real_index] + one_ecg_data = self.ecg_event_label_df.loc[ecg_real_index] + + # ȡECG¼ʼʱ + event_start_time = datetime.strptime(one_ecg_data["Date"] + " " + one_ecg_data["Time"], '%Y/%m/%d %H:%M:%S') + ecg_SP = (event_start_time - self.ecg_start_time).seconds + ecg_duration = int(float(str(one_ecg_data["Duration"]).split("(")[0]) + 0.5) + ecg_EP = ecg_SP + ecg_duration + + # ȡBCG¼ʼʱ + bcg_SP = one_bcg_data["new_start"] + bcg_EP = one_bcg_data["new_end"] + bcg_duration = bcg_EP - bcg_SP + + logging.info(f"sampNo:{self.sampNo} " + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + + if one_bcg_data['Event type'] != one_ecg_data['Event type']: + logging.error(f"sampNo:{self.sampNo} PSG¼ʱ䲻һ£Ų" + f"bcg[index:{bcg_index} epoch:{one_bcg_data['Epoch']} event:{one_bcg_data['Event type']}] " + f"ecg:[index:{ecg_index} epoch:{one_ecg_data['Epoch']} event:{one_ecg_data['Event type']}]") + raise ValueError() + + # չ + ecg_SP = ecg_SP - front_add_second + ecg_EP = ecg_EP + back_add_second + bcg_SP = bcg_SP - front_add_second - (ecg_duration - bcg_duration) // 2 + bcg_EP = bcg_EP + back_add_second + (ecg_duration - bcg_duration) // 2 + + # ͼ + plt.figure(figsize=(12, 6), dpi=150) + # ͼ֮ı + gs = gridspec.GridSpec(7, 1, height_ratios=[1, 1, 1, 1, 1, 3, 1]) + plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) + plt.margins(0, 0) + plt.tight_layout() + + # Flow1 + plt.subplot(gs[0]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow T") + + # Flow2 + plt.subplot(gs[1]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Flow P", + title=f"PSG sampNo:{self.sampNo} Index:{ecg_index + 1}/{len(self.ecg_event_label_filtered_df)} " + f"Epoch:{one_ecg_data['Epoch']} Duration:{ecg_duration}") + + plt.subplot(gs[2]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Tho") + + plt.subplot(gs[3]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="Effort Abd") + + plt.subplot(gs[4]) + self.plt_channel(plt_=plt, SP=ecg_SP, EP=ecg_EP, channel="SpO2", event_code=[5]) + + plt.subplot(gs[5]) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="orgdata", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False) + + plt.subplot(gs[6]) + self.plt_channel(plt_=plt, SP=bcg_SP, EP=bcg_EP, channel="0.7lowpass_resp", + event_code=[6, 7, 8, 9, 10, 1, 2, 3, 4], + event_show_under=False, + ax_bottom=True, + title=f" sampNo:{self.sampNo} Index:{bcg_index + 1}/{len(self.bcg_event_label_filtered_df)} " + f"Epoch:{one_bcg_data['Epoch']} Duration:{bcg_duration}", + ) + + figManager = plt.get_current_fig_manager() + figManager.window.showMaximized() + plt.show() + + def plt_channel(self, plt_, SP, EP, channel, event_code=[1, 2, 3, 4], event_show_under=False, + ax_top=False, ax_bottom=False, ax_left=True, ax_right=False, title=None): + """ + + :param plt_: + :param SP: ʾʼ + :param EP: ʾ + :param channel: ͨ + :param event_code: Ҫɫ¼ + :param event_show_under: ¼ɫʾź + :param ax_top: ʾϿ + :param ax_bottom: ʾ¿ + :param ax_left: ʾ + :param ax_right: ʾҿ + :param title: ʾ + :return: + """ + linestyle = "-" + SP = 0 if SP < 0 else SP + plt_.plot(np.linspace(SP, EP, (EP - SP) * self.frequency), + self.signal_select[channel][SP * self.frequency:EP * self.frequency], label=channel, + color=self.color_cycle[0]) + + for j in event_code: + if channel == "SpO2": + mask = self.spo2_event_label[SP * self.frequency:EP * self.frequency] == j + elif channel == "orgdata" or channel == "0.7lowpass_resp": + if j <= 5: + mask = self.bcg_event_label[SP * self.frequency:EP * self.frequency] == j + else: + mask = self.artifact_event_label[SP * self.frequency:EP * self.frequency] == j + linestyle = "-" + else: + mask = self.ecg_event_label[SP * self.frequency:EP * self.frequency] == j + + if event_show_under: + min_point = self.signal_select[channel][SP * self.frequency:EP * self.frequency].min() + len_segment = EP * self.frequency - SP * self.frequency + y = (min_point.repeat(len_segment) * mask).astype('float') + np.place(y, y == 0, np.nan) + else: + y = (self.signal_select[channel][SP * self.frequency:EP * self.frequency] * mask).astype('float') + + np.place(y, y == 0, np.nan) + plt_.plot(np.linspace(SP, EP, (EP - SP) * self.frequency), y, color=self.color_cycle[j], + linestyle=linestyle) + plt_.legend(loc=1) + + if title is not None: + plt_.title(title) + ax = plt.gca() + ax.spines["top"].set_visible(ax_top) + ax.spines["right"].set_visible(ax_right) + ax.spines["bottom"].set_visible(ax_bottom) + ax.spines["left"].set_visible(ax_left) + # xticks = [[]] if xticks else [range(SP, EP, 5), [str(i) for i in range(0, (EP - SP), 5)]] + # print(xticks) + # plt_.xticks(*xticks) # ȥx + + def show_all_event(self, start_bcg_index: int = 0, front_add_second: int = 60, + back_add_second: int = 60): + + for index in range(start_bcg_index, len(self.bcg_event_label_filtered_df)): + self.show_one_event(index, + index, + front_add_second=front_add_second, + back_add_second=back_add_second + ) + + +if __name__ == '__main__': + PSG_Data_Path = "../Data/PSG/" + PSG_Label_Path = "../Data/PSG_label/" + BCG_Data_Path = "../Data/BCG/" + BCG_Label_Path = "../Data/BCG_label/" + all_paths = [PSG_Data_Path, PSG_Label_Path, BCG_Data_Path, BCG_Label_Path] + prepareData = Quality_Relabel(all_paths, 670) + prepareData.show_all_event()