Spaces:
Running
Running
Franny Dean
commited on
Commit
•
5b91528
1
Parent(s):
2a0c362
attempt to add lvad
Browse files- .ipynb_checkpoints/app-checkpoint.py +321 -5
- app.py +321 -4
.ipynb_checkpoints/app-checkpoint.py
CHANGED
@@ -495,7 +495,265 @@ def pvloop_simulator_plot_only(Rm, Ra, Emax, Emin, Vd, Tc, start_v):
|
|
495 |
plt.title('Simulated PI-SSL LV Pressure Volume Loop', fontsize=16)
|
496 |
return plot
|
497 |
|
498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
|
500 |
def generate_example():
|
501 |
# get random input
|
@@ -518,6 +776,53 @@ def generate_example():
|
|
518 |
animated = "prediction.mp4"
|
519 |
return video, animated, Rm, Ra, Emax, Emin, Vd, Tc, start_v
|
520 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
title = "<h1 style='text-align: center; margin-bottom: 1rem'> Non-Invasive Medical Digital Twins using Physics-Informed Self-Supervised Learning </h1>"
|
522 |
|
523 |
description = """
|
@@ -529,10 +834,16 @@ We demonstrate the ability of our model to predict left ventricular pressure-vol
|
|
529 |
|
530 |
title2 = "<h3 style='text-align: center'> Physics-based model simulation</h3>"
|
531 |
|
532 |
-
description2 = """
|
|
|
533 |
Our model uses a hydraulic analogy model of cardiac function from <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a>. Below you can input values of predicted parameters and output a simulated pressure-volume loop predicted from the <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a> model, which is an ordinary differential equation. Tune parameters and press 'Run simulation.'
|
534 |
"""
|
535 |
|
|
|
|
|
|
|
|
|
|
|
536 |
gr.Markdown(title)
|
537 |
gr.Markdown(description)
|
538 |
|
@@ -573,13 +884,18 @@ with gr.Blocks() as demo:
|
|
573 |
sl5 = gr.Slider(4.0, 25.0, value= 4.0, label="Vd (ml)")
|
574 |
sl6 = gr.Slider(0.4, 1.7, value= 0.4, label="Tc (s)")
|
575 |
sl7 = gr.Slider(0.0, 280.0, value= 140., label="start_v (ml)")
|
576 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
577 |
|
578 |
generate_button.click(fn=generate_example, outputs = [video,plot,Rm,Ra,Emax,Emin,Vd,Tc,start_v])
|
579 |
|
580 |
-
|
581 |
simulation_button.click(fn=pvloop_simulator_plot_only, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7], outputs = [gr.Plot()])
|
582 |
|
583 |
-
|
584 |
|
585 |
demo.launch()
|
|
|
495 |
plt.title('Simulated PI-SSL LV Pressure Volume Loop', fontsize=16)
|
496 |
return plot
|
497 |
|
498 |
+
#########################################
|
499 |
+
# LVAD functions
|
500 |
+
# RELU for diodes
|
501 |
+
def r(u):
|
502 |
+
return max(u, 0.)
|
503 |
+
|
504 |
+
def heart_ode0(y, t, Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd):
|
505 |
+
x1, x2, x3, x4, x5 = y #here y is a vector of 5 values (not functions), at time t, used for getting (dy/dt)(t)
|
506 |
+
P_lv = Plv(x1+Vd,Emax,Emin,t,Tc,Vd)
|
507 |
+
dydt = [r(x2-P_lv)/Rm-r(P_lv-x4)/Ra, (x3-x2)/(Rs*Cr)-r(x2-P_lv)/(Cr*Rm), (x2-x3)/(Rs*Cs)+x5/Cs, -x5/Ca+r(P_lv-x4)/(Ca*Ra), (x4-x3-Rc*x5)/Ls]
|
508 |
+
return dydt
|
509 |
+
|
510 |
+
def getslope(y1, y2, y3, x1, x2, x3):
|
511 |
+
sum_x = x1 + x2 + x3
|
512 |
+
sum_y = y1 + y2 + y3
|
513 |
+
sum_xy = x1*y1 + x2*y2 + x3*y3
|
514 |
+
sum_xx = x1*x1 + x2*x2 + x3*x3
|
515 |
+
# calculate the coefficients of the least-squares line
|
516 |
+
n = 3
|
517 |
+
slope = (n*sum_xy - sum_x*sum_y) / (n*sum_xx - sum_x*sum_x)
|
518 |
+
return slope
|
519 |
+
|
520 |
+
### ODE: for each t (here fixed), gives dy/dt as a function of y(t) at that t, so can be used for integrating the vector y over time
|
521 |
+
#it is run for each t going from 0 to tmax
|
522 |
+
def lvad_ode(y, t, Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew):
|
523 |
+
|
524 |
+
#from simaan2008dynamical:
|
525 |
+
Ri = 0.0677
|
526 |
+
R0 = 0.0677
|
527 |
+
Rk = 0
|
528 |
+
x1bar = 1.
|
529 |
+
alpha = -3.5
|
530 |
+
Li = 0.0127
|
531 |
+
L0 = 0.0127
|
532 |
+
b0 = -0.296
|
533 |
+
b1 = -0.027
|
534 |
+
b2 = 9.9025e-7
|
535 |
+
|
536 |
+
x1, x2, x3, x4, x5, x6, x7 = y #here y is a vector of 5 values (not functions), at time t, used for getting (dy/dt)(t)
|
537 |
+
|
538 |
+
P_lv = Plv(x1+Vd,Emax,Emin,t,Tc,Vd)
|
539 |
+
if (P_lv <= x1bar): Rk = alpha * (P_lv - x1bar)
|
540 |
+
Lstar = Li + L0 + b1
|
541 |
+
Lstar2 = -Li -L0 +b1
|
542 |
+
Rstar = Ri + + R0 + Rk + b0
|
543 |
+
|
544 |
+
dydt = [-x6 + r(x2-P_lv)/Rm-r(P_lv-x4)/Ra, (x3-x2)/(Rs*Cr)-r(x2-P_lv)/(Cr*Rm), (x2-x3)/(Rs*Cs)+x5/Cs, -x5/Ca+r(P_lv-x4)/(Ca*Ra) + x6/Ca, (x4-x3)/Ls-Rc*x5/Ls, -P_lv / Lstar2 + x4/Lstar2 + (Ri+R0+Rk-b0) / Lstar2 * x6 - b2 / Lstar2 * x7**2, ratew]
|
545 |
+
|
546 |
+
return dydt
|
547 |
+
|
548 |
+
#returns pv loop and ef when there is no lvad:
|
549 |
+
def f_nolvad(Tc, start_v, Emax, showpvloop):
|
550 |
+
|
551 |
+
N = 20
|
552 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
553 |
+
|
554 |
+
start_pla = float(start_v*Elastance(Emax, Emin, 0, Tc))
|
555 |
+
start_pao = 75.
|
556 |
+
start_pa = start_pao
|
557 |
+
start_qt = 0 #aortic flow is Q_T and is 0 at ED, also see Fig5 in simaan2008dynamical
|
558 |
+
|
559 |
+
y0 = [start_v, start_pla, start_pa, start_pao, start_qt]
|
560 |
+
|
561 |
+
t = np.linspace(0, Tc*N, int(60000*N)) #spaced numbers over interval (start, stop, number_of_steps), 60000 time instances for each heart cycle
|
562 |
+
#changed to 60000 for having integer positions for Tmax
|
563 |
+
#obtain 5D vector solution:
|
564 |
+
sol = odeint(heart_ode0, y0, t, args = (Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc,Vd)) #t: list of values
|
565 |
+
|
566 |
+
result_Vlv = np.array(sol[:, 0]) + Vd
|
567 |
+
result_Plv = np.array([Plv(v, Emax, Emin, xi, Tc, Vd) for xi,v in zip(t,sol[:, 0])])
|
568 |
+
|
569 |
+
#if showpvloop: plt.plot(result_Vlv[18*60000:20*60000], result_Plv[18*60000:20*60000], color='black', label='Without LVAD')
|
570 |
+
|
571 |
+
ved = sol[19*60000, 0] + Vd
|
572 |
+
ves = sol[200*int(60/Tc)+9000+19*60000, 0] + Vd
|
573 |
+
ef = (ved-ves)/ved * 100.
|
574 |
+
minv = min(result_Vlv[19*60000:20*60000-1])
|
575 |
+
minp = min(result_Plv[19*60000:20*60000-1])
|
576 |
+
|
577 |
+
result_pao = np.array(sol[:, 3])
|
578 |
+
pao_ed = min(result_pao[(N-1)*60000:N*60000-1])
|
579 |
+
pao_es = max(result_pao[(N-1)*60000:N*60000-1])
|
580 |
+
|
581 |
+
return ef, pao_ed, pao_es, ((ved - ves) * 60/Tc ) / 1000, sol[19*60000, 0], sol[19*60000, 1], sol[19*60000, 2], sol[19*60000, 3], sol[19*60000, 4], result_Vlv[18*60000:20*60000], result_Plv[18*60000:20*60000]
|
582 |
+
|
583 |
+
#returns the w at which suction occurs: (i.e. for which the slope of the envelopes of x6 becomes negative)
|
584 |
+
def get_suctionw(Tc, start_v, Emax, y00, y01, y02, y03, y04, w0, x60, ratew): #slope is slope0 for w
|
585 |
+
|
586 |
+
N = 70
|
587 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
588 |
+
|
589 |
+
start_pla = float(start_v*Elastance(Emax, Emin, 0, Tc))
|
590 |
+
start_pao = 75.
|
591 |
+
start_pa = start_pao
|
592 |
+
start_qt = 0 #aortic flow is Q_T and is 0 at ED, also see Fig5 in simaan2008dynamical
|
593 |
+
|
594 |
+
y0 = [start_v, start_pla, start_pa, start_pao, start_qt, x60, w0]
|
595 |
+
y0 = [y00, y01, y02, y03, y04, x60, w0]
|
596 |
+
|
597 |
+
ncycle = 20000
|
598 |
+
n = N * ncycle
|
599 |
+
sol = np.zeros((n, 7))
|
600 |
+
t = np.linspace(0., Tc * N, n)
|
601 |
+
for j in range(7):
|
602 |
+
sol[0][j] = y0[j]
|
603 |
+
|
604 |
+
result_Vlv = []
|
605 |
+
result_Plv = []
|
606 |
+
result_x6 = []
|
607 |
+
result_x7 = []
|
608 |
+
envx6 = []
|
609 |
+
timesenvx6 = []
|
610 |
+
slopes = []
|
611 |
+
ws = []
|
612 |
+
|
613 |
+
minx6 = 99999
|
614 |
+
tmin = 0
|
615 |
+
tlastupdate = 0
|
616 |
+
lastw = w0
|
617 |
+
update = 1
|
618 |
+
|
619 |
+
#solve the ODE step by step by adding dydt*dt:
|
620 |
+
for j in range(0, n-1):
|
621 |
+
#update y with dydt * dt
|
622 |
+
y = sol[j]
|
623 |
+
dydt = lvad_ode(y, t[j], Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew)
|
624 |
+
for k in range(7):
|
625 |
+
dydt[k] = dydt[k] * (t[j+1] - t[j])
|
626 |
+
sol[j+1] = sol[j] + dydt
|
627 |
+
|
628 |
+
#update the min of x6 in the current cylce. also keep the time at which the min is obtained (for getting the slope later)
|
629 |
+
if (minx6 > sol[j][5]):
|
630 |
+
minx6 = sol[j][5]
|
631 |
+
tmin = t[j]
|
632 |
+
|
633 |
+
#add minimum of x6 once each cycle ends: (works). then reset minx6 to 99999 for calculating again the minimum
|
634 |
+
if (j%ncycle==0 and j>1):
|
635 |
+
envx6.append(minx6)
|
636 |
+
timesenvx6.append(tmin)
|
637 |
+
minx6 = 99999
|
638 |
+
|
639 |
+
if (len(envx6)>=3):
|
640 |
+
slope = getslope(envx6[-1], envx6[-2], envx6[-3], timesenvx6[-1], timesenvx6[-2], timesenvx6[-3])
|
641 |
+
slopes.append(slope)
|
642 |
+
ws.append(y[6])
|
643 |
+
|
644 |
+
for i in range(n):
|
645 |
+
result_x6.append(sol[i, 5])
|
646 |
+
result_x7.append(sol[i, 6])
|
647 |
+
|
648 |
+
suction_w = 0
|
649 |
+
for i in range(2, len(slopes)):
|
650 |
+
if (slopes[i] < 0):
|
651 |
+
suction_w = ws[i-1]
|
652 |
+
break
|
653 |
+
|
654 |
+
return suction_w
|
655 |
+
|
656 |
+
def f_lvad(Tc, start_v, Emax, c, slope, w0, x60, y00, y01, y02, y03, y04): #slope is slope0 for w
|
657 |
+
|
658 |
+
N = 70
|
659 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
660 |
+
|
661 |
+
y0 = [y00, y01, y02, y03, y04, x60, w0]
|
662 |
+
|
663 |
+
ncycle = 10000
|
664 |
+
n = N * ncycle
|
665 |
+
sol = np.zeros((n, 7))
|
666 |
+
t = np.linspace(0., Tc * N, n)
|
667 |
+
for j in range(7):
|
668 |
+
sol[0][j] = y0[j]
|
669 |
+
|
670 |
+
result_Vlv = []
|
671 |
+
result_Plv = []
|
672 |
+
result_x6 = []
|
673 |
+
result_x7 = []
|
674 |
+
envx6 = []
|
675 |
+
timesenvx6 = []
|
676 |
+
|
677 |
+
minx6 = 99999
|
678 |
+
tmin = 0
|
679 |
+
tlastupdate = 0
|
680 |
+
lastw = w0
|
681 |
+
update = 1
|
682 |
+
ratew = 0 #6000/60
|
683 |
+
|
684 |
+
#solve the ODE step by step by adding dydt*dt:
|
685 |
+
for j in range(0, n-1):
|
686 |
+
#update y with dydt * dt
|
687 |
+
y = sol[j]
|
688 |
+
dydt = lvad_ode(y, t[j], Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew)
|
689 |
+
for k in range(7):
|
690 |
+
dydt[k] = dydt[k] * (t[j+1] - t[j])
|
691 |
+
sol[j+1] = sol[j] + dydt
|
692 |
+
|
693 |
+
#update the min of x6 in the current cylce. also keep the time at which the min is obtained (for getting the slope later)
|
694 |
+
if (minx6 > sol[j][5]):
|
695 |
+
minx6 = sol[j][5]
|
696 |
+
tmin = t[j]
|
697 |
+
|
698 |
+
#add minimum of x6 once each cycle ends: (works). then reset minx6 to 99999 for calculating again the minimum
|
699 |
+
if (j%ncycle==0 and j>1):
|
700 |
+
envx6.append(minx6)
|
701 |
+
timesenvx6.append(tmin)
|
702 |
+
minx6 = 99999
|
703 |
+
|
704 |
+
#update w (if 0.005 s. have passed since the last update):
|
705 |
+
if (slope<0):
|
706 |
+
update = 0
|
707 |
+
if (t[j+1] - tlastupdate > 0.005 and slope>0 and update==1): #abs(slope)>0.0001
|
708 |
+
# if there are enough points of envelope: calculate slope:
|
709 |
+
if (len(envx6)>=3):
|
710 |
+
slope = getslope(envx6[-1], envx6[-2], envx6[-3], timesenvx6[-1], timesenvx6[-2], timesenvx6[-3])
|
711 |
+
sol[j+1][6] = lastw + c * slope
|
712 |
+
#otherwise: take arbitrary rate (see Fig. 16a in simaan2008dynamical)
|
713 |
+
else:
|
714 |
+
sol[j+1][6] = lastw + 0.005 * slope
|
715 |
+
#save w(k) (see formula (8) simaan2008dynamical) and the last time of update t[j] (will have to wait 0.005 s for next update of w)
|
716 |
+
tlastupdate = t[j+1]
|
717 |
+
lastw = sol[j+1][6]
|
718 |
+
|
719 |
+
#save functions and print MAP, CO:
|
720 |
+
map = 0
|
721 |
+
Pao = []
|
722 |
+
|
723 |
+
for i in range(n):
|
724 |
+
result_Vlv.append(sol[i, 0] + Vd)
|
725 |
+
result_Plv.append(Plv(sol[i, 0]+Vd, Emax, Emin, t[i], Tc, Vd))
|
726 |
+
result_x6.append(sol[i, 5])
|
727 |
+
result_x7.append(sol[i, 6])
|
728 |
+
Pao.append(sol[i, 3])
|
729 |
+
|
730 |
+
colors0=np.zeros((len(result_Vlv[65*ncycle:70*ncycle]), 3))
|
731 |
+
for col in colors0:
|
732 |
+
col[0]=41/255
|
733 |
+
col[1]=128/255
|
734 |
+
col[2]=205/255
|
735 |
+
|
736 |
+
|
737 |
+
#get co and ef:
|
738 |
+
ved = max(result_Vlv[50 * ncycle:52 * ncycle])
|
739 |
+
ves = min(result_Vlv[50 * ncycle:52 * ncycle])
|
740 |
+
#ves = result_Vlv[50 * ncycle + int(ncycle * 0.2 /Tc + 0.15 * ncycle)]
|
741 |
+
ef = (ved-ves)/ved*100
|
742 |
+
CO = ((ved - ves) * 60/Tc ) / 1000
|
743 |
+
|
744 |
+
#get MAP:
|
745 |
+
for i in range(n - 5*ncycle, n):
|
746 |
+
map += sol[i, 2]
|
747 |
+
map = map/(5*ncycle)
|
748 |
+
|
749 |
+
result_pao = np.array(sol[:, 3])
|
750 |
+
pao_ed = min(Pao[50 * ncycle:52 * ncycle])
|
751 |
+
pao_es = max(Pao[50 * ncycle:52 * ncycle])
|
752 |
+
|
753 |
+
return ef, pao_ed, pao_es, CO, map, result_Vlv[65*ncycle:70*ncycle], result_Plv[65*ncycle:70*ncycle]
|
754 |
+
|
755 |
+
#############################
|
756 |
+
## Demo functions
|
757 |
|
758 |
def generate_example():
|
759 |
# get random input
|
|
|
776 |
animated = "prediction.mp4"
|
777 |
return video, animated, Rm, Ra, Emax, Emin, Vd, Tc, start_v
|
778 |
|
779 |
+
|
780 |
+
def lvad_plots(Rm, Ra, Emax, Emin, Vd, Tc, start_v, gamma):
|
781 |
+
|
782 |
+
ncycle = 10000
|
783 |
+
|
784 |
+
Rs = 1.
|
785 |
+
Rc = 0.0398
|
786 |
+
Ca= 0.08
|
787 |
+
Cs= 1.33
|
788 |
+
Cr= 4.4
|
789 |
+
Ls=0.0005
|
790 |
+
|
791 |
+
#get values for periodic loops:
|
792 |
+
ef_nolvad, pao_ed, pao_es, co_nolvad, y00, y01, y02, y03, y04, Vlv0, Plv0 = f_nolvad(Tc, start_v, Emax, 0)
|
793 |
+
#pao_eds = [pao_ed]
|
794 |
+
#pao_ess = [pao_es]
|
795 |
+
|
796 |
+
#get suction w: (make w go linearly from w0 to w0 + maxtime * 400, and find w at which suction occurs)
|
797 |
+
w0 = 5000.
|
798 |
+
ratew = 400.
|
799 |
+
x60 = 0.
|
800 |
+
suctionw = get_suctionw(Tc, start_v, Emax, y00, y01, y02, y03, y04, w0, x60, ratew)
|
801 |
+
|
802 |
+
#gamma = 1.8
|
803 |
+
c = 0.065 #(in simaan2008dynamical: 0.67, but too fast -> 0.061 gives better shape)
|
804 |
+
slope0 = 100.
|
805 |
+
w0 = suctionw / gamma #if doesn't work (x6 negative), change gamma down to 1.4 or up to 2.1
|
806 |
+
|
807 |
+
#compute new pv loops and ef with lvad added:
|
808 |
+
new_ef, pao_ed, pao_es, CO, MAP, Vlvs, Plvs = f_lvad(Tc, start_v, Emax, c, slope0, w0, x60, y00, y01, y02, y03, y04)
|
809 |
+
|
810 |
+
print("\nParameters: Tc, start_v, Emax:", Tc, start_v, Emax)
|
811 |
+
print("Suction speed:", suctionw)
|
812 |
+
print("EF before LVAD:", ef_nolvad)
|
813 |
+
print("CO before LVAD:", co_nolvad)
|
814 |
+
print("New EF after LVAD:", new_ef, "New CO:", CO, "New MAP:", MAP, "\n\n")
|
815 |
+
|
816 |
+
plt.plot(Vlv0, Plv0, color='blue', label='No LVAD') #blue
|
817 |
+
plt.plot(Vlvs, Plvs, color=(78/255, 192/255, 44/255), label=f"LVAD, ω(0)= {round(w0,2)}r/min") #green
|
818 |
+
plt.xlabel('LV volume (ml)')
|
819 |
+
plt.ylabel('LV pressure (mmHg)')
|
820 |
+
plt.legend(loc='upper left', framealpha=1)
|
821 |
+
plt.ylim((0,220))
|
822 |
+
plt.xlim((0,250))
|
823 |
+
|
824 |
+
return plt
|
825 |
+
|
826 |
title = "<h1 style='text-align: center; margin-bottom: 1rem'> Non-Invasive Medical Digital Twins using Physics-Informed Self-Supervised Learning </h1>"
|
827 |
|
828 |
description = """
|
|
|
834 |
|
835 |
title2 = "<h3 style='text-align: center'> Physics-based model simulation</h3>"
|
836 |
|
837 |
+
description2 = """
|
838 |
+
\n \n
|
839 |
Our model uses a hydraulic analogy model of cardiac function from <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a>. Below you can input values of predicted parameters and output a simulated pressure-volume loop predicted from the <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a> model, which is an ordinary differential equation. Tune parameters and press 'Run simulation.'
|
840 |
"""
|
841 |
|
842 |
+
description3 = """
|
843 |
+
\n\n
|
844 |
+
This model can incorporate a tunable left-ventricular assistance device (LVAD) for in-silico experimentation. Click to view the effect of adding an LVAD to the simulated PV loop.
|
845 |
+
"""
|
846 |
+
|
847 |
gr.Markdown(title)
|
848 |
gr.Markdown(description)
|
849 |
|
|
|
884 |
sl5 = gr.Slider(4.0, 25.0, value= 4.0, label="Vd (ml)")
|
885 |
sl6 = gr.Slider(0.4, 1.7, value= 0.4, label="Tc (s)")
|
886 |
sl7 = gr.Slider(0.0, 280.0, value= 140., label="start_v (ml)")
|
887 |
+
|
888 |
+
gr.Markdown(description2)
|
889 |
+
|
890 |
+
LVAD_button = gr.Button("Add LVAD")
|
891 |
+
|
892 |
+
with gr.Row():
|
893 |
+
gamma = gr.Slider(1.0, 2.0, value= 1.4, label="Pump speed, ω(0)")
|
894 |
|
895 |
generate_button.click(fn=generate_example, outputs = [video,plot,Rm,Ra,Emax,Emin,Vd,Tc,start_v])
|
896 |
|
|
|
897 |
simulation_button.click(fn=pvloop_simulator_plot_only, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7], outputs = [gr.Plot()])
|
898 |
|
899 |
+
LVAD_button.click(fn=lvad_plots, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7,gamma], outputs = [gr.Plot()])
|
900 |
|
901 |
demo.launch()
|
app.py
CHANGED
@@ -495,7 +495,265 @@ def pvloop_simulator_plot_only(Rm, Ra, Emax, Emin, Vd, Tc, start_v):
|
|
495 |
plt.title('Simulated PI-SSL LV Pressure Volume Loop', fontsize=16)
|
496 |
return plot
|
497 |
|
498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
|
500 |
def generate_example():
|
501 |
# get random input
|
@@ -518,6 +776,53 @@ def generate_example():
|
|
518 |
animated = "prediction.mp4"
|
519 |
return video, animated, Rm, Ra, Emax, Emin, Vd, Tc, start_v
|
520 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
title = "<h1 style='text-align: center; margin-bottom: 1rem'> Non-Invasive Medical Digital Twins using Physics-Informed Self-Supervised Learning </h1>"
|
522 |
|
523 |
description = """
|
@@ -529,10 +834,16 @@ We demonstrate the ability of our model to predict left ventricular pressure-vol
|
|
529 |
|
530 |
title2 = "<h3 style='text-align: center'> Physics-based model simulation</h3>"
|
531 |
|
532 |
-
description2 = """
|
|
|
533 |
Our model uses a hydraulic analogy model of cardiac function from <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a>. Below you can input values of predicted parameters and output a simulated pressure-volume loop predicted from the <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a> model, which is an ordinary differential equation. Tune parameters and press 'Run simulation.'
|
534 |
"""
|
535 |
|
|
|
|
|
|
|
|
|
|
|
536 |
gr.Markdown(title)
|
537 |
gr.Markdown(description)
|
538 |
|
@@ -573,12 +884,18 @@ with gr.Blocks() as demo:
|
|
573 |
sl5 = gr.Slider(4.0, 25.0, value= 4.0, label="Vd (ml)")
|
574 |
sl6 = gr.Slider(0.4, 1.7, value= 0.4, label="Tc (s)")
|
575 |
sl7 = gr.Slider(0.0, 280.0, value= 140., label="start_v (ml)")
|
576 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
577 |
|
578 |
generate_button.click(fn=generate_example, outputs = [video,plot,Rm,Ra,Emax,Emin,Vd,Tc,start_v])
|
579 |
|
580 |
simulation_button.click(fn=pvloop_simulator_plot_only, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7], outputs = [gr.Plot()])
|
581 |
|
582 |
-
|
583 |
|
584 |
demo.launch()
|
|
|
495 |
plt.title('Simulated PI-SSL LV Pressure Volume Loop', fontsize=16)
|
496 |
return plot
|
497 |
|
498 |
+
#########################################
|
499 |
+
# LVAD functions
|
500 |
+
# RELU for diodes
|
501 |
+
def r(u):
|
502 |
+
return max(u, 0.)
|
503 |
+
|
504 |
+
def heart_ode0(y, t, Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd):
|
505 |
+
x1, x2, x3, x4, x5 = y #here y is a vector of 5 values (not functions), at time t, used for getting (dy/dt)(t)
|
506 |
+
P_lv = Plv(x1+Vd,Emax,Emin,t,Tc,Vd)
|
507 |
+
dydt = [r(x2-P_lv)/Rm-r(P_lv-x4)/Ra, (x3-x2)/(Rs*Cr)-r(x2-P_lv)/(Cr*Rm), (x2-x3)/(Rs*Cs)+x5/Cs, -x5/Ca+r(P_lv-x4)/(Ca*Ra), (x4-x3-Rc*x5)/Ls]
|
508 |
+
return dydt
|
509 |
+
|
510 |
+
def getslope(y1, y2, y3, x1, x2, x3):
|
511 |
+
sum_x = x1 + x2 + x3
|
512 |
+
sum_y = y1 + y2 + y3
|
513 |
+
sum_xy = x1*y1 + x2*y2 + x3*y3
|
514 |
+
sum_xx = x1*x1 + x2*x2 + x3*x3
|
515 |
+
# calculate the coefficients of the least-squares line
|
516 |
+
n = 3
|
517 |
+
slope = (n*sum_xy - sum_x*sum_y) / (n*sum_xx - sum_x*sum_x)
|
518 |
+
return slope
|
519 |
+
|
520 |
+
### ODE: for each t (here fixed), gives dy/dt as a function of y(t) at that t, so can be used for integrating the vector y over time
|
521 |
+
#it is run for each t going from 0 to tmax
|
522 |
+
def lvad_ode(y, t, Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew):
|
523 |
+
|
524 |
+
#from simaan2008dynamical:
|
525 |
+
Ri = 0.0677
|
526 |
+
R0 = 0.0677
|
527 |
+
Rk = 0
|
528 |
+
x1bar = 1.
|
529 |
+
alpha = -3.5
|
530 |
+
Li = 0.0127
|
531 |
+
L0 = 0.0127
|
532 |
+
b0 = -0.296
|
533 |
+
b1 = -0.027
|
534 |
+
b2 = 9.9025e-7
|
535 |
+
|
536 |
+
x1, x2, x3, x4, x5, x6, x7 = y #here y is a vector of 5 values (not functions), at time t, used for getting (dy/dt)(t)
|
537 |
+
|
538 |
+
P_lv = Plv(x1+Vd,Emax,Emin,t,Tc,Vd)
|
539 |
+
if (P_lv <= x1bar): Rk = alpha * (P_lv - x1bar)
|
540 |
+
Lstar = Li + L0 + b1
|
541 |
+
Lstar2 = -Li -L0 +b1
|
542 |
+
Rstar = Ri + + R0 + Rk + b0
|
543 |
+
|
544 |
+
dydt = [-x6 + r(x2-P_lv)/Rm-r(P_lv-x4)/Ra, (x3-x2)/(Rs*Cr)-r(x2-P_lv)/(Cr*Rm), (x2-x3)/(Rs*Cs)+x5/Cs, -x5/Ca+r(P_lv-x4)/(Ca*Ra) + x6/Ca, (x4-x3)/Ls-Rc*x5/Ls, -P_lv / Lstar2 + x4/Lstar2 + (Ri+R0+Rk-b0) / Lstar2 * x6 - b2 / Lstar2 * x7**2, ratew]
|
545 |
+
|
546 |
+
return dydt
|
547 |
+
|
548 |
+
#returns pv loop and ef when there is no lvad:
|
549 |
+
def f_nolvad(Tc, start_v, Emax, showpvloop):
|
550 |
+
|
551 |
+
N = 20
|
552 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
553 |
+
|
554 |
+
start_pla = float(start_v*Elastance(Emax, Emin, 0, Tc))
|
555 |
+
start_pao = 75.
|
556 |
+
start_pa = start_pao
|
557 |
+
start_qt = 0 #aortic flow is Q_T and is 0 at ED, also see Fig5 in simaan2008dynamical
|
558 |
+
|
559 |
+
y0 = [start_v, start_pla, start_pa, start_pao, start_qt]
|
560 |
+
|
561 |
+
t = np.linspace(0, Tc*N, int(60000*N)) #spaced numbers over interval (start, stop, number_of_steps), 60000 time instances for each heart cycle
|
562 |
+
#changed to 60000 for having integer positions for Tmax
|
563 |
+
#obtain 5D vector solution:
|
564 |
+
sol = odeint(heart_ode0, y0, t, args = (Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc,Vd)) #t: list of values
|
565 |
+
|
566 |
+
result_Vlv = np.array(sol[:, 0]) + Vd
|
567 |
+
result_Plv = np.array([Plv(v, Emax, Emin, xi, Tc, Vd) for xi,v in zip(t,sol[:, 0])])
|
568 |
+
|
569 |
+
#if showpvloop: plt.plot(result_Vlv[18*60000:20*60000], result_Plv[18*60000:20*60000], color='black', label='Without LVAD')
|
570 |
+
|
571 |
+
ved = sol[19*60000, 0] + Vd
|
572 |
+
ves = sol[200*int(60/Tc)+9000+19*60000, 0] + Vd
|
573 |
+
ef = (ved-ves)/ved * 100.
|
574 |
+
minv = min(result_Vlv[19*60000:20*60000-1])
|
575 |
+
minp = min(result_Plv[19*60000:20*60000-1])
|
576 |
+
|
577 |
+
result_pao = np.array(sol[:, 3])
|
578 |
+
pao_ed = min(result_pao[(N-1)*60000:N*60000-1])
|
579 |
+
pao_es = max(result_pao[(N-1)*60000:N*60000-1])
|
580 |
+
|
581 |
+
return ef, pao_ed, pao_es, ((ved - ves) * 60/Tc ) / 1000, sol[19*60000, 0], sol[19*60000, 1], sol[19*60000, 2], sol[19*60000, 3], sol[19*60000, 4], result_Vlv[18*60000:20*60000], result_Plv[18*60000:20*60000]
|
582 |
+
|
583 |
+
#returns the w at which suction occurs: (i.e. for which the slope of the envelopes of x6 becomes negative)
|
584 |
+
def get_suctionw(Tc, start_v, Emax, y00, y01, y02, y03, y04, w0, x60, ratew): #slope is slope0 for w
|
585 |
+
|
586 |
+
N = 70
|
587 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
588 |
+
|
589 |
+
start_pla = float(start_v*Elastance(Emax, Emin, 0, Tc))
|
590 |
+
start_pao = 75.
|
591 |
+
start_pa = start_pao
|
592 |
+
start_qt = 0 #aortic flow is Q_T and is 0 at ED, also see Fig5 in simaan2008dynamical
|
593 |
+
|
594 |
+
y0 = [start_v, start_pla, start_pa, start_pao, start_qt, x60, w0]
|
595 |
+
y0 = [y00, y01, y02, y03, y04, x60, w0]
|
596 |
+
|
597 |
+
ncycle = 20000
|
598 |
+
n = N * ncycle
|
599 |
+
sol = np.zeros((n, 7))
|
600 |
+
t = np.linspace(0., Tc * N, n)
|
601 |
+
for j in range(7):
|
602 |
+
sol[0][j] = y0[j]
|
603 |
+
|
604 |
+
result_Vlv = []
|
605 |
+
result_Plv = []
|
606 |
+
result_x6 = []
|
607 |
+
result_x7 = []
|
608 |
+
envx6 = []
|
609 |
+
timesenvx6 = []
|
610 |
+
slopes = []
|
611 |
+
ws = []
|
612 |
+
|
613 |
+
minx6 = 99999
|
614 |
+
tmin = 0
|
615 |
+
tlastupdate = 0
|
616 |
+
lastw = w0
|
617 |
+
update = 1
|
618 |
+
|
619 |
+
#solve the ODE step by step by adding dydt*dt:
|
620 |
+
for j in range(0, n-1):
|
621 |
+
#update y with dydt * dt
|
622 |
+
y = sol[j]
|
623 |
+
dydt = lvad_ode(y, t[j], Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew)
|
624 |
+
for k in range(7):
|
625 |
+
dydt[k] = dydt[k] * (t[j+1] - t[j])
|
626 |
+
sol[j+1] = sol[j] + dydt
|
627 |
+
|
628 |
+
#update the min of x6 in the current cylce. also keep the time at which the min is obtained (for getting the slope later)
|
629 |
+
if (minx6 > sol[j][5]):
|
630 |
+
minx6 = sol[j][5]
|
631 |
+
tmin = t[j]
|
632 |
+
|
633 |
+
#add minimum of x6 once each cycle ends: (works). then reset minx6 to 99999 for calculating again the minimum
|
634 |
+
if (j%ncycle==0 and j>1):
|
635 |
+
envx6.append(minx6)
|
636 |
+
timesenvx6.append(tmin)
|
637 |
+
minx6 = 99999
|
638 |
+
|
639 |
+
if (len(envx6)>=3):
|
640 |
+
slope = getslope(envx6[-1], envx6[-2], envx6[-3], timesenvx6[-1], timesenvx6[-2], timesenvx6[-3])
|
641 |
+
slopes.append(slope)
|
642 |
+
ws.append(y[6])
|
643 |
+
|
644 |
+
for i in range(n):
|
645 |
+
result_x6.append(sol[i, 5])
|
646 |
+
result_x7.append(sol[i, 6])
|
647 |
+
|
648 |
+
suction_w = 0
|
649 |
+
for i in range(2, len(slopes)):
|
650 |
+
if (slopes[i] < 0):
|
651 |
+
suction_w = ws[i-1]
|
652 |
+
break
|
653 |
+
|
654 |
+
return suction_w
|
655 |
+
|
656 |
+
def f_lvad(Tc, start_v, Emax, c, slope, w0, x60, y00, y01, y02, y03, y04): #slope is slope0 for w
|
657 |
+
|
658 |
+
N = 70
|
659 |
+
global Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emin, Vd
|
660 |
+
|
661 |
+
y0 = [y00, y01, y02, y03, y04, x60, w0]
|
662 |
+
|
663 |
+
ncycle = 10000
|
664 |
+
n = N * ncycle
|
665 |
+
sol = np.zeros((n, 7))
|
666 |
+
t = np.linspace(0., Tc * N, n)
|
667 |
+
for j in range(7):
|
668 |
+
sol[0][j] = y0[j]
|
669 |
+
|
670 |
+
result_Vlv = []
|
671 |
+
result_Plv = []
|
672 |
+
result_x6 = []
|
673 |
+
result_x7 = []
|
674 |
+
envx6 = []
|
675 |
+
timesenvx6 = []
|
676 |
+
|
677 |
+
minx6 = 99999
|
678 |
+
tmin = 0
|
679 |
+
tlastupdate = 0
|
680 |
+
lastw = w0
|
681 |
+
update = 1
|
682 |
+
ratew = 0 #6000/60
|
683 |
+
|
684 |
+
#solve the ODE step by step by adding dydt*dt:
|
685 |
+
for j in range(0, n-1):
|
686 |
+
#update y with dydt * dt
|
687 |
+
y = sol[j]
|
688 |
+
dydt = lvad_ode(y, t[j], Rs, Rm, Ra, Rc, Ca, Cs, Cr, Ls, Emax, Emin, Tc, Vd, ratew)
|
689 |
+
for k in range(7):
|
690 |
+
dydt[k] = dydt[k] * (t[j+1] - t[j])
|
691 |
+
sol[j+1] = sol[j] + dydt
|
692 |
+
|
693 |
+
#update the min of x6 in the current cylce. also keep the time at which the min is obtained (for getting the slope later)
|
694 |
+
if (minx6 > sol[j][5]):
|
695 |
+
minx6 = sol[j][5]
|
696 |
+
tmin = t[j]
|
697 |
+
|
698 |
+
#add minimum of x6 once each cycle ends: (works). then reset minx6 to 99999 for calculating again the minimum
|
699 |
+
if (j%ncycle==0 and j>1):
|
700 |
+
envx6.append(minx6)
|
701 |
+
timesenvx6.append(tmin)
|
702 |
+
minx6 = 99999
|
703 |
+
|
704 |
+
#update w (if 0.005 s. have passed since the last update):
|
705 |
+
if (slope<0):
|
706 |
+
update = 0
|
707 |
+
if (t[j+1] - tlastupdate > 0.005 and slope>0 and update==1): #abs(slope)>0.0001
|
708 |
+
# if there are enough points of envelope: calculate slope:
|
709 |
+
if (len(envx6)>=3):
|
710 |
+
slope = getslope(envx6[-1], envx6[-2], envx6[-3], timesenvx6[-1], timesenvx6[-2], timesenvx6[-3])
|
711 |
+
sol[j+1][6] = lastw + c * slope
|
712 |
+
#otherwise: take arbitrary rate (see Fig. 16a in simaan2008dynamical)
|
713 |
+
else:
|
714 |
+
sol[j+1][6] = lastw + 0.005 * slope
|
715 |
+
#save w(k) (see formula (8) simaan2008dynamical) and the last time of update t[j] (will have to wait 0.005 s for next update of w)
|
716 |
+
tlastupdate = t[j+1]
|
717 |
+
lastw = sol[j+1][6]
|
718 |
+
|
719 |
+
#save functions and print MAP, CO:
|
720 |
+
map = 0
|
721 |
+
Pao = []
|
722 |
+
|
723 |
+
for i in range(n):
|
724 |
+
result_Vlv.append(sol[i, 0] + Vd)
|
725 |
+
result_Plv.append(Plv(sol[i, 0]+Vd, Emax, Emin, t[i], Tc, Vd))
|
726 |
+
result_x6.append(sol[i, 5])
|
727 |
+
result_x7.append(sol[i, 6])
|
728 |
+
Pao.append(sol[i, 3])
|
729 |
+
|
730 |
+
colors0=np.zeros((len(result_Vlv[65*ncycle:70*ncycle]), 3))
|
731 |
+
for col in colors0:
|
732 |
+
col[0]=41/255
|
733 |
+
col[1]=128/255
|
734 |
+
col[2]=205/255
|
735 |
+
|
736 |
+
|
737 |
+
#get co and ef:
|
738 |
+
ved = max(result_Vlv[50 * ncycle:52 * ncycle])
|
739 |
+
ves = min(result_Vlv[50 * ncycle:52 * ncycle])
|
740 |
+
#ves = result_Vlv[50 * ncycle + int(ncycle * 0.2 /Tc + 0.15 * ncycle)]
|
741 |
+
ef = (ved-ves)/ved*100
|
742 |
+
CO = ((ved - ves) * 60/Tc ) / 1000
|
743 |
+
|
744 |
+
#get MAP:
|
745 |
+
for i in range(n - 5*ncycle, n):
|
746 |
+
map += sol[i, 2]
|
747 |
+
map = map/(5*ncycle)
|
748 |
+
|
749 |
+
result_pao = np.array(sol[:, 3])
|
750 |
+
pao_ed = min(Pao[50 * ncycle:52 * ncycle])
|
751 |
+
pao_es = max(Pao[50 * ncycle:52 * ncycle])
|
752 |
+
|
753 |
+
return ef, pao_ed, pao_es, CO, map, result_Vlv[65*ncycle:70*ncycle], result_Plv[65*ncycle:70*ncycle]
|
754 |
+
|
755 |
+
#############################
|
756 |
+
## Demo functions
|
757 |
|
758 |
def generate_example():
|
759 |
# get random input
|
|
|
776 |
animated = "prediction.mp4"
|
777 |
return video, animated, Rm, Ra, Emax, Emin, Vd, Tc, start_v
|
778 |
|
779 |
+
|
780 |
+
def lvad_plots(Rm, Ra, Emax, Emin, Vd, Tc, start_v, gamma):
|
781 |
+
|
782 |
+
ncycle = 10000
|
783 |
+
|
784 |
+
Rs = 1.
|
785 |
+
Rc = 0.0398
|
786 |
+
Ca= 0.08
|
787 |
+
Cs= 1.33
|
788 |
+
Cr= 4.4
|
789 |
+
Ls=0.0005
|
790 |
+
|
791 |
+
#get values for periodic loops:
|
792 |
+
ef_nolvad, pao_ed, pao_es, co_nolvad, y00, y01, y02, y03, y04, Vlv0, Plv0 = f_nolvad(Tc, start_v, Emax, 0)
|
793 |
+
#pao_eds = [pao_ed]
|
794 |
+
#pao_ess = [pao_es]
|
795 |
+
|
796 |
+
#get suction w: (make w go linearly from w0 to w0 + maxtime * 400, and find w at which suction occurs)
|
797 |
+
w0 = 5000.
|
798 |
+
ratew = 400.
|
799 |
+
x60 = 0.
|
800 |
+
suctionw = get_suctionw(Tc, start_v, Emax, y00, y01, y02, y03, y04, w0, x60, ratew)
|
801 |
+
|
802 |
+
#gamma = 1.8
|
803 |
+
c = 0.065 #(in simaan2008dynamical: 0.67, but too fast -> 0.061 gives better shape)
|
804 |
+
slope0 = 100.
|
805 |
+
w0 = suctionw / gamma #if doesn't work (x6 negative), change gamma down to 1.4 or up to 2.1
|
806 |
+
|
807 |
+
#compute new pv loops and ef with lvad added:
|
808 |
+
new_ef, pao_ed, pao_es, CO, MAP, Vlvs, Plvs = f_lvad(Tc, start_v, Emax, c, slope0, w0, x60, y00, y01, y02, y03, y04)
|
809 |
+
|
810 |
+
print("\nParameters: Tc, start_v, Emax:", Tc, start_v, Emax)
|
811 |
+
print("Suction speed:", suctionw)
|
812 |
+
print("EF before LVAD:", ef_nolvad)
|
813 |
+
print("CO before LVAD:", co_nolvad)
|
814 |
+
print("New EF after LVAD:", new_ef, "New CO:", CO, "New MAP:", MAP, "\n\n")
|
815 |
+
|
816 |
+
plt.plot(Vlv0, Plv0, color='blue', label='No LVAD') #blue
|
817 |
+
plt.plot(Vlvs, Plvs, color=(78/255, 192/255, 44/255), label=f"LVAD, ω(0)= {round(w0,2)}r/min") #green
|
818 |
+
plt.xlabel('LV volume (ml)')
|
819 |
+
plt.ylabel('LV pressure (mmHg)')
|
820 |
+
plt.legend(loc='upper left', framealpha=1)
|
821 |
+
plt.ylim((0,220))
|
822 |
+
plt.xlim((0,250))
|
823 |
+
|
824 |
+
return plt
|
825 |
+
|
826 |
title = "<h1 style='text-align: center; margin-bottom: 1rem'> Non-Invasive Medical Digital Twins using Physics-Informed Self-Supervised Learning </h1>"
|
827 |
|
828 |
description = """
|
|
|
834 |
|
835 |
title2 = "<h3 style='text-align: center'> Physics-based model simulation</h3>"
|
836 |
|
837 |
+
description2 = """
|
838 |
+
\n \n
|
839 |
Our model uses a hydraulic analogy model of cardiac function from <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a>. Below you can input values of predicted parameters and output a simulated pressure-volume loop predicted from the <a href='https://ieeexplore.ieee.org/document/4729737/keywords#keywords'>Simaan et al 2008</a> model, which is an ordinary differential equation. Tune parameters and press 'Run simulation.'
|
840 |
"""
|
841 |
|
842 |
+
description3 = """
|
843 |
+
\n\n
|
844 |
+
This model can incorporate a tunable left-ventricular assistance device (LVAD) for in-silico experimentation. Click to view the effect of adding an LVAD to the simulated PV loop.
|
845 |
+
"""
|
846 |
+
|
847 |
gr.Markdown(title)
|
848 |
gr.Markdown(description)
|
849 |
|
|
|
884 |
sl5 = gr.Slider(4.0, 25.0, value= 4.0, label="Vd (ml)")
|
885 |
sl6 = gr.Slider(0.4, 1.7, value= 0.4, label="Tc (s)")
|
886 |
sl7 = gr.Slider(0.0, 280.0, value= 140., label="start_v (ml)")
|
887 |
+
|
888 |
+
gr.Markdown(description2)
|
889 |
+
|
890 |
+
LVAD_button = gr.Button("Add LVAD")
|
891 |
+
|
892 |
+
with gr.Row():
|
893 |
+
gamma = gr.Slider(1.0, 2.0, value= 1.4, label="Pump speed, ω(0)")
|
894 |
|
895 |
generate_button.click(fn=generate_example, outputs = [video,plot,Rm,Ra,Emax,Emin,Vd,Tc,start_v])
|
896 |
|
897 |
simulation_button.click(fn=pvloop_simulator_plot_only, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7], outputs = [gr.Plot()])
|
898 |
|
899 |
+
LVAD_button.click(fn=lvad_plots, inputs = [sl1,sl2,sl3,sl4,sl5,sl6,sl7,gamma], outputs = [gr.Plot()])
|
900 |
|
901 |
demo.launch()
|