-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_server.py
More file actions
116 lines (86 loc) · 3.7 KB
/
api_server.py
File metadata and controls
116 lines (86 loc) · 3.7 KB
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
"""
Flask API + embedded web UI (``web/index.html``) connected to the Keras model.
Run:
python api_server.py
Your default browser opens to the UI automatically (disable with ``--no-browser``).
The HTML lives inside this package at ``web/index.html`` — do not rely on ``file://``;
browsers block ``fetch()`` from local files to the API.
"""
from __future__ import annotations
import argparse
import sys
import threading
import time
import webbrowser
from pathlib import Path
from flask import Flask, jsonify, request, send_from_directory
# Inference (TensorFlow + OpenCV) is imported inside ``api_predict`` so importing this
# module stays light for tests and ``flask run`` startup.
PACKAGE_DIR = Path(__file__).resolve().parent
WEB_DIR = PACKAGE_DIR / "web"
WEB_INDEX = WEB_DIR / "index.html"
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 8 * 1024 * 1024 # 8 MB max upload
@app.get("/")
def index():
"""Serve the MaskGuard landing page from ``web/index.html``."""
if not WEB_INDEX.is_file():
return (
f"<pre>Missing UI file: {WEB_INDEX}\n"
"Restore web/index.html in the project root.</pre>",
404,
)
return send_from_directory(WEB_DIR, "index.html")
@app.get("/health")
def health():
"""Lightweight check for demos / debugging."""
model_ok = (PACKAGE_DIR / "model" / "mask_detector.h5").is_file()
return jsonify({"status": "ok", "model_file_present": model_ok, "ui_html_present": WEB_INDEX.is_file()})
@app.post("/api/predict")
def api_predict():
"""
Accept a webcam frame or upload as ``multipart/form-data`` field ``image`` (JPEG/PNG).
Returns JSON: ``{ ok, width, height, faces: [{ x1,y1,x2,y2, label, confidence, ... }] }``
"""
if "image" not in request.files:
return jsonify({"ok": False, "error": "Missing form field 'image'.", "faces": []}), 400
file_storage = request.files["image"]
if not file_storage or not file_storage.filename:
return jsonify({"ok": False, "error": "Empty upload.", "faces": []}), 400
raw = file_storage.read()
if not raw:
return jsonify({"ok": False, "error": "Empty image body.", "faces": []}), 400
# Decode with OpenCV only — invalid JPEGs never load TensorFlow
from image_codec import decode_upload_bytes
bgr = decode_upload_bytes(raw)
if bgr is None:
return jsonify({"ok": False, "error": "Could not decode image bytes.", "faces": []}), 400
from inference_backend import predict_bgr
result = predict_bgr(bgr)
status = 200 if result.get("ok") else 503
return jsonify(result), status
def main() -> int:
"""Start Flask; optionally open the system browser to the packaged UI."""
parser = argparse.ArgumentParser(description="MaskGuard web UI + /api/predict")
parser.add_argument("--port", type=int, default=5000, help="HTTP port (default: 5000)")
parser.add_argument(
"--no-browser",
action="store_true",
help="Do not auto-open a browser window (servers/SSH)",
)
args = parser.parse_args()
if not WEB_INDEX.is_file():
print(f"[WARN] UI not found at {WEB_INDEX}", file=sys.stderr)
url = f"http://127.0.0.1:{args.port}/"
def open_browser() -> None:
time.sleep(0.9)
webbrowser.open(url)
if not args.no_browser:
threading.Thread(target=open_browser, daemon=True).start()
print(f"MaskGuard — UI + API at {url}")
print(" (Embedded UI: web/index.html — use this server; file:// cannot call /api/predict.)")
# threaded=True so quick successive frame posts do not block each other badly
app.run(host="127.0.0.1", port=args.port, debug=False, threaded=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())