ans123's picture
Update app.py
2680fbd verified
raw
history blame
13.8 kB
import os
import json
import torch
import clip
import faiss
import numpy as np
from PIL import Image
import gradio as gr
import openai
import requests
from tqdm import tqdm
from io import BytesIO
# ─────────────────────────────────────────────
# 🧠 STEP 1: LOAD CLIP MODEL
# ─────────────────────────────────────────────
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
# ─────────────────────────────────────────────
# πŸ“¦ STEP 2: LOAD PROFILE DATA FROM JSON
# ─────────────────────────────────────────────
def load_profile_data(json_file_path=None, json_data=None):
"""Load profile data either from a file or directly from JSON data"""
if json_file_path and os.path.exists(json_file_path):
with open(json_file_path, 'r') as f:
profiles = json.load(f)
elif json_data:
profiles = json_data
else:
# Sample data structure as fallback
profiles = [
{
"Id": "sample-id",
"Name": "Sample Profile",
"Age": 25,
"Bio": "Sample bio",
"Photos": [
"https://example.com/sample.jpg"
]
}
]
return profiles
# ─────────────────────────────────────────────
# πŸ–ΌοΈ STEP 3: DOWNLOAD AND PROCESS IMAGES
# ─────────────────────────────────────────────
def download_and_process_image(url):
"""Download image from URL and return PIL Image"""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
img = Image.open(BytesIO(response.content)).convert("RGB")
return img
except Exception as e:
print(f"⚠️ Error downloading image from {url}: {e}")
return None
def generate_embeddings(profiles, max_images=500):
"""Generate CLIP embeddings for profile images"""
embeddings = []
image_urls = []
profile_info = [] # Store name, age, etc. for each image
image_count = 0
print(f"🧠 Generating CLIP embeddings for profile images...")
for profile in tqdm(profiles, desc="Processing profiles"):
name = profile.get("Name", "Unknown")
age = profile.get("Age", "?")
for photo_url in profile.get("Photos", []):
if image_count >= max_images:
break
try:
img = download_and_process_image(photo_url)
if img is None:
continue
img_input = preprocess(img).unsqueeze(0).to(device)
with torch.no_grad():
emb = model.encode_image(img_input).cpu().numpy().flatten()
emb /= np.linalg.norm(emb)
embeddings.append(emb)
image_urls.append(photo_url)
profile_info.append({
"Name": name,
"Age": age,
"Id": profile.get("Id", "Unknown"),
"Bio": profile.get("Bio", "")
})
image_count += 1
except Exception as e:
print(f"⚠️ Error with {photo_url}: {e}")
if image_count >= max_images:
break
if embeddings:
embeddings = np.vstack(embeddings).astype("float32")
else:
embeddings = np.array([]).astype("float32")
print(f"βœ… Finished embedding {len(embeddings)} images.")
return embeddings, image_urls, profile_info
# ─────────────────────────────────────────────
# ⚑ STEP 4: BUILD FAISS INDEX
# ─────────────────────────────────────────────
def build_faiss_index(embeddings):
"""Build FAISS index from embeddings"""
if len(embeddings) == 0:
return None
dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)
index.add(embeddings)
return index
# ─────────────────────────────────────────────
# πŸ” STEP 5: LOAD OPENAI API KEY
# ─────────────────────────────────────────────
def init_openai():
openai.api_key = os.getenv("OPENAI_API_KEY")
if not openai.api_key:
print("⚠️ Warning: OPENAI_API_KEY not found. GPT-4 analysis will not be available.")
# ─────────────────────────────────────────────
# πŸ”Ž STEP 6: SEARCH FUNCTIONALITY
# ─────────────────────────────────────────────
def search_similar_faces(user_image, index, image_urls, profile_info, top_k=5):
"""Search for similar faces using CLIP + FAISS"""
if index is None:
return [], [], 0, "No index available. Please load profile data first."
try:
user_image = user_image.convert("RGB")
tensor = preprocess(user_image).unsqueeze(0).to(device)
with torch.no_grad():
query_emb = model.encode_image(tensor).cpu().numpy().astype("float32")
query_emb /= np.linalg.norm(query_emb)
except Exception as e:
return [], [], 0, f"Image preprocessing failed: {e}"
scores, indices = index.search(query_emb, top_k)
scores, indices = scores.flatten(), indices.flatten()
matching_images = []
match_details = []
for i in range(len(indices)):
idx = indices[i]
score = scores[i]
try:
url = image_urls[idx]
info = profile_info[idx]
img = download_and_process_image(url)
if img:
matching_images.append(img)
match_details.append({
"url": url,
"score": score,
"info": info
})
except Exception as e:
print(f"⚠️ Error processing match at index {idx}: {e}")
risk_score = min(100, int(np.mean(scores) * 100)) if scores.size > 0 else 0
return matching_images, match_details, risk_score
# ─────────────────────────────────────────────
# 🧠 STEP 7: GPT-4 ANALYSIS
# ─────────────────────────────────────────────
def generate_gpt4_analysis(match_details):
"""Generate fun analysis using GPT-4"""
if not openai.api_key:
return "GPT-4 analysis not available (API key not configured)"
if not match_details:
return "No matches found for analysis"
try:
names = [f"{d['info']['Name']} ({d['info']['Age']})" for d in match_details]
prompt = (
f"The uploaded face matches closely with: {', '.join(names)}. "
f"Based on this, should the user be suspicious? "
f"Analyze like a funny but smart AI dating detective. Keep it concise."
)
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You're a playful but intelligent AI face-matching analyst."},
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content
except Exception as e:
return f"(OpenAI error): {e}"
# ─────────────────────────────────────────────
# πŸŽ›οΈ STEP 8: APPLICATION CLASS
# ─────────────────────────────────────────────
class TinderScanner:
def __init__(self):
self.index = None
self.image_urls = []
self.profile_info = []
self.profiles = []
# Initialize OpenAI
init_openai()
def load_data(self, json_text=None, json_file=None):
"""Load profile data and build index"""
try:
if json_text:
json_data = json.loads(json_text)
self.profiles = load_profile_data(json_data=json_data)
elif json_file:
self.profiles = load_profile_data(json_file_path=json_file)
else:
return "Please provide either JSON text or a JSON file"
embeddings, self.image_urls, self.profile_info = generate_embeddings(self.profiles)
if len(embeddings) > 0:
self.index = build_faiss_index(embeddings)
return f"βœ… Successfully loaded {len(self.profiles)} profiles with {len(self.image_urls)} photos"
else:
return "⚠️ No valid images found in the provided data"
except Exception as e:
return f"❌ Error loading data: {e}"
def scan_face(self, user_image, json_input=None):
"""Process a user image and find matches"""
# Load data if provided and not already loaded
if json_input and not self.index:
load_result = self.load_data(json_text=json_input)
if "Successfully" not in load_result:
return [], "", "", load_result
if not self.index:
return [], "", "", "Please load profile data first by providing JSON input"
if user_image is None:
return [], "", "", "Please upload a face image"
images, match_details, risk_score = search_similar_faces(
user_image, self.index, self.image_urls, self.profile_info
)
# Format match captions
captions = []
for detail in match_details:
info = detail["info"]
captions.append(f"{info['Name']} ({info['Age']}) - Score: {detail['score']:.2f}")
# Generate GPT-4 analysis
explanation = generate_gpt4_analysis(match_details)
return images, "\n".join(captions), f"{risk_score}/100", explanation
# ─────────────────────────────────────────────
# πŸ–₯️ STEP 9: GRADIO UI
# ─────────────────────────────────────────────
def create_ui():
scanner = TinderScanner()
with gr.Blocks(title="Enhanced Tinder Scanner") as demo:
gr.Markdown("# πŸ” Tinder Scanner Pro – Face Match Detector")
gr.Markdown("Scan a face image to find visual matches in Tinder profiles and get a cheeky GPT-4 analysis.")
with gr.Tabs():
with gr.TabItem("Setup Data"):
with gr.Row():
with gr.Column():
json_input = gr.Textbox(
label="JSON Profile Data",
placeholder='Paste JSON data here. Format: [{"Id": "...", "Name": "...", "Age": 25, "Photos": ["url1", "url2"]}]',
lines=10
)
load_btn = gr.Button("Load Profile Data", variant="primary")
data_status = gr.Textbox(label="Status")
load_btn.click(
fn=scanner.load_data,
inputs=[json_input],
outputs=[data_status]
)
with gr.TabItem("Scan Face"):
with gr.Row():
with gr.Column():
user_image = gr.Image(type="pil", label="Upload a Face Image")
scan_btn = gr.Button("Scan Face", variant="primary")
with gr.Column():
matches_gallery = gr.Gallery(label="πŸ” Top Matches", columns=[5], height="auto")
match_details = gr.Textbox(label="Match Details")
risk_score = gr.Textbox(label="🚨 Similarity Score")
gpt_analysis = gr.Textbox(label="🧠 GPT-4 Analysis")
scan_btn.click(
fn=scanner.scan_face,
inputs=[user_image, json_input],
outputs=[matches_gallery, match_details, risk_score, gpt_analysis]
)
return demo
# ─────────────────────────────────────────────
# πŸš€ STEP 10: MAIN EXECUTION
# ─────────────────────────────────────────────
if __name__ == "__main__":
demo = create_ui()
demo.launch()