OSCAR-code/SleepLib/loader_plugins/resmed_loader.cpp

1482 lines
53 KiB
C++

/*
SleepLib ResMed Loader Implementation
Author: Mark Watkins <jedimark64@users.sourceforge.net>
License: GPL
*/
#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"
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::fromAscii(header.version,8).toLong(&ok);
if (!ok)
return false;
//patientident=QString::fromAscii(header.patientident,80);
recordingident=QString::fromAscii(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::fromAscii(header.datetime,16),"dd.MM.yyHH.mm.ss");
//startDate.toTimeSpec(Qt::UTC);
QDate d2=startDate.date();
if (d2.year()<2000) {
d2.setYMD(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::fromAscii(header.num_header_bytes,8).toLong(&ok);
if (!ok)
return false;
//reserved44=QString::fromAscii(header.reserved,44);
num_data_records=QString::fromAscii(header.num_data_records,8).toLong(&ok);
if (!ok)
return false;
dur_data_record=QString::fromAscii(header.dur_data_records,8).toDouble(&ok)*1000.0;
if (!ok)
return false;
num_signals=QString::fromAscii(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)
{
QFile f(name);
if (!f.open(QIODevice::ReadOnly)) return false;
if (!f.isReadable()) return false;
filename=name;
filesize=f.size();
datasize=filesize-EDFHeaderSize;
if (datasize<0) return false;
//Urk.. This needs fixing for VC++, as it doesn't have packed attribute type..
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;
QString a;
a.sprintf("%i",resmed_data_version);
m->properties[STR_PROP_DataVersion]=a;
m->properties[STR_PROP_Path]="{"+STR_GEN_DataFolder+"}/"+m->GetClass()+"_"+serial+"/";
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";
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()) {
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<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..
///////////////////////////////////////////////////////////////////////////////////
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;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 ext,rest,datestr;//,s,codestr;
SessionID sessionid;
QDateTime date;
int size=flist.size();
sessfiles.clear();
// For each file in filelist...
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;
// Check the file extnsion
ext=filename.section(".",1).toLower();
if (ext!=ext_EDF) continue;
// Split the filename into components
rest=filename.section(".",0,0);
datestr=filename.section("_",0,1);
// Take the filename's date, and convert it to epoch to form the sessionID.
date=QDateTime::fromString(datestr,"yyyyMMdd_HHmmss");
if (!date.isValid())
continue; // Skip file if dates invalid
sessionid=date.toTime_t();
////////////////////////////////////////////////////////////////////////////////////////////
// Resmed bugs up on the session filenames.. 1 or 2 seconds either way
// Moral of the story, when writing firmware and saving in batches, use the same datetimes.
////////////////////////////////////////////////////////////////////////////////////////////
if (sessfiles.find(sessionid)==sessfiles.end()) {
if (sessfiles.find(sessionid+2)!=sessfiles.end()) sessionid+=2;
else if (sessfiles.find(sessionid+1)!=sessfiles.end()) sessionid++;
else if (sessfiles.find(sessionid-1)!=sessfiles.end()) sessionid--;
else if (sessfiles.find(sessionid-2)!=sessfiles.end()) sessionid-=2;
}
// 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();
}
QString fn;
Session *sess;
int cnt=0;
size=sessfiles.size();
QHash<SessionID,int> sessday;
/////////////////////////////////////////////////////////////////////////////
// Scan over file list and knock out of dayused list
/////////////////////////////////////////////////////////////////////////////
for (QMap<SessionID,QVector<QString> >::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.
int dn=-1;
for (int j=edn;j<strfirst.size();j++){
time_t st=strfirst.at(j);
time_t et=strlast.at(j);
if (sessionid>=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;dn<days;dn++) {
if (!dayused[dn]) continue; // Skip days with loadable data.
if (!daystarttimes.contains(dn)) continue;
int j;
int scnt=daystarttimes[dn].size();
sess=NULL;
// For each mask on/off segment.
for (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 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;
if ((sig=stredf.lookupName("Leak Med"))) {
valmed=sig->data[dn];
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];
sess->m_valuesummary[CPAP_Leak][val95]=45;
}
if ((sig=stredf.lookupName("Leak Max"))) {
valmax=sig->data[dn];
sess->setMax(CPAP_Leak,valmax*sig->gain*60.0);
sess->m_valuesummary[CPAP_Leak][valmax]=4;
}
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;
}
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;
}
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;
}
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;
}
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;
}
if ((sig=stredf.lookupName("Mask Pres Med"))) {
valmed=sig->data[dn];
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];
sess->m_valuesummary[CPAP_Pressure][val95]=45;
}
if ((sig=stredf.lookupName("Mask Pres Max"))) {
valmax=sig->data[dn];
sess->setMax(CPAP_Pressure,valmax*sig->gain);
sess->m_valuesummary[CPAP_Pressure][valmax]=4;
}
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;
}
if ((sig=stredf.lookupName("Exp Pres Med"))) {
valmed=sig->data[dn];
sess->m_gain[CPAP_EPAP]=sig->gain;
sess->m_valuesummary[CPAP_EPAP][valmed]=51;
}
if ((sig=stredf.lookupName("Exp Pres 95"))) {
val95=sig->data[dn];
sess->m_valuesummary[CPAP_EPAP][val95]=45;
}
if ((sig=stredf.lookupName("Exp Pres Max"))) {
valmax=sig->data[dn];
sess->setMax(CPAP_EPAP,valmax*sig->gain);
sess->m_valuesummary[CPAP_EPAP][valmax]=4;
}
if ((sig=stredf.lookupName("Mask Dur"))) {
dur=sig->data[dn]*sig->gain;
}
if ((sig=stredf.lookupName("OAI"))) {
tmp=sig->data[dn]*sig->gain;
sess->setCph(CPAP_Obstructive,tmp);
sess->setCount(CPAP_Obstructive,tmp*(dur/60.0));
}
if ((sig=stredf.lookupName("HI"))) {
tmp=sig->data[dn]*sig->gain;
sess->setCph(CPAP_Hypopnea,tmp);
sess->setCount(CPAP_Hypopnea,tmp*(dur/60.0));
}
if ((sig=stredf.lookupName("UAI"))) {
tmp=sig->data[dn]*sig->gain;
sess->setCph(CPAP_Apnea,tmp);
sess->setCount(CPAP_Apnea,tmp*(dur/60.0));
}
if ((sig=stredf.lookupName("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<SessionID,QVector<QString> >::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<si.value().size();++i) {
QString filename=si.value()[i]+".";
QString fullpath=newpath+filename;
// Copy the EDF file to the backup folder
QString backup_file=backup_path+datalog+"/"+filename;
QFile().copy(fullpath+ext_EDF, backup_file+ext_EDF);
QFile().copy(fullpath+ext_CRC, backup_file+ext_CRC);
fullpath+=ext_EDF;
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=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(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);
}
}
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);
for (int s=0;s<edf.GetNumSignals();s++) {
recs=edf.edfsignals[s]->nr*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 (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") {
if (!EL[0]) {
if (!(EL[0]=sess->AddEventList(CPAP_Obstructive,EVL_Event))) return false;
}
EL[0]->AddEvent(tt,duration);
} else if (t=="hypopnea") {
if (!EL[1]) {
if (!(EL[1]=sess->AddEventList(CPAP_Hypopnea,EVL_Event))) return false;
}
EL[1]->AddEvent(tt,duration+10); // Only Hyponea's Need the extra duration???
} else if (t=="apnea") {
if (!EL[2]) {
if (!(EL[2]=sess->AddEventList(CPAP_Apnea,EVL_Event))) return false;
}
EL[2]->AddEvent(tt,duration);
} else if (t=="central apnea") {
//code=CPAP_ClearAirway;
if (!EL[3]) {
if (!(EL[3]=sess->AddEventList(CPAP_ClearAirway,EVL_Event))) return false;
}
EL[3]->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=edf.edfsignals[s]->nr*edf.GetNumDataRecords();
ChannelID code;
if (edf.edfsignals[s]->label=="Flow") {
es.gain*=60;
es.physical_dimension="L/M";
code=CPAP_FlowRate;
} else if (edf.edfsignals[s]->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());
//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 square)
{
bool first=true;
double rate=(duration/recs); // milliseconds per record
double tt=edf.startdate;
//sess->UpdateFirst(tt);
EventDataType c,last;
EventList *el=sess->AddEventList(code,EVL_Event,es.gain,es.offset,min,max);
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;
}
for (int i=startpos;i<recs;i++) {
c=es.data[i];
if (first) {
el->AddEvent(tt,c);
first=false;
} else {
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);
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=edf.edfsignals[s]->nr*edf.GetNumDataRecords();
ChannelID code;
if (edf.edfsignals[s]->label.startsWith("Puls")) {
code=OXI_Pulse;
} else if (edf.edfsignals[s]->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;
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"))) {
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()
{
// Courtesy Troy Schultz
RMS9ModelMap[36001]="S9 Escape";
RMS9ModelMap[36002]="S9 Escape Auto";
RMS9ModelMap[36003]="S9 Elite";
RMS9ModelMap[36004]="S9 VPAP S";
RMS9ModelMap[36005]="S9 AutoSet";
RMS9ModelMap[36006]="S9 VPAP Auto";
RMS9ModelMap[36007]="S9 VPAP Adapt";
RMS9ModelMap[36008]="S9 VPAP 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");
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");
resmed_codes[RMS9_SetPressure].push_back("Eingest. Druck");
resmed_codes[RMS9_SetPressure].push_back("Set Pressure"); // Prescription
resmed_codes[RMS9_SetPressure].push_back("Pres. prescrite");
resmed_codes[RMS9_EPR].push_back("EPR");
resmed_codes[RMS9_EPRSet].push_back("EPR Level");
resmed_codes[RMS9_EPRSet].push_back("EPR-Stufe");
resmed_codes[RMS9_EPRSet].push_back("Niveau EPR");
resmed_codes[CPAP_PressureMax].push_back("Max Pressure");
resmed_codes[CPAP_PressureMax].push_back("Max. Druck");
resmed_codes[CPAP_PressureMax].push_back("Pression max.");
resmed_codes[CPAP_PressureMin].push_back("Min Pressure");
resmed_codes[CPAP_PressureMin].push_back("Min. Druck");
resmed_codes[CPAP_PressureMin].push_back("Pression min.");
// STR.edf
}
bool resmed_initialized=false;
void ResmedLoader::Register()
{
if (resmed_initialized) return;
qDebug() << "Registering ResmedLoader";
RegisterLoader(new ResmedLoader());
ResInitModelMap();
resmed_initialized=true;
}