Spaces:
Sleeping
Sleeping
Download app.py from webstk3/chest-xray-explainer: direct link, hf CLI and curl.
- Browser
- Download file 6.03 kB
-
https://huggingface.co/spaces/webstk3/chest-xray-explainer/resolve/main/app.py
- Command line
-
hf download hf://spaces/webstk3/chest-xray-explainer/app.py
-
curl -L -o app.py https://huggingface.co/spaces/webstk3/chest-xray-explainer/resolve/main/app.py
6.03 kB
| """ | |
| Chest X-Ray Classifier with Grad-CAM Explainability | |
| """ | |
| import traceback | |
| import sys | |
| print("=" * 50) | |
| print("STEP 1: Starting imports...") | |
| sys.stdout.flush() | |
| try: | |
| import torch | |
| print(" torch imported OK") | |
| import numpy as np | |
| print(" numpy imported OK") | |
| import gradio as gr | |
| print(" gradio imported OK") | |
| from PIL import Image | |
| print(" PIL imported OK") | |
| from torchvision import transforms | |
| print(" torchvision transforms imported OK") | |
| import timm | |
| print(" timm imported OK") | |
| from pytorch_grad_cam import GradCAM | |
| print(" GradCAM imported OK") | |
| from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget | |
| print(" ClassifierOutputTarget imported OK") | |
| from pytorch_grad_cam.utils.image import show_cam_on_image | |
| print(" show_cam_on_image imported OK") | |
| except Exception as e: | |
| print(f"IMPORT ERROR: {e}") | |
| traceback.print_exc() | |
| sys.stdout.flush() | |
| raise | |
| sys.stdout.flush() | |
| # CONFIG | |
| CHEXPERT_LABELS = [ | |
| "No Finding", | |
| "Enlarged Cardiomediastinum", | |
| "Cardiomegaly", | |
| "Lung Opacity", | |
| "Lung Lesion", | |
| "Edema", | |
| "Consolidation", | |
| "Pneumonia", | |
| "Atelectasis", | |
| "Pneumothorax", | |
| "Pleural Effusion", | |
| "Pleural Other", | |
| "Fracture", | |
| "Support Devices", | |
| ] | |
| DEVICE = torch.device("cpu") | |
| # PREPROCESS | |
| preprocess = transforms.Compose([ | |
| transforms.Resize((224, 224)), | |
| transforms.ToTensor(), | |
| transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), | |
| ]) | |
| def load_model(): | |
| """Load DenseNet-121.""" | |
| print("STEP 2: Loading model...") | |
| sys.stdout.flush() | |
| model = timm.create_model("densenet121", pretrained=True, num_classes=14) | |
| model = model.to(DEVICE) | |
| model.eval() | |
| print("STEP 3: Model loaded OK") | |
| sys.stdout.flush() | |
| return model | |
| def classify_and_explain(image): | |
| """Run classification and Grad-CAM.""" | |
| try: | |
| if image is None: | |
| return None, "Please upload a chest X-ray image." | |
| print("INFERENCE: Starting...") | |
| sys.stdout.flush() | |
| image_rgb = image.convert("RGB") | |
| input_tensor = preprocess(image_rgb).unsqueeze(0).to(DEVICE) | |
| rgb_display = np.array(image_rgb.resize((224, 224))).astype(np.float32) / 255.0 | |
| with torch.no_grad(): | |
| logits = model(input_tensor) | |
| probabilities = torch.sigmoid(logits).cpu().numpy()[0] | |
| top_idx = int(np.argmax(probabilities)) | |
| top_label = CHEXPERT_LABELS[top_idx] | |
| top_conf = float(probabilities[top_idx]) | |
| print(f"INFERENCE: Top prediction = {top_label} ({top_conf:.1%})") | |
| sys.stdout.flush() | |
| # Grad-CAM | |
| print("INFERENCE: Computing Grad-CAM...") | |
| sys.stdout.flush() | |
| cam = GradCAM(model=model, target_layers=target_layers) | |
| targets = [ClassifierOutputTarget(top_idx)] | |
| grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0] | |
| print("INFERENCE: Creating overlay...") | |
| sys.stdout.flush() | |
| heatmap_overlay = show_cam_on_image(rgb_display, grayscale_cam, use_rgb=True) | |
| print("INFERENCE: Done!") | |
| sys.stdout.flush() | |
| # Results | |
| sorted_indices = np.argsort(probabilities)[::-1] | |
| result_md = f"## Top Prediction: **{top_label}** ({top_conf:.1%})\n\n" | |
| result_md += "### All Condition Probabilities\n\n" | |
| result_md += "| Rank | Condition | Probability | Confidence |\n" | |
| result_md += "|:----:|-----------|:-----------:|:----------:|\n" | |
| for rank, idx in enumerate(sorted_indices, 1): | |
| label = CHEXPERT_LABELS[idx] | |
| prob = float(probabilities[idx]) | |
| bar_len = int(prob * 20) | |
| bar = "X" * bar_len + "-" * (20 - bar_len) | |
| emoji = "HIGH" if prob > 0.5 else "MED" if prob > 0.3 else "LOW" | |
| result_md += f"| {rank} | {label} | {prob:.1%} | {emoji} `{bar}` |\n" | |
| result_md += "\n---\n" | |
| result_md += f"**Grad-CAM target:** {top_label} - red regions indicate areas the model focused on.\n" | |
| return heatmap_overlay, result_md | |
| except Exception as e: | |
| error_msg = f"Error during inference: {e}\n{traceback.format_exc()}" | |
| print(error_msg) | |
| sys.stdout.flush() | |
| return None, f"**Error:** {e}" | |
| # BUILD UI | |
| print("STEP 4: Building Gradio UI...") | |
| sys.stdout.flush() | |
| with gr.Blocks( | |
| title="Chest X-Ray Classifier with Grad-CAM", | |
| theme=gr.themes.Soft(), | |
| ) as demo: | |
| gr.Markdown( | |
| """ | |
| # Chest X-Ray Classifier with Grad-CAM Explainability | |
| Upload a chest X-ray to classify **14 thoracic conditions** and see | |
| **where the model is looking** via Grad-CAM heatmaps. | |
| > **Disclaimer:** This is an educational tool only. NOT a medical device. | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_image = gr.Image(type="pil", label="Upload Chest X-Ray", height=400) | |
| classify_btn = gr.Button("Classify and Explain", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| output_image = gr.Image(label="Grad-CAM Heatmap Overlay", height=400) | |
| output_text = gr.Markdown(value="*Upload an image and click Classify to see results.*") | |
| with gr.Accordion("How Grad-CAM Works", open=False): | |
| gr.Markdown( | |
| """ | |
| **Grad-CAM** highlights which parts of the image the model focused on. | |
| - **Red** = High attention | |
| - **Yellow** = Moderate attention | |
| - **Green/Blue** = Low attention | |
| Reference: Selvaraju et al., "Grad-CAM", ICCV 2017. | |
| """ | |
| ) | |
| classify_btn.click(fn=classify_and_explain, inputs=[input_image], outputs=[output_image, output_text]) | |
| # LOAD MODEL AND LAUNCH | |
| print("STEP 5: Loading model at startup...") | |
| sys.stdout.flush() | |
| model = load_model() | |
| target_layers = [model.features[-1]] | |
| print("STEP 6: Launching server...") | |
| sys.stdout.flush() | |
| demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False) | |