Spaces:
Running
Running
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -106,67 +106,96 @@ def analyze_piv_csv(file):
|
|
| 106 |
if not num_cols:
|
| 107 |
return None, "No numeric columns found. Check your CSV file."
|
| 108 |
|
| 109 |
-
fig
|
| 110 |
-
fig.patch.set_facecolor("#
|
| 111 |
-
fig.suptitle("PIV Data Analysis — SJSU CardioLab MCL", color="white", fontsize=
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
ax.
|
| 118 |
-
ax.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
for spine in ["top","right"]: ax.spines[spine].set_visible(False)
|
| 120 |
for spine in ["bottom","left"]: ax.spines[spine].set_color("#2d4a8a")
|
| 121 |
|
| 122 |
-
x =
|
| 123 |
-
|
|
|
|
| 124 |
shear_col = next((c for c in cols if any(k in c for k in ["shear","stress","tau","wss"])), num_cols[1] if len(num_cols)>1 else None)
|
|
|
|
|
|
|
| 125 |
|
| 126 |
-
# Plot 1 - Velocity
|
| 127 |
-
ax1 = axes[0
|
| 128 |
if vel_col:
|
| 129 |
-
|
| 130 |
-
ax1.
|
| 131 |
-
ax1.
|
| 132 |
-
ax1.
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
if shear_col:
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
ax2.
|
| 141 |
-
ax2.fill_between(
|
| 142 |
-
ax2.
|
| 143 |
-
|
| 144 |
-
ax2.
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
ax4.axis("off")
|
| 158 |
-
stats = ""
|
| 159 |
risk = []
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
plt.tight_layout()
|
| 172 |
buf = io.BytesIO()
|
|
|
|
| 106 |
if not num_cols:
|
| 107 |
return None, "No numeric columns found. Check your CSV file."
|
| 108 |
|
| 109 |
+
fig = plt.figure(figsize=(16, 11))
|
| 110 |
+
fig.patch.set_facecolor("#0a1628")
|
| 111 |
+
fig.suptitle("PIV Data Analysis — SJSU CardioLab MCL", color="white", fontsize=18, fontweight="bold", y=0.98)
|
| 112 |
+
|
| 113 |
+
gs = fig.add_gridspec(2, 2, hspace=0.38, wspace=0.32, left=0.08, right=0.97, top=0.93, bottom=0.08)
|
| 114 |
+
axes = [fig.add_subplot(gs[0,0]), fig.add_subplot(gs[0,1]), fig.add_subplot(gs[1,0]), fig.add_subplot(gs[1,1])]
|
| 115 |
+
|
| 116 |
+
def style_ax(ax, title, xlabel, ylabel):
|
| 117 |
+
ax.set_facecolor("#132340")
|
| 118 |
+
ax.set_title(title, color="white", fontweight="bold", fontsize=13, pad=10)
|
| 119 |
+
ax.set_xlabel(xlabel, color="#7eb8f7", fontsize=11)
|
| 120 |
+
ax.set_ylabel(ylabel, color="#7eb8f7", fontsize=11)
|
| 121 |
+
ax.tick_params(colors="#a8b2d8", labelsize=10)
|
| 122 |
+
ax.grid(True, alpha=0.25, color="#2d4a8a", linestyle="--")
|
| 123 |
for spine in ["top","right"]: ax.spines[spine].set_visible(False)
|
| 124 |
for spine in ["bottom","left"]: ax.spines[spine].set_color("#2d4a8a")
|
| 125 |
|
| 126 |
+
x = np.arange(len(df))
|
| 127 |
+
time_col = next((c for c in cols if "time" in c or "frame" in c or "x" == c), None)
|
| 128 |
+
vel_col = next((c for c in cols if any(k in c for k in ["vel","speed","v_mag","magnitude"])), num_cols[0] if num_cols else None)
|
| 129 |
shear_col = next((c for c in cols if any(k in c for k in ["shear","stress","tau","wss"])), num_cols[1] if len(num_cols)>1 else None)
|
| 130 |
+
x_vals = df[time_col] if time_col else x
|
| 131 |
+
x_label = time_col.title() if time_col else "Sample Index"
|
| 132 |
|
| 133 |
+
# Plot 1 - Velocity profile - large filled area chart
|
| 134 |
+
ax1 = axes[0]
|
| 135 |
if vel_col:
|
| 136 |
+
v_data = df[vel_col].values
|
| 137 |
+
ax1.fill_between(x_vals, v_data, alpha=0.25, color="#e63946")
|
| 138 |
+
ax1.plot(x_vals, v_data, color="#e63946", linewidth=3, marker="o", markersize=5, label="Velocity")
|
| 139 |
+
ax1.axhline(y=2.0, color="#ffd700", linestyle="--", linewidth=2, label="Risk threshold: 2.0 m/s")
|
| 140 |
+
max_v = v_data.max()
|
| 141 |
+
max_i = v_data.argmax()
|
| 142 |
+
ax1.annotate(f"Peak: {max_v:.2f} m/s", xy=(x_vals.iloc[max_i] if time_col else max_i, max_v),
|
| 143 |
+
xytext=(10, 10), textcoords="offset points", color="#ffd700", fontsize=10, fontweight="bold",
|
| 144 |
+
arrowprops=dict(arrowstyle="->", color="#ffd700", lw=1.5))
|
| 145 |
+
ax1.legend(fontsize=10, labelcolor="white", facecolor="#132340", framealpha=0.8)
|
| 146 |
+
ax1.set_ylim(bottom=0)
|
| 147 |
+
style_ax(ax1, "Velocity Profile", x_label, "Velocity (m/s)")
|
| 148 |
+
|
| 149 |
+
# Plot 2 - Shear stress with risk zones
|
| 150 |
+
ax2 = axes[1]
|
| 151 |
if shear_col:
|
| 152 |
+
s_data = df[shear_col].values
|
| 153 |
+
x_plot = x_vals.values if time_col else x
|
| 154 |
+
ax2.fill_between(x_plot, s_data, alpha=0.25, color="#4361ee")
|
| 155 |
+
ax2.fill_between(x_plot, s_data, 10, where=s_data>10, alpha=0.4, color="#e63946", label="High risk zone")
|
| 156 |
+
ax2.fill_between(x_plot, s_data, 5, where=(s_data>5)&(s_data<=10), alpha=0.3, color="#ffd700", label="Caution zone")
|
| 157 |
+
ax2.plot(x_plot, s_data, color="#4361ee", linewidth=3, marker="s", markersize=5)
|
| 158 |
+
ax2.axhline(y=5, color="#ffd700", linestyle="--", linewidth=2, label="Caution: 5 Pa")
|
| 159 |
+
ax2.axhline(y=10, color="#e63946", linestyle="--", linewidth=2, label="High risk: 10 Pa")
|
| 160 |
+
ax2.legend(fontsize=9, labelcolor="white", facecolor="#132340", framealpha=0.8)
|
| 161 |
+
ax2.set_ylim(bottom=0)
|
| 162 |
+
style_ax(ax2, "Wall Shear Stress", x_label, "Shear Stress (Pa)")
|
| 163 |
+
|
| 164 |
+
# Plot 3 - Velocity vs Shear scatter plot
|
| 165 |
+
ax3 = axes[2]
|
| 166 |
+
if vel_col and shear_col:
|
| 167 |
+
sc = ax3.scatter(df[vel_col], df[shear_col], c=x, cmap="RdYlGn_r", s=80, edgecolors="white", linewidth=0.5, zorder=5)
|
| 168 |
+
plt.colorbar(sc, ax=ax3, label="Time progression").ax.yaxis.label.set_color("white")
|
| 169 |
+
ax3.axvline(x=2.0, color="#ffd700", linestyle="--", linewidth=2, label="Vel. risk: 2.0")
|
| 170 |
+
ax3.axhline(y=10, color="#e63946", linestyle="--", linewidth=2, label="Shear risk: 10")
|
| 171 |
+
ax3.legend(fontsize=9, labelcolor="white", facecolor="#132340", framealpha=0.8)
|
| 172 |
+
style_ax(ax3, "Velocity vs Shear Stress", "Velocity (m/s)", "Shear Stress (Pa)")
|
| 173 |
+
|
| 174 |
+
# Plot 4 - Clinical summary dashboard
|
| 175 |
+
ax4 = axes[3]
|
| 176 |
+
ax4.set_facecolor("#0d1b3e")
|
| 177 |
ax4.axis("off")
|
|
|
|
| 178 |
risk = []
|
| 179 |
+
summary = "CLINICAL SUMMARY"+chr(10)+"━"*24+chr(10)+chr(10)
|
| 180 |
+
for col in num_cols[:4]:
|
| 181 |
+
mn = round(df[col].mean(),3)
|
| 182 |
+
mx = round(df[col].max(),3)
|
| 183 |
+
mn_v = round(df[col].min(),3)
|
| 184 |
+
summary += f"{col[:14]:14s}"+chr(10)+f" Mean: {mn:8.3f}"+chr(10)+f" Max: {mx:8.3f}"+chr(10)+f" Min: {mn_v:8.3f}"+chr(10)+chr(10)
|
| 185 |
+
if "vel" in col and mx > 2.0: risk.append("HIGH VELOCITY (>2.0 m/s)")
|
| 186 |
+
if "shear" in col and mx > 10: risk.append("HIGH SHEAR (>10 Pa)")
|
| 187 |
+
summary += "━"*24+chr(10)
|
| 188 |
+
if risk:
|
| 189 |
+
summary += "RISK FLAGS DETECTED:"+chr(10)
|
| 190 |
+
for r in risk: summary += " ⚠ "+r+chr(10)
|
| 191 |
+
overall = "HIGH RISK — Clinical review needed"
|
| 192 |
+
else:
|
| 193 |
+
summary += "STATUS: All values normal"+chr(10)
|
| 194 |
+
overall = "LOW RISK — Continue monitoring"
|
| 195 |
+
summary += chr(10)+"OVERALL: "+overall
|
| 196 |
+
ax4.text(0.05, 0.97, summary, transform=ax4.transAxes, color="white", fontsize=10,
|
| 197 |
+
va="top", fontfamily="monospace",
|
| 198 |
+
bbox=dict(boxstyle="round,pad=0.8", facecolor="#132340", edgecolor="#e63946" if risk else "#2ecc71", linewidth=2))
|
| 199 |
|
| 200 |
plt.tight_layout()
|
| 201 |
buf = io.BytesIO()
|