Unmeshraj commited on
Commit
634f5f4
·
1 Parent(s): c827b4a

Add application file

Browse files
Files changed (3) hide show
  1. Dockerfile +35 -0
  2. app.py +119 -0
  3. requirements.txt +15 -0
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a slim Python image
2
+ FROM python:3.11-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV PORT=7860
7
+ ENV HOME=/home/user
8
+
9
+ # Install system dependencies as root
10
+ RUN apt-get update && apt-get install -y \
11
+ libgl1 \
12
+ libglib2.0-0 \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Create a non-root user
16
+ RUN useradd -m -u 1000 user
17
+ USER user
18
+ ENV PATH="/home/user/.local/bin:$PATH"
19
+
20
+ # Set working directory
21
+ WORKDIR /app
22
+
23
+ # Copy requirements and install
24
+ COPY --chown=user requirements.txt .
25
+ RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
26
+ pip install --no-cache-dir -r requirements.txt
27
+
28
+ # Copy the application code
29
+ COPY --chown=user . .
30
+
31
+ # Expose the port (Hugging Face default is 7860)
32
+ EXPOSE 7860
33
+
34
+ # Run the application with Gunicorn
35
+ CMD ["sh", "-c", "gunicorn -b 0.0.0.0:$PORT --timeout 120 app:app"]
app.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+
4
+ import cv2 # type:ignore
5
+ import numpy as np # type:ignore
6
+ from deepface import DeepFace # type:ignore
7
+ from dotenv import load_dotenv # type:ignore
8
+ from flask import Flask, jsonify, request # type:ignore
9
+ from flask_cors import CORS # type:ignore
10
+ from supabase import create_client # type:ignore
11
+
12
+ load_dotenv()
13
+
14
+ SUPABASE_URL = os.getenv("SUPABASE_URL") or os.getenv("NEXT_PUBLIC_SUPABASE_URL")
15
+ SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY") or os.getenv("NEXT_PUBLIC_SUPABASE_SERVICE_KEY")
16
+
17
+ FACE_MODEL = "Facenet512"
18
+ MATCH_THRESHOLD = 22
19
+
20
+ supabase = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
21
+
22
+ app = Flask(__name__)
23
+ CORS(app)
24
+
25
+ def base64_to_image(base64_str: str):
26
+ """Convert base64 image to OpenCV BGR image"""
27
+ try:
28
+ img_bytes = base64.b64decode(base64_str.split(",")[1])
29
+ np_arr = np.frombuffer(img_bytes, np.uint8)
30
+ img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
31
+ return img
32
+ except Exception:
33
+ raise ValueError("Invalid base64 image")
34
+
35
+ def get_embedding(img_path):
36
+ result = DeepFace.represent(
37
+ img_path=img_path,
38
+ model_name=FACE_MODEL,
39
+ enforce_detection=True
40
+ )
41
+ return result[0]["embedding"]
42
+
43
+
44
+ def extract_single_embedding(img):
45
+ """Extract exactly ONE face embedding"""
46
+ result = DeepFace.represent(
47
+ img_path=img,
48
+ model_name=FACE_MODEL,
49
+ enforce_detection=True
50
+ )
51
+
52
+ if not result or len(result) != 1:
53
+ raise ValueError("Exactly one face must be visible")
54
+
55
+ return np.array(result[0]["embedding"], dtype=np.float32)
56
+
57
+ @app.route("/enroll", methods=["POST"])
58
+ def enroll_face():
59
+ data = request.get_json(force=True)
60
+ user_id = data.get("userId")
61
+ image_base64 = data.get("image")
62
+
63
+ if not user_id or not image_base64:
64
+ return jsonify({"error": "userId and image required"}), 400
65
+
66
+ try:
67
+ img = base64_to_image(image_base64)
68
+ embedding = extract_single_embedding(img)
69
+
70
+ supabase.table("user_profiles").update({
71
+ "face_embedding": embedding.tolist()
72
+ }).eq("id", user_id).execute()
73
+
74
+ return jsonify({
75
+ "success": True,
76
+ "message": "Face enrolled successfully"
77
+ })
78
+
79
+ except Exception as e:
80
+ return jsonify({"error": str(e)}), 500
81
+
82
+ @app.route("/verify-face-by-qr", methods=["POST"])
83
+ def verify_face_by_qr():
84
+ data = request.get_json(force=True)
85
+ user_id = data.get("user_id")
86
+ image_base64 = data.get("image")
87
+
88
+ try:
89
+ if not user_id or not image_base64:
90
+ return jsonify({"error": "user_id and image required"}), 400
91
+
92
+ res = supabase.table("user_profiles") \
93
+ .select("face_embedding") \
94
+ .eq("id", user_id) \
95
+ .execute()
96
+
97
+ if not res.data:
98
+ return jsonify({"error": "User not found"}), 404
99
+
100
+ stored_embedding = res.data[0].get("face_embedding")
101
+
102
+ if not stored_embedding:
103
+ return jsonify({"error": "Face not enrolled"}), 404
104
+
105
+ stored_embedding = np.array(stored_embedding, dtype=np.float32)
106
+ live_embedding = np.array(get_embedding(image_base64))
107
+ distance = np.linalg.norm(stored_embedding - live_embedding)
108
+ is_match = distance < MATCH_THRESHOLD
109
+ return jsonify({
110
+ "success": True,
111
+ "match": bool(is_match),
112
+ "distance": float(distance)
113
+ })
114
+ except Exception as e:
115
+ print(e)
116
+ return jsonify({"error": str(e)}), 500
117
+ if __name__ == "__main__":
118
+ port = int(os.environ.get("PORT", 5000))
119
+ app.run(host="0.0.0.0", port=port)
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask==3.0.0
2
+ Flask-CORS>=4.0.1
3
+ deepface>=0.0.96
4
+ tf-keras
5
+ fastapi==0.95.2
6
+ uvicorn[standard]==0.22.0
7
+ opencv-python-headless==4.8.1.78
8
+ numpy<2.0.0
9
+ Pillow>=10.0.1
10
+ gunicorn==21.2.0
11
+ supabase==2.13.0
12
+ httpx==0.27.2
13
+ python-dotenv==1.0.0
14
+ setuptools
15
+ wheel