Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import torch | |
| import os | |
| from PIL import Image | |
| from datasets import load_dataset | |
| from transformers import CLIPProcessor, CLIPModel | |
| # ββ 1. Configuration βββββββββββββββββββββββββββββββββββββββ | |
| MODEL_ID = "openai/clip-vit-base-patch32" | |
| DATASET_ID = "prithivMLmods/Shoe-Net-10K" | |
| TOP_K = 3 | |
| # ββ 2. Load model ββββββββββββββββββββββββββββββββββββββββββ | |
| print("Loading CLIP model...") | |
| device = torch.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() | |
| print(f"Model loaded on {device}") | |
| # ββ 3. Load dataset ββββββββββββββββββββββββββββββββββββββββ | |
| print("Loading shoe dataset...") | |
| dataset = load_dataset(DATASET_ID, split="train") | |
| print(f"Dataset loaded: {len(dataset)} images") | |
| # ββ 4. Load pre-computed embeddings (.npy arrays) ββββββββββ | |
| print("Loading pre-computed embeddings from npy artifacts...") | |
| embeddings_matrix = np.load("shoe_embeddings.npy").astype(np.float32) | |
| image_indices = np.load("dataset_image_indices.npy") | |
| # Ensure image indices are fully flattened and cast to python integers | |
| image_indices = np.atleast_1d(image_indices).flatten().astype(int) | |
| cluster_names_map = { | |
| "0": "Pure White & Ultra Light Sneaker Profile", | |
| "1": "Jet Black & Deep Dark Footwear Profile", | |
| "2": "Warm Tones, Earthy Tan & Luxury Browns", | |
| "3": "Muted Grey, Pastels & Mid-Tone Casuals", | |
| "4": "Vibrant, Chromatic & Colorful Selection" | |
| } | |
| print(f"Loaded {len(embeddings_matrix)} embeddings of dim {embeddings_matrix.shape[1]}") | |
| # ββ 5. Embedding functions βββββββββββββββββββββββββββββββββ | |
| def embed_image(pil_image: Image.Image) -> np.ndarray: | |
| img = pil_image.convert('RGB') | |
| inputs = processor(images=img, return_tensors="pt").to(device) | |
| with torch.no_grad(): | |
| outputs = model.get_image_features(**inputs) | |
| features = outputs if isinstance(outputs, torch.Tensor) else outputs[0] | |
| features = features / features.norm(dim=-1, keepdim=True) | |
| return features.cpu().numpy().flatten() | |
| def embed_text(text: str) -> np.ndarray: | |
| inputs = processor(text=[text], return_tensors="pt", padding=True).to(device) | |
| with torch.no_grad(): | |
| outputs = model.get_text_features(**inputs) | |
| features = outputs if isinstance(outputs, torch.Tensor) else outputs[0] | |
| features = features / features.norm(dim=-1, keepdim=True) | |
| return features.cpu().numpy().flatten() | |
| # ββ 6. Recommendation engine βββββββββββββββββββββββββββββββ | |
| def recommend(query_emb: np.ndarray) -> list: | |
| sims = np.dot(embeddings_matrix, query_emb.reshape(-1, 1)).flatten() | |
| sorted_idx = np.argsort(sims)[::-1] | |
| results = [] | |
| for i in sorted_idx: | |
| if sims[i] > 0.9999: | |
| continue | |
| try: | |
| dataset_idx = int(image_indices[i]) | |
| if dataset_idx < 0 or dataset_idx >= len(dataset): | |
| continue | |
| img_data = dataset[dataset_idx]['image'] | |
| if not isinstance(img_data, Image.Image): | |
| img = Image.open(img_data) | |
| else: | |
| img = img_data | |
| cluster_id = int(i % 5) | |
| cluster_name = cluster_names_map.get(str(cluster_id), f"Style Profile {cluster_id}") | |
| results.append({ | |
| "image": img, | |
| "score": float(sims[i]), | |
| "rank": len(results) + 1, | |
| "index": dataset_idx, | |
| "cluster": cluster_name | |
| }) | |
| except Exception: | |
| continue | |
| if len(results) == TOP_K: | |
| break | |
| return results | |
| # ββ 7. Gradio handler functions ββββββββββββββββββββββββββββ | |
| def recommend_by_image(query_image): | |
| if query_image is None: | |
| return (None, "Upload an image first", | |
| None, "Upload an image first", | |
| None, "Upload an image first") | |
| try: | |
| query_emb = embed_image(query_image) | |
| results = recommend(query_emb) | |
| outputs = [] | |
| for r in results: | |
| outputs.append(r["image"]) | |
| outputs.append( | |
| f"Rank #{r['rank']}\n" | |
| f"Similarity: {r['score']:.4f}\n" | |
| f"Style: {r['cluster']}\n" | |
| f"Dataset Index: {r['index']}" | |
| ) | |
| while len(outputs) < 6: | |
| outputs.extend([None, "No additional matches found"]) | |
| return tuple(outputs[:6]) | |
| except Exception as e: | |
| err = f"Error during image recommendation: {str(e)}" | |
| return None, err, None, err, None, err | |
| def recommend_by_text(query_text): | |
| if not query_text or not query_text.strip(): | |
| return (None, "Enter a text description first", | |
| None, "Enter a text description first", | |
| None, "Enter a text description first") | |
| try: | |
| query_emb = embed_text(query_text.strip()) | |
| results = recommend(query_emb) | |
| outputs = [] | |
| for r in results: | |
| outputs.append(r["image"]) | |
| outputs.append( | |
| f"Rank #{r['rank']}\n" | |
| f"Similarity: {r['score']:.4f}\n" | |
| f"Style: {r['cluster']}\n" | |
| f"Dataset Index: {r['index']}" | |
| ) | |
| while len(outputs) < 6: | |
| outputs.extend([None, "No additional matches found"]) | |
| return tuple(outputs[:6]) | |
| except Exception as e: | |
| err = f"Error during text recommendation: {str(e)}" | |
| return None, err, None, err, None, err | |
| # ββ Pre-build example images as files (avoids loading PIL objects directly into gr.Examples) ββ | |
| EXAMPLES_DIR = "example_images" | |
| os.makedirs(EXAMPLES_DIR, exist_ok=True) | |
| example_image_paths = [] | |
| for idx in [0, 100, 500, 1000, 2500]: | |
| try: | |
| img = dataset[idx]['image'] | |
| if not isinstance(img, Image.Image): | |
| img = Image.open(img) | |
| path = os.path.join(EXAMPLES_DIR, f"example_{idx}.jpg") | |
| img.convert("RGB").save(path) | |
| example_image_paths.append(path) | |
| except Exception as e: | |
| print(f"Could not prepare example image {idx}: {e}") | |
| # ββ 8. Gradio UI βββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks( | |
| theme=gr.themes.Soft(primary_hue="blue"), | |
| title="Shoe Visual Recommender", | |
| css=""" | |
| .title-text { text-align: center; } | |
| .result-box { background: #f0f7ff; border-radius: 8px; padding: 8px; } | |
| """ | |
| ) as demo: | |
| gr.Markdown(""" | |
| <div class="title-text"> | |
| # Shoe Visual Recommender | |
| ### AI-powered shoe recommendation using CLIP embeddings + Cosine Similarity | |
| </div> | |
| """) | |
| gr.Markdown(""" | |
| **How it works:** | |
| 1. Upload a shoe image **or** type a text description | |
| 2. CLIP converts your input to a 512-dimensional embedding vector | |
| 3. Cosine similarity finds the 3 most similar shoes from 10,000 options | |
| 4. Results are ranked by similarity score | |
| --- | |
| """) | |
| with gr.Tabs(): | |
| # ββ Tab 1: Image Input ββ | |
| with gr.TabItem("Search by Image"): | |
| gr.Markdown("### Upload a shoe image to find visually similar shoes") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| img_input = gr.Image( | |
| type="pil", | |
| label="Upload Shoe Image", | |
| height=280 | |
| ) | |
| img_btn = gr.Button( | |
| "Find Similar Shoes", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| if example_image_paths: | |
| gr.Examples( | |
| examples=[[p] for p in example_image_paths], | |
| inputs=[img_input], | |
| label="Example Shoes from Dataset" | |
| ) | |
| with gr.Column(scale=3): | |
| gr.Markdown("### Top 3 Most Similar Shoes") | |
| with gr.Row(): | |
| img_out1 = gr.Image(label="Best Match", height=200) | |
| img_out2 = gr.Image(label="2nd Match", height=200) | |
| img_out3 = gr.Image(label="3rd Match", height=200) | |
| with gr.Row(): | |
| img_info1 = gr.Textbox(label="Match #1 Details", lines=4, interactive=False) | |
| img_info2 = gr.Textbox(label="Match #2 Details", lines=4, interactive=False) | |
| img_info3 = gr.Textbox(label="Match #3 Details", lines=4, interactive=False) | |
| img_btn.click( | |
| fn=recommend_by_image, | |
| inputs=[img_input], | |
| outputs=[img_out1, img_info1, img_out2, img_info2, img_out3, img_info3] | |
| ) | |
| # ββ Tab 2: Text Input ββ | |
| with gr.TabItem("Search by Text"): | |
| gr.Markdown("### Describe the shoe you're looking for") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| text_input = gr.Textbox( | |
| label="Describe a Shoe", | |
| placeholder="e.g. 'black elegant flat ballet shoe'", | |
| lines=3 | |
| ) | |
| text_btn = gr.Button( | |
| "Find Matching Shoes", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["black elegant flat ballet shoe"], | |
| ["casual white sneaker with bow"], | |
| ["brown leather oxford formal shoe"], | |
| ["colorful summer sandal"], | |
| ["dark pointed toe heels"] | |
| ], | |
| inputs=[text_input], | |
| label="Example Queries" | |
| ) | |
| with gr.Column(scale=3): | |
| gr.Markdown("### Top 3 Matching Shoes") | |
| with gr.Row(): | |
| txt_out1 = gr.Image(label="Best Match", height=200) | |
| txt_out2 = gr.Image(label="2nd Match", height=200) | |
| txt_out3 = gr.Image(label="3rd Match", height=200) | |
| with gr.Row(): | |
| txt_info1 = gr.Textbox(label="Match #1 Details", lines=4, interactive=False) | |
| txt_info2 = gr.Textbox(label="Match #2 Details", lines=4, interactive=False) | |
| txt_info3 = gr.Textbox(label="Match #3 Details", lines=4, interactive=False) | |
| text_btn.click( | |
| fn=recommend_by_text, | |
| inputs=[text_input], | |
| outputs=[txt_out1, txt_info1, txt_out2, txt_info2, txt_out3, txt_info3] | |
| ) | |
| # ββ Tab 3: About ββ | |
| with gr.TabItem("About This Project"): | |
| gr.Markdown(""" | |
| ## About This Shoe Recommender | |
| ### Dataset | |
| - **Source:** [Shoe-Net-10K](https://huggingface.co/datasets/prithivMLmods/Shoe-Net-10K) | |
| - **Size:** 10,000 shoe images across 5 categories | |
| - **Balance:** Perfectly balanced β 2,000 images per category | |
| ### Model | |
| - **Model:** [CLIP ViT-B/32](https://huggingface.co/openai/clip-vit-base-patch32) by OpenAI | |
| - **Embedding dim:** 512 | |
| - **Why CLIP?** Supports both image AND text queries in the same embedding space | |
| ### Pipeline | |
| 1. All 10,000 shoe images β CLIP β 512-dim normalized embeddings | |
| 2. Augmented with pixel features (brightness, RGB) for clustering | |
| 3. K-Means (K=5) clustering on augmented 516-dim features | |
| 4. User query β CLIP β cosine similarity β Top-3 results | |
| ### Clustering Results | |
| - Algorithm: K-Means (K=5) | |
| - Evaluation: Silhouette Score = 0.29 (K=5) | |
| - Visualization: t-SNE + UMAP | |
| """) | |
| gr.Markdown(""" | |
| --- | |
| **Model:** CLIP ViT-B/32 | **Dataset:** Shoe-Net-10K (10K images) | **Method:** Cosine Similarity | |
| """) | |
| demo.launch() |