mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-05 10:40:42 +00:00
Split EDF Parser from ResMed importer
This commit is contained in:
parent
826322ff86
commit
6f22172d51
294
sleepyhead/SleepLib/loader_plugins/edfparser.cpp
Normal file
294
sleepyhead/SleepLib/loader_plugins/edfparser.cpp
Normal file
@ -0,0 +1,294 @@
|
||||
/* EDF Parser Implementation
|
||||
*
|
||||
* 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 Linux
|
||||
* distribution for more details. */
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include "zlib.h"
|
||||
|
||||
|
||||
#include "edfparser.h"
|
||||
|
||||
EDFParser::EDFParser(QString name)
|
||||
{
|
||||
buffer = nullptr;
|
||||
Open(name);
|
||||
}
|
||||
EDFParser::~EDFParser()
|
||||
{
|
||||
for (QVector<EDFSignal>::iterator s = edfsignals.begin(); s != edfsignals.end(); s++) {
|
||||
if ((*s).data) { delete [](*s).data; }
|
||||
}
|
||||
|
||||
if (buffer) { delete [] buffer; }
|
||||
}
|
||||
// Read a 16 bits integer
|
||||
qint16 EDFParser::Read16()
|
||||
{
|
||||
if ((pos + 2) > filesize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef Q_LITTLE_ENDIAN
|
||||
// Intel, etc...
|
||||
qint16 res = *(qint16 *)&buffer[pos];
|
||||
#else
|
||||
// ARM, PPC, etc..
|
||||
qint16 res = quint8(buffer[pos]) | (qint8(buffer[pos+1]) << 8);
|
||||
#endif
|
||||
|
||||
pos += 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
QString EDFParser::Read(unsigned n)
|
||||
{
|
||||
if ((pos + long(n)) > filesize) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QByteArray buf(&buffer[pos], n);
|
||||
pos+=n;
|
||||
|
||||
return buf.trimmed();
|
||||
}
|
||||
bool EDFParser::Parse()
|
||||
{
|
||||
bool ok;
|
||||
QString temp, temp2;
|
||||
|
||||
version = QString::fromLatin1(header.version, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//patientident=QString::fromLatin1(header.patientident,80);
|
||||
recordingident = QString::fromLatin1(header.recordingident, 80); // Serial number is in here..
|
||||
int snp = recordingident.indexOf("SRN=");
|
||||
serialnumber.clear();
|
||||
/*char * idx=index(header.recordingident,'=');
|
||||
idx++;
|
||||
for (int i=0;i<16;++i) {
|
||||
if (*idx==0x20) break;
|
||||
serialnumber+=*idx;
|
||||
++idx;
|
||||
} */
|
||||
|
||||
for (int i = snp + 4; i < recordingident.length(); i++) {
|
||||
if (recordingident[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
|
||||
serialnumber += recordingident[i];
|
||||
}
|
||||
|
||||
QDateTime startDate = QDateTime::fromString(QString::fromLatin1(header.datetime, 16), "dd.MM.yyHH.mm.ss");
|
||||
//startDate.toTimeSpec(Qt::UTC);
|
||||
|
||||
QDate d2 = startDate.date();
|
||||
|
||||
if (d2.year() < 2000) {
|
||||
d2.setDate(d2.year() + 100, d2.month(), d2.day());
|
||||
startDate.setDate(d2);
|
||||
}
|
||||
|
||||
if (!startDate.isValid()) {
|
||||
qDebug() << "Invalid date time retreieved parsing EDF File " << filename;
|
||||
return false;
|
||||
}
|
||||
|
||||
startdate = qint64(startDate.toTime_t()) * 1000L;
|
||||
//startdate-=timezoneOffset();
|
||||
|
||||
//qDebug() << startDate.toString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
num_header_bytes = QString::fromLatin1(header.num_header_bytes, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//reserved44=QString::fromLatin1(header.reserved,44);
|
||||
num_data_records = QString::fromLatin1(header.num_data_records, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dur_data_record = (QString::fromLatin1(header.dur_data_records, 8).toDouble(&ok) * 1000.0L);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
num_signals = QString::fromLatin1(header.num_signals, 4).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enddate = startdate + dur_data_record * qint64(num_data_records);
|
||||
// if (dur_data_record==0)
|
||||
// return false;
|
||||
|
||||
// this could be loaded quicker by transducer_type[signal] etc..
|
||||
|
||||
// Initialize fixed-size signal list.
|
||||
edfsignals.resize(num_signals);
|
||||
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
sig.data = nullptr;
|
||||
sig.label = Read(16);
|
||||
|
||||
signal_labels.push_back(sig.label);
|
||||
signalList[sig.label].push_back(&sig);
|
||||
signal.push_back(&sig);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].transducer_type = Read(80); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_dimension = Read(8); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_minimum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_maximum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].digital_minimum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &e = edfsignals[i];
|
||||
e.digital_maximum = Read(8).toDouble(&ok);
|
||||
e.gain = (e.physical_maximum - e.physical_minimum) / (e.digital_maximum - e.digital_minimum);
|
||||
e.offset = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].prefiltering = Read(80); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].nr = Read(8).toLong(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].reserved = Read(32); }
|
||||
|
||||
// allocate the buffers
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
//qDebug//cout << "Reading signal " << signals[i]->label << endl;
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
|
||||
long recs = sig.nr * num_data_records;
|
||||
|
||||
if (num_data_records < 0) {
|
||||
sig.data = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
sig.data = new qint16 [recs];
|
||||
sig.pos = 0;
|
||||
}
|
||||
|
||||
for (int x = 0; x < num_data_records; x++) {
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
#ifdef Q_LITTLE_ENDIAN
|
||||
// Intel x86, etc..
|
||||
memcpy((char *)&sig.data[sig.pos], (char *)&buffer[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.data[sig.pos++]=t;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool EDFParser::Open(QString name)
|
||||
{
|
||||
if (buffer != nullptr) {
|
||||
qWarning() << "EDFParser::Open() called with buffer already initialized";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.endsWith(STR_ext_gz)) {
|
||||
// Open and decempress file to buffer
|
||||
|
||||
filename = name.mid(0, -3);
|
||||
|
||||
// Get file length from inside gzip file
|
||||
QFile fi(name);
|
||||
|
||||
if (!fi.open(QFile::ReadOnly) || !fi.seek(fi.size() - 4)) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
unsigned char ch[4];
|
||||
fi.read((char *)ch, 4);
|
||||
filesize = ch[0] | (ch [1] << 8) | (ch[2] << 16) | (ch[3] << 24);
|
||||
|
||||
datasize = filesize - EDFHeaderSize;
|
||||
if (datasize < 0) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
// Open gzip file for reading
|
||||
gzFile f = gzopen(name.toLatin1(), "rb");
|
||||
if (!f) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
// Decompressed header and data block
|
||||
gzread(f, (char *)&header, EDFHeaderSize);
|
||||
buffer = new char [datasize];
|
||||
gzread(f, buffer, datasize);
|
||||
gzclose(f);
|
||||
} else {
|
||||
|
||||
// Open and read uncompressed file
|
||||
QFile f(name);
|
||||
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
filename = name;
|
||||
filesize = f.size();
|
||||
datasize = filesize - EDFHeaderSize;
|
||||
|
||||
if (datasize < 0) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
f.read((char *)&header, EDFHeaderSize);
|
||||
|
||||
buffer = new char [datasize];
|
||||
f.read(buffer, datasize);
|
||||
f.close();
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
return true;
|
||||
|
||||
badfile:
|
||||
qDebug() << "EDFParser::Open() Couldn't open file" << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
EDFSignal *EDFParser::lookupLabel(QString name, int index)
|
||||
{
|
||||
QHash<QString, QList<EDFSignal *> >::iterator it = signalList.find(name);
|
||||
|
||||
if (it == signalList.end()) return nullptr;
|
||||
|
||||
if (index >= it.value().size()) return nullptr;
|
||||
|
||||
return it.value()[index];
|
||||
}
|
170
sleepyhead/SleepLib/loader_plugins/edfparser.h
Normal file
170
sleepyhead/SleepLib/loader_plugins/edfparser.h
Normal file
@ -0,0 +1,170 @@
|
||||
/* EDF Parser Header
|
||||
*
|
||||
* 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 Linux
|
||||
* distribution for more details. */
|
||||
|
||||
#ifndef EDFPARSER_H
|
||||
#define EDFPARSER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
|
||||
#include "SleepLib/common.h"
|
||||
|
||||
const QString STR_ext_EDF = "edf";
|
||||
const QString STR_ext_gz = ".gz";
|
||||
|
||||
/*! \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 EDFHeader {
|
||||
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 BUILD_WITH_MSVC
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
;
|
||||
|
||||
const int EDFHeaderSize = sizeof(EDFHeader);
|
||||
|
||||
/*! \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:
|
||||
//! \brief Name of this Signal
|
||||
QString label;
|
||||
|
||||
//! \brief Tranducer Type (source of the data, usually blank)
|
||||
QString transducer_type;
|
||||
|
||||
//! \brief The units of measurements represented by this signal
|
||||
QString physical_dimension;
|
||||
|
||||
//! \brief The minimum limits of the ungained data
|
||||
EventDataType physical_minimum;
|
||||
|
||||
//! \brief The maximum limits of the ungained data
|
||||
EventDataType physical_maximum;
|
||||
|
||||
//! \brief The minimum limits of the data with gain and offset applied
|
||||
EventDataType digital_minimum;
|
||||
|
||||
//! \brief The maximum limits of the data with gain and offset applied
|
||||
EventDataType digital_maximum;
|
||||
|
||||
//! \brief Raw integer data is multiplied by this value
|
||||
EventDataType gain;
|
||||
|
||||
//! \brief This value is added to the raw data
|
||||
EventDataType offset;
|
||||
|
||||
//! \brief Any prefiltering methods used (usually blank)
|
||||
QString prefiltering;
|
||||
|
||||
//! \brief Number of records
|
||||
long nr;
|
||||
|
||||
//! \brief Reserved (usually blank)
|
||||
QString reserved;
|
||||
|
||||
//! \brief Pointer to the signals sample data
|
||||
qint16 *data;
|
||||
|
||||
//! \brief a non-EDF extra used internally to count the signal data
|
||||
int pos;
|
||||
};
|
||||
|
||||
|
||||
/*! \class EDFParser
|
||||
\author Mark Watkins <jedimark64_at_users.sourceforge.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 EDFParser
|
||||
{
|
||||
public:
|
||||
//! \brief Constructs an EDFParser object, opening the filename if one supplied
|
||||
EDFParser(QString filename = "");
|
||||
|
||||
~EDFParser();
|
||||
|
||||
//! \brief Open the EDF+ file, and read it's header
|
||||
bool Open(QString name);
|
||||
|
||||
//! \brief Read n bytes of 8 bit data from the EDF+ data stream
|
||||
QString Read(unsigned n);
|
||||
|
||||
//! \brief Read 16 bit word of data from the EDF+ data stream
|
||||
qint16 Read16();
|
||||
|
||||
//! \brief Vector containing the list of EDFSignals contained in this edf file
|
||||
QVector<EDFSignal> edfsignals;
|
||||
|
||||
//! \brief An by-name indexed into the EDFSignal data
|
||||
QStringList signal_labels;
|
||||
|
||||
//! \brief ResMed likes to use the SAME signal name
|
||||
QHash<QString, QList<EDFSignal *> > signalList;
|
||||
|
||||
QList<EDFSignal *> signal;
|
||||
|
||||
//! \brief Look up signal names by SleepLib ChannelID.. A little "ResMed"ified.. :/
|
||||
//EDFSignal *lookupSignal(ChannelID);
|
||||
EDFSignal *lookupLabel(QString name, int index=0);
|
||||
|
||||
//! \brief Returns the number of signals contained in this EDF file
|
||||
long GetNumSignals() { return num_signals; }
|
||||
|
||||
//! \brief Returns the number of data records contained per signal.
|
||||
long GetNumDataRecords() { return num_data_records; }
|
||||
|
||||
//! \brief Returns the duration represented by this EDF file (in milliseconds)
|
||||
qint64 GetDuration() { return dur_data_record; }
|
||||
|
||||
//! \brief Returns the patientid field from the EDF header
|
||||
QString GetPatient() { return patientident; }
|
||||
|
||||
//! \brief Parse the EDF+ file into the list of EDFSignals.. Must be call Open(..) first.
|
||||
bool Parse();
|
||||
char *buffer;
|
||||
|
||||
//! \brief The EDF+ files header structure, used as a place holder while processing the text data.
|
||||
EDFHeader header;
|
||||
|
||||
QString filename;
|
||||
long filesize;
|
||||
long datasize;
|
||||
long pos;
|
||||
|
||||
long version;
|
||||
long num_header_bytes;
|
||||
long num_data_records;
|
||||
qint64 dur_data_record;
|
||||
long num_signals;
|
||||
|
||||
QString patientident;
|
||||
QString recordingident;
|
||||
QString serialnumber;
|
||||
qint64 startdate;
|
||||
qint64 enddate;
|
||||
QString reserved44;
|
||||
};
|
||||
|
||||
|
||||
#endif // EDFPARSER_H
|
@ -62,12 +62,18 @@ QHash<ChannelID, QStringList> resmed_codes;
|
||||
|
||||
const QString STR_ext_TGT = "tgt";
|
||||
const QString STR_ext_CRC = "crc";
|
||||
const QString STR_ext_EDF = "edf";
|
||||
const QString STR_ext_gz = ".gz";
|
||||
|
||||
|
||||
ResMedEDFParser::ResMedEDFParser(QString filename)
|
||||
:EDFParser(filename)
|
||||
{
|
||||
}
|
||||
ResMedEDFParser::~ResMedEDFParser()
|
||||
{
|
||||
}
|
||||
|
||||
// Looks up foreign language Signal names that match this channelID
|
||||
EDFSignal *EDFParser::lookupSignal(ChannelID ch)
|
||||
EDFSignal *ResMedEDFParser::lookupSignal(ChannelID ch)
|
||||
{
|
||||
// Get list of all known foreign language names for this channel
|
||||
QHash<ChannelID, QStringList>::iterator channames = resmed_codes.find(ch);
|
||||
@ -110,36 +116,13 @@ bool matchSignal(ChannelID ch, const QString & name)
|
||||
return false;
|
||||
}
|
||||
|
||||
EDFSignal *EDFParser::lookupLabel(QString name, int index)
|
||||
{
|
||||
QHash<QString, QList<EDFSignal *> >::iterator it = signalList.find(name);
|
||||
|
||||
if (it == signalList.end()) return nullptr;
|
||||
|
||||
if (index >= it.value().size()) return nullptr;
|
||||
|
||||
return it.value()[index];
|
||||
}
|
||||
|
||||
EDFParser::EDFParser(QString name)
|
||||
{
|
||||
buffer = nullptr;
|
||||
Open(name);
|
||||
}
|
||||
EDFParser::~EDFParser()
|
||||
{
|
||||
for (QVector<EDFSignal>::iterator s = edfsignals.begin(); s != edfsignals.end(); s++) {
|
||||
if ((*s).data) { delete [](*s).data; }
|
||||
}
|
||||
|
||||
if (buffer) { delete [] buffer; }
|
||||
}
|
||||
|
||||
void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
|
||||
{
|
||||
QStringList::iterator strend = strfiles.end();
|
||||
for (QStringList::iterator it = strfiles.begin(); it != strend; ++it) {
|
||||
EDFParser str(*it);
|
||||
ResMedEDFParser str(*it);
|
||||
if (!str.Parse()) continue;
|
||||
if (mach->serial() != str.serialnumber) {
|
||||
qDebug() << "Trying to import a STR.edf from another machine, skipping" << mach->serial() << str.serialnumber;
|
||||
@ -538,260 +521,7 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
|
||||
}
|
||||
}
|
||||
|
||||
// Read a 16 bits integer
|
||||
qint16 EDFParser::Read16()
|
||||
{
|
||||
if ((pos + 2) > filesize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef Q_LITTLE_ENDIAN
|
||||
// Intel, etc...
|
||||
qint16 res = *(qint16 *)&buffer[pos];
|
||||
#else
|
||||
// ARM, PPC, etc..
|
||||
qint16 res = quint8(buffer[pos]) | (qint8(buffer[pos+1]) << 8);
|
||||
#endif
|
||||
|
||||
pos += 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
QString EDFParser::Read(unsigned n)
|
||||
{
|
||||
if ((pos + long(n)) > filesize) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QByteArray buf(&buffer[pos], n);
|
||||
pos+=n;
|
||||
|
||||
return buf.trimmed();
|
||||
}
|
||||
bool EDFParser::Parse()
|
||||
{
|
||||
bool ok;
|
||||
QString temp, temp2;
|
||||
|
||||
version = QString::fromLatin1(header.version, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//patientident=QString::fromLatin1(header.patientident,80);
|
||||
recordingident = QString::fromLatin1(header.recordingident, 80); // Serial number is in here..
|
||||
int snp = recordingident.indexOf("SRN=");
|
||||
serialnumber.clear();
|
||||
/*char * idx=index(header.recordingident,'=');
|
||||
idx++;
|
||||
for (int i=0;i<16;++i) {
|
||||
if (*idx==0x20) break;
|
||||
serialnumber+=*idx;
|
||||
++idx;
|
||||
} */
|
||||
|
||||
for (int i = snp + 4; i < recordingident.length(); i++) {
|
||||
if (recordingident[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
|
||||
serialnumber += recordingident[i];
|
||||
}
|
||||
|
||||
QDateTime startDate = QDateTime::fromString(QString::fromLatin1(header.datetime, 16), "dd.MM.yyHH.mm.ss");
|
||||
//startDate.toTimeSpec(Qt::UTC);
|
||||
|
||||
QDate d2 = startDate.date();
|
||||
|
||||
if (d2.year() < 2000) {
|
||||
d2.setDate(d2.year() + 100, d2.month(), d2.day());
|
||||
startDate.setDate(d2);
|
||||
}
|
||||
|
||||
if (!startDate.isValid()) {
|
||||
qDebug() << "Invalid date time retreieved parsing EDF File " << filename;
|
||||
return false;
|
||||
}
|
||||
|
||||
startdate = qint64(startDate.toTime_t()) * 1000L;
|
||||
//startdate-=timezoneOffset();
|
||||
|
||||
//qDebug() << startDate.toString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
num_header_bytes = QString::fromLatin1(header.num_header_bytes, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//reserved44=QString::fromLatin1(header.reserved,44);
|
||||
num_data_records = QString::fromLatin1(header.num_data_records, 8).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dur_data_record = (QString::fromLatin1(header.dur_data_records, 8).toDouble(&ok) * 1000.0L);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
num_signals = QString::fromLatin1(header.num_signals, 4).toLong(&ok);
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
enddate = startdate + dur_data_record * qint64(num_data_records);
|
||||
// if (dur_data_record==0)
|
||||
// return false;
|
||||
|
||||
// this could be loaded quicker by transducer_type[signal] etc..
|
||||
|
||||
// Initialize fixed-size signal list.
|
||||
edfsignals.resize(num_signals);
|
||||
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
sig.data = nullptr;
|
||||
sig.label = Read(16);
|
||||
|
||||
signal_labels.push_back(sig.label);
|
||||
signalList[sig.label].push_back(&sig);
|
||||
signal.push_back(&sig);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].transducer_type = Read(80); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_dimension = Read(8); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_minimum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].physical_maximum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].digital_minimum = Read(8).toDouble(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &e = edfsignals[i];
|
||||
e.digital_maximum = Read(8).toDouble(&ok);
|
||||
e.gain = (e.physical_maximum - e.physical_minimum) / (e.digital_maximum - e.digital_minimum);
|
||||
e.offset = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].prefiltering = Read(80); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].nr = Read(8).toLong(&ok); }
|
||||
|
||||
for (int i = 0; i < num_signals; i++) { edfsignals[i].reserved = Read(32); }
|
||||
|
||||
// allocate the buffers
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
//qDebug//cout << "Reading signal " << signals[i]->label << endl;
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
|
||||
long recs = sig.nr * num_data_records;
|
||||
|
||||
if (num_data_records < 0) {
|
||||
sig.data = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
sig.data = new qint16 [recs];
|
||||
sig.pos = 0;
|
||||
}
|
||||
|
||||
for (int x = 0; x < num_data_records; x++) {
|
||||
for (int i = 0; i < num_signals; i++) {
|
||||
EDFSignal &sig = edfsignals[i];
|
||||
#ifdef Q_LITTLE_ENDIAN
|
||||
// Intel x86, etc..
|
||||
memcpy((char *)&sig.data[sig.pos], (char *)&buffer[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.data[sig.pos++]=t;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool EDFParser::Open(QString name)
|
||||
{
|
||||
if (buffer != nullptr) {
|
||||
qWarning() << "EDFParser::Open() called with buffer already initialized";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.endsWith(STR_ext_gz)) {
|
||||
// Open and decempress file to buffer
|
||||
|
||||
filename = name.mid(0, -3);
|
||||
|
||||
// Get file length from inside gzip file
|
||||
QFile fi(name);
|
||||
|
||||
if (!fi.open(QFile::ReadOnly) || !fi.seek(fi.size() - 4)) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
unsigned char ch[4];
|
||||
fi.read((char *)ch, 4);
|
||||
filesize = ch[0] | (ch [1] << 8) | (ch[2] << 16) | (ch[3] << 24);
|
||||
|
||||
datasize = filesize - EDFHeaderSize;
|
||||
if (datasize < 0) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
// Open gzip file for reading
|
||||
gzFile f = gzopen(name.toLatin1(), "rb");
|
||||
if (!f) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
// Decompressed header and data block
|
||||
gzread(f, (char *)&header, EDFHeaderSize);
|
||||
buffer = new char [datasize];
|
||||
gzread(f, buffer, datasize);
|
||||
gzclose(f);
|
||||
} else {
|
||||
|
||||
// Open and read uncompressed file
|
||||
QFile f(name);
|
||||
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
filename = name;
|
||||
filesize = f.size();
|
||||
datasize = filesize - EDFHeaderSize;
|
||||
|
||||
if (datasize < 0) {
|
||||
goto badfile;
|
||||
}
|
||||
|
||||
f.read((char *)&header, EDFHeaderSize);
|
||||
|
||||
buffer = new char [datasize];
|
||||
f.read(buffer, datasize);
|
||||
f.close();
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
return true;
|
||||
|
||||
badfile:
|
||||
qDebug() << "EDFParser::Open() Couldn't open file" << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void ResmedImport::run()
|
||||
@ -2844,7 +2574,7 @@ bool ResmedLoader::LoadBRP(Session *sess, const QString & path)
|
||||
}
|
||||
|
||||
// Convert EDFSignal data to sleepyheads Time-Delta Event format
|
||||
void ResmedLoader::ToTimeDelta(Session *sess, EDFParser &edf, EDFSignal &es, ChannelID code,
|
||||
void ResmedLoader::ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &es, ChannelID code,
|
||||
long recs, qint64 duration, EventDataType t_min, EventDataType t_max, bool square)
|
||||
{
|
||||
if (t_min == t_max) {
|
||||
@ -2995,7 +2725,7 @@ void ResmedLoader::ToTimeDelta(Session *sess, EDFParser &edf, EDFSignal &es, Cha
|
||||
// Load SAD Oximetry Signals
|
||||
bool ResmedLoader::LoadSAD(Session *sess, const QString & path)
|
||||
{
|
||||
EDFParser edf(path);
|
||||
ResMedEDFParser edf(path);
|
||||
if (!edf.Parse())
|
||||
return false;
|
||||
|
||||
@ -3043,7 +2773,7 @@ bool ResmedLoader::LoadSAD(Session *sess, const QString & path)
|
||||
|
||||
bool ResmedLoader::LoadPLD(Session *sess, const QString & path)
|
||||
{
|
||||
EDFParser edf(path);
|
||||
ResMedEDFParser edf(path);
|
||||
if (!edf.Parse())
|
||||
return false;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* SleepLib RESMED Loader Header
|
||||
/* SleepLib RESMED Loader Header
|
||||
*
|
||||
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||
*
|
||||
@ -14,6 +14,7 @@
|
||||
#include "SleepLib/machine.h" // Base class: MachineLoader
|
||||
#include "SleepLib/machine_loader.h"
|
||||
#include "SleepLib/profiles.h"
|
||||
#include "SleepLib/loader_plugins/edfparser.h"
|
||||
|
||||
//********************************************************************************************
|
||||
/// IMPORTANT!!!
|
||||
@ -30,75 +31,13 @@ EDFType lookupEDFType(QString text);
|
||||
|
||||
const QString resmed_class_name = STR_MACH_ResMed;
|
||||
|
||||
/*! \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 EDFHeader {
|
||||
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 BUILD_WITH_MSVC
|
||||
__attribute__((packed))
|
||||
#endif
|
||||
;
|
||||
class ResMedEDFParser:public EDFParser
|
||||
{
|
||||
public:
|
||||
ResMedEDFParser(QString filename = "");
|
||||
~ResMedEDFParser();
|
||||
EDFSignal *lookupSignal(ChannelID ch);
|
||||
|
||||
const int EDFHeaderSize = sizeof(EDFHeader);
|
||||
|
||||
/*! \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:
|
||||
//! \brief Name of this Signal
|
||||
QString label;
|
||||
|
||||
//! \brief Tranducer Type (source of the data, usually blank)
|
||||
QString transducer_type;
|
||||
|
||||
//! \brief The units of measurements represented by this signal
|
||||
QString physical_dimension;
|
||||
|
||||
//! \brief The minimum limits of the ungained data
|
||||
EventDataType physical_minimum;
|
||||
|
||||
//! \brief The maximum limits of the ungained data
|
||||
EventDataType physical_maximum;
|
||||
|
||||
//! \brief The minimum limits of the data with gain and offset applied
|
||||
EventDataType digital_minimum;
|
||||
|
||||
//! \brief The maximum limits of the data with gain and offset applied
|
||||
EventDataType digital_maximum;
|
||||
|
||||
//! \brief Raw integer data is multiplied by this value
|
||||
EventDataType gain;
|
||||
|
||||
//! \brief This value is added to the raw data
|
||||
EventDataType offset;
|
||||
|
||||
//! \brief Any prefiltering methods used (usually blank)
|
||||
QString prefiltering;
|
||||
|
||||
//! \brief Number of records
|
||||
long nr;
|
||||
|
||||
//! \brief Reserved (usually blank)
|
||||
QString reserved;
|
||||
|
||||
//! \brief Pointer to the signals sample data
|
||||
qint16 *data;
|
||||
|
||||
//! \brief a non-EDF extra used internally to count the signal data
|
||||
int pos;
|
||||
};
|
||||
|
||||
struct STRRecord
|
||||
@ -257,80 +196,6 @@ struct STRRecord
|
||||
};
|
||||
|
||||
|
||||
/*! \class EDFParser
|
||||
\author Mark Watkins <jedimark64_at_users.sourceforge.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 EDFParser
|
||||
{
|
||||
public:
|
||||
//! \brief Constructs an EDFParser object, opening the filename if one supplied
|
||||
EDFParser(QString filename = "");
|
||||
|
||||
~EDFParser();
|
||||
|
||||
//! \brief Open the EDF+ file, and read it's header
|
||||
bool Open(QString name);
|
||||
|
||||
//! \brief Read n bytes of 8 bit data from the EDF+ data stream
|
||||
QString Read(unsigned n);
|
||||
|
||||
//! \brief Read 16 bit word of data from the EDF+ data stream
|
||||
qint16 Read16();
|
||||
|
||||
//! \brief Vector containing the list of EDFSignals contained in this edf file
|
||||
QVector<EDFSignal> edfsignals;
|
||||
|
||||
//! \brief An by-name indexed into the EDFSignal data
|
||||
QStringList signal_labels;
|
||||
|
||||
//! \brief ResMed likes to use the SAME signal name
|
||||
QHash<QString, QList<EDFSignal *> > signalList;
|
||||
|
||||
QList<EDFSignal *> signal;
|
||||
|
||||
//! \brief Look up signal names by SleepLib ChannelID.. A little "ResMed"ified.. :/
|
||||
EDFSignal *lookupSignal(ChannelID);
|
||||
EDFSignal *lookupLabel(QString name, int index=0);
|
||||
|
||||
//! \brief Returns the number of signals contained in this EDF file
|
||||
long GetNumSignals() { return num_signals; }
|
||||
|
||||
//! \brief Returns the number of data records contained per signal.
|
||||
long GetNumDataRecords() { return num_data_records; }
|
||||
|
||||
//! \brief Returns the duration represented by this EDF file (in milliseconds)
|
||||
qint64 GetDuration() { return dur_data_record; }
|
||||
|
||||
//! \brief Returns the patientid field from the EDF header
|
||||
QString GetPatient() { return patientident; }
|
||||
|
||||
//! \brief Parse the EDF+ file into the list of EDFSignals.. Must be call Open(..) first.
|
||||
bool Parse();
|
||||
char *buffer;
|
||||
|
||||
//! \brief The EDF+ files header structure, used as a place holder while processing the text data.
|
||||
EDFHeader header;
|
||||
|
||||
QString filename;
|
||||
long filesize;
|
||||
long datasize;
|
||||
long pos;
|
||||
|
||||
long version;
|
||||
long num_header_bytes;
|
||||
long num_data_records;
|
||||
qint64 dur_data_record;
|
||||
long num_signals;
|
||||
|
||||
QString patientident;
|
||||
QString recordingident;
|
||||
QString serialnumber;
|
||||
qint64 startdate;
|
||||
qint64 enddate;
|
||||
QString reserved44;
|
||||
};
|
||||
|
||||
class ResmedLoader;
|
||||
|
||||
@ -414,7 +279,7 @@ class ResmedLoader : public CPAPLoader
|
||||
virtual const QString &loaderName() { return resmed_class_name; }
|
||||
|
||||
//! \brief Converts EDFSignal data to time delta packed EventList, and adds to Session
|
||||
void ToTimeDelta(Session *sess, EDFParser &edf, EDFSignal &es, ChannelID code, long recs,
|
||||
void ToTimeDelta(Session *sess, ResMedEDFParser &edf, EDFSignal &es, ChannelID code, long recs,
|
||||
qint64 duration, EventDataType min = 0, EventDataType max = 0, bool square = false);
|
||||
|
||||
//! \brief Register the ResmedLoader with the list of other machine loaders
|
||||
|
@ -163,7 +163,8 @@ SOURCES += \
|
||||
SleepLib/journal.cpp \
|
||||
SleepLib/progressdialog.cpp \
|
||||
SleepLib/loader_plugins/cms50f37_loader.cpp \
|
||||
profileselector.cpp
|
||||
profileselector.cpp \
|
||||
SleepLib/loader_plugins/edfparser.cpp
|
||||
|
||||
HEADERS += \
|
||||
common_gui.h \
|
||||
@ -227,7 +228,8 @@ HEADERS += \
|
||||
SleepLib/loader_plugins/cms50f37_loader.h \
|
||||
build_number.h \
|
||||
profileselector.h \
|
||||
SleepLib/appsettings.h
|
||||
SleepLib/appsettings.h \
|
||||
SleepLib/loader_plugins/edfparser.h
|
||||
|
||||
FORMS += \
|
||||
daily.ui \
|
||||
|
Loading…
Reference in New Issue
Block a user