|
|
from flask import Flask, render_template, request, jsonify |
|
|
from werkzeug.utils import secure_filename |
|
|
import os, json, time |
|
|
|
|
|
APP_ROOT = os.path.dirname(os.path.abspath(__file__)) |
|
|
UPLOAD_DIR = os.path.join(APP_ROOT, 'uploads') |
|
|
os.makedirs(UPLOAD_DIR, exist_ok=True) |
|
|
|
|
|
app = Flask(__name__) |
|
|
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024 |
|
|
|
|
|
|
|
|
prompt_path = os.path.join(APP_ROOT, 'prompts', 'skin_prompt.txt') |
|
|
if os.path.exists(prompt_path): |
|
|
with open(prompt_path, 'r', encoding='utf-8') as f: |
|
|
SYSTEM_PROMPT = f.read() |
|
|
else: |
|
|
SYSTEM_PROMPT = "MISSING_PROMPT: please add prompts/skin_prompt.txt" |
|
|
|
|
|
|
|
|
schema_path = os.path.join(APP_ROOT, 'schema', 'output_schema.json') |
|
|
if os.path.exists(schema_path): |
|
|
with open(schema_path, 'r', encoding='utf-8') as f: |
|
|
OUTPUT_SCHEMA = json.load(f) |
|
|
else: |
|
|
OUTPUT_SCHEMA = {} |
|
|
|
|
|
def _stub_model_inference(image_paths, metadata: dict): |
|
|
""" |
|
|
Deterministic stub to simulate the model output that conforms to the schema. |
|
|
Replace this with your real model call (e.g., OpenAI, local vision model, etc.). |
|
|
""" |
|
|
num_images = len(image_paths) |
|
|
quality = 'good' if num_images and metadata.get('lighting', 'even') == 'even' else 'fair' |
|
|
|
|
|
result = { |
|
|
"image_quality": { |
|
|
"overall": quality, |
|
|
"issues": [] if quality == 'good' else ["lighting"], |
|
|
"retake_advice": "确保均匀光线、正面构图、去除滤镜与妆容。" |
|
|
}, |
|
|
"global_assessment": { |
|
|
"skin_type_estimate": metadata.get('self_report_skin_type', 'unknown'), |
|
|
"overall_risk_score": 42, |
|
|
"notes": "演示结果:请接入真实模型以获得更准确评估。" |
|
|
}, |
|
|
"region_scores": { |
|
|
"T_zone_oiliness": 58, |
|
|
"U_zone_dryness": 25, |
|
|
"cheek_redness": 18, |
|
|
"undereye_dark_circle": 40 |
|
|
}, |
|
|
"concerns": [ |
|
|
{"name": "acne", "severity": 35, "evidence": "T区可见散在闭口样阴影。", "confidence": 0.72}, |
|
|
{"name": "pores", "severity": 55, "evidence": "鼻翼与鼻梁两侧纹理增粗。", "confidence": 0.77} |
|
|
], |
|
|
"pigmentation": { |
|
|
"uneven_tone": 28, |
|
|
"spots": 10, |
|
|
"melasma_like": 0, |
|
|
"sun_damage_like": 22 |
|
|
}, |
|
|
"aging_signs": { |
|
|
"fine_lines": 20, |
|
|
"wrinkles": 8, |
|
|
"loss_of_elasticity": 12, |
|
|
"texture_roughness": 30 |
|
|
}, |
|
|
"sensitivity_inflammation": { |
|
|
"redness": 18, |
|
|
"visible_capillaries": 5, |
|
|
"irritation": 10 |
|
|
}, |
|
|
"top_priorities": [ |
|
|
{"name": "pores", "reason": "毛孔纹理明显,影响观感。"}, |
|
|
{"name": "oil_control", "reason": "T区油脂分泌偏旺。"}, |
|
|
{"name": "uneven_tone", "reason": "肤色轻度不匀影响通透感。"} |
|
|
], |
|
|
"daily_plan": { |
|
|
"am": [ |
|
|
"氨基酸洁面", |
|
|
"2-5%烟酰胺精华(避眼周)", |
|
|
"广谱防晒 SPF50+ PA++++" |
|
|
], |
|
|
"pm": [ |
|
|
"温和洁面", |
|
|
"0.5-2%水杨酸/杜鹃花酸按需点涂", |
|
|
"清爽型保湿乳" |
|
|
], |
|
|
"extras_weekly": [ |
|
|
"每周1-2次温和去角质(避免与酸叠加)" |
|
|
] |
|
|
}, |
|
|
"ingredient_recommendations": [ |
|
|
{ |
|
|
"goal": "oil_control", |
|
|
"actives": ["niacinamide 2-5%", "salicylic acid 0.5-2%"], |
|
|
"usage": "先低后高,观察耐受,白天注意防晒。", |
|
|
"cautions": "与强酸类/维A初期分开使用,避免刺激叠加。" |
|
|
} |
|
|
], |
|
|
"risk_flags": [], |
|
|
"follow_up": { |
|
|
"reshoot_tips": ["remove_makeup", "even_lighting", "front_facing", "no_filters"], |
|
|
"retake_interval_days": 28 |
|
|
} |
|
|
} |
|
|
|
|
|
return result |
|
|
|
|
|
@app.route('/') |
|
|
def index(): |
|
|
return render_template('index.html') |
|
|
|
|
|
@app.post('/api/analyze') |
|
|
def analyze(): |
|
|
files = request.files.getlist('images') |
|
|
metadata_json = request.form.get('metadata', '{}') |
|
|
try: |
|
|
metadata = json.loads(metadata_json) |
|
|
except Exception: |
|
|
return jsonify({"error": "metadata must be valid JSON"}), 400 |
|
|
|
|
|
saved = [] |
|
|
ts = int(time.time()) |
|
|
for f in files[:5]: |
|
|
if not f.filename: |
|
|
continue |
|
|
fname = f"{ts}_" + secure_filename(f.filename) |
|
|
path = os.path.join(UPLOAD_DIR, fname) |
|
|
f.save(path) |
|
|
saved.append(path) |
|
|
|
|
|
|
|
|
output = _stub_model_inference(saved, metadata) |
|
|
|
|
|
|
|
|
required_top_keys = [ |
|
|
"image_quality", "global_assessment", "region_scores", "concerns", |
|
|
"pigmentation", "aging_signs", "sensitivity_inflammation", |
|
|
"top_priorities", "daily_plan", "ingredient_recommendations", |
|
|
"risk_flags", "follow_up" |
|
|
] |
|
|
missing = [k for k in required_top_keys if k not in output] |
|
|
if missing: |
|
|
return jsonify({"error": f"missing keys: {missing}"}), 500 |
|
|
|
|
|
return jsonify(output) |
|
|
|
|
|
if __name__ == '__main__': |
|
|
app.run(host='0.0.0.0', port=5000, debug=True) |