/**
* @module IFace
*/
import ICAO from './ICAO.js'
import ImageSource from './ImageSource.js'
import { BASE_URL, checkResponse } from './Common.js'
// This is not exported as we may eventually detect this from the plugin
// See: Iface.matchModes()
const VALID_MODES = ['accurate', 'balanced', 'fast', 'accurate_server']
/**
* Probe
* @classdesc Probe Class for Innovatrics IFace SDK
*
* @see {@link IFace}
*/
export class Probe extends ImageSource {
/**
* Instantiate an IFace Probe
* @constructor
* @param {string} source - The image source for the probe.
* @param {string} [type] - The type of data in the image source, valid values
* are 'url', 'base64', and 'dataurl'. If not provided 'base64' is the default.
*
* @example
* const candidate = new Candidate('/person/123.jpg', '123', 'url')
*/
constructor (source, type = 'base64') {
super(source, type)
}
}
/**
* Candidate
* @classdesc Candidate Class for Innovatrics IFace SDK
* @extends ImageSource
* @see {@link IFace}
*/
export class Candidate extends ImageSource {
id
// TODO: Support setting different modes on individual candidates
// an example usecase for this is to set 'accurate_server' the most recent
// photo, and 'fast' for the remaining set (since they likely have already
// been verified at some point) In the plugin we would have to re-template the
// probe for each additional mode and it doesn't make sense to do so until
// the plugin is checking each photo against the probe in a separate thread.
mode
/**
* Instantiate an IFace Candidate
* @constructor
* @param {string} source - The image source for the candidate.
* @param {string} id - The ID for the candidate when the match results are
* returned this Id will be returned with it's respective results.
* @param {string} [type] - The type of data in the image source, valid values
* are 'url', 'base64', and 'dataurl'. If not provided 'base64' is the default.
*
* @example
* const candidate = new Candidate('/person/123.jpg', '123', 'url')
*/
constructor (source, id, type = 'base64') {
super(source, type)
if (id) {
this.id = id
} else {
throw new Error('Candidate ID not provided')
}
}
toJSON () {
return {
id: this.id,
base64: this.imageData
}
}
}
/**
* IFace
* @classdesc Class for Innovatrics IFace SDK
*
*/
export class IFace extends ICAO {
/**
* Instantiate an IFace plugin
* @constructor
* @param {string} [baseURL] - Protocol, domain, and port for the service.
*
* @example
* const iface = new IFace()
*/
constructor (baseUrl = BASE_URL) {
super('capture_verify_iface', baseUrl)
}
/**
* Return list of available face matching modes
*
* @returns {string[]} List of available face matching modes
*
* @async
* @example
* const iface = new IFace()
* const modes = await iface.matchModes()
* // do something with modes, such as build a <select> element
* const select = document.createElement('select')
* modes.forEach(mode => {
* const option = document.createElement('option')
* option.value = mode
* select.appendChild(mode)
* })
* document.body.appendChild(select)
*/
async matchModes () {
// This is async as we may later get this from the plugin at runtime
return VALID_MODES
}
/**
* Check the ICAO compliance of an image
* @param {string} image - A base64 encoded image.
*
* @param {string|bool} [crop] - If falsy image will not be cropped, otherwise
* the value will be interpreted as the preferred crop method which will vary
* by plugin implementation.
*
* @param {string} [background=#ffffff] - Hexadecimal color that will fill in
* any part of the cropped image that falls outside the original source image.
*
* @returns {object} Results of the ICAO check, this may vary by plugin
* implementation.
*
* @async
* @example
* // Take a photo with a Canon Camera and perform ICAO checks on the
* // returned image
* const camera = new CanonCamera()
* const photo = await camera.takePhoto('base64')
* const iface = new IFace()
* const results = await iface.icao(photo)
* const img = document.createElement('img')
* img.src = 'data:image/jpeg;base64,' + results.cropped
* console.log(`found ${result.faces_found} faces in the image`)
* document.body.appendChild(img)
*/
async icao (image, cropMethod = false, cropBackground) {
return await this.check(image, cropMethod, cropBackground)
}
/**
* Perform a facial match against one or more photos.
*
* @param {object|string} probe - Either a Probe object or a base64 encoded
* image that is being compared or searched against one or more candidates
*
* @param {object[]} candidates - An array of candidate objects against which
* the probe photo is compared.
* @param {string} candidates[].id - Id of the photo, when the results of the
* matched are returned, this id will be returned so results can be matched
* back to their original image. Must be less than 16 characters.
* @param {string} candidates[].base64 - Base64 encoded string containing the
* photo.
* @param {string} [candidates[].mode] - Matching mode to use for this photo.
* If left unspecified will use the mode provided in the mode parameter.
*
* @param {string} [mode=fast] - Matching mode to use for all images, can be
* overriden on each candidate if desired. Valid values are: 'accurate',
* 'balanced', 'fast', and 'accurate_server'.
*
* @async
* @example
* // Create an iface object
* const iface = new IFace()
*
* // Obtain a photo for the probe photo
* const probe = await camera.takePhoto('base64')
*
* // Create a candidate set from the person's previous photos
* const candidates = [
* new Candidate('/person/1/photo/2.jpg', '3', 'url'),
* new Candidate('/person/1/photo/1.jpg', '2', 'url')
* ]
*
* // Match the probe to all the candidates using the 'balanced' mode
* const results = await iface.match(probe, candidates, 'balanced')
*
* // use the results
* console.log(results)
*/
async match (probe, candidates, mode = 'fast') {
if (VALID_MODES.indexOf(mode) === -1) {
throw new Error('Invalid mode provided')
}
// If this is a Probe object fetch it's image data
if (typeof probe === 'object' && typeof probe.data === 'function') {
await probe.data()
}
// And fetch data for candidates
const asyncOperations = candidates.map(async candidate => {
if (typeof candidate === 'object' &&
typeof candidate.data === 'function') {
await candidate.data()
}
})
// Wait for all async operations to complete
await Promise.all(asyncOperations)
const body = {
probe,
mode,
candidates
}
const options = {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
}
const url = `${this.baseUrl}/plugin/${this.id}/face/match`
const response = await fetch(url, options)
await checkResponse(response)
return await response.json()
}
}
export default IFace