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()
|