refactor: pisahkan flask_ml_api dari repo Laravel - hapus 14461 file dari tracking (venv, dataset, model, notebook)

This commit is contained in:
jouel88 2026-05-02 01:11:20 +07:00
parent 481d83f943
commit d740e8170f
14466 changed files with 7 additions and 2421810 deletions

7
.gitignore vendored
View File

@ -25,4 +25,11 @@ Homestead.yaml
Thumbs.db
_ide_helper.php
.phpstorm.meta.php
_ide_helper_models.php
# Flask ML API (repo terpisah)
/flask_ml_api/
# Research notebooks & scripts
*.ipynb
/notebook_face_recognition.py

View File

@ -1,519 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "899c35b3",
"metadata": {},
"source": [
"# 🎓 Evaluasi Kinerja Sistem Pengenalan Wajah Berbasis LBP dan SVM\n",
"\n",
"**Laporan Eksperimen Pengujian Biometrik Lanjut**\n",
"\n",
"Notebook ini merupakan panduan saintifik yang lengkap untuk demonstrasi uji coba *Computer Vision Traditional* LBP-SVM. Mencakup Analisa Komponen Vektor hingga Kurva Evaluasi Metrik Biometrik Standar Skripsi/Tesis.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c21ecf7",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import sys\n",
"import json\n",
"import logging\n",
"import numpy as np\n",
"import pandas as pd\n",
"import joblib\n",
"import cv2\n",
"from PIL import Image\n",
"\n",
"import seaborn as sns\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.patches as patches\n",
"\n",
"from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, roc_curve, auc\n",
"from sklearn.decomposition import PCA\n",
"import warnings\n",
"from sklearn.exceptions import InconsistentVersionWarning\n",
"warnings.filterwarnings(\"ignore\", category=InconsistentVersionWarning)\n",
"\n",
"try:\n",
" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) \n",
"except NameError:\n",
" BASE_DIR = os.path.abspath('')\n",
"\n",
"if not os.path.exists(os.path.join(BASE_DIR, 'python_scripts')):\n",
" BASE_DIR = r\"C:\\xampp\\htdocs\\mpg-hris\"\n",
"\n",
"DATASET_DIR = os.path.join(BASE_DIR, 'storage', 'app', 'private', 'face_datasets')\n",
"MODEL_DIR = os.path.join(BASE_DIR, 'storage', 'app', 'private', 'face_models')\n",
"DEBUG_DIR = os.path.join(BASE_DIR, 'storage', 'app', 'private', 'debug_verify')\n",
"\n",
"DF_APPROVED = 1.5\n",
"DF_PENDING = 0.5\n",
"\n",
"scripts_path = os.path.join(BASE_DIR, 'python_scripts')\n",
"if scripts_path not in sys.path:\n",
" sys.path.insert(0, scripts_path)\n",
" \n",
"from lbp_features import extract_lbp_features, preprocess_face, FACE_SIZE, _lbp_uniform\n",
"\n",
"face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')\n",
"profile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_profileface.xml')\n",
"print(\"✅ Lingkungan eksekusi Akademik Advanced berhasil dimuat.\")\n"
]
},
{
"cell_type": "markdown",
"id": "961e72b4",
"metadata": {},
"source": [
"## 1. Arsitektur Klasifikasi: Load Model Multi-Class SVM\n",
"* **StandardScaler**: Mengubah mean pada dataset fitur LBP (1792 dimensi) ke nilai 0 berdeviasi 1.\n",
"* **SVM Decision Function**: Kami menggunakan kernel probabilistik margin L2 dengan evaluasi absolut jarak Hyperplane.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7a07d5f",
"metadata": {},
"outputs": [],
"source": [
"def load_system_model():\n",
" svm_path = os.path.join(MODEL_DIR, 'face_model.pkl')\n",
" scaler_path = os.path.join(MODEL_DIR, 'face_scaler.pkl')\n",
" labels_path = os.path.join(MODEL_DIR, 'face_labels.json')\n",
" svm_m = joblib.load(svm_path)\n",
" scaler_m = joblib.load(scaler_path)\n",
" with open(labels_path, 'r') as f:\n",
" lbl = json.load(f)\n",
" return svm_m, scaler_m, lbl\n",
"\n",
"svm, scaler, labels = load_system_model()\n",
"kelas_terdaftar = [str(c) for c in svm.classes_ if str(c) != 'unknown']\n",
"\n",
"print(f\"✅ Model klasifikasi berhasil diload ke memory. Identitas Aktif: {kelas_terdaftar}\")\n"
]
},
{
"cell_type": "markdown",
"id": "56d2305c",
"metadata": {},
"source": [
"## 2. API Pipeline Wajah dan Crop Geometrik"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8213583b",
"metadata": {},
"outputs": [],
"source": [
"def detect_and_crop_face(gray):\n",
" h, w = gray.shape\n",
" if max(h, w) > 640:\n",
" scale = 640.0 / max(h, w)\n",
" gray = cv2.resize(gray, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA)\n",
" h, w = gray.shape\n",
"\n",
" min_size = int(min(h, w) * 0.1)\n",
" faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(min_size, min_size))\n",
" if len(faces) == 0:\n",
" faces = face_cascade.detectMultiScale(gray, 1.05, 3, minSize=(min_size, min_size))\n",
" if len(faces) == 0:\n",
" faces = profile_cascade.detectMultiScale(gray, 1.1, 3, minSize=(min_size, min_size))\n",
"\n",
" if len(faces) == 0: return None\n",
" faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)\n",
" (x, y, fw, fh) = faces[0]\n",
" padding = int(max(fw, fh) * 0.1)\n",
" x1, y1 = max(0, x - padding), max(0, y - padding)\n",
" x2, y2 = min(w, x + fw + padding), min(h, y + fh + padding)\n",
" crop = gray[y1:y2, x1:x2]\n",
" return cv2.resize(crop, FACE_SIZE, interpolation=cv2.INTER_AREA)\n",
"\n",
"def process_image_file(img_path):\n",
" try:\n",
" pil_img = Image.open(img_path)\n",
" exif = pil_img.getexif()\n",
" orient = exif.get(274, 1)\n",
" if orient == 3: pil_img = pil_img.rotate(180, expand=True)\n",
" elif orient == 6: pil_img = pil_img.rotate(270, expand=True)\n",
" elif orient == 8: pil_img = pil_img.rotate(90, expand=True)\n",
" img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)\n",
" except Exception:\n",
" img = cv2.imread(img_path)\n",
" if img is None: return None, None, \"Fail\"\n",
" gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img\n",
" face = detect_and_crop_face(gray)\n",
" if face is None: return img, None, \"No Face\"\n",
" proc = preprocess_face(face)\n",
" feats = extract_lbp_features(proc)\n",
" return face, feats, \"OK\"\n",
"\n",
"def classify_secure(feats, expected_user_id):\n",
" classes = [str(c) for c in svm.classes_]\n",
" feats_s = scaler.transform([feats])\n",
" df_values = svm.decision_function(feats_s)[0]\n",
" if expected_user_id in classes:\n",
" idx = classes.index(expected_user_id)\n",
" user_df = float(df_values[idx] if hasattr(df_values, '__len__') else df_values)\n",
" else: user_df = -999.0\n",
"\n",
" df_sorted = sorted(enumerate(df_values), key=lambda x: -x[1])\n",
" predicted = str(classes[df_sorted[0][0]])\n",
" predicted_is_user = (predicted == expected_user_id)\n",
" if predicted_is_user and user_df >= DF_APPROVED: status = \"APPROVED\"\n",
" elif predicted_is_user and user_df >= DF_PENDING: status = \"PENDING\"\n",
" else: status = \"REJECTED\"\n",
" return {'predicted': predicted, 'user_df': user_df, 'status': status, 'match': status in (\"APPROVED\", \"PENDING\")}\n"
]
},
{
"cell_type": "markdown",
"id": "3ef9c6fc",
"metadata": {},
"source": [
"## 3. Visualisasi Anatomi Feature Extraction LBP Penuh"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3c69098",
"metadata": {},
"outputs": [],
"source": [
"def detailed_visual_demo():\n",
" sample_file = None\n",
" target_user = None\n",
" for d in os.listdir(DATASET_DIR):\n",
" dir_path = os.path.join(DATASET_DIR, d)\n",
" if os.path.isdir(dir_path):\n",
" files = [f for f in os.listdir(dir_path) if f.startswith('raw_frame_') and f.endswith('.jpg')]\n",
" if files:\n",
" sample_file = os.path.join(dir_path, files[0])\n",
" target_user = d\n",
" break\n",
" if not sample_file: return\n",
"\n",
" raw_img = cv2.imread(sample_file)\n",
" raw_rgb = cv2.cvtColor(raw_img, cv2.COLOR_BGR2RGB)\n",
" gray = cv2.cvtColor(raw_img, cv2.COLOR_BGR2GRAY)\n",
" faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))\n",
" x, y, w, h = (0,0,0,0)\n",
" if len(faces) > 0:\n",
" faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)\n",
" x, y, w, h = faces[0]\n",
"\n",
" face_raw, feats, msg = process_image_file(sample_file)\n",
" if face_raw is None: return\n",
" processed = preprocess_face(face_raw)\n",
" lbp_map = _lbp_uniform(processed, n_points=8, radius=1)\n",
" res = classify_secure(feats, expected_user_id=target_user)\n",
" \n",
" fig = plt.figure(figsize=(19, 11))\n",
" fig.suptitle(f\"Bedah Algoritma Vektor Wajah | Sampel Asli: User {target_user}\", fontsize=20, fontweight='bold', y=0.98)\n",
" \n",
" ax1 = plt.subplot(2, 3, 1)\n",
" ax1.imshow(raw_rgb)\n",
" if w > 0:\n",
" pad = int(max(w, h) * 0.1)\n",
" ax1.add_patch(patches.Rectangle((max(0, x-pad), max(0, y-pad)), w+(pad*2), h+(pad*2), linewidth=3, edgecolor='yellow', facecolor='none'))\n",
" ax1.set_title(\"1. Deteksi Wajah Geometris\", fontsize=13)\n",
" ax1.axis('off')\n",
" \n",
" ax2 = plt.subplot(2, 3, 2)\n",
" ax2.imshow(face_raw, cmap='gray')\n",
" ax2.set_title(\"2. Potongan Skala Abu 128px\", fontsize=13)\n",
" ax2.axis('off')\n",
" \n",
" ax3 = plt.subplot(2, 3, 3)\n",
" ax3.imshow(processed, cmap='gray')\n",
" ax3.set_title(\"3. Preprocessing Histogram CLAHE\", fontsize=13)\n",
" ax3.axis('off')\n",
" \n",
" ax4 = plt.subplot(2, 3, 4)\n",
" ax4.imshow(lbp_map, cmap='viridis')\n",
" ax4.set_title(\"4. Local Binary Map (LBP Texture)\", fontsize=13)\n",
" ax4.axis('off')\n",
" \n",
" ax5 = plt.subplot(2, 3, 5)\n",
" ax5.imshow(processed, cmap='gray')\n",
" step_x = 128 // 8\n",
" step_y = 128 // 8\n",
" for i in range(1, 8):\n",
" ax5.axhline(i*step_y, color='lime', linestyle='dashed', alpha=0.8)\n",
" ax5.axvline(i*step_x, color='lime', linestyle='dashed', alpha=0.8)\n",
" ax5.set_title(\"5. Peta 8x8 Lokalisasi Vektor\", fontsize=13)\n",
" ax5.axis('off')\n",
" \n",
" ax6 = plt.subplot(2, 3, 6)\n",
" ax6.bar(range(180), feats[:180], color='dodgerblue')\n",
" ax6.set_title(\"6. Output Vektor Array LBP 1D (SVM Feed)\", fontsize=13)\n",
" \n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"detailed_visual_demo()\n"
]
},
{
"cell_type": "markdown",
"id": "e077bbaf",
"metadata": {},
"source": [
"## 4. Evaluasi Akurasi Dasar (Confusion Matrix)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4391500d",
"metadata": {},
"outputs": [],
"source": [
"y_true = []\n",
"y_pred = []\n",
"\n",
"for uid in kelas_terdaftar:\n",
" folder = os.path.join(DATASET_DIR, uid)\n",
" files = sorted([f for f in os.listdir(folder) if f.startswith('raw_frame_') and f.endswith('.jpg')])\n",
" for fname in files:\n",
" face, feats, info = process_image_file(os.path.join(folder, fname))\n",
" if feats is not None:\n",
" result = classify_secure(feats, expected_user_id=uid)\n",
" y_true.append(uid)\n",
" y_pred.append(result['predicted'])\n",
"\n",
"labels_in_data = sorted(list(set(y_true + y_pred)))\n",
"cm = confusion_matrix(y_true, y_pred, labels=labels_in_data)\n",
"\n",
"fig, ax = plt.subplots(figsize=(8,6))\n",
"disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels_in_data)\n",
"disp.plot(cmap='Blues', ax=ax, values_format='d')\n",
"plt.title(\"Confusion Matrix Otentikasi SVM\", fontweight='bold', pad=20)\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "7f922126",
"metadata": {},
"source": [
"## 5. Visualisasi Multivariabel 1792 Dimensi Jarak SVM (PCA 2D Cluster)\n",
"Jika anda bertanya *'Bagaimana cara SVM membedakan manusia satu dengan lainnya?'*, grafik ini jawabannya.\n",
"Sistem ini menggunakan algoritma dimensi **Principal Component Analysis (PCA)** untuk mereduksi `1792 Dimensi` ke 2 dimensi X dan Y agar bisa terlihat oleh kacamata penguji. \n",
"Apabila setiap klasifikasi identitas mengerucut dan mengumpul secara berkelompok (tidak berpencar atau meledak acak), maka model Machine Learning kita valid secara Geometrik."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e9f4ae7",
"metadata": {},
"outputs": [],
"source": [
"X_data = [] # Array Matrix Fitur\n",
"y_labels = [] # Array Identitas Sah\n",
"genuine_scores = [] # Track skor keyakinan untuk label sebenarnya\n",
"impostor_scores = [] # Track invasi hacker ketika coba masuk account kita\n",
"\n",
"for uid in kelas_terdaftar:\n",
" folder = os.path.join(DATASET_DIR, uid)\n",
" files = sorted([f for f in os.listdir(folder) if f.startswith('raw_frame_') and f.endswith('.jpg')])\n",
" for fname in files:\n",
" face, feats, msg = process_image_file(os.path.join(folder, fname))\n",
" if feats is not None:\n",
" X_data.append(feats)\n",
" y_labels.append(uid)\n",
" \n",
" # --- Perhitungan Skor Jarak untuk Grafik Histogram Lonceng dan EER Nantinya ---\n",
" feats_s = scaler.transform([feats])\n",
" df_values = svm.decision_function(feats_s)[0]\n",
" for class_idx, predicted_uid in enumerate(svm.classes_):\n",
" predicted_uid = str(predicted_uid)\n",
" if predicted_uid == 'unknown': continue\n",
" \n",
" score = df_values[class_idx]\n",
" if predicted_uid == uid:\n",
" genuine_scores.append(score)\n",
" else:\n",
" impostor_scores.append(score)\n",
"\n",
"# --- MULAI PLOT PCA ---\n",
"print(\"Melakukan faktorisasi kompresi dimensi PCA...\")\n",
"pca = PCA(n_components=2)\n",
"components = pca.fit_transform(X_data)\n",
"\n",
"df_pca = pd.DataFrame(data = components, columns = ['Komponen LBP Skala Utama 1', 'Komponen LBP Skala Utama 2'])\n",
"df_pca['Identitas Ground Truth'] = y_labels\n",
"\n",
"plt.figure(figsize=(10, 7))\n",
"sns.scatterplot(\n",
" x='Komponen LBP Skala Utama 1', y='Komponen LBP Skala Utama 2',\n",
" hue='Identitas Ground Truth', data=df_pca, \n",
" palette=sns.color_palette(\"Set1\", len(kelas_terdaftar)),\n",
" s=120, alpha=0.9, edgecolor='white', linewidth=0.5\n",
")\n",
"plt.title('Pemetaan Relasi Fitur Wajah Manusia SVM (Clustering Visual)', fontsize=15, fontweight='bold', pad=15)\n",
"plt.grid(True, linestyle='--', alpha=0.6)\n",
"plt.legend(title='Database Pegawai', bbox_to_anchor=(1.05, 1), loc='upper left')\n",
"plt.tight_layout()\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "bc72d777",
"metadata": {},
"source": [
"## 6. Histogram Lonceng Biometrik (*Genuine Score vs Impostor Score*)\n",
"Grafik penting otentikasi biometrik yang berfungsi melihat \"Jarak Jembatan\" (*Gap Wall*) ketebalan perlindungan Margin antara Wajah Pengguna Asli (*Genuine*) dan Penyusup Acak (*Impostor*).\n",
"Apabila Grafik Merah (*Impostor*) menembus dan bersinggungan erat menutupi bukit warna Hijau (*Authentic*), sistem tersebut dinilai rentan dan berbahaya!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ce954f49",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(12, 6))\n",
"sns.histplot(genuine_scores, color='seagreen', label='Genuine Access (Orang Dalam Sah)', kde=True, stat=\"density\", bins=20, alpha=0.5, edgecolor=\"none\")\n",
"sns.histplot(impostor_scores, color='crimson', label='Impostor Access (Orang Lain Tersesat)', kde=True, stat=\"density\", bins=20, alpha=0.5, edgecolor=\"none\")\n",
"\n",
"plt.axvline(x=DF_APPROVED, color='gold', linestyle='--', linewidth=3, label=f'Margin Titik Kunci Server: DF({DF_APPROVED})')\n",
"\n",
"plt.title('Sebaran Probabilitas Decision Margin SVM Wajah', fontsize=15, fontweight='bold')\n",
"plt.xlabel('SVM Decision Margin Score / Angka Kepastian')\n",
"plt.ylabel('Kepadatan Distribusi Data (%)')\n",
"plt.legend(loc='upper right')\n",
"plt.grid(axis='y', linestyle='--', alpha=0.5)\n",
"plt.tight_layout()\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "20287582",
"metadata": {},
"source": [
"## 7. Receiver Operating Characteristic (ROC) & Titik Evaluasi Otentikasi EER Ekstrim\n",
"- **Kurva Biru ROC (Gambar Kiri)**: Menggambarkan luasan area keakuratan (*Area Under Curve / AUC*), dinilai superlatif bilamana mendekati angka 1.00.\n",
"- **Titik Silang EER (Gambar Kanan)**: Adalah titik krusial persinggungan antara Resiko Alarm Palsu **(FAR)** dengan Resiko Salah Tolak Orang Nyata **(FRR)**. Secara akademis _Golden Standard Error Rate_ harus bernilai 0% ~ 1%. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b319996",
"metadata": {},
"outputs": [],
"source": [
"# Label array: Asli (1) dan Palsu / Impostor (0)\n",
"y_binary_roc = [1] * len(genuine_scores) + [0] * len(impostor_scores)\n",
"y_scores_roc = genuine_scores + impostor_scores\n",
"\n",
"fpr_roc, tpr_roc, thresholds_roc = roc_curve(y_binary_roc, y_scores_roc)\n",
"roc_auc = auc(fpr_roc, tpr_roc)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))\n",
"\n",
"# ---- 1. ROC CURVE ----\n",
"ax1.plot(fpr_roc, tpr_roc, color='dodgerblue', lw=3, label=f'Support Vector ROC (AUC = {roc_auc:.4f})')\n",
"ax1.plot([0, 1], [0, 1], color='darkgrey', lw=2, linestyle='--', label='Garis Lemparan Koin Acak (Keburukan)')\n",
"ax1.set_xlim([-0.05, 1.0])\n",
"ax1.set_ylim([0.0, 1.05])\n",
"ax1.set_xlabel('False Positive Rate (Rasio Kebocoran Penipu)', fontsize=12)\n",
"ax1.set_ylabel('True Positive Rate (Rasio Menyetujui Akun Sendiri)', fontsize=12)\n",
"ax1.set_title('Kesempurnaan Deteksi / ROC Curve', fontweight='bold', fontsize=14)\n",
"ax1.legend(loc='lower right')\n",
"ax1.grid(True, linestyle=':', alpha=0.7)\n",
"\n",
"# ---- 2. EER (EQUAL ERROR RATE) CURVE ----\n",
"# Formula Dasar Biometrik FRR adalah (100% - Tingkat Akseptasi Kebenaran TPR / False Rejection)\n",
"frr = 1 - tpr_roc\n",
"far = fpr_roc\n",
"\n",
"# Deteksi titik perpapasan/silang untuk mendapatkan Absolute Equal Error Rate (EER) Tengah\n",
"eer_index = np.argmin(np.abs(far - frr))\n",
"eer_threshold_val = thresholds_roc[eer_index]\n",
"eer_percent = far[eer_index]\n",
"\n",
"ax2.plot(thresholds_roc, far, color='crimson', lw=2.5, label='FAR (Orang Asing Diterima / Bahaya)')\n",
"ax2.plot(thresholds_roc, frr, color='navy', lw=2.5, label='FRR (Orang Asli Ditolak Mesin)')\n",
"ax2.axvline(x=eer_threshold_val, color='gold', linestyle='--', linewidth=2, label=f'EER={eer_percent*100:.2f}% (Toleransi Error Mutlak)\\nMargin Kritis di: {eer_threshold_val:.2f}')\n",
"\n",
"ax2.set_xlim([-1.5, 3.5]) # Memfokuskan grafik ke pergeseran persimpangan Decision Values\n",
"ax2.set_ylim([-0.05, 1.05])\n",
"ax2.set_xlabel('SVM Operational Decision Boundary Margin Limit', fontsize=12)\n",
"ax2.set_ylabel('Level Bahaya Error Tolerance', fontsize=12)\n",
"ax2.set_title('Kesetimbangan Resiko Operasional: FAR vs FRR', fontweight='bold', fontsize=14)\n",
"ax2.legend(loc='upper right')\n",
"ax2.grid(True, linestyle=':', alpha=0.7)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"=\" * 60)\n",
"print(f\" >>> AREA UNDER CURVE KUALITAS SVM MURNI: {roc_auc:.4f} <<< \")\n",
"print(\" Titik AUC 1.000 memformulasikan Algoritma telah mengenali Identitas tanpa misklasifikasi sedetik pun di atas kertas.\")\n",
"print(f\" ✅ Equal Error Rate (EER) Absolut ditekan ke angka ekstrim: {eer_percent*100:.2f} % !!!\")\n",
"print(\"=\" * 60)\n"
]
},
{
"cell_type": "markdown",
"id": "75690eb9",
"metadata": {},
"source": [
"## 8. Verifikasi Kasus Lintas Domain (Kondisi Lapangan Aplikasi Mobile)\n",
"Penyelesaian *Domain Gap* untuk mendiagnosa jepretan kamera Low Quality langsung dari Flutter Front-End Apps."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4594f26",
"metadata": {},
"outputs": [],
"source": [
"if not os.path.exists(DEBUG_DIR): print(\"Folder debug_verify log tidak ditemukan.\")\n",
"else:\n",
" debug_files = sorted([f for f in os.listdir(DEBUG_DIR) if f.endswith('.jpg') and f.startswith('user')])\n",
" \n",
" if len(debug_files) == 0: print(\"Berdasarkan pengumpulan lapangan belum ada data log dari Presensi App.\")\n",
" else:\n",
" print(f\"Ditemukan {len(debug_files)} Gambar Log Absen Harian Teruji Secara Domain Silang:\\n\")\n",
" print(f\"{'Nama File Uji':<32} {'Target':<7} {'Status':<10} {'Margin SVM':>10} {'Akurasi Jauh'}\")\n",
" print(\"-\" * 80)\n",
" \n",
" real_total = 0\n",
" real_correct = 0\n",
" for fname in debug_files:\n",
" expected_uid = fname.split('_')[0].replace('user', '')\n",
" fpath = os.path.join(DEBUG_DIR, fname)\n",
" face, feats, msg = process_image_file(fpath)\n",
" if feats is None: continue\n",
" r = classify_secure(feats, expected_user_id=expected_uid)\n",
" real_total += 1\n",
" if r['match']: real_correct += 1\n",
" \n",
" mar = \"✔️ Lolos Security\" if r['match'] else \"❌ Gagal (Wajah Ditolak/Noise)\"\n",
" print(f\"{fname:<32} User {expected_uid:<2} {r['status']:<10} {r['user_df']:>10.3f} {mar}\")\n",
" \n",
" print(\"-\" * 80)\n",
" print(f\"Keandalan Validasi Perbedaan Domain & Resolusi Ekstrem Kamera Mobile: {real_correct}/{real_total} ({(real_correct/real_total)*100 if real_total else 0:.1f}%)\")\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -1,753 +0,0 @@
<?php
// @formatter:off
// phpcs:ignoreFile
/**
* A helper file for your Eloquent Models
* Copy the phpDocs from this file to the correct Model,
* And remove them from this file, to prevent double declarations.
*
* @author Barry vd. Heuvel <barryvdh@gmail.com>
*/
namespace App\Models{
/**
* @property string $id_approval
* @property string $id_surat
* @property int $id_approver
* @property string|null $id_ttd_approver
* @property int $tahap 1=Manajer, 2=HRD
* @property string $status
* @property string|null $catatan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User $approver
* @property-read \App\Models\SuratIzin $suratIzin
* @property-read \App\Models\TandaTangan|null $tandaTanganApprover
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereCatatan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereIdApproval($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereIdApprover($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereIdSurat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereIdTtdApprover($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereTahap($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ApprovalSurat whereUpdatedAt($value)
* @mixin \Eloquent
*/
class ApprovalSurat extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id
* @property int $id_user
* @property string|null $path_model_yml
* @property int $is_verified 0:Pending, 1:Approved, 2:Rejected
* @property string|null $last_updated
* @property string|null $encoding_wajah
* @property string $tanggal_latih
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereEncodingWajah($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereIsVerified($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereLastUpdated($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah wherePathModelYml($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereTanggalLatih($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereUpdatedAt($value)
* @mixin \Eloquent
*/
class DataWajah extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id
* @property string $id_penggunaan
* @property int $id_poin_sumber
* @property int $jumlah_diambil
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\PenggunaanPoin $penggunaan
* @property-read \App\Models\PoinLembur $poinSumber
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereIdPenggunaan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereIdPoinSumber($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereJumlahDiambil($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DetailPenggunaanPoin whereUpdatedAt($value)
* @mixin \Eloquent
*/
class DetailPenggunaanPoin extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_divisi
* @property string $nama_divisi
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi whereIdDivisi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi whereNamaDivisi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Divisi whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Divisi extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id
* @property string $tanggal
* @property string $keterangan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur whereKeterangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur whereTanggal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|HariLibur whereUpdatedAt($value)
* @mixin \Eloquent
*/
class HariLibur extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_jabatan
* @property string $nama_jabatan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan whereIdJabatan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan whereNamaJabatan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Jabatan whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Jabatan extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_jadwal
* @property int|null $id_user
* @property string|null $tanggal
* @property int|null $id_shift
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\PenggunaanPoin|null $penggunaanPoin
* @property-read \App\Models\ShiftKerja|null $shift
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereIdJadwal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereIdShift($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereTanggal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JadwalKerja whereUpdatedAt($value)
* @mixin \Eloquent
*/
class JadwalKerja extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_jenis_izin
* @property string $nama_izin
* @property string|null $created_at
* @property string|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin whereIdJenisIzin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin whereNamaIzin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisIzin whereUpdatedAt($value)
* @mixin \Eloquent
*/
class JenisIzin extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_kompensasi
* @property string $nama_kompensasi
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi whereIdKompensasi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi whereNamaKompensasi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisKompensasi whereUpdatedAt($value)
* @mixin \Eloquent
*/
class JenisKompensasi extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_pengurangan
* @property string $nama_pengurangan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PenggunaanPoin> $penggunaanPoin
* @property-read int|null $penggunaan_poin_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan whereIdPengurangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan whereNamaPengurangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|JenisPengurangan whereUpdatedAt($value)
* @mixin \Eloquent
*/
class JenisPengurangan extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_kantor
* @property string $nama_kantor
* @property string|null $alamat
* @property numeric|null $latitude
* @property numeric|null $longitude
* @property int $radius
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read int|null $users_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereAlamat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereIdKantor($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereLatitude($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereLongitude($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereNamaKantor($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereRadius($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Kantor whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Kantor extends \Eloquent {}
}
namespace App\Models{
/**
* @property string $id_lembur
* @property int|null $id_user
* @property \Illuminate\Support\Carbon $tanggal_lembur
* @property string $jam_mulai
* @property string $jam_selesai
* @property int|null $durasi_menit
* @property string|null $keterangan
* @property int|null $jumlah_poin
* @property int|null $id_kompensasi
* @property int $id_status
* @property string|null $alasan_penolakan
* @property \Illuminate\Support\Carbon $tanggal_diajukan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\JenisKompensasi|null $kompensasi
* @property-read \App\Models\StatusPengajuan $status
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereAlasanPenolakan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereDurasiMenit($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereIdKompensasi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereIdLembur($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereIdStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereJamMulai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereJamSelesai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereJumlahPoin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereKeterangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereTanggalDiajukan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereTanggalLembur($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Lembur whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Lembur extends \Eloquent {}
}
namespace App\Models{
/**
* @property string $id_izin
* @property int|null $id_user
* @property string $tanggal_mulai
* @property string $tanggal_selesai
* @property int|null $id_jenis_izin
* @property string|null $alasan
* @property string|null $bukti_file
* @property int $id_status
* @property string|null $alasan_penolakan
* @property string $tanggal_diajukan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\JenisIzin|null $jenisIzin
* @property-read \App\Models\StatusPengajuan $statusPengajuan
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereAlasan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereAlasanPenolakan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereBuktiFile($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereIdIzin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereIdJenisIzin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereIdStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereTanggalDiajukan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereTanggalMulai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereTanggalSelesai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PengajuanIzin whereUpdatedAt($value)
* @mixin \Eloquent
*/
class PengajuanIzin extends \Eloquent {}
}
namespace App\Models{
/**
* @property string $id_penggunaan
* @property int|null $id_user
* @property \Illuminate\Support\Carbon $tanggal_penggunaan
* @property int|null $jumlah_poin
* @property string|null $jam_masuk_custom
* @property string|null $jam_pulang_custom
* @property int|null $id_pengurangan
* @property int $id_status
* @property string|null $alasan_penolakan
* @property \Illuminate\Support\Carbon $tanggal_diajukan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\JenisPengurangan|null $jenisPengurangan
* @property-read \App\Models\StatusPengajuan $status
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereAlasanPenolakan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereIdPenggunaan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereIdPengurangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereIdStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereJamMasukCustom($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereJamPulangCustom($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereJumlahPoin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereTanggalDiajukan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereTanggalPenggunaan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PenggunaanPoin whereUpdatedAt($value)
* @mixin \Eloquent
*/
class PenggunaanPoin extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_pengumuman
* @property string|null $judul
* @property string|null $isi
* @property \Illuminate\Support\Carbon|null $tanggal
* @property int|null $dibuat_oleh
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $pembuat
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereDibuatOleh($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereIdPengumuman($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereIsi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereJudul($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereTanggal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Pengumuman whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Pengumuman extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_permission
* @property string $nama_permission
* @property string $slug
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Role> $roles
* @property-read int|null $roles_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission whereIdPermission($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission whereNamaPermission($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Permission whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Permission extends \Eloquent {}
}
namespace App\Models{
/**
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin query()
* @mixin \Eloquent
*/
class Poin extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_poin
* @property int|null $id_user
* @property int|null $jumlah_poin
* @property int|null $sisa_poin
* @property string|null $id_lembur
* @property string|null $keterangan
* @property \Illuminate\Support\Carbon $tanggal
* @property \Illuminate\Support\Carbon|null $expired_at
* @property bool|null $is_fully_used
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Lembur|null $lembur
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereExpiredAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereIdLembur($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereIdPoin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereIsFullyUsed($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereJumlahPoin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereKeterangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereSisaPoin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereTanggal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PoinLembur whereUpdatedAt($value)
* @mixin \Eloquent
*/
class PoinLembur extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_presensi
* @property int|null $id_user
* @property string $tanggal
* @property string|null $jam_masuk
* @property string|null $jam_pulang
* @property numeric|null $lat_masuk
* @property numeric|null $lon_masuk
* @property numeric|null $lat_pulang
* @property numeric|null $lon_pulang
* @property string|null $foto_wajah_masuk
* @property string|null $foto_wajah_pulang
* @property int|null $id_status
* @property string|null $alasan_telat
* @property string|null $keterangan_pulang
* @property string|null $waktu_terlambat
* @property string|null $waktu_masuk_awal
* @property string|null $waktu_pulang_awal
* @property string|null $waktu_pulang_akhir
* @property int $verifikasi_wajah
* @property int $id_validasi
* @property string|null $keterangan_luar_radius
* @property string|null $alasan_penolakan
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string|null $deleted_at
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereAlasanPenolakan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereAlasanTelat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereFotoWajahMasuk($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereFotoWajahPulang($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereIdPresensi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereIdStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereIdValidasi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereJamMasuk($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereJamPulang($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereKeteranganLuarRadius($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereKeteranganPulang($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereLatMasuk($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereLatPulang($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereLonMasuk($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereLonPulang($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereTanggal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereVerifikasiWajah($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereWaktuMasukAwal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereWaktuPulangAkhir($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereWaktuPulangAwal($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Presensi whereWaktuTerlambat($value)
* @mixin \Eloquent
*/
class Presensi extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_riwayat
* @property int $id_user_1
* @property int $id_jadwal_1
* @property int $id_user_2
* @property int $id_jadwal_2
* @property string|null $keterangan
* @property int $created_by
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User $execAdmin
* @property-read \App\Models\JadwalKerja $jadwal1
* @property-read \App\Models\JadwalKerja $jadwal2
* @property-read \App\Models\User $user1
* @property-read \App\Models\User $user2
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereCreatedBy($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereIdJadwal1($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereIdJadwal2($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereIdRiwayat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereIdUser1($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereIdUser2($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereKeterangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RiwayatTukarShift whereUpdatedAt($value)
* @mixin \Eloquent
*/
class RiwayatTukarShift extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_role
* @property string $nama_role
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Permission> $permissions
* @property-read int|null $permissions_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read int|null $users_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereIdRole($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereNamaRole($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Role extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_shift
* @property string|null $nama_shift
* @property string|null $jam_mulai
* @property string|null $jam_selesai
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\JadwalKerja> $jadwalKerja
* @property-read int|null $jadwal_kerja_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereIdShift($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereJamMulai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereJamSelesai($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereNamaShift($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|ShiftKerja whereUpdatedAt($value)
* @mixin \Eloquent
*/
class ShiftKerja extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id_status
* @property string $nama_status
* @property string|null $created_at
* @property string|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan whereIdStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan whereNamaStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|StatusPengajuan whereUpdatedAt($value)
* @mixin \Eloquent
*/
class StatusPengajuan extends \Eloquent {}
}
namespace App\Models{
/**
* @property string $id_surat
* @property string $id_izin
* @property int $id_user
* @property string $nomor_surat
* @property string $isi_surat
* @property string|null $id_ttd_pengaju
* @property string $status_surat
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ApprovalSurat|null $approvalHrd
* @property-read \App\Models\ApprovalSurat|null $approvalManajer
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ApprovalSurat> $approvals
* @property-read int|null $approvals_count
* @property-read \App\Models\PengajuanIzin $pengajuanIzin
* @property-read \App\Models\TandaTangan|null $tandaTanganPengaju
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereIdIzin($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereIdSurat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereIdTtdPengaju($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereIsiSurat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereNomorSurat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereStatusSurat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|SuratIzin whereUpdatedAt($value)
* @mixin \Eloquent
*/
class SuratIzin extends \Eloquent {}
}
namespace App\Models{
/**
* @property string $id_tanda_tangan
* @property int $id_user
* @property string $file_ttd
* @property int $is_active
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan active()
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereFileTtd($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereIdTandaTangan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereIdUser($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereIsActive($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|TandaTangan whereUpdatedAt($value)
* @mixin \Eloquent
*/
class TandaTangan extends \Eloquent {}
}
namespace App\Models{
/**
* @property int $id
* @property string|null $nik
* @property string $nama_lengkap
* @property string $email
* @property string|null $no_telp
* @property string|null $alamat
* @property string|null $email_verified_at
* @property string $password
* @property int $sisa_cuti
* @property string|null $foto
* @property string|null $remember_token
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property int|null $id_kantor
* @property int|null $id_divisi
* @property int|null $id_jabatan
* @property int $status_aktif
* @property string|null $tgl_bergabung
* @property int $is_face_registered
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\DataWajah|null $dataWajah
* @property-read \App\Models\Divisi|null $divisi
* @property-read \App\Models\Jabatan|null $jabatan
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\JadwalKerja> $jadwalKerja
* @property-read int|null $jadwal_kerja_count
* @property-read \App\Models\Kantor|null $kantor
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Lembur> $lemburs
* @property-read int|null $lemburs_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Laravel\Sanctum\PersonalAccessToken> $tokens
* @property-read int|null $tokens_count
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereAlamat($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereFoto($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereIdDivisi($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereIdJabatan($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereIdKantor($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereIsFaceRegistered($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereNamaLengkap($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereNik($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereNoTelp($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereSisaCuti($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereStatusAktif($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTglBergabung($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User withoutTrashed()
* @mixin \Eloquent
*/
class User extends \Eloquent {}
}

View File

@ -1,181 +0,0 @@
import os
import uuid
import logging
from functools import wraps
from flask import Flask, request, jsonify
from flask_cors import CORS
from config import DATASETS_DIR, MODELS_DIR, TEMP_DIR, MAX_CONTENT_LENGTH, API_KEY
from services.extract_service import extract_frames
from services.train_service import train_model
from services.verify_service import verify_face
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(levelname)s: %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
CORS(app)
def require_api_key(f):
@wraps(f)
def decorated(*args, **kwargs):
key = request.headers.get('X-API-Key', '')
if key != API_KEY:
return jsonify({"status": "error", "message": "API key tidak valid."}), 401
return f(*args, **kwargs)
return decorated
@app.route('/health', methods=['GET'])
def health():
model_exists = os.path.exists(os.path.join(MODELS_DIR, 'face_model.pkl'))
return jsonify({
"status": "ok",
"model_loaded": model_exists,
"datasets_dir": DATASETS_DIR,
"models_dir": MODELS_DIR,
})
@app.route('/extract-frames', methods=['POST'])
@require_api_key
def api_extract_frames():
if 'video' not in request.files:
return jsonify({"status": "error", "message": "File video tidak ditemukan."}), 400
user_id = request.form.get('user_id')
target_frames = int(request.form.get('target_frames', 200))
if not user_id:
return jsonify({"status": "error", "message": "user_id wajib diisi."}), 400
video_file = request.files['video']
temp_video = os.path.join(TEMP_DIR, f"enroll_{user_id}_{uuid.uuid4().hex[:8]}.mp4")
try:
video_file.save(temp_video)
output_dir = os.path.join(DATASETS_DIR, str(user_id))
result = extract_frames(temp_video, output_dir, target_frames=target_frames)
logger.info(
f"Extract frames berhasil untuk user {user_id}. "
f"Frames: {result['total_extracted']}"
)
return jsonify({
"status": "success",
"message": f"Berhasil mengekstrak {result['total_extracted']} frame wajah frontal",
**result
})
except Exception as e:
logger.error(f"Extract frames GAGAL untuk user {user_id}: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
finally:
if os.path.exists(temp_video):
os.remove(temp_video)
@app.route('/train-model', methods=['POST'])
@require_api_key
def api_train_model():
data = request.get_json(silent=True) or {}
approved_user_ids = data.get('approved_user_ids')
if not approved_user_ids or not isinstance(approved_user_ids, list):
return jsonify({
"status": "error",
"message": "approved_user_ids wajib diisi sebagai array."
}), 400
approved_str = [str(uid) for uid in approved_user_ids]
try:
logger.info(
f"Memulai training model SVM. "
f"Approved users: {approved_str}"
)
result = train_model(DATASETS_DIR, MODELS_DIR, approved_str)
logger.info(
f"Training selesai. Users: {result['total_users']}, "
f"CV: {result['cv_score']*100:.2f}%, "
f"Test: {result['test_accuracy']*100:.2f}%"
)
return jsonify({
"status": "success",
"message": (
f"Model SVM berhasil dilatih. "
f"{result['total_users']} user, "
f"CV={result['cv_score']*100:.2f}%, "
f"Test={result['test_accuracy']*100:.2f}%"
),
**result
})
except Exception as e:
logger.error(f"Training model GAGAL: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/verify-face', methods=['POST'])
@require_api_key
def api_verify_face():
if 'file' not in request.files:
return jsonify({"status": "error", "message": "File tidak ditemukan."}), 400
user_id = request.form.get('user_id')
is_video = request.form.get('is_video', 'false').lower() == 'true'
if not user_id:
return jsonify({"status": "error", "message": "user_id wajib diisi."}), 400
uploaded_file = request.files['file']
ext = 'mp4' if is_video else 'jpg'
temp_file = os.path.join(TEMP_DIR, f"verify_{user_id}_{uuid.uuid4().hex[:8]}.{ext}")
try:
uploaded_file.save(temp_file)
result = verify_face(MODELS_DIR, user_id, temp_file, is_video=is_video)
logger.info(
f"Verifikasi user {user_id}: "
f"status={result.get('verification_status')}, "
f"match={result.get('match')}, "
f"svm_df={result.get('svm_df')}, "
f"confidence={result.get('confidence')}, "
f"predicted={result.get('predicted_user')}, "
f"blur={result.get('blur_score')}, "
f"frames_approved={result.get('frames_approved')}/{result.get('frames_total')}"
)
return jsonify(result)
except Exception as e:
logger.error(f"Verifikasi GAGAL untuk user {user_id}: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
finally:
if os.path.exists(temp_file):
os.remove(temp_file)
if __name__ == '__main__':
logger.info("=" * 50)
logger.info("MPG HRIS - Flask ML API Server")
logger.info(f"Datasets: {DATASETS_DIR}")
logger.info(f"Models: {MODELS_DIR}")
logger.info("=" * 50)
app.run(host='0.0.0.0', port=5000, debug=True)

View File

@ -1,15 +0,0 @@
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
STORAGE_DIR = os.path.join(BASE_DIR, 'storage')
DATASETS_DIR = os.path.join(STORAGE_DIR, 'face_datasets')
MODELS_DIR = os.path.join(STORAGE_DIR, 'face_models')
TEMP_DIR = os.path.join(STORAGE_DIR, 'temp')
MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB
API_KEY = os.environ.get('FLASK_ML_API_KEY', 'dev-api-key-mpg-hris')
for d in [STORAGE_DIR, DATASETS_DIR, MODELS_DIR, TEMP_DIR]:
os.makedirs(d, exist_ok=True)

View File

@ -1,9 +0,0 @@
flask
flask-cors
opencv-contrib-python-headless
numpy
scikit-learn
scikit-image
joblib
Pillow
gunicorn

View File

@ -1 +0,0 @@
# Services package

View File

@ -1,195 +0,0 @@
import cv2
import os
import numpy as np
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from utils.lbp_features import preprocess_face, FACE_SIZE
BLUR_THRESHOLD = 30.0
MIN_FACE_RATIO = 0.15
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
def get_blur_score(img):
return cv2.Laplacian(img, cv2.CV_64F).var()
def detect_frontal_face(gray_frame):
h, w = gray_frame.shape
min_size = int(min(h, w) * MIN_FACE_RATIO)
faces = face_cascade.detectMultiScale(
gray_frame, scaleFactor=1.1, minNeighbors=5,
minSize=(min_size, min_size)
)
if len(faces) == 0:
faces = face_cascade.detectMultiScale(
gray_frame, scaleFactor=1.05, minNeighbors=4,
minSize=(min_size, min_size)
)
if len(faces) == 0:
return None
faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)
return faces[0]
def estimate_yaw_from_symmetry(gray_face):
h, w = gray_face.shape
mid = w // 2
left_half = gray_face[:, :mid].astype(np.float64)
right_half = cv2.flip(gray_face[:, mid:], 1).astype(np.float64)
min_w = min(left_half.shape[1], right_half.shape[1])
left_half = left_half[:, :min_w]
right_half = right_half[:, :min_w]
if left_half.size == 0 or right_half.size == 0:
return 999.0
left_mean = np.mean(left_half)
right_mean = np.mean(right_half)
diff = abs(left_mean - right_mean)
estimated_yaw = diff * 0.8
return estimated_yaw
def process_frame(frame, gray_frame):
face_rect = detect_frontal_face(gray_frame)
if face_rect is None:
return None, None, "no_face", 0.0, 0.0
(x, y, w, h) = face_rect
padding = int(max(w, h) * 0.1)
x1 = max(0, x - padding)
y1 = max(0, y - padding)
x2 = min(gray_frame.shape[1], x + w + padding)
y2 = min(gray_frame.shape[0], y + h + padding)
face_crop_gray = gray_frame[y1:y2, x1:x2]
face_crop_color = frame[y1:y2, x1:x2]
face_resized = cv2.resize(face_crop_gray, FACE_SIZE, interpolation=cv2.INTER_AREA)
blur_score = get_blur_score(face_resized)
if blur_score < BLUR_THRESHOLD:
return None, None, f"blur ({round(blur_score, 1)})", blur_score, 0.0
yaw_estimate = estimate_yaw_from_symmetry(face_resized)
return face_resized, face_crop_color, "ok", blur_score, yaw_estimate
def extract_frames(video_path, output_dir, target_frames=30):
if not os.path.exists(video_path):
raise Exception(f"Video tidak ditemukan: {video_path}")
if not os.path.exists(output_dir):
os.makedirs(output_dir)
else:
for f in os.listdir(output_dir):
if (f.startswith("frame_") or f.startswith("raw_frame_")) and f.endswith(".jpg"):
os.remove(os.path.join(output_dir, f))
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise Exception("Gagal membuka file video.")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap.get(cv2.CAP_PROP_FPS)
if total_frames <= 0:
raise Exception("Video tidak valid (0 frame).")
candidates = []
frame_idx = 0
skipped_no_face = 0
skipped_blur = 0
skipped_side = 0
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face_img, face_color, status, blur_score, yaw = process_frame(frame, gray)
if face_img is None:
if "blur" in status:
skipped_blur += 1
else:
skipped_no_face += 1
frame_idx += 1
continue
candidates.append({
'frame_idx': frame_idx,
'face_gray': face_img,
'face_color': face_color,
'blur_score': blur_score,
'yaw': yaw
})
frame_idx += 1
cap.release()
if len(candidates) == 0:
raise Exception(
"Tidak ada frame wajah frontal berkualitas yang berhasil diekstrak. "
"Pastikan wajah menghadap depan dengan pencahayaan cukup."
)
candidates.sort(key=lambda c: c['blur_score'], reverse=True)
if len(candidates) > target_frames:
candidates_by_idx = sorted(candidates, key=lambda c: c['frame_idx'])
chunk_size = len(candidates_by_idx) // target_frames
selected = []
for i in range(target_frames):
start = i * chunk_size
end = min(start + chunk_size, len(candidates_by_idx))
chunk = candidates_by_idx[start:end]
best_in_chunk = max(chunk, key=lambda c: c['blur_score'])
selected.append(best_in_chunk)
candidates = sorted(selected, key=lambda c: c['frame_idx'])
saved_count = 0
for cand in candidates:
filename = f"frame_{saved_count:03d}.jpg"
filename_raw = f"raw_frame_{saved_count:03d}.jpg"
cv2.imwrite(
os.path.join(output_dir, filename),
cand['face_gray'],
[int(cv2.IMWRITE_JPEG_QUALITY), 95]
)
cv2.imwrite(os.path.join(output_dir, filename_raw), cand['face_color'])
saved_count += 1
return {
"total_video_frames": total_frames,
"video_fps": round(fps, 1),
"total_candidates": len(candidates),
"total_extracted": saved_count,
"target_frames": target_frames,
"skipped_no_face": skipped_no_face,
"skipped_blur": skipped_blur,
"skipped_side_face": skipped_side,
"output_dir": output_dir
}

View File

@ -1,186 +0,0 @@
import cv2
import os
import sys
import json
import numpy as np
import joblib
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from skimage.feature import local_binary_pattern
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from utils.lbp_features import (
extract_lbp_features, extract_lbp_from_augmented,
augmentasi_lbp, preprocess_face, FACE_SIZE, LBP_SCALES
)
def load_images(dataset_path):
valid_ext = ('.jpg', '.jpeg', '.png')
images = []
if not os.path.exists(dataset_path):
return images
files = sorted([
f for f in os.listdir(dataset_path)
if f.lower().endswith(valid_ext) and (
f.startswith('frame_') or f.startswith('selfie_')
)
])
for filename in files:
filepath = os.path.join(dataset_path, filename)
img = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)
if img is not None:
if img.shape != FACE_SIZE:
img = cv2.resize(img, FACE_SIZE, interpolation=cv2.INTER_AREA)
images.append(img)
return images
def train_model(base_datasets_path, model_output_path, approved_user_ids=None):
if not os.path.exists(base_datasets_path):
raise Exception(f"Base datasets path tidak ditemukan: {base_datasets_path}")
user_images_dict = {}
label_map = {}
user_stats = {}
for folder_name in sorted(os.listdir(base_datasets_path)):
folder_path = os.path.join(base_datasets_path, folder_name)
if not os.path.isdir(folder_path):
continue
user_id = folder_name
if approved_user_ids and user_id not in approved_user_ids:
continue
images = load_images(folder_path)
if len(images) < 10:
continue
label_map[user_id] = user_id
user_images_dict[user_id] = images
if len(label_map) == 0:
raise Exception("Tidak ada user dengan dataset valid (minimal 10 gambar per user).")
if len(label_map) < 2:
raise Exception(
"Minimal 2 user yang sudah di-approve diperlukan untuk melatih model SVM. "
f"Saat ini baru {len(label_map)} user."
)
X_ori = []
y_ori = []
X_aug = []
y_aug = []
for user_id, images in user_images_dict.items():
ori_count = 0
aug_count = 0
for img in images:
proc_img = preprocess_face(img)
features = extract_lbp_features(proc_img)
X_ori.append(features)
y_ori.append(user_id)
ori_count += 1
pola = local_binary_pattern(proc_img, P=8, R=1, method='uniform')
lbp_uint8 = (pola * 255.0 / 9).astype(np.uint8)
variasi = augmentasi_lbp(lbp_uint8)
for aug_img in variasi:
aug_features = extract_lbp_from_augmented(aug_img)
X_aug.append(aug_features)
y_aug.append(user_id)
aug_count += 1
user_stats[user_id] = ori_count + aug_count
X = np.array(X_ori + X_aug)
y = np.array(y_ori + y_aug)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
param_grid = {
'svm__C': [0.1, 1, 10, 20],
'svm__gamma': ['scale', 'auto', 0.01, 0.001, 0.1]
}
pipe_search = Pipeline([
('scaler', StandardScaler()),
('svm', SVC(kernel='rbf', probability=False))
])
grid = GridSearchCV(
pipe_search, param_grid,
cv=5, scoring='accuracy',
refit=False, return_train_score=True, verbose=0
)
grid.fit(X_train, y_train)
best_C = grid.best_params_['svm__C']
best_gamma = grid.best_params_['svm__gamma']
cv_score = grid.best_score_
model_final = Pipeline([
('scaler', StandardScaler()),
('svm', SVC(kernel='rbf', C=best_C, gamma=best_gamma, probability=True))
])
model_final.fit(X_train, y_train)
test_acc = model_final.score(X_test, y_test)
if not os.path.exists(model_output_path):
os.makedirs(model_output_path)
model_file = os.path.join(model_output_path, "face_model.pkl")
scaler_file = os.path.join(model_output_path, "face_scaler.pkl")
labels_file = os.path.join(model_output_path, "face_labels.json")
temp_model_file = os.path.join(model_output_path, "face_model_temp.pkl")
temp_scaler_file = os.path.join(model_output_path, "face_scaler_temp.pkl")
temp_labels_file = os.path.join(model_output_path, "face_labels_temp.json")
joblib.dump(model_final.named_steps['svm'], temp_model_file)
joblib.dump(model_final.named_steps['scaler'], temp_scaler_file)
labels_data = {
"classes": list(model_final.named_steps['svm'].classes_),
"user_ids": list(label_map.keys()),
"best_C": best_C,
"best_gamma": str(best_gamma),
"cv_score": round(cv_score, 4),
"test_accuracy": round(test_acc, 4),
}
with open(temp_labels_file, 'w') as f:
json.dump(labels_data, f, indent=2)
os.replace(temp_model_file, model_file)
os.replace(temp_scaler_file, scaler_file)
os.replace(temp_labels_file, labels_file)
return {
"total_users": len(label_map),
"user_stats": user_stats,
"total_samples": len(X),
"train_samples": len(X_train),
"test_samples": len(X_test),
"feature_dimension": int(X.shape[1]),
"best_C": best_C,
"best_gamma": str(best_gamma),
"cv_score": round(cv_score, 4),
"test_accuracy": round(test_acc, 4),
"classes": list(model_final.named_steps['svm'].classes_),
"model_path": model_file,
"scaler_path": scaler_file,
"labels_path": labels_file
}

View File

@ -1,366 +0,0 @@
import cv2
import os
import sys
import json
import logging
import numpy as np
import joblib
logger = logging.getLogger('flask_ml')
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from utils.lbp_features import extract_lbp_features, preprocess_face, FACE_SIZE
UNKNOWN_LABEL = "unknown"
BLUR_THRESHOLD = 30.0
DF_APPROVED = 1.5
DF_PENDING = 0.5
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
profile_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_profileface.xml'
)
def get_blur_score(img):
return cv2.Laplacian(img, cv2.CV_64F).var()
def fix_exif_rotation(img_path):
try:
from PIL import Image
pil_img = Image.open(img_path)
exif = pil_img.getexif()
orientation = exif.get(274, 1)
if orientation == 3:
pil_img = pil_img.rotate(180, expand=True)
elif orientation == 6:
pil_img = pil_img.rotate(270, expand=True)
elif orientation == 8:
pil_img = pil_img.rotate(90, expand=True)
img_array = np.array(pil_img)
if len(img_array.shape) == 3 and img_array.shape[2] == 3:
img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
return img_array
except ImportError:
return None
except Exception:
return None
def detect_and_crop_face(gray):
h, w = gray.shape
if max(h, w) > 640:
scale = 640.0 / max(h, w)
gray = cv2.resize(gray, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA)
h, w = gray.shape
min_size = int(min(h, w) * 0.1)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(min_size, min_size))
if len(faces) == 0:
faces = face_cascade.detectMultiScale(gray, 1.05, 3, minSize=(min_size, min_size))
if len(faces) == 0:
faces = profile_cascade.detectMultiScale(gray, 1.1, 3, minSize=(min_size, min_size))
if len(faces) == 0:
flipped = cv2.flip(gray, 1)
faces = face_cascade.detectMultiScale(flipped, 1.05, 3, minSize=(min_size, min_size))
if len(faces) > 0:
gray = flipped
if len(faces) == 0:
return None
faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True)
(x, y, fw, fh) = faces[0]
padding = int(max(fw, fh) * 0.1)
x1 = max(0, x - padding)
y1 = max(0, y - padding)
x2 = min(w, x + fw + padding)
y2 = min(h, y + fh + padding)
face_crop = gray[y1:y2, x1:x2]
if face_crop.shape[0] < 10 or face_crop.shape[1] < 10:
return None
face_resized = cv2.resize(face_crop, FACE_SIZE, interpolation=cv2.INTER_AREA)
return face_resized
def preprocess_image(image_path):
if not os.path.exists(image_path):
raise Exception("File gambar tidak ditemukan.")
img = fix_exif_rotation(image_path)
if img is None:
img = cv2.imread(image_path)
if img is None:
raise Exception("Gagal membaca file gambar.")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img
face = detect_and_crop_face(gray)
if face is None:
raise Exception(
"Wajah tidak terdeteksi. Pastikan pencahayaan cukup dan wajah terlihat jelas."
)
blur_score = get_blur_score(face)
if blur_score < BLUR_THRESHOLD:
raise Exception(
f"Foto terlalu buram (Score: {round(blur_score, 1)}). Harap foto ulang."
)
face_preprocessed = preprocess_face(face)
return face_preprocessed, blur_score
def extract_frames_from_video(video_path, target_frames=10):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise Exception("Gagal membuka file video verifikasi.")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames <= 0:
cap.release()
raise Exception("Video tidak valid (0 frame).")
candidates = []
frame_idx = 0
sample_interval = max(1, total_frames // (target_frames * 3))
while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx % sample_interval != 0:
frame_idx += 1
continue
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face_raw = detect_and_crop_face(gray)
if face_raw is None:
frame_idx += 1
continue
blur_score = get_blur_score(face_raw)
if blur_score < BLUR_THRESHOLD:
frame_idx += 1
continue
face_preprocessed = preprocess_face(face_raw)
candidates.append({
'face': face_preprocessed,
'blur_score': blur_score,
'frame_idx': frame_idx,
})
frame_idx += 1
cap.release()
if len(candidates) == 0:
raise Exception(
"Tidak ada frame wajah yang valid dalam video. "
"Pastikan wajah menghadap depan dengan pencahayaan cukup."
)
candidates.sort(key=lambda c: c['blur_score'], reverse=True)
selected = candidates[:target_frames]
return selected
def _classify_frame(features, svm, scaler, expected_user):
classes = [str(c) for c in svm.classes_]
features_s = scaler.transform([features])
df_values = svm.decision_function(features_s)[0]
proba = svm.predict_proba(features_s)[0]
if expected_user in classes:
idx = classes.index(expected_user)
user_df = float(df_values[idx] if hasattr(df_values, '__len__') else df_values)
user_conf = float(proba[idx])
else:
user_df = -999.0
user_conf = 0.0
df_sorted = sorted(enumerate(df_values), key=lambda x: -x[1])
predicted_class = str(classes[df_sorted[0][0]])
top_df = float(df_values[df_sorted[0][0]])
second_df = float(df_values[df_sorted[1][0]]) if len(df_sorted) > 1 else -999.0
df_margin = top_df - second_df
# === Verifikasi 1:1 ===
# Untuk model LBP+SVM, cukup cek skor OVR user sendiri.
# Gap check dihapus karena terlalu ketat untuk variasi pencahayaan/sudut.
gap = top_df - user_df if predicted_class != expected_user else 0.0
if user_df >= DF_APPROVED:
status = "APPROVED"
is_match = True
elif user_df >= DF_PENDING:
status = "PENDING"
is_match = True
else:
status = "REJECTED"
is_match = False
return {
'is_match': is_match,
'status': status,
'user_df': user_df,
'confidence': user_conf,
'predicted_class': predicted_class,
'df_margin': round(df_margin, 4),
'gap': round(gap, 4),
}
def _aggregate_frame_results(frame_results):
total = len(frame_results)
approved = sum(1 for r in frame_results if r['status'] == 'APPROVED')
pending = sum(1 for r in frame_results if r['status'] == 'PENDING')
rejected = sum(1 for r in frame_results if r['status'] == 'REJECTED')
approved_ratio = approved / total
pending_ratio = pending / total
avg_df = float(np.mean([r['user_df'] for r in frame_results]))
avg_conf = float(np.mean([r['confidence'] for r in frame_results]))
if approved_ratio >= 0.4:
final_status = "APPROVED"
final_match = True
elif (approved_ratio + pending_ratio) >= 0.5:
final_status = "PENDING"
final_match = True
else:
final_status = "REJECTED"
final_match = False
return {
'verification_status': final_status,
'match': final_match,
'avg_df': round(avg_df, 4),
'confidence': round(avg_conf, 4),
'frames_total': total,
'frames_approved': approved,
'frames_pending': pending,
'frames_rejected': rejected,
'approved_ratio': round(approved_ratio, 3),
}
def verify_face(model_dir, user_id, input_path, is_video=False):
model_file = os.path.join(model_dir, "face_model.pkl")
scaler_file = os.path.join(model_dir, "face_scaler.pkl")
labels_file = os.path.join(model_dir, "face_labels.json")
if not os.path.exists(model_file):
raise Exception("Model wajah belum tersedia. Belum ada data training.")
if not os.path.exists(scaler_file):
raise Exception("File scaler belum tersedia.")
if not os.path.exists(labels_file):
raise Exception("File label belum tersedia.")
svm = joblib.load(model_file)
scaler = joblib.load(scaler_file)
expected_user = str(user_id)
if is_video:
try:
frames = extract_frames_from_video(input_path, target_frames=10)
except Exception as e_vid:
return {
"status": "success",
"match": False,
"confidence": 0,
"verification_status": "PREPROCESSING_FAILED",
"message": str(e_vid),
"blur_score": 0,
"frames_total": 0,
}
frame_results = []
blur_scores = []
for i, frame_data in enumerate(frames):
face = frame_data['face']
blur_scores.append(frame_data['blur_score'])
features = extract_lbp_features(face)
result = _classify_frame(features, svm, scaler, expected_user)
frame_results.append(result)
logger.info(
f" Frame {i}: predicted={result['predicted_class']}, "
f"user_df={result['user_df']:.4f}, "
f"gap={result['gap']:.4f}, "
f"conf={result['confidence']:.4f}, "
f"status={result['status']}"
)
aggregated = _aggregate_frame_results(frame_results)
avg_blur = float(np.mean(blur_scores))
return {
"status": "success",
"match": bool(aggregated['match']),
"verification_status": aggregated['verification_status'],
"confidence": aggregated['confidence'],
"svm_df": aggregated['avg_df'],
"blur_score": round(avg_blur, 1),
"frames_total": aggregated['frames_total'],
"frames_approved": aggregated['frames_approved'],
"frames_pending": aggregated['frames_pending'],
"frames_rejected": aggregated['frames_rejected'],
"approved_ratio": aggregated['approved_ratio'],
"predicted_user": expected_user if aggregated['match'] else "unknown",
"actual_predicted": max(set(r['predicted_class'] for r in frame_results), key=lambda c: sum(1 for r in frame_results if r['predicted_class'] == c)),
"expected_user": expected_user,
"user_id": int(user_id) if str(user_id).isdigit() else str(user_id),
"message": None,
}
else:
try:
processed_face, blur_score = preprocess_image(input_path)
except Exception as e_proc:
return {
"status": "success",
"match": False,
"confidence": 0,
"verification_status": "PREPROCESSING_FAILED",
"message": str(e_proc),
"blur_score": 0,
}
features = extract_lbp_features(processed_face)
result = _classify_frame(features, svm, scaler, expected_user)
return {
"status": "success",
"match": bool(result['is_match']),
"verification_status": result['status'],
"confidence": float(round(result['confidence'], 4)),
"svm_df": float(round(result['user_df'], 4)),
"predicted_user": result['predicted_class'],
"expected_user": expected_user,
"blur_score": float(round(blur_score, 1)),
"user_id": int(user_id) if str(user_id).isdigit() else str(user_id),
"message": None,
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show More