Spaces:
Running on Zero
Running on Zero
| try: | |
| import spaces | |
| except ImportError: | |
| class spaces: | |
| def GPU(func): | |
| return func | |
| import os | |
| import sys | |
| # Dynamic symlink and path configuration to support 'gemmasight' imports on Hugging Face Spaces | |
| _current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| _symlink_path = os.path.join(_current_dir, "gemmasight") | |
| if not os.path.exists(_symlink_path): | |
| try: | |
| os.symlink(_current_dir, _symlink_path) | |
| except Exception: | |
| pass | |
| sys.path.insert(0, _current_dir) | |
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image, ImageDraw | |
| from gemmasight.config import DATA_DIR | |
| from gemmasight.inference import GemmaSightInferencePipeline | |
| from gemmasight.utils.visualization import ( | |
| GRADIO_CUSTOM_CSS, | |
| SYSTEM_STATUS_CONSOLE, | |
| format_prediction_stat_html, | |
| format_retrieved_cases_html | |
| ) | |
| # 1. Initialize Pipeline | |
| # Run in Simulation/Fallback Mode by default to prevent OOM / missing gated weight crashes. | |
| # Will load real models if FORCE_SIMULATION is set to False in config.py or GPU is active. | |
| pipeline = GemmaSightInferencePipeline() | |
| # 2. Pre-generate interactive clinical examples | |
| EXAMPLES_DIR = os.path.join(DATA_DIR, "examples") | |
| os.makedirs(EXAMPLES_DIR, exist_ok=True) | |
| mss_example_path = os.path.join(EXAMPLES_DIR, "mss_example.png") | |
| msih_example_path = os.path.join(EXAMPLES_DIR, "msih_example.png") | |
| def generate_example_images(): | |
| """Generates two realistic looking synthetic H&E pathology images for demo purposes.""" | |
| if not os.path.exists(mss_example_path): | |
| # Create a pink/purple H&E stained looking patch (MSS) | |
| mss_img = Image.new("RGB", (224, 224), color=(220, 160, 200)) # Eosin pink base | |
| draw = ImageDraw.Draw(mss_img) | |
| # Draw some glandular/cellular structures | |
| for i in range(5): | |
| x, y = 30 + i*40, 40 + (i%2)*60 | |
| draw.ellipse([x, y, x+30, y+35], fill=(130, 80, 160), outline=(80, 40, 100), width=2) # Purple hematoxylin nuclei | |
| draw.ellipse([x+5, y+5, x+25, y+30], fill=(240, 210, 230)) # Lighter cytoplasm | |
| mss_img.save(mss_example_path) | |
| if not os.path.exists(msih_example_path): | |
| # Create a more hyper-cellular, infiltrated patch (MSI-High) | |
| msih_img = Image.new("RGB", (224, 224), color=(210, 150, 190)) | |
| draw = ImageDraw.Draw(msih_img) | |
| # Distorted glands | |
| for i in range(4): | |
| x, y = 20 + i*50, 30 + (i%3)*40 | |
| draw.ellipse([x, y, x+40, y+25], fill=(110, 60, 140), outline=(70, 30, 80), width=2) | |
| draw.ellipse([x+8, y+4, x+32, y+21], fill=(230, 190, 220)) | |
| # Draw tons of tiny dark purple dots representing Tumor-Infiltrating Lymphocytes (TILs) | |
| for _ in range(120): | |
| rx = np.random.randint(5, 219) | |
| ry = np.random.randint(5, 219) | |
| draw.ellipse([rx, ry, rx+3, ry+3], fill=(50, 20, 90)) # Deep blue/purple lymphocytes | |
| msih_img.save(msih_example_path) | |
| # Generate example patches | |
| generate_example_images() | |
| # 3. Main Gradio Inference Callback | |
| def run_clinical_pipeline(input_image): | |
| """ | |
| Inference callback triggered by Gradio. | |
| """ | |
| if input_image is None: | |
| return ( | |
| gr.update(value="<p style='color: #ef4444;'>Error: Specimen image is required.</p>"), | |
| None, | |
| gr.update(value=""), | |
| "" | |
| ) | |
| try: | |
| # Run 5-phase diagnosis pipeline | |
| prob, label, heatmap, retrieved_cases, report = pipeline.run_diagnosis(input_image) | |
| # Format HTML widgets | |
| stat_html = format_prediction_stat_html(prob, label) | |
| retrieved_html = format_retrieved_cases_html(retrieved_cases) | |
| return stat_html, heatmap, retrieved_html, report | |
| except Exception as e: | |
| import traceback | |
| err_msg = f"Clinical Execution Error: {str(e)}\n\n{traceback.format_exc()}" | |
| print(err_msg) | |
| return ( | |
| gr.update(value=f"<p style='color: #ef4444;'>Execution Error: {str(e)}</p>"), | |
| None, | |
| gr.update(value=""), | |
| f"An error occurred during pipeline execution:\n\n{str(e)}" | |
| ) | |
| # 4. Assemble Dashboard | |
| with gr.Blocks(title="GemmaSight - Clinical Multimodal Pathology Assistant", css=GRADIO_CUSTOM_CSS) as demo: | |
| # Title Header Block | |
| with gr.Row(elem_classes="clinical-header"): | |
| with gr.Column(): | |
| gr.HTML("<h1>GemmaSight</h1>") | |
| gr.HTML("<p>State-of-the-Art Multimodal AI Pathology Assistant for Colorectal Cancer MSI/MSS Status</p>") | |
| # System Status Console Bar | |
| gr.HTML(SYSTEM_STATUS_CONSOLE) | |
| # Main Body Layout (Sidebar + Two Result Columns) | |
| with gr.Row(): | |
| # LEFT COLUMN: Specimen Input & Settings | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📥 1. Specimen Acquisition") | |
| input_image = gr.Image( | |
| label="H&E-Stained Microscope Specimen Slide (224x224)", | |
| type="pil", | |
| sources=["upload", "clipboard"] | |
| ) | |
| run_btn = gr.Button("🔬 Run Multimodal Diagnosis", elem_classes="run-btn") | |
| # Interactive Examples Block | |
| gr.Examples( | |
| examples=[ | |
| [mss_example_path], | |
| [msih_example_path] | |
| ], | |
| inputs=input_image, | |
| label="Microscope Specimen Trays (Click to Analyze)" | |
| ) | |
| # Diagnostic explanation text | |
| with gr.Accordion("Clinical Parameters & Methods", open=False): | |
| gr.Markdown(""" | |
| - **Slide-Representative Patching**: Operates on a single 224×224 slide-representative patch at 20x magnification. In high-throughput settings, this aligns with standard Whole Slide Image (WSI) patching pipelines where individual patch embeddings are aggregated into a patient-level patient diagnosis. | |
| - **Dual-Encoder Fusion**: Fuses Google Path Foundation Model (384-dim ViT) with MedSigLIP (1152-dim vision-language encoder) to capture spatial and biochemical descriptors (1536-dim total). | |
| - **Classification Engine**: 3-layer regularized Multi-Layer Perceptron trained to predict MSI-High status. | |
| - **Explainability**: Sliding-window occlusion (40x40 stride=20) measures local prediction confidence drops, mapped to diagnostic hotspots (top 20%). | |
| - **MedGemma Report**: Integrates heatmap insights and FAISS reference matches to write the pathology brief. | |
| """) | |
| # MIDDLE COLUMN: Saliency Heatmap & Diagnostics | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📊 2. Decision Cognition") | |
| # Result Stats Card | |
| output_stats = gr.HTML( | |
| value="<div style='text-align: center; padding: 24px; border-radius: 14px; background: rgba(15, 23, 42, 0.25); border: 1px solid rgba(255,255,255,0.05); color: #64748b;'>Awaiting specimen acquisition and diagnostic run...</div>" | |
| ) | |
| # Heatmap Visualization | |
| output_heatmap = gr.Image( | |
| label="Diagnostic Hotspots: Saliency Heatmap (Top 20% Occulted Highlights)", | |
| type="pil", | |
| interactive=False | |
| ) | |
| # RIGHT COLUMN: Case Retrieval & Clinical Report | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📚 3. Clinical Evidence & Reporting") | |
| # FAISS Case Match List | |
| with gr.Tab("Historical Pathology Matches (FAISS)"): | |
| output_cases = gr.HTML( | |
| value="<p style='color: #64748b; padding: 16px;'>Awaiting clinical reference search...</p>" | |
| ) | |
| # Generated Report Text Box | |
| with gr.Tab("Synthesized clinical diagnostic report"): | |
| output_report = gr.Markdown( | |
| value="*Report brief will be synthesized by MedGemma 4B-IT upon pipeline execution...*", | |
| elem_classes="report-card" | |
| ) | |
| # Bind Interactions | |
| run_btn.click( | |
| fn=run_clinical_pipeline, | |
| inputs=input_image, | |
| outputs=[output_stats, output_heatmap, output_cases, output_report], | |
| api_name="diagnose" | |
| ) | |
| # 5. Launcher | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, show_api=False) | |