clip-saliency / app.py
yuchen-xiyue
Add Resnet option
0386bd0
Raw
History Blame Contribute Delete
7.23 kB
import os
import tempfile
import warnings
from PIL import Image
import cv2
import gradio as gr
import numpy as np
import torch
import torch.nn.functional as F
from gradio_imageslider import ImageSlider
from utils import interpret
import clip_explain.CLIP.clip as clip
device = "cuda" if torch.cuda.is_available() else "cpu"
vit_model, vit_preprocess = clip.load("ViT-B/32", device=device, jit=False)
cnn_model, cnn_preprocess = clip.load("RN50", device=device, jit=False)
gradients_and_activations = {}
def save_gradients(module, grad_in, grad_out):
gradients_and_activations["gradients"] = grad_out[0].detach()
def save_activations(module, input, output):
gradients_and_activations["activations"] = output.detach()
def get_cnn_gradcam(model, image, text, device):
gradients_and_activations.clear()
target_layer = model.visual.layer4[-1]
handle_forward = target_layer.register_forward_hook(save_activations)
handle_backward = target_layer.register_full_backward_hook(save_gradients)
model.zero_grad()
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
logit_scale = model.logit_scale.exp()
logits = logit_scale * image_features @ text_features.t()
score = logits[0, 0]
score.backward(retain_graph=True)
handle_forward.remove()
handle_backward.remove()
gradients = gradients_and_activations.get("gradients")
activations = gradients_and_activations.get("activations")
if gradients is None or activations is None:
print("Warning: Gradients or activations are None. Using fallback layer.")
gradients_and_activations.clear()
model.zero_grad()
target_layer = model.visual.layer3[-1]
handle_forward = target_layer.register_forward_hook(save_activations)
handle_backward = target_layer.register_full_backward_hook(save_gradients)
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)
logits = model.logit_scale.exp() * image_features @ text_features.t()
score = logits[0, 0]
score.backward(retain_graph=True)
handle_forward.remove()
handle_backward.remove()
gradients = gradients_and_activations.get("gradients")
activations = gradients_and_activations.get("activations")
print(f"Gradients shape: {gradients.shape}")
print(f"Activations shape: {activations.shape}")
if len(activations.shape) == 4:
weights = torch.mean(gradients, dim=(2, 3), keepdim=True)
cam = torch.sum(weights * activations, dim=1, keepdim=True)
cam = F.relu(cam)
cam = F.interpolate(cam, size=(224, 224), mode='bilinear', align_corners=False)
cam = cam.squeeze().detach().cpu().numpy()
cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)
else:
pooled_gradients = torch.mean(gradients, dim=0)
cam = torch.outer(activations.squeeze(0), pooled_gradients)
cam = torch.sum(cam, dim=0).detach().cpu().numpy()
size = int(np.sqrt(cam.shape[0]))
cam = cam[:size*size].reshape(size, size)
cam = cv2.resize(cam, (224, 224))
cam = np.maximum(cam, 0)
cam = cam / (cam.max() + 1e-8)
return cam
def process_image(image, text, model_type="Transformer (ViT)"):
if image is None or text.strip() == "":
return (image, image), None
if isinstance(image, np.ndarray):
original_img = Image.fromarray(image)
else:
original_img = image
if original_img.mode == 'I;16' or original_img.mode == 'I':
original_img = np.array(original_img)
original_img = ((original_img / 65535.0) * 255).astype(np.uint8)
original_img = Image.fromarray(original_img)
elif original_img.mode not in ['RGB', 'L']:
original_img = original_img.convert('RGB')
if model_type == "Transformer (ViT)":
model = vit_model
preprocess = vit_preprocess
use_transformer = True
else:
model = cnn_model
preprocess = cnn_preprocess
use_transformer = False
try:
processed_img = preprocess(original_img).unsqueeze(0).to(device)
except Exception as e:
warnings.warn(f"Error preprocessing image: {e}")
return (original_img, original_img), None
texts = [text]
tokenized_text = clip.tokenize(texts).to(device)
if use_transformer:
_, image_relevance = interpret(
model=model,
image=processed_img,
texts=tokenized_text,
device=device
)
dim = int(image_relevance[0].numel() ** 0.5)
rel = image_relevance[0].reshape(1, 1, dim, dim)
rel = torch.nn.functional.interpolate(rel, size=224, mode='bilinear')
rel = rel.reshape(224, 224).cpu().data.numpy()
else:
heatmap = get_cnn_gradcam(model, processed_img, tokenized_text, device)
rel = cv2.resize(heatmap, (224, 224))
rel = (rel - rel.min()) / (rel.max() - rel.min())
resized_img = original_img.resize((224, 224))
img_array = np.array(resized_img)
if len(img_array.shape) == 2:
img_array = np.stack([img_array, img_array, img_array], axis=2)
img_array = img_array / 255.0
def show_cam_on_image(img, mask):
heatmap = cv2.applyColorMap(np.uint8(255 * mask), cv2.COLORMAP_JET)
heatmap = np.float32(heatmap) / 255
cam = heatmap + np.float32(img)
cam = cam / np.max(cam)
return np.uint8(255 * cam)
vis = show_cam_on_image(img_array, rel)
saliency_img = Image.fromarray(vis)
with tempfile.NamedTemporaryFile(delete=False, suffix='.npy') as tmp:
np_path = tmp.name
np.save(np_path, rel)
return (resized_img, saliency_img), np_path
title = "CLIP Saliency Visualization"
description = "Use this tool to visualize attention maps and salient regions of the CLIP model. Based on https://github.com/hila-chefer/Transformer-MM-Explainability."
demo = gr.Interface(
fn=process_image,
inputs=[
gr.Image(
type="pil",
label="Upload Image",
sources=["upload", "clipboard"]
),
gr.Textbox(
label="Enter Text Prompt",
placeholder="Enter text to compare with..."
),
gr.Radio(
["Transformer (ViT)", "CNN (ResNet)"],
label="Model Architecture",
value="Transformer (ViT)"
)
],
outputs=[
ImageSlider(label="Original vs Saliency Map", type="pil", slider_color="#2BB8AB"),
gr.File(label="Raw Depth (NumPy File)")
],
title=title,
description=description,
allow_flagging="never"
)
if __name__ == "__main__":
demo.launch()