This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
imagerepo/app/ml/siglip.py
T
bvandeusen 8af9f12544 fix(ml): tolerate truncated images in WD14 and SigLIP preprocess
PIL's strict load was raising OSError on images missing the trailing
end-of-stream marker (e.g. '6 bytes not processed' on a JPEG without
its FF D9 EOI), failing the entire ML task for an image the model
could otherwise score fine. Set ImageFile.LOAD_TRUNCATED_IMAGES = True
in both ML modules so a minutely-corrupt tail doesn't block tagging
or embedding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 22:35:03 -04:00

63 lines
2.5 KiB
Python

"""SigLIP SO400M image-embedding wrapper (PyTorch CPU)."""
from __future__ import annotations
import os
import numpy as np
from PIL import Image, ImageFile
# Mirror wd14.py: tolerate minutely-truncated source images so embedding
# doesn't fail on the same images WD14 successfully tagged.
ImageFile.LOAD_TRUNCATED_IMAGES = True
# Defer torch/transformers imports to lazily-loaded functions to allow
# importing MODEL_VERSION in non-ML-worker contexts (e.g., web container
# centroid-recompute enqueue logic).
_torch = None
_AutoModel = None
_AutoProcessor = None
MODEL_NAME = os.environ.get('SIGLIP_MODEL_NAME', 'google/siglip-so400m-patch14-384')
MODEL_VERSION = os.environ.get('SIGLIP_MODEL_VERSION', 'siglip-so400m-patch14-384')
# Model files live flat under this directory (written by scripts/download_models.py via
# snapshot_download(local_dir=...)). We point from_pretrained at the local path directly
# so transformers bypasses its HF cache layout and doesn't need network access at load time.
_LOCAL_DIR = os.path.join(os.environ.get('ML_MODEL_DIR', '/models'), 'siglip')
_model = None
_processor = None
# NOT thread-safe. Must run in the ml-worker container with --concurrency=1.
def _load() -> None:
global _model, _processor, _torch, _AutoModel, _AutoProcessor
if _model is not None:
return
# Lazy import torch/transformers so this module can be imported in contexts
# where they're not available (e.g., web container for centroid-recompute enqueue).
import torch
from transformers import AutoModel, AutoProcessor
_torch = torch
_AutoModel = AutoModel
_AutoProcessor = AutoProcessor
_processor = _AutoProcessor.from_pretrained(_LOCAL_DIR)
_model = _AutoModel.from_pretrained(_LOCAL_DIR)
_model.eval()
def infer(image_path: str) -> np.ndarray:
"""Return a 1152-dim float32 numpy embedding for the image.
SigLIP uses a MAP-pooled vision head — the pooled output is the retrieval-ready
embedding the model was trained to produce. `get_image_features` on transformers
>= 4.45 returns a BaseModelOutputWithPooling, so pull `.pooler_output` explicitly
rather than relying on the first-field fallback from indexing.
"""
_load()
img = Image.open(image_path).convert('RGB')
with _torch.no_grad():
inputs = _processor(images=img, return_tensors='pt')
out = _model.get_image_features(**inputs)
pooled = out.pooler_output if hasattr(out, 'pooler_output') else out # (1, 1152)
return pooled[0].numpy().astype(np.float32)