def gqrs_detect(sig: np.ndarray, fs: Real, **kwargs) -> np.ndarray: """ default kwargs: d_sig=None, adc_gain=None, adc_zero=None, threshold=1.0, hr=75, RRdelta=0.2, RRmin=0.28, RRmax=2.4, QS=0.07, QT=0.35, RTmin=0.25, RTmax=0.33, QRSa=750, QRSamin=130 """ kw = dict(d_sig=None, adc_gain=None, adc_zero=None, threshold=1.0, hr=75, RRdelta=0.2, RRmin=0.28, RRmax=2.4, QS=0.07, QT=0.35, RTmin=0.25, RTmax=0.33, QRSa=750, QRSamin=130) kw = {k: kwargs.get(k, v) for k, v in kw.items()} rpeaks = _gqrs_detect(sig, fs, **kw) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def ssf_detect(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ Slope Sum Function (SSF) might be too simple References: ----------- [1] Zong, W., et al. "An open-source algorithm to detect onset of arterial blood pressure pulses." Computers in Cardiology, 2003. IEEE, 2003. """ rpeaks, = BSE.ssf_segmenter( signal=sig, sampling_rate=fs, threshold=kwargs.get("threshold", 20), before=kwargs.get("before", 0.03), after=kwargs.get("after", 0.01), ) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def get_rpeaks(ecg): """ function: get_rpeaks returns an array of indices of the r peaks in a given ecg Args: ecg : np.ndarray an ecg Returns: rpeaks : np.ndarray an array of indices of the r peaks """ try: ecg = ecg[:, 0] except: pass filtered, _, _ = biosppy_tools.filter_signal(signal=ecg, ftype='FIR', band='bandpass', order=150, frequency=[3, 45], sampling_rate=500) rpeaks, = biosppy_ecg.hamilton_segmenter(signal=filtered, sampling_rate=500) # correct R-peak locations rpeaks, = biosppy_ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=500, tol=0.05) return np.array(rpeaks)
def feature_extraction(recording, signal, labels): data = [] for i in tqdm(range(len(labels)), desc=recording, file=sys.stdout): segment = signal[i * fs * 60:(i + 1) * fs * 60] segment, _, _ = st.filter_signal(segment, ftype='FIR', band='bandpass', order=int(0.3 * fs), frequency=[3, 45], sampling_rate=fs) # Finding R peaks rpeaks, = hamilton_segmenter(segment, sampling_rate=fs) rpeaks, = correct_rpeaks(segment, rpeaks, sampling_rate=fs, tol=0.1) # Extracting feature label = 0 if labels[i] == "N" else 1 if 40 <= len(rpeaks) <= 200: # Remove abnormal R peaks rri_tm, rri = rpeaks[1:] / float(fs), np.diff(rpeaks, axis=-1) / float(fs) rri = medfilt(rri, kernel_size=3) edr_tm, edr = rpeaks / float(fs), segment[rpeaks] # Remove physiologically impossible HR signal if np.all(np.logical_and(60 / rri >= hr_min, 60 / rri <= hr_max)): rri_time_features, rri_frequency_features = time_domain( rri * 1000), frequency_domain(rri, rri_tm) edr_frequency_features = frequency_domain(edr, edr_tm) # 6 + 6 + 6 + 1 = 19 data.append([ rri_time_features["rmssd"], rri_time_features["sdnn"], rri_time_features["nn50"], rri_time_features["pnn50"], rri_time_features["mrri"], rri_time_features["mhr"], rri_frequency_features["vlf"] / rri_frequency_features["total_power"], rri_frequency_features["lf"] / rri_frequency_features["total_power"], rri_frequency_features["hf"] / rri_frequency_features["total_power"], rri_frequency_features["lf_hf"], rri_frequency_features["lfnu"], rri_frequency_features["hfnu"], edr_frequency_features["vlf"] / edr_frequency_features["total_power"], edr_frequency_features["lf"] / edr_frequency_features["total_power"], edr_frequency_features["hf"] / edr_frequency_features["total_power"], edr_frequency_features["lf_hf"], edr_frequency_features["lfnu"], edr_frequency_features["hfnu"], label ]) else: data.append([np.nan] * 18 + [label]) else: data.append([np.nan] * 18 + [label]) data = np.array(data, dtype="float") return data
def __init__(self, sig, fs=250.0): assert len(sig.shape) == 1, 'The signal must be 1-dimension.' assert sig.shape[0] >= fs * 6, 'The signal must >= 6 seconds.' self.sig = utils.WTfilt_1d(sig) self.fs = fs self.rpeaks, = ecg.hamilton_segmenter(signal=self.sig, sampling_rate=self.fs) self.rpeaks, = ecg.correct_rpeaks(signal=self.sig, rpeaks=self.rpeaks, sampling_rate=self.fs) self.RR_intervals = np.diff(self.rpeaks) self.dRR = np.diff(self.RR_intervals)
def christov_detect(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ References: ----------- [1] Ivaylo I. Christov, "Real time electrocardiogram QRS detection using combined adaptive threshold", BioMedical Engineering OnLine 2004, vol. 3:28, 2004 """ rpeaks, = BSE.christov_segmenter(signal=sig, sampling_rate=fs) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def pantompkins(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ to keep in accordance of parameters with `xqrs` and `gqrs` References: ----------- [1] Pan, Jiapu, and Willis J. Tompkins. "A real-time QRS detection algorithm." IEEE transactions on biomedical engineering 3 (1985): 230-236. """ rpeaks = _pantompkins(sig, fs) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def xqrs_detect(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ default kwargs: sampfrom=0, sampto='end', conf=None, learn=True, verbose=True """ kw = dict(sampfrom=0, sampto='end', conf=None, learn=True, verbose=False) kw = {k: kwargs.get(k,v) for k,v in kw.items()} rpeaks = _xqrs_detect(sig, fs, **kw) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def filter_signal(signal, sampling_rate): order = int(0.3 * sampling_rate) filtered, _, _, = ecg.st.filter_signal(signal=signal, ftype='FIR', band='bandpass', order=order, frequency=[3, 45], sampling_rate=sampling_rate) rpeaks, = ecg.hamilton_segmenter(signal=filtered, sampling_rate=sampling_rate) rpeaks_corrected, = ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=sampling_rate, tol=0.05) rpeaks = peaks_to_series(signal, rpeaks) rpeaks_corrected = peaks_to_series(signal, rpeaks_corrected) return filtered, rpeaks_corrected
def hamilton_detect(sig: np.ndarray, fs: Real, **kwargs) -> np.ndarray: """ References ---------- [1] Hamilton, Pat. "Open source ECG analysis." Computers in cardiology. IEEE, 2002. """ # segment rpeaks, = BSE.hamilton_segmenter(signal=sig, sampling_rate=fs) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def worker(name, labels): print("processing %s!" % name) X = [] y = [] groups = [] signals = wfdb.rdrecord(os.path.join(base_dir, name), channels=[0]).p_signal[:, 0] # Read recording for j in range(len(labels)): if j < before or \ (j + 1 + after) > len(signals) / float(sample): continue signal = signals[int((j - before) * sample):int((j + 1 + after) * sample)] signal, _, _ = st.filter_signal( signal, ftype='FIR', band='bandpass', order=int(0.3 * fs), frequency=[3, 45], sampling_rate=fs) # Filtering the ecg signal to remove noise # Find R peaks rpeaks, = hamilton_segmenter(signal, sampling_rate=fs) # Extract R-peaks rpeaks, = correct_rpeaks(signal, rpeaks=rpeaks, sampling_rate=fs, tol=0.1) if len(rpeaks) / (1 + after + before) < 40 or \ len(rpeaks) / (1 + after + before) > 200: # Remove abnormal R peaks signal continue # Extract RRI, Ampl signal rri_tm, rri_signal = rpeaks[1:] / float(fs), np.diff(rpeaks) / float( fs) rri_signal = medfilt(rri_signal, kernel_size=3) ampl_tm, ampl_siganl = rpeaks / float(fs), signal[rpeaks] hr = 60 / rri_signal # Remove physiologically impossible HR signal if np.all(np.logical_and(hr >= hr_min, hr <= hr_max)): # Save extracted signal X.append([(rri_tm, rri_signal), (ampl_tm, ampl_siganl)]) y.append(0. if labels[j] == 'N' else 1.) groups.append(name) print("over %s!" % name) return X, y, groups
def gamboa_detect(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ References: ----------- [1] Gamboa, Hugo. "Multi-modal behavioral biometrics based on HCI and electrophysiology." PhD ThesisUniversidade (2008). """ rpeaks, = BSE.gamboa_segmenter( signal=sig, sampling_rate=fs, tol=kwargs.get("tol", 0.48), ) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def engzee_detect(sig:np.ndarray, fs:Real, **kwargs) -> np.ndarray: """ References: ----------- [1] W. Engelse and C. Zeelenberg, "A single scan algorithm for QRS detection and feature extraction", IEEE Comp. in Cardiology, vol. 6, pp. 37-42, 1979 [2] A. Lourenco, H. Silva, P. Leite, R. Lourenco and A. Fred, "Real Time Electrocardiogram Segmentation for Finger Based ECG Biometrics", BIOSIGNALS 2012, pp. 49-54, 2012 """ rpeaks, = BSE.engzee_segmenter( signal=sig, sampling_rate=fs, threshold=kwargs.get("threshold", 0.48), ) # correct R-peak locations rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=kwargs.get("correct_tol", 0.05), ) return rpeaks
def feature_extraction(x): X = [] [ts, filtered_sig, rpeaks, temp_ts, temp, hr_ts, heart_rate] = ecg.ecg(signal=x, sampling_rate=300, show=False) rpeaks = ecg.correct_rpeaks(signal=x, rpeaks=rpeaks, sampling_rate=300, tol=0.1) peaks = x[rpeaks] if len(heart_rate) < 2: heart_rate = [0, 1] if len(hr_ts) < 2: hr_ts = [0, 1] X.append(np.median(peaks)) X.append(np.min(peaks)) X.append(np.max(peaks)) X.append(np.median(np.diff(rpeaks))) X.append(np.min(np.diff(rpeaks))) X.append(np.max(np.diff(rpeaks))) X.append(np.std(np.diff(rpeaks))) X.append(np.median(heart_rate)) X.append(np.min(heart_rate)) X.append(np.max(heart_rate)) X.append(np.median(np.diff(heart_rate))) X.append(np.min(np.diff(heart_rate))) X.append(np.max(np.diff(heart_rate))) X.append(np.std(np.diff(heart_rate))) X.append(np.min(np.diff(hr_ts))) X.append(np.max(np.diff(hr_ts))) X.append(np.std(np.diff(hr_ts))) # X += list(np.median(temp, axis=0)) # X += list(np.min(temp, axis=0)) # X += list(np.max(temp, axis=0)) return np.array(X)
def offline_rr_interval_orignal(signal, fs=200): # 逐波心率(bmp),原始 ecg_signal = np.array(signal) sampling_rate = fs sampling_rate = float(sampling_rate) # filter signal order = int(0.3 * sampling_rate) filtered, _, _ = st.filter_signal(signal=ecg_signal, ftype='FIR', band='bandpass', order=order, frequency=[5, 30], sampling_rate=sampling_rate) rpeaks, = eecg.hamilton_segmenter(signal=filtered, sampling_rate=sampling_rate) # correct R-peak locations rpeaks, = eecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=sampling_rate, tol=0.05) rpeaks_orignal = np.unique(rpeaks) rr_interval_orignal = np.diff(rpeaks_orignal) rpeaks_whithoutfirst = rpeaks_orignal[1:] rpeaks_whithoutfirst = rpeaks_orignal outlier = [] for jk in range(len(rr_interval_orignal)): if rr_interval_orignal[jk] >= sampling_rate * 2 or rr_interval_orignal[jk] <= sampling_rate * 0.4: if filtered[rr_interval_orignal[jk]] >= filtered[rr_interval_orignal[jk-1]]: outlier.append(jk-1) else: outlier.append(jk) outlier.reverse() rr_interval_outlier = np.asarray(rr_interval_orignal) for jk1 in outlier: rr_interval_outlier = np.delete(rr_interval_outlier, jk1) rpeaks_whithoutfirst = np.delete(rpeaks_whithoutfirst, jk1) return rpeaks_whithoutfirst.tolist(), filtered
from frecg.tools import * from frecg.ecg import * from biosppy.signals.ecg import hamilton_segmenter, correct_rpeaks import os for root, dirs, files in os.walk("data_pasien/splitted/"): for file in files: dataset = splitted_file_load("data_pasien/splitted/%s" % file) summary = [] for ecg_raw in dataset: filtered = filter_ecg(signal=ecg_raw, sampling_rate=250) # r_peaks = getr_peaks(filtered) # segment rpeaks, = hamilton_segmenter(signal=filtered, sampling_rate=250) r_peaks, = correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=250, tol=0.05) if (len(r_peaks) > 5): feature, predicted_beats, rr, hr, start_stop = classify_peaks( r_peaks) summary.append(create_summary_minute(predicted_beats, rr, hr)) else: summary.append([0, 0, 0, 0, 0, 0, 0, 0]) print(file, "done") save_summary_tocsv(summary, 'result/' + file)
def rinfo_correct(self, rpeaks=None, tolerance=0.05): """Get corrected ECG R peaks. """ return ecg.correct_rpeaks(self.__ecg, rpeaks, self.__signal_rate, tolerance)
def run_algo(algorithm: str, sig: numpy.ndarray, freq_sampling: int) -> List[int]: """ run a qrs detector on a signal :param algorithm: name of the qrs detector to use :type algorithm: str :param sig: values of the sampled signal to study :type sig: ndarray :param freq_sampling: value of sampling frequency of the signal :type freq_sampling: int :return: localisations of qrs detections :rtype: list(int) """ detectors = Detectors(freq_sampling) if algorithm == 'Pan-Tompkins-ecg-detector': qrs_detections = detectors.pan_tompkins_detector(sig) elif algorithm == 'Hamilton-ecg-detector': qrs_detections = detectors.hamilton_detector(sig) elif algorithm == 'Christov-ecg-detector': qrs_detections = detectors.christov_detector(sig) elif algorithm == 'Engelse-Zeelenberg-ecg-detector': qrs_detections = detectors.engzee_detector(sig) elif algorithm == 'SWT-ecg-detector': qrs_detections = detectors.swt_detector(sig) elif algorithm == 'Matched-filter-ecg-detector' and freq_sampling == 360: qrs_detections = detectors.matched_filter_detector( sig, 'templates/template_360hz.csv') elif algorithm == 'Matched-filter-ecg-detector' and freq_sampling == 250: qrs_detections = detectors.matched_filter_detector( sig, 'templates/template_250hz.csv') elif algorithm == 'Two-average-ecg-detector': qrs_detections = detectors.two_average_detector(sig) elif algorithm == 'Hamilton-biosppy': qrs_detections = bsp_ecg.ecg(signal=sig, sampling_rate=freq_sampling, show=False)[2] elif algorithm == 'Christov-biosppy': order = int(0.3 * freq_sampling) filtered, _, _ = bsp_tools.filter_signal(signal=sig, ftype='FIR', band='bandpass', order=order, frequency=[3, 45], sampling_rate=freq_sampling) rpeaks, = bsp_ecg.christov_segmenter(signal=filtered, sampling_rate=freq_sampling) rpeaks, = bsp_ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, tol=0.05) _, qrs_detections = bsp_ecg.extract_heartbeats( signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, before=0.2, after=0.4) elif algorithm == 'Engelse-Zeelenberg-biosppy': order = int(0.3 * freq_sampling) filtered, _, _ = bsp_tools.filter_signal(signal=sig, ftype='FIR', band='bandpass', order=order, frequency=[3, 45], sampling_rate=freq_sampling) rpeaks, = bsp_ecg.engzee_segmenter(signal=filtered, sampling_rate=freq_sampling) rpeaks, = bsp_ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, tol=0.05) _, qrs_detections = bsp_ecg.extract_heartbeats( signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, before=0.2, after=0.4) elif algorithm == 'Gamboa-biosppy': order = int(0.3 * freq_sampling) filtered, _, _ = bsp_tools.filter_signal(signal=sig, ftype='FIR', band='bandpass', order=order, frequency=[3, 45], sampling_rate=freq_sampling) rpeaks, = bsp_ecg.gamboa_segmenter(signal=filtered, sampling_rate=freq_sampling) rpeaks, = bsp_ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, tol=0.05) _, qrs_detections = bsp_ecg.extract_heartbeats( signal=filtered, rpeaks=rpeaks, sampling_rate=freq_sampling, before=0.2, after=0.4) elif algorithm == 'mne-ecg': qrs_detections = mne_ecg.qrs_detector(freq_sampling, sig) elif algorithm == 'heartpy': rol_mean = rolling_mean(sig, windowsize=0.75, sample_rate=100.0) qrs_detections = hp_pkdetection.detect_peaks( sig, rol_mean, ma_perc=20, sample_rate=100.0)['peaklist'] elif algorithm == 'gqrs-wfdb': qrs_detections = processing.qrs.gqrs_detect(sig=sig, fs=freq_sampling) elif algorithm == 'xqrs-wfdb': qrs_detections = processing.xqrs_detect(sig=sig, fs=freq_sampling) else: raise ValueError( f'Sorry... unknown algorithm. Please check the list {algorithms_list}' ) cast_qrs_detections = [int(element) for element in qrs_detections] return cast_qrs_detections
def seq_lab_net_detect(sig: np.ndarray, fs: Real, correction: bool = False, **kwargs) -> np.ndarray: """ finished, checked, use model of entry 0416 of CPSC2019, to detect R peaks in single-lead ECGs of arbitrary length NOTE: `sig` should have units in mV, NOT in μV! Parameters: ----------- sig: ndarray, the (raw) ECG signal of arbitrary length, with units in mV fs: real number, sampling frequency of `sig` correction: bool, default False, if True, correct rpeaks to local maximum in a small nbh of rpeaks detected by DL model using `BSE.correct_rpeaks` kwargs: dict, optional key word arguments, including - verbose, int, default 0, print verbosity - batch_size, int, default None, batch size for feeding into the model NOTE: ----- `rpeaks` might not always be the local maxima, e.g. in aVR lead, hence after `correction` using `BSE.correct_rpeaks`, the "corrected" position might NOT be correct Returns: -------- rpeaks: ndarray, indices of rpeaks in `sig` References: ----------- [1] Cai, Wenjie, and Danqin Hu. "QRS complex detection using novel deep learning neural networks." IEEE Access (2020). """ verbose = kwargs.get("verbose", 0) batch_size = kwargs.get("batch_size", None) model_fs = 500 model_granularity = 8 # 1/8 times of model_fs # pre-process sig_rsmp = _seq_lab_net_pre_process(sig, verbose=verbose) if fs != model_fs: sig_rsmp = resample_poly(sig_rsmp, up=model_fs, down=int(fs)) max_single_batch_half_len = 10 * 60 * model_fs if len(sig_rsmp) > 2 * max_single_batch_half_len: if batch_size is None: batch_size = 64 if verbose >= 1: print( f"the signal is too long, hence split into segments for parallel computing of batch size {batch_size}" ) if batch_size is not None: model_input_len = 5000 half_overlap_len = 256 # approximately 0.5s, should be divisible by `model_granularity` half_overlap_len_prob = half_overlap_len // model_granularity overlap_len = 2 * half_overlap_len forward_len = model_input_len - overlap_len n_segs, residue = divmod(len(sig_rsmp) - overlap_len, forward_len) if residue != 0: sig_rsmp = np.append(sig_rsmp, np.zeros((forward_len - residue, ))) n_segs += 1 n_batches = math.ceil(n_segs / batch_size) if verbose >= 2: print(f"number of batches = {n_batches}") prob = [] segs = list(range(n_segs)) for b_idx in range(n_batches): # b_start = b_idx * batch_size * forward_len b_start = b_idx * batch_size b_segs = segs[b_start:b_start + batch_size] b_input = np.vstack([ sig_rsmp[idx * forward_len:idx * forward_len + model_input_len] for idx in b_segs ]).reshape((-1, model_input_len, 1)) prob_cnn = CNN_MODEL.predict(b_input) prob_crnn = CRNN_MODEL.predict(b_input) b_prob = (prob_cnn[..., 0] + prob_crnn[..., 0]) / 2 b_prob = b_prob[..., half_overlap_len_prob:-half_overlap_len_prob] prob += b_prob.flatten().tolist() if b_idx == 0: head_prob = (b_prob[0, :half_overlap_len_prob]).tolist() if b_idx == n_batches - 1: tail_prob = (b_prob[-1, -half_overlap_len_prob:]).tolist() if verbose >= 1: print(f"{b_idx+1}/{n_batches} batches", end="\r") # prob, output from the for loop, # is the array of probabilities for sig_rsmp[half_overlap_len: -half_overlap_len] prob = list(repeat(0, half_overlap_len_prob)) + prob + list( repeat(0, half_overlap_len_prob)) # prob = head_prob + prob + tail_prob # NOTE: head and tail might not be trustable prob = np.array(prob) else: prob_cnn = CNN_MODEL.predict(sig_rsmp.reshape((1, len(sig_rsmp), 1))) prob_crnn = CRNN_MODEL.predict(sig_rsmp.reshape((1, len(sig_rsmp), 1))) prob = ((prob_cnn + prob_crnn) / 2).squeeze() # prob --> qrs mask --> qrs intervals --> rpeaks rpeaks = _seq_lab_net_post_process(prob, 0.5, verbose=verbose) # convert from resampled positions to original positions rpeaks = (np.round((fs / model_fs) * rpeaks)).astype(int) rpeaks = rpeaks[np.where(rpeaks < len(sig))[0]] # adjust to the "true" rpeaks, # i.e. the max in a small nbh of each element in `rpeaks` if correction: rpeaks, = BSE.correct_rpeaks( signal=sig, rpeaks=rpeaks, sampling_rate=fs, tol=0.05, ) return rpeaks
def inference( self, input: Union[np.ndarray, Tensor], bin_pred_thr: float = 0.5, duration_thr: int = 4 * 16, dist_thr: Union[int, Sequence[int]] = 200, correction: bool = False) -> Tuple[np.ndarray, List[np.ndarray]]: """ finished, NOT checked, auxiliary function to `forward`, for CPSC2019, NOTE: each segment of input be better filtered using `_remove_spikes_naive`, and normalized to a suitable mean and std Parameters ---------- input: ndarray or Tensor, input tensor, of shape (batch_size, channels, seq_len) bin_pred_thr: float, default 0.5, the threshold for making binary predictions from scalar predictions duration_thr: int, default 4*16, minimum duration for a "true" qrs complex, units in ms dist_thr: int or sequence of int, default 200, if is sequence of int, (0-th element). minimum distance for two consecutive qrs complexes, units in ms; (1st element).(optional) maximum distance for checking missing qrs complexes, units in ms, e.g. [200, 1200] if is int, then is the case of (0-th element). correction: bool, default False, if True, correct rpeaks to local maximum in a small nbh of rpeaks detected by DL model using `BSE.correct_rpeaks` Returns ------- pred: ndarray, the array of scalar predictions rpeaks: list of ndarray, list of rpeak indices for each batch element """ if torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") self.to(device) batch_size, channels, seq_len = input.shape if isinstance(input, np.ndarray): _input = torch.from_numpy(input).to(device) else: _input = input.to(device) pred = self.forward(_input) pred = self.sigmoid(pred) pred = pred.cpu().detach().numpy().squeeze(-1) # prob --> qrs mask --> qrs intervals --> rpeaks rpeaks = self._inference_post_process(pred=pred, bin_pred_thr=bin_pred_thr, duration_thr=duration_thr, dist_thr=dist_thr) if correction: rpeaks = [ BSE.correct_rpeaks( signal=b_input, rpeaks=b_rpeaks, sampling_rate=self.config.fs, tol=0.05, )[0] for b_input, b_rpeaks in zip( _input.detach().numpy().squeeze(1), rpeaks) ] return pred, rpeaks
# low pass filter filtered = butter_lowpass_filter(signal, cutoff, sampling_rate, order) # detrend with a low-pass order = 20 filtered -= filtfilt( [1] * order, [order], filtered) # this is a very simple moving average filter # Rpeaks Rpeaks = engzee_segmenter(signal=filtered, sampling_rate=sampling_rate, threshold=0)['rpeaks'] Rpeaks = correct_rpeaks(signal=filtered, rpeaks=Rpeaks, sampling_rate=sampling_rate, tol=rpeak_tol)['rpeaks'] # peak to peak intervals IBI = [] for i in range(1, len(Rpeaks)): IBI.append(Rpeaks[i] - Rpeaks[i - 1]) IBI = np.array(IBI) / sampling_rate IBI = correct(IBI, 3) # feautres for IBI target.append(RMS(IBI)) target.append(IBI.mean()) target.append(IBI.std()) target.append(skew(IBI)) target.append(kurtosis(IBI))
def feature_extraction(sample, sampling_rate=300): # Normalize raw signal signal = ecg.st.normalize(sample)['signal'] # ensure numpy signal = np.array(signal) sampling_rate = float(sampling_rate) # filter signal order = int(0.3 * sampling_rate) filtered, _, _ = ecg.st.filter_signal(signal=signal, ftype='FIR', band='bandpass', order=order, frequency=[5, 15], sampling_rate=sampling_rate) # segment rpeaks, = ecg.hamilton_segmenter(signal=filtered, sampling_rate=sampling_rate) # correct R-peak locations rpeaks, = ecg.correct_rpeaks(signal=filtered, rpeaks=rpeaks, sampling_rate=sampling_rate, tol=0.05) templates, rpeaks = ecg.extract_heartbeats(signal=filtered, rpeaks=rpeaks, sampling_rate=sampling_rate, before=0.2, after=0.4) # get time vectors length = len(signal) T = (length - 1) / sampling_rate ts = np.linspace(0, T, length, endpoint=False) # Extract time domain measures rpeaks_time = ts[rpeaks] rr_intervals = extract_rr_intervals(rpeaks_time) rr_diffs = extract_rr_diffs(rr_intervals) bpm = get_bpm(rr_intervals) ibi = np.mean(rr_intervals) sdnn = np.std(rr_intervals) sdsd = np.std(rr_diffs) rmssd = np.sqrt(np.mean(rr_diffs**2)) nn20 = np.sum([rr_diffs > 0.02]) / len(rr_diffs) nn50 = np.sum([rr_diffs > 0.05]) / len(rr_diffs) # QRS qpeaks = find_q_point(filtered, rpeaks) speaks = find_s_point(filtered, rpeaks) # Extract wavelet power power_spectrum = ecg.st.power_spectrum(filtered, 300) frequency = [power_spectrum['freqs'][0], power_spectrum['freqs'][-1]] wavelet_energie = ecg.st.band_power(power_spectrum['freqs'], power_spectrum['power'], frequency)['avg_power'] # Amplitudes qamplitudes = filtered[qpeaks] ramplitudes = filtered[rpeaks] samplitudes = filtered[speaks] # QRS duration qrs_duration = ts[speaks] - ts[qpeaks] qrs_duration_diffs = extract_rr_diffs(qrs_duration) iqrs = np.mean(qrs_duration) sdqrs = np.std(qrs_duration) sdqrsdiffs = np.std(qrs_duration_diffs) rmssqrsdiffs = np.sqrt(np.mean(qrs_duration_diffs**2)) extracted_features = [ bpm, ibi, sdnn, sdsd, rmssd, nn20, nn50, wavelet_energie, iqrs, sdqrs, sdqrsdiffs, rmssqrsdiffs, np.median(qamplitudes), np.min(qamplitudes), np.max(qamplitudes), np.median(ramplitudes), np.min(ramplitudes), np.max(ramplitudes), np.median(samplitudes), np.min(samplitudes), np.max(samplitudes) ] return extracted_features
import biosppy.signals.ecg as bse import numpy as np #%% Train data_train = pd.read_csv('Dropbox/MK/ETH MSc Statistics/Machine Learning/Advanced Machine Learning/exercises/project/task3/X_train.csv') del data_train['id'] rpeaks_init = [] for i in range(0,data_train.shape[0]): rpeaks_init.append(bse.engzee_segmenter(signal=data_train.iloc[i,:].values, sampling_rate=300)) rpeaks_corrected = [] for i in range(0,data_train.shape[0]): rpeaks_corrected.append(bse.correct_rpeaks(signal=data_train.iloc[i,:].values, rpeaks=rpeaks_init[i][0], sampling_rate=300, tol=0.05)) heartbeats = [] for i in range(0,data_train.shape[0]): heartbeats.append(bse.extract_heartbeats(signal=data_train.iloc[i,:].values, rpeaks=rpeaks_corrected[i][0], sampling_rate=300, before=0.2, after=0.4) ) R_peaks = [] for i in range(0,data_train.shape[0]): R = [] for j in range(0,len(heartbeats[i][1])): R.append(heartbeats[i][1][j]) R_peaks.append(np.array(R)) df = pd.DataFrame(R_peaks) df.to_csv('R_train.csv', index=True, header=False)
logging.info("使用compare_segmentation对比算法结果与人工标记 ...") tic = time.time() eval_results = ecg.compare_segmentation(ann, rpeaks, fs, tol=0.02) toc = time.time() logging.info("完成. 用时: %f 秒. 返回结果类型为 %s ." % (toc - tic, str(type(eval_results)))) dict_results = eval_results.as_dict() logging.info("********** 结果报告 *************") logging.info("* 准确率(acc): %.3f *" % dict_results["acc"]) logging.info("* 总体表现(performance): %.3f *" % dict_results["performance"]) logging.info("*********************************") correct_tol = 0.05 logging.info("使用correct_rpeaks校正R波位置, 最大校正范围 %.3f 秒 ..." % correct_tol) tic = time.time() rpeaks_correct = ecg.correct_rpeaks(signal, rpeaks, fs, tol=correct_tol) toc = time.time() logging.info("完成. 用时: %f 秒. 返回结果类型为 %s ." % (toc - tic, str(type(rpeaks_correct)))) rpeaks_correct = rpeaks_correct.as_dict()["rpeaks"] logging.info("绘制部分R峰校正前后的位置 ...") num_plot_samples = 3600 sig_plot = signal[:num_plot_samples] rpeaks_plot = rpeaks[rpeaks <= num_plot_samples] rpeaks_correct_plot = rpeaks_correct[rpeaks_correct <= num_plot_samples] plt.figure() plt.grid(True) plt.plot(sig_plot, label="ECG") plt.plot(rpeaks_plot, sig_plot[rpeaks_plot], "ro", label="rpeaks") plt.plot(rpeaks_correct_plot, sig_plot[rpeaks_correct_plot], "b*", label="corrected rpeaks")