visual-search / app.py
FayssalJ's picture
Upload app.py
2d37781 verified
Raw
History Blame
2.8 kB
"""
Visual Search API - HuggingFace Space
"""
import os
import gradio as gr
import torch
import numpy as np
from PIL import Image
# Pinecone config from HF Secrets
PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY')
PINECONE_HOST = os.environ.get('PINECONE_HOST')
# Model (loaded on first use)
model = None
def load_model():
"""Load Jina CLIP v2 model."""
global model
if model is None:
print("Loading Jina CLIP v2...")
from transformers import AutoModel
model = AutoModel.from_pretrained(
"jinaai/jina-clip-v2",
trust_remote_code=True
)
model.eval()
print("Model loaded!")
return model
def get_embedding(image: Image.Image) -> list:
"""Generate 512-dim embedding for an image."""
m = load_model()
with torch.no_grad():
emb = m.encode_image(image)
if hasattr(emb, 'cpu'):
emb = emb.cpu().numpy()
emb = emb.flatten()
emb = emb / np.linalg.norm(emb)
if len(emb) > 512:
emb = emb[:512]
return emb.tolist()
def query_pinecone(embedding: list, top_k: int = 12) -> list:
"""Query Pinecone for similar products."""
if not PINECONE_API_KEY or not PINECONE_HOST:
return []
import requests
resp = requests.post(
f"https://{PINECONE_HOST}/query",
headers={
"Api-Key": PINECONE_API_KEY,
"Content-Type": "application/json"
},
json={
"vector": embedding,
"topK": top_k,
"includeMetadata": True
},
timeout=15
)
if resp.status_code != 200:
return []
matches = resp.json().get('matches', [])
return [
{
'handle': m.get('metadata', {}).get('handle', m.get('id')),
'title': m.get('metadata', {}).get('title', ''),
'score': m.get('score', 0),
}
for m in matches
]
def search(image):
"""Main search function."""
if image is None:
return "No image provided"
try:
embedding = get_embedding(image)
products = query_pinecone(embedding)
if not products:
return "No similar products found"
result = "\n".join([
f"{i+1}. {p['title']} ({p['handle']}) - score: {p['score']:.3f}"
for i, p in enumerate(products)
])
return result
except Exception as e:
return f"Error: {str(e)}"
# Simple Gradio interface
demo = gr.Interface(
fn=search,
inputs=gr.Image(type="pil", label="Upload Image"),
outputs=gr.Textbox(label="Similar Products", lines=15),
title="Visual Product Search",
description="Upload an image to find similar products."
)
if __name__ == "__main__":
demo.launch()