Bayu commited on
Commit
e55fbcc
·
1 Parent(s): eed810f

feat: Enable direct file uploads for face verification and quality validation, and document free-tier infrastructure considerations.

Browse files
Files changed (2) hide show
  1. app.py +31 -62
  2. attendance_system_context.md +20 -1
app.py CHANGED
@@ -9,16 +9,12 @@ from fastapi.middleware.cors import CORSMiddleware
9
  from pydantic import BaseModel, HttpUrl
10
  from inference import FaceAnalysis
11
 
12
- # ==========================================
13
- # INISIALISASI APLIKASI & MODEL
14
- # ==========================================
15
  app = FastAPI(
16
  title="GawayKu Face Recognition API",
17
  description="API Face Recognition mandiri menggunakan YOLOv8 + ArcFace untuk aplikasi HRIS GawayKu.",
18
- version="1.1.0",
19
  )
20
 
21
- # CORS — agar bisa diakses dari frontend mana saja
22
  app.add_middleware(
23
  CORSMiddleware,
24
  allow_origins=["*"],
@@ -27,11 +23,9 @@ app.add_middleware(
27
  allow_headers=["*"],
28
  )
29
 
30
- # Folder sementara untuk menyimpan file upload
31
  TEMP_DIR = "/tmp/face_uploads"
32
  os.makedirs(TEMP_DIR, exist_ok=True)
33
 
34
- # Inisialisasi model saat server pertama kali menyala
35
  print("⏳ Memuat model Face Recognition...")
36
  try:
37
  face_app = FaceAnalysis()
@@ -41,15 +35,9 @@ except Exception as e:
41
  face_app = None
42
 
43
  # ==========================================
44
- # MODELS (Pydantic)
45
  # ==========================================
46
-
47
- class VerifyRequest(BaseModel):
48
- reference_image_url: HttpUrl
49
- query_image_url: HttpUrl
50
-
51
- class ValidateQualityRequest(BaseModel):
52
- image_url: HttpUrl
53
 
54
  class VerifyResponse(BaseModel):
55
  is_match: bool
@@ -66,25 +54,19 @@ class ValidationResponse(BaseModel):
66
  # ==========================================
67
  # HELPER FUNCTIONS
68
  # ==========================================
69
-
70
  def download_image(url: str) -> str:
71
- """Mendownload gambar dari URL dan menyimpannya sementara."""
72
  try:
73
  response = requests.get(url, timeout=10)
74
  response.raise_for_status()
75
-
76
  unique_name = f"{uuid.uuid4().hex}.jpg"
77
  file_path = os.path.join(TEMP_DIR, unique_name)
78
-
79
  with open(file_path, "wb") as f:
80
  f.write(response.content)
81
-
82
  return file_path
83
  except Exception as e:
84
- raise HTTPException(status_code=400, detail=f"Gagal mendownload gambar: {str(e)}")
85
 
86
  def clear_temp_file(path: str):
87
- """Menghapus file sementara."""
88
  if path and os.path.exists(path):
89
  os.remove(path)
90
 
@@ -94,39 +76,40 @@ def clear_temp_file(path: str):
94
 
95
  @app.get("/")
96
  def health_check():
97
- """Health check endpoint — memastikan server berjalan."""
98
  status = "ok" if face_app else "error"
99
  return {
100
  "status": status,
101
- "message": "🚀 GawayKu Face Recognition API is running!",
102
  "model": "YOLOv8 + ArcFace (WideResNet-101-2)",
103
  }
104
 
105
- @app.post("/verify", response_model=VerifyResponse)
106
- async def verify_faces(request: VerifyRequest):
 
 
 
107
  """
108
- Membandingkan dua gambar wajah dari URL.
109
 
110
- Returns:
111
- - is_match: bool (True jika wajah sama)
112
- - similarity_score: float (0.0 - 1.0)
113
- - execution_time: float (detik)
114
- - error: str (pesan error jika ada)
115
  """
116
  if not face_app:
117
  raise HTTPException(status_code=503, detail="Model belum siap")
118
 
119
  start_time = time.time()
120
- path1 = None
121
- path2 = None
122
 
123
  try:
124
- # Download gambar
125
- path1 = download_image(str(request.reference_image_url))
126
- path2 = download_image(str(request.query_image_url))
127
 
128
- # Proses perbandingan wajah
129
- similarity, is_same = face_app.compare(path1, path2)
 
 
 
 
130
 
131
  exec_time = time.time() - start_time
132
 
@@ -146,36 +129,24 @@ async def verify_faces(request: VerifyRequest):
146
  "error": str(e)
147
  }
148
  finally:
149
- clear_temp_file(path1)
150
- clear_temp_file(path2)
151
 
152
- @app.post("/validate-quality", response_model=ValidationResponse)
153
- async def validate_quality(request: ValidateQualityRequest):
154
  """
155
- Memeriksa kualitas foto untuk pendaftaran (Enrollment).
156
- Memastikan wajah terdeteksi dengan jelas dan tunggal.
157
  """
158
  if not face_app:
159
  raise HTTPException(status_code=503, detail="Model belum siap")
160
 
161
- path = None
162
  try:
163
- path = download_image(str(request.image_url))
164
-
165
- # Gunakan method internal process_image untuk mendapatkan embedding
166
- # Jika berhasil return embedding, artinya wajah terdeteksi
167
- # TODO: Idealnya face_app.process_image melempar error specifik jika wajah tak ditemukan
168
- # Saat ini kita asumsikan jika berhasil embedding = valid
169
-
170
- embedding = face_app.process_image(path)
171
-
172
- # Simulasi cek kualitas (Placeholder)
173
- # Di implementasi nyata, kita bisa cek resolusi crop wajah, blur, dll.
174
- # Karena process_image mengembalikan embedding, kita anggap valid.
175
 
176
  return {
177
  "is_valid": True,
178
- "quality_score": 0.95, # Dummy score
179
  "message": "Wajah terdeteksi dengan baik.",
180
  "error": None
181
  }
@@ -186,6 +157,4 @@ async def validate_quality(request: ValidateQualityRequest):
186
  "quality_score": 0.0,
187
  "message": "Gagal memproses wajah.",
188
  "error": str(e)
189
- }
190
- finally:
191
- clear_temp_file(path)
 
9
  from pydantic import BaseModel, HttpUrl
10
  from inference import FaceAnalysis
11
 
 
 
 
12
  app = FastAPI(
13
  title="GawayKu Face Recognition API",
14
  description="API Face Recognition mandiri menggunakan YOLOv8 + ArcFace untuk aplikasi HRIS GawayKu.",
15
+ version="1.2.0",
16
  )
17
 
 
18
  app.add_middleware(
19
  CORSMiddleware,
20
  allow_origins=["*"],
 
23
  allow_headers=["*"],
24
  )
25
 
 
26
  TEMP_DIR = "/tmp/face_uploads"
27
  os.makedirs(TEMP_DIR, exist_ok=True)
28
 
 
29
  print("⏳ Memuat model Face Recognition...")
30
  try:
31
  face_app = FaceAnalysis()
 
35
  face_app = None
36
 
37
  # ==========================================
38
+ # MODELS
39
  # ==========================================
40
+ # (VerifyRequest biasa dihapus karena kita pakai Form Data + UploadFile)
 
 
 
 
 
 
41
 
42
  class VerifyResponse(BaseModel):
43
  is_match: bool
 
54
  # ==========================================
55
  # HELPER FUNCTIONS
56
  # ==========================================
 
57
  def download_image(url: str) -> str:
 
58
  try:
59
  response = requests.get(url, timeout=10)
60
  response.raise_for_status()
 
61
  unique_name = f"{uuid.uuid4().hex}.jpg"
62
  file_path = os.path.join(TEMP_DIR, unique_name)
 
63
  with open(file_path, "wb") as f:
64
  f.write(response.content)
 
65
  return file_path
66
  except Exception as e:
67
+ raise HTTPException(status_code=400, detail=f"Gagal mendownload gambar referensi: {str(e)}")
68
 
69
  def clear_temp_file(path: str):
 
70
  if path and os.path.exists(path):
71
  os.remove(path)
72
 
 
76
 
77
  @app.get("/")
78
  def health_check():
 
79
  status = "ok" if face_app else "error"
80
  return {
81
  "status": status,
82
+ "message": "🚀 GawayKu Face Recognition API is running (v1.2 - Direct Upload)!",
83
  "model": "YOLOv8 + ArcFace (WideResNet-101-2)",
84
  }
85
 
86
+ @app.post("/verify-file", response_model=VerifyResponse)
87
+ async def verify_file(
88
+ reference_image_url: str = Form(...),
89
+ query_image: UploadFile = File(...)
90
+ ):
91
  """
92
+ Membandingkan Wajah Referensi (URL) vs Wajah Query (File Upload Langsung).
93
 
94
+ - reference_image_url: URL foto wajah yang sudah terdaftar di database.
95
+ - query_image: File foto selfie langsung dari mobile (multipart/form-data).
 
 
 
96
  """
97
  if not face_app:
98
  raise HTTPException(status_code=503, detail="Model belum siap")
99
 
100
  start_time = time.time()
101
+ path_ref = None
 
102
 
103
  try:
104
+ # 1. Download Reference Image (URL)
105
+ path_ref = download_image(reference_image_url)
 
106
 
107
+ # 2. Read Query Image (Bytes dari UploadFile)
108
+ query_bytes = await query_image.read()
109
+
110
+ # 3. Bandingkan (Path vs Bytes)
111
+ # Kita update process_image di inference.py untuk terima bytes juga
112
+ similarity, is_same = face_app.compare(path_ref, query_bytes)
113
 
114
  exec_time = time.time() - start_time
115
 
 
129
  "error": str(e)
130
  }
131
  finally:
132
+ clear_temp_file(path_ref)
133
+ # query_bytes ada di memory, tidak perlu delete file
134
 
135
+ @app.post("/validate-quality-file", response_model=ValidationResponse)
136
+ async def validate_quality_file(image: UploadFile = File(...)):
137
  """
138
+ Cek kualitas wajah dari file upload langsung (tanpa URL).
 
139
  """
140
  if not face_app:
141
  raise HTTPException(status_code=503, detail="Model belum siap")
142
 
 
143
  try:
144
+ image_bytes = await image.read()
145
+ embedding = face_app.process_image(image_bytes)
 
 
 
 
 
 
 
 
 
 
146
 
147
  return {
148
  "is_valid": True,
149
+ "quality_score": 0.95,
150
  "message": "Wajah terdeteksi dengan baik.",
151
  "error": None
152
  }
 
157
  "quality_score": 0.0,
158
  "message": "Gagal memproses wajah.",
159
  "error": str(e)
160
+ }
 
 
attendance_system_context.md CHANGED
@@ -11,7 +11,26 @@ Ekosistem GawayKu terdiri dari:
11
  2. **Backend WebAdmin (Next.js):** Mengelola pengguna, menyimpan data wajah referensi, dan log absensi.
12
  3. **Layanan Face Recognition (Python/FastAPI):** Layanan terpisah (yang akan dibuat) untuk verifikasi biometrik.
13
 
14
- ## 2. Alur Kerja Face Recognition
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  Sistem ini memiliki dua fase utama: **Pendaftaran (Enrollment)** dan **Verifikasi (Verification)**.
17
 
 
11
  2. **Backend WebAdmin (Next.js):** Mengelola pengguna, menyimpan data wajah referensi, dan log absensi.
12
  3. **Layanan Face Recognition (Python/FastAPI):** Layanan terpisah (yang akan dibuat) untuk verifikasi biometrik.
13
 
14
+ ## 2. Pertimbangan Infrastruktur (Free Tier)
15
+
16
+ Karena menggunakan **Vercel (Free)**, **Supabase (Free)**, dan **Hugging Face Spaces (Free)**, berikut adalah batasan dan solusi:
17
+
18
+ ### **Vercel (Backend API)**
19
+ - **Aman:** Vercel hanya meneruskan request ke Hugging Face. Beban komputasi ada di Hugging Face.
20
+ - **Risiko:** Vercel Function (Free) memiliki timeout 10-60 detik. Jika Hugging Face sedang *cold start* (tidur), request dari Vercel bisa timeout.
21
+ - **Solusi:** Pastikan Hugging Face selalu aktif (lihat poin Hugging Face).
22
+
23
+ ### **Supabase (Database) & Cloudinary (Storage)**
24
+ - **Database (Supabase):** Aman (URL text sangat ringan).
25
+ - **Storage (Cloudinary):** Jauh lebih baik! Free tier Cloudinary (~25 Credits) sangat cukup untuk ribuan foto absensi.
26
+ - **Keuntungan:** Cloudinary otomatis mengoptimalkan delivery gambar, jadi download ke API Face Recognition bisa lebih cepat.
27
+ - **Solusi:** Tetap lakukan resize di mobile app untuk menghemat kuota bandwidth Cloudinary.
28
+
29
+ ### **Hugging Face Spaces (CPU Basic)**
30
+ - **Isu Utama:** Space akan "tidur" setelah 48 jam tidak aktif. *Cold start* butuh 2-3 menit.
31
+ - **Solusi (Wajib):** Setup cron-job gratis (misal: console.cron-job.org) untuk "ping" endpoint Health Check (`GET /`) setiap 1 jam agar Space tidak pernah tidur.
32
+
33
+ ## 3. Alur Kerja Face Recognition
34
 
35
  Sistem ini memiliki dua fase utama: **Pendaftaran (Enrollment)** dan **Verifikasi (Verification)**.
36