/* SleepLib ResMed Loader Implementation Author: Mark Watkins License: GPL */ #include #include #include #include #include #include #include #include #include #include "resmed_loader.h" #include "SleepLib/session.h" extern QProgressBar *qprogress; QHash RMS9ModelMap; EDFParser::EDFParser(QString name) { buffer=NULL; Open(name); } EDFParser::~EDFParser() { QVector::iterator s; for (s=edfsignals.begin();s!=edfsignals.end();s++) { if ((*s)->data) delete [] (*s)->data; delete *s; } if (buffer) delete [] buffer; } qint16 EDFParser::Read16() { unsigned char *buf=(unsigned char *)buffer; if (pos>=filesize) return 0; qint16 res=*(qint16 *)&buf[pos]; //qint16 res=(buf[pos] ^128)<< 8 | buf[pos+1] ^ 128; pos+=2; return res; } QString EDFParser::Read(int si) { QString str; if (pos>=filesize) return ""; for (int i=0;idata=NULL; edfsignals[i]->label=Read(16); } for (int i=0;itransducer_type=Read(80); for (int i=0;iphysical_dimension=Read(8); for (int i=0;iphysical_minimum=Read(8).toDouble(&ok); for (int i=0;iphysical_maximum=Read(8).toDouble(&ok); for (int i=0;idigital_minimum=Read(8).toDouble(&ok); for (int i=0;iprefiltering=Read(80); for (int i=0;inr=Read(8).toLong(&ok); for (int i=0;ireserved=Read(32); // allocate the buffers for (int i=0;ilabel << endl; EDFSignal & sig=*edfsignals[i]; long recs=sig.nr * num_data_records; if (num_data_records<0) return false; sig.data=new qint16 [recs]; sig.pos=0; } for (int x=0;x ml=profile->GetMachines(MT_CPAP); bool found=false; QVector::iterator i; for (i=ml.begin(); i!=ml.end(); i++) { if (((*i)->GetClass()==resmed_class_name) && ((*i)->properties["Serial"]==serial)) { ResmedList[serial]=*i; //static_cast(*i); found=true; break; } } if (found) return *i; qDebug() << "Create ResMed Machine" << serial; Machine *m=new CPAP(profile,0); m->SetClass(resmed_class_name); ResmedList[serial]=m; profile->AddMachine(m); m->properties["Serial"]=serial; m->properties["Brand"]="ResMed"; QString a; a.sprintf("%i",resmed_data_version); m->properties["DataVersion"]=a; return m; } long event_cnt=0; int ResmedLoader::Open(QString & path,Profile *profile) { QString newpath; QString dirtag="DATALOG"; if (path.endsWith("/"+dirtag)) { return 0; // id10t user.. //newpath=path; } else { newpath=path+"/"+dirtag; } QString idfile=path+"/Identification.tgt"; QFile f(idfile); QHash idmap; if (f.open(QIODevice::ReadOnly)) { if (!f.isReadable()) return 0; 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; } } } QDir dir(newpath); if ((!dir.exists() || !dir.isReadable())) return 0; 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(); for (int i=0;isetValue((float(i+1)/float(size)*33.0)); QApplication::processEvents(); } Machine *m=NULL; QString fn; Session *sess; int cnt=0; size=sessfiles.size(); 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::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"; } } else if (i.key()=="PNA") { m->properties["Model"]=i.value(); } else if (i.key()=="PCD") { bool ok; int j=i.value().toInt(&ok); if (RMS9ModelMap.find(j)!=RMS9ModelMap.end()) { m->properties["SubModel"]=RMS9ModelMap[j]; } } else { m->properties[i.key()]=i.value(); } } if (m->SessionExists(sessionid)) { done=true; break; } sess=new Session(m,sessionid); first=false; } 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; //} } } if (qprogress) qprogress->setValue(33.0+(float(++cnt)/float(size)*33.0)); QApplication::processEvents(); if (!sess) continue; if (!sess->first()) { delete sess; continue; } else { sess->SetChanged(true); m->AddSession(sess,profile); // Adding earlier than I really like here.. } if (!done && sess) { ChannelID e[]={ CPAP_Obstructive, CPAP_Hypopnea, CPAP_ClearAirway, CPAP_Apnea }; for (unsigned i=0;icount(e[i]); sess->max(e[i]); sess->min(e[i]); sess->avg(e[i]); sess->p90(e[i]); sess->cph(e[i]); sess->sph(e[i]); } sess->setCph(CPAP_AHI,sess->cph(CPAP_Obstructive)+sess->cph(CPAP_Hypopnea)+sess->cph(CPAP_ClearAirway)+sess->cph(CPAP_Apnea)); sess->setSph(CPAP_AHI,sess->sph(CPAP_Obstructive)+sess->sph(CPAP_Hypopnea)+sess->sph(CPAP_ClearAirway)+sess->sph(CPAP_Apnea)); ChannelID a[]={ CPAP_FlowRate, CPAP_MaskPressure, CPAP_Leak, CPAP_Snore, CPAP_EPAP, CPAP_IPAP, CPAP_TidalVolume, CPAP_RespiratoryRate, CPAP_PatientTriggeredBreaths,CPAP_MinuteVentilation, CPAP_FlowLimitGraph, CPAP_PressureSupport,CPAP_Pressure }; for (unsigned i=0;ieventlist.contains(a[i])) { sess->min(a[i]); sess->max(a[i]); sess->avg(a[i]); sess->wavg(a[i]); sess->p90(a[i]); sess->cph(a[i]); } } sess->settings[CPAP_Mode]=MODE_APAP; } } m->Save(); if (qprogress) qprogress->setValue(100); qDebug() << "Total Events " << event_cnt; return 1; } bool ResmedLoader::LoadEVE(Session *sess,EDFParser &edf) { // EVEnt records have useless duration record. QString t; long recs; double duration; char * data; char c; long pos; bool sign,ok; double d; double tt; ChannelID code; //Event *e; //totaldur=edf.GetNumDataRecords()*edf.GetDuration(); EventList *EL[4]={NULL}; sess->updateFirst(edf.startdate); //if (edf.enddate>edf.startdate) sess->set_last(edf.enddate); for (int s=0;snr*edf.GetNumDataRecords()*2; //qDebug() << edf.edfsignals[s]->label << " " << t; data=(char *)edf.edfsignals[s]->data; pos=0; tt=edf.startdate; sess->updateFirst(tt); duration=0; while (poseventlist[code].push_back(EL[0]); } EL[0]->AddEvent(tt,duration); } else if (t=="hypopnea") { code=CPAP_Hypopnea; if (!EL[1]) { EL[1]=new EventList(code,EVL_Event); sess->eventlist[code].push_back(EL[1]); } EL[1]->AddEvent(tt,duration); } else if (t=="apnea") { code=CPAP_Apnea; if (!EL[2]) { EL[2]=new EventList(code,EVL_Event); sess->eventlist[code].push_back(EL[2]); } EL[2]->AddEvent(tt,duration); } else if (t=="central apnea") { code=CPAP_ClearAirway; if (!EL[3]) { EL[3]=new EventList(code,EVL_Event); sess->eventlist[code].push_back(EL[3]); } EL[3]->AddEvent(tt,duration); } else { if (t!="recording starts") { qDebug() << "Unknown ResMed annotation field: " << t; } } } if (pos>=recs) { qDebug() << "Short EDF EVE file" << edf.filename; break; } // pos++; } while ((data[pos]==0) && pos=recs) break; } sess->updateLast(tt); // qDebug(data); } return true; } bool ResmedLoader::LoadBRP(Session *sess,EDFParser &edf) { QString t; sess->updateFirst(edf.startdate); qint64 duration=edf.GetNumDataRecords()*edf.GetDuration(); sess->updateLast(edf.startdate+duration); for (int s=0;snr*edf.GetNumDataRecords(); ChannelID code; if (edf.edfsignals[s]->label=="Flow") { code=CPAP_FlowRate; } else if (edf.edfsignals[s]->label=="Mask Pres") { code=CPAP_MaskPressure; //for (int i=0;idata[i]/=50.0; } else { qDebug() << "Unknown Signal " << edf.edfsignals[s]->label; continue; } double rate=double(duration)/double(recs); //es.gain=1; EventList *a=new EventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); if (code==CPAP_MaskPressure) { /*int v=ceil(a->max()/1); a->setMax(v*1); v=floor(a->min()/1); a->setMin(v*1); */ } else if (code==CPAP_FlowRate) { a->setMax(1); a->setMin(-1); } sess->eventlist[code].push_back(a); //delete edf.edfsignals[s]->data; //edf.edfsignals[s]->data=NULL; // so it doesn't get deleted when edf gets trashed. } return true; } EventList * ResmedLoader::ToTimeDelta(Session *sess,EDFParser &edf, EDFSignal & es, ChannelID code, long recs, qint64 duration,EventDataType min,EventDataType max) { bool first=true; double rate=(duration/recs); // milliseconds per record double tt=edf.startdate; //sess->UpdateFirst(tt); EventDataType c,last; //if (gain==0) gain=1; EventList *el=new EventList(code,EVL_Event,es.gain,es.offset,min,max); sess->eventlist[code].push_back(el); for (int i=0;iAddEvent(tt,c); first=false; } else { if (last!=c) { el->AddEvent(tt,c); } } tt+=rate; last=c; } el->AddEvent(tt,c); sess->updateLast(tt); return el; } bool ResmedLoader::LoadSAD(Session *sess,EDFParser &edf) { // Oximeter bull crap.. this oximeter is not reported of highly.. // nonetheless, the data is easy to access. return true; } bool ResmedLoader::LoadPLD(Session *sess,EDFParser &edf) { // Is it save to assume the order does not change here? enum PLDType { MaskPres=0, TherapyPres, ExpPress, Leak, RR, Vt, Mv, SnoreIndex, FFLIndex, U1, U2 }; sess->updateFirst(edf.startdate); qint64 duration=edf.GetNumDataRecords()*edf.GetDuration(); sess->updateLast(edf.startdate+duration); QString t; int emptycnt=0; for (int s=0;seventlist[code].push_back(a); //a->AddWaveform(edf.startdate,es.data,recs,duration); EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); a->setMax(1); a->setMin(0); } else if (es.label=="Therapy Pres") { code=CPAP_TherapyPressure; //EventList *a=new EventList(code,EVL_Waveform,es.gain,es.offset,es.physical_minimum,es.physical_maximum,rate); //sess->eventlist[code].push_back(a); //a->AddWaveform(edf.startdate,es.data,recs,duration); EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="MV") { code=CPAP_MinuteVentilation; //EventList *a=new EventList(code,EVL_Waveform,es.gain,es.offset,es.physical_minimum,es.physical_maximum,rate); //sess->eventlist[code].push_back(a); //a->AddWaveform(edf.startdate,es.data,recs,duration); EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="RR") { code=CPAP_RespiratoryRate; EventList *a=new EventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); sess->eventlist[code].push_back(a); a->AddWaveform(edf.startdate,es.data,recs,duration); //ToTimeDelta(sess,edf,es, code,recs,duration); } else if (es.label=="Vt") { code=CPAP_TidalVolume; //EventList *a=new EventList(code,EVL_Waveform,es.gain,es.offset,es.physical_minimum,es.physical_maximum,rate); //sess->eventlist[code].push_back(a); //a->AddWaveform(edf.startdate,es.data,recs,duration); es.physical_maximum=es.physical_minimum=0; es.gain*=1000.0; EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="Leak") { code=CPAP_Leak; // es.gain*=100.0; //es.gain=1;//10.0; es.offset=-0.5; EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); a->setMax(1); a->setMin(0); } else if (es.label=="FFL Index") { code=CPAP_FlowLimitGraph; //es.gain=1;//10.0; EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); a->setMax(1); a->setMin(0); } else if (es.label=="Mask Pres") { code=CPAP_Pressure; //es.gain=1; EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="Exp Press") { code=CPAP_ExpiratoryPressure; EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="") { if (emptycnt==0) { code=RMS9_Empty1; ToTimeDelta(sess,edf,es, code,recs,duration); } else if (emptycnt==1) { code=RMS9_Empty2; ToTimeDelta(sess,edf,es, code,recs,duration); } else { qDebug() << "Unobserved Empty Signal " << es.label; } emptycnt++; } else { qDebug() << "Unobserved Signal " << es.label; } } return true; } void ResInitModelMap() { // Courtesy Troy Schultz RMS9ModelMap[36001]="ResMed S9 Escape"; RMS9ModelMap[36002]="ResMed S9 Escape Auto"; RMS9ModelMap[36003]="ResMed S9 Elite"; RMS9ModelMap[36004]="ResMed S9 VPAP S"; RMS9ModelMap[36005]="ResMed S9 AutoSet"; RMS9ModelMap[36006]="ResMed S9 VPAP Auto"; RMS9ModelMap[36007]="ResMed S9 VPAP Adapt"; RMS9ModelMap[36008]="ResMed S9 VPAP ST"; // S8 Series RMS9ModelMap[33007]="ResMed S8 Escape"; RMS9ModelMap[33039]="ResMed S8 Elite II"; RMS9ModelMap[33051]="ResMed S8 Escape II"; RMS9ModelMap[33064]="ResMed S8 Escape II AutoSet"; RMS9ModelMap[33064]="ResMed S8 Escape II AutoSet"; RMS9ModelMap[33129]="ResMed S8 AutoSet II"; }; bool resmed_initialized=false; void ResmedLoader::Register() { if (resmed_initialized) return; qDebug() << "Registering ResmedLoader"; RegisterLoader(new ResmedLoader()); ResInitModelMap(); resmed_initialized=true; }