From 531edc6ad27acc97448d59dbaa754fc0e9017829 Mon Sep 17 00:00:00 2001 From: Phil Olynyk Date: Wed, 31 Jul 2019 14:36:40 -0400 Subject: [PATCH] Interim commit - resmed_loader still needs work --- oscar/SleepLib/loader_plugins/edfparser.cpp | 237 +++++++++++------- oscar/SleepLib/loader_plugins/edfparser.h | 118 +++++---- .../SleepLib/loader_plugins/resmed_loader.cpp | 149 +++++++---- oscar/SleepLib/loader_plugins/resmed_loader.h | 218 ++++------------ 4 files changed, 361 insertions(+), 361 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/edfparser.cpp b/oscar/SleepLib/loader_plugins/edfparser.cpp index e1e4a5ab..809583f1 100644 --- a/oscar/SleepLib/loader_plugins/edfparser.cpp +++ b/oscar/SleepLib/loader_plugins/edfparser.cpp @@ -19,100 +19,104 @@ #include "edfparser.h" -EDFParser::EDFParser(QString name) +EDFInfo::EDFInfo() { filesize = 0; datasize = 0; signalPtr = nullptr; hdrPtr = nullptr; - fileData.clear(); - if (!name.isEmpty()) - Open(name); + fileData = nullptr; } -EDFParser::~EDFParser() + +EDFInfo::~EDFInfo() { for (auto & s : edfsignals) { if (s.value) delete [] s.value; } +// for (auto & a : annotations) +// delete a; } -bool EDFParser::Open(const QString & name) +QByteArray * EDFInfo::Open(const QString & name) { if (hdrPtr != nullptr) { - qWarning() << "EDFParser::Open() called with file already open " << name; + qWarning() << "EDFInfo::Open() called with file already open " << name; sleep(1); - return false; + return nullptr; } QFile fi(name); if (!fi.open(QFile::ReadOnly)) { - qDebug() << "EDFParser::Open() Couldn't open file " << name; + qDebug() << "EDFInfo::Open() Couldn't open file " << name; sleep(1); - return false; + return nullptr; } + fileData = new QByteArray(); if (name.endsWith(STR_ext_gz)) { - fileData = gUncompress(fi.readAll()); // Open and decompress file + *fileData = gUncompress(fi.readAll()); // Open and decompress file } else { - fileData = fi.readAll(); // Open and read uncompressed file + *fileData = fi.readAll(); // Open and read uncompressed file } fi.close(); - if (fileData.size() <= EDFHeaderSize) { - qDebug() << "EDFParser::Open() File too short " << name; + if (fileData->size() <= EDFHeaderSize) { + delete fileData; + qDebug() << "EDFInfo::Open() File too short " << name; sleep(1); - return false; + return nullptr; } - hdrPtr = (EDFHeaderRaw *)fileData.constData(); - signalPtr = (char *)fileData.constData() + EDFHeaderSize; filename = name; - filesize = fileData.size(); - datasize = filesize - EDFHeaderSize; - pos = 0; - return true; + return fileData; } -bool EDFParser::Parse() +bool EDFInfo::Parse(QByteArray * fileData ) { bool ok; - if (hdrPtr == nullptr) { - qWarning() << "EDFParser::Parse() called without valid EDF data " << filename; + if (fileData == nullptr) { + qWarning() << "EDFInfo::Parse() called without valid EDF data " << filename; sleep(1); return false; } + + hdrPtr = (EDFHeaderRaw *)(*fileData).constData(); + signalPtr = (char *)(*fileData).constData() + EDFHeaderSize; + filesize = (*fileData).size(); + datasize = filesize - EDFHeaderSize; + pos = 0; eof = false; edfHdr.version = QString::fromLatin1(hdrPtr->version, 8).toLong(&ok); if (!ok) { - qWarning() << "EDFParser::Parser() Bad Version " << filename; + qWarning() << "EDFInfo::Parse() Bad Version " << filename; sleep(1); return false; } - //patientident=QString::fromLatin1(header.patientident,80); + edfHdr.patientident=QString::fromLatin1(hdrPtr->patientident,80); edfHdr.recordingident = QString::fromLatin1(hdrPtr->recordingident, 80); // Serial number is in here.. edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss"); edfHdr.num_header_bytes = QString::fromLatin1(hdrPtr->num_header_bytes, 8).toLong(&ok); if (!ok) { - qWarning() << "EDFParser::Parde() Bad header byte count " << filename; + qWarning() << "EDFInfo::Parse() Bad header byte count " << filename; sleep(1); return false; } edfHdr.reserved44=QString::fromLatin1(hdrPtr->reserved, 44); edfHdr.num_data_records = QString::fromLatin1(hdrPtr->num_data_records, 8).toLong(&ok); if (!ok) { - qWarning() << "EDFParser::Parse() Bad data record count " << filename; + qWarning() << "EDFInfo::Parse() Bad data record count " << filename; sleep(1); return false; } edfHdr.duration_Seconds = QString::fromLatin1(hdrPtr->dur_data_records, 8).toDouble(&ok); if (!ok) { - qWarning() << "EDFParser::Parse() Bad duration " << filename; + qWarning() << "EDFInfo::Parse() Bad duration " << filename; sleep(1); return false; } edfHdr.num_signals = QString::fromLatin1(hdrPtr->num_signals, 4).toLong(&ok); if (!ok) { - qWarning() << "EDFParser::Parse() Bad number of signals " << filename; + qWarning() << "EDFInfo::Parse() Bad number of signals " << filename; sleep(1); return false; } @@ -123,49 +127,49 @@ bool EDFParser::Parse() // Now copy all the Signal descriptives into edfsignals for (auto & sig : edfsignals) { sig.value = nullptr; - sig.label = Read(16); + sig.label = ReadBytes(16); signal_labels.push_back(sig.label); signalList[sig.label].push_back(&sig); if (eof) { - qWarning() << "EDFParser::Parse() Early end of file " << filename; + qWarning() << "EDFInfo::Parse() Early end of file " << filename; sleep(1); return false; } } for (auto & sig : edfsignals) { - sig.transducer_type = Read(80); + sig.transducer_type = ReadBytes(80); } for (auto & sig : edfsignals) { - sig.physical_dimension = Read(8); + sig.physical_dimension = ReadBytes(8); } for (auto & sig : edfsignals) { - sig.physical_minimum = Read(8).toDouble(&ok); + sig.physical_minimum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { - sig.physical_maximum = Read(8).toDouble(&ok); + sig.physical_maximum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { - sig.digital_minimum = Read(8).toDouble(&ok); + sig.digital_minimum = ReadBytes(8).toDouble(&ok); } for (auto & sig : edfsignals) { - sig.digital_maximum = Read(8).toDouble(&ok); + sig.digital_maximum = ReadBytes(8).toDouble(&ok); sig.gain = (sig.physical_maximum - sig.physical_minimum) / (sig.digital_maximum - sig.digital_minimum); sig.offset = 0; } for (auto & sig : edfsignals) { - sig.prefiltering = Read(80); + sig.prefiltering = ReadBytes(80); } for (auto & sig : edfsignals) { - sig.nr = Read(8).toLong(&ok); + sig.nr = ReadBytes(8).toLong(&ok); } for (auto & sig : edfsignals) { - sig.reserved = Read(32); + sig.reserved = ReadBytes(32); } // could do it earlier, but it won't crash from > EOF Reads if (eof) { - qWarning() << "EDFParser::Parse() Early end of file " << filename; + qWarning() << "EDFInfo::Parse() Early end of file " << filename; sleep(1); return false; } @@ -180,7 +184,7 @@ bool EDFParser::Parse() if (allocsize > (datasize - pos)) { // Space required more than the remainder left to read, // so abort and let the user clean up the corrupted file themselves - qWarning() << "EDFParser::Parse(): " << filename << " is too short!"; + qWarning() << "EDFInfo::Parse(): " << filename << " is too short!"; sleep(1); return false; } @@ -197,61 +201,120 @@ bool EDFParser::Parse() } for (int x = 0; x < edfHdr.num_data_records; x++) { for (auto & sig : edfsignals) { + if ( sig.label.contains("ANNOTATIONS") ) { + annotations.push_back(ReadAnnotations( (char *)&signalPtr[pos], sig.nr*2)); + pos += sig.nr * 2; + } else { // it's got genuine 16-bit values #ifdef Q_LITTLE_ENDIAN - // Intel x86, etc.. - memcpy((char *)&sig.value[sig.pos], (char *)&signalPtr[pos], sig.nr * 2); - sig.pos += sig.nr; - pos += sig.nr * 2; + // Intel x86, etc.. + memcpy((char *)&sig.value[sig.pos], (char *)&signalPtr[pos], sig.nr * 2); + sig.pos += sig.nr; + pos += sig.nr * 2; #else - // Big endian safe - for (int j=0;j * EDFInfo::ReadAnnotations(const char * data, int charLen) +{ + QVector * annoVec = new QVector; + + // Process event annotation record - for (int i = snp + 4; i < edfHdr.recordingident.length(); i++) { - if (edfHdr.recordingident[i] == ' ') { + long pos = 0; + double offset; + double duration; + + while (pos < charLen) { + QString text; + bool sign, ok; + char c = data[pos]; + + if ((c != '+') && (c != '-')) // Annotaion must start with a +/- sign + break; + sign = (data[pos++] == '+'); + + text = ""; + c = data[pos]; + + do { // collect the offset + text += c; + pos++; + c = data[pos]; + } while ((c != AnnoSep) && (c != AnnoDurMark)); // a duration is optional + + offset = text.toDouble(&ok); + if (!ok) { + qDebug() << "Faulty offset in annotation record "; + // sleep(1); break; } - serialnumber += edfHdr.recordingident[i]; + + if (!sign) + offset = -offset; + + duration = -1.0; // This indicates no duration supplied + // First entry + if (data[pos] == AnnoDurMark) { // get duration.(preceded by decimal 21 byte) + pos++; + text = ""; + + do { // collect the duration + text += data[pos]; + pos++; + } while ((data[pos] != AnnoSep) && (pos < charLen)); // separator code + + duration = text.toDouble(&ok); + if (!ok) { + qDebug() << "Faulty duration in annotation record "; + // sleep(1); + break; + } + } + + while ((data[pos] == AnnoSep) && (pos < charLen)) { + int textLen = 0; + pos++; + const char * textStart = &data[pos]; + if (data[pos] == AnnoEnd) + break; + if (data[pos] == AnnoSep) { + pos++; + break; + } + do { // collect the annotation text + pos++; // officially UTF-8 is allowed here, so don't mangle it + textLen++; + } while ((data[pos] != AnnoSep) && (pos < charLen)); // separator code + text.fromUtf8(textStart, textLen); + annoVec->push_back( Annotation( offset, duration, text) ); + if (pos >= charLen) { + qDebug() << "Short EDF Annotations record"; + // sleep(1); + break; + } + } + + while ((pos < charLen) && (data[pos] == AnnoEnd)) + pos++; + + if (pos >= charLen) + break; } - - QDate d2 = edfHdr.startdate_orig.date(); - if (d2.year() < 2000) { - d2.setDate(d2.year() + 100, d2.month(), d2.day()); - edfHdr.startdate_orig.setDate(d2); - } - - if (!edfHdr.startdate_orig.isValid()) { - qDebug() << "Invalid date time retreieved parsing EDF File " << filename; - sleep(1); - return false; - } - - startdate = qint64(edfHdr.startdate_orig.toTime_t()) * 1000LL; - //startdate-=timezoneOffset(); - if (startdate == 0) { - qDebug() << "Invalid startdate = 0 in EDF File " << filename; - sleep(1); - return false; - } - - dur_data_record = (edfHdr.duration_Seconds * 1000.0L); - - enddate = startdate + dur_data_record * qint64(edfHdr.num_data_records); - - return true; + return annoVec; } // Read a 16 bits integer -qint16 EDFParser::Read16() +qint16 EDFInfo::Read16() { if ((pos + 2) > datasize) { eof = true; @@ -266,7 +329,7 @@ qint16 EDFParser::Read16() return res; } -QString EDFParser::Read(unsigned n) +QString EDFInfo::ReadBytes(unsigned n) { if ((pos + long(n)) > datasize) { eof = true; @@ -277,7 +340,7 @@ QString EDFParser::Read(unsigned n) return buf.trimmed(); } -EDFSignal *EDFParser::lookupLabel(const QString & name, int index) +EDFSignal *EDFInfo::lookupLabel(const QString & name, int index) { auto it = signalList.find(name); if (it == signalList.end()) diff --git a/oscar/SleepLib/loader_plugins/edfparser.h b/oscar/SleepLib/loader_plugins/edfparser.h index 75d1d2d8..73568576 100644 --- a/oscar/SleepLib/loader_plugins/edfparser.h +++ b/oscar/SleepLib/loader_plugins/edfparser.h @@ -57,8 +57,8 @@ struct EDFHeaderQT { long num_header_bytes; QString reserved44; long num_data_records; - long duration_Seconds; - long num_signals; + double duration_Seconds; + int num_signals; }; /*! \struct EDFSignal @@ -67,92 +67,100 @@ struct EDFHeaderQT { */ struct EDFSignal { public: - QString label; //! \brief Name of this Signal - QString transducer_type; //! \brief Tranducer Type (source of the data, usually blank) - QString physical_dimension; //! \brief The units of measurements represented by this signal - EventDataType physical_minimum; //! \brief The minimum limits of the ungained data - EventDataType physical_maximum; //! \brief The maximum limits of the ungained data - EventDataType digital_minimum; //! \brief The minimum limits of the data with gain and offset applied - EventDataType digital_maximum; //! \brief The maximum limits of the data with gain and offset applied - EventDataType gain; //! \brief Raw integer data is multiplied by this value - EventDataType offset; //! \brief This value is added to the raw data - QString prefiltering; //! \brief Any prefiltering methods used (usually blank) - long nr; //! \brief Number of records - QString reserved; //! \brief Reserved (usually blank) - qint16 *value; //! \brief Pointer to the signals sample data - int pos; //! \brief a non-EDF extra used internally to count the signal data + QString label; //! \brief Name of this Signal + QString transducer_type; //! \brief Tranducer Type (source of the data, usually blank) + QString physical_dimension; //! \brief The units of measurements represented by this signal + EventDataType physical_minimum; //! \brief The minimum limits of the ungained data + EventDataType physical_maximum; //! \brief The maximum limits of the ungained data + EventDataType digital_minimum; //! \brief The minimum limits of the data with gain and offset applied + EventDataType digital_maximum; //! \brief The maximum limits of the data with gain and offset applied + EventDataType gain; //! \brief Raw integer data is multiplied by this value + EventDataType offset; //! \brief This value is added to the raw data + QString prefiltering; //! \brief Any prefiltering methods used (usually blank) + long nr; //! \brief Number of records + QString reserved; //! \brief Reserved (usually blank) + qint16 *value; //! \brief Pointer to the signals sample data + + int pos; //! \brief a non-EDF extra used internally to count the signal data }; +/*! \class Annotation + \author Phil Olynyk + \brief Hold the annotation text from an EDF file + */ +class Annotation +{ + public: + Annotation() { duration = -1.0; }; + Annotation( double off, double dur, QString tx ) { + offset = off; + duration = dur; + text = tx; + }; + virtual ~Annotation() {}; -/*! \class EDFParser + double offset; + double duration; + QString text; +}; + +/*! \class EDFInfo + \author Phil Olynyk \author Mark Watkins \brief Parse an EDF+ data file into a list of EDFSignal's \note More information on the EDF+ file format can be obtained from http://edfplus.info */ -class EDFParser +class EDFInfo { public: //! \brief Constructs an EDFParser object, opening the filename if one supplied - EDFParser(QString filename = ""); + EDFInfo(); - ~EDFParser(); + virtual ~EDFInfo(); - //! \brief Open the EDF+ file, and read it's header - bool Open(const QString & name); + virtual QByteArray * Open(const QString & name); //! \brief Open the EDF+ file, and read it's header - //! \brief Parse the EDF+ file into the list of EDFSignals.. Must be call Open(..) first. - bool Parse(); + virtual bool Parse(QByteArray * fileData); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first. - //! \brief Read n bytes of 8 bit data from the EDF+ data stream - QString Read(unsigned n); + virtual EDFSignal *lookupLabel(const QString & name, int index=0); //! \brief Return a ptr to the i'th signal with that name - //! \brief Read 16 bit word of data from the EDF+ data stream - qint16 Read16(); + virtual long GetNumSignals() { return edfHdr.num_signals; } //! \brief Returns the number of signals contained in this EDF file - //! \brief Return a ptr to the i'th signal with the given name (if multiple signal with the same name(?!)) - EDFSignal *lookupLabel(const QString & name, int index=0); + virtual long GetNumDataRecords() { return edfHdr.num_data_records; } //! \brief Returns the number of data records contained per signal. - //! \brief Returns the number of signals contained in this EDF file - long GetNumSignals() { return edfHdr.num_signals; } + virtual double GetDuration() { return edfHdr.duration_Seconds; } //! \brief Returns the duration represented by this EDF file - //! \brief Returns the number of data records contained per signal. - long GetNumDataRecords() { return edfHdr.num_data_records; } - - //! \brief Returns the duration represented by this EDF file (in milliseconds) - qint64 GetDuration() { return dur_data_record; } - - //! \brief Returns the patientid field from the EDF header - QString GetPatient() { return edfHdr.patientident; } + virtual QString GetPatient() { return edfHdr.patientident; } //! \brief Returns the patientid field from the EDF header // The data members follow - //! \brief The header in a QT friendly form - EDFHeaderQT edfHdr; + QString filename; //! \brief For debug and error messages - //! \brief Vector containing the list of EDFSignals contained in this edf file - QVector edfsignals; + EDFHeaderQT edfHdr; //! \brief The header in a QT friendly form - //! \brief An by-name indexed into the EDFSignal data - QStringList signal_labels; + QVector edfsignals; //! \brief Holds the EDFSignals contained in this edf file - //! \brief ResMed sometimes re-uses the SAME signal name - QHash > signalList; + QVector< QVector * > annotations; //! \brief Holds the Annotaions for this EDF file - //! \brief The following are computed from the edfHdr data - QString serialnumber; - qint64 dur_data_record; - qint64 startdate; - qint64 enddate; + QStringList signal_labels; //! \brief An by-name indexed into the EDFSignal data + + QHash > signalList; //! \brief ResMed sometimes re-uses the SAME signal name // the following could be private + private: + QVector * ReadAnnotations( const char * data, int charLen ); //! \brief Create an Annotaion vector from the signal values + + QString ReadBytes(unsigned n); //! \brief Read n bytes of 8 bit data from the EDF+ data stream + + qint16 Read16(); //! \brief Read 16 bit word of data from the EDF+ data stream + //! \brief This is the array holding the EDF file data - QByteArray fileData; + QByteArray * fileData; //! \brief The EDF+ files header structure, used as a place holder while processing the text data. EDFHeaderRaw *hdrPtr; //! \brief This is the array of signal descriptors and values char *signalPtr; - QString filename; long filesize; long datasize; long pos; diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp index 2a150ec4..d75f38e7 100644 --- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp +++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp @@ -59,11 +59,54 @@ const QString STR_ext_TGT = "tgt"; const QString STR_ext_CRC = "crc"; -ResMedEDFParser::ResMedEDFParser(QString filename) :EDFParser(filename) { } -ResMedEDFParser::~ResMedEDFParser() { } +ResMedEDFInfo::ResMedEDFInfo() :EDFInfo() { } +ResMedEDFInfo::~ResMedEDFInfo() { } + +bool ResMedEDFInfo::Parse(QByteArray * fileData ) // overrides and calls the super's Parse +{ + EDFInfo::Parse( fileData ); + + // Now massage some stuff into OSCAR's layout + int snp = edfHdr.recordingident.indexOf("SRN="); + serialnumber.clear(); + + for (int i = snp + 4; i < edfHdr.recordingident.length(); i++) { + if (edfHdr.recordingident[i] == ' ') { + break; + } + serialnumber += edfHdr.recordingident[i]; + } + + QDate d2 = edfHdr.startdate_orig.date(); + if (d2.year() < 2000) { + d2.setDate(d2.year() + 100, d2.month(), d2.day()); + edfHdr.startdate_orig.setDate(d2); + } + + if (!edfHdr.startdate_orig.isValid()) { + qDebug() << "Invalid date time retreieved parsing EDF File " << filename; + sleep(1); + return false; + } + + startdate = qint64(edfHdr.startdate_orig.toTime_t()) * 1000LL; + //startdate-=timezoneOffset(); + if (startdate == 0) { + qDebug() << "Invalid startdate = 0 in EDF File " << filename; + sleep(1); + return false; + } + + dur_data_record = (edfHdr.duration_Seconds * 1000.0L); + + enddate = startdate + dur_data_record * qint64(edfHdr.num_data_records); + + return true; + +} // Looks up foreign language Signal names that match this channelID -EDFSignal *ResMedEDFParser::lookupSignal(ChannelID ch) +EDFSignal *ResMedEDFInfo::lookupSignal(ChannelID ch) { // Get list of all known foreign language names for this channel auto channames = resmed_codes.find(ch); @@ -113,7 +156,7 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) int totalRecs = 0; for (auto it=STRmap.begin(), end=STRmap.end(); it != end; ++it) { STRFile & file = it.value(); - ResMedEDFParser & str = *file.edf; + ResMedEDFInfo & str = *file.edf; totalRecs += str.GetNumDataRecords(); } @@ -126,7 +169,7 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) for (auto it=STRmap.begin(), end=STRmap.end(); it != end; ++it) { STRFile & file = it.value(); QString & strfile = file.filename; - ResMedEDFParser & str = *file.edf; + ResMedEDFInfo & str = *file.edf; QDate date = str.edfHdr.startdate_orig.date(); // each STR.edf record starts at 12 noon @@ -569,7 +612,7 @@ ResmedLoader::ResmedLoader() { m_type = MT_CPAP; timeInTimeDelta = timeInLoadBRP = timeInLoadPLD = timeInLoadEVE = 0; - timeInLoadCSL = timeInLoadSAD = timeInEDFParser = timeInEDFOpen = timeInAddWaveform = 0; + timeInLoadCSL = timeInLoadSAD = timeInEDFInfo = timeInEDFOpen = timeInAddWaveform = 0; } @@ -675,8 +718,9 @@ EDFType lookupEDFType(const QString & text) // Pretend to parse the EVE file to get the duration out of it. int PeekAnnotations(const QString & path, quint32 &start, quint32 &end) { - ResMedEDFParser edf(path); - if (!edf.Parse()) + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); + if (!edf.Parse(fileData)) return -1; QString t; @@ -895,7 +939,7 @@ EDFduration getEDFDuration(const QString & filename) // Have to get the actual duration of the EVE file by parsing the annotations. :( - // Can we cache the stupid EDFParser file for later ??? + // Can we cache the stupid EDFInfo file for later ??? int recs = PeekAnnotations(filename, st2, en2); if (recs > 0) { @@ -1764,8 +1808,9 @@ int ResmedLoader::Open(const QString & dirpath) // Now place any of these files in the Backup folder sorted by the file date for (auto & filename : strfiles) { - ResMedEDFParser * stredf = new ResMedEDFParser(filename); - if ( ! stredf->Parse()) { + ResMedEDFInfo * stredf = new ResMedEDFInfo(); + QByteArray * fileData = stredf->Open(filename); + if ( ! stredf->Parse(fileData)) { qDebug() << "Faulty STR file" << filename; delete stredf; continue; @@ -1838,8 +1883,9 @@ int ResmedLoader::Open(const QString & dirpath) if (STRmap.contains(date)) continue; - ResMedEDFParser * stredf = new ResMedEDFParser(fi.canonicalFilePath()); - if (!stredf->Parse()) { + ResMedEDFInfo * stredf = new ResMedEDFInfo(); + QByteArray * fileData = stredf->Open(fi.canonicalFilePath() ); + if (!stredf->Parse(fileData)) { qDebug() << "Faulty STR file" << filename; delete stredf; continue; @@ -2002,7 +2048,7 @@ int ResmedLoader::Open(const QString & dirpath) qDebug() << "Total toTimeDelta function usage:" << totalbytes << "in" << double(totalns) / 1000000000.0 << "seconds"; qDebug() << "Total CPU time in EDF Open" << timeInEDFOpen; - qDebug() << "Total CPU time in EDF Parser" << timeInEDFParser; + qDebug() << "Total CPU time in EDF Parser" << timeInEDFInfo; qDebug() << "Total CPU time in LoadBRP" << timeInLoadBRP; qDebug() << "Total CPU time in LoadPLD" << timeInLoadPLD; qDebug() << "Total CPU time in LoadSAD" << timeInLoadSAD; @@ -2013,10 +2059,10 @@ int ResmedLoader::Open(const QString & dirpath) } #endif - sessfiles.clear(); - strsess.clear(); +// sessfiles.clear(); +// strsess.clear(); +// strdate.clear(); - strdate.clear(); channel_efficiency.clear(); channel_time.clear(); @@ -2098,14 +2144,15 @@ bool ResmedLoader::LoadCSL(Session *sess, const QString & path) time.start(); #endif - ResMedEDFParser edf(path); + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif - if (!edf.Parse()) + if (!edf.Parse(fileData)) return false; #ifdef DEBUG_EFFICIENCY @@ -2249,7 +2296,7 @@ bool ResmedLoader::LoadCSL(Session *sess, const QString & path) timeMutex.lock(); timeInLoadCSL += time.elapsed(); timeInEDFOpen += edfopentime; - timeInEDFParser += edfparsetime; + timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif @@ -2262,12 +2309,13 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path) QTime time; time.start(); #endif - ResMedEDFParser edf(path); + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif - if (!edf.Parse()) + if (!edf.Parse(fileData)) return false; #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); @@ -2422,7 +2470,7 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path) timeMutex.lock(); timeInLoadEVE += time.elapsed(); timeInEDFOpen += edfopentime; - timeInEDFParser += edfparsetime; + timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif @@ -2435,12 +2483,13 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) QTime time; time.start(); #endif - ResMedEDFParser edf(path); + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif - if (!edf.Parse()) + if (!edf.Parse(fileData)) return false; #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); @@ -2450,7 +2499,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) sess->updateFirst(edf.startdate); QTime time2; - qint64 duration = edf.GetNumDataRecords() * edf.GetDuration(); + qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateLast(edf.startdate + duration); for (auto & es : edf.edfsignals) { @@ -2510,7 +2559,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) timeMutex.lock(); timeInLoadBRP += time.elapsed(); timeInEDFOpen += edfopentime; - timeInEDFParser += edfparsetime; + timeInEDFInfo += edfparsetime; timeInAddWaveform += AddWavetime; timeMutex.unlock(); #endif @@ -2519,7 +2568,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) } // Convert EDFSignal data to OSCAR's Time-Delta Event format -void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &es, ChannelID code, +void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es, ChannelID code, long recs, qint64 duration, EventDataType t_min, EventDataType t_max, bool square) { if (t_min == t_max) { @@ -2569,9 +2618,7 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e break; } - tt += rate; - } while (sptr < eptr); if (!el) @@ -2585,13 +2632,11 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e tmp = EventDataType(last) * es.gain; if ((tmp >= t_min) && (tmp <= t_max)) { - if (tmp < min) { + if (tmp < min) min = tmp; - } - if (tmp > max) { + if (tmp > max) max = tmp; - } el->AddEvent(tt, last); } else { @@ -2601,22 +2646,18 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e el->setDimension(es.physical_dimension); el = sess->AddEventList(code, EVL_Event, es.gain, es.offset, 0, 0); - } else { + } else el->clear(); // reuse the object - } } } tmp = EventDataType(c) * es.gain; if ((tmp >= t_min) && (tmp <= t_max)) { - if (tmp < min) { + if (tmp < min) min = tmp; - } - - if (tmp > max) { + if (tmp > max) max = tmp; - } el->AddEvent(tt, c); } else { @@ -2625,7 +2666,8 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e // Create and attach new EventList el = sess->AddEventList(code, EVL_Event, es.gain, es.offset, 0, 0); - } else { el->clear(); } + } else + el->clear(); } } @@ -2636,9 +2678,8 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e tmp = EventDataType(c) * es.gain; - if ((tmp >= t_min) && (tmp <= t_max)) { + if ((tmp >= t_min) && (tmp <= t_max)) el->AddEvent(tt, c); - } sess->setMin(code, min); sess->setMax(code, max); @@ -2667,7 +2708,7 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &e timeInTimeDelta += time.elapsed(); timeMutex.unlock(); #endif -} +} // end ResMedLoader::ToTimeDelta // Load SAD Oximetry Signals bool ResmedLoader::LoadSAD(Session *sess, const QString & path) @@ -2677,14 +2718,15 @@ bool ResmedLoader::LoadSAD(Session *sess, const QString & path) time.start(); #endif - ResMedEDFParser edf(path); + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif - if (!edf.Parse()) + if (!edf.Parse(fileData)) return false; #ifdef DEBUG_EFFICIENCY @@ -2693,7 +2735,7 @@ bool ResmedLoader::LoadSAD(Session *sess, const QString & path) #endif sess->updateFirst(edf.startdate); - qint64 duration = edf.GetNumDataRecords() * edf.GetDuration(); + qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateLast(edf.startdate + duration); for (auto & es : edf.edfsignals) { @@ -2732,7 +2774,7 @@ bool ResmedLoader::LoadSAD(Session *sess, const QString & path) timeMutex.lock(); timeInLoadSAD += time.elapsed(); timeInEDFOpen += edfopentime; - timeInEDFParser += edfparsetime; + timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif return true; @@ -2745,12 +2787,13 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) QTime time; time.start(); #endif - ResMedEDFParser edf(path); + ResMedEDFInfo edf; + QByteArray * fileData = edf.Open(path); #ifdef DEBUG_EFFICIENCY int edfopentime = time.elapsed(); time.start(); #endif - if (!edf.Parse()) + if (!edf.Parse(fileData)) return false; #ifdef DEBUG_EFFICIENCY int edfparsetime = time.elapsed(); @@ -2760,7 +2803,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) // Is it safe 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(); + qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis(); sess->updateFirst(edf.startdate); sess->updateLast(edf.startdate + duration); QString t; @@ -2896,7 +2939,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) timeMutex.lock(); timeInLoadPLD += time.elapsed(); timeInEDFOpen += edfopentime; - timeInEDFParser += edfparsetime; + timeInEDFInfo += edfparsetime; timeMutex.unlock(); #endif diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.h b/oscar/SleepLib/loader_plugins/resmed_loader.h index bd7101a4..b7e4aec8 100644 --- a/oscar/SleepLib/loader_plugins/resmed_loader.h +++ b/oscar/SleepLib/loader_plugins/resmed_loader.h @@ -30,18 +30,46 @@ EDFType lookupEDFType(const QString & text); const QString resmed_class_name = STR_MACH_ResMed; -class ResMedEDFParser:public EDFParser +class ResMedEDFInfo : public EDFInfo { public: - ResMedEDFParser(QString filename = ""); - ~ResMedEDFParser(); + ResMedEDFInfo(); + ~ResMedEDFInfo(); + + virtual bool Parse(QByteArray * fileData) override; // overrides and calls the super's Parse + + virtual qint64 GetDurationMillis() { return dur_data_record; } // overrides the super + EDFSignal *lookupSignal(ChannelID ch); + + + + //! \brief The following are computed from the edfHdr data + QString serialnumber; + qint64 dur_data_record; + qint64 startdate; + qint64 enddate; + +}; + +class EDFduration +{ +public: + EDFduration() { start = end = 0; type = EDF_UNKNOWN; } + EDFduration(quint32 start, quint32 end, QString path) : + start(start), end(end), path(path) {} + + quint32 start; + quint32 end; + QString path; + QString filename; + EDFType type; }; - -struct STRRecord +class STRRecord { +public: STRRecord() { maskon.clear(); maskoff.clear(); @@ -125,82 +153,8 @@ struct STRRecord date=QDate(); } - STRRecord(const STRRecord & copy) { - maskon = copy.maskon; - maskoff = copy.maskoff; - maskdur = copy.maskdur; - maskevents = copy.maskevents; - mode = copy.mode; - rms9_mode = copy.rms9_mode; - set_pressure = copy.set_pressure; - epap = copy.epap; - max_pressure = copy.max_pressure; - min_pressure = copy.min_pressure; - max_ps = copy.max_ps; - min_ps = copy.min_ps; - ps = copy.ps; - max_epap = copy.max_epap; - min_epap = copy.min_epap; - ipap = copy.ipap; - max_ipap = copy.max_ipap; - min_ipap = copy.min_ipap; - epr = copy.epr; - epr_level = copy.epr_level; - sessionid = copy.sessionid; - ahi = copy.ahi; - ai = copy.ai; - oai = copy.oai; - hi = copy.hi; - uai = copy.uai; - cai = copy.cai; - csr = copy.csr; - - date = copy.date; - leak50 = copy.leak50; - leak95 = copy.leak95; - leakmax = copy.leakmax; - rr50 = copy.rr50; - rr95 = copy.rr95; - rrmax = copy.rrmax; - mv50 = copy.mv50; - mv95 = copy.mv95; - mvmax = copy.mvmax; - ie50 = copy.ie50; - ie95 = copy.ie95; - iemax = copy.iemax; - tv50 = copy.tv50; - tv95 = copy.tv95; - tvmax = copy.tvmax; - mp50 = copy.mp50; - mp95 = copy.mp95; - mpmax = copy.mpmax; - - - tgtepap50 = copy.tgtepap50; - tgtepap95 = copy.tgtepap95; - tgtepapmax = copy.tgtepapmax; - tgtipap50 = copy.tgtipap50; - tgtipap95 = copy.tgtipap95; - tgtipapmax = copy.tgtipapmax; - - s_EPREnable = copy.s_EPREnable; - s_EPR_ClinEnable = copy.s_EPREnable; - s_RampEnable = copy.s_RampEnable; - s_RampTime = copy.s_RampTime; - - s_SmartStart = copy.s_SmartStart; - s_PtAccess = copy.s_PtAccess; - s_ABFilter = copy.s_ABFilter; - s_Mask = copy.s_Mask; - - s_Tube = copy.s_Tube; - s_ClimateControl = copy.s_ClimateControl; - s_HumEnable = copy.s_HumEnable; - s_HumLevel = copy.s_HumLevel; - s_TempEnable = copy.s_TempEnable; - s_Temp = copy.s_Temp; - ramp_pressure = copy.ramp_pressure; - } + +// All the data members QVector maskon; QVector maskoff; @@ -279,47 +233,6 @@ struct STRRecord class ResmedLoader; -struct EDFGroup { - EDFGroup() { } - EDFGroup(QString &brp, QString &eve, QString &pld, QString &sad, QString &csl) { - BRP = brp; - EVE = eve; - CSL = csl; - PLD = pld; - SAD = sad; - } - EDFGroup(const EDFGroup & copy) { - BRP = copy.BRP; - EVE = copy.EVE; - CSL = copy.CSL; - PLD = copy.PLD; - SAD = copy.SAD; - } - QString BRP; - QString EVE; - QString CSL; - QString PLD; - QString SAD; -}; - -struct EDFduration { - EDFduration() { start = end = 0; type = EDF_UNKNOWN; } - EDFduration(const EDFduration & copy) { - path = copy.path; - start = copy.start; - end = copy.end; - type = copy.type; - filename = copy.filename; - } - EDFduration(quint32 start, quint32 end, QString path) : - start(start), end(end), path(path) {} - quint32 start; - quint32 end; - QString path; - QString filename; - EDFType type; -}; - struct ResMedDay { QDate date; STRRecord str; @@ -342,50 +255,19 @@ protected: ResMedDay * resday; }; -struct STRFile { +class STRFile +{ +public: STRFile() : filename(QString()), edf(nullptr) {} - STRFile(QString name, ResMedEDFParser *str) : + STRFile(QString name, ResMedEDFInfo *str) : filename(name), edf(str) {} - STRFile(const STRFile & copy) { - filename = copy.filename; - edf = copy.edf; - } - ~STRFile() { - } + virtual ~STRFile() {} QString filename; - ResMedEDFParser * edf; + ResMedEDFInfo * edf; }; -/*class ResmedImport:public ImportTask -{ -public: - ResmedImport(ResmedLoader * l, SessionID s, QHash grp, Machine * m): loader(l), sessionid(s), files(grp), mach(m) {} - virtual ~ResmedImport() {} - virtual void run(); - -protected: - ResmedLoader * loader; - SessionID sessionid; - QHash files; - Machine * mach; -}; - -class ResmedImportStage2:public ImportTask -{ -public: - ResmedImportStage2(ResmedLoader * l, STRRecord r, Machine * m): loader(l), R(r), mach(m) {} - virtual ~ResmedImportStage2() {} - virtual void run(); - -protected: - ResmedLoader * loader; - STRRecord R; - Machine * mach; -}; */ - - /*! \class ResmedLoader @@ -416,7 +298,7 @@ class ResmedLoader : public CPAPLoader virtual const QString &loaderName() { return resmed_class_name; } //! \brief Converts EDFSignal data to time delta packed EventList, and adds to Session - void ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &es, ChannelID code, long recs, + void ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es, ChannelID code, long recs, qint64 duration, EventDataType min = 0, EventDataType max = 0, bool square = false); //! \brief Register the ResmedLoader with the list of other machine loaders @@ -443,7 +325,8 @@ class ResmedLoader : public CPAPLoader bool LoadPLD(Session *sess, const QString & path); virtual MachineInfo newInfo() { - return MachineInfo(MT_CPAP, 0, resmed_class_name, QObject::tr("ResMed"), QString(), QString(), QString(), QObject::tr("S9"), QDateTime::currentDateTime(), resmed_data_version); + return MachineInfo(MT_CPAP, 0, resmed_class_name, QObject::tr("ResMed"), QString(), + QString(), QString(), QObject::tr("S9"), QDateTime::currentDateTime(), resmed_data_version); } virtual void initChannels(); @@ -463,17 +346,20 @@ class ResmedLoader : public CPAPLoader volatile int sessionCount; protected: +//! \brief The STR.edf file is a unique edf file with many signals void ParseSTR(Machine *, QMap &); - //! \brief Scan for new files to import, group into sessions and add to task que int scanFiles(Machine * mach, const QString & datalog_path); +//! \brief Write a backup copy to the backup path QString backup(const QString & file, const QString & backup_path); - QMap sessfiles; - QMap strsess; - QMap > strdate; +// The data members +// QMap sessfiles; +// QMap strsess; +// QMap > strdate; + QMap resdayList; #ifdef DEBUG_EFFICIENCY @@ -485,7 +371,7 @@ protected: volatile qint64 timeInLoadCSL; volatile qint64 timeInLoadSAD; volatile qint64 timeInEDFOpen; - volatile qint64 timeInEDFParser; + volatile qint64 timeInEDFInfo; volatile qint64 timeInAddWaveform; volatile qint64 timeInTimeDelta; QMutex timeMutex;