webstk3's picture
Disable SSR mode to fix Node.js crash
6d5a2f4
Raw History Blame Contribute Delete
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)