File size: 5,154 Bytes
31160e7
2ffe6e2
31160e7
 
 
 
2ffe6e2
 
 
 
31160e7
 
2ffe6e2
31160e7
 
 
 
 
 
2ffe6e2
 
 
 
 
 
31160e7
2ffe6e2
 
 
 
 
31160e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ffe6e2
31160e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ffe6e2
 
 
 
 
 
31160e7
 
 
 
 
 
2ffe6e2
31160e7
 
 
2ffe6e2
31160e7
 
 
 
 
 
 
 
 
2ffe6e2
31160e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ffe6e2
 
 
 
31160e7
 
 
 
 
 
2ffe6e2
 
31160e7
2ffe6e2
 
 
 
 
31160e7
2ffe6e2
 
 
 
 
31160e7
 
 
 
 
 
 
 
 
 
2ffe6e2
 
 
 
 
31160e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ffe6e2
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
import os
from typing import Optional, Dict, Any, List

from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from PIL import Image
from io import BytesIO

from transformers import pipeline

# =========================
# إعداد أسماء الموديلات (تقدر تغيّرها من Variables في السبيس لو حاب)
# =========================

DETECTOR_MODEL = os.getenv("DETECTOR_MODEL", "Tinny-Robot/acne")
SEVERITY_MODEL = os.getenv("SEVERITY_MODEL", "imfarzanansari/skintelligent-acne")
CONDITION_MODEL = os.getenv("CONDITION_MODEL", "Tanishq77/skin-condition-classifier")

# =========================
# تحميل الموديلات محلياً (مرّة وحدة عند تشغيل السبيس)
# =========================

print(f"Loading detector model: {DETECTOR_MODEL}")
detector = pipeline("object-detection", model=DETECTOR_MODEL)

print(f"Loading severity model: {SEVERITY_MODEL}")
severity_model = pipeline("image-classification", model=SEVERITY_MODEL)

print(f"Loading condition model: {CONDITION_MODEL}")
condition_model = pipeline("image-classification", model=CONDITION_MODEL)


# =========================
# Schemas
# =========================

class SeverityOut(BaseModel):
    raw: str
    label_ar: str
    score: float


class ConditionOut(BaseModel):
    label: str
    score: float


class AnalysisResponse(BaseModel):
    num_lesions: int
    severity: SeverityOut
    condition: ConditionOut
    meta: Dict[str, Any]


# =========================
# Utilities
# =========================

def map_severity_label_ar(label: str) -> str:
    m = {
        "clear": "صافية",
        "mild": "خفيفة",
        "moderate": "متوسطة",
        "severe": "شديدة",
    }
    return m.get(label.lower(), label)


def read_image_from_bytes(image_bytes: bytes) -> Image.Image:
    try:
        img = Image.open(BytesIO(image_bytes)).convert("RGB")
        return img
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Invalid image file: {e}")


# =========================
# FastAPI app
# =========================

app = FastAPI(title="Acne Orchestrator (Local Models)", version="0.2.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # عدّلها لو تبغى Origins معيّنة
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/")
async def root():
    return {
        "message": "Acne Orchestrator is running (local models).",
        "models": {
            "detector": DETECTOR_MODEL,
            "severity": SEVERITY_MODEL,
            "condition": CONDITION_MODEL,
        },
    }


@app.post("/analyze", response_model=AnalysisResponse)
async def analyze(
    file: UploadFile = File(...),
    age: Optional[int] = Form(None),
    skin_type: Optional[str] = Form(None),
    notes: Optional[str] = Form(None),
):
    """
    يستقبل صورة + معلومات بسيطة، ويستخدم 3 موديلات محلياً:
    - detector: يحدد عدد البثور (object detection)
    - severity_model: يقدّر شدة حب الشباب
    - condition_model: يحدّد نوع الحالة الجلدية العامة (acne, eczema, rosacea, ...)
    """

    image_bytes = await file.read()
    if not image_bytes:
        raise HTTPException(status_code=400, detail="Empty image file.")

    # قراءة الصورة كـ PIL
    image = read_image_from_bytes(image_bytes)

    # 1) كشف البثور
    det_results: List[Dict[str, Any]] = detector(image)
    # فلتره بسيطة للثقة
    det_results = [d for d in det_results if d.get("score", 0) > 0.3]
    num_lesions = len(det_results)

    # 2) شدة الحالة
    sev_preds: List[Dict[str, Any]] = severity_model(image)
    if not sev_preds:
        raise HTTPException(status_code=500, detail="Empty response from severity model.")
    sev_top = max(sev_preds, key=lambda x: x.get("score", 0))
    severity_raw = str(sev_top.get("label", "unknown"))
    severity_score = float(sev_top.get("score", 0.0))
    severity_ar = map_severity_label_ar(severity_raw)

    severity_obj = SeverityOut(
        raw=severity_raw,
        label_ar=severity_ar,
        score=severity_score,
    )

    # 3) نوع الحالة الجلدية
    cond_preds: List[Dict[str, Any]] = condition_model(image)
    if not cond_preds:
        raise HTTPException(status_code=500, detail="Empty response from condition model.")
    cond_top = max(cond_preds, key=lambda x: x.get("score", 0))
    condition_label = str(cond_top.get("label", "unknown"))
    condition_score = float(cond_top.get("score", 0.0))

    condition_obj = ConditionOut(
        label=condition_label,
        score=condition_score,
    )

    meta = {
        "age": age,
        "skin_type": skin_type,
        "notes": notes,
        "filename": file.filename,
        "content_type": file.content_type,
    }

    return AnalysisResponse(
        num_lesions=num_lesions,
        severity=severity_obj,
        condition=condition_obj,
        meta=meta,
    )