From 811ac9e3a9f20f8a594a7320642518f622ad9f89 Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Mon, 20 May 2019 01:27:50 -0700 Subject: [PATCH 01/17] Improve documentation about OSCAR for Doxygen use. --- oscar/mainwindow.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/oscar/mainwindow.h b/oscar/mainwindow.h index 184f93be..442499c4 100644 --- a/oscar/mainwindow.h +++ b/oscar/mainwindow.h @@ -47,9 +47,14 @@ class MainWindow; This document is an attempt to provide a little technical insight into OSCAR's program internals. \section project_info Further Information - The project was hosted on sourceforge, and it's original project page can be reached at http://sourceforge.net/projects/sleepyhead. + OSCAR is hosted on Gitlab with full source code available there. - There was also a SleepyHead Wiki containing further information + Help for users can be found in the OSCAR Help Wiki. + + Data structures are described in a OSCAR Data Information Wiki. + + The SleepyHead project was hosted on sourceforge, and it's original project page can be reached at http://sourceforge.net/projects/sleepyhead. + There was also a SleepyHead Wiki containing further information. \section structure Program Structure OSCAR is written in C++ using Qt Toolkit library, and comprises of 3 main components From 275ba9a09b4b61e8da4d4881694afb0afd4855db Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Mon, 20 May 2019 01:29:31 -0700 Subject: [PATCH 02/17] Batch file to make running Doxygen easier. Improvements to Doxyfile control file. --- Doxyfile | 8 ++++---- makedoxy.bat | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 makedoxy.bat diff --git a/Doxyfile b/Doxyfile index c0accd20..aec9bdb4 100644 --- a/Doxyfile +++ b/Doxyfile @@ -50,7 +50,7 @@ PROJECT_NUMBER = 1.0.x # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = OpenSource CPAP Analysis Reporter +PROJECT_BRIEF = Open_Source_CPAP_Analysis_Reporter # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not @@ -1643,7 +1643,7 @@ INCLUDED_BY_GRAPH = YES # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. -CALL_GRAPH = NO +CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function @@ -1651,7 +1651,7 @@ CALL_GRAPH = NO # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. -CALLER_GRAPH = NO +CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. @@ -1721,7 +1721,7 @@ DOT_TRANSPARENT = NO # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. -DOT_MULTI_TARGETS = NO +DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and diff --git a/makedoxy.bat b/makedoxy.bat new file mode 100644 index 00000000..a7f75cc6 --- /dev/null +++ b/makedoxy.bat @@ -0,0 +1,4 @@ +setlocal +set path="c:\Program Files (x86)\Graphviz2.38\bin";%path% +doxygen +endlocal \ No newline at end of file From efc03004a62a740b1c422401c6793d74d869addd Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Tue, 21 May 2019 15:11:53 -0700 Subject: [PATCH 03/17] Add more method documentation for Doxygen. --- oscar/SleepLib/machine_common.h | 3 ++- oscar/SleepLib/session.h | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/oscar/SleepLib/machine_common.h b/oscar/SleepLib/machine_common.h index f8650699..cac60064 100644 --- a/oscar/SleepLib/machine_common.h +++ b/oscar/SleepLib/machine_common.h @@ -46,7 +46,8 @@ qint64 timezoneOffset(); enum SummaryType { ST_CNT, ST_SUM, ST_AVG, ST_WAVG, ST_PERC, ST_90P, ST_MIN, ST_MAX, ST_MID, ST_CPH, ST_SPH, ST_FIRST, ST_LAST, ST_HOURS, ST_SESSIONS, ST_SETMIN, ST_SETAVG, ST_SETMAX, ST_SETWAVG, ST_SETSUM, ST_SESSIONID, ST_DATE }; /*! \enum MachineType - \brief Generalized type of a machine + \brief Generalized type of a machine. MT_CPAP is any type of xPAP machine, MT_OXIMETER any type of Oximeter + \brief MT_SLEEPSTAGE stage of sleep detector (ZEO importer), MT_JOURNAL for optional notes, MT_POSITION for sleep position detector (Somnopose) */ enum MachineType { MT_UNKNOWN = 0, MT_CPAP, MT_OXIMETER, MT_SLEEPSTAGE, MT_JOURNAL, MT_POSITION, MT_UNCATEGORIZED = 99}; //void InitMapsWithoutAwesomeInitializerLists(); diff --git a/oscar/SleepLib/session.h b/oscar/SleepLib/session.h index 7bb1fb30..663d053e 100644 --- a/oscar/SleepLib/session.h +++ b/oscar/SleepLib/session.h @@ -63,6 +63,7 @@ class Session Session(Machine *, SessionID); virtual ~Session(); + //! \brief Checks whether the supplied time is within the bounds of this session (ms since epoch) inline bool checkInside(qint64 time) { return ((time >= s_first) && (time <= s_last)); } @@ -113,13 +114,16 @@ class Session //! \brief Sets whether or not session is being used. void setEnabled(bool b); + //! \brief Return the earliest time in session (in milliseconds since epoch) inline qint64 realFirst() const { return s_first; } + + //! \brief Return the latest time in session (in milliseconds since epoch) inline qint64 realLast() const { return s_last; } - //! \brief Return the start of this sessions time range (in milliseconds since epoch) + //! \brief Return the start of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 first(); - //! \brief Return the end of this sessions time range (in milliseconds since epoch) + //! \brief Return the end of this sessions time range, adjusted for clock drift (in milliseconds since epoch) qint64 last(); //! \brief Return the millisecond length of this session @@ -156,10 +160,13 @@ class Session //! \brief Just set the end of the timerange without comparing void really_set_last(qint64 d) { s_last = d; } + //! \brief Set time to lower of 'd' and existing s_first void set_first(qint64 d) { if (!s_first) { s_first = d; } else if (d < s_first) { s_first = d; } } + + //! \brief Set last time to higher of 'd' and existing s_last. Throw warning if 'd' less than s_first. void set_last(qint64 d) { if (d <= s_first) { qWarning() << "Session::set_last() d<=s_first"; @@ -387,11 +394,16 @@ class Session //! \brief Returns true if session only contains summary data inline bool summaryOnly() { return s_summaryOnly; } + + //! \brief Returns true if there are no settings for this session inline bool noSettings() { return s_noSettings; } + //! \brief Mark this session as containing summary data only or not (true, false) inline void setSummaryOnly(bool b) { s_summaryOnly = b; } + //! \brief Mark this ssession as having settings data or not (true, false) inline void setNoSettings(bool b) { s_noSettings = b; } + //! \brief Mark whether this session has loaded data (true, false) void setOpened(bool b = true) { s_events_loaded = b; s_summary_loaded = b; @@ -411,6 +423,7 @@ class Session QString eventFile() const; + //! \brief Returns MachineType for this session MachineType type() { return s_machtype; } @@ -423,13 +436,17 @@ protected: SessionID s_session; Machine *s_machine; + //! \brief Time session begins (in ms since epoch) qint64 s_first; + //! \brief Time session ends (in ms since epoch) qint64 s_last; bool s_changed; bool s_lonesession; bool s_evchecksum_checked; bool _first_session; bool s_summaryOnly; + + //! \brief True if there are no settings for this session bool s_noSettings; bool s_summary_loaded; From 7fc534f75b8f6f6b0f12cd6c9e88cdd7910383e6 Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Tue, 21 May 2019 15:12:42 -0700 Subject: [PATCH 04/17] Format cmH2O properly in machine settings on Daily page. Needed only for ResMed and because formatted data is saved in database at original import. --- oscar/daily.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/oscar/daily.cpp b/oscar/daily.cpp index 0866b908..84c0a51f 100644 --- a/oscar/daily.cpp +++ b/oscar/daily.cpp @@ -964,12 +964,11 @@ QString Daily::getMachineSettings(Day * day) { ChannelID cpapmode = loader->CPAPModeChannel(); schema::Channel & chan = schema::channel[cpapmode]; - first[cpapmode] = QString("

%1

%3") + first[cpapmode] = QString("

%1

%3") .arg(chan.label()) .arg(chan.description()) .arg(day->getCPAPMode()); - if (sess) for (; it != it_end; ++it) { ChannelID code = it.key(); @@ -991,12 +990,14 @@ QString Daily::getMachineSettings(Day * day) { data = it.value().toString() + " "+ chan.units(); } + if (code ==0xe202) // Format EPR relief correctly + data = formatRelief(data); + QString tmp = QString("

%1

%3") .arg(schema::channel[code].label()) .arg(schema::channel[code].description()) .arg(data); - if ((code == CPAP_IPAP) || (code == CPAP_EPAP) || (code == CPAP_IPAPHi) From a5316025dd7bfdd86acc3610f5a50b6c78ae76e0 Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Tue, 21 May 2019 15:14:18 -0700 Subject: [PATCH 05/17] Add ignore of JSON file used by Sourcetrail. --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f34ae412..dccc9062 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ Makefile* *~ #Doxygen output does not go in repository -doxydoc \ No newline at end of file +doxydoc + +#SourceTrail files +*.json \ No newline at end of file From a433b29af1ceaeeda9a44e440164e161a1d549ac Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Tue, 21 May 2019 16:24:21 -0700 Subject: [PATCH 06/17] Change heading from Prescription Settings to Machine Settings --- oscar/statistics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oscar/statistics.cpp b/oscar/statistics.cpp index 1217ad10..3e9dbe7e 100644 --- a/oscar/statistics.cpp +++ b/oscar/statistics.cpp @@ -872,7 +872,7 @@ QString Statistics::GenerateRXChanges() QString html = "

"; html += QString(""); html += ""; - html += ""; + html += ""; // QString extratxt; From 4697f84ea98c9daa2f0161d786a1ef187014643d Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Wed, 22 May 2019 13:49:28 -0700 Subject: [PATCH 07/17] Revise Statistics page heading to use less space. Reformat and correct errors in HTML strings. --- oscar/statistics.cpp | 88 ++++++++++++++++++++++++-------------------- oscar/statistics.h | 1 + 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/oscar/statistics.cpp b/oscar/statistics.cpp index 3e9dbe7e..bb383d2c 100644 --- a/oscar/statistics.cpp +++ b/oscar/statistics.cpp @@ -575,10 +575,7 @@ Statistics::Statistics(QObject *parent) : } -const QString table_width = "width=99%"; -QString Statistics::htmlHeader(bool showheader) -{ - +QString Statistics::getUserInfo () { QString address = p_profile->user->address(); address.replace("\n", "
"); @@ -600,53 +597,66 @@ QString Statistics::htmlHeader(bool showheader) } } + return userinfo; +} +const QString table_width = "width=99%"; +QString Statistics::htmlHeader(bool showheader) +{ QString html = QString("")+ - "" + "" + "" + "" + "" + ""; - QPixmap logoPixmap(":/icons/logo-md.png"); + QPixmap logoPixmap(":/icons/logo-lg.png"); + if (showheader) { - html += "
" - +resizeHTMLPixmap(logoPixmap,64,64)+"
" - "" + STR_TR_OSCAR + "
" - "" + tr("Usage Statistics") + "
"; -// "" + tr("(NOT approved for compliance or medical reporting purposes)")+"
"; - if (!userinfo.isEmpty()) - html += "
"+userinfo; - html += "

"; - } + html += "
" + tr("Changes to Prescription Settings") + "
" + tr("Changes to Machine Settings") + "
" + "" + "" + "" + "" + "" + "
" + getUserInfo() + "" + "" + STR_TR_OSCAR + "   
" + "" + QObject::tr("Usage Statistics") + "   " + "
" + resizeHTMLPixmap(logoPixmap,120,120)+"   
" + "
" + "

"; + } return html; } QString Statistics::htmlFooter(bool showinfo) @@ -654,7 +664,7 @@ QString Statistics::htmlFooter(bool showinfo) QString html; if (showinfo) { - html += "
"; + html += "
"; html += tr("This report was generated by OSCAR v%1").arg(ShortVersionString) + "
" +tr("OSCAR is free open-source CPAP report software"); html += "
"; diff --git a/oscar/statistics.h b/oscar/statistics.h index 1a9c86c2..dff2f30e 100644 --- a/oscar/statistics.h +++ b/oscar/statistics.h @@ -156,6 +156,7 @@ class Statistics : public QObject void saveRXChanges(); void updateRXChanges(); + QString getUserInfo(); QString GenerateHTML(); QString GenerateMachineList(); QString GenerateRXChanges(); From 498e15c56bc78ff30853cde3f556bbff35b8a0a3 Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Wed, 22 May 2019 19:03:59 -0700 Subject: [PATCH 08/17] Remove some WebKit code (there's lots more to go). --- oscar/statistics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oscar/statistics.cpp b/oscar/statistics.cpp index bb383d2c..068982b0 100644 --- a/oscar/statistics.cpp +++ b/oscar/statistics.cpp @@ -604,7 +604,7 @@ const QString table_width = "width=99%"; QString Statistics::htmlHeader(bool showheader) { QString html = QString("")+ - +/******** "" - - "" - - "" -******/ "" ""; @@ -659,6 +633,8 @@ QString Statistics::htmlHeader(bool showheader) } return html; } + +// HTML for page footer QString Statistics::htmlFooter(bool showinfo) { QString html; @@ -675,7 +651,8 @@ QString Statistics::htmlFooter(bool showinfo) } - +// Calculate AHI for a period as total # of events / total hours used +// Add RERA if calculating RDI instead of just AHI EventDataType calcAHI(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_Obstructive, MT_CPAP, start, end) @@ -698,6 +675,7 @@ EventDataType calcAHI(QDate start, QDate end) return val; } +// Calculate flow limits per hour EventDataType calcFL(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_FlowLimit, MT_CPAP, start, end)); @@ -712,6 +690,7 @@ EventDataType calcFL(QDate start, QDate end) return val; } +// Calculate ...(what are these?) EventDataType calcSA(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_SensAwake, MT_CPAP, start, end)); @@ -726,7 +705,7 @@ EventDataType calcSA(QDate start, QDate end) return val; } - +// Structure for recording Prescription Changes (now called Machine Settings Changes) struct RXChange { RXChange() { highlight = 0; machine = nullptr; } RXChange(const RXChange ©) { @@ -777,6 +756,7 @@ struct UsageData { EventDataType ahi; EventDataType hours; }; + bool operator <(const UsageData &c1, const UsageData &c2) { if (c1.ahi < c2.ahi) { @@ -1016,10 +996,10 @@ QString Statistics::GenerateHTML() QDate cpapyear = lastcpap.addMonths(-12); // but not before the first available date of course - if (cpapweek < firstcpap) { cpapweek = firstcpap; } - if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } + if (cpapweek < firstcpap) { cpapweek = firstcpap; } + if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } if (cpap6month < firstcpap) { cpap6month = firstcpap; } - if (cpapyear < firstcpap) { cpapyear = firstcpap; } + if (cpapyear < firstcpap) { cpapyear = firstcpap; } QString ahitxt = getRDIorAHIText(); @@ -1031,12 +1011,15 @@ QString Statistics::GenerateHTML() // Compute number of monthly periods for a monthly rather than standard time distribution int number_periods = 0; if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { - QDate beginDate = max(firstcpap, lastcpap.addYears(-1)); + QDate beginDate = qMax(firstcpap, lastcpap.addYears(-1)); int beginMonth = beginDate.month(); int lastMonth = lastcpap.month(); - if (lastMonth - beginMonth < 0) lastMonth +=12; // handle time extending to next year + if (lastMonth < beginMonth) lastMonth += 12; // handle time extending to next year number_periods = lastMonth - beginMonth + 1; - qDebug() << "begin" << beginDate << "beginMonth" << beginMonth << "lastMonth" << lastMonth << "periods" << number_periods; + if (number_periods < 1) { + qDebug() << "*** Begin" << beginDate << "beginMonth" << beginMonth << "lastMonth" << lastMonth << "periods" << number_periods; + number_periods = 1; + } // But not more than one year if (number_periods > 12) { number_periods = 12; @@ -1047,8 +1030,8 @@ QString Statistics::GenerateHTML() QList periods; - bool skipsection = false;; + // Loop through all rows of the Statistics report for (QList::iterator i = rows.begin(); i != rows.end(); ++i) { StatisticsRow &row = (*i); QString name; @@ -1057,6 +1040,7 @@ QString Statistics::GenerateHTML() last = p_profile->LastGoodDay(row.type); first = p_profile->FirstGoodDay(row.type); + // Clear the periods (columns) periods.clear(); if (p_profile->general->statReportMode() == STAT_MODE_STANDARD) { periods.push_back(Period(last,last,tr("Most Recent"))); @@ -1064,7 +1048,7 @@ QString Statistics::GenerateHTML() periods.push_back(Period(qMax(last.addDays(-29),first), last, tr("Last 30 Days"))); periods.push_back(Period(qMax(last.addMonths(-6), first), last, tr("Last 6 Months"))); periods.push_back(Period(qMax(last.addMonths(-12), first), last, tr("Last Year"))); - } else { + } else { // STAT_MODE_MONTHLY or STAT_MODE_RANGE QDate l=last,s=last; periods.push_back(Period(last,last,tr("Last Session"))); diff --git a/oscar/statistics.h b/oscar/statistics.h index 4ffdb7ec..be33e6d1 100644 --- a/oscar/statistics.h +++ b/oscar/statistics.h @@ -15,10 +15,14 @@ #include "SleepLib/schema.h" #include "SleepLib/machine.h" +//! \brief Type of calculation on one statistics row enum StatCalcType { SC_UNDEFINED=0, SC_COLUMNHEADERS, SC_HEADING, SC_SUBHEADING, SC_MEDIAN, SC_AVG, SC_WAVG, SC_90P, SC_MIN, SC_MAX, SC_CPH, SC_SPH, SC_AHI, SC_HOURS, SC_COMPLIANCE, SC_DAYS, SC_ABOVE, SC_BELOW }; +/*! \struct StatisticsRow + \brief Describes a single row on the statistics page + */ struct StatisticsRow { StatisticsRow() { calc=SC_UNDEFINED; } StatisticsRow(QString src, QString calc, QString type) { @@ -40,6 +44,7 @@ struct StatisticsRow { StatCalcType calc; MachineType type; + //! \brief Looks up calculation type for this row StatCalcType lookupCalc(QString calc) { if (calc.compare("avg",Qt::CaseInsensitive)==0) { @@ -74,6 +79,7 @@ struct StatisticsRow { return SC_UNDEFINED; } + //! \brief Look up machine type MachineType lookupType(QString type) { if (type.compare("cpap", Qt::CaseInsensitive)==0) { @@ -94,6 +100,7 @@ struct StatisticsRow { QString value(QDate start, QDate end); }; +//! \class Prescription (Machine) setting class RXItem { public: RXItem() { From 76e6f44991f3fc45ec18724e6b2200a810186e77 Mon Sep 17 00:00:00 2001 From: Seeker4 Date: Wed, 29 May 2019 19:35:19 -0700 Subject: [PATCH 17/17] Calculate compliance as number of compliant days / total number of days (instead of days used) so statistics matches overview calculation. --- oscar/statistics.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oscar/statistics.cpp b/oscar/statistics.cpp index 52f94492..a41c5660 100644 --- a/oscar/statistics.cpp +++ b/oscar/statistics.cpp @@ -1492,7 +1492,9 @@ QString StatisticsRow::value(QDate start, QDate end) value = QString("%1").arg(formatTime(p_profile->calcHours(type, start, end) / days)); } else if (calc == SC_COMPLIANCE) { float c = p_profile->countCompliantDays(type, start, end); - float p = (100.0 / days) * c; +// float p = (100.0 / days) * c; + float realDays = qAbs(start.daysTo(end)) + 1; + float p = (100.0 / realDays) * c; value = QString("%1%").arg(p, 0, 'f', 0); } else if (calc == SC_DAYS) { value = QString("%1").arg(p_profile->countDays(type, start, end));