sugan04 commited on
Commit
56f05d5
·
verified ·
1 Parent(s): c204ce7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -102
app.py CHANGED
@@ -9,7 +9,6 @@ sys.modules["pyaudioop"] = audioop_mock
9
  import gradio as gr
10
  import modal
11
  from PIL import Image
12
- import numpy as np
13
  import io
14
  import datetime
15
  from huggingface_hub import InferenceClient
@@ -18,7 +17,7 @@ from reportlab.lib import colors
18
  from reportlab.lib.units import cm
19
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle, HRFlowable
20
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
21
- from reportlab.lib.enums import TA_CENTER, TA_LEFT
22
 
23
  print("All imports OK")
24
 
@@ -109,7 +108,6 @@ def generate_pdf(original_image, annotated_image, seen, alert, explanation):
109
  story.append(Spacer(1, 0.2*cm))
110
  story.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#203a43')))
111
  story.append(Spacer(1, 0.4*cm))
112
-
113
  story.append(Paragraph("Segmentation Output", section_style))
114
  img_width, img_height = 8.5*cm, 6.5*cm
115
  orig_buf = io.BytesIO(); original_image.save(orig_buf, format="PNG"); orig_buf.seek(0)
@@ -125,14 +123,12 @@ def generate_pdf(original_image, annotated_image, seen, alert, explanation):
125
  ('BOX',(0,0),(0,0),0.5,colors.HexColor('#dddddd')),
126
  ('BOX',(1,0),(1,0),0.5,colors.HexColor('#dddddd')),]))
127
  story.append(img_table); story.append(Spacer(1, 0.5*cm))
128
-
129
  story.append(Paragraph("Safety Assessment", section_style))
130
  if any(d in alert for d in DANGER_CLASSES):
131
  story.append(Paragraph(f"WARNING: {alert}", danger_style))
132
  else:
133
  story.append(Paragraph(f"SAFE: {alert}", safe_style))
134
  story.append(Spacer(1, 0.4*cm))
135
-
136
  story.append(Paragraph("Detected Tissues & Instruments", section_style))
137
  table_data = [["Structure", "Confidence", "Risk Level"]]
138
  rows_danger = []
@@ -159,12 +155,10 @@ def generate_pdf(original_image, annotated_image, seen, alert, explanation):
159
  if is_danger: det_style.append(('FONTNAME',(2,r),(2,r),'Helvetica-Bold'))
160
  det_table.setStyle(TableStyle(det_style))
161
  story.append(det_table); story.append(Spacer(1, 0.5*cm))
162
-
163
  story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#cccccc')))
164
  story.append(Spacer(1, 0.3*cm))
165
  story.append(Paragraph("Anatomy Teaching Note", section_style))
166
  story.append(Paragraph(explanation, body_style)); story.append(Spacer(1, 0.5*cm))
167
-
168
  story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#cccccc')))
169
  story.append(Spacer(1, 0.3*cm))
170
  model_data = [
@@ -192,12 +186,65 @@ def generate_pdf(original_image, annotated_image, seen, alert, explanation):
192
  return pdf_path
193
 
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  def segment_image(input_image, conf_threshold=0.25):
196
  global last_result, chat_context
197
  if input_image is None:
198
  return (None, "Please upload a surgical frame to begin.", "No image provided.",
199
  "No explanation yet.", gr.update(visible=False), gr.update(visible=False),
200
- gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), [])
 
201
 
202
  detector = get_detector()
203
  image_bytes = pil_to_bytes(input_image)
@@ -252,12 +299,19 @@ def segment_image(input_image, conf_threshold=0.25):
252
  f"I've analysed the frame. Detected: **{', '.join(tissue_list) if tissue_list else 'no tissues'}**.\n\n"
253
  f"{explanation}\n\nFeel free to ask me anything about these structures or general laparoscopic surgery!"}]
254
 
 
 
 
 
 
 
255
  return (annotated_image, summary, alert, explanation,
256
  gr.update(visible=True), gr.update(visible=True),
257
  gr.update(value=q1, visible=bool(q1)),
258
  gr.update(value=q2, visible=bool(q2)),
259
  gr.update(value=q3, visible=bool(q3)),
260
- initial_chat)
 
261
 
262
 
263
  def chat_response(message, history):
@@ -312,91 +366,6 @@ body { font-family: 'Inter', sans-serif; }
312
  .footer-note { text-align: center; font-size: 0.78rem; color: #888; margin-top: 16px; padding-bottom: 24px; }
313
  #suggested-q button { background: rgba(32,58,67,0.08) !important; border: 1px solid #203a43 !important; color: #203a43 !important; border-radius: 999px !important; font-size: 0.82rem !important; padding: 6px 14px !important; }
314
  #suggested-q button:hover { background: #203a43 !important; color: white !important; }
315
-
316
- /* ── Loading overlay animation ── */
317
- #loading-overlay {
318
- display: none;
319
- position: fixed;
320
- top: 0; left: 0; right: 0; bottom: 0;
321
- background: rgba(10, 20, 25, 0.88);
322
- z-index: 9999;
323
- flex-direction: column;
324
- align-items: center;
325
- justify-content: center;
326
- color: white;
327
- font-family: 'Inter', sans-serif;
328
- backdrop-filter: blur(4px);
329
- }
330
- #loading-overlay.active { display: flex; }
331
- .scan-ring {
332
- width: 80px; height: 80px;
333
- border: 4px solid rgba(255,255,255,0.08);
334
- border-top: 4px solid #4f98a3;
335
- border-right: 4px solid #4f98a3;
336
- border-radius: 50%;
337
- animation: spin 0.9s linear infinite;
338
- margin-bottom: 24px;
339
- }
340
- .scan-ring-outer {
341
- width: 110px; height: 110px;
342
- border: 2px solid rgba(79,152,163,0.2);
343
- border-top: 2px solid rgba(79,152,163,0.6);
344
- border-radius: 50%;
345
- animation: spin 1.8s linear infinite reverse;
346
- position: absolute;
347
- }
348
- .scan-center {
349
- display: flex; flex-direction: column;
350
- align-items: center; justify-content: center;
351
- position: relative; margin-bottom: 28px;
352
- width: 110px; height: 110px;
353
- }
354
- @keyframes spin { to { transform: rotate(360deg); } }
355
- @keyframes pulse { 0%,100% { opacity:0.6; } 50% { opacity:1; } }
356
- .scan-text { font-size: 1.05rem; font-weight: 500; letter-spacing: 0.04em; }
357
- .scan-sub { font-size: 0.78rem; opacity: 0.45; margin-top: 8px; letter-spacing: 0.06em; text-transform: uppercase; }
358
- .scan-dots span {
359
- display: inline-block; width: 6px; height: 6px;
360
- background: #4f98a3; border-radius: 50%; margin: 0 3px;
361
- animation: pulse 1.2s ease-in-out infinite;
362
- }
363
- .scan-dots span:nth-child(2) { animation-delay: 0.2s; }
364
- .scan-dots span:nth-child(3) { animation-delay: 0.4s; }
365
- """
366
-
367
- loading_overlay_html = """
368
- <div id="loading-overlay">
369
- <div class="scan-center">
370
- <div class="scan-ring-outer"></div>
371
- <div class="scan-ring"></div>
372
- </div>
373
- <div class="scan-text">🔬 Analysing surgical frame</div>
374
- <div class="scan-dots" style="margin-top:10px">
375
- <span></span><span></span><span></span>
376
- </div>
377
- <div class="scan-sub">Running YOLOv8 on Modal GPU</div>
378
- </div>
379
- <script>
380
- window.showSurgiLoader = function() {
381
- document.getElementById('loading-overlay').classList.add('active');
382
- };
383
- window.hideSurgiLoader = function() {
384
- document.getElementById('loading-overlay').classList.remove('active');
385
- };
386
- // Attach to run button after Gradio renders
387
- function attachLoader() {
388
- var btn = document.querySelector('#run-btn button');
389
- if (btn) {
390
- btn.addEventListener('click', function() {
391
- window.showSurgiLoader();
392
- });
393
- } else {
394
- setTimeout(attachLoader, 500);
395
- }
396
- }
397
- document.addEventListener('DOMContentLoaded', attachLoader);
398
- setTimeout(attachLoader, 1500);
399
- </script>
400
  """
401
 
402
  header_html = """
@@ -404,7 +373,7 @@ header_html = """
404
  <h1>🔬 Surgical Tissue Segmentation</h1>
405
  <p>Upload a laparoscopic surgery frame — or click any example below. The AI identifies tissues, flags danger zones, and explains anatomy for surgical trainees.</p>
406
  <div class="badge-row">
407
- <span class="badge">YOLOv26-seg</span><span class="badge">Llama 3.1 8B</span>
408
  <span class="badge">Modal GPU</span><span class="badge">CholecSeg8k Dataset</span>
409
  <span class="badge">13 Classes</span><span class="badge">mAP50: 0.581</span>
410
  </div>
@@ -413,7 +382,6 @@ header_html = """
413
 
414
  with gr.Blocks(title="SurgiSight — Surgical Tissue Segmentation") as demo:
415
  gr.HTML(header_html)
416
- gr.HTML(loading_overlay_html)
417
 
418
  with gr.Row():
419
  with gr.Column(scale=1):
@@ -425,6 +393,9 @@ with gr.Blocks(title="SurgiSight — Surgical Tissue Segmentation") as demo:
425
  with gr.Column(scale=1):
426
  output_img = gr.Image(type="pil", label="Output — Segmented Result", height=350)
427
 
 
 
 
428
  danger_box = gr.Textbox(label="⚠ Safety Alert", lines=2, elem_id="danger-box",
429
  placeholder="Safety alert will appear here...")
430
  output_text = gr.Textbox(label="Detected Tissues & Confidence", lines=6, elem_id="output-text",
@@ -455,16 +426,11 @@ with gr.Blocks(title="SurgiSight — Surgical Tissue Segmentation") as demo:
455
 
456
  gr.HTML('<div class="footer-note">All footage from CholecSeg8k research dataset (MICCAI 2020). No patient data. &nbsp;|&nbsp; Built for Build Small Hackathon 2026</div>')
457
 
458
- # Run segmentation — show loader on click, hide when done
459
  run_btn.click(
460
- fn=None, js="() => window.showSurgiLoader()"
461
- ).then(
462
  fn=segment_image,
463
  inputs=[input_img, conf_slider],
464
  outputs=[output_img, output_text, danger_box, explain_box,
465
- pdf_btn, chat_section, sq1, sq2, sq3, chatbot]
466
- ).then(
467
- fn=None, js="() => window.hideSurgiLoader()"
468
  )
469
 
470
  pdf_btn.click(fn=export_pdf, inputs=[], outputs=[pdf_output]
 
9
  import gradio as gr
10
  import modal
11
  from PIL import Image
 
12
  import io
13
  import datetime
14
  from huggingface_hub import InferenceClient
 
17
  from reportlab.lib.units import cm
18
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle, HRFlowable
19
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
20
+ from reportlab.lib.enums import TA_CENTER
21
 
22
  print("All imports OK")
23
 
 
108
  story.append(Spacer(1, 0.2*cm))
109
  story.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#203a43')))
110
  story.append(Spacer(1, 0.4*cm))
 
111
  story.append(Paragraph("Segmentation Output", section_style))
112
  img_width, img_height = 8.5*cm, 6.5*cm
113
  orig_buf = io.BytesIO(); original_image.save(orig_buf, format="PNG"); orig_buf.seek(0)
 
123
  ('BOX',(0,0),(0,0),0.5,colors.HexColor('#dddddd')),
124
  ('BOX',(1,0),(1,0),0.5,colors.HexColor('#dddddd')),]))
125
  story.append(img_table); story.append(Spacer(1, 0.5*cm))
 
126
  story.append(Paragraph("Safety Assessment", section_style))
127
  if any(d in alert for d in DANGER_CLASSES):
128
  story.append(Paragraph(f"WARNING: {alert}", danger_style))
129
  else:
130
  story.append(Paragraph(f"SAFE: {alert}", safe_style))
131
  story.append(Spacer(1, 0.4*cm))
 
132
  story.append(Paragraph("Detected Tissues & Instruments", section_style))
133
  table_data = [["Structure", "Confidence", "Risk Level"]]
134
  rows_danger = []
 
155
  if is_danger: det_style.append(('FONTNAME',(2,r),(2,r),'Helvetica-Bold'))
156
  det_table.setStyle(TableStyle(det_style))
157
  story.append(det_table); story.append(Spacer(1, 0.5*cm))
 
158
  story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#cccccc')))
159
  story.append(Spacer(1, 0.3*cm))
160
  story.append(Paragraph("Anatomy Teaching Note", section_style))
161
  story.append(Paragraph(explanation, body_style)); story.append(Spacer(1, 0.5*cm))
 
162
  story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#cccccc')))
163
  story.append(Spacer(1, 0.3*cm))
164
  model_data = [
 
186
  return pdf_path
187
 
188
 
189
+ LOADING_HTML = """
190
+ <div id="surgi-loader" style="display:none; text-align:center; padding: 32px 20px;
191
+ background: linear-gradient(135deg,#0f2027,#203a43); border-radius:14px; margin:12px 0;">
192
+ <div style="display:inline-block; position:relative; width:70px; height:70px; margin-bottom:16px;">
193
+ <div style="position:absolute;inset:0;border:3px solid rgba(255,255,255,0.08);
194
+ border-top:3px solid #4f98a3; border-radius:50%;
195
+ animation:surgiSpin 0.85s linear infinite;"></div>
196
+ <div style="position:absolute;inset:-12px;border:2px solid rgba(79,152,163,0.15);
197
+ border-top:2px solid rgba(79,152,163,0.5); border-radius:50%;
198
+ animation:surgiSpin 1.7s linear infinite reverse;"></div>
199
+ <div style="position:absolute;inset:22px;background:#4f98a3;border-radius:50%;
200
+ animation:surgiPulse 1.2s ease-in-out infinite;"></div>
201
+ </div>
202
+ <div style="color:white;font-size:1rem;font-weight:500;letter-spacing:0.03em;">
203
+ 🔬 Analysing surgical frame...
204
+ </div>
205
+ <div style="color:rgba(255,255,255,0.4);font-size:0.75rem;margin-top:6px;letter-spacing:0.07em;text-transform:uppercase;">
206
+ Running YOLOv8 on Modal GPU
207
+ </div>
208
+ <div style="margin-top:14px;">
209
+ <span style="display:inline-block;width:7px;height:7px;background:#4f98a3;border-radius:50%;
210
+ margin:0 4px;animation:surgiDot 1.2s ease-in-out infinite;"></span>
211
+ <span style="display:inline-block;width:7px;height:7px;background:#4f98a3;border-radius:50%;
212
+ margin:0 4px;animation:surgiDot 1.2s ease-in-out 0.2s infinite;"></span>
213
+ <span style="display:inline-block;width:7px;height:7px;background:#4f98a3;border-radius:50%;
214
+ margin:0 4px;animation:surgiDot 1.2s ease-in-out 0.4s infinite;"></span>
215
+ </div>
216
+ </div>
217
+ <style>
218
+ @keyframes surgiSpin { to { transform: rotate(360deg); } }
219
+ @keyframes surgiPulse { 0%,100%{opacity:0.4;transform:scale(0.8)} 50%{opacity:1;transform:scale(1.1)} }
220
+ @keyframes surgiDot { 0%,100%{opacity:0.3;transform:translateY(0)} 50%{opacity:1;transform:translateY(-4px)} }
221
+ </style>
222
+ <script>
223
+ (function() {
224
+ function hookRunButton() {
225
+ var btn = document.querySelector('#run-btn button');
226
+ if (!btn) { setTimeout(hookRunButton, 600); return; }
227
+ btn.addEventListener('click', function() {
228
+ document.getElementById('surgi-loader').style.display = 'block';
229
+ });
230
+ }
231
+ if (document.readyState === 'loading') {
232
+ document.addEventListener('DOMContentLoaded', function() { setTimeout(hookRunButton, 800); });
233
+ } else {
234
+ setTimeout(hookRunButton, 800);
235
+ }
236
+ })();
237
+ </script>
238
+ """
239
+
240
+
241
  def segment_image(input_image, conf_threshold=0.25):
242
  global last_result, chat_context
243
  if input_image is None:
244
  return (None, "Please upload a surgical frame to begin.", "No image provided.",
245
  "No explanation yet.", gr.update(visible=False), gr.update(visible=False),
246
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), [],
247
+ gr.update(value=LOADING_HTML.replace('display:block','display:none')))
248
 
249
  detector = get_detector()
250
  image_bytes = pil_to_bytes(input_image)
 
299
  f"I've analysed the frame. Detected: **{', '.join(tissue_list) if tissue_list else 'no tissues'}**.\n\n"
300
  f"{explanation}\n\nFeel free to ask me anything about these structures or general laparoscopic surgery!"}]
301
 
302
+ # Return hidden loader HTML to replace it (hides the spinner)
303
+ hidden_loader = LOADING_HTML.replace(
304
+ 'id="surgi-loader" style="display:none;',
305
+ 'id="surgi-loader" style="display:none; visibility:hidden;'
306
+ )
307
+
308
  return (annotated_image, summary, alert, explanation,
309
  gr.update(visible=True), gr.update(visible=True),
310
  gr.update(value=q1, visible=bool(q1)),
311
  gr.update(value=q2, visible=bool(q2)),
312
  gr.update(value=q3, visible=bool(q3)),
313
+ initial_chat,
314
+ gr.update(value=hidden_loader))
315
 
316
 
317
  def chat_response(message, history):
 
366
  .footer-note { text-align: center; font-size: 0.78rem; color: #888; margin-top: 16px; padding-bottom: 24px; }
367
  #suggested-q button { background: rgba(32,58,67,0.08) !important; border: 1px solid #203a43 !important; color: #203a43 !important; border-radius: 999px !important; font-size: 0.82rem !important; padding: 6px 14px !important; }
368
  #suggested-q button:hover { background: #203a43 !important; color: white !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  """
370
 
371
  header_html = """
 
373
  <h1>🔬 Surgical Tissue Segmentation</h1>
374
  <p>Upload a laparoscopic surgery frame — or click any example below. The AI identifies tissues, flags danger zones, and explains anatomy for surgical trainees.</p>
375
  <div class="badge-row">
376
+ <span class="badge">YOLOv26n-seg</span><span class="badge">Llama 3.1 8B</span>
377
  <span class="badge">Modal GPU</span><span class="badge">CholecSeg8k Dataset</span>
378
  <span class="badge">13 Classes</span><span class="badge">mAP50: 0.581</span>
379
  </div>
 
382
 
383
  with gr.Blocks(title="SurgiSight — Surgical Tissue Segmentation") as demo:
384
  gr.HTML(header_html)
 
385
 
386
  with gr.Row():
387
  with gr.Column(scale=1):
 
393
  with gr.Column(scale=1):
394
  output_img = gr.Image(type="pil", label="Output — Segmented Result", height=350)
395
 
396
+ # Loading animation — shown by JS on click, hidden when Python returns
397
+ loader_html = gr.HTML(value=LOADING_HTML)
398
+
399
  danger_box = gr.Textbox(label="⚠ Safety Alert", lines=2, elem_id="danger-box",
400
  placeholder="Safety alert will appear here...")
401
  output_text = gr.Textbox(label="Detected Tissues & Confidence", lines=6, elem_id="output-text",
 
426
 
427
  gr.HTML('<div class="footer-note">All footage from CholecSeg8k research dataset (MICCAI 2020). No patient data. &nbsp;|&nbsp; Built for Build Small Hackathon 2026</div>')
428
 
 
429
  run_btn.click(
 
 
430
  fn=segment_image,
431
  inputs=[input_img, conf_slider],
432
  outputs=[output_img, output_text, danger_box, explain_box,
433
+ pdf_btn, chat_section, sq1, sq2, sq3, chatbot, loader_html]
 
 
434
  )
435
 
436
  pdf_btn.click(fn=export_pdf, inputs=[], outputs=[pdf_output]