新增粗对齐重采样功能,优化相关异常处理和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 = TheilSenRegressor()
theilsen.fit(X, y) theilsen.fit(X, y)
slope = theilsen.coef_[0] 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) theilsen_y = theilsen.predict(X)

View File

@ -5,17 +5,19 @@ from gc import collect
from math import floor, ceil from math import floor, ceil
from pathlib import Path from pathlib import Path
from traceback import format_exc from traceback import format_exc
import soxr
from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication
from numpy import array from numpy import array
from overrides import overrides from overrides import overrides
from pandas import read_csv, DataFrame from pandas import read_csv, DataFrame
from yaml import dump, load, FullLoader from yaml import dump, load, FullLoader
import numpy as np
from func.utils.ConfigParams import Filename, Params from func.utils.ConfigParams import Filename, Params
from func.utils.PublicFunc import PublicFunc from func.utils.PublicFunc import PublicFunc
from func.utils.Constants import Constants from func.utils.Constants import Constants
from func.utils.Result import Result from func.utils.Result import Result
from numpy import float32
from ui.MainWindow.MainWindow_cut_PAIR_FILE import Ui_MainWindow_cut_PAIR_FILE from ui.MainWindow.MainWindow_cut_PAIR_FILE import Ui_MainWindow_cut_PAIR_FILE
@ -25,13 +27,15 @@ Config = {
ButtonState = { ButtonState = {
"Default": { "Default": {
"checkBox_roughCut": False, "checkBox_roughCut": True,
"checkBox_roughResample": False,
"pushButton_deleteRoughCut": False, "pushButton_deleteRoughCut": False,
"pushButton_execute": True, "pushButton_execute": True,
"spinBox_OrgBCGShift": False "spinBox_OrgBCGShift": False
}, },
"Current": { "Current": {
"checkBox_roughCut": False, "checkBox_roughCut": True,
"checkBox_roughResample": False,
"pushButton_deleteRoughCut": False, "pushButton_deleteRoughCut": False,
"pushButton_execute": True, "pushButton_execute": True,
"spinBox_OrgBCGShift": False "spinBox_OrgBCGShift": False
@ -136,9 +140,11 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("Sync", "RoughCut") Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("Sync", "RoughCut")
ButtonState["Default"]["pushButton_deleteRoughCut"] = True ButtonState["Default"]["pushButton_deleteRoughCut"] = True
ButtonState["Default"]["checkBox_roughResample"] = True
self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys())) self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
self.ui.spinBox_OrgBCGShift.setEnabled(True) self.ui.spinBox_OrgBCGShift.setEnabled(True)
ButtonState["Current"]["pushButton_deleteRoughCut"] = True ButtonState["Current"]["pushButton_deleteRoughCut"] = True
ButtonState["Current"]["checkBox_roughResample"] = True
PublicFunc.finish_operation(self, ButtonState) PublicFunc.finish_operation(self, ButtonState)
else: else:
@ -155,9 +161,11 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
if "RoughCut" in Config["ChannelSave"][key]: if "RoughCut" in Config["ChannelSave"][key]:
Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("RoughCut", "Sync") Config["ChannelSave"][key] = Config["ChannelSave"][key].replace("RoughCut", "Sync")
ButtonState["Default"]["pushButton_deleteRoughCut"] = False ButtonState["Default"]["pushButton_deleteRoughCut"] = False
ButtonState["Default"]["checkBox_roughResample"] = False
self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys())) self.ui.plainTextEdit_channel.setPlainText(', '.join(Config["ChannelInput"].keys()))
self.ui.spinBox_OrgBCGShift.setEnabled(False) self.ui.spinBox_OrgBCGShift.setEnabled(False)
ButtonState["Current"]["pushButton_deleteRoughCut"] = False ButtonState["Current"]["pushButton_deleteRoughCut"] = False
ButtonState["Current"]["checkBox_roughResample"] = False
PublicFunc.finish_operation(self, ButtonState) PublicFunc.finish_operation(self, ButtonState)
@ -207,6 +215,17 @@ class MainWindow_cut_PAIR_FILE(QMainWindow):
else: else:
PublicFunc.text_output(self.ui, "(3/5)" + Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_FINISHED, Constants.TIPS_TYPE_INFO) 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())) result_approximate = self.data.calc_approximately_align_info(int(self.ui.spinBox_OrgBCGShift.value()))
if not result_approximate.status: if not result_approximate.status:
PublicFunc.text_output(self.ui, "(3/5)" + result_approximate.info, Constants.TIPS_TYPE_ERROR) 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.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() result = self.data.cut_data()
if not result.status: if not result.status:
PublicFunc.text_output(self.ui, "(3/5)" + result.info, Constants.TIPS_TYPE_ERROR) 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: class Data:
def __init__(self, root_path, sampID): def __init__(self, root_path, sampID):
self.actualBCGFreq = None
self.TimeBiasSecond = None self.TimeBiasSecond = None
self.alignInfo = None self.alignInfo = None
@ -415,15 +436,51 @@ class Data:
pos = df["pos"].values[-1] pos = df["pos"].values[-1]
ApplyFrequency = df["ApplyFrequency"].values[-1] ApplyFrequency = df["ApplyFrequency"].values[-1]
self.TimeBiasSecond = pos / ApplyFrequency self.TimeBiasSecond = pos / ApplyFrequency
self.actualBCGFreq = df["estimate_freq"].values[-1] * Config["BCGFreq"]
return Result().success(info=Constants.INPUT_FINISHED) return Result().success(info=Constants.INPUT_FINISHED)
except Exception as e: except Exception as e:
self.TimeBiasSecond = 0 self.TimeBiasSecond = 0
self.actualBCGFreq = Config["BCGFreq"]
traceback.print_exc() traceback.print_exc()
return Result().failure(info=Constants.INPUT_FAILURE return Result().failure(info=Constants.INPUT_FAILURE
+ Constants.FAILURE_REASON["Get_Approximately_Align_Info_Exception"] + Constants.FAILURE_REASON["Get_Approximately_Align_Info_Exception"]
+ "\n" + format_exc()) + "\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): def calc_approximately_align_info(self, OrgBCGShift=0):
try: try:
# 获取BCG长度 # 获取BCG长度
@ -454,10 +511,10 @@ class Data:
self.alignInfo = { self.alignInfo = {
"cut_index": { "cut_index": {
"front_BCG": front_BCG * BCG_freq,
"back_BCG": back_BCG * BCG_freq,
"front_ECG": front_ECG * ECG_freq, "front_ECG": front_ECG * ECG_freq,
"back_ECG": back_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) return Result().success(info=Constants.CUT_PAIR_FILE_GETTING_APPROXIMATE_ALIGN_INFO_CALC_FINISHED)
@ -488,6 +545,7 @@ class Data:
def cut_data(self): def cut_data(self):
try: try:
for key, raw in self.raw.items(): for key, raw in self.raw.items():
if Config["ChannelInput"][key].startswith("PSG:"): if Config["ChannelInput"][key].startswith("PSG:"):
# 转换切割点 # 转换切割点
@ -538,6 +596,7 @@ class Data:
# 获取记录开始时间 # 获取记录开始时间
start_time = str(self.startTime[0]).split(" ")[1] start_time = str(self.startTime[0]).split(" ")[1]
start_time = Data.get_time_to_seconds(start_time) 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( 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"] 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["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"] / 1000)) 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 = self.SALabel[self.SALabel["End"] >= 0]
self.SALabel.loc[self.SALabel["Start"] < 0, "Start"] = 0 self.SALabel.loc[self.SALabel["Start"] < 0, "Start"] = 0
self.SALabel = self.SALabel[self.SALabel["Start"] < ECG_length] self.SALabel = self.SALabel[self.SALabel["Start"] < ECG_length]

View File

@ -186,7 +186,8 @@ class Constants:
"cut_Rpeak_Not_Exist": "切割后R峰不存在", "cut_Rpeak_Not_Exist": "切割后R峰不存在",
"Get_Approximately_Align_Info_Exception": "(获取粗对齐信息异常)", "Get_Approximately_Align_Info_Exception": "(获取粗对齐信息异常)",
"Calculate_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_FINISHED: str = "删除历史粗对齐文件完成"
CUT_PAIR_FILE_DELETE_ROUGH_CUT_FILE_FAILURE: 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" ARTIFACT_LABEL_PLOT_LABEL_ORGBCG_SYNC: str = "OrgBCG_Sync"

View File

@ -113,6 +113,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Current"].keys(): if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(False) widget.setEnabled(False)
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(False)
@staticmethod @staticmethod
def __enableAllButton__(mainWindow, buttonState): def __enableAllButton__(mainWindow, buttonState):
# 启用按钮 # 启用按钮
@ -128,6 +132,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Current"].keys(): if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(buttonState["Current"][widget.objectName()]) widget.setEnabled(buttonState["Current"][widget.objectName()])
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Current"].keys():
widget.setEnabled(buttonState["Current"][widget.objectName()])
@staticmethod @staticmethod
def __resetAllButton__(mainWindow, buttonState): def __resetAllButton__(mainWindow, buttonState):
# 启用按钮 # 启用按钮
@ -143,6 +151,10 @@ class PublicFunc:
if widget.objectName() in buttonState["Default"].keys(): if widget.objectName() in buttonState["Default"].keys():
widget.setEnabled(buttonState["Default"][widget.objectName()]) widget.setEnabled(buttonState["Default"][widget.objectName()])
if isinstance(widget, QCheckBox):
if widget.objectName() in buttonState["Default"].keys():
widget.setEnabled(buttonState["Default"][widget.objectName()])
@staticmethod @staticmethod
def __styleAllButton__(mainWindow, buttonState): 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.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 = QPushButton(self.groupBox_2)
self.pushButton_deleteRoughCut.setObjectName(u"pushButton_deleteRoughCut") self.pushButton_deleteRoughCut.setObjectName(u"pushButton_deleteRoughCut")
self.pushButton_deleteRoughCut.setFont(font) self.pushButton_deleteRoughCut.setFont(font)
@ -120,12 +126,14 @@ class Ui_MainWindow_cut_PAIR_FILE(object):
self.gridLayout_3.setObjectName(u"gridLayout_3") self.gridLayout_3.setObjectName(u"gridLayout_3")
self.label = QLabel(self.groupBox_2) self.label = QLabel(self.groupBox_2)
self.label.setObjectName(u"label") self.label.setObjectName(u"label")
self.label.setEnabled(False)
self.label.setFont(font) self.label.setFont(font)
self.gridLayout_3.addWidget(self.label, 1, 0, 1, 1) self.gridLayout_3.addWidget(self.label, 1, 0, 1, 1)
self.spinBox = QSpinBox(self.groupBox_2) self.spinBox = QSpinBox(self.groupBox_2)
self.spinBox.setObjectName(u"spinBox") self.spinBox.setObjectName(u"spinBox")
self.spinBox.setEnabled(False)
self.spinBox.setFont(font) self.spinBox.setFont(font)
self.spinBox.setMinimum(1) self.spinBox.setMinimum(1)
self.spinBox.setMaximum(1000000) 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_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.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_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.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_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)) 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> </property>
</widget> </widget>
</item> </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> <item>
<widget class="QPushButton" name="pushButton_deleteRoughCut"> <widget class="QPushButton" name="pushButton_deleteRoughCut">
<property name="font"> <property name="font">
@ -127,6 +139,9 @@
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font"> <property name="font">
<font> <font>
<pointsize>12</pointsize> <pointsize>12</pointsize>
@ -139,6 +154,9 @@
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="spinBox"> <widget class="QSpinBox" name="spinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font"> <property name="font">
<font> <font>
<pointsize>12</pointsize> <pointsize>12</pointsize>