bhavinmatariya commited on
Commit
6bc6106
·
verified ·
1 Parent(s): 756e906

Upload 4 files

Browse files
Files changed (4) hide show
  1. .dockerignore +19 -0
  2. Dockerfile +33 -0
  3. app.py +299 -0
  4. requirements.txt +9 -0
.dockerignore ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ *.so
7
+ *.egg
8
+ *.egg-info
9
+ dist
10
+ build
11
+ .git
12
+ .gitignore
13
+ .env
14
+ .venv
15
+ venv/
16
+ *.md
17
+ !README.md
18
+ main.py
19
+
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y \
5
+ libgl1-mesa-glx \
6
+ libglib2.0-0 \
7
+ libsm6 \
8
+ libxext6 \
9
+ libxrender-dev \
10
+ libgomp1 \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Set working directory
14
+ WORKDIR /app
15
+
16
+ # Copy requirements first for better caching
17
+ COPY requirements.txt .
18
+
19
+ # Install Python dependencies
20
+ RUN pip install --no-cache-dir -r requirements.txt
21
+
22
+ # Copy application code
23
+ COPY app.py .
24
+
25
+ # Expose port (Hugging Face Spaces uses port 7860 by default)
26
+ EXPOSE 7860
27
+
28
+ # Set environment variables
29
+ ENV PYTHONUNBUFFERED=1
30
+
31
+ # Run the application
32
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
33
+
app.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from fastapi.responses import JSONResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import os
5
+ import tempfile
6
+ import json
7
+ import requests
8
+
9
+ # Import deepface with error handling
10
+ try:
11
+ from deepface import DeepFace
12
+ import cv2
13
+ import numpy as np
14
+ DEEPFACE_AVAILABLE = True
15
+ except ImportError as e:
16
+ DEEPFACE_AVAILABLE = False
17
+ print(f"Warning: DeepFace not available. Please install dependencies: {e}")
18
+
19
+
20
+ def convert_to_serializable(obj):
21
+ """Convert numpy types and other non-serializable types to native Python types"""
22
+ if DEEPFACE_AVAILABLE:
23
+ if isinstance(obj, np.integer):
24
+ return int(obj)
25
+ elif isinstance(obj, np.floating):
26
+ return float(obj)
27
+ elif isinstance(obj, np.ndarray):
28
+ return obj.tolist()
29
+
30
+ # Handle dict and list recursively
31
+ if isinstance(obj, dict):
32
+ return {key: convert_to_serializable(value) for key, value in obj.items()}
33
+ elif isinstance(obj, list):
34
+ return [convert_to_serializable(item) for item in obj]
35
+
36
+ # Try to convert to float if it's a number-like object
37
+ try:
38
+ if hasattr(obj, 'item'): # numpy scalar
39
+ return obj.item()
40
+ except (AttributeError, ValueError):
41
+ pass
42
+
43
+ return obj
44
+
45
+
46
+ app = FastAPI(title="Age, Emotion, and Gender Detection API", version="1.0.0")
47
+
48
+ # Add CORS middleware to allow requests from React app
49
+ app.add_middleware(
50
+ CORSMiddleware,
51
+ allow_origins=["*"], # In production, replace with specific origins
52
+ allow_credentials=True,
53
+ allow_methods=["*"],
54
+ allow_headers=["*"],
55
+ )
56
+
57
+
58
+ @app.get("/")
59
+ async def root():
60
+ return {
61
+ "message": "Age, Emotion, and Gender Detection API",
62
+ "endpoints": {
63
+ "/analyze": "POST - Upload an image to detect age, emotion, and gender",
64
+ "/skin-analysis": "POST - Upload an image for comprehensive skin analysis"
65
+ }
66
+ }
67
+
68
+
69
+ @app.post("/analyze")
70
+ async def analyze_image(file: UploadFile = File(...)):
71
+ """
72
+ Upload an image and get age, emotion, and gender detection results.
73
+
74
+ Args:
75
+ file: Image file to analyze (supports common image formats)
76
+
77
+ Returns:
78
+ JSON response with age, gender, and emotion information
79
+ """
80
+ # Check if DeepFace is available
81
+ if not DEEPFACE_AVAILABLE:
82
+ raise HTTPException(
83
+ status_code=503,
84
+ detail="DeepFace module not available. Please install dependencies: pip install -r requirements.txt"
85
+ )
86
+
87
+ # Validate file type
88
+ if not file.content_type or not file.content_type.startswith('image/'):
89
+ raise HTTPException(status_code=400, detail="File must be an image")
90
+
91
+ # Create a temporary file to save the uploaded image
92
+ tmp_file_path = None
93
+ try:
94
+ # Read file contents
95
+ contents = await file.read()
96
+
97
+ # Create temporary file and write contents
98
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp_file:
99
+ tmp_file.write(contents)
100
+ tmp_file_path = tmp_file.name
101
+
102
+ # Analyze the image
103
+ try:
104
+ results = DeepFace.analyze(
105
+ img_path=tmp_file_path,
106
+ actions=['age', 'gender', 'emotion'],
107
+ enforce_detection=False # Continue even if face detection fails
108
+ )
109
+
110
+ # Handle both single result and list of results
111
+ if isinstance(results, list):
112
+ result = results[0]
113
+ else:
114
+ result = results
115
+
116
+ # Extract age (convert to native Python int)
117
+ age = int(result.get("age", 0))
118
+
119
+ # Extract gender and convert numpy types
120
+ gender_dict = result.get("gender", {})
121
+ gender_dict = convert_to_serializable(gender_dict)
122
+ if gender_dict:
123
+ dominant_gender = max(gender_dict, key=gender_dict.get)
124
+ gender_confidence = float(gender_dict[dominant_gender])
125
+ else:
126
+ dominant_gender = "Unknown"
127
+ gender_confidence = 0.0
128
+
129
+ # Extract emotion and convert numpy types
130
+ emotion_dict = result.get("emotion", {})
131
+ emotion_dict = convert_to_serializable(emotion_dict)
132
+ if emotion_dict:
133
+ dominant_emotion = max(emotion_dict, key=emotion_dict.get)
134
+ emotion_confidence = float(emotion_dict[dominant_emotion])
135
+ else:
136
+ dominant_emotion = "Unknown"
137
+ emotion_confidence = 0.0
138
+
139
+ # Prepare response with all values converted to native Python types
140
+ response = {
141
+ "success": True,
142
+ "age": age,
143
+ "gender": {
144
+ "prediction": dominant_gender,
145
+ "confidence": round(gender_confidence, 2),
146
+ "all_predictions": gender_dict
147
+ },
148
+ "emotion": {
149
+ "prediction": dominant_emotion,
150
+ "confidence": round(emotion_confidence, 2),
151
+ "all_predictions": emotion_dict
152
+ }
153
+ }
154
+
155
+ return JSONResponse(content=response)
156
+
157
+ except Exception as e:
158
+ raise HTTPException(
159
+ status_code=500,
160
+ detail=f"Error analyzing image: {str(e)}"
161
+ )
162
+
163
+ finally:
164
+ # Clean up temporary file (close it first on Windows)
165
+ if tmp_file_path and os.path.exists(tmp_file_path):
166
+ try:
167
+ # On Windows, we need to ensure the file is closed before deletion
168
+ import time
169
+ time.sleep(0.1) # Small delay to ensure file is released
170
+ os.unlink(tmp_file_path)
171
+ except (PermissionError, OSError) as e:
172
+ # If deletion fails, try to delete on next attempt or ignore
173
+ # The OS will clean up temp files eventually
174
+ pass
175
+
176
+
177
+ @app.post("/skin-analysis")
178
+ async def skin_analysis(file: UploadFile = File(...)):
179
+ """
180
+ Upload an image and get comprehensive skin analysis results.
181
+
182
+ Args:
183
+ file: Image file to analyze (supports common image formats)
184
+
185
+ Returns:
186
+ JSON response with detailed skin analysis information
187
+ """
188
+ # Validate file type
189
+ if not file.content_type or not file.content_type.startswith('image/'):
190
+ raise HTTPException(status_code=400, detail="File must be an image")
191
+
192
+ # Get API key from environment variable
193
+ api_key = os.getenv("AILABAPI_API_KEY", "")
194
+
195
+ # Create a temporary file to save the uploaded image
196
+ tmp_file_path = None
197
+ try:
198
+ # Read file contents
199
+ contents = await file.read()
200
+
201
+ # Create temporary file and write contents
202
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp_file:
203
+ tmp_file.write(contents)
204
+ tmp_file_path = tmp_file.name
205
+
206
+ # Prepare request to ailabapi
207
+ url = "https://www.ailabapi.com/api/portrait/analysis/skin-analysis"
208
+
209
+ # Open the temporary file for the request
210
+ with open(tmp_file_path, 'rb') as image_file:
211
+ files = {"image": (file.filename or "image.jpg", image_file, file.content_type)}
212
+ headers = {"ailabapi-api-key": api_key}
213
+
214
+ # Make request to ailabapi
215
+ response = requests.post(url, files=files, headers=headers)
216
+
217
+ if response.status_code != 200:
218
+ raise HTTPException(
219
+ status_code=response.status_code,
220
+ detail=f"External API error: {response.text}"
221
+ )
222
+
223
+ data = response.json()
224
+
225
+ # Mapping dictionaries
226
+ yes_no_mapping = {0: "No", 1: "Yes"}
227
+ eyelids_mapping = {
228
+ 0: "Single eyelids",
229
+ 1: "Parallel Double Eyelids",
230
+ 2: "Scalloped Double Eyelids"
231
+ }
232
+ skin_type_mapping = {
233
+ 0: "Oily skin",
234
+ 1: "Dry skin",
235
+ 2: "Neutral skin",
236
+ 3: "Combination skin"
237
+ }
238
+
239
+ # Fields that use Yes/No mapping
240
+ yes_no_fields = [
241
+ "pores_left_cheek", "nasolabial_fold", "eye_pouch", "forehead_wrinkle",
242
+ "skin_spot", "acne", "pores_forehead", "pores_jaw", "eye_finelines",
243
+ "dark_circle", "crows_feet", "pores_right_cheek", "blackhead",
244
+ "glabella_wrinkle", "mole"
245
+ ]
246
+
247
+ # Transform the result data
248
+ if "result" in data:
249
+ result = data["result"]
250
+
251
+ # Transform Yes/No fields
252
+ for field in yes_no_fields:
253
+ if field in result and "value" in result[field]:
254
+ result[field]["value_label"] = yes_no_mapping.get(result[field]["value"], "Unknown")
255
+
256
+ # Transform eyelid fields
257
+ if "left_eyelids" in result and "value" in result["left_eyelids"]:
258
+ result["left_eyelids"]["value_label"] = eyelids_mapping.get(result["left_eyelids"]["value"], "Unknown")
259
+
260
+ if "right_eyelids" in result and "value" in result["right_eyelids"]:
261
+ result["right_eyelids"]["value_label"] = eyelids_mapping.get(result["right_eyelids"]["value"], "Unknown")
262
+
263
+ # Transform skin_type
264
+ if "skin_type" in result:
265
+ if "skin_type" in result["skin_type"]:
266
+ result["skin_type"]["skin_type_label"] = skin_type_mapping.get(result["skin_type"]["skin_type"], "Unknown")
267
+ if "details" in result["skin_type"]:
268
+ for detail in result["skin_type"]["details"]:
269
+ if "value" in detail:
270
+ detail["value_label"] = skin_type_mapping.get(detail["value"], "Unknown")
271
+
272
+ return JSONResponse(content=data)
273
+
274
+ except requests.exceptions.RequestException as e:
275
+ raise HTTPException(
276
+ status_code=500,
277
+ detail=f"Error calling external API: {str(e)}"
278
+ )
279
+ except Exception as e:
280
+ raise HTTPException(
281
+ status_code=500,
282
+ detail=f"Error processing image: {str(e)}"
283
+ )
284
+ finally:
285
+ # Clean up temporary file
286
+ if tmp_file_path and os.path.exists(tmp_file_path):
287
+ try:
288
+ import time
289
+ time.sleep(0.1)
290
+ os.unlink(tmp_file_path)
291
+ except (PermissionError, OSError):
292
+ pass
293
+
294
+
295
+ @app.get("/health")
296
+ async def health_check():
297
+ """Health check endpoint"""
298
+ return {"status": "healthy"}
299
+
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
4
+ deepface
5
+ opencv-python
6
+ numpy
7
+ tensorflow-cpu
8
+ Pillow
9
+ requests