coder1208 commited on
Commit
9e59cc7
·
verified ·
1 Parent(s): 9c1f394

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -333
app.py DELETED
@@ -1,333 +0,0 @@
1
- """
2
- Visual Product Matcher - Hugging Face Spaces Backend
3
- AI-powered product similarity search using CLIP model
4
- """
5
-
6
- import gradio as gr
7
- from flask import Flask, request, jsonify
8
- from sentence_transformers import SentenceTransformer
9
- from PIL import Image
10
- import requests
11
- import json
12
- import numpy as np
13
- import io
14
- import base64
15
- import os
16
- import threading
17
-
18
- # Initialize Flask app for API endpoints
19
- flask_app = Flask(__name__)
20
-
21
- # Load model
22
- print("🔄 Loading CLIP model...")
23
- model = SentenceTransformer("sentence-transformers/clip-ViT-B-32")
24
- print("✅ Model loaded successfully!")
25
-
26
- # Load products database
27
- print("📂 Loading products database...")
28
- with open("server/products.json", "r", encoding="utf-8") as f:
29
- products = json.load(f)
30
- print(f"✅ Loaded {len(products)} products")
31
-
32
- # Configuration
33
- IMAGE_FOLDER = "server/assets/product"
34
-
35
- # ============================================================================
36
- # HELPER FUNCTIONS
37
- # ============================================================================
38
-
39
- def cosine_similarity(a, b):
40
- """Calculate cosine similarity between two vectors"""
41
- a = np.array(a)
42
- b = np.array(b)
43
- return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
44
-
45
-
46
- def load_image_from_base64(data_string):
47
- """Load image from base64 string"""
48
- try:
49
- if "," in data_string:
50
- image_bytes = base64.b64decode(data_string.split(",")[-1])
51
- else:
52
- image_bytes = base64.b64decode(data_string)
53
- return Image.open(io.BytesIO(image_bytes)).convert("RGB")
54
- except Exception as e:
55
- raise ValueError(f"Failed to decode base64 image: {str(e)}")
56
-
57
-
58
- def load_image_from_url(url):
59
- """Load image from URL"""
60
- if url.startswith("data:image"):
61
- return load_image_from_base64(url)
62
-
63
- headers = {'User-Agent': 'Mozilla/5.0'}
64
- response = requests.get(url, timeout=15, headers=headers)
65
- response.raise_for_status()
66
- return Image.open(io.BytesIO(response.content)).convert("RGB")
67
-
68
-
69
- def search_similar_products(image, top_k=20):
70
- """
71
- Core search function - finds similar products
72
- Returns list of products sorted by similarity
73
- """
74
- query_embedding = model.encode(image)
75
-
76
- results = []
77
- for product in products:
78
- if "embedding" not in product:
79
- continue
80
-
81
- score = cosine_similarity(query_embedding, product["embedding"])
82
-
83
- # Use relative path for HF Spaces
84
- image_url = f"/file={IMAGE_FOLDER}/{product['image']}"
85
-
86
- results.append({
87
- "id": product.get("id", ""),
88
- "name": product["name"],
89
- "price": product["price"],
90
- "category": product.get("category", ""),
91
- "description": product.get("description", ""),
92
- "image": image_url,
93
- "similarity": score
94
- })
95
-
96
- results.sort(key=lambda x: x["similarity"], reverse=True)
97
- return results[:top_k]
98
-
99
-
100
- # ============================================================================
101
- # FLASK API ENDPOINTS (for Vercel integration)
102
- # ============================================================================
103
-
104
- @flask_app.route("/api/health", methods=["GET"])
105
- def health_check():
106
- """Health check endpoint"""
107
- return jsonify({
108
- "status": "ok",
109
- "service": "Visual Product Matcher",
110
- "model": "sentence-transformers/clip-ViT-B-32",
111
- "products_loaded": len(products)
112
- })
113
-
114
-
115
- @flask_app.route("/api/match", methods=["POST", "OPTIONS"])
116
- def match_products():
117
- """
118
- Main API endpoint for product matching
119
- Accepts: {"image": "base64_data"} or {"imageUrl": "http://..."}
120
- Returns: List of matching products
121
- """
122
- # Handle CORS preflight
123
- if request.method == "OPTIONS":
124
- response = jsonify({"status": "ok"})
125
- response.headers.add("Access-Control-Allow-Origin", "*")
126
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
127
- response.headers.add("Access-Control-Allow-Methods", "POST, OPTIONS")
128
- return response
129
-
130
- try:
131
- data = request.json
132
-
133
- if not data:
134
- return jsonify({"error": "No JSON data provided"}), 400
135
-
136
- image_data = data.get("image")
137
- image_url = data.get("imageUrl")
138
-
139
- if not image_data and not image_url:
140
- return jsonify({"error": "No image provided"}), 400
141
-
142
- # Load image
143
- if image_data:
144
- image = load_image_from_base64(image_data)
145
- else:
146
- image = load_image_from_url(image_url)
147
-
148
- # Search for similar products
149
- results = search_similar_products(image, top_k=20)
150
-
151
- # Add CORS headers
152
- response = jsonify(results)
153
- response.headers.add("Access-Control-Allow-Origin", "*")
154
- response.headers.add("Access-Control-Allow-Headers", "Content-Type")
155
-
156
- return response
157
-
158
- except Exception as e:
159
- error_response = jsonify({"error": str(e)})
160
- error_response.headers.add("Access-Control-Allow-Origin", "*")
161
- return error_response, 400
162
-
163
-
164
- # ============================================================================
165
- # GRADIO UI (for web interface)
166
- # ============================================================================
167
-
168
- def gradio_search(image):
169
- """Gradio interface function"""
170
- if image is None:
171
- return "⚠️ Please upload an image", []
172
-
173
- try:
174
- results = search_similar_products(image, top_k=6)
175
-
176
- # Format text output
177
- output_lines = ["# 🔍 Search Results\n"]
178
-
179
- for i, product in enumerate(results, 1):
180
- output_lines.append(f"### {i}. {product['name']}")
181
- output_lines.append(f"**Price:** ${product['price']}")
182
- output_lines.append(f"**Similarity:** {product['similarity']:.1%}")
183
- output_lines.append(f"**Category:** {product['category']}")
184
- if product['description']:
185
- output_lines.append(f"*{product['description']}*")
186
- output_lines.append("---")
187
-
188
- text_output = "\n".join(output_lines)
189
-
190
- # Get images for gallery
191
- image_paths = []
192
- for product in results:
193
- img_path = f"{IMAGE_FOLDER}/{product['image'].split('/')[-1]}"
194
- if os.path.exists(img_path):
195
- image_paths.append(img_path)
196
-
197
- return text_output, image_paths
198
-
199
- except Exception as e:
200
- return f"❌ Error: {str(e)}", []
201
-
202
-
203
- # Create Gradio interface
204
- with gr.Blocks(
205
- title="🔍 Visual Product Matcher",
206
- theme=gr.themes.Soft(),
207
- css=".gradio-container {max-width: 1200px; margin: auto;}"
208
- ) as gradio_interface:
209
-
210
- gr.Markdown(
211
- """
212
- # 🔍 Visual Product Matcher
213
-
214
- Upload a product image to find similar items!
215
-
216
- **Features:**
217
- - 🤖 AI-powered similarity search using CLIP
218
- - 📸 Upload images or use camera
219
- - ⚡ Instant results
220
-
221
- ---
222
- """
223
- )
224
-
225
- with gr.Row():
226
- with gr.Column(scale=1):
227
- image_input = gr.Image(
228
- type="pil",
229
- label="📸 Upload Product Image",
230
- sources=["upload", "webcam", "clipboard"]
231
- )
232
-
233
- search_btn = gr.Button(
234
- "🔍 Search Similar Products",
235
- variant="primary",
236
- size="lg"
237
- )
238
-
239
- gr.Markdown(
240
- """
241
- ### 💡 Tips:
242
- - Upload clear product images
243
- - Works best with centered products
244
- - Supported: JPG, PNG, WebP
245
- """
246
- )
247
-
248
- with gr.Column(scale=1):
249
- text_output = gr.Markdown(label="Results")
250
-
251
- with gr.Row():
252
- gallery = gr.Gallery(
253
- label="🎨 Similar Products",
254
- columns=3,
255
- rows=2,
256
- height="auto",
257
- object_fit="contain"
258
- )
259
-
260
- search_btn.click(
261
- fn=gradio_search,
262
- inputs=[image_input],
263
- outputs=[text_output, gallery]
264
- )
265
-
266
- # Example images
267
- example_images = []
268
- for i in [1, 2, 10]:
269
- img_path = f"{IMAGE_FOLDER}/product_{i}.jpg"
270
- if os.path.exists(img_path):
271
- example_images.append([img_path])
272
-
273
- if example_images:
274
- gr.Markdown("---\n### 📌 Try These Examples:")
275
- gr.Examples(examples=example_images, inputs=image_input)
276
-
277
- gr.Markdown(
278
- """
279
- ---
280
-
281
- ### 🔌 API Access
282
-
283
- **Endpoint:** `POST /api/match`
284
-
285
- **Request:**
286
- ```json
287
- {
288
- "image": "base64_image_data",
289
- "imageUrl": "https://example.com/image.jpg"
290
- }
291
- ```
292
-
293
- **Response:** Array of similar products with similarity scores
294
-
295
- ---
296
-
297
- Built with ❤️ using Sentence Transformers CLIP
298
- """
299
- )
300
-
301
-
302
- # ============================================================================
303
- # LAUNCH APP
304
- # ============================================================================
305
-
306
- def run_flask():
307
- """Run Flask API in background thread"""
308
- flask_app.run(host="0.0.0.0", port=7860)
309
-
310
-
311
- if __name__ == "__main__":
312
- # Start Flask API in background
313
- flask_thread = threading.Thread(target=run_flask, daemon=True)
314
- flask_thread.start()
315
-
316
- print("\n" + "="*60)
317
- print("🚀 Visual Product Matcher Started!")
318
- print("="*60)
319
- print(f"📊 Products loaded: {len(products)}")
320
- print(f"🤖 Model: sentence-transformers/clip-ViT-B-32")
321
- print("="*60)
322
- print("\n🌐 Gradio UI: Available at Space URL")
323
- print("🔌 API Endpoints:")
324
- print(" - GET /api/health")
325
- print(" - POST /api/match")
326
- print("="*60 + "\n")
327
-
328
- # Launch Gradio
329
- gradio_interface.launch(
330
- server_name="0.0.0.0",
331
- server_port=7860,
332
- share=False
333
- )