From ff0f9fd915ac577b4c80424e96dc928796d0d229 Mon Sep 17 00:00:00 2001 From: Shai Ben-Tovim Date: Wed, 21 Mar 2018 12:58:27 +0200 Subject: [PATCH] - face: Added support for LargePersonGroup operations - face:identify() updated to support LargePersonGroups (breaking change) - face:verify() updated to support LargePersonGroups (non-breaking) - face: Updated all list() methods to support start/top optional params - face: updated tests for all additions and changes - updated api docs with additions and changes --- README.md | 349 ++++++++++++++++++++++++++++++++++++-- dist/face.js | 421 +++++++++++++++++++++++++++++++++++++++++++--- src/face.js | 411 +++++++++++++++++++++++++++++++++++++++++--- test/test_face.js | 296 +++++++++++++++++++++++++++++++- 4 files changed, 1417 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index a4cfb58..5da720d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ For the full documentation, please see the API reference below. * [.trainingStatus(personGroupId)](#Client.face.personGroup.trainingStatus) ⇒ Promise * [.trainingStart(personGroupId)](#Client.face.personGroup.trainingStart) ⇒ Promise * [.update(personGroupId, name, userData)](#Client.face.personGroup.update) ⇒ Promise - * [.list()](#Client.face.personGroup.list) ⇒ Promise + * [.list(options)](#Client.face.personGroup.list) ⇒ Promise * [.person](#Client.face.person) : object * [.addFace(personGroupId, personId, options)](#Client.face.person.addFace) ⇒ Promise * [.deleteFace(personGroupId, personId, persistedFaceId)](#Client.face.person.deleteFace) ⇒ Promise @@ -106,11 +106,29 @@ For the full documentation, please see the API reference below. * [.get(personGroupId, personId)](#Client.face.person.get) ⇒ Promise * [.update(personGroupId, personId, name, userData)](#Client.face.person.update) ⇒ Promise * [.list(personGroupId)](#Client.face.person.list) ⇒ Promise + * [.largePersonGroup](#Client.face.largePersonGroup) : object + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.create) ⇒ Promise + * [.delete(largePersonGroupId)](#Client.face.largePersonGroup.delete) ⇒ Promise + * [.get(largePersonGroupId)](#Client.face.largePersonGroup.get) ⇒ Promise + * [.trainingStatus(largePersonGroupId)](#Client.face.largePersonGroup.trainingStatus) ⇒ Promise + * [.trainingStart(largePersonGroupId)](#Client.face.largePersonGroup.trainingStart) ⇒ Promise + * [.update(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.update) ⇒ Promise + * [.list(options)](#Client.face.largePersonGroup.list) ⇒ Promise + * [.largePersonGroupPerson](#Client.face.largePersonGroupPerson) : object + * [.addFace(largePersonGroupId, personId, options)](#Client.face.largePersonGroupPerson.addFace) ⇒ Promise + * [.deleteFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.deleteFace) ⇒ Promise + * [.updateFace(largePersonGroupId, personId, persistedFaceId, userData)](#Client.face.largePersonGroupPerson.updateFace) ⇒ Promise + * [.getFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.getFace) ⇒ Promise + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroupPerson.create) ⇒ Promise + * [.delete(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.delete) ⇒ Promise + * [.get(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.get) ⇒ Promise + * [.update(largePersonGroupId, personId, name, userData)](#Client.face.largePersonGroupPerson.update) ⇒ Promise + * [.list(largePersonGroupId)](#Client.face.largePersonGroupPerson.list) ⇒ Promise * _inner_ * [~detect(options)](#Client.face..detect) ⇒ Promise * [~similar(sourceFace, options)](#Client.face..similar) ⇒ Promise * [~grouping(faces)](#Client.face..grouping) ⇒ Promise - * [~identify(faces, personGroupId, maxNumOfCandidatesReturned, confidenceThreshold)](#Client.face..identify) ⇒ Promise + * [~identify(faces, options)](#Client.face..identify) ⇒ Promise * [~verify(faces)](#Client.face..verify) ⇒ Promise * [.text](#Client.text) : object * [~proof(text, preContextText, postContextText, market)](#Client.text..proof) ⇒ Promise @@ -187,7 +205,7 @@ Analyze the emotions of one or more faces in an image. * [.trainingStatus(personGroupId)](#Client.face.personGroup.trainingStatus) ⇒ Promise * [.trainingStart(personGroupId)](#Client.face.personGroup.trainingStart) ⇒ Promise * [.update(personGroupId, name, userData)](#Client.face.personGroup.update) ⇒ Promise - * [.list()](#Client.face.personGroup.list) ⇒ Promise + * [.list(options)](#Client.face.personGroup.list) ⇒ Promise * [.person](#Client.face.person) : object * [.addFace(personGroupId, personId, options)](#Client.face.person.addFace) ⇒ Promise * [.deleteFace(personGroupId, personId, persistedFaceId)](#Client.face.person.deleteFace) ⇒ Promise @@ -198,11 +216,29 @@ Analyze the emotions of one or more faces in an image. * [.get(personGroupId, personId)](#Client.face.person.get) ⇒ Promise * [.update(personGroupId, personId, name, userData)](#Client.face.person.update) ⇒ Promise * [.list(personGroupId)](#Client.face.person.list) ⇒ Promise + * [.largePersonGroup](#Client.face.largePersonGroup) : object + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.create) ⇒ Promise + * [.delete(largePersonGroupId)](#Client.face.largePersonGroup.delete) ⇒ Promise + * [.get(largePersonGroupId)](#Client.face.largePersonGroup.get) ⇒ Promise + * [.trainingStatus(largePersonGroupId)](#Client.face.largePersonGroup.trainingStatus) ⇒ Promise + * [.trainingStart(largePersonGroupId)](#Client.face.largePersonGroup.trainingStart) ⇒ Promise + * [.update(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.update) ⇒ Promise + * [.list(options)](#Client.face.largePersonGroup.list) ⇒ Promise + * [.largePersonGroupPerson](#Client.face.largePersonGroupPerson) : object + * [.addFace(largePersonGroupId, personId, options)](#Client.face.largePersonGroupPerson.addFace) ⇒ Promise + * [.deleteFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.deleteFace) ⇒ Promise + * [.updateFace(largePersonGroupId, personId, persistedFaceId, userData)](#Client.face.largePersonGroupPerson.updateFace) ⇒ Promise + * [.getFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.getFace) ⇒ Promise + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroupPerson.create) ⇒ Promise + * [.delete(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.delete) ⇒ Promise + * [.get(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.get) ⇒ Promise + * [.update(largePersonGroupId, personId, name, userData)](#Client.face.largePersonGroupPerson.update) ⇒ Promise + * [.list(largePersonGroupId)](#Client.face.largePersonGroupPerson.list) ⇒ Promise * _inner_ * [~detect(options)](#Client.face..detect) ⇒ Promise * [~similar(sourceFace, options)](#Client.face..similar) ⇒ Promise * [~grouping(faces)](#Client.face..grouping) ⇒ Promise - * [~identify(faces, personGroupId, maxNumOfCandidatesReturned, confidenceThreshold)](#Client.face..identify) ⇒ Promise + * [~identify(faces, options)](#Client.face..identify) ⇒ Promise * [~verify(faces)](#Client.face..verify) ⇒ Promise @@ -328,7 +364,7 @@ not from the detect method. * [.trainingStatus(personGroupId)](#Client.face.personGroup.trainingStatus) ⇒ Promise * [.trainingStart(personGroupId)](#Client.face.personGroup.trainingStart) ⇒ Promise * [.update(personGroupId, name, userData)](#Client.face.personGroup.update) ⇒ Promise - * [.list()](#Client.face.personGroup.list) ⇒ Promise + * [.list(options)](#Client.face.personGroup.list) ⇒ Promise @@ -415,11 +451,18 @@ Updates an existing person group's display name and userData. -##### personGroup.list() ⇒ Promise -Lists all person groups in the current subscription. +##### personGroup.list(options) ⇒ Promise +List person groups’s pesonGroupId, name, and userData. **Kind**: static method of [personGroup](#Client.face.personGroup) **Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| options | object | List opentions | +| options.start | string | List person groups from the least personGroupId greater than the "start". It contains no more than 64 characters. Default is empty. | +| options.top | integer | The number of person groups to list, ranging in [1, 1000]. Default is 1000. | + #### face.person : object @@ -568,6 +611,277 @@ Lists all persons in a person group, with the person information. | options.start | string | List persons from the least personId greater than the "start". It contains no more than 64 characters. Default is empty. | | options.top | Number | Optional count of persons to return. Valid range is [1,1000]. (Default: 1000) | + + +#### face.largePersonGroup : object +**Kind**: static namespace of [face](#Client.face) + +* [.largePersonGroup](#Client.face.largePersonGroup) : object + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.create) ⇒ Promise + * [.delete(largePersonGroupId)](#Client.face.largePersonGroup.delete) ⇒ Promise + * [.get(largePersonGroupId)](#Client.face.largePersonGroup.get) ⇒ Promise + * [.trainingStatus(largePersonGroupId)](#Client.face.largePersonGroup.trainingStatus) ⇒ Promise + * [.trainingStart(largePersonGroupId)](#Client.face.largePersonGroup.trainingStart) ⇒ Promise + * [.update(largePersonGroupId, name, userData)](#Client.face.largePersonGroup.update) ⇒ Promise + * [.list(options)](#Client.face.largePersonGroup.list) ⇒ Promise + + + +##### largePersonGroup.create(largePersonGroupId, name, userData) ⇒ Promise +Create a new large person group with user-specified largePersonGroupId, name, and optional userData. +A large person group is the container of the uploaded person data, including face images and face recognition feature, and up to 1,000,000 people. +The Identify() method searches person faces in a specified large person group. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | Numbers, en-us letters in lower case, '-', '_'. Max length: 64 | +| name | string | Person group display name. The maximum length is 128. | +| userData | string | User-provided data attached to the group. The size limit is 16KB. | + + + +##### largePersonGroup.delete(largePersonGroupId) ⇒ Promise +Deletes an existing large person group. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | ID of large person group to delete | + + + +##### largePersonGroup.get(largePersonGroupId) ⇒ Promise +Gets an existing large person group. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | ID of large person group to get | + + + +##### largePersonGroup.trainingStatus(largePersonGroupId) ⇒ Promise +To check large person group training status completed or still ongoing. +LargePersonGroup Training is an asynchronous operation triggered by LargePersonGroup - Train API. +Training time depends on the number of person entries, and their faces in a large person group. +It could be in seconds, or up to half an hour for 1,000,000 persons. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | ID of large person group to get | + + + +##### largePersonGroup.trainingStart(largePersonGroupId) ⇒ Promise +Submit a large person group training task. +Training is a crucial step that only a trained large person group can be used by Face - Identify. +The training task is an asynchronous task. Training time depends on the number of person entries, +and their faces in a large person group. It could be in several seconds, or up to half a hour for 1,000,000 persons. +To check training completion, please use LargePersonGroup - Get Training Status. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | ID of large person group to get | + + + +##### largePersonGroup.update(largePersonGroupId, name, userData) ⇒ Promise +Update an existing large person group's name and userData. +The properties keep unchanged if they are not in request body. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | ID of large person group to update | +| name | string | Person group display name. The maximum length is 128. | +| userData | string | User-provided data attached to the group. The size limit is 16KB. | + + + +##### largePersonGroup.list(options) ⇒ Promise +List all existing large person groups’s largePesonGroupId, name, and userData. + +**Kind**: static method of [largePersonGroup](#Client.face.largePersonGroup) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| options | object | List opentions | +| options.start | string | List large person groups from the least largePersonGroupId greater than the "start". It contains no more than 64 characters. Default is empty. | +| options.top | integer | The number of large person groups to list, ranging in [1, 1000]. Default is 1000. | + + + +#### face.largePersonGroupPerson : object +**Kind**: static namespace of [face](#Client.face) + +* [.largePersonGroupPerson](#Client.face.largePersonGroupPerson) : object + * [.addFace(largePersonGroupId, personId, options)](#Client.face.largePersonGroupPerson.addFace) ⇒ Promise + * [.deleteFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.deleteFace) ⇒ Promise + * [.updateFace(largePersonGroupId, personId, persistedFaceId, userData)](#Client.face.largePersonGroupPerson.updateFace) ⇒ Promise + * [.getFace(largePersonGroupId, personId, persistedFaceId)](#Client.face.largePersonGroupPerson.getFace) ⇒ Promise + * [.create(largePersonGroupId, name, userData)](#Client.face.largePersonGroupPerson.create) ⇒ Promise + * [.delete(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.delete) ⇒ Promise + * [.get(largePersonGroupId, personId)](#Client.face.largePersonGroupPerson.get) ⇒ Promise + * [.update(largePersonGroupId, personId, name, userData)](#Client.face.largePersonGroupPerson.update) ⇒ Promise + * [.list(largePersonGroupId)](#Client.face.largePersonGroupPerson.list) ⇒ Promise + + + +##### largePersonGroupPerson.addFace(largePersonGroupId, personId, options) ⇒ Promise +Add a face image to a person into a large person group for face identification or verification. +Adding/deleting faces to/from a same person will be processed sequentially. +Adding/deleting faces to/from different persons are processed in parallel. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person that the face is added to. | +| options | object | The source specification. | +| options.url | string | URL to image to be used. | +| options.path | string | Path to image to be used. | +| options.data | string | Image as a binary buffer | +| options.userData | string | Optional. Attach user data to person's face. The maximum length is 1024. | +| options.targetFace | object | Optional. The rectangle of the face in the image. | + + + +##### largePersonGroupPerson.deleteFace(largePersonGroupId, personId, persistedFaceId) ⇒ Promise +Delete a face from a person in a large person group. +Face data and image related to this face entry will be also deleted. +Adding/deleting faces to/from a same person will be processed sequentially. +Adding/deleting faces to/from different persons are processed in parallel. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise; successful response is empty + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person that the face is removed from. | +| persistedFaceId | string | The ID of the face to be deleted. | + + + +##### largePersonGroupPerson.updateFace(largePersonGroupId, personId, persistedFaceId, userData) ⇒ Promise +Update a person persisted face's userData field. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person that the face is updated on. | +| persistedFaceId | string | The ID of the face to be updated. | +| userData | string | Optional. Attach user data to person's face. The maximum length is 1024. | + + + +##### largePersonGroupPerson.getFace(largePersonGroupId, personId, persistedFaceId) ⇒ Promise +Retrieve person face information. +The persisted person face is specified by its largePersonGroupId, personId and persistedFaceId. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person that the face is to get from. | +| persistedFaceId | string | The ID of the face to get. | + + + +##### largePersonGroupPerson.create(largePersonGroupId, name, userData) ⇒ Promise +Create a new person in a specified large person group. +To add face to this person, please call LargePersonGroup PersonFace - Add. +The number of persons has a subscription limit. Free subscription amount is 1000 persons. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| name | string | Target person's display name. The maximum length is 128. | +| userData | string | Optional fields for user-provided data attached to a person. Size limit is 16KB. | + + + +##### largePersonGroupPerson.delete(largePersonGroupId, personId) ⇒ Promise +Delete an existing person from a large person group. +All stored person data, and face images in the person entry will be deleted. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person to delete. | + + + +##### largePersonGroupPerson.get(largePersonGroupId, personId) ⇒ Promise +Retrieve a person's name and userData, and the persisted faceIds representing the registered person face image. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person to get. | + + + +##### largePersonGroupPerson.update(largePersonGroupId, personId, name, userData) ⇒ Promise +Updates a person's information. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | largePersonGroupId of the target large person group. | +| personId | string | The target person's id. | +| name | string | Target person's display name. The maximum length is 128. | +| userData | string | Optional fields for user-provided data attached to a person. Size limit is 16KB. | + + + +##### largePersonGroupPerson.list(largePersonGroupId) ⇒ Promise +List all persons’ information in the specified large person group, +including personId, name, userData and persistedFaceIds of registered person faces. + +**Kind**: static method of [largePersonGroupPerson](#Client.face.largePersonGroupPerson) +**Returns**: Promise - - Promise resolving with the resulting JSON + +| Param | Type | Description | +| --- | --- | --- | +| largePersonGroupId | string | The target person's person group. | +| options.start | string | List persons from the least personId greater than the "start". It contains no more than 64 characters. Default is empty. | +| options.top | Number | Optional count of persons to return. Valid range is [1,1000]. (Default: 1000) | + #### face~detect(options) ⇒ Promise @@ -643,13 +957,12 @@ limit of 100 faces. -#### face~identify(faces, personGroupId, maxNumOfCandidatesReturned, confidenceThreshold) ⇒ Promise -Identifies persons from a person group by one or more input faces. -To recognize which person a face belongs to, Face Identification needs a person group -that contains number of persons. Each person contains one or more faces. After a person -group prepared, it should be trained to make it ready for identification. Then the -identification API compares the input face to those persons' faces in person group and -returns the best-matched candidate persons, ranked by confidence. +#### face~identify(faces, options) ⇒ Promise +1-to-many identification to find the closest matches of the specific query person face(s) from a person group or large person group. +For each face in the faceIds array, Face Identify will compute similarities between the query face and all the faces in the person group +(given by personGroupId) or large person group (given by largePersonGroupId), and return candidate person(s) +for that face ranked by similarity confidence. +The person group/large person group should be trained to make it ready for identification. **Kind**: inner method of [face](#Client.face) **Returns**: Promise - - Promise resolving with the resulting JSON @@ -657,9 +970,11 @@ returns the best-matched candidate persons, ranked by confidence. | Param | Type | Description | | --- | --- | --- | | faces | Array.<string> | Array of faceIds to use | -| personGroupId | string | Id of person group from which faces will be identified | -| maxNumOfCandidatesReturned | Number | Optional max number of candidates per face (default=1, max=5) | -| confidenceThreshold | Number | Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). | +| options | object | Identify options | +| options.personGroupId | string | Id of person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) | +| options.largePersonGroupId | string | Id of large person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) | +| options.maxNumOfCandidatesReturned | Number | Optional max number of candidates per face (default=1, max=5) | +| options.confidenceThreshold | Number | Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). | diff --git a/dist/face.js b/dist/face.js index 2f5481a..f35edd9 100644 --- a/dist/face.js +++ b/dist/face.js @@ -13,6 +13,8 @@ var identifyPath = '/identify'; var verifyPath = '/verify'; var personGroupPath = '/persongroups'; var personPath = '/persongroups'; +var largePersonGroupPath = '/largepersongroups'; +var largePersonGroupPersonPath = '/largepersongroups'; var faceListPath = '/facelists'; /** @@ -262,31 +264,36 @@ var face = function face(key, host) { } /** - * Identifies persons from a person group by one or more input faces. - * To recognize which person a face belongs to, Face Identification needs a person group - * that contains number of persons. Each person contains one or more faces. After a person - * group prepared, it should be trained to make it ready for identification. Then the - * identification API compares the input face to those persons' faces in person group and - * returns the best-matched candidate persons, ranked by confidence. + * 1-to-many identification to find the closest matches of the specific query person face(s) from a person group or large person group. + * For each face in the faceIds array, Face Identify will compute similarities between the query face and all the faces in the person group + * (given by personGroupId) or large person group (given by largePersonGroupId), and return candidate person(s) + * for that face ranked by similarity confidence. + * The person group/large person group should be trained to make it ready for identification. * * @param {string[]} faces - Array of faceIds to use - * @param {string} personGroupId - Id of person group from which faces will be identified - * @param {Number} maxNumOfCandidatesReturned - Optional max number of candidates per face (default=1, max=5) - * @param {Number} confidenceThreshold - Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). + * @param {object} options - Identify options + * @param {string} options.personGroupId - Id of person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) + * @param {string} options.largePersonGroupId - Id of large person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) + * @param {Number} options.maxNumOfCandidatesReturned - Optional max number of candidates per face (default=1, max=5) + * @param {Number} options.confidenceThreshold - Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). * @return {Promise} - Promise resolving with the resulting JSON */ - function identify(faces, personGroupId, maxNumOfCandidatesReturned, confidenceThreshold) { + function identify(faces, options) { return new _Promise(function (resolve, reject) { var body = { faceIds: faces, - personGroupId: personGroupId, - maxNumOfCandidatesReturned: maxNumOfCandidatesReturned || 1 + maxNumOfCandidatesReturned: options.maxNumOfCandidatesReturned || 1 }; - - if (confidenceThreshold !== undefined) { - body.confidenceThreshold = confidenceThreshold; + if (options.personGroupId !== undefined) { + body.personGroupId = options.personGroupId; + } + if (options.largePersonGroupId !== undefined) { + body.largePersonGroupId = options.largePersonGroupId; } + if (options.confidenceThreshold !== undefined) { + body.confidenceThreshold = options.confidenceThreshold; + } request.post({ uri: host + rootPath + identifyPath, headers: { 'Ocp-Apim-Subscription-Key': key }, @@ -318,10 +325,10 @@ var face = function face(key, host) { return reject('Faces array must contain two face ids'); } } else if (faces) { - if (faces.faceId && faces.personId && faces.personGroupId) { + if (faces.faceId && faces.personId && (faces.personGroupId || faces.largePersonGroupId)) { body = faces; } else { - return reject('Faces object must have faceId, personId, and personGroupId fields'); + return reject('Faces object must have faceId, personId, and either personGroupId or largePersonGroupId fields'); } } else { return reject('Faces must either be an array containing two face ids, or' + 'an object with the fields \'faceId\', \'personId\', and \'personGroupId\''); @@ -629,14 +636,18 @@ var face = function face(key, host) { }, /** - * Lists all person groups in the current subscription. + * List person groups’s pesonGroupId, name, and userData. + * @param {object} options - List opentions + * @param {string} options.start - List person groups from the least personGroupId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {integer} options.top - The number of person groups to list, ranging in [1, 1000]. Default is 1000. * @return {Promise} - Promise resolving with the resulting JSON */ - list: function list() { + list: function list(options) { return new _Promise(function (resolve, reject) { request({ uri: host + rootPath + personGroupPath, - headers: { 'Ocp-Apim-Subscription-Key': key } + headers: { 'Ocp-Apim-Subscription-Key': key }, + qs: options }, function (error, response) { response.body = JSON.parse(response.body); return _return(error, response, resolve, reject); @@ -847,6 +858,372 @@ var face = function face(key, host) { } }; + /** + * @namespace + * @memberof Client.face + */ + var largePersonGroup = { + /** + * Create a new large person group with user-specified largePersonGroupId, name, and optional userData. + * A large person group is the container of the uploaded person data, including face images and face recognition feature, and up to 1,000,000 people. + * The Identify() method searches person faces in a specified large person group. + * + * @param {string} largePersonGroupId - Numbers, en-us letters in lower case, '-', '_'. Max length: 64 + * @param {string} name - Person group display name. The maximum length is 128. + * @param {string} userData - User-provided data attached to the group. The size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + create: function create(largePersonGroupId, name, userData) { + return new _Promise(function (resolve, reject) { + request.put({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: { 'Ocp-Apim-Subscription-Key': key }, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Deletes an existing large person group. + * + * @param {string} largePersonGroupId - ID of large person group to delete + * @return {Promise} - Promise resolving with the resulting JSON + */ + 'delete': function _delete(largePersonGroupId) { + return new _Promise(function (resolve, reject) { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Gets an existing large person group. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + get: function get(largePersonGroupId) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * To check large person group training status completed or still ongoing. + * LargePersonGroup Training is an asynchronous operation triggered by LargePersonGroup - Train API. + * Training time depends on the number of person entries, and their faces in a large person group. + * It could be in seconds, or up to half an hour for 1,000,000 persons. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + trainingStatus: function trainingStatus(largePersonGroupId) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId + '/training', + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Submit a large person group training task. + * Training is a crucial step that only a trained large person group can be used by Face - Identify. + * The training task is an asynchronous task. Training time depends on the number of person entries, + * and their faces in a large person group. It could be in several seconds, or up to half a hour for 1,000,000 persons. + * To check training completion, please use LargePersonGroup - Get Training Status. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + trainingStart: function trainingStart(largePersonGroupId) { + return new _Promise(function (resolve, reject) { + request.post({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId + '/train', + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Update an existing large person group's name and userData. + * The properties keep unchanged if they are not in request body. + * + * @param {string} largePersonGroupId - ID of large person group to update + * @param {string} name - Person group display name. The maximum length is 128. + * @param {string} userData - User-provided data attached to the group. The size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + update: function update(largePersonGroupId, name, userData) { + return new _Promise(function (resolve, reject) { + request.patch({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: { 'Ocp-Apim-Subscription-Key': key }, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * List all existing large person groups’s largePesonGroupId, name, and userData. + * @param {object} options - List opentions + * @param {string} options.start - List large person groups from the least largePersonGroupId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {integer} options.top - The number of large person groups to list, ranging in [1, 1000]. Default is 1000. + * @return {Promise} - Promise resolving with the resulting JSON + */ + list: function list(options) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPath, + headers: { 'Ocp-Apim-Subscription-Key': key }, + qs: options + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + } + }; + + /** + * @namespace + * @memberof Client.face + */ + var largePersonGroupPerson = { + /** + * Add a face image to a person into a large person group for face identification or verification. + * Adding/deleting faces to/from a same person will be processed sequentially. + * Adding/deleting faces to/from different persons are processed in parallel. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is added to. + * @param {object} options - The source specification. + * @param {string} options.url - URL to image to be used. + * @param {string} options.path - Path to image to be used. + * @param {string} options.data - Image as a binary buffer + * @param {string} options.userData - Optional. Attach user data to person's face. The maximum length is 1024. + * @param {object} options.targetFace - Optional. The rectangle of the face in the image. + * @return {Promise} - Promise resolving with the resulting JSON + */ + addFace: function addFace(largePersonGroupId, personId, options) { + var qs = {}; + if (options) { + qs.userData = options.userData; + if (options.targetFace) { + qs.targetFace = [options.targetFace.left, options.targetFace.top, options.targetFace.width, options.targetFace.height].join(); + } + } + var url = largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces'; + return _postImage(url, options, qs); + }, + + /** + * Delete a face from a person in a large person group. + * Face data and image related to this face entry will be also deleted. + * Adding/deleting faces to/from a same person will be processed sequentially. + * Adding/deleting faces to/from different persons are processed in parallel. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is removed from. + * @param {string} persistedFaceId - The ID of the face to be deleted. + * @return {Promise} - Promise; successful response is empty + */ + deleteFace: function deleteFace(largePersonGroupId, personId, persistedFaceId) { + return new _Promise(function (resolve, reject) { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Update a person persisted face's userData field. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is updated on. + * @param {string} persistedFaceId - The ID of the face to be updated. + * @param {string} userData - Optional. Attach user data to person's face. The maximum length is 1024. + * @return {Promise} - Promise resolving with the resulting JSON + */ + updateFace: function updateFace(largePersonGroupId, personId, persistedFaceId, userData) { + return new _Promise(function (resolve, reject) { + request.patch({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: { 'Ocp-Apim-Subscription-Key': key }, + json: true, + body: userData ? { userData: userData } : {} + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Retrieve person face information. + * The persisted person face is specified by its largePersonGroupId, personId and persistedFaceId. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is to get from. + * @param {string} persistedFaceId - The ID of the face to get. + * @return {Promise} - Promise resolving with the resulting JSON + */ + getFace: function getFace(largePersonGroupId, personId, persistedFaceId) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Create a new person in a specified large person group. + * To add face to this person, please call LargePersonGroup PersonFace - Add. + * The number of persons has a subscription limit. Free subscription amount is 1000 persons. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} name - Target person's display name. The maximum length is 128. + * @param {string} userData - Optional fields for user-provided data attached to a person. Size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + create: function create(largePersonGroupId, name, userData) { + return new _Promise(function (resolve, reject) { + request.post({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons', + headers: { 'Ocp-Apim-Subscription-Key': key }, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Delete an existing person from a large person group. + * All stored person data, and face images in the person entry will be deleted. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person to delete. + * @return {Promise} - Promise resolving with the resulting JSON + */ + 'delete': function _delete(largePersonGroupId, personId) { + return new _Promise(function (resolve, reject) { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Retrieve a person's name and userData, and the persisted faceIds representing the registered person face image. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person to get. + * @return {Promise} - Promise resolving with the resulting JSON + */ + get: function get(largePersonGroupId, personId) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: { 'Ocp-Apim-Subscription-Key': key } + }, function (error, response) { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Updates a person's information. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person's id. + * @param {string} name - Target person's display name. The maximum length is 128. + * @param {string} userData - Optional fields for user-provided data attached to a person. Size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + update: function update(largePersonGroupId, personId, name, userData) { + return new _Promise(function (resolve, reject) { + request.patch({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: { 'Ocp-Apim-Subscription-Key': key }, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * List all persons’ information in the specified large person group, + * including personId, name, userData and persistedFaceIds of registered person faces. + * + * @param {string} largePersonGroupId - The target person's person group. + * @param {string} options.start - List persons from the least personId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {Number} options.top - Optional count of persons to return. Valid range is [1,1000]. (Default: 1000) + * @return {Promise} - Promise resolving with the resulting JSON + */ + list: function list(largePersonGroupId, options) { + return new _Promise(function (resolve, reject) { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons', + headers: { 'Ocp-Apim-Subscription-Key': key }, + qs: options + }, function (error, response) { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + } + }; + return { detect: detect, similar: similar, @@ -855,7 +1232,9 @@ var face = function face(key, host) { verify: verify, faceList: faceList, personGroup: personGroup, - person: person + person: person, + largePersonGroup: largePersonGroup, + largePersonGroupPerson: largePersonGroupPerson }; }; diff --git a/src/face.js b/src/face.js index 6da262c..8bca56f 100644 --- a/src/face.js +++ b/src/face.js @@ -11,6 +11,8 @@ const identifyPath = '/identify'; const verifyPath = '/verify'; const personGroupPath = '/persongroups'; const personPath = '/persongroups'; +const largePersonGroupPath = '/largepersongroups'; +const largePersonGroupPersonPath = '/largepersongroups'; const faceListPath = '/facelists'; /** @@ -254,31 +256,36 @@ var face = function (key, host) { } /** - * Identifies persons from a person group by one or more input faces. - * To recognize which person a face belongs to, Face Identification needs a person group - * that contains number of persons. Each person contains one or more faces. After a person - * group prepared, it should be trained to make it ready for identification. Then the - * identification API compares the input face to those persons' faces in person group and - * returns the best-matched candidate persons, ranked by confidence. + * 1-to-many identification to find the closest matches of the specific query person face(s) from a person group or large person group. + * For each face in the faceIds array, Face Identify will compute similarities between the query face and all the faces in the person group + * (given by personGroupId) or large person group (given by largePersonGroupId), and return candidate person(s) + * for that face ranked by similarity confidence. + * The person group/large person group should be trained to make it ready for identification. * * @param {string[]} faces - Array of faceIds to use - * @param {string} personGroupId - Id of person group from which faces will be identified - * @param {Number} maxNumOfCandidatesReturned - Optional max number of candidates per face (default=1, max=5) - * @param {Number} confidenceThreshold - Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). + * @param {object} options - Identify options + * @param {string} options.personGroupId - Id of person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) + * @param {string} options.largePersonGroupId - Id of large person group from which faces will be identified (personGroupId and largePersonGroupId should not be provided at the same time) + * @param {Number} options.maxNumOfCandidatesReturned - Optional max number of candidates per face (default=1, max=5) + * @param {Number} options.confidenceThreshold - Confidence threshold of identification, used to judge whether one face belong to one person. The range of confidenceThreshold is [0, 1] (default specified by algorithm). * @return {Promise} - Promise resolving with the resulting JSON */ - function identify(faces, personGroupId, maxNumOfCandidatesReturned, confidenceThreshold) { + function identify(faces, options) { return new _Promise(function (resolve, reject) { let body = { faceIds: faces, - personGroupId: personGroupId, - maxNumOfCandidatesReturned: maxNumOfCandidatesReturned || 1 + maxNumOfCandidatesReturned: options.maxNumOfCandidatesReturned || 1 }; - - if (confidenceThreshold !== undefined) { - body.confidenceThreshold = confidenceThreshold; + if (options.personGroupId !== undefined) { + body.personGroupId = options.personGroupId; + } + if (options.largePersonGroupId !== undefined) { + body.largePersonGroupId = options.largePersonGroupId; } + if (options.confidenceThreshold !== undefined) { + body.confidenceThreshold = options.confidenceThreshold; + } request.post({ uri: host + rootPath + identifyPath, headers: {'Ocp-Apim-Subscription-Key': key}, @@ -308,10 +315,10 @@ var face = function (key, host) { return reject('Faces array must contain two face ids'); } } else if (faces) { - if (faces.faceId && faces.personId && faces.personGroupId) { + if (faces.faceId && faces.personId && (faces.personGroupId || faces.largePersonGroupId)) { body = faces; } else { - return reject('Faces object must have faceId, personId, and personGroupId fields'); + return reject('Faces object must have faceId, personId, and either personGroupId or largePersonGroupId fields'); } } else { return reject('Faces must either be an array containing two face ids, or' + @@ -618,14 +625,18 @@ var face = function (key, host) { }, /** - * Lists all person groups in the current subscription. + * List person groups’s pesonGroupId, name, and userData. + * @param {object} options - List opentions + * @param {string} options.start - List person groups from the least personGroupId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {integer} options.top - The number of person groups to list, ranging in [1, 1000]. Default is 1000. * @return {Promise} - Promise resolving with the resulting JSON */ - list: function () { + list: function (options) { return new _Promise((resolve, reject) => { request({ uri: host + rootPath + personGroupPath, - headers: {'Ocp-Apim-Subscription-Key': key} + headers: {'Ocp-Apim-Subscription-Key': key}, + qs: options }, function (error, response) { response.body = JSON.parse(response.body); return _return(error, response, resolve, reject); @@ -826,6 +837,362 @@ var face = function (key, host) { } }; + /** + * @namespace + * @memberof Client.face + */ + var largePersonGroup = { + /** + * Create a new large person group with user-specified largePersonGroupId, name, and optional userData. + * A large person group is the container of the uploaded person data, including face images and face recognition feature, and up to 1,000,000 people. + * The Identify() method searches person faces in a specified large person group. + * + * @param {string} largePersonGroupId - Numbers, en-us letters in lower case, '-', '_'. Max length: 64 + * @param {string} name - Person group display name. The maximum length is 128. + * @param {string} userData - User-provided data attached to the group. The size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + create: function (largePersonGroupId, name, userData) { + return new _Promise((resolve, reject) => { + request.put({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: {'Ocp-Apim-Subscription-Key': key}, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Deletes an existing large person group. + * + * @param {string} largePersonGroupId - ID of large person group to delete + * @return {Promise} - Promise resolving with the resulting JSON + */ + delete: function (largePersonGroupId) { + return new _Promise((resolve, reject) => { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Gets an existing large person group. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + get: function (largePersonGroupId) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * To check large person group training status completed or still ongoing. + * LargePersonGroup Training is an asynchronous operation triggered by LargePersonGroup - Train API. + * Training time depends on the number of person entries, and their faces in a large person group. + * It could be in seconds, or up to half an hour for 1,000,000 persons. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + trainingStatus: function (largePersonGroupId) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId + '/training', + headers: {'Ocp-Apim-Subscription-Key': key} + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Submit a large person group training task. + * Training is a crucial step that only a trained large person group can be used by Face - Identify. + * The training task is an asynchronous task. Training time depends on the number of person entries, + * and their faces in a large person group. It could be in several seconds, or up to half a hour for 1,000,000 persons. + * To check training completion, please use LargePersonGroup - Get Training Status. + * + * @param {string} largePersonGroupId - ID of large person group to get + * @return {Promise} - Promise resolving with the resulting JSON + */ + trainingStart: function (largePersonGroupId) { + return new _Promise((resolve, reject) => { + request.post({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId + '/train', + headers: {'Ocp-Apim-Subscription-Key': key} + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Update an existing large person group's name and userData. + * The properties keep unchanged if they are not in request body. + * + * @param {string} largePersonGroupId - ID of large person group to update + * @param {string} name - Person group display name. The maximum length is 128. + * @param {string} userData - User-provided data attached to the group. The size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + update: function (largePersonGroupId, name, userData) { + return new _Promise((resolve, reject) => { + request.patch({ + uri: host + rootPath + largePersonGroupPath + '/' + largePersonGroupId, + headers: {'Ocp-Apim-Subscription-Key': key}, + json: true, + body: { + name: name, + userData: userData + } + }, function (error, response) { + return _return(error, response, resolve, reject); + }); + }); + }, + + /** + * List all existing large person groups’s largePesonGroupId, name, and userData. + * @param {object} options - List opentions + * @param {string} options.start - List large person groups from the least largePersonGroupId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {integer} options.top - The number of large person groups to list, ranging in [1, 1000]. Default is 1000. + * @return {Promise} - Promise resolving with the resulting JSON + */ + list: function (options) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPath, + headers: {'Ocp-Apim-Subscription-Key': key}, + qs: options + }, function (error, response) { + response.body = JSON.parse(response.body); + return _return(error, response, resolve, reject); + }); + }); + } + }; + + /** + * @namespace + * @memberof Client.face + */ + var largePersonGroupPerson = { + /** + * Add a face image to a person into a large person group for face identification or verification. + * Adding/deleting faces to/from a same person will be processed sequentially. + * Adding/deleting faces to/from different persons are processed in parallel. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is added to. + * @param {object} options - The source specification. + * @param {string} options.url - URL to image to be used. + * @param {string} options.path - Path to image to be used. + * @param {string} options.data - Image as a binary buffer + * @param {string} options.userData - Optional. Attach user data to person's face. The maximum length is 1024. + * @param {object} options.targetFace - Optional. The rectangle of the face in the image. + * @return {Promise} - Promise resolving with the resulting JSON + */ + addFace: function (largePersonGroupId, personId, options) { + var qs = {}; + if (options) { + qs.userData = options.userData; + if (options.targetFace) { + qs.targetFace = [options.targetFace.left, options.targetFace.top, options.targetFace.width, options.targetFace.height].join(); + } + } + var url = largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces'; + return _postImage(url, options, qs); + }, + + /** + * Delete a face from a person in a large person group. + * Face data and image related to this face entry will be also deleted. + * Adding/deleting faces to/from a same person will be processed sequentially. + * Adding/deleting faces to/from different persons are processed in parallel. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is removed from. + * @param {string} persistedFaceId - The ID of the face to be deleted. + * @return {Promise} - Promise; successful response is empty + */ + deleteFace: function (largePersonGroupId, personId, persistedFaceId) { + return new _Promise((resolve, reject) => { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, (error, response) => _return(error, response, resolve, reject)); + }); + }, + + /** + * Update a person persisted face's userData field. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is updated on. + * @param {string} persistedFaceId - The ID of the face to be updated. + * @param {string} userData - Optional. Attach user data to person's face. The maximum length is 1024. + * @return {Promise} - Promise resolving with the resulting JSON + */ + updateFace: function (largePersonGroupId, personId, persistedFaceId, userData) { + return new _Promise((resolve, reject) => { + request.patch({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: {'Ocp-Apim-Subscription-Key': key}, + json: true, + body: userData ? {userData: userData} : {} + }, (error, response) => _return(error, response, resolve, reject)); + }); + }, + + /** + * Retrieve person face information. + * The persisted person face is specified by its largePersonGroupId, personId and persistedFaceId. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person that the face is to get from. + * @param {string} persistedFaceId - The ID of the face to get. + * @return {Promise} - Promise resolving with the resulting JSON + */ + getFace: function (largePersonGroupId, personId, persistedFaceId) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId + '/persistedFaces/' + persistedFaceId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, (error, response) => { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Create a new person in a specified large person group. + * To add face to this person, please call LargePersonGroup PersonFace - Add. + * The number of persons has a subscription limit. Free subscription amount is 1000 persons. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} name - Target person's display name. The maximum length is 128. + * @param {string} userData - Optional fields for user-provided data attached to a person. Size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + create: function (largePersonGroupId, name, userData) { + return new _Promise((resolve, reject) => { + request.post({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons', + headers: {'Ocp-Apim-Subscription-Key': key}, + json: true, + body: { + name: name, + userData: userData + } + }, (error, response) => _return(error, response, resolve, reject)); + }); + }, + + /** + * Delete an existing person from a large person group. + * All stored person data, and face images in the person entry will be deleted. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person to delete. + * @return {Promise} - Promise resolving with the resulting JSON + */ + delete: function (largePersonGroupId, personId) { + return new _Promise((resolve, reject) => { + request({ + method: 'DELETE', + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, (error, response) => _return(error, response, resolve, reject)); + }); + }, + + /** + * Retrieve a person's name and userData, and the persisted faceIds representing the registered person face image. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person to get. + * @return {Promise} - Promise resolving with the resulting JSON + */ + get: function (largePersonGroupId, personId) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: {'Ocp-Apim-Subscription-Key': key} + }, (error, response) => { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + }, + + /** + * Updates a person's information. + * + * @param {string} largePersonGroupId - largePersonGroupId of the target large person group. + * @param {string} personId - The target person's id. + * @param {string} name - Target person's display name. The maximum length is 128. + * @param {string} userData - Optional fields for user-provided data attached to a person. Size limit is 16KB. + * @return {Promise} - Promise resolving with the resulting JSON + */ + update: function (largePersonGroupId, personId, name, userData) { + return new _Promise((resolve, reject) => { + request.patch({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons/' + personId, + headers: {'Ocp-Apim-Subscription-Key': key}, + json: true, + body: { + name: name, + userData: userData + } + }, (error, response) => _return(error, response, resolve, reject)); + }); + }, + + /** + * List all persons’ information in the specified large person group, + * including personId, name, userData and persistedFaceIds of registered person faces. + * + * @param {string} largePersonGroupId - The target person's person group. + * @param {string} options.start - List persons from the least personId greater than the "start". It contains no more than 64 characters. Default is empty. + * @param {Number} options.top - Optional count of persons to return. Valid range is [1,1000]. (Default: 1000) + * @return {Promise} - Promise resolving with the resulting JSON + */ + list: function (largePersonGroupId, options) { + return new _Promise((resolve, reject) => { + request({ + uri: host + rootPath + largePersonGroupPersonPath + '/' + largePersonGroupId + '/persons', + headers: {'Ocp-Apim-Subscription-Key': key}, + qs: options + }, (error, response) => { + response.body = JSON.parse(response.body); + _return(error, response, resolve, reject); + }); + }); + } + }; + return { detect: detect, similar: similar, @@ -834,7 +1201,9 @@ var face = function (key, host) { verify: verify, faceList: faceList, personGroup: personGroup, - person: person + person: person, + largePersonGroup: largePersonGroup, + largePersonGroupPerson: largePersonGroupPerson }; }; diff --git a/test/test_face.js b/test/test_face.js index d91abc8..9ff4ea8 100644 --- a/test/test_face.js +++ b/test/test_face.js @@ -12,6 +12,8 @@ var billFaceId, billPersistedFaces = [], personGroupId = uuid.v4(), personGroupId2 = uuid.v4(), + largePersonGroupId1 = uuid.v4(), + largePersonGroupId2 = uuid.v4(), personFaceListId = uuid.v4(), subValid = true, billPersonId, @@ -652,7 +654,6 @@ describe('Project Oxford Face API Test', function () { it('gets a PersonGroup\'s training status', function (done) { client.face.personGroup.trainingStatus(personGroupId) .then(function (response) { - assert.equal(response.personGroupId, personGroupId); assert.equal(response.status, 'notstarted'); done(); }) @@ -855,4 +856,297 @@ describe('Project Oxford Face API Test', function () { altClient.face.detect({ url: 'just-checking!' }); }); }); + + describe('#LargePersonGroup', function () { + before(function(done) { + // In order to test the + // training feature, we have to start training - sadly, we can't + // delete the group then. So we clean up before we run tests - and to wait + // for cleanup to finish, we're just using done(). + client.face.largePersonGroup.list().then(function (response) { + var promises = []; + + response.forEach(function (largePersonGroup) { + if (largePersonGroup.name.indexOf('po-node-test-large-group') > -1) { + promises.push(client.face.largePersonGroup.delete(largePersonGroup.largePersonGroupId)); + } + }); + + _Promise.all(promises).then(function () { + done(); + }); + }); + }); + + it('creates a LargePersonGroup', function (done) { + client.face.largePersonGroup.create(largePersonGroupId1, 'po-node-test-large-group-1', 'test-data') + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('lists LargePersonGroups', function (done) { + client.face.largePersonGroup.list({top: 5}) + .then(function (response) { + assert.ok(response); + assert.ok((response.length > 0)); + assert.ok(response[0].largePersonGroupId); + done(); + }); + }); + + it('gets a LargePersonGroups', function (done) { + client.face.largePersonGroup.get(largePersonGroupId1) + .then(function (response) { + assert.equal(response.largePersonGroupId, largePersonGroupId1); + assert.equal(response.name, 'po-node-test-large-group-1'); + assert.equal(response.userData, 'test-data'); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('updates a LargePersonGroups', function (done) { + client.face.largePersonGroup.update(largePersonGroupId1, 'po-node-test-large-group-2', 'test-data2') + .then(function (response) { + assert.ok(true, "void response expected");; + done(); + }).catch(function (response) { + assert.equal(response, 'LargePersonGroupTrainingNotFinished') + }); + }); + + it('gets a LargePersonGroups\'s training status', function (done) { + client.face.largePersonGroup.trainingStatus(largePersonGroupId1) + .then(function (response) { + assert.equal(response.status, 'notstarted'); + done(); + }) + .catch(function (error) { + assert.equal(error.code, 'LargePersonGroupNotTrained'); + done(); + }); + }); + + it('starts a LargePersonGroups\'s training', function (done) { + client.face.largePersonGroup.trainingStart(largePersonGroupId1) + .then(function (response) { + assert.ok(true, "void response expected");; + done(); + }).catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('deletes a LargePersonGroups', function (done) { + client.face.largePersonGroup.delete(largePersonGroupId1) + .then(function (response) { + assert.ok(!response, "void response"); + done(); + }).catch(function (response) { + assert.equal(JSON.parse(response).error.code, 'LargePersonGroupTrainingNotFinished'); + done(); + }); + }); + }); + + describe('#LargePersonGroupPerson', function () { + + it('creates a LargePersonGroup for the Person', function (done) { + client.face.largePersonGroup.create(largePersonGroupId2, 'po-node-test-large-group', 'test-data') + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('creates a Person', function (done) { + client.face.largePersonGroupPerson.create(largePersonGroupId2, 'test-bill', 'test-data') + .then(function (response) { + assert.ok(response.personId); + billPersonId = response.personId; + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('gets a Person', function (done) { + client.face.largePersonGroupPerson.get(largePersonGroupId2, billPersonId) + .then(function (response) { + assert.equal(response.personId, billPersonId); + assert.equal(response.name, 'test-bill'); + assert.equal(response.userData, 'test-data'); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('updates a Person', function (done) { + client.face.largePersonGroupPerson.update(largePersonGroupId2, billPersonId, 'test-bill2', 'test-data2') + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('adds a face to a Person', function (done) { + client.face.largePersonGroupPerson.addFace(largePersonGroupId2, billPersonId, { + path: './test/images/face1.jpg', + userData: 'test-data-face' + }) + .then(function (response) { + billPersonPersistedFaceId = response.persistedFaceId; + assert.ok(billPersonPersistedFaceId); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('gets a face from a Person', function (done) { + client.face.largePersonGroupPerson.getFace(largePersonGroupId2, billPersonId, billPersonPersistedFaceId) + .then(function (response) { + assert.equal(response.persistedFaceId, billPersonPersistedFaceId); + assert.equal(response.userData, 'test-data-face'); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('verify a face of a Person in a LargePersonGroup', function (done) { + client.face.verify({ + faceId: billFaceId, + personId: billPersonId, + largePersonGroupId: largePersonGroupId2 + }) + .then(function (response) { + assert.ok(true, response.isIdentical); + assert.ok(response.confidence > 0.5); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('starts a LargePersonGroups\'s training', function (done) { + client.face.largePersonGroup.trainingStart(largePersonGroupId2) + .then(function (response) { + assert.ok(true, "void response expected");; + done(); + }).catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('gets a LargePersonGroup\'s training status - validate trainign is successful', function (done) { + client.face.largePersonGroup.trainingStatus(largePersonGroupId2) + .then(function (response) { + assert.equal(response.status, 'succeeded'); + done(); + }) + .catch(function (error) { + assert.equal(error.code, 'LargePersonGroupNotTrained'); + done(); + }); + }); + + it('identify a face of a Person in a LargePersonGroup', function (done) { + client.face.identify([billFaceId],{largePersonGroupId: largePersonGroupId2}) + .then(function (response) { + assert.ok(response); + assert.ok((response.length > 0)); + assert.ok(response[0].candidates); + assert.ok(response[0].candidates.length > 0); + assert.ok(response[0].candidates[0].confidence > 0.5); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('updates a face on a Person', function (done) { + client.face.largePersonGroupPerson.updateFace(largePersonGroupId2, billPersonId, billPersonPersistedFaceId, 'test-data-face-updated') + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('deletes a face on a Person', function (done) { + client.face.largePersonGroupPerson.deleteFace(largePersonGroupId2, billPersonId, billPersonPersistedFaceId) + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('lists Persons', function (done) { + client.face.largePersonGroupPerson.list(largePersonGroupId2) + .then(function (response) { + assert.equal(response.length, 1); + assert.equal(response[0].personId, billPersonId); + assert.equal(response[0].name, 'test-bill2'); + assert.equal(response[0].userData, 'test-data2'); + assert.ok(!response[0].persistedFaceIds || response[0].persistedFaceIds.length == 0); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + + it('deletes a Person', function (done) { + client.face.largePersonGroupPerson.delete(largePersonGroupId2, billPersonId) + .then(function (response) { + assert.ok(true, "void response expected"); + done(); + }) + .catch(function (error) { + assert.ok(false, JSON.stringify(error)); + done(); + }); + }); + }); });