import spaces
import gradio as gr
import torch
from transformers import CLIPModel, CLIPProcessor
from PIL import Image
# ─── Model Loading (module-level for ZeroGPU) ───────────────────────────
MODEL_ID = "kwanY/styleid"
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CLIPModel.from_pretrained(MODEL_ID).to(device)
processor = CLIPProcessor.from_pretrained(MODEL_ID)
model.eval()
# ─── Inference ───────────────────────────────────────────────────────────
@spaces.GPU(duration=30)
def compare_faces(img1: Image.Image, img2: Image.Image):
"""Compare face identity between two images using StyleID."""
if img1 is None or img2 is None:
return "⚠️ Please upload both images.", ""
def get_embedding(img):
inputs = processor(images=img.convert("RGB"), return_tensors="pt").to(device)
with torch.no_grad():
emb = model.get_image_features(**inputs)
emb = emb / emb.norm(dim=-1, keepdim=True)
return emb
e1 = get_embedding(img1)
e2 = get_embedding(img2)
score = (e1 * e2).sum(dim=-1).item()
# Interpretation based on paper thresholds
if score >= 0.55:
verdict = "✅ Confidently Same Person"
color = "#22c55e"
elif score >= 0.45:
verdict = "✅ Very Likely Same Person"
color = "#4ade80"
elif score >= 0.35:
verdict = "🟡 Probably Same Person"
color = "#facc15"
elif score >= 0.25:
verdict = "🟠 Uncertain — Borderline"
color = "#fb923c"
else:
verdict = "❌ Likely Different People"
color = "#ef4444"
# Create a visual gauge
score_pct = max(0, min(100, int(score * 100)))
gauge_html = f"""
{score:.3f}
{verdict}
Different
Uncertain
Same
"""
details = f"""### How to Read the Score
| Range | Meaning |
|-------|---------|
| **> 0.55** | ✅ Confidently same person |
| **0.45 – 0.55** | ✅ Very likely same person |
| **0.35 – 0.45** | 🟡 Probably same person |
| **0.25 – 0.35** | 🟠 Uncertain / borderline |
| **< 0.25** | ❌ Likely different people |
**Your Score: {score:.4f}**
> **Note:** StyleID is optimized for comparing a real photo against its stylized version (anime, cartoon, painting, sketch, etc.). Works best with a single prominent face per image.
"""
return gauge_html, details
# ─── UI ──────────────────────────────────────────────────────────────────
DESCRIPTION = """
# 🎭 StyleID — Does AI Preserve Your Face?
Upload a **real photo** and a **stylized portrait** (anime, cartoon, painting, sketch, etc.)
→ StyleID checks if the identity is preserved across art styles.
**Powered by [StyleID](https://huggingface.co/kwanY/styleid)** — a perception-aware face identity model trained to match human judgments across stylizations.
### 🔬 How it works
StyleID uses a fine-tuned CLIP ViT-L/14 encoder calibrated against human psychometric experiments.
Unlike standard face recognition (ArcFace, AdaFace), StyleID is specifically designed for **cross-style** identity matching —
real photo ↔ anime, cartoon, painting, caricature, 3D render, etc.
### ⚡ Use Cases
- Check if your AI-stylized portrait still looks like you
- Evaluate face-swap / face-transfer quality
- Compare identity preservation across different AI art tools
- Research: benchmark stylization models for ID preservation
"""
TIPS = """
### 💡 Tips for Best Results
1. **One face per image** — StyleID doesn't do face detection
2. **Face should be prominent** — at least 25% of the image
3. **Frontal or 3/4 view works best** — extreme profiles may reduce scores
4. **Works across styles** — anime, cartoon, oil painting, watercolor, pencil sketch, 3D render, caricature
5. **NOT a security tool** — for research/evaluation only
"""
with gr.Blocks(
title="StyleID — Face Identity Across Art Styles",
theme=gr.themes.Soft(
primary_hue="violet",
secondary_hue="blue",
),
css="footer { display: none !important; }"
) as demo:
gr.Markdown(DESCRIPTION)
with gr.Row():
with gr.Column():
img1 = gr.Image(type="pil", label="📸 Original Photo (Real Face)", height=400)
with gr.Column():
img2 = gr.Image(type="pil", label="🎨 Stylized Portrait (Anime, Cartoon, etc.)", height=400)
btn = gr.Button("🔍 Compare Identity", variant="primary", size="lg")
with gr.Row():
with gr.Column():
gauge = gr.HTML(label="Identity Score")
with gr.Column():
details = gr.Markdown(label="Details")
btn.click(
fn=compare_faces,
inputs=[img1, img2],
outputs=[gauge, details],
)
with gr.Accordion("💡 Tips & Info", open=False):
gr.Markdown(TIPS)
gr.Markdown("""
---
**Model:** [kwanY/styleid](https://huggingface.co/kwanY/styleid) | **Paper:** [StyleID (arXiv 2604.21689)](https://arxiv.org/abs/2604.21689) | **License:** Research use only — not for biometric authentication.
""")
demo.launch()