UNPKG

ekyc-sdk-arm

Version:

Automated Recognition Module

651 lines (646 loc) 24.1 kB
import * as FaceAPI from 'face-api.js'; import axios from 'axios'; var Question; (function (Question) { Question["FacingLeft"] = "FacingLeft"; Question["FacingRight"] = "FacingRight"; Question["MouthOpen"] = "MouthOpen"; Question["Smiling"] = "Smiling"; })(Question || (Question = {})); var ProcessType; (function (ProcessType) { ProcessType["GET_RESULT"] = "GET_RESULT"; ProcessType["SAVE_TRANSACTION"] = "SAVE_TRANSACTION"; ProcessType["ERROR_API"] = "ERROR_API"; })(ProcessType || (ProcessType = {})); var TypeCall; (function (TypeCall) { TypeCall["CONSUMER"] = "CONSUMER"; TypeCall["APIKEY"] = "APIKEY"; TypeCall["JWT"] = "JWT"; })(TypeCall || (TypeCall = {})); class LivenessService { AxiosCall; Path; ApiKey; TypeCall; constructor(api_key, path_api, type) { this.Path = path_api; this.ApiKey = api_key; this.TypeCall = type; var headers = {}; switch (type) { case TypeCall.APIKEY: headers = { "x-api-key": this.ApiKey }; break; case TypeCall.CONSUMER: headers = { "x-consumer-custom-id": this.ApiKey }; break; case TypeCall.JWT: headers = { "Authorization": `Bearer ${this.ApiKey}` }; break; } this.AxiosCall = axios.create({ baseURL: this.Path, headers: headers }); } async GetConfig() { try { const response = await this.AxiosCall.get("/v1/liveness/config").then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { throw new Error('different error than axios'); } } } async CreateSession(param) { try { const response = await this.AxiosCall.post("/v1/liveness/session", param).then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { throw new Error('different error than axios'); } } } async SaveTransaction(param) { const paramFrom = new FormData(); paramFrom.append('image', param.image); paramFrom.append('is_timeout', param.is_timeout.toString()); paramFrom.append('session_id', param.session_id); paramFrom.append('question_no', param.question_no.toString()); paramFrom.append('round', param.round.toString()); paramFrom.append('question', param.question); try { const response = await this.AxiosCall.post("/v1/liveness/save_trx", paramFrom).then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { const err = error; throw err; } } } SetPathAPI(path_api) { this.Path = path_api; this.AxiosCall = axios.create({ baseURL: this.Path }); } GetPathAPI() { return this.Path; } SetApiKey(api_key) { this.ApiKey = api_key; } GetApiKey() { return this.ApiKey; } SetTypeCall(type) { this.TypeCall = type; var headers = {}; switch (type) { case TypeCall.APIKEY: headers = { "x-api-key": this.ApiKey }; break; case TypeCall.CONSUMER: headers = { "x-consumer-custom-id": this.ApiKey }; break; case TypeCall.JWT: headers = { "Authentication": `Bearer ${this.ApiKey}` }; break; } this.AxiosCall = axios.create({ baseURL: this.Path, headers: headers }); } GetTypeCall() { return this.TypeCall; } async Verify(request) { const paramFrom = new FormData(); paramFrom.append('cust_uuid', request.cust_uuid); paramFrom.append('source_image', request.source_image); try { const response = await this.AxiosCall.post("/v1/liveness/verify", paramFrom, { headers: { 'Content-Type': 'multipart/form-data' } }).then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { throw new Error('different error than axios'); } } } async GetResult(session_id) { const paramFrom = new FormData(); paramFrom.append('session_id', session_id); try { const response = await this.AxiosCall.post("/v1/liveness/get_result", paramFrom, { headers: { 'Content-Type': 'multipart/form-data' } }).then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { throw new Error('different error than axios'); } } } async CheckStatusService(cust_uuid) { try { const response = await this.AxiosCall.get(`/v1/liveness/status?cust_uuid=${cust_uuid}`).then(res => res.data); return response; } catch (error) { if (axios.isAxiosError(error)) { throw error.response?.data; } else { throw new Error('different error than axios'); } } } } class LivenessSDK { ApiKey = ""; PathAPI = "http://localhost:8000"; Cust_UUID = ""; LivenessService; MinConfident = 0.5; Round = 0; QuestionNo = 0; SessionID = ""; ProcessStatus = false; centerConfident = 0.1; ConfigChannel = { questions: 0, question_list: [], question_timeout_in_second: 0, retry_proof: 0, retry_verification: 0, session_expired_in_second: 0, }; SessionQuestions; constructor(api_key, path_api = "", type, minConfident = 0.5) { if (path_api != "") { this.PathAPI = path_api; } this.ApiKey = api_key; this.SessionQuestions = []; this.LivenessService = new LivenessService(this.ApiKey, this.PathAPI, type); this.MinConfident = minConfident; } async GetConfig() { try { const response = await this.LivenessService.GetConfig(); this.ConfigChannel = this.CheckFormatConfig(response); return this.ConfigChannel; } catch (error) { throw error; } } GetConfidentConfig() { return this.MinConfident; } CheckFormatConfig(config) { if (config.questions * 1) { config.questions *= 1; } if (config.question_timeout_in_second * 1) { config.question_timeout_in_second *= 1; } if (config.retry_proof * 1) { config.retry_proof *= 1; } if (config.retry_verification * 1) { config.retry_verification *= 1; } if (config.session_expired_in_second * 1) { config.session_expired_in_second *= 1; } return config; } async CreateSession(cust_uuid) { this.Cust_UUID = cust_uuid; const param = { cust_uuid: this.Cust_UUID, }; try { const response = await this.LivenessService.CreateSession(param); this.Round = response.round; this.QuestionNo = 1; this.ConfigChannel = this.CheckFormatConfig(response.config); this.SessionID = response.session_id; var tempQuestion = ""; var currentQuestion = ""; if (this.SessionQuestions.length != 0) { tempQuestion = this.SessionQuestions.join(":"); currentQuestion = this.SessionQuestions[this.QuestionNo - 1]; } for (let index = 0; index < 10; index++) { this.SessionQuestions = []; while (this.SessionQuestions.length < this.ConfigChannel.questions) { const question_index = Math.floor(Math.random() * this.ConfigChannel.question_list.length); if (this.SessionQuestions.includes(this.ConfigChannel.question_list[question_index]) || (this.SessionQuestions.length == 0 && this.ConfigChannel.question_list[question_index] == currentQuestion)) { continue; } else { this.SessionQuestions.push(this.ConfigChannel.question_list[question_index]); } } if (this.SessionQuestions.join(":") != tempQuestion) { break; } } return response; } catch (error) { if (axios.isAxiosError(error)) { throw error; } else { throw error; } } } GetQuestionSession() { return this.SessionQuestions; } GetCurrentQuestion() { var currentQuestion; if (this.QuestionNo <= this.SessionQuestions.length) { currentQuestion = { questions: this.SessionQuestions, question_no: this.QuestionNo, current_question: this.SessionQuestions[this.QuestionNo - 1], }; } else { currentQuestion = { questions: this.SessionQuestions, current_question: "", question_no: this.QuestionNo, }; } return currentQuestion; } GetSessionID() { return this.SessionID; } async SaveTransaction(image, is_timeout = false) { var parameterRequest; // const result = await this.ProcessImage(image); const result = true; if (is_timeout || result) { parameterRequest = { image: image, is_timeout: is_timeout, session_id: this.SessionID, question_no: this.QuestionNo, round: this.Round, question: Question[this.SessionQuestions[this.QuestionNo - 1]], }; var responseValue; try { const response = await this.LivenessService.SaveTransaction(parameterRequest); if (response.condition_valid) { if (this.QuestionNo === this.ConfigChannel.questions) { const resultResponse = await this.LivenessService.GetResult(this.SessionID); responseValue = response; responseValue.is_timeout = false; responseValue.session_status = true; responseValue.session = {}; responseValue.result = resultResponse; } else { const responseDetail = response; responseValue = responseDetail; responseValue.is_timeout = false; responseValue.session_status = true; responseValue.session = {}; responseValue.result = {}; this.QuestionNo += 1; } } else { const responseError = response; responseValue = { session_id: responseError.session_id, condition_valid: responseError.condition_valid, is_timeout: false, session_status: true, details: {}, session: {}, result: {}, }; const resultResponse = await this.LivenessService.GetResult(this.SessionID); if (this.CheckPropertyInObj(responseError, "details", "reason")) { switch (responseError.details.reason) { case "session_expire": responseValue.session_status = false; break; case "timeout": responseValue.is_timeout = true; break; } if (responseError.details.reason == "session_expire" || responseError.details.reason == "timeout") { responseValue = { session_id: responseError.session_id, condition_valid: responseError.condition_valid, is_timeout: false, session_status: true, details: {}, session: responseError.details, result: resultResponse, }; } } else { responseValue = { session_id: responseError.session_id, condition_valid: responseError.condition_valid, is_timeout: false, session_status: true, details: responseError.details, session: {}, result: resultResponse, }; } } return responseValue; } catch (error) { if (axios.isAxiosError(error)) { throw error; } else { const err = error; if (err.error.type === "session_expire") { try { await this.LivenessService.GetResult(this.SessionID); } catch (error) { throw error; } } throw err; } } } else { return false; } } CheckPropertyInObj(obj, ...args) { return args.reduce((obj, level) => obj && obj[level], obj); } async LoadModels(path) { const MODEL_URL = path; await Promise.all([ // FaceAPI.loadTinyFaceDetectorModel(MODEL_URL), // FaceAPI.loadTinyYolov2Model(MODEL_URL), // FaceAPI.loadAgeGenderModel(MODEL_URL), FaceAPI.loadFaceLandmarkModel(MODEL_URL), FaceAPI.loadFaceLandmarkTinyModel(MODEL_URL), FaceAPI.loadFaceRecognitionModel(MODEL_URL), FaceAPI.loadFaceExpressionModel(MODEL_URL), FaceAPI.loadSsdMobilenetv1Model(MODEL_URL), ]); } GetMeanPosition(l) { return l .map((a) => [a.x, a.y]) .reduce((a, b) => [a[0] + b[0], a[1] + b[1]]) .map((a) => a / l.length); } GetTop(l) { return l.map((a) => a.y).reduce((a, b) => Math.min(a, b)); } async ProcessImage(image_file) { var url_file; if (typeof image_file == "string") { url_file = image_file; } else { url_file = URL.createObjectURL(image_file); } const image = await FaceAPI.fetchImage(url_file); const options = new FaceAPI.SsdMobilenetv1Options({ minConfidence: this.MinConfident, }); const result = await FaceAPI.detectSingleFace(image, options) .withFaceLandmarks(true) .withFaceDescriptor() .withFaceExpressions(); var answer = []; if (result) { const eyeRight = this.GetMeanPosition(result.landmarks.getRightEye()); const eyeLeft = this.GetMeanPosition(result.landmarks.getLeftEye()); const nose = this.GetMeanPosition(result.landmarks.getNose()); const mouth = this.GetMeanPosition(result.landmarks.getMouth()); const jaw = this.GetTop(result.landmarks.getJawOutline()); const mouthUpper = result.landmarks.getMouth()[14].y; const mouthLower = result.landmarks.getMouth()[18].y; const mouthOffset = mouthLower - mouthUpper; const rx = (jaw - mouth[1]) / result.detection.box.height; const ry = (eyeLeft[0] + (eyeRight[0] - eyeLeft[0]) / 2 - nose[0]) / result.detection.box.width; if (ry < -0.075 && rx < 0.02) { answer.push(Question.FacingLeft.toString()); } if (ry > 0.065 && rx < 0.02) { answer.push(Question.FacingRight.toString()); } if (result.expressions.happy >= 0.8) { answer.push(Question.Smiling.toString()); } if (mouthOffset >= 15) { answer.push(Question.MouthOpen.toString()); } } if (answer.length == 0) { return false; } if (this.SessionQuestions.length <= this.QuestionNo) { if (answer.includes(this.SessionQuestions[this.QuestionNo - 1])) { return true; } else { return false; } } else { return false; } } SetAPIKey(api_key) { this.ApiKey = api_key; this.LivenessService.SetApiKey(this.ApiKey); } GetAPIKey() { return this.ApiKey; } SetPathAPI(path_api) { this.PathAPI = path_api; this.LivenessService.SetPathAPI(path_api); } GetPathAPI() { return this.PathAPI; } SetTypeCall(type) { this.LivenessService.SetTypeCall(type); } GetTypeCall() { return this.LivenessService.GetTypeCall(); } setCenterConfident(centerConfident) { this.centerConfident = centerConfident; } async DetectFace(image) { var url_file; if (typeof image == "string") { url_file = image; } else { url_file = URL.createObjectURL(image); } const imageBase = await FaceAPI.fetchImage(url_file); const options = new FaceAPI.SsdMobilenetv1Options({ minConfidence: this.MinConfident, }); const result = await FaceAPI.detectAllFaces(imageBase, options); var response = { num_faces: result.length, detect_face: result.length > 0 ? true : false, face_left: false, face_right: false, face_center: false, open_month: false, smiling: false, ry: 0, rx: 0, }; if (result.length == 1) { const options = new FaceAPI.SsdMobilenetv1Options({ minConfidence: this.MinConfident, }); const processFace = await FaceAPI.detectSingleFace(imageBase, options) .withFaceLandmarks(true) .withFaceDescriptor() .withFaceExpressions(); if (processFace) { const eyeRight = this.GetMeanPosition(processFace.landmarks.getRightEye()); const eyeLeft = this.GetMeanPosition(processFace.landmarks.getLeftEye()); const nose = this.GetMeanPosition(processFace.landmarks.getNose()); const mouth = this.GetMeanPosition(processFace.landmarks.getMouth()); const jaw = this.GetTop(processFace.landmarks.getJawOutline()); const mouthUpper = processFace.landmarks.getMouth()[14].y; const mouthLower = processFace.landmarks.getMouth()[18].y; const mouthOffset = mouthLower - mouthUpper; const rx = (jaw - mouth[1]) / processFace.detection.box.height; const ry = (eyeLeft[0] + (eyeRight[0] - eyeLeft[0]) / 2 - nose[0]) / processFace.detection.box.width; response.ry = ry; response.rx = rx; const face_val = Number(ry.toFixed(2)); if (ry < -0.08 && rx < 0.015) { response.face_left = true; } if (ry > 0.06 && rx < 0.015) { response.face_right = true; } if (processFace.expressions.happy >= 0.8) { response.smiling = true; } if (mouthOffset >= 15) { response.open_month = true; } const start = processFace.detection.box.x; const end = processFace.detection.box.y + processFace.detection.box.width; const sizeXStart = start; const sizeXEnd = processFace.alignedRect.imageWidth - end; const spaceXSize = sizeXStart >= sizeXEnd ? sizeXStart - sizeXEnd : sizeXEnd - sizeXStart; const startY = processFace.detection.box.y; const endY = processFace.detection.box.y + processFace.detection.box.height; const sizeYStart = startY; const sizeYEnd = processFace.alignedRect.imageHeight - endY; const spaceYSize = sizeYStart >= sizeYEnd ? sizeYStart - sizeYEnd : sizeYEnd - sizeYStart; if (spaceYSize <= processFace.alignedRect.imageHeight * this.centerConfident && spaceXSize <= processFace.alignedRect.imageWidth * this.centerConfident && face_val > -0.06 && face_val < 0.07 && rx < -0.3) { response.face_center = true; } } } return response; } async Verify(cust_uuid, source_image) { var param; param = { cust_uuid: cust_uuid, source_image: source_image, }; try { const response = await this.LivenessService.Verify(param); return response; } catch (error) { throw error; } } async CheckServiceStatus(cust_uuid = "") { const cust_uuid_param = cust_uuid != "" ? cust_uuid : this.Cust_UUID; try { const response = await this.LivenessService.CheckStatusService(cust_uuid_param); return response; } catch (error) { throw error; } } } export { LivenessSDK };