File size: 8,963 Bytes
19ac182
 
 
 
 
 
 
210ffdf
 
 
19ac182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210ffdf
 
 
 
19ac182
210ffdf
 
19ac182
210ffdf
 
 
 
 
 
19ac182
210ffdf
 
 
 
 
 
 
19ac182
210ffdf
 
 
19ac182
210ffdf
 
 
19ac182
210ffdf
 
19ac182
210ffdf
 
 
 
 
 
19ac182
210ffdf
 
 
19ac182
210ffdf
 
 
 
 
19ac182
210ffdf
 
 
 
 
19ac182
210ffdf
 
19ac182
210ffdf
 
 
 
 
 
 
 
 
 
19ac182
 
 
 
 
210ffdf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import os
import tempfile
import json
import requests
from io import BytesIO
from dotenv import load_dotenv
load_dotenv()

# Import deepface with error handling
try:
    from deepface import DeepFace
    import cv2
    import numpy as np
    DEEPFACE_AVAILABLE = True
except ImportError as e:
    DEEPFACE_AVAILABLE = False
    print(f"Warning: DeepFace not available. Please install dependencies: {e}")


def convert_to_serializable(obj):
    """Convert numpy types and other non-serializable types to native Python types"""
    if DEEPFACE_AVAILABLE:
        if isinstance(obj, np.integer):
            return int(obj)
        elif isinstance(obj, np.floating):
            return float(obj)
        elif isinstance(obj, np.ndarray):
            return obj.tolist()
    
    # Handle dict and list recursively
    if isinstance(obj, dict):
        return {key: convert_to_serializable(value) for key, value in obj.items()}
    elif isinstance(obj, list):
        return [convert_to_serializable(item) for item in obj]
    
    # Try to convert to float if it's a number-like object
    try:
        if hasattr(obj, 'item'):  # numpy scalar
            return obj.item()
    except (AttributeError, ValueError):
        pass
    
    return obj


app = FastAPI(title="Age, Emotion, and Gender Detection API", version="1.0.0")

# Add CORS middleware to allow requests from React app
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, replace with specific origins
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/")
async def root():
    return {
        "message": "Age, Emotion, and Gender Detection API",
        "endpoints": {
            "/analyze": "POST - Upload an image to detect age, emotion, and gender",
            "/skin-analysis": "POST - Upload an image for comprehensive skin analysis"
        }
    }


@app.post("/analyze")
async def analyze_image(file: UploadFile = File(...)):
    """
    Upload an image and get age, emotion, and gender detection results.
    
    Args:
        file: Image file to analyze (supports common image formats)
    
    Returns:
        JSON response with age, gender, and emotion information
    """
    # Check if DeepFace is available
    if not DEEPFACE_AVAILABLE:
        raise HTTPException(
            status_code=503,
            detail="DeepFace module not available. Please install dependencies: pip install -r requirements.txt"
        )
    
    # Validate file type
    if not file.content_type or not file.content_type.startswith('image/'):
        raise HTTPException(status_code=400, detail="File must be an image")
    
    # Create a temporary file to save the uploaded image
    tmp_file_path = None
    try:
        # Read file contents
        contents = await file.read()
        
        # Create temporary file and write contents
        with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp_file:
            tmp_file.write(contents)
            tmp_file_path = tmp_file.name
        
        # Analyze the image
        try:
            results = DeepFace.analyze(
                img_path=tmp_file_path,
                actions=['age', 'gender', 'emotion'],
                enforce_detection=False  # Continue even if face detection fails
            )
            
            # Handle both single result and list of results
            if isinstance(results, list):
                result = results[0]
            else:
                result = results
            
            # Extract age (convert to native Python int)
            age = int(result.get("age", 0))
            
            # Extract gender and convert numpy types
            gender_dict = result.get("gender", {})
            gender_dict = convert_to_serializable(gender_dict)
            if gender_dict:
                dominant_gender = max(gender_dict, key=gender_dict.get)
                gender_confidence = float(gender_dict[dominant_gender])
            else:
                dominant_gender = "Unknown"
                gender_confidence = 0.0
            
            # Extract emotion and convert numpy types
            emotion_dict = result.get("emotion", {})
            emotion_dict = convert_to_serializable(emotion_dict)
            if emotion_dict:
                dominant_emotion = max(emotion_dict, key=emotion_dict.get)
                emotion_confidence = float(emotion_dict[dominant_emotion])
            else:
                dominant_emotion = "Unknown"
                emotion_confidence = 0.0
            
            # Prepare response with all values converted to native Python types
            response = {
                "success": True,
                "age": age,
                "gender": {
                    "prediction": dominant_gender,
                    "confidence": round(gender_confidence, 2),
                    "all_predictions": gender_dict
                },
                "emotion": {
                    "prediction": dominant_emotion,
                    "confidence": round(emotion_confidence, 2),
                    "all_predictions": emotion_dict
                }
            }
            
            return JSONResponse(content=response)
            
        except Exception as e:
            raise HTTPException(
                status_code=500,
                detail=f"Error analyzing image: {str(e)}"
            )
    
    finally:
        # Clean up temporary file (close it first on Windows)
        if tmp_file_path and os.path.exists(tmp_file_path):
            try:
                # On Windows, we need to ensure the file is closed before deletion
                import time
                time.sleep(0.1)  # Small delay to ensure file is released
                os.unlink(tmp_file_path)
            except (PermissionError, OSError) as e:
                # If deletion fails, try to delete on next attempt or ignore
                # The OS will clean up temp files eventually
                pass


@app.post("/skin-analysis")
async def skin_analysis(file: UploadFile = File(...)):
    """
    Upload an image and get comprehensive skin analysis results.
    
    Args:
        file: Image file to analyze (supports common image formats)
    
    Returns:
        JSON response with detailed skin analysis information
    """
    # Validate file type
    if not file.content_type or not file.content_type.startswith('image/'):
        raise HTTPException(status_code=400, detail="File must be an image")
    
    # Get API key from environment variable
    api_key = os.getenv("RAPIDAPI_KEY", "")
    if not api_key:
        raise HTTPException(
            status_code=500,
            detail="RAPIDAPI_KEY environment variable is not set"
        )
    
    try:
        # Read file contents
        contents = await file.read()
        
        # Prepare request to RapidAPI
        url = "https://skin-analysis-api.p.rapidapi.com/predict"
        querystring = {"lang": "en"}
        
        # Use BytesIO to create a file-like object from the contents
        file_obj = BytesIO(contents)
        
        # Try "file" as field name first (common for RapidAPI endpoints)
        files = {"file": (file.filename or "image.jpg", file_obj, file.content_type)}
        headers = {
            "x-rapidapi-key": api_key,
            "x-rapidapi-host": "skin-analysis-api.p.rapidapi.com"
        }
        
        # Make request to RapidAPI
        # Note: When using files parameter, requests automatically sets Content-Type to multipart/form-data
        response = requests.post(url, files=files, headers=headers, params=querystring)
        
        # If "file" doesn't work, try "image" as fallback
        if response.status_code == 400 and "No file found" in response.text:
            file_obj.seek(0)  # Reset file pointer
            files = {"image": (file.filename or "image.jpg", file_obj, file.content_type)}
            response = requests.post(url, files=files, headers=headers, params=querystring)
        
        if response.status_code != 200:
            raise HTTPException(
                status_code=response.status_code,
                detail=f"External API error: {response.text}"
            )
        
        data = response.json()
        return JSONResponse(content=data)
        
    except requests.exceptions.RequestException as e:
        raise HTTPException(
            status_code=500,
            detail=f"Error calling external API: {str(e)}"
        )
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Error processing image: {str(e)}"
        )


@app.get("/health")
async def health_check():
    """Health check endpoint"""
    return {"status": "healthy"}