UNPKG

watson-developer-cloud

Version:

Client library to use the IBM Watson Services and AlchemyAPI

1,086 lines (1,006 loc) 32 kB
/** * Copyright 2014 IBM Corp. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; const extend = require('extend'); const pick = require('object.pick'); const isStream = require('isstream'); const requestFactory = require('../lib/requestwrapper'); const util = require('util'); const BaseService = require('../lib/base_service'); const NEGATIVE_EXAMPLES = 'negative_examples'; /** * JS-style logical XOR - works on objects, booleans, strings, etc following normal js truthy/falsy conventions * @private * @param {*} a * @param {*} b * @return {boolean} * @constructor */ function xor(a, b) { return (a || b) && !(a && b); } /** * Determine content-type header for .zip, .png, and .jpg files * (The only formats currently supported by the service) * * based on https://github.com/watson-developer-cloud/node-sdk/issues/333 * * @param {Buffer} buffer * @return {String|undefined} */ function detectContentType(buffer) { const signature = buffer.readUInt32BE(); switch (signature) { // eslint-disable-line default-case case 0x504b0304: case 0x504b0506: case 0x504b0708: return 'application/zip'; case 0x89504e47: return 'image/png'; case 0xffd8ffe0: case 0xffd8ffe1: case 0xffd8ffe8: return 'image/jpeg'; // default is `return undefined` } } /** * Verifies that a stream images_file or a string url is included * also gracefully handles cases of image_file instead of images_file * * @private * @param {Object} params */ function fixupImageParam(params) { if (params && params.image_file && !params.images_file) { params.images_file = params.image_file; } if (!params || !xor(params.images_file, params.url)) { throw new Error('Watson VisualRecognition.classify() requires either an images_file or a url parameter'); } if (Buffer.isBuffer(params.images_file)) { params.images_file = { value: params.images_file, options: { contentType: detectContentType(params.images_file) } }; } } /** * Creates a function that can be called on responses to format the error then fire the cb * * @private * @param {Function} [cb] * @return {Function} */ function errorFormatter(cb) { const callback = typeof cb === 'function' ? cb /* no op */ : (function() {}); return function(err, result) { if (err) { callback(err, result); } else { if (result.status === 'ERROR') { if (result.statusInfo === 'invalid-api-key') { callback( { error: result.statusInfo, code: result.statusInfo === 'invalid-api-key' ? 401 : 400 }, null ); } } else { callback(err, result); } } }; } /** * Visual Recognition v3 * @param {Object} options * @constructor */ function VisualRecognitionV3(options) { BaseService.call(this, options); // Check if 'version_date' was provided if (typeof this._options.version_date === 'undefined') { throw new Error('Argument error: version_date was not specified, use 2016-05-20'); } this._options.qs.version = this._options.version_date; // todo: confirm service expects version not version_date } util.inherits(VisualRecognitionV3, BaseService); VisualRecognitionV3.prototype.name = 'visual_recognition'; VisualRecognitionV3.prototype.version = 'v3'; VisualRecognitionV3.URL = 'https://gateway-a.watsonplatform.net/visual-recognition/api'; VisualRecognitionV3.prototype.serviceDefaults = { alchemy: true }; /** * Grab the api key * * @param {Object} options * @private * @return {Object} new options object */ VisualRecognitionV3.prototype.initCredentials = function(options) { options.api_key = options.api_key || options.apikey; options = extend( {}, this.getCredentialsFromBluemix(this.name), // todo: test if this works this.getCredentialsFromEnvironment(this.name), options ); if (!options.use_unauthenticated) { if (!options.api_key) { throw new Error('Argument error: api_key was not specified'); } // Per documentation, Alchemy* services use `apikey`, but Visual Recognition uses (`api_key`) // (Either will work in most cases, but the VR Collections & Similarity Search beta only supports `api_key`) options.qs = extend({ api_key: options.api_key }, options.qs); } return options; }; /** * Pulls api_key from SERVICE_NAME_API_KEY env property * * @param {String} name * @return {{api_key: String|undefined}} */ VisualRecognitionV3.prototype.getCredentialsFromEnvironment = function(name) { return { api_key: process.env[name.toUpperCase() + '_API_KEY'], url: process.env[name + '_URL'] }; }; /** * Bluemix uses a different naming convention for VR v3 than for other services * @return {*} */ VisualRecognitionV3.prototype.getCredentialsFromBluemix = function() { return BaseService.prototype.getCredentialsFromBluemix.call(this, 'watson_vision_combined'); }; /** * Accepts either a url, a single image file, or a zip file with multiple * images (.jpeg, .png, .gif) and scores every available classifier * on each image. It then applies a threshold and returns the list * of relevant classifier scores for each image. * * @example * * { * "images": [{ * "classifiers": [{ * "classes": [{ * "class": "animal", * "score": 0.998771, * "type_hierarchy": "/animals" * }, { * "class": "mammal", * "score": 0.998499, * "type_hierarchy": "/animals/mammal" * }, { * "class": "dog", * "score": 0.900249, * "type_hierarchy": "/animals/pets/dog" * }, { * "class": "puppy", * "score": 0.5, * "type_hierarchy": "/animals/pets/puppy" * }], * "classifier_id": "default", * "name": "default" * }], * "image": "dog.jpg" * }], * "images_processed": 1 * } * * @param {Object} params * @param {ReadStream|Buffer|Object} [params.images_file] The image file (.jpg, .png, .gif) or compressed (.zip) file of images to classify. The total number of images is limited to 20. Either images_file or url must be specified. The SDK attempts to determine content-type automatically, but this may be overridden by providing an object: {value: Buffer|Stream, options: {filename: 'file.ext', contentType: 'image/type'}} * @param {String} [params.url] The URL of an image (.jpg, .png, .gif). Redirects are followed, so you can use shortened URLs. The resolved URL is returned in the response. Either images_file or url must be specified. * @param {Array} [params.classifier_ids=['default']] An array of classifier IDs to classify the images against. * @param {Array} [params.owners=['me','IBM']] An array with the value(s) "IBM" and/or "me" to specify which classifiers to run. * @param {Number} [params.threshold] A floating point value that specifies the minimum score a class must have to be displayed in the response. * @param {Function} callback * * @return {ReadableStream|undefined} * */ VisualRecognitionV3.prototype.classify = function(params, callback) { try { fixupImageParam(params); } catch (e) { callback(e); return; } params = extend( { classifier_ids: ['default'], owners: ['me', 'IBM'] }, params ); let parameters; if (params.images_file) { parameters = { options: { url: '/v3/classify', method: 'POST', formData: { images_file: params.images_file, parameters: { value: JSON.stringify(pick(params, ['classifier_ids', 'owners', 'threshold'])), options: { contentType: 'application/json' } } }, headers: pick(params, 'Accept-Language') }, defaultOptions: this._options }; } else { parameters = { options: { url: '/v3/classify', method: 'GET', json: true, qs: pick(params, ['url', 'classifier_ids', 'owners', 'threshold']), headers: pick(params, 'Accept-Language') }, defaultOptions: this._options }; } return requestFactory(parameters, errorFormatter(callback)); }; /** * Accepts either a url, a single image file, or a zip file with multiple * images (.jpeg, .png, .gif) and attempts to extract faces and * identities. It then applies a threshold * and returns the list of relevant identities, locations, and metadata * for found faces for each image. * * @example * { * "images": [{ * "faces": [{ * "age": { * "max": 54, * "min": 45, * "score": 0.40459 * }, * "face_location": { * "height": 131, * "left": 80, * "top": 68, * "width": 123 * }, * "gender": { * "gender": "MALE", * "score": 0.993307 * }, * "identity": { * "name": "Barack Obama", * "score": 0.970688, * "type_hierarchy": "/people/politicians/democrats/barack obama" * } * }], * "image": "obama.jpg" * }], * "images_processed": 1 * } * * @param {Object} params * @param {ReadStream} [params.images_file] The image file (.jpg, .png, .gif) or compressed (.zip) file of images to classify. The total number of images is limited to 15. Either images_file or url must be specified. * @param {String} [params.url] The URL of an image (.jpg, .png, .gif). Redirects are followed, so you can use shortened URLs. The resolved URL is returned in the response. Either images_file or url must be specified. * @param {Function} callback * * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.detectFaces = function(params, callback) { try { fixupImageParam(params); } catch (e) { callback(e); return; } let parameters; if (params.images_file) { parameters = { options: { url: '/v3/detect_faces', method: 'POST', json: true, formData: pick(params, ['images_file']) }, defaultOptions: this._options }; } else { parameters = { options: { url: '/v3/detect_faces', method: 'GET', json: true, qs: pick(params, ['url']) }, defaultOptions: this._options }; } return requestFactory(parameters, errorFormatter(callback)); }; /** * Accepts either a url, single image file, or a zip file with multiple * images (.jpeg, .png, .gif) and attempts to recognize text * found in the image. It then applies a threshold * and returns the list of relevant locations, strings, and metadata * for discovered text in each image. * * @example * { * "images": [{ * "image": "car.png", * "text": "3 jag [brio]", * "words": [{ * "line_number": 0, * "location": { * "height": 53, * "left": 204, * "top": 294, * "width": 27 * }, * "score": 0.50612, * "word": "3" * }, { * "line_number": 0, * "location": { * "height": 32, * "left": 264, * "top": 288, * "width": 56 * }, * "score": 0.958628, * "word": "jag" * }, { * "line_number": 0, * "location": { * "height": 40, * "left": 324, * "top": 288, * "width": 92 * }, * "score": 0.00165806, * "word": "brio" * }] * }], * "images_processed": 1 * } * * @param {Object} params * @param {ReadStream} [params.images_file] The image file (.jpg, .png, .gif) or compressed (.zip) file of images to classify. The total number of images is limited to 10. Either images_file or url must be specified. * @param {String} [params.url] The URL of an image (.jpg, .png, .gif). Redirects are followed, so you can use shortened URLs. The resolved URL is returned in the response. Either images_file or url must be specified. * @param {Function} callback * * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.recognizeText = function(params, callback) { try { fixupImageParam(params); } catch (e) { callback(e); return; } let parameters; if (params.images_file) { parameters = { options: { url: '/v3/recognize_text', method: 'POST', json: true, formData: pick(params, ['images_file']) }, defaultOptions: this._options }; } else { parameters = { options: { url: '/v3/recognize_text', method: 'GET', json: true, qs: pick(params, ['url']) }, defaultOptions: this._options }; } return requestFactory(parameters, errorFormatter(callback)); }; /** * Train a new classifier from example images which are uploaded. * This call returns before training has completed. You'll need to use the * getClassifer method to make sure the classifier has completed training and * was successful before you can classify any images with the newly created * classifier. * * @example * { * foo_positive_examples: fs.createReadStream('./foo-pics.zip'), * negative_examples: fs.createReadStream('./not-foo-pics.zip'), * name: 'to-foo-or-not' * } * @example * { * foo_positive_examples: fs.createReadStream('./foo-pics.zip'), * bar_positive_examples: fs.createReadStream('./bar-pics.zip'), * name: 'foo-vs-bar' * } * @example * { * foo_positive_examples: fs.createReadStream('./foo-pics.zip'), * bar_positive_examples: fs.createReadStream('./bar-pics.zip'), * negative_examples: fs.createReadStream('./not-foo-pics.zip'), * name: 'foo-bar-not' * } * * @example * { * "classifier_id": "fruit_679357912", * "name": "fruit", * "owner": "a3a48ea7-492b-448b-87d7-9dade8bde5a9", * "status": "training", * "created": "2016-05-23T21:50:41.680Z", * "classes": [{ * "class": "banana" * }, { * "class": "apple" * }] * } * * @param {Object} params * @param {String} params.name The desired short name of the new classifier. * @param {ReadStream} params.classname_positive_examples <your_class_name>_positive_examples One or more compressed (.zip) files of images that depict the visual subject for a class within the new classifier. Must contain a minimum of 10 images. You may supply multiple files with different class names in the key. * @param {ReadStream} [params.negative_examples] A compressed (.zip) file of images that do not depict the visual subject of any of the classes of the new classifier. Must contain a minimum of 10 images. Required if only one positive set is provided. * * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.createClassifier = function(params, callback) { params = params || {}; const example_keys = Object.keys(params).filter(function(key) { return key === NEGATIVE_EXAMPLES || key.match(/^.+_positive_examples$/); }); if (example_keys.length < 2) { callback(new Error('Missing required parameters: either two *_positive_examples or one *_positive_examples and one negative_examples must be provided.')); return; } // todo: validate that all *_examples are streams or else objects with buffers and content-types const allowed_keys = ['name', NEGATIVE_EXAMPLES].concat(example_keys); const parameters = { options: { url: '/v3/classifiers', method: 'POST', json: true, formData: pick(params, allowed_keys) }, requiredParams: ['name'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Retrain a existing classifier from example images which are uploaded. * This call returns before retraining has completed. You'll need to use the * getClassifer method to make sure the classifier has completed retraining and * was successful before you can classify any images with the retrained * classifier. * * @example * { * "classifier_id": "fruit_679357912", * "name": "fruit", * "owner": "a3a48ea7-492b-448b-87d7-9dade8bde5a9", * "status": "training", * "created": "2016-05-23T21:50:41.680Z", * "classes": [{ * "class": "banana" * }, { * "class": "apple" * }] * } * * @param {Object} params * @param {ReadStream} params.classname_positive_examples <your_class_name>_positive_examples One or more compressed (.zip) files of images that depict the visual subject for a class within the classifier. You may supply multiple files with different class names in the key. * @param {ReadStream} [params.negative_examples] A compressed (.zip) file of images that do not depict the visual subject of any of the classes of the classifier. * * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.retrainClassifier = function(params, callback) { params = params || {}; const allowed_keys = Object.keys(params).filter(function(key) { return key === NEGATIVE_EXAMPLES || key.match(/^.+_positive_examples$/); }); const parameters = { options: { url: '/v3/classifiers/' + params.classifier_id, method: 'POST', json: true, formData: pick(params, allowed_keys) }, requiredParams: [], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Retrieve a list of all classifiers, including built-in and * user-created classifiers. * * @example * { * "classifiers": [{ * "classifier_id": "fruit_679357912", * "name": "fruit", * "status": "ready" * }, { * "classifier_id": "Dogs_2017013066", * "name": "Dogs", * "status": "ready" * }] * } * * @param {Object} params * @param {Boolean} [params.verbose=false] * @param {Function} callback * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.listClassifiers = function(params, callback) { const parameters = { options: { method: 'GET', url: '/v3/classifiers', qs: pick(params, ['verbose']), json: true }, defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Retrieves information about a specific classifier. * * @example * { * "classifier_id": "fruit_679357912", * "name": "fruit", * "owner": "a3a42ea7-492b-448b-87d7-9dfde8bde519 ", * "status": "ready", * "created": "2016-05-23T21:50:41.680Z", * "classes": [{ * "class": "banana" * }, { * "class": "apple" * }] * } * * @param {Object} params * @param {Boolean} params.classifier_id The classifier id * @param {Function} callback * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.getClassifier = function(params, callback) { const parameters = { options: { method: 'GET', url: '/v3/classifiers/{classifier_id}', path: params, json: true }, requiredParams: ['classifier_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Deletes a custom classifier with the specified classifier id. * * @param {Object} params * @param {String} params.classifier_id The classifier id * @param {Function} callback * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.deleteClassifier = function(params, callback) { const parameters = { options: { method: 'DELETE', url: '/v3/classifiers/{classifier_id}', path: params, json: true }, requiredParams: ['classifier_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; // collections & similarity search /** * Create a collection * Beta. Create a new collection of images to search. You can create a maximum of 5 collections. * * Example response: { collection_id: 'integration_test_1474313373701_d9665f', name: 'integration_test_1474313373701', status: 'available', created: '2016-09-19T19:29:34.019Z', images: 0, capacity: 1000000 } * @param {Object} params * @param {String} params.name The name of the new collection. The name can be a maximum of 128 UTF8 characters, with no spaces. * @param {Function} callback */ VisualRecognitionV3.prototype.createCollection = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections', method: 'POST', json: true, formData: pick(params, ['name']) }, requiredParams: ['name'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Retrieve collection details * Beta. Retrieve information about a specific collection. * * Example response: { collection_id: 'integration_test_1474313373701_d9665f', name: 'integration_test_1474313373701', status: 'available', created: '2016-09-19T19:29:34.019Z', images: 0, capacity: 1000000 } * @param {Object} params * @param {String} params.collection_id * @param {Function} callback */ VisualRecognitionV3.prototype.getCollection = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}', method: 'GET', json: true, path: params }, requiredParams: ['collection_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * List collections * Beta. List all custom collections. * * Example response: { collections: [ { collection_id: 'integration_test_1474313967414_0e320b', name: 'integration_test_1474313967414', status: 'available', created: '2016-09-19T19:39:27.811Z', images: 0, capacity: 1000000 } ] } * @param {Object} [params] * @param {Function} callback */ VisualRecognitionV3.prototype.listCollections = function(params, callback) { if (typeof params === 'function' && !callback) { callback = params; } const parameters = { options: { url: '/v3/collections', method: 'GET', json: true }, defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Delete a collection * Beta. Delete a user created collection. * * @param {Object} params * @param {String} params.collection_id * @param {Function} callback */ VisualRecognitionV3.prototype.deleteCollection = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}', method: 'DELETE', json: true, path: params }, requiredParams: ['collection_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Add an image to a collection * Beta. Add images to a collection. Each collection can contain 1000000 images. * * Example Response: { "images": [ { "image_id": "9725bc", "image_file": "obama.jpg", "created": "2016-09-20T14:41:49.927Z", "metadata": { "foo": "bar" } } ], "images_processed": 1 } * @param {Object} params * @param {String} params.collection_id * @param {ReadableStream} params.image_file The image file (.jpg or .png) of the image to add to the collection. Maximum file size of 2 MB. * @param {Object} [params.metadata] optional arbitrary metadata. This can be anything that can be specified in a JSON object. For example, key-value pairs. Maximum 2 KB of metadata for each image. * @param {Function} callback */ VisualRecognitionV3.prototype.addImage = function(params, callback) { params = params || {}; if (!params.image_file || !isStream(params.image_file)) { throw new Error('image_file param must be a standard Node.js Stream'); } const parameters = { options: { url: '/v3/collections/{collection_id}/images', method: 'POST', json: true, path: params, formData: { image_file: params.image_file, metadata: { value: JSON.stringify(params.metadata || {}), options: { contentType: 'application/json', filename: 'metadata.json' // it doesn't matter what the filename is, but the service requires that *some* filename be set or else it ignores the metadata } } } }, requiredParams: ['collection_id', 'image_file'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * List images in a collection * Beta. List the first 100 images in a collection. Each collection can contain 1000000 images. * * Example Response: { images: [ { image_id: '83f3ff', image_file: 'obama.jpg', created: '2016-09-19T21:07:15.141Z' } ] } * * @param {Object} params * @param {String} params.collection_id * @param {ReadableStream} params.image_file The image file (.jpg or .png) of the image to add to the collection. Maximum file size of 2 MB. * @param {Object} [params.metadata] optional arbitrary metadata. This can be anything that can be specified in a JSON object. For example, key-value pairs. Maximum 2 KB of metadata for each image. * @param {Function} callback */ VisualRecognitionV3.prototype.listImages = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images', method: 'GET', json: true, path: params }, requiredParams: ['collection_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Get image details * Beta. List details about a specific image in a collection. * * Example Response: { image_id: '83f3ff', image_file: 'obama.jpg', created: '2016-09-19T21:07:15.141Z' * @param {Object} params * @param {String} params.collection_id * @param {String} params.image_id * @param {Function} callback */ VisualRecognitionV3.prototype.getImage = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images/{image_id}', method: 'GET', json: true, path: params }, requiredParams: ['collection_id', 'image_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Delete an image * Beta. Delete an image from a collection. * * @param {Object} params * @param {String} params.collection_id * @param {String} params.image_id * @param {Function} callback */ VisualRecognitionV3.prototype.deleteImage = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images/{image_id}', method: 'DELETE', json: true, path: params }, requiredParams: ['collection_id', 'image_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Add or update metadata * Beta. Add metadata to a specific image in a collection. * * @param {Object} params * @param {String} params.collection_id * @param {String} params.image_id * @param {Object} params.metadata Can be anything that can be specified in a JSON object. For example, key-value pairs. Maximum 2 KB of metadata for each image. * @param {Function} callback */ VisualRecognitionV3.prototype.setImageMetadata = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images/{image_id}/metadata', method: 'PUT', json: true, path: params, headers: { 'Content-Type': 'multipart/form-data' }, // todo: manually create a body string that looks like a POST form data body even though it's a PUT formData: { metadata: { value: JSON.stringify(params.metadata || {}), options: { contentType: 'application/json', filename: 'metadata.json' // it doesn't matter what the filename is, but the service requires that *some* filename be set or else it gives a confusing "Missing multipart/form-data" error } } } }, requiredParams: ['collection_id', 'image_id', 'metadata'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Get image metadata * Beta. View the metadata for a specific image in a collection. * * Example Response: {"foo": "bar"} * * @param {Object} params * @param {String} params.collection_id * @param {ReadableStream} params.image_id * @param {Function} callback */ VisualRecognitionV3.prototype.getImageMetadata = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images/{image_id}/metadata', method: 'GET', json: true, path: params }, requiredParams: ['collection_id', 'image_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Delete image metadata * Beta. Delete all metadata associated with an image. * * @param {Object} params * @param {String} params.collection_id * @param {ReadableStream} params.image_id * @param {Function} callback */ VisualRecognitionV3.prototype.deleteImageMetadata = function(params, callback) { params = params || {}; const parameters = { options: { url: '/v3/collections/{collection_id}/images/{image_id}/metadata', method: 'DELETE', json: true, path: params }, requiredParams: ['collection_id', 'image_id'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; /** * Find similar images * Beta. Upload an image to find similar images in your custom collection. * * Example response: { "similar_images":[ { "image_id":"dresses_1257263", "created":"2016-09-04T21:49:16.908Z", "metadata":{ "weight":10, "cut":"a line", "color":"red" }, "score":"0.79" } ], "image_file":"red_dress.jpg", "images_processed": 1 } * @param {Object} params * @param {String} params.collection_id The collection id * @param {ReadableStream} params.image_file The image file (.jpg or .png) of the image to search against the collection. * @param {Number} [params.limit=10] limit The number of similar results you want returned. Default limit is 10 results, you can specify a maximum limit of 100 results. * @param {Function} callback * @return {ReadableStream|undefined} */ VisualRecognitionV3.prototype.findSimilar = function(params, callback) { params = params || {}; if (!params.image_file || !isStream(params.image_file)) { throw new Error('image_file param must be a standard Node.js Stream'); } const parameters = { options: { url: '/v3/collections/{collection_id}/find_similar', method: 'POST', json: true, qs: pick(params, ['limit']), formData: pick(params, ['image_file']), path: pick(params, ['collection_id']) }, requiredParams: ['collection_id', 'image_file'], defaultOptions: this._options }; return requestFactory(parameters, errorFormatter(callback)); }; module.exports = VisualRecognitionV3;