Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -27,7 +27,6 @@ models = {name: YOLO(path) for name, path in model_paths.items()}
|
|
| 27 |
mp_face_mesh = mp.solutions.face_mesh
|
| 28 |
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=True)
|
| 29 |
|
| 30 |
-
|
| 31 |
# -------------------- SKIN TYPE DETECTOR --------------------
|
| 32 |
def detect_skin_type_from_image(img):
|
| 33 |
img = cv2.resize(img, (400, 400))
|
|
@@ -77,7 +76,6 @@ def detect_skin_type_from_image(img):
|
|
| 77 |
|
| 78 |
return percentages, f"{final_type} ({final_value}%)"
|
| 79 |
|
| 80 |
-
|
| 81 |
# -------------------- HELPERS --------------------
|
| 82 |
def run_model(model_name, model, img, face_polygon, face_area):
|
| 83 |
if model_name == "pores2":
|
|
@@ -119,7 +117,6 @@ def run_model(model_name, model, img, face_polygon, face_area):
|
|
| 119 |
|
| 120 |
return skin_percentages
|
| 121 |
|
| 122 |
-
|
| 123 |
def normalize_and_merge(percentages):
|
| 124 |
normalized = {}
|
| 125 |
for cls_name, value in percentages.items():
|
|
@@ -142,98 +139,72 @@ def normalize_and_merge(percentages):
|
|
| 142 |
|
| 143 |
return normalized
|
| 144 |
|
| 145 |
-
|
| 146 |
# -------------------- ROUTES --------------------
|
| 147 |
@app.route("/", methods=["GET"])
|
| 148 |
def home():
|
| 149 |
return jsonify({
|
| 150 |
"message": "✅ Skin API is running",
|
| 151 |
-
"usage": "POST
|
| 152 |
})
|
| 153 |
|
| 154 |
-
|
| 155 |
@app.route("/analyze", methods=["POST"])
|
| 156 |
def analyze():
|
| 157 |
try:
|
| 158 |
files = request.files.getlist("file")
|
| 159 |
-
if not files or len(files) !=
|
| 160 |
return jsonify({
|
| 161 |
"success": False,
|
| 162 |
"analysis": [],
|
| 163 |
-
"error": "You must upload exactly
|
| 164 |
}), 400
|
| 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 |
-
skin_type_percentages, _ = detect_skin_type_from_image(img)
|
| 214 |
-
all_skin_types.append(skin_type_percentages)
|
| 215 |
-
|
| 216 |
-
# -------------------- AVERAGE RESULTS --------------------
|
| 217 |
-
avg_results = defaultdict(float)
|
| 218 |
-
for res in all_model_results:
|
| 219 |
-
for k, v in res.items():
|
| 220 |
-
avg_results[k] += v
|
| 221 |
-
for k in avg_results:
|
| 222 |
-
avg_results[k] = round(avg_results[k] / 3, 2)
|
| 223 |
-
|
| 224 |
-
# -------------------- AVERAGE SKIN TYPE --------------------
|
| 225 |
-
avg_skin_type_percentages = defaultdict(float)
|
| 226 |
-
for st in all_skin_types:
|
| 227 |
-
for k, v in st.items():
|
| 228 |
-
avg_skin_type_percentages[k] += v
|
| 229 |
-
for k in avg_skin_type_percentages:
|
| 230 |
-
avg_skin_type_percentages[k] = round(avg_skin_type_percentages[k] / 3, 2)
|
| 231 |
-
|
| 232 |
-
final_skin_type = max(avg_skin_type_percentages, key=avg_skin_type_percentages.get)
|
| 233 |
-
final_skin_type_str = f"{final_skin_type} ({avg_skin_type_percentages[final_skin_type]}%)"
|
| 234 |
-
|
| 235 |
-
# -------------------- FORMAT RESPONSE --------------------
|
| 236 |
-
analysis_list = [f"{cls_name.upper()}: {value}%" for cls_name, value in avg_results.items()]
|
| 237 |
analysis_list.append(f"SKIN TYPE: {final_skin_type_str}")
|
| 238 |
|
| 239 |
return jsonify({"success": True, "analysis": ["\n".join(analysis_list)]})
|
|
@@ -241,7 +212,6 @@ def analyze():
|
|
| 241 |
except Exception as e:
|
| 242 |
return jsonify({"success": False, "analysis": [], "error": str(e)}), 500
|
| 243 |
|
| 244 |
-
|
| 245 |
# -------------------- RUN --------------------
|
| 246 |
if __name__ == "__main__":
|
| 247 |
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 27 |
mp_face_mesh = mp.solutions.face_mesh
|
| 28 |
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=True)
|
| 29 |
|
|
|
|
| 30 |
# -------------------- SKIN TYPE DETECTOR --------------------
|
| 31 |
def detect_skin_type_from_image(img):
|
| 32 |
img = cv2.resize(img, (400, 400))
|
|
|
|
| 76 |
|
| 77 |
return percentages, f"{final_type} ({final_value}%)"
|
| 78 |
|
|
|
|
| 79 |
# -------------------- HELPERS --------------------
|
| 80 |
def run_model(model_name, model, img, face_polygon, face_area):
|
| 81 |
if model_name == "pores2":
|
|
|
|
| 117 |
|
| 118 |
return skin_percentages
|
| 119 |
|
|
|
|
| 120 |
def normalize_and_merge(percentages):
|
| 121 |
normalized = {}
|
| 122 |
for cls_name, value in percentages.items():
|
|
|
|
| 139 |
|
| 140 |
return normalized
|
| 141 |
|
|
|
|
| 142 |
# -------------------- ROUTES --------------------
|
| 143 |
@app.route("/", methods=["GET"])
|
| 144 |
def home():
|
| 145 |
return jsonify({
|
| 146 |
"message": "✅ Skin API is running",
|
| 147 |
+
"usage": "POST one image (form-data key 'file') to /analyze"
|
| 148 |
})
|
| 149 |
|
|
|
|
| 150 |
@app.route("/analyze", methods=["POST"])
|
| 151 |
def analyze():
|
| 152 |
try:
|
| 153 |
files = request.files.getlist("file")
|
| 154 |
+
if not files or len(files) != 1:
|
| 155 |
return jsonify({
|
| 156 |
"success": False,
|
| 157 |
"analysis": [],
|
| 158 |
+
"error": "You must upload exactly 1 image."
|
| 159 |
}), 400
|
| 160 |
|
| 161 |
+
file = files[0]
|
| 162 |
+
file_bytes = np.frombuffer(file.read(), np.uint8)
|
| 163 |
+
img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
|
| 164 |
+
if img is None:
|
| 165 |
+
return jsonify({"success": False, "analysis": [], "error": "Invalid image."}), 400
|
| 166 |
+
|
| 167 |
+
img_h, img_w = img.shape[:2]
|
| 168 |
+
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 169 |
+
result = face_mesh.process(rgb_img)
|
| 170 |
+
|
| 171 |
+
if not result.multi_face_landmarks:
|
| 172 |
+
return jsonify({"success": False, "analysis": [], "error": "No face detected."}), 400
|
| 173 |
+
|
| 174 |
+
# Face polygon
|
| 175 |
+
for landmarks in result.multi_face_landmarks:
|
| 176 |
+
points = np.array([[int(lm.x * img_w), int(lm.y * img_h)] for lm in landmarks.landmark])
|
| 177 |
+
hull = cv2.convexHull(points)
|
| 178 |
+
face_area = cv2.contourArea(hull)
|
| 179 |
+
face_polygon = Polygon(hull.reshape(-1, 2))
|
| 180 |
+
break
|
| 181 |
+
|
| 182 |
+
# Run YOLO models in parallel
|
| 183 |
+
combined_percentages = {}
|
| 184 |
+
with ThreadPoolExecutor() as executor:
|
| 185 |
+
futures = {
|
| 186 |
+
executor.submit(run_model, model_name, model, img, face_polygon, face_area): model_name
|
| 187 |
+
for model_name, model in models.items()
|
| 188 |
+
}
|
| 189 |
+
for future in as_completed(futures):
|
| 190 |
+
skin_percentages = future.result()
|
| 191 |
+
combined_percentages.update(skin_percentages)
|
| 192 |
+
|
| 193 |
+
final_percentages = normalize_and_merge(combined_percentages)
|
| 194 |
+
|
| 195 |
+
# Merge wrinkles + forehead
|
| 196 |
+
wrinkle_value = final_percentages.get("wrinkles", 0.0) + final_percentages.get("forehead", 0.0)
|
| 197 |
+
final_percentages["wrinkles"] = round(wrinkle_value, 2)
|
| 198 |
+
if "forehead" in final_percentages:
|
| 199 |
+
final_percentages.pop("forehead")
|
| 200 |
+
|
| 201 |
+
# Skin type
|
| 202 |
+
skin_type_percentages, _ = detect_skin_type_from_image(img)
|
| 203 |
+
final_skin_type = max(skin_type_percentages, key=skin_type_percentages.get)
|
| 204 |
+
final_skin_type_str = f"{final_skin_type} ({skin_type_percentages[final_skin_type]}%)"
|
| 205 |
+
|
| 206 |
+
# Format response
|
| 207 |
+
analysis_list = [f"{cls_name.upper()}: {value}%" for cls_name, value in final_percentages.items()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
analysis_list.append(f"SKIN TYPE: {final_skin_type_str}")
|
| 209 |
|
| 210 |
return jsonify({"success": True, "analysis": ["\n".join(analysis_list)]})
|
|
|
|
| 212 |
except Exception as e:
|
| 213 |
return jsonify({"success": False, "analysis": [], "error": str(e)}), 500
|
| 214 |
|
|
|
|
| 215 |
# -------------------- RUN --------------------
|
| 216 |
if __name__ == "__main__":
|
| 217 |
app.run(host="0.0.0.0", port=7860)
|