diff --git a/.gitignore b/.gitignore index 9ac740f..e049d29 100644 --- a/.gitignore +++ b/.gitignore @@ -174,6 +174,11 @@ ipython_config.py # Remove previous ipynb_checkpoints # git rm -r .ipynb_checkpoints/ +heartbeat_annotation/ +logs/* +.idea/* +!./logs + # ---> 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 diff --git a/BCGDataset/Dataset_operation.py b/BCGDataset/Dataset_operation.py new file mode 100644 index 0000000..a93f14c --- /dev/null +++ b/BCGDataset/Dataset_operation.py @@ -0,0 +1,271 @@ +# encoding:utf-8 + +import os +import numpy as np +import pandas as pd +import warnings +import matplotlib.pyplot as plt +import matplotlib.colors as colors + +from scipy import signal +from glob import glob +from torch.utils.data import Dataset, DataLoader, TensorDataset +warnings.filterwarnings("ignore") + +class BCGDataset(Dataset): + def __init__(self, train=True): + if train: + self.data = np.array(pd.read_csv("./in_data/train.txt").iloc[:,np.arange(1000)]) + self.label = np.array(pd.read_csv("./in_data/train.txt").iloc[:,np.arange(1000,2000)]) + else: + self.data = np.array(pd.read_csv("./in_data/test.txt").iloc[:, np.arange(1000)]) + self.label = np.array(pd.read_csv("./in_data/test.txt").iloc[:, np.arange(1000, 2000)]) + + def __getitem__(self, index): + return self.data[index], self.label[index] + + def __len__(self): + return len(self.label) + +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!") + length_before = len(data) + length_after = length_before//down_radio + data = data[:length_after*down_radio] + data = data.reshape(-1,down_radio) + data = data[:,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 AmpMovement(self, data, win_size, threshold=20, get_judge_line=False): + """ + 基于幅值方法检测体动: + 1.将输入信号按win_size切分 + 2.将每个win_size信号片段分窗,每个窗2s,步长为2s + 3.计算一分钟所有信号窗的最大峰谷值差,获取中位数和均值 + 4.所有2s时间窗内,大于中位数/均值的2.2倍视为体动 + 5.体动间间隔过短的信号,同样标记为体动 + :param data: Input signal + :param win_size: Size of the win(Must be a multiple of 2) + :return: State of signal + """ + Dataframe, cover_num = self.Splitwin(data, len_win=win_size, coverage=1.0, calculate_to_end=True) + state_all = np.array([]) + Amp_list = np.array([]) + for win in range(Dataframe.shape[0]): + state = np.array([]) + # two seconds window + data_win = self.Splitwin(Dataframe[win], len_win=2, coverage=1.0) + Amp = np.zeros(data_win.shape[0]) + for i in range(data_win.shape[0]): + Amp[i] = np.max(data_win[i]) - np.min(data_win[i]) # max - min + # 取..位数 + Median_Amp = np.percentile(Amp, 20) # 20% + if get_judge_line: + Amp_list = np.append(Amp_list, np.full(win_size * self.sample_rate, 2.3 * Median_Amp)) + + for i in range(len(Amp)): + if (Amp[i] > 2.1 * Median_Amp): + state = np.append(state, "Movement") + elif Amp[i] < threshold: + state = np.append(state, "Nobody") + else: + state = np.append(state, "Sleep") + + if win == Dataframe.shape[0] - 1 and cover_num > 0: + state = state[-int(cover_num):] + + state_all = np.append(state_all, state) + + if get_judge_line: + return state_all, Amp_list + else: + return state_all + + def preprocess1(self): + # ---------------------------------------------------------- + data_dir = "../in_data/" + dir_list = os.listdir(data_dir) + + data_list = [data_dir + dir + "/orgData.txt" for dir in dir_list] + label_list = [data_dir + dir + "/label.txt" for dir in dir_list] + print(data_list) + print(label_list) + for i in range(len(data_list)): + orgBCG = np.array(pd.read_csv(data_list[i], header=None)).reshape(-1) + orgLabel = np.array(pd.read_csv(label_list[i])).reshape(-1) + + # ---------------------Movement Detection------------------------- + operation = BCG_Operation() + BCG = operation.Butterworth(data=orgBCG, type="bandpass", low_cut=2.5, high_cut=10, order=2) + state_win60 = operation.AmpMovement(orgBCG, win_size=60) + + visual_state = np.array([]) + for num in range(state_win60.shape[0]): + print("state_num/all_state: ", num, '/', state_win60.shape[0]) + if state_win60[num] == "Movement": + visual_state = np.append(visual_state, np.full(2000, 1)) + else: + visual_state = np.append(visual_state, np.full(2000, 0)) + + # ------------------------------------------------------------------ + downBCG = operation.down_sample(data=orgBCG, down_radio=10) + downLabel = operation.down_sample(data=orgLabel, down_radio=10) + downState = operation.down_sample(data=visual_state, down_radio=10) + + length_before = len(downState) + length_after = length_before // 1000 + downBCG = downBCG[:length_after * 1000] + downLabel = downLabel[:length_after * 1000] + downState = downState[:length_after * 1000] + + downBCG = downBCG.reshape(-1, 1000) + downLabel = downLabel.reshape(-1, 1000) + downState = downState.reshape(-1, 1000) + downState = np.max(downState, axis=1) + + df_BCG = pd.DataFrame(downBCG) + df_label = pd.DataFrame(downLabel) + df_state = pd.DataFrame(downState, columns=["state"]) + df_BCG.to_csv() + + df_all = pd.concat([df_BCG, df_label, df_state], axis=1) + df_all.to_csv(data_dir + "/data" + str(i + 1) + ".txt", index=False) + +def read_all_data(data_dir): + df_all = pd.read_csv(data_dir) + df_clean = df_all[ df_all["state"]==0.0 ] + df_artifact = df_all[ df_all["state"]==1.0 ] + data_clean = df_clean.iloc[:,np.arange(1000)] + label_clean = df_clean.iloc[:,np.arange(1000,2000)] + data_artifact = df_artifact.iloc[:,np.arange(1000)] + label_artifact = df_artifact.iloc[:,np.arange(1000,2000)] + + return np.array(data_clean),np.array(label_clean),np.array(data_artifact),np.array(label_artifact) + + +#orgBCG = np.array(pd.read_csv("../in_data/data1zuo/orgData.txt", header=None)).reshape(-1) +#orgLabel = np.array(pd.read_csv("../in_data/data1zuo/label.txt")).reshape(-1) +## ---------------------Movement Detection------------------------- +#operation = BCG_Operation() +#BCG = operation.Butterworth(data=orgBCG, type="bandpass", low_cut=2.5, high_cut=10, order=2) +#state_win60 = operation.AmpMovement(orgBCG, win_size=60) +#visual_state = np.array([]) +#for num in range(state_win60.shape[0]): +# print("state_num/all_state: ", num, '/', state_win60.shape[0]) +# if state_win60[num] == "Movement": +# visual_state = np.append(visual_state, np.full(2000, 1)) +# else: +# visual_state = np.append(visual_state, np.full(2000, 0)) +## ------------------------------------------------------------------ +#downBCG = operation.down_sample(data=orgBCG, down_radio=10) +#downLabel = operation.down_sample(data=orgLabel, down_radio=10) +#downState = operation.down_sample(data=visual_state, down_radio=10) +#length_before = len(downState) +#length_after = length_before // 1000 +#downBCG = downBCG[:length_after * 1000] +#downLabel = downLabel[:length_after * 1000] +#downState = downState[:length_after * 1000] +#downBCG = downBCG.reshape(-1, 1000) +#downLabel = downLabel.reshape(-1, 1000) +#downState = downState.reshape(-1, 1000) +#downState = np.max(downState, axis=1) +#df_BCG = pd.DataFrame(downBCG) +#df_label = pd.DataFrame(downLabel) +#df_state = pd.DataFrame(downState, columns=["state"]) +#df_BCG.to_csv() +#df_all = pd.concat([df_BCG, df_label, df_state], axis=1) +#df_all.to_csv("../in_data/data1zuo.txt", index=False) + + +#data_dir = glob("../in_data/*.txt") +#print(data_dir) +#for num in range(len(data_dir)): +# if num==0 : +# all_data = pd.read_csv(data_dir[num]) +# else: +# all_data = pd.concat([all_data,pd.read_csv(data_dir[num])],ignore_index=True,axis=0) +# +#all_data.to_csv("../in_data/all_data.txt",index=False) + + + +#data = pd.read_csv("../in_data/all_data.txt") +#clean_data = data[data["state"]==0] +#Movement_data = data[data["state"]==1] +#print(data.shape) +#print(clean_data.shape) +# +## -------------------- 划分训练集和测试集:7:3 ---------------------------- +#sample = clean_data.sample(int(0.3*len(clean_data))) +#sample_index = sample.index +#print(sample.shape) +#print(sample_index) +## 剩余数据 +#all_index = clean_data.index +## 去除sample之后剩余的数据 +#residue_index = all_index.difference(sample_index) +#print(residue_index.shape) +#print(residue_index) +#residue = clean_data.loc[residue_index] +## 保存 +#test = pd.concat([sample,Movement_data],ignore_index=True) +#test.to_csv("../in_data/test.txt",index=False) +#residue.to_csv("../in_data/train.txt",index=False) + diff --git a/BCGDataset/__init__.py b/BCGDataset/__init__.py new file mode 100644 index 0000000..2d1f7a5 --- /dev/null +++ b/BCGDataset/__init__.py @@ -0,0 +1 @@ +from .Dataset_operation import BCGDataset,BCG_Operation,read_all_data \ No newline at end of file diff --git a/MainWindow.py b/MainWindow.py new file mode 100644 index 0000000..e47eb0b --- /dev/null +++ b/MainWindow.py @@ -0,0 +1,699 @@ +# -*- 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, 1187) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout.setObjectName("gridLayout") + self.gridLayout_left = QtWidgets.QGridLayout() + self.gridLayout_left.setObjectName("gridLayout_left") + self.groupBox_func_select = QtWidgets.QGroupBox(self.centralwidget) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_func_select.setFont(font) + self.groupBox_func_select.setObjectName("groupBox_func_select") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_func_select) + self.gridLayout_2.setObjectName("gridLayout_2") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 6, 1, 1, 2) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem1, 0, 3, 9, 1) + self.pushButton_detect_Rpeaks = QtWidgets.QPushButton(self.groupBox_func_select) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Rpeaks.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Rpeaks.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(24) + self.pushButton_detect_Rpeaks.setFont(font) + self.pushButton_detect_Rpeaks.setObjectName("pushButton_detect_Rpeaks") + self.gridLayout_2.addWidget(self.pushButton_detect_Rpeaks, 5, 1, 1, 2) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem2, 2, 1, 1, 2) + spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem3, 4, 1, 1, 2) + spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem4, 0, 1, 1, 2) + self.pushButton_detect_Jpeaks = QtWidgets.QPushButton(self.groupBox_func_select) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Jpeaks.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Jpeaks.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(24) + self.pushButton_detect_Jpeaks.setFont(font) + self.pushButton_detect_Jpeaks.setObjectName("pushButton_detect_Jpeaks") + self.gridLayout_2.addWidget(self.pushButton_detect_Jpeaks, 7, 1, 1, 2) + self.pushButton_rootpath_open = QtWidgets.QPushButton(self.groupBox_func_select) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_rootpath_open.setFont(font) + self.pushButton_rootpath_open.setObjectName("pushButton_rootpath_open") + self.gridLayout_2.addWidget(self.pushButton_rootpath_open, 1, 2, 1, 1) + self.pushButton_resample1000Hz = QtWidgets.QPushButton(self.groupBox_func_select) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_resample1000Hz.sizePolicy().hasHeightForWidth()) + self.pushButton_resample1000Hz.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(24) + self.pushButton_resample1000Hz.setFont(font) + self.pushButton_resample1000Hz.setObjectName("pushButton_resample1000Hz") + self.gridLayout_2.addWidget(self.pushButton_resample1000Hz, 3, 1, 1, 2) + self.lineEdit_rootpath = QtWidgets.QLineEdit(self.groupBox_func_select) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(14) + self.lineEdit_rootpath.setFont(font) + self.lineEdit_rootpath.setObjectName("lineEdit_rootpath") + self.gridLayout_2.addWidget(self.lineEdit_rootpath, 1, 1, 1, 1) + spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem5, 0, 0, 9, 1) + spacerItem6 = QtWidgets.QSpacerItem(518, 57, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem6, 8, 1, 1, 2) + self.gridLayout_2.setColumnStretch(0, 2) + self.gridLayout_2.setRowStretch(0, 2) + self.gridLayout_2.setRowStretch(1, 2) + self.gridLayout_2.setRowStretch(2, 2) + self.gridLayout_2.setRowStretch(3, 3) + self.gridLayout_2.setRowStretch(4, 2) + self.gridLayout_2.setRowStretch(5, 3) + self.gridLayout_2.setRowStretch(6, 2) + self.gridLayout_2.setRowStretch(7, 3) + self.gridLayout_2.setRowStretch(8, 2) + self.gridLayout_left.addWidget(self.groupBox_func_select, 0, 1, 1, 1) + self.gridLayout.addLayout(self.gridLayout_left, 0, 0, 1, 1) + self.verticalLayout_right = QtWidgets.QVBoxLayout() + self.verticalLayout_right.setObjectName("verticalLayout_right") + self.groupBox_plot = QtWidgets.QGroupBox(self.centralwidget) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_plot.setFont(font) + self.groupBox_plot.setObjectName("groupBox_plot") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_plot) + self.gridLayout_3.setObjectName("gridLayout_3") + self.verticalLayout_canvas = QtWidgets.QVBoxLayout() + self.verticalLayout_canvas.setObjectName("verticalLayout_canvas") + self.gridLayout_3.addLayout(self.verticalLayout_canvas, 0, 0, 1, 1) + self.verticalLayout_right.addWidget(self.groupBox_plot) + self.gridLayout.addLayout(self.verticalLayout_right, 0, 3, 1, 1) + self.verticalLayout_middle = QtWidgets.QVBoxLayout() + self.verticalLayout_middle.setObjectName("verticalLayout_middle") + self.groupBox_resample1000Hz = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_resample1000Hz.sizePolicy().hasHeightForWidth()) + self.groupBox_resample1000Hz.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_resample1000Hz.setFont(font) + self.groupBox_resample1000Hz.setObjectName("groupBox_resample1000Hz") + self.gridLayout_64 = QtWidgets.QGridLayout(self.groupBox_resample1000Hz) + self.gridLayout_64.setObjectName("gridLayout_64") + self.pushButton_resample1000Hz_view = QtWidgets.QPushButton(self.groupBox_resample1000Hz) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_resample1000Hz_view.sizePolicy().hasHeightForWidth()) + self.pushButton_resample1000Hz_view.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_resample1000Hz_view.setFont(font) + self.pushButton_resample1000Hz_view.setObjectName("pushButton_resample1000Hz_view") + self.gridLayout_64.addWidget(self.pushButton_resample1000Hz_view, 3, 0, 1, 1) + self.groupBox_resample1000Hz_inputFile_check = QtWidgets.QGroupBox(self.groupBox_resample1000Hz) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_resample1000Hz_inputFile_check.sizePolicy().hasHeightForWidth()) + self.groupBox_resample1000Hz_inputFile_check.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_resample1000Hz_inputFile_check.setFont(font) + self.groupBox_resample1000Hz_inputFile_check.setObjectName("groupBox_resample1000Hz_inputFile_check") + self.gridLayout_70 = QtWidgets.QGridLayout(self.groupBox_resample1000Hz_inputFile_check) + self.gridLayout_70.setObjectName("gridLayout_70") + self.lineEdit_resample1000Hz_DSbcg_sig_path = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.lineEdit_resample1000Hz_DSbcg_sig_path.setFont(font) + self.lineEdit_resample1000Hz_DSbcg_sig_path.setObjectName("lineEdit_resample1000Hz_DSbcg_sig_path") + self.gridLayout_70.addWidget(self.lineEdit_resample1000Hz_DSbcg_sig_path, 3, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_4.setFont(font) + self.label_4.setObjectName("label_4") + self.gridLayout_70.addWidget(self.label_4, 0, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_6.setFont(font) + self.label_6.setObjectName("label_6") + self.gridLayout_70.addWidget(self.label_6, 4, 0, 1, 1) + self.lineEdit_resample1000Hz_raw_org_path = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.lineEdit_resample1000Hz_raw_org_path.setFont(font) + self.lineEdit_resample1000Hz_raw_org_path.setObjectName("lineEdit_resample1000Hz_raw_org_path") + self.gridLayout_70.addWidget(self.lineEdit_resample1000Hz_raw_org_path, 1, 0, 1, 1) + self.label_5 = QtWidgets.QLabel(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_5.setFont(font) + self.label_5.setObjectName("label_5") + self.gridLayout_70.addWidget(self.label_5, 2, 0, 1, 1) + self.lineEdit_resample1000Hz_save_path = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.lineEdit_resample1000Hz_save_path.setFont(font) + self.lineEdit_resample1000Hz_save_path.setObjectName("lineEdit_resample1000Hz_save_path") + self.gridLayout_70.addWidget(self.lineEdit_resample1000Hz_save_path, 5, 0, 1, 1) + self.gridLayout_64.addWidget(self.groupBox_resample1000Hz_inputFile_check, 0, 0, 1, 2) + self.pushButton_resample1000Hz_save = QtWidgets.QPushButton(self.groupBox_resample1000Hz) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_resample1000Hz_save.sizePolicy().hasHeightForWidth()) + self.pushButton_resample1000Hz_save.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_resample1000Hz_save.setFont(font) + self.pushButton_resample1000Hz_save.setObjectName("pushButton_resample1000Hz_save") + self.gridLayout_64.addWidget(self.pushButton_resample1000Hz_save, 3, 1, 1, 1) + self.groupBox_resample1000Hz_input_args = QtWidgets.QGroupBox(self.groupBox_resample1000Hz) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_resample1000Hz_input_args.sizePolicy().hasHeightForWidth()) + self.groupBox_resample1000Hz_input_args.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_resample1000Hz_input_args.setFont(font) + self.groupBox_resample1000Hz_input_args.setObjectName("groupBox_resample1000Hz_input_args") + self.gridLayout_71 = QtWidgets.QGridLayout(self.groupBox_resample1000Hz_input_args) + self.gridLayout_71.setObjectName("gridLayout_71") + self.lineEdit_resample1000Hz_original_sampling_rate = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_resample1000Hz_original_sampling_rate.setFont(font) + self.lineEdit_resample1000Hz_original_sampling_rate.setPlaceholderText("") + self.lineEdit_resample1000Hz_original_sampling_rate.setObjectName("lineEdit_resample1000Hz_original_sampling_rate") + self.gridLayout_71.addWidget(self.lineEdit_resample1000Hz_original_sampling_rate, 0, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.gridLayout_71.addWidget(self.label_3, 1, 0, 1, 1) + self.lineEdit_resample1000Hz_target_sampling_rate = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_resample1000Hz_target_sampling_rate.setFont(font) + self.lineEdit_resample1000Hz_target_sampling_rate.setPlaceholderText("") + self.lineEdit_resample1000Hz_target_sampling_rate.setObjectName("lineEdit_resample1000Hz_target_sampling_rate") + self.gridLayout_71.addWidget(self.lineEdit_resample1000Hz_target_sampling_rate, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_2.setFont(font) + self.label_2.setObjectName("label_2") + self.gridLayout_71.addWidget(self.label_2, 0, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label.setFont(font) + self.label.setObjectName("label") + self.gridLayout_71.addWidget(self.label, 2, 0, 1, 1) + self.lineEdit_resample1000Hz_cut_second = QtWidgets.QLineEdit(self.groupBox_resample1000Hz_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_resample1000Hz_cut_second.setFont(font) + self.lineEdit_resample1000Hz_cut_second.setPlaceholderText("") + self.lineEdit_resample1000Hz_cut_second.setObjectName("lineEdit_resample1000Hz_cut_second") + self.gridLayout_71.addWidget(self.lineEdit_resample1000Hz_cut_second, 2, 1, 1, 1) + self.gridLayout_64.addWidget(self.groupBox_resample1000Hz_input_args, 1, 0, 1, 2) + self.gridLayout_64.setColumnStretch(0, 1) + self.gridLayout_64.setColumnStretch(1, 1) + self.gridLayout_64.setRowStretch(0, 5) + self.gridLayout_64.setRowStretch(1, 4) + self.gridLayout_64.setRowStretch(2, 5) + self.gridLayout_64.setRowStretch(3, 2) + self.pushButton_resample1000Hz_view.raise_() + self.pushButton_resample1000Hz_save.raise_() + self.groupBox_resample1000Hz_input_args.raise_() + self.groupBox_resample1000Hz_inputFile_check.raise_() + self.verticalLayout_middle.addWidget(self.groupBox_resample1000Hz) + self.groupBox_detect_Rpeaks = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_detect_Rpeaks.sizePolicy().hasHeightForWidth()) + self.groupBox_detect_Rpeaks.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_detect_Rpeaks.setFont(font) + self.groupBox_detect_Rpeaks.setObjectName("groupBox_detect_Rpeaks") + self.gridLayout_67 = QtWidgets.QGridLayout(self.groupBox_detect_Rpeaks) + self.gridLayout_67.setObjectName("gridLayout_67") + self.groupBox_detect_Rpeaks_inputFile_check = QtWidgets.QGroupBox(self.groupBox_detect_Rpeaks) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_detect_Rpeaks_inputFile_check.sizePolicy().hasHeightForWidth()) + self.groupBox_detect_Rpeaks_inputFile_check.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_detect_Rpeaks_inputFile_check.setFont(font) + self.groupBox_detect_Rpeaks_inputFile_check.setObjectName("groupBox_detect_Rpeaks_inputFile_check") + self.gridLayout_72 = QtWidgets.QGridLayout(self.groupBox_detect_Rpeaks_inputFile_check) + self.gridLayout_72.setObjectName("gridLayout_72") + self.lineEdit_detect_Rpeaks_filter_ecg_path = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.lineEdit_detect_Rpeaks_filter_ecg_path.setFont(font) + self.lineEdit_detect_Rpeaks_filter_ecg_path.setObjectName("lineEdit_detect_Rpeaks_filter_ecg_path") + self.gridLayout_72.addWidget(self.lineEdit_detect_Rpeaks_filter_ecg_path, 1, 0, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_7.setFont(font) + self.label_7.setObjectName("label_7") + self.gridLayout_72.addWidget(self.label_7, 0, 0, 1, 1) + self.textBrowser = QtWidgets.QTextBrowser(self.groupBox_detect_Rpeaks_inputFile_check) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.textBrowser.sizePolicy().hasHeightForWidth()) + self.textBrowser.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(12) + self.textBrowser.setFont(font) + self.textBrowser.setStyleSheet("background-color: rgb(85, 255, 255);") + self.textBrowser.setObjectName("textBrowser") + self.gridLayout_72.addWidget(self.textBrowser, 4, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_8.setFont(font) + self.label_8.setObjectName("label_8") + self.gridLayout_72.addWidget(self.label_8, 2, 0, 1, 1) + self.lineEdit_detect_Rpeaks_save_path = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_inputFile_check) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.lineEdit_detect_Rpeaks_save_path.setFont(font) + self.lineEdit_detect_Rpeaks_save_path.setObjectName("lineEdit_detect_Rpeaks_save_path") + self.gridLayout_72.addWidget(self.lineEdit_detect_Rpeaks_save_path, 3, 0, 1, 1) + self.gridLayout_72.setRowStretch(0, 1) + self.gridLayout_72.setRowStretch(1, 1) + self.gridLayout_72.setRowStretch(2, 1) + self.gridLayout_72.setRowStretch(3, 1) + self.gridLayout_72.setRowStretch(4, 2) + self.gridLayout_67.addWidget(self.groupBox_detect_Rpeaks_inputFile_check, 0, 0, 1, 2) + self.pushButton_detect_Rpeaks_save = QtWidgets.QPushButton(self.groupBox_detect_Rpeaks) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Rpeaks_save.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Rpeaks_save.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_detect_Rpeaks_save.setFont(font) + self.pushButton_detect_Rpeaks_save.setObjectName("pushButton_detect_Rpeaks_save") + self.gridLayout_67.addWidget(self.pushButton_detect_Rpeaks_save, 3, 1, 1, 1) + self.pushButton_detect_Rpeaks_view = QtWidgets.QPushButton(self.groupBox_detect_Rpeaks) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Rpeaks_view.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Rpeaks_view.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_detect_Rpeaks_view.setFont(font) + self.pushButton_detect_Rpeaks_view.setObjectName("pushButton_detect_Rpeaks_view") + self.gridLayout_67.addWidget(self.pushButton_detect_Rpeaks_view, 3, 0, 1, 1) + self.groupBox_detect_Rpeaks_input_args = QtWidgets.QGroupBox(self.groupBox_detect_Rpeaks) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_detect_Rpeaks_input_args.sizePolicy().hasHeightForWidth()) + self.groupBox_detect_Rpeaks_input_args.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_detect_Rpeaks_input_args.setFont(font) + self.groupBox_detect_Rpeaks_input_args.setObjectName("groupBox_detect_Rpeaks_input_args") + self.gridLayout_73 = QtWidgets.QGridLayout(self.groupBox_detect_Rpeaks_input_args) + self.gridLayout_73.setObjectName("gridLayout_73") + self.radioButton_detector_method_Wt = QtWidgets.QRadioButton(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_detector_method_Wt.setFont(font) + self.radioButton_detector_method_Wt.setObjectName("radioButton_detector_method_Wt") + self.gridLayout_73.addWidget(self.radioButton_detector_method_Wt, 6, 0, 1, 1) + self.label_9 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_9.setFont(font) + self.label_9.setObjectName("label_9") + self.gridLayout_73.addWidget(self.label_9, 0, 0, 1, 1) + self.lineEdit_detect_Rpeaks_bandpass_low = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_detect_Rpeaks_bandpass_low.setFont(font) + self.lineEdit_detect_Rpeaks_bandpass_low.setPlaceholderText("") + self.lineEdit_detect_Rpeaks_bandpass_low.setObjectName("lineEdit_detect_Rpeaks_bandpass_low") + self.gridLayout_73.addWidget(self.lineEdit_detect_Rpeaks_bandpass_low, 2, 1, 1, 1) + self.radioButton_detector_method_ta = QtWidgets.QRadioButton(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_detector_method_ta.setFont(font) + self.radioButton_detector_method_ta.setObjectName("radioButton_detector_method_ta") + self.gridLayout_73.addWidget(self.radioButton_detector_method_ta, 4, 1, 1, 3) + self.label_11 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_11.setFont(font) + self.label_11.setObjectName("label_11") + self.gridLayout_73.addWidget(self.label_11, 1, 0, 1, 1) + self.radioButton_detector_method_Hamilton = QtWidgets.QRadioButton(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_detector_method_Hamilton.setFont(font) + self.radioButton_detector_method_Hamilton.setObjectName("radioButton_detector_method_Hamilton") + self.gridLayout_73.addWidget(self.radioButton_detector_method_Hamilton, 6, 1, 1, 3) + self.lineEdit_detect_Rpeaks_sampling_rate = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_detect_Rpeaks_sampling_rate.setFont(font) + self.lineEdit_detect_Rpeaks_sampling_rate.setPlaceholderText("") + self.lineEdit_detect_Rpeaks_sampling_rate.setObjectName("lineEdit_detect_Rpeaks_sampling_rate") + self.gridLayout_73.addWidget(self.lineEdit_detect_Rpeaks_sampling_rate, 0, 1, 1, 3) + self.label_12 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_12.setFont(font) + self.label_12.setObjectName("label_12") + self.gridLayout_73.addWidget(self.label_12, 2, 0, 1, 1) + self.radioButton_detector_method_pt = QtWidgets.QRadioButton(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_detector_method_pt.setFont(font) + self.radioButton_detector_method_pt.setObjectName("radioButton_detector_method_pt") + self.gridLayout_73.addWidget(self.radioButton_detector_method_pt, 4, 0, 1, 1) + self.radioButton_detector_method_Engzee = QtWidgets.QRadioButton(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.radioButton_detector_method_Engzee.setFont(font) + self.radioButton_detector_method_Engzee.setObjectName("radioButton_detector_method_Engzee") + self.gridLayout_73.addWidget(self.radioButton_detector_method_Engzee, 8, 0, 1, 1) + self.lineEdit_detect_Rpeaks_bandpass_high = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_detect_Rpeaks_bandpass_high.setFont(font) + self.lineEdit_detect_Rpeaks_bandpass_high.setPlaceholderText("") + self.lineEdit_detect_Rpeaks_bandpass_high.setObjectName("lineEdit_detect_Rpeaks_bandpass_high") + self.gridLayout_73.addWidget(self.lineEdit_detect_Rpeaks_bandpass_high, 2, 3, 1, 1) + self.lineEdit_detect_Rpeaks_peaks_value = QtWidgets.QLineEdit(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.lineEdit_detect_Rpeaks_peaks_value.setFont(font) + self.lineEdit_detect_Rpeaks_peaks_value.setPlaceholderText("") + self.lineEdit_detect_Rpeaks_peaks_value.setObjectName("lineEdit_detect_Rpeaks_peaks_value") + self.gridLayout_73.addWidget(self.lineEdit_detect_Rpeaks_peaks_value, 1, 1, 1, 3) + self.label_13 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_input_args) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_13.setFont(font) + self.label_13.setObjectName("label_13") + self.gridLayout_73.addWidget(self.label_13, 2, 2, 1, 1) + self.label_10 = QtWidgets.QLabel(self.groupBox_detect_Rpeaks_input_args) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) + self.label_10.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.label_10.setFont(font) + self.label_10.setObjectName("label_10") + self.gridLayout_73.addWidget(self.label_10, 3, 0, 1, 4) + self.gridLayout_67.addWidget(self.groupBox_detect_Rpeaks_input_args, 1, 0, 1, 2) + self.groupBox_detect_Rpeaks_signal_parts_list = QtWidgets.QGroupBox(self.groupBox_detect_Rpeaks) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_detect_Rpeaks_signal_parts_list.sizePolicy().hasHeightForWidth()) + self.groupBox_detect_Rpeaks_signal_parts_list.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_detect_Rpeaks_signal_parts_list.setFont(font) + self.groupBox_detect_Rpeaks_signal_parts_list.setObjectName("groupBox_detect_Rpeaks_signal_parts_list") + self.gridLayout_74 = QtWidgets.QGridLayout(self.groupBox_detect_Rpeaks_signal_parts_list) + self.gridLayout_74.setObjectName("gridLayout_74") + self.pushButton_detect_Rpeaks_left = QtWidgets.QPushButton(self.groupBox_detect_Rpeaks_signal_parts_list) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Rpeaks_left.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Rpeaks_left.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_detect_Rpeaks_left.setFont(font) + self.pushButton_detect_Rpeaks_left.setObjectName("pushButton_detect_Rpeaks_left") + self.gridLayout_74.addWidget(self.pushButton_detect_Rpeaks_left, 0, 2, 1, 1) + self.pushButton_detect_Rpeaks_right = QtWidgets.QPushButton(self.groupBox_detect_Rpeaks_signal_parts_list) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_detect_Rpeaks_right.sizePolicy().hasHeightForWidth()) + self.pushButton_detect_Rpeaks_right.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_detect_Rpeaks_right.setFont(font) + self.pushButton_detect_Rpeaks_right.setObjectName("pushButton_detect_Rpeaks_right") + self.gridLayout_74.addWidget(self.pushButton_detect_Rpeaks_right, 1, 2, 1, 1) + self.tableWidget_detect_Rpeaks_signal_parts_list = QtWidgets.QTableWidget(self.groupBox_detect_Rpeaks_signal_parts_list) + self.tableWidget_detect_Rpeaks_signal_parts_list.setColumnCount(1) + self.tableWidget_detect_Rpeaks_signal_parts_list.setObjectName("tableWidget_detect_Rpeaks_signal_parts_list") + self.tableWidget_detect_Rpeaks_signal_parts_list.setRowCount(0) + self.tableWidget_detect_Rpeaks_signal_parts_list.verticalHeader().setVisible(False) + self.gridLayout_74.addWidget(self.tableWidget_detect_Rpeaks_signal_parts_list, 0, 0, 2, 2) + self.gridLayout_74.setColumnStretch(0, 1) + self.gridLayout_74.setColumnStretch(1, 1) + self.gridLayout_74.setColumnStretch(2, 1) + self.gridLayout_67.addWidget(self.groupBox_detect_Rpeaks_signal_parts_list, 2, 0, 1, 2) + self.gridLayout_67.setRowStretch(0, 4) + self.gridLayout_67.setRowStretch(1, 4) + self.gridLayout_67.setRowStretch(2, 4) + self.gridLayout_67.setRowStretch(3, 1) + self.verticalLayout_middle.addWidget(self.groupBox_detect_Rpeaks) + self.groupBox_detect_Jpeaks = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_detect_Jpeaks.sizePolicy().hasHeightForWidth()) + self.groupBox_detect_Jpeaks.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_detect_Jpeaks.setFont(font) + self.groupBox_detect_Jpeaks.setObjectName("groupBox_detect_Jpeaks") + self.gridLayout_69 = QtWidgets.QGridLayout(self.groupBox_detect_Jpeaks) + self.gridLayout_69.setObjectName("gridLayout_69") + self.pushButton_5 = QtWidgets.QPushButton(self.groupBox_detect_Jpeaks) + self.pushButton_5.setObjectName("pushButton_5") + self.gridLayout_69.addWidget(self.pushButton_5, 0, 0, 1, 1) + self.gridLayout_69.setColumnStretch(0, 1) + self.verticalLayout_middle.addWidget(self.groupBox_detect_Jpeaks) + self.groupBox_info = QtWidgets.QGroupBox(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_info.sizePolicy().hasHeightForWidth()) + self.groupBox_info.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.groupBox_info.setFont(font) + self.groupBox_info.setObjectName("groupBox_info") + self.gridLayout_66 = QtWidgets.QGridLayout(self.groupBox_info) + self.gridLayout_66.setObjectName("gridLayout_66") + self.textBrowser_infoOutput = QtWidgets.QTextBrowser(self.groupBox_info) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(10) + self.textBrowser_infoOutput.setFont(font) + self.textBrowser_infoOutput.setObjectName("textBrowser_infoOutput") + self.gridLayout_66.addWidget(self.textBrowser_infoOutput, 0, 0, 1, 2) + self.pushButton_backToMenu = QtWidgets.QPushButton(self.groupBox_info) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton_backToMenu.sizePolicy().hasHeightForWidth()) + self.pushButton_backToMenu.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("黑体") + font.setPointSize(14) + self.pushButton_backToMenu.setFont(font) + self.pushButton_backToMenu.setObjectName("pushButton_backToMenu") + self.gridLayout_66.addWidget(self.pushButton_backToMenu, 1, 0, 1, 1) + self.gridLayout_66.setColumnStretch(0, 1) + self.gridLayout_66.setRowStretch(0, 4) + self.gridLayout_66.setRowStretch(1, 1) + self.verticalLayout_middle.addWidget(self.groupBox_info) + self.verticalLayout_middle.setStretch(0, 8) + self.verticalLayout_middle.setStretch(1, 5) + self.verticalLayout_middle.setStretch(2, 8) + self.verticalLayout_middle.setStretch(3, 3) + self.gridLayout.addLayout(self.verticalLayout_middle, 0, 2, 1, 1) + self.gridLayout.setColumnStretch(0, 5) + self.gridLayout.setColumnStretch(2, 3) + self.gridLayout.setColumnStretch(3, 15) + MainWindow.setCentralWidget(self.centralwidget) + 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.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Heartbeat_Annotation")) + self.groupBox_func_select.setTitle(_translate("MainWindow", "功能选择")) + self.pushButton_detect_Rpeaks.setText(_translate("MainWindow", "ECG的R峰提取")) + self.pushButton_detect_Jpeaks.setText(_translate("MainWindow", "BCG的J峰提取")) + self.pushButton_rootpath_open.setText(_translate("MainWindow", "浏览")) + self.pushButton_resample1000Hz.setText(_translate("MainWindow", "重采样至1000Hz")) + self.lineEdit_rootpath.setPlaceholderText(_translate("MainWindow", "数据根目录")) + self.groupBox_plot.setTitle(_translate("MainWindow", "绘图区")) + self.groupBox_resample1000Hz.setTitle(_translate("MainWindow", "重采样至1000Hz")) + self.pushButton_resample1000Hz_view.setText(_translate("MainWindow", "查看结果")) + self.groupBox_resample1000Hz_inputFile_check.setTitle(_translate("MainWindow", "程序识别到的文件路径")) + self.lineEdit_resample1000Hz_DSbcg_sig_path.setPlaceholderText(_translate("MainWindow", "DSbcg_sig.txt文件路径")) + self.label_4.setText(_translate("MainWindow", "raw_org.txt")) + self.label_6.setText(_translate("MainWindow", "保存路径")) + self.lineEdit_resample1000Hz_raw_org_path.setPlaceholderText(_translate("MainWindow", "raw_org.txt文件路径")) + self.label_5.setText(_translate("MainWindow", "DSbcg_sig.txt")) + self.lineEdit_resample1000Hz_save_path.setPlaceholderText(_translate("MainWindow", "文件保存路径")) + self.pushButton_resample1000Hz_save.setText(_translate("MainWindow", "保存结果")) + self.groupBox_resample1000Hz_input_args.setTitle(_translate("MainWindow", "<重采样>参数输入")) + self.lineEdit_resample1000Hz_original_sampling_rate.setText(_translate("MainWindow", "100")) + self.label_3.setText(_translate("MainWindow", "目标采样率(Hz)")) + self.lineEdit_resample1000Hz_target_sampling_rate.setText(_translate("MainWindow", "1000")) + self.label_2.setText(_translate("MainWindow", "原始采样率(Hz)")) + self.label.setText(_translate("MainWindow", "裁剪的时间(秒)")) + self.groupBox_detect_Rpeaks.setTitle(_translate("MainWindow", "ECG的R峰提取")) + self.groupBox_detect_Rpeaks_inputFile_check.setTitle(_translate("MainWindow", "程序识别到的文件路径")) + self.lineEdit_detect_Rpeaks_filter_ecg_path.setPlaceholderText(_translate("MainWindow", "filter_ecg.txt文件路径")) + self.label_7.setText(_translate("MainWindow", "filter_ecg.txt")) + self.textBrowser.setHtml(_translate("MainWindow", "\n" +"\n" +"

将在此目录的文件夹下生成一一对应的hecg.txt和hRpeak.txt若干个

")) + self.label_8.setText(_translate("MainWindow", "保存路径")) + self.lineEdit_detect_Rpeaks_save_path.setPlaceholderText(_translate("MainWindow", "文件保存路径")) + self.pushButton_detect_Rpeaks_save.setText(_translate("MainWindow", "保存结果")) + self.pushButton_detect_Rpeaks_view.setText(_translate("MainWindow", "查看结果")) + self.groupBox_detect_Rpeaks_input_args.setTitle(_translate("MainWindow", "参数输入")) + self.radioButton_detector_method_Wt.setText(_translate("MainWindow", "Wt")) + self.label_9.setText(_translate("MainWindow", "信号采样率(Hz)")) + self.lineEdit_detect_Rpeaks_bandpass_low.setText(_translate("MainWindow", "2")) + self.radioButton_detector_method_ta.setText(_translate("MainWindow", "ta")) + self.label_11.setText(_translate("MainWindow", "寻峰阈值(个)")) + self.radioButton_detector_method_Hamilton.setText(_translate("MainWindow", "Hamilton")) + self.lineEdit_detect_Rpeaks_sampling_rate.setText(_translate("MainWindow", "1000")) + self.label_12.setText(_translate("MainWindow", "带通滤波截止频率(Hz)")) + self.radioButton_detector_method_pt.setText(_translate("MainWindow", "pt")) + self.radioButton_detector_method_Engzee.setText(_translate("MainWindow", "Engzee")) + self.lineEdit_detect_Rpeaks_bandpass_high.setText(_translate("MainWindow", "15")) + self.lineEdit_detect_Rpeaks_peaks_value.setText(_translate("MainWindow", "200")) + self.label_13.setText(_translate("MainWindow", "~")) + self.label_10.setText(_translate("MainWindow", "R峰检测方法选择(一般选pt)")) + self.groupBox_detect_Rpeaks_signal_parts_list.setTitle(_translate("MainWindow", "信号片段列表")) + self.pushButton_detect_Rpeaks_left.setText(_translate("MainWindow", "上一个")) + self.pushButton_detect_Rpeaks_right.setText(_translate("MainWindow", "下一个")) + self.groupBox_detect_Jpeaks.setTitle(_translate("MainWindow", "BCG的J峰提取")) + self.pushButton_5.setText(_translate("MainWindow", "PushButton")) + self.groupBox_info.setTitle(_translate("MainWindow", "信息")) + self.pushButton_backToMenu.setText(_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..f4ec68b --- /dev/null +++ b/MainWindow.ui @@ -0,0 +1,1058 @@ + + + MainWindow + + + + 0 + 0 + 1920 + 1187 + + + + Heartbeat_Annotation + + + + + + + + + + 黑体 + 10 + + + + 功能选择 + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 黑体 + 24 + + + + ECG的R峰提取 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 黑体 + 24 + + + + BCG的J峰提取 + + + + + + + + 黑体 + 14 + + + + 浏览 + + + + + + + + 0 + 0 + + + + + 黑体 + 24 + + + + 重采样至1000Hz + + + + + + + + Times New Roman + 14 + + + + 数据根目录 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 518 + 57 + + + + + + + + + + + + + + + + 黑体 + 10 + + + + 绘图区 + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 重采样至1000Hz + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 查看结果 + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 程序识别到的文件路径 + + + + + + + 黑体 + 10 + + + + DSbcg_sig.txt文件路径 + + + + + + + + 黑体 + 14 + + + + raw_org.txt + + + + + + + + 黑体 + 14 + + + + 保存路径 + + + + + + + + 黑体 + 10 + + + + raw_org.txt文件路径 + + + + + + + + 黑体 + 14 + + + + DSbcg_sig.txt + + + + + + + + 黑体 + 10 + + + + 文件保存路径 + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 保存结果 + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + <重采样>参数输入 + + + + + + + 黑体 + 14 + + + + 100 + + + + + + + + + + + 黑体 + 14 + + + + 目标采样率(Hz) + + + + + + + + 黑体 + 14 + + + + 1000 + + + + + + + + + + + 黑体 + 14 + + + + 原始采样率(Hz) + + + + + + + + 黑体 + 14 + + + + 裁剪的时间(秒) + + + + + + + + 黑体 + 14 + + + + + + + + + + + + pushButton_resample1000Hz_view + pushButton_resample1000Hz_save + groupBox_resample1000Hz_input_args + groupBox_resample1000Hz_inputFile_check + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + ECG的R峰提取 + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 程序识别到的文件路径 + + + + + + + 黑体 + 10 + + + + filter_ecg.txt文件路径 + + + + + + + + 黑体 + 14 + + + + filter_ecg.txt + + + + + + + + 0 + 0 + + + + + 黑体 + 12 + + + + background-color: rgb(85, 255, 255); + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'黑体','黑体','黑体'; font-size:12pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'黑体','黑体';">将在此目录的文件夹下生成一一对应的hecg.txt和hRpeak.txt若干个</span></p></body></html> + + + + + + + + 黑体 + 14 + + + + 保存路径 + + + + + + + + 黑体 + 10 + + + + 文件保存路径 + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 保存结果 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 查看结果 + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + <R峰提取>参数输入 + + + + + + + 黑体 + 14 + + + + Wt + + + + + + + + 黑体 + 14 + + + + 信号采样率(Hz) + + + + + + + + 黑体 + 14 + + + + 2 + + + + + + + + + + + 黑体 + 14 + + + + ta + + + + + + + + 黑体 + 14 + + + + 寻峰阈值(个) + + + + + + + + 黑体 + 14 + + + + Hamilton + + + + + + + + 黑体 + 14 + + + + 1000 + + + + + + + + + + + 黑体 + 14 + + + + 带通滤波截止频率(Hz) + + + + + + + + 黑体 + 14 + + + + pt + + + + + + + + 黑体 + 14 + + + + Engzee + + + + + + + + 黑体 + 14 + + + + 15 + + + + + + + + + + + 黑体 + 14 + + + + 200 + + + + + + + + + + + 黑体 + 14 + + + + ~ + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + R峰检测方法选择(一般选pt) + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 信号片段列表 + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 上一个 + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 下一个 + + + + + + + 1 + + + false + + + + + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + BCG的J峰提取 + + + + + + PushButton + + + + + + + + + + + 0 + 0 + + + + + 黑体 + 10 + + + + 信息 + + + + + + + 黑体 + 10 + + + + + + + + + 0 + 0 + + + + + 黑体 + 14 + + + + 返回主菜单 + + + + + + + + + + + + + 数据路径选择 + + + + 黑体 + 14 + + + + + + 加载存档 + + + + 黑体 + 14 + + + + + + + diff --git a/detect_Rpeak2.py b/detect_Rpeak2.py new file mode 100644 index 0000000..e1e0462 --- /dev/null +++ b/detect_Rpeak2.py @@ -0,0 +1,102 @@ +import pandas as pd +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import ecgdetectors +from ecgdetectors import Detectors +from scipy import fftpack +import os +from BCGDataset import BCGDataset,BCG_Operation,read_all_data +import math + +def Normalize(data): + return (data - np.min(data))/(np.max(data) - np.min(data)) +def refinement( data, peak): + if len(data) == 0 or len(peak) <=2 : return None + firstPeak = peak[0] + lastPeak = peak[-1] + meanPeak = np.quantile( data[peak[1:-1]], 0.2 ) + if data[firstPeak] < meanPeak * 0.6 : + peak = np.delete(peak, 0) + if data[lastPeak] < meanPeak * 0.6 : + peak = np.delete(peak, -1) + return np.array(peak) + +def find_TPeak(data,peaks,th=50): + """ + 找出真实的J峰或R峰 + :param data: BCG或ECG数据 + :param peaks: 初步峰值(从label中导出的location_R) + :param th: 范围阈值 + :return: 真实峰值 + """ + return_peak = [] + for peak in peaks: + if peak>len(data):continue + min_win,max_win = max(0,int(peak-th)),min(len(data),int(peak+th)) + return_peak.append(np.argmax(data[min_win:max_win])+min_win) + return np.array(return_peak) + +def Rpeak_Detection(raw_ecg,fs,low_cut,high_cut,th1,detector_method): + detectors = Detectors(sampling_frequency=fs) + method_dic = {'pt': detectors.pan_tompkins_detector, + 'ta': detectors.two_average_detector, + "Engzee": detectors.engzee_detector, + "Wt": detectors.swt_detector, + "Christov": detectors.christov_detector, + "Hamilton": detectors.hamilton_detector + } + detectormethods = method_dic[detector_method] + + # raw_ecg = raw_ecg[200*sample_rate:] + preprocessing = BCG_Operation(sample_rate=fs) # 对ECG做了降采样处理 + raw_ecg = preprocessing.Butterworth(raw_ecg, "bandpass", low_cut=low_cut, high_cut=high_cut, order=3) * 4 + #######################限制幅值处理############################################ + # for i in range(len(raw_ecg)): + # if raw_ecg[i] > 300 or raw_ecg[i] < -300: + # raw_ecg[i] = 0 + ############################################################################## + + ##############################切割处理########################################## + all_file_num = math.ceil((len(raw_ecg) / fs / 60 / 60)) + ecg_seq = np.array(np.arange(all_file_num)) + ecg_seq = ecg_seq.astype(np.ndarray) + R_peak_seq = np.array(np.arange(all_file_num)) + R_peak_seq = R_peak_seq.astype(np.ndarray) + Interval_seq = np.array(np.arange(all_file_num)) + Interval_seq = Interval_seq.astype(np.ndarray) + RRIV_seq = np.array(np.arange(all_file_num)) + RRIV_seq = RRIV_seq.astype(np.ndarray) + + for file_num in range(1,all_file_num+1): + if file_num != all_file_num: + new_ecg = raw_ecg[fs*3600*(file_num - 1) : fs*3600*file_num] + else: + new_ecg = raw_ecg[fs*3600*(file_num - 1):] + ############################################################################## + + R_peak = np.array(detectormethods(new_ecg)) - 100 + # R_peak = np.array(detectors.pan_tompkins_detector(raw_ecg))-100 + + R_peak = find_TPeak(new_ecg, R_peak, th=int(th1 * fs / 1000)) + R_peak = refinement(new_ecg, R_peak) + + RR_Interval = np.full(len(R_peak) - 1, np.nan) + + for i in range(len(R_peak) - 1): + RR_Interval[i] = R_peak[i + 1] - R_peak[i] + + RRIV = np.full(len(RR_Interval) - 1, np.nan) + for i in range(len(RR_Interval) - 1): + RRIV[i] = RR_Interval[i + 1] - RR_Interval[i] + + Interval = np.full(len(new_ecg), np.nan) + for i in range(len(R_peak) - 1): + Interval[R_peak[i]: R_peak[i + 1]] = R_peak[i + 1] - R_peak[i] + + ecg_seq[file_num - 1] = new_ecg + R_peak_seq[file_num - 1] = R_peak + Interval_seq[file_num - 1] = Interval + RRIV_seq[file_num - 1] = RRIV + + return ecg_seq, R_peak_seq, Interval_seq, RRIV_seq \ No newline at end of file diff --git a/heartbeat_annotation.py b/heartbeat_annotation.py new file mode 100644 index 0000000..c82728c --- /dev/null +++ b/heartbeat_annotation.py @@ -0,0 +1,428 @@ +""" +@author:Yosa +@file:heartbeat_annotation.py +@email:2023025086@m.scnu.edu.cn +@time:2025/2/18 +""" + +import sys +from logging import NOTSET, getLogger, FileHandler, Formatter, StreamHandler, info, error, debug +from time import time, strftime, localtime + +import numpy as np +from PyQt5.QtGui import QFont, QDoubleValidator, QIntValidator +from matplotlib.pyplot import title +from pandas import DataFrame, read_csv, read_excel, Series, concat +from matplotlib.ticker import FuncFormatter +from numpy import load, nan, zeros, append, linspace, place +from matplotlib import use +from matplotlib import pyplot as plt, gridspec +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT +from datetime import datetime +from pathlib import Path +from PyQt5.QtCore import QCoreApplication, QTimer +from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox, QButtonGroup, QApplication, QTableWidgetItem, \ + QLineEdit, QAction, QTableWidget +from scipy import signal +from scipy.signal import butter, filtfilt, find_peaks + +import detect_Rpeak2 +import resample_1000hz +from MainWindow import Ui_MainWindow + +use("Qt5Agg") # 声明使用 QT5 + +# 设置日志 +logger = getLogger() +logger.setLevel(NOTSET) +realtime = strftime('%Y%m%d', localtime(time())) +if not Path("logs").exists(): + Path("logs").mkdir(exist_ok=True) +fh = FileHandler(Path("logs") / (realtime + ".log"), mode='a') +fh.setLevel(NOTSET) +fh.setFormatter(Formatter("%(asctime)s: %(message)s")) +logger.addHandler(fh) + +ch = StreamHandler() +ch.setLevel(NOTSET) +ch.setFormatter(Formatter("%(asctime)s: %(message)s")) +logger.addHandler(ch) +getLogger('matplotlib.font_manager').disabled = True +info("------------------------------------") +info("------heartbeat_annotation.py-------") + +class MainWindow(QMainWindow, Ui_MainWindow): + + root_path = Path("") + data1 = None + data2 = None + data3 = None + ecg_seq = None + R_peak_seq = None + Interval_seq = None + RRIV_seq = None + temp = None + + # 程序初始化操作 + 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 = NavigationToolbar2QT(self.canvas) + self.verticalLayout_canvas.addWidget(self.canvas) + self.verticalLayout_canvas.addWidget(self.figToolbar) + + # 界面状态初始化 + self.groupBox_func_select.setVisible(True) + self.groupBox_resample1000Hz.setVisible(False) + self.groupBox_detect_Rpeaks.setVisible(False) + self.groupBox_detect_Jpeaks.setVisible(False) + self.groupBox_info.setVisible(False) + self.groupBox_plot.setVisible(False) + self.lineEdit_resample1000Hz_raw_org_path.setEnabled(False) + self.lineEdit_resample1000Hz_DSbcg_sig_path.setEnabled(False) + self.lineEdit_detect_Rpeaks_filter_ecg_path.setEnabled(False) + self.groupBox_detect_Rpeaks_signal_parts_list.setEnabled(False) + + # 设置表格属性 + self.tableWidget_detect_Rpeaks_signal_parts_list.setHorizontalHeaderLabels(['信号片段']) + self.tableWidget_detect_Rpeaks_signal_parts_list.setEditTriggers(QTableWidget.NoEditTriggers) + self.tableWidget_detect_Rpeaks_signal_parts_list.horizontalHeader().setStretchLastSection(True) + self.tableWidget_detect_Rpeaks_signal_parts_list.horizontalHeader().setSectionResizeMode(1) + + # 槽函数连接初始化 + self.pushButton_rootpath_open.clicked.connect(self.slot_btn_rootpath_open) + self.pushButton_resample1000Hz.clicked.connect(self.slot_btn_resample1000Hz) + self.pushButton_detect_Rpeaks.clicked.connect(self.slot_btn_detect_Rpeaks) + self.pushButton_detect_Jpeaks.clicked.connect(self.slot_btn_detect_Jpeaks) + self.pushButton_backToMenu.clicked.connect(self.slot_btn_backToMenu) + self.pushButton_resample1000Hz_view.clicked.connect(self.slot_btn_resample1000Hz_view) + self.pushButton_resample1000Hz_save.clicked.connect(self.slot_btn_resample1000Hz_save) + self.pushButton_detect_Rpeaks_view.clicked.connect(self.slot_btn_detect_Rpeaks_view) + self.pushButton_detect_Rpeaks_save.clicked.connect(self.slot_btn_detect_Rpeaks_save) + self.pushButton_detect_Rpeaks_left.clicked.connect(self.slot_btn_detect_Rpeaks_left) + self.pushButton_detect_Rpeaks_right.clicked.connect(self.slot_btn_detect_Rpeaks_right) + self.tableWidget_detect_Rpeaks_signal_parts_list.cellDoubleClicked.connect(self.slot_tableWidget_detect_Rpeaks_signal_parts_list_on_cell_double_clicked) + + # 消息弹窗初始化 + self.msgBox = QMessageBox() + self.msgBox.setWindowTitle("消息") + + def slot_btn_rootpath_open(self): + fileDialog = QFileDialog() + if self.sender() == self.pushButton_rootpath_open: + fileDialog.setFileMode(QFileDialog.Directory) + fileDialog.setOption(QFileDialog.ShowDirsOnly, True) + if fileDialog.exec_() == QFileDialog.Accepted: + self.root_path = fileDialog.selectedFiles()[0] + if not self.root_path: + error("Root Path not Exist...") + self.textBrowser_update("操作:根目录路径输入错误") + self.msgBox.setText("根目录路径输入错误") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + self.lineEdit_rootpath.setText(self.root_path) + self.root_path = Path(self.root_path) + info("Loading Root Path...") + else: + info("Canceled Loading Root Path.") + self.textBrowser_update("提示:根目录路径选择取消") + self.msgBox.setText("根目录路径选择取消") + self.msgBox.setIcon(QMessageBox.Warning) + self.msgBox.exec() + + def slot_btn_resample1000Hz(self): + raw_org_path = self.root_path / "raw_org.txt" + DSbcg_sig_path = self.root_path / "bcg_test" / "DSbcg_sig.txt" + if not raw_org_path.exists() or not DSbcg_sig_path.exists(): + error("Can't Find raw_org.txt or DSbcg_sig.txt.") + self.textBrowser_update("错误:无法找到raw_org.txt或DSbcg_sig.txt,无法执行<重采样>,请检查文件是否存在") + self.msgBox.setText("无法找到raw_org.txt或DSbcg_sig.txt,无法执行<重采样>,请检查文件是否存在") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + info("Found raw_org.txt and DSbcg_sig.txt.") + + # 画框子图初始化 + self.gs = gridspec.GridSpec(1, 1, height_ratios=[1]) + 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([]) + self.ax0 = self.figure.add_subplot(self.gs[0]) + self.ax0 = plt.gca() + self.ax0.grid(True) + self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + + self.lineEdit_resample1000Hz_raw_org_path.setText(str(raw_org_path)) + self.lineEdit_resample1000Hz_DSbcg_sig_path.setText(str(DSbcg_sig_path)) + self.lineEdit_resample1000Hz_save_path.setText(str(self.root_path / "DSbcg_sig_1000hz3.txt")) + self.textBrowser_update("提示:找到raw_org.txt和DSbcg_sig.txt") + self.data1 = read_csv(raw_org_path, encoding="utf-8").to_numpy() + self.data2 = read_csv(DSbcg_sig_path, encoding="utf-8", sep="\t") + self.groupBox_func_select.setVisible(False) + self.groupBox_resample1000Hz.setVisible(True) + self.groupBox_info.setVisible(True) + self.groupBox_plot.setVisible(True) + + def slot_btn_resample1000Hz_view(self): + if self.lineEdit_resample1000Hz_original_sampling_rate.text() != "" and self.lineEdit_resample1000Hz_target_sampling_rate.text() != "" and self.lineEdit_resample1000Hz_cut_second.text() != "": + self.ax0.remove() + self.ax0 = self.figure.add_subplot(self.gs[0]) + self.ax0 = plt.gca() + self.ax0.grid(True) + self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + data_before = self.data1 + data_test = resample_1000hz.upsample(self.data2.iloc[:, 2], float(self.lineEdit_resample1000Hz_original_sampling_rate.text()), float(self.lineEdit_resample1000Hz_target_sampling_rate.text())) + data_new = resample_1000hz.upsample(self.data2.iloc[int(float(self.lineEdit_resample1000Hz_original_sampling_rate.text()) * float(self.lineEdit_resample1000Hz_cut_second.text())):, 2], float(self.lineEdit_resample1000Hz_original_sampling_rate.text()), float(self.lineEdit_resample1000Hz_target_sampling_rate.text())) + self.ax0.plot(data_before, 'r', label="Original Data") + self.ax0.plot(data_test + 200, 'g', label="Filtered Data") + self.ax0.plot(data_new, 'b', label="Data After Cut") + self.ax0.legend(loc='upper right') + self.canvas.draw() + self.data3 = data_new + info("Finished Data Plot.") + self.textBrowser_update("提示:完成绘图") + else: + error(f"Miss Args for Resample1000Hz.") + self.textBrowser_update("错误:参数输入存在空值") + self.msgBox.setText("参数输入存在空值") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + + def slot_btn_resample1000Hz_save(self): + if self.data3 is not None: + if self.lineEdit_resample1000Hz_save_path.text() != "" and self.lineEdit_resample1000Hz_save_path.text().endswith(".txt"): + reply = QMessageBox.question(self, "警告:确认操作", f"你确定要将裁剪结果保存到{self.lineEdit_resample1000Hz_save_path.text()}?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + np.savetxt(self.lineEdit_resample1000Hz_save_path.text(), self.data3, fmt='%.4f') + info(f"Saved Data After Cut to {self.lineEdit_resample1000Hz_save_path.text()}.") + self.textBrowser_update(f"提示:保存成功至{self.lineEdit_resample1000Hz_save_path.text()}") + self.msgBox.setText(f"保存成功至{self.lineEdit_resample1000Hz_save_path.text()}") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.textBrowser_update("错误:保存路径输入有误,请检查后重新执行保存") + self.msgBox.setText("保存路径输入有误,请检查后重新执行保存") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + error(f"data new is None.") + self.textBrowser_update("错误:裁切后的数据不存在") + self.msgBox.setText("裁切后的数据不存在") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + + def slot_btn_detect_Rpeaks(self): + filter_ecg_path = self.root_path / "filter_ecg.txt" + if not filter_ecg_path.exists(): + error("Can't Find filter_ecg.txt.") + self.textBrowser_update("错误:无法找到filter_ecg.txt,无法执行,请检查文件是否存在") + self.msgBox.setText("无法找到filter_ecg.txt,无法执行,请检查文件是否存在") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + return + info("Found filter_ecg.txt.") + + # 画框子图初始化 + self.gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1]) + 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([]) + self.ax0 = self.figure.add_subplot(self.gs[0]) + self.ax0 = plt.gca() + self.ax0.grid(True) + self.ax0.tick_params(axis='x', colors='white') + self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + self.ax1 = self.figure.add_subplot(self.gs[1], sharex=self.ax0) + self.ax1 = plt.gca() + self.ax1.grid(True) + self.ax1.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + + self.lineEdit_detect_Rpeaks_filter_ecg_path.setText(str(filter_ecg_path)) + self.lineEdit_detect_Rpeaks_save_path.setText(str(self.root_path / "label")) + self.textBrowser_update("提示:找到filter_ecg.txt") + self.data1 = read_csv(filter_ecg_path, encoding="utf-8").to_numpy().reshape(-1) + self.groupBox_func_select.setVisible(False) + self.groupBox_detect_Rpeaks.setVisible(True) + self.groupBox_info.setVisible(True) + self.groupBox_plot.setVisible(True) + self.radioButton_detector_method_pt.setChecked(True) + + def slot_btn_detect_Rpeaks_view(self): + if self.lineEdit_detect_Rpeaks_sampling_rate.text() != "" and self.lineEdit_detect_Rpeaks_peaks_value.text() != "" and self.lineEdit_detect_Rpeaks_bandpass_low.text() != "" and self.lineEdit_detect_Rpeaks_bandpass_high.text() != "": + if self.lineEdit_detect_Rpeaks_save_path.text() != "": + self.ax0.remove() + self.ax0 = self.figure.add_subplot(self.gs[0]) + self.ax0 = plt.gca() + self.ax0.grid(True) + self.ax0.tick_params(axis='x', colors='white') + self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + self.ax1.remove() + self.ax1 = self.figure.add_subplot(self.gs[1], sharex=self.ax0) + self.ax1 = plt.gca() + self.ax1.grid(True) + self.ax1.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + ecg_data = self.data1 + if self.radioButton_detector_method_pt.isChecked(): + detector_method = 'pt' + elif self.radioButton_detector_method_ta.isChecked(): + detector_method = 'ta' + elif self.radioButton_detector_method_Wt.isChecked(): + detector_method = 'Wt' + elif self.radioButton_detector_method_Hamilton.isChecked(): + detector_method = 'Hamilton' + elif self.radioButton_detector_method_Engzee.isChecked(): + detector_method = 'Engzee' + self.ecg_seq, self.R_peak_seq, self.Interval_seq, self.RRIV_seq = detect_Rpeak2.Rpeak_Detection(ecg_data, int(self.lineEdit_detect_Rpeaks_sampling_rate.text()), int(self.lineEdit_detect_Rpeaks_bandpass_low.text()), int(self.lineEdit_detect_Rpeaks_bandpass_high.text()), int(self.lineEdit_detect_Rpeaks_peaks_value.text()), detector_method) + if len(self.ecg_seq) != len(self.R_peak_seq) != len(self.Interval_seq) != len(self.RRIV_seq): + error("len(self.ecg_seq) and len(self.R_peak_seq) and len(self.Interval_seq) and len(self.RRIV_seq) are not equal.") + self.textBrowser_update("错误:ecg_seq和R_peak_seq和Interval_seq和RRIV_seq的长度不相等") + return + + info(f"Data Length:{len(ecg_data)}") + self.textBrowser_update(f"数据长度:{len(ecg_data)}") + info(f"Data Duration:{len(ecg_data) / int(self.lineEdit_detect_Rpeaks_sampling_rate.text()) / 60}分钟") + self.textBrowser_update(f"数据时长:{len(ecg_data) / int(self.lineEdit_detect_Rpeaks_sampling_rate.text()) / 60}分钟") + info(f"Data Parts:{len(self.ecg_seq)}小时") + self.textBrowser_update(f"数据总时长:{len(self.ecg_seq)}小时") + + self.tableWidget_detect_Rpeaks_signal_parts_list.setRowCount(len(self.ecg_seq)) + for row in range(len(self.ecg_seq)): + item = QTableWidgetItem(str(row + 1)) + self.tableWidget_detect_Rpeaks_signal_parts_list.setItem(row, 0, item) + + self.groupBox_detect_Rpeaks_signal_parts_list.setEnabled(True) + + self.ax0.plot(self.R_peak_seq[0][2: ], self.RRIV_seq[0], 'r.', label="RRIV") + self.ax0.legend(loc='upper right') + self.ax1.plot(self.ecg_seq[0], 'r', label="ECG") + self.ax1.plot(self.R_peak_seq[0], self.ecg_seq[0][self.R_peak_seq[0]], 'b*', label="R_peaks") + self.ax1.plot(self.Interval_seq[0], 'g', label="Interval") + self.ax1.legend(loc='upper right') + self.canvas.draw() + self.temp = 0 + info("Finished R peaks Detect and Data Part 1 Plot.") + self.textBrowser_update("提示:完成R峰提取并绘制信号第1段") + else: + error(f"Miss Args for detect_Rpeaks.") + self.textBrowser_update("错误:参数输入存在空值") + self.msgBox.setText("参数输入存在空值") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + + def slot_btn_detect_Rpeaks_save(self): + if self.ecg_seq is not None and self.R_peak_seq is not None: + if self.lineEdit_detect_Rpeaks_save_path.text() != "": + if Path(self.lineEdit_detect_Rpeaks_save_path.text()).is_dir() == False: + Path(self.lineEdit_detect_Rpeaks_save_path.text()).mkdir(parents=True, exist_ok=True) + info("Save Path is not Exist, Made it as a New Directory.") + self.textBrowser_update("提示:检测到保存路径所指向的文件夹不存在,已创建相应文件夹") + reply = QMessageBox.question(self, "警告:确认操作", f"你确定要将裁剪结果保存到{self.lineEdit_resample1000Hz_save_path.text()}?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + for idx in range(len(self.ecg_seq)): + DataFrame(self.ecg_seq[idx].reshape(-1)).to_csv(str(Path(self.lineEdit_detect_Rpeaks_save_path.text()) / f"{idx + 1}ecg.txt"), index=False, header=None) + DataFrame(self.R_peak_seq[idx].reshape(-1)).to_csv(str(Path(self.lineEdit_detect_Rpeaks_save_path.text()) / f"{idx + 1}Rpeak.txt"), index=False, header=None) + info(f"Saved Data {idx + 1} to Directory {self.lineEdit_detect_Rpeaks_save_path.text()}.") + self.textBrowser_update(f"提示:保存片段{idx + 1}成功至文件夹{self.lineEdit_detect_Rpeaks_save_path.text()}") + self.msgBox.setText(f"保存成功至{self.lineEdit_detect_Rpeaks_save_path.text()}") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + self.textBrowser_update("错误:保存路径输入有误,请检查后重新执行保存") + self.msgBox.setText("保存路径输入有误,请检查后重新执行保存") + self.msgBox.setIcon(QMessageBox.Information) + self.msgBox.exec() + else: + error(f"data is None.") + self.textBrowser_update("错误:需要保存的数据不存在") + self.msgBox.setText("需要保存的数据不存在") + self.msgBox.setIcon(QMessageBox.Critical) + self.msgBox.exec() + + def slot_tableWidget_detect_Rpeaks_signal_parts_list_on_cell_double_clicked(self, row, column): + self.temp = int(self.tableWidget_detect_Rpeaks_signal_parts_list.item(row, column).text()) - 1 + self.detect_Rpeaks_update_plot(self.temp) + info(f"Finished Data Part {self.temp + 1} Plot.") + self.textBrowser_update(f"提示:完成绘制信号第{self.temp + 1}段") + + def slot_btn_detect_Rpeaks_left(self): + if self.temp <= 0: + self.msgBox.setText("你正在查看第1段信号") + self.msgBox.setIcon(QMessageBox.Warning) + self.msgBox.exec() + else: + self.temp -= 1 + self.detect_Rpeaks_update_plot(self.temp) + info(f"Finished Data Part {self.temp + 1} Plot.") + self.textBrowser_update(f"提示:完成绘制信号第{self.temp + 1}段") + + def slot_btn_detect_Rpeaks_right(self): + if self.temp >= int(self.tableWidget_detect_Rpeaks_signal_parts_list.item(self.tableWidget_detect_Rpeaks_signal_parts_list.rowCount() - 1, 0).text()) - 1: + self.msgBox.setText("你正在查看最后1段信号") + self.msgBox.setIcon(QMessageBox.Warning) + self.msgBox.exec() + else: + self.temp += 1 + self.detect_Rpeaks_update_plot(self.temp) + info(f"Finished Data Part {self.temp + 1} Plot.") + self.textBrowser_update(f"提示:完成绘制信号第{self.temp + 1}段") + + def detect_Rpeaks_update_plot(self, part_index): + self.ax0.remove() + self.ax0 = self.figure.add_subplot(self.gs[0]) + self.ax0 = plt.gca() + self.ax0.grid(True) + self.ax0.tick_params(axis='x', colors='white') + self.ax0.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + self.ax1.remove() + self.ax1 = self.figure.add_subplot(self.gs[1], sharex=self.ax0) + self.ax1 = plt.gca() + self.ax1.grid(True) + self.ax1.xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:.0f}")) + self.ax0.plot(self.R_peak_seq[part_index][2:], self.RRIV_seq[part_index], 'r.', label="RRIV") + self.ax0.legend(loc='upper right') + self.ax1.plot(self.ecg_seq[part_index], 'r', label="ECG") + self.ax1.plot(self.R_peak_seq[part_index], self.ecg_seq[part_index][self.R_peak_seq[part_index]], 'b*', label="R_peaks") + self.ax1.plot(self.Interval_seq[part_index], 'g', label="Interval") + self.ax1.legend(loc='upper right') + self.canvas.draw() + + def slot_btn_detect_Jpeaks(self): + pass + + def slot_btn_backToMenu(self): + self.data1 = None + self.data2 = None + self.data3 = None + self.ecg_seq = None + self.R_peak_seq = None + self.Interval_seq = None + self.RRIV_seq = None + self.temp = None + self.tableWidget_detect_Rpeaks_signal_parts_list.clearContents() + self.groupBox_func_select.setVisible(True) + self.groupBox_resample1000Hz.setVisible(False) + self.groupBox_detect_Rpeaks.setVisible(False) + self.groupBox_detect_Jpeaks.setVisible(False) + self.groupBox_info.setVisible(False) + self.groupBox_plot.setVisible(False) + self.groupBox_detect_Rpeaks_signal_parts_list.setEnabled(False) + self.figure.clf() + + def textBrowser_update(self, message): + self.textBrowser_infoOutput.append(str(datetime.now().strftime("%H:%M:%S")) + ": " + message) + +# 主函数 +if __name__ == '__main__': + app = QApplication(sys.argv) + mainWindow = MainWindow() + mainWindow.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/resample_1000hz.py b/resample_1000hz.py new file mode 100644 index 0000000..bfb8029 --- /dev/null +++ b/resample_1000hz.py @@ -0,0 +1,19 @@ +""" + @Author: cys + @Email: 2022024904@m.scnu.edu.cn + @FileName: resample_1000hz.py + @Function: 将预处理后的数据(100Hz)重采样至1000Hz + @DateTime: 2023/11/27 9:59 + @SoftWare: PyCharm +""" + +from scipy.signal import resample + +def upsample(original_signal, original_sampling_rate, target_sampling_rate): + """""" + # 计算重采样后的数据点数量 + num_samples_target = int(len(original_signal) * (target_sampling_rate / original_sampling_rate)) + # 使用 scipy 的 resample 函数进行重采样 + resampled_signal = resample(original_signal, num_samples_target) + + return resampled_signal \ No newline at end of file