Better ResMed Session sorting, Parse EVE's differently

This commit is contained in:
Mark Watkins 2014-09-01 14:49:05 +10:00
parent defb171f4b
commit 504aa231f1
12 changed files with 721 additions and 122 deletions

View File

@ -382,6 +382,8 @@ class gGraphView
//! \brief Returns true if all Graph objects contain NO day data. ie, graph area is completely empty. //! \brief Returns true if all Graph objects contain NO day data. ie, graph area is completely empty.
bool isEmpty(); bool isEmpty();
Day * day() { return m_day; }
//! \brief Tell all graphs to deslect any highlighted areas //! \brief Tell all graphs to deslect any highlighted areas
void deselect(); void deselect();

View File

@ -420,6 +420,7 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
maxx = w.max_x, minx = w.min_x; maxx = w.max_x, minx = w.min_x;
} }
// hmmm.. subtract_offset.. // hmmm.. subtract_offset..
EventDataType miny = m_physminy; EventDataType miny = m_physminy;
@ -428,6 +429,7 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
w.roundY(miny, maxy); w.roundY(miny, maxy);
//#define DEBUG_AUTOSCALER //#define DEBUG_AUTOSCALER
#ifdef DEBUG_AUTOSCALER #ifdef DEBUG_AUTOSCALER
QString a = QString().sprintf("%.2f - %.2f",miny, maxy); QString a = QString().sprintf("%.2f - %.2f",miny, maxy);

View File

@ -437,6 +437,8 @@ void SummaryChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
int days = ceil(double(maxx-minx) / 86400000.0); int days = ceil(double(maxx-minx) / 86400000.0);
bool buttuglydaysteps = !p_profile->appearance->animations();
double lcursor = w.graphView()->currentTime(); double lcursor = w.graphView()->currentTime();
if (days >= 1) { if (days >= 1) {
@ -444,10 +446,13 @@ void SummaryChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
double a = lcursor - w.min_x; double a = lcursor - w.min_x;
double c = a / b; double c = a / b;
minx = floor(double(minx)/86400000.0); if (buttuglydaysteps) {
minx *= 86400000L; // this kills the beautiful smooth scrolling and makes days stop on day boundaries :(
minx = floor(double(minx)/86400000.0);
minx *= 86400000L;
maxx = minx + 86400000L * qint64(days)-1; maxx = minx + 86400000L * qint64(days)-1;
}
b = maxx - minx; b = maxx - minx;
double d = c * b; double d = c * b;

View File

@ -6,13 +6,13 @@
* License. See the file COPYING in the main directory of the Linux * License. See the file COPYING in the main directory of the Linux
* distribution for more details. */ * distribution for more details. */
#include "Graphs/gXAxis.h"
#include <QDebug> #include <QDebug>
#include <QFontMetrics> #include <QFontMetrics>
#include <math.h> #include <math.h>
#include "Graphs/gXAxis.h"
#include "SleepLib/profiles.h"
#include "Graphs/glcommon.h" #include "Graphs/glcommon.h"
#include "Graphs/gGraph.h" #include "Graphs/gGraph.h"
#include "Graphs/gGraphView.h" #include "Graphs/gGraphView.h"
@ -121,11 +121,14 @@ void gXAxis::paint(QPainter &painter, gGraph &w, const QRegion &region)
int days = ceil(double(maxx-minx) / 86400000.0); int days = ceil(double(maxx-minx) / 86400000.0);
if (m_roundDays && (days >= 1)) { bool buttuglydaysteps = !p_profile->appearance->animations();
minx = floor(double(minx)/86400000.0); if (buttuglydaysteps) {
minx *= 86400000L; if (m_roundDays && (days >= 1)) {
minx = floor(double(minx)/86400000.0);
minx *= 86400000L;
maxx = minx + 86400000L * qint64(days); maxx = minx + 86400000L * qint64(days);
}
} }

View File

@ -460,6 +460,157 @@ EventDataType Day::p90(ChannelID code)
return percentile(code, 0.90F); return percentile(code, 0.90F);
} }
EventDataType Day::rangeCount(ChannelID code, qint64 st, qint64 et)
{
int cnt = 0;
QList<Session *>::iterator end = sessions.end();
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it);
if (sess.enabled()) {
cnt += sess.rangeCount(code, st, et);
}
}
return cnt;
}
EventDataType Day::rangeSum(ChannelID code, qint64 st, qint64 et)
{
double val = 0;
QList<Session *>::iterator end = sessions.end();
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it);
if (sess.enabled()) {
val += sess.rangeSum(code, st, et);
}
}
return val;
}
EventDataType Day::rangeAvg(ChannelID code, qint64 st, qint64 et)
{
double val = 0;
int cnt = 0;
QList<Session *>::iterator end = sessions.end();
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it);
if (sess.enabled()) {
val += sess.rangeSum(code, st, et);
cnt += sess.rangeCount(code, st,et);
}
}
if (cnt == 0) { return 0; }
val /= double(cnt);
return val;
}
EventDataType Day::rangeWavg(ChannelID code, qint64 st, qint64 et)
{
double sum = 0;
double cnt = 0;
QList<Session *>::iterator end = sessions.end();
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it);
QHash<ChannelID, QVector<EventList *> >::iterator EVEC = sess.eventlist.find(code);
if (EVEC == sess.eventlist.end()) continue;
QVector<EventList *>::iterator EL;
QVector<EventList *>::iterator EVEC_end = EVEC.value().end();
for (EL = EVEC.value().begin(); EL != EVEC_end; ++EL) {
EventList * el = *EL;
if (el->count() < 1) continue;
EventDataType lastdata = el->data(0);
qint64 lasttime = el->time(0);
if (lasttime < st)
lasttime = st;
for (unsigned i=1; i<el->count(); i++) {
double data = el->data(i);
qint64 time = el->time(i);
if (time < st) {
lasttime = st;
lastdata = data;
continue;
}
if (time > et) {
time = et;
}
double duration = double(time - lasttime) / 1000.0;
sum += data * duration;
cnt += duration;
if (time >= et) break;
lasttime = time;
lastdata = data;
}
}
}
if (cnt < 0.000001)
return 0;
return sum / cnt;
}
// Boring non weighted percentile
EventDataType Day::rangePercentile(ChannelID code, float p, qint64 st, qint64 et)
{
int count = rangeCount(code, st,et);
QVector<EventDataType> list;
list.resize(count);
int idx = 0;
QList<Session *>::iterator end = sessions.end();
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it);
QHash<ChannelID, QVector<EventList *> >::iterator EVEC = sess.eventlist.find(code);
if (EVEC == sess.eventlist.end()) continue;
QVector<EventList *>::iterator EL;
QVector<EventList *>::iterator EVEC_end = EVEC.value().end();
for (EL = EVEC.value().begin(); EL != EVEC_end; ++EL) {
EventList * el = *EL;
for (unsigned i=0; i<el->count(); i++) {
qint64 time = el->time(i);
if ((time < st) || (time > et)) continue;
list[idx++] = el->data(i);
}
}
}
// TODO: use nth_element instead..
qSort(list);
float b = float(idx) * p;
int a = floor(b);
int c = ceil(b);
if ((a == c) || (c >= idx)) {
return list[a];
}
EventDataType v1 = list[a];
EventDataType v2 = list[c];
EventDataType diff = v2 - v1; // the whole == C-A
double ba = b - float(a); // A....B...........C == B-A
double val = v1 + diff * ba;
return val;
}
EventDataType Day::avg(ChannelID code) EventDataType Day::avg(ChannelID code)
{ {
double val = 0; double val = 0;
@ -470,15 +621,16 @@ EventDataType Day::avg(ChannelID code)
for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) { for (QList<Session *>::iterator it = sessions.begin(); it != end; ++it) {
Session &sess = *(*it); Session &sess = *(*it);
if (sess.enabled() && sess.m_avg.contains(code)) { if (sess.enabled()) {
val += sess.avg(code); val += sess.sum(code);
cnt++; // hmm.. averaging averages doesn't feel right.. cnt += sess.count(code);
} }
} }
if (cnt == 0) { return 0; } if (cnt == 0) { return 0; }
val /= double(cnt);
return EventDataType(val / float(cnt)); return val;
} }
EventDataType Day::sum(ChannelID code) EventDataType Day::sum(ChannelID code)
@ -1234,25 +1386,36 @@ bool Day::removeSession(Session *sess)
QString Day::getCPAPMode() QString Day::getCPAPMode()
{ {
Q_ASSERT(machine(MT_CPAP) != nullptr); Machine * mach = machine(MT_CPAP);
if (!mach) return STR_MessageBox_Error;
CPAPMode mode = (CPAPMode)(int)qRound(settings_wavg(CPAP_Mode)); CPAPLoader * loader = qobject_cast<CPAPLoader *>(mach->loader());
if (mode == MODE_CPAP) {
return QObject::tr("Fixed"); ChannelID modechan = loader->CPAPModeChannel();
} else if (mode == MODE_APAP) {
return QObject::tr("Auto"); schema::Channel & chan = schema::channel[modechan];
} else if (mode == MODE_BILEVEL_FIXED ) {
return QObject::tr("Fixed Bi-Level"); int mode = (CPAPMode)(int)qRound(settings_wavg(modechan));
} else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) {
return QObject::tr("Auto Bi-Level (Fixed PS)"); return chan.option(mode);
} else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) {
return QObject::tr("Auto Bi-Level (Variable PS)");
} else if (mode == MODE_ASV) { // if (mode == MODE_CPAP) {
return QObject::tr("ASV Fixed EPAP"); // return QObject::tr("Fixed");
} else if (mode == MODE_ASV_VARIABLE_EPAP) { // } else if (mode == MODE_APAP) {
return QObject::tr("ASV Variable EPAP"); // return QObject::tr("Auto");
} // } else if (mode == MODE_BILEVEL_FIXED ) {
return STR_TR_Unknown; // return QObject::tr("Fixed Bi-Level");
// } else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) {
// return QObject::tr("Auto Bi-Level (Fixed PS)");
// } else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) {
// return QObject::tr("Auto Bi-Level (Variable PS)");
// } else if (mode == MODE_ASV) {
// return QObject::tr("ASV Fixed EPAP");
// } else if (mode == MODE_ASV_VARIABLE_EPAP) {
// return QObject::tr("ASV Variable EPAP");
// }
// return STR_TR_Unknown;
} }
QString Day::getPressureRelief() QString Day::getPressureRelief()

View File

@ -46,6 +46,12 @@ class Day
//! \brief Add Session to this Day object (called during Load) //! \brief Add Session to this Day object (called during Load)
void addSession(Session *s); void addSession(Session *s);
EventDataType rangeCount(ChannelID code, qint64 st, qint64 et);
EventDataType rangeSum(ChannelID code, qint64 st, qint64 et);
EventDataType rangeAvg(ChannelID code, qint64 st, qint64 et);
EventDataType rangeWavg(ChannelID code, qint64 st, qint64 et);
EventDataType rangePercentile(ChannelID code, float p, qint64 st, qint64 et);
//! \brief Returns the count of all this days sessions' events for this day //! \brief Returns the count of all this days sessions' events for this day
EventDataType count(ChannelID code); EventDataType count(ChannelID code);

View File

@ -31,7 +31,7 @@ QHash<QString, QList<quint16> > Resmed_Model_Map;
const QString STR_UnknownModel = "Resmed S9 ???"; const QString STR_UnknownModel = "Resmed S9 ???";
ChannelID RMS9_EPR, RMS9_EPRLevel; ChannelID RMS9_EPR, RMS9_EPRLevel, RMS9_Mode;
// Return the model name matching the supplied model number. // Return the model name matching the supplied model number.
@ -226,8 +226,11 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
if ((sig = str.lookupSignal(CPAP_Mode))) { if ((sig = str.lookupSignal(CPAP_Mode))) {
int mod = EventDataType(sig->data[rec]) * sig->gain + sig->offset; int mod = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
R.rms9_mode = mod;
if (mod >= 8) { // mod 8 == vpap adapt variable epap if (mod == 11) {
mode = MODE_APAP;
} else if (mod >= 8) { // mod 8 == vpap adapt variable epap
mode = MODE_ASV_VARIABLE_EPAP; mode = MODE_ASV_VARIABLE_EPAP;
} else if (mod >= 7) { // mod 7 == vpap adapt } else if (mod >= 7) { // mod 7 == vpap adapt
mode = MODE_ASV; mode = MODE_ASV;
@ -245,6 +248,12 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
} }
R.mode = mode; R.mode = mode;
if ((mod == 0) && (sig = str.lookupLabel("S.C.StartPress"))) {
R.ramp_pressure = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if (((mod == 1) || (mod == 11)) && (sig = str.lookupLabel("S.AS.StartPress"))) {
R.ramp_pressure = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
} }
@ -318,16 +327,15 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
if (!haveipap) { if (!haveipap) {
R.ipap = R.min_ipap = R.max_ipap = R.max_epap + R.max_ps;
} }
// if (mode == MODE_ASV_VARIABLE_EPAP) { if (mode == MODE_ASV_VARIABLE_EPAP) {
// // ResMed reuses this code on 36037.. the dummies :( R.min_ipap = R.min_epap + R.min_ps;
// } else if (mode == MODE_ASV) { R.max_ipap = R.max_epap + R.max_ps;
// if (!haveipap) { } else if (mode == MODE_ASV) {
// R.ipap = R.min_ipap = R.max_ipap = R.max_epap + R.max_ps; R.min_ipap = R.epap + R.min_ps;
// } R.max_ipap = R.epap + R.max_ps;
// } }
EventDataType epr = -1, epr_level = -1; EventDataType epr = -1, epr_level = -1;
if ((sig = str.lookupSignal(RMS9_EPR))) { if ((sig = str.lookupSignal(RMS9_EPR))) {
@ -356,6 +364,8 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
} }
} }
if ((sig = str.lookupLabel("AHI"))) { if ((sig = str.lookupLabel("AHI"))) {
R.ahi = EventDataType(sig->data[rec]) * sig->gain + sig->offset; R.ahi = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
} }
@ -372,6 +382,53 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
R.cai = EventDataType(sig->data[rec]) * sig->gain + sig->offset; R.cai = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
} }
if ((sig = str.lookupLabel("S.RampTime"))) {
R.s_RampTime = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.RampEnable"))) {
R.s_RampEnable = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.EPR.ClinEnable"))) {
R.s_EPR_ClinEnable = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.EPR.EPREnable"))) {
R.s_EPREnable = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.ABFilter"))) {
R.s_ABFilter = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.ClimateControl"))) {
R.s_ClimateControl = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.Mask"))) {
R.s_Mask = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.PtAccess"))) {
R.s_PtAccess = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.SmartStart"))) {
R.s_SmartStart = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.HumEnable"))) {
R.s_HumEnable = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.HumLevel"))) {
R.s_HumLevel = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.TempEnable"))) {
R.s_TempEnable = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.Temp"))) {
R.s_Temp = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.Tube"))) {
R.s_Tube = EventDataType(sig->data[rec]) * sig->gain + sig->offset;
}
laston = ontime; laston = ontime;
QDateTime dontime = QDateTime::fromTime_t(ontime); QDateTime dontime = QDateTime::fromTime_t(ontime);
@ -384,6 +441,8 @@ void ResmedLoader::ParseSTR(Machine *mach, QStringList strfiles)
//QDateTime dofftime = QDateTime::fromTime_t(offtime); //QDateTime dofftime = QDateTime::fromTime_t(offtime);
//qDebug() << "Mask on" << dontime << "Mask off" << dofftime; //qDebug() << "Mask on" << dontime << "Mask off" << dofftime;
} }
// Wait... ResMed has a DST bug here...should I be replicating it by using multiples of 86400 seconds?
dt = dt.addDays(1); dt = dt.addDays(1);
} }
} }
@ -675,18 +734,36 @@ void ResmedImport::run()
} }
loader->saveMutex.unlock(); loader->saveMutex.unlock();
if (!group.EVE.isEmpty()) { Q_FOREACH(QString file, files[EDF_PLD]) {
loader->LoadEVE(sess, group.EVE); loader->LoadPLD(sess, file);
#ifdef SESSION_DEBUG
sess->session_files.append(file);
#endif
} }
if (!group.BRP.isEmpty()) { Q_FOREACH(QString file, files[EDF_BRP]) {
loader->LoadBRP(sess, group.BRP); loader->LoadBRP(sess, file);
#ifdef SESSION_DEBUG
sess->session_files.append(file);
#endif
} }
if (!group.PLD.isEmpty()) { Q_FOREACH(QString file, files[EDF_SAD]) {
loader->LoadPLD(sess, group.PLD); loader->LoadSAD(sess, file);
#ifdef SESSION_DEBUG
sess->session_files.append(file);
#endif
} }
if (!group.SAD.isEmpty()) {
loader->LoadSAD(sess, group.SAD); // Load annotations afterwards so durations are set correctly
Q_FOREACH(QString file, files[EDF_CSL]) {
// loader->LoadCSL(sess, file);
} }
Q_FOREACH(QString file, files[EDF_EVE]) {
loader->LoadEVE(sess, file);
#ifdef SESSION_DEBUG
sess->session_files.append(file);
#endif
}
if (sess->first() == 0) { if (sess->first() == 0) {
// loader->saveMutex.lock(); // loader->saveMutex.lock();
@ -745,8 +822,13 @@ void ResmedImport::run()
// Save maskon time in session setting so we can use it later to avoid doubleups. // Save maskon time in session setting so we can use it later to avoid doubleups.
sess->settings[RMS9_MaskOnTime] = R.maskon; sess->settings[RMS9_MaskOnTime] = R.maskon;
#ifdef SESSION_DEBUG
sess->session_files.append("STR.edf");
#endif
if (R.mode >= 0) { if (R.mode >= 0) {
sess->settings[CPAP_Mode] = R.mode; sess->settings[CPAP_Mode] = R.mode;
sess->settings[RMS9_Mode] = R.rms9_mode;
if (R.mode == MODE_CPAP) { if (R.mode == MODE_CPAP) {
if (R.set_pressure >= 0) { if (R.set_pressure >= 0) {
sess->settings[CPAP_Pressure] = R.set_pressure; sess->settings[CPAP_Pressure] = R.set_pressure;
@ -832,21 +914,27 @@ ResmedLoader::~ResmedLoader()
void ResmedImportStage2::run() void ResmedImportStage2::run()
{ {
if (R.maskon == R.maskoff) return;
Session * sess = new Session(mach, R.maskon); Session * sess = new Session(mach, R.maskon);
sess->really_set_first(qint64(R.maskon) * 1000L); sess->really_set_first(qint64(R.maskon) * 1000L);
sess->really_set_last(qint64(R.maskoff) * 1000L); sess->really_set_last(qint64(R.maskoff) * 1000L);
// Claim this record for future imports // Claim this record for future imports
sess->settings[RMS9_MaskOnTime] = R.maskon; sess->settings[RMS9_MaskOnTime] = R.maskon;
sess->setSummaryOnly(true); sess->setSummaryOnly(true);
#ifdef SESSION_DEBUG
sess->session_files.append("STR.edf");
#endif
sess->SetChanged(true); sess->SetChanged(true);
// First take the settings // First take the settings
if (R.mode >= 0) { if (R.mode >= 0) {
sess->settings[CPAP_Mode] = R.mode; sess->settings[CPAP_Mode] = R.mode;
sess->settings[RMS9_Mode] = R.rms9_mode;
if (R.mode == MODE_CPAP) { if (R.mode == MODE_CPAP) {
if (R.set_pressure >= 0) { if (R.set_pressure >= 0) {
sess->settings[CPAP_Pressure] = R.set_pressure; sess->settings[CPAP_Pressure] = R.set_pressure;
@ -1079,9 +1167,143 @@ EDFType lookupEDFType(QString text)
} else return EDF_UNKNOWN; } else return EDF_UNKNOWN;
} }
// Pretend to parse the EVE file to get the duration out of it.
int PeekEVE(const QString & path, quint32 &start, quint32 &end)
{
EDFParser edf(path);
if (!edf.Parse())
return -1;
QString t;
double duration;
char *data;
char c;
long pos;
bool sign, ok;
double d;
double tt;
int recs = 0;
int goodrecs = 0;
// Notes: Event records have useless duration record.
start = edf.startdate / 1000L;
// Process event annotation records
for (int s = 0; s < edf.GetNumSignals(); s++) {
recs = edf.edfsignals[s].nr * edf.GetNumDataRecords() * 2;
data = (char *)edf.edfsignals[s].data;
pos = 0;
tt = edf.startdate;
duration = 0;
while (pos < recs) {
c = data[pos];
if ((c != '+') && (c != '-')) {
break;
}
if (data[pos++] == '+') { sign = true; }
else { sign = false; }
t = "";
c = data[pos];
do {
t += c;
pos++;
c = data[pos];
} while ((c != 20) && (c != 21)); // start code
d = t.toDouble(&ok);
if (!ok) {
qDebug() << "Faulty EDF EVE file " << edf.filename;
break;
}
if (!sign) { d = -d; }
tt = edf.startdate + qint64(d * 1000.0);
duration = 0;
// First entry
if (data[pos] == 21) {
pos++;
// get duration.
t = "";
do {
t += data[pos];
pos++;
} while ((data[pos] != 20) && (pos < recs)); // start code
duration = t.toDouble(&ok);
if (!ok) {
qDebug() << "Faulty EDF EVE file (at %" << pos << ") " << edf.filename;
break;
}
}
end = (tt / 1000.0);
while ((data[pos] == 20) && (pos < recs)) {
t = "";
pos++;
if (data[pos] == 0) {
break;
}
if (data[pos] == 20) {
pos++;
break;
}
do {
t += tolower(data[pos++]);
} while ((data[pos] != 20) && (pos < recs)); // start code
if (!t.isEmpty() && (t!="recording starts")) {
goodrecs++;
// if (matchSignal(CPAP_Obstructive, t)) {
// } else if (matchSignal(CPAP_Hypopnea, t)) {
// } else if (matchSignal(CPAP_Apnea, t)) {
// } else if (matchSignal(CPAP_ClearAirway, t)) {
// } else {
// if (t != "recording starts") {
// qDebug() << "Unobserved ResMed annotation field: " << t;
// }
// }
}
if (pos >= recs) {
qDebug() << "Short EDF EVE file" << edf.filename;
break;
}
// pos++;
}
while ((data[pos] == 0) && (pos < recs)) { pos++; }
if (pos >= recs) { break; }
}
}
return goodrecs;
}
// Looks inside an EDF or EDF.gz and grabs the start and duration // Looks inside an EDF or EDF.gz and grabs the start and duration
EDFduration getEDFDuration(QString filename) EDFduration getEDFDuration(QString filename)
{ {
QString ext = filename.section("_", -1).section(".",0,0).toUpper();
bool ok1, ok2; bool ok1, ok2;
int num_records; int num_records;
@ -1169,31 +1391,34 @@ EDFduration getEDFDuration(QString filename)
quint32 end = start + rec_duration * num_records; quint32 end = start + rec_duration * num_records;
QString filedate = filename.section("/",-1).section("_",0,1); QString filedate = filename.section("/",-1).section("_",0,1);
QString ext = filename.section("_", -1).section(".",0,0).toUpper();
QDateTime dt2 = QDateTime::fromString(filedate, "yyyyMMdd_hhmmss"); QDateTime dt2 = QDateTime::fromString(filedate, "yyyyMMdd_hhmmss");
quint32 st2 = dt2.toTime_t(); quint32 st2 = dt2.toTime_t();
start = qMin(st2, start); start = qMin(st2, start);
if (end < start) end = qMax(st2, start); // This alone should really cover the EVE.EDF condition if (end < start) end = qMax(st2, start);
// if (ext == "EVE") { if (ext == "EVE") {
// // This is an unavoidable kludge, because there genuinely is no duration given for EVE files. // S10 Forces us to parse EVE files to find their real durations
// // It could partially be avoided by parsing the EDF annotations completely, but on days with no events, this would be pointless. quint32 en2;
// // Add some seconds to make sure some overlap happens with related sessions. // Have to get the actual duration of the EVE file by parsing the annotations. :(
int recs = PeekEVE(filename, st2, en2);
if (recs > 0) {
start = qMin(st2, start);
end = qMax(en2, end);
EDFduration dur(start, end, filename);
// // ************** Be cautious with this value ************** dur.type = lookupEDFType(ext.toUpper());
// // A Firmware bug causes (perhaps with failing SD card) sessions to sometimes take a long time to write, and it can screw this up return dur;
// // I've really got no way of detecting the other condition.. I can either have one or the other. } else {
// empty EVE file, don't give a crap about it...
// // Wait... EVE and BRP start at the same time.. that should be enough to counter overlaps! return EDFduration(0, 0, filename);
// end += 1; }
// } // A Firmware bug causes (perhaps with failing SD card) sessions to sometimes take a long time to write
}
if ((end - start) < 10) end = start + 10;
EDFduration dur(start, end, filename); EDFduration dur(start, end, filename);
@ -1324,12 +1549,13 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
QString fullname = fi.canonicalFilePath(); QString fullname = fi.canonicalFilePath();
// Peek inside the EDF file and get the EDFDuration record for the session matching that follows // Peek inside the EDF file and get the EDFDuration record for the session matching that follows
EDFduration dur = getEDFDuration(fullname);
dur.filename = filename;
QMap<QString, EDFduration>::iterator it = newfiles.insert(filename, getEDFDuration(fullname)); if (dur.start != dur.end) { // make sure empty EVE's are skipped
EDFduration *dur = &it.value(); QMap<QString, EDFduration>::iterator it = newfiles.insert(filename, getEDFDuration(fullname));
dur->filename = filename; filesbytype[dur.type].append(&it.value());
}
filesbytype[dur->type].append(dur);
} }
} }
@ -1337,30 +1563,28 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
EDForder.push_back(EDF_PLD); EDForder.push_back(EDF_PLD);
EDForder.push_back(EDF_BRP); EDForder.push_back(EDF_BRP);
EDForder.push_back(EDF_SAD); EDForder.push_back(EDF_SAD);
EDForder.push_back(EDF_EVE);
EDForder.push_back(EDF_CSL); EDForder.push_back(EDF_CSL);
for (int i=0; i<3; i++) { for (int i=0; i<4; i++) {
EDFType basetype = EDForder.takeFirst(); EDFType basetype = EDForder.takeFirst();
// Process PLD files // Process PLD files
QList<EDFduration *> & LIST = filesbytype[basetype]; QList<EDFduration *> & LIST = filesbytype[basetype];
int pld_size = LIST.size(); int base_size = LIST.size();
for (int f=0; f < pld_size; ++f) { for (int f=0; f < base_size; ++f) {
const EDFduration * dur = LIST.at(f); const EDFduration * dur = LIST.at(f);
quint32 start = dur->start; quint32 start = dur->start;
if (start == 0) continue; if (start == 0) continue;
quint32 end = dur->end; quint32 end = dur->end;
QHash<EDFType, QString> grp; QHash<EDFType, QStringList> grp;
grp[EDF_PLD] = create_backups ? backup(dur->path, backup_path) : dur->path;; grp[basetype].append(create_backups ? backup(dur->path, backup_path) : dur->path);
QStringList files; QStringList files;
files.append(dur->filename); files.append(dur->filename);
for (int o=0; o<EDForder.size(); ++o) { for (int o=0; o<EDForder.size(); ++o) {
EDFType type = EDForder.at(o); EDFType type = EDForder.at(o);
@ -1369,6 +1593,7 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
QList<EDFduration *>::iterator list_end = EDF_list.end(); QList<EDFduration *>::iterator list_end = EDF_list.end();
for (item = EDF_list.begin(); item != list_end; ++item) { for (item = EDF_list.begin(); item != list_end; ++item) {
const EDFduration * dur2 = *item; const EDFduration * dur2 = *item;
if (dur2->start == 0) continue;
// Do the sessions Overlap? // Do the sessions Overlap?
if ((start < dur2->end) && ( dur2->start < end)) { if ((start < dur2->end) && ( dur2->start < end)) {
@ -1377,18 +1602,41 @@ int ResmedLoader::scanFiles(Machine * mach, QString datalog_path)
files.append(dur2->filename); files.append(dur2->filename);
grp[type] = create_backups ? backup(dur2->path, backup_path) : dur2->path; grp[type].append(create_backups ? backup(dur2->path, backup_path) : dur2->path);
filesbytype[type].erase(item); filesbytype[type].erase(item);
break;
} }
} }
} }
// EVE annotation files can cover multiple sessions
QList<EDFduration *> & EDF_list = filesbytype[EDF_EVE];
QList<EDFduration *>::iterator item;
QList<EDFduration *>::iterator list_end = EDF_list.end();
for (item = EDF_list.begin(); item != list_end; ++item) {
const EDFduration * dur2 = *item;
if (dur2->start == 0) continue;
// Do the sessions Overlap?
if ((start < dur2->end) && ( dur2->start < end)) {
// start = qMin(start, dur2->start);
// end = qMax(end, dur2->end);
files.append(dur2->filename);
grp[EDF_EVE].append(create_backups ? backup(dur2->path, backup_path) : dur2->path);
}
}
if (mach->SessionExists(start) == nullptr) { if (mach->SessionExists(start) == nullptr) {
EDFGroup group(grp[EDF_BRP], grp[EDF_EVE], grp[EDF_PLD], grp[EDF_SAD], grp[EDF_CSL]); //EDFGroup group(grp[EDF_BRP], grp[EDF_EVE], grp[EDF_PLD], grp[EDF_SAD], grp[EDF_CSL]);
queTask(new ResmedImport(this, start, group, mach)); if (grp.size() > 0) {
for (int i=0; i<files.size(); i++) skipfiles[files.at(i)] = start; queTask(new ResmedImport(this, start, grp, mach));
for (int i=0; i<files.size(); i++) skipfiles[files.at(i)] = start;
}
} }
} }
} }
@ -1756,32 +2004,79 @@ int ResmedLoader::Open(QString path)
// Scan DATALOG files, sort, and import any new sessions // Scan DATALOG files, sort, and import any new sessions
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
int new_sessions = scanFiles(m, newpath); int num_new_sessions = scanFiles(m, newpath);
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
// Now look for any new summary data that can be extracted from STR.edf records // Now look for any new summary data that can be extracted from STR.edf records
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
QMap<quint32, STRRecord>::iterator it;
QMap<quint32, STRRecord>::iterator end = strsess.end();
QHash<SessionID, Session *>::iterator sessit;
QHash<SessionID, Session *>::iterator sessend = m->sessionlist.end();;
int size = m->sessionlist.size(); int size = m->sessionlist.size();
int cnt=0; int cnt=0;
Session * sess; Session * sess;
// Scan through all sessions, and remove any strsess records that have a matching session already
for (sessit = m->sessionlist.begin(); sessit != sessend; ++sessit) {
sess = *sessit;
quint32 key = sess->settings[RMS9_MaskOnTime].toUInt();
QMap<quint32, STRRecord>::iterator e = strsess.find(key); // Scan through all sessions, and remove any strsess records that have a matching session already
if (e != end) { // for (sessit = m->sessionlist.begin(); sessit != sessend; ++sessit) {
strsess.erase(e); // sess = *sessit;
// quint32 key = sess->settings[RMS9_MaskOnTime].toUInt();
// // Ugly.. need to check sessions overlaps..
// QMap<quint32, STRRecord>::iterator e = strsess.find(key);
// if (e != end) {
// strsess.erase(e);
// }
// }
///
QHash<SessionID, Session *>::iterator sessit;
QHash<SessionID, Session *>::iterator sessend = m->sessionlist.end();;
QMap<SessionID, Session *>::iterator sit;
QMap<SessionID, Session *>::iterator ns_end = new_sessions.end();
QMap<quint32, STRRecord>::iterator it;
QMap<quint32, STRRecord>::iterator end = strsess.end();
QList<quint32> strlist;
for (it = strsess.begin(); it != end; ++it) {
STRRecord & R = it.value();
quint32 s1 = R.maskon;
quint32 e1 = R.maskoff;
bool fnd = false;
for (sessit = m->sessionlist.begin(); sessit != sessend; ++sessit) {
sess = sessit.value();
quint32 s2 = sess->session();
quint32 e2 = s2 + (sess->length() / 1000L);
if ((s1 < e2) && (s2 < e1)) {
strlist.push_back(it.key());
fnd = true;
break;
}
}
if (!fnd) for (sit = new_sessions.begin(); sit != ns_end; ++sit) {
sess = sit.value();
quint32 s2 = sess->session();
quint32 e2 = s2 + (sess->length() / 1000L);
if ((s1 < e2) && (s2 < e1)) {
strlist.push_back(it.key());
fnd = true;
break;
}
} }
} }
for (int i=0; i<strlist.size(); i++) {
int k = strlist.at(i);
strsess.remove(k);
}
///
size = strsess.size(); size = strsess.size();
cnt=0; cnt=0;
@ -1810,10 +2105,11 @@ int ResmedLoader::Open(QString path)
continue; continue;
} }
queTask(new ResmedImportStage2(this, R, m)); queTask(new ResmedImportStage2(this, R, m));
} }
new_sessions += countTasks(); num_new_sessions += countTasks();
runTasks(); runTasks();
finishAddingSessions(); finishAddingSessions();
@ -1852,7 +2148,7 @@ int ResmedLoader::Open(QString path)
channel_time.clear(); channel_time.clear();
qDebug() << "Total Events " << event_cnt; qDebug() << "Total Events " << event_cnt;
return new_sessions; return num_new_sessions;
} }
@ -1968,7 +2264,7 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path)
data = (char *)edf.edfsignals[s].data; data = (char *)edf.edfsignals[s].data;
pos = 0; pos = 0;
tt = edf.startdate; tt = edf.startdate;
sess->updateFirst(tt); // sess->updateFirst(tt);
duration = 0; duration = 0;
while (pos < recs) { while (pos < recs) {
@ -2040,18 +2336,19 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path)
if (!t.isEmpty()) { if (!t.isEmpty()) {
if (matchSignal(CPAP_Obstructive, t)) { if (matchSignal(CPAP_Obstructive, t)) {
OA->AddEvent(tt, duration);
if (sess->checkInside(tt)) OA->AddEvent(tt, duration);
} else if (matchSignal(CPAP_Hypopnea, t)) { } else if (matchSignal(CPAP_Hypopnea, t)) {
HY->AddEvent(tt, duration + 10); // Only Hyponea's Need the extra duration??? if (sess->checkInside(tt)) HY->AddEvent(tt, duration + 10); // Only Hyponea's Need the extra duration???
} else if (matchSignal(CPAP_Apnea, t)) { } else if (matchSignal(CPAP_Apnea, t)) {
UA->AddEvent(tt, duration); if (sess->checkInside(tt)) UA->AddEvent(tt, duration);
} else if (matchSignal(CPAP_ClearAirway, t)) { } else if (matchSignal(CPAP_ClearAirway, t)) {
// Not all machines have it, so only create it when necessary.. // Not all machines have it, so only create it when necessary..
if (!CA) { if (!CA) {
if (!(CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event))) { return false; } if (!(CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event))) { return false; }
} }
CA->AddEvent(tt, duration); if (sess->checkInside(tt)) CA->AddEvent(tt, duration);
} else { } else {
if (t != "recording starts") { if (t != "recording starts") {
qDebug() << "Unobserved ResMed annotation field: " << t; qDebug() << "Unobserved ResMed annotation field: " << t;
@ -2072,7 +2369,7 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path)
if (pos >= recs) { break; } if (pos >= recs) { break; }
} }
sess->updateLast(tt); // sess->updateLast(tt);
} }
return true; return true;
@ -2366,7 +2663,6 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path)
ToTimeDelta(sess, edf, es, code, recs, duration, 0, 0); ToTimeDelta(sess, edf, es, code, recs, duration, 0, 0);
} else if (matchSignal(CPAP_IPAP, es.label)) { } else if (matchSignal(CPAP_IPAP, es.label)) {
code = CPAP_IPAP; code = CPAP_IPAP;
sess->settings[CPAP_Mode] = MODE_BILEVEL_FIXED;
es.physical_maximum = 25; es.physical_maximum = 25;
es.physical_minimum = 4; es.physical_minimum = 4;
ToTimeDelta(sess, edf, es, code, recs, duration, 0, 0); ToTimeDelta(sess, edf, es, code, recs, duration, 0, 0);
@ -2564,6 +2860,8 @@ void ResInitModelMap()
resmed_codes[RMS9_SetPressure].push_back("Inställt tryck"); resmed_codes[RMS9_SetPressure].push_back("Inställt tryck");
resmed_codes[RMS9_SetPressure].push_back("Inställt tryck"); resmed_codes[RMS9_SetPressure].push_back("Inställt tryck");
resmed_codes[RMS9_EPR].push_back("EPR"); resmed_codes[RMS9_EPR].push_back("EPR");
resmed_codes[RMS9_EPR].push_back("S.EPR.EPRType");
resmed_codes[RMS9_EPR].push_back("\xE5\x91\xBC\xE6\xB0\x94\xE9\x87\x8A\xE5\x8E\x8B\x28\x45\x50"); // Chinese resmed_codes[RMS9_EPR].push_back("\xE5\x91\xBC\xE6\xB0\x94\xE9\x87\x8A\xE5\x8E\x8B\x28\x45\x50"); // Chinese
resmed_codes[RMS9_EPRLevel].push_back("EPR Level"); resmed_codes[RMS9_EPRLevel].push_back("EPR Level");
resmed_codes[RMS9_EPRLevel].push_back("EPR-Stufe"); resmed_codes[RMS9_EPRLevel].push_back("EPR-Stufe");
@ -2621,6 +2919,7 @@ void ResInitModelMap()
} }
ChannelID ResmedLoader::CPAPModeChannel() { return RMS9_Mode; }
ChannelID ResmedLoader::PresReliefMode() { return RMS9_EPR; } ChannelID ResmedLoader::PresReliefMode() { return RMS9_EPR; }
ChannelID ResmedLoader::PresReliefLevel() { return RMS9_EPRLevel; } ChannelID ResmedLoader::PresReliefLevel() { return RMS9_EPRLevel; }
@ -2628,6 +2927,28 @@ void ResmedLoader::initChannels()
{ {
using namespace schema; using namespace schema;
Channel * chan = nullptr; Channel * chan = nullptr;
channel.add(GRP_CPAP, chan = new Channel(RMS9_Mode = 0xe203, SETTING, MT_CPAP, SESSION,
"RMS9_Mode",
QObject::tr("Mode"),
QObject::tr("CPAP Mode"),
QObject::tr("Mode"),
"", LOOKUP, Qt::green));
chan->addOption(0, QObject::tr("CPAP"));
chan->addOption(1, QObject::tr("APAP"));
chan->addOption(2, QObject::tr("VPAP-T"));
chan->addOption(3, QObject::tr("VPAP-S"));
chan->addOption(4, QObject::tr("VPAP-S/T"));
chan->addOption(5, QObject::tr("??"));
chan->addOption(6, QObject::tr("VPAPauto"));
chan->addOption(7, QObject::tr("ASV"));
chan->addOption(8, QObject::tr("ASVAuto"));
chan->addOption(9, QObject::tr("???"));
chan->addOption(10, QObject::tr("???"));
chan->addOption(11, QObject::tr("Auto for Her"));
channel.add(GRP_CPAP, chan = new Channel(RMS9_EPR = 0xe201, SETTING, MT_CPAP, SESSION, channel.add(GRP_CPAP, chan = new Channel(RMS9_EPR = 0xe201, SETTING, MT_CPAP, SESSION,
"EPR", QObject::tr("EPR"), "EPR", QObject::tr("EPR"),
QObject::tr("ResMed Exhale Pressure Relief"), QObject::tr("ResMed Exhale Pressure Relief"),

View File

@ -109,6 +109,7 @@ struct STRRecord
maskdur = 0; maskdur = 0;
maskevents = -1; maskevents = -1;
mode = -1; mode = -1;
rms9_mode = -1;
set_pressure = -1; set_pressure = -1;
epap = -1; epap = -1;
max_pressure = -1; max_pressure = -1;
@ -136,6 +137,26 @@ struct STRRecord
leakmax = -1; leakmax = -1;
leakgain = 0; leakgain = 0;
s_RampTime = -1;
s_RampEnable = -1;
s_EPR_ClinEnable = -1;
s_EPREnable = -1;
s_PtAccess = -1;
s_ABFilter = -1;
s_Mask = -1;
s_Tube = -1;
s_ClimateControl = -1;
s_HumEnable = -1;
s_HumLevel = -1;
s_TempEnable = -1;
s_Temp = -1;
s_SmartStart = -1;
ramp_pressure = -1;
date=QDate(); date=QDate();
} }
STRRecord(const STRRecord & copy) { STRRecord(const STRRecord & copy) {
@ -144,6 +165,7 @@ struct STRRecord
maskdur = copy.maskdur; maskdur = copy.maskdur;
maskevents = copy.maskevents; maskevents = copy.maskevents;
mode = copy.mode; mode = copy.mode;
rms9_mode = copy.rms9_mode;
set_pressure = copy.set_pressure; set_pressure = copy.set_pressure;
epap = copy.epap; epap = copy.epap;
max_pressure = copy.max_pressure; max_pressure = copy.max_pressure;
@ -169,12 +191,26 @@ struct STRRecord
leak95 = copy.leak95; leak95 = copy.leak95;
leakmax = copy.leakmax; leakmax = copy.leakmax;
leakgain = copy.leakgain; leakgain = copy.leakgain;
s_SmartStart = copy.s_SmartStart;
s_PtAccess = copy.s_PtAccess;
s_ABFilter = copy.s_ABFilter;
s_Mask = copy.s_Mask;
s_Tube = copy.s_Tube;
s_ClimateControl = copy.s_ClimateControl;
s_HumEnable = copy.s_HumEnable;
s_HumLevel = copy.s_HumLevel;
s_TempEnable = copy.s_TempEnable;
s_Temp = copy.s_Temp;
ramp_pressure = copy.ramp_pressure;
} }
quint32 maskon; quint32 maskon;
quint32 maskoff; quint32 maskoff;
EventDataType maskdur; EventDataType maskdur;
EventDataType maskevents; EventDataType maskevents;
EventDataType mode; EventDataType mode;
EventDataType rms9_mode;
EventDataType set_pressure; EventDataType set_pressure;
EventDataType max_pressure; EventDataType max_pressure;
EventDataType min_pressure; EventDataType min_pressure;
@ -199,7 +235,25 @@ struct STRRecord
EventDataType leak95; EventDataType leak95;
EventDataType leakmax; EventDataType leakmax;
EventDataType leakgain; EventDataType leakgain;
EventDataType ramp_pressure;
QDate date; QDate date;
EventDataType s_RampTime;
int s_RampEnable;
int s_EPR_ClinEnable;
int s_EPREnable;
int s_PtAccess;
int s_ABFilter;
int s_Mask;
int s_Tube;
int s_ClimateControl;
int s_HumEnable;
EventDataType s_HumLevel;
int s_TempEnable;
EventDataType s_Temp;
int s_SmartStart;
}; };
@ -306,14 +360,14 @@ struct EDFGroup {
class ResmedImport:public ImportTask class ResmedImport:public ImportTask
{ {
public: public:
ResmedImport(ResmedLoader * l, SessionID s, EDFGroup g, Machine * m): loader(l), sessionid(s), group(g), mach(m) {} ResmedImport(ResmedLoader * l, SessionID s, QHash<EDFType, QStringList> f, Machine * m): loader(l), sessionid(s), files(f), mach(m) {}
virtual ~ResmedImport() {} virtual ~ResmedImport() {}
virtual void run(); virtual void run();
protected: protected:
ResmedLoader * loader; ResmedLoader * loader;
SessionID sessionid; SessionID sessionid;
EDFGroup group; QHash<EDFType, QStringList> files;
Machine * mach; Machine * mach;
}; };
@ -396,6 +450,8 @@ class ResmedLoader : public CPAPLoader
virtual ChannelID PresReliefMode(); virtual ChannelID PresReliefMode();
virtual ChannelID PresReliefLevel(); virtual ChannelID PresReliefLevel();
virtual ChannelID CPAPModeChannel();
//////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -125,6 +125,7 @@ public:
virtual ChannelID PresReliefLevel() { return NoChannel; } virtual ChannelID PresReliefLevel() { return NoChannel; }
virtual ChannelID HumidifierConnected() { return NoChannel; } virtual ChannelID HumidifierConnected() { return NoChannel; }
virtual ChannelID HumidifierLevel() { return CPAP_HumidSetting; } virtual ChannelID HumidifierLevel() { return CPAP_HumidSetting; }
virtual ChannelID CPAPModeChannel() { return CPAP_Mode; }
virtual void initChannels() {} virtual void initChannels() {}
}; };

View File

@ -11,6 +11,8 @@
#ifndef SESSION_H #ifndef SESSION_H
#define SESSION_H #define SESSION_H
#define SESSION_DEBUG
#include <QDebug> #include <QDebug>
#include <QHash> #include <QHash>
#include <QVector> #include <QVector>
@ -36,6 +38,10 @@ class Session
Session(Machine *, SessionID); Session(Machine *, SessionID);
virtual ~Session(); virtual ~Session();
inline bool checkInside(qint64 time) {
return ((time >= s_first) && (time <= s_last));
}
//! \brief Stores the session in the directory supplied by path //! \brief Stores the session in the directory supplied by path
bool Store(QString path); bool Store(QString path);
@ -338,6 +344,10 @@ class Session
const QString & eventFile() { return s_eventfile; } const QString & eventFile() { return s_eventfile; }
#if defined(SESSION_DEBUG)
QStringList session_files;
#endif
protected: protected:
SessionID s_session; SessionID s_session;
@ -356,8 +366,6 @@ protected:
// for debugging // for debugging
bool destroyed; bool destroyed;
}; };

View File

@ -59,7 +59,7 @@ void Daily::setSidebarVisible(bool visible)
{ {
QList<int> a; QList<int> a;
int panel_width = visible ? 350 : 0; int panel_width = visible ? 370 : 0;
a.push_back(panel_width); a.push_back(panel_width);
a.push_back(this->width() - panel_width); a.push_back(this->width() - panel_width);
ui->splitter_2->setStretchFactor(1,1); ui->splitter_2->setStretchFactor(1,1);
@ -160,7 +160,7 @@ Daily::Daily(QWidget *parent,gGraphView * shared)
// gGraph * SG; // gGraph * SG;
// graphlist[STR_GRAPH_DailySummary] = SG = new gGraph(STR_GRAPH_DailySummary, GraphView, QObject::tr("Summary"), QObject::tr("Summary of this daily information"), default_height); // graphlist[STR_GRAPH_DailySummary] = SG = new gGraph(STR_GRAPH_DailySummary, GraphView, QObject::tr("Summary"), QObject::tr("Summary of this daily information"), default_height);
// SG->AddLayer(new gLabelArea(nullptr),LayerLeft,gYAxis::Margin); // SG->AddLayer(new gLabelArea(nullptr),LayerLeft,gYAxis::Margin);
// SG->AddLayer(AddCPAP(new gDailySummary())); // SG->AddLayer(new gDailySummary());
graphlist[STR_GRAPH_SleepFlags] = SF = new gGraph(STR_GRAPH_SleepFlags, GraphView, STR_TR_EventFlags, STR_TR_EventFlags, default_height); graphlist[STR_GRAPH_SleepFlags] = SF = new gGraph(STR_GRAPH_SleepFlags, GraphView, STR_TR_EventFlags, STR_TR_EventFlags, default_height);
SF->setPinned(true); SF->setPinned(true);
@ -911,6 +911,7 @@ QString Daily::getSessionInformation(Day * day)
sess->settings[SESSION_ENABLED]=true; sess->settings[SESSION_ENABLED]=true;
} }
bool b=sess->settings[SESSION_ENABLED].toBool(); bool b=sess->settings[SESSION_ENABLED].toBool();
html+=QString("<tr class='datarow2'><td colspan=5 align=center>%2</td></tr>" html+=QString("<tr class='datarow2'><td colspan=5 align=center>%2</td></tr>"
"<tr class='datarow2'>" "<tr class='datarow2'>"
"<td width=26><a href='toggle"+type+"session=%1'>" "<td width=26><a href='toggle"+type+"session=%1'>"
@ -927,6 +928,11 @@ QString Daily::getSessionInformation(Day * day)
.arg(fd.date().toString(Qt::SystemLocaleShortDate)) .arg(fd.date().toString(Qt::SystemLocaleShortDate))
.arg(fd.toString("HH:mm:ss")) .arg(fd.toString("HH:mm:ss"))
.arg(ld.toString("HH:mm:ss")); .arg(ld.toString("HH:mm:ss"));
#ifdef SESSION_DEBUG
for (int i=0; i< sess->session_files.size(); ++i) {
html+=QString("<tr><td colspan=5 align=center>%1</td></tr>").arg(sess->session_files[i].section("/",-1));
}
#endif
} }
} }
@ -962,10 +968,20 @@ QString Daily::getMachineSettings(Day * day) {
} }
QMap<int, QString> first; QMap<int, QString> first;
CPAPLoader * loader = qobject_cast<CPAPLoader *>(cpap->loader());
ChannelID cpapmode = loader->CPAPModeChannel();
schema::Channel & chan = schema::channel[cpapmode];
first[cpapmode] = QString("<tr class='datarow'><td><a class='info' href='#'>%1<span>%2</span></a></td><td colspan=4>%3</td></tr>")
.arg(schema::channel[cpapmode].label())
.arg(schema::channel[cpapmode].description())
.arg(day->getCPAPMode());
if (sess) for (; it != it_end; ++it) { if (sess) for (; it != it_end; ++it) {
ChannelID code = it.key(); ChannelID code = it.key();
if ((code <= 1) || (code == RMS9_MaskOnTime)) continue; if ((code <= 1) || (code == RMS9_MaskOnTime) || (code == CPAP_Mode) || (code == cpapmode)) continue;
schema::Channel & chan = schema::channel[code]; schema::Channel & chan = schema::channel[code];
@ -986,8 +1002,7 @@ QString Daily::getMachineSettings(Day * day) {
.arg(data); .arg(data);
if ((code == CPAP_Mode) if ((code == CPAP_IPAP)
|| (code == CPAP_IPAP)
|| (code == CPAP_EPAP) || (code == CPAP_EPAP)
|| (code == CPAP_IPAPHi) || (code == CPAP_IPAPHi)
|| (code == CPAP_EPAPHi) || (code == CPAP_EPAPHi)
@ -1005,7 +1020,7 @@ QString Daily::getMachineSettings(Day * day) {
} }
} }
ChannelID order[] = { CPAP_Mode, CPAP_Pressure, CPAP_PressureMin, CPAP_PressureMax, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAPHi, CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_PS, CPAP_PSMin, CPAP_PSMax }; ChannelID order[] = { cpapmode, CPAP_Pressure, CPAP_PressureMin, CPAP_PressureMax, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAPHi, CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_PS, CPAP_PSMin, CPAP_PSMax };
int os = sizeof(order) / sizeof(ChannelID); int os = sizeof(order) / sizeof(ChannelID);
for (int i=0 ;i < os; ++i) { for (int i=0 ;i < os; ++i) {
if (first.contains(order[i])) html += first[order[i]]; if (first.contains(order[i])) html += first[order[i]];
@ -1955,18 +1970,35 @@ void Daily::on_LineCursorUpdate(double time)
void Daily::on_RangeUpdate(double minx, double maxx) void Daily::on_RangeUpdate(double minx, double maxx)
{ {
// static qint64 last_minx = 0;
// static qint64 last_maxx = 0;
//if ((last_minx != minx) || (last_maxx != maxx)) {
if (minx > 1) { if (minx > 1) {
dateDisplay->setText(GraphView->getRangeString()); dateDisplay->setText(GraphView->getRangeString());
} else { } else {
dateDisplay->setText(QString(GraphView->emptyText())); dateDisplay->setText(QString(GraphView->emptyText()));
} }
//}
// last_minx=minx; /* // Delay render some stats...
// last_maxx=maxx; Day * day = GraphView->day();
if (day) {
QTime time;
time.start();
QList<ChannelID> list = day->getSortedMachineChannels(schema::WAVEFORM);
for (int i=0; i< list.size();i++) {
schema::Channel & chan = schema::channel[list.at(i)];
ChannelID code = chan.id();
if (!day->channelExists(code)) continue;
float avg = day->rangeAvg(code, minx, maxx);
float wavg = day->rangeWavg(code, minx, maxx);
float median = day->rangePercentile(code, 0.5, minx, maxx);
float p90 = day->rangePercentile(code, 0.9, minx, maxx);
// qDebug() << chan.label()
// << "AVG=" << avg
// << "WAVG=" << wavg;
// << "MED" << median
// << "90%" << p90;
}
qDebug() << time.elapsed() << "ms";
}*/
} }

View File

@ -381,8 +381,8 @@ gGraph *Overview::createGraph(QString code, QString name, QString units, YTicker
void Overview::on_LineCursorUpdate(double time) void Overview::on_LineCursorUpdate(double time)
{ {
if (time > 1) { if (time > 1) {
QDateTime dt = QDateTime::fromMSecsSinceEpoch(time); QDateTime dt = QDateTime::fromMSecsSinceEpoch(time,Qt::UTC);
QString txt = dt.toString("dd MMM yyyy"); QString txt = dt.toString("dd MMM yyyy (dddd)");
dateLabel->setText(txt); dateLabel->setText(txt);
} else dateLabel->setText(QString(GraphView->emptyText())); } else dateLabel->setText(QString(GraphView->emptyText()));
} }