Commit ·
d02862c
1
Parent(s): ba3f67e
Fixed API error
Browse files
api.py
CHANGED
|
@@ -20,7 +20,7 @@ def _load_image(image):
|
|
| 20 |
return image
|
| 21 |
|
| 22 |
|
| 23 |
-
def check_liveness(image)
|
| 24 |
img = _load_image(image)
|
| 25 |
if img is None:
|
| 26 |
return {"error": "No image provided"}
|
|
@@ -43,3 +43,5 @@ def check_liveness(image) -> dict:
|
|
| 43 |
return {"error": "Request timed out"}
|
| 44 |
except requests.exceptions.RequestException as e:
|
| 45 |
return {"error": f"API request failed: {str(e)}"}
|
|
|
|
|
|
|
|
|
| 20 |
return image
|
| 21 |
|
| 22 |
|
| 23 |
+
def check_liveness(image):
|
| 24 |
img = _load_image(image)
|
| 25 |
if img is None:
|
| 26 |
return {"error": "No image provided"}
|
|
|
|
| 43 |
return {"error": "Request timed out"}
|
| 44 |
except requests.exceptions.RequestException as e:
|
| 45 |
return {"error": f"API request failed: {str(e)}"}
|
| 46 |
+
except ValueError as e:
|
| 47 |
+
return {"error": f"Invalid response from server (not JSON): {str(e)}"}
|
ui.py
CHANGED
|
@@ -3,38 +3,74 @@ from api import check_liveness
|
|
| 3 |
|
| 4 |
EXAMPLE_IMAGES = [[f"assets/{i}.jpg"] for i in range(1, 4)]
|
| 5 |
|
|
|
|
| 6 |
|
| 7 |
-
def _format_result_html(result: dict) -> str:
|
| 8 |
-
if not result or "error" in result:
|
| 9 |
-
return ""
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
if isinstance(score, str):
|
| 15 |
-
try:
|
| 16 |
-
score = float(score)
|
| 17 |
-
except (ValueError, TypeError):
|
| 18 |
-
score = 0
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
if is_genuine:
|
| 21 |
badge_text = "✅ GENUINE / LIVE"
|
| 22 |
badge_color = "#059669"
|
| 23 |
bg_color = "#ecfdf5"
|
| 24 |
border_color = "#a7f3d0"
|
| 25 |
else:
|
| 26 |
-
badge_text = "❌ SPOOF / FAKE"
|
| 27 |
badge_color = "#dc2626"
|
| 28 |
bg_color = "#fef2f2"
|
| 29 |
border_color = "#fecaca"
|
| 30 |
|
| 31 |
-
detail_rows = "".join(
|
| 32 |
-
f'<tr><td style="padding:6px 12px;font-weight:600;color:#64748b;'
|
| 33 |
-
f'border-bottom:1px solid #f1f5f9;">{k}</td>'
|
| 34 |
-
f'<td style="padding:6px 12px;color:#334155;border-bottom:1px solid #f1f5f9;">{v}</td></tr>'
|
| 35 |
-
for k, v in result.items()
|
| 36 |
-
)
|
| 37 |
-
|
| 38 |
return f"""
|
| 39 |
<div style="padding:12px 0;">
|
| 40 |
<div style="background:{bg_color};border:2px solid {border_color};
|
|
@@ -49,7 +85,16 @@ def _format_result_html(result: dict) -> str:
|
|
| 49 |
</div>
|
| 50 |
<div style="margin-top:16px;background:white;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;">
|
| 51 |
<table style="width:100%;border-collapse:collapse;">
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
</table>
|
| 54 |
</div>
|
| 55 |
</div>
|
|
@@ -66,7 +111,7 @@ def process_image(image):
|
|
| 66 |
)
|
| 67 |
|
| 68 |
result = check_liveness(image)
|
| 69 |
-
if "error" in result:
|
| 70 |
return (
|
| 71 |
f'<div style="padding:20px;text-align:center;color:#dc2626;">{result["error"]}</div>',
|
| 72 |
result,
|
|
|
|
| 3 |
|
| 4 |
EXAMPLE_IMAGES = [[f"assets/{i}.jpg"] for i in range(1, 4)]
|
| 5 |
|
| 6 |
+
THRESHOLD = 0.5
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
def _normalize(d: dict) -> dict:
|
| 10 |
+
return {k.strip().replace(" ", "_"): v for k, v in d.items()}
|
| 11 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
def _format_result_html(results: list) -> str:
|
| 14 |
+
if not results or (isinstance(results, dict) and "error" in results):
|
| 15 |
+
return ""
|
| 16 |
+
|
| 17 |
+
if isinstance(results, dict):
|
| 18 |
+
results = [results]
|
| 19 |
+
|
| 20 |
+
max_prob = 0
|
| 21 |
+
max_attack = ""
|
| 22 |
+
rows = ""
|
| 23 |
+
for item in results:
|
| 24 |
+
r = _normalize(item)
|
| 25 |
+
attack = r.get("attack_method", "Unknown")
|
| 26 |
+
prob = r.get("liveness_probability", 0)
|
| 27 |
+
score = r.get("liveness_score", 0)
|
| 28 |
+
quality = r.get("quality_score", 0)
|
| 29 |
+
state = r.get("state", "")
|
| 30 |
+
|
| 31 |
+
if isinstance(prob, str):
|
| 32 |
+
try:
|
| 33 |
+
prob = float(prob)
|
| 34 |
+
except (ValueError, TypeError):
|
| 35 |
+
prob = 0
|
| 36 |
+
|
| 37 |
+
if prob > max_prob:
|
| 38 |
+
max_prob = prob
|
| 39 |
+
max_attack = attack
|
| 40 |
+
|
| 41 |
+
pct = round(max(0, min(prob, 1)) * 100)
|
| 42 |
+
bar_color = "#059669" if prob < THRESHOLD else "#dc2626"
|
| 43 |
+
|
| 44 |
+
rows += f"""
|
| 45 |
+
<tr>
|
| 46 |
+
<td style="padding:8px 12px;font-weight:600;color:#334155;border-bottom:1px solid #f1f5f9;">
|
| 47 |
+
{attack}
|
| 48 |
+
</td>
|
| 49 |
+
<td style="padding:8px 12px;border-bottom:1px solid #f1f5f9;">
|
| 50 |
+
<div style="display:flex;align-items:center;gap:8px;">
|
| 51 |
+
<div style="flex:1;background:#e2e8f0;border-radius:999px;height:8px;overflow:hidden;">
|
| 52 |
+
<div style="width:{pct}%;height:100%;background:{bar_color};border-radius:999px;"></div>
|
| 53 |
+
</div>
|
| 54 |
+
<span style="font-weight:700;color:{bar_color};min-width:40px;text-align:right;">{pct}%</span>
|
| 55 |
+
</div>
|
| 56 |
+
</td>
|
| 57 |
+
<td style="padding:8px 12px;color:#64748b;border-bottom:1px solid #f1f5f9;text-align:center;">
|
| 58 |
+
{state}
|
| 59 |
+
</td>
|
| 60 |
+
</tr>"""
|
| 61 |
+
|
| 62 |
+
is_genuine = max_prob < THRESHOLD
|
| 63 |
if is_genuine:
|
| 64 |
badge_text = "✅ GENUINE / LIVE"
|
| 65 |
badge_color = "#059669"
|
| 66 |
bg_color = "#ecfdf5"
|
| 67 |
border_color = "#a7f3d0"
|
| 68 |
else:
|
| 69 |
+
badge_text = f"❌ SPOOF / FAKE — {max_attack}"
|
| 70 |
badge_color = "#dc2626"
|
| 71 |
bg_color = "#fef2f2"
|
| 72 |
border_color = "#fecaca"
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
return f"""
|
| 75 |
<div style="padding:12px 0;">
|
| 76 |
<div style="background:{bg_color};border:2px solid {border_color};
|
|
|
|
| 85 |
</div>
|
| 86 |
<div style="margin-top:16px;background:white;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;">
|
| 87 |
<table style="width:100%;border-collapse:collapse;">
|
| 88 |
+
<thead>
|
| 89 |
+
<tr style="background:#f8fafc;">
|
| 90 |
+
<th style="padding:8px 12px;text-align:left;font-weight:600;color:#64748b;">Attack Method</th>
|
| 91 |
+
<th style="padding:8px 12px;text-align:left;font-weight:600;color:#64748b;">Probability</th>
|
| 92 |
+
<th style="padding:8px 12px;text-align:center;font-weight:600;color:#64748b;">Status</th>
|
| 93 |
+
</tr>
|
| 94 |
+
</thead>
|
| 95 |
+
<tbody>
|
| 96 |
+
{rows}
|
| 97 |
+
</tbody>
|
| 98 |
</table>
|
| 99 |
</div>
|
| 100 |
</div>
|
|
|
|
| 111 |
)
|
| 112 |
|
| 113 |
result = check_liveness(image)
|
| 114 |
+
if isinstance(result, dict) and "error" in result:
|
| 115 |
return (
|
| 116 |
f'<div style="padding:20px;text-align:center;color:#dc2626;">{result["error"]}</div>',
|
| 117 |
result,
|