Moved Respiratory Rate & Graph AHI calcs to own module, PRS1 ASV pressure fixes

This commit is contained in:
Mark Watkins 2011-11-28 11:39:28 +10:00
parent 4d0cccfce9
commit 7dd271ca64
9 changed files with 439 additions and 204 deletions

View File

@ -2411,6 +2411,7 @@ void gGraphView::keyPressEvent(QKeyEvent * event)
return;
}
gGraph *g;
// Pick the first valid graph in the primary group
for (int i=0;i<m_graphs.size();i++) {
if (m_graphs[i]->group()==0) {
if (!m_graphs[i]->isEmpty()) {
@ -2420,6 +2421,7 @@ void gGraphView::keyPressEvent(QKeyEvent * event)
}
}
if (!g) return;
g->keyPressEvent(event);
if (event->key()==Qt::Key_Left) {

277
SleepLib/calcs.cpp Normal file
View File

@ -0,0 +1,277 @@
/*
Custom CPAP/Oximetry Calculations Header
Copyright (c)2011 Mark Watkins <jedimark@users.sourceforge.net>
License: GPL
*/
#include "calcs.h"
#include "profiles.h"
Calculation::Calculation(ChannelID id,QString name)
:m_id(id),m_name(name)
{
}
Calculation::~Calculation()
{
}
CalcRespRate::CalcRespRate(ChannelID id)
:Calculation(id,"Resp. Rate")
{
}
// Generate RespiratoryRate graph
int CalcRespRate::calculate(Session *session)
{
if (session->eventlist.contains(CPAP_RespRate)) return 0; // already exists?
if (!session->eventlist.contains(CPAP_FlowRate)) return 0; //need flow waveform
EventList *flow, *rr;
int cnt=0;
for (int ws=0; ws < session->eventlist[CPAP_FlowRate].size(); ws++) {
flow=session->eventlist[CPAP_FlowRate][ws];
if (flow->count() > 5) {
rr=new EventList(EVL_Event);
session->eventlist[CPAP_RespRate].push_back(rr);
cnt+=filterFlow(flow,rr,flow->rate());
}
}
return cnt;
}
int CalcRespRate::filterFlow(EventList *in, EventList *out, double rate)
{
int size=in->count();
EventDataType *stage1=new EventDataType [size];
EventDataType *stage2=new EventDataType [size];
QVector<EventDataType> med;
med.reserve(8);
EventDataType r;
int cnt;
EventDataType c;
//double avg;
int i;
/*i=3;
stage1[0]=in->data(0);
stage1[1]=in->data(1);
stage1[2]=in->data(2);
for (;i<size-2;i++) {
med.clear();
for (quint32 k=0;k<5;k++) {
med.push_back(in->data(i-2+k));
}
qSort(med);
stage1[i]=med[3];
}
stage1[i]=in->data(i);
i++;
stage1[i]=in->data(i);
i++;
stage1[i]=in->data(i);
*/
//i++;
//stage1[i]=in->data(i);
// Anti-Alias the flow waveform to get rid of jagged edges.
stage2[0]=stage1[0];
stage2[1]=stage1[1];
stage2[2]=stage1[2];
i=3;
for (;i<size-3;i++) {
cnt=0;
r=0;
for (quint32 k=0;k<7;k++) {
//r+=stage1[i-3+k];
r+=in->data(i-3+k);
cnt++;
}
c=r/float(cnt);
stage2[i]=c;
}
stage2[i]=in->data(i);
i++;
stage2[i]=in->data(i);
i++;
stage2[i]=in->data(i);
//i++;
//stage2[i]=in->data(i);
float weight=0.6;
//stage2[0]=in->data(0);
stage1[0]=stage2[0];
for (int i=1;i<size;i++) {
//stage2[i]=in->data(i);
stage1[i]=weight*stage2[i]+(1.0-weight)*stage1[i-1];
}
qint64 time=in->first();
qint64 u1=0,u2=0,len,l1=0,l2=0;
EventDataType lastc=0,thresh=0;
QVector<int> breaths;
QVector<qint64> breaths_start;
for (i=0;i<size;i++) {
c=stage1[i];
if (c>thresh) {
if (lastc<=thresh) {
u2=u1;
u1=time;
if (u2>0) {
len=abs(u2-u1);
//if (len>1500) {
breaths_start.push_back(time);
breaths.push_back(len);
//}
}
}
} else {
if (lastc>thresh) {
l2=l1;
l1=time;
if (l2>0) {
len=abs(l2-l1);
//if (len>1500) {
// breaths2_start.push_back(time);
// breaths2.push_back(len);
//}
}
}
}
lastc=c;
time+=rate;
}
qint64 window=60000;
qint64 t1=in->first()-window/2;
qint64 t2=in->first()+window/2;
qint64 t;
EventDataType br,q;
//int z=0;
int l;
QVector<int> breaths2;
QVector<qint64> breaths2_start;
int fir=0;
do {
br=0;
bool first=true;
bool cont=false;
for (int i=fir;i<breaths.size();i++) {
t=breaths_start[i];
l=breaths[i];
if (t+l < t1) continue;
if (t > t2) break;
if (first) {
first=false;
fir=i;
}
//q=1;
if (t<t1) {
// move to start of previous breath
t1=breaths_start[++i];
t2=t1+window;
fir=i;
cont=true;
break;
//q=(t+l)-t1;
//br+=(1.0/double(l))*double(q);
} else if (t+l>t2) {
q=t2-t;
br+=(1.0/double(l))*double(q);
continue;
} else
br+=1.0;
}
if (cont) continue;
breaths2.push_back(br);
breaths2_start.push_back(t1+window/2);
//out->AddEvent(t,br);
//stage2[z++]=br;
t1+=window/2.0;
t2+=window/2.0;
} while (t2<in->last());
for (int i=1;i<breaths2.size()-2;i++) {
t=breaths2_start[i];
med.clear();
for (int j=0;j<4;j++) {
med.push_back(breaths2[i+j-1]);
}
qSort(med);
br=med[2];
out->AddEvent(t,br);
}
delete [] stage2;
delete [] stage1;
return out->count();
}
EventDataType calcAHI(Session *session,qint64 start, qint64 end)
{
double hours,ahi,cnt;
if ((start==end) && (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;
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;
}
CalcAHIGraph::CalcAHIGraph(ChannelID id):
Calculation(id,"AHI/hour")
{
}
int CalcAHIGraph::calculate(Session *session)
{
if (session->eventlist.contains(CPAP_AHI)) return 0; // abort if already there
const qint64 winsize=30000; // 30 second windows
qint64 first=session->first(),
last=session->last(),
f;
EventList *AHI=new EventList(EVL_Event);
session->eventlist[CPAP_AHI].push_back(AHI);
EventDataType ahi;
for (qint64 ti=first;ti<=last;ti+=winsize) {
f=ti-3600000L;
ahi=calcAHI(session,f,ti);
AHI->AddEvent(ti,ahi);
ti+=winsize;
}
return AHI->count();
}

41
SleepLib/calcs.h Normal file
View File

@ -0,0 +1,41 @@
/*
Custom CPAP/Oximetry Calculations Header
Copyright (c)2011 Mark Watkins <jedimark@users.sourceforge.net>
License: GPL
*/
#ifndef CALCS_H
#define CALCS_H
#include "day.h"
class Calculation
{
public:
Calculation(ChannelID id,QString name);
virtual ~Calculation();
virtual int calculate(Session *session)=0;
protected:
ChannelID m_id;
QString m_name;
};
class CalcRespRate:public Calculation
{
public:
CalcRespRate(ChannelID id=CPAP_RespRate);
virtual int calculate(Session *session);
protected:
int filterFlow(EventList *in, EventList *out,double rate);
};
class CalcAHIGraph:public Calculation
{
public:
CalcAHIGraph(ChannelID id=CPAP_AHI);
virtual int calculate(Session *session);
protected:
};
EventDataType calcAHI(Session *session,qint64 start=0, qint64 end=0);
#endif // CALCS_H

View File

@ -372,7 +372,6 @@ int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile)
sess->settings[PRS1_FlexMode]=PR_BIFLEX;
}
sess->setAvg(CPAP_Pressure,(sess->avg(CPAP_EPAP)+sess->avg(CPAP_IPAP))/2.0);
sess->setWavg(CPAP_Pressure,(sess->wavg(CPAP_EPAP)+sess->wavg(CPAP_IPAP))/2.0);
sess->setMin(CPAP_Pressure,sess->min(CPAP_EPAP));
@ -928,9 +927,9 @@ bool PRS1Loader::Parse002ASV(Session *session,unsigned char *buffer,int size,qin
break;
case 0x0d: // All the other ASV graph stuff.
if (!Code[12]) {
if (!(Code[12]=session->AddEventList(CPAP_IPAP,EVL_Event))) return false;
if (!(Code[13]=session->AddEventList(CPAP_IPAPLo,EVL_Event))) return false;
if (!(Code[14]=session->AddEventList(CPAP_IPAPHi,EVL_Event))) return false;
if (!(Code[12]=session->AddEventList(CPAP_IPAP,EVL_Event,0.1))) return false;
if (!(Code[13]=session->AddEventList(CPAP_IPAPLo,EVL_Event,0.1))) return false;
if (!(Code[14]=session->AddEventList(CPAP_IPAPHi,EVL_Event,0.1))) return false;
if (!(Code[15]=session->AddEventList(CPAP_Leak,EVL_Event))) return false;
if (!(Code[16]=session->AddEventList(CPAP_RespRate,EVL_Event))) return false;
if (!(Code[17]=session->AddEventList(CPAP_PTB,EVL_Event))) return false;
@ -938,7 +937,7 @@ bool PRS1Loader::Parse002ASV(Session *session,unsigned char *buffer,int size,qin
if (!(Code[18]=session->AddEventList(CPAP_MinuteVent,EVL_Event))) return false;
if (!(Code[19]=session->AddEventList(CPAP_TidalVolume,EVL_Event))) return false;
if (!(Code[20]=session->AddEventList(CPAP_Snore,EVL_Event))) return false;
if (!(Code[22]=session->AddEventList(CPAP_EPAP,EVL_Event))) return false;
if (!(Code[22]=session->AddEventList(CPAP_EPAP,EVL_Event,0.1))) return false;
if (!(Code[23]=session->AddEventList(CPAP_PS,EVL_Event))) return false;
}
Code[12]->AddEvent(t,data[0]=buffer[pos++]); // IAP
@ -1280,203 +1279,9 @@ bool PRS1Loader::OpenWaveforms(Session *session,QString filename)
}
session->updateLast(start+qint64(wdur[i])*1000L);
}
if (num_signals<2) {
CalcRespiratoryRate(session);
}
// lousy family 5 check to see if already has RespRate
return true;
}
// Generate RespiratoryRate graph
void PRS1Loader::CalcRespiratoryRate(Session *session)
{
EventList *flow, *rr;
for (int ws=0; ws < session->eventlist[CPAP_FlowRate].size(); ws++) {
flow=session->eventlist[CPAP_FlowRate][ws];
if (flow->count() > 5) {
rr=new EventList(EVL_Event);//EVL_Waveform,1,0,0,0,60000);
session->eventlist[CPAP_RespRate].push_back(rr);
filterFlow(flow,rr);
}
}
}
void PRS1Loader::filterFlow(EventList *in, EventList *out)
{
int size=in->count();
EventDataType *stage1=new EventDataType [size];
EventDataType *stage2=new EventDataType [size];
QVector<EventDataType> med;
med.reserve(8);
EventDataType r;
int cnt;
// Anti-Alias the flow waveform to get rid of jagged edges.
EventDataType c;
double avg;
int i;
/*i=2;
stage1[0]=in->data(0);
stage1[1]=in->data(1);
//stage1[2]=in->data(2);
for (;i<size-2;i++) {
med.clear();
for (quint32 k=0;k<5;k++) {
med.push_back(in->data(i-2+k));
}
qSort(med);
stage1[i]=med[3];
}
stage1[i]=in->data(i);
i++;
stage1[i]=in->data(i); */
//i++;
//stage1[i]=in->data(i);
stage2[0]=stage1[0];
stage2[1]=stage1[1];
stage2[2]=stage1[2];
i=3;
for (;i<size-3;i++) {
cnt=0;
r=0;
for (quint32 k=0;k<7;k++) {
//r+=stage1[i-3+k];
r+=in->data(i-3+k);
cnt++;
}
c=r/float(cnt);
stage2[i]=c;
}
stage2[i]=in->data(i);
i++;
stage2[i]=in->data(i);
i++;
stage2[i]=in->data(i);
//i++;
//stage2[i]=in->data(i);
float weight=0.6;
//stage2[0]=in->data(0);
stage1[0]=stage2[0];
for (int i=1;i<size;i++) {
//stage2[i]=in->data(i);
stage1[i]=weight*stage2[i]+(1.0-weight)*stage1[i-1];
}
qint64 time=in->first();
qint64 u1=0,u2=0,len,l1=0,l2=0;
EventDataType lastc=0,thresh=0;
QVector<int> breaths;
QVector<qint64> breaths_start;
for (i=0;i<size;i++) {
c=stage1[i];
if (c>thresh) {
if (lastc<=thresh) {
u2=u1;
u1=time;
if (u2>0) {
len=abs(u2-u1);
//if (len>1500) {
breaths_start.push_back(time);
breaths.push_back(len);
//}
}
}
} else {
if (lastc>thresh) {
l2=l1;
l1=time;
if (l2>0) {
len=abs(l2-l1);
//if (len>1500) {
// breaths2_start.push_back(time);
// breaths2.push_back(len);
//}
}
}
}
lastc=c;
time+=200;
}
qint64 window=60000;
qint64 t1=in->first()-window/2;
qint64 t2=in->first()+window/2;
qint64 t;
EventDataType br,q;
int z=0;
int l;
QVector<int> breaths2;
QVector<qint64> breaths2_start;
int fir=0;
do {
br=0;
bool first=true;
bool cont=false;
for (int i=fir;i<breaths.size();i++) {
t=breaths_start[i];
l=breaths[i];
if (t+l < t1) continue;
if (t > t2) break;
if (first) {
first=false;
fir=i;
}
//q=1;
if (t<t1) {
// move to start of previous breath
t1=breaths_start[++i];
t2=t1+window;
fir=i;
cont=true;
break;
//q=(t+l)-t1;
//br+=(1.0/double(l))*double(q);
} else if (t+l>t2) {
q=t2-t;
br+=(1.0/double(l))*double(q);
continue;
} else
br+=1.0;
}
if (cont) continue;
breaths2.push_back(br);
breaths2_start.push_back(t1+window/2);
//out->AddEvent(t,br);
//stage2[z++]=br;
t1+=window/2.0;
t2+=window/2.0;
} while (t2<in->last());
for (int i=1;i<breaths2.size()-2;i++) {
t=breaths2_start[i];
med.clear();
for (int j=0;j<4;j++) {
med.push_back(breaths2[i+j-1]);
}
qSort(med);
br=med[2];
out->AddEvent(t,br);
}
delete [] stage2;
delete [] stage1;
}
void InitModelMap()
{

View File

@ -19,6 +19,7 @@ License: GPL
#include "resmed_loader.h"
#include "SleepLib/session.h"
#include "SleepLib/calcs.h"
extern QProgressBar *qprogress;
QHash<int,QString> RMS9ModelMap;

View File

@ -12,6 +12,7 @@
#include <QMessageBox>
#include <QMetaType>
#include <algorithm>
#include <SleepLib/calcs.h>
using namespace std;
@ -406,6 +407,12 @@ bool Session::LoadEvents(QString filename)
void Session::UpdateSummaries()
{
CalcAHIGraph ahi;
CalcRespRate calc;
ahi.calculate(this);
calc.calculate(this);
ChannelID id;
QHash<ChannelID,QVector<EventList *> >::iterator c;
for (c=eventlist.begin();c!=eventlist.end();c++) {
@ -555,6 +562,93 @@ bool Session::channelExists(ChannelID id)
return true;
}
int Session::rangeCount(ChannelID id, qint64 first,qint64 last)
{
QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
if (j==eventlist.end()) {
return 0;
}
QVector<EventList *> & evec=j.value();
int sum=0;
qint64 t;
for (int i=0;i<evec.size();i++) {
EventList & ev=*evec[i];
for (unsigned j=0;j<ev.count();j++) {
t=ev.time(j);
if ((t>=first) && (t<=last)) {
sum++;
}
}
}
return sum;
}
double Session::rangeSum(ChannelID id, qint64 first,qint64 last)
{
QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
if (j==eventlist.end()) {
return 0;
}
QVector<EventList *> & evec=j.value();
double sum=0;
qint64 t;
for (int i=0;i<evec.size();i++) {
EventList & ev=*evec[i];
for (unsigned j=0;j<ev.count();j++) {
t=ev.time(j);
if ((t>=first) && (t<=last)) {
sum+=ev.data(j);
}
}
}
return sum;
}
EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
{
QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
if (j==eventlist.end()) {
return 0;
}
QVector<EventList *> & evec=j.value();
EventDataType v,min=999999999;
qint64 t;
for (int i=0;i<evec.size();i++) {
EventList & ev=*evec[i];
for (unsigned j=0;j<ev.count();j++) {
t=ev.time(j);
if ((t>=first) && (t<=last)) {
v=ev.data(j);
if (v<min) min=v;
}
}
}
return min;
}
EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
{
QHash<ChannelID,QVector<EventList *> >::iterator j=eventlist.find(id);
if (j==eventlist.end()) {
return 0;
}
QVector<EventList *> & evec=j.value();
EventDataType v,max=-999999999;
qint64 t;
for (int i=0;i<evec.size();i++) {
EventList & ev=*evec[i];
for (unsigned j=0;j<ev.count();j++) {
t=ev.time(j);
if ((t>=first) && (t<=last)) {
v=ev.data(j);
if (v>max) max=v;
}
}
}
return max;
}
int Session::count(ChannelID id)
{
QHash<ChannelID,int>::iterator i=m_cnt.find(id);

View File

@ -107,6 +107,12 @@ public:
void setLast(ChannelID id,qint64 val) { m_lastchan[id]=val; }
int count(ChannelID id);
int rangeCount(ChannelID id, qint64 first,qint64 last);
double rangeSum(ChannelID id, qint64 first,qint64 last);
EventDataType rangeMin(ChannelID id, qint64 first,qint64 last);
EventDataType rangeMax(ChannelID id, qint64 first,qint64 last);
double sum(ChannelID id);
EventDataType avg(ChannelID id);
EventDataType wavg(ChannelID i);

View File

@ -68,7 +68,8 @@ SOURCES += main.cpp\
newprofile.cpp \
exportcsv.cpp \
common_gui.cpp \
SleepLib/loader_plugins/intellipap_loader.cpp
SleepLib/loader_plugins/intellipap_loader.cpp \
SleepLib/calcs.cpp
unix:SOURCES += qextserialport/posix_qextserialport.cpp
unix:!macx:SOURCES += qextserialport/qextserialenumerator_unix.cpp
@ -124,7 +125,8 @@ HEADERS += \
newprofile.h \
exportcsv.h \
common_gui.h \
SleepLib/loader_plugins/intellipap_loader.h
SleepLib/loader_plugins/intellipap_loader.h \
SleepLib/calcs.h
FORMS += \
@ -161,3 +163,5 @@ OTHER_FILES += \

View File

@ -179,9 +179,11 @@ Daily::Daily(QWidget *parent,gGraphView * shared, MainWindow *mw)
bool square=PROFILE["SquareWavePlots"].toBool();
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_Pressure,QColor("dark green"),square)));
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_EPAP,Qt::blue,square)));
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_IPAP,Qt::red,square)));
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_IPAPLo,Qt::red,square)));
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_IPAP,Qt::yellow,square)));
PRD->AddLayer(AddCPAP(new gLineChart(CPAP_IPAPHi,Qt::red,square)));
AHI->AddLayer(AddCPAP(new AHIChart(Qt::darkYellow)));
AHI->AddLayer(AddCPAP(new gLineChart(CPAP_AHI,Qt::darkYellow,square)));
LEAK->AddLayer(AddCPAP(new gLineChart(CPAP_Leak,Qt::darkYellow,square)));
LEAK->AddLayer(AddCPAP(new gLineChart(CPAP_MaxLeak,Qt::darkRed,square)));
SNORE->AddLayer(AddCPAP(new gLineChart(CPAP_Snore,Qt::darkGray,true)));
@ -203,6 +205,9 @@ Daily::Daily(QWidget *parent,gGraphView * shared, MainWindow *mw)
SPO2->AddLayer(AddOXI(new gLineChart(OXI_SPO2,Qt::blue,square)));
PLETHY->AddLayer(AddOXI(new gLineChart(OXI_Plethy,Qt::darkBlue,false)));
PTB->setForceMaxY(100);
SPO2->setForceMaxY(100);
INTSPO2->setForceMaxY(100);
//FRW->setRecMinY(-120);
//FRW->setRecMaxY(0);