From b80ae705254844002c9d440bb49d4a08cc966d29 Mon Sep 17 00:00:00 2001 From: Phil Olynyk Date: Sat, 3 Aug 2019 10:59:08 -0400 Subject: [PATCH] Changed class EDFParser to EDFInfo, and changed member names to show usage. Added an Annotaions list member to avoid reparsing. Skipping directories before ignore-before date. Added a new app to dump STR.edf files. Still not complete. --- dumpSTR.pro | 32 ++ dumpSTR/common.h | 23 ++ dumpSTR/edfparser.cpp | 351 ++++++++++++++++++ dumpSTR/edfparser.h | 175 +++++++++ dumpSTR/main.cpp | 157 ++++++++ oscar/SleepLib/loader_plugins/edfparser.cpp | 56 +-- oscar/SleepLib/loader_plugins/edfparser.h | 6 +- .../SleepLib/loader_plugins/resmed_loader.cpp | 318 ++++++++-------- 8 files changed, 920 insertions(+), 198 deletions(-) create mode 100644 dumpSTR.pro create mode 100644 dumpSTR/common.h create mode 100644 dumpSTR/edfparser.cpp create mode 100644 dumpSTR/edfparser.h create mode 100644 dumpSTR/main.cpp diff --git a/dumpSTR.pro b/dumpSTR.pro new file mode 100644 index 00000000..2e3aaa39 --- /dev/null +++ b/dumpSTR.pro @@ -0,0 +1,32 @@ +#------------------------------------------------- +# +# Project created by pholynyk 2019Aug01T21:00:00 +# +#------------------------------------------------- + +message(Platform is $$QMAKESPEC ) + +CONFIG += c++11 +CONFIG += rtti +CONFIG -= debug_and_release + +QT += core widgets + +TARGET = dumpSTR + +TEMPLATE = app + +QMAKE_TARGET_PRODUCT = dumpSTR +QMAKE_TARGET_COMPANY = The OSCAR Team +QMAKE_TARGET_COPYRIGHT = © 2019 The OSCAR Team +QMAKE_TARGET_DESCRIPTION = "OpenSource STR.edf Dumper" +VERSION = 0.5.0 + +SOURCES += \ + dumpSTR/main.cpp \ + dumpSTR/edfparser.cpp \ + +HEADERS += \ + dumpSTR/common.h \ + dumpSTR/edfparser.h \ + diff --git a/dumpSTR/common.h b/dumpSTR/common.h new file mode 100644 index 00000000..4bbb8261 --- /dev/null +++ b/dumpSTR/common.h @@ -0,0 +1,23 @@ +/* Common code and junk + * + * Copyright (C) 2011-2018 Mark Watkins + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of the source code + * for more details. */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include + + +#ifndef nullptr +#define nullptr NULL +#endif + +typedef float EventDataType; + +#endif // COMMON_H diff --git a/dumpSTR/edfparser.cpp b/dumpSTR/edfparser.cpp new file mode 100644 index 00000000..412477f2 --- /dev/null +++ b/dumpSTR/edfparser.cpp @@ -0,0 +1,351 @@ +/* EDF Parser Implementation + * + * Copyright (c) 2019 The OSCAR Team + * Copyright (c) 2011-2018 Mark Watkins + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of the source code + * for more details. */ + +#include +#include +#include +#include +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include "edfparser.h" + +EDFInfo::EDFInfo() +{ + filesize = 0; + datasize = 0; + signalPtr = nullptr; + hdrPtr = nullptr; + fileData = nullptr; +} + +EDFInfo::~EDFInfo() +{ + for (auto & s : edfsignals) { + if (s.dataArray) + delete [] s.dataArray; + } +// for (auto & a : annotations) +// delete a; +} + +QByteArray * EDFInfo::Open(const QString & name) +{ + if (hdrPtr != nullptr) { + qWarning() << "EDFInfo::Open() called with file already open " << name; + sleep(1); + return nullptr; + } + QFile fi(name); + if (!fi.open(QFile::ReadOnly)) { + qDebug() << "EDFInfo::Open() Couldn't open file " << name; + sleep(1); + return nullptr; + } + fileData = new QByteArray(); +// if (name.endsWith(STR_ext_gz)) { +// *fileData = gUncompress(fi.readAll()); // Open and decompress file +// } else { + *fileData = fi.readAll(); // Open and read uncompressed file +// } + fi.close(); + if (fileData->size() <= EDFHeaderSize) { + delete fileData; + qDebug() << "EDFInfo::Open() File too short " << name; + sleep(1); + return nullptr; + } + filename = name; + return fileData; +} + +bool EDFInfo::Parse(QByteArray * fileData ) +{ + bool ok; + + 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(); + pos = 0; + + eof = false; + edfHdr.version = QString::fromLatin1(hdrPtr->version, 8).toLong(&ok); + if (!ok) { + qWarning() << "EDFInfo::Parse() Bad Version " << filename; + sleep(1); + return false; + } + + edfHdr.patientident=QString::fromLatin1(hdrPtr->patientident,80).trimmed(); + edfHdr.recordingident = QString::fromLatin1(hdrPtr->recordingident, 80).trimmed(); // Serial number is in here.. + edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss"); + QDate d2 = edfHdr.startdate_orig.date(); + if (d2.year() < 2000) { + d2.setDate(d2.year() + 100, d2.month(), d2.day()); + edfHdr.startdate_orig.setDate(d2); + } + + edfHdr.num_header_bytes = QString::fromLatin1(hdrPtr->num_header_bytes, 8).toLong(&ok); + if (!ok) { + qWarning() << "EDFInfo::Parse() Bad header byte count " << filename; + sleep(1); + return false; + } + edfHdr.reserved44=QString::fromLatin1(hdrPtr->reserved, 44).trimmed(); + edfHdr.num_data_records = QString::fromLatin1(hdrPtr->num_data_records, 8).toLong(&ok); + if (!ok) { + 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() << "EDFInfo::Parse() Bad duration " << filename; + sleep(1); + return false; + } + edfHdr.num_signals = QString::fromLatin1(hdrPtr->num_signals, 4).toLong(&ok); + if (!ok) { + qWarning() << "EDFInfo::Parse() Bad number of signals " << filename; + sleep(1); + return false; + } + datasize = filesize - edfHdr.num_header_bytes; + + // Initialize fixed-size signal list. + edfsignals.resize(edfHdr.num_signals); + + // Now copy all the Signal descriptives into edfsignals + for (auto & sig : edfsignals) { + sig.dataArray = nullptr; + sig.label = ReadBytes(16); + + signal_labels.push_back(sig.label); + signalList[sig.label].push_back(&sig); + if (eof) { + qWarning() << "EDFInfo::Parse() Early end of file " << filename; + sleep(1); + return false; + } + } + for (auto & sig : edfsignals) { + sig.transducer_type = ReadBytes(80).trimmed(); + } + for (auto & sig : edfsignals) { + sig.physical_dimension = ReadBytes(8); + } + for (auto & sig : edfsignals) { + sig.physical_minimum = ReadBytes(8).toDouble(&ok); + } + for (auto & sig : edfsignals) { + sig.physical_maximum = ReadBytes(8).toDouble(&ok); + } + for (auto & sig : edfsignals) { + sig.digital_minimum = ReadBytes(8).toDouble(&ok); + } + for (auto & sig : edfsignals) { + 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 = ReadBytes(80).trimmed(); + } + for (auto & sig : edfsignals) { + sig.sampleCnt = ReadBytes(8).toLong(&ok); + } + for (auto & sig : edfsignals) { + sig.reserved = ReadBytes(32).trimmed(); + } + + // could do it earlier, but it won't crash from > EOF Reads + if (eof) { + qWarning() << "EDFInfo::Parse() Early end of file " << filename; + sleep(1); + return false; + } + + // Now check the file isn't truncated before allocating space for the values + long totalsize = 0; + for (auto & sig : edfsignals) { + if (edfHdr.num_data_records > 0) { + totalsize += sig.sampleCnt * edfHdr.num_data_records * 2; + } + } + if (totalsize > datasize) { + // Space required more than the remainder left to read, + // so abort and let the user clean up the corrupted file themselves + qWarning() << "EDFInfo::Parse(): " << filename << " is too short!"; + sleep(1); + return false; + } + + // allocate the arrays for the signal values + for (auto & sig : edfsignals) { + long samples = sig.sampleCnt * edfHdr.num_data_records; + if (edfHdr.num_data_records <= 0) + sig.dataArray = nullptr; + else + sig.dataArray = new qint16 [samples]; +// qDebug() << "Allocated " << samples << " at " << sig.dataArray; + } + for (int recNo = 0; recNo < edfHdr.num_data_records; recNo++) { + for (auto & sig : edfsignals) { + if ( sig.label.contains("ANNOTATIONS") ) { + annotations.push_back(ReadAnnotations( (char *)&signalPtr[pos], sig.sampleCnt*2)); + pos += sig.sampleCnt * 2; + } else { // it's got genuine 16-bit values + for (int j=0;j * EDFInfo::ReadAnnotations(const char * data, int charLen) +{ + QVector * annoVec = new QVector; + + // Process event annotation record + + 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; + } + + 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; + } + return annoVec; +} + +// Read a 16 bits integer +qint16 EDFInfo::Read16() +{ + if ((pos + 2) > datasize) { + eof = true; + return 0; + } +#ifdef Q_LITTLE_ENDIAN // Intel, etc... + qint16 res = *(qint16 *)&signalPtr[pos]; +#else // ARM, PPC, etc.. + qint16 res = quint8(signalPtr[pos]) | (qint8(signalPtr[pos+1]) << 8); +#endif + pos += 2; + return res; +} + +QString EDFInfo::ReadBytes(unsigned n) +{ + if ((pos + long(n)) > datasize) { + eof = true; + return QString(); + } + QByteArray buf(&signalPtr[pos], n); + pos+=n; + return buf.trimmed(); +} + +EDFSignal *EDFInfo::lookupLabel(const QString & name, int index) +{ + auto it = signalList.find(name); + if (it == signalList.end()) + return nullptr; + + if (index >= it.value().size()) + return nullptr; + + return it.value()[index]; +} + diff --git a/dumpSTR/edfparser.h b/dumpSTR/edfparser.h new file mode 100644 index 00000000..83a27f5a --- /dev/null +++ b/dumpSTR/edfparser.h @@ -0,0 +1,175 @@ +/* EDF Parser Header + * + * Copyright (c) 2019 The OSCAR Team + * Copyright (c) 2011-2018 Mark Watkins + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of the source code + * for more details. */ + +#ifndef EDFPARSER_H +#define EDFPARSER_H + +#include +#include +#include +#include +#include + +// #include "SleepLib/common.h" + +typedef float EventDataType; + +const QString STR_ext_EDF = "edf"; +const QString STR_ext_gz = ".gz"; + +const char AnnoSep = 20; +const char AnnoDurMark = 21; +const char AnnoEnd = 0; + +/*! \struct EDFHeader + \brief Represents the EDF+ header structure, used as a place holder while processing the text data. + \note More information on the EDF+ file format can be obtained from http://edfplus.info +*/ +struct EDFHeaderRaw { + char version[8]; + char patientident[80]; + char recordingident[80]; + char datetime[16]; + char num_header_bytes[8]; + char reserved[44]; + char num_data_records[8]; + char dur_data_records[8]; + char num_signals[4]; +} +#ifndef _MSC_VER +__attribute__((packed)) +#endif +; +const int EDFHeaderSize = sizeof(EDFHeaderRaw); + +/*! \struct EDFHeaderQT + \brief Contains the QT version of the EDF header information + */ +struct EDFHeaderQT { + public: + long version; + QString patientident; + QString recordingident; + QDateTime startdate_orig; + long num_header_bytes; + QString reserved44; + long num_data_records; + double duration_Seconds; + int num_signals; +}; + +/*! \struct EDFSignal + \brief Contains information about a single EDF+ Signal + \note More information on the EDF+ file format can be obtained from http://edfplus.info + */ +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 sampleCnt; //! \brief Number of samples per record + QString reserved; //! \brief Reserved (usually blank) + + qint16 * dataArray; //! \brief Offset in record to the signals sample 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() {}; + + 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 EDFInfo +{ + public: + //! \brief Constructs an EDFParser object, opening the filename if one supplied + EDFInfo(); + + virtual ~EDFInfo(); + + virtual QByteArray * Open(const QString & name); //! \brief Open the EDF+ file, and read it's header + + virtual bool Parse(QByteArray * fileData); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first. + + virtual EDFSignal *lookupLabel(const QString & name, int index=0); //! \brief Return a ptr to the i'th signal with that name + + virtual long GetNumSignals() { return edfHdr.num_signals; } //! \brief Returns the number of signals contained in this EDF file + + virtual long GetNumDataRecords() { return edfHdr.num_data_records; } //! \brief Returns the number of data records contained per signal. + + virtual double GetDuration() { return edfHdr.duration_Seconds; } //! \brief Returns the duration represented by this EDF file + + virtual QString GetPatient() { return edfHdr.patientident; } //! \brief Returns the patientid field from the EDF header + +// The data members follow + + QString filename; //! \brief For debug and error messages + + EDFHeaderQT edfHdr; //! \brief The header in a QT friendly form + + QVector edfsignals; //! \brief Holds the EDFSignals contained in this edf file + +// QVector< QVector > dataRecords; //! \brief Holds the datarecords + + QVector< QVector * > annotations; //! \brief Holds the Annotaions for this EDF file + + 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; + //! \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; + + long filesize; + long datasize; + long pos; + bool eof; +}; + + +#endif // EDFPARSER_H diff --git a/dumpSTR/main.cpp b/dumpSTR/main.cpp new file mode 100644 index 00000000..1d44707f --- /dev/null +++ b/dumpSTR/main.cpp @@ -0,0 +1,157 @@ +/* Dump an STR.edf file */ + +#include +// #include +#include + +typedef float EventDataType; + +#include "edfparser.h" + +// using namespace std; + +void dumpHeader( const EDFHeaderQT hdr ) { + qDebug() << "EDF Header:"; + qDebug() << "Version " << hdr.version << " Patient >" << hdr.patientident << "<"; + qDebug() << "Recording >" << hdr.recordingident << "<"; + qDebug() << "Date: " << hdr.startdate_orig.toString(); + qDebug() << "Header size (bytes): " << hdr.num_header_bytes; + qDebug() << "EDF type: >" << hdr.reserved44 << "<"; + qDebug() << "Duration(secs): " << hdr.duration_Seconds; + qDebug() << "Number of Signals: " << hdr.num_signals << "\n"; +} + +void ifprint( QString label, QString text) { + if ( text.isEmpty() ) + return; + qDebug() << label << ": " << text; + return; +} + +void dumpSignals( const QVector sigs ) { + int i = 1; + for (auto sig: sigs) { + qDebug() << "Signal #" << i++; + qDebug() << "Label: " << sig.label; + ifprint( "Transducer", sig.transducer_type ); + ifprint( "Dimension", sig.physical_dimension ); + qDebug() << "Physical min: " << sig.physical_minimum << " max: " << sig.physical_maximum; + qDebug() << "Digital min: " << sig.digital_minimum << " max: " << sig.digital_maximum; + qDebug() << "Gain: " << sig.gain << " Offset: " << sig.offset; + ifprint( "Pre-filter", sig.prefiltering ); + qDebug() << "Sample Count: " << sig.sampleCnt; + qDebug() << "dataArray is at " << sig.dataArray << "\n"; + } +} + +int main(int argc, char *argv[]) { + +// QString homeDocs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"; +// QCoreApplication::setApplicationName(getAppName()); +// QCoreApplication::setOrganizationName(getDeveloperName()); +// QCoreApplication::setOrganizationDomain(getDeveloperDomain()); + + int first = 0, last = 0; + int firstSig = 1, lastSig = 0; + + QApplication a(argc, argv); + QStringList args = a.arguments(); + + if ( args.size() < 2 ) { + qDebug() << args[0] << " needs a filename" ; + exit(1); + } + + QString filename = args[args.size()-1]; + bool showall = false, brief = false; + + for (int i = 1; i < args.size()-1; i++) { + if (args[i] == "-f") + first = args[++i].toInt(); + else if (args[i] == "-g") + firstSig = args[++i].toInt(); + else if (args[i] == "-l") + last = args[++i].toInt(); + else if (args[i] == "-m") + lastSig = args[++i].toInt(); + else if (args[i] == "-a") + showall = true; + else if (args[i] == "-b") + brief = true; + } + + EDFInfo str; + QByteArray * buffer = str.Open(filename); + str.Parse(buffer); + + QDate d2 = str.edfHdr.startdate_orig.date(); + if (d2.year() < 2000) { + d2.setDate(d2.year() + 100, d2.month(), d2.day()); + str.edfHdr.startdate_orig.setDate(d2); + } + + + + QDate date = str.edfHdr.startdate_orig.date(); // each STR.edf record starts at 12 noon + + qDebug() << str.filename << " starts at " << date << " for " << str.GetNumDataRecords() + << " days, with " << str.GetNumSignals() << " signals"; + + if (args.size() == 2) { + exit(0); + } + + dumpHeader( (str.edfHdr) ); + + dumpSignals( (str.edfsignals) ); + + if ( brief ) + exit(0); + + int size = str.GetNumDataRecords(); + + if (showall) { + first = 0; + last = size; + firstSig = 1; + lastSig = str.GetNumSignals(); + } + if (lastSig == 0 ) + lastSig = str.GetNumSignals(); + + // For each data record, representing 1 day each + for (int rec = first; rec < last+1; ++rec, date = date.addDays(1)) { + qDebug() << "Record no. " << rec << " Date: " << date.toString() ; + for (int j = firstSig-1; j < lastSig; j++ ) { + // qDebug() << "Signal #" << j; + EDFSignal sig = str.edfsignals[j]; +// if ( sig == nullptr ) { +// qDebug() << "Bad sig pointer at signal " << j; +// exit(2); +// } + if ( ! sig.label.contains("Annotations")) { + qint16 * sample = sig.dataArray; +// qDebug() << "Sample pointer is " << sample; + if (sample == nullptr) { + qDebug() << "Bad sample pointer at signal " << j; + exit(3); + } + QString dataStr = ""; + if (sig.sampleCnt == 1) { +// qDebug() << "Single sample is " << sample[rec]; + dataStr.setNum(sample[rec]); +// qDebug() << "Datastr is " << dataStr; + } + else { + for (int i = 0; i < sig.sampleCnt; i++ ) { + QString num; + num.setNum(sample[rec*sig.sampleCnt + i]); + dataStr.append(" ").append(num); + } + } + qDebug() << "#" << j+1 << sig.label << dataStr; + } + } + } + +} diff --git a/oscar/SleepLib/loader_plugins/edfparser.cpp b/oscar/SleepLib/loader_plugins/edfparser.cpp index 809583f1..b12f5bea 100644 --- a/oscar/SleepLib/loader_plugins/edfparser.cpp +++ b/oscar/SleepLib/loader_plugins/edfparser.cpp @@ -31,8 +31,8 @@ EDFInfo::EDFInfo() EDFInfo::~EDFInfo() { for (auto & s : edfsignals) { - if (s.value) - delete [] s.value; + if (s.dataArray) + delete [] s.dataArray; } // for (auto & a : annotations) // delete a; @@ -95,6 +95,14 @@ bool EDFInfo::Parse(QByteArray * fileData ) 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"); + // This conversion will fail in 2086 after when the spec calls for the year to be 'yy' instead of digits + // The solution is left for the afflicted - it won't be me! + QDate d2 = edfHdr.startdate_orig.date(); + if (d2.year() < 2000) { + d2.setDate(d2.year() + 100, d2.month(), d2.day()); + edfHdr.startdate_orig.setDate(d2); + } + edfHdr.num_header_bytes = QString::fromLatin1(hdrPtr->num_header_bytes, 8).toLong(&ok); if (!ok) { qWarning() << "EDFInfo::Parse() Bad header byte count " << filename; @@ -126,7 +134,7 @@ bool EDFInfo::Parse(QByteArray * fileData ) // Now copy all the Signal descriptives into edfsignals for (auto & sig : edfsignals) { - sig.value = nullptr; + sig.dataArray = nullptr; sig.label = ReadBytes(16); signal_labels.push_back(sig.label); @@ -161,7 +169,7 @@ bool EDFInfo::Parse(QByteArray * fileData ) sig.prefiltering = ReadBytes(80); } for (auto & sig : edfsignals) { - sig.nr = ReadBytes(8).toLong(&ok); + sig.sampleCnt = ReadBytes(8).toLong(&ok); } for (auto & sig : edfsignals) { sig.reserved = ReadBytes(32); @@ -178,7 +186,7 @@ bool EDFInfo::Parse(QByteArray * fileData ) long allocsize = 0; for (auto & sig : edfsignals) { if (edfHdr.num_data_records > 0) { - allocsize += sig.nr * edfHdr.num_data_records * 2; + allocsize += sig.sampleCnt * edfHdr.num_data_records * 2; } } if (allocsize > (datasize - pos)) { @@ -191,36 +199,28 @@ bool EDFInfo::Parse(QByteArray * fileData ) // allocate the arrays for the signal values for (auto & sig : edfsignals) { - long recs = sig.nr * edfHdr.num_data_records; + long recs = sig.sampleCnt * edfHdr.num_data_records; if (edfHdr.num_data_records <= 0) { - sig.value = nullptr; + sig.dataArray = nullptr; continue; } - sig.value = new qint16 [recs]; - sig.pos = 0; + sig.dataArray = new qint16 [recs]; +// sig.pos = 0; } - for (int x = 0; x < edfHdr.num_data_records; x++) { + for (int recNo = 0; recNo < edfHdr.num_data_records; recNo++) { 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; -#else - // Big endian safe - for (int j=0;j & STRmap) continue; } - int recstart = rec * maskon->nr; + int recstart = rec * maskon->sampleCnt; bool validday = false; - for (int s = 0; s < maskon->nr; ++s) { - qint32 on = maskon->value[recstart + s]; - qint32 off = maskoff->value[recstart + s]; + for (int s = 0; s < maskon->sampleCnt; ++s) { + qint32 on = maskon->dataArray[recstart + s]; + qint32 off = maskoff->dataArray[recstart + s]; - if ((on >= 0) && (off >= 0)) + if (((on >= 0) && (off >= 0)) && (on != off)) // ignore very short on-off times validday=true; } if (!validday) { @@ -234,11 +228,11 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) // For every mask on, there will be a session within 1 minute either way // We can use that for data matching // Scan the mask on/off events by minute - R.maskon.resize(maskon->nr); - R.maskoff.resize(maskoff->nr); - for (int s = 0; s < maskon->nr; ++s) { - qint32 on = maskon->value[recstart + s]; - qint32 off = maskoff->value[recstart + s]; + R.maskon.resize(maskon->sampleCnt); + R.maskoff.resize(maskoff->sampleCnt); + for (int s = 0; s < maskon->sampleCnt; ++s) { + qint32 on = maskon->dataArray[recstart + s]; + qint32 off = maskoff->dataArray[recstart + s]; R.maskon[s] = (on>0) ? (timestamp + (on * 60)) : 0; R.maskoff[s] = (off>0) ? (timestamp + (off * 60)) : 0; @@ -250,14 +244,14 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) if ((R.maskon[0]==0) && (R.maskoff[0]>0)) { R.maskon[0] = timestamp; } - if ((R.maskon[maskon->nr-1] > 0) && (R.maskoff[maskoff->nr-1] == 0)) { - R.maskoff[maskoff->nr-1] = QDateTime(date,QTime(12,0,0)).addDays(1).toTime_t() - 1; + if ((R.maskon[maskon->sampleCnt-1] > 0) && (R.maskoff[maskoff->sampleCnt-1] == 0)) { + R.maskoff[maskoff->sampleCnt-1] = QDateTime(date,QTime(12,0,0)).addDays(1).toTime_t() - 1; } CPAPMode mode = MODE_UNKNOWN; if ((sig = str.lookupSignal(CPAP_Mode))) { - int mod = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + int mod = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.rms9_mode = mod; if (mod == 11) { @@ -281,188 +275,188 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) // Settings.CPAP.Starting Pressure if ((mod == 0) && (sig = str.lookupLabel("S.C.StartPress"))) { - R.ramp_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // Settings.Adaptive Starting Pressure? // mode 11 = APAP for her? if (((mod == 1) || (mod == 11)) && (sig = str.lookupLabel("S.AS.StartPress"))) { - R.ramp_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((R.mode == MODE_BILEVEL_FIXED) && (sig = str.lookupLabel("S.BL.StartPress"))) { // Bilevel Starting Pressure - R.ramp_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (((R.mode == MODE_ASV) || (R.mode == MODE_ASV_VARIABLE_EPAP)) && (sig = str.lookupLabel("S.VA.StartPress"))) { // Bilevel Starting Pressure - R.ramp_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } } if ((sig = str.lookupLabel("Mask Dur"))) { - R.maskdur = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.maskdur = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("Leak Med")) || (sig = str.lookupLabel("Leak.50"))) { float gain = sig->gain * 60.0; - R.leak50 = EventDataType(sig->value[rec]) * gain; + R.leak50 = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("Leak Max"))|| (sig = str.lookupLabel("Leak.Max"))) { float gain = sig->gain * 60.0; - R.leakmax = EventDataType(sig->value[rec]) * gain; + R.leakmax = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("Leak 95")) || (sig = str.lookupLabel("Leak.95"))) { float gain = sig->gain * 60.0; - R.leak95 = EventDataType(sig->value[rec]) * gain; + R.leak95 = EventDataType(sig->dataArray[rec]) * gain; } if ((sig = str.lookupLabel("RespRate.50")) || (sig = str.lookupLabel("RR Med"))) { - R.rr50 = EventDataType(sig->value[rec]) * sig->gain; + R.rr50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("RespRate.Max")) || (sig = str.lookupLabel("RR Max"))) { - R.rrmax = EventDataType(sig->value[rec]) * sig->gain; + R.rrmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("RespRate.95")) || (sig = str.lookupLabel("RR 95"))) { - R.rr95 = EventDataType(sig->value[rec]) * sig->gain; + R.rr95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.50")) || (sig = str.lookupLabel("Min Vent Med"))) { - R.mv50 = EventDataType(sig->value[rec]) * sig->gain; + R.mv50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.Max")) || (sig = str.lookupLabel("Min Vent Max"))) { - R.mvmax = EventDataType(sig->value[rec]) * sig->gain; + R.mvmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MinVent.95")) || (sig = str.lookupLabel("Min Vent 95"))) { - R.mv95 = EventDataType(sig->value[rec]) * sig->gain; + R.mv95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TidVol.50")) || (sig = str.lookupLabel("Tid Vol Med"))) { - R.tv50 = EventDataType(sig->value[rec]) * (sig->gain*1000.0); + R.tv50 = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("TidVol.Max")) || (sig = str.lookupLabel("Tid Vol Max"))) { - R.tvmax = EventDataType(sig->value[rec]) * (sig->gain*1000.0); + R.tvmax = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("TidVol.95")) || (sig = str.lookupLabel("Tid Vol 95"))) { - R.tv95 = EventDataType(sig->value[rec]) * (sig->gain*1000.0); + R.tv95 = EventDataType(sig->dataArray[rec]) * (sig->gain*1000.0); } if ((sig = str.lookupLabel("MaskPress.50")) || (sig = str.lookupLabel("Mask Pres Med"))) { - R.mp50 = EventDataType(sig->value[rec]) * sig->gain; + R.mp50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("MaskPress.Max")) || (sig = str.lookupLabel("Mask Pres Max"))) { - R.mpmax = EventDataType(sig->value[rec]) * sig->gain ; + R.mpmax = EventDataType(sig->dataArray[rec]) * sig->gain ; } if ((sig = str.lookupLabel("MaskPress.95")) || (sig = str.lookupLabel("Mask Pres 95"))) { - R.mp95 = EventDataType(sig->value[rec]) * sig->gain ; + R.mp95 = EventDataType(sig->dataArray[rec]) * sig->gain ; } if ((sig = str.lookupLabel("TgtEPAP.50")) || (sig = str.lookupLabel("Exp Pres Med"))) { - R.tgtepap50 = EventDataType(sig->value[rec]) * sig->gain; + R.tgtepap50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtEPAP.Max")) || (sig = str.lookupLabel("Exp Pres Max"))) { - R.tgtepapmax = EventDataType(sig->value[rec]) * sig->gain; + R.tgtepapmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtEPAP.95")) || (sig = str.lookupLabel("Exp Pres 95"))) { - R.tgtepap95 = EventDataType(sig->value[rec]) * sig->gain; + R.tgtepap95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.50")) || (sig = str.lookupLabel("Insp Pres Med"))) { - R.tgtipap50 = EventDataType(sig->value[rec]) * sig->gain; + R.tgtipap50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.Max")) || (sig = str.lookupLabel("Insp Pres Max"))) { - R.tgtipapmax = EventDataType(sig->value[rec]) * sig->gain; + R.tgtipapmax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("TgtIPAP.95")) || (sig = str.lookupLabel("Insp Pres 95"))) { - R.tgtipap95 = EventDataType(sig->value[rec]) * sig->gain; + R.tgtipap95 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E Med"))) { - R.ie50 = EventDataType(sig->value[rec]) * sig->gain; + R.ie50 = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E Max"))) { - R.iemax = EventDataType(sig->value[rec]) * sig->gain; + R.iemax = EventDataType(sig->dataArray[rec]) * sig->gain; } if ((sig = str.lookupLabel("I:E 95"))) { - R.ie95 = EventDataType(sig->value[rec]) * sig->gain; + R.ie95 = EventDataType(sig->dataArray[rec]) * sig->gain; } bool haveipap = false; Q_UNUSED( haveipap ); // if (R.mode == MODE_BILEVEL_FIXED) { if ((sig = str.lookupSignal(CPAP_IPAP))) { - R.ipap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_EPAP))) { - R.epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (R.mode == MODE_ASV) { if ((sig = str.lookupLabel("S.AV.StartPress"))) { - EventDataType sp = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + EventDataType sp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.ramp_pressure = sp; } if ((sig = str.lookupLabel("S.AV.EPAP"))) { - R.min_epap = R.max_epap = R.epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_epap = R.max_epap = R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AV.MinPS"))) { - R.min_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AV.MaxPS"))) { - R.max_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.max_ipap = R.epap + R.max_ps; R.min_ipap = R.epap + R.min_ps; } } if (R.mode == MODE_ASV_VARIABLE_EPAP) { if ((sig = str.lookupLabel("S.AA.StartPress"))) { - EventDataType sp = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + EventDataType sp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.ramp_pressure = sp; } if ((sig = str.lookupLabel("S.AA.MinEPAP"))) { - R.min_epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MaxEPAP"))) { - R.max_epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MinPS"))) { - R.min_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.AA.MaxPS"))) { - R.max_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; R.max_ipap = R.max_epap + R.max_ps; R.min_ipap = R.min_epap + R.min_ps; } } if ((sig = str.lookupSignal(CPAP_PressureMax))) { - R.max_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_PressureMin))) { - R.min_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(RMS9_SetPressure))) { - R.set_pressure = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.set_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_EPAPHi))) { - R.max_epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_EPAPLo))) { - R.min_epap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(CPAP_IPAPHi))) { - R.max_ipap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_IPAPLo))) { - R.min_ipap = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; haveipap = true; } if ((sig = str.lookupSignal(CPAP_PS))) { - R.ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } - // Okay, problem here: THere are TWO PSMin & MAX values on the 36037 with the same string + // Okay, problem here: THere are TWO PSMin & MAX dataArrays on the 36037 with the same string // One is for ASV mode, and one is for ASVAuto int psvar = (mode == MODE_ASV_VARIABLE_EPAP) ? 1 : 0; if ((sig = str.lookupLabel("Max PS", psvar))) { - R.max_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("Min PS", psvar))) { - R.min_ps = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } // ///// if (!haveipap) { @@ -480,24 +474,24 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) bool a10 = false; if ((mode == MODE_CPAP) || (mode == MODE_APAP)) { if ((sig = str.lookupSignal(RMS9_EPR))) { - epr= EventDataType(sig->value[rec]) * sig->gain + sig->offset; + epr= EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupSignal(RMS9_EPRLevel))) { - epr_level= EventDataType(sig->value[rec]) * sig->gain + sig->offset; + epr_level= EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.EPR.EPRType"))) { a10 = true; - epr = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + epr = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; epr += 1; } int epr_on=0, clin_epr_on=0; if ((sig = str.lookupLabel("S.EPR.EPREnable"))) { // first check machines opinion a10 = true; - epr_on = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + epr_on = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (epr_on && (sig = str.lookupLabel("S.EPR.ClinEnable"))) { a10 = true; - clin_epr_on = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + clin_epr_on = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if (a10 && !(epr_on && clin_epr_on)) { epr = 0; @@ -526,71 +520,71 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap & STRmap) } if ((sig = str.lookupLabel("AHI"))) { - R.ahi = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ahi = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("AI"))) { - R.ai = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.ai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("HI"))) { - R.hi = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.hi = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("UAI"))) { - R.uai = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.uai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("CAI"))) { - R.cai = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.cai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("OAI"))) { - R.oai = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.oai = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("CSR"))) { - R.csr = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.csr = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.RampTime"))) { - R.s_RampTime = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_RampTime = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.RampEnable"))) { - R.s_RampEnable = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_RampEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.EPR.ClinEnable"))) { - R.s_EPR_ClinEnable = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_EPR_ClinEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.EPR.EPREnable"))) { - R.s_EPREnable = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_EPREnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.ABFilter"))) { - R.s_ABFilter = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_ABFilter = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.ClimateControl"))) { - R.s_ClimateControl = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_ClimateControl = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.Mask"))) { - R.s_Mask = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_Mask = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.PtAccess"))) { - R.s_PtAccess = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_PtAccess = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.SmartStart"))) { - R.s_SmartStart = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_SmartStart = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.HumEnable"))) { - R.s_HumEnable = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_HumEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.HumLevel"))) { - R.s_HumLevel = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_HumLevel = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.TempEnable"))) { - R.s_TempEnable = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_TempEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.Temp"))) { - R.s_Temp = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_Temp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.Tube"))) { - R.s_Tube = EventDataType(sig->value[rec]) * sig->gain + sig->offset; + R.s_Tube = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } } } @@ -739,9 +733,9 @@ int PeekAnnotations(const QString & path, quint32 &start, quint32 &end) tt = edf.startdate; // Process event annotation records for (int s = 0; s < edf.GetNumSignals(); s++) { - int charLen = edf.edfsignals[s].nr * edf.GetNumDataRecords() * 2; + int charLen = edf.edfsignals[s].sampleCnt * edf.GetNumDataRecords() * 2; - char * data = (char *)edf.edfsignals[s].value; + char * data = (char *)edf.edfsignals[s].dataArray; long pos = 0; double offset; double duration; @@ -836,18 +830,22 @@ EDFduration getEDFDuration(const QString & filename) { QString ext = filename.section("_", -1).section(".",0,0).toUpper(); - bool ok1, ok2; + if ((ext == "EVE") || (ext == "CSL")) { // don't even try with Annotation-only edf files + EDFduration dur(0, 0, filename); + dur.type = lookupEDFType(ext.toUpper()); + return dur; + } + bool ok1, ok2; int num_records; double rec_duration; - QDateTime startDate; +// We will just look at the header part of the edf file here if (!filename.endsWith(".gz", Qt::CaseInsensitive)) { QFile file(filename); - if (!file.open(QFile::ReadOnly)) { + if (!file.open(QFile::ReadOnly)) return EDFduration(0, 0, filename); - } if (!file.seek(0xa8)) { file.close(); @@ -855,7 +853,7 @@ EDFduration getEDFDuration(const QString & filename) } QByteArray bytes = file.read(16).trimmed(); - + // We'll fix the xx85 problem below startDate = QDateTime::fromString(QString::fromLatin1(bytes, 16), "dd.MM.yyHH.mm.ss"); if (!file.seek(0xec)) { @@ -871,10 +869,10 @@ EDFduration getEDFDuration(const QString & filename) file.close(); } else { gzFile f = gzopen(filename.toLatin1(), "rb"); - if (!f) { + if (!f) return EDFduration(0, 0, filename); - } + // Decompressed header and data block if (!gzseek(f, 0xa8, SEEK_SET)) { gzclose(f); return EDFduration(0, 0, filename); @@ -890,7 +888,6 @@ EDFduration getEDFDuration(const QString & filename) return EDFduration(0, 0, filename); } - // Decompressed header and data block char cbytes[9] = {0}; gzread(f, (char *)&cbytes, 8); str = QString(cbytes).trimmed(); @@ -901,7 +898,6 @@ EDFduration getEDFDuration(const QString & filename) rec_duration = str.toDouble(&ok2); gzclose(f); - } QDate d2 = startDate.date(); @@ -915,9 +911,8 @@ EDFduration getEDFDuration(const QString & filename) return EDFduration(0, 0, filename); } - if (!(ok1 && ok2)) { + if (!(ok1 && ok2)) return EDFduration(0, 0, filename); - } quint32 start = startDate.toTime_t(); quint32 end = start + rec_duration * num_records; @@ -927,35 +922,11 @@ EDFduration getEDFDuration(const QString & filename) QDateTime dt2 = QDateTime::fromString(filedate, "yyyyMMdd_hhmmss"); quint32 st2 = dt2.toTime_t(); - start = qMin(st2, start); + start = qMin(st2, start); // They should be the same, usually if (end < start) end = qMax(st2, start); - if ((ext == "EVE") || (ext == "CSL")) { - // S10 Forces us to parse EVE files to find their real durations - quint32 en2; - - // Have to get the actual duration of the EVE file by parsing the annotations. :( - - - // Can we cache the stupid EDFInfo file for later ??? - - int recs = PeekAnnotations(filename, st2, en2); - if (recs > 0) { - start = qMin(st2, start); - end = qMax(en2, end); - EDFduration dur(start, end, filename); - - dur.type = lookupEDFType(ext.toUpper()); - - return dur; - } else { - // empty annotations file, don't give a crap about it... - return EDFduration(0, 0, filename); - } - // A Firmware bug causes (perhaps with failing SD card) sessions to sometimes take a long time to write - } EDFduration dur(start, end, filename); @@ -984,6 +955,7 @@ int ResmedLoader::scanFiles(Machine * mach, const QString & datalog_path) /////////////////////////////////////////////////////////////////////////////////////// // Generate list of files for later processing /////////////////////////////////////////////////////////////////////////////////////// + qDebug() << "Generating list of EDF files"; #ifdef DEBUG_EFFICIENCY time.start(); @@ -1002,29 +974,43 @@ int ResmedLoader::scanFiles(Machine * mach, const QString & datalog_path) QString filename; bool ok; - QFileInfoList flist = dir.entryInfoList(); - int flistSize = flist.size(); + QFileInfoList dirlist = dir.entryInfoList(); + int dirlistSize = dirlist.size(); - qDebug() << "Generating list of EDF files"; + QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate(); + bool ignoreOldSessions = p_profile->session->ignoreOlderSessions(); + // Scan for any sub folders and create files lists - for (int i = 0; i < flistSize ; i++) { - const QFileInfo & fi = flist.at(i); + for (int i = 0; i < dirlistSize ; i++) { + const QFileInfo & fi = dirlist.at(i); filename = fi.fileName(); int len = filename.length(); - if ((len == 4) || (len == 8)) { + if (len == 4) { // when does this happen? filename.toInt(&ok); - if (ok) { - // Get file lists under this directory - - dir.setPath(fi.canonicalFilePath()); - dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); - dir.setSorting(QDir::Name); - - // Append all files to one big QFileInfoList - EDFfiles.append(dir.entryInfoList()); + if ( ! ok ) { + qDebug() << "Skipping directory " << filename; + continue; } + } else if (len == 8) { // test directory date + if (ignoreOldSessions) { + QDateTime dirDate = QDateTime().fromString(filename, "yyyyMMdd"); + if (dirDate.date() < ignoreBefore.date()) { + qDebug() << "Skipping directory " << filename; + continue; + } + } + } else { + qDebug() << "Skipping directory " << filename; + continue; } + // Get file lists under this directory + dir.setPath(fi.canonicalFilePath()); + dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); + dir.setSorting(QDir::Name); + + // Append all files to one big QFileInfoList + EDFfiles.append(dir.entryInfoList()); } #ifdef DEBUG_EFFICIENCY qDebug() << "Generating EDF files list took" << time.elapsed() << "ms"; @@ -1054,8 +1040,6 @@ int ResmedLoader::scanFiles(Machine * mach, const QString & datalog_path) QCoreApplication::processEvents(); // Scan through all folders looking for EDF files, skip any already imported and peek inside to get durations - QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate(); - bool ignoreOldSessions = p_profile->session->ignoreOlderSessions(); qDebug() << "Starting EDF duration scan pass"; for (int i=0; i < totalfiles; ++i) { @@ -2181,9 +2165,9 @@ bool ResmedLoader::LoadCSL(Session *sess, const QString & path) // Process event annotation records for (int s = 0; s < edf.GetNumSignals(); s++) { - int recs = edf.edfsignals[s].nr * edf.GetNumDataRecords() * 2; + int recs = edf.edfsignals[s].sampleCnt * edf.GetNumDataRecords() * 2; - data = (char *)edf.edfsignals[s].value; + data = (char *)edf.edfsignals[s].dataArray; pos = 0; tt = edf.startdate; // sess->updateFirst(tt); @@ -2347,9 +2331,9 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path) // Process event annotation records for (int s = 0; s < edf.GetNumSignals(); s++) { - recs = edf.edfsignals[s].nr * edf.GetNumDataRecords() * 2; + recs = edf.edfsignals[s].sampleCnt * edf.GetNumDataRecords() * 2; - data = (char *)edf.edfsignals[s].value; + data = (char *)edf.edfsignals[s].dataArray; pos = 0; tt = edf.startdate; @@ -2503,7 +2487,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) sess->updateLast(edf.startdate + duration); for (auto & es : edf.edfsignals) { - long recs = es.nr * edf.GetNumDataRecords(); + long recs = es.sampleCnt * edf.GetNumDataRecords(); if (recs < 0) continue; ChannelID code; @@ -2534,7 +2518,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path) #ifdef DEBUG_EFFICIENCY time2.start(); #endif - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); #ifdef DEBUG_EFFICIENCY AddWavetime+= time2.elapsed(); #endif @@ -2593,7 +2577,7 @@ void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFInfo &edf, EDFSignal &es, tt += rate * startpos; } - qint16 *sptr = es.value; + qint16 *sptr = es.dataArray; qint16 *eptr = sptr + recs; sptr += startpos; @@ -2740,13 +2724,13 @@ bool ResmedLoader::LoadSAD(Session *sess, const QString & path) for (auto & es : edf.edfsignals) { //qDebug() << "SAD:" << es.label << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum; - long recs = es.nr * edf.GetNumDataRecords(); + long recs = es.sampleCnt * edf.GetNumDataRecords(); ChannelID code; bool hasdata = false; for (int i = 0; i < recs; ++i) { - if (es.value[i] != -1) { + if (es.dataArray[i] != -1) { hasdata = true; break; } @@ -2815,7 +2799,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) for (auto & es : edf.edfsignals) { a = nullptr; - recs = es.nr * edf.GetNumDataRecords(); + recs = es.sampleCnt * edf.GetNumDataRecords(); if (recs <= 0) continue; @@ -2848,7 +2832,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) } else if (matchSignal(CPAP_RespRate, es.label)) { code = CPAP_RespRate; a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); } else if (matchSignal(CPAP_TidalVolume, es.label)) { code = CPAP_TidalVolume; es.gain *= 1000.0; @@ -2883,9 +2867,9 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) // es.physical_maximum /= 100.0; // es.physical_minimum /= 100.0; // qDebug() << "IE Gain, Max, Min" << es.gain << es.physical_maximum << es.physical_minimum; -// qDebug() << "IE count, data..." << es.nr << es.value[0] << es.value[1] << es.value[2] << es.value[3] << es.value[4]; +// qDebug() << "IE count, data..." << es.sampleCnt << es.dataArray[0] << es.dataArray[1] << es.dataArray[2] << es.dataArray[3] << es.dataArray[4]; a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); // a = ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (matchSignal(CPAP_Ti, es.label)) { code = CPAP_Ti; @@ -2894,7 +2878,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) if (sess->eventlist.contains(code)) continue; a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); // a = ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (matchSignal(CPAP_Te, es.label)) { code = CPAP_Te; @@ -2902,12 +2886,12 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path) if (sess->eventlist.contains(code)) continue; a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); // a = ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (matchSignal(CPAP_TgMV, es.label)) { code = CPAP_TgMV; a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate); - a->AddWaveform(edf.startdate, es.value, recs, duration); + a->AddWaveform(edf.startdate, es.dataArray, recs, duration); //a=ToTimeDelta(sess,edf,es, code,recs,duration,0,0); } else if (es.label == "") { // What the hell resmed?? if (emptycnt == 0) {