| 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) |