You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

196 lines
8.3 KiB

/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
const KioskConfirm = require("hr_attendance.kiosk_confirm")
const session = require('web.session');
var rpc = require('web.rpc');
const MODEL_URL = '/face_recognized_attendance_login/static/src/js/weights';
faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL);
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL);
faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL);
faceapi.nets.tinyFaceDetector.load(MODEL_URL);
faceapi.nets.faceLandmark68TinyNet.load(MODEL_URL);
faceapi.nets.faceExpressionNet.load(MODEL_URL);
faceapi.nets.ageGenderNet.load(MODEL_URL);
patch(KioskConfirm.prototype,'face_recognized_attendance_login.kiosk',{
events: {
"click .o_hr_attendance_back_button": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); },
"click .o_hr_attendance_sign_in_out_icon": _.debounce(async function () {
await this.startWebcam();
}, 200, true),
},
// -------To start the camera-------
async startWebcam() {
const video = this.el.querySelector('#video');
try {
const video = this.el.querySelector('#video');
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('getUserMedia is not supported in this browser.');
}
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
};
this.faceRecognition(video);
} catch (error) {
console.error('An error occurred while accessing the camera:', error);
this.__parentedParent.notifications.add(
'Unable to access webcam. Please check your device permissions or use a supported browser.', {
title: 'Webcam Error',
type: 'danger',
sticky: true,
className: "p-4"
}
);
}
},
// -----To start the face recognition-----------
async faceRecognition(video) {
const labeledFaceDescriptors = await this.getLabeledFaceDescriptions(video);
if (!labeledFaceDescriptors) {
console.error('No labeled face descriptors available.');
this.stopWebcamAndDetection();
return;
}
if (!this.faceMatcher) {
const labeledFaceDescriptors = await this.getLabeledFaceDescriptions();
this.faceMatcher = new faceapi.FaceMatcher([labeledFaceDescriptors]);
if (labeledFaceDescriptors && labeledFaceDescriptors.descriptor) {
this.faceMatcher = new faceapi.FaceMatcher([labeledFaceDescriptors.descriptor]);
} else {
console.error("Could not get face descriptor from reference image");
this.__parentedParent.notification.add("Failed to initialize face recognition, Please upload a new, properly formatted image.", {
type: "danger",
title: "Image detection failed!",
});
this.stopRecognition(video);
return;
}
}
let attendanceMarked = false;
let notificationSent = false;
this.faceRecognitionInterval = setInterval(async () => {
try {
const detections = await faceapi
.detectAllFaces(video)
.withFaceLandmarks()
.withFaceDescriptors();
if (detections.length === 0) {
if (!notificationSent) {
this.__parentedParent.notifications.add(
'No face detected.', {
title: 'Detection Failed!',
type: 'danger',
sticky: false,
className: "p-4"
}
);
notificationSent = true;
}
this.stopWebcamAndDetection();
return;
}
detections.forEach((detection) => {
const match = this.faceMatcher.findBestMatch(detection.descriptor);
if (match._distance < 0.4 && !attendanceMarked) {
const modal = this.el.querySelector('#video');
if (modal) {
modal.style.display = 'none';
}
attendanceMarked = true;
notificationSent = false;
this.markAttendance();
clearInterval(this.faceRecognitionInterval);
this.stopWebcamAndDetection();
}
});
if (!attendanceMarked && !notificationSent) {
this.__parentedParent.notifications.add(
'Face is not recognized.', {
title: 'No Match!',
type: 'danger',
sticky: false,
className: "p-4"
}
);
notificationSent = true;
this.stopWebcamAndDetection();
}
} catch (error) {
console.error('Error during face recognition:', error);
this.stopWebcamAndDetection();
}
}, 100);
},
// ---------Fetch labeled face descriptions (employee's face data)------
async getLabeledFaceDescriptions(video) {
const employee_image_base64 = await rpc.query({
model: 'hr.employee',
method: 'get_kiosk_image',
args: [this.employee_id]
});
if (employee_image_base64) {
const employee_image = new Image();
employee_image.src = "data:image/jpeg;base64," + employee_image_base64;
try {
const detections = await faceapi
.detectSingleFace(employee_image)
.withFaceLandmarks()
.withFaceExpressions()
.withFaceDescriptor();
if (!detections) {
console.error('No face detected in the image.');
this.__parentedParent.notifications.add(
'No face detected in the image.Please upload a new, properly formatted image in the profile.', {
title: 'Image detection failed!',
type: 'danger',
sticky: false,
className: "p-4"
}
);
return;
}
return detections;
} catch (error) {
console.error('Error during face detection:', error);
}
} else {
console.error('No image data found for the employee.');
}
},
// ----------Function to stop webcam and face detection-----
stopWebcamAndDetection() {
const video = this.el.querySelector('#video');
if (video.srcObject) {
const stream = video.srcObject;
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null; //
}
if (this.faceRecognitionInterval) {
clearInterval(this.faceRecognitionInterval);
this.faceRecognitionInterval = null;
}
this.faceMatcher = null;
},
// ------------Redirecting to welcome/checkout page ----------------------------------
markAttendance() {
const self = this;
this._rpc({
model: 'hr.employee',
method: 'attendance_manual',
args: [[this.employee_id], 'hr_attendance.hr_attendance_action_my_attendances']
}).then((result) => {
if (result.action) {
self.do_action(result.action);
} else if (result.warning) {
self.do_warn(result.warning);
}
}).catch((error) => {
console.error('Error marking attendance:', error);
});
},
})