mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-08 12:10:43 +00:00
Fix PRS1 F3V3 parser to use ending timestamps like all the other parsers.
This fixes a regression introduced by fda9fcd1
, which fixed import for
all the other machines but broke F3V3.
Also move the generation of HY/CA/OA events out of the parser and into
the importer.
This commit is contained in:
parent
19facdba8f
commit
3c214db13d
@ -1090,6 +1090,9 @@ enum PRS1ParsedEventType
|
|||||||
EV_PRS1_LL,
|
EV_PRS1_LL,
|
||||||
EV_PRS1_UNK_DURATION, // unknown duration event, rename once we figure it out
|
EV_PRS1_UNK_DURATION, // unknown duration event, rename once we figure it out
|
||||||
EV_PRS1_HY,
|
EV_PRS1_HY,
|
||||||
|
EV_PRS1_OA_COUNT, // F3V3 only
|
||||||
|
EV_PRS1_CA_COUNT, // F3V3 only
|
||||||
|
EV_PRS1_HY_COUNT, // F3V3 only
|
||||||
EV_PRS1_TOTLEAK,
|
EV_PRS1_TOTLEAK,
|
||||||
EV_PRS1_LEAK, // unintentional leak
|
EV_PRS1_LEAK, // unintentional leak
|
||||||
EV_PRS1_AUTO_PRESSURE_SET,
|
EV_PRS1_AUTO_PRESSURE_SET,
|
||||||
@ -1497,6 +1500,9 @@ PRS1_VALUE_EVENT(PRS1RERAEvent, EV_PRS1_RERA); // TODO: should this really be a
|
|||||||
PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour?
|
PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour?
|
||||||
PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1);
|
PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1);
|
||||||
PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2);
|
PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2);
|
||||||
|
PRS1_VALUE_EVENT(PRS1HypopneaCount, EV_PRS1_HY_COUNT); // F3V3 only
|
||||||
|
PRS1_VALUE_EVENT(PRS1ClearAirwayCount, EV_PRS1_CA_COUNT); // F3V3 only
|
||||||
|
PRS1_VALUE_EVENT(PRS1ObstructiveApneaCount, EV_PRS1_OA_COUNT); // F3V3 only
|
||||||
|
|
||||||
PRS1_ALARM_EVENT(PRS1DisconnectAlarmEvent, EV_PRS1_DISCONNECT_ALARM);
|
PRS1_ALARM_EVENT(PRS1DisconnectAlarmEvent, EV_PRS1_DISCONNECT_ALARM);
|
||||||
PRS1_ALARM_EVENT(PRS1ApneaAlarmEvent, EV_PRS1_APNEA_ALARM);
|
PRS1_ALARM_EVENT(PRS1ApneaAlarmEvent, EV_PRS1_APNEA_ALARM);
|
||||||
@ -1580,6 +1586,10 @@ static const QHash<PRS1ParsedEventType,QVector<ChannelID*>> PRS1ImportChannelMap
|
|||||||
{ PRS1SnoresAtPressureEvent::TYPE, { /* Not imported */ } },
|
{ PRS1SnoresAtPressureEvent::TYPE, { /* Not imported */ } },
|
||||||
{ PRS1AutoPressureSetEvent::TYPE, { /* Not imported */ } },
|
{ PRS1AutoPressureSetEvent::TYPE, { /* Not imported */ } },
|
||||||
{ PRS1UnknownDurationEvent::TYPE, { &PRS1_0E } },
|
{ PRS1UnknownDurationEvent::TYPE, { &PRS1_0E } },
|
||||||
|
|
||||||
|
{ PRS1HypopneaCount::TYPE, { &CPAP_Hypopnea } }, // F3V3 only, generates individual events on import
|
||||||
|
{ PRS1ObstructiveApneaCount::TYPE, { &CPAP_Obstructive } }, // F3V3 only, generates individual events on import
|
||||||
|
{ PRS1ClearAirwayCount::TYPE, { &CPAP_ClearAirway } }, // F3V3 only, generates individual events on import
|
||||||
};
|
};
|
||||||
|
|
||||||
//********************************************************************************************
|
//********************************************************************************************
|
||||||
@ -2683,7 +2693,44 @@ bool PRS1Import::ImportEventChunk(PRS1DataChunk* event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (e->m_type) {
|
||||||
|
// F3V3 doesn't have individual HY/CA/OA events, only counts every 2 minutes, where
|
||||||
|
// nonzero counts show up as overview flags. Currently OSCAR doesn't have a way to
|
||||||
|
// chart those numeric statistics, so we generate events based on the count.
|
||||||
|
//
|
||||||
|
// TODO: This (and VS2) would probably be better handled as numeric charts only,
|
||||||
|
// along with enhancing overview flags to be drawn when channels have nonzero values,
|
||||||
|
// instead of the fictitious "events" that are currently generated.
|
||||||
|
case PRS1HypopneaCount::TYPE:
|
||||||
|
case PRS1ClearAirwayCount::TYPE:
|
||||||
|
case PRS1ObstructiveApneaCount::TYPE:
|
||||||
|
// Make sure PRS1ClearAirwayEvent/etc. isn't supported before generating events from counts.
|
||||||
|
CHECK_VALUE(supported.contains(PRS1HypopneaEvent::TYPE), false);
|
||||||
|
CHECK_VALUE(supported.contains(PRS1ClearAirwayEvent::TYPE), false);
|
||||||
|
CHECK_VALUE(supported.contains(PRS1ObstructiveApneaEvent::TYPE), false);
|
||||||
|
|
||||||
|
// Divide each count into events evenly spaced over the interval.
|
||||||
|
// NOTE: This is slightly fictional, but there's no waveform data for F3V3, so it won't
|
||||||
|
// incorrectly associate specific events with specific flow or pressure events.
|
||||||
|
if (e->m_value > 0) {
|
||||||
|
qint64 blockduration = statIntervalEnd - statIntervalStart;
|
||||||
|
qint64 div = blockduration / e->m_value;
|
||||||
|
qint64 tt = t;
|
||||||
|
PRS1ParsedDurationEvent ee(e->m_type, t, 0);
|
||||||
|
for (int i=0; i < e->m_value; ++i) {
|
||||||
|
ImportEvent(tt, &ee);
|
||||||
|
tt += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider whether to have a numeric channel for HY/CA/OA that gets charted like VS does,
|
||||||
|
// in which case we can fall through.
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
ImportEvent(t, e);
|
ImportEvent(t, e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@ -2782,7 +2829,8 @@ void PRS1Import::ImportEvent(qint64 t, PRS1ParsedEvent* e)
|
|||||||
if (e->m_value > 0) {
|
if (e->m_value > 0) {
|
||||||
// TODO: currently these get drawn on our waveforms, but they probably shouldn't,
|
// TODO: currently these get drawn on our waveforms, but they probably shouldn't,
|
||||||
// since they don't have a precise timestamp. They should continue to be drawn
|
// since they don't have a precise timestamp. They should continue to be drawn
|
||||||
// on the flags overview.
|
// on the flags overview. See the comment in ImportEventChunk regarding flags
|
||||||
|
// for numeric channels.
|
||||||
VS2 = *channels.at(1);
|
VS2 = *channels.at(1);
|
||||||
AddEvent(VS2, t, 0, 1);
|
AddEvent(VS2, t, 0, 1);
|
||||||
}
|
}
|
||||||
@ -2993,9 +3041,9 @@ static const QVector<PRS1ParsedEventType> ParsedEventsF3V3 = {
|
|||||||
PRS1RespiratoryRateEvent::TYPE,
|
PRS1RespiratoryRateEvent::TYPE,
|
||||||
PRS1MinuteVentilationEvent::TYPE,
|
PRS1MinuteVentilationEvent::TYPE,
|
||||||
PRS1LeakEvent::TYPE,
|
PRS1LeakEvent::TYPE,
|
||||||
PRS1HypopneaEvent::TYPE,
|
PRS1HypopneaCount::TYPE,
|
||||||
PRS1ClearAirwayEvent::TYPE,
|
PRS1ClearAirwayCount::TYPE,
|
||||||
PRS1ObstructiveApneaEvent::TYPE,
|
PRS1ObstructiveApneaCount::TYPE,
|
||||||
// No PP, FL, VS, RERA, PB, LL
|
// No PP, FL, VS, RERA, PB, LL
|
||||||
// No TB
|
// No TB
|
||||||
};
|
};
|
||||||
@ -3016,30 +3064,27 @@ bool PRS1DataChunk::ParseEventsF3V3(void)
|
|||||||
qWarning() << "F3V3 event file with fileVersion 3?";
|
qWarning() << "F3V3 event file with fileVersion 3?";
|
||||||
}
|
}
|
||||||
|
|
||||||
int t = 0, tt;
|
int t = 0;
|
||||||
static const int record_size = 0x10;
|
static const int record_size = 0x10;
|
||||||
int size = this->m_data.size()/record_size;
|
int size = this->m_data.size()/record_size;
|
||||||
CHECK_VALUE(this->m_data.size() % record_size, 0);
|
CHECK_VALUE(this->m_data.size() % record_size, 0);
|
||||||
unsigned char * h = (unsigned char *)this->m_data.data();
|
unsigned char * h = (unsigned char *)this->m_data.data();
|
||||||
|
|
||||||
int hy, oa, ca;
|
static const qint64 block_duration = 120;
|
||||||
qint64 div = 0;
|
|
||||||
|
|
||||||
// Make sure the assumptions here agree with the header
|
// Make sure the assumptions here agree with the header
|
||||||
CHECK_VALUE(this->htype, PRS1_HTYPE_INTERVAL);
|
CHECK_VALUE(this->htype, PRS1_HTYPE_INTERVAL);
|
||||||
CHECK_VALUE(this->interval_count, size);
|
CHECK_VALUE(this->interval_count, size);
|
||||||
CHECK_VALUE(this->interval_seconds, 120);
|
CHECK_VALUE(this->interval_seconds, block_duration);
|
||||||
for (auto & channel : this->waveformInfo) {
|
for (auto & channel : this->waveformInfo) {
|
||||||
CHECK_VALUE(channel.interleave, 1);
|
CHECK_VALUE(channel.interleave, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const qint64 block_duration = 120;
|
|
||||||
|
|
||||||
for (int x=0; x < size; x++) {
|
for (int x=0; x < size; x++) {
|
||||||
// TODO: t here is inconsistent with all other parsers: they receive events at the end
|
// Use the timestamp of the end of this interval, to be consistent with other parsers,
|
||||||
// of the 2-minute interval with stats for the preceding 2 minutes. This uses the
|
// but see note below regarding the duration of the final interval.
|
||||||
// starting timestamp currently.
|
t += block_duration;
|
||||||
//
|
|
||||||
// TODO: The duration of the final interval isn't clearly defined in this format:
|
// TODO: The duration of the final interval isn't clearly defined in this format:
|
||||||
// there appears to be no way (apart from looking at the summary or waveform data)
|
// there appears to be no way (apart from looking at the summary or waveform data)
|
||||||
// to determine the end time, which may truncate the last interval.
|
// to determine the end time, which may truncate the last interval.
|
||||||
@ -3056,44 +3101,12 @@ bool PRS1DataChunk::ParseEventsF3V3(void)
|
|||||||
if (h[9] < 13 || h[9] > 84) UNEXPECTED_VALUE(h[9], "13-84"); // not sure what this is.. encore doesn't graph it.
|
if (h[9] < 13 || h[9] > 84) UNEXPECTED_VALUE(h[9], "13-84"); // not sure what this is.. encore doesn't graph it.
|
||||||
CHECK_VALUES(h[10], 0, 8); // 8 shows as a Low Pressure (LP) alarm
|
CHECK_VALUES(h[10], 0, 8); // 8 shows as a Low Pressure (LP) alarm
|
||||||
this->AddEvent(new PRS1MinuteVentilationEvent(t, h[11]));
|
this->AddEvent(new PRS1MinuteVentilationEvent(t, h[11]));
|
||||||
|
this->AddEvent(new PRS1HypopneaCount(t, h[12])); // count of hypopnea events
|
||||||
|
this->AddEvent(new PRS1ClearAirwayCount(t, h[13])); // count of clear airway events
|
||||||
|
this->AddEvent(new PRS1ObstructiveApneaCount(t, h[14])); // count of obstructive events
|
||||||
this->AddEvent(new PRS1LeakEvent(t, h[15]));
|
this->AddEvent(new PRS1LeakEvent(t, h[15]));
|
||||||
|
|
||||||
hy = h[12]; // count of hypopnea events
|
|
||||||
ca = h[13]; // count of clear airway events
|
|
||||||
oa = h[14]; // count of obstructive events
|
|
||||||
|
|
||||||
// divide each event evenly over the 2 minute block
|
|
||||||
// NOTE: This is slightly fictional, but there's no waveform data for these machines, so it won't
|
|
||||||
// incorrectly associate specific events with specific flow or pressure events.
|
|
||||||
// TODO: Consider whether to have a numeric channel for H/CA/OA that gets charted like VS does.
|
|
||||||
// currently have another good way to represent flags with numeric values (s
|
|
||||||
if (hy > 0) {
|
|
||||||
div = block_duration / hy;
|
|
||||||
tt = t;
|
|
||||||
for (int i=0; i < hy; ++i) {
|
|
||||||
this->AddEvent(new PRS1HypopneaEvent(tt, 0));
|
|
||||||
tt += div;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ca > 0) {
|
|
||||||
div = block_duration / ca;
|
|
||||||
tt = t;
|
|
||||||
for (int i=0; i < ca; ++i) {
|
|
||||||
this->AddEvent(new PRS1ClearAirwayEvent(tt, 0));
|
|
||||||
tt += div;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oa > 0) {
|
|
||||||
div = block_duration / oa;
|
|
||||||
tt = t;
|
|
||||||
for (int i=0; i < oa; ++i) {
|
|
||||||
this->AddEvent(new PRS1ObstructiveApneaEvent(tt, 0));
|
|
||||||
tt += div;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h += record_size;
|
h += record_size;
|
||||||
t += block_duration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->duration = t;
|
this->duration = t;
|
||||||
|
Loading…
Reference in New Issue
Block a user