handwriting-ocr / api /inference.py
ashkansadri's picture
init
22997db
Raw History Blame Contribute Delete
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)
}