Spaces:
Running
Running
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,228 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
import streamlit as st
|
| 3 |
-
import numpy as np
|
| 4 |
-
import matplotlib.pyplot as plt
|
| 5 |
-
from matplotlib.patches import Circle, Arc, Rectangle
|
| 6 |
-
from matplotlib.lines import Line2D
|
| 7 |
-
|
| 8 |
-
st.set_page_config(page_title="Flow Down Gradients — Simple Explorer", layout="wide")
|
| 9 |
-
|
| 10 |
-
# ---------------- Pressure presets (mmHg) ----------------
|
| 11 |
-
PRESSURE_PRESETS = {
|
| 12 |
-
"Demo Preset": {
|
| 13 |
-
"cap_o2": 52, "cap_co2": 49,
|
| 14 |
-
"alv_o2": 55, "alv_co2": 45,
|
| 15 |
-
"thoracic": 761,
|
| 16 |
-
},
|
| 17 |
-
"Preset 1": {
|
| 18 |
-
"cap_o2": 50, "cap_co2": 45,
|
| 19 |
-
"alv_o2": 100, "alv_co2": 40,
|
| 20 |
-
"thoracic": 763,
|
| 21 |
-
},
|
| 22 |
-
"Preset 2": {
|
| 23 |
-
"cap_o2": 60, "cap_co2": 45,
|
| 24 |
-
"alv_o2": 58, "alv_co2": 40,
|
| 25 |
-
"thoracic": 757,
|
| 26 |
-
},
|
| 27 |
-
"Preset 3": {
|
| 28 |
-
"cap_o2": 48, "cap_co2": 45,
|
| 29 |
-
"alv_o2": 53, "alv_co2": 8,
|
| 30 |
-
"thoracic": 768,
|
| 31 |
-
},
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
def get_pressure_preset_from_query():
|
| 35 |
-
qp = st.query_params
|
| 36 |
-
key = qp.get("preset") if "preset" in qp else None
|
| 37 |
-
if key in PRESSURE_PRESETS:
|
| 38 |
-
return key
|
| 39 |
-
return "Demo Preset"
|
| 40 |
-
|
| 41 |
-
if "pressure_preset" not in st.session_state:
|
| 42 |
-
st.session_state.pressure_preset = get_pressure_preset_from_query()
|
| 43 |
-
|
| 44 |
-
# ---------------- Action Potential presets ----------------
|
| 45 |
-
AP_PRESETS = {
|
| 46 |
-
"Preset 1": {"more_na": "Extracellular", "more_k": "Intracellular", "more_neg": "Intracellular"},
|
| 47 |
-
"Preset 2": {"more_na": "Intracellular", "more_k": "Extracellular", "more_neg": "Extracellular"},
|
| 48 |
-
"Preset 3": {"more_na": "Extracellular", "more_k": "Intracellular", "more_neg": "Extracellular"},
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
if "ap_preset" not in st.session_state:
|
| 52 |
-
st.session_state.ap_preset = "Preset 1"
|
| 53 |
-
|
| 54 |
-
# ---------------- UI ----------------
|
| 55 |
-
st.title("Flow Down Gradients — Simple Explorer")
|
| 56 |
-
|
| 57 |
-
tab1, tab2 = st.tabs([
|
| 58 |
-
"Pressure (Alveolus ↔ Capillary)",
|
| 59 |
-
"Concentration: Action Potential (extracellular ↔ intracellular)",
|
| 60 |
-
])
|
| 61 |
-
|
| 62 |
-
# ---------------- Tab 1: Pressure ----------------
|
| 63 |
-
with tab1:
|
| 64 |
-
st.subheader("Pressure Gradients (mmHg)")
|
| 65 |
-
# Preset buttons
|
| 66 |
-
bcols = st.columns(4)
|
| 67 |
-
for name, col in zip(PRESSURE_PRESETS.keys(), bcols):
|
| 68 |
-
if col.button(name, use_container_width=True):
|
| 69 |
-
st.session_state.pressure_preset = name
|
| 70 |
-
|
| 71 |
-
# Current preset
|
| 72 |
-
preset_name = st.session_state.pressure_preset
|
| 73 |
-
p = PRESSURE_PRESETS[preset_name]
|
| 74 |
-
|
| 75 |
-
# Values
|
| 76 |
-
st.markdown(f"**Current preset:** {preset_name}")
|
| 77 |
-
st.markdown(
|
| 78 |
-
f"- **Intracapillary O₂:** {p['cap_o2']} mmHg \n"
|
| 79 |
-
f"- **Intracapillary CO₂:** {p['cap_co2']} mmHg \n"
|
| 80 |
-
f"- **Intraalveolar O₂:** {p['alv_o2']} mmHg \n"
|
| 81 |
-
f"- **Intraalveolar CO₂:** {p['alv_co2']} mmHg \n"
|
| 82 |
-
f"- **Thoracic pressure:** {p['thoracic']} mmHg"
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
# Diagram (enlarged, placed below values)
|
| 86 |
-
fig, ax = plt.subplots(figsize=(11.5, 5.6))
|
| 87 |
-
ax.set_xlim(0, 12); ax.set_ylim(0, 6); ax.axis("off")
|
| 88 |
-
|
| 89 |
-
# Alveolus: round sac
|
| 90 |
-
alve_center = (4.0, 3.0); alve_r = 2.1
|
| 91 |
-
alve = Circle(alve_center, alve_r, fill=False, lw=2)
|
| 92 |
-
ax.add_patch(alve)
|
| 93 |
-
ax.text(alve_center[0], alve_center[1] + alve_r + 0.35, "Alveolus", ha="center", fontsize=13)
|
| 94 |
-
|
| 95 |
-
# Capillary: thin vessel hugging right side (two arcs + short straight segments)
|
| 96 |
-
cap_outer = Arc((6.2, 3.0), 3.6, 3.6, angle=0, theta1=-65, theta2=65, lw=6, capstyle="round")
|
| 97 |
-
cap_inner = Arc((6.2, 3.0), 3.0, 3.0, angle=0, theta1=-65, theta2=65, lw=6, capstyle="round")
|
| 98 |
-
ax.add_patch(cap_outer); ax.add_patch(cap_inner)
|
| 99 |
-
# Straight ends to right
|
| 100 |
-
ax.plot([7.8, 11.0], [4.2, 4.2], lw=6, solid_capstyle="round")
|
| 101 |
-
ax.plot([7.8, 11.0], [1.8, 1.8], lw=6, solid_capstyle="round")
|
| 102 |
-
ax.text(11.2, 3.0, "Capillary", va="center", fontsize=13, rotation=90)
|
| 103 |
-
|
| 104 |
-
# Accessibility-friendly markers/colors
|
| 105 |
-
color_o2, marker_o2 = "tab:blue", "o" # O2 = blue circle
|
| 106 |
-
color_co2, marker_co2 = "tab:orange", "s" # CO2 = orange square
|
| 107 |
-
|
| 108 |
-
# Dot counts scaled from mmHg (cap at 150 points per region)
|
| 109 |
-
maxdots = 150
|
| 110 |
-
ao2 = int(np.clip(p["alv_o2"], 0, maxdots))
|
| 111 |
-
aco2 = int(np.clip(p["alv_co2"], 0, maxdots))
|
| 112 |
-
co2 = int(np.clip(p["cap_o2"], 0, maxdots))
|
| 113 |
-
cco2 = int(np.clip(p["cap_co2"], 0, maxdots))
|
| 114 |
-
|
| 115 |
-
# Alveolar dots: random inside circle
|
| 116 |
-
rngA = np.random.default_rng(101)
|
| 117 |
-
need = ao2 + aco2
|
| 118 |
-
if need > 0:
|
| 119 |
-
xs, ys = [], []
|
| 120 |
-
while len(xs) < need:
|
| 121 |
-
x = rngA.uniform(alve_center[0]-alve_r+0.12, alve_center[0]+alve_r-0.12)
|
| 122 |
-
y = rngA.uniform(alve_center[1]-alve_r+0.12, alve_center[1]+alve_r-0.12)
|
| 123 |
-
if (x-alve_center[0])**2 + (y-alve_center[1])**2 <= (alve_r-0.18)**2:
|
| 124 |
-
xs.append(x); ys.append(y)
|
| 125 |
-
xs = np.array(xs); ys = np.array(ys)
|
| 126 |
-
if ao2 > 0:
|
| 127 |
-
ax.scatter(xs[:ao2], ys[:ao2], s=36, c=color_o2, marker=marker_o2)
|
| 128 |
-
if aco2 > 0:
|
| 129 |
-
ax.scatter(xs[ao2:], ys[ao2:], s=36, c=color_co2, marker=marker_co2)
|
| 130 |
-
|
| 131 |
-
# Capillary dots: right rectangular segment for clarity
|
| 132 |
-
rngC = np.random.default_rng(202)
|
| 133 |
-
cx = rngC.uniform(8.2, 10.8, co2 + cco2)
|
| 134 |
-
cy = rngC.uniform(1.95, 4.05, co2 + cco2)
|
| 135 |
-
if co2 > 0:
|
| 136 |
-
ax.scatter(cx[:co2], cy[:co2], s=36, c=color_o2, marker=marker_o2)
|
| 137 |
-
if cco2 > 0:
|
| 138 |
-
ax.scatter(cx[co2:], cy[co2:], s=36, c=color_co2, marker=marker_co2)
|
| 139 |
-
|
| 140 |
-
# Clean, accessible legend using Line2D handles (not cramped)
|
| 141 |
-
legend_elements = [
|
| 142 |
-
Line2D([0], [0], marker=marker_o2, color='w', label='O₂ (Blue • Circle)',
|
| 143 |
-
markerfacecolor=color_o2, markersize=10),
|
| 144 |
-
Line2D([0], [0], marker=marker_co2, color='w', label='CO₂ (Orange • Square)',
|
| 145 |
-
markerfacecolor=color_co2, markersize=10),
|
| 146 |
-
]
|
| 147 |
-
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0.02, 0.98), frameon=False)
|
| 148 |
-
|
| 149 |
-
# Stamp preset name
|
| 150 |
-
ax.text(0.98, 0.05, f"{preset_name}", transform=ax.transAxes, ha="right", va="bottom", fontsize=11, alpha=0.8)
|
| 151 |
-
|
| 152 |
-
st.pyplot(fig, clear_figure=True)
|
| 153 |
-
|
| 154 |
-
# ---------------- Tab 2: Action Potential (presets, no sliders) ----------------
|
| 155 |
-
with tab2:
|
| 156 |
-
st.subheader("Static starting conditions (no animation)")
|
| 157 |
-
bcols = st.columns(3)
|
| 158 |
-
for name, col in zip(AP_PRESETS.keys(), bcols):
|
| 159 |
-
if col.button(name, use_container_width=True):
|
| 160 |
-
st.session_state.ap_preset = name
|
| 161 |
-
|
| 162 |
-
ap = AP_PRESETS[st.session_state.ap_preset]
|
| 163 |
-
st.markdown(f"**Current preset:** {st.session_state.ap_preset}")
|
| 164 |
-
st.markdown(
|
| 165 |
-
f"- More **Na⁺**: {ap['more_na']} \n"
|
| 166 |
-
f"- More **K⁺**: {ap['more_k']} \n"
|
| 167 |
-
f"- More **negative**: {ap['more_neg']}"
|
| 168 |
-
)
|
| 169 |
-
|
| 170 |
-
fig, ax = plt.subplots(figsize=(11.5, 5.0))
|
| 171 |
-
ax.set_xlim(0, 12); ax.set_ylim(0, 6); ax.axis("off")
|
| 172 |
-
|
| 173 |
-
# Compartments
|
| 174 |
-
ax.add_patch(Rectangle((0.8, 1.0), 4.5, 4.0, fill=False, lw=2)) # Extracellular
|
| 175 |
-
ax.add_patch(Rectangle((6.7, 1.0), 4.5, 4.0, fill=False, lw=2)) # Intracellular
|
| 176 |
-
ax.plot([6.2, 6.2], [1.0, 5.0], lw=8) # Membrane
|
| 177 |
-
|
| 178 |
-
ax.text(3.05, 5.25, "Extracellular", ha="center", fontsize=12)
|
| 179 |
-
ax.text(8.95, 5.25, "Intracellular", ha="center", fontsize=12)
|
| 180 |
-
|
| 181 |
-
# Polarity signs ABOVE labels
|
| 182 |
-
if ap["more_neg"] == "Intracellular":
|
| 183 |
-
ax.text(3.05, 5.85, "+", ha="center", va="center", fontsize=24, alpha=0.6)
|
| 184 |
-
ax.text(8.95, 5.85, "−", ha="center", va="center", fontsize=24, alpha=0.9)
|
| 185 |
-
else:
|
| 186 |
-
ax.text(3.05, 5.85, "−", ha="center", va="center", fontsize=24, alpha=0.9)
|
| 187 |
-
ax.text(8.95, 5.85, "+", ha="center", va="center", fontsize=24, alpha=0.6)
|
| 188 |
-
|
| 189 |
-
# Colors + shapes (accessibility): Na+ = blue circle, K+ = orange square
|
| 190 |
-
color_na, marker_na = "tab:blue", "o"
|
| 191 |
-
color_k, marker_k = "tab:orange", "s"
|
| 192 |
-
|
| 193 |
-
# Dot counts: base + extra on "more" side
|
| 194 |
-
base_amt = 12
|
| 195 |
-
extra_amt = 12
|
| 196 |
-
na_out = base_amt + (extra_amt if ap["more_na"] == "Extracellular" else 0)
|
| 197 |
-
na_in = base_amt + (extra_amt if ap["more_na"] == "Intracellular" else 0)
|
| 198 |
-
k_out = base_amt + (extra_amt if ap["more_k"] == "Extracellular" else 0)
|
| 199 |
-
k_in = base_amt + (extra_amt if ap["more_k"] == "Intracellular" else 0)
|
| 200 |
-
|
| 201 |
-
rng2 = np.random.default_rng(808)
|
| 202 |
-
xo = rng2.uniform(1.1, 5.0, na_out + k_out); yo = rng2.uniform(1.2, 4.8, na_out + k_out)
|
| 203 |
-
xi = rng2.uniform(7.0, 11.4, na_in + k_in ); yi = rng2.uniform(1.2, 4.8, na_in + k_in )
|
| 204 |
-
|
| 205 |
-
# Draw ions with fixed color+shape per type, regardless of side
|
| 206 |
-
if na_out > 0:
|
| 207 |
-
ax.scatter(xo[:na_out], yo[:na_out], s=45, c=color_na, marker=marker_na, label="Na⁺")
|
| 208 |
-
if k_out > 0:
|
| 209 |
-
ax.scatter(xo[na_out:], yo[na_out:], s=45, c=color_k, marker=marker_k, label="K⁺")
|
| 210 |
-
if na_in > 0:
|
| 211 |
-
ax.scatter(xi[:na_in], yi[:na_in], s=45, c=color_na, marker=marker_na)
|
| 212 |
-
if k_in > 0:
|
| 213 |
-
ax.scatter(xi[na_in:], yi[na_in:], s=45, c=color_k, marker=marker_k)
|
| 214 |
-
|
| 215 |
-
# Legend
|
| 216 |
-
legend_elements = [
|
| 217 |
-
Line2D([0], [0], marker=marker_na, color='w', label='Na⁺ (Blue • Circle)',
|
| 218 |
-
markerfacecolor=color_na, markersize=10),
|
| 219 |
-
Line2D([0], [0], marker=marker_k, color='w', label='K⁺ (Orange • Square)',
|
| 220 |
-
markerfacecolor=color_k, markersize=10),
|
| 221 |
-
]
|
| 222 |
-
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0.01, 0.98), frameon=False)
|
| 223 |
-
|
| 224 |
-
# Stamp preset
|
| 225 |
-
ax.text(0.98, 0.05, f"{st.session_state.ap_preset}", transform=ax.transAxes,
|
| 226 |
-
ha="right", va="bottom", fontsize=11, alpha=0.8)
|
| 227 |
-
|
| 228 |
-
st.pyplot(fig, clear_figure=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|