Preliminary support for PrismaLine devices

This commit is contained in:
axt 2021-12-31 00:51:33 +01:00
parent 422e2cc38f
commit cd82571dc6
4 changed files with 243 additions and 91 deletions

View File

@ -96,6 +96,20 @@ bool EDFInfo::Open(const QString & name)
return true; return true;
} }
bool EDFInfo::Open(const QByteArray &data)
{
fileData = data;
if (fileData.size() <= EDFHeaderSize) {
fileData.clear();;
qDebug() << "EDFInfo::Open() buffer too short";
return false;
}
// TODO AXT
filename = "bytearray";
return true;
}
QDateTime EDFInfo::getStartDT( QString dateTimeStr ) QDateTime EDFInfo::getStartDT( QString dateTimeStr )
{ {
// edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss"); // edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss");
@ -146,14 +160,15 @@ bool EDFInfo::parseHeader( EDFHeaderRaw *hdrPtr )
} }
edfHdr.reserved44=QString::fromLatin1(hdrPtr->reserved, 44).trimmed(); edfHdr.reserved44=QString::fromLatin1(hdrPtr->reserved, 44).trimmed();
edfHdr.num_data_records = QString::fromLatin1(hdrPtr->num_data_records, 8).toLong(&ok); edfHdr.num_data_records = QString::fromLatin1(hdrPtr->num_data_records, 8).toLong(&ok);
if ( (! ok) || (edfHdr.num_data_records < 1) ) { // TODO AXT
#ifdef EDF_DEBUG // if ( (! ok) || (edfHdr.num_data_records < 1) ) {
qWarning() << "EDFInfo::Parse() Bad data record count " << filename; //#ifdef EDF_DEBUG
// sleep(1); // qWarning() << "EDFInfo::Parse() Bad data record count " << filename;
#endif // // sleep(1);
fileData.clear(); //#endif
return false; // fileData.clear();
} // return false;
// }
edfHdr.duration_Seconds = QString::fromLatin1(hdrPtr->dur_data_records, 8).toDouble(&ok); edfHdr.duration_Seconds = QString::fromLatin1(hdrPtr->dur_data_records, 8).toDouble(&ok);
if (!ok) { if (!ok) {
#ifdef EDF_DEBUG #ifdef EDF_DEBUG

View File

@ -126,6 +126,8 @@ class EDFInfo
virtual ~EDFInfo(); virtual ~EDFInfo();
virtual bool Open(const QString & name); //! \brief Open the EDF+ file, and read it's header virtual bool Open(const QString & name); //! \brief Open the EDF+ file, and read it's header
virtual bool Open(const QByteArray &data);
virtual bool Parse(); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first. virtual bool Parse(); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first.

View File

@ -13,6 +13,7 @@
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QBuffer> #include <QBuffer>
#include <QByteArray>
#include <QDataStream> #include <QDataStream>
#include <QMessageBox> #include <QMessageBox>
#include <QJsonDocument> #include <QJsonDocument>
@ -28,7 +29,13 @@
#include "SleepLib/calcs.h" #include "SleepLib/calcs.h"
#include "rawdata.h" #include "rawdata.h"
#define CONFIG_FILE "config.pscfg"
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include "../thirdparty/miniz.h"
#define PRISMA_SMART_CONFIG_FILE "config.pscfg"
#define PRISMA_LINE_CONFIG_FILE "config.pcfg"
#define PRISMA_LINE_THERAPY_FILE "therapy.pdat"
//******************************************************************************************** //********************************************************************************************
/// IMPORTANT!!! /// IMPORTANT!!!
@ -51,14 +58,18 @@ ChannelID PrismaLoader::CPAPModeChannel() { return Prisma_Mode; }
//******************************************************************************************** //********************************************************************************************
bool WMEDFInfo::ParseSignalData() { bool WMEDFInfo::ParseSignalData() {
// Now check the file isn't truncated before allocating space for the values int bytes = 0;
long allocsize = 0;
for (auto & sig : edfsignals) { for (auto & sig : edfsignals) {
if (edfHdr.num_data_records > 0) { if (sig.reserved == "#1") {
allocsize += sig.sampleCnt * edfHdr.num_data_records * 2; bytes += 1 * sig.sampleCnt;
}
else if (sig.reserved == "#2") {
bytes += 2 * sig.sampleCnt;
} }
} }
// allocate the arrays for the signal values // allocate the arrays for the signal values
edfHdr.num_data_records = (fileData.size() - edfHdr.num_header_bytes) / bytes;
for (auto & sig : edfsignals) { for (auto & sig : edfsignals) {
long samples = sig.sampleCnt * edfHdr.num_data_records; long samples = sig.sampleCnt * edfHdr.num_data_records;
if (edfHdr.num_data_records <= 0) { if (edfHdr.num_data_records <= 0) {
@ -115,19 +126,19 @@ quint8 WMEDFInfo::Read8U()
void PrismaImport::run() void PrismaImport::run()
{ {
qDebug() << "PRISMA IMPORT" << eventFileName << " " << signalFileName; qDebug() << "PRISMA IMPORT" << sessionid;
if (!wmedf.Open(signalFileName)) { if (!wmedf.Open(signalData)) {
qWarning() << "Signal file open failed" << signalFileName; qWarning() << "Signal file open failed";
return; return;
} }
if (!wmedf.Parse()) { if (!wmedf.Parse()) {
qWarning() << "Signal file parsing failed" << signalFileName; qWarning() << "Signal file parsing failed";
return; return;
} }
eventFile = new PrismaEventFile(eventFileName); eventFile = new PrismaEventFile(eventData);
startdate = qint64(wmedf.edfHdr.startdate_orig.toTime_t()) * 1000L; startdate = qint64(wmedf.edfHdr.startdate_orig.toTime_t()) * 1000L;
enddate = startdate + wmedf.GetDuration() * qint64(wmedf.GetNumDataRecords()) * 1000; enddate = startdate + wmedf.GetDuration() * qint64(wmedf.GetNumDataRecords()) * 1000;
@ -178,17 +189,45 @@ void PrismaImport::run()
session->settings[Prisma_PMaxOA] = parameters[PRISMA_PMAXOA] / 100; session->settings[Prisma_PMaxOA] = parameters[PRISMA_PMAXOA] / 100;
// add waveforms // add waveforms
AddWaveform(CPAP_Pressure, QString("CPAPPressure")); // common
AddWaveform(CPAP_EPAP, QString("EPAP"));
AddWaveform(CPAP_MaskPressure, QString("Pressure")); AddWaveform(CPAP_MaskPressure, QString("Pressure"));
AddWaveform(CPAP_FlowRate, QString("RespFlow")); AddWaveform(CPAP_FlowRate, QString("RespFlow"));
AddWaveform(CPAP_Leak, QString("LeakFlowBreath")); AddWaveform(CPAP_Leak, QString("LeakFlowBreath"));
AddWaveform(Prisma_ObstructLevel, QString("ObstructLevel")); AddWaveform(Prisma_ObstructLevel, QString("ObstructLevel"));
AddWaveform(Prisma_SPRStatus, QString("SPRStatus"));
//prisma smart
// AddWaveform(CPAP_Pressure, QString("CPAPPressure"));
AddWaveform(CPAP_EPAP, QString("EPAP"));
AddWaveform(CPAP_IPAP, QString("IPAP"));
AddWaveform(Prisma_rMVFluctuation, QString("rMVFluctuation")); AddWaveform(Prisma_rMVFluctuation, QString("rMVFluctuation"));
AddWaveform(Prisma_rRMV, QString("rRMV")); AddWaveform(Prisma_rRMV, QString("rRMV"));
AddWaveform(Prisma_PressureMeasured, QString("PressureMeasured")); AddWaveform(Prisma_PressureMeasured, QString("PressureMeasured"));
AddWaveform(Prisma_FlowFull, QString("FlowFull")); AddWaveform(Prisma_FlowFull, QString("FlowFull"));
AddWaveform(Prisma_SPRStatus, QString("SPRStatus"));
// prisma line
AddWaveform(CPAP_EPAP, QString("EPAPsoll"));
AddWaveform(CPAP_IPAP, QString("IPAPsoll"));
// AddWaveform(CPAP_EEPAP, QString("EEPAPsoll"));
// AddWaveform(CPAP_RespRate, "BreathFrequency");
// AddWaveform(CPAP_TidalVolume, "BreathVolume");
// AddWaveform(CPAP_IE, "InspExpirRel");
// AddWaveform(OXI_Pulse, "HeartFrequency");
// AddWaveform(OXI_SPO2, "SpO2");
// AddWaveform(CPAP_LeakTotal, QString("TotalLeakage"));
// AddWaveform(CPAP_Test1, QString("RSBI"));
// 20A, 25ST
// MV.txt
// rAMV.txt
// 25S:
// rMVFluctuation.txt
// RSBI.txt
// TotalLeakage.txt
// add signals // add signals
AddEvents(CPAP_Obstructive, PRISMA_EVENT_OBSTRUCTIVE_APNEA); AddEvents(CPAP_Obstructive, PRISMA_EVENT_OBSTRUCTIVE_APNEA);
@ -245,17 +284,11 @@ void PrismaImport::AddEvents(ChannelID channel, QList<Prisma_Event_Type> eventTy
} }
//******************************************************************************************** //********************************************************************************************
PrismaEventFile::PrismaEventFile(QString fname) PrismaEventFile::PrismaEventFile(QByteArray &buffer) {
{
QFile file(fname);
QDomDocument dom; QDomDocument dom;
dom.setContent(buffer);
if(file.open(QIODevice::ReadOnly)) {
dom.setContent(&file);
file.close();
QDomElement root = dom.documentElement(); QDomElement root = dom.documentElement();
QDomNodeList deviceEventNodelist = root.elementsByTagName("DeviceEvent"); QDomNodeList deviceEventNodelist = root.elementsByTagName("DeviceEvent");
for(int i=0; i < deviceEventNodelist.count(); i++) for(int i=0; i < deviceEventNodelist.count(); i++)
{ {
@ -280,8 +313,8 @@ PrismaEventFile::PrismaEventFile(QString fname)
int strength = node.attribute("Strength").toInt(); int strength = node.attribute("Strength").toInt();
m_events[eventId].append(PrismaEvent(endTime, duration, pressure, strength)); m_events[eventId].append(PrismaEvent(endTime, duration, pressure, strength));
} }
qDebug() << "SIZE" << m_events.size();
} }
};
//******************************************************************************************** //********************************************************************************************
@ -336,8 +369,9 @@ PrismaLoader::~PrismaLoader()
bool PrismaLoader::Detect(const QString & selectedPath) bool PrismaLoader::Detect(const QString & selectedPath)
{ {
QFile configFile(selectedPath + QDir::separator() + CONFIG_FILE); QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE);
return configFile.exists(); QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE);
return prismaSmartConfigFile.exists() || prismaLineConfigFile.exists();
} }
int PrismaLoader::Open(const QString & selectedPath) int PrismaLoader::Open(const QString & selectedPath)
@ -350,27 +384,15 @@ int PrismaLoader::Open(const QString & selectedPath)
qDebug() << "Prisma opening" << selectedPath; qDebug() << "Prisma opening" << selectedPath;
QString configFilePath = selectedPath + QDir::separator() + CONFIG_FILE;
QFile configFile(configFilePath);
if (!configFile.exists()) // TODO AXT || !configFile.isReadable() fails
{
qDebug() << "Prisma config file error" << configFile << " " << configFile.exists() << " " << configFile.isReadable();
return 0;
}
m_abort = false; QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE);
emit setProgressValue(0); QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE);
emit updateMessage(QObject::tr("Getting Ready..."));
QCoreApplication::processEvents();
MachineInfo info = PeekInfoFromConfig(configFilePath);
qDebug() << "Prisma machine info" << info.serial;
MachineInfo info = PeekInfoFromConfig(selectedPath);
if (info.type == MT_UNKNOWN) { if (info.type == MT_UNKNOWN) {
emit deviceIsUnsupported(info); emit deviceIsUnsupported(info);
return -1; return -1;
} }
m_ctx->CreateMachineFromInfo(info); m_ctx->CreateMachineFromInfo(info);
if (!s_PrismaModelInfo.IsTested(info.modelnumber)) { if (!s_PrismaModelInfo.IsTested(info.modelnumber)) {
@ -378,6 +400,11 @@ int PrismaLoader::Open(const QString & selectedPath)
emit deviceIsUntested(info); emit deviceIsUntested(info);
} }
m_abort = false;
emit setProgressValue(0);
emit updateMessage(QObject::tr("Getting Ready..."));
QCoreApplication::processEvents();
emit updateMessage(QObject::tr("Backing Up Files...")); emit updateMessage(QObject::tr("Backing Up Files..."));
QCoreApplication::processEvents(); QCoreApplication::processEvents();
@ -390,6 +417,9 @@ int PrismaLoader::Open(const QString & selectedPath)
emit updateMessage(QObject::tr("Scanning Files...")); emit updateMessage(QObject::tr("Scanning Files..."));
QCoreApplication::processEvents(); QCoreApplication::processEvents();
if (prismaSmartConfigFile.exists()) // TODO AXT || !configFile.isReadable() fails
{
// TODO AXT extract // TODO AXT extract
char out[12]; char out[12];
int serialInDecimal; int serialInDecimal;
@ -397,9 +427,83 @@ int PrismaLoader::Open(const QString & selectedPath)
snprintf(out, 12, "%010d", serialInDecimal); snprintf(out, 12, "%010d", serialInDecimal);
ScanFiles(info, selectedPath + QDir::separator() + out); ScanFiles(info, selectedPath + QDir::separator() + out);
}
else if (prismaLineConfigFile.exists())
{
QSet<SessionID> sessions;
QHash<SessionID, QString> eventFiles;
QHash<SessionID, QString> signalFiles;
QFile prismaLineTherapyFile(selectedPath + QDir::separator() + PRISMA_LINE_THERAPY_FILE);
if (!prismaLineTherapyFile.exists()) { // TODO AXT || !configFile.isReadable() fails
qDebug() << "Prisma line therapy file error" << prismaLineTherapyFile;
return 0;
}
if (!prismaLineTherapyFile.open(QIODevice::ReadOnly)) {
qDebug() << "Prisma line therapy file not readable" << prismaLineTherapyFile;
return 0;
}
QByteArray therapyData = prismaLineTherapyFile.readAll();
prismaLineTherapyFile.close();
mz_bool status;
mz_zip_archive zip_archive;
mz_zip_archive_file_stat file_stat;
memset(&zip_archive, 0, sizeof(zip_archive));
status = mz_zip_reader_init_mem(&zip_archive, (const void*)therapyData.constData(), therapyData.size(), 0);
if (!status)
{
qDebug() << "mz_zip_reader_init_file() failed!";
return 0;
}
int n = mz_zip_reader_get_num_files(&zip_archive);
for (int i = 0; i < n; ++i) {
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) {
qDebug() << "mz_zip_reader_file_stat() failed!";
mz_zip_reader_end(&zip_archive);
return 0;
}
qDebug() << file_stat.m_filename;
QString fileName(file_stat.m_filename);
if (fileName.contains("event_") && fileName.endsWith(".xml")) {
SessionID sid = fileName.mid(fileName.size()-4-6,6).toLong();
sessions += sid;
eventFiles[sid] = fileName;
}
if (fileName.contains("signal_") && fileName.endsWith(".wmedf")) {
SessionID sid = fileName.mid(fileName.size()-6-6,6).toLong();
sessions += sid;
signalFiles[sid] = fileName;
}
}
qDebug() << sessions;
for(auto & sid : sessions) {
size_t uncomp_size_events;
void *extract_events = mz_zip_reader_extract_file_to_heap(&zip_archive, eventFiles[sid].toLocal8Bit(), &uncomp_size_events, 0);
QByteArray eventData((const char*)extract_events, uncomp_size_events);
free(extract_events);
size_t uncomp_size_signals;
void *extract_signals = mz_zip_reader_extract_file_to_heap(&zip_archive, signalFiles[sid].toLocal8Bit(), &uncomp_size_signals, 0);
QByteArray signalData((const char*)extract_signals, uncomp_size_signals);
free(extract_signals);
queTask(new PrismaImport(this, info, sid, eventData, signalData));
}
mz_zip_reader_end(&zip_archive);
} else {
qDebug() << "Prisma config file error" << selectedPath;
return 0;
}
int tasks = countTasks(); int tasks = countTasks();
qDebug() << "Task count " << tasks;
emit updateMessage(QObject::tr("Importing Sessions...")); emit updateMessage(QObject::tr("Importing Sessions..."));
QCoreApplication::processEvents(); QCoreApplication::processEvents();
@ -416,20 +520,22 @@ MachineInfo PrismaLoader::PeekInfo(const QString & selectedPath)
if (!Detect(selectedPath)) if (!Detect(selectedPath))
return MachineInfo(); return MachineInfo();
return PeekInfoFromConfig(selectedPath + QDir::separator() + CONFIG_FILE); return PeekInfoFromConfig(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE);
} }
MachineInfo PrismaLoader::PeekInfoFromConfig(const QString & configFilePath) MachineInfo PrismaLoader::PeekInfoFromConfig(const QString & selectedPath)
{ {
QFile configFile(configFilePath); QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE);
QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE);
if (configFile.exists()) { // TODO AXT, extract into ConfigFile class
if (!configFile.open(QIODevice::ReadOnly)) { if (prismaSmartConfigFile.exists()) {
if (!prismaSmartConfigFile.open(QIODevice::ReadOnly)) {
return MachineInfo(); return MachineInfo();
} }
MachineInfo info = newInfo(); MachineInfo info = newInfo();
QByteArray configData = configFile.readAll(); QByteArray configData = prismaSmartConfigFile.readAll();
configFile.close(); prismaSmartConfigFile.close();
QJsonDocument configDoc(QJsonDocument::fromJson(configData)); QJsonDocument configDoc(QJsonDocument::fromJson(configData));
QJsonObject configObj = configDoc.object(); QJsonObject configObj = configDoc.object();
@ -440,10 +546,24 @@ MachineInfo PrismaLoader::PeekInfoFromConfig(const QString & configFilePath)
// TODO AXT load props // TODO AXT load props
info.properties["cica"] = "mica"; info.properties["cica"] = "mica";
return info; return info;
} else if (prismaLineConfigFile.exists()) {
// TODO AXT add loader
if (!prismaLineConfigFile.open(QIODevice::ReadOnly)) {
return MachineInfo();
}
MachineInfo info = newInfo();
prismaLineConfigFile.close();
info.modelnumber=42;
info.model = "Unknown PrismaLine";
info.serial = "0x42424242";
// TODO AXT load props
info.properties["cica"] = "mica";
return info;
} }
return MachineInfo(); return MachineInfo();
} }
// TODO AXT PrismaSmart specific, extract it into a parser class with the config files
void PrismaLoader::ScanFiles(const MachineInfo& info, const QString & machinePath) void PrismaLoader::ScanFiles(const MachineInfo& info, const QString & machinePath)
{ {
Q_ASSERT(m_ctx); Q_ASSERT(m_ctx);
@ -500,7 +620,22 @@ void PrismaLoader::ScanFiles(const MachineInfo& info, const QString & machinePat
} }
for(auto & sid : sessions) { for(auto & sid : sessions) {
queTask(new PrismaImport(this, info, sid, eventFiles[sid], signalFiles[sid])); QByteArray eventData;
QByteArray signalData;
QFile efile(eventFiles[sid]);
if(efile.open(QIODevice::ReadOnly)) {
eventData = efile.readAll();
efile.close();
}
QFile sfile(signalFiles[sid]);
if(sfile.open(QIODevice::ReadOnly)) {
signalData = sfile.readAll();
sfile.close();
}
queTask(new PrismaImport(this, info, sid, eventData, signalData));
} }
} }

View File

@ -111,7 +111,7 @@ class PrismaEventFile;
class PrismaImport:public ImportTask class PrismaImport:public ImportTask
{ {
public: public:
PrismaImport(PrismaLoader * l, const MachineInfo& m, SessionID s, QString e, QString d): loader(l), machineInfo(m), sessionid(s), eventFileName(e), signalFileName(d) {} PrismaImport(PrismaLoader * l, const MachineInfo& m, SessionID s, QByteArray e, QByteArray d): loader(l), machineInfo(m), sessionid(s), eventData(e), signalData(d) {}
virtual ~PrismaImport() {}; virtual ~PrismaImport() {};
//! \brief PrismaImport thread starts execution here. //! \brief PrismaImport thread starts execution here.
@ -121,8 +121,8 @@ protected:
PrismaLoader * loader; PrismaLoader * loader;
const MachineInfo & machineInfo; const MachineInfo & machineInfo;
SessionID sessionid; SessionID sessionid;
QString eventFileName; QByteArray eventData;
QString signalFileName; QByteArray signalData;
qint64 startdate; qint64 startdate;
qint64 enddate; qint64 enddate;
WMEDFInfo wmedf; WMEDFInfo wmedf;
@ -186,7 +186,7 @@ class PrismaLoader : public CPAPLoader
protected: protected:
MachineInfo PeekInfoFromConfig(const QString & configPath); MachineInfo PeekInfoFromConfig(const QString & selectedPath);
//! \brief Scans the given directories for session data and create an import task for each logical session. //! \brief Scans the given directories for session data and create an import task for each logical session.
void ScanFiles(const MachineInfo& info, const QString & path); void ScanFiles(const MachineInfo& info, const QString & path);
@ -210,7 +210,7 @@ protected:
class PrismaEventFile class PrismaEventFile
{ {
public: public:
PrismaEventFile(QString fname); PrismaEventFile(QByteArray &buffer);
QHash<int, int> getParameters() {return m_parameters; } QHash<int, int> getParameters() {return m_parameters; }
QList<PrismaEvent> getEvents(int eventId) {return m_events.contains(eventId) ? m_events[eventId] : QList<PrismaEvent>(); } QList<PrismaEvent> getEvents(int eventId) {return m_events.contains(eventId) ? m_events[eventId] : QList<PrismaEvent>(); }