sugan04 commited on
Commit
037e21a
·
verified ·
1 Parent(s): 7bdad65

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -55
app.py CHANGED
@@ -44,7 +44,6 @@ LANGUAGES = {
44
 
45
  last_result = {}
46
  chat_context = {}
47
- # Store last AI reply text for replay button
48
  last_ai_reply = {"text": "", "lang": "en"}
49
 
50
  try:
@@ -156,9 +155,7 @@ def generate_pdf(original_image, annotated_image, seen, alert, explanation):
156
  story += [model_table, Spacer(1,0.4*cm),
157
  HRFlowable(width="100%", thickness=1, color=colors.HexColor('#1a3a5c')),
158
  Spacer(1,0.2*cm),
159
- Paragraph("DISCLAIMER: This report is generated by an AI research prototype for educational purposes only. "
160
- "It is NOT a medical device and must not be used for clinical decision-making. "
161
- "All demo footage is from the publicly available CholecSeg8k research dataset — no real patient data involved.", footer_style),
162
  Paragraph("Built for Build Small Hackathon 2026", footer_style)]
163
  doc.build(story)
164
  return pdf_path
@@ -195,7 +192,7 @@ def segment_image(input_image, conf_threshold=0.25):
195
  try:
196
  prompt = (f"You are a surgical anatomy teacher for a junior resident. "
197
  f"Detected in a laparoscopic cholecystectomy frame: {', '.join(tissue_list)}. "
198
- f"In 3 sentences, explain what the resident should know — what they are, why they matter, what to watch out for.")
199
  response = client.chat_completion([{"role":"user","content":prompt}], max_tokens=180, temperature=0.4)
200
  explanation = response.choices[0].message.content.strip()
201
  except Exception as e:
@@ -220,7 +217,8 @@ def segment_image(input_image, conf_threshold=0.25):
220
 
221
  def chat_response(message, history, language):
222
  global chat_context, last_ai_reply
223
- if not message.strip(): return history, "", gr.update(visible=False), gr.update(visible=False)
 
224
  tissue_list = chat_context.get("tissue_list", [])
225
  alert = chat_context.get("alert", "")
226
  lang_cfg = LANGUAGES.get(language, LANGUAGES["English"])
@@ -230,23 +228,25 @@ def chat_response(message, history, language):
230
  + "Answer concisely in 2-4 sentences. "
231
  + lang_cfg["prompt"])
232
  messages = [{"role": "system", "content": system_prompt}]
233
- for msg in history: messages.append({"role": msg["role"], "content": msg["content"]})
 
234
  messages.append({"role": "user", "content": message})
235
  try:
236
  response = client.chat_completion(messages, max_tokens=200, temperature=0.5)
237
  reply = response.choices[0].message.content.strip()
238
  except Exception as e:
239
  reply = f"Error: {str(e)}"
240
- new_history = history + [{"role": "user", "content": message}, {"role": "assistant", "content": reply}]
 
 
 
241
  last_ai_reply = {"text": reply, "lang": lang_cfg["code"]}
242
- # Auto-generate TTS and show audio player
243
  audio_path = text_to_speech(reply, lang_cfg["code"])
244
  audio_update = gr.update(value=audio_path, visible=True) if audio_path else gr.update(visible=False)
245
  return new_history, "", audio_update, gr.update(visible=True)
246
 
247
 
248
  def replay_last(language):
249
- """Replay button — re-generates TTS for last AI reply."""
250
  global last_ai_reply
251
  lang_cfg = LANGUAGES.get(language, LANGUAGES["English"])
252
  text = last_ai_reply.get("text", "")
@@ -254,14 +254,13 @@ def replay_last(language):
254
  return gr.update(value=audio_path, visible=True) if audio_path else gr.update(visible=False)
255
 
256
 
257
- def use_suggested(q, h, lang): return chat_response(q, h, lang)
258
-
259
  def export_pdf():
260
  if not last_result: return None
261
  return generate_pdf(last_result["original"], last_result["annotated"],
262
  last_result["seen"], last_result["alert"], last_result["explanation"])
263
 
264
 
 
265
  css = """
266
  .gradio-container { max-width: 1120px !important; margin: 0 auto !important; }
267
  .generating, .progress-text, .progress-bar-wrap, .eta-bar, .eta-text { display: none !important; }
@@ -271,47 +270,57 @@ css = """
271
  flex-direction:column; align-items:center; justify-content:center;
272
  }
273
  #surgi-overlay.active { display:flex !important; }
274
- .or-spinner { width:48px;height:48px;border:4px solid rgba(255,255,255,0.2);border-top-color:#fff;border-radius:50%;animation:spin 0.8s linear infinite;margin-bottom:16px; }
275
- @keyframes spin{to{transform:rotate(360deg)}}
276
- .or-text { color:#fff;font-size:1rem;font-weight:600;text-align:center; }
277
- .or-sub { color:rgba(255,255,255,0.6);font-size:0.82rem;margin-top:6px; }
 
 
 
 
 
278
  """
279
 
280
- overlay_html = """
281
- <div id="surgi-overlay">
282
- <div class="or-spinner"></div>
283
- <div class="or-text">Analysing Surgical Frame…</div>
284
- <div class="or-sub">YOLOv26n-seg · Modal GPU · Llama 3.1</div>
285
- </div>
286
- <script>
287
- (function(){
288
- var attached=false;
289
- function attachBtn(){
290
- var btn=document.querySelector('#run-btn button');
291
- if(btn&&!attached){
292
- btn.addEventListener('click',function(){ document.getElementById('surgi-overlay').classList.add('active'); });
293
- attached=true;
294
- } else if(!btn){ setTimeout(attachBtn,500); }
 
 
 
 
 
 
295
  }
296
- var obs=new MutationObserver(function(){
297
- var img=document.querySelector('#output-frame img');
298
- if(img&&img.src&&img.src.length>80){ document.getElementById('surgi-overlay').classList.remove('active'); }
299
- });
300
- obs.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['src']});
301
- setTimeout(function(){ document.getElementById('surgi-overlay').classList.remove('active'); },90000);
302
- if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded',function(){setTimeout(attachBtn,900);}); }
303
- else { setTimeout(attachBtn,900); }
304
- })();
305
- </script>
306
- """
307
 
308
- with gr.Blocks(title="SurgiSight Surgical AI", css=css) as demo:
 
 
 
 
 
 
 
 
 
309
 
310
- gr.HTML(overlay_html)
311
 
312
  gr.Markdown("""
313
  # SurgiSight — Surgical Tissue Segmentation
314
- **AI-powered anatomy detection for laparoscopic training** · YOLOv8n-seg · Llama 3.1 8B · Modal GPU · CholecSeg8k (MICCAI 2020) · mAP50: 0.581
315
 
316
  > Research prototype — No real patient data.
317
  """)
@@ -337,7 +346,6 @@ with gr.Blocks(title="SurgiSight — Surgical AI", css=css) as demo:
337
  pdf_btn = gr.Button("⬇ Export Full Report (PDF)", visible=False)
338
  pdf_output = gr.File(label="Download Report", visible=False)
339
 
340
- # ── Chat section ──────────────────────────────────────────────────────────
341
  with gr.Column(visible=False) as chat_section:
342
  gr.Markdown("---\n### 💬 AI Anatomy Consult")
343
 
@@ -354,14 +362,12 @@ with gr.Blocks(title="SurgiSight — Surgical AI", css=css) as demo:
354
 
355
  chatbot = gr.Chatbot(height=340, show_label=False)
356
 
357
- # Audio player — appears after each AI reply, autoplay on
358
  audio_out = gr.Audio(
359
  label="🔊 AI Voice Reply",
360
  visible=False,
361
  autoplay=True,
362
- format="mp3",
363
- show_download_button=False,
364
- waveform_options={"show_controls": True}
365
  )
366
 
367
  with gr.Row():
@@ -376,7 +382,7 @@ with gr.Blocks(title="SurgiSight — Surgical AI", css=css) as demo:
376
 
377
  gr.Markdown("---\n*CholecSeg8k · MICCAI 2020 · No patient data · Built for Build Small Hackathon 2026*")
378
 
379
- # ── Wiring ────────────────────────────────────────────────────────────────
380
  run_btn.click(fn=segment_image, inputs=[input_img, conf_slider],
381
  outputs=[output_img, output_text, danger_box, explain_box,
382
  pdf_btn, chat_section, sq1, sq2, sq3, chatbot])
@@ -393,9 +399,9 @@ with gr.Blocks(title="SurgiSight — Surgical AI", css=css) as demo:
393
 
394
  replay_btn.click(fn=replay_last, inputs=[lang_select], outputs=[audio_out])
395
 
396
- sq1.click(fn=use_suggested, inputs=[sq1, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
397
- sq2.click(fn=use_suggested, inputs=[sq2, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
398
- sq3.click(fn=use_suggested, inputs=[sq3, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
399
 
400
  if __name__ == "__main__":
401
- demo.launch()
 
44
 
45
  last_result = {}
46
  chat_context = {}
 
47
  last_ai_reply = {"text": "", "lang": "en"}
48
 
49
  try:
 
155
  story += [model_table, Spacer(1,0.4*cm),
156
  HRFlowable(width="100%", thickness=1, color=colors.HexColor('#1a3a5c')),
157
  Spacer(1,0.2*cm),
158
+ Paragraph("DISCLAIMER: Research prototype only. Not a medical device. No real patient data.", footer_style),
 
 
159
  Paragraph("Built for Build Small Hackathon 2026", footer_style)]
160
  doc.build(story)
161
  return pdf_path
 
192
  try:
193
  prompt = (f"You are a surgical anatomy teacher for a junior resident. "
194
  f"Detected in a laparoscopic cholecystectomy frame: {', '.join(tissue_list)}. "
195
+ f"In 3 sentences, explain what the resident should know.")
196
  response = client.chat_completion([{"role":"user","content":prompt}], max_tokens=180, temperature=0.4)
197
  explanation = response.choices[0].message.content.strip()
198
  except Exception as e:
 
217
 
218
  def chat_response(message, history, language):
219
  global chat_context, last_ai_reply
220
+ if not message.strip():
221
+ return history, "", gr.update(visible=False), gr.update(visible=False)
222
  tissue_list = chat_context.get("tissue_list", [])
223
  alert = chat_context.get("alert", "")
224
  lang_cfg = LANGUAGES.get(language, LANGUAGES["English"])
 
228
  + "Answer concisely in 2-4 sentences. "
229
  + lang_cfg["prompt"])
230
  messages = [{"role": "system", "content": system_prompt}]
231
+ for msg in history:
232
+ messages.append({"role": msg["role"], "content": msg["content"]})
233
  messages.append({"role": "user", "content": message})
234
  try:
235
  response = client.chat_completion(messages, max_tokens=200, temperature=0.5)
236
  reply = response.choices[0].message.content.strip()
237
  except Exception as e:
238
  reply = f"Error: {str(e)}"
239
+ new_history = history + [
240
+ {"role": "user", "content": message},
241
+ {"role": "assistant", "content": reply}
242
+ ]
243
  last_ai_reply = {"text": reply, "lang": lang_cfg["code"]}
 
244
  audio_path = text_to_speech(reply, lang_cfg["code"])
245
  audio_update = gr.update(value=audio_path, visible=True) if audio_path else gr.update(visible=False)
246
  return new_history, "", audio_update, gr.update(visible=True)
247
 
248
 
249
  def replay_last(language):
 
250
  global last_ai_reply
251
  lang_cfg = LANGUAGES.get(language, LANGUAGES["English"])
252
  text = last_ai_reply.get("text", "")
 
254
  return gr.update(value=audio_path, visible=True) if audio_path else gr.update(visible=False)
255
 
256
 
 
 
257
  def export_pdf():
258
  if not last_result: return None
259
  return generate_pdf(last_result["original"], last_result["annotated"],
260
  last_result["seen"], last_result["alert"], last_result["explanation"])
261
 
262
 
263
+ # CSS passed to launch() in Gradio 6
264
  css = """
265
  .gradio-container { max-width: 1120px !important; margin: 0 auto !important; }
266
  .generating, .progress-text, .progress-bar-wrap, .eta-bar, .eta-text { display: none !important; }
 
270
  flex-direction:column; align-items:center; justify-content:center;
271
  }
272
  #surgi-overlay.active { display:flex !important; }
273
+ .or-spinner {
274
+ width:48px; height:48px;
275
+ border:4px solid rgba(255,255,255,0.2);
276
+ border-top-color:#fff; border-radius:50%;
277
+ animation:spin 0.8s linear infinite; margin-bottom:16px;
278
+ }
279
+ @keyframes spin { to { transform:rotate(360deg) } }
280
+ .or-text { color:#fff; font-size:1rem; font-weight:600; text-align:center; }
281
+ .or-sub { color:rgba(255,255,255,0.6); font-size:0.82rem; margin-top:6px; }
282
  """
283
 
284
+ # JS runs once on page load — safe place for scripts in Gradio 6
285
+ overlay_js = """
286
+ () => {
287
+ // Inject overlay HTML
288
+ const div = document.createElement('div');
289
+ div.id = 'surgi-overlay';
290
+ div.innerHTML = `
291
+ <div class="or-spinner"></div>
292
+ <div class="or-text">Analysing Surgical Frame…</div>
293
+ <div class="or-sub">YOLOv8 · Modal GPU · Llama 3.1</div>
294
+ `;
295
+ document.body.appendChild(div);
296
+
297
+ // Attach click listener to run button
298
+ function attachBtn() {
299
+ const btn = document.querySelector('#run-btn button');
300
+ if (btn) {
301
+ btn.addEventListener('click', () => div.classList.add('active'));
302
+ } else {
303
+ setTimeout(attachBtn, 500);
304
+ }
305
  }
306
+ attachBtn();
 
 
 
 
 
 
 
 
 
 
307
 
308
+ // Hide overlay when output image appears
309
+ new MutationObserver(() => {
310
+ const img = document.querySelector('#output-frame img');
311
+ if (img && img.src && img.src.length > 80) div.classList.remove('active');
312
+ }).observe(document.body, {childList:true, subtree:true, attributes:true, attributeFilter:['src']});
313
+
314
+ // Safety timeout
315
+ setTimeout(() => div.classList.remove('active'), 90000);
316
+ }
317
+ """
318
 
319
+ with gr.Blocks(title="SurgiSight — Surgical AI", js=overlay_js) as demo:
320
 
321
  gr.Markdown("""
322
  # SurgiSight — Surgical Tissue Segmentation
323
+ **AI-powered anatomy detection for laparoscopic training** · YOLOv26n-seg · Llama 3.1 8B · Modal GPU · CholecSeg8k (MICCAI 2020) · mAP50: 0.581
324
 
325
  > Research prototype — No real patient data.
326
  """)
 
346
  pdf_btn = gr.Button("⬇ Export Full Report (PDF)", visible=False)
347
  pdf_output = gr.File(label="Download Report", visible=False)
348
 
 
349
  with gr.Column(visible=False) as chat_section:
350
  gr.Markdown("---\n### 💬 AI Anatomy Consult")
351
 
 
362
 
363
  chatbot = gr.Chatbot(height=340, show_label=False)
364
 
365
+ # Audio player — autoplay, appears after each reply
366
  audio_out = gr.Audio(
367
  label="🔊 AI Voice Reply",
368
  visible=False,
369
  autoplay=True,
370
+ format="mp3"
 
 
371
  )
372
 
373
  with gr.Row():
 
382
 
383
  gr.Markdown("---\n*CholecSeg8k · MICCAI 2020 · No patient data · Built for Build Small Hackathon 2026*")
384
 
385
+ # Wiring
386
  run_btn.click(fn=segment_image, inputs=[input_img, conf_slider],
387
  outputs=[output_img, output_text, danger_box, explain_box,
388
  pdf_btn, chat_section, sq1, sq2, sq3, chatbot])
 
399
 
400
  replay_btn.click(fn=replay_last, inputs=[lang_select], outputs=[audio_out])
401
 
402
+ sq1.click(fn=chat_response, inputs=[sq1, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
403
+ sq2.click(fn=chat_response, inputs=[sq2, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
404
+ sq3.click(fn=chat_response, inputs=[sq3, chatbot, lang_select], outputs=[chatbot, chat_input, audio_out, replay_btn])
405
 
406
  if __name__ == "__main__":
407
+ demo.launch(css=css)