eaglelandsonce commited on
Commit
a9811c6
·
verified ·
1 Parent(s): ad54177

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -103
app.py CHANGED
@@ -1,4 +1,6 @@
 
1
  import io
 
2
  import gradio as gr
3
  from PIL import Image, ImageDraw, ImageFont
4
 
@@ -9,12 +11,58 @@ from azure.ai.vision.face.models import (
9
  FaceAttributeTypeDetection01,
10
  )
11
  from azure.core.credentials import AzureKeyCredential
 
12
 
13
- # ✅ Hardcoded endpoint (safe to keep in code)
14
- AI_SERVICE_ENDPOINT = "https://face59137214.cognitiveservices.azure.com/"
15
 
16
 
17
- def pil_to_bytes(img: Image.Image) -> bytes:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  if img.mode != "RGB":
19
  img = img.convert("RGB")
20
  buf = io.BytesIO()
@@ -22,120 +70,141 @@ def pil_to_bytes(img: Image.Image) -> bytes:
22
  return buf.getvalue()
23
 
24
 
25
- def analyze_and_annotate(image: Image.Image, api_key: str):
26
  if image is None:
27
  return "No image provided.", None
28
 
29
- if not api_key or not api_key.strip():
30
- return "Please paste your AI_SERVICE_KEY in the box.", image
31
-
32
- # Create Face client using hardcoded endpoint + user-pasted key
33
- face_client = FaceClient(
34
- endpoint=AI_SERVICE_ENDPOINT,
35
- credential=AzureKeyCredential(api_key.strip())
36
- )
37
-
38
- # Facial features to retrieve (same as the lab)
39
- features = [
40
- FaceAttributeTypeDetection01.HEAD_POSE,
41
- FaceAttributeTypeDetection01.OCCLUSION,
42
- FaceAttributeTypeDetection01.ACCESSORIES,
43
- ]
44
-
45
- img_bytes = pil_to_bytes(image)
46
-
47
- detected_faces = face_client.detect(
48
- image_content=img_bytes,
49
- detection_model=FaceDetectionModel.DETECTION01,
50
- recognition_model=FaceRecognitionModel.RECOGNITION01,
51
- return_face_id=False,
52
- return_face_attributes=features,
53
- )
54
-
55
- if not detected_faces:
56
- return "0 faces detected.", image
57
-
58
- # Prepare annotation image
59
- annotated = image.copy()
60
- if annotated.mode != "RGB":
61
- annotated = annotated.convert("RGB")
62
- draw = ImageDraw.Draw(annotated)
63
-
64
- # Font (best-effort)
65
  try:
66
- font = ImageFont.truetype("arial.ttf", 24)
67
- except Exception:
68
- font = ImageFont.load_default()
69
-
70
- lines = [f"{len(detected_faces)} faces detected.\n"]
71
-
72
- for idx, face in enumerate(detected_faces, start=1):
73
- r = face.face_rectangle
74
- left, top, right, bottom = r.left, r.top, r.left + r.width, r.top + r.height
75
-
76
- # Bounding box + face number label
77
- draw.rectangle([(left, top), (right, bottom)], outline="lime", width=5)
78
-
79
- label = str(idx)
80
- x0, y0, x1, y1 = draw.textbbox((0, 0), label, font=font)
81
- tw, th = x1 - x0, y1 - y0
82
- pad = 6
83
- label_bg = [(left, max(0, top - th - 2 * pad)), (left + tw + 2 * pad, top)]
84
- draw.rectangle(label_bg, fill="lime")
85
- draw.text((left + pad, label_bg[0][1] + pad), label, fill="black", font=font)
86
-
87
- # Extract attributes
88
- attrs = face.face_attributes
89
- hp = getattr(attrs, "head_pose", None)
90
- occ = getattr(attrs, "occlusion", None)
91
- acc = getattr(attrs, "accessories", None)
92
-
93
- yaw = getattr(hp, "yaw", None) if hp else None
94
- pitch = getattr(hp, "pitch", None) if hp else None
95
- roll = getattr(hp, "roll", None) if hp else None
96
-
97
- forehead_occ = occ.get("foreheadOccluded") if isinstance(occ, dict) else None
98
- eye_occ = occ.get("eyeOccluded") if isinstance(occ, dict) else None
99
- mouth_occ = occ.get("mouthOccluded") if isinstance(occ, dict) else None
100
-
101
- accessories_list = []
102
- if acc:
103
- for a in acc:
104
- accessories_list.append(str(a.type))
105
-
106
- # Report
107
- lines.append(f"Face number {idx}")
108
- lines.append(f" - Bounding box: left={left}, top={top}, width={r.width}, height={r.height}")
109
- lines.append(f" - Head Pose (Yaw): {yaw}")
110
- lines.append(f" - Head Pose (Pitch): {pitch}")
111
- lines.append(f" - Head Pose (Roll): {roll}")
112
- lines.append(f" - Forehead occluded?: {forehead_occ}")
113
- lines.append(f" - Eye occluded?: {eye_occ}")
114
- lines.append(f" - Mouth occluded?: {mouth_occ}")
115
- lines.append(" - Accessories:")
116
- if accessories_list:
117
- for a in accessories_list:
118
- lines.append(f" - {a}")
119
- else:
120
- lines.append(" - None")
121
- lines.append("")
122
-
123
- return "\n".join(lines).strip(), annotated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
 
126
  demo = gr.Interface(
127
  fn=analyze_and_annotate,
128
  inputs=[
129
  gr.Image(type="pil", label="Drag & drop an image"),
130
- gr.Textbox(label="AI_SERVICE_KEY (paste here)", type="password", placeholder="Paste your key..."),
 
 
 
 
131
  ],
132
  outputs=[
133
- gr.Textbox(label="Face analysis (numbers + attributes)", lines=18),
134
- gr.Image(type="pil", label="Annotated image (boxes + numbers)"),
135
  ],
136
- title="Azure AI Face Analysis (Drag & Drop)",
137
- description=f"Endpoint is hardcoded to: {AI_SERVICE_ENDPOINT}",
 
 
 
138
  )
139
 
140
  if __name__ == "__main__":
 
141
  demo.launch()
 
1
+ import os
2
  import io
3
+ import traceback
4
  import gradio as gr
5
  from PIL import Image, ImageDraw, ImageFont
6
 
 
11
  FaceAttributeTypeDetection01,
12
  )
13
  from azure.core.credentials import AzureKeyCredential
14
+ from azure.core.exceptions import ClientAuthenticationError, HttpResponseError
15
 
16
+ # ✅ Hardcoded endpoint (you requested this)
17
+ HARDCODED_ENDPOINT = "https://face59137214.cognitiveservices.azure.com/"
18
 
19
 
20
+ def _normalize_endpoint(endpoint: str) -> str:
21
+ endpoint = (endpoint or "").strip()
22
+ if not endpoint.startswith("https://"):
23
+ raise ValueError(f"Endpoint must start with https:// (got: {endpoint})")
24
+ if not endpoint.endswith("/"):
25
+ endpoint += "/"
26
+ return endpoint
27
+
28
+
29
+ def _get_endpoint() -> str:
30
+ # Allow Spaces Secret override if you want:
31
+ # Settings → Variables and secrets → New secret → AI_SERVICE_ENDPOINT
32
+ env_ep = os.getenv("AI_SERVICE_ENDPOINT", "").strip()
33
+ endpoint = env_ep if env_ep else HARDCODED_ENDPOINT
34
+ return _normalize_endpoint(endpoint)
35
+
36
+
37
+ def _get_key(ui_key: str) -> str:
38
+ # Prefer UI key if provided; else use Spaces Secret AI_SERVICE_KEY
39
+ key = (ui_key or "").strip()
40
+ if not key:
41
+ key = os.getenv("AI_SERVICE_KEY", "").strip()
42
+
43
+ if not key or len(key) < 20:
44
+ raise ValueError(
45
+ "Missing AI_SERVICE_KEY.\n\n"
46
+ "Fix:\n"
47
+ "• Best: add a Hugging Face Space Secret named AI_SERVICE_KEY\n"
48
+ " (Settings → Variables and secrets → New secret)\n"
49
+ "• Or paste the full key into the UI box."
50
+ )
51
+ return key
52
+
53
+
54
+ def _make_face_client(ui_key: str) -> FaceClient:
55
+ endpoint = _get_endpoint()
56
+ key = _get_key(ui_key)
57
+
58
+ # ✅ Safe debug (no secret leakage)
59
+ print(f"[DEBUG] endpoint={endpoint}")
60
+ print(f"[DEBUG] key_len={len(key)} last4={key[-4:]}")
61
+
62
+ return FaceClient(endpoint=endpoint, credential=AzureKeyCredential(key))
63
+
64
+
65
+ def _pil_to_bytes(img: Image.Image) -> bytes:
66
  if img.mode != "RGB":
67
  img = img.convert("RGB")
68
  buf = io.BytesIO()
 
70
  return buf.getvalue()
71
 
72
 
73
+ def analyze_and_annotate(image: Image.Image, ui_key: str):
74
  if image is None:
75
  return "No image provided.", None
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  try:
78
+ face_client = _make_face_client(ui_key)
79
+
80
+ # Same feature set as the lab
81
+ features = [
82
+ FaceAttributeTypeDetection01.HEAD_POSE,
83
+ FaceAttributeTypeDetection01.OCCLUSION,
84
+ FaceAttributeTypeDetection01.ACCESSORIES,
85
+ ]
86
+
87
+ img_bytes = _pil_to_bytes(image)
88
+
89
+ detected_faces = face_client.detect(
90
+ image_content=img_bytes,
91
+ detection_model=FaceDetectionModel.DETECTION01,
92
+ recognition_model=FaceRecognitionModel.RECOGNITION01,
93
+ return_face_id=False,
94
+ return_face_attributes=features,
95
+ )
96
+
97
+ if not detected_faces:
98
+ return "0 faces detected.", image
99
+
100
+ # Prepare annotation image
101
+ annotated = image.copy()
102
+ if annotated.mode != "RGB":
103
+ annotated = annotated.convert("RGB")
104
+ draw = ImageDraw.Draw(annotated)
105
+
106
+ try:
107
+ font = ImageFont.truetype("arial.ttf", 24)
108
+ except Exception:
109
+ font = ImageFont.load_default()
110
+
111
+ lines = [f"{len(detected_faces)} faces detected.\n"]
112
+
113
+ for idx, face in enumerate(detected_faces, start=1):
114
+ r = face.face_rectangle
115
+ left, top, right, bottom = r.left, r.top, r.left + r.width, r.top + r.height
116
+
117
+ # Draw rectangle
118
+ draw.rectangle([(left, top), (right, bottom)], outline="lime", width=5)
119
+
120
+ # Draw label background + number
121
+ label = str(idx)
122
+ x0, y0, x1, y1 = draw.textbbox((0, 0), label, font=font)
123
+ tw, th = x1 - x0, y1 - y0
124
+ pad = 6
125
+ label_bg = [(left, max(0, top - th - 2 * pad)), (left + tw + 2 * pad, top)]
126
+ draw.rectangle(label_bg, fill="lime")
127
+ draw.text((left + pad, label_bg[0][1] + pad), label, fill="black", font=font)
128
+
129
+ # Extract attributes (guarded)
130
+ attrs = face.face_attributes
131
+ hp = getattr(attrs, "head_pose", None)
132
+ occ = getattr(attrs, "occlusion", None)
133
+ acc = getattr(attrs, "accessories", None)
134
+
135
+ yaw = getattr(hp, "yaw", None) if hp else None
136
+ pitch = getattr(hp, "pitch", None) if hp else None
137
+ roll = getattr(hp, "roll", None) if hp else None
138
+
139
+ forehead_occ = occ.get("foreheadOccluded") if isinstance(occ, dict) else None
140
+ eye_occ = occ.get("eyeOccluded") if isinstance(occ, dict) else None
141
+ mouth_occ = occ.get("mouthOccluded") if isinstance(occ, dict) else None
142
+
143
+ accessories_list = []
144
+ if acc:
145
+ for a in acc:
146
+ accessories_list.append(str(a.type))
147
+
148
+ # Output lines including bounding box numbers
149
+ lines.append(f"Face number {idx}")
150
+ lines.append(f" - Bounding box: left={left}, top={top}, width={r.width}, height={r.height}")
151
+ lines.append(f" - Head Pose (Yaw): {yaw}")
152
+ lines.append(f" - Head Pose (Pitch): {pitch}")
153
+ lines.append(f" - Head Pose (Roll): {roll}")
154
+ lines.append(f" - Forehead occluded?: {forehead_occ}")
155
+ lines.append(f" - Eye occluded?: {eye_occ}")
156
+ lines.append(f" - Mouth occluded?: {mouth_occ}")
157
+ lines.append(" - Accessories:")
158
+ if accessories_list:
159
+ for a in accessories_list:
160
+ lines.append(f" - {a}")
161
+ else:
162
+ lines.append(" - None")
163
+ lines.append("")
164
+
165
+ return "\n".join(lines).strip(), annotated
166
+
167
+ except ClientAuthenticationError:
168
+ # This is your 401 case
169
+ return (
170
+ "AUTH ERROR (401): Invalid key or wrong endpoint for this key.\n\n"
171
+ "Fix checklist:\n"
172
+ "1) In your Hugging Face Space: Settings → Variables and secrets → add secret AI_SERVICE_KEY\n"
173
+ "2) Make sure the key belongs to the same Azure resource as the endpoint.\n"
174
+ "3) Make sure the endpoint is the *resource endpoint* (regional) and ends with '/'.\n"
175
+ "4) If the key was exposed, rotate/regenerate it in Azure.\n",
176
+ image,
177
+ )
178
+
179
+ except HttpResponseError as e:
180
+ return (f"Azure request failed:\n{str(e)}", image)
181
+
182
+ except Exception as e:
183
+ tb = traceback.format_exc()
184
+ return (f"APP ERROR:\n{e}\n\nTraceback:\n{tb}", image)
185
 
186
 
187
  demo = gr.Interface(
188
  fn=analyze_and_annotate,
189
  inputs=[
190
  gr.Image(type="pil", label="Drag & drop an image"),
191
+ gr.Textbox(
192
+ label="AI_SERVICE_KEY (paste here) — or set as Spaces Secret AI_SERVICE_KEY",
193
+ type="password",
194
+ placeholder="Paste your key here (recommended: use Spaces Secrets instead)…",
195
+ ),
196
  ],
197
  outputs=[
198
+ gr.Textbox(label="Face analysis (numbers + attributes + bounding boxes)", lines=18),
199
+ gr.Image(type="pil", label="Annotated image (boxes + face numbers)"),
200
  ],
201
+ title="Azure AI Face Detection (Drag & Drop)",
202
+ description=(
203
+ "If you get 401, your key and endpoint don’t match. "
204
+ "Best practice on Spaces: set AI_SERVICE_KEY as a Secret."
205
+ ),
206
  )
207
 
208
  if __name__ == "__main__":
209
+ # On Spaces you can also do demo.launch(server_name="0.0.0.0", server_port=7860)
210
  demo.launch()