godamartonaron / GODA_pyPPG

pyPPG v01
HomePage
Other
28 stars 9 forks source link

ValueError: bands must be between 0 and 1 relative to Nyquist #27

Closed saurabh-kataria closed 1 day ago

saurabh-kataria commented 1 day ago

I have signal x of length 8 sec with fs=40 i.e. x.shape = (320,)


ValueError Traceback (most recent call last) Cell In[30], line 1 ----> 1 fiducials = fpex.get_fiducials(s=s)

File /scratch/skatar6/anaconda3/envs/tmp5/lib/python3.10/site-packages/pyPPG/fiducials.py:50, in FpCollection.get_fiducials(self, s) 48 ppg_fp=pd.DataFrame() 49 peaks, onsets = self.get_peak_onset(peak_detector) ---> 50 dicroticnotch = self.get_dicrotic_notch(peaks, onsets) 52 vpg_fp = self.get_vpg_fiducials(onsets) 53 apg_fp = self.get_apg_fiducials(onsets, peaks)

File /scratch/skatar6/anaconda3/envs/tmp5/lib/python3.10/site-packages/pyPPG/fiducials.py:815, in FpCollection.get_dicrotic_notch(self, peaks, onsets) 813 f = [0, (FcU / Fn), (FcD / Fn), 1] # Frequency band edges 814 a = [1, 1, 0, 0] # Amplitudes --> 815 b = firls(n, f, a) 817 lp_ppg = filtfilt(b, 1, dxx) # Low pass filtered signal with 20 cut off Frequency and 5 Hz Transition width 819 ## The weighting is calculated and applied to each beat individually

File /scratch/skatar6/anaconda3/envs/tmp5/lib/python3.10/site-packages/scipy/signal/_fir_filter_design.py:981, in firls(numtaps, bands, desired, weight, nyq, fs) 979 raise ValueError("bands must contain frequency pairs.") 980 if (bands < 0).any() or (bands > 1).any(): --> 981 raise ValueError("bands must be between 0 and 1 relative to Nyquist") 982 bands.shape = (-1, 2) 984 # check remaining params

ValueError: bands must be between 0 and 1 relative to Nyquist

saurabh-kataria commented 1 day ago

Does anyone have a simple script to get features of a short PPG signal?

saurabh-kataria commented 1 day ago

I think I was able to get features for my low resolution low length signal. If you have some feedback, let me know.

import numpy as np
import pandas as pd
from dotmap import DotMap
import pyPPG.preproc as PP
import pyPPG.fiducials as FP
import pyPPG.biomarkers as BM
from pyPPG import PPG, Fiducials, Biomarkers
from scipy.signal import resample

def extract_pyPPG_features(signal_array, fs=40):
    """
    Extracts pyPPG features from a 1D PPG signal by resampling it to 125 Hz.

    Parameters:
    - signal_array: numpy array, the raw PPG signal
    - fs: int, original sampling frequency (default is 40 Hz)

    Returns:
    - features: numpy array of shape (204,), the extracted features
    """
    # Ensure the signal is at least 15 seconds long
    min_length_seconds = 15
    required_length = int(min_length_seconds * fs)
    if len(signal_array) < required_length:
        # Tile the signal to reach the required length
        num_repeats = int(np.ceil(required_length / len(signal_array)))
        signal_array = np.tile(signal_array, num_repeats)
    # Trim the signal to the required length
    signal_array = signal_array[:required_length]

    # Resample the signal from original fs to 125 Hz
    fs_original = fs
    fs_new = 125  # Desired sampling frequency for pyPPG
    num_samples = int(len(signal_array) * fs_new / fs_original)
    signal_array_resampled = resample(signal_array, num_samples)
    fs = fs_new  # Update the sampling frequency

    # Create a DotMap object for the signal
    signal = DotMap()
    signal.v = signal_array_resampled
    signal.fs = fs
    signal.start_sig = 0
    signal.end_sig = len(signal_array_resampled)
    signal.filtering = True
    signal.fL = 0.5  # Lower cutoff frequency (Hz)
    signal.fH = 12   # Upper cutoff frequency (Hz)
    signal.order = 4
    signal.sm_wins = {'ppg': 50, 'vpg': 10, 'apg': 10, 'jpg': 10}

    # Initialize the correction for fiducial points
    corr_on = ['on', 'dn', 'dp', 'v', 'w', 'f']
    correction = pd.DataFrame({key: [True] for key in corr_on})
    signal.correction = correction

    # Preprocess the signal
    prep = PP.Preprocess(
        fL=signal.fL,
        fH=signal.fH,
        order=signal.order,
        sm_wins=signal.sm_wins
    )
    signal.ppg, signal.vpg, signal.apg, signal.jpg = prep.get_signals(s=signal)

    # Create a PPG object
    s = PPG(signal)

    # Use the default fiducial point collection
    fpex = FP.FpCollection(s=s)

    # Extract fiducial points
    fiducials = fpex.get_fiducials(s=s)

    # Create a Fiducials object
    fp = Fiducials(fp=fiducials)

    # Calculate Biomarkers
    bmex = BM.BmCollection(s=s, fp=fp)
    bm_defs, bm_vals, bm_stats = bmex.get_biomarkers()
    bm = Biomarkers(bm_defs=bm_defs, bm_vals=bm_vals, bm_stats=bm_stats)

    # Extract the features
    # Combine means and stds of ppg_sig, sig_ratios, ppg_derivs, derivs_ratios
    categories = ['ppg_sig', 'sig_ratios', 'ppg_derivs', 'derivs_ratios']
    stats = ['mean', 'std']
    features = []
    for stat in stats:
        for category in categories:
            values = bm_stats[category].loc[stat].values
            features.extend(values)

    features = np.array(features)
    return features