Merge branch 'master' into cxh_dev

This commit is contained in:
marques
2025-06-16 18:42:43 +08:00
8 changed files with 116 additions and 134 deletions

View File

@ -3,7 +3,6 @@ from pathlib import Path
from traceback import format_exc
import matplotlib.pyplot as plt
from PySide6.QtCore import QEvent
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication
from matplotlib.backends.backend_qt import NavigationToolbar2QT
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
@ -275,8 +274,6 @@ class MainWindow_approximately_align(QMainWindow):
PublicFunc.__resetAllButton__(self, ButtonState)
# self.ui.groupBox_align_position.setEnabled(False)
self.ui.pushButton_input.clicked.connect(self.__slot_btn_input__)
self.ui.pushButton_input_setting.clicked.connect(self.setting.show)
self.ui.pushButton_Standardize.clicked.connect(self.__slot_btn_Standardize__)

View File

@ -9,7 +9,7 @@ from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication, QTableWidg
from matplotlib import gridspec, patches
from matplotlib.backends.backend_qt import NavigationToolbar2QT
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from numpy import array, sum as np_sum, nonzero
from numpy import array
from numpy.fft import fft, fftfreq
from overrides import overrides
from pandas import read_csv, DataFrame, concat

View File

@ -365,6 +365,21 @@ class MainWindow(QMainWindow, Ui_Signal_Label):
path.mkdir(parents=True, exist_ok=True)
def set_dark_mode_status(self):
module_list = [
self,
self.approximately_align,
self.preprocess,
self.detect_Jpeak,
self.detect_Rpeak,
self.label_check,
self.precisely_align,
self.cut_PSG,
self.artifact_label,
self.bcg_quality_label,
self.resp_quality_label,
self.SA_label
]
try:
if self.ui.checkBox_darkmode.isChecked():
QApplication.styleHints().setColorScheme(Qt.ColorScheme.Dark)
@ -374,65 +389,12 @@ class MainWindow(QMainWindow, Ui_Signal_Label):
PublicFunc.msgbox_output(self, Constants.MAINWINDOW_DARKMODE_FAILURE + "" + format_exc(), Constants.MSGBOX_TYPE_ERROR)
return
try:
MainWindow.update_widget_style(self)
except RuntimeError:
pass
try:
if self.approximately_align is not None:
MainWindow.update_widget_style(self.approximately_align)
except RuntimeError:
pass
try:
if self.preprocess is not None:
MainWindow.update_widget_style(self.preprocess)
except RuntimeError:
pass
try:
if self.detect_Jpeak is not None:
MainWindow.update_widget_style(self.detect_Jpeak)
except RuntimeError:
pass
try:
if self.detect_Rpeak is not None:
MainWindow.update_widget_style(self.detect_Rpeak)
except RuntimeError:
pass
try:
if self.label_check is not None:
MainWindow.update_widget_style(self.label_check)
except RuntimeError:
pass
try:
if self.precisely_align is not None:
MainWindow.update_widget_style(self.precisely_align)
except RuntimeError:
pass
try:
if self.cut_PSG is not None:
MainWindow.update_widget_style(self.cut_PSG)
except RuntimeError:
pass
try:
if self.artifact_label is not None:
MainWindow.update_widget_style(self.artifact_label)
except RuntimeError:
pass
try:
if self.bcg_quality_label is not None:
MainWindow.update_widget_style(self.bcg_quality_label)
except RuntimeError:
pass
try:
if self.resp_quality_label is not None:
MainWindow.update_widget_style(self.resp_quality_label)
except RuntimeError:
pass
try:
if self.SA_label is not None:
MainWindow.update_widget_style(self.SA_label)
except RuntimeError:
pass
for module in module_list:
try:
if module is not None:
MainWindow.update_widget_style(module)
except RuntimeError:
pass
@staticmethod
def update_widget_style(mainWindow):

View File

@ -1645,9 +1645,6 @@ class Data:
self.correlation_align_point_match_ECG = array([]).astype(int)
self.correlation_align_point_match_BCG = array([]).astype(int)
self.argmax_BCG = None
self.argmax_ECG = None
def open_file(self):
if Path(Config["Path"]["Input_OrgBCG"]).is_file():
Config["Path"]["Input_OrgBCG"] = str(Path(Config["Path"]["Input_OrgBCG"]).parent)
@ -1740,8 +1737,6 @@ class Data:
self.Rpeak = read_csv(Config["Path"]["Input_Rpeak"],
encoding=Params.UTF8_ENCODING,
header=None).to_numpy().reshape(-1)
self.argmax_BCG = np_argmax(self.raw_BCG)
self.argmax_ECG = np_argmax(self.raw_ECG)
except Exception as e:
return Result().failure(info=Constants.INPUT_FAILURE +
Constants.FAILURE_REASON["Open_Data_Exception"] + "\n" + format_exc())
@ -1949,55 +1944,63 @@ class Data:
def correlation_align(self, mode):
try:
if mode == "init":
anchor0 = [Config["front"]["anchor_R"], Config["front"]["anchor_J"]]
anchor1 = [Config["back"]["anchor_R"], Config["back"]["anchor_J"]]
Config["orgfs"] = ((int(anchor1[1]) - int(anchor0[1])) * Config["InputConfig"]["UseFreq"] /
(int(anchor1[0]) - int(anchor0[0])))
Config["offset_anchor"] = anchor0[0] - anchor0[1]
orgfs = Config["orgfs"]
off = Config["offset_anchor"]
Config["orgfs"] = ((int(Config["back"]["anchor_J"]) - int(Config["front"]["anchor_J"])) * Config["InputConfig"]["UseFreq"] /
(int(Config["back"]["anchor_R"]) - int(Config["front"]["anchor_R"])))
Config["offset_anchor"] = Config["front"]["anchor_R"] - Config["front"]["anchor_J"]
self.res_orgBcg = self.raw_orgBcg.copy()
self.res_BCG = self.raw_BCG.copy()
self.cut_ECG = self.raw_ECG.copy()
self.cut_Rpeak = self.Rpeak.copy()
if off > 0:
self.cut_ECG = self.cut_ECG[off:]
anchor0[0] = anchor0[0] - off
anchor1[0] = anchor1[0] - off
idxs = where(self.cut_Rpeak > off)[0]
self.cut_Rpeak = self.cut_Rpeak[idxs] - off
Config["frontcut_index_BCG"], Config["frontcut_index_ECG"] = 0, 0
if Config["offset_anchor"] > 0:
self.cut_ECG = self.cut_ECG[Config["offset_anchor"]:]
Config["front"]["anchor_R"] = Config["front"]["anchor_R"] - Config["offset_anchor"]
Config["back"]["anchor_R"] = Config["back"]["anchor_R"] - Config["offset_anchor"]
idxs = where(self.cut_Rpeak > Config["offset_anchor"])[0]
self.cut_Rpeak = self.cut_Rpeak[idxs] - Config["offset_anchor"]
Config["frontcut_index_ECG"] += Config["offset_anchor"]
else:
self.res_BCG = self.res_BCG[-off:]
self.res_orgBcg = self.res_orgBcg[-off:]
anchor0[1] = anchor0[1] + off
anchor1[1] = anchor1[1] + off
self.res_BCG = self.res_BCG[-Config["offset_anchor"]:]
self.res_orgBcg = self.res_orgBcg[-Config["offset_anchor"]:]
Config["front"]["anchor_J"] = Config["front"]["anchor_J"] + Config["offset_anchor"]
Config["back"]["anchor_J"] = Config["back"]["anchor_J"] + Config["offset_anchor"]
Config["frontcut_index_BCG"] -= Config["offset_anchor"]
self.res_BCG = resample(self.res_BCG, orgfs, Config["InputConfig"]["UseFreq"])
self.res_orgBcg = resample(self.res_orgBcg, orgfs, Config["InputConfig"]["UseFreq"])
self.res_BCG = resample(self.res_BCG, Config["orgfs"], Config["InputConfig"]["UseFreq"])
self.res_orgBcg = resample(self.res_orgBcg, Config["orgfs"], Config["InputConfig"]["UseFreq"])
anchor0[1] = round(int(anchor0[1]) * Config["InputConfig"]["UseFreq"] / orgfs)
anchor1[1] = round(int(anchor1[1]) * Config["InputConfig"]["UseFreq"] / orgfs)
off = anchor1[0] - anchor1[1]
Config["front"]["anchor_J"] = round(int(Config["front"]["anchor_J"]) * Config["InputConfig"]["UseFreq"] / Config["orgfs"])
Config["back"]["anchor_J"] = round(int(Config["back"]["anchor_J"]) * Config["InputConfig"]["UseFreq"] / Config["orgfs"])
Config["offset_anchor"] = Config["back"]["anchor_R"] - Config["back"]["anchor_J"]
if off > 0:
self.cut_ECG = self.cut_ECG[off:]
anchor0[0] = anchor0[0] - off
anchor1[0] = anchor1[0] - off
idxs = where(self.cut_Rpeak > off)[0]
self.cut_Rpeak = self.cut_Rpeak[idxs] - off
if Config["offset_anchor"] > 0:
self.cut_ECG = self.cut_ECG[Config["offset_anchor"]:]
Config["front"]["anchor_R"] = Config["front"]["anchor_R"] - Config["offset_anchor"]
Config["back"]["anchor_R"] = Config["back"]["anchor_R"] - Config["offset_anchor"]
idxs = where(self.cut_Rpeak > Config["offset_anchor"])[0]
self.cut_Rpeak = self.cut_Rpeak[idxs] - Config["offset_anchor"]
Config["frontcut_index_ECG"] += Config["offset_anchor"] * Config["orgfs"] / Config["InputConfig"]["UseFreq"]
else:
self.res_BCG = self.res_BCG[-off:]
self.res_orgBcg = self.res_orgBcg[-off:]
anchor0[1] = anchor0[1] + off
anchor1[1] = anchor1[1] + off
self.res_BCG = self.res_BCG[-Config["offset_anchor"]:]
self.res_orgBcg = self.res_orgBcg[-Config["offset_anchor"]:]
Config["front"]["anchor_J"] = Config["front"]["anchor_J"] + Config["offset_anchor"]
Config["back"]["anchor_J"] = Config["back"]["anchor_J"] + Config["offset_anchor"]
Config["frontcut_index_BCG"] -= Config["offset_anchor"] * Config["orgfs"] / Config["InputConfig"]["UseFreq"]
datalen = np_min([len(self.cut_ECG), len(self.res_BCG)])
self.cut_ECG = self.cut_ECG[:datalen]
self.res_BCG = self.res_BCG[:datalen]
self.res_orgBcg = self.res_orgBcg[:datalen]
Config["frontcut_index_BCG"] = int(Config["frontcut_index_BCG"])
Config["frontcut_index_ECG"] = int(Config["frontcut_index_ECG"])
Config["backcut_index_BCG"] = int(Config["frontcut_index_BCG"] + datalen)
Config["backcut_index_ECG"] = int(Config["frontcut_index_ECG"] + datalen)
a = np_max([np_max(self.cut_ECG), np_max(self.res_BCG)])
b = np_min([np_min(self.cut_ECG), np_min(self.res_BCG)])
peak_ECG, _ = find_peaks(self.cut_ECG)
@ -2009,8 +2012,8 @@ class Data:
result = {
"res_BCG": self.res_BCG,
"cut_ECG": self.cut_ECG,
"anchor00": anchor0[0],
"anchor10": anchor1[0],
"anchor00": Config["front"]["anchor_R"],
"anchor10": Config["back"]["anchor_R"],
"a": a,
"b": b,
"peak_ECG": peak_ECG,
@ -2034,38 +2037,41 @@ class Data:
def data_postprocess(self):
try:
if len(self.correlation_align_point_match_ECG) != 2 and len(self.correlation_align_point_match_BCG) != 2:
off = 0
Config["offset_anchor"] = 0
else:
self.correlation_align_point_match_ECG.sort()
self.correlation_align_point_match_BCG.sort()
off = round(((int(self.correlation_align_point_match_ECG[1]) - int(
Config["offset_anchor"] = round(((int(self.correlation_align_point_match_ECG[1]) - int(
self.correlation_align_point_match_BCG[1])) + (int(self.correlation_align_point_match_ECG[0]) - int(
self.correlation_align_point_match_BCG[0]))) / 2)
anchor0 = [Config["front"]["anchor_R"], Config["front"]["anchor_J"]]
anchor1 = [Config["back"]["anchor_R"], Config["back"]["anchor_J"]]
if off > 0:
self.cut_ECG = self.cut_ECG[off:]
anchor0[0] = anchor0[0] - off
anchor1[0] = anchor1[0] - off
self.cut_Rpeak = self.cut_Rpeak[where(self.cut_Rpeak > off)[0]] - off
if Config["offset_anchor"] > 0:
self.cut_ECG = self.cut_ECG[Config["offset_anchor"]:]
Config["front"]["anchor_R"] = Config["front"]["anchor_R"] - Config["offset_anchor"]
Config["back"]["anchor_R"] = Config["back"]["anchor_R"] - Config["offset_anchor"]
self.cut_Rpeak = self.cut_Rpeak[where(self.cut_Rpeak > Config["offset_anchor"])[0]] - Config["offset_anchor"]
Config["frontcut_index_ECG"] += Config["offset_anchor"] * Config["orgfs"] / Config["InputConfig"]["UseFreq"]
else:
self.res_BCG = self.res_BCG[-off:]
self.res_orgBcg = self.res_orgBcg[-off:]
anchor0[1] = anchor0[1] + off
anchor1[1] = anchor1[1] + off
self.res_BCG = self.res_BCG[-Config["offset_anchor"]:]
self.res_orgBcg = self.res_orgBcg[-Config["offset_anchor"]:]
Config["front"]["anchor_J"] = Config["front"]["anchor_J"] + Config["offset_anchor"]
Config["back"]["anchor_J"] = Config["back"]["anchor_J"] + Config["offset_anchor"]
Config["frontcut_index_BCG"] -= Config["offset_anchor"] * Config["orgfs"] / Config["InputConfig"]["UseFreq"]
datalen = np_min([len(self.cut_ECG), len(self.res_BCG)])
self.cut_ECG = self.cut_ECG[:datalen]
self.res_BCG = self.res_BCG[:datalen]
self.res_orgBcg = self.res_orgBcg[:datalen]
Config["frontcut_index_BCG"] = int(Config["frontcut_index_BCG"])
Config["frontcut_index_ECG"] = int(Config["frontcut_index_ECG"])
Config["backcut_index_BCG"] = int(Config["frontcut_index_BCG"] + datalen)
Config["backcut_index_ECG"] = int(Config["frontcut_index_ECG"] + datalen)
idxs = where(self.cut_Rpeak < datalen)[0]
self.cut_Rpeak = self.cut_Rpeak[idxs]
Config["offset_correct"] = off
self.cut_Jpeak = []
peaks, _ = find_peaks(self.res_BCG)
for i in self.cut_Rpeak:
@ -2074,22 +2080,11 @@ class Data:
self.cut_Jpeak.append(peaks[idx])
self.cut_Jpeak = asarray(self.cut_Jpeak).astype(int)
frontcut_index_BCG = int(
(self.argmax_BCG - np_argmax(self.res_BCG) / Config["InputConfig"]["UseFreq"] * Config["orgfs"]))
backcut_index_BCG = int(len(self.res_BCG) / Config["InputConfig"]["UseFreq"] * Config["orgfs"] + np_argmax(
self.raw_BCG) - np_argmax(self.res_BCG) / Config["InputConfig"]["UseFreq"] * Config["orgfs"])
frontcut_index_ECG = self.argmax_ECG - np_argmax(self.cut_ECG)
backcut_index_ECG = len(self.cut_ECG) + self.argmax_ECG - np_argmax(self.cut_ECG)
Config["frontcut_index_BCG"] = frontcut_index_BCG
Config["backcut_index_BCG"] = backcut_index_BCG
Config["frontcut_index_ECG"] = frontcut_index_ECG
Config["backcut_index_ECG"] = backcut_index_ECG
except Exception as e:
return Result().failure(info=Constants.PRECISELY_ALIGN_POSTPROCESS_VIEW_FAILURE +
Constants.FAILURE_REASON["PostProcess_Align_Exception"] + "\n" + format_exc())
info = f"{Constants.PRECISELY_ALIGN_POSTPROCESS_VIEW_FINISHED}BCG前后段被切割的坐标值为[{frontcut_index_BCG}, {backcut_index_BCG}]ECG前后段被切割的坐标值为[{frontcut_index_ECG}, {backcut_index_ECG}]"
info = f"{Constants.PRECISELY_ALIGN_POSTPROCESS_VIEW_FINISHED}BCG前后段被切割的坐标值为[{Config['frontcut_index_BCG']}, {Config['backcut_index_BCG']}]ECG前后段被切割的坐标值为[{Config['frontcut_index_ECG']}, {Config['backcut_index_ECG']}]"
return Result().success(info=info)
def save_alignInfo(self):

View File

@ -94,6 +94,30 @@ class Constants:
background-color: rgba(255, 0, 0, 128); /* 鼠标悬停时的背景颜色 */
}"""
CHECKBOX_STYLE_NORMAL: str = '''
QCheckBox {
border: 2px solid rgb(128, 128, 128);
border-radius: 11px;
}
QCheckBox::indicator{
width: 20px;
height: 20px;
border-radius: 10px;
}
QCheckBox::indicator:checked {
background-color: rgba(0, 255, 0, 192);
image: url(./image/correct.svg);
}
QCheckBox::indicator:unchecked {
background-color: rgba(255, 0, 0, 128);
}
QCheckBox::indicator:disabled {
background-color: rgba(119, 136, 153, 128);
}'''
FAILURE_REASON: dict = {
"Path_Not_Exist": "(路径不存在)",
"File_Not_Exist": "(数据文件不存在)",

View File

@ -2,7 +2,7 @@ from datetime import datetime
from logging import error, info
from pathlib import Path
from PySide6.QtWidgets import QMessageBox, QWidget, QPushButton, QProgressBar, QApplication, QRadioButton
from PySide6.QtWidgets import QMessageBox, QWidget, QPushButton, QProgressBar, QApplication, QRadioButton, QCheckBox
from func.utils.Constants import Constants
from func.utils.CustomException import TipsTypeValueNotExistError, MsgBoxTypeValueNotExistError
@ -152,6 +152,8 @@ class PublicFunc:
if isinstance(widget, QPushButton):
if widget.objectName() in buttonState["Default"].keys():
widget.setStyleSheet(Constants.LABELBTN_STYLE_NORMAL)
if isinstance(widget, QCheckBox):
widget.setStyleSheet(Constants.CHECKBOX_STYLE_NORMAL)
@staticmethod
def add_progressbar(mainWindow):