新增粗对齐重采样功能,优化相关异常处理和UI元素,更新常量定义

This commit is contained in:
2026-01-22 21:46:39 +08:00
parent a0254d8e66
commit 0935aefeb2
6 changed files with 120 additions and 10 deletions

View File

@ -1472,7 +1472,14 @@ class Data:
theilsen = TheilSenRegressor()
theilsen.fit(X, y)
slope = theilsen.coef_[0]
frequency = 1 - slope / epoch_second / temp_freq if slope != 0 else float('inf')
# frequency = 1 - slope / epoch_second / temp_freq if slope != 0 else float('inf')
if slope != 0:
drift_rate = slope / epoch_second
# frequency = temp_freq * (1 - drift_rate)
frequency = 1 - drift_rate
else:
# frequency = float(temp_freq)
frequency = 1
theilsen_y = theilsen.predict(X)

View File

@ -5,17 +5,19 @@ from gc import collect
from math import floor, ceil
from pathlib import Path
from traceback import format_exc
import soxr
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication
from numpy import array
from overrides import overrides
from pandas import read_csv, DataFrame
from yaml import dump, load, FullLoader
import numpy as np
from func.utils.ConfigParams import Filename, Params
from func.utils.PublicFunc import PublicFunc
from func.utils.Constants import Constants
from func.utils.Result import Result
from numpy import float32
from ui.MainWindow.MainWindow_cut_PAIR_FILE import Ui_MainWindow_cut_PAIR_FILE
@ -25,13 +27,15 @@ Config = {
ButtonState = {
"Default": {
"checkBox_roughCut": False,
"checkBox_roughCut": True,
"checkBox_roughResample": False,
"pushButton_deleteRoughCut": False,
"pushButton_execute": True,
"spinBox_OrgBCGShift": False
},
"Current": {
"checkBox_roughCut": False,
"checkBox_roughCut": True,
"checkBox_roughResample": False,
"pushButton_deleteRoughCut": False,
"pushButton_execute": True,
"spinBox_OrgBCGShift": False
@ -136,9 +140,11 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("Sync", "RoughCut")
ButtonState["Default"]["pushButton_deleteRoughCut"] = True
ButtonState["Default"]["checkBox_roughResample"] = True
self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
self.ui.spinBox_OrgBCGShift.setEnabled(True)
ButtonState["Current"]["pushButton_deleteRoughCut"] = True
ButtonState["Current"]["checkBox_roughResample"] = True
PublicFunc.finish_operation(self, ButtonState)
else:
@ -155,9 +161,11 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
if "RoughCut" in Config["ChannelSave"][key]:
Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("RoughCut", "Sync")
ButtonState["Default"]["pushButton_deleteRoughCut"] = False
ButtonState["Default"]["checkBox_roughResample"] = False
self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
self.ui.spinBox_OrgBCGShift.setEnabled(False)
ButtonState["Current"]["pushButton_deleteRoughCut"] = False
ButtonState["Current"]["checkBox_roughResample"] = False
PublicFunc.finish_operation(self, ButtonState)
@ -207,6 +215,17 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
else:
PublicFunc.text_output(self.ui, "(3/5)" + Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_FINISHED, Constants.TIPS_TYPE_INFO)
if self.ui.checkBox_roughResample.isChecked():
result_resample = self.data.resample_BCG()
if not result_resample.status:
PublicFunc.text_output(self.ui, "(3/5)" + result_resample.info, Constants.TIPS_TYPE_ERROR)
PublicFunc.msgbox_output(self, result_resample.info, Constants.MSGBOX_TYPE_ERROR)
PublicFunc.finish_operation(self, ButtonState)
return
else:
PublicFunc.text_output(self.ui, "(3/5)" + result_resample.info, Constants.TIPS_TYPE_INFO)
result_approximate = self.data.calc_approximately_align_info(int(self.ui.spinBox_OrgBCGShift.value()))
if not result_approximate.status:
PublicFunc.text_output(self.ui, "(3/5)" + result_approximate.info, Constants.TIPS_TYPE_ERROR)
@ -220,6 +239,7 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
# 切割数据
PublicFunc.progressbar_update(self, 3, 5, Constants.CUT_PAIR_FILE_CUTTING_DATA, 40)
PublicFunc.text_output(self.ui, "(3/5)" + str(self.data.alignInfo["cut_index"]), Constants.TIPS_TYPE_INFO)
result = self.data.cut_data()
if not result.status:
PublicFunc.text_output(self.ui, "(3/5)" + result.info, Constants.TIPS_TYPE_ERROR)
@ -287,6 +307,7 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
class Data:
def __init__(self, root_path, sampID):
self.actualBCGFreq = None
self.TimeBiasSecond = None
self.alignInfo = None
@ -415,15 +436,51 @@ class Data:
pos = df["pos"].values[-1]
ApplyFrequency = df["ApplyFrequency"].values[-1]
self.TimeBiasSecond = pos / ApplyFrequency
self.actualBCGFreq = df["estimate_freq"].values[-1] * Config["BCGFreq"]
return Result().success(info=Constants.INPUT_FINISHED)
except Exception as e:
self.TimeBiasSecond = 0
self.actualBCGFreq = Config["BCGFreq"]
traceback.print_exc()
return Result().failure(info=Constants.INPUT_FAILURE
+ Constants.FAILURE_REASON["Get_Approximately_Align_Info_Exception"]
+ "\n" + format_exc())
def resample_BCG(self):
try:
for key in self.raw.keys():
if Config["ChannelInput"][key].startswith("OrgBCG:"):
# data = self.raw[key]
# n_samples = len(data)
# duration = n_samples / self.actualBCGFreq
#
# t_old = np.linspace(0, duration, n_samples, endpoint=False)
# n_new = int(np.round(duration * Config["BCGFreq"]))
# t_new = np.linspace(0, duration, n_new, endpoint=False)
# self.raw[key] = np.interp(t_new, t_old, data)
resample_signal = soxr.resample(
x=self.raw[key].astype(np.float64),
in_rate=self.actualBCGFreq,
out_rate=Config["BCGFreq"],
quality='VHQ'
)
print(f"Resampled BCG from {self.actualBCGFreq}Hz to {Config['BCGFreq']}Hz, original length: {len(self.raw[key])}, new length: {len(resample_signal)}")
self.raw[key] = resample_signal.astype(self.raw[key].dtype)
self.TimeBiasSecond = int(self.TimeBiasSecond * (self.actualBCGFreq / Config["BCGFreq"]))
return Result().success(info=Constants.CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FINISHED)
except Exception as e:
traceback.print_exc()
print(e)
return Result().failure(info=Constants.CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FAILURE +
Constants.FAILURE_REASON["Resample_BCG_Exception"] + "\n" + format_exc())
def calc_approximately_align_info(self, OrgBCGShift=0):
try:
# 获取BCG长度
@ -454,10 +511,10 @@ class Data:
self.alignInfo = {
"cut_index": {
"front_BCG": front_BCG * BCG_freq,
"back_BCG": back_BCG * BCG_freq,
"front_ECG": front_ECG * ECG_freq,
"back_ECG": back_ECG * ECG_freq,
"front_BCG": front_BCG * BCG_freq,
"back_BCG": back_BCG * BCG_freq
}
}
return Result().success(info=Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_CALC_FINISHED)
@ -488,6 +545,7 @@ class Data:
def cut_data(self):
try:
for key, raw in self.raw.items():
if Config["ChannelInput"][key].startswith("PSG:"):
# 转换切割点
@ -538,6 +596,7 @@ class Data:
# 获取记录开始时间
start_time = str(self.startTime[0]).split(" ")[1]
start_time = Data.get_time_to_seconds(start_time)
ECG_freq = Config["ECGFreq"]
# 计算起始时间秒数和终止时间秒数
self.SALabel["Start"] = (self.SALabel["Time"].apply(self.get_time_to_seconds) - start_time).apply(
@ -546,8 +605,8 @@ class Data:
# 标签映射
ECG_length = self.alignInfo["cut_index"]["back_ECG"] - self.alignInfo["cut_index"]["front_ECG"]
self.SALabel["Start"] = self.SALabel["Start"] - round((self.alignInfo["cut_index"]["front_ECG"] / 1000))
self.SALabel["End"] = self.SALabel["End"] - round((self.alignInfo["cut_index"]["front_ECG"] / 1000))
self.SALabel["Start"] = self.SALabel["Start"] - round((self.alignInfo["cut_index"]["front_ECG"] / ECG_freq))
self.SALabel["End"] = self.SALabel["End"] - round((self.alignInfo["cut_index"]["front_ECG"] / ECG_freq))
self.SALabel = self.SALabel[self.SALabel["End"] >= 0]
self.SALabel.loc[self.SALabel["Start"] < 0, "Start"] = 0
self.SALabel = self.SALabel[self.SALabel["Start"] < ECG_length]

View File

@ -186,7 +186,8 @@ class Constants:
"cut_Rpeak_Not_Exist": "切割后R峰不存在",
"Get_Approximately_Align_Info_Exception": "(获取粗对齐信息异常)",
"Calculate_Approximately_Align_Info_Exception": "(计算粗对齐信息异常)",
"Delete_Rough_Cut_File_Exception": "(删除历史粗对齐文件异常)"
"Delete_Rough_Cut_File_Exception": "(删除历史粗对齐文件异常)",
"Resample_BCG_Exception": "粗对齐重采样BCG异常",
}
# 数据粗同步
@ -377,6 +378,10 @@ class Constants:
CUT_PAIR_FILE_DELETE_ROUGH_CUT_FILE_FINISHED: str = "删除历史粗对齐文件完成"
CUT_PAIR_FILE_DELETE_ROUGH_CUT_FILE_FAILURE: str = "删除历史粗对齐文件失败"
CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG: str = "正在粗对齐重采样BCG"
CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FINISHED: str = "粗对齐重采样BCG完成"
CUT_PAIR_FILE_ROUGH_RESAMPLE_BCG_FAILURE: str = "粗对齐重采样BCG失败"
# 体动标注
ARTIFACT_LABEL_PLOT_LABEL_ORGBCG_SYNC: str = "OrgBCG_Sync"

View File

@ -113,6 +113,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(False)
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(False)
@staticmethod
def __enableAllButton__(mainWindow, buttonState):
# 启用按钮
@ -128,6 +132,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(buttonState["Current"][widget.objectName()])
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(buttonState["Current"][widget.objectName()])
@staticmethod
def __resetAllButton__(mainWindow, buttonState):
# 启用按钮
@ -143,6 +151,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Default"].keys():
widget.setEnabled(buttonState["Default"][widget.objectName()])
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Default"].keys():
widget.setEnabled(buttonState["Default"][widget.objectName()])
@staticmethod
def __styleAllButton__(mainWindow, buttonState):
# 启用按钮

View File

@ -68,6 +68,12 @@ class Ui_MainWindow_cut_PAIR_FILE(object):
self.horizontalLayout_3.addWidget(self.checkBox_roughCut)
self.checkBox_roughResample = QCheckBox(self.groupBox_2)
self.checkBox_roughResample.setObjectName(u"checkBox_roughResample")
self.checkBox_roughResample.setFont(font)
self.horizontalLayout_3.addWidget(self.checkBox_roughResample)
self.pushButton_deleteRoughCut = QPushButton(self.groupBox_2)
self.pushButton_deleteRoughCut.setObjectName(u"pushButton_deleteRoughCut")
self.pushButton_deleteRoughCut.setFont(font)
@ -120,12 +126,14 @@ class Ui_MainWindow_cut_PAIR_FILE(object):
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.label = QLabel(self.groupBox_2)
self.label.setObjectName(u"label")
self.label.setEnabled(False)
self.label.setFont(font)
self.gridLayout_3.addWidget(self.label, 1, 0, 1, 1)
self.spinBox = QSpinBox(self.groupBox_2)
self.spinBox.setObjectName(u"spinBox")
self.spinBox.setEnabled(False)
self.spinBox.setFont(font)
self.spinBox.setMinimum(1)
self.spinBox.setMaximum(1000000)
@ -247,6 +255,7 @@ class Ui_MainWindow_cut_PAIR_FILE(object):
self.groupBox_3.setTitle(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u5197\u4f59\u6570\u636e\u5207\u5272\u548c\u6807\u7b7e\u6620\u5c04", None))
self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u786e\u5b9a\u6570\u636e", None))
self.checkBox_roughCut.setText(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u7c97\u5bf9\u9f50\u7ed3\u679c\u5207\u5272\u6a21\u5f0f", None))
self.checkBox_roughResample.setText(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u7c97\u5bf9\u9f50\u91cd\u91c7\u6837", None))
self.pushButton_deleteRoughCut.setText(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u5220\u9664\u7c97\u5bf9\u9f50\u5207\u5272\u6587\u4ef6", None))
self.label_2.setText(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u9700\u8981\u5207\u5272\u7684\u901a\u9053\u540d\uff1a", None))
self.label_6.setText(QCoreApplication.translate("MainWindow_cut_PAIR_FILE", u"\u9700\u8981\u6620\u5c04\u7684\u6807\u7b7e\uff1a", None))

View File

@ -60,6 +60,18 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_roughResample">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>粗对齐重采样</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_deleteRoughCut">
<property name="font">
@ -127,6 +139,9 @@
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
@ -139,6 +154,9 @@
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>