OSCAR-code/SleepLib/calcs.cpp

1123 lines
31 KiB
C++
Raw Normal View History

/*
Custom CPAP/Oximetry Calculations Header
Copyright (c)2011 Mark Watkins <jedimark@users.sourceforge.net>
License: GPL
*/
2011-12-10 17:07:05 +00:00
#include <cmath>
#include <algorithm>
2011-12-17 15:12:35 +00:00
#include "calcs.h"
#include "profiles.h"
2011-12-17 15:12:35 +00:00
extern double round(double number);
bool SearchApnea(Session *session, qint64 time, qint64 dist)
{
if (session->SearchEvent(CPAP_Obstructive,time,dist)) return true;
if (session->SearchEvent(CPAP_Apnea,time,dist)) return true;
if (session->SearchEvent(CPAP_ClearAirway,time,dist)) return true;
if (session->SearchEvent(CPAP_Hypopnea,time,dist)) return true;
return false;
}
// Sort BreathPeak by peak index
bool operator<(const BreathPeak & p1, const BreathPeak & p2)
{
return p1.start < p2.start;
}
//! \brief Filters input to output with a percentile filter with supplied width.
//! \param samples Number of samples
//! \param width number of surrounding samples to consider
//! \param percentile fractional percentage, between 0 and 1
void percentileFilter(EventDataType * input, EventDataType * output, int samples, int width, EventDataType percentile)
{
if (samples<=0)
return;
if (percentile>1)
percentile=1;
QVector<EventDataType> buf(width);
int s,e;
int z1=width/2;
int z2=z1+(width % 2);
int nm1=samples-1;
//int j;
// Scan through all of input
for (int k=0;k < samples;k++) {
s=k-z1;
e=k+z2;
// Cap bounds
if (s < 0) s=0;
if (e > nm1) e=nm1;
//
int j=0;
for (int i=s; i < e; i++) {
buf[j++]=input[i];
}
j--;
EventDataType val=j * percentile;
EventDataType fl=floor(val);
// If even percentile, or already max value..
if ((val==fl) || (j>=width-1)) {
nth_element(buf.begin(),buf.begin()+j,buf.begin()+width-1);
val=buf[j];
} else {
// Percentile lies between two points, interpolate.
double v1,v2;
nth_element(buf.begin(),buf.begin()+j,buf.begin()+width-1);
v1=buf[j];
nth_element(buf.begin(),buf.begin()+j+1,buf.begin()+width-1);
v2=buf[j+1];
val = v1 + (v2-v1)*(val-fl);
}
output[k]=val;
}
}
void xpassFilter(EventDataType * input, EventDataType * output, int samples, EventDataType weight)
{
// prime the first value
output[0]=input[0];
for (int i=1;i<samples-1;i++) {
output[i]=weight*input[i] + (1.0-weight)*output[i-1];
}
output[samples-1]=input[samples-1];
}
FlowParser::FlowParser()
{
m_session=NULL;
m_flow=NULL;
m_filtered=NULL;
m_gain=1;
m_samples=0;
m_startsUpper=true;
// Allocate filter chain buffers..
for (int i=0;i<num_filter_buffers;i++) {
m_buffers[i]=(EventDataType *) malloc(max_filter_buf_size);
}
m_filtered=(EventDataType *) malloc(max_filter_buf_size);
}
FlowParser::~FlowParser()
{
free (m_filtered);
for (int i=0;i<num_filter_buffers;i++) {
free(m_buffers[i]);
}
}
void FlowParser::clearFilters()
{
m_filters.clear();
}
EventDataType * FlowParser::applyFilters(EventDataType * data, int samples)
{
EventDataType *in=NULL,*out=NULL;
if (m_filters.size()==0) {
//qDebug() << "Trying to apply empty filter list in FlowParser..";
return NULL;
}
int numfilt=m_filters.size();
for (int i=0;i<numfilt;i++) {
if (i==0) {
in=data;
out=m_buffers[0];
if (in==out) {
//qDebug() << "Error: If you need to use internal m_buffers as initial input, use the second one. No filters were applied";
return NULL;
}
} else {
in=m_buffers[(i+1) % 2];
out=m_buffers[i % 2];
}
// If final link in chain, pass it back out to input data
if (i==numfilt-1) {
out=data;
}
Filter & filter=m_filters[i];
if (filter.type==FilterNone) {
// Just copy it..
memcpy(in,out,samples*sizeof(EventDataType));
} else if (filter.type==FilterPercentile) {
percentileFilter(in,out,samples,filter.param1,filter.param2);
} else if (filter.type==FilterXPass) {
xpassFilter(in,out,samples,filter.param1);
}
}
return out;
}
void FlowParser::openFlow(Session * session, EventList * flow)
{
if (!flow) {
qDebug() << "called FlowParser::processFlow() with a null EventList!";
return;
}
m_session=session;
m_flow=flow;
m_gain=flow->gain();
m_rate=flow->rate();
m_samples=flow->count();
EventStoreType *inraw=flow->rawData();
// Make sure we won't overflow internal buffers
if (m_samples > max_filter_buf_size) {
qDebug() << "Error: Sample size exceeds max_filter_buf_size in FlowParser::openFlow().. Capping!!!";
m_samples=max_filter_buf_size;
}
// Begin with the second internal buffer
EventDataType * buf=m_filtered;
EventDataType c;
// Apply gain to waveform
for (int i=0;i<m_samples;i++) {
c= EventDataType(*inraw++) * m_gain;
*buf++ = c;
}
// Apply the rest of the filters chain
buf=applyFilters(m_filtered, m_samples);
if (buf!=m_filtered) {
int i=5;
}
calcPeaks(m_filtered, m_samples);
}
// Calculates breath upper & lower peaks for a chunk of EventList data
void FlowParser::calcPeaks(EventDataType * input, int samples)
{
if (samples<=0)
return;
EventDataType min=0,max=0, c, lastc=0;
EventDataType zeroline=0;
// For each sample in flow waveform
double rate=m_flow->rate();
double flowstart=m_flow->first();
double lasttime,time;
double peakmax=flowstart, peakmin=flowstart;
time=lasttime=flowstart;
breaths.clear();
// Estimate storage space needed using typical average breaths per minute.
m_minutes=double(m_flow->last() - m_flow->first()) / 60000.0;
const double avgbpm=20;
int guestimate=m_minutes*avgbpm;
breaths.reserve(guestimate);
// Prime min & max, and see which side of the zero line we are starting from.
c=input[0];
min=max=c;
lastc=c;
m_startsUpper=(c >= zeroline);
qint32 start=0,middle=0;//,end=0;
int sps=1000/m_rate;
// For each samples, find the peak upper and lower breath components
//bool dirty=false;
int len=0, lastk=0; //lastlen=0
//EventList *uf2=m_session->AddEventList(CPAP_UserFlag2,EVL_Event);
qint64 sttime=time;//, ettime=time;
for (int k=0; k < samples; k++) {
c=input[k];
// dirty=false;
if (c >= zeroline) {
// Did we just cross the zero line going up?
if (lastc < zeroline) {
// This helps filter out dirty breaths..
len=k-start;
if ((max>3) && ((max-min) > 8) && (len>sps) && (middle > start)) {
breaths.push_back(BreathPeak(min, max, start, peakmax, middle, peakmin, k));
//EventDataType g0=(0-lastc) / (c-lastc);
//double d=(m_rate*g0);
2012-01-09 03:50:08 +00:00
//double d1=flowstart+ (start*rate);
//double d2=peakmax;
2012-01-09 03:50:08 +00:00
//uf1->AddEvent(d1,0);
//uf2->AddEvent(d2,0);
//lastlen=k-start;
// Set max for start of the upper breath cycle
max=c;
peakmax=time;
// Starting point of next breath cycle
start=k;
sttime=time;
}/* else {
if ((max <=3) || ((max-min) <= 8)) {
start=k;
}
}*/
} else if (c > max) {
// Update upper breath peak
max=c;
peakmax=time;
}
}
if (c < zeroline) {
// Did we just cross the zero line going down?
if (lastc >= zeroline) {
// Set min for start of the lower breath cycle
min=c;
peakmin=time;
middle=k;
} else if (c < min) {
// Update lower breath peak
min=c;
peakmin=time;
}
}
lasttime=time;
time+=rate;
//if (!dirty)
lastc=c;
lastk=k;
}
}
//! \brief Calculate Respiratory Rate, TidalVolume, Minute Ventilation, Ti & Te..
// These are grouped together because, a) it's faster, and b) some of these calculations rely on others.
void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool calcMv)
{
if (!m_session) {
return;
}
// Don't even bother if only a few breaths in this chunk
const int lowthresh=4;
int nm=breaths.size();
if (nm < lowthresh)
return;
const qint64 minute=60000;
double start=m_flow->first();
double time=start;
int bs,be,bm;
double st,et,mt;
/////////////////////////////////////////////////////////////////////////////////
// Respiratory Rate setup
/////////////////////////////////////////////////////////////////////////////////
EventDataType minrr,maxrr;
EventList * RR=NULL;
quint32 * rr_tptr=NULL;
EventStoreType * rr_dptr=NULL;
if (calcResp) {
RR=m_session->AddEventList(CPAP_RespRate,EVL_Event);
minrr=RR->Min(), maxrr=RR->Max();
RR->setFirst(time+minute);
RR->getData().resize(nm);
RR->getTime().resize(nm);
rr_tptr=RR->rawTime();
rr_dptr=RR->rawData();
}
int rr_count=0;
double len, st2,et2,adj, stmin, b, rr=0;
int idx;
/////////////////////////////////////////////////////////////////////////////////
// Inspiratory / Expiratory Time setup
/////////////////////////////////////////////////////////////////////////////////
double lastte2=0,lastti2=0,lastte=0, lastti=0, te, ti, ti1, te1,c;
EventList * Te=NULL, * Ti=NULL;
if (calcTi) {
Ti=m_session->AddEventList(CPAP_Ti,EVL_Event);
Ti->setGain(0.1);
}
if (calcTe) {
Te=m_session->AddEventList(CPAP_Te,EVL_Event);
Te->setGain(0.1);
}
/////////////////////////////////////////////////////////////////////////////////
// Tidal Volume setup
/////////////////////////////////////////////////////////////////////////////////
EventList * TV;
EventDataType mintv, maxtv, tv;
double val1, val2;
quint32 * tv_tptr;
EventStoreType * tv_dptr;
int tv_count=0;
if (calcTv) {
TV=m_session->AddEventList(CPAP_TidalVolume,EVL_Event);
mintv=TV->Min(), maxtv=TV->Max();
TV->setFirst(start);
TV->getData().resize(nm);
TV->getTime().resize(nm);
tv_tptr=TV->rawTime();
tv_dptr=TV->rawData();
}
/////////////////////////////////////////////////////////////////////////////////
// Minute Ventilation setup
/////////////////////////////////////////////////////////////////////////////////
EventList * MV=NULL;
EventDataType mv;
if (calcMv) {
MV=m_session->AddEventList(CPAP_MinuteVent,EVL_Event);
MV->setGain(0.125);
}
EventDataType sps=(1000.0/m_rate); // Samples Per Second
qint32 timeval=0; // Time relative to start
for (idx=0;idx<nm;idx++) {
bs=breaths[idx].start;
bm=breaths[idx].middle;
be=breaths[idx].end;
// Calculate start, middle and end time of this breath
st=start+bs * m_rate;
mt=start+bm * m_rate;
timeval=be * m_rate;
et=start+timeval;
/////////////////////////////////////////////////////////////////////
// Calculate Inspiratory Time (Ti) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTi) {
ti=(mt-st)/100.0;
ti1=(lastti2+lastti+ti)/3.0;
Ti->AddEvent(mt,ti1);
lastti2=lastti;
lastti=ti;
}
/////////////////////////////////////////////////////////////////////
// Calculate Expiratory Time (Te) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTe) {
te=(et-mt)/100.0; // (/1000 * 10)
// Average last three values..
te1=(lastte2+lastte+te)/3.0;
Te->AddEvent(mt,te1);
lastte2=lastte;
lastte=te;
}
/////////////////////////////////////////////////////////////////////
// Calculate TidalVolume
/////////////////////////////////////////////////////////////////////
if (calcTv) {
val1=0, val2=0;
// Scan the upper breath
for (int j=bs;j<bm;j++) {
// convert flow to ml/s to L/min and divide by samples per second
c=double(qAbs(m_filtered[j])) * 1000.0 / 60.0 / sps;
val2+=c;
//val2+=c*c; // for RMS
}
// calculate root mean square
//double n=bm-bs;
//double q=(1/n)*val2;
//double x=sqrt(q)*2;
//val2=x;
tv=val2;
if (tv < mintv) mintv=tv;
if (tv > maxtv) maxtv=tv;
*tv_tptr++ = timeval;
*tv_dptr++ = tv / 20.0;
tv_count++;
}
/////////////////////////////////////////////////////////////////////
// Respiratory Rate Calculations
/////////////////////////////////////////////////////////////////////
if (calcResp) {
stmin=et-minute;
if (stmin < start)
stmin=start;
len=et-stmin;
if (len < minute)
continue;
rr=0;
//et2=et;
// Step back through last minute and count breaths
for (int i=idx;i>=0;i--) {
st2=start + double(breaths[i].start) * m_rate;
et2=start + double(breaths[i].end) * m_rate;
if (et2 < stmin)
break;
len=et2-st2;
if (st2 < stmin) {
// Partial breath
st2=stmin;
adj=et2 - st2;
b=(1.0 / len) * adj;
} else b=1;
rr+=b;
}
// Calculate min & max
if (rr < minrr) minrr=rr;
if (rr > maxrr) maxrr=rr;
// Add manually.. (much quicker)
*rr_tptr++ = timeval;
// Use the same gains as ResMed..
*rr_dptr++ = rr * 5.0;
rr_count++;
//rr->AddEvent(et,br * 50.0);
}
if (calcMv && calcResp && calcTv) {
mv=(tv/1000.0) * rr;
MV->AddEvent(et,mv * 8.0);
}
}
/////////////////////////////////////////////////////////////////////
// Respiratory Rate post filtering
/////////////////////////////////////////////////////////////////////
2012-01-09 04:23:15 +00:00
if (calcResp) {
RR->setGain(0.2);
2012-01-09 04:23:15 +00:00
RR->setMin(minrr);
RR->setMax(maxrr);
RR->setFirst(start);
RR->setLast(et);
RR->setCount(rr_count);
}
/////////////////////////////////////////////////////////////////////
// Tidal Volume post filtering
/////////////////////////////////////////////////////////////////////
2012-01-09 04:23:15 +00:00
if (calcTv) {
TV->setGain(20);
2012-01-09 04:23:15 +00:00
TV->setMin(mintv);
TV->setMax(maxtv);
TV->setFirst(start);
TV->setLast(et);
TV->setCount(tv_count);
}
}
void FlowParser::flagEvents()
{
int numbreaths=breaths.size();
EventDataType val,mx,mn;
QVector<EventDataType> br(numbreaths);
// QVector<qint64> bstart(numbreaths);
// QVector<qint64> bend(numbreaths);
// QVector<EventDataType> bvalue(numbreaths);
double start=m_flow->first();
double sps=1000.0/m_rate;
double st,mt,et, dur;
qint64 len;
for (int i=0;i<numbreaths;i++) {
// st=start+breaths[i].start * m_rate;
// et=start+breaths[i].end * m_rate;
// bstart[i]=st;
// bend[i]=et;
val=breaths[i].max - breaths[i].min;
//bvalue[i]=val;
br[i]=val;
}
2011-12-12 18:37:34 +00:00
const EventDataType perc=0.95;
int idx=numbreaths*perc;
nth_element(br.begin(),br.begin()+idx,br.end());
EventDataType peak=*(br.begin()+idx);
EventDataType cutoffval=peak * (PROFILE.cpap->userFlowRestriction()/100.0);
EventDataType duration=PROFILE.cpap->userEventDuration();
QVector<qint64> good;
EventList * uf1=NULL;
//EventList * uf2=m_session->AddEventList(CPAP_UserFlag2,EVL_Event);
// EventList * uf3=m_session->AddEventList(CPAP_UserFlag3,EVL_Event);
double lastst=start, lastet=start;
good.reserve(numbreaths);
bool bad=false;
int bs,bm,be;
for (int i=0;i<numbreaths;i++) {
bs=breaths[i].start;
bm=breaths[i].middle;
be=breaths[i].end;
mx=breaths[i].max;
mn=breaths[i].min;
val=mx - mn;
int i=bs;
for (;i<bm;i++) {
if (qAbs(m_filtered[i]) > cutoffval) {
bs=i;
break;
}
}
i=be;
for (;i>bm;i--) {
if (qAbs(m_filtered[i]) > cutoffval) {
be=i;
break;
}
}
st=start + bs * m_rate;
mt=start + bm * m_rate;
et=start + be * m_rate;
len=st-lastet;
dur=len/1000.0;
if (dur>=duration) {
2012-01-09 08:34:40 +00:00
//if (!SearchApnea(m_session,st-len/2,15000)) {
if (!uf1) {
uf1=m_session->AddEventList(CPAP_UserFlag1,EVL_Event);
}
uf1->AddEvent(st-len/2,dur);
//}
}
// Uncomment to use UserFlags to show waveform crossover points
// Good for debugging this stuff. (Make sure to add the EventLists up above)
2011-12-12 18:37:34 +00:00
if (val > cutoffval) {
//uf2->AddEvent(st,0);
//uf2->AddEvent(mt,0);
//uf3->AddEvent(et,0);
lastet=et;
lastst=st;
}
}
return;
//EventList *uf1=NULL;
2012-01-09 08:34:40 +00:00
// int lastbad=-1;
// qint64 firstbad=0;
// bool fr=false; // flow restriction
// for (int i=0;i<numbreaths;i++) {
// st=start+ breaths[i].start * m_rate;
// et=start+ breaths[i].end * m_rate;
// fr=false;
// int j=i;
// for (j=i;j<numbreaths;j++) {
// mx=breaths[j].max;
// mn=breaths[j].min;
// val=mx-mn;
// if (val > cutoffval)
// break;
// fr=true;
// et=start + breaths[j].end * m_rate;
// }
// if (fr) {
// i=j-1; // rewind
// len=et-st;
// dur=(len) / 1000.0;
// if (dur >= duration) {
// if (!uf1) {
// uf1=m_session->AddEventList(CPAP_UserFlag1,EVL_Event);
// }
// uf1->AddEvent(et-(len/2),dur);
// }
// }
// }
}
void calcRespRate(Session *session, FlowParser * flowparser)
{
if (session->machine()->GetType()!=MT_CPAP) return;
// if (session->machine()->GetClass()!=STR_MACH_PRS1) return;
if (!session->eventlist.contains(CPAP_FlowRate)) {
//qDebug() << "calcRespRate called without FlowRate waveform available";
return; //need flow waveform
}
bool trashfp;
if (!flowparser) {
flowparser=new FlowParser();
trashfp=true;
//qDebug() << "calcRespRate called without valid FlowParser object.. using a slow throw-away!";
//return;
} else {
trashfp=false;
}
bool calcResp=!session->eventlist.contains(CPAP_RespRate);
bool calcTv=!session->eventlist.contains(CPAP_TidalVolume);
bool calcTi=!session->eventlist.contains(CPAP_Ti);
bool calcTe=!session->eventlist.contains(CPAP_Te);
bool calcMv=!session->eventlist.contains(CPAP_MinuteVent);
// If any of these three missing, remove all, and switch all on
if (!(calcResp & calcTv & calcMv)) {
calcTv=calcMv=calcResp=true;
QVector<EventList *> & list=session->eventlist[CPAP_RespRate];
for (int i=0;i<list.size();i++) {
delete list[i];
}
session->eventlist[CPAP_RespRate].clear();
QVector<EventList *> & list2=session->eventlist[CPAP_TidalVolume];
for (int i=0;i<list2.size();i++) {
delete list2[i];
}
session->eventlist[CPAP_TidalVolume].clear();
QVector<EventList *> & list3=session->eventlist[CPAP_MinuteVent];
for (int i=0;i<list3.size();i++) {
delete list3[i];
}
session->eventlist[CPAP_MinuteVent].clear();
}
flowparser->clearFilters();
// No filters works rather well with the new peak detection algorithm..
// Although the output could use filtering.
//flowparser->addFilter(FilterPercentile,7,0.5);
//flowparser->addFilter(FilterPercentile,5,0.5);
//flowparser->addFilter(FilterXPass,0.5);
EventList *flow;
for (int ws=0; ws < session->eventlist[CPAP_FlowRate].size(); ws++) {
flow=session->eventlist[CPAP_FlowRate][ws];
if (flow->count() > 20) {
flowparser->openFlow(session, flow);
flowparser->calc(calcResp, calcTv, calcTi ,calcTe, calcMv);
flowparser->flagEvents();
}
}
if (trashfp) {
delete flowparser;
}
}
EventDataType calcAHI(Session *session,qint64 start, qint64 end)
{
double hours,ahi,cnt;
if (start<0) {
// much faster..
hours=session->hours();
cnt=session->count(CPAP_Obstructive)
+session->count(CPAP_Hypopnea)
+session->count(CPAP_ClearAirway)
+session->count(CPAP_Apnea);
ahi=cnt/hours;
} else {
hours=double(end-start)/3600000L;
if (hours==0) return 0;
cnt=session->rangeCount(CPAP_Obstructive,start,end)
+session->rangeCount(CPAP_Hypopnea,start,end)
+session->rangeCount(CPAP_ClearAirway,start,end)
+session->rangeCount(CPAP_Apnea,start,end);
ahi=cnt/hours;
}
return ahi;
}
int calcAHIGraph(Session *session)
{
const qint64 window_step=30000; // 30 second windows
double window_size=p_profile->cpap->AHIWindow();
qint64 window_size_ms=window_size*60000L;
bool zeroreset=p_profile->cpap->AHIReset();
2011-11-30 22:56:31 +00:00
if (session->machine()->GetType()!=MT_CPAP) return 0;
if (session->eventlist.contains(CPAP_AHI)) return 0; // abort if already there
if (!session->channelExists(CPAP_Obstructive) &&
!session->channelExists(CPAP_Hypopnea) &&
!session->channelExists(CPAP_Apnea) &&
!session->channelExists(CPAP_ClearAirway)) return 0;
qint64 first=session->first(),
last=session->last(),
f;
EventList *AHI=new EventList(EVL_Event);
session->eventlist[CPAP_AHI].push_back(AHI);
EventDataType ahi;
qint64 ti=first,lastti=first;
double avg=0;
int cnt=0;
double events;
double hours=(window_size/60.0);
if (zeroreset) {
// I personally don't see the point of resetting each hour.
do {
// For each window, in 30 second increments
for (qint64 t=ti;t < ti+window_size_ms; t+=window_step) {
if (t > last)
break;
events=session->rangeCount(CPAP_Obstructive,ti,t)
+session->rangeCount(CPAP_Hypopnea,ti,t)
+session->rangeCount(CPAP_ClearAirway,ti,t)
+session->rangeCount(CPAP_Apnea,ti,t);
//ahi=calcAHI(session,ti,t)* hours;
ahi = events / hours;
AHI->AddEvent(t,ahi);
avg+=ahi;
cnt++;
}
lastti=ti;
ti+=window_size_ms;
} while (ti<last);
} else {
for (ti=first;ti<last;ti+=window_step) {
// if (ti>last) {
//// AHI->AddEvent(last,ahi);
//// avg+=ahi;
//// cnt++;
// break;
// }
f=ti-window_size_ms;
ahi=calcAHI(session,f,ti);
avg+=ahi;
cnt++;
AHI->AddEvent(ti,ahi);
lastti=ti;
ti+=window_step;
}
}
AHI->AddEvent(lastti,0);
if (!cnt) avg=0; else avg/=double(cnt);
session->setAvg(CPAP_AHI,avg);
return AHI->count();
}
2011-11-30 12:32:16 +00:00
int calcLeaks(Session *session)
{
2011-11-30 22:56:31 +00:00
if (session->machine()->GetType()!=MT_CPAP) return 0;
2011-11-30 12:32:16 +00:00
if (session->eventlist.contains(CPAP_Leak)) return 0; // abort if already there
if (!session->eventlist.contains(CPAP_LeakTotal)) return 0; // can't calculate without this..
if (session->settings[CPAP_Mode].toInt()>MODE_APAP) return 0; // Don't bother calculating when in APAP mode
2011-11-30 12:32:16 +00:00
const qint64 winsize=3600000; // 5 minute window
//qint64 first=session->first(), last=session->last(), f;
2011-11-30 12:32:16 +00:00
EventList *leak=new EventList(EVL_Event);
session->eventlist[CPAP_Leak].push_back(leak);
const int rbsize=128;
EventDataType rbuf[rbsize],tmp,median;
qint64 rtime[rbsize],ti;
int rpos=0;
int tcnt=0;
QVector<EventDataType> med;
2012-01-05 13:47:04 +00:00
qint64 start;
quint32 * tptr;
EventStoreType * dptr;
EventDataType gain;
2011-11-30 12:32:16 +00:00
for (int i=0;i<session->eventlist[CPAP_LeakTotal].size();i++) {
EventList & el=*session->eventlist[CPAP_LeakTotal][i];
2012-01-05 13:47:04 +00:00
gain=el.gain();
dptr=el.rawData();
tptr=el.rawTime();
start=el.first();
2011-11-30 12:32:16 +00:00
for (unsigned j=0;j<el.count();j++) {
2012-01-05 13:47:04 +00:00
tmp=*dptr++ * gain;
ti=start+ *tptr++;
2011-11-30 12:32:16 +00:00
rbuf[rpos]=tmp;
rtime[rpos]=ti;
tcnt++;
rpos++;
int rcnt;
if (tcnt<rbsize) rcnt=tcnt; else rcnt=rbsize;
med.clear();
for (int k=0;k<rcnt;k++) {
if (rtime[k] > ti-winsize) // if fits in time window, add to the list
med.push_back(rbuf[k]);
}
int idx=float(med.size() * 0.0);
if (idx>=med.size()) idx--;
2012-01-05 13:47:04 +00:00
nth_element(med.begin(),med.begin()+idx,med.end());
median=tmp-(*(med.begin()+idx));
2011-11-30 12:32:16 +00:00
if (median<0) median=0;
leak->AddEvent(ti,median);
rpos=rpos % rbsize;
}
}
return leak->count();
}
int calcPulseChange(Session *session)
{
if (session->eventlist.contains(OXI_PulseChange)) return 0;
QHash<ChannelID,QVector<EventList *> >::iterator it=session->eventlist.find(OXI_Pulse);
if (it==session->eventlist.end()) return 0;
EventDataType val,val2,change,tmp;
qint64 time,time2;
2011-12-21 12:47:47 +00:00
qint64 window=PROFILE.oxi->pulseChangeDuration();
window*=1000;
change=PROFILE.oxi->pulseChangeBPM();
EventList *pc=new EventList(EVL_Event,1,0,0,0,0,true);
2011-11-28 13:58:43 +00:00
pc->setFirst(session->first(OXI_Pulse));
qint64 lastt;
EventDataType lv=0;
int li=0;
int max;
for (int e=0;e<it.value().size();e++) {
EventList & el=*(it.value()[e]);
for (unsigned i=0;i<el.count();i++) {
val=el.data(i);
time=el.time(i);
2011-11-28 13:58:43 +00:00
lastt=0;
lv=change;
max=0;
2011-11-28 13:58:43 +00:00
for (unsigned j=i+1;j<el.count();j++) { // scan ahead in the window
time2=el.time(j);
if (time2 > time+window) break;
val2=el.data(j);
2011-12-02 00:38:32 +00:00
tmp=qAbs(val2-val);
2011-11-28 13:58:43 +00:00
if (tmp > lv) {
lastt=time2;
if (tmp>max) max=tmp;
//lv=tmp;
2011-11-28 13:58:43 +00:00
li=j;
}
}
2011-11-28 13:58:43 +00:00
if (lastt>0) {
qint64 len=(lastt-time)/1000.0;
pc->AddEvent(lastt,len,tmp);
2011-11-28 13:58:43 +00:00
i=li;
}
}
}
if (pc->count()==0) {
delete pc;
return 0;
}
session->eventlist[OXI_PulseChange].push_back(pc);
2011-12-17 14:38:15 +00:00
session->setMin(OXI_PulseChange,pc->Min());
session->setMax(OXI_PulseChange,pc->Max());
session->setCount(OXI_PulseChange,pc->count());
session->setFirst(OXI_PulseChange,pc->first());
session->setLast(OXI_PulseChange,pc->last());
return pc->count();
}
int calcSPO2Drop(Session *session)
{
if (session->eventlist.contains(OXI_SPO2Drop)) return 0;
QHash<ChannelID,QVector<EventList *> >::iterator it=session->eventlist.find(OXI_SPO2);
if (it==session->eventlist.end()) return 0;
EventDataType val,val2,change,tmp;
qint64 time,time2;
2011-12-21 12:47:47 +00:00
qint64 window=PROFILE.oxi->spO2DropDuration();
window*=1000;
change=PROFILE.oxi->spO2DropPercentage();
EventList *pc=new EventList(EVL_Event,1,0,0,0,0,true);
2011-11-28 13:58:43 +00:00
qint64 lastt;
EventDataType lv=0;
int li=0;
2011-12-10 16:15:47 +00:00
// Fix me.. Time scale varies.
//const unsigned ringsize=30;
//EventDataType ring[ringsize]={0};
//qint64 rtime[ringsize]={0};
//int rp=0;
int min;
2011-12-10 16:15:47 +00:00
int cnt=0;
2011-12-10 17:07:05 +00:00
tmp=0;
qint64 start=0;
// Calculate median baseline
QList<EventDataType> med;
for (int e=0;e<it.value().size();e++) {
EventList & el=*(it.value()[e]);
for (unsigned i=0;i<el.count();i++) {
val=el.data(i);
2011-12-10 16:15:47 +00:00
time=el.time(i);
if (val>0) med.push_back(val);
2011-12-10 17:07:05 +00:00
if (!start) start=time;
if (time > start+3600000) break; // just look at the first hour
2011-12-10 17:07:05 +00:00
tmp+=val;
cnt++;
}
}
qSort(med);
int midx=float(med.size())*0.90;
if (midx>med.size()-1) midx=med.size()-1;
EventDataType baseline=med[midx];
session->settings[OXI_SPO2Drop]=baseline;
//EventDataType baseline=round(tmp/EventDataType(cnt));
2011-12-10 17:07:05 +00:00
EventDataType current;
qDebug() << "Calculated baseline" << baseline;
for (int e=0;e<it.value().size();e++) {
EventList & el=*(it.value()[e]);
for (unsigned i=0;i<el.count();i++) {
current=el.data(i);
if (!current) continue;
time=el.time(i);
/*ring[rp]=val;
2011-12-10 16:15:47 +00:00
rtime[rp]=time;
rp++;
rp=rp % ringsize;
if (i<ringsize) {
for (unsigned j=i;j<ringsize;j++) {
ring[j]=val;
2011-12-10 16:15:47 +00:00
rtime[j]=0;
}
}
tmp=0;
2011-12-10 16:15:47 +00:00
cnt=0;
for (unsigned j=0;j<ringsize;j++) {
2011-12-10 17:07:05 +00:00
if (rtime[j] > time-300000) { // only look at recent entries..
2011-12-10 16:15:47 +00:00
tmp+=ring[j];
cnt++;
}
}
2011-12-10 16:15:47 +00:00
if (!cnt) {
unsigned j=abs((rp-1) % ringsize);
2011-12-10 17:07:05 +00:00
tmp=(ring[j]+val)/2;
} else tmp/=EventDataType(cnt); */
2011-12-10 17:07:05 +00:00
val=baseline;
2011-11-28 13:58:43 +00:00
lastt=0;
lv=val;
2011-11-28 13:58:43 +00:00
min=val;
2011-12-10 17:07:05 +00:00
for (unsigned j=i;j<el.count();j++) { // scan ahead in the window
time2=el.time(j);
2011-12-10 17:07:05 +00:00
//if (time2 > time+window) break;
val2=el.data(j);
2011-12-10 17:07:05 +00:00
if (val2 > baseline-change) break;
lastt=time2;
li=j+1;
}
2011-11-28 13:58:43 +00:00
if (lastt>0) {
qint64 len=(lastt-time);
if (len>=window) {
pc->AddEvent(lastt,len/1000,val-min);
i=li;
}
2011-11-28 13:58:43 +00:00
}
}
}
if (pc->count()==0) {
delete pc;
return 0;
}
session->eventlist[OXI_SPO2Drop].push_back(pc);
2011-12-17 14:38:15 +00:00
session->setMin(OXI_SPO2Drop,pc->Min());
session->setMax(OXI_SPO2Drop,pc->Max());
session->setCount(OXI_SPO2Drop,pc->count());
session->setFirst(OXI_SPO2Drop,pc->first());
session->setLast(OXI_SPO2Drop,pc->last());
return pc->count();
}