File size: 7,573 Bytes
481d782 9116554 c81c57f 481d782 a9110af 481d782 9116554 a9110af 481d782 a9110af 481d782 9116554 1c20170 481d782 cbd59bf 481d782 9116554 481d782 9116554 481d782 9116554 481d782 9116554 481d782 9116554 481d782 9116554 481d782 9116554 481d782 137f859 9116554 e17b146 9116554 e17b146 9116554 e17b146 9116554 cda7f8d c81c57f 481d782 9116554 481d782 137f859 481d782 9116554 481d782 23a9730 9754663 9116554 0c38478 23a9730 9754663 9a785ac 9754663 9116554 9754663 e608753 cbd59bf c2fb0dd aa9769c c2fb0dd aa9769c c2fb0dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | import sys
import platform
import os
import torch
import pickle
import numpy as np
from PIL import Image
from tqdm import tqdm
import clip # ✅ Required for custom model queries using OpenAI's CLIP
from pathlib import Path
# SQLite patch for Streamlit deployment (non-Windows)
if platform.system() != "Windows":
try:
import pysqlite3
sys.modules["sqlite3"] = pysqlite3
except ImportError:
raise ImportError("pysqlite3-binary is not installed. Add it to requirements.txt for deployment.")
# ✅ HuggingFace CLIP model for ChromaDB
from transformers import CLIPModel, CLIPProcessor
# ✅ ChromaDB and embedding functions
import chromadb
from chromadb.utils.embedding_functions import EmbeddingFunction
# Optional dataset downloader
from download import download_flickr8k_dataset
TOP_K = 5
# -------------------------
# HuggingFace CLIP Wrapper
# -------------------------
class CLIPEmbeddingFunction(EmbeddingFunction):
def __init__(self):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(self.device).eval()
self.processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
def embed_image(self, image):
if isinstance(image, str):
image = Image.open(image).convert("RGB")
inputs = self.processor(images=image, return_tensors="pt").to(self.device)
with torch.no_grad():
image_features = self.model.get_image_features(**inputs)
return image_features.cpu().numpy()[0].tolist()
def embed_text(self, text):
inputs = self.processor(text=text, return_tensors="pt", padding=True, truncation=True).to(self.device)
with torch.no_grad():
text_features = self.model.get_text_features(**inputs)
return text_features.cpu().numpy()[0].tolist()
def embed_combined(self, text, image):
text_emb = torch.tensor(self.embed_text(text))
image_emb = torch.tensor(self.embed_image(image))
avg_emb = (text_emb + image_emb) / 2
return avg_emb.numpy().tolist()
# -------------------------
# CLIP Setup
# -------------------------
clip_embedder = CLIPEmbeddingFunction()
device = "cuda" if torch.cuda.is_available() else "cpu"
clip_model, clip_preprocess = clip.load("ViT-B/32", device=device)
# -------------------------
# Load Custom Model Files
# -------------------------
with open("nn_model.pkl", "rb") as f:
custom_nn_model = pickle.load(f)
with open("image_paths.pkl", "rb") as f:
custom_image_paths = pickle.load(f)
custom_image_embeddings = np.load("image_embeddings.npy")
# -------------------------
# ChromaDB Training
# -------------------------
def train_rag_on_images(image_folder):
if not os.path.exists(image_folder):
print(f"{image_folder} not found. Downloading dataset...")
download_flickr8k_dataset()
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection(name="image_search")
image_paths = []
for root, _, files in os.walk(image_folder):
for file in files:
if file.lower().endswith((".jpg", ".jpeg", ".png")):
image_paths.append(os.path.join(root, file))
image_paths = image_paths[:4000]
print(f"Embedding {len(image_paths)} images...")
for idx, path in enumerate(tqdm(image_paths)):
embedding = clip_embedder.embed_image(path)
collection.add(
documents=[path],
embeddings=[embedding],
ids=[f"img_{idx}"]
)
print("Training complete. Embeddings stored in ChromaDB.")
# -------------------------
# Unified Custom Query
# -------------------------
def query_custom_model(prompt=None, image=None, k=4):
if prompt is None and image is None:
raise ValueError("You must provide at least a prompt or an image.")
query_embed = None
if prompt:
tokenized = clip.tokenize(prompt).to(device)
with torch.no_grad():
text_embed = clip_model.encode_text(tokenized).cpu().numpy()
query_embed = text_embed
if image:
tensor = clip_preprocess(image).unsqueeze(0).to(device)
with torch.no_grad():
image_embed = clip_model.encode_image(tensor).cpu().numpy()
if query_embed is not None:
query_embed = (query_embed + image_embed) / 2.0
else:
query_embed = image_embed
dists, indices = custom_nn_model.kneighbors(query_embed, n_neighbors=k)
# return [custom_image_paths[i] for i in indices[0]]
return [Path(custom_image_paths[i]).as_posix() for i in indices[0]]
# -------------------------
# ChromaDB Query
# -------------------------
def query_similar_images(prompt=None, image=None, k=4, show_custom=True):
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_collection("image_search")
if prompt and image:
query_vector = clip_embedder.embed_combined(prompt, image)
elif image:
query_vector = clip_embedder.embed_image(image)
elif prompt:
query_vector = clip_embedder.embed_text(prompt)
else:
raise ValueError("Please provide at least a prompt or an image.")
results = collection.query(query_embeddings=[query_vector], n_results=k)
clip_image_paths = results['documents'][0]
if any(not os.path.exists(path) for path in clip_image_paths):
print("Some image files are missing. Re-downloading dataset...")
download_flickr8k_dataset()
custom_results = query_custom_model(prompt, image, k=k) if show_custom else []
return clip_image_paths, custom_results
# -------------------------
# Optional Fallback Loader
# -------------------------
def load_custom_model(model_dir=".", base_image_dir="flickr8k/Flicker8k_Dataset"):
with open(f"{model_dir}/nn_model.pkl", "rb") as f:
nn_model = pickle.load(f)
with open(f"{model_dir}/image_paths.pkl", "rb") as f:
image_paths = pickle.load(f)
image_embeddings = np.load(f"{model_dir}/image_embeddings.npy")
# 🔧 Normalize paths for Linux environments
image_paths = [p.replace("\\", "/") for p in image_paths]
fixed_paths = []
for path in image_paths:
if not os.path.exists(path):
filename = os.path.basename(path)
fixed_paths.append(os.path.join(base_image_dir, filename))
else:
fixed_paths.append(path)
return nn_model, image_embeddings, fixed_paths
# Inside image_search.py
def update_custom_image_paths(folder_path):
global custom_image_paths
custom_image_paths = [os.path.join(folder_path, name) for name in os.listdir(folder_path) if name.lower().endswith((".jpg", ".jpeg", ".png"))]
def query_by_image(image_path, nn_model, image_paths):
image = Image.open(image_path).convert("RGB")
tensor = clip_preprocess(image).unsqueeze(0).to(device)
with torch.no_grad():
image_features = clip_model.encode_image(tensor).cpu().numpy()
dists, indices = nn_model.kneighbors(image_features, n_neighbors=TOP_K)
return [image_paths[i] for i in indices[0]]
def query_by_text(prompt, nn_model, image_paths):
# Tokenize and encode the text
with torch.no_grad():
text_tokens = clip.tokenize([prompt]).to(device)
text_features = clip_model.encode_text(text_tokens).cpu().numpy()
# Get top matches
dists, indices = nn_model.kneighbors(text_features, n_neighbors=TOP_K)
return [image_paths[i] for i in indices[0]]
|