RohitManglik's picture
Update app.py
e29b522 verified
Raw
History Blame Contribute Delete
7.06 kB
import os
from huggingface_hub import login
login(token=os.getenv("HF_TOKEN"))
import gradio as gr
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models
import torchvision.transforms as transforms
import numpy as np
from PIL import Image
import json
import os
import pydicom
from pydicom.uid import ImplicitVRLittleEndian
from huggingface_hub import snapshot_download
# =========================
# ๐Ÿ“ MODEL CONFIG
# =========================
MODEL_REPO = "InfoBayAI/resnet18-ct-pathology-classifier"
SAVE_DIR = snapshot_download(repo_id=MODEL_REPO)
print("Model downloaded to:", SAVE_DIR) # same folder saved during training
with open(os.path.join(SAVE_DIR, "config.json"), "r") as f:
config = json.load(f)
with open(os.path.join(SAVE_DIR, "labels.json"), "r") as f:
label_names_raw = json.load(f)
# labels.json keys are strings ("0","1",...) โ†’ convert to int keys
label_names = {int(k): v for k, v in label_names_raw.items()}
IMG_SIZE = config["img_size"] # 224
NUM_CLASSES = config["num_classes"] # 4
# =========================
# โš™๏ธ DEVICE
# =========================
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# =========================
# ๐Ÿง  LOAD MODEL
# โ€” architecture MUST match training exactly โ€”
# =========================
model = models.resnet18(pretrained=False)
# โœ… Same custom FC head used in training
model.fc = nn.Sequential(
nn.Linear(model.fc.in_features, 256),
nn.ReLU(),
nn.Dropout(0.4),
nn.Linear(256, NUM_CLASSES)
)
model.load_state_dict(
torch.load(os.path.join(SAVE_DIR, "pytorch_model.bin"), map_location=device)
)
model.to(device)
model.eval()
print("โœ… CT Pathology Model Loaded Successfully")
# =========================
# ๐Ÿงผ PREPROCESS
# โ€” same val_transform used during training โ€”
# =========================
val_transform = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.ToTensor(),
])
# =========================
# ๐Ÿ–ผ๏ธ LOAD IMAGE (DICOM + normal formats)
# โ€” exact same logic as training load_image() โ€”
# =========================
def load_image_from_pil(pil_image):
"""Gradio passes a PIL image directly โ€” just convert to RGB."""
return pil_image.convert("RGB")
def load_dicom(path):
"""Load a DICOM file from disk path and return a PIL RGB image."""
try:
dcm = pydicom.dcmread(path, force=True)
if not hasattr(dcm, "file_meta") or dcm.file_meta is None:
dcm.file_meta = pydicom.dataset.FileMetaDataset()
if not hasattr(dcm.file_meta, "TransferSyntaxUID"):
dcm.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
try:
dcm.decompress()
except:
pass
img = dcm.pixel_array.astype(np.float32)
if img.max() == img.min():
return None
img = (img - img.min()) / (img.max() - img.min())
img = (img * 255).astype(np.uint8)
return Image.fromarray(img).convert("RGB")
except Exception as e:
print(f"โŒ DICOM load failed | {e}")
return None
# =========================
# ๐Ÿ”ฎ PREDICT
# =========================
def predict_ct(image):
if image is None:
return "โš ๏ธ Please upload a CT scan image.", None
img = load_image_from_pil(image)
tensor = val_transform(img).unsqueeze(0).to(device)
with torch.no_grad():
output = model(tensor)
probs = F.softmax(output, dim=1)
probs_np = probs.cpu().numpy()[0]
sorted_indices = np.argsort(probs_np)[::-1]
top_label = label_names[sorted_indices[0]]
top_conf = probs_np[sorted_indices[0]] * 100
formatted_probs = "\n".join([
f" {label_names[i]:<25} โ€” {probs_np[i]*100:.2f}%"
for i in sorted_indices
])
result_text = f"""
๐Ÿซ CT Scan Pathology Classification
๐Ÿ“Œ Prediction : {top_label}
๐Ÿ” Confidence : {top_conf:.2f}%
๐Ÿ“Š All Class Probabilities:
{formatted_probs}
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
โš ๏ธ AI-assisted output โ€” NOT a medical diagnosis.
Always consult a qualified radiologist.
"""
probs_dict = {
label_names[i]: float(probs_np[i])
for i in range(NUM_CLASSES)
}
return result_text, probs_dict
# =========================
# ๐ŸŽจ GRADIO UI
# =========================
def create_interface():
with gr.Blocks(
theme=gr.themes.Soft(),
css="""
.gradio-container {
max-width: 1600px !important;
margin: auto;
}
.gr-image {
min-height: 500px !important;
}
textarea {
font-size: 15px !important;
font-family: monospace !important;
}
h1, h2, h3 {
text-align: center;
}
"""
) as interface:
gr.Markdown("# ๐Ÿซ CT Scan Pathology Classifier")
gr.Markdown(
"### Detects: **Hydropneumothorax ยท Brain Gliosis ยท Liver Cirrhosis ยท Liver Abscess**"
)
gr.Markdown("---")
with gr.Row(equal_height=True):
# โ”€โ”€ LEFT: Image upload โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with gr.Column(scale=1.2):
image_input = gr.Image(
type="pil",
label="๐Ÿ“ค Upload CT Scan (JPG / PNG / BMP)",
height=500,
sources=["upload"] # webcam & clipboard disabled
)
with gr.Row():
predict_btn = gr.Button("๐Ÿš€ Analyze CT Scan", variant="primary")
clear_btn = gr.Button("๐Ÿงน Clear")
# โ”€โ”€ RIGHT: Results โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
with gr.Column(scale=1.8):
output_text = gr.Textbox(
label="๐Ÿ“‹ Classification Result",
lines=18
)
output_chart = gr.Label(
label="๐Ÿ“Š Confidence Breakdown"
)
gr.Markdown("---")
gr.Markdown(
"๐Ÿ’ก **Tips:** Use axial CT slices for best results. "
"Convert DICOM to PNG/JPG before uploading if needed."
)
# โ”€โ”€ Button actions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
predict_btn.click(
fn=predict_ct,
inputs=image_input,
outputs=[output_text, output_chart]
)
clear_btn.click(
fn=lambda: (None, "", None),
inputs=[],
outputs=[image_input, output_text, output_chart]
)
return interface
# =========================
# ๐Ÿš€ LAUNCH
# =========================
if __name__ == "__main__":
app = create_interface()
app.launch(share=True)