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.
This commit is contained in:
Phil Olynyk 2019-08-03 10:59:08 -04:00
parent 37c9e615e4
commit b80ae70525
8 changed files with 920 additions and 198 deletions

32
dumpSTR.pro Normal file
View File

@ -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 \

23
dumpSTR/common.h Normal file
View File

@ -0,0 +1,23 @@
/* Common code and junk
*
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
*
* 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 <QString>
#include <QDateTime>
#include <QObject>
#ifndef nullptr
#define nullptr NULL
#endif
typedef float EventDataType;
#endif // COMMON_H

351
dumpSTR/edfparser.cpp Normal file
View File

@ -0,0 +1,351 @@
/* EDF Parser Implementation
*
* Copyright (c) 2019 The OSCAR Team
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
*
* 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 <QDateTime>
#include <QDebug>
#include <QFile>
#include <QMutexLocker>
#ifdef _MSC_VER
#include <QtZlib/zlib.h>
#else
#include <zlib.h>
#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<sig.sampleCnt;j++) { // Big endian safe
qint16 t=Read16();
sig.dataArray[recNo*sig.sampleCnt + j]=t;
}
}
}
}
return true;
}
// Parse the EDF file to get the annotations out of it.
QVector<Annotation> * EDFInfo::ReadAnnotations(const char * data, int charLen)
{
QVector<Annotation> * annoVec = new QVector<Annotation>;
// 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];
}

175
dumpSTR/edfparser.h Normal file
View File

@ -0,0 +1,175 @@
/* EDF Parser Header
*
* Copyright (c) 2019 The OSCAR Team
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
*
* 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 <QString>
#include <QVector>
#include <QHash>
#include <QList>
#include <QDateTime>
// #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 <mark@jedimark.net>
\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<EDFSignal> edfsignals; //! \brief Holds the EDFSignals contained in this edf file
// QVector< QVector<qint16> > dataRecords; //! \brief Holds the datarecords
QVector< QVector<Annotation> * > annotations; //! \brief Holds the Annotaions for this EDF file
QStringList signal_labels; //! \brief An by-name indexed into the EDFSignal data
QHash<QString, QList<EDFSignal *> > signalList; //! \brief ResMed sometimes re-uses the SAME signal name
// the following could be private
private:
QVector<Annotation> * 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

157
dumpSTR/main.cpp Normal file
View File

@ -0,0 +1,157 @@
/* Dump an STR.edf file */
#include <QApplication>
// #include <iostream>
#include <QDebug>
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<EDFSignal> 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;
}
}
}
}

View File

@ -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<sig.nr;j++) {
qint16 t=Read16();
sig.value[sig.pos++]=t;
}
#endif
}
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<sig.sampleCnt;j++) { // Big endian safe
qint16 t=Read16();
sig.dataArray[recNo*sig.sampleCnt+j]=t;
}
}
}
}
return true;
return true;
}
// Parse the EDF file to get the annotations out of it.

View File

@ -77,11 +77,11 @@ struct EDFSignal {
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
long sampleCnt; //! \brief Number of samples per record
QString reserved; //! \brief Reserved (usually blank)
qint16 *value; //! \brief Pointer to the signals sample data
qint16 * dataArray; //! \brief Pointer to the signals sample data
int pos; //! \brief a non-EDF extra used internally to count the signal data
// int pos; //! \brief a non-EDF extra used internally to count the signal data
};
/*! \class Annotation

View File

@ -77,12 +77,6 @@ bool ResMedEDFInfo::Parse(QByteArray * fileData ) // overrides and calls the sup
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);
@ -207,14 +201,14 @@ void ResmedLoader::ParseSTR(Machine *mach, QMap<QDate, STRFile> & 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<QDate, STRFile> & 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<QDate, STRFile> & 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<QDate, STRFile> & 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<QDate, STRFile> & 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<QDate, STRFile> & 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();
QDateTime ignoreBefore = p_profile->session->ignoreOlderSessionsDate();
bool ignoreOldSessions = p_profile->session->ignoreOlderSessions();
qDebug() << "Generating list of EDF files";
// 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) {