Spaces:
Runtime error
Runtime error
Download api/inference.py from handwriting-ocr-api/handwriting-ocr: direct link, hf CLI and curl.
- Browser
- Download file 4.21 kB
-
https://huggingface.co/spaces/handwriting-ocr-api/handwriting-ocr/resolve/main/api/inference.py
- Command line
-
hf download hf://spaces/handwriting-ocr-api/handwriting-ocr/api/inference.py
-
curl -L -o inference.py https://huggingface.co/spaces/handwriting-ocr-api/handwriting-ocr/resolve/main/api/inference.py
4.21 kB
| # api/inference.py | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from PIL import Image, ImageOps | |
| import numpy as np | |
| from io import BytesIO | |
| import os | |
| from src.model import OptimizedOCR | |
| class OCRPredictor: | |
| def __init__(self): | |
| self.device = torch.device("cpu") # Force CPU for Koyeb | |
| self.model = None | |
| self.idx_to_label = self._create_label_map() | |
| def _create_label_map(self): | |
| """Creates the label mapping dictionary""" | |
| label_map = {} | |
| # Add digits (0-9) | |
| for i in range(10): | |
| label_map[i] = str(i) | |
| # Add uppercase letters (A-Z) | |
| for i in range(26): | |
| label_map[i + 10] = chr(65 + i) | |
| return label_map | |
| async def _load_model(self): | |
| """Lazy loading of the model""" | |
| if self.model is None: | |
| try: | |
| self.model = OptimizedOCR(num_classes=36).to(self.device) | |
| model_path = 'best_model.pth' | |
| if not os.path.exists(model_path): | |
| raise FileNotFoundError(f"Model file not found at {model_path}") | |
| # Load model with map_location to ensure CPU loading | |
| self.model.load_state_dict( | |
| torch.load(model_path, map_location=self.device) | |
| ) | |
| self.model.eval() | |
| except Exception as e: | |
| raise Exception(f"Error loading model: {str(e)}") | |
| def _enhance_image(self, image: Image.Image) -> Image.Image: | |
| """Enhance image quality for better OCR""" | |
| # Convert to grayscale | |
| image = ImageOps.grayscale(image) | |
| # Increase contrast | |
| image = ImageOps.autocontrast(image, cutoff=10) | |
| # Resize to 27x27 using Lanczos resampling | |
| image = image.resize((27, 27), Image.LANCZOS) | |
| return image | |
| async def _preprocess_image(self, image_data: bytes) -> torch.Tensor: | |
| """Preprocesses the input image for model prediction""" | |
| try: | |
| # Open image using PIL | |
| image = Image.open(BytesIO(image_data)) | |
| # Enhance image | |
| image = self._enhance_image(image) | |
| # Convert to numpy array | |
| image_array = np.array(image) | |
| # Normalize and binarize | |
| image_array = image_array.astype(np.float32) / 255.0 | |
| threshold = np.mean(image_array) | |
| binary_image = (image_array > threshold).astype(np.float32) | |
| # Ensure dark background (invert if needed) | |
| if np.mean(binary_image) > 0.5: | |
| binary_image = 1.0 - binary_image | |
| # Add batch and channel dimensions | |
| image_tensor = torch.FloatTensor(binary_image).unsqueeze(0).unsqueeze(0) | |
| return image_tensor | |
| except Exception as e: | |
| raise Exception(f"Error preprocessing image: {str(e)}") | |
| async def predict_character(self, file) -> dict: | |
| """Predicts the character in the uploaded image""" | |
| try: | |
| # Load model if not loaded | |
| await self._load_model() | |
| # Read image data | |
| image_data = await file.read() | |
| # Preprocess image | |
| image_tensor = await self._preprocess_image(image_data) | |
| image_tensor = image_tensor.to(self.device) | |
| # Make prediction | |
| with torch.no_grad(): | |
| output = self.model(image_tensor) | |
| probabilities = torch.softmax(output, dim=1) | |
| predicted_idx = output.argmax(1).item() | |
| confidence = float(probabilities[0][predicted_idx]) | |
| predicted_char = self.idx_to_label[predicted_idx] | |
| return { | |
| "character": predicted_char, | |
| "confidence": confidence, | |
| "status": "success" | |
| } | |
| except Exception as e: | |
| return { | |
| "status": "error", | |
| "error": str(e) | |
| } |