diff --git a/func/Module_approximately_align.py b/func/Module_approximately_align.py index 59cc553..a094a2d 100644 --- a/func/Module_approximately_align.py +++ b/func/Module_approximately_align.py @@ -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) diff --git a/func/Module_cut_pair_file.py b/func/Module_cut_pair_file.py index 592a334..caf50a3 100644 --- a/func/Module_cut_pair_file.py +++ b/func/Module_cut_pair_file.py @@ -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] diff --git a/func/utils/Constants.py b/func/utils/Constants.py index 1fa7b09..ff0f2ae 100644 --- a/func/utils/Constants.py +++ b/func/utils/Constants.py @@ -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" diff --git a/func/utils/PublicFunc.py b/func/utils/PublicFunc.py index d369c04..d8aa24e 100644 --- a/func/utils/PublicFunc.py +++ b/func/utils/PublicFunc.py @@ -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): # 启用按钮 diff --git a/ui/MainWindow/MainWindow_cut_PAIR_FILE.py b/ui/MainWindow/MainWindow_cut_PAIR_FILE.py index 8fc99dd..3c9fc20 100644 --- a/ui/MainWindow/MainWindow_cut_PAIR_FILE.py +++ b/ui/MainWindow/MainWindow_cut_PAIR_FILE.py @@ -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)) diff --git a/ui/MainWindow/MainWindow_cut_PAIR_FILE.ui b/ui/MainWindow/MainWindow_cut_PAIR_FILE.ui index 7e5a17d..3d036f6 100644 --- a/ui/MainWindow/MainWindow_cut_PAIR_FILE.ui +++ b/ui/MainWindow/MainWindow_cut_PAIR_FILE.ui @@ -60,6 +60,18 @@ + + + + + 12 + + + + 粗对齐重采样 + + + @@ -127,6 +139,9 @@ + + false + 12 @@ -139,6 +154,9 @@ + + false + 12