Spaces:
Sleeping
Sleeping
| """Inference wrapper around the persisted CV model.""" | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[2])) | |
| from src.runtime import configure_runtime # noqa: E402 | |
| configure_runtime() | |
| import torch | |
| from PIL import Image | |
| from torchvision import transforms | |
| from src.config import CV_CLASSES_PATH, CV_MODEL_PATH # noqa: E402 | |
| from src.cv.train import IMAGENET_MEAN, IMAGENET_STD, build_model # noqa: E402 | |
| class DishClassifier: | |
| def __init__(self, model_path: Path | None = None) -> None: | |
| model_path = model_path or CV_MODEL_PATH | |
| if not model_path.exists(): | |
| raise FileNotFoundError( | |
| f"CV model missing at {model_path}. Run 'python -m src.cv.train'." | |
| ) | |
| checkpoint = torch.load(model_path, map_location="cpu") | |
| self.classes: list[str] = checkpoint["classes"] | |
| self.model_name: str = checkpoint["model_name"] | |
| # temperature scaling factor (set by src.cv.calibrate); 1.0 = uncalibrated | |
| self.temperature: float = float(checkpoint.get("temperature") or 1.0) | |
| self.model = build_model(self.model_name, len(self.classes)) | |
| self.model.load_state_dict(checkpoint["state_dict"]) | |
| self.model.eval() | |
| self.tf = transforms.Compose( | |
| [ | |
| transforms.Resize(256), | |
| transforms.CenterCrop(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD), | |
| ] | |
| ) | |
| def predict(self, image: Image.Image, topk: int = 3) -> list[dict[str, Any]]: | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| x = self.tf(image).unsqueeze(0) | |
| logits = self.model(x) | |
| if self.temperature and self.temperature > 0: | |
| logits = logits / self.temperature | |
| probs = torch.softmax(logits, dim=1).squeeze(0) | |
| k = min(topk, len(self.classes)) | |
| top_probs, top_idx = probs.topk(k) | |
| return [ | |
| {"label": self.classes[int(i)], "confidence": float(p)} | |
| for p, i in zip(top_probs, top_idx) | |
| ] | |
| def load_classes() -> list[str]: | |
| if CV_CLASSES_PATH.exists(): | |
| return json.loads(CV_CLASSES_PATH.read_text()) | |
| return [] | |