Skip to content

Commit 170c138

Browse files
committed
Allow Visual Recognition to classify Buffers, attempt to automatically determine content type.
Fixes #333 Also improved formatting of VR error messages.
1 parent 3967096 commit 170c138

3 files changed

Lines changed: 66 additions & 7 deletions

File tree

lib/requestwrapper.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ function formatErrorIfExists(cb) {
6262

6363
// If we have a response and it contains an error
6464
if (body && (body.error || body.error_code)) {
65+
// visual recognition sets body.error to a json object with code/description/error_id instead of putting them top-left
66+
if (typeof body.error === 'object' && body.error.description) {
67+
var errObj = body.error; // just in case there's a body.error.error...
68+
Object.keys(body.error).forEach(function(key) {
69+
body[key] = body.error[key];
70+
});
71+
body.error = errObj.description;
72+
}
6573
error = new Error(body.error || 'Error Code: ' + body.error_code);
6674
error.code = body.error_code;
6775
Object.keys(body).forEach(function(key) {

test/integration/test.visual_recognition.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ describe('visual_recognition_integration', function() {
6666
});
6767
});
6868

69+
it('should classify from a buffer', function(done) {
70+
this.retries(0);
71+
var params = {
72+
images_file: fs.readFileSync(__dirname + '/../resources/car.png')
73+
};
74+
visual_recognition.classify(params, function(err, result) {
75+
if (err) {
76+
return done(err);
77+
}
78+
//console.log(JSON.stringify(result, null, 2));
79+
assert.equal(result.images_processed, 1);
80+
assert(result.images[0].classifiers.length);
81+
assert(result.images[0].classifiers[0].classes.some(function(c) {
82+
return c.class === 'car'
83+
}));
84+
85+
done();
86+
});
87+
});
88+
6989
it('should classify an image via url', function(done) {
7090
var params = {
7191
url: 'https://watson-test-resources.mybluemix.net/resources/car.png'

visual-recognition/v3.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,39 @@ function xor(a, b) {
3737
return ( a || b ) && !( a && b );
3838
}
3939

40+
/**
41+
* Determine content-type header for .zip, .png, and .jpg files
42+
* (The only formats currently supported by the service)
43+
*
44+
* based on https://github.com/watson-developer-cloud/node-sdk/issues/333
45+
*
46+
* @param {Buffer} buffer
47+
* @returns {String|undefined}
48+
*/
49+
function detectContentType(buffer) {
50+
var signature = buffer.readUInt32BE();
51+
switch(signature) { // eslint-disable-line default-case
52+
case 0x504B0304:
53+
case 0x504B0506:
54+
case 0x504B0708:
55+
return 'application/zip';
56+
case 0x89504E47:
57+
return 'image/png';
58+
case 0xFFD8FFE0:
59+
case 0xFFD8FFE1:
60+
case 0xFFD8FFE8:
61+
return 'image/jpeg';
62+
// default is `return undefined`
63+
}
64+
}
65+
4066
/**
4167
* Verifies that a stream images_file or a string url is included
4268
* also gracefully handles cases of image_file instead of images_file
4369
*
4470
* @private
4571
*/
46-
function verifyParams(params) {
72+
function fixupImageParam(params) {
4773
if (params && params.image_file && !params.images_file) {
4874
params.images_file = params.image_file;
4975
}
@@ -52,8 +78,13 @@ function verifyParams(params) {
5278
throw new Error('Watson VisualRecognition.classify() requires either an images_file or a url parameter');
5379
}
5480

55-
if (params.images_file && !isStream(params.images_file)) {
56-
throw new Error('images_file param must be a standard Node.js Stream');
81+
if (Buffer.isBuffer(params.images_file)) {
82+
params.images_file = {
83+
value: params.images_file,
84+
options: {
85+
contentType: detectContentType(params.images_file)
86+
}
87+
}
5788
}
5889
}
5990

@@ -185,7 +216,7 @@ VisualRecognitionV3.prototype.getCredentialsFromBluemix = function() {
185216
* }
186217
*
187218
* @param {Object} params
188-
* @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 20. Either images_file or url must be specified.
219+
* @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'}}
189220
* @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.
190221
* @param {Array} [params.classifier_ids=['default']] An array of classifier IDs to classify the images against.
191222
* @param {Array} [params.owners=['me','IBM']] An array with the value(s) "IBM" and/or "me" to specify which classifiers to run.
@@ -198,7 +229,7 @@ VisualRecognitionV3.prototype.getCredentialsFromBluemix = function() {
198229
VisualRecognitionV3.prototype.classify = function(params, callback) {
199230

200231
try {
201-
verifyParams(params);
232+
fixupImageParam(params);
202233
} catch (e) {
203234
callback(e);
204235
return;
@@ -291,7 +322,7 @@ VisualRecognitionV3.prototype.classify = function(params, callback) {
291322
*/
292323
VisualRecognitionV3.prototype.detectFaces = function(params, callback) {
293324
try {
294-
verifyParams(params);
325+
fixupImageParam(params);
295326
} catch (e) {
296327
callback(e);
297328
return;
@@ -381,7 +412,7 @@ VisualRecognitionV3.prototype.detectFaces = function(params, callback) {
381412
*/
382413
VisualRecognitionV3.prototype.recognizeText = function(params, callback) {
383414
try {
384-
verifyParams(params);
415+
fixupImageParam(params);
385416
} catch (e) {
386417
callback(e);
387418
return;

0 commit comments

Comments
 (0)