Files changed (1) hide show
  1. app.py +471 -0
app.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import tensorflow as tf
4
+ import numpy as np
5
+ import cv2
6
+ from PIL import Image
7
+ import os
8
+ import warnings
9
+ import base64
10
+ import io
11
+ from werkzeug.utils import secure_filename
12
+
13
+ warnings.filterwarnings('ignore')
14
+
15
+ # Initialize Flask app
16
+ app = Flask(__name__)
17
+ CORS(app) # Enable CORS for all routes
18
+
19
+ # Configure TensorFlow to use CPU only
20
+ tf.config.set_visible_devices([], 'GPU')
21
+ os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
22
+
23
+ # Define face shape labels
24
+ face_shape_labels = ['Heart', 'Oblong', 'Oval', 'Round', 'Square']
25
+
26
+ # Global variables for models
27
+ face_detection_model = None
28
+
29
+ # Define the model path (update this path according to your setup)
30
+ model_path = './Try_Face_Detection_AI_1.keras' # Update this path
31
+
32
+ ##############################################################
33
+ # FACE DETECTION AND PROCESSING FUNCTIONS
34
+ ##############################################################
35
+
36
+ def detect_face_with_opencv(image):
37
+ """Detect face using OpenCV's Haar Cascade"""
38
+ if image is None:
39
+ return None
40
+
41
+ # Convert to numpy array if needed
42
+ if not isinstance(image, np.ndarray):
43
+ if hasattr(image, 'convert'):
44
+ image = np.array(image.convert('RGB'))
45
+ else:
46
+ image = np.array(image)
47
+
48
+ # Convert to grayscale for face detection
49
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
50
+
51
+ # Load OpenCV's face detector
52
+ face_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
53
+ if not os.path.exists(face_cascade_path):
54
+ print(f"Error: Haar cascade file not found at {face_cascade_path}")
55
+ return None
56
+
57
+ face_cascade = cv2.CascadeClassifier(face_cascade_path)
58
+
59
+ # Detect faces
60
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
61
+
62
+ if len(faces) > 0:
63
+ x, y, w, h = faces[0] # Get the first face
64
+ face_img = image[y:y+h, x:x+w]
65
+ return face_img
66
+ else:
67
+ return None
68
+
69
+ def extract_face(image):
70
+ """Extract face from image"""
71
+ if image is None:
72
+ return None
73
+
74
+ face_img = detect_face_with_opencv(image)
75
+
76
+ if face_img is not None:
77
+ return cv2.resize(face_img, (224, 224))
78
+
79
+ # If OpenCV fails, use the whole image
80
+ print("WARNING: Could not detect face with OpenCV")
81
+ if isinstance(image, np.ndarray):
82
+ resized = cv2.resize(image, (224, 224))
83
+ return resized
84
+ elif hasattr(image, 'resize'):
85
+ resized = image.resize((224, 224))
86
+ return np.array(resized)
87
+ return None
88
+
89
+ def preprocess_image(image):
90
+ """Preprocess image for model input"""
91
+ if image is None:
92
+ return None
93
+
94
+ try:
95
+ if isinstance(image, np.ndarray):
96
+ if len(image.shape) == 3 and image.shape[2] == 3:
97
+ rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
98
+ else:
99
+ rgb_image = image
100
+ else:
101
+ if hasattr(image, 'convert'):
102
+ rgb_image = np.array(image.convert('RGB'))
103
+ else:
104
+ rgb_image = np.array(image)
105
+
106
+ # Ensure image is the right shape
107
+ if rgb_image.shape[0] != 224 or rgb_image.shape[1] != 224:
108
+ resized_image = cv2.resize(rgb_image, (224, 224))
109
+ else:
110
+ resized_image = rgb_image
111
+
112
+ # Handle different channel formats
113
+ if len(resized_image.shape) == 2: # Grayscale
114
+ resized_image = cv2.cvtColor(resized_image, cv2.COLOR_GRAY2RGB)
115
+ elif resized_image.shape[2] == 4: # RGBA
116
+ resized_image = cv2.cvtColor(resized_image, cv2.COLOR_RGBA2RGB)
117
+
118
+ normalized_image = resized_image / 255.0
119
+ image_batch = np.expand_dims(normalized_image, axis=0)
120
+ return image_batch
121
+ except Exception as e:
122
+ print(f"Error in image preprocessing: {e}")
123
+ return None
124
+
125
+ def load_face_shape_model():
126
+ """Load face shape detection model"""
127
+ global face_detection_model
128
+ try:
129
+ # Force CPU usage to avoid CUDA issues
130
+ with tf.device('/CPU:0'):
131
+ face_detection_model = tf.keras.models.load_model(model_path)
132
+ print("Face shape detection model loaded successfully!")
133
+ return face_detection_model
134
+ except Exception as e:
135
+ print(f"Warning: Could not load face shape model: {e}")
136
+ # Create a dummy model for testing if real one isn't available
137
+ face_detection_model = tf.keras.Sequential([
138
+ tf.keras.layers.Input(shape=(224, 224, 3)),
139
+ tf.keras.layers.Conv2D(16, 3, activation='relu'),
140
+ tf.keras.layers.GlobalAveragePooling2D(),
141
+ tf.keras.layers.Dense(5, activation='softmax')
142
+ ])
143
+ print("Created dummy face shape model for testing")
144
+ return face_detection_model
145
+
146
+ def predict_face_shape(image):
147
+ """Predict face shape using the loaded model"""
148
+ global face_detection_model
149
+
150
+ if image is None:
151
+ return {"error": "No image provided"}
152
+
153
+ # Extract face from image
154
+ face_image = extract_face(image)
155
+ if face_image is None:
156
+ return {"error": "Could not process the face in the image"}
157
+
158
+ # Load model if not loaded
159
+ if face_detection_model is None:
160
+ try:
161
+ face_detection_model = load_face_shape_model()
162
+ except Exception as e:
163
+ print(f"Error loading model: {e}")
164
+ return {"error": "Could not load the face shape detection model"}
165
+
166
+ try:
167
+ # Preprocess the image
168
+ preprocessed_image = preprocess_image(face_image)
169
+
170
+ if preprocessed_image is None:
171
+ return {"error": "Could not process the image"}
172
+
173
+ # Make prediction - Force CPU usage
174
+ with tf.device('/CPU:0'):
175
+ predictions = face_detection_model.predict(preprocessed_image)
176
+ predicted_class = np.argmax(predictions)
177
+ confidence = float(predictions[0][predicted_class]) * 100
178
+
179
+ return {
180
+ "face_shape": face_shape_labels[predicted_class],
181
+ "confidence": round(confidence, 1)
182
+ }
183
+ except Exception as e:
184
+ print(f"Error in face shape prediction: {e}")
185
+ # Provide a default face shape when model fails
186
+ return {
187
+ "face_shape": "Oval",
188
+ "confidence": 50.0,
189
+ "note": "Default prediction due to processing error"
190
+ }
191
+
192
+ ##############################################################
193
+ # RECOMMENDATION DATA
194
+ ##############################################################
195
+
196
+ face_shape_recommendations = {
197
+ "Heart": {
198
+ "Glasses": [
199
+ "Cat Eye Frames", "Round Frames", "Clear Frames", "Oval Glasses", "Alford Glasses",
200
+ "Tortoiseshell Sunglasses", "Transparent Eyeglasses Frames", "Geometric Frames",
201
+ "Aviator Glasses", "Clubmaster Frames", "Oversized Glasses", "Square Frames",
202
+ "Wayfarer Glasses", "Browline Glasses", "Rimless Glasses", "Classic Aviators",
203
+ "Butterfly Frames", "Pantos Frames", "Pilot Glasses", "Rectangle Frames"
204
+ ],
205
+ "Watches": [
206
+ "Luxury Watch", "Minimalist Watch", "Chronograph Watch", "Pilot Watch", "Diver Watch",
207
+ "Sveston Sports Watch", "Casio G-Shock", "Casio Edifice", "Casio Protrek", "Fossil Silicon Watch",
208
+ "Swiss Military Alpine", "Hanowa Puma Watch", "Swiss Chronograph", "Smart BT Calling Watch",
209
+ "Infinity Smart Watch", "Vogue Smart Watch", "Realme Watch S2", "Mibro Watch C4",
210
+ "Redmi Watch 5", "Bold Dial Watch"
211
+ ],
212
+ "Hats": [
213
+ "Beanie", "Wide-Brim Hat", "Trilby", "Newsboy Cap", "Cowboy Hat",
214
+ "Trucker Hat", "Safari Hat", "Flat Cap", "Boater Hat", "Top Hat",
215
+ "Classic Fedora", "Chitrali Cap", "Gilgiti Cap", "Pakol", "Baseball Cap",
216
+ "Snapback Cap", "Bucket Hat", "Beret", "Panama Hat", "Pork Pie Hat"
217
+ ]
218
+ },
219
+ "Oblong": {
220
+ "Glasses": [
221
+ "Aviators", "Oversized Glasses", "Round Frames", "Square Frames", "Wayfarer Glasses",
222
+ "Tortoiseshell Sunglasses", "Transparent Eyeglasses Frames", "Geometric Frames",
223
+ "Cat Eye Frames", "Clubmaster Frames", "Oval Glasses", "Clear Frames",
224
+ "Butterfly Frames", "Pantos Frames", "Pilot Glasses", "Rectangle Frames",
225
+ "Browline Glasses", "Rimless Glasses", "Classic Aviators", "Embellished Sunglasses"
226
+ ],
227
+ "Watches": [
228
+ "Pilot Watch", "Luxury Watch", "Minimalist Watch", "Chronograph Watch", "Diver Watch",
229
+ "Sveston Sports Watch", "Casio G-Shock", "Casio Edifice", "Casio Protrek", "Fossil Silicon Watch",
230
+ "Swiss Military Alpine", "Hanowa Puma Watch", "Swiss Chronograph", "Smart BT Calling Watch",
231
+ "Infinity Smart Watch", "Vogue Smart Watch", "Realme Watch S2", "Mibro Watch C4",
232
+ "Redmi Watch 5", "Bold Dial Watch"
233
+ ],
234
+ "Hats": [
235
+ "Trilby", "Newsboy Cap", "Cowboy Hat", "Safari Hat", "Flat Cap",
236
+ "Trucker Hat", "Beanie", "Wide-Brim Hat", "Boater Hat", "Top Hat",
237
+ "Classic Fedora", "Chitrali Cap", "Gilgiti Cap", "Pakol", "Baseball Cap",
238
+ "Snapback Cap", "Bucket Hat", "Beret", "Panama Hat", "Pork Pie Hat"
239
+ ]
240
+ },
241
+ "Oval": {
242
+ "Glasses": [
243
+ "Wayfarer Glasses", "Geometric Frames", "Cat Eye Frames", "Round Frames", "Clear Frames",
244
+ "Aviator Glasses", "Clubmaster Frames", "Square Frames", "Oversized Glasses", "Oval Glasses",
245
+ "Transparent Frames", "Tortoiseshell Frames", "Browline Glasses", "Classic Aviators",
246
+ "Butterfly Frames", "Rimless Glasses", "Rectangle Frames", "Pilot Glasses",
247
+ "Metal Frame Glasses", "Gradient Sunglasses"
248
+ ],
249
+ "Watches": [
250
+ "Diver Watch", "Dress Watch", "Luxury Watch", "Minimalist Watch", "Chronograph Watch",
251
+ "Smart BT Calling Watch", "Realme Watch S2", "Fossil Gen 6 Smartwatch", "Casio Edifice",
252
+ "Swiss Military Alpine", "Sveston Classic", "Hanowa Chronograph", "Infinity Smart Watch",
253
+ "Mibro T1 Smartwatch", "Vogue Smart Watch", "T500+ Smart Watch", "Casio F91W",
254
+ "Xiaomi Watch 2", "Skeleton Watch", "Bold Dial Watch"
255
+ ],
256
+ "Hats": [
257
+ "Cowboy Hat", "Safari Hat", "Trilby", "Newsboy Cap", "Flat Cap",
258
+ "Wide-Brim Hat", "Boater Hat", "Top Hat", "Classic Fedora", "Pakol",
259
+ "Gilgiti Cap", "Baseball Cap", "Bucket Hat", "Snapback Cap", "Beret",
260
+ "Panama Hat", "Pork Pie Hat", "Sun Hat", "Chitrali Cap", "Trucker Hat"
261
+ ]
262
+ },
263
+ "Round": {
264
+ "Glasses": [
265
+ "Square Frames", "Browline Glasses", "Cat Eye Frames", "Round Frames", "Clear Frames",
266
+ "Wayfarer Glasses", "Geometric Frames", "Clubmaster Frames", "Rectangle Frames",
267
+ "Tortoiseshell Frames", "Metal Frame Glasses", "Oversized Glasses", "Aviator Glasses",
268
+ "Butterfly Frames", "Classic Aviators", "Transparent Frames", "Rimless Glasses",
269
+ "Oval Glasses", "Pilot Glasses", "Gradient Sunglasses"
270
+ ],
271
+ "Watches": [
272
+ "Bold Dial Watch", "Square Dial Watch", "Luxury Watch", "Minimalist Watch", "Chronograph Watch",
273
+ "Casio G-Shock", "Sveston Classic Watch", "Swiss Military Alpine", "Hanowa Smart Watch",
274
+ "Infinity Smart Watch", "Fossil Smart Watch", "Realme Watch S2", "Mibro T1 Smartwatch",
275
+ "Dress Watch", "Smart BT Calling Watch", "Casio Edifice", "Vogue Smart Watch",
276
+ "T500+ Smart Watch", "Skeleton Watch", "Retro Watch"
277
+ ],
278
+ "Hats": [
279
+ "Flat Cap", "Boater Hat", "Trilby", "Newsboy Cap", "Cowboy Hat",
280
+ "Wide-Brim Hat", "Safari Hat", "Classic Fedora", "Pakol", "Chitrali Cap",
281
+ "Snapback Cap", "Bucket Hat", "Top Hat", "Baseball Cap", "Panama Hat",
282
+ "Pork Pie Hat", "Sun Hat", "Beret", "Trucker Hat", "Gilgiti Cap"
283
+ ]
284
+ },
285
+ "Square": {
286
+ "Glasses": [
287
+ "Rimless Glasses", "Classic Aviators", "Cat Eye Frames", "Round Frames", "Clear Frames",
288
+ "Wayfarer Glasses", "Geometric Frames", "Clubmaster Frames", "Square Frames", "Tortoiseshell Glasses",
289
+ "Aviator Glasses", "Browline Glasses", "Transparent Frames", "Butterfly Frames",
290
+ "Rectangle Frames", "Pilot Glasses", "Metal Frame Glasses", "Oversized Frames",
291
+ "Oval Glasses", "Gradient Sunglasses"
292
+ ],
293
+ "Watches": [
294
+ "Skeleton Watch", "Retro Watch", "Luxury Watch", "Minimalist Watch", "Chronograph Watch",
295
+ "Dress Watch", "Casio Edifice", "Smart BT Calling Watch", "Infinity Smart Watch",
296
+ "Realme Watch S2", "Fossil Gen 6", "Mibro T1", "Swiss Military Alpine",
297
+ "Hanowa Puma Watch", "Casio G-Shock", "Redmi Watch 5", "Vogue Smart Watch",
298
+ "Bold Dial Watch", "Square Dial Watch", "Pilot Watch"
299
+ ],
300
+ "Hats": [
301
+ "Top Hat", "Classic Fedora", "Trilby", "Newsboy Cap", "Cowboy Hat",
302
+ "Flat Cap", "Safari Hat", "Boater Hat", "Snapback Cap", "Bucket Hat",
303
+ "Baseball Cap", "Panama Hat", "Pork Pie Hat", "Beret", "Sun Hat",
304
+ "Wide-Brim Hat", "Trucker Hat", "Chitrali Cap", "Pakol", "Gilgiti Cap"
305
+ ]
306
+ }
307
+ }
308
+
309
+ ##############################################################
310
+ # API ROUTES
311
+ ##############################################################
312
+
313
+ @app.route('/', methods=['GET'])
314
+ def home():
315
+ """Health check endpoint"""
316
+ return jsonify({
317
+ "message": "AI Fashion Recommendation API is running!",
318
+ "version": "1.0",
319
+ "endpoints": {
320
+ "image_recommendations": "/predict/image",
321
+ "text_recommendations": "/predict/text",
322
+ "face_shape_detection": "/detect/face-shape"
323
+ }
324
+ })
325
+
326
+ @app.route('/predict/image', methods=['POST'])
327
+ def predict_image_recommendations():
328
+ """Get fashion recommendations based on uploaded image"""
329
+ try:
330
+ # Check if image is provided
331
+ if 'image' not in request.files and 'image_base64' not in request.json:
332
+ return jsonify({"error": "No image provided"}), 400
333
+
334
+ # Get categories
335
+ categories = request.form.getlist('categories') if 'categories' in request.form else []
336
+
337
+ # If using JSON with base64 image
338
+ if request.is_json:
339
+ data = request.get_json()
340
+ categories = data.get('categories', [])
341
+
342
+ if 'image_base64' in data:
343
+ # Decode base64 image
344
+ image_data = base64.b64decode(data['image_base64'])
345
+ image = Image.open(io.BytesIO(image_data))
346
+ else:
347
+ return jsonify({"error": "No image provided"}), 400
348
+ else:
349
+ # Handle file upload
350
+ image_file = request.files['image']
351
+ image = Image.open(image_file.stream)
352
+
353
+ if not categories:
354
+ return jsonify({"error": "Please select at least one product category"}), 400
355
+
356
+ # Predict face shape
357
+ face_shape_result = predict_face_shape(image)
358
+
359
+ if "error" in face_shape_result:
360
+ face_shape = "Oval" # Default
361
+ face_shape_info = {
362
+ "face_shape": face_shape,
363
+ "confidence": 50.0,
364
+ "note": "Using default face shape due to detection error"
365
+ }
366
+ else:
367
+ face_shape = face_shape_result["face_shape"]
368
+ face_shape_info = face_shape_result
369
+
370
+ # Get recommendations
371
+ recommendations = {}
372
+ for category in categories:
373
+ face_rec = face_shape_recommendations.get(face_shape, {}).get(category, [])
374
+ recommendations[category] = face_rec[:5] if face_rec else []
375
+
376
+ return jsonify({
377
+ "face_shape_info": face_shape_info,
378
+ "recommendations": recommendations,
379
+ "categories": categories
380
+ })
381
+
382
+ except Exception as e:
383
+ return jsonify({"error": f"Internal server error: {str(e)}"}), 500
384
+
385
+ @app.route('/predict/text', methods=['POST'])
386
+ def predict_text_recommendations():
387
+ """Get fashion recommendations based on text attributes"""
388
+ try:
389
+ data = request.get_json()
390
+
391
+ gender = data.get('gender')
392
+ skin_tone = data.get('skin_tone')
393
+ age_group = data.get('age_group')
394
+ categories = data.get('categories', [])
395
+
396
+ if not categories:
397
+ return jsonify({"error": "Please select at least one product category"}), 400
398
+
399
+ # For text-based recommendations, use Oval as default face shape
400
+ recommendations = {}
401
+ for category in categories:
402
+ face_rec = face_shape_recommendations.get("Oval", {}).get(category, [])
403
+ recommendations[category] = face_rec[:5] if face_rec else []
404
+
405
+ return jsonify({
406
+ "user_attributes": {
407
+ "gender": gender,
408
+ "skin_tone": skin_tone,
409
+ "age_group": age_group
410
+ },
411
+ "recommendations": recommendations,
412
+ "categories": categories,
413
+ "note": "Recommendations based on general fashion trends"
414
+ })
415
+
416
+ except Exception as e:
417
+ return jsonify({"error": f"Internal server error: {str(e)}"}), 500
418
+
419
+ @app.route('/detect/face-shape', methods=['POST'])
420
+ def detect_face_shape_only():
421
+ """Detect face shape from uploaded image"""
422
+ try:
423
+ # Check if image is provided
424
+ if 'image' not in request.files and 'image_base64' not in request.json:
425
+ return jsonify({"error": "No image provided"}), 400
426
+
427
+ # Handle different input methods
428
+ if request.is_json:
429
+ data = request.get_json()
430
+ if 'image_base64' in data:
431
+ # Decode base64 image
432
+ image_data = base64.b64decode(data['image_base64'])
433
+ image = Image.open(io.BytesIO(image_data))
434
+ else:
435
+ return jsonify({"error": "No image provided"}), 400
436
+ else:
437
+ # Handle file upload
438
+ image_file = request.files['image']
439
+ image = Image.open(image_file.stream)
440
+
441
+ # Predict face shape
442
+ face_shape_result = predict_face_shape(image)
443
+
444
+ return jsonify(face_shape_result)
445
+
446
+ except Exception as e:
447
+ return jsonify({"error": f"Internal server error: {str(e)}"}), 500
448
+
449
+ @app.route('/categories', methods=['GET'])
450
+ def get_categories():
451
+ """Get available product categories"""
452
+ return jsonify({
453
+ "categories": ["Glasses", "Watches", "Hats"],
454
+ "face_shapes": face_shape_labels,
455
+ "gender_options": ["Male", "Female", "Kid", "Transgender"],
456
+ "skin_tone_options": ["Fair", "Medium", "Dark"],
457
+ "age_group_options": ["Child (0-12)", "Teen (13-19)", "Young Adult (20-35)", "Adult (36-50)", "Senior (51+)"]
458
+ })
459
+
460
+ ##############################################################
461
+ # MAIN EXECUTION
462
+ ##############################################################
463
+
464
+ if __name__ == '__main__':
465
+ # Load the face shape detection model on startup
466
+ print("Loading face shape detection model...")
467
+ load_face_shape_model()
468
+ print("API is ready!")
469
+
470
+ # Run the Flask app
471
+ app.run(host='0.0.0.0', port=5000, debug=True)