| 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 |
|
|
| |
| |
| |
| 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") |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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) |
| ) |
|
|
| |
| 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() |
| 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 |
|
|
| |
| model = FusionNet(len(class_names)).to(device) |
| model.load_state_dict(torch.load(model_path, map_location=device)) |
| model.eval() |
|
|
| |
| |
| |
| def generate_gradcam_densenet(feature_map, original_img): |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| with torch.no_grad(): |
| 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 |
|
|
| |
| gradcam_densenet = generate_gradcam_densenet(model.feature_map_densenet, processed_img) |
|
|
| return f"Prediction: {pred_class}\nConfidence: {confidence:.2f}%", gradcam_densenet |
|
|
| |
| |
| |
| 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() |
|
|