/* SleepLib ResMed Loader Implementation Author: Mark Watkins <jedimark64@users.sourceforge.net> License: GPL Copyright: (c)2011 Mark Watkins */ #include <QApplication> #include <QString> #include <QDateTime> #include <QDir> #include <QFile> #include <QMessageBox> #include <QProgressBar> #include <QDebug> #include <cmath> #include "resmed_loader.h" #include "SleepLib/session.h" #include "SleepLib/calcs.h" #ifdef DEBUG_EFFICIENCY #include <QElapsedTimer> // only available in 4.8 #endif extern QProgressBar *qprogress; QHash<int,QString> RMS9ModelMap; QHash<ChannelID, QVector<QString> > resmed_codes; // Looks up foreign language Signal names that match this channelID EDFSignal * EDFParser::lookupSignal(ChannelID ch) { QHash<ChannelID, QVector<QString> >::iterator ci; QHash<QString,EDFSignal *>::iterator jj; ci=resmed_codes.find(ch); if (ci==resmed_codes.end()) return NULL; for (int i=0;i<ci.value().size();i++) { jj=lookup.find(ci.value()[i]); if (jj==lookup.end()) continue; return jj.value(); } return NULL; } EDFSignal * EDFParser::lookupName(QString name) { QHash<QString,EDFSignal *>::iterator i=lookup.find(name); if (i!=lookup.end()) return i.value(); return NULL; } EDFParser::EDFParser(QString name) { buffer=NULL; Open(name); } EDFParser::~EDFParser() { QVector<EDFSignal *>::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;i<si;i++) { str+=buffer[pos++]; } return str.trimmed(); } bool EDFParser::Parse() { bool ok; QString temp,temp2; version=QString::fromLatin1(header.version,8).toLong(&ok); if (!ok) return false; //patientident=QString::fromLatin1(header.patientident,80); recordingident=QString::fromLatin1(header.recordingident,80); // Serial number is in here.. int snp=recordingident.indexOf("SRN="); serialnumber.clear(); /*char * idx=index(header.recordingident,'='); idx++; for (int i=0;i<16;++i) { if (*idx==0x20) break; serialnumber+=*idx; ++idx; } */ for (int i=snp+4;i<recordingident.length();i++) { if (recordingident[i]==' ') break; serialnumber+=recordingident[i]; } QDateTime startDate=QDateTime::fromString(QString::fromLatin1(header.datetime,16),"dd.MM.yyHH.mm.ss"); //startDate.toTimeSpec(Qt::UTC); QDate d2=startDate.date(); if (d2.year()<2000) { d2.setDate(d2.year()+100,d2.month(),d2.day()); startDate.setDate(d2); } if (!startDate.isValid()) { qDebug() << "Invalid date time retreieved parsing EDF File " << filename; return false; } startdate=qint64(startDate.toTime_t())*1000L; //startdate-=timezoneOffset(); //qDebug() << startDate.toString("yyyy-MM-dd HH:mm:ss"); num_header_bytes=QString::fromLatin1(header.num_header_bytes,8).toLong(&ok); if (!ok) return false; //reserved44=QString::fromLatin1(header.reserved,44); num_data_records=QString::fromLatin1(header.num_data_records,8).toLong(&ok); if (!ok) return false; dur_data_record=QString::fromLatin1(header.dur_data_records,8).toDouble(&ok)*1000.0; if (!ok) return false; num_signals=QString::fromLatin1(header.num_signals,4).toLong(&ok); if (!ok) return false; enddate=startdate+dur_data_record*qint64(num_data_records); // if (dur_data_record==0) // return false; // this could be loaded quicker by transducer_type[signal] etc.. for (int i=0;i<num_signals;i++) { EDFSignal *signal=new EDFSignal; edfsignals.push_back(signal); signal->data=NULL; edfsignals[i]->label=Read(16); lookup[edfsignals[i]->label]=signal; } for (int i=0;i<num_signals;i++) edfsignals[i]->transducer_type=Read(80); for (int i=0;i<num_signals;i++) edfsignals[i]->physical_dimension=Read(8); for (int i=0;i<num_signals;i++) edfsignals[i]->physical_minimum=Read(8).toDouble(&ok); for (int i=0;i<num_signals;i++) edfsignals[i]->physical_maximum=Read(8).toDouble(&ok); for (int i=0;i<num_signals;i++) edfsignals[i]->digital_minimum=Read(8).toDouble(&ok); for (int i=0;i<num_signals;i++) { EDFSignal & e=*edfsignals[i]; e.digital_maximum=Read(8).toDouble(&ok); e.gain=(e.physical_maximum-e.physical_minimum)/(e.digital_maximum-e.digital_minimum); e.offset=0; } for (int i=0;i<num_signals;i++) edfsignals[i]->prefiltering=Read(80); for (int i=0;i<num_signals;i++) edfsignals[i]->nr=Read(8).toLong(&ok); for (int i=0;i<num_signals;i++) edfsignals[i]->reserved=Read(32); // allocate the buffers for (int i=0;i<num_signals;i++) { //qDebug//cout << "Reading signal " << signals[i]->label << 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<num_data_records;x++) { for (int i=0;i<num_signals;i++) { EDFSignal & sig=*edfsignals[i]; memcpy((char *)&sig.data[sig.pos],(char *)&buffer[pos],sig.nr*2); sig.pos+=sig.nr; pos+=sig.nr*2; // big endian will probably screw up without this.. /*for (int j=0;j<sig.nr;j++) { qint16 t=Read16(); sig.data[sig.pos++]=t; } */ } } return true; } bool EDFParser::Open(QString name) { //Urk.. This needs fixing for VC++, as it doesn't have packed attribute type.. if (name.endsWith(".gz")) { filename=name.mid(0,-3); QFile fi(name); fi.open(QFile::ReadOnly); fi.seek(fi.size()-4); unsigned char ch[4]; fi.read((char *)ch,4); filesize=ch[0] | (ch [1] << 8) | (ch[2] << 16) | (ch[3] << 24); datasize=filesize-EDFHeaderSize; if (datasize<0) return false; qDebug() << "Size of" << name << "uncompressed=" << filesize; gzFile f=gzopen(name.toLatin1(),"rb"); if (!f) { qDebug() << "EDFParser::Open() Couldn't open file" << name; return false; } gzread(f,(char *)&header,EDFHeaderSize); buffer=new char [datasize]; //gzbuffer(f,65536*2); gzread(f,buffer,datasize); gzclose(f); } else { QFile f(name); if (!f.open(QIODevice::ReadOnly)) return false; filename=name; filesize=f.size(); datasize=filesize-EDFHeaderSize; if (datasize<0) return false; f.read((char *)&header,EDFHeaderSize); //qDebug() << "Opening " << name; buffer=new char [datasize]; f.read(buffer,datasize); f.close(); } pos=0; return true; } ResmedLoader::ResmedLoader() { } ResmedLoader::~ResmedLoader() { } Machine *ResmedLoader::CreateMachine(QString serial,Profile *profile) { if (!profile) return NULL; QList<Machine *> ml=profile->GetMachines(MT_CPAP); bool found=false; QList<Machine *>::iterator i; for (i=ml.begin(); i!=ml.end(); i++) { if (((*i)->GetClass()==resmed_class_name) && ((*i)->properties[STR_PROP_Serial]==serial)) { ResmedList[serial]=*i; //static_cast<CPAP *>(*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[STR_PROP_Serial]=serial; m->properties[STR_PROP_Brand]=STR_MACH_ResMed; m->properties[STR_PROP_DataVersion]=QString::number(resmed_data_version); QString path="{"+STR_GEN_DataFolder+"}/"+m->GetClass()+"_"+serial+"/"; m->properties[STR_PROP_Path]=path; m->properties[STR_PROP_BackupPath]=path+"Backup/"; return m; } 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"; const QString ext_gz=".gz"; QString serial; // Serial number QString key,value; QString line; QString newpath; QString filename; QHash<QString,QString> 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+"/"+datalog+"/"; } // Add separator back path+="/"; // 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; } } 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()) { // No STR.edf.. Do we have a STR.edf.gz? strpath+=ext_gz; 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); bool create_backups=PROFILE.session->backupCardData(); bool compress_backups=PROFILE.session->compressBackupData(); QString backup_path=PROFILE.Get(m->properties[STR_PROP_BackupPath]); if (backup_path.isEmpty()) backup_path=PROFILE.Get(m->properties[STR_PROP_Path])+"Backup/"; if (path==backup_path) { create_backups=false; } /////////////////////////////////////////////////////////////////////////////////// // Parse the idmap into machine objects properties, (overwriting any old values) /////////////////////////////////////////////////////////////////////////////////// for (QHash<QString,QString>::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(&ok); 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.. // (Unless we are importing from this backup folder) /////////////////////////////////////////////////////////////////////////////////// if (create_backups) { 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 if (strpath.endsWith(ext_gz)) // Already compressed. QFile::copy(strpath,backup_path+strfile+ext_EDF+ext_gz); else { // Compress STR file to backup folder QString strf=backup_path+strfile+ext_EDF; if (QFile::exists(strf)) QFile::remove(strf); compress_backups ? compressFile(strpath,strf) : QFile::copy(strpath,strf); } QFile::copy(path+"STR.crc",backup_path+"STR.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;s<stredf.GetNumSignals();s++) { // EDFSignal & es=*stredf.edfsignals[s]; // long recs=es.nr*stredf.GetNumDataRecords(); // //qDebug() << "STREDF:" << es.label << recs; // } // Process STR.edf and find first and last time for each day QVector<qint8> dayused; dayused.resize(days); QList<SessionID> strfirst; QList<SessionID> strlast; QList<int> strday; QList<bool> dayfoo; QHash<qint16,QList<time_t> > daystarttimes; QHash<qint16,QList<time_t> > 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;i<days;i++) { // EDFSignal *maskon=stredf.lookup["Mask On"]; // EDFSignal *maskoff=stredf.lookup["Mask Off"]; // int j=i*10; // // Counts for on and off don't line up, and I'm not sure why // // The extra 'off' always seems to start with a 1 at the beginning // // A signal it's carried over from the day before perhaps? (noon boundary) // int ckon=0,ckoff=0; // for (int k=0;k<10;k++) { // on=maskon->data[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<ckon;k++) { // on=o1[k]; // off=o2[k+offset]; // f=time+on*60; // l=time+off*60; // daystarttimes[i].push_back(f); // dayendtimes[i].push_back(l); // if (!st || (st > 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 /////////////////////////////////////////////////////////////////////////////////// dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist=dir.entryInfoList(); QString datestr; SessionID sessionid; QDateTime date; int size=flist.size(); sessfiles.clear(); bool gz; QMap<SessionID,QStringList>::iterator si; // For each file in flist... for (int i=0;i<size;i++) { QFileInfo fi=flist.at(i); filename=fi.fileName(); // Forget about it if it can't be read. if (!fi.isReadable()) continue; // Accept only .edf and .edf.gz files if (filename.right(4).toLower() != "."+ext_EDF) { if (filename.right(7).toLower() != "."+ext_EDF+ext_gz) continue; gz=true; } else gz=false; // Extract the session date out of the filename datestr=filename.section("_",0,1); // Take the filename's date, and date=QDateTime::fromString(datestr,"yyyyMMdd_HHmmss"); date=date.toUTC(); // Skip file if dates invalid, the filename is clearly wrong.. if (!date.isValid()) continue; // convert this date to UNIX epoch to form the sessionID sessionid=date.toTime_t(); //////////////////////////////////////////////////////////////////////////////////////////// // Resmed bugs up on the session filenames.. More than these 3 seconds // Moral of the story, when writing firmware and saving in batches, use the same datetimes, // and provide firmware updates for free to your customers. //////////////////////////////////////////////////////////////////////////////////////////// si=sessfiles.find(sessionid); if (si==sessfiles.end()) { // Scan 3 seconds either way for sessions.. for (int j=1;j<3;j++) { if ((si=sessfiles.find(sessionid+j)) != sessfiles.end()) { sessionid+=j; break; } else if ((si=sessfiles.find(sessionid-j)) != sessfiles.end()) { sessionid-=j; break; } } } // Push current filename to ordered-by-sessionid list if (si!=sessfiles.end()) { // Ignore if already compressed version of the same file exists.. (just in case) if (!gz) { if (si.value().contains(filename+ext_gz,Qt::CaseInsensitive)) continue; } else { QString str=filename; str.chop(3); if (si.value().contains(str,Qt::CaseInsensitive)) continue; } si.value().push_back(filename); } else { sessfiles[sessionid].push_back(filename); } if ((i % 10)==0) { // Update the progress bar if (qprogress) qprogress->setValue((float(i+1)/float(size)*10.0)); QApplication::processEvents(); } } QString fn; Session *sess; int cnt=0; size=sessfiles.size(); QHash<SessionID,int> sessday; ///////////////////////////////////////////////////////////////////////////// // Scan over file list and knock out of dayused list ///////////////////////////////////////////////////////////////////////////// //int dn; // for (QMap<SessionID,QStringList>::iterator si=sessfiles.begin();si!=sessfiles.end();si++) { // sessionid=si.key(); // // Earliest possible day number // int edn=((sessionid-time)/86400)-1; // if (edn<0) edn=0; // // Find real day number from str.edf mask on/off data. // dn=-1; // for (int j=edn;j<strfirst.size();j++){ // st=strfirst.at(j); // if (sessionid>=st) { // et=strlast.at(j); // if (sessionid<(et+300)) { // dn=j; // break; // } // } // } // // If found, mark day off so STR.edf summary data isn't used instead of the real thing. // if (dn>=0) { // dayused[dn]=0; // } // } EDFSignal *sig; ///////////////////////////////////////////////////////////////////////////// // For all days not in session lists, (to get at days without data records) ///////////////////////////////////////////////////////////////////////////// // for (dn=0;dn<days;dn++) { // // Skip days with loadable data. // if (!dayused[dn]) // continue; // if (!daystarttimes.contains(dn)) // continue; // sess=NULL; // int scnt=daystarttimes[dn].size(); // count of sessions for this day // // Create a new session for each mask on/off segment in a day // // But only populate the last one with summary data. // for (int j=0;j<scnt;j++) { // st=daystarttimes[dn].at(j); // // Skip if session already exists // if (m->SessionExists(st)) // continue; // et=dayendtimes[dn].at(j); // // Create the session // sess=new Session(m,st); // sess->really_set_first(qint64(st)*1000L); // sess->really_set_last(qint64(et)*1000L); // sess->SetChanged(true); // m->AddSession(sess,profile); // } // // Add the actual data to the last session // EventDataType tmp,dur; // if (sess) { // ///////////////////////////////////////////////////////////////////// // // 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) { // sess->settings[CPAP_PresReliefMode]=EventDataType(sig->data[dn])*sig->gain; // } // sig=stredf.lookupSignal(RMS9_EPRSet); // if (sig) { // sess->settings[CPAP_PresReliefSet]=EventDataType(sig->data[dn])*sig->gain; // } // ///////////////////////////////////////////////////////////////////// // // 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_Pressure]=pressure; // } // } else { // VPAP or Auto // if (mode>5) { // if (mode>=7) // sess->settings[CPAP_Mode]=MODE_ASV; // else // sess->settings[CPAP_Mode]=MODE_BIPAP; // EventDataType tmp,epap=0,ipap=0; // if ((sig=stredf.lookupName("EPAP"))) { // epap=sig->data[dn]*sig->gain; // sess->settings[CPAP_EPAP]=epap; // sess->setMin(CPAP_EPAP,epap); // } // if ((sig=stredf.lookupName("IPAP"))) { // ipap=sig->data[dn]*sig->gain; // sess->settings[CPAP_IPAP]=ipap; // } // if ((sig=stredf.lookupName("PS"))) { // tmp=sig->data[dn]*sig->gain; // sess->settings[CPAP_PS]=tmp; // technically this is IPAP-EPAP // if (!ipap) { // // not really possible. but anyway, just in case.. // sess->settings[CPAP_IPAP]=epap+tmp; // } // } // if ((sig=stredf.lookupName("Min PS"))) { // tmp=sig->data[dn]*sig->gain; // sess->settings[CPAP_PSMin]=tmp; // sess->settings[CPAP_IPAPLo]=epap+tmp; // sess->setMin(CPAP_IPAP,epap+tmp); // } // if ((sig=stredf.lookupName("Max PS"))) { // tmp=sig->data[dn]*sig->gain; // sess->settings[CPAP_PSMax]=tmp; // sess->settings[CPAP_IPAPHi]=epap+tmp; // } // if ((sig=stredf.lookupName("RR"))) { // Is this a setting to force respiratory rate on S/T machines? // tmp=sig->data[dn]; // sess->settings[CPAP_RespRate]=tmp*sig->gain; // } // if ((sig=stredf.lookupName("Easy-Breathe"))) { // tmp=sig->data[dn]*sig->gain; // sess->settings[CPAP_PresReliefSet]=tmp; // sess->settings[CPAP_PresReliefType]=(int)PR_EASYBREATHE; // sess->settings[CPAP_PresReliefMode]=(int)PM_FullTime; // } // } 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); // } // } // } // EventDataType valmed=0,valmax=0,val95=0; // ///////////////////////////////////////////////////////////////////// // // Leak Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Leak Med"))) { // valmed=sig->data[dn]; // if (valmed>=0) { // sess->m_gain[CPAP_Leak]=sig->gain*60.0; // sess->m_valuesummary[CPAP_Leak][valmed]=51; // } // } // if ((sig=stredf.lookupName("Leak 95"))) { // val95=sig->data[dn]; // if (val95>=0) // sess->m_valuesummary[CPAP_Leak][val95]=45; // } // if ((sig=stredf.lookupName("Leak Max"))) { // valmax=sig->data[dn]; // if (valmax>=0) { // sess->setMax(CPAP_Leak,valmax*sig->gain*60.0); // sess->m_valuesummary[CPAP_Leak][valmax]=4; // } // } // ///////////////////////////////////////////////////////////////////// // // Minute Ventilation Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Min Vent Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_MinuteVent]=sig->gain; // sess->m_valuesummary[CPAP_MinuteVent][valmed]=51; // } // if ((sig=stredf.lookupName("Min Vent 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_MinuteVent][val95]=45; // } // if ((sig=stredf.lookupName("Min Vent Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_MinuteVent,valmax*sig->gain); // sess->m_valuesummary[CPAP_MinuteVent][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // Respiratory Rate Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("RR Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_RespRate]=sig->gain; // sess->m_valuesummary[CPAP_RespRate][valmed]=51; // } // if ((sig=stredf.lookupName("RR 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_RespRate][val95]=45; // } // if ((sig=stredf.lookupName("RR Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_RespRate,valmax*sig->gain); // sess->m_valuesummary[CPAP_RespRate][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // Tidal Volume Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Tid Vol Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_TidalVolume]=sig->gain*1000.0; // sess->m_valuesummary[CPAP_TidalVolume][valmed]=51; // } // if ((sig=stredf.lookupName("Tid Vol 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_TidalVolume][val95]=45; // } // if ((sig=stredf.lookupName("Tid Vol Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_TidalVolume,valmax*sig->gain*1000.0); // sess->m_valuesummary[CPAP_TidalVolume][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // Target Minute Ventilation Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Targ Vent Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_TgMV]=sig->gain; // sess->m_valuesummary[CPAP_TgMV][valmed]=51; // } // if ((sig=stredf.lookupName("Targ Vent 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_TgMV][val95]=45; // } // if ((sig=stredf.lookupName("Targ Vent Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_TgMV,valmax*sig->gain); // sess->m_valuesummary[CPAP_TgMV][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // I:E Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("I:E Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_IE]=sig->gain; // sess->m_valuesummary[CPAP_IE][valmed]=51; // } // if ((sig=stredf.lookupName("I:E 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_IE][val95]=45; // } // if ((sig=stredf.lookupName("I:E Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_IE,valmax*sig->gain); // sess->m_valuesummary[CPAP_IE][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // Mask Pressure Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Mask Pres Med"))) { // valmed=sig->data[dn]; // if (valmed >= 0) { // sess->m_gain[CPAP_Pressure]=sig->gain; // sess->m_valuesummary[CPAP_Pressure][valmed]=51; // } // } // if ((sig=stredf.lookupName("Mask Pres 95"))) { // val95=sig->data[dn]; // if (val95 >= 0) { // sess->m_valuesummary[CPAP_Pressure][val95]=45; // } // } // if ((sig=stredf.lookupName("Mask Pres Max"))) { // valmax=sig->data[dn]; // if (valmax >= 0) { // sess->setMax(CPAP_Pressure,valmax*sig->gain); // sess->m_valuesummary[CPAP_Pressure][valmax]=4; // } // } // ///////////////////////////////////////////////////////////////////// // // Therapy Pressure Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Therapy Pres Me"))) { // valmed=sig->data[dn]; // if (valmed >= 0) { // //sess->m_gain[CPAP_Pressure]=sig->gain; // //sess->m_valuesummary[CPAP_Pressure][valmed]=51; // } // } // if ((sig=stredf.lookupName("Therapy Pres 95"))) { // val95=sig->data[dn]; // if (val95 >= 0) { //// sess->m_valuesummary[CPAP_Pressure][val95]=45; // } // } // if ((sig=stredf.lookupName("Therapy Pres Ma"))) { // valmax=sig->data[dn]; // if (valmax >= 0) { //// sess->setMax(CPAP_Pressure,valmax*sig->gain); //// sess->m_valuesummary[CPAP_Pressure][valmax]=4; // } // } // ///////////////////////////////////////////////////////////////////// // // Inspiratory Pressure (IPAP) Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Insp Pres Med"))) { // valmed=sig->data[dn]; // sess->m_gain[CPAP_IPAP]=sig->gain; // sess->m_valuesummary[CPAP_IPAP][valmed]=51; // } // if ((sig=stredf.lookupName("Insp Pres 95"))) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_IPAP][val95]=45; // } // if ((sig=stredf.lookupName("Insp Pres Max"))) { // valmax=sig->data[dn]; // sess->setMax(CPAP_IPAP,valmax*sig->gain); // sess->m_valuesummary[CPAP_IPAP][valmax]=4; // } // ///////////////////////////////////////////////////////////////////// // // Expiratory Pressure (EPAP) Summary // ///////////////////////////////////////////////////////////////////// // if ((sig=stredf.lookupName("Exp Pres Med"))) { // valmed=sig->data[dn]; // if (valmed>=0) { // sess->m_gain[CPAP_EPAP]=sig->gain; // sess->m_valuesummary[CPAP_EPAP][valmed]=51; // } // } // if ((sig=stredf.lookupName("Exp Pres 95"))) { // if (val95>=0) { // val95=sig->data[dn]; // sess->m_valuesummary[CPAP_EPAP][val95]=45; // } // } // if ((sig=stredf.lookupName("Exp Pres Max"))) { // valmax=sig->data[dn]; // if (valmax>=0) { // sess->setMax(CPAP_EPAP,valmax*sig->gain); // sess->m_valuesummary[CPAP_EPAP][valmax]=4; // } // } // ///////////////////////////////////////////////////////////////////// // // Duration and Event Indices // ///////////////////////////////////////////////////////////////////// // dur=0; // if ((sig=stredf.lookupName("Mask Dur"))) { // dur=sig->data[dn]*sig->gain; // dur/=60.0f; // convert to hours. // } // if ((sig=stredf.lookupName("OAI"))) { // Obstructive Apnea Index // tmp=sig->data[dn]*sig->gain; // if (tmp>=0) { // sess->setCph(CPAP_Obstructive,tmp); // sess->setCount(CPAP_Obstructive,tmp*dur); // Converting from indice to counts.. // } // } // if ((sig=stredf.lookupName("HI"))) { // Hypopnea Index // tmp=sig->data[dn]*sig->gain; // if (tmp>=0) { // sess->setCph(CPAP_Hypopnea,tmp); // sess->setCount(CPAP_Hypopnea,tmp*dur); // } // } // if ((sig=stredf.lookupName("UAI"))) { // Unspecified Apnea Index // tmp=sig->data[dn]*sig->gain; // if (tmp>=0) { // sess->setCph(CPAP_Apnea,tmp); // sess->setCount(CPAP_Apnea,tmp*dur); // } // } // if ((sig=stredf.lookupName("CAI"))) { // "Central" Apnea Index // tmp=sig->data[dn]*sig->gain; // if (tmp>=0) { // sess->setCph(CPAP_ClearAirway,tmp); // sess->setCount(CPAP_ClearAirway,tmp*dur); // } // } // } // } backup_path+=datalog+"/"; ///////////////////////////////////////////////////////////////////////////// // Scan through new file list and import sessions ///////////////////////////////////////////////////////////////////////////// for (QMap<SessionID,QStringList>::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); QString fullpath,backupfile,backfile, crcfile; // Process EDF File List for (int i=0;i<si.value().size();++i) { filename=si.value()[i]; gz=(filename.right(3).toLower()==ext_gz); fullpath=newpath+filename; // Copy the EDF file to the backup folder if (create_backups) { backupfile=backup_path+filename; bool dobackup=true; if (!gz && QFile::exists(backupfile+".gz")) { dobackup=false; } else if (QFile::exists(backupfile)) { if (gz) { // don't bother, it's already there and compressed. dobackup=false; } else { // non compressed file is there.. if (compress_backups) { // remove old edf file, as we are writing a compressed one QFile::remove(backupfile); } else { // don't bother copying it. dobackup=false; } } } if (dobackup) { if (!gz) { compress_backups ? compressFile(fullpath, backupfile) : QFile::copy(fullpath, backupfile); } else { // already compressed, just copy it. QFile::copy(fullpath, backupfile); } } if (!gz) { backfile=filename.replace(".edf",".crc",Qt::CaseInsensitive); } else { backfile=filename.replace(".edf.gz",".crc",Qt::CaseInsensitive); } backupfile=backup_path+backfile; crcfile=newpath+backfile; QFile::copy(crcfile, backupfile); } EDFParser edf(fullpath); // Parse the actual file if (!edf.Parse()) continue; // 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"; } fn=filename.section("_",-1).section(".",0,0).toLower(); if (fn=="eve") LoadEVE(sess,edf); else if (fn=="pld") LoadPLD(sess,edf); else if (fn=="brp") LoadBRP(sess,edf); else if (fn=="sad") LoadSAD(sess,edf); } if ((++cnt%10) ==0) { if (qprogress) qprogress->setValue(10.0+(float(cnt)/float(size)*90.0)); QApplication::processEvents(); } if (!sess) continue; if (!sess->first()) { delete sess; continue; } else { sess->SetChanged(true); qint64 dif=sess->first()-stredf.startdate; int dn=dif/86400000L; if (dn<days) { int mode; sig=stredf.lookupSignal(CPAP_Mode); if (sig) { 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[CPAP_PresReliefMode]=EventDataType(sig->data[dn])*sig->gain; } sig=stredf.lookupSignal(RMS9_EPRSet); if (sig) { sess->settings[CPAP_PresReliefSet]=EventDataType(sig->data[dn])*sig->gain; } 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_Pressure]=pressure; } } else if (mode>5) { if (mode>=7) sess->settings[CPAP_Mode]=MODE_ASV; else sess->settings[CPAP_Mode]=MODE_BIPAP; EventDataType tmp,epap=0,ipap=0; if (stredf.lookup.contains("EPAP")) { sig=stredf.lookup["EPAP"]; epap=sig->data[dn]*sig->gain; sess->settings[CPAP_EPAP]=epap; } if (stredf.lookup.contains("IPAP")) { sig=stredf.lookup["IPAP"]; ipap=sig->data[dn]*sig->gain; sess->settings[CPAP_IPAP]=ipap; } if (stredf.lookup.contains("PS")) { sig=stredf.lookup["PS"]; tmp=sig->data[dn]*sig->gain; sess->settings[CPAP_PS]=tmp; // technically this is IPAP-EPAP if (!ipap) { // not really possible. but anyway, just in case.. sess->settings[CPAP_IPAP]=epap+tmp; } } if (stredf.lookup.contains("Min PS")) { sig=stredf.lookup["Min PS"]; tmp=sig->data[dn]*sig->gain; sess->settings[CPAP_PSMin]=tmp; sess->settings[CPAP_IPAPLo]=epap+tmp; sess->setMin(CPAP_IPAP,epap+tmp); } if (stredf.lookup.contains("Max PS")) { sig=stredf.lookup["Max PS"]; tmp=sig->data[dn]*sig->gain; sess->settings[CPAP_PSMax]=tmp; sess->settings[CPAP_IPAPHi]=epap+tmp; } if (stredf.lookup.contains("RR")) { // Is this a setting to force respiratory rate on S/T machines? sig=stredf.lookup["RR"]; tmp=sig->data[dn]; sess->settings[CPAP_RespRate]=tmp*sig->gain; } if (stredf.lookup.contains("Easy-Breathe")) { sig=stredf.lookup["Easy-Breathe"]; tmp=sig->data[dn]*sig->gain; sess->settings[CPAP_PresReliefSet]=tmp; sess->settings[CPAP_PresReliefType]=(int)PR_EASYBREATHE; sess->settings[CPAP_PresReliefMode]=(int)PM_FullTime; } } 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); } } } // The following only happens when the STR.edf file is not up to date.. // This will only happen when the user fails to back up their SDcard properly. // Basically takes a guess. if (!sess->settings.contains(CPAP_Mode)) { //The following is a lame assumption if 50th percentile == max, then it's CPAP EventDataType p50=sess->percentile(CPAP_Pressure,0.50); EventDataType max=sess->Max(CPAP_Pressure); if (max==p50) { sess->settings[CPAP_Mode]=MODE_CPAP; sess->settings[CPAP_PressureMin]=p50; } else { // It's not cpap, so just take the highest setting for this machines history. // This may fail if the missing str data is at the beginning of a fresh import. CPAPMode mode=(CPAPMode)(int)PROFILE.calcSettingsMax(CPAP_Mode,MT_CPAP,sess->machine()->FirstDay(),sess->machine()->LastDay()); if (mode<MODE_APAP) mode=MODE_APAP; sess->settings[CPAP_Mode]=mode; // Assuming 10th percentile should cover for ramp/warmup sess->settings[CPAP_PressureMin]=sess->percentile(CPAP_Pressure,0.10); sess->settings[CPAP_PressureMax]=sess->Max(CPAP_Pressure); } } //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); } } #ifdef DEBUG_EFFICIENCY { qint64 totalbytes=0; qint64 totalns=0; qDebug() << "Time Delta Efficiency Information"; for (QHash<ChannelID,qint64>::iterator it=channel_efficiency.begin();it!=channel_efficiency.end();it++) { ChannelID code=it.key(); qint64 value=it.value(); qint64 ns=channel_time[code]; totalbytes+=value; totalns+=ns; double secs=double(ns)/1000000000.0L; QString s=value < 0 ? "saved" : "cost"; qDebug() << "Time-Delta conversion for "+schema::channel[code].label()+" "+s+" "+QString::number(qAbs(value))+" bytes and took "+QString::number(secs,'f',4)+"s"; } qDebug() << "Total toTimeDelta function usage:" << totalbytes << "in" << double(totalns)/1000000000.0 << "seconds"; } #endif if (m) { 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); EventList *OA=NULL, *HY=NULL, *CA=NULL, *UA=NULL; // Allow for empty sessions.. OA=sess->AddEventList(CPAP_Obstructive,EVL_Event); HY=sess->AddEventList(CPAP_Hypopnea,EVL_Event); UA=sess->AddEventList(CPAP_Apnea,EVL_Event); for (int s=0;s<edf.GetNumSignals();s++) { recs=edf.edfsignals[s]->nr*edf.GetNumDataRecords()*2; data=(char *)edf.edfsignals[s]->data; pos=0; tt=edf.startdate; sess->updateFirst(tt); duration=0; while (pos<recs) { c=data[pos]; if ((c!='+') && (c!='-')) break; if (data[pos++]=='+') sign=true; else sign=false; t=""; c=data[pos]; do { t+=c; pos++; c=data[pos]; } while ((c!=20) && (c!=21)); // start code d=t.toDouble(&ok); if (!ok) { qDebug() << "Faulty EDF EVE file " << edf.filename; break; } if (!sign) d=-d; tt=edf.startdate+qint64(d*1000.0); duration=0; // First entry if (data[pos]==21) { pos++; // get duration. t=""; do { t+=data[pos]; pos++; } while ((data[pos]!=20) && (pos<recs)); // start code duration=t.toDouble(&ok); if (!ok) { qDebug() << "Faulty EDF EVE file (at %" << pos << ") " << edf.filename; break; } } while ((data[pos]==20) && (pos<recs)) { t=""; pos++; if (data[pos]==0) break; if (data[pos]==20) { pos++; break; } do { t+=tolower(data[pos++]); } while ((data[pos]!=20) && (pos<recs)); // start code if (!t.isEmpty()) { if (t=="obstructive apnea") { OA->AddEvent(tt,duration); } else if (t=="hypopnea") { HY->AddEvent(tt,duration+10); // Only Hyponea's Need the extra duration??? } else if (t=="apnea") { UA->AddEvent(tt,duration); } else if (t=="central apnea") { // Not all machines have it, so only create it when necessary.. if (!CA) { if (!(CA=sess->AddEventList(CPAP_ClearAirway,EVL_Event))) return false; } CA->AddEvent(tt,duration); } else { if (t!="recording starts") { qDebug() << "Unobserved ResMed annotation field: " << t; } } } if (pos>=recs) { qDebug() << "Short EDF EVE file" << edf.filename; break; } // pos++; } while ((data[pos]==0) && pos<recs) pos++; if (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;s<edf.GetNumSignals();s++) { EDFSignal & es=*edf.edfsignals[s]; //qDebug() << "BRP:" << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum; long recs=es.nr*edf.GetNumDataRecords(); ChannelID code; if (es.label=="Flow") { es.gain*=60; es.physical_dimension="L/M"; code=CPAP_FlowRate; } else if (es.label.startsWith("Mask Pres")) { code=CPAP_MaskPressureHi; } else if (es.label.startsWith("Resp Event")) { code=CPAP_RespEvent; } else { qDebug() << "Unobserved ResMed BRP Signal " << edf.edfsignals[s]->label; continue; } double rate=double(duration)/double(recs); EventList *a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->setDimension(es.physical_dimension); a->AddWaveform(edf.startdate,es.data,recs,duration); sess->setMin(code,a->Min()); sess->setMax(code,a->Max()); } return true; } EventList * ResmedLoader::ToTimeDelta(Session *sess,EDFParser &edf, EDFSignal & es, ChannelID code, long recs, qint64 duration,EventDataType min,EventDataType max,bool square) { #ifdef DEBUG_EFFICIENCY QElapsedTimer time; time.start(); #endif double rate=(duration/recs); // milliseconds per record double tt=edf.startdate; //sess->UpdateFirst(tt); EventDataType c,last; int startpos=0; if ((code==CPAP_Pressure) || (code==CPAP_IPAP) || (code==CPAP_EPAP)) { startpos=20; // Shave the first 20 seconds of pressure data tt+=rate*startpos; } qint16 * sptr=es.data; qint16 * eptr=sptr+recs; sptr+=startpos; EventList *el=NULL; if (recs>startpos+1) { el=sess->AddEventList(code,EVL_Event,es.gain,es.offset,min,max); c=last=*sptr++; el->AddEvent(tt,last); for (; sptr < eptr; sptr++) { //int i=startpos;i<recs;i++) { c=*sptr; //es.data[i]; if (last!=c) { if (square) el->AddEvent(tt,last); // square waves look better on some charts. el->AddEvent(tt,c); } tt+=rate; last=c; } el->AddEvent(tt,c); sess->updateLast(tt); } #ifdef DEBUG_EFFICIENCY qint64 t=time.nsecsElapsed(); int cnt=el->count(); int bytes=cnt * (sizeof(EventStoreType)+sizeof(quint32)); int wvbytes=recs * (sizeof(EventStoreType)); QHash<ChannelID,qint64>::iterator it=channel_efficiency.find(code); if (it==channel_efficiency.end()) { channel_efficiency[code]=wvbytes-bytes; channel_time[code]=t; } else { it.value()+=wvbytes-bytes; channel_time[code]+=t; } #endif return el; } bool ResmedLoader::LoadSAD(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;s<edf.GetNumSignals();s++) { EDFSignal & es=*edf.edfsignals[s]; //qDebug() << "SAD:" << es.label << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum; long recs=es.nr*edf.GetNumDataRecords(); ChannelID code; if (es.label.startsWith("Puls")) { code=OXI_Pulse; } else if (es.label=="SpO2") { code=OXI_SPO2; } else { qDebug() << "Unobserved ResMed SAD Signal " << edf.edfsignals[s]->label; continue; } bool hasdata=false; for (int i=0;i<recs;i++) { if (es.data[i]!=-1) { hasdata=true; break; } } if (hasdata) { EventList *a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); if (a) { sess->setMin(code,a->Min()); sess->setMax(code,a->Max()); } } } 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 }; qint64 duration=edf.GetNumDataRecords()*edf.GetDuration(); sess->updateFirst(edf.startdate); sess->updateLast(edf.startdate+duration); QString t; int emptycnt=0; EventList *a=NULL; double rate; long recs; ChannelID code; for (int s=0;s<edf.GetNumSignals();s++) { EDFSignal & es=*edf.edfsignals[s]; recs=es.nr*edf.GetNumDataRecords(); if (recs<=0) continue; rate=double(duration)/double(recs); //qDebug() << "EVE:" << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum << es.gain; if (es.label=="Snore Index") { code=CPAP_Snore; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("Therapy Pres")) { code=CPAP_Pressure; //TherapyPressure; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="Insp Pressure") { code=CPAP_IPAP; //TherapyPressure; sess->settings[CPAP_Mode]=MODE_BIPAP; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if ((es.label=="MV") || (es.label=="VM")){ code=CPAP_MinuteVent; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if ((es.label=="RR") || (es.label=="AF") || (es.label=="FR")) { code=CPAP_RespRate; a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); } else if ((es.label=="Vt") || (es.label=="VC")) { code=CPAP_TidalVolume; es.physical_maximum=es.physical_minimum=0; es.gain*=1000.0; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if ((es.label=="Leak") || (es.label.startsWith("Leck")) || (es.label.startsWith("Lekk"))) { code=CPAP_Leak; es.gain*=60; es.physical_dimension="L/M"; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0,true); } else if (es.label=="FFL Index") { code=CPAP_FLG; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("Mask Pres")) { code=CPAP_MaskPressure; a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("Exp Press")) { code=CPAP_EPAP;//ExpiratoryPressure a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("I:E")) { code=CPAP_IE;//I:E ratio? a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); //a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("Ti")) { code=CPAP_Ti; a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); //a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("Te")) { code=CPAP_Te; a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); //a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label.startsWith("TgMV")) { code=CPAP_TgMV; a=sess->AddEventList(code,EVL_Waveform,es.gain,es.offset,0,0,rate); a->AddWaveform(edf.startdate,es.data,recs,duration); //a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label=="") { if (emptycnt==0) { code=RMS9_E01; a=ToTimeDelta(sess,edf,es, code,recs,duration); } else if (emptycnt==1) { code=RMS9_E02; a=ToTimeDelta(sess,edf,es, code,recs,duration); } else { qDebug() << "Unobserved Empty Signal " << es.label; } emptycnt++; } else { qDebug() << "Unobserved ResMed PLD Signal " << es.label; a=NULL; } if (a) { sess->setMin(code,a->Min()); sess->setMax(code,a->Max()); a->setDimension(es.physical_dimension); } } return true; } void ResInitModelMap() { const QString RMS9_STR_Escape="S9 Escape"; const QString RMS9_STR_EscapeAuto="S9 Escape Auto"; const QString RMS9_STR_Elite="S9 Elite"; const QString RMS9_STR_AutoSet="S9 AutoSet"; const QString RMS9_STR_AutoSetForHer="S9 AutoSet for Her"; const QString RMS9_STR_AutoSetCS="S9 AutoSet CS"; const QString RMS9_STR_AutoSet25="S9 AutoSet 25"; const QString RMS9_STR_VPAP_S="S9 VPAP S"; const QString RMS9_STR_VPAP_Auto="S9 VPAP Auto"; const QString RMS9_STR_VPAP_Adapt="S9 VPAP Adapt"; const QString RMS9_STR_VPAP_ST="S9 VPAP ST"; const QString RMS9_STR_VPAP_STA="S9 VPAP ST-A"; const QString RMS9_STR_VPAP_ST22="S9 VPAP ST 22"; // Escape Series RMS9ModelMap[36001]=RMS9ModelMap[36011]=RMS9ModelMap[36021]=RMS9ModelMap[36141]= RMS9ModelMap[36201]=RMS9ModelMap[36221]=RMS9ModelMap[36261]=RMS9ModelMap[36301]= RMS9ModelMap[36361]=RMS9_STR_Escape; // Escape Auto Series RMS9ModelMap[36002]=RMS9ModelMap[36012]=RMS9ModelMap[36022]=RMS9ModelMap[36302]= RMS9ModelMap[36362]=RMS9_STR_EscapeAuto; // Elite Series RMS9ModelMap[36003]=RMS9ModelMap[36013]=RMS9ModelMap[36023]=RMS9ModelMap[36103]= RMS9ModelMap[36113]=RMS9ModelMap[36123]=RMS9ModelMap[36143]=RMS9ModelMap[36203]= RMS9ModelMap[36223]=RMS9ModelMap[36243]=RMS9ModelMap[36263]=RMS9ModelMap[36303]= RMS9ModelMap[36343]=RMS9ModelMap[36363]=RMS9_STR_Elite; // AutoSet Series RMS9ModelMap[36005]=RMS9ModelMap[36015]=RMS9ModelMap[36025]=RMS9ModelMap[36105]= RMS9ModelMap[36115]=RMS9ModelMap[36125]=RMS9ModelMap[36145]=RMS9ModelMap[36205]= RMS9ModelMap[36225]=RMS9ModelMap[36245]=RMS9ModelMap[36265]=RMS9ModelMap[36305]= RMS9ModelMap[36325]=RMS9ModelMap[36345]=RMS9ModelMap[36365]=RMS9_STR_AutoSet; // AutoSet CS Series RMS9ModelMap[36100]=RMS9ModelMap[36110]=RMS9ModelMap[36120]=RMS9ModelMap[36140]= RMS9ModelMap[36200]=RMS9ModelMap[36220]=RMS9ModelMap[36360]=RMS9_STR_AutoSetCS; // AutoSet 25 Series RMS9ModelMap[36106]=RMS9ModelMap[36116]=RMS9ModelMap[36126]=RMS9ModelMap[36146]= RMS9ModelMap[36206]=RMS9ModelMap[36226]=RMS9ModelMap[36366]=RMS9_STR_AutoSet25; // Girly "For Her" AutoSet Series RMS9ModelMap[36065]=RMS9_STR_AutoSetForHer; // VPAP S Series (+H5i +Climate Control) RMS9ModelMap[36004]=RMS9ModelMap[36014]=RMS9ModelMap[36024]=RMS9ModelMap[36114]= RMS9ModelMap[36124]=RMS9ModelMap[36144]=RMS9ModelMap[36204]=RMS9ModelMap[36224]= RMS9ModelMap[36284]=RMS9ModelMap[36304]=RMS9_STR_VPAP_S; // VPAP Auto Series (+H5i +Climate Control) RMS9ModelMap[36006]=RMS9ModelMap[36016]=RMS9ModelMap[36026]=RMS9_STR_VPAP_Auto; // VPAP Adapt Series (+H5i +Climate Control) RMS9ModelMap[36007]=RMS9ModelMap[36017]=RMS9ModelMap[36027]=RMS9ModelMap[36367]=RMS9_STR_VPAP_Adapt; // VPAP ST Series (+H5i +Climate Control) RMS9ModelMap[36008]=RMS9ModelMap[36018]=RMS9ModelMap[36028]=RMS9ModelMap[36108]= RMS9ModelMap[36148]=RMS9ModelMap[36208]=RMS9ModelMap[36228]=RMS9ModelMap[36368]=RMS9_STR_VPAP_ST; // VPAP ST 22 Series RMS9ModelMap[36118]=RMS9ModelMap[36128]=RMS9_STR_VPAP_ST22; // VPAP ST-A Series RMS9ModelMap[36039]=RMS9ModelMap[36159]=RMS9ModelMap[36169]=RMS9ModelMap[36379]=RMS9_STR_VPAP_STA; // 36003, 36013, 36023, 36103, 36113, 36123, 36143, 36203, // 36223, 36243, 36263, 36303, 36343, 36363 S9 Elite Series // 36005, 36015, 36025, 36105, 36115, 36125, 36145, 36205, // 36225, 36245, 36265, 36305, 36325, 36345, 36365 S9 AutoSet Series // 36065 S9 AutoSet for Her // 36001, 36011, 36021, 36141, 36201, 36221, 36261, 36301, // 36361 S9 Escape // 36002, 36012, 36022, 36302, 36362 S9 Escape Auto // 36004, 36014, 36024, 36114, 36124, 36144, 36204, 36224, // 36284, 36304 S9 VPAP S (+ H5i, + Climate Control) // 36006, 36016, 36026 S9 VPAP AUTO (+ H5i, + Climate Control) // 36007, 36017, 36027, 36367 // S9 VPAP ADAPT (+ H5i, + Climate // Control) // 36008, 36018, 36028, 36108, 36148, 36208, 36228, 36368 S9 VPAP ST (+ H5i, + Climate Control) // 36100, 36110, 36120, 36140, 36200, 36220, 36360 S9 AUTOSET CS // 36106, 36116, 36126, 36146, 36206, 36226, 36366 S9 AUTOSET 25 // 36118, 36128 S9 VPAP ST 22 // 36039, 36159, 36169, 36379 S9 VPAP ST-A // 24921, 24923, 24925, 24926, 24927 ResMed Power Station II (RPSII) // 33030 S8 Compact // 33001, 33007, 33013, 33036, 33060 S8 Escape // 33032 S8 Lightweight // 33033 S8 AutoScore // 33048, 33051, 33052, 33053, 33054, 33061 S8 Escape II // 33055 S8 Lightweight II // 33021 S8 Elite // 33039, 33045, 33062, 33072, 33073, 33074, 33075 S8 Elite II // 33044 S8 AutoScore II // 33105, 33112, 33126 S8 AutoSet (including Spirit & Vantage) // 33128, 33137 S8 Respond // 33129, 33141, 33150 S8 AutoSet II // 33136, 33143, 33144, 33145, 33146, 33147, 33148 S8 AutoSet Spirit II // 33138 S8 AutoSet C // 26101, 26121 VPAP Auto 25 // 26119, 26120 VPAP S // 26110, 26122 VPAP ST // 26104, 26105, 26125, 26126 S8 Auto 25 // 26102, 26103, 26106, 26107, 26108, 26109, 26123, 26127 VPAP IV // 26112, 26113, 26114, 26115, 26116, 26117, 26118, 26124 VPAP IV ST /* S8 Series RMS9ModelMap[33007]="S8 Escape"; RMS9ModelMap[33039]="S8 Elite II"; RMS9ModelMap[33051]="S8 Escape II"; RMS9ModelMap[33064]="S8 Escape II AutoSet"; RMS9ModelMap[33064]="S8 Escape II AutoSet"; RMS9ModelMap[33129]="S8 AutoSet II"; */ resmed_codes[CPAP_FlowRate].push_back("Flow"); resmed_codes[CPAP_MaskPressureHi].push_back("Mask Pres"); resmed_codes[CPAP_MaskPressureHi].push_back("Mask Pressure"); // vpap resmed_codes[CPAP_RespEvent].push_back("Resp Event"); resmed_codes[CPAP_MaskPressure].push_back("Mask Pres"); resmed_codes[CPAP_MaskPressure].push_back("Mask Pressure"); // vpap resmed_codes[CPAP_Pressure].push_back("Therapy Pres"); // not on vpap resmed_codes[CPAP_IPAP].push_back("Insp Pressure"); // on vpap resmed_codes[CPAP_EPAP].push_back("Exp Press"); resmed_codes[CPAP_EPAP].push_back("Exp Pressure"); // vpap resmed_codes[CPAP_Leak].push_back("Leak"); resmed_codes[CPAP_Leak].push_back("Leck."); resmed_codes[CPAP_RespRate].push_back("RR"); resmed_codes[CPAP_RespRate].push_back("AF"); resmed_codes[CPAP_RespRate].push_back("FR"); resmed_codes[CPAP_TidalVolume].push_back("Vt"); resmed_codes[CPAP_TidalVolume].push_back("VC"); resmed_codes[CPAP_MinuteVent].push_back("MV"); resmed_codes[CPAP_MinuteVent].push_back("VM"); resmed_codes[CPAP_IE].push_back("I:E"); // vpap resmed_codes[CPAP_Snore].push_back("Snore Index"); resmed_codes[CPAP_FLG].push_back("FFL Index"); resmed_codes[CPAP_RespEvent].push_back("RE"); resmed_codes[CPAP_Ti].push_back("Ti"); resmed_codes[CPAP_Te].push_back("Te"); // Sad (oximetry) resmed_codes[OXI_Pulse].push_back("Pulse"); resmed_codes[OXI_Pulse].push_back("Puls"); // German resmed_codes[OXI_Pulse].push_back("Pols"); // Dutch resmed_codes[OXI_SPO2].push_back("SpO2"); // Event annotations resmed_codes[CPAP_Obstructive].push_back("Obstructive apnea"); resmed_codes[CPAP_Hypopnea].push_back("Hypopnea"); resmed_codes[CPAP_Apnea].push_back("Apnea"); resmed_codes[CPAP_ClearAirway].push_back("Central apnea"); resmed_codes[CPAP_Mode].push_back("Mode"); resmed_codes[CPAP_Mode].push_back("Modus"); // Dutch & German resmed_codes[RMS9_SetPressure].push_back("Eingest. Druck"); // German resmed_codes[RMS9_SetPressure].push_back("Ingestelde druk"); // Dutch resmed_codes[RMS9_SetPressure].push_back("Set Pressure"); // English - Prescription resmed_codes[RMS9_SetPressure].push_back("Pres. prescrite"); // French resmed_codes[RMS9_EPR].push_back("EPR"); resmed_codes[RMS9_EPRSet].push_back("EPR Level"); resmed_codes[RMS9_EPRSet].push_back("EPR-Stufe"); // French resmed_codes[RMS9_EPRSet].push_back("EPR-niveau"); // Dutch resmed_codes[RMS9_EPRSet].push_back("Niveau EPR"); // German resmed_codes[CPAP_PressureMax].push_back("Max Pressure"); resmed_codes[CPAP_PressureMax].push_back("Max. Druck"); // German resmed_codes[CPAP_PressureMax].push_back("Max druk"); // Dutch resmed_codes[CPAP_PressureMax].push_back("Pression max."); // French resmed_codes[CPAP_PressureMin].push_back("Min Pressure"); resmed_codes[CPAP_PressureMin].push_back("Min. Druck"); // German resmed_codes[CPAP_PressureMin].push_back("Min druk"); // Dutch resmed_codes[CPAP_PressureMin].push_back("Pression min."); // French // STR.edf } bool resmed_initialized=false; void ResmedLoader::Register() { if (resmed_initialized) return; qDebug() << "Registering ResmedLoader"; RegisterLoader(new ResmedLoader()); ResInitModelMap(); resmed_initialized=true; }