Saicharan21 commited on
Commit
41a90a0
Β·
verified Β·
1 Parent(s): 0a2ba20

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +355 -170
app.py CHANGED
@@ -1,6 +1,11 @@
1
  import gradio as gr
2
  import os, requests, io
3
  import numpy as np
 
 
 
 
 
4
  from groq import Groq
5
  from PIL import Image
6
 
@@ -28,103 +33,250 @@ textarea, input[type=number] { background: #f7fafc !important; color: #1a202c !i
28
  label span { color: #2b6cb0 !important; font-weight: 600 !important; font-size: 0.85em !important; text-transform: uppercase !important; }
29
  """
30
 
31
- def analyze_upad_photo(image):
32
- if image is None:
33
- return None, "Please upload a uPAD photo first."
 
34
  try:
35
- img = Image.fromarray(image) if not isinstance(image, Image.Image) else image
36
- img_array = np.array(img)
37
- h, w = img_array.shape[:2]
38
-
39
- # Find the detection zone - center 30% of image
40
- # This is where the Jaffe reaction orange-red color appears
41
- y1 = int(h * 0.35)
42
- y2 = int(h * 0.65)
43
- x1 = int(w * 0.35)
44
- x2 = int(w * 0.65)
45
- zone = img_array[y1:y2, x1:x2]
46
-
47
- # Extract RGB from detection zone
48
- R = float(np.mean(zone[:,:,0]))
49
- G = float(np.mean(zone[:,:,1]))
50
- B = float(np.mean(zone[:,:,2]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- # Jaffe reaction: orange-red color = high R, low B
53
- # Higher R-B score = more creatinine
54
- orange_score = R - B
55
 
56
- # Calibrated formula for Jaffe reaction uPAD
57
- # Based on: orange-red color intensity maps to creatinine concentration
58
- creatinine = max(0, round(0.018 * orange_score - 0.3, 2))
59
 
60
- # CKD Staging
61
- if creatinine < 1.2:
62
- stage = "Normal"
63
- stage_color = "GREEN"
64
- action = "No CKD detected. Continue monitoring annually."
65
- elif creatinine < 1.5:
66
- stage = "Borderline"
67
- stage_color = "YELLOW"
68
- action = "Borderline range. Repeat test in 3 months. Consult physician."
69
- elif creatinine < 3.0:
70
- stage = "Stage 2 CKD"
71
- stage_color = "ORANGE"
72
- action = "Stage 2 CKD detected. Consult nephrologist. Confirm with Heska Element HT5."
73
- elif creatinine < 6.0:
74
- stage = "Stage 3-4 CKD"
75
- stage_color = "RED"
76
- action = "Advanced CKD. Immediate medical consultation required."
77
- else:
78
- stage = "Stage 5 CKD"
79
- stage_color = "CRITICAL"
80
- action = "Kidney failure range. Emergency medical care needed."
81
 
82
- # Draw analysis box on image
83
- result_img = img.copy()
84
- import PIL.ImageDraw as ImageDraw
85
- draw = ImageDraw.Draw(result_img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- # Draw detection zone box in green
88
- draw.rectangle([x1, y1, x2, y2], outline=(0, 255, 0), width=3)
89
- draw.rectangle([x1-1, y1-1, x2+1, y2+1], outline=(0, 200, 0), width=1)
90
-
91
- result = (
92
- "uPAD PHOTO ANALYSIS RESULTS" + chr(10) +
93
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + chr(10) +
94
- "DETECTION ZONE (center 30%):" + chr(10) +
95
- " R (Red): " + str(round(R, 1)) + chr(10) +
96
- " G (Green): " + str(round(G, 1)) + chr(10) +
97
- " B (Blue): " + str(round(B, 1)) + chr(10) +
98
- " Orange Score (R-B): " + str(round(orange_score, 1)) + chr(10) +
99
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + chr(10) +
100
- "CREATININE: " + str(creatinine) + " mg/dL" + chr(10) +
101
- "CKD STAGE: " + stage + " [" + stage_color + "]" + chr(10) +
102
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + chr(10) +
103
- "ACTION: " + action + chr(10) + chr(10) +
104
- "Normal range: 0.6-1.2 mg/dL" + chr(10) +
105
- "Confirm results with: Heska Element HT5" + chr(10) +
106
- "Method: Jaffe Reaction (picric acid)"
107
- )
108
 
109
- return result_img, result
110
 
111
  except Exception as e:
112
- return None, "Error analyzing image: " + str(e)
113
-
114
- def analyze_upad_manual(r, g, b):
115
- c = max(0, round(0.02*(float(r)-float(b))-0.5, 2))
116
- if c < 1.2: s = "Normal - No CKD"
117
- elif c < 1.5: s = "Borderline - Monitor"
118
- elif c < 3.0: s = "Stage 2 CKD"
119
- elif c < 6.0: s = "Stage 3-4 CKD"
120
- else: s = "Stage 5 CKD - Kidney Failure"
121
- return ("uPAD MANUAL ANALYSIS" + chr(10) +
122
- "━━━━━━━━━━━━━━━━━━━━" + chr(10) +
123
- "RGB: R=" + str(r) + " G=" + str(g) + " B=" + str(b) + chr(10) +
124
- "Creatinine: " + str(c) + " mg/dL" + chr(10) +
125
- "CKD Stage: " + s + chr(10) +
126
- "Confirm with: Heska Element HT5")
127
 
 
128
  def get_pubmed(query, n=5):
129
  try:
130
  r = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
@@ -196,65 +348,78 @@ def voice_chat(audio, history):
196
  history.append({"role":"assistant","content":"Voice error: "+str(e)})
197
  return history
198
 
199
- def generate_image(prompt):
200
- if not prompt.strip(): return None, "Please enter a description.", ""
201
- if not HF_TOKEN: return None, "Error: Add HF_TOKEN to Space Settings Secrets.", ""
202
  try:
203
- enhanced = prompt
204
- description = ""
205
- if GROQ_KEY:
206
- try:
207
- client = Groq(api_key=GROQ_KEY)
208
- msgs = [
209
- {"role":"system","content":"You are a biomedical visualization expert for SJSU CardioLab. Do two things: 1) Write a clear 2-3 sentence description of what the image will show. 2) Write a detailed image generation prompt. Format: DESCRIPTION: [description] PROMPT: [prompt]"},
210
- {"role":"user","content":"Create image for: "+prompt+". CardioLab context: 27mm SJM Regent bileaflet mechanical heart valve, Sylgard 184 transparent silicone MCL, green laser PIV, Arduino Uno stepper motor TGT, Whatman paper uPAD microfluidic device, Jaffe reaction orange-red color CKD creatinine."}
211
- ]
212
- resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=300)
213
- full_resp = resp.choices[0].message.content
214
- if "DESCRIPTION:" in full_resp and "PROMPT:" in full_resp:
215
- description = full_resp.split("DESCRIPTION:")[1].split("PROMPT:")[0].strip()
216
- enhanced = full_resp.split("PROMPT:")[1].strip()
217
- else:
218
- description = full_resp[:200]
219
- enhanced = "Highly detailed scientific biomedical illustration: "+prompt+", professional medical diagram, photorealistic, high quality, labeled"
220
- except: enhanced = prompt
221
- headers = {"Authorization":"Bearer "+HF_TOKEN,"Content-Type":"application/json"}
222
- payload = {"inputs":enhanced,"parameters":{"num_inference_steps":8,"guidance_scale":7.5}}
223
- models = [
224
- "https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell",
225
- "https://router.huggingface.co/hf-inference/models/stabilityai/stable-diffusion-xl-base-1.0",
226
- ]
227
- for model_url in models:
228
- try:
229
- r = requests.post(model_url,headers=headers,json=payload,timeout=60)
230
- if r.status_code == 200:
231
- img = Image.open(io.BytesIO(r.content))
232
- return img, "Image generated!", description
233
- except: continue
234
- return None, "Models busy. Try again in 30 seconds.", description
235
  except Exception as e:
236
- return None, "Error: "+str(e), ""
237
 
238
  def piv_tool(velocity, shear, hr):
239
- v = "HIGH - stenosis risk" if float(velocity)>2.0 else "NORMAL"
240
- s = "HIGH - thrombosis risk" if float(shear)>10 else "ELEVATED" if float(shear)>5 else "NORMAL"
241
- hr_s = "ABNORMAL" if float(hr)<60 or float(hr)>100 else "NORMAL"
242
- return ("PIV ANALYSIS RESULTS"+chr(10)+"━━━━━━━━━━━━━━━━━━━━"+chr(10)+
243
- "Velocity: "+str(velocity)+" m/s β†’ "+v+chr(10)+
244
- "Shear: "+str(shear)+" Pa β†’ "+s+chr(10)+
245
- "Heart Rate: "+str(hr)+" bpm β†’ "+hr_s)
246
 
247
  def tgt_tool(tat,pf12,hemo,platelets,time):
248
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
249
- r="HIGH THROMBOGENIC RISK" if risk>=3 else "MODERATE RISK" if risk>=2 else "LOW RISK"
250
- return ("TGT BLOOD ANALYSIS"+chr(10)+"━━━━━━━━━━━━━━━━━━━━"+chr(10)+
251
- "Time: "+str(time)+" min"+chr(10)+
252
- "TAT: "+str(tat)+(" HIGH" if float(tat)>15 else " NORMAL")+chr(10)+
253
- "PF1.2: "+str(pf12)+(" HIGH" if float(pf12)>2.0 else " NORMAL")+chr(10)+
254
- "Hemo: "+str(hemo)+(" HIGH" if float(hemo)>50 else " NORMAL")+chr(10)+
255
- "Platelets: "+str(platelets)+(" LOW" if float(platelets)<150 else " NORMAL")+chr(10)+
256
- "━━━━━━━━━━━━━━━━━━━━"+chr(10)+"OVERALL: "+r)
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  with gr.Blocks(title="CardioLab AI", css=CSS) as demo:
259
  gr.HTML('''<div style="background:linear-gradient(135deg,#1a237e,#b71c1c);padding:25px;text-align:center;border-radius:12px 12px 0 0"><div style="font-size:2.8em;font-weight:900;color:#fff;letter-spacing:3px">CardioLab AI</div></div>''')
260
 
@@ -289,64 +454,84 @@ with gr.Blocks(title="CardioLab AI", css=CSS) as demo:
289
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
290
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  with gr.Tab("uPAD Photo"):
293
- gr.Markdown("### Upload uPAD Photo β€” AI reads color automatically and gives instant CKD diagnosis")
294
- gr.Markdown("**How it works:** AI finds the detection zone in center of image, extracts RGB color from Jaffe reaction area, calculates creatinine level, gives CKD stage")
295
- gr.Markdown("**Supported:** Photo from phone camera, scanned image, or microscope image of uPAD test strip")
296
  with gr.Row():
297
  with gr.Column(scale=1):
298
  photo_input = gr.Image(label="Upload uPAD Photo", type="numpy", height=300)
299
  analyze_btn = gr.Button("Analyze uPAD Photo", variant="primary")
300
- gr.Markdown("**Tips for best results:**")
301
- gr.Markdown("- Take photo in good lighting")
302
- gr.Markdown("- Keep uPAD flat and centered")
303
- gr.Markdown("- Detection zone is center 30% of image")
304
  with gr.Column(scale=1):
305
- photo_result_img = gr.Image(label="Analyzed Image (green box = detection zone)", type="pil", height=300)
306
- photo_result_text = gr.Textbox(label="CKD Analysis Result", lines=16)
307
  analyze_btn.click(analyze_upad_photo, inputs=photo_input, outputs=[photo_result_img, photo_result_text])
308
 
309
  with gr.Tab("uPAD Manual"):
310
- gr.Markdown("### Enter RGB values manually if you already measured them")
311
  with gr.Row():
312
  with gr.Column():
313
- r=gr.Number(label="R value", value=210, info="Range: 0-255")
314
- g=gr.Number(label="G value", value=140, info="Range: 0-255")
315
- b=gr.Number(label="B value", value=80, info="Range: 0-255")
316
  out3=gr.Textbox(label="Result", lines=6)
317
- gr.Button("Analyze uPAD", variant="primary").click(analyze_upad_manual,inputs=[r,g,b],outputs=out3)
 
 
318
 
319
  with gr.Tab("AI Image"):
320
- gr.Markdown("### Real AI Image Generation using FLUX.1")
321
  with gr.Row():
322
- img_prompt = gr.Textbox(placeholder="e.g. bileaflet mechanical heart valve | uPAD microfluidic device | Arduino TGT circuit", label="Describe the image", lines=3, scale=4)
323
  with gr.Column(scale=1):
324
- img_btn = gr.Button("Generate Image", variant="primary")
325
  img_status = gr.Textbox(label="Status", lines=2)
326
- img_desc = gr.Textbox(label="AI Description", lines=3, interactive=False)
327
- img_output = gr.Image(label="Generated Image", type="pil", height=450)
328
  img_btn.click(generate_image, inputs=img_prompt, outputs=[img_output, img_status, img_desc])
329
 
330
- with gr.Tab("PIV"):
331
- gr.Markdown("### Analyze PIV flow data from Mock Circulatory Loop")
332
  with gr.Row():
333
  with gr.Column():
334
- v=gr.Number(label="Max Velocity m/s", value=1.8, info="Normal: 0.5-2.0 m/s")
335
- s=gr.Number(label="Wall Shear Stress Pa", value=6.5, info="Normal: below 5 Pa")
336
- h=gr.Number(label="Heart Rate bpm", value=72, info="Normal: 60-100 bpm")
337
- piv_out=gr.Textbox(label="Result", lines=6)
338
  gr.Button("Analyze PIV", variant="primary").click(piv_tool,inputs=[v,s,h],outputs=piv_out)
339
 
340
- with gr.Tab("TGT"):
341
- gr.Markdown("### Interpret Thrombogenicity Tester blood analysis results")
342
  with gr.Row():
343
  with gr.Column():
344
- t1=gr.Number(label="TAT ng/mL", value=18, info="Normal: below 8")
345
- t2=gr.Number(label="PF1.2 nmol/L", value=2.5, info="Normal: below 2.0")
346
- t3=gr.Number(label="Free Hemoglobin mg/L", value=60, info="Normal: below 20")
347
- t4=gr.Number(label="Platelet Count", value=140, info="Normal: above 150")
348
  t5=gr.Number(label="Time minutes", value=40)
349
- out2=gr.Textbox(label="Result", lines=10)
350
  gr.Button("Analyze TGT", variant="primary").click(tgt_tool,inputs=[t1,t2,t3,t4,t5],outputs=out2)
351
 
352
  demo.launch()
 
1
  import gradio as gr
2
  import os, requests, io
3
  import numpy as np
4
+ import pandas as pd
5
+ import matplotlib
6
+ matplotlib.use("Agg")
7
+ import matplotlib.pyplot as plt
8
+ import matplotlib.patches as mpatches
9
  from groq import Groq
10
  from PIL import Image
11
 
 
33
  label span { color: #2b6cb0 !important; font-weight: 600 !important; font-size: 0.85em !important; text-transform: uppercase !important; }
34
  """
35
 
36
+ # ─── PIV CSV ANALYSIS ────────────────────────────────────────────
37
+ def analyze_piv_csv(file):
38
+ if file is None:
39
+ return None, "Please upload a PIV CSV file."
40
  try:
41
+ df = pd.read_csv(file.name)
42
+ cols = [c.lower().strip() for c in df.columns]
43
+ df.columns = cols
44
+
45
+ fig, axes = plt.subplots(2, 2, figsize=(14, 10))
46
+ fig.patch.set_facecolor("#0d1b3e")
47
+ fig.suptitle("PIV Data Analysis β€” SJSU CardioLab MCL", color="white", fontsize=16, fontweight="bold", y=1.02)
48
+
49
+ colors = ["#e63946", "#4361ee", "#2ecc71", "#e67e22"]
50
+
51
+ # Plot 1 β€” Velocity over time or position
52
+ ax1 = axes[0, 0]
53
+ ax1.set_facecolor("#1a2744")
54
+ vel_col = next((c for c in cols if "vel" in c or "v_" in c or "u" == c or "speed" in c), cols[0] if len(cols)>0 else None)
55
+ x_col = next((c for c in cols if "time" in c or "x" in c or "pos" in c or "frame" in c), None)
56
+ if vel_col and x_col:
57
+ ax1.plot(df[x_col], df[vel_col], color="#e63946", linewidth=2, label=vel_col)
58
+ ax1.set_xlabel(x_col, color="#a8b2d8")
59
+ ax1.set_ylabel(vel_col, color="#a8b2d8")
60
+ elif vel_col:
61
+ ax1.plot(df[vel_col], color="#e63946", linewidth=2)
62
+ ax1.set_ylabel(vel_col, color="#a8b2d8")
63
+ else:
64
+ ax1.plot(df.iloc[:,0], color="#e63946", linewidth=2)
65
+ ax1.set_title("Velocity Profile", color="white", fontweight="bold")
66
+ ax1.tick_params(colors="#a8b2d8")
67
+ ax1.grid(True, alpha=0.2, color="#2d4a8a")
68
+ ax1.spines["bottom"].set_color("#2d4a8a")
69
+ ax1.spines["left"].set_color("#2d4a8a")
70
+ ax1.spines["top"].set_visible(False)
71
+ ax1.spines["right"].set_visible(False)
72
+
73
+ # Plot 2 β€” Shear stress if available
74
+ ax2 = axes[0, 1]
75
+ ax2.set_facecolor("#1a2744")
76
+ shear_col = next((c for c in cols if "shear" in c or "stress" in c or "tau" in c or "wss" in c), None)
77
+ if shear_col:
78
+ ax2.fill_between(range(len(df)), df[shear_col], alpha=0.7, color="#4361ee")
79
+ ax2.plot(df[shear_col], color="#4361ee", linewidth=2)
80
+ ax2.axhline(y=5, color="#e63946", linestyle="--", linewidth=1.5, label="Risk threshold (5 Pa)")
81
+ ax2.axhline(y=10, color="#ff4444", linestyle="--", linewidth=1.5, label="High risk (10 Pa)")
82
+ ax2.set_ylabel("Shear Stress (Pa)", color="#a8b2d8")
83
+ ax2.legend(fontsize=8, labelcolor="white", facecolor="#1a2744")
84
+ else:
85
+ # Plot second numeric column
86
+ num_cols = df.select_dtypes(include=[np.number]).columns.tolist()
87
+ if len(num_cols) >= 2:
88
+ ax2.fill_between(range(len(df)), df[num_cols[1]], alpha=0.7, color="#4361ee")
89
+ ax2.plot(df[num_cols[1]], color="#4361ee", linewidth=2)
90
+ ax2.set_ylabel(num_cols[1], color="#a8b2d8")
91
+ ax2.set_title("Shear Stress / Secondary Variable", color="white", fontweight="bold")
92
+ ax2.tick_params(colors="#a8b2d8")
93
+ ax2.grid(True, alpha=0.2, color="#2d4a8a")
94
+ ax2.spines["bottom"].set_color("#2d4a8a")
95
+ ax2.spines["left"].set_color("#2d4a8a")
96
+ ax2.spines["top"].set_visible(False)
97
+ ax2.spines["right"].set_visible(False)
98
+
99
+ # Plot 3 β€” Distribution histogram
100
+ ax3 = axes[1, 0]
101
+ ax3.set_facecolor("#1a2744")
102
+ num_cols = df.select_dtypes(include=[np.number]).columns.tolist()
103
+ if num_cols:
104
+ ax3.hist(df[num_cols[0]].dropna(), bins=30, color="#2ecc71", alpha=0.8, edgecolor="#1a2744")
105
+ ax3.set_xlabel(num_cols[0], color="#a8b2d8")
106
+ ax3.set_ylabel("Count", color="#a8b2d8")
107
+ ax3.set_title("Value Distribution", color="white", fontweight="bold")
108
+ ax3.tick_params(colors="#a8b2d8")
109
+ ax3.grid(True, alpha=0.2, color="#2d4a8a")
110
+ ax3.spines["bottom"].set_color("#2d4a8a")
111
+ ax3.spines["left"].set_color("#2d4a8a")
112
+ ax3.spines["top"].set_visible(False)
113
+ ax3.spines["right"].set_visible(False)
114
+
115
+ # Plot 4 β€” Summary stats
116
+ ax4 = axes[1, 1]
117
+ ax4.set_facecolor("#1a2744")
118
+ ax4.axis("off")
119
+ num_cols = df.select_dtypes(include=[np.number]).columns.tolist()
120
+ summary_text = "SUMMARY STATISTICS" + chr(10) + "━"*22 + chr(10)
121
+ risk_flags = []
122
+ for col in num_cols[:4]:
123
+ mean_val = df[col].mean()
124
+ max_val = df[col].max()
125
+ min_val = df[col].min()
126
+ summary_text += f"{col[:15]}:" + chr(10)
127
+ summary_text += f" Mean: {mean_val:.3f}" + chr(10)
128
+ summary_text += f" Max: {max_val:.3f}" + chr(10)
129
+ summary_text += f" Min: {min_val:.3f}" + chr(10)
130
+ if "vel" in col.lower() and max_val > 2.0:
131
+ risk_flags.append("HIGH VELOCITY - stenosis risk")
132
+ if "shear" in col.lower() and max_val > 10:
133
+ risk_flags.append("HIGH SHEAR - thrombosis risk")
134
+ if risk_flags:
135
+ summary_text += chr(10) + "RISK FLAGS:" + chr(10)
136
+ for flag in risk_flags:
137
+ summary_text += " ⚠ " + flag + chr(10)
138
+ ax4.text(0.05, 0.95, summary_text, transform=ax4.transAxes,
139
+ color="white", fontsize=9, verticalalignment="top",
140
+ fontfamily="monospace",
141
+ bbox=dict(boxstyle="round", facecolor="#0d1b3e", edgecolor="#4361ee", alpha=0.8))
142
+
143
+ plt.tight_layout()
144
+ buf = io.BytesIO()
145
+ plt.savefig(buf, format="png", facecolor=fig.get_facecolor(), bbox_inches="tight", dpi=120)
146
+ buf.seek(0)
147
+ img = Image.open(buf)
148
+ plt.close()
149
+
150
+ # AI analysis
151
+ ai_summary = ""
152
+ if GROQ_KEY:
153
+ try:
154
+ client = Groq(api_key=GROQ_KEY)
155
+ stats = df.describe().to_string()
156
+ msgs = [{"role":"system","content":"You are a PIV flow analysis expert for SJSU CardioLab. Analyze the statistics from the PIV CSV data and provide a clinical interpretation. Mention velocity ranges, shear stress levels, risk of stenosis or thrombosis, and recommendations."}]
157
+ msgs.append({"role":"user","content":"Analyze this PIV data from our Mock Circulatory Loop with 27mm SJM Regent MHV at 70bpm 5L/min:"+chr(10)+stats[:1000]})
158
+ resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=400)
159
+ ai_summary = chr(10)+"━"*30+chr(10)+"AI CLINICAL INTERPRETATION:"+chr(10)+resp.choices[0].message.content
160
+ except: pass
161
 
162
+ result_text = ("PIV CSV ANALYSIS COMPLETE"+chr(10)+
163
+ "Rows: "+str(len(df))+" | Columns: "+str(len(df.columns))+chr(10)+
164
+ "Columns: "+", ".join(df.columns.tolist())+chr(10)+ai_summary)
165
 
166
+ return img, result_text
 
 
167
 
168
+ except Exception as e:
169
+ return None, "Error reading CSV: "+str(e)+chr(10)+"Make sure your CSV has headers and numeric data."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ # ─── TGT CSV ANALYSIS ────────────────────────────────────────────
172
+ def analyze_tgt_csv(file):
173
+ if file is None:
174
+ return None, "Please upload a TGT CSV file."
175
+ try:
176
+ df = pd.read_csv(file.name)
177
+ cols = [c.lower().strip() for c in df.columns]
178
+ df.columns = cols
179
+
180
+ fig, axes = plt.subplots(2, 2, figsize=(14, 10))
181
+ fig.patch.set_facecolor("#0d1b3e")
182
+ fig.suptitle("TGT Blood Analysis β€” SJSU CardioLab", color="white", fontsize=16, fontweight="bold", y=1.02)
183
+
184
+ # Expected TGT columns: time, TAT, PF12, hemoglobin, platelets
185
+ time_col = next((c for c in cols if "time" in c or "min" in c), None)
186
+ tat_col = next((c for c in cols if "tat" in c or "thrombin" in c), None)
187
+ pf_col = next((c for c in cols if "pf" in c or "pf1" in c or "prothrombin" in c), None)
188
+ hemo_col = next((c for c in cols if "hemo" in c or "hemoglobin" in c or "hgb" in c), None)
189
+ plt_col = next((c for c in cols if "platelet" in c or "plt" in c), None)
190
+
191
+ num_cols = df.select_dtypes(include=[np.number]).columns.tolist()
192
+ x_axis = df[time_col] if time_col else range(len(df))
193
+ x_label = time_col if time_col else "Sample Number"
194
+
195
+ normal_limits = {"tat":8, "pf":2.0, "hemo":20, "platelet":150}
196
+
197
+ def style_ax(ax, title):
198
+ ax.set_facecolor("#1a2744")
199
+ ax.set_title(title, color="white", fontweight="bold")
200
+ ax.tick_params(colors="#a8b2d8")
201
+ ax.set_xlabel(x_label, color="#a8b2d8")
202
+ ax.grid(True, alpha=0.2, color="#2d4a8a")
203
+ ax.spines["bottom"].set_color("#2d4a8a")
204
+ ax.spines["left"].set_color("#2d4a8a")
205
+ ax.spines["top"].set_visible(False)
206
+ ax.spines["right"].set_visible(False)
207
+
208
+ # Plot 1 β€” TAT
209
+ ax1 = axes[0, 0]
210
+ col = tat_col if tat_col else (num_cols[0] if num_cols else None)
211
+ if col:
212
+ ax1.plot(x_axis, df[col], color="#e63946", linewidth=2.5, marker="o", markersize=6, label=col)
213
+ ax1.axhline(y=8, color="#ffd700", linestyle="--", linewidth=1.5, label="Normal limit (8 ng/mL)")
214
+ ax1.fill_between(x_axis, df[col], alpha=0.3, color="#e63946")
215
+ ax1.set_ylabel("TAT (ng/mL)", color="#a8b2d8")
216
+ ax1.legend(fontsize=8, labelcolor="white", facecolor="#1a2744")
217
+ style_ax(ax1, "Thrombin-Antithrombin (TAT)")
218
+
219
+ # Plot 2 β€” PF1.2
220
+ ax2 = axes[0, 1]
221
+ col2 = pf_col if pf_col else (num_cols[1] if len(num_cols)>1 else None)
222
+ if col2:
223
+ ax2.plot(x_axis, df[col2], color="#4361ee", linewidth=2.5, marker="s", markersize=6, label=col2)
224
+ ax2.axhline(y=2.0, color="#ffd700", linestyle="--", linewidth=1.5, label="Normal limit (2.0)")
225
+ ax2.fill_between(x_axis, df[col2], alpha=0.3, color="#4361ee")
226
+ ax2.set_ylabel("PF1.2 (nmol/L)", color="#a8b2d8")
227
+ ax2.legend(fontsize=8, labelcolor="white", facecolor="#1a2744")
228
+ style_ax(ax2, "Prothrombin Fragment (PF1.2)")
229
+
230
+ # Plot 3 β€” Hemoglobin / Hemolysis
231
+ ax3 = axes[1, 0]
232
+ col3 = hemo_col if hemo_col else (num_cols[2] if len(num_cols)>2 else None)
233
+ if col3:
234
+ ax3.bar(range(len(df)), df[col3], color="#2ecc71", alpha=0.8, edgecolor="#1a2744", label=col3)
235
+ ax3.axhline(y=20, color="#ffd700", linestyle="--", linewidth=1.5, label="Normal limit (20 mg/L)")
236
+ ax3.set_ylabel("Free Hemoglobin (mg/L)", color="#a8b2d8")
237
+ ax3.legend(fontsize=8, labelcolor="white", facecolor="#1a2744")
238
+ style_ax(ax3, "Free Hemoglobin (Hemolysis)")
239
+
240
+ # Plot 4 β€” Platelets
241
+ ax4 = axes[1, 1]
242
+ col4 = plt_col if plt_col else (num_cols[3] if len(num_cols)>3 else None)
243
+ if col4:
244
+ ax4.plot(x_axis, df[col4], color="#e67e22", linewidth=2.5, marker="^", markersize=6, label=col4)
245
+ ax4.axhline(y=150, color="#ffd700", linestyle="--", linewidth=1.5, label="Normal minimum (150)")
246
+ ax4.fill_between(x_axis, df[col4], 150, where=df[col4]<150, alpha=0.3, color="#e63946", label="Below normal")
247
+ ax4.set_ylabel("Platelet Count (10Β³/ΞΌL)", color="#a8b2d8")
248
+ ax4.legend(fontsize=8, labelcolor="white", facecolor="#1a2744")
249
+ style_ax(ax4, "Platelet Count")
250
+
251
+ plt.tight_layout()
252
+ buf = io.BytesIO()
253
+ plt.savefig(buf, format="png", facecolor=fig.get_facecolor(), bbox_inches="tight", dpi=120)
254
+ buf.seek(0)
255
+ img = Image.open(buf)
256
+ plt.close()
257
+
258
+ # AI analysis
259
+ ai_summary = ""
260
+ if GROQ_KEY:
261
+ try:
262
+ client = Groq(api_key=GROQ_KEY)
263
+ stats = df.describe().to_string()
264
+ msgs = [{"role":"system","content":"You are a hematology expert for SJSU CardioLab TGT testing. Analyze the blood biomarker data and provide thrombogenicity assessment. Comment on TAT levels, PF1.2, hemolysis, platelet consumption. Give overall thrombogenic risk: LOW MODERATE or HIGH. Reference normal ranges: TAT below 8 ng/mL, PF1.2 below 2.0 nmol/L, Hemoglobin below 20 mg/L, Platelets above 150."}]
265
+ msgs.append({"role":"user","content":"Analyze TGT blood data from 27mm SJM Regent MHV tested in our MCL at 70bpm 5L/min:"+chr(10)+stats[:1000]})
266
+ resp = client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=400)
267
+ ai_summary = chr(10)+"━"*30+chr(10)+"AI THROMBOGENICITY ASSESSMENT:"+chr(10)+resp.choices[0].message.content
268
+ except: pass
269
 
270
+ result_text = ("TGT CSV ANALYSIS COMPLETE"+chr(10)+
271
+ "Rows: "+str(len(df))+" | Columns: "+str(len(df.columns))+chr(10)+
272
+ "Columns detected: "+", ".join(df.columns.tolist())+chr(10)+ai_summary)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
+ return img, result_text
275
 
276
  except Exception as e:
277
+ return None, "Error reading CSV: "+str(e)+chr(10)+"Make sure your CSV has headers like: time, TAT, PF12, hemoglobin, platelets"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
+ # ─── OTHER FUNCTIONS ──────────────────────────────────────────────
280
  def get_pubmed(query, n=5):
281
  try:
282
  r = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi",
 
348
  history.append({"role":"assistant","content":"Voice error: "+str(e)})
349
  return history
350
 
351
+ def analyze_upad_photo(image):
352
+ if image is None:
353
+ return None, "Please upload a uPAD photo first."
354
  try:
355
+ img = Image.fromarray(image) if not isinstance(image, Image.Image) else image
356
+ img_array = np.array(img)
357
+ h, w = img_array.shape[:2]
358
+ y1,y2 = int(h*0.35),int(h*0.65)
359
+ x1,x2 = int(w*0.35),int(w*0.65)
360
+ zone = img_array[y1:y2, x1:x2]
361
+ R = float(np.mean(zone[:,:,0]))
362
+ G = float(np.mean(zone[:,:,1]))
363
+ B = float(np.mean(zone[:,:,2]))
364
+ orange_score = R - B
365
+ creatinine = max(0, round(0.018 * orange_score - 0.3, 2))
366
+ if creatinine < 1.2: stage,action = "Normal","No CKD. Monitor annually."
367
+ elif creatinine < 1.5: stage,action = "Borderline","Repeat in 3 months. Consult physician."
368
+ elif creatinine < 3.0: stage,action = "Stage 2 CKD","Consult nephrologist. Confirm with Heska HT5."
369
+ elif creatinine < 6.0: stage,action = "Stage 3-4 CKD","Advanced CKD. Immediate medical consultation."
370
+ else: stage,action = "Stage 5 CKD","Kidney failure range. Emergency care needed."
371
+ result_img = img.copy()
372
+ import PIL.ImageDraw as ImageDraw
373
+ draw = ImageDraw.Draw(result_img)
374
+ draw.rectangle([x1,y1,x2,y2], outline=(0,255,0), width=3)
375
+ result = ("uPAD PHOTO ANALYSIS"+chr(10)+"━"*27+chr(10)+
376
+ "R: "+str(round(R,1))+" G: "+str(round(G,1))+" B: "+str(round(B,1))+chr(10)+
377
+ "Orange Score: "+str(round(orange_score,1))+chr(10)+"━"*27+chr(10)+
378
+ "CREATININE: "+str(creatinine)+" mg/dL"+chr(10)+
379
+ "CKD STAGE: "+stage+chr(10)+"━"*27+chr(10)+
380
+ "ACTION: "+action+chr(10)+"Confirm with: Heska Element HT5")
381
+ return result_img, result
 
 
 
 
 
382
  except Exception as e:
383
+ return None, "Error: "+str(e)
384
 
385
  def piv_tool(velocity, shear, hr):
386
+ v = "HIGH - stenosis" if float(velocity)>2.0 else "NORMAL"
387
+ s = "HIGH - thrombosis" if float(shear)>10 else "ELEVATED" if float(shear)>5 else "NORMAL"
388
+ return "PIV: Velocity "+str(velocity)+" m/s - "+v+chr(10)+"Shear "+str(shear)+" Pa - "+s+chr(10)+"HR "+str(hr)+" bpm"
 
 
 
 
389
 
390
  def tgt_tool(tat,pf12,hemo,platelets,time):
391
  risk=sum([float(tat)>15,float(pf12)>2.0,float(hemo)>50,float(platelets)<150])
392
+ r="HIGH RISK" if risk>=3 else "MODERATE" if risk>=2 else "LOW RISK"
393
+ return "TGT: TAT "+str(tat)+" PF1.2 "+str(pf12)+chr(10)+"Hemo "+str(hemo)+" Plt "+str(platelets)+chr(10)+"Time "+str(time)+" min"+chr(10)+"RESULT: "+r
 
 
 
 
 
 
394
 
395
+ def generate_image(prompt):
396
+ if not prompt.strip(): return None,"Enter description.","";
397
+ if not HF_TOKEN: return None,"Error: Add HF_TOKEN to Space secrets.","";
398
+ try:
399
+ enhanced=prompt
400
+ description=""
401
+ if GROQ_KEY:
402
+ try:
403
+ client=Groq(api_key=GROQ_KEY)
404
+ msgs=[{"role":"system","content":"Biomedical visualization expert. Format: DESCRIPTION: [desc] PROMPT: [prompt]"},{"role":"user","content":"Create image for: "+prompt}]
405
+ resp=client.chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=200)
406
+ full=resp.choices[0].message.content
407
+ if "DESCRIPTION:" in full and "PROMPT:" in full:
408
+ description=full.split("DESCRIPTION:")[1].split("PROMPT:")[0].strip()
409
+ enhanced=full.split("PROMPT:")[1].strip()
410
+ except: pass
411
+ headers={"Authorization":"Bearer "+HF_TOKEN,"Content-Type":"application/json"}
412
+ payload={"inputs":enhanced,"parameters":{"num_inference_steps":8}}
413
+ for url in ["https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell","https://router.huggingface.co/hf-inference/models/stabilityai/stable-diffusion-xl-base-1.0"]:
414
+ try:
415
+ r=requests.post(url,headers=headers,json=payload,timeout=60)
416
+ if r.status_code==200:
417
+ return Image.open(io.BytesIO(r.content)),"Generated!",description
418
+ except: continue
419
+ return None,"Models busy. Try again.",description
420
+ except Exception as e: return None,"Error: "+str(e),""
421
+
422
+ # ─── UI ───────────────────────────────────────────────────────────
423
  with gr.Blocks(title="CardioLab AI", css=CSS) as demo:
424
  gr.HTML('''<div style="background:linear-gradient(135deg,#1a237e,#b71c1c);padding:25px;text-align:center;border-radius:12px 12px 0 0"><div style="font-size:2.8em;font-weight:900;color:#fff;letter-spacing:3px">CardioLab AI</div></div>''')
425
 
 
454
  search_btn.click(quick_search, inputs=search_input, outputs=search_output)
455
  search_input.submit(quick_search, inputs=search_input, outputs=search_output)
456
 
457
+ with gr.Tab("PIV Data"):
458
+ gr.Markdown("### Upload PIV CSV β€” AI generates charts and clinical interpretation")
459
+ gr.Markdown("**Expected columns:** time/x, velocity, shear_stress (any names work β€” AI detects automatically)")
460
+ with gr.Row():
461
+ with gr.Column(scale=1):
462
+ piv_file = gr.File(label="Upload PIV CSV File", file_types=[".csv"])
463
+ piv_analyze_btn = gr.Button("Analyze PIV Data", variant="primary")
464
+ gr.Markdown("**Sample CSV format:**")
465
+ gr.Markdown("```\ntime,velocity,shear_stress\n0,0.5,2.1\n1,1.2,4.5\n2,1.8,7.2\n```")
466
+ with gr.Column(scale=2):
467
+ piv_chart = gr.Image(label="PIV Charts", type="pil", height=450)
468
+ piv_ai_result = gr.Textbox(label="AI Clinical Analysis", lines=10)
469
+ piv_analyze_btn.click(analyze_piv_csv, inputs=piv_file, outputs=[piv_chart, piv_ai_result])
470
+
471
+ with gr.Tab("TGT Data"):
472
+ gr.Markdown("### Upload TGT CSV β€” AI generates blood biomarker charts and thrombogenicity assessment")
473
+ gr.Markdown("**Expected columns:** time, TAT, PF12, hemoglobin, platelets (any names work β€” AI detects automatically)")
474
+ with gr.Row():
475
+ with gr.Column(scale=1):
476
+ tgt_file = gr.File(label="Upload TGT CSV File", file_types=[".csv"])
477
+ tgt_analyze_btn = gr.Button("Analyze TGT Data", variant="primary")
478
+ gr.Markdown("**Sample CSV format:**")
479
+ gr.Markdown("```\ntime,TAT,PF12,hemoglobin,platelets\n0,5.2,1.1,12,210\n20,9.8,1.8,18,195\n40,14.2,2.4,35,178\n60,18.6,3.1,62,145\n```")
480
+ with gr.Column(scale=2):
481
+ tgt_chart = gr.Image(label="TGT Blood Analysis Charts", type="pil", height=450)
482
+ tgt_ai_result = gr.Textbox(label="AI Thrombogenicity Assessment", lines=10)
483
+ tgt_analyze_btn.click(analyze_tgt_csv, inputs=tgt_file, outputs=[tgt_chart, tgt_ai_result])
484
+
485
  with gr.Tab("uPAD Photo"):
486
+ gr.Markdown("### Upload uPAD Photo β€” Instant CKD diagnosis from Jaffe reaction color")
 
 
487
  with gr.Row():
488
  with gr.Column(scale=1):
489
  photo_input = gr.Image(label="Upload uPAD Photo", type="numpy", height=300)
490
  analyze_btn = gr.Button("Analyze uPAD Photo", variant="primary")
 
 
 
 
491
  with gr.Column(scale=1):
492
+ photo_result_img = gr.Image(label="Analyzed Image", type="pil", height=300)
493
+ photo_result_text = gr.Textbox(label="CKD Result", lines=12)
494
  analyze_btn.click(analyze_upad_photo, inputs=photo_input, outputs=[photo_result_img, photo_result_text])
495
 
496
  with gr.Tab("uPAD Manual"):
 
497
  with gr.Row():
498
  with gr.Column():
499
+ r=gr.Number(label="R value", value=210)
500
+ g=gr.Number(label="G value", value=140)
501
+ b=gr.Number(label="B value", value=80)
502
  out3=gr.Textbox(label="Result", lines=6)
503
+ gr.Button("Analyze", variant="primary").click(
504
+ lambda r,g,b: "Creatinine: "+str(max(0,round(0.02*(r-b)-0.5,2)))+" mg/dL"+chr(10)+("Normal" if max(0,round(0.02*(r-b)-0.5,2))<1.2 else "Borderline" if max(0,round(0.02*(r-b)-0.5,2))<1.5 else "CKD"),
505
+ inputs=[r,g,b], outputs=out3)
506
 
507
  with gr.Tab("AI Image"):
 
508
  with gr.Row():
509
+ img_prompt = gr.Textbox(placeholder="e.g. bileaflet heart valve | uPAD microfluidic | Arduino TGT circuit", label="Describe image", lines=3, scale=4)
510
  with gr.Column(scale=1):
511
+ img_btn = gr.Button("Generate", variant="primary")
512
  img_status = gr.Textbox(label="Status", lines=2)
513
+ img_desc = gr.Textbox(label="AI Description", lines=2, interactive=False)
514
+ img_output = gr.Image(label="Generated Image", type="pil", height=400)
515
  img_btn.click(generate_image, inputs=img_prompt, outputs=[img_output, img_status, img_desc])
516
 
517
+ with gr.Tab("PIV Manual"):
 
518
  with gr.Row():
519
  with gr.Column():
520
+ v=gr.Number(label="Max Velocity m/s", value=1.8)
521
+ s=gr.Number(label="Wall Shear Stress Pa", value=6.5)
522
+ h=gr.Number(label="Heart Rate bpm", value=72)
523
+ piv_out=gr.Textbox(label="Result", lines=5)
524
  gr.Button("Analyze PIV", variant="primary").click(piv_tool,inputs=[v,s,h],outputs=piv_out)
525
 
526
+ with gr.Tab("TGT Manual"):
 
527
  with gr.Row():
528
  with gr.Column():
529
+ t1=gr.Number(label="TAT ng/mL", value=18)
530
+ t2=gr.Number(label="PF1.2 nmol/L", value=2.5)
531
+ t3=gr.Number(label="Free Hemoglobin mg/L", value=60)
532
+ t4=gr.Number(label="Platelet Count", value=140)
533
  t5=gr.Number(label="Time minutes", value=40)
534
+ out2=gr.Textbox(label="Result", lines=8)
535
  gr.Button("Analyze TGT", variant="primary").click(tgt_tool,inputs=[t1,t2,t3,t4,t5],outputs=out2)
536
 
537
  demo.launch()