Files
presensi/app/Modules/Attendance/Controllers/FaceVerifyController.php
2026-03-05 14:37:36 +07:00

106 lines
3.7 KiB
PHP

<?php
namespace App\Modules\Attendance\Controllers;
use App\Core\BaseApiController;
use App\Modules\Face\Models\StudentFaceModel;
use App\Modules\Face\Services\FaceService;
use CodeIgniter\HTTP\ResponseInterface;
/**
* FaceVerifyController
*
* POST /api/attendance/verify-face
* Body: { "student_id": 123, "image": "data:image/jpeg;base64,..." }
*
* Strategi: Option 1 — bandingkan probe hanya dengan embedding milik student_id kandidat.
*/
class FaceVerifyController extends BaseApiController
{
public function verify(): ResponseInterface
{
$payload = $this->request->getJSON(true) ?? [];
$studentId = (int) ($payload['student_id'] ?? 0);
$imageData = (string) ($payload['image'] ?? '');
if ($studentId < 1) {
return $this->errorResponse('student_id wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
if ($imageData === '') {
return $this->errorResponse('image (base64) wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
}
$tmpFile = tempnam(sys_get_temp_dir(), 'probe_');
try {
$raw = $this->decodeBase64Image($imageData);
if ($raw === null) {
throw new \RuntimeException('Format gambar tidak valid (harus base64)');
}
file_put_contents($tmpFile, $raw);
$faceService = new FaceService();
$probe = $faceService->extractEmbeddingWithQuality($tmpFile)['embedding'];
$faceModel = new StudentFaceModel();
$rows = $faceModel->where('student_id', $studentId)->findAll();
if (empty($rows)) {
return $this->errorResponse('Belum ada embedding wajah untuk siswa ini', null, null, ResponseInterface::HTTP_NOT_FOUND);
}
$bestSim = -1.0;
$bestSource = null;
foreach ($rows as $row) {
$embedding = json_decode($row['embedding'] ?? '[]', true);
if (! is_array($embedding) || $embedding === []) {
continue;
}
$sim = $faceService->cosineSimilarity($probe, array_map('floatval', $embedding));
if ($sim > $bestSim) {
$bestSim = $sim;
$bestSource = $row['source'] ?? null;
}
}
if ($bestSim < 0) {
return $this->errorResponse('Tidak ada embedding valid untuk siswa ini', null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
}
$threshold = $faceService->getDefaultThreshold();
$status = $bestSim >= $threshold ? 'match' : 'no_match';
$data = [
'student_id' => $studentId,
'similarity' => $bestSim,
'threshold' => $threshold,
'matched_source'=> $bestSource,
'status' => $status,
];
return $this->successResponse($data, 'Face verification processed');
} catch (\Throwable $e) {
return $this->errorResponse('Gagal verifikasi wajah: ' . $e->getMessage(), null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
} finally {
if (is_file($tmpFile)) {
@unlink($tmpFile);
}
}
}
protected function decodeBase64Image(string $input): ?string
{
$input = trim($input);
if ($input === '') {
return null;
}
if (strpos($input, 'base64,') !== false) {
$parts = explode('base64,', $input, 2);
$input = $parts[1];
}
$data = base64_decode($input, true);
return $data === false ? null : $data;
}
}