diff --git a/Graphs/gFlagsLine.cpp b/Graphs/gFlagsLine.cpp index 8e01af21..787656ed 100644 --- a/Graphs/gFlagsLine.cpp +++ b/Graphs/gFlagsLine.cpp @@ -75,7 +75,7 @@ void gFlagsGroup::paint(gGraph &w, int left, int top, int width, int height) for (int i=0;iadd(left,linetop,left,linetop+m_barh,left+width-1,linetop+m_barh,left+width-1,linetop,*barcol); + quads->add(left, linetop, left, linetop+m_barh, left+width-1, linetop+m_barh, left+width-1, linetop, *barcol); // Paint the actual flags lvisible[i]->paint(w,left,linetop,width,m_barh); @@ -84,7 +84,7 @@ void gFlagsGroup::paint(gGraph &w, int left, int top, int width, int height) GLShortBuffer *outlines=w.lines(); QColor blk=Qt::black; - outlines->add(left-1, top, left-1, top+height,left-1, top+height, left+width,top+height, blk); + outlines->add(left-1, top, left-1, top+height, left-1, top+height, left+width,top+height, blk); outlines->add(left+width,top+height, left+width, top, left+width, top, left-1, top, blk); //lines->add(left-1, top, left-1, top+height); @@ -137,7 +137,7 @@ void gFlagsLine::paint(gGraph & w,int left, int top, int width, int height) float bartop=top+2; float bottom=top+height-2; bool verts_exceeded=false; - qint64 X,Y; + qint64 X,X2,L; m_flag_color=schema::channel[m_code].defaultColor(); for (QVector::iterator s=m_day->begin();s!=m_day->end(); s++) { if (!(*s)->enabled()) continue; @@ -148,17 +148,22 @@ void gFlagsLine::paint(gGraph & w,int left, int top, int width, int height) for (quint32 i=0;i maxx) break; x1=(X - minx) * xmult + left; if (m_flt==FT_Bar) { lines->add(x1,bartop,x1,bottom,m_flag_color); if (lines->full()) { verts_exceeded=true; break; } } else if (m_flt==FT_Span) { - x2=(Y-minx)*xmult+left; + x2=(X2-minx)*xmult+left; //w1=x2-x1; - quads->add(x1,bartop,x1,bottom,x2,bottom,x2,bartop,m_flag_color); + /*if (qAbs(x1-x2)<=1) { + x1-=1; + x2+=1; + }*/ + quads->add(x2,bartop,x1,bartop, x1,bottom,x2,bottom,m_flag_color); if (quads->full()) { verts_exceeded=true; break; } } } diff --git a/Graphs/gGraphView.cpp b/Graphs/gGraphView.cpp index 6a680cac..03be9f7f 100644 --- a/Graphs/gGraphView.cpp +++ b/Graphs/gGraphView.cpp @@ -212,13 +212,13 @@ void GLShortBuffer::add(GLshort x1, GLshort y1, GLshort x2, GLshort y2,GLshort x GLShortBuffer::GLShortBuffer(int max,int type, bool stippled) :GLBuffer(max,type,stippled) { - buffer=new GLshort [max+8]; - colors=new GLubyte[max*4+(8*4)]; + buffer=(GLshort *)calloc(sizeof(GLshort),max+8); + colors=(GLubyte *)calloc(sizeof(GLubyte),max*4+(8*4)); } GLShortBuffer::~GLShortBuffer() { - if (colors) delete [] colors; - if (buffer) delete [] buffer; + if (colors) free(colors); + if (buffer) free(buffer); } void GLShortBuffer::add(GLshort x, GLshort y,QColor & color) @@ -307,6 +307,7 @@ void GLShortBuffer::draw() { if (m_cnt>0) { bool antialias=m_forceantialias || (PROFILE.ExistsAndTrue("UseAntiAliasing") && m_antialias); + if (m_stippled) antialias=true; float size=m_size; if (antialias) { glEnable(GL_BLEND); @@ -325,7 +326,6 @@ void GLShortBuffer::draw() if (m_type==GL_LINES || m_type==GL_LINE_LOOP) { if (m_stippled) { glLineStipple(1, 0xcccc); - glEnable(GL_BLEND); glEnable(GL_LINE_STIPPLE); } else { glLineStipple(1, 0xFFFF); @@ -342,6 +342,7 @@ void GLShortBuffer::draw() glEnable(GL_SCISSOR_TEST); } + glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_SHORT, 0, buffer); if (m_colcnt>0) { @@ -350,13 +351,12 @@ void GLShortBuffer::draw() } else { glColor4ub(m_color.red(),m_color.green(),m_color.blue(),m_color.alpha()); } - glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(m_type, 0, m_cnt >> 1); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); + // glDisableClientState(GL_COLOR_ARRAY); if (m_colcnt>0) { glDisableClientState(GL_COLOR_ARRAY); } + glDisableClientState(GL_VERTEX_ARRAY); //qDebug() << "I Drawed" << m_cnt << "vertices"; m_cnt=0; @@ -368,10 +368,11 @@ void GLShortBuffer::draw() if (m_type==GL_POLYGON) { glPolygonMode(GL_BACK,GL_FILL); } - if (m_stippled) { - glDisable(GL_BLEND); - glDisable(GL_LINE_STIPPLE); - glLineStipple(1, 0xFFFF); + if (m_type==GL_LINES || m_type==GL_LINE_LOOP) { + if (m_stippled) { + glDisable(GL_LINE_STIPPLE); + glLineStipple(1, 0xFFFF); + } } if (antialias) { diff --git a/Graphs/gLineOverlay.cpp b/Graphs/gLineOverlay.cpp index edc8e529..b841efd8 100644 --- a/Graphs/gLineOverlay.cpp +++ b/Graphs/gLineOverlay.cpp @@ -92,7 +92,7 @@ void gLineOverlayBar::paint(gGraph & w, int left, int topp, int width, int heigh if (x2width+left) x1=width+left; //double w1=x2-x1; - quads->add(x1,start_py,x2,start_py,x2,start_py+height,x1,start_py+height,m_flag_color); + quads->add(x2,start_py, x1,start_py, x1,start_py+height, x2,start_py+height,m_flag_color); if (quads->full()) { verts_exceeded=true; break; } } else if (m_flt==FT_Dot) { if ((PROFILE.appearance->overlayType()==ODT_Bars) || (xx<3600000)) { diff --git a/SleepLib/day.cpp b/SleepLib/day.cpp index fa7a2ea2..1d8ef4ee 100644 --- a/SleepLib/day.cpp +++ b/SleepLib/day.cpp @@ -515,6 +515,10 @@ bool Day::channelHasData(ChannelID id) r=true; break; } + if (sessions[i]->m_valuesummary.contains(id)) { + r=true; + break; + } } return r; } diff --git a/SleepLib/loader_plugins/intellipap_loader.cpp b/SleepLib/loader_plugins/intellipap_loader.cpp index c4665a91..c955364d 100644 --- a/SleepLib/loader_plugins/intellipap_loader.cpp +++ b/SleepLib/loader_plugins/intellipap_loader.cpp @@ -95,15 +95,15 @@ int IntellipapLoader::Open(QString & path,Profile *profile) //lookup["Se"]="Se"; //05 //lookup["Si"]="Si"; //05 //lookup["Mi"]="Mi"; //0 - //lookup["Uh"]="Uh"; //0000.0 - //lookup["Up"]="Up"; //0000.0 + lookup["Uh"]="HoursMeter"; //0000.0 + lookup["Up"]="ComplianceMeter"; //0000.0 //lookup["Er"]="ErrorCode"; // E00 //lookup["El"]="LastErrorCode"; // E00 00/00/0000 //lookup["Hp"]="Hp"; //1 //lookup["Hs"]="Hs"; //02 //lookup["Lu"]="LowUseThreshold"; // defaults to 0 (4 hours) lookup["Sf"]="SmartFlex"; - //lookup["Sm"]="SmartFlexMode"; + lookup["Sm"]="SmartFlexMode"; lookup["Ks=s"]="Ks_s"; lookup["Ks=i"]="Ks_i"; @@ -203,8 +203,8 @@ int IntellipapLoader::Open(QString & path,Profile *profile) sess->AddEventList(CPAP_EPAP,EVL_Event); sess->AddEventList(CPAP_Pressure,EVL_Event); - sess->AddEventList(CPAP_Te,EVL_Event); - sess->AddEventList(CPAP_Ti,EVL_Event); + sess->AddEventList(INTELLIPAP_Unknown1,EVL_Event); + sess->AddEventList(INTELLIPAP_Unknown2,EVL_Event); sess->AddEventList(CPAP_LeakTotal,EVL_Event); sess->AddEventList(CPAP_MaxLeak,EVL_Event); @@ -320,6 +320,15 @@ int IntellipapLoader::Open(QString & path,Profile *profile) quint64 first=qint64(sid)*1000L; quint64 last=qint64(SessionEnd[i])*1000L; + sess->settings[CPAP_PresReliefType]=(PRTypes)PR_SMARTFLEX; + sess->settings[CPAP_PresReliefSet]=set1["SmartFlex"].toInt(); + int sfm=set1["SmartFlexMode"].toInt(); + if (sfm==0) { + sess->settings[CPAP_PresReliefMode]=PM_FullTime; + } else { + sess->settings[CPAP_PresReliefMode]=PM_RampOnly; + } + EventDataType max=sess->Max(CPAP_IPAP); EventDataType min=sess->Min(CPAP_EPAP); EventDataType pres=sess->Min(CPAP_Pressure); diff --git a/SleepLib/loader_plugins/intellipap_loader.h b/SleepLib/loader_plugins/intellipap_loader.h index 9a3e822c..0d5242db 100644 --- a/SleepLib/loader_plugins/intellipap_loader.h +++ b/SleepLib/loader_plugins/intellipap_loader.h @@ -18,7 +18,7 @@ //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // -const int intellipap_data_version=1; +const int intellipap_data_version=2; // //******************************************************************************************** diff --git a/SleepLib/loader_plugins/prs1_loader.cpp b/SleepLib/loader_plugins/prs1_loader.cpp index affebcb5..ea21ba89 100644 --- a/SleepLib/loader_plugins/prs1_loader.cpp +++ b/SleepLib/loader_plugins/prs1_loader.cpp @@ -380,8 +380,8 @@ int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile) //sess->summaryCPAP_Mode]!=MODE_ASV) sess->settings[CPAP_Mode]=MODE_BIPAP; - if (sess->settings[PRS1_FlexMode].toInt()!=PR_NONE) { - sess->settings[PRS1_FlexMode]=PR_BIFLEX; + if (sess->settings[CPAP_PresReliefType].toInt()!=PR_NONE) { + sess->settings[CPAP_PresReliefType]=PR_BIFLEX; } sess->setAvg(CPAP_Pressure,(sess->avg(CPAP_EPAP)+sess->avg(CPAP_IPAP))/2.0); @@ -476,10 +476,13 @@ bool PRS1Loader::ParseSummary(Machine *mach, qint32 sequence, quint32 timestamp, // This is incorrect.. if (data[offset+0x08] & 0x80) { // Flex Setting if (data[offset+0x08] & 0x08) { - if (max>0) session->settings[PRS1_FlexMode]=(int)PR_AFLEX; - else session->settings[PRS1_FlexMode]=(int)PR_CFLEXPLUS; - } else session->settings[PRS1_FlexMode]=(int)PR_CFLEX; - } else session->settings[PRS1_FlexMode]=(int)PR_NONE; + if (max>0) session->settings[CPAP_PresReliefType]=(int)PR_AFLEX; + else session->settings[CPAP_PresReliefType]=(int)PR_CFLEXPLUS; + } else session->settings[CPAP_PresReliefType]=(int)PR_CFLEX; + } else session->settings[CPAP_PresReliefType]=(int)PR_NONE; + + session->settings[CPAP_PresReliefMode]=(int)PM_FullTime; // only has one mode + session->settings[PRS1_FlexSet]=(int)(data[offset+0x08] & 3); session->settings[PRS1_HumidSetting]=(int)data[offset+0x09]&0x0f; diff --git a/SleepLib/loader_plugins/prs1_loader.h b/SleepLib/loader_plugins/prs1_loader.h index b132bf7c..7d4b4926 100644 --- a/SleepLib/loader_plugins/prs1_loader.h +++ b/SleepLib/loader_plugins/prs1_loader.h @@ -21,7 +21,7 @@ License: GPL //******************************************************************************************** // Please INCREMENT the following value when making changes to this loaders implementation. // -const int prs1_data_version=8; +const int prs1_data_version=9; // //******************************************************************************************** diff --git a/SleepLib/loader_plugins/resmed_loader.cpp b/SleepLib/loader_plugins/resmed_loader.cpp index 8e92b725..d4717bf4 100644 --- a/SleepLib/loader_plugins/resmed_loader.cpp +++ b/SleepLib/loader_plugins/resmed_loader.cpp @@ -253,89 +253,255 @@ long event_cnt=0; int ResmedLoader::Open(QString & path,Profile *profile) { + const QString datalog="DATALOG"; + const QString idfile="Identification."; + const QString strfile="STR."; + + const QString ext_TGT="tgt"; + const QString ext_CRC="crc"; + const QString ext_EDF="edf"; + + QString serial; // Serial number + QString key,value; + QString line; QString newpath; + QString filename; - QString dirtag="DATALOG"; - if (path.endsWith(QDir::separator()+dirtag)) { - return 0; - //newpath=path; + QHash idmap; // Temporary properties hash + + // Strip off end "/" if any + if (path.endsWith("/")) + path=path.section("/",0,-2); + + // Strip off DATALOG from path, and set newpath to the path contianing DATALOG + if (path.endsWith(datalog)) { + newpath=path+"/"; + path=path.section("/",0,-2); } else { - newpath=path+QDir::separator()+dirtag; + newpath=path+"/"+datalog+"/"; } - if (!QDir().exists(newpath)) return 0; - QString idfile=path+QDir::separator()+"Identification.tgt"; - QFile f(idfile); - if (!f.exists()) return 0; - QHash idmap; - if (f.open(QIODevice::ReadOnly)) { - if (!f.isReadable()) - return 0; + // Add separator back + path+="/"; - while (!f.atEnd()) { - QString line=f.readLine().trimmed(); - QString key,value; - if (!line.isEmpty()) { - key=line.section(" ",0,0); - value=line.section(" ",1); - key=key.section("#",1); - idmap[key]=value; + // Check DATALOG folder exists and is readable + if (!QDir().exists(newpath)) + return 0; + + /////////////////////////////////////////////////////////////////////////////////// + // Parse Identification.tgt file (containing serial number and machine information) + /////////////////////////////////////////////////////////////////////////////////// + filename=path+idfile+ext_TGT; + QFile f(filename); + // Abort if this file is dodgy.. + if (!f.exists() || !f.open(QIODevice::ReadOnly)) + return 0; + + // Parse # entries into idmap. + while (!f.atEnd()) { + line=f.readLine().trimmed(); + if (!line.isEmpty()) { + key=line.section(" ",0,0); + value=line.section(" ",1); + key=key.section("#",1); + if (key=="SRN") { + key=STR_PROP_Serial; + serial=value; } + idmap[key]=value; } } - QString strfile=path+QDir::separator()+"STR.edf"; - EDFParser stredf(strfile); + f.close(); + // Abort if no serial number + if (serial.isEmpty()) { + qDebug() << "S9 Data card has no valid serial number in Indentification.tgt"; + return 0; + } + + // Early check for STR.edf file, so we can early exit before creating faulty machine record. + QString strpath=path+strfile+ext_EDF; // STR.edf file + f.setFileName(strpath); + if (!f.exists()) { + qDebug() << "Missing STR.edf file"; + return 0; + } + + /////////////////////////////////////////////////////////////////////////////////// + // Create machine object (unless it's already registered) + /////////////////////////////////////////////////////////////////////////////////// + Machine *m=CreateMachine(serial,profile); + + /////////////////////////////////////////////////////////////////////////////////// + // Parse the idmap into machine objects properties, (overwriting any old values) + /////////////////////////////////////////////////////////////////////////////////// + for (QHash::iterator i=idmap.begin();i!=idmap.end();i++) { + m->properties[i.key()]=i.value(); + + if (i.key()=="PCD") { // Lookup Product Code for real model string + bool ok; + int j=i.value().toInt(); + if (ok) m->properties[STR_PROP_Model]=RMS9ModelMap[j]; + } + } + + /////////////////////////////////////////////////////////////////////////////////// + // Open and Parse STR.edf file + /////////////////////////////////////////////////////////////////////////////////// + EDFParser stredf(strpath); if (!stredf.Parse()) { qDebug() << "Faulty file" << strfile; return 0; } + if (stredf.serialnumber!=serial) { + qDebug() << "Identification.tgt Serial number doesn't match STR.edf!"; + } + + + // Creating early as we need the object + QDir dir(newpath); + + /////////////////////////////////////////////////////////////////////////////////// + // Create the backup folder for storing a copy of everything in.. + /////////////////////////////////////////////////////////////////////////////////// + QString backup_path=PROFILE.Get(m->properties[STR_PROP_Path])+"Backup/"; + if (!dir.exists(backup_path)) { + if (!dir.mkpath(backup_path+datalog)) { + qDebug() << "Could not create S9 backup directory :-/"; + } + } + + // Copy Identification files to backup folder + QFile::copy(path+idfile+ext_TGT,backup_path+idfile+ext_TGT); + QFile::copy(path+idfile+ext_CRC,backup_path+idfile+ext_CRC); + + // Copy STR files to backup folder + QFile::copy(strpath,backup_path+strfile+ext_EDF); + QFile::copy(path+strfile+ext_CRC,backup_path+strfile+ext_CRC); + + /////////////////////////////////////////////////////////////////////////////////// + // Process the actual STR.edf data + /////////////////////////////////////////////////////////////////////////////////// qint64 duration=stredf.GetNumDataRecords()*stredf.GetDuration(); int days=duration/86400000L; - //QDateTime dt1=QDateTime::fromTime_t(stredf.startdate/1000L); - //QDateTime dt2=QDateTime::fromTime_t(stredf.enddate/1000L); - //QDate dd1=dt1.date(); - //QDate dd2=dt2.date(); - for (int s=0;snr*stredf.GetNumDataRecords(); + QDateTime dt1=QDateTime::fromTime_t(stredf.startdate/1000L); + QDateTime dt2=QDateTime::fromTime_t(stredf.enddate/1000L); + QDate dd1=dt1.date(); + QDate dd2=dt2.date(); - qDebug() << "STREDF:" << stredf.edfsignals[s]->label << recs; + for (int s=0;s dayused; + dayused.resize(days); + QList strfirst; + QList strlast; + QList strday; + QList dayfoo; - if ((!dir.exists() || !dir.isReadable())) - return 0; + QHash > daystarttimes; + QHash > dayendtimes; + qint16 on,off; + qint16 o1[10],o2[10]; + time_t st,et; + time_t time=stredf.startdate/1000L; // == 12pm on first day + for (int i=0;idata[j+k]; + off=maskoff->data[j+k]; + o1[k]=on; + o2[k]=off; + if (on >= 0) ckon++; + if (off >= 0) ckoff++; + } + + // set to true if day starts with machine running + int offset=ckoff-ckon; + dayfoo.push_back(offset>0); + + st=0,et=0; + time_t l,f; + + // Find the Min & Max times for this day + for (int k=0;k f)) st=f; + if (!et || (et < l)) et=l; + } + strfirst.push_back(st); + strlast.push_back(et); + strday.push_back(i); + dayused[i]=ckon; + time+=86400; + } + + // reset time to first day + time=stredf.startdate/1000; + + /////////////////////////////////////////////////////////////////////////////////// + // Open DATALOG file and build list of session files + /////////////////////////////////////////////////////////////////////////////////// - qDebug() << "ResmedLoader::Open newpath=" << newpath; dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist=dir.entryInfoList(); - QMap > sessfiles; QString ext,rest,datestr;//,s,codestr; SessionID sessionid; QDateTime date; - QString filename; int size=flist.size(); + + sessfiles.clear(); + + // For each file in filelist... for (int i=0;isetValue((float(i+1)/float(size)*33.0)); + // Push current filename to sanitized by-session list + sessfiles[sessionid].push_back(rest); + + // Update the progress bar + if (qprogress) qprogress->setValue((float(i+1)/float(size)*10.0)); QApplication::processEvents(); - } - Machine *m=NULL; - QString fn; Session *sess; int cnt=0; size=sessfiles.size(); + + QHash sessday; + + + ///////////////////////////////////////////////////////////////////////////// + // Scan over file list and knock out of dayused list + ///////////////////////////////////////////////////////////////////////////// for (QMap >::iterator si=sessfiles.begin();si!=sessfiles.end();si++) { sessionid=si.key(); - //qDebug() << "Parsing Session " << sessionid; - bool done=false; - bool first=true; - sess=NULL; - for (int i=0;i=st) { + if (sessionid<(et+300)) { + dn=j; + break; + } + } + } + if (dn>=0) { + dayused[dn]=0; + } + } + + EDFSignal *sig; + + ///////////////////////////////////////////////////////////////////////////// + // For all days not in session lists, (to get at days without data records) + ///////////////////////////////////////////////////////////////////////////// + for (int dn=0;dnSessionExists(st)) + continue; + + et=dayendtimes[dn].at(j); + + // Create session + sess=new Session(m,st); + sess->really_set_first(qint64(st)*1000L); + sess->really_set_last(qint64(et)*1000L); + + ///////////////////////////////////////////////////////////////////// + // CPAP Mode + ///////////////////////////////////////////////////////////////////// + int mode; + sig=stredf.lookupSignal(CPAP_Mode); + if (sig) { + mode=sig->data[dn]; + } else mode=0; + + + ///////////////////////////////////////////////////////////////////// + // EPR Settings + ///////////////////////////////////////////////////////////////////// + sess->settings[CPAP_PresReliefType]=PR_EPR; + + // Note: AutoSV machines don't have both fields + sig=stredf.lookupSignal(RMS9_EPR); + if (sig) { + int i=sig->data[dn]; + sess->settings[CPAP_PresReliefMode]=i; + } + sig=stredf.lookupSignal(RMS9_EPRSet); + if (sig) { + sess->settings[CPAP_PresReliefSet]=sig->data[dn]; + } + + + ///////////////////////////////////////////////////////////////////// + // Set Min & Max pressures depending on CPAP mode + ///////////////////////////////////////////////////////////////////// + if (mode==0) { + sess->settings[CPAP_Mode]=MODE_CPAP; + sig=stredf.lookupSignal(RMS9_SetPressure); // ?? What's meant by Set Pressure? + if (sig) { + EventDataType pressure=sig->data[dn]*sig->gain; + sess->settings[CPAP_PressureMin]=pressure; + } + } else { // VPAP or Auto + if (mode>5) { + sess->settings[CPAP_Mode]=MODE_BIPAP; + } else { + sess->settings[CPAP_Mode]=MODE_APAP; + + } + sig=stredf.lookupSignal(CPAP_PressureMin); + if (sig) { + EventDataType pressure=sig->data[dn]*sig->gain; + sess->settings[CPAP_PressureMin]=pressure; + sess->setMin(CPAP_Pressure,pressure); + } + sig=stredf.lookupSignal(CPAP_PressureMax); + if (sig) { + EventDataType pressure=sig->data[dn]*sig->gain; + sess->settings[CPAP_PressureMax]=pressure; + sess->setMax(CPAP_Pressure,pressure); + } + } + + sess->SetChanged(true); + m->AddSession(sess,profile); + + } + // Add the remainder to the last session + EventDataType tmp,dur; + if (sess) { + EventDataType valmed=0,valmax=0,val95=0; + + if (stredf.lookup.contains("Leak Med")) { + sig=stredf.lookup["Leak Med"]; + valmed=sig->data[dn]; + sess->setMedian(CPAP_Leak,valmed*sig->gain*60.0); + sess->m_gain[CPAP_Leak]=sig->gain*60.0; + sess->m_valuesummary[CPAP_Leak][valmed]=50; + } + if (stredf.lookup.contains("Leak 95")) { + sig=stredf.lookup["Leak 95"]; + val95=sig->data[dn]; + sess->set95p(CPAP_Leak,val95*sig->gain*60.0); + sess->m_valuesummary[CPAP_Leak][val95]=45; + } + if (stredf.lookup.contains("Leak Max")) { + sig=stredf.lookup["Leak Max"]; + valmax=sig->data[dn]; + sess->setMax(CPAP_Leak,valmax*sig->gain*60.0); + sess->m_valuesummary[CPAP_Leak][valmax]=5; + } + + if (stredf.lookup.contains("Mask Pres Med")) { + sig=stredf.lookup["Mask Pres Med"]; + valmed=sig->data[dn]; + sess->setMedian(CPAP_Pressure,valmed*sig->gain); + sess->m_gain[CPAP_Pressure]=sig->gain; + sess->m_valuesummary[CPAP_Pressure][valmed]=50; + } + if (stredf.lookup.contains("Mask Pres 95")) { + sig=stredf.lookup["Mask Pres 95"]; + val95=sig->data[dn]; + sess->set95p(CPAP_Pressure,val95*sig->gain); + sess->m_valuesummary[CPAP_Pressure][val95]=45; + } + if (stredf.lookup.contains("Mask Pres Max")) { + sig=stredf.lookup["Mask Pres Max"]; + valmax=sig->data[dn]; + sess->setMax(CPAP_Pressure,valmax*sig->gain); + sess->m_valuesummary[CPAP_Pressure][valmax]=5; + } + + + if (stredf.lookup.contains("Mask Dur")) { + sig=stredf.lookup["Mask Dur"]; + dur=sig->data[dn]*sig->gain; + } + if (stredf.lookup.contains("OAI")) { + sig=stredf.lookup["OAI"]; + tmp=sig->data[dn]*sig->gain; + sess->setCph(CPAP_Obstructive,tmp); + sess->setCount(CPAP_Obstructive,tmp*(dur/60.0)); + } + if (stredf.lookup.contains("HI")) { + sig=stredf.lookup["HI"]; + tmp=sig->data[dn]*sig->gain; + sess->setCph(CPAP_Hypopnea,tmp); + sess->setCount(CPAP_Hypopnea,tmp*(dur/60.0)); + } + if (stredf.lookup.contains("UAI")) { + sig=stredf.lookup["UAI"]; + tmp=sig->data[dn]*sig->gain; + sess->setCph(CPAP_Apnea,tmp); + sess->setCount(CPAP_Apnea,tmp*(dur/60.0)); + } + if (stredf.lookup.contains("CAI")) { + sig=stredf.lookup["CAI"]; + tmp=sig->data[dn]*sig->gain; + sess->setCph(CPAP_ClearAirway,tmp); + sess->setCount(CPAP_ClearAirway,tmp*(dur/60.0)); + } + + + + } + + } + + ///////////////////////////////////////////////////////////////////////////// + // Scan through new file list and import sessions + ///////////////////////////////////////////////////////////////////////////// + for (QMap >::iterator si=sessfiles.begin();si!=sessfiles.end();si++) { + sessionid=si.key(); + + // Skip file if already imported + if (m->SessionExists(sessionid)) + continue; + + // Create the session + sess=new Session(m,sessionid); + + // Process EDF File List + for (int i=0;i::iterator i=idmap.begin();i!=idmap.end();i++) { - if (i.key()=="SRN") { - if (edf.serialnumber!=i.value()) { - qDebug() << "edf Serial number doesn't match Identification.tgt"; - } - if (edf.serialnumber!=stredf.serialnumber) { - qDebug() << "edf Serial number doesn't match STR.edf!"; - } - } else if (i.key()=="PNA") { - //m->properties[STR_PROP_Model]=""; //i.value(); - } else if (i.key()=="PCD") { // Product Code.. - bool ok; - int j=i.value().toInt(&ok); - if (RMS9ModelMap.find(j)!=RMS9ModelMap.end()) { - m->properties[STR_PROP_Model]=RMS9ModelMap[j]; - } - } else { - m->properties[i.key()]=i.value(); - } - } - if (m->SessionExists(sessionid)) { - done=true; - break; - } - sess=new Session(m,sessionid); - first=false; + // Give a warning if doesn't match the machine serial number in Identification.tgt + if (edf.serialnumber!=serial) { + qDebug() << "edf Serial number doesn't match Identification.tgt"; } - if (!done) { - if (fn=="eve.edf") LoadEVE(sess,edf); - else if (fn=="pld.edf") LoadPLD(sess,edf); - else if (fn=="brp.edf") LoadBRP(sess,edf); - else if (fn=="sad.edf") LoadSAD(sess,edf); - //if (first) { - //first=false; - //} - } + fn=fullpath.section("_",-1).toLower(); + + if (fn=="eve.edf") LoadEVE(sess,edf); + else if (fn=="pld.edf") LoadPLD(sess,edf); + else if (fn=="brp.edf") LoadBRP(sess,edf); + else if (fn=="sad.edf") LoadSAD(sess,edf); } - if (qprogress) qprogress->setValue(33.0+(float(++cnt)/float(size)*66.0)); + if (qprogress) qprogress->setValue(10.0+(float(++cnt)/float(size)*90.0)); QApplication::processEvents(); - EDFSignal *sig; if (!sess) continue; if (!sess->first()) { delete sess; @@ -428,15 +783,19 @@ int ResmedLoader::Open(QString & path,Profile *profile) mode=sig->data[dn]; } else mode=0; + sess->settings[CPAP_PresReliefType]=PR_EPR; + // AutoSV machines don't have both fields sig=stredf.lookupSignal(RMS9_EPR); if (sig) { - sess->settings[RMS9_EPR]=sig->data[dn]; + int i=sig->data[dn]; + sess->settings[CPAP_PresReliefMode]=i; + } sig=stredf.lookupSignal(RMS9_EPRSet); if (sig) { - sess->settings[RMS9_EPRSet]=sig->data[dn]; + sess->settings[CPAP_PresReliefSet]=sig->data[dn]; } if (mode==0) { @@ -494,7 +853,7 @@ int ResmedLoader::Open(QString & path,Profile *profile) //Rather than take a dodgy guess, EPR settings can take a hit, and this data can simply be missed.. // Add the session to the machine & profile objects - m->AddSession(sess,profile); // Adding earlier than I really like here.. + m->AddSession(sess,profile); } } diff --git a/SleepLib/loader_plugins/resmed_loader.h b/SleepLib/loader_plugins/resmed_loader.h index 54eb591f..c52f1058 100644 --- a/SleepLib/loader_plugins/resmed_loader.h +++ b/SleepLib/loader_plugins/resmed_loader.h @@ -210,6 +210,9 @@ protected: //! \brief Parse the PRD low resolution data, and save to Session * sess //! This contains the Pressure, Leak, Respiratory Rate, Minute Ventilation, Tidal Volume, etc.. bool LoadPLD(Session *sess,EDFParser &edf); + + QMap > sessfiles; + }; #endif // RESMED_LOADER_H diff --git a/SleepLib/machine.cpp b/SleepLib/machine.cpp index 388b5c16..6283235c 100644 --- a/SleepLib/machine.cpp +++ b/SleepLib/machine.cpp @@ -62,15 +62,44 @@ Session *Machine::SessionExists(SessionID session) } } -Day *Machine::AddSession(Session *s,Profile *p) +// Find date this session belongs in +QDate Machine::pickDate(qint64 first) +{ + QTime split_time=PROFILE.session->daySplitTime(); + int combine_sessions=PROFILE.session->combineCloseSessions(); + + QDateTime d2=QDateTime::fromTime_t(first/1000); + + QDate date=d2.date(); + QTime time=d2.time(); + + int closest_session=0; + + if (time 0) { + QMap::iterator dit=day.find(date.addDays(-1)); // Check Day Before + if (dit != day.end()) { + QDateTime lt=QDateTime::fromTime_t(dit.value()->last()/1000L); + closest_session=lt.secsTo(d2)/60; + if (closest_session < combine_sessions) { + date=date.addDays(-1); + } + } + } + + return date; +} + +QDate Machine::AddSession(Session *s,Profile *p) { if (!s) { qWarning() << "Empty Session in Machine::AddSession()"; - return NULL; + return QDate(); } if (!p) { qWarning() << "Empty Profile in Machine::AddSession()"; - return NULL; + return QDate(); } if (s->session()>highest_sessionid) highest_sessionid=s->session(); @@ -92,7 +121,6 @@ Day *Machine::AddSession(Session *s,Profile *p) QMap::iterator dit,nextday; - bool combine_next_day=false; int closest_session=0; @@ -121,7 +149,7 @@ Day *Machine::AddSession(Session *s,Profile *p) if (session_length=60)) - return NULL; + return QDate(); } if (!firstsession) { @@ -159,7 +187,7 @@ Day *Machine::AddSession(Session *s,Profile *p) } day.erase(nextday); } - return dd; + return date; } // This functions purpose is murder and mayhem... It deletes all of a machines data. @@ -421,10 +449,14 @@ CPAP_ClearAirway, CPAP_Apnea, CPAP_CSR, CPAP_LeakFlag, CPAP_ExP, CPAP_NRI, CPAP_ CPAP_RERA, CPAP_PressurePulse, CPAP_FlowLimit, CPAP_FlowRate, CPAP_MaskPressure, CPAP_MaskPressureHi, CPAP_RespEvent, CPAP_Snore, CPAP_MinuteVent, CPAP_RespRate, CPAP_TidalVolume, CPAP_PTB, CPAP_Leak, CPAP_LeakMedian, CPAP_LeakTotal, CPAP_MaxLeak, CPAP_FLG, CPAP_IE, CPAP_Te, CPAP_Ti, CPAP_TgMV, -CPAP_UserFlag1, CPAP_UserFlag2, CPAP_BrokenSummary, CPAP_BrokenWaveform, CPAP_RDI; +CPAP_UserFlag1, CPAP_UserFlag2, CPAP_BrokenSummary, CPAP_BrokenWaveform, CPAP_RDI, +CPAP_PresReliefSet, CPAP_PresReliefMode, CPAP_PresReliefType; + ChannelID RMS9_E01, RMS9_E02, RMS9_EPR, RMS9_EPRSet, RMS9_SetPressure; ChannelID INTP_SmartFlex; +ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2; + ChannelID PRS1_00, PRS1_01, PRS1_08, PRS1_0A, PRS1_0B, PRS1_0C, PRS1_0E, PRS1_0F, PRS1_10, PRS1_12, PRS1_FlexMode, PRS1_FlexSet, PRS1_HumidStatus, PRS1_HumidSetting, PRS1_SysLock, PRS1_SysOneResistStat, PRS1_SysOneResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI; diff --git a/SleepLib/machine.h b/SleepLib/machine.h index b046d491..ea01a37c 100644 --- a/SleepLib/machine.h +++ b/SleepLib/machine.h @@ -95,7 +95,10 @@ public: Session * SessionExists(SessionID session); //! \brief Adds the session to this machine object, and the Master Profile list. (used during load) - Day *AddSession(Session *s,Profile *p); + QDate AddSession(Session *s,Profile *p); + + //! \brief Find the date this session belongs in, according to profile settings + QDate pickDate(qint64 start); //! \brief Sets the Class of machine (Used to reference the particular loader that created it) void SetClass(QString t) { m_class=t; } diff --git a/SleepLib/machine_common.h b/SleepLib/machine_common.h index 6af49cfa..74842437 100644 --- a/SleepLib/machine_common.h +++ b/SleepLib/machine_common.h @@ -63,6 +63,10 @@ enum PRTypes//:short { PR_UNKNOWN=0,PR_NONE,PR_CFLEX,PR_CFLEXPLUS,PR_AFLEX,PR_BIFLEX,PR_EPR,PR_SMARTFLEX }; +enum PRModes//:short +{ + PM_UNKNOWN=0,PM_RampOnly,PM_FullTime +}; //extern map DefaultMCShortNames; @@ -85,7 +89,8 @@ CPAP_ClearAirway, CPAP_Apnea, CPAP_CSR, CPAP_LeakFlag, CPAP_ExP, CPAP_NRI, CPAP_ CPAP_RERA, CPAP_PressurePulse, CPAP_FlowLimit, CPAP_FlowRate, CPAP_MaskPressure, CPAP_MaskPressureHi, CPAP_RespEvent, CPAP_Snore, CPAP_MinuteVent, CPAP_RespRate, CPAP_TidalVolume, CPAP_PTB, CPAP_Leak, CPAP_LeakMedian, CPAP_LeakTotal, CPAP_MaxLeak, CPAP_FLG, CPAP_IE, CPAP_Te, CPAP_Ti, CPAP_TgMV, -CPAP_UserFlag1, CPAP_UserFlag2, CPAP_BrokenSummary, CPAP_BrokenWaveform, CPAP_RDI; +CPAP_UserFlag1, CPAP_UserFlag2, CPAP_BrokenSummary, CPAP_BrokenWaveform, CPAP_RDI, +CPAP_PresReliefSet, CPAP_PresReliefMode, CPAP_PresReliefType; extern ChannelID RMS9_E01, RMS9_E02, RMS9_EPR, RMS9_EPRSet, RMS9_SetPressure; extern ChannelID INTP_SmartFlex; @@ -93,6 +98,8 @@ extern ChannelID PRS1_00, PRS1_01, PRS1_08, PRS1_0A, PRS1_0B, PRS1_0C, PRS1_0E, PRS1_FlexMode, PRS1_FlexSet, PRS1_HumidStatus, PRS1_HumidSetting, PRS1_SysLock, PRS1_SysOneResistStat, PRS1_SysOneResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI; +extern ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2; + extern ChannelID OXI_Pulse, OXI_SPO2, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy; extern ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, Bookmark_Start, Bookmark_End, Bookmark_Notes; diff --git a/SleepLib/schema.cpp b/SleepLib/schema.cpp index 26482e5a..230a3d5a 100644 --- a/SleepLib/schema.cpp +++ b/SleepLib/schema.cpp @@ -106,6 +106,11 @@ void init() CPAP_Te=schema::channel["Te"].id(); CPAP_Ti=schema::channel["Ti"].id(); CPAP_TgMV=schema::channel["TgMV"].id(); + + CPAP_PresReliefSet=schema::channel["PresRelSet"].id(); + CPAP_PresReliefMode=schema::channel["PresRelMode"].id(); + CPAP_PresReliefType=schema::channel["PresRelType"].id(); + CPAP_UserFlag1=schema::channel["UserFlag1"].id(); CPAP_UserFlag2=schema::channel["UserFlag2"].id(); RMS9_E01=schema::channel["RMS9_E01"].id(); @@ -135,6 +140,8 @@ void init() PRS1_AutoOff=schema::channel["AutoOff"].id(); PRS1_MaskAlert=schema::channel["MaskAlert"].id(); PRS1_ShowAHI=schema::channel["ShowAHI"].id(); + INTELLIPAP_Unknown1=schema::channel["IntUnk1"].id(); + INTELLIPAP_Unknown2=schema::channel["IntUnk2"].id(); OXI_Pulse=schema::channel["Pulse"].id(); OXI_SPO2=schema::channel["SPO2"].id(); OXI_PulseChange=schema::channel["PulseChange"].id(); diff --git a/SleepLib/session.cpp b/SleepLib/session.cpp index 01d00f48..20511da7 100644 --- a/SleepLib/session.cpp +++ b/SleepLib/session.cpp @@ -22,7 +22,7 @@ const quint16 filetype_data=1; // This is the uber important database version for SleepyHeads internal storage // Increment this after stuffing with Session's save & load code. -const quint16 summary_version=9; +const quint16 summary_version=10; const quint16 events_version=8; Session::Session(Machine * m,SessionID session) @@ -139,6 +139,8 @@ bool Session::StoreSummary(QString filename) out << m_avg; out << m_wavg; out << m_90p; + out << m_95p; + out << m_med; out << m_min; out << m_max; out << m_cph; @@ -299,6 +301,10 @@ bool Session::LoadSummary(QString filename) in >> m_avg; in >> m_wavg; in >> m_90p; + if (version >= 10) { + in >> m_95p; + in >> m_med; + } in >> m_min; in >> m_max; in >> m_cph; @@ -613,22 +619,24 @@ void Session::UpdateSummaries() Min(id); Max(id); count(id); + last(id); + first(id); if ((id==CPAP_FlowRate) || (id==CPAP_MaskPressure)) continue; cph(id); sph(id); avg(id); wavg(id); - p90(id); - last(id); - first(id); +// p90(id); +// p95(id); +// median(id); } } - if (channelExists(CPAP_Obstructive)) { + /*if (channelExists(CPAP_Obstructive)) { setCph(CPAP_AHI,cph(CPAP_Obstructive)+cph(CPAP_Hypopnea)+cph(CPAP_ClearAirway)); setSph(CPAP_AHI,sph(CPAP_Obstructive)+sph(CPAP_Hypopnea)+sph(CPAP_ClearAirway)); - } + }*/ } @@ -994,6 +1002,41 @@ EventDataType Session::p90(ChannelID id) // 90th Percentile m_90p[id]=val; return val; } + +EventDataType Session::p95(ChannelID id) +{ + QHash::iterator i=m_95p.find(id); + if (i!=m_95p.end()) + return i.value(); + + if (!eventlist.contains(id)) { + m_95p[id]=0; + return 0; + } + + EventDataType val=percentile(id,0.95); + m_95p[id]=val; + return val; + +} + +EventDataType Session::median(ChannelID id) +{ + QHash::iterator i=m_med.find(id); + if (i!=m_med.end()) + return i.value(); + + if (!eventlist.contains(id)) { + m_med[id]=0; + return 0; + } + + EventDataType val=percentile(id,0.5); + m_med[id]=val; + return val; +} + + bool sortfunction (EventStoreType i,EventStoreType j) { return (i m_avg; QHash m_wavg; QHash m_90p; + QHash m_95p; + QHash m_med; QHash m_min; QHash m_max; QHash m_cph; // Counts per hour (eg AHI) @@ -161,7 +163,9 @@ public: void setMax(ChannelID id,EventDataType val) { m_max[id]=val; } void setAvg(ChannelID id,EventDataType val) { m_avg[id]=val; } void setWavg(ChannelID id,EventDataType val) { m_wavg[id]=val; } + void setMedian(ChannelID id,EventDataType val) { m_med[id]=val; } void set90p(ChannelID id,EventDataType val) { m_90p[id]=val; } + void set95p(ChannelID id,EventDataType val) { m_95p[id]=val; } void setCph(ChannelID id,EventDataType val) { m_cph[id]=val; } void setSph(ChannelID id,EventDataType val) { m_sph[id]=val; } void setFirst(ChannelID id,qint64 val) { m_firstchan[id]=val; } @@ -200,6 +204,12 @@ public: //! \brief Returns (and caches) the 90th Percentile of all events of type id EventDataType p90(ChannelID id); + //! \brief Returns (and caches) the 95th Percentile of all events of type id + EventDataType p95(ChannelID id); + + //! \brief Returns (and caches) the Median (50th Perc) of all events of type id + EventDataType median(ChannelID id); + //! \brief Returns (and caches) the Count-Per-Hour of all events of type id EventDataType cph(ChannelID id); diff --git a/daily.cpp b/daily.cpp index 7ef9edc1..8df9cd2b 100644 --- a/daily.cpp +++ b/daily.cpp @@ -715,15 +715,17 @@ void Daily::Load(QDate date) } else { GraphView->setCubeImage(images["nodata"]); GraphView->setEmptyText(tr("No Data")); - emptyToggleArea->setText("No data available for this day"); + emptyToggleArea->setText("No graph data available for this day"); } if (cpap) { if (GraphView->isEmpty()) { - GraphView->setCubeImage(images["brick"]); - GraphView->setEmptyText(tr("Brick Machine :(")); + if (cpap->machine->GetClass()!=STR_MACH_ResMed) { + GraphView->setCubeImage(images["brick"]); + GraphView->setEmptyText(tr("No Graphs :(")); - isBrick=true; + isBrick=true; + } } mode=(CPAPMode)(int)cpap->settings_max(CPAP_Mode); diff --git a/docs/channels.xml b/docs/channels.xml index 8d6ffc96..1c53415c 100644 --- a/docs/channels.xml +++ b/docs/channels.xml @@ -74,6 +74,8 @@ Important: One id code per item, DO NOT CHANGE ID NUMBERS!!! + + + + + + + + diff --git a/exportcsv.ui b/exportcsv.ui index 1c711691..6969e36d 100644 --- a/exportcsv.ui +++ b/exportcsv.ui @@ -13,6 +13,10 @@ Export as CSV + + + :/icons/save.png:/icons/save.png + @@ -269,7 +273,9 @@ - + + + cancelButton diff --git a/newprofile.ui b/newprofile.ui index 49bcfa77..49b79496 100644 --- a/newprofile.ui +++ b/newprofile.ui @@ -13,6 +13,10 @@ Edit User Profile + + + :/icons/bob-v3.0.png:/icons/bob-v3.0.png + diff --git a/overview.cpp b/overview.cpp index 1e3be4e0..81e6584c 100644 --- a/overview.cpp +++ b/overview.cpp @@ -238,6 +238,7 @@ Overview::Overview(QWidget *parent,gGraphView * shared) : lk=new SummaryChart(tr("Avg Leak"),GT_LINE); lk->addSlice(CPAP_Leak,QColor("dark grey"),ST_PERC,false,0.95); lk->addSlice(CPAP_Leak,QColor("dark blue"),ST_WAVG,false); + lk->addSlice(CPAP_Leak,QColor("grey"),ST_MAX,false); //lk->addSlice(CPAP_Leak,QColor("dark yellow")); //pr->addSlice(CPAP_IPAP,QColor("red")); LK->AddLayer(lk); diff --git a/oximetry.cpp b/oximetry.cpp index 59e6cc0f..25855c37 100644 --- a/oximetry.cpp +++ b/oximetry.cpp @@ -1311,7 +1311,7 @@ void Oximetry::on_saveButton_clicked() if (m->SessionExists(session->session())) { m->sessionlist.erase(m->sessionlist.find(session->session())); } - QString path=PROFILE.Get(m->properties[STR_PROP_Path])+QString().sprintf("%08x",session->session()); + QString path=PROFILE.Get(m->properties[STR_PROP_Path])+QString().sprintf("%08lx",session->session()); QString f1=path+".000"; QString f2=path+".001"; QFile::remove(f1); diff --git a/preferencesdialog.ui b/preferencesdialog.ui index 486ca712..7e2fa9de 100644 --- a/preferencesdialog.ui +++ b/preferencesdialog.ui @@ -22,6 +22,10 @@ Preferences + + + :/icons/preferences.png:/icons/preferences.png + true @@ -2066,7 +2070,9 @@ this application to be unstable with this feature enabled. - + + + okButton diff --git a/profileselect.ui b/profileselect.ui index ca9dbcc9..04729a4b 100644 --- a/profileselect.ui +++ b/profileselect.ui @@ -15,7 +15,7 @@ - :/docs/sheep.png:/docs/sheep.png + :/icons/bob-v3.0.png:/icons/bob-v3.0.png