XAI_Vision_Inspector / utils /image_utils.py
Lee Henriques
HF clean deployment
8c58a75
Raw
History Blame Contribute Delete
3.96 kB
"""
Image preprocessing utilities for XAI Vision Inspector.
Handles loading, preprocessing for model inference, and denormalization for display.
"""
import numpy as np
import torch
import torchvision.transforms as T
from PIL import Image
from typing import Tuple, Optional
import io
import urllib.request
# ImageNet normalization constants
IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
# Standard input size for most torchvision models
DEFAULT_SIZE = 224
def get_transform(input_size: int = DEFAULT_SIZE) -> T.Compose:
"""Standard ImageNet preprocessing pipeline."""
return T.Compose([
T.Resize((input_size, input_size)),
T.ToTensor(),
T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
])
def preprocess_image(
pil_image: Image.Image,
input_size: int = DEFAULT_SIZE,
device: Optional[str] = None,
) -> Tuple[torch.Tensor, np.ndarray]:
"""
Preprocess a PIL Image for model inference.
Args:
pil_image: Input PIL image (any mode)
input_size: Target spatial size
device: Target device
Returns:
tensor: (1, 3, H, W) normalized float32 tensor, on device
display: (H, W, 3) uint8 numpy array for visualization (no normalization)
"""
# Ensure RGB
if pil_image.mode != "RGB":
pil_image = pil_image.convert("RGB")
# Resize for display
display_img = pil_image.resize((input_size, input_size), Image.LANCZOS)
display_np = np.array(display_img, dtype=np.uint8)
# Preprocess for model
transform = get_transform(input_size)
tensor = transform(pil_image).unsqueeze(0)
if device:
tensor = tensor.to(device)
return tensor, display_np
def denormalize_tensor(tensor: torch.Tensor) -> np.ndarray:
"""
Reverse ImageNet normalization and convert tensor to displayable numpy array.
Args:
tensor: (1, 3, H, W) or (3, H, W) normalized tensor
Returns:
(H, W, 3) uint8 numpy array
"""
if tensor.dim() == 4:
tensor = tensor.squeeze(0)
mean = torch.tensor(IMAGENET_MEAN).view(3, 1, 1)
std = torch.tensor(IMAGENET_STD).view(3, 1, 1)
img = tensor.cpu() * std + mean
img = img.permute(1, 2, 0).numpy()
img = np.clip(img * 255, 0, 255).astype(np.uint8)
return img
def load_image_from_bytes(data: bytes) -> Image.Image:
"""Load a PIL Image from raw bytes (e.g. from Streamlit uploader)."""
return Image.open(io.BytesIO(data)).convert("RGB")
def load_image_from_url(url: str, timeout: int = 10) -> Image.Image:
"""Load a PIL Image from a URL."""
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=timeout) as response:
data = response.read()
return load_image_from_bytes(data)
def pil_to_numpy(pil_image: Image.Image, size: int = DEFAULT_SIZE) -> np.ndarray:
"""Resize and convert PIL image to (H, W, 3) uint8 numpy array."""
img = pil_image.resize((size, size), Image.LANCZOS).convert("RGB")
return np.array(img, dtype=np.uint8)
# ─── Sample images ────────────────────────────────────────────────────────────
SAMPLE_IMAGES = {
"Golden Retriever": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Golden_Retriever_Smiling.jpg/640px-Golden_Retriever_Smiling.jpg",
"Tabby Cat": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg",
"Red Fox": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/Red_Fox_%28Vulpes_vulpes%29_-_British_Wildlife_Centre-3.jpg/640px-Red_Fox_%28Vulpes_vulpes%29_-_British_Wildlife_Centre-3.jpg",
"African Elephant": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/African_Bush_Elephant.jpg/640px-African_Bush_Elephant.jpg",
}