File size: 7,613 Bytes
a9811c6
c9e71d3
a9811c6
c9e71d3
 
 
 
 
 
 
 
 
 
a9811c6
c9e71d3
a9811c6
 
c9e71d3
 
a9811c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9e71d3
 
 
 
 
 
 
a9811c6
c9e71d3
 
 
 
a9811c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9e71d3
 
 
 
 
 
a9811c6
 
 
 
 
c9e71d3
 
a9811c6
 
c9e71d3
a9811c6
 
 
 
 
c9e71d3
 
 
a9811c6
c9e71d3
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
import os
import io
import traceback
import gradio as gr
from PIL import Image, ImageDraw, ImageFont

from azure.ai.vision.face import FaceClient
from azure.ai.vision.face.models import (
    FaceDetectionModel,
    FaceRecognitionModel,
    FaceAttributeTypeDetection01,
)
from azure.core.credentials import AzureKeyCredential
from azure.core.exceptions import ClientAuthenticationError, HttpResponseError

# ✅ Hardcoded endpoint (you requested this)
HARDCODED_ENDPOINT = "https://face59137214.cognitiveservices.azure.com/"


def _normalize_endpoint(endpoint: str) -> str:
    endpoint = (endpoint or "").strip()
    if not endpoint.startswith("https://"):
        raise ValueError(f"Endpoint must start with https:// (got: {endpoint})")
    if not endpoint.endswith("/"):
        endpoint += "/"
    return endpoint


def _get_endpoint() -> str:
    # Allow Spaces Secret override if you want:
    # Settings → Variables and secrets → New secret → AI_SERVICE_ENDPOINT
    env_ep = os.getenv("AI_SERVICE_ENDPOINT", "").strip()
    endpoint = env_ep if env_ep else HARDCODED_ENDPOINT
    return _normalize_endpoint(endpoint)


def _get_key(ui_key: str) -> str:
    # Prefer UI key if provided; else use Spaces Secret AI_SERVICE_KEY
    key = (ui_key or "").strip()
    if not key:
        key = os.getenv("AI_SERVICE_KEY", "").strip()

    if not key or len(key) < 20:
        raise ValueError(
            "Missing AI_SERVICE_KEY.\n\n"
            "Fix:\n"
            "• Best: add a Hugging Face Space Secret named AI_SERVICE_KEY\n"
            "  (Settings → Variables and secrets → New secret)\n"
            "• Or paste the full key into the UI box."
        )
    return key


def _make_face_client(ui_key: str) -> FaceClient:
    endpoint = _get_endpoint()
    key = _get_key(ui_key)

    # ✅ Safe debug (no secret leakage)
    print(f"[DEBUG] endpoint={endpoint}")
    print(f"[DEBUG] key_len={len(key)} last4={key[-4:]}")

    return FaceClient(endpoint=endpoint, credential=AzureKeyCredential(key))


def _pil_to_bytes(img: Image.Image) -> bytes:
    if img.mode != "RGB":
        img = img.convert("RGB")
    buf = io.BytesIO()
    img.save(buf, format="JPEG", quality=95)
    return buf.getvalue()


def analyze_and_annotate(image: Image.Image, ui_key: str):
    if image is None:
        return "No image provided.", None

    try:
        face_client = _make_face_client(ui_key)

        # Same feature set as the lab
        features = [
            FaceAttributeTypeDetection01.HEAD_POSE,
            FaceAttributeTypeDetection01.OCCLUSION,
            FaceAttributeTypeDetection01.ACCESSORIES,
        ]

        img_bytes = _pil_to_bytes(image)

        detected_faces = face_client.detect(
            image_content=img_bytes,
            detection_model=FaceDetectionModel.DETECTION01,
            recognition_model=FaceRecognitionModel.RECOGNITION01,
            return_face_id=False,
            return_face_attributes=features,
        )

        if not detected_faces:
            return "0 faces detected.", image

        # Prepare annotation image
        annotated = image.copy()
        if annotated.mode != "RGB":
            annotated = annotated.convert("RGB")
        draw = ImageDraw.Draw(annotated)

        try:
            font = ImageFont.truetype("arial.ttf", 24)
        except Exception:
            font = ImageFont.load_default()

        lines = [f"{len(detected_faces)} faces detected.\n"]

        for idx, face in enumerate(detected_faces, start=1):
            r = face.face_rectangle
            left, top, right, bottom = r.left, r.top, r.left + r.width, r.top + r.height

            # Draw rectangle
            draw.rectangle([(left, top), (right, bottom)], outline="lime", width=5)

            # Draw label background + number
            label = str(idx)
            x0, y0, x1, y1 = draw.textbbox((0, 0), label, font=font)
            tw, th = x1 - x0, y1 - y0
            pad = 6
            label_bg = [(left, max(0, top - th - 2 * pad)), (left + tw + 2 * pad, top)]
            draw.rectangle(label_bg, fill="lime")
            draw.text((left + pad, label_bg[0][1] + pad), label, fill="black", font=font)

            # Extract attributes (guarded)
            attrs = face.face_attributes
            hp = getattr(attrs, "head_pose", None)
            occ = getattr(attrs, "occlusion", None)
            acc = getattr(attrs, "accessories", None)

            yaw = getattr(hp, "yaw", None) if hp else None
            pitch = getattr(hp, "pitch", None) if hp else None
            roll = getattr(hp, "roll", None) if hp else None

            forehead_occ = occ.get("foreheadOccluded") if isinstance(occ, dict) else None
            eye_occ = occ.get("eyeOccluded") if isinstance(occ, dict) else None
            mouth_occ = occ.get("mouthOccluded") if isinstance(occ, dict) else None

            accessories_list = []
            if acc:
                for a in acc:
                    accessories_list.append(str(a.type))

            # Output lines including bounding box numbers
            lines.append(f"Face number {idx}")
            lines.append(f" - Bounding box: left={left}, top={top}, width={r.width}, height={r.height}")
            lines.append(f" - Head Pose (Yaw): {yaw}")
            lines.append(f" - Head Pose (Pitch): {pitch}")
            lines.append(f" - Head Pose (Roll): {roll}")
            lines.append(f" - Forehead occluded?: {forehead_occ}")
            lines.append(f" - Eye occluded?: {eye_occ}")
            lines.append(f" - Mouth occluded?: {mouth_occ}")
            lines.append(" - Accessories:")
            if accessories_list:
                for a in accessories_list:
                    lines.append(f"   - {a}")
            else:
                lines.append("   - None")
            lines.append("")

        return "\n".join(lines).strip(), annotated

    except ClientAuthenticationError:
        # This is your 401 case
        return (
            "AUTH ERROR (401): Invalid key or wrong endpoint for this key.\n\n"
            "Fix checklist:\n"
            "1) In your Hugging Face Space: Settings → Variables and secrets → add secret AI_SERVICE_KEY\n"
            "2) Make sure the key belongs to the same Azure resource as the endpoint.\n"
            "3) Make sure the endpoint is the *resource endpoint* (regional) and ends with '/'.\n"
            "4) If the key was exposed, rotate/regenerate it in Azure.\n",
            image,
        )

    except HttpResponseError as e:
        return (f"Azure request failed:\n{str(e)}", image)

    except Exception as e:
        tb = traceback.format_exc()
        return (f"APP ERROR:\n{e}\n\nTraceback:\n{tb}", image)


demo = gr.Interface(
    fn=analyze_and_annotate,
    inputs=[
        gr.Image(type="pil", label="Drag & drop an image"),
        gr.Textbox(
            label="AI_SERVICE_KEY (paste here) — or set as Spaces Secret AI_SERVICE_KEY",
            type="password",
            placeholder="Paste your key here (recommended: use Spaces Secrets instead)…",
        ),
    ],
    outputs=[
        gr.Textbox(label="Face analysis (numbers + attributes + bounding boxes)", lines=18),
        gr.Image(type="pil", label="Annotated image (boxes + face numbers)"),
    ],
    title="Azure AI Face Detection (Drag & Drop)",
    description=(
        "If you get 401, your key and endpoint don’t match. "
        "Best practice on Spaces: set AI_SERVICE_KEY as a Secret."
    ),
)

if __name__ == "__main__":
    # On Spaces you can also do demo.launch(server_name="0.0.0.0", server_port=7860)
    demo.launch()