classification-gradio / predict.py
Phoenixnaru's picture
Fix ZeroGPU compatibility by importing spaces first and adding @spaces.GPU decorator
6d376f7
Raw History Blame Contribute Delete
5.6 kB
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import cv2
import numpy as np
import os
class CustomCnnModel(nn.Module):
def __init__(self, input_dim=128, num_classes=3):
super(CustomCnnModel, self).__init__()
self.input_dim = input_dim
self.num_classes = num_classes
self.conv_layers = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
# Compute dynamically the input size for fully connected layers
self._to_linear = None
self._get_conv_output(self.input_dim)
self.fc_layers = nn.Sequential(
nn.Linear(self._to_linear, 512),
nn.ReLU(),
nn.Linear(512, 128),
nn.ReLU(),
nn.Linear(128, self.num_classes)
)
def _get_conv_output(self, input_dim=128):
with torch.no_grad():
dummy_input = torch.zeros(1, 3, input_dim, input_dim)
output = self.conv_layers(dummy_input)
self._to_linear = output.view(1, -1).size(1)
def forward(self, x):
x = self.conv_layers(x)
x = x.view(x.size(0), -1) # Flatten
x = self.fc_layers(x)
return x
class ImageClassifier:
def __init__(self, model_path, class_name=None):
# Default to CPU on startup to safely support ZeroGPU environments
self.device = torch.device("cpu")
self.model = CustomCnnModel(input_dim=128, num_classes=3)
self.model.load_state_dict(torch.load(model_path, map_location="cpu"))
# If CUDA is immediately available, move model
if torch.cuda.is_available():
try:
self.device = torch.device("cuda")
self.model.to(self.device)
except Exception:
self.device = torch.device("cpu")
self.model.eval()
if class_name is None:
self.class_name = {0: 'Cat', 1: 'Dog', 2: 'Person'}
else:
self.class_name = class_name
self.transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
def predict(self, image_input):
"""
Accepts either a file path string or a PIL Image/numpy array.
Returns:
label (str): Name of predicted class
labeled_image: Annotated PIL Image (or file path if a string path was supplied)
"""
is_path_input = isinstance(image_input, str)
if is_path_input:
image = Image.open(image_input).convert("RGB")
elif isinstance(image_input, Image.Image):
image = image_input.convert("RGB")
elif isinstance(image_input, np.ndarray):
image = Image.fromarray(image_input).convert("RGB")
else:
raise ValueError(f"Unsupported image input type: {type(image_input)}")
# Dynamically attach to GPU if active (supports ZeroGPU during @spaces.GPU calls)
current_cuda_available = torch.cuda.is_available()
target_device = torch.device("cuda" if current_cuda_available else "cpu")
if self.device != target_device:
try:
self.model.to(target_device)
self.device = target_device
except Exception:
self.device = torch.device("cpu")
self.model.to(self.device)
image_tensor = self.transform(image).unsqueeze(0).to(self.device)
with torch.no_grad():
output = self.model(image_tensor)
_, predicted = torch.max(output, 1)
label = self.class_name.get(predicted.item(), f"Class {predicted.item()}")
# Annotate image with the predicted label
img_np = np.array(image)
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
h, w = img_bgr.shape[:2]
font_scale = max(0.6, min(w, h) / 300.0)
thickness = max(1, int(min(w, h) / 150.0))
font = cv2.FONT_HERSHEY_SIMPLEX
(text_w, text_h), baseline = cv2.getTextSize(label, font, font_scale, thickness)
box_padding = 8
cv2.rectangle(
img_bgr,
(10, 10),
(10 + text_w + 2 * box_padding, 10 + text_h + 2 * box_padding),
(0, 0, 0),
-1
)
cv2.putText(
img_bgr,
label,
(10 + box_padding, 10 + text_h + box_padding),
font,
font_scale,
(0, 255, 0),
thickness
)
annotated_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
labeled_image = Image.fromarray(annotated_rgb)
if is_path_input:
output_path = os.path.abspath("labeled_image.jpg")
labeled_image.save(output_path)
return label, output_path
return label, labeled_image