Spaces:
Sleeping
Sleeping
Download predict.py from Phoenixnaru/classification-gradio: direct link, hf CLI and curl.
- Browser
- Download file 5.6 kB
-
https://huggingface.co/spaces/Phoenixnaru/classification-gradio/resolve/main/predict.py
- Command line
-
hf download hf://spaces/Phoenixnaru/classification-gradio/predict.py
-
curl -L -o predict.py https://huggingface.co/spaces/Phoenixnaru/classification-gradio/resolve/main/predict.py
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 |