SDV and PVV from cardioQ #466

PenTing Liao (2021-12-07 23:37) · 542 view(s)

Dear Vital Recorder developer,

Thank you very much for providing such a powerful tool. I recently successfully slave the signal from CardioQ by following your instruction. I'm hoping to apply a filter using the same algorithm for PPV to produce SDV (stroke distance variation) and PVV (peak velocity variation). I've attached the modified code and was wondering if you wouldn't mind take a look. Also I can't seem to locate where the filters folder is. I went in to the vital recorder folder in the program files folder but only found utilities folder and 3 app tab (vital, vitalSync, VitalUtils). I tried to create a folder named filters and put the text file "Stroke Distance Variation.js" in it and didn't work. It'd be greatly appreciated if you ocld assist me in this.

Thank you in advance,

PT

 

from .. import arr
import numpy as np
import time
import scipy.interpolate
import scipy.signal

last_sdv = 0
last_pvv = 0

cfg = {
    'name': ’Stroke Distance Variation',
    'group': ’SD’,
    'desc': 'Calculate pulse pressure variation using modified version of the method in the reference',
    'reference': 'Aboy et al, An Enhanced Automatic Algorithm for Estimation of Respiratory Variations in Arterial Pulse Pressure During Regions of Abrupt Hemodynamic Changes. IEEE TRANSACTIONS ON BIOMEDICAL ENGINEERING, VOL. 56, NO. 10, OCTOBER 2009',
    'overlap': 20,
    'interval': 30, # 30초는 되어야 rr을 추정 가능함
    'inputs': [{'name': ‘flow’, 'type': 'wav'}],
    'outputs': [
        {'name': ‘SDV', 'type': 'num', 'min': 0, 'max': 30, 'unit': '%'},
        {'name': ‘PVV', 'type': 'num', 'min': 0, 'max': 30, 'unit': '%'},
        {'name': ‘SD_RR', 'type': 'num', 'min': 0, 'max': 30, 'unit': '/min'}
        ]
}


def run(inp, opt, cfg):
    """
    calculate sdv from flow waveform
    :param flow: flow waveform
    :return: max, min, upper envelope, lower envelope, respiratory rate, sdv
    """
    global last_sdv, last_pfvv

    data = arr.interp_undefined(inp[‘flow’][‘vals'])
    srate = inp[‘flow’][‘srate']

    data = arr.resample_hz(data, srate, 100)
    srate = 100

    if len(data) < 30 * srate:
        print('hr < 30')
        return

    # beat detection
    minlist, maxlist = arr.detect_peaks(data, srate)
    maxlist = maxlist[1:]

    # beat lengths
    beatlens = []
    beats_128 = []
    beats_128_valid = []
    for i in range(0, len(minlist)-1):
        beatlen = minlist[i+1] - minlist[i]  # in samps
        if not 30 < beatlen < 300:
            beats_128.append(None)
            continue

        sd = data[maxlist[i]] - data[minlist[i]]  # stroke distance
        if not  1 < sd < 30:
            beats_128.append(None)
            continue

        beatlens.append(beatlen)
        beat = data[minlist[i]:minlist[i+1]]
        resampled = arr.resample(beat, 128)
        beats_128.append(resampled)
        beats_128_valid.append(resampled)

    if not beats_128_valid:
        return

    avgbeat = np.array(beats_128_valid).mean(axis=0)

    meanlen = np.mean(beatlens)
    stdlen = np.std(beatlens)
    if stdlen > meanlen * 0.2: # irregular rhythm
        return

    # remove beats with correlation < 0.9
    sd_vals = []
    pv_vals = []
    for i in range(0, len(minlist)-1):
        if beats_128[i] is None or not len(beats_128[i]):
            continue
        if np.corrcoef(avgbeat, beats_128[i])[0, 1] < 0.9:
            continue
        sd = data[maxlist[i]] - data[minlist[i]]  # stroke distance
        pv = data[maxlist[i]]
        sd_vals.append({'dt': minlist[i] / srate, 'val': sd})
        pv_vals.append({'dt': minlist[i] / srate, 'val': pv})

    dtstart = time.time()

    # estimates resp rate
    # upper env
    idx_start = max(min(minlist),min(maxlist))
    idx_end = min(max(minlist),max(maxlist))
    xa = scipy.interpolate.CubicSpline(maxlist, [data[idx] for idx in maxlist])(np.arange(idx_start, idx_end))

    # lower env
    xb = scipy.interpolate.CubicSpline(minlist, [data[idx] for idx in minlist])(np.arange(idx_start, idx_end))
    rr = arr.estimate_resp_rate(xa-xb, srate)

    dtend = time.time()
    #print('rr {}'.format(rr))

    # split by respiration
    nsamp_in_breath = int(srate * 60 / rr)
    m = int(len(data) / nsamp_in_breath)  # m segments exist

    raw_sds = []
    raw_pvs = []
    sdvs = []
    pvvs = []
    for ibreath in np.arange(0, m - 1, 0.5):
        sds_breath = []
        pvs_breath = []

        for sde in sd_vals:
            if ibreath * nsamp_in_breath < sde['dt'] * srate < (ibreath + 1) * nsamp_in_breath:
                sds_breath.append(sde['val'])

        for pve in pv_vals:
            if ibreath * nsamp_in_breath < pve['dt'] * srate < (ibreath + 1) * nsamp_in_breath:
                pvs_breath.append(pve['val'])

        if len(sds_breath) < 4:
            continue

        if len(pvs_breath) < 4:
            continue

        sd_min = min(sds_breath)
        sd_max = max(sds_breath)
        pv_min = min(pvs_breath)
        pv_max = max(pvs_breath)

        sdv = (sd_max - sd_min) / (sd_max + sd_min) * 200
        if not 0 < ppv < 50:
            continue

        pvv = (pv_max - pv_min) / (pv_max + pv_min) * 200
        if not 0 < pvv < 50:
            continue

        # kalman filter
        if last_sdv == 0: # first time
            last_sdv = sdv
        elif abs(last_sdv - sdv) <= 1.0:
            ppv = last_sdv
        elif abs(last_sdv - sdv) <= 25.0:  # sdv cannot be changed abruptly
            sdv = (sdv + last_sdv) * 0.5
            last_sdv = sdv
        else:
            continue

        if last_pvv == 0: # first time
            last_pvv = pvv
        elif abs(last_pvv - pvv) <= 1.0:
            pvv = last_pvv
        elif abs(last_pvv - pvv) <= 25.0:  # pvv cannot be changed abruptly
            pvv = (pvv + last_pvv) * 0.5
            last_pvv = pvv
        else:
            continue

        sdvs.append(sdv)
        pvvs.append(pvv)

    median_sdv = np.median(sdvs)
    median_pvv = np.median(pvvs)

    return [
        [{'dt': cfg['interval'], 'val': median_sdv}],
        [{'dt': cfg['interval'], 'val': median_pvv}],
        [{'dt': cfg['interval'], 'val': rr}]
    ]