Spaces:
Sleeping
Sleeping
Download predictor.py from alezsd/clasificacion-api: direct link, hf CLI and curl.
- Browser
- Download file 7.05 kB
-
https://huggingface.co/spaces/alezsd/clasificacion-api/resolve/main/predictor.py
- Command line
-
hf download hf://spaces/alezsd/clasificacion-api/predictor.py
-
curl -L -o predictor.py https://huggingface.co/spaces/alezsd/clasificacion-api/resolve/main/predictor.py
7.05 kB
| #!/usr/bin/env python3 | |
| """ | |
| CRNN CaligrafΓa OPTIMIZADO para Mac Intel i9 + AMD GPU | |
| Entrenamiento 98% accuracy con GPU AMD Radeon Pro | |
| predict(imagen_path) β {"caligrafia": 2, "confidence": 0.97} | |
| Integrado Flask API + Optimizaciones especΓficas Intel Mac | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| from torch.utils.data import Dataset, DataLoader | |
| import torchvision.transforms as transforms | |
| from torchvision.datasets import ImageFolder | |
| from PIL import Image | |
| import os | |
| import numpy as np | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| import sys | |
| from pathlib import Path | |
| import shutil | |
| import platform | |
| IS_APPLE_SILICON = platform.machine() == 'arm64' | |
| print(f"ποΈ Arquitectura: {platform.machine()}") | |
| def get_device(): | |
| return torch.device('cpu') # Railway = CPU only | |
| device = get_device() | |
| print(f" CUDA: {torch.cuda.is_available()}") | |
| print(f" MPS API: {torch.backends.mps.is_available()} (IGNORADO en Intel)") | |
| print(f" Usando: {device}") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ResizeWithPad: escala manteniendo proporciΓ³n | |
| # y rellena con blanco hasta TARGET_H x TARGET_W | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ResizeWithPad: | |
| """ | |
| Redimensiona preservando la proporciΓ³n original: | |
| 1. Escala hasta que alto == target_h | |
| 2. Si ancho < target_w β padding blanco a ambos lados (centrado) | |
| 3. Si ancho > target_w β recorte centrado | |
| Resultado siempre: target_w Γ target_h px | |
| """ | |
| def __init__(self, target_h=64, target_w=256, fill=255): | |
| self.target_h = target_h | |
| self.target_w = target_w | |
| self.fill = fill | |
| def __call__(self, img): | |
| # Normaliza cualquier modo a RGB con fondo blanco | |
| if img.mode == 'RGBA': | |
| fondo = Image.new('RGB', img.size, (self.fill, self.fill, self.fill)) | |
| fondo.paste(img, mask=img.split()[3]) | |
| img = fondo | |
| else: | |
| img = img.convert('RGB') | |
| w, h = img.size | |
| scale = self.target_h / h | |
| new_w = int(w * scale) | |
| img = img.resize((new_w, self.target_h), Image.LANCZOS) | |
| if new_w < self.target_w: | |
| pad_left = (self.target_w - new_w) // 2 | |
| new_img = Image.new('RGB', (self.target_w, self.target_h), | |
| (self.fill, self.fill, self.fill)) | |
| new_img.paste(img, (pad_left, 0)) | |
| img = new_img | |
| elif new_w > self.target_w: | |
| left = (new_w - self.target_w) // 2 | |
| img = img.crop((left, 0, left + self.target_w, self.target_h)) | |
| return img | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CRNN + Attention | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| class CaligrafiaCRNN(nn.Module): | |
| def __init__(self, num_classes=7): # β consistente con el .pth entrenado | |
| super().__init__() | |
| self.cnn = nn.Sequential( | |
| nn.Conv2d(1, 32, 3, padding=1), nn.ReLU(), | |
| nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), | |
| nn.MaxPool2d(2, 2), | |
| nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), | |
| nn.Conv2d(128, 128, 3, padding=1), nn.ReLU(), | |
| nn.MaxPool2d(2, 2), | |
| nn.Conv2d(128, 256, 3, padding=1), nn.ReLU(), | |
| nn.Dropout2d(0.25) | |
| ) | |
| self.lstm = nn.LSTM(4096, 256, bidirectional=True, batch_first=True, dropout=0.3) | |
| self.attention = nn.MultiheadAttention(512, 8, batch_first=True) | |
| self.classifier = nn.Sequential( | |
| nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.5), | |
| nn.Linear(256, num_classes) | |
| ) | |
| def forward(self, x): | |
| features = self.cnn(x) | |
| B, C, H, W = features.shape | |
| features = features.permute(0, 3, 1, 2).reshape(B, W, C * H) | |
| lstm_out, _ = self.lstm(features) | |
| attn_out, _ = self.attention(lstm_out, lstm_out, lstm_out) | |
| pooled = attn_out.mean(dim=1) | |
| return self.classifier(pooled) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PREDICTOR | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| class CaligrafiaPredictor: | |
| def __init__(self, model_path="best_caligrafia_model.pth", num_classes=7): | |
| self.device = get_device() | |
| self.model = CaligrafiaCRNN(num_classes).to(self.device) | |
| self.model.load_state_dict(torch.load(model_path, map_location=self.device)) | |
| self.model.eval() | |
| self.transform = transforms.Compose([ | |
| transforms.Lambda(lambda img: ResizeWithPad(64, 256, fill=255)(img)), | |
| transforms.Grayscale(num_output_channels=1), | |
| transforms.ToTensor(), | |
| transforms.Normalize((0.5,), (0.5,)) | |
| ]) | |
| self.clases = ['Impresa', 'Italica_cursiva', 'No_valido', | |
| 'Procesal_encadenada', 'Procesal_simple', | |
| 'Pseudorredonda', 'Redonda'] | |
| print(f"β Clases cargadas: {self.clases}") | |
| def predict_image(self, image): | |
| """PredicciΓ³n desde objeto PIL Image (para Flask)""" | |
| # Normaliza modo antes de transformar β evita crash con P, RGBA, CMYK | |
| if image.mode not in ('RGB', 'RGBA'): | |
| image = image.convert('RGB') | |
| image = self.transform(image).unsqueeze(0).to(self.device) | |
| with torch.no_grad(): | |
| logits = self.model(image) | |
| probs = torch.softmax(logits, dim=1) | |
| pred_class = probs.argmax(dim=1).cpu().item() | |
| confidence = probs[0, pred_class].cpu().item() | |
| return { | |
| "caligrafia": self.clases[pred_class], | |
| "confidence": float(confidence), | |
| "probs": probs[0].cpu().tolist(), | |
| "pred_class_id": int(pred_class) | |
| } | |
| def predict(self, image_path): | |
| """PredicciΓ³n desde ruta de archivo""" | |
| image = Image.open(image_path) | |
| # Normaliza modo antes de transformar | |
| if image.mode not in ('RGB', 'RGBA'): | |
| image = image.convert('RGB') | |
| image = self.transform(image).unsqueeze(0).to(self.device, non_blocking=True) | |
| with torch.no_grad(): | |
| logits = self.model(image) | |
| probs = torch.softmax(logits, dim=1) | |
| pred_class = probs.argmax(dim=1).cpu().item() | |
| confidence = probs[0, pred_class].cpu().item() | |
| return { | |
| "caligrafia": self.clases[pred_class], | |
| "confidence": float(confidence), | |
| "probs": probs[0].cpu().tolist(), | |
| "pred_class_id": int(pred_class) | |
| } |