SDV and PVV from cardioQ #466
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}]
]