drvikasgaur commited on
Commit
4bcfb24
Β·
verified Β·
1 Parent(s): 6acf35a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +440 -175
app.py CHANGED
@@ -130,41 +130,88 @@ CLINICAL_GUIDANCE = (
130
  # ============================================================
131
  WELCOME_HTML = f"""
132
  <div style="max-width:980px;margin:0 auto;">
133
- <div style="padding:14px 16px;border-radius:14px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.10);">
134
- <div style="font-size:18px;font-weight:900;margin-bottom:6px;">
135
- TB X-ray Assistant <span style="opacity:0.75;font-weight:700;font-size:13px;">(research / decision support)</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  </div>
137
 
138
- <div style="opacity:0.9;font-size:13px;line-height:1.35;">
139
- Upload chest X-rays to get an AI screening score, heatmaps, and a simple consensus output.
140
  </div>
141
 
142
- <div style="margin-top:10px;display:flex;flex-wrap:wrap;gap:8px;">
143
- <span style="font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.10);">
144
- <b>{MODEL_NAME_TBNET}</b> + Grad-CAM
145
- </span>
146
- <span style="font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.10);">
147
- Auto lung mask + fail-safe
148
- </span>
149
- <span style="font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.10);">
150
- <b>{MODEL_NAME_RADIO}</b> (optional)
151
- </span>
152
- <span style="font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.10);">
153
- Consensus: βœ… LOW Β· ⚠️ INDET Β· ⚠️ SCREEN+ Β· 🚩 TB+
154
- </span>
155
- <span style="font-size:12px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.10);">
156
- Phone/WhatsApp Mode
157
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </div>
159
 
160
- <div style="margin-top:10px;opacity:0.85;font-size:12.5px;">
161
- <b>Tip:</b> Turn on Phone/WhatsApp Mode for phone photos, WhatsApp-forwards, or screenshots with borders.
 
 
162
  </div>
 
163
  </div>
164
 
165
- <div style="margin-top:12px;padding:10px 12px;border-left:5px solid #f59e0b;border-radius:12px;background:rgba(245,158,11,0.10);font-size:12.5px;line-height:1.35;">
166
- <b>Clinical disclaimer:</b> Not diagnostic. If TB is suspected clinically, pursue CBNAAT/GeneXpert/sputum and/or CT chest regardless of AI output.
 
 
 
 
167
  </div>
 
168
  </div>
169
  """
170
 
@@ -198,6 +245,153 @@ def badge_color_for_state(state: str) -> str:
198
  return "rgba(148,163,184,0.12)" # gray
199
 
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  # ============================================================
202
  # LUNG U-NET (INFERENCE)
203
  # ============================================================
@@ -1063,14 +1257,28 @@ def run_analysis(
1063
  gallery_items = []
1064
  details_md: List[str] = []
1065
 
1066
- # Top banner
1067
  summary_md.append(f"""
1068
- <div style="border:1px solid rgba(255,255,255,0.10); border-radius:14px; padding:12px; margin:10px 0;">
1069
- <div style="font-size:18px; font-weight:900;">Results</div>
1070
- <div style="margin-top:6px; opacity:0.92;">
1071
- Each image shows: <b>{MODEL_NAME_TBNET}</b> result, <b>{MODEL_NAME_RADIO}</b> result (optional), then a <b>final consensus</b>.
1072
- <br/>
1073
- <b>Key:</b> βœ… LOW &nbsp; | &nbsp; ⚠️ INDETERMINATE &nbsp; | &nbsp; ⚠️ SCREEN+ &nbsp; | &nbsp; 🚩 TB+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1074
  </div>
1075
  </div>
1076
  """)
@@ -1099,7 +1307,7 @@ def run_analysis(
1099
  img_size=224,
1100
  )
1101
 
1102
- # RADIO (optional)
1103
  radio_text_long = f"{MODEL_NAME_RADIO} disabled."
1104
  radio_raw_overlay = None
1105
  radio_masked_overlay = None
@@ -1147,7 +1355,7 @@ def run_analysis(
1147
  radio_band = None
1148
  radio_masked_ran = False
1149
 
1150
- # Consensus
1151
  consensus_label, consensus_detail, tb_state, radio_state = build_consensus(
1152
  tb_prob=out["prob"],
1153
  tb_band=out["band"],
@@ -1163,84 +1371,106 @@ def run_analysis(
1163
  attention = "Diffuse / non-focal" if out.get("diffuse_risk", False) else "Focal / localized"
1164
 
1165
  warns = out.get("warnings", [])
1166
- top_warns = warns[:3] if warns else []
1167
- top_warn_line = " β€’ ".join([html_escape(w) for w in top_warns]) if top_warns else "None"
1168
-
1169
  radio_primary_line = "N/A" if radio_primary_val is None else f"{radio_primary_val:.4f}"
1170
  radio_raw_line = "N/A" if radio_raw_val is None else f"{radio_raw_val:.4f}"
1171
  radio_masked_line = "Not run" if radio_masked_val is None else f"{radio_masked_val:.4f}"
1172
 
1173
- if consensus_label == "DISAGREE":
1174
- next_step = "βœ… Next step: Treat as <b>indeterminate</b> β†’ radiologist review + microbiology if clinically suspected."
1175
- elif consensus_label in ("AGREE: TB+", "AGREE: SCREEN+"):
1176
- next_step = "βœ… Next step: Prompt clinician/radiologist review; consider microbiological confirmation if clinically suspected."
1177
- elif consensus_label in ("AGREE: LOW",):
1178
- next_step = "βœ… Next step: If symptoms/risk factors exist, still correlate clinically and consider further testing."
1179
- else:
1180
- next_step = "βœ… Next step: Correlate clinically; radiologist review recommended if uncertainty or symptoms present."
1181
-
1182
- state_badge_tb = f"""
1183
- <span style="padding:4px 10px; border-radius:999px; background:{badge_color_for_state(tb_state)}; font-weight:800;">
1184
- {pretty_state(tb_state)}
1185
- </span>"""
1186
- state_badge_radio = f"""
1187
- <span style="padding:4px 10px; border-radius:999px; background:{badge_color_for_state(radio_state)}; font-weight:800;">
1188
- {pretty_state(radio_state)}
1189
- </span>"""
1190
-
1191
- tb_card = f"""
1192
- <div style="border:1px solid rgba(255,255,255,0.12); border-radius:14px; padding:12px; margin:10px 0;">
1193
- <div style="font-size:16px; font-weight:900; margin-bottom:6px;">{html_escape(name)} β€” {MODEL_NAME_TBNET}</div>
1194
- <div style="margin-bottom:6px;"><b>State:</b> {state_badge_tb}</div>
1195
- <div><b>Result:</b> {html_escape(tb_label)} &nbsp; <span style="opacity:0.9;">(p={tb_prob_line})</span></div>
1196
- <div style="margin-top:6px; opacity:0.92;">
1197
- <b>Reliability:</b> Quality <b>{q:.0f}/100</b> &nbsp; | &nbsp; Lung mask <b>{cov*100:.1f}%</b> &nbsp; | &nbsp; Attention <b>{attention}</b>
 
 
 
 
 
 
 
 
 
1198
  </div>
1199
- <div style="margin-top:6px; opacity:0.92;"><b>Top notes:</b> {top_warn_line}</div>
1200
- <div style="margin-top:10px; padding:10px 12px; border-left:6px solid rgba(96,165,250,0.9); background: rgba(96,165,250,0.10); border-radius:12px;">
1201
- {recommendation_for_band(out.get("band"))}
1202
  </div>
1203
- </div>
1204
- """
1205
 
1206
- if not use_radio:
1207
- radio_card = f"""
1208
- <div style="border:1px solid rgba(255,255,255,0.10); border-radius:14px; padding:12px; margin:10px 0; opacity:0.9;">
1209
- <div style="font-size:16px; font-weight:900; margin-bottom:6px;">{html_escape(name)} β€” {MODEL_NAME_RADIO}</div>
1210
- <div>RADIO is disabled. Enable it to view its independent output and heatmaps.</div>
1211
- </div>
1212
- """
1213
- else:
1214
- gate_info = f"Masked gate={float(radio_gate):.2f} | LungCov={cov:.2f} | Masked ran={radio_masked_ran}"
1215
- radio_card = f"""
1216
- <div style="border:1px solid rgba(255,255,255,0.12); border-radius:14px; padding:12px; margin:10px 0;">
1217
- <div style="font-size:16px; font-weight:900; margin-bottom:6px;">{html_escape(name)} β€” {MODEL_NAME_RADIO}</div>
1218
- <div style="margin-bottom:6px;"><b>State:</b> {state_badge_radio}</div>
1219
- <div><b>Result:</b> {html_escape(radio_result_short)}</div>
1220
- <div style="margin-top:6px; opacity:0.92;">
1221
- <b>Scores:</b> PRIMARY <b>{radio_primary_line}</b> &nbsp; | &nbsp; RAW <b>{radio_raw_line}</b> &nbsp; | &nbsp; MASKED <b>{radio_masked_line}</b>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1222
  </div>
1223
- <div style="margin-top:6px; opacity:0.85;">{html_escape(gate_info)}</div>
1224
- </div>
1225
- """
1226
 
1227
- consensus_card = f"""
1228
- <div style="border:1px solid rgba(255,255,255,0.14); border-radius:16px; padding:12px; margin:10px 0;">
1229
- <div style="font-size:16px; font-weight:950; margin-bottom:6px;">{html_escape(name)} β€” Final consensus</div>
1230
- <div style="margin-bottom:6px;">
1231
- <b>Comparison:</b> TBNet {pretty_state(tb_state)} &nbsp; vs &nbsp; RADIO {pretty_state(radio_state)}
 
 
 
 
 
1232
  </div>
1233
- <div style="margin-bottom:6px;"><b>Consensus:</b> {html_escape(consensus_label)}</div>
1234
- <div style="opacity:0.9; margin-bottom:10px;">{html_escape(consensus_detail)}</div>
1235
- <div style="padding:10px 12px; border-left:6px solid rgba(245,158,11,0.95); background: rgba(245,158,11,0.12); border-radius:12px;">
1236
- {next_step}
1237
  </div>
 
1238
  </div>
1239
  """
1240
 
1241
- summary_md.append(tb_card)
1242
- summary_md.append(radio_card)
1243
- summary_md.append(consensus_card)
1244
 
1245
  # Gallery
1246
  orig_rgb = cv2.cvtColor(cv2.resize(out["orig_gray"], (512, 512)), cv2.COLOR_GRAY2RGB)
@@ -1248,27 +1478,27 @@ def run_analysis(
1248
  mask_overlay = cv2.resize(out["mask_overlay"], (512, 512))
1249
  overlay_big = cv2.resize(out["overlay"], (512, 512))
1250
 
1251
- gallery_items.append((orig_rgb, f"{name} β€’ ORIGINAL"))
1252
- gallery_items.append((vis_rgb, f"{name} β€’ PHONE-PROC" if phone_mode else f"{name} β€’ INPUT"))
1253
- gallery_items.append((mask_overlay, f"{name} β€’ Lung mask overlay"))
1254
  if out["proc_gray"] is not None:
1255
  proc_rgb = cv2.cvtColor(cv2.resize(out["proc_gray"], (512, 512)), cv2.COLOR_GRAY2RGB)
1256
- gallery_items.append((proc_rgb, f"{name} β€’ Masked model input (224x224)"))
1257
- gallery_items.append((overlay_big, f"{name} β€’ TBNet Grad-CAM overlay"))
1258
 
1259
  if radio_raw_overlay is not None:
1260
- gallery_items.append((cv2.resize(radio_raw_overlay, (512, 512)), f"{name} β€’ RADIO RAW heatmap"))
1261
  if radio_masked_overlay is not None:
1262
- gallery_items.append((cv2.resize(radio_masked_overlay, (512, 512)), f"{name} β€’ RADIO MASKED heatmap"))
1263
 
1264
- # Details (collapsible per image) β€” FIXED (no welcome HTML here)
1265
  warn_txt = "\n".join([f"- {w}" for w in out["warnings"]]) if out["warnings"] else "- None"
1266
  details_md.append(
1267
  f"""
1268
  <details>
1269
- <summary><b>{html_escape(name)}</b> β€” detailed report</summary>
1270
 
1271
- **TBNet**
1272
  - Result: **{html_escape(tb_label)}**
1273
  - Probability: {tb_prob_line}
1274
  - Band: {out.get("band", "YELLOW")}
@@ -1276,16 +1506,23 @@ def run_analysis(
1276
  - Lung mask coverage: {cov*100:.1f}%
1277
  - Attention: {attention}
1278
 
1279
- **Consensus**
1280
- - TBNet state: {pretty_state(tb_state)}
1281
- - RADIO state: {pretty_state(radio_state)}
1282
  - Consensus label: **{html_escape(consensus_label)}**
1283
- - Detail: {html_escape(consensus_detail)}
1284
 
1285
- **Warnings**
1286
  {warn_txt}
1287
 
1288
- **RADIO (full)**
 
 
 
 
 
 
 
1289
  {radio_text_long}
1290
 
1291
  </details>
@@ -1309,99 +1546,129 @@ def build_ui():
1309
  .card {border:1px solid rgba(255,255,255,0.12); border-radius:14px; padding:14px; margin:10px 0;}
1310
  """
1311
 
1312
- with gr.Blocks(title="TB X-ray Assistant (TBNet + RADIO)") as demo:
1313
 
1314
- # ---------------------------
1315
- # Welcome screen (shown first)
1316
- # ---------------------------
1317
  with gr.Column(visible=True) as welcome_screen:
1318
- gr.Markdown('<div class="title">Welcome β€” TB X-ray Assistant (HF Spaces)</div>')
1319
  gr.HTML(WELCOME_HTML)
1320
- continue_btn = gr.Button("Continue β†’", variant="primary")
1321
 
1322
- # ---------------------------
1323
- # Main app UI (hidden initially)
1324
- # ---------------------------
1325
  with gr.Column(visible=False) as main_app:
1326
- gr.Markdown('<div class="title">TB X-ray Assistant (Auto Lung Mask β€’ Research Use)</div>')
 
1327
  gr.Markdown(
1328
- f"<div class='subtitle'>Auto lung mask β†’ <b>{MODEL_NAME_TBNET}</b> + Grad-CAM β€’ "
1329
- f"Optional <b>{MODEL_NAME_RADIO}</b> (C-RADIOv4 + heads) β€’ Clear per-model results + consensus</div>"
 
 
1330
  )
1331
 
1332
  gr.Markdown(
1333
- "<div class='warnbox'><b>Clinical disclaimer:</b> Decision support only (not diagnostic). "
1334
- "If TB is clinically suspected, pursue microbiology / CT as appropriate regardless of AI output.</div>"
 
1335
  )
1336
 
1337
  with gr.Row():
1338
- with gr.Column(scale=1):
1339
- gr.Markdown("#### Model settings")
1340
-
1341
- tb_weights = gr.Textbox(label="TBNet weights (.pt)", value=DEFAULT_TB_WEIGHTS)
1342
- lung_weights = gr.Textbox(label="Lung U-Net weights (.pt)", value=DEFAULT_LUNG_WEIGHTS)
1343
-
1344
- backbone = gr.Dropdown(
1345
- choices=["efficientnet_b0"],
1346
- value="efficientnet_b0",
1347
- label="TBNet backbone"
1348
- )
1349
 
1350
- threshold = gr.Slider(
1351
- 0.01, 0.99, value=TBNET_SCREEN_THR, step=0.01,
1352
- label=f"Reference threshold (TBNet screen+) = {TBNET_SCREEN_THR:.2f}"
1353
  )
1354
 
1355
  phone_mode = gr.Checkbox(
1356
  value=False,
1357
- label="Phone/WhatsApp Mode (safe: conditional crop + conditional CLAHE)"
1358
  )
 
1359
  gr.Markdown(
1360
- "<div class='subtitle'>Enable for WhatsApp images, phone photos, or screenshots. "
1361
- "Leave off for clean digital exports.</div>"
 
 
1362
  )
1363
 
1364
- use_radio = gr.Checkbox(value=True, label=f"Enable {MODEL_NAME_RADIO}")
1365
- radio_gate = gr.Slider(
1366
- 0.10, 0.40, value=RADIO_GATE_DEFAULT, step=0.01,
1367
- label="RADIO masked gate (run masked head if lung coverage β‰₯ gate)"
1368
  )
1369
 
1370
- gr.Markdown(
1371
- "<div class='warnbox'><b>Fail-safe:</b> If lung segmentation is too small or looks unreliable, "
1372
- f"{MODEL_NAME_TBNET} scoring is disabled to avoid unsafe outputs.</div>"
1373
- )
1374
 
1375
- gr.Markdown(
1376
- f"<div class='subtitle'>Device: <b>{DEVICE}</b> (FORCE_CPU={FORCE_CPU})</div>"
1377
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1378
 
1379
  back_btn = gr.Button("← Back to Welcome", variant="secondary")
1380
 
1381
- with gr.Column(scale=2):
1382
- gr.Markdown("#### Upload images")
1383
- files = gr.Files(
1384
- label="Upload one or multiple X-ray images",
1385
- file_types=[".png", ".jpg", ".jpeg", ".bmp"]
1386
- )
1387
- run_btn = gr.Button("Run Analysis", variant="primary")
1388
- status = gr.Textbox(label="Status", value="Ready.", interactive=False)
1389
 
1390
- gr.Markdown("""
1391
- <div class='legend'><b>Gallery legend:</b><br/>
1392
- 1) ORIGINAL &nbsp;β€’&nbsp; 2) INPUT / PHONE-PROC &nbsp;β€’&nbsp; 3) Lung mask overlay &nbsp;β€’&nbsp;
1393
- 4) Masked model input &nbsp;β€’&nbsp; 5) TBNet Grad-CAM &nbsp;β€’&nbsp; 6) RADIO heatmaps</div>
 
 
 
 
 
1394
  """)
1395
 
1396
- gr.Markdown("#### Summary (per image)")
1397
- summary = gr.Markdown("Upload images and click <b>Run Analysis</b>.")
1398
- gallery = gr.Gallery(label="Visual outputs", columns=3, height=560)
1399
 
1400
  with gr.Row():
1401
  with gr.Column(scale=1):
1402
  disclaimer_box = gr.Markdown(CLINICAL_DISCLAIMER)
1403
  with gr.Column(scale=2):
1404
- gr.Markdown("#### Detailed report (expand per image)")
 
 
 
1405
  details = gr.Markdown("")
1406
 
1407
  run_btn.click(
@@ -1419,9 +1686,7 @@ def build_ui():
1419
  outputs=[summary, gallery, details, disclaimer_box, status]
1420
  )
1421
 
1422
- # ---------------------------
1423
  # Transitions
1424
- # ---------------------------
1425
  continue_btn.click(
1426
  fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
1427
  inputs=[],
@@ -1443,7 +1708,7 @@ if __name__ == "__main__":
1443
  server_name="0.0.0.0",
1444
  server_port=int(os.environ.get("PORT", 7860)),
1445
  show_error=True,
1446
- ssr_mode=False, # avoids Gradio SSR localhost/proxy issue
1447
  css="""
1448
  .title {font-size: 28px; font-weight: 900; margin-bottom: 6px;}
1449
  .subtitle {font-size: 14px; opacity: 0.88; margin-bottom: 14px;}
 
130
  # ============================================================
131
  WELCOME_HTML = f"""
132
  <div style="max-width:980px;margin:0 auto;">
133
+
134
+ <div style="padding:20px;border-radius:18px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.12);">
135
+
136
+ <div style="font-size:24px;font-weight:950;margin-bottom:8px;">
137
+ Chest X-ray TB Screening Assistant
138
+ </div>
139
+
140
+ <div style="font-size:15px;line-height:1.55;opacity:0.92;">
141
+ This app helps review chest X-ray images for signs that may be seen with pulmonary tuberculosis,
142
+ also called TB. It is meant to support medical review, not replace a doctor, radiologist, or lab test.
143
+ </div>
144
+
145
+ <div style="margin-top:18px;padding:14px;border-radius:14px;background:rgba(59,130,246,0.10);border:1px solid rgba(59,130,246,0.22);">
146
+ <div style="font-size:17px;font-weight:900;margin-bottom:8px;">How the app works</div>
147
+
148
+ <div style="font-size:14px;line-height:1.55;">
149
+ <b>Step 1 β€” Upload a chest X-ray image.</b><br/>
150
+ You can upload a normal X-ray image, phone photo, WhatsApp image, or screenshot.
151
+ <br/><br/>
152
+
153
+ <b>Step 2 β€” The app checks the lung area.</b><br/>
154
+ It tries to find the lung region first, so the AI focuses on the chest instead of borders,
155
+ labels, or background.
156
+ <br/><br/>
157
+
158
+ <b>Step 3 β€” The AI checks for TB-like patterns.</b><br/>
159
+ The app gives a simple result and shows image views that help explain what was checked.
160
+ </div>
161
  </div>
162
 
163
+ <div style="margin-top:18px;font-size:17px;font-weight:900;">
164
+ What the result means
165
  </div>
166
 
167
+ <div style="margin-top:10px;display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;">
168
+
169
+ <div style="padding:12px;border-radius:14px;background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.25);">
170
+ <div style="font-weight:900;">βœ… Low</div>
171
+ <div style="font-size:13px;line-height:1.45;margin-top:4px;">
172
+ The AI did not find a strong TB-like pattern in the image.
173
+ </div>
174
+ </div>
175
+
176
+ <div style="padding:12px;border-radius:14px;background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.25);">
177
+ <div style="font-weight:900;">⚠️ Unclear</div>
178
+ <div style="font-size:13px;line-height:1.45;margin-top:4px;">
179
+ The image or AI result is not reliable enough. Medical review is advised.
180
+ </div>
181
+ </div>
182
+
183
+ <div style="padding:12px;border-radius:14px;background:rgba(245,158,11,0.18);border:1px solid rgba(245,158,11,0.32);">
184
+ <div style="font-weight:900;">⚠️ Needs review</div>
185
+ <div style="font-size:13px;line-height:1.45;margin-top:4px;">
186
+ The AI found a possible TB-like pattern. A clinician or radiologist should review it.
187
+ </div>
188
+ </div>
189
+
190
+ <div style="padding:12px;border-radius:14px;background:rgba(239,68,68,0.16);border:1px solid rgba(239,68,68,0.32);">
191
+ <div style="font-weight:900;">🚩 TB-like pattern seen</div>
192
+ <div style="font-size:13px;line-height:1.45;margin-top:4px;">
193
+ The AI found stronger TB-like features. Prompt medical review is recommended.
194
+ </div>
195
+ </div>
196
+
197
  </div>
198
 
199
+ <div style="margin-top:18px;padding:13px;border-radius:14px;background:rgba(148,163,184,0.10);border:1px solid rgba(148,163,184,0.22);font-size:14px;line-height:1.5;">
200
+ <b>When to use Phone/WhatsApp Mode:</b><br/>
201
+ Turn it on if the X-ray is a phone photo, WhatsApp-forwarded image, screenshot, cropped image,
202
+ or has black/white borders. It helps the app handle common image-quality issues.
203
  </div>
204
+
205
  </div>
206
 
207
+ <div style="margin-top:14px;padding:14px 16px;border-left:6px solid #f59e0b;border-radius:14px;background:rgba(245,158,11,0.12);font-size:14px;line-height:1.55;">
208
+ <b>Important medical notice:</b><br/>
209
+ This app is not a diagnostic test. A low AI result does <b>not</b> rule out TB, especially early,
210
+ subtle, diffuse, or miliary TB. If symptoms or clinical suspicion are present, please seek
211
+ clinician/radiologist review and consider CBNAAT/GeneXpert, sputum testing, and/or CT chest
212
+ regardless of the AI result.
213
  </div>
214
+
215
  </div>
216
  """
217
 
 
245
  return "rgba(148,163,184,0.12)" # gray
246
 
247
 
248
+ # ============================================================
249
+ # PATIENT-FRIENDLY RESULT HELPERS
250
+ # ============================================================
251
+ def patient_state_label(state: str) -> str:
252
+ return {
253
+ "LOW": "βœ… Low TB-like pattern",
254
+ "INDET": "⚠️ Unclear result",
255
+ "SCREEN+": "⚠️ Needs medical review",
256
+ "TB+": "🚩 TB-like pattern seen",
257
+ "N/A": "⚠️ Not available",
258
+ }.get(state, "⚠️ Unclear result")
259
+
260
+
261
+ def patient_state_meaning(state: str) -> str:
262
+ return {
263
+ "LOW": (
264
+ "The AI did not find a strong TB-like pattern in this X-ray. "
265
+ "This does not completely rule out TB."
266
+ ),
267
+ "INDET": (
268
+ "The result is not clear enough to rely on. This may happen because of image quality, "
269
+ "cropping, unusual positioning, or disagreement between AI checks."
270
+ ),
271
+ "SCREEN+": (
272
+ "The AI found a possible TB-like pattern. This should be reviewed by a clinician or radiologist."
273
+ ),
274
+ "TB+": (
275
+ "The AI found stronger TB-like features. This is not a diagnosis, but prompt medical review is recommended."
276
+ ),
277
+ "N/A": (
278
+ "The app could not safely calculate a result for this image."
279
+ ),
280
+ }.get(state, "The result is unclear and should be reviewed clinically.")
281
+
282
+
283
+ def patient_quality_text(q: float) -> Tuple[str, str]:
284
+ if q >= 75:
285
+ return "Good image quality", "The image looks clear enough for AI screening."
286
+ if q >= 55:
287
+ return "Acceptable image quality", "The image can be checked, but some quality limits may affect accuracy."
288
+ return "Low image quality", "The image may be blurry, dark, overexposed, cropped, or compressed. The result is less reliable."
289
+
290
+
291
+ def patient_lung_mask_text(cov: float) -> Tuple[str, str]:
292
+ if cov < FAIL_COV:
293
+ return "Lung area not found safely", "The app could not confidently find enough lung area, so TB scoring may be disabled."
294
+ if cov < WARN_COV:
295
+ return "Lung area partly found", "The app found only part of the lung area. The result may be less reliable."
296
+ return "Lung area found", "The app found enough lung area to run the AI screening."
297
+
298
+
299
+ def patient_attention_text(diffuse: bool) -> str:
300
+ if diffuse:
301
+ return "The AI attention was spread out and not focused in one clear area. This makes the result less certain."
302
+ return "The AI attention was focused enough to create a useful heatmap."
303
+
304
+
305
+ def patient_score_text(prob: Optional[float]) -> str:
306
+ if prob is None:
307
+ return "No TB score was calculated because the safety check stopped the AI from giving a possibly unreliable result."
308
+ return (
309
+ f"For transparency, the AI screening score was <b>{prob:.3f}</b>. "
310
+ "A higher score means the AI saw more TB-like pattern, but the score alone is not a diagnosis."
311
+ )
312
+
313
+
314
+ def patient_radio_text(use_radio: bool, radio_state: str, radio_primary_val: Optional[float]) -> str:
315
+ if not use_radio:
316
+ return "The second AI check was turned off."
317
+ if radio_state == "N/A" or radio_primary_val is None:
318
+ return "The second AI check was not available for this image."
319
+ return (
320
+ f"The second AI check gave: <b>{patient_state_label(radio_state)}</b>. "
321
+ f"For transparency, its score was <b>{radio_primary_val:.3f}</b>."
322
+ )
323
+
324
+
325
+ def patient_agreement_text(tb_state: str, radio_state: str, use_radio: bool) -> str:
326
+ if not use_radio:
327
+ return "Only the main AI check was used for this image."
328
+
329
+ if radio_state == "N/A":
330
+ return "Only the main AI check was available for this image."
331
+
332
+ if tb_state == radio_state:
333
+ return "Both AI checks gave a similar result."
334
+
335
+ if tb_state == "LOW" and radio_state in ("SCREEN+", "TB+"):
336
+ return "The two AI checks disagreed. Because one check found a possible TB-like pattern, treat this as unclear and review medically."
337
+
338
+ if radio_state == "LOW" and tb_state in ("SCREEN+", "TB+"):
339
+ return "The two AI checks disagreed. Because one check found a possible TB-like pattern, treat this as unclear and review medically."
340
+
341
+ return "The two AI checks were not fully aligned, so medical review is advised."
342
+
343
+
344
+ def patient_final_state(tb_state: str, radio_state: str, use_radio: bool, consensus_label: str) -> str:
345
+ if tb_state == "TB+" or radio_state == "TB+" or consensus_label == "AGREE: TB+":
346
+ return "TB+"
347
+
348
+ if tb_state == "SCREEN+" or radio_state == "SCREEN+" or consensus_label == "AGREE: SCREEN+":
349
+ return "SCREEN+"
350
+
351
+ if tb_state == "INDET" or radio_state == "INDET":
352
+ return "INDET"
353
+
354
+ if tb_state == "LOW" and (not use_radio or radio_state in ("LOW", "N/A")):
355
+ return "LOW"
356
+
357
+ return "INDET"
358
+
359
+
360
+ def patient_next_step_text(final_state: str) -> str:
361
+ if final_state == "TB+":
362
+ return (
363
+ "Please arrange prompt clinician/radiologist review. If TB is clinically suspected, "
364
+ "microbiological confirmation such as CBNAAT/GeneXpert and sputum testing is recommended."
365
+ )
366
+
367
+ if final_state == "SCREEN+":
368
+ return (
369
+ "Please get clinician/radiologist review. This is not a diagnosis, but the image should not be ignored."
370
+ )
371
+
372
+ if final_state == "INDET":
373
+ return (
374
+ "Do not rely on this AI result alone. Repeat with a better-quality image if possible and seek medical review "
375
+ "if symptoms or TB risk factors are present."
376
+ )
377
+
378
+ return (
379
+ "No strong TB-like pattern was found by the AI. However, if there are symptoms, TB exposure, weight loss, fever, "
380
+ "cough, immunosuppression, or strong clinical concern, medical review and testing are still advised."
381
+ )
382
+
383
+
384
+ def patient_warning_html(warnings: List[str]) -> str:
385
+ if not warnings:
386
+ return "No major image-quality warnings were detected."
387
+
388
+ items = "".join([f"<li>{html_escape(w)}</li>" for w in warnings[:4]])
389
+ extra = "" if len(warnings) <= 4 else f"<li>{len(warnings) - 4} more note(s) in the technical details.</li>"
390
+ return f"<ul style='margin:6px 0 0 18px;padding:0;'>{items}{extra}</ul>"
391
+
392
+
393
+
394
+
395
  # ============================================================
396
  # LUNG U-NET (INFERENCE)
397
  # ============================================================
 
1257
  gallery_items = []
1258
  details_md: List[str] = []
1259
 
 
1260
  summary_md.append(f"""
1261
+ <div style="border:1px solid rgba(255,255,255,0.14);border-radius:18px;padding:16px;margin:12px 0;background:rgba(255,255,255,0.035);">
1262
+ <div style="font-size:20px;font-weight:950;margin-bottom:8px;">Your X-ray screening results</div>
1263
+
1264
+ <div style="font-size:14px;line-height:1.55;opacity:0.94;">
1265
+ The app checks each image and gives a simple result. Please read the result together with the
1266
+ <b>image reliability</b> and <b>what to do next</b> sections.
1267
+ </div>
1268
+
1269
+ <div style="margin-top:12px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:8px;">
1270
+ <div style="padding:10px;border-radius:12px;background:rgba(34,197,94,0.12);border:1px solid rgba(34,197,94,0.24);font-size:13px;">
1271
+ βœ… <b>Low</b><br/>No strong TB-like pattern found.
1272
+ </div>
1273
+ <div style="padding:10px;border-radius:12px;background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.24);font-size:13px;">
1274
+ ⚠️ <b>Unclear</b><br/>Result is not reliable enough.
1275
+ </div>
1276
+ <div style="padding:10px;border-radius:12px;background:rgba(245,158,11,0.18);border:1px solid rgba(245,158,11,0.30);font-size:13px;">
1277
+ ⚠️ <b>Needs review</b><br/>Possible TB-like pattern.
1278
+ </div>
1279
+ <div style="padding:10px;border-radius:12px;background:rgba(239,68,68,0.16);border:1px solid rgba(239,68,68,0.30);font-size:13px;">
1280
+ 🚩 <b>TB-like pattern seen</b><br/>Prompt review advised.
1281
+ </div>
1282
  </div>
1283
  </div>
1284
  """)
 
1307
  img_size=224,
1308
  )
1309
 
1310
+ # RADIO / second AI check (optional)
1311
  radio_text_long = f"{MODEL_NAME_RADIO} disabled."
1312
  radio_raw_overlay = None
1313
  radio_masked_overlay = None
 
1355
  radio_band = None
1356
  radio_masked_ran = False
1357
 
1358
+ # Consensus / final result
1359
  consensus_label, consensus_detail, tb_state, radio_state = build_consensus(
1360
  tb_prob=out["prob"],
1361
  tb_band=out["band"],
 
1371
  attention = "Diffuse / non-focal" if out.get("diffuse_risk", False) else "Focal / localized"
1372
 
1373
  warns = out.get("warnings", [])
 
 
 
1374
  radio_primary_line = "N/A" if radio_primary_val is None else f"{radio_primary_val:.4f}"
1375
  radio_raw_line = "N/A" if radio_raw_val is None else f"{radio_raw_val:.4f}"
1376
  radio_masked_line = "Not run" if radio_masked_val is None else f"{radio_masked_val:.4f}"
1377
 
1378
+ final_state = patient_final_state(
1379
+ tb_state=tb_state,
1380
+ radio_state=radio_state,
1381
+ use_radio=use_radio,
1382
+ consensus_label=consensus_label,
1383
+ )
1384
+
1385
+ final_label = patient_state_label(final_state)
1386
+ final_meaning = patient_state_meaning(final_state)
1387
+
1388
+ quality_title, quality_text = patient_quality_text(q)
1389
+ mask_title, mask_text = patient_lung_mask_text(cov)
1390
+
1391
+ tb_score_text = patient_score_text(out["prob"])
1392
+ radio_user_text = patient_radio_text(use_radio, radio_state, radio_primary_val)
1393
+ agreement_text = patient_agreement_text(tb_state, radio_state, use_radio)
1394
+ next_step_user = patient_next_step_text(final_state)
1395
+ warning_html = patient_warning_html(warns)
1396
+
1397
+ final_badge = f"""
1398
+ <span style="display:inline-block;padding:7px 13px;border-radius:999px;background:{badge_color_for_state(final_state)};font-weight:900;font-size:15px;">
1399
+ {final_label}
1400
+ </span>
1401
+ """
1402
+
1403
+ patient_card = f"""
1404
+ <div style="border:1px solid rgba(255,255,255,0.16);border-radius:18px;padding:16px;margin:14px 0;background:rgba(255,255,255,0.035);">
1405
+
1406
+ <div style="font-size:18px;font-weight:950;margin-bottom:8px;">
1407
+ Result for: {html_escape(name)}
1408
+ </div>
1409
+
1410
+ <div style="margin:8px 0 12px 0;">
1411
+ {final_badge}
1412
  </div>
1413
+
1414
+ <div style="font-size:14px;line-height:1.55;opacity:0.95;margin-bottom:14px;">
1415
+ {html_escape(final_meaning)}
1416
  </div>
 
 
1417
 
1418
+ <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px;margin-top:10px;">
1419
+
1420
+ <div style="padding:12px;border-radius:14px;background:rgba(59,130,246,0.10);border:1px solid rgba(59,130,246,0.22);">
1421
+ <div style="font-weight:900;margin-bottom:5px;">1) What the main AI check found</div>
1422
+ <div style="font-size:13.5px;line-height:1.5;">
1423
+ <b>{patient_state_label(tb_state)}</b><br/>
1424
+ {tb_score_text}
1425
+ </div>
1426
+ </div>
1427
+
1428
+ <div style="padding:12px;border-radius:14px;background:rgba(99,102,241,0.10);border:1px solid rgba(99,102,241,0.22);">
1429
+ <div style="font-weight:900;margin-bottom:5px;">2) Second AI check</div>
1430
+ <div style="font-size:13.5px;line-height:1.5;">
1431
+ {radio_user_text}
1432
+ </div>
1433
+ </div>
1434
+
1435
+ <div style="padding:12px;border-radius:14px;background:rgba(148,163,184,0.10);border:1px solid rgba(148,163,184,0.22);">
1436
+ <div style="font-weight:900;margin-bottom:5px;">3) Image reliability</div>
1437
+ <div style="font-size:13.5px;line-height:1.5;">
1438
+ <b>{quality_title}</b><br/>
1439
+ {quality_text}<br/><br/>
1440
+ <b>{mask_title}</b><br/>
1441
+ {mask_text}
1442
+ </div>
1443
+ </div>
1444
+
1445
+ </div>
1446
+
1447
+ <div style="margin-top:12px;padding:12px;border-radius:14px;background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.28);">
1448
+ <div style="font-weight:950;margin-bottom:5px;">What to do next</div>
1449
+ <div style="font-size:14px;line-height:1.55;">
1450
+ {next_step_user}
1451
+ </div>
1452
  </div>
 
 
 
1453
 
1454
+ <div style="margin-top:12px;padding:12px;border-radius:14px;background:rgba(255,255,255,0.045);border:1px solid rgba(255,255,255,0.12);">
1455
+ <div style="font-weight:900;margin-bottom:5px;">Why this result may or may not be reliable</div>
1456
+ <div style="font-size:13.5px;line-height:1.5;">
1457
+ {html_escape(agreement_text)}
1458
+ <br/><br/>
1459
+ {html_escape(patient_attention_text(out.get("diffuse_risk", False)))}
1460
+ <br/><br/>
1461
+ <b>Image notes:</b>
1462
+ {warning_html}
1463
+ </div>
1464
  </div>
1465
+
1466
+ <div style="margin-top:12px;font-size:12.5px;line-height:1.45;opacity:0.82;">
1467
+ This is an AI screening output only. It cannot confirm or exclude TB. Clinical symptoms and doctor/radiologist review remain important.
 
1468
  </div>
1469
+
1470
  </div>
1471
  """
1472
 
1473
+ summary_md.append(patient_card)
 
 
1474
 
1475
  # Gallery
1476
  orig_rgb = cv2.cvtColor(cv2.resize(out["orig_gray"], (512, 512)), cv2.COLOR_GRAY2RGB)
 
1478
  mask_overlay = cv2.resize(out["mask_overlay"], (512, 512))
1479
  overlay_big = cv2.resize(out["overlay"], (512, 512))
1480
 
1481
+ gallery_items.append((orig_rgb, f"{name} β€’ Original image"))
1482
+ gallery_items.append((vis_rgb, f"{name} β€’ Image checked by the app" if not phone_mode else f"{name} β€’ Phone/WhatsApp cleaned image"))
1483
+ gallery_items.append((mask_overlay, f"{name} β€’ Lung area found by the app"))
1484
  if out["proc_gray"] is not None:
1485
  proc_rgb = cv2.cvtColor(cv2.resize(out["proc_gray"], (512, 512)), cv2.COLOR_GRAY2RGB)
1486
+ gallery_items.append((proc_rgb, f"{name} β€’ Lung-only image checked by AI"))
1487
+ gallery_items.append((overlay_big, f"{name} β€’ Main AI heatmap"))
1488
 
1489
  if radio_raw_overlay is not None:
1490
+ gallery_items.append((cv2.resize(radio_raw_overlay, (512, 512)), f"{name} β€’ Second AI heatmap"))
1491
  if radio_masked_overlay is not None:
1492
+ gallery_items.append((cv2.resize(radio_masked_overlay, (512, 512)), f"{name} β€’ Second AI lung-only heatmap"))
1493
 
1494
+ # Technical details (collapsed per image)
1495
  warn_txt = "\n".join([f"- {w}" for w in out["warnings"]]) if out["warnings"] else "- None"
1496
  details_md.append(
1497
  f"""
1498
  <details>
1499
+ <summary><b>{html_escape(name)}</b> β€” technical details</summary>
1500
 
1501
+ **Main AI check / TBNet**
1502
  - Result: **{html_escape(tb_label)}**
1503
  - Probability: {tb_prob_line}
1504
  - Band: {out.get("band", "YELLOW")}
 
1506
  - Lung mask coverage: {cov*100:.1f}%
1507
  - Attention: {attention}
1508
 
1509
+ **Final comparison**
1510
+ - Main AI state: {pretty_state(tb_state)}
1511
+ - Second AI state: {pretty_state(radio_state)}
1512
  - Consensus label: **{html_escape(consensus_label)}**
1513
+ - Technical detail: {html_escape(consensus_detail)}
1514
 
1515
+ **Image / safety warnings**
1516
  {warn_txt}
1517
 
1518
+ **Second AI / RADIO full output**
1519
+ - Short result: {html_escape(radio_result_short)}
1520
+ - Primary score: {radio_primary_line}
1521
+ - Raw score: {radio_raw_line}
1522
+ - Masked score: {radio_masked_line}
1523
+ - Masked ran: {radio_masked_ran}
1524
+ - Band: {radio_band}
1525
+
1526
  {radio_text_long}
1527
 
1528
  </details>
 
1546
  .card {border:1px solid rgba(255,255,255,0.12); border-radius:14px; padding:14px; margin:10px 0;}
1547
  """
1548
 
1549
+ with gr.Blocks(title="Chest X-ray TB Screening Assistant") as demo:
1550
 
1551
+ # Welcome screen
 
 
1552
  with gr.Column(visible=True) as welcome_screen:
1553
+ gr.Markdown('<div class="title">Welcome β€” Chest X-ray TB Screening Assistant</div>')
1554
  gr.HTML(WELCOME_HTML)
1555
+ continue_btn = gr.Button("Start checking X-ray images β†’", variant="primary")
1556
 
1557
+ # Main app UI
 
 
1558
  with gr.Column(visible=False) as main_app:
1559
+ gr.Markdown('<div class="title">Upload chest X-ray images</div>')
1560
+
1561
  gr.Markdown(
1562
+ "<div class='subtitle'>"
1563
+ "Upload one or more chest X-ray images. The app will check the image, show a simple AI screening result, "
1564
+ "and suggest what to do next. This is not a diagnosis."
1565
+ "</div>"
1566
  )
1567
 
1568
  gr.Markdown(
1569
+ "<div class='warnbox'><b>Important:</b> This app supports medical review only. "
1570
+ "If TB is clinically suspected, please seek clinician/radiologist review and consider CBNAAT/GeneXpert, "
1571
+ "sputum testing, and/or CT chest regardless of the AI result.</div>"
1572
  )
1573
 
1574
  with gr.Row():
1575
+ with gr.Column(scale=2):
1576
+ gr.Markdown("### 1) Upload X-ray image(s)")
 
 
 
 
 
 
 
 
 
1577
 
1578
+ files = gr.Files(
1579
+ label="Upload chest X-ray images",
1580
+ file_types=[".png", ".jpg", ".jpeg", ".bmp"]
1581
  )
1582
 
1583
  phone_mode = gr.Checkbox(
1584
  value=False,
1585
+ label="This image is a phone photo, WhatsApp image, screenshot, or has borders"
1586
  )
1587
+
1588
  gr.Markdown(
1589
+ "<div class='subtitle'>"
1590
+ "Turn this on for images taken from a phone camera, WhatsApp, screenshots, cropped images, "
1591
+ "or images with black/white borders."
1592
+ "</div>"
1593
  )
1594
 
1595
+ use_radio = gr.Checkbox(
1596
+ value=True,
1597
+ label="Use a second AI check for comparison"
 
1598
  )
1599
 
1600
+ run_btn = gr.Button("Check X-ray image(s)", variant="primary")
1601
+ status = gr.Textbox(label="Status", value="Ready to check images.", interactive=False)
 
 
1602
 
1603
+ with gr.Column(scale=1):
1604
+ gr.Markdown("### 2) What you will get")
1605
+
1606
+ gr.Markdown("""
1607
+ <div class='legend'>
1608
+ <b>The report will show:</b><br/><br/>
1609
+ βœ… A simple result: Low / Unclear / Needs review / TB-like pattern seen<br/>
1610
+ βœ… Image reliability: whether the image was clear enough<br/>
1611
+ βœ… Lung area check: whether the app found the lung region<br/>
1612
+ βœ… Heatmap images: where the AI focused<br/>
1613
+ βœ… Suggested next step
1614
+ </div>
1615
+ """)
1616
+
1617
+ with gr.Accordion("Advanced settings β€” usually leave unchanged", open=False):
1618
+ tb_weights = gr.Textbox(label="Main AI model weights", value=DEFAULT_TB_WEIGHTS)
1619
+ lung_weights = gr.Textbox(label="Lung area model weights", value=DEFAULT_LUNG_WEIGHTS)
1620
+
1621
+ backbone = gr.Dropdown(
1622
+ choices=["efficientnet_b0"],
1623
+ value="efficientnet_b0",
1624
+ label="Main AI backbone"
1625
+ )
1626
+
1627
+ threshold = gr.Slider(
1628
+ 0.01, 0.99, value=TBNET_SCREEN_THR, step=0.01,
1629
+ label=f"Screening threshold = {TBNET_SCREEN_THR:.2f}"
1630
+ )
1631
+
1632
+ radio_gate = gr.Slider(
1633
+ 0.10, 0.40, value=RADIO_GATE_DEFAULT, step=0.01,
1634
+ label="Second AI lung-mask gate"
1635
+ )
1636
+
1637
+ gr.Markdown(
1638
+ f"<div class='subtitle'>Device: <b>{DEVICE}</b> | FORCE_CPU={FORCE_CPU}</div>"
1639
+ )
1640
 
1641
  back_btn = gr.Button("← Back to Welcome", variant="secondary")
1642
 
1643
+ gr.Markdown("### Results")
1644
+ summary = gr.HTML(
1645
+ "<div style='padding:14px;border-radius:14px;background:rgba(255,255,255,0.04);"
1646
+ "border:1px solid rgba(255,255,255,0.10);'>"
1647
+ "Upload one or more images, then click <b>Check X-ray image(s)</b>."
1648
+ "</div>"
1649
+ )
 
1650
 
1651
+ gr.Markdown("### Image views")
1652
+ gr.Markdown("""
1653
+ <div class='legend'>
1654
+ <b>How to read the image views:</b><br/>
1655
+ <b>Original</b> = your uploaded image<br/>
1656
+ <b>Image checked by the app</b> = image after basic cleanup if selected<br/>
1657
+ <b>Lung area found by the app</b> = area the app thinks is lung<br/>
1658
+ <b>AI heatmap</b> = area the AI focused on most
1659
+ </div>
1660
  """)
1661
 
1662
+ gallery = gr.Gallery(label="Image views", columns=3, height=560)
 
 
1663
 
1664
  with gr.Row():
1665
  with gr.Column(scale=1):
1666
  disclaimer_box = gr.Markdown(CLINICAL_DISCLAIMER)
1667
  with gr.Column(scale=2):
1668
+ gr.Markdown("### Technical details")
1669
+ gr.Markdown(
1670
+ "This section is mainly for clinicians, researchers, or developers."
1671
+ )
1672
  details = gr.Markdown("")
1673
 
1674
  run_btn.click(
 
1686
  outputs=[summary, gallery, details, disclaimer_box, status]
1687
  )
1688
 
 
1689
  # Transitions
 
1690
  continue_btn.click(
1691
  fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
1692
  inputs=[],
 
1708
  server_name="0.0.0.0",
1709
  server_port=int(os.environ.get("PORT", 7860)),
1710
  show_error=True,
1711
+ ssr_mode=False,
1712
  css="""
1713
  .title {font-size: 28px; font-weight: 900; margin-bottom: 6px;}
1714
  .subtitle {font-size: 14px; opacity: 0.88; margin-bottom: 14px;}