""" ModelRegistry — singleton that lazy-loads vision models per worker process. Usage from Celery tasks: from app.services.vision.registry import registry embedder = registry.get_embedder() vec = embedder.embed_image(img) Models are created on first access and cached for the worker's lifetime. The registry reads settings.vision to decide which backend to use and where model weights live. """ import logging from functools import lru_cache from app.config import settings from app.services.vision.base import Embedder, OCREngine, ObjectDetector, FaceProcessor, ContentClassifier logger = logging.getLogger(__name__) class ModelRegistry: """Central access point for all vision models.""" def __init__(self): self._vision = settings.vision @lru_cache(maxsize=1) def get_embedder(self) -> Embedder: logger.info("Loading embedder: %s (backend=%s)", self._vision.embedder.name, self._vision.backend) return self._load_backend().create_embedder() @lru_cache(maxsize=1) def get_ocr(self) -> OCREngine: logger.info("Loading OCR engine (backend=%s)", self._vision.backend) return self._load_backend().create_ocr() @lru_cache(maxsize=1) def get_detector(self) -> ObjectDetector: logger.info("Loading object detector (backend=%s)", self._vision.backend) return self._load_backend().create_detector() @lru_cache(maxsize=1) def get_face_processor(self) -> FaceProcessor: logger.info("Loading face processor (backend=%s)", self._vision.backend) return self._load_backend().create_face_processor() @lru_cache(maxsize=1) def get_classifier(self) -> ContentClassifier: logger.info("Loading content classifier (backend=%s)", self._vision.backend) return self._load_backend().create_classifier() @lru_cache(maxsize=1) def _load_backend(self): """Import and instantiate the configured backend.""" backend_name = self._vision.backend if backend_name == "onnx": from app.services.vision.onnx_backend import ONNXBackend return ONNXBackend(self._vision) elif backend_name == "rocm": from app.services.vision.rocm_backend import ROCmBackend return ROCmBackend(self._vision) else: raise ValueError(f"Unknown vision backend: {backend_name}") def warmup(self): """Pre-load all enabled models. Called from Celery worker_process_init on the vision queue to avoid cold-start latency on the first task.""" logger.info("Warming up vision models...") self.get_embedder() if self._vision.ocr.enabled: self.get_ocr() if self._vision.detector.enabled: self.get_detector() if self._vision.faces.enabled: self.get_face_processor() if self._vision.classifier.enabled: self.get_classifier() logger.info("Vision model warmup complete") # Module-level singleton. Import this from tasks. registry = ModelRegistry()