BrainMdpi / app.py
Ehsanul75's picture
Update app.py
54cce5d verified
Raw
History Blame Contribute Delete
5.59 kB
import cv2
import numpy as np
import torch
import torch.nn as nn
import torchvision.models as models
from skimage.segmentation import watershed
from scipy import ndimage as ndi
from skimage.feature import peak_local_max
import gradio as gr
# -----------------------------
# Config
# -----------------------------
model_path = "fusion_best.pth"
class_names = ["512Glioma", "512Meningioma", "512Normal", "512Pituitary"]
img_size = (224, 224)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# -----------------------------
# Preprocessing
# -----------------------------
def preprocess_image(img):
img_resized = cv2.resize(np.array(img), img_size)
img_gray = cv2.cvtColor(img_resized, cv2.COLOR_BGR2GRAY)
img_norm = (img_gray - np.mean(img_gray)) / (np.std(img_gray) + 1e-8)
img_norm = np.clip(img_norm, -3, 3)
img_norm = ((img_norm - img_norm.min()) / (img_norm.max() - img_norm.min()) * 255).astype(np.uint8)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_clahe = clahe.apply(img_norm)
img_denoised = cv2.fastNlMeansDenoising(img_clahe, h=10, templateWindowSize=7, searchWindowSize=21)
ret, thresh = cv2.threshold(img_denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
distance = ndi.distance_transform_edt(thresh)
coordinates = peak_local_max(distance, labels=thresh, footprint=np.ones((3,3)))
markers = np.zeros_like(distance, dtype=int)
for i, coord in enumerate(coordinates, 1):
markers[coord[0], coord[1]] = i
labels = watershed(-distance, markers, mask=thresh)
watershed_mask = (labels > 0).astype(np.uint8)
img_final_gray = cv2.bitwise_and(img_denoised, img_denoised, mask=watershed_mask*255)
img_final_rgb = np.stack([img_final_gray]*3, axis=-1)
return img_final_rgb
# -----------------------------
# Model
# -----------------------------
class FusionNet(nn.Module):
def __init__(self, num_classes):
super().__init__()
mobilenet = models.mobilenet_v2(weights=models.MobileNet_V2_Weights.IMAGENET1K_V1)
self.mobilenet = mobilenet.features
self.mobilenet_out = mobilenet.last_channel
densenet = models.densenet201(weights=models.DenseNet201_Weights.IMAGENET1K_V1)
self.densenet = densenet.features
self.densenet_out = densenet.classifier.in_features
self.gap = nn.AdaptiveAvgPool2d((1,1))
self.fc = nn.Sequential(
nn.Linear(self.mobilenet_out + self.densenet_out, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, 256),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(256, num_classes)
)
# Freeze backbones
for p in self.mobilenet.parameters():
p.requires_grad = False
for p in self.densenet.parameters():
p.requires_grad = False
def forward(self, x):
x1 = self.mobilenet(x)
x1_gap = self.gap(x1).view(x1.size(0), -1)
x2 = self.densenet(x)
self.feature_map_densenet = x2.detach() # store feature map for Grad-CAM (detached!)
x2_gap = self.gap(x2).view(x2.size(0), -1)
fused = torch.cat([x1_gap, x2_gap], dim=1)
out = self.fc(fused)
return out
# Load model
model = FusionNet(len(class_names)).to(device)
model.load_state_dict(torch.load(model_path, map_location=device))
model.eval()
# -----------------------------
# Grad-CAM DenseNet201 only
# -----------------------------
def generate_gradcam_densenet(feature_map, original_img):
# Compute channel-wise mean for Grad-CAM without backward (memory efficient)
cam = feature_map.mean(dim=1).squeeze().cpu().numpy()
cam = np.maximum(cam, 0)
cam = cv2.resize(cam, (original_img.shape[1], original_img.shape[0]))
cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)
heatmap = cv2.applyColorMap(np.uint8(255*cam), cv2.COLORMAP_JET)
overlay = cv2.addWeighted(original_img, 0.6, heatmap, 0.4, 0)
return overlay
# -----------------------------
# Prediction + Grad-CAM
# -----------------------------
def predict_with_gradcam(img):
img_np = np.array(img)
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
processed_img = preprocess_image(img_bgr)
input_tensor = torch.from_numpy(processed_img / 255.0).permute(2,0,1).float().unsqueeze(0).to(device)
mean = torch.tensor([0.485,0.456,0.406],device=device).view(1,3,1,1)
std = torch.tensor([0.229,0.224,0.225],device=device).view(1,3,1,1)
input_tensor = (input_tensor - mean)/std
# Forward pass
with torch.no_grad(): # no gradients saved → memory optimized
output = model(input_tensor)
probs = torch.nn.functional.softmax(output[0], dim=0)
pred_idx = torch.argmax(output[0]).item()
pred_class = class_names[pred_idx]
confidence = probs[pred_idx].item()*100
# Grad-CAM (DenseNet only, memory efficient)
gradcam_densenet = generate_gradcam_densenet(model.feature_map_densenet, processed_img)
return f"Prediction: {pred_class}\nConfidence: {confidence:.2f}%", gradcam_densenet
# -----------------------------
# Gradio Interface
# -----------------------------
demo = gr.Interface(
fn=predict_with_gradcam,
inputs=gr.Image(type="pil", label="Upload Brain MRI Image"),
outputs=[
gr.Textbox(label="Prediction Result"),
gr.Image(label="Grad-CAM DenseNet201")
],
title="Brain Tumor Classification",
description="Upload a brain MRI image to classify and grad cam visualization."
)
if __name__ == "__main__":
demo.launch()