Import profiling & optimisations. Fix summary chart pressure not showing for CPAP amongst APAP days.

This commit is contained in:
Mark Watkins 2012-01-10 01:38:41 +10:00
parent bd72f00041
commit e022cee75e
15 changed files with 318 additions and 346 deletions

View File

@ -167,7 +167,7 @@ void SummaryChart::SetDay(Day * nullday)
if (code==CPAP_Pressure) {
if ((cpapmode>MODE_CPAP) && (mode==MODE_CPAP)) {
hascode=false;
if ((type==ST_PERC) && (m_typeval[j]==0.5)) {
if ((type==ST_PERC) && (typeval==0.5)) {
type=ST_SETWAVG;
hascode=true;
}
@ -177,7 +177,7 @@ void SummaryChart::SetDay(Day * nullday)
}
if (hascode) {
m_days[dn]=day;
switch(m_type[j]) {
switch(type) {
case ST_AVG: tmp=day->avg(code); break;
case ST_SUM: tmp=day->sum(code); break;
case ST_WAVG: tmp=day->wavg(code); break;

View File

@ -90,10 +90,10 @@ void xpassFilter(EventDataType * input, EventDataType * output, int samples, Eve
// prime the first value
output[0]=input[0];
for (int i=1;i<samples-1;i++) {
for (int i=1;i<samples;i++) {
output[i]=weight*input[i] + (1.0-weight)*output[i-1];
}
output[samples-1]=input[samples-1];
//output[samples-1]=input[samples-1];
}
FlowParser::FlowParser()
@ -106,17 +106,14 @@ FlowParser::FlowParser()
m_startsUpper=true;
// Allocate filter chain buffers..
for (int i=0;i<num_filter_buffers;i++) {
m_buffers[i]=(EventDataType *) malloc(max_filter_buf_size);
}
m_filtered=(EventDataType *) malloc(max_filter_buf_size);
}
FlowParser::~FlowParser()
{
free (m_filtered);
for (int i=0;i<num_filter_buffers;i++) {
free(m_buffers[i]);
}
// for (int i=0;i<num_filter_buffers;i++) {
// free(m_buffers[i]);
// }
}
void FlowParser::clearFilters()
{
@ -130,6 +127,9 @@ EventDataType * FlowParser::applyFilters(EventDataType * data, int samples)
//qDebug() << "Trying to apply empty filter list in FlowParser..";
return NULL;
}
for (int i=0;i<num_filter_buffers;i++) {
m_buffers[i]=(EventDataType *) malloc(max_filter_buf_size);
}
int numfilt=m_filters.size();
@ -142,14 +142,15 @@ EventDataType * FlowParser::applyFilters(EventDataType * data, int samples)
return NULL;
}
} else {
in=m_buffers[(i+1) % 2];
out=m_buffers[i % 2];
in=m_buffers[(i+1) % num_filter_buffers];
out=m_buffers[i % num_filter_buffers];
}
// If final link in chain, pass it back out to input data
if (i==numfilt-1) {
out=data;
}
Filter & filter=m_filters[i];
if (filter.type==FilterNone) {
// Just copy it..
@ -161,6 +162,9 @@ EventDataType * FlowParser::applyFilters(EventDataType * data, int samples)
}
}
for (int i=0;i<num_filter_buffers;i++) {
free(m_buffers[i]);
}
return out;
}
void FlowParser::openFlow(Session * session, EventList * flow)
@ -186,16 +190,15 @@ void FlowParser::openFlow(Session * session, EventList * flow)
EventDataType * buf=m_filtered;
EventDataType c;
// Apply gain to waveform
for (int i=0;i<m_samples;i++) {
c= EventDataType(*inraw++) * m_gain;
*buf++ = c;
EventStoreType *eptr=inraw+m_samples;
// Convert from store type to floats..
for (; inraw < eptr; inraw++) {
*buf++ = EventDataType(*inraw) * m_gain;
}
// Apply the rest of the filters chain
buf=applyFilters(m_filtered, m_samples);
if (buf!=m_filtered) {
int i=5;
}
calcPeaks(m_filtered, m_samples);
}
@ -257,7 +260,8 @@ void FlowParser::calcPeaks(EventDataType * input, int samples)
len=k-start;
if ((max>3) && ((max-min) > 8) && (len>sps) && (middle > start)) {
breaths.push_back(BreathPeak(min, max, start, peakmax, middle, peakmin, k));
// peak detection may not be needed..
breaths.push_back(BreathPeak(min, max, start, middle, k)); //, peakmin, peakmax));
//EventDataType g0=(0-lastc) / (c-lastc);
//double d=(m_rate*g0);
//double d1=flowstart+ (start*rate);
@ -365,11 +369,11 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
EventList * Te=NULL, * Ti=NULL;
if (calcTi) {
Ti=m_session->AddEventList(CPAP_Ti,EVL_Event);
Ti->setGain(0.1);
Ti->setGain(0.02);
}
if (calcTe) {
Te=m_session->AddEventList(CPAP_Te,EVL_Event);
Te->setGain(0.1);
Te->setGain(0.02);
}
@ -425,7 +429,7 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
// Calculate Inspiratory Time (Ti) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTi) {
ti=(mt-st)/100.0;
ti=((mt-st)/1000.0)*50.0;
ti1=(lastti2+lastti+ti)/3.0;
Ti->AddEvent(mt,ti1);
lastti2=lastti;
@ -435,7 +439,7 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
// Calculate Expiratory Time (Te) for this breath
/////////////////////////////////////////////////////////////////////
if (calcTe) {
te=(et-mt)/100.0; // (/1000 * 10)
te=((et-mt)/1000.0)*50.0;
// Average last three values..
te1=(lastte2+lastte+te)/3.0;
Te->AddEvent(mt,te1);
@ -478,10 +482,8 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
if (stmin < start)
stmin=start;
len=et-stmin;
if (len < minute)
continue;
rr=0;
if (len >= minute) {
//et2=et;
// Step back through last minute and count breaths
@ -502,8 +504,10 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
}
// Calculate min & max
if (rr < minrr) minrr=rr;
if (rr > maxrr) maxrr=rr;
if (rr < minrr)
minrr=rr;
if (rr > maxrr)
maxrr=rr;
// Add manually.. (much quicker)
*rr_tptr++ = timeval;
@ -515,6 +519,7 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
//rr->AddEvent(et,br * 50.0);
}
}
if (calcMv && calcResp && calcTv) {
mv=(tv/1000.0) * rr;
MV->AddEvent(et,mv * 8.0);
@ -659,129 +664,6 @@ void FlowParser::flagEvents()
}
}
}
/* QVector<qint64> good;
good.reserve(numbreaths);
bool bad=false;
int bs,bm,be;
for (int idx=0;idx<numbreaths;idx++) {
bs=breaths[i].start;
bm=breaths[i].middle;
be=breaths[i].end;
mx=breaths[i].max;
mn=breaths[i].min;
val=mx - mn;
}
for (int i=0;i<numbreaths;i++) {
bs=breaths[i].start;
bm=breaths[i].middle;
be=breaths[i].end;
mx=breaths[i].max;
mn=breaths[i].min;
val=mx - mn;
et=start + be * m_rate;
if (et<lastet) continue;
if (val > cutoffval) continue;
int j=bs;
for (;j>0;j--) {
if (qAbs(m_filtered[j]) > cutoffval) {
bs=j;
break;
}
}
if (bs==be) continue;
j=be;
for (;j<m_samples;j++) {
if (qAbs(m_filtered[j]) > cutoffval) {
be=j;
break;
}
}
st=start + bs * m_rate;
mt=start + bm * m_rate;
et=start + be * m_rate;
len=et-st;
dur=len/1000.0;
if (dur>=duration) {
//if (!SearchApnea(m_session,st-len/2,15000)) {
if (!uf1) {
uf1=m_session->AddEventList(CPAP_UserFlag1,EVL_Event);
}
uf1->AddEvent(et-len/2,dur);
//}
}
// Uncomment to use UserFlags to show waveform crossover points
// Good for debugging this stuff. (Make sure to add the EventLists up above)
//if (val > cutoffval) {
//uf2->AddEvent(st,0);
//uf2->AddEvent(mt,0);
//uf3->AddEvent(et,0);
lastet=et;
lastst=st;
//}
}
return;
*/
//EventList *uf1=NULL;
// int lastbad=-1;
// qint64 firstbad=0;
// bool fr=false; // flow restriction
// for (int i=0;i<numbreaths;i++) {
// st=start+ breaths[i].start * m_rate;
// et=start+ breaths[i].end * m_rate;
// fr=false;
// int j=i;
// for (j=i;j<numbreaths;j++) {
// mx=breaths[j].max;
// mn=breaths[j].min;
// val=mx-mn;
// if (val > cutoffval)
// break;
// fr=true;
// et=start + breaths[j].end * m_rate;
// }
// if (fr) {
// i=j-1; // rewind
// len=et-st;
// dur=(len) / 1000.0;
// if (dur >= duration) {
// if (!uf1) {
// uf1=m_session->AddEventList(CPAP_UserFlag1,EVL_Event);
// }
// uf1->AddEvent(et-(len/2),dur);
// }
// }
// }
}
void calcRespRate(Session *session, FlowParser * flowparser)
@ -812,8 +694,11 @@ void calcRespRate(Session *session, FlowParser * flowparser)
bool calcMv=!session->eventlist.contains(CPAP_MinuteVent);
int z=(calcResp ? 1 : 0) + (calcTv ? 1 : 0) + (calcMv ? 1 : 0);
// If any of these three missing, remove all, and switch all on
if (!(calcResp & calcTv & calcMv)) {
if (z>0 && z<3) {
if (!calcResp && !calcTv && !calcMv)
calcTv=calcMv=calcResp=true;
QVector<EventList *> & list=session->eventlist[CPAP_RespRate];
@ -904,6 +789,8 @@ int calcAHIGraph(Session *session)
f;
EventList *AHI=new EventList(EVL_Event);
AHI->setGain(0.02);
session->eventlist[CPAP_AHI].push_back(AHI);
EventDataType ahi;
@ -931,7 +818,7 @@ int calcAHIGraph(Session *session)
ahi = events / hours;
AHI->AddEvent(t,ahi);
AHI->AddEvent(t,ahi * 50);
avg+=ahi;
cnt++;
}
@ -951,7 +838,7 @@ int calcAHIGraph(Session *session)
ahi=calcAHI(session,f,ti);
avg+=ahi;
cnt++;
AHI->AddEvent(ti,ahi);
AHI->AddEvent(ti,ahi * 50);
lastti=ti;
ti+=window_step;
}

View File

@ -43,24 +43,24 @@ struct Filter {
};
struct BreathPeak {
BreathPeak() { min=0; max=0; start=0; peakmax=0; middle=0; peakmin=0; end=0; }
BreathPeak(EventDataType _min, EventDataType _max, qint32 _start, qint64 _peakmax, qint32 _middle, qint64 _peakmin, qint32 _end) {
BreathPeak() { min=0; max=0; start=0; middle=0; end=0; } // peakmin=0; peakmax=0; }
BreathPeak(EventDataType _min, EventDataType _max, qint32 _start, qint32 _middle, qint32 _end) {//, qint64 _peakmin, qint64 _peakmax) {
min=_min;
max=_max;
start=_start;
middle=_middle;
end=_end;
peakmax=_peakmax;
peakmin=_peakmin;
//peakmax=_peakmax;
//peakmin=_peakmin;
}
BreathPeak(const BreathPeak & copy) {
min=copy.min;
max=copy.max;
start=copy.start;
peakmax=copy.peakmax;
middle=copy.middle;
peakmin=copy.peakmin;
end=copy.end;
//peakmin=copy.peakmin;
//peakmax=copy.peakmax;
}
int samplelength() { return end-start; }
int upperLength() { return middle-start; }
@ -69,10 +69,10 @@ struct BreathPeak {
EventDataType min; // peak value
EventDataType max; // peak value
qint32 start; // beginning zero cross
qint64 peakmax; // max peak index
qint32 middle; // ending zero cross
qint64 peakmin; // min peak index
qint32 end; // ending zero cross
//qint64 peakmin; // min peak index
//qint64 peakmax; // max peak index
};
bool operator<(const BreathPeak & p1, const BreathPeak & p2);

View File

@ -47,6 +47,41 @@ EventDataType EventList::data2(quint32 i)
return EventDataType(m_data2[i]);
}
void EventList::AddEvent(qint64 time, EventStoreType data)
{
m_data.push_back(data);
// Apply gain & offset
EventDataType val=EventDataType(data)*m_gain; // ignoring m_offset
if (m_update_minmax) {
if (m_min>val) m_min=val;
else if (m_max<val) m_max=val;
}
if (!m_first) {
m_first=time;
m_last=time;
}
if (m_first>time) {
// Crud.. Update all the previous records
// This really shouldn't happen.
qint32 t=(m_first-time);
for (quint32 i=0;i<m_count;i++) {
m_time[i]-=t;
}
m_first=time;
}
if (m_last < time)
m_last=time;
quint32 t=(time-m_first);
m_time.push_back(t);
m_count++;
}
void EventList::AddEvent(qint64 time, EventStoreType data, EventStoreType data2)
{
// Apply gain & offset
@ -78,7 +113,9 @@ void EventList::AddEvent(qint64 time, EventStoreType data, EventStoreType data2)
}
m_first=time;
}
if (m_last < time) m_last=time;
if (m_last < time)
m_last=time;
quint32 t=(time-m_first);
m_time.push_back(t);
@ -122,24 +159,25 @@ void EventList::AddWaveform(qint64 start, qint16 * data, int recs, qint64 durati
EventStoreType raw;
EventDataType val;
qint16 * sp=data;
qint16 * ep=data+recs;
qint16 * sp;
EventStoreType * dp=&edata[r];
if (m_update_minmax) {
for (int i=0;i<recs;i++) {
raw=*sp++;
val=EventDataType(raw)*m_gain+m_offset;
if (m_min>val) m_min=val;
if (m_max<val) m_max=val;
*dp=raw;
dp++;
register EventDataType min=m_min,max=m_max,val,gain=m_gain;
//if (m_offset;
for (sp=data; sp<ep; sp++) {
*dp++=raw=*sp;
val=EventDataType(*sp)*gain;
if (min > val) min=val;
if (max < val) max=val;
}
m_min=min;
m_max=max;
} else {
for (int i=0;i<recs;i++) {
raw=*(sp++);
for (sp=data; sp < ep; sp++) {
*dp++=raw=*sp;
val=EventDataType(raw)*m_gain+m_offset;
*dp=raw;
dp++;
}
}
@ -181,24 +219,24 @@ void EventList::AddWaveform(qint64 start, unsigned char * data, int recs, qint64
EventStoreType raw;
EventDataType val;
unsigned char * sp=data;
unsigned char * sp;
unsigned char * ep=data+recs;
EventStoreType * dp=&edata[r];
if (m_update_minmax) {
for (int i=0;i<recs;i++) {
raw=*sp++;
val=EventDataType(val)*m_gain+m_offset;
// ignoring m_offset
for (sp=data; sp < ep; sp++) {
raw=*sp;
val=EventDataType(raw)*m_gain;
if (m_min>val) m_min=val;
if (m_max<val) m_max=val;
*dp=raw;
dp++;
*dp++=raw;
}
} else {
for (int i=0;i<recs;i++) {
raw=*sp++;
val=EventDataType(val)*m_gain+m_offset;
*dp=raw;
dp++;
for (sp=data; sp < ep; sp++) {
raw=*sp;
val=EventDataType(raw)*m_gain;
*dp++=raw;
}
}
@ -242,24 +280,23 @@ void EventList::AddWaveform(qint64 start, char * data, int recs, qint64 duration
EventStoreType raw;
EventDataType val;
char * sp=data;
char * sp;
char * ep=data+recs;
EventStoreType * dp=&edata[r];
if (m_update_minmax) {
for (int i=0;i<recs;i++) {
raw=*sp++;
for (sp=data; sp < ep; sp++) {
raw=*sp;
val=EventDataType(val)*m_gain+m_offset;
if (m_min>val) m_min=val;
if (m_max<val) m_max=val;
*dp=raw;
dp++;
*dp++=raw;
}
} else {
for (int i=0;i<recs;i++) {
raw=*sp++;
for (sp=data; sp < ep; sp++) {
raw=*sp;
val=EventDataType(val)*m_gain+m_offset;
*dp=raw;
dp++;
*dp++=raw;
}
}
}

View File

@ -27,7 +27,8 @@ public:
/*! \brief Add an event starting at time, containing data to this event list
Note, data2 is only used if second_field is specified in the constructor */
void AddEvent(qint64 time, EventStoreType data, EventStoreType data2=0);
void AddEvent(qint64 time, EventStoreType data);
void AddEvent(qint64 time, EventStoreType data, EventStoreType data2);
void AddWaveform(qint64 start, qint16 * data, int recs, qint64 duration);
void AddWaveform(qint64 start, unsigned char * data, int recs, qint64 duration);
void AddWaveform(qint64 start, char * data, int recs, qint64 duration);

View File

@ -219,12 +219,12 @@ bool CMS50Loader::OpenSPORFile(QString path,Machine *mach,Profile *profile)
return false;
}
QDateTime last_pulse_time=date;
QDateTime last_spo2_time=date;
//QDateTime last_pulse_time=date;
//QDateTime last_spo2_time=date;
EventDataType last_pulse=buffer[0];
EventDataType last_spo2=buffer[1];
EventDataType cp,cs;
EventDataType cp=0,cs=0;
Session *sess=new Session(mach,sessid);
sess->updateFirst(starttime);

View File

@ -40,6 +40,7 @@ extern QProgressBar *qprogress;
QHash<int,QString> ModelMap;
#define PRS1_CRC_CHECK
#ifdef PRS1_CRC_CHECK
typedef quint16 crc_t;
@ -359,10 +360,11 @@ int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile)
}
// sessions are fully loaded here..
cnt++;
if ((++cnt % 10)==0) {
if (qprogress) qprogress->setValue(0.0+(float(cnt)/float(size)*100.0));
QApplication::processEvents();
}
}
// strictly can do this in the above loop, but this is cautionary
cnt=0;
//QVector<SessionID> KillList;
@ -417,9 +419,10 @@ int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile)
sess->SetChanged(true);
m->AddSession(sess,profile);
cnt++;
if (qprogress) qprogress->setValue(33.0+(float(cnt)/float(size)*33.0));
if ((++cnt % 10) ==0) {
//if (qprogress) qprogress->setValue(33.0+(float(cnt)/float(size)*33.0));
QApplication::processEvents();
}
}
@ -1029,34 +1032,38 @@ bool PRS1Loader::Parse002(qint32 sequence, quint32 timestamp, unsigned char *buf
}
bool PRS1Loader::ParseWaveform(qint32 sequence, quint32 timestamp, unsigned char *data, quint16 size, quint16 duration, quint16 num_signals, quint16 interleave, quint8 sample_format)
{
if (!new_sessions.contains(sequence))
return false;
//bool PRS1Loader::ParseWaveform(qint32 sequence, quint32 timestamp, unsigned char *data, quint16 size, quint16 duration, quint16 num_signals, quint16 interleave, quint8 sample_format)
//{
// Q_UNUSED(interleave)
// Q_UNUSED(sample_format)
// // this whole function is currently unused..
qint64 t=qint64(timestamp)*1000L;
// if (!new_sessions.contains(sequence))
// return false;
Session *session=new_sessions[sequence];
// qint64 t=qint64(timestamp)*1000L;
if (num_signals==1) {
session->updateFirst(t);
double d=duration*1000;
EventDataType rate=d / EventDataType(size);
EventList *ev=session->AddEventList(CPAP_FlowRate,EVL_Waveform,1.0,0.00,0,0,rate);
ev->AddWaveform(t,(char *)data,size,qint64(duration)*1000L);
session->updateLast(t+qint64(duration)*1000L);
// Session *session=new_sessions[sequence];
}
// if (num_signals==1) {
// session->updateFirst(t);
// double d=duration*1000;
// EventDataType rate=d / EventDataType(size);
// EventList *ev=session->AddEventList(CPAP_FlowRate,EVL_Waveform,1.0,0.00,0,0,rate);
// ev->AddWaveform(t,(char *)data,size,qint64(duration)*1000L);
// session->updateLast(t+qint64(duration)*1000L);
return true;
}
// }
// return true;
//}
bool PRS1Loader::OpenFile(Machine *mach, QString filename)
{
int sequence,version;
quint32 timestamp;
qint64 pos;
unsigned char ext,htype,sum;
unsigned char ext,sum, htype;
unsigned char *header,*data;
int chunk,hl;
quint16 size,datasize,c16,crc;
@ -1090,6 +1097,7 @@ bool PRS1Loader::OpenFile(Machine *mach, QString filename)
size=(header[2] << 8) | header[1];
htype=header[3]; // 00 = normal // 01=waveform // could be a bool?
Q_UNUSED(htype);
version=header[4]; // == 5
ext=header[6];
sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
@ -1097,6 +1105,7 @@ bool PRS1Loader::OpenFile(Machine *mach, QString filename)
if (ext==5) {
duration=header[0xf] | header[0x10] << 8; // block duration in seconds
Q_UNUSED(duration);
num_signals=header[0x12] | header[0x13] << 8;
if (num_signals>2) {
qWarning() << "More than 2 Waveforms in " << filename;
@ -1265,6 +1274,8 @@ bool PRS1Loader::OpenWaveforms(SessionID sid, QString filename)
}
length=m_buffer[pos+0x1] | m_buffer[pos+0x2] << 8; // block length in bytes
duration=m_buffer[pos+0xf] | m_buffer[pos+0x10] << 8; // block duration in seconds
if (diff<0) {
qDebug() << "Padding waveform to keep sync" << block;
//diff=qAbs(diff);

View File

@ -79,8 +79,8 @@ protected:
//! \brief Parse a .005 waveform file, extracting Flow Rate waveform (and Mask Pressure data if available)
bool OpenWaveforms(SessionID sid, QString filename);
//! \brief ParseWaveform chunk.. Currently unused, as the old one works fine.
bool ParseWaveform(qint32 sequence, quint32 timestamp, unsigned char *data, quint16 size, quint16 duration, quint16 num_signals, quint16 interleave, quint8 sample_format);
// //! \brief ParseWaveform chunk.. Currently unused, as the old one works fine.
//bool ParseWaveform(qint32 sequence, quint32 timestamp, unsigned char *data, quint16 size, quint16 duration, quint16 num_signals, quint16 interleave, quint8 sample_format);
//! \brief Parse a data chunk from the .000 (brick) and .001 (summary) files.
bool ParseSummary(Machine *mach, qint32 sequence, quint32 timestamp, unsigned char *data, quint16 size, char version);

View File

@ -584,10 +584,12 @@ int ResmedLoader::Open(QString & path,Profile *profile)
// Push current filename to ordered-by-sessionid list
sessfiles[sessionid].push_back(filename);
if ((i%10) ==0) {
// Update the progress bar
if (qprogress) qprogress->setValue((float(i+1)/float(size)*10.0));
QApplication::processEvents();
}
}
QString fn;
Session *sess;
@ -932,6 +934,7 @@ int ResmedLoader::Open(QString & path,Profile *profile)
/////////////////////////////////////////////////////////////////////
// Duration and Event Indices
/////////////////////////////////////////////////////////////////////
dur=0;
if ((sig=stredf.lookupName("Mask Dur"))) {
dur=sig->data[dn]*sig->gain;
dur/=60.0f; // convert to hours.
@ -1044,8 +1047,10 @@ int ResmedLoader::Open(QString & path,Profile *profile)
else if (fn=="brp") LoadBRP(sess,edf);
else if (fn=="sad") LoadSAD(sess,edf);
}
if (qprogress) qprogress->setValue(10.0+(float(++cnt)/float(size)*90.0));
if ((++cnt%10) ==0) {
if (qprogress) qprogress->setValue(10.0+(float(cnt)/float(size)*90.0));
QApplication::processEvents();
}
if (!sess) continue;
if (!sess->first()) {
@ -1342,9 +1347,6 @@ bool ResmedLoader::LoadBRP(Session *sess,EDFParser &edf)
//qDebug() << "BRP:" << es.digital_maximum << es.digital_minimum << es.physical_maximum << es.physical_minimum;
long recs=es.nr*edf.GetNumDataRecords();
ChannelID code;
if (es.offset>0) {
int i=5;
}
if (es.label=="Flow") {
es.gain*=60;
es.physical_dimension="L/M";
@ -1372,37 +1374,43 @@ EventList * ResmedLoader::ToTimeDelta(Session *sess,EDFParser &edf, EDFSignal &
QElapsedTimer time;
time.start();
#endif
bool first=true;
double rate=(duration/recs); // milliseconds per record
double tt=edf.startdate;
//sess->UpdateFirst(tt);
EventDataType c,last;
EventList *el=sess->AddEventList(code,EVL_Event,es.gain,es.offset,min,max);
int startpos=0;
if ((code==CPAP_Pressure) || (code==CPAP_IPAP) || (code==CPAP_EPAP)) {
startpos=20; // Shave the first 20 seconds of pressure data
tt+=rate*startpos;
}
for (int i=startpos;i<recs;i++) {
c=es.data[i];
qint16 * sptr=es.data;
qint16 * eptr=sptr+recs;
sptr+=startpos;
EventList *el=NULL;
if (recs>startpos+1) {
el=sess->AddEventList(code,EVL_Event,es.gain,es.offset,min,max);
c=last=*sptr++;
el->AddEvent(tt,last);
for (; sptr < eptr; sptr++) { //int i=startpos;i<recs;i++) {
c=*sptr; //es.data[i];
if (first) {
el->AddEvent(tt,c);
first=false;
} else {
if (last!=c) {
if (square) el->AddEvent(tt,last); // square waves look better on some charts.
el->AddEvent(tt,c);
}
}
tt+=rate;
last=c;
}
el->AddEvent(tt,c);
sess->updateLast(tt);
}
#ifdef DEBUG_EFFICIENCY
@ -1472,15 +1480,12 @@ bool ResmedLoader::LoadPLD(Session *sess,EDFParser &edf)
sess->updateLast(edf.startdate+duration);
QString t;
int emptycnt=0;
EventList *a;
EventList *a=NULL;
double rate;
long recs;
ChannelID code;
for (int s=0;s<edf.GetNumSignals();s++) {
EDFSignal & es=*edf.edfsignals[s];
if (es.offset>0) {
int i=5;
}
recs=es.nr*edf.GetNumDataRecords();
if (recs<=0) continue;
rate=double(duration)/double(recs);

View File

@ -268,6 +268,7 @@ bool Machine::Load()
ext_s=fi.fileName().section(".",-1);
ext=ext_s.toInt(&ok,10);
if (!ok) continue;
sesstr=fi.fileName().section(".",0,-2);
sessid=sesstr.toLong(&ok,16);
if (!ok) continue;
@ -278,12 +279,10 @@ bool Machine::Load()
int size=sessfiles.size();
int cnt=0;
for (s=sessfiles.begin(); s!=sessfiles.end(); s++) {
cnt++;
if ((cnt % 10)==0) {
if ((++cnt % 50)==0) { // This is slow.. :-/
if (qprogress) qprogress->setValue((float(cnt)/float(size)*100.0));
}
QApplication::processEvents();
}
Session *sess=new Session(this,s.key());
@ -330,8 +329,10 @@ bool Machine::Save()
savelistSize=m_savelist.size();
if (!PROFILE.session->multithreading()) {
for (int i=0;i<savelistSize;i++) {
if ((i % 10) ==0) {
qprogress->setValue(0+(float(savelistCnt)/float(savelistSize)*100.0));
QApplication::processEvents();
}
Session *s=m_savelist.at(i);
s->UpdateSummaries();
s->Store(path);

View File

@ -215,6 +215,7 @@ bool Session::LoadSummary(QString filename)
QHash<ChannelID,EventDataType> cruft;
if (version<7) {
// This code is deprecated.. just here incase anyone tries anything crazy...
QHash<QString,QVariant> v1;
in >> v1;
settings.clear();
@ -320,6 +321,7 @@ bool Session::LoadSummary(QString filename)
}
// not really a good idea to do this... should flag and do a reindex
if (version < summary_version) {
qDebug() << "Upgrading Summary file to version" << summary_version;
@ -428,7 +430,11 @@ bool Session::StoreEvents(QString filename)
qint32 datasize=databytes.size();
// Checksum the _uncompressed_ data
quint16 chk=qChecksum(databytes.data(),databytes.size());
quint16 chk=0;
if (compress) {
// This checksum is hideously slow.. only using during compression, and not sure I should at all :-/
chk=qChecksum(databytes.data(),databytes.size());
}
header << datasize;
header << chk;
@ -654,12 +660,14 @@ void Session::updateCountSummary(ChannelID code)
qint64 start,time,lasttime=0;
qint32 len,cnt;
quint32 * tptr;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
for (int i=0;i<ev.value().size();i++) {
EventList & e=*(ev.value()[i]);
start=e.first();
cnt=e.count();
dptr=e.rawData();
eptr=dptr+cnt;
EventDataType rate=0;
m_gain[code]=e.gain();
@ -669,9 +677,11 @@ void Session::updateCountSummary(ChannelID code)
tptr=e.rawTime();
lasttime=start + *tptr;
// Event version
for (int j=0;j<cnt;j++) {
for (;dptr < eptr; dptr++) {
time=start + *tptr++;
raw=*dptr++;
raw=*dptr;
valsum[raw]++;
@ -685,8 +695,8 @@ void Session::updateCountSummary(ChannelID code)
}
} else {
// Waveform version, first just count
for (int j=0;j<cnt;j++) {
raw=*dptr++;
for (;dptr < eptr; dptr++) {
raw=*dptr;
valsum[raw]++;
}
@ -954,8 +964,9 @@ int Session::rangeCount(ChannelID id, qint64 first,qint64 last)
cnt=ev.count();
start=ev.first();
quint32 * tptr=ev.rawTime();
for (int j=0;j<cnt;j++) {
t=start + *tptr++;
quint32 * eptr=tptr+cnt;
for (;tptr < eptr; tptr++) {
t=start + *tptr;
if (t >= first) {
if (t<=last) {
@ -977,7 +988,7 @@ double Session::rangeSum(ChannelID id, qint64 first,qint64 last)
double sum=0,gain;
qint64 t,start;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
quint32 * tptr;
int cnt,idx;
@ -989,6 +1000,7 @@ double Session::rangeSum(ChannelID id, qint64 first,qint64 last)
start=ev.first();
dptr=ev.rawData();
cnt=ev.count();
eptr=dptr+cnt;
gain=ev.gain();
rate=ev.rate();
if (ev.type()==EVL_Waveform) {
@ -996,24 +1008,26 @@ double Session::rangeSum(ChannelID id, qint64 first,qint64 last)
// Skip the samples before first
idx=(first - ev.first()) / rate;
}
dptr+=idx; //???? foggy.
t=start;
for (int j=idx;j<cnt;j++) {
for (;dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
if (t <= last) {
sum+=EventDataType(*dptr) * gain;
} else break;
dptr++;
t+=rate;
}
} else {
tptr=ev.rawTime();
for (int j=0;j < cnt;j++) {
for (;dptr < eptr; dptr++) {
t=start + *tptr++;
if (t >= first) {
if (t <= last) {
sum+=EventDataType(*dptr) * gain;
} else break;
}
dptr++;
}
}
}
@ -1029,7 +1043,7 @@ EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
EventDataType gain,v,min=999999999;
qint64 t,start,rate;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
quint32 * tptr;
int cnt,idx;
@ -1041,6 +1055,7 @@ EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
dptr=ev.rawData();
start=ev.first();
cnt=ev.count();
eptr=dptr+cnt;
gain=ev.gain();
if (ev.type()==EVL_Waveform) {
@ -1052,19 +1067,19 @@ EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
// Skip the samples before first
idx=(first - ev.first())/rate;
}
dptr+=idx;
for (int j=idx;j<cnt;j++) {
for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
if (t<=last) {
v=EventDataType(*dptr) * gain;
if (v<min)
min=v;
} else break;
dptr++;
t+=rate;
}
} else {
tptr=ev.rawTime();
for (int j=0;j<cnt;j++) {
for (; dptr < eptr; dptr++) { //int j=0;j<cnt;j++) {
t=start + *tptr++;
if (t >= first) {
if (t <= last) {
@ -1073,7 +1088,6 @@ EventDataType Session::rangeMin(ChannelID id, qint64 first,qint64 last)
min=v;
} else break;
}
dptr++;
}
}
}
@ -1089,7 +1103,7 @@ EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
EventDataType gain,v,max=-999999999;
qint64 t,start,rate;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
quint32 * tptr;
int cnt,idx;
@ -1101,6 +1115,7 @@ EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
start=ev.first();
dptr=ev.rawData();
cnt=ev.count();
eptr=dptr + cnt;
gain=ev.gain();
if (ev.type()==EVL_Waveform) {
rate=ev.rate();
@ -1111,17 +1126,17 @@ EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
// Skip the samples before first
idx=(first - ev.first())/rate;
}
for (int j=idx;j<cnt;j++) {
dptr+=idx;
for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
if (t<=last) {
v=EventDataType(*dptr) * gain;
if (v>max) max=v;
} else break;
dptr++;
t+=rate;
}
} else {
tptr=ev.rawTime();
for (int j=0;j<cnt;j++) {
for (;dptr < eptr; dptr++) {
t=start + *tptr++;
if (t>=first) {
if (t<=last) {
@ -1129,7 +1144,6 @@ EventDataType Session::rangeMax(ChannelID id, qint64 first,qint64 last)
if (v>max) max=v;
} else break;
}
dptr++;
}
}
}
@ -1171,15 +1185,16 @@ double Session::sum(ChannelID id)
QVector<EventList *> & evec=j.value();
double gain,sum=0;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
int cnt;
for (int i=0;i<evec.size();i++) {
EventList & ev=*(evec[i]);
gain=ev.gain();
cnt=ev.count();
dptr=ev.rawData();
eptr=dptr+cnt;
for (int j=0;j<cnt;j++) {
for (; dptr < eptr; dptr++) {
sum+=double(*dptr) * gain;
}
}
@ -1202,17 +1217,17 @@ EventDataType Session::avg(ChannelID id)
double val=0,gain;
int cnt=0;
EventStoreType * dptr;
EventStoreType * dptr, * eptr;
int es;
for (int i=0;i<evec.size();i++) {
EventList & ev=*(evec[i]);
dptr=ev.rawData();
gain=ev.gain();
es=ev.count();
cnt=ev.count();
eptr=dptr+cnt;
for (int j=0;j<es;j++) {
val+=double(*dptr++) * gain;
cnt++;
for (; dptr < eptr; dptr++) {
val+=double(*dptr) * gain;
}
}
@ -1268,7 +1283,7 @@ EventDataType Session::percentile(ChannelID id,EventDataType percent)
EventDataType gain=evec[0]->gain();
EventStoreType * dptr, * sptr;
EventStoreType * dptr, * sptr, *eptr;
int tt=0,cnt;
for (int i=0;i<size;i++) {
@ -1283,9 +1298,9 @@ EventDataType Session::percentile(ChannelID id,EventDataType percent)
sptr=ev.rawData();
dptr=array.data();
for (int j=0;j<cnt;j++) {
*dptr++ = * sptr++;
//array.push_back(evec[i]->raw(j));
eptr=sptr+cnt;
for (; sptr < eptr; sptr++) {
*dptr++ = * sptr;
}
}

View File

@ -105,13 +105,13 @@ Daily::Daily(QWidget *parent,gGraphView * shared)
RR=new gGraph(GraphView,tr("Resp. Rate"),schema::channel[CPAP_RespRate].description()+"\n("+schema::channel[CPAP_RespRate].units()+")",default_height);
TV=new gGraph(GraphView,tr("Tidal Volume"),schema::channel[CPAP_TidalVolume].description()+"\n("+schema::channel[CPAP_TidalVolume].units()+")",default_height);
MV=new gGraph(GraphView,tr("Minute Vent."),schema::channel[CPAP_MinuteVent].description()+"\n("+schema::channel[CPAP_MinuteVent].units()+")",default_height);
TgMV=new gGraph(GraphView,tr("Tgt. Min. Vent"),schema::channel[CPAP_TgMV].description()+"\n("+schema::channel[CPAP_TgMV].units()+")",default_height);
FLG=new gGraph(GraphView,tr("Flow Limitation"),schema::channel[CPAP_FLG].description()+"\n("+schema::channel[CPAP_FLG].units()+")",default_height);
PTB=new gGraph(GraphView,tr("Pat. Trig. Breath"),schema::channel[CPAP_PTB].description()+"\n("+schema::channel[CPAP_PTB].units()+")",default_height);
RE=new gGraph(GraphView,tr("Resp. Event"),schema::channel[CPAP_RespEvent].description()+"\n("+schema::channel[CPAP_RespEvent].units()+")",default_height);
TI=new gGraph(GraphView,tr("Insp. Time"),schema::channel[CPAP_Ti].description()+"\n("+schema::channel[CPAP_Ti].units()+")",default_height);
TE=new gGraph(GraphView,tr("Exp. Time"),schema::channel[CPAP_Te].description()+"\n("+schema::channel[CPAP_Te].units()+")",default_height);
IE=new gGraph(GraphView,tr("IE"),schema::channel[CPAP_IE].description()+"\n("+schema::channel[CPAP_IE].units()+")",default_height);
TE=new gGraph(GraphView,tr("Te"),schema::channel[CPAP_Te].description()+"\n("+schema::channel[CPAP_Te].units()+")",default_height);
TI=new gGraph(GraphView,tr("Ti"),schema::channel[CPAP_Ti].description()+"\n("+schema::channel[CPAP_Ti].units()+")",default_height);
TgMV=new gGraph(GraphView,tr("Tgt. Min. Vent"),schema::channel[CPAP_TgMV].description()+"\n("+schema::channel[CPAP_TgMV].units()+")",default_height);
int oxigrp=PROFILE.ExistsAndTrue("SyncOximetry") ? 0 : 1;
PULSE=new gGraph(GraphView,STR_TR_PulseRate,schema::channel[OXI_Pulse].description()+"\n("+schema::channel[OXI_Pulse].units()+")",default_height,oxigrp);
@ -136,6 +136,7 @@ Daily::Daily(QWidget *parent,gGraphView * shared)
evseg->AddSlice(CPAP_RERA,QColor(0xff,0xff,0x80,0xff),tr("RE"));
evseg->AddSlice(CPAP_NRI,QColor(0x00,0x80,0x40,0xff),tr("NR"));
evseg->AddSlice(CPAP_FlowLimit,QColor(0x40,0x40,0x40,0xff),tr("FL"));
//evseg->AddSlice(CPAP_UserFlag1,QColor(0x40,0x40,0x40,0xff),tr("UF"));
GAHI->AddLayer(AddCPAP(evseg));
GAHI->setMargins(0,0,0,0);
@ -254,6 +255,7 @@ Daily::Daily(QWidget *parent,gGraphView * shared)
// lc->addPlot(CPAP_Test1,Qt::darkRed,square);
MV->AddLayer(AddCPAP(new gLineChart(CPAP_MinuteVent,Qt::darkCyan,square)));
TV->AddLayer(AddCPAP(lc=new gLineChart(CPAP_TidalVolume,Qt::magenta,square)));
//lc->addPlot(CPAP_Test2,Qt::darkYellow,square);
@ -825,10 +827,6 @@ void Daily::Load(QDate date)
html+=QString("<tr><td align='left' bgcolor='%1'><b><font color='%2'><a class=info href='event=%6'>%3<span>%4</span></a></font></b></td><td width=20% bgcolor='%1'><b><font color='%2'>%5%</font></b></td></tr>\n")
.arg(schema::channel[CPAP_NRI].defaultColor().name()).arg("black").arg(tr("NRI")).arg(schema::channel[CPAP_NRI].description()).arg(nri,0,'f',2).arg(CPAP_NRI);
}
if (cpap->machine->GetClass()==STR_MACH_PRS1) {
html+=QString("<tr><td align='left' bgcolor='%1'><b><font color='%2'><a class=info href='event=%6'>%3<span>%4</span></a></font></b></td><td width=20% bgcolor='%1'><b><font color='%2'>%5%</font></b></td></tr>\n")
.arg("#80ff80").arg("black").arg(tr("PB/CSR")).arg(schema::channel[CPAP_CSR].description()).arg(csr,0,'f',2).arg(CPAP_CSR);
}
if (PROFILE.cpap->userEventFlagging()) {
EventDataType uf1=cpap->count(CPAP_UserFlag1) / cpap->hours();
if (uf1>0)
@ -852,10 +850,13 @@ void Daily::Load(QDate date)
html+=QString("<tr><td align='left' bgcolor='%1'><b><font color='%2'><a class=info2 href='event=%6'>%3<span>%4</span></a></font></b></td><td width=20% bgcolor='%1'><b><font color='%2'>%5</font></b></td></tr>\n")
.arg("#ff4040").arg("black").arg(tr("VSnore")).arg(schema::channel[CPAP_VSnore].description()).arg(cpap->count(CPAP_VSnore)/cpap->hours(),0,'f',2).arg(CPAP_VSnore);
} else {
html+="<tr bgcolor='#404040'><td colspan=2>&nbsp;</td></tr>";
//html+="<tr bgcolor='#404040'><td colspan=2>&nbsp;</td></tr>";
html+=QString("<tr><td align='left' bgcolor='%1'><b><font color='%2'><a class=info2 href='event=%6'>%3<span>%4</span></a></font></b></td><td width=20% bgcolor='%1'><b><font color='%2'>%5</font></b></td></tr>\n")
.arg("#ff4040").arg("black").arg(tr("VSnore2")).arg(schema::channel[CPAP_VSnore2].description()).arg(cpap->count(CPAP_VSnore2)/cpap->hours(),0,'f',2).arg(CPAP_VSnore2);
}
html+=QString("<tr><td align='left' bgcolor='%1'><b><font color='%2'><a class=info href='event=%6'>%3<span>%4</span></a></font></b></td><td width=20% bgcolor='%1'><b><font color='%2'>%5%</font></b></td></tr>\n")
.arg("#80ff80").arg("black").arg(tr("PB/CSR")).arg(schema::channel[CPAP_CSR].description()).arg(csr,0,'f',2).arg(CPAP_CSR);
html+="</table></td>";
} else if (cpap->machine->GetClass()==STR_MACH_Intellipap) {
html+="<td colspan=2 valign=top><table cellspacing=0 cellpadding=2 border=0 width='100%'>";

View File

@ -42,8 +42,8 @@ Important: One id code per item, DO NOT CHANGE ID NUMBERS!!!
<channel id="0x1107" class="data" name="PTB" details="Patient Triggered Breaths" label="Pat. Trig. Breaths" unit="%" color="dark grey"/>
<channel id="0x1108" class="data" name="Leak" details="Leak Rate" label="Leaks" unit="L/min" color="dark green"/>
<channel id="0x1109" class="data" name="IE" details="Inspiratory:Expiratory" label="I:E" unit="ratio" color="dark red"/>
<channel id="0x110a" class="data" name="Te" details="Expiratory Time" label="Te" unit="" color="dark green"/>
<channel id="0x110b" class="data" name="Ti" details="Inspiratory Time" label="Ti" unit="" color="dark blue"/>
<channel id="0x110a" class="data" name="Te" details="Expiratory Time" label="Exp Time" unit="seconds" color="dark green"/>
<channel id="0x110b" class="data" name="Ti" details="Inspiratory Time" label="Insp Time" unit="seconds" color="dark blue"/>
<channel id="0x110c" class="data" name="Pressure" details="Pressure" label="Pressure" unit="cmH20" color="dark green"/>
<channel id="0x110d" class="data" name="IPAP" details="Inspiratory Pressure" label="IPAP" unit="cmH20" color="orange"/>
<channel id="0x110e" class="data" name="EPAP" details="Expiratory Pressure" label="EPAP" unit="cmH20" color="light blue"/>

View File

@ -24,6 +24,7 @@
<li>Option to automatically maintain backup folder for ResMed users. (on by default)</li>
<li>Compression options to save disk space for SleepyHead data and backups.</li>
<li>Rewritten Flow Rate Calculations Module gives MinuteVent, RespiratoryRate, TidalVolume, Te & Ti to machines where it's missing, (provided Flow Waveform is available) and is much more accurate than before.</li>
<li>User Event flagging now works on ResMed machines too. It must be switched on before import.</li>
<li>New Context cube to make empty pages more attractive.. Yes you can switch it off. No it doesn't take much resources.</li>
<li>Plenty of other bug fixes, including more oximetry fixes.</li>
</list></p>
@ -36,7 +37,8 @@
The hard stuffs done, and everything needs time to settle and have the bugs knocked out of it, however there still are some minor tweaks and features in the pipeline...</p>
<list>
<li>Lots more Performance Optimizations</li>
<li>Graphical tweaks, animations & improvements</li>
<li>Better custom event flagging (including a workaround for the dreaded ResMed S9 drift bug)</li>
<li>More graphical tweaks, animations & improvements</li>
<li>Improving the quality of printed report data, and showing more data and statistics.</li>
<li>Improving the CSV export feature, and making it more customizable</li>
</list>

View File

@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>640</width>
<height>407</height>
<height>414</height>
</rect>
</property>
<property name="maximumSize">
@ -42,7 +42,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="importTab">
<attribute name="title">
@ -312,24 +312,26 @@ p, li { white-space: pre-wrap; }
<property name="toolTip">
<string>This maintains a backup of SD-card data for ResMed machines,
ResMed machines delete high resolution data older than 7 days, and graph data older than 30 days..
ResMed machines delete high resolution data older than 7 days,
and graph data older than 30 days..
Sleepyhead can keep a copy of this data if you ever need to reinstall.
(Highly recomended, unless your short on disk space and don't care about the graph data)</string>
(Highly recomended, unless your short on disk space or don't care about the graph data)</string>
</property>
<property name="text">
<string>Create SD Card Backups during Import (only works with ResMed for now)</string>
<string>Create SD Card Backups during Import (only for ResMed so far, highly recommended)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="compressSessionData">
<property name="toolTip">
<string>This makes SleepyHead's data take less disk space (about half as much),
but it makes import take longer, and also makes switching between days a little slower.</string>
<string>This makes SleepyHead's data take around half as much space.
But it makes import and day changing take longer..
If you've got a new computer with a small solid state disk, this is a good option.</string>
</property>
<property name="text">
<string>Compress Session Data (slower, but makes SleepyHead data smaller)</string>
<string>Compress Session Data (makes SleepyHead data smaller, but day changing slower.)</string>
</property>
</widget>
</item>
@ -344,7 +346,7 @@ SleepyHead can import from this compressed backup directory natively..
To use with ResScan will require the .gz files to be uncompressed first..</string>
</property>
<property name="text">
<string>Compress SD Card Backups (slower, but makes backups smaller)</string>
<string>Compress SD Card Backups (slower first import, but makes backups smaller)</string>
</property>
</widget>
</item>
@ -370,7 +372,7 @@ To use with ResScan will require the .gz files to be uncompressed first..</strin
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Note: &lt;span style=&quot; font-style:italic;&quot;&gt;Compression options don't automatically recompress already saved data.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Note: &lt;span style=&quot; font-style:italic;&quot;&gt;Compression options don't automatically recompress already saved data. (yet)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -690,9 +692,8 @@ p, li { white-space: pre-wrap; }
<widget class="QGroupBox" name="customEventGroupbox">
<property name="toolTip">
<string>Enable/disable experimental event flagging enhancements.
It allows detecting borderline events on PRS1 machines..
Has no effect on other machines (yet).</string>
It allows detecting borderline events, and some the machine missed.
This option must be enabled before import, otherwise a purge is required.</string>
</property>
<property name="title">
<string>Custom PRS1 Event Flagging</string>
@ -729,7 +730,8 @@ Has no effect on other machines (yet).</string>
</sizepolicy>
</property>
<property name="toolTip">
<string>Percentage of restriction in airflow</string>
<string>Percentage of restriction in airflow from the median value.
A value of 20% works well for detecting apneas. </string>
</property>
<property name="suffix">
<string>%</string>
@ -833,7 +835,8 @@ p, li { white-space: pre-wrap; }
<item row="0" column="1">
<widget class="QSpinBox" name="ahiGraphWindowSize">
<property name="toolTip">
<string>Adjusts the amount of data considered for each point in the AHI/Hour graph.</string>
<string>Adjusts the amount of data considered for each point in the AHI/Hour graph.
Defaults to 60 minutes.. Highly recommend it's left at this value.</string>
</property>
<property name="suffix">
<string> minutes</string>
@ -852,7 +855,8 @@ p, li { white-space: pre-wrap; }
<item row="0" column="2">
<widget class="QCheckBox" name="ahiGraphZeroReset">
<property name="toolTip">
<string>Reset the counter to zero at beginning of each (time) window</string>
<string>Reset the counter to zero at beginning of each (time) window.
Note: Unless you purge and reimport after changing this, you won't see the changes.</string>
</property>
<property name="text">
<string>Zero Reset</string>
@ -1089,6 +1093,9 @@ p, li { white-space: pre-wrap; }
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Tries to forces the oximetry data to link with CPAP when possible.</string>
</property>
<property name="text">
<string>Link Oximetry and CPAP graphs</string>
</property>
@ -1433,6 +1440,9 @@ Mainly affects the importer.</string>
</item>
<item>
<widget class="QSpinBox" name="updateCheckEvery">
<property name="toolTip">
<string>Sourceforge hosts this project for free.. Please be considerate of their resources..</string>
</property>
<property name="maximum">
<number>90</number>
</property>
@ -1613,7 +1623,8 @@ Mainly affects the importer.</string>
</sizepolicy>
</property>
<property name="toolTip">
<string>The visual method of displaying waveform overlay flags.</string>
<string>The visual method of displaying waveform overlay flags.
</string>
</property>
<item>
<property name="text">
@ -1725,7 +1736,8 @@ this application to be unstable with this feature enabled.</string>
<item>
<widget class="QCheckBox" name="animationsAndTransitionsCheckbox">
<property name="toolTip">
<string>Turn on/off the spinning &quot;context&quot; cube.</string>
<string>Turn on/off the spinning &quot;context&quot; cube.
It really doesn't use that much resources.. :)</string>
</property>
<property name="text">
<string>Animations &amp;&amp; Fancy Stuff</string>