Skip to content
Snippets Groups Projects
Commit 13b1167e authored by Robert L. Read's avatar Robert L. Read
Browse files

adding work-of-breathing

parent 559a1fda
No related branches found
No related tags found
No related merge requests found
......@@ -325,7 +325,7 @@ an interactive or static analysis of a respiration. It's primary purpose is
</div>
</div>
<div>
<label for="taip">TAIP (ms): </label>
<label for="taip">Rise Time (ms): </label>
<div class="value_and_fences">
<div class="calcnum">
<label id="taip"> </label>
......@@ -343,7 +343,7 @@ an interactive or static analysis of a respiration. It&#39;s primary purpose is
</div>
</div>
<div>
<label for="taip">TRIP (ms): </label>
<label for="taip">Fall Time (ms): </label>
<div class="value_and_fences">
<div class="calcnum">
<label id="trip"> </label>
......@@ -359,6 +359,23 @@ an interactive or static analysis of a respiration. It&#39;s primary purpose is
</div>
</div>
</div>
<div>
<label for="wob">W. of B. (J/L): </label>
<div class="value_and_fences">
<div class="calcnum">
<label id="wob"> </label>
</div>
<div class="vertical_alarms">
<div class="limit max">
<label for="wob_h">H:</label>
<input class="fence" id="wob_h" type='text'> </input>
</div>
<div class="limit min">
<label for="wob_l">L:</label>
<input class="fence" id="wob_l" type='text'> </input>
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -370,11 +387,11 @@ an interactive or static analysis of a respiration. It&#39;s primary purpose is
<div>
Parameters:
<div>
<label for="tarip_h">TAIP/TRIP High Pressure (cm H2O):</label>
<label for="tarip_h">Rise/Fall High Pressure (cm H2O):</label>
<input class="fence" id="tarip_h" type='text'> </input>
</div>
<div>
<label for="tarip_l">TAIP/TRIP Low Pressure (cm H2O):</label>
<label for="tarip_l">Rise/Fall Low Pressure (cm H2O):</label>
<input class="fence" id="tarip_l" type='text'> </input>
</div>
</div>
......@@ -667,10 +684,30 @@ function plot(samples, trans, breaths) {
,0);
var max_v = Math.max(max_inh,max_exh);
// not sure why I have null work, have to understand.
var max_work_per_liter = 0;
var work_per_liter = [];
for(var i = 0; i < breaths.length; i++) {
var b = breaths[i];
if (b.work != null) {
var wpl = b.work / b.vol_i;
if (wpl > max_work_per_liter)
max_work_per_liter = wpl;
var center = ((trans[b.trans_begin_inhale].ms + trans[b.trans_cross_zero].ms) - 2*min) / (2.0 * 1000.0);
work_per_liter.push({ wpl: wpl, center: center });
}
}
var exh_v = unpack(breaths, 'vol_e').map(e => 100 * e / max_v);
var t_exh_v = unpack(breaths, 'vol_e').map(e => Math.round(e*1000.0)+"ml exh");
var inh_v = unpack(breaths, 'vol_i').map(i => 100 * i / max_v);
var t_inh_v = unpack(breaths, 'vol_i').map(e => Math.round(e*1000.0)+"ml inh");
var work_v = work_per_liter.map(w => (1/3) * (100 * w.wpl / max_work_per_liter));
var t_work = work_per_liter.map(w => w.wpl.toFixed(2)+"J/L");
var work_centers = unpack(work_per_liter, 'center');
// now to graph properly, I must find the center of an inhalation.
// I have packed these into the breaths...
var inhale_centers = breaths.map(b => ((trans[b.trans_begin_inhale].ms + trans[b.trans_cross_zero].ms) - 2*min) / (2.0 * 1000.0));
......@@ -698,6 +735,17 @@ function plot(samples, trans, breaths) {
text: t_exh_v,
};
var workPlot = {type: "scatter", mode: "markers+text",
name: "WoB J",
textposition: 'bottom center',
x: work_centers,
y: work_v,
text: t_work,
marker: { size: 8,
color: 'blue',
symbol: 'triangle-right'}
};
event_graph.push(workPlot);
event_graph.push(inhPlot);
event_graph.push(exhPlot);
}
......@@ -733,7 +781,7 @@ function plot(samples, trans, breaths) {
// Basically, we will show only the first of these in a "run"
// This is rather difficult; we will instead just use 200 ms
// as a window.
const time_windwow_ms = 200;
const TIME_WINDOW_MS = 400;
var lows = [];
var highs = [];
......@@ -741,10 +789,10 @@ function plot(samples, trans, breaths) {
for(var i = 0; i < messages.length; i++) {
var m = messages[i];
if (m.buff == FLOW_TOO_LOW) {
if ((lows.length == 0) || ((lows.length > 0) && (lows[lows.length-1].ms < (m.ms - 200))))
if ((lows.length == 0) || ((lows.length > 0) && (lows[lows.length-1].ms < (m.ms - TIME_WINDOW_MS))))
lows.push(m);
} else if (m.buff == FLOW_TOO_HIGH) {
if ((highs.length == 0) || ((highs.length > 0) && (highs[highs.length-1].ms < (m.ms - 200))))
if ((highs.length == 0) || ((highs.length > 0) && (highs[highs.length-1].ms < (m.ms - TIME_WINDOW_MS))))
highs.push(m);
} else {
others.push(m);
......@@ -933,7 +981,13 @@ function compute_fio2_mean(secs,samples) {
}
}
// returns:
// [ respiration rate,
// tidal volume (avg),
// minute_volume
// EIRatio,
// Work-of-Breathing (avg)
// ]
function compute_respiration_rate(secs,samples,transitions,breaths) {
// In order to compute the number of breaths
// in the last s seconds, I compute those breaths
......@@ -944,7 +998,7 @@ function compute_respiration_rate(secs,samples,transitions,breaths) {
var first_inhale_ms = -1;
var last_inhale_ms = -1;
if (breaths.length == 0) {
return [0,0,0,"NA"];
return [0,0,0,"NA","NA"];
} else {
const recent_ms = samples[samples.length - 1].ms;
var cur_ms = recent_ms;
......@@ -959,6 +1013,9 @@ function compute_respiration_rate(secs,samples,transitions,breaths) {
// divide by the inhlation_duration.
var vol_ci = 0.0;
var wob = 0.0;
var wob_cnt = 0;
while((i >=0) && (breaths[i].ms > (cur_ms - secs*1000))) {
cnt++;
vol_i += breaths[i].vol_i;
......@@ -975,6 +1032,11 @@ function compute_respiration_rate(secs,samples,transitions,breaths) {
const zero_ms = transitions[breaths[i].trans_cross_zero].ms;
time_inh += (zero_ms - inh_ms);
time_exh += (exh_ms - zero_ms);
if (breaths[i].work != null) {
wob += breaths[i].work / breaths[i].vol_i;
wob_cnt++;
}
i--;
}
if ((cnt > 1) && (first_inhale_ms != last_inhale_ms)) {
......@@ -990,16 +1052,18 @@ function compute_respiration_rate(secs,samples,transitions,breaths) {
var tidal_volume = 1000.0 * vol_i / cnt;
var EIratio = (time_inh == 0) ? null : time_exh / time_inh;
var WorkOfBreathing_J_per_L = wob / wob_cnt;
return [
rr,
tidal_volume,
minute_volume,
EIratio];
EIratio,
WorkOfBreathing_J_per_L];
} else {
return [0,0,0,"NA"];
return [0,0,0,"NA","NA"];
}
}
}
}
var LIMITS = {
max: { h: 40,
......@@ -1021,7 +1085,9 @@ var LIMITS = {
taip: { h: 100, // These are in ms
l: 0},
trip: { h: 100, // These are in ms
l: 0},
l: 0},
wob: { h: 3,
l: 0.2},
}
function load_ui_with_defaults(limits) {
......@@ -1067,13 +1133,19 @@ function process(samples) {
var [transitions,breaths] = computeMovingWindowTrace(samples,t,v);
plot(samples,transitions,breaths);
// How many seconds backwards should we look? Perhaps 20?
var [bpm,tv,mv,EIratio,fio2] = compute_respiration_rate(RESPIRATION_RATE_WINDOW_SECONDS,samples,transitions,breaths);
var [bpm,tv,mv,EIratio,wob] = compute_respiration_rate(RESPIRATION_RATE_WINDOW_SECONDS,samples,transitions,breaths);
var alarms = [];
$("#bpm").text(bpm.toFixed(1));
$("#tv").text(tv.toFixed(0));
$("#mv").text((mv).toFixed(2));
$("#mv").text(mv.toFixed(2));
if (wob == "NA") {
$("#wob").text("NA");
} else {
$("#wob").text(wob.toFixed(2));
}
if (EIratio == "NA") {
$("#ier").text("NA");
} else {
......@@ -1096,8 +1168,10 @@ function process(samples) {
alarms = alarms.concat(check_high_and_low(LIMITS,"bpm",bpm.toFixed(1),b_ms));
alarms = alarms.concat(check_high_and_low(LIMITS,"tv",tv.toFixed(0),b_ms));
alarms = alarms.concat(check_high_and_low(LIMITS,"mv",(mv).toFixed(2),b_ms));
alarms = alarms.concat(check_high_and_low(LIMITS,"mv",mv.toFixed(2),b_ms));
alarms = alarms.concat(check_high_and_low(LIMITS,"ier",(1.0 / EIratio).toFixed(1),b_ms));
if (wob != "NA")
alarms = alarms.concat(check_high_and_low(LIMITS,"wob",wob.toFixed(2),b_ms));
// These must be moved out; in fact,
// they must be made configurable!
......@@ -1182,26 +1256,34 @@ function retrieveAndPlot(){
// WARNING: This is a hack...if the timestamp is negative,
// we treat it as a limited (beyond range of sensor) measurement.
// Our goal is to warn about this, but for now we will just
// ignore and correct.
// ignore and correct.
if (cur_sam == null) {
console.log("No return");
return;
}
if (cur_sam && cur_sam.length == 0) {
console.log("no samples; potential misconfiguration!");
} else {
cur_sam = sanitize_samples(cur_sam);
if (INITS_ONLY) {
samples = cur_sam;
INITS_ONLY = false;
} else {
var discard = Math.max(0,
samples.length + cur_sam.length - MAX_SAMPLES_TO_STORE_S);
samples = samples.slice(discard);
}
samples = samples.concat(cur_sam);
// We are not guaranteeed to get samples in order
// we sort them....
samples = samples.sort((a,b) => a.ms - b.ms);
process(samples);
if (typeof(cur_sam) == "string") {
console.log(cur_sam);
} else {
cur_sam = sanitize_samples(cur_sam);
if (INITS_ONLY) {
samples = cur_sam;
INITS_ONLY = false;
} else {
var discard = Math.max(0,
samples.length + cur_sam.length - MAX_SAMPLES_TO_STORE_S);
samples = samples.slice(discard);
}
samples = samples.concat(cur_sam);
// We are not guaranteeed to get samples in order
// we sort them....
samples = samples.sort((a,b) => a.ms - b.ms);
process(samples);
}
}
},
error: function(xhr, ajaxOptions, thrownError) {
......@@ -1416,11 +1498,10 @@ function PressureVolumeWork(breath, transitions, samples) {
var beginTime_ms = beginTransition.ms;
var endTransition = transitions[breath.trans_cross_zero];
var endTime_ms = endTransition.ms;
var flows = samples.filter(s => s.event == 'M' && s.type == 'F' && s.ms >= beginTime_ms && s.ms <= endTime_ms);
var pressures = samples.filter(s => s.event == 'M' && s.type == 'D' && s.loc == 'A'&& s.ms >= beginTime_ms && s.ms <= endTime_ms);
console.log(flows);
console.log(pressures);
var flows = samples.filter(s => s.event == 'M' && s.type == 'F' &&
s.ms >= beginTime_ms && s.ms <= endTime_ms);
var pressures = samples.filter(s => s.event == 'M' && s.type == 'D' &&
s.loc == 'A'&& s.ms >= beginTime_ms && s.ms <= endTime_ms);
// Note: The algorithm below relies on the fact that there is
// only one flow or pressure with a single ms value; and that
......@@ -1433,8 +1514,8 @@ function PressureVolumeWork(breath, transitions, samples) {
var ct = Math.min(flows[0].ms,pressures[0].ms);
var lfp = { val : flows[0].val, ms: flows[0].ms } ; // last flow point
var lpp = { val : pressures[0].val, ms: pressures[0].ms }; // last pressure_point
var fi = increment_past(flows,ct,0); // Index of next flow sample
var pi = increment_past(pressures,ct,0); // Index of next pressure sample
var fi = increment_past(flows,flows[0].ms,0); // Index of next flow sample
var pi = increment_past(pressures,pressures[0].ms,0); // Index of next pressure sample
var w = 0; // current work
// compute flow at time ms give index and last point
......@@ -1445,17 +1526,24 @@ function PressureVolumeWork(breath, transitions, samples) {
return last.val + (cur.val - last.val)*(ms - ms0)/(ms1 - ms0);
}
function increment_past(array,ms,index) {
var begin = index;
while(index < array.length && array[index].ms <= ms)
index++;
if (index == begin) debugger;
if (index >= array.length)
return null;
else
return index;
}
// A fundamental invariant:
// pressures[pi].ms > lpp.ms
// flows[pi].ms > lfp.ms
while ((fi + pi) < (flows.length + pressures.length)) {
// Invariant always increment fi or pi
// fi and pi point to unprocessed value
console.assert(pressures[pi].ms > lpp.ms);
console.assert(flows[fi].ms > lfp.ms);
var ms;
if (pressures[pi].ms <= flows[fi].ms) { // process pressure
ms = pressures[pi].ms;
......@@ -1473,7 +1561,8 @@ function PressureVolumeWork(breath, transitions, samples) {
if ((fi === null) || (pi === null))
break;
var dur_s = (ms - ct) / 1000;
console.log("dur_s",dur_s);
console.assert(pressures[pi].ms > lpp.ms);
console.assert(flows[fi].ms > lfp.ms);
var nf = f(ms,flows[fi],lfp);
var np = f(ms,pressures[pi],lpp);
var f1 = (lfp.val + nf)/2;
......@@ -1490,48 +1579,6 @@ function PressureVolumeWork(breath, transitions, samples) {
lpp = { val : p1, ms: ms };
ct = ms;
}
console.log("begin transition time, end transition time:",beginTime_ms,endTime_ms);
console.log("ms,w",ms,w);
//var pressureVolume_prod= 0;
//for(var j = a; j < z-1; j++) {
// // I'll use qadrilateral approximation.
// // We'll form each quadrilateral between two samples.
// var ms = flows[j+1].ms - flows[j].ms;
// var ht = (((flows[j+1].val*pressures[j+1].val) + (flows[j].val*pressures[j+1].val ))/2) * CONVERT_PIRDS_TO_SLM;
// // Flow in standard liters per minute,
// // divide by 60000 to get liters/s
// pressureVolume_prod += ms * ht/60000;
// if (isNaN(pressureVolume_prod)) {
// debugger;
// }
// }
// pressure cm H2O --> atm (divide by 1033)
// return pressureVolume_prod/1033
// average pressure * average flow ~ approximation of work
var pAv_cm = 0;
for (var i = 0; i<pressures.length; i++) {
pAv_cm += pressures[i].val/10;
}
pAv_cm /= pressures.length;
var pressures_pascales = pAv_cm * 98.0665; //cm to pasc.
var fAv_lpm = 0;
for (var i = 0; i<flows.length; i++) {
fAv_lpm += flows[i].val/1000;
}
fAv_lpm /= flows.length;
console.log("flows:",flows,"\npressures:",pressures)
console.log("pAv, vAv = ",pAv_cm,fAv_lpm);
var flow_cubicMetersPerSecond = fAv_lpm/1000/60; //lpm to cmps
var avPower = pressures_pascales*flow_cubicMetersPerSecond; // Watts
var avWork = avPower * (endTime_ms - beginTime_ms)/1000; // Joules
console.log("Power (Watts):",avPower);
console.log("Work (Joules):",avWork);
console.log("ms, Work main (Joules)",ms,w);
return w;
}
}
......@@ -1542,42 +1589,52 @@ function generate_synthetic_trace() {
const SAMPLES_MS_PER_SAMPLE = 1;
var trace = [];
var cur = 0;
function push_samples(start,num,d,f) {
// p is a probability of occuring.
function push_samples(start,num,d,f,pd,pf) {
for(var i = start; i < start+num; i++) {
trace.push(
{
event: "M",
loc: "A",
ms: i,
num: 0,
type: "D",
val: d
});
trace.push(
{
event: "M",
loc: "A",
ms: i,
num: 0,
type: "F",
val: f
});
if (Math.random() < pd) {
trace.push(
{
event: "M",
loc: "A",
ms: i,
num: 0,
type: "D",
val: d
});
}
if (Math.random() < pf) {
trace.push(
{
event: "M",
loc: "A",
ms: i,
num: 0,
type: "F",
val: f
});
}
}
}
push_samples(0,SAMPLES_PER_PHASE,-200,-50000);
push_samples(SAMPLES_PER_PHASE,SAMPLES_PER_PHASE,200,50000);
push_samples(SAMPLES_PER_PHASE*2,SAMPLES_PER_PHASE,-200,-50000);
push_samples(SAMPLES_PER_PHASE*3,SAMPLES_PER_PHASE,200,50000);
const P1 = 1/2;
const P2 = 1/2;
push_samples(SAMPLES_PER_PHASE,SAMPLES_PER_PHASE,200,50000,P1,P2);
push_samples(0,SAMPLES_PER_PHASE,-200,-50000,P1,P2);
push_samples(SAMPLES_PER_PHASE,SAMPLES_PER_PHASE,200,50000,P1,P2);
push_samples(SAMPLES_PER_PHASE*2,SAMPLES_PER_PHASE,-200,-50000,P1,P2);
push_samples(SAMPLES_PER_PHASE*3,SAMPLES_PER_PHASE,200,50000,P1,P2);
return trace;
}
function nearp(x,y,d) {
return Math.abs(x - y) <= d;
}
function testWorkSynthetic(){ // breaths give us inspiration transition points
var samples = generate_synthetic_trace();
const JOULES_IN_BREATH = 1 * 1961.33 * 50000 / (60e+6);
var flows = samples.filter(s => s.event == 'M' && s.type == 'F');
var first_time = flows[0].ms;
var last_time = flows[flows.length - 1].ms;
......@@ -1592,10 +1649,10 @@ function testWorkSynthetic(){ // breaths give us inspiration transition points
console.log(breaths);
for(i = 0; i<breaths.length; i++) {
var w = PressureVolumeWork(breaths[i], transitions, samples);
console.assert((w == null) || (w == JOULES_IN_BREATH));
console.log("final = ",w);
console.assert((w == null) || (nearp(w,JOULES_IN_BREATH),0.1));
console.log("final (Joules) = ",w);
}
return true;
}
......@@ -1680,6 +1737,8 @@ function testWorkSynthetic(){ // breaths give us inspiration transition points
trans_end_exhale: i,
}
);
var w = PressureVolumeWork(breaths[breaths.length-1], transitions, samples);
breaths[breaths.length-1].work = w;
beg = i;
expiring = false;
vole = integrateSamples(last,transitions[i].sample,flows);
......@@ -1758,6 +1817,8 @@ function computeMovingWindowTrace(samples,t,v) {
trans_end_exhale: i,
}
);
var w = PressureVolumeWork(breaths[0], transitions, samples);
breaths[0].work = w;
beg = i;
expiring = false;
vole = integrateSamples(last,transitions[i].sample,flows);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment