Fix PRS1 Waveform duration error

This commit is contained in:
Mark Watkins 2014-07-21 23:47:17 +10:00
parent 7cebae26d1
commit 3666391553
5 changed files with 254 additions and 210 deletions

View File

@ -191,9 +191,6 @@ QString gLineChart::getMetaString(ChannelID code, qint64 xpos)
}
}
QDateTime dt=QDateTime::fromMSecsSinceEpoch(xpos);
text = dt.toString("MMM dd - HH:mm:ss:zzz")+" "+text;
lastcode = code;
lasttime = xpos;
lasttext = text;
@ -276,14 +273,26 @@ void gLineChart::paint(QPainter &painter, gGraph &w, const QRegion &region)
if (p_profile->appearance->lineCursorMode()) {
qint64 time = w.currentTime();
QDateTime dt=QDateTime::fromMSecsSinceEpoch(time);
QString text = dt.toString("MMM dd - HH:mm:ss:zzz");
if ((time > minx) && (time < maxx)) {
double xpos = (time - minx) * xmult;
painter.setPen(QPen(QBrush(QColor(0,255,0,255)),1));
painter.drawLine(left+xpos, top-w.marginTop()-3, left+xpos, top+height+w.bottom-1);
}
QString text = getMetaString(m_codes[0], time);
//QString text = getMetaString(m_codes[0], time);
for (int i=0; i<m_codes.size(); ++i) {
ChannelID code = m_codes[i];
if (m_day->channelHasData(code)) {
EventDataType val = m_day->lookupValue(code, time);
text += " "+QString("%1: %2 %3").arg(schema::channel[code].label()).arg(val).arg(schema::channel[code].units());
}
}
int wid, h;
GetTextExtent(text, wid, h);

View File

@ -270,6 +270,8 @@ EventDataType *FlowParser::applyFilters(EventDataType *data, int samples)
return out;
}
// Opens the flow rate EventList, applies the input filter chain, and calculates peaks
void FlowParser::openFlow(Session *session, EventList *flow)
{
if (!flow) {
@ -286,8 +288,7 @@ void FlowParser::openFlow(Session *session, EventList *flow)
// Make sure we won't overflow internal buffers
if (m_samples > max_filter_buf_size) {
qDebug() <<
"Error: Sample size exceeds max_filter_buf_size in FlowParser::openFlow().. Capping!!!";
qDebug() << "Error: Sample size exceeds max_filter_buf_size in FlowParser::openFlow().. Capping!!!";
m_samples = max_filter_buf_size;
}
@ -304,6 +305,7 @@ void FlowParser::openFlow(Session *session, EventList *flow)
// Apply the rest of the filters chain
buf = applyFilters(m_filtered, m_samples);
// Scan for and create an index of each breath
calcPeaks(m_filtered, m_samples);
}
@ -330,8 +332,11 @@ void FlowParser::calcPeaks(EventDataType *input, int samples)
// Estimate storage space needed using typical average breaths per minute.
m_minutes = double(m_flow->last() - m_flow->first()) / 60000.0;
const double avgbpm = 20;
const double avgbpm = 20; // average breaths per minute of a standard human
int guestimate = m_minutes * avgbpm;
// reserve some memory
breaths.reserve(guestimate);
// Prime min & max, and see which side of the zero line we are starting from.
@ -340,13 +345,13 @@ void FlowParser::calcPeaks(EventDataType *input, int samples)
lastc = c;
m_startsUpper = (c >= zeroline);
qint32 start = 0, middle = 0; //,end=0;
qint32 start = 0, middle = 0;
int sps = 1000 / m_rate;
int len = 0, lastk = 0; //lastlen=0
int len = 0, lastk = 0;
qint64 sttime = time; //, ettime=time;
qint64 sttime = time;
// For each samples, find the peak upper and lower breath components
for (int k = 0; k < samples; k++) {
@ -362,13 +367,12 @@ void FlowParser::calcPeaks(EventDataType *input, int samples)
if ((max > 3) && ((max - min) > 8) && (len > sps) && (middle > start)) {
// peak detection may not be needed..
breaths.push_back(BreathPeak(min, max, start, middle, k)); //, peakmin, peakmax));
breaths.push_back(BreathPeak(min, max, start, middle, k));
// Set max for start of the upper breath cycle
max = c;
peakmax = time;
// Starting point of next breath cycle
start = k;
sttime = time;
@ -500,7 +504,7 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
if (calcMv) {
MV = m_session->AddEventList(CPAP_MinuteVent, EVL_Event);
MV->setGain(0.125);
MV->setGain(0.125); // gain set to 1/8th
}
EventDataType sps = (1000.0 / m_rate); // Samples Per Second
@ -509,19 +513,15 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
qint32 timeval = 0; // Time relative to start
BreathPeak * bpstr = breaths.data();
BreathPeak * bpend = bpstr + nm;
// For each breath...
for (BreathPeak * bp = bpstr; bp != bpend; ++bp) {
bs = bp->start;
bm = bp->middle;
be = bp->end;
// for (idx = 0; idx < nm; idx++) {
// bs = breaths[idx].start;
// bm = breaths[idx].middle;
// be = breaths[idx].end;
// Calculate start, middle and end time of this breath
st = start + bs * m_rate;
mt = start + bm * m_rate;
@ -530,15 +530,22 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
et = start + timeval;
/////////////////////////////////////////////////////////////////////
// Calculate Inspiratory Time (Ti) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTi) {
// Ti is simply the time between the start of a breath and it's midpoint
// Note the 50.0 is the gain value, to give it better resolution
// (and mt and st are in milliseconds)
ti = ((mt - st) / 1000.0) * 50.0;
ti1 = (lastti2 + lastti + ti) / 3.0;
Ti->AddEvent(mt, ti1);
// Average for a little smoothing
ti1 = (lastti2 + lastti + ti * 2) / 4.0;
Ti->AddEvent(mt, ti1); // Add the event
// Track the last two values to use for averaging
lastti2 = lastti;
lastti = ti;
}
@ -547,9 +554,11 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
// Calculate Expiratory Time (Te) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTe) {
// Te is simply the time between the second half of the breath
te = ((et - mt) / 1000.0) * 50.0;
// Average last three values..
te1 = (lastte2 + lastte + te) / 3.0;
te1 = (lastte2 + lastte + te * 2 ) / 4.0;
Te->AddEvent(mt, te1);
lastte2 = lastte;
@ -569,13 +578,29 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
val2 += c;
//val2+=c*c; // for RMS
}
tv = val2;
bool usebothhalves = false;
if (usebothhalves) {
for (int j = bm; j < be; j++) {
// convert flow to ml/s to L/min and divide by samples per second
c = double(qAbs(m_filtered[j])) * 1000.0 / 60.0 / sps;
val1 += c;
//val1 += c*c; // for RMS
}
tv = (qAbs(val2) + qAbs(val1)) / 2;
}
// Add the other half here and average might make it more accurate
// But last time I tried it was pretty messy
// Perhaps needs a bit of history averaging..
// calculate root mean square
//double n=bm-bs;
//double q=(1/n)*val2;
//double x=sqrt(q)*2;
//val2=x;
tv = val2;
if (tv < mintv) { mintv = tv; }
@ -600,17 +625,12 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
len = et - stmin;
rr = 0;
len2 = 0;
//if ( len >= minute) {
//et2=et;
// Step back through last minute and count breaths
BreathPeak *bpstr1 = bpstr-1;
for (BreathPeak *p = bp; p != bpstr1; --p) {
st2 = start + double(p->start) * m_rate;
et2 = start + double(p->end) * m_rate;
// for (int i = idx; i >= 0; i--) {
// st2 = start + double(breaths[i].start) * m_rate;
// et2 = start + double(breaths[i].end) * m_rate;
if (et2 < stmin) {
break;
@ -652,13 +672,13 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
*rr_dptr++ = rr * 5.0;
rr_count++;
//rr->AddEvent(et,br * 50.0);
//}
}
if (calcMv && calcResp && calcTv) {
// Minute Ventilation is tidal volume times respiratory rate
mv = (tv / 1000.0) * rr;
// The 8.0 is the gain of the MV EventList to boost resolution
MV->AddEvent(et, mv * 8.0);
}
}

View File

@ -1423,6 +1423,7 @@ bool PRS1SessionData::ParseWaveforms()
continue;
}
quint64 ti = quint64(waveform->timestamp) * 1000L;
qint64 dur = qint64(waveform->duration) * 1000L;
if (num > 1) {
// Process interleaved samples
@ -1439,19 +1440,19 @@ bool PRS1SessionData::ParseWaveforms()
} while (pos < size);
if (data[0].size() > 0) {
EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, (waveform->duration * 1000.0) / data[0].size());
flow->AddWaveform(ti, (char *)data[0].data(), data[0].size(), waveform->duration);
EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, dur / data[0].size());
flow->AddWaveform(ti, (char *)data[0].data(), data[0].size(), dur);
}
if (data[1].size() > 0) {
EventList * pres = session->AddEventList(CPAP_MaskPressureHi, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, (waveform->duration * 1000.0) / data[1].size());
pres->AddWaveform(ti, (unsigned char *)data[1].data(), data[1].size(), waveform->duration);
EventList * pres = session->AddEventList(CPAP_MaskPressureHi, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, dur / data[1].size());
pres->AddWaveform(ti, (unsigned char *)data[1].data(), data[1].size(), dur);
}
} else {
// Non interleaved, so can process it much faster
EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, (waveform->duration * 1000.0) / waveform->m_data.size());
flow->AddWaveform(ti, (char *)waveform->m_data.data(), waveform->m_data.size(), waveform->duration);
EventList * flow = session->AddEventList(CPAP_FlowRate, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, dur / waveform->m_data.size());
flow->AddWaveform(ti, (char *)waveform->m_data.data(), waveform->m_data.size(), dur);
}
}
return true;

View File

@ -960,27 +960,32 @@ EventDataType Session::SearchValue(ChannelID code, qint64 time)
quint32 *tptr;
int cnt;
if (code == CPAP_FlowRate) {
int i=5;
}
if (it != eventlist.end()) {
int el_size=it.value().size();
for (int i = 0; i < el_size; i++) {
EventList *el = it.value()[i];
if ((time < el->first()) || (time > el->last())) continue;
if ((time >= el->first())
&& (time < el->last())) {
cnt = el->count();
cnt = el->count();
if (el->type() == EVL_Waveform) {
qint64 tt = time - el->first();
qint64 i = tt / el->rate();
return el->data(i);
} else {
start = el->first();
tptr = el->rawTime();
if (el->type() == EVL_Waveform) {
qint64 tt = time - el->first();
qint64 i = tt / el->rate();
return el->data(i);
} else {
start = el->first();
tptr = el->rawTime();
for (int j = 0; j < cnt-1; j++) {
tptr++;
tt = start + *tptr;
if (tt > time) {
return el->data(j);
for (int j = 0; j < cnt-1; j++) {
tptr++;
tt = start + *tptr;
if (tt > time) {
return el->data(j);
}
}
}
}

View File

@ -51,7 +51,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="importTab">
<attribute name="title">
@ -712,144 +712,7 @@ p, li { white-space: pre-wrap; }
</item>
<item>
<layout class="QGridLayout" name="gridLayout_11">
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="complianceGroupbox">
<property name="toolTip">
<string>Don't show any compliance information</string>
</property>
<property name="title">
<string>Show Compliance</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<property name="spacing">
<number>4</number>
</property>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="complianceHours">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Regard days with under this usage as &quot;incompliant&quot;. 4 hours is usually considered compliant.</string>
</property>
<property name="suffix">
<string> hours</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>8.000000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_39">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>as over</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_40">
<property name="text">
<string>of usage per night</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_44">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>CPAP Clock Drift</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="AddRERAtoAHI">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
<string>Shows Respiratory Disturbance Index instead of Apnea/Hypopnea Index (RDI=AHI + RERA)</string>
</property>
<property name="text">
<string>Use RDI instead of AHI (PRS1 only)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="clockDrift">
<property name="toolTip">
<string>Don't touch this unless you know your CPAP clock is out.
Try to sync it to your PC's clock (which should be synced to a timeserver)</string>
</property>
<property name="suffix">
<string> seconds</string>
</property>
<property name="minimum">
<number>-7200</number>
</property>
<property name="maximum">
<number>7200</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<item row="5" column="0" rowspan="2" colspan="2">
<widget class="QGroupBox" name="customEventGroupbox">
<property name="toolTip">
<string>Enable/disable experimental event flagging enhancements.
@ -859,6 +722,9 @@ This option must be enabled before import, otherwise a purge is required.</strin
<property name="title">
<string>Custom User Event Flagging</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
@ -1036,11 +902,43 @@ p, li { white-space: pre-wrap; }
</layout>
</widget>
</item>
<item row="5" column="0" colspan="2">
<item row="1" column="0">
<widget class="QCheckBox" name="showLeakRedline">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Whether to show the leak redline in the leak graph</string>
</property>
<property name="text">
<string>Show Leak Redline</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="leakRedlineSpinbox">
<property name="toolTip">
<string>User definable threshold considered large leak</string>
</property>
<property name="suffix">
<string> L/min</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="ahiGraphGroupbox">
<property name="title">
<string>AHI/Hour Graph Settings</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
@ -1109,32 +1007,143 @@ Defaults to 60 minutes.. Highly recommend it's left at this value.</string>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="leakRedlineSpinbox">
<property name="toolTip">
<string>User definable threshold considered large leak</string>
<item row="0" column="0">
<widget class="QLabel" name="label_44">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="suffix">
<string> L/min</string>
</property>
<property name="decimals">
<number>1</number>
<property name="text">
<string>CPAP Clock Drift</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="showLeakRedline">
<item row="0" column="1">
<widget class="QSpinBox" name="clockDrift">
<property name="toolTip">
<string>Don't touch this unless you know your CPAP clock is out.
Try to sync it to your PC's clock (which should be synced to a timeserver)</string>
</property>
<property name="suffix">
<string> seconds</string>
</property>
<property name="minimum">
<number>-7200</number>
</property>
<property name="maximum">
<number>7200</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" rowspan="2" colspan="2">
<widget class="QGroupBox" name="complianceGroupbox">
<property name="toolTip">
<string>Don't show any compliance information</string>
</property>
<property name="title">
<string>Show Compliance</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<property name="spacing">
<number>4</number>
</property>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="complianceHours">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Regard days with under this usage as &quot;incompliant&quot;. 4 hours is usually considered compliant.</string>
</property>
<property name="suffix">
<string> hours</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>8.000000000000000</double>
</property>
<property name="value">
<double>4.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_39">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>as over</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_40">
<property name="text">
<string>of usage per night</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="AddRERAtoAHI">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="toolTip">
<string>Whether to show the leak redline in the leak graph</string>
<string>Shows Respiratory Disturbance Index instead of Apnea/Hypopnea Index (RDI=AHI + RERA)</string>
</property>
<property name="text">
<string>Show Leak Redline</string>
<string>Use RDI instead of AHI (PRS1 only)</string>
</property>
</widget>
</item>