mmrech commited on
Commit
a8862db
·
1 Parent(s): db851de

Add interactive slice viewer: scroll through slices with slider and navigation buttons

Browse files
Files changed (1) hide show
  1. app.py +209 -15
app.py CHANGED
@@ -409,6 +409,88 @@ def process_sequence(image_files, prompt_text, modality, window_type):
409
  else:
410
  return [], "❌ No images were processed successfully. Check console for error details."
411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  with gr.Blocks() as demo:
413
  gr.Markdown("# 🏥 NeuroSAM 3: Medical Image Segmentation")
414
 
@@ -488,15 +570,16 @@ with gr.Blocks() as demo:
488
  interactive=False
489
  )
490
 
491
- with gr.Tab("Sequence / Batch Processing"):
492
- gr.Markdown("**Process multiple images from the same subject to see segmentation sequence**")
493
  with gr.Row():
494
  with gr.Column():
495
  files_input = gr.File(
496
- label="Upload Multiple Images (Select multiple files)",
497
  file_types=[".dcm", ".png", ".jpg", ".jpeg"],
498
  file_count="multiple",
499
- type="filepath"
 
500
  )
501
 
502
  text_input_batch = gr.Textbox(
@@ -520,11 +603,81 @@ with gr.Blocks() as demo:
520
  info="CT windowing preset (ignored for MRI)"
521
  )
522
 
523
- submit_batch_btn = gr.Button("Process Sequence", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
 
525
  with gr.Column():
526
  gallery_output = gr.Gallery(
527
- label="Segmentation Sequence Results",
528
  show_label=True,
529
  elem_id="gallery",
530
  columns=2,
@@ -532,12 +685,10 @@ with gr.Blocks() as demo:
532
  height="auto"
533
  )
534
 
535
- gr.Markdown("### Batch Status")
536
- status_batch_text = gr.Textbox(
537
- label="Processing Status",
538
- value="Ready. Upload multiple medical image files to process a sequence.",
539
- interactive=False,
540
- lines=5
541
  )
542
 
543
  with gr.Tab("Compare with Ground Truth"):
@@ -625,11 +776,54 @@ with gr.Blocks() as demo:
625
  outputs=[image_output, status_text]
626
  )
627
 
628
- # Batch/sequence processing
629
  submit_batch_btn.click(
630
- fn=process_sequence,
631
  inputs=[files_input, text_input_batch, modality_dropdown_batch, window_dropdown_batch],
632
- outputs=[gallery_output, status_batch_text]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  )
634
 
635
  # Ground truth comparison
 
409
  else:
410
  return [], "❌ No images were processed successfully. Check console for error details."
411
 
412
+ # Store processed results for interactive viewer
413
+ processed_results_cache = {}
414
+
415
+ def process_slices_for_viewer(image_files, prompt_text, modality, window_type):
416
+ """Process all slices and cache results for interactive viewing."""
417
+ if model is None or processor is None:
418
+ return None, 0, "❌ Error: Model not loaded.", "No slices loaded"
419
+
420
+ if not image_files:
421
+ return None, 0, "⚠️ Please upload medical image files.", "No slices loaded"
422
+
423
+ # Handle single file or list of files
424
+ if isinstance(image_files, str):
425
+ image_files = [image_files]
426
+
427
+ # Filter out None files
428
+ image_files = [f for f in image_files if f is not None]
429
+
430
+ if not image_files:
431
+ return None, 0, "⚠️ No valid files uploaded.", "No slices loaded"
432
+
433
+ results = []
434
+ status_messages = []
435
+
436
+ for idx, image_file in enumerate(image_files):
437
+ status_msg = f"Processing slice {idx + 1}/{len(image_files)}..."
438
+ status_messages.append(status_msg)
439
+
440
+ result = process_medical_image(image_file, prompt_text, modality, window_type)
441
+
442
+ if result:
443
+ results.append(result)
444
+ status_messages.append(f"✅ Slice {idx + 1} processed")
445
+ else:
446
+ status_messages.append(f"❌ Failed to process slice {idx + 1}")
447
+
448
+ if results:
449
+ # Cache results with a unique key
450
+ cache_key = f"{len(image_files)}_{prompt_text}_{modality}"
451
+ processed_results_cache[cache_key] = results
452
+
453
+ max_slices = len(results) - 1
454
+ status = f"✅ Processed {len(results)}/{len(image_files)} slices!\nUse slider or buttons to navigate."
455
+ slice_info = f"Slice 1/{len(results)}"
456
+
457
+ return results[0], max_slices, status, slice_info
458
+ else:
459
+ return None, 0, "❌ No slices were processed successfully.", "No slices loaded"
460
+
461
+ def navigate_slice(slice_idx, image_files, prompt_text, modality, window_type):
462
+ """Navigate to a specific slice in the sequence."""
463
+ if not image_files:
464
+ return None, "No slices loaded"
465
+
466
+ # Handle single file or list of files
467
+ if isinstance(image_files, str):
468
+ image_files = [image_files]
469
+
470
+ # Filter out None files
471
+ image_files = [f for f in image_files if f is not None]
472
+
473
+ if not image_files:
474
+ return None, "No slices loaded"
475
+
476
+ slice_idx = int(slice_idx)
477
+ cache_key = f"{len(image_files)}_{prompt_text}_{modality}"
478
+
479
+ if cache_key in processed_results_cache:
480
+ results = processed_results_cache[cache_key]
481
+ if 0 <= slice_idx < len(results):
482
+ slice_info = f"Slice {slice_idx + 1}/{len(results)}"
483
+ return results[slice_idx], slice_info
484
+
485
+ # If not cached, process on the fly (fallback)
486
+ if 0 <= slice_idx < len(image_files):
487
+ result = process_medical_image(image_files[slice_idx], prompt_text, modality, window_type)
488
+ if result:
489
+ slice_info = f"Slice {slice_idx + 1}/{len(image_files)}"
490
+ return result, slice_info
491
+
492
+ return None, f"Invalid slice index: {slice_idx}"
493
+
494
  with gr.Blocks() as demo:
495
  gr.Markdown("# 🏥 NeuroSAM 3: Medical Image Segmentation")
496
 
 
570
  interactive=False
571
  )
572
 
573
+ with gr.Tab("Interactive Slice Viewer"):
574
+ gr.Markdown("**Scroll through multiple slices/images from the same subject interactively**")
575
  with gr.Row():
576
  with gr.Column():
577
  files_input = gr.File(
578
+ label="Upload Multiple Images/Slices (Select multiple files)",
579
  file_types=[".dcm", ".png", ".jpg", ".jpeg"],
580
  file_count="multiple",
581
+ type="filepath",
582
+ info="Upload multiple slices from the same subject (e.g., axial MRI slices)"
583
  )
584
 
585
  text_input_batch = gr.Textbox(
 
603
  info="CT windowing preset (ignored for MRI)"
604
  )
605
 
606
+ submit_batch_btn = gr.Button("Process All Slices", variant="primary", size="lg")
607
+
608
+ gr.Markdown("---")
609
+ gr.Markdown("### 🎛️ Slice Navigator")
610
+ slice_slider = gr.Slider(
611
+ minimum=0,
612
+ maximum=0,
613
+ step=1,
614
+ value=0,
615
+ label="Slice Number",
616
+ info="Use slider or arrow keys to navigate through slices",
617
+ interactive=False
618
+ )
619
+
620
+ with gr.Row():
621
+ prev_btn = gr.Button("⬆️ Previous Slice", size="sm")
622
+ next_btn = gr.Button("⬇️ Next Slice", size="sm")
623
+ auto_play_btn = gr.Button("▶️ Auto-play", size="sm")
624
+
625
+ with gr.Column():
626
+ current_slice_output = gr.Image(
627
+ label="Current Slice Segmentation",
628
+ type="filepath",
629
+ height=600
630
+ )
631
+
632
+ gr.Markdown("### Slice Info")
633
+ slice_info_text = gr.Textbox(
634
+ label="Current Slice",
635
+ value="No slices loaded",
636
+ interactive=False
637
+ )
638
+
639
+ gr.Markdown("### Status")
640
+ status_batch_text = gr.Textbox(
641
+ label="Processing Status",
642
+ value="Ready. Upload multiple medical image files to process a sequence.",
643
+ interactive=False,
644
+ lines=4
645
+ )
646
+
647
+ with gr.Tab("Gallery View"):
648
+ gr.Markdown("**View all segmentations in a gallery grid**")
649
+ with gr.Row():
650
+ with gr.Column():
651
+ files_input_gallery = gr.File(
652
+ label="Upload Multiple Images (Select multiple files)",
653
+ file_types=[".dcm", ".png", ".jpg", ".jpeg"],
654
+ file_count="multiple",
655
+ type="filepath"
656
+ )
657
+
658
+ text_input_gallery = gr.Textbox(
659
+ label="Text Prompt",
660
+ value="brain",
661
+ placeholder="e.g. brain, tumor, skull, eyes"
662
+ )
663
+
664
+ with gr.Row():
665
+ modality_dropdown_gallery = gr.Dropdown(
666
+ ["CT", "MRI"],
667
+ label="Modality",
668
+ value="MRI"
669
+ )
670
+ window_dropdown_gallery = gr.Dropdown(
671
+ ["Brain (Grey Matter)", "Bone (Skull)", "Soft Tissue (Face)"],
672
+ label="Windowing Strategy (CT only)",
673
+ value="Brain (Grey Matter)"
674
+ )
675
+
676
+ submit_gallery_btn = gr.Button("Process & Show Gallery", variant="primary", size="lg")
677
 
678
  with gr.Column():
679
  gallery_output = gr.Gallery(
680
+ label="Segmentation Gallery",
681
  show_label=True,
682
  elem_id="gallery",
683
  columns=2,
 
685
  height="auto"
686
  )
687
 
688
+ status_gallery_text = gr.Textbox(
689
+ label="Status",
690
+ value="Ready. Upload multiple images to view in gallery.",
691
+ interactive=False
 
 
692
  )
693
 
694
  with gr.Tab("Compare with Ground Truth"):
 
776
  outputs=[image_output, status_text]
777
  )
778
 
779
+ # Interactive slice viewer
780
  submit_batch_btn.click(
781
+ fn=process_slices_for_viewer,
782
  inputs=[files_input, text_input_batch, modality_dropdown_batch, window_dropdown_batch],
783
+ outputs=[current_slice_output, slice_slider, status_batch_text, slice_info_text]
784
+ ).then(
785
+ lambda max_val: gr.Slider(maximum=max_val, interactive=True),
786
+ inputs=[slice_slider],
787
+ outputs=[slice_slider]
788
+ )
789
+
790
+ def update_slice(slice_num, files, prompt, mod, window):
791
+ result, info = navigate_slice(int(slice_num), files, prompt, mod, window)
792
+ return result, info
793
+
794
+ slice_slider.change(
795
+ fn=update_slice,
796
+ inputs=[slice_slider, files_input, text_input_batch, modality_dropdown_batch, window_dropdown_batch],
797
+ outputs=[current_slice_output, slice_info_text]
798
+ )
799
+
800
+ def prev_slice(current, files, prompt, mod, window):
801
+ new_val = max(0, current - 1)
802
+ result, info = navigate_slice(new_val, files, prompt, mod, window)
803
+ return new_val, result, info
804
+
805
+ def next_slice(current, max_val, files, prompt, mod, window):
806
+ new_val = min(max_val, current + 1)
807
+ result, info = navigate_slice(new_val, files, prompt, mod, window)
808
+ return new_val, result, info
809
+
810
+ prev_btn.click(
811
+ fn=prev_slice,
812
+ inputs=[slice_slider, files_input, text_input_batch, modality_dropdown_batch, window_dropdown_batch],
813
+ outputs=[slice_slider, current_slice_output, slice_info_text]
814
+ )
815
+
816
+ next_btn.click(
817
+ fn=next_slice,
818
+ inputs=[slice_slider, slice_slider, files_input, text_input_batch, modality_dropdown_batch, window_dropdown_batch],
819
+ outputs=[slice_slider, current_slice_output, slice_info_text]
820
+ )
821
+
822
+ # Gallery view
823
+ submit_gallery_btn.click(
824
+ fn=process_sequence,
825
+ inputs=[files_input_gallery, text_input_gallery, modality_dropdown_gallery, window_dropdown_gallery],
826
+ outputs=[gallery_output, status_gallery_text]
827
  )
828
 
829
  # Ground truth comparison