Skip to content

Commit

Permalink
Coerce requests from string or buffer. (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukesneeringer authored Nov 28, 2017
1 parent 0bd9859 commit 3eaa0fe
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 100 deletions.
14 changes: 1 addition & 13 deletions packages/google-cloud-vision/samples/quickstart.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,9 @@ const vision = require('@google-cloud/vision');
// Creates a client
const client = new vision.ImageAnnotatorClient();

// The name of the image file to annotate
const fileName = './resources/wakeupcat.jpg';

// Prepare the request object
const request = {
image: {
source: {
filename: fileName,
},
},
};

// Performs label detection on the image file
client
.labelDetection(request)
.labelDetection('./resources/wakeupcat.jpg')
.then(results => {
const labels = results[0].labelAnnotations;

Expand Down
179 changes: 129 additions & 50 deletions packages/google-cloud-vision/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,71 @@ const promisify = require('@google-cloud/common').util.promisify;
const gax = require('google-gax');

/*!
* Find a given image and fire a callback with the appropriate image structure.
* Convert non-object request forms into a correctly-formatted object.
*
* @param {object} image An object representing what is known about the
* image.
* @param {object|string|Buffer} request An object representing an
* AnnotateImageRequest. May also be a string representing the path
* (filename or URL) to the image, or a buffer representing the image itself.
*
* @returns An object representing an AnnotateImageRequest.
*/
let _requestToObject = request => {
if (is.string(request)) {
// Is this a URL or a local file?
// Guess based on what the string looks like, and build the full
// request object in the correct format.
if (request.indexOf('://') === -1 || request.indexOf('file://') === 0) {
request = {image: {source: {filename: request}}};
} else {
request = {image: {source: {imageUri: request}}};
}
} else if (Buffer.isBuffer(request)) {
// Drop the buffer one level lower; it will get dealt with later
// in the function. This allows sending <Buffer> and {image: <Buffer>} to
// both work identically.
request = {image: request};
}
return request;
};

/*!
* Coerce several nicer iterations of "how to specify an image" to the
* full sturcture expected by the Vision API.
*
* @param {object} request An object representing an AnnotateImageRequest.
* It may include `image.source.filename` or a buffer passed to
* `image.content`, which are coerced into their canonical forms by this
* function.
* @param {function} callback The callback to run.
*/
var coerceImage = (image, callback) => {
let _coerceRequest = (request, callback) => {
// At this point, request must be an object with an `image` key; if not,
// it is an error. If there is no image, throw an exception.
if (!is.object(request) || is.undefined(request.image)) {
return callback(new Error('No image present.'));
}

// If this is a buffer, read it and send the object
// that the Vision API expects.
if (Buffer.isBuffer(image)) {
callback(null, {
content: image.toString('base64'),
});
return;
if (Buffer.isBuffer(request.image)) {
request.image = {content: request.image.toString('base64')};
}

// File exists on disk.
if (image.source && image.source.filename) {
fs.readFile(image.source.filename, {encoding: 'base64'}, (err, blob) => {
// If the file is specified as a filename and exists on disk, read it
// and coerce it into the base64 content.
if (request.image.source && request.image.source.filename) {
fs.readFile(request.image.source.filename, (err, blob) => {
if (err) {
callback(err);
return;
}
callback(null, {content: blob.toString('base64')});
request.image.content = blob.toString('base64');
delete request.image.source;
return callback(null, request);
});
return;
} else {
return callback(null, request);
}

// No other options were relevant; return the image with no modification.
callback(null, image);
return;
};

/*!
Expand All @@ -68,12 +102,29 @@ var coerceImage = (image, callback) => {
* asking for the single feature annotation.
*/
var _createSingleFeatureMethod = featureValue => {
return function(annotateImageRequest, callOptions) {
return function(annotateImageRequest, callOptions, callback) {
// Sanity check: If we got a string or buffer, we need this to be
// in object form now, so we can tack on the features list.
//
// Do the minimum required conversion, which can also be guaranteed to
// be synchronous (e.g. no file loading yet; that is handled by
// annotateImage later.
annotateImageRequest = _requestToObject(annotateImageRequest);

// If a callback was provided and options were skipped, normalize
// the argument names.
if (is.undefined(callback) && is.function(callOptions)) {
callback = callOptions;
callOptions = undefined;
}

// Add the feature to the request.
annotateImageRequest.features = annotateImageRequest.features || [
{
type: featureValue,
},
];

// If the user submitted explicit features that do not line up with
// the precise method called, throw an exception.
for (let feature of annotateImageRequest.features) {
Expand All @@ -84,8 +135,9 @@ var _createSingleFeatureMethod = featureValue => {
);
}
}

// Call the underlying #annotateImage method.
return this.annotateImage(annotateImageRequest, callOptions);
return this.annotateImage(annotateImageRequest, callOptions, callback);
};
};

Expand All @@ -108,8 +160,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#annotateImage
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -163,21 +218,15 @@ module.exports = apiVersion => {
callOptions = undefined;
}

// If there is no image, throw an exception.
if (is.undefined(request.image)) {
throw new Error('Attempted to call `annotateImage` with no image.');
}

// If we got a filename for the image, open the file and transform
// it to content.
return coerceImage(request.image, (err, image) => {
return _coerceRequest(request, (err, req) => {
if (err) {
return callback(err);
}
request.image = image;

// Call the GAPIC batch annotation function.
let requests = {requests: [request]};
let requests = {requests: [req]};
return this.batchAnnotateImages(requests, callOptions, (err, r) => {
// If there is an error, handle it.
if (err) {
Expand Down Expand Up @@ -212,8 +261,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#faceDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -266,8 +318,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#landmarkDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -320,8 +375,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#logoDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -365,8 +423,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#labelDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -410,8 +471,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#textDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -455,8 +519,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#documentTextDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -500,8 +567,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#safeSearchDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -545,8 +615,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#imageProperties
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -590,8 +663,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#cropHints
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down Expand Up @@ -635,8 +711,11 @@ module.exports = apiVersion => {
* @see google.cloud.vision.v1.AnnotateImageRequest
*
* @method v1.ImageAnnotatorClient#webDetection
* @param {object} request A representation of the request being sent to the
* Vision API. This is an {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* @param {object|string|Buffer} request A representation of the request
* being sent to the Vision API. This is an
* {@link google.cloud.vision.v1.AnnotateImageRequest AnnotateImageRequest}.
* For simple cases, you may also send a string (the URL or filename of
* the image) or a buffer (the image itself).
* @param {object} request.image A dictionary-like object representing the
* image. This should have a single key (`source`, `content`).
*
Expand Down
42 changes: 12 additions & 30 deletions packages/google-cloud-vision/system-test/vision.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,43 +81,25 @@ describe('Vision', function() {

it('should detect from a URL', () => {
var url = 'https://upload.wikimedia.org/wikipedia/commons/5/51/Google.png';
return client
.logoDetection({
image: {
source: {imageUri: url},
},
})
.then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
return client.logoDetection(url).then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
});

it('should detect from a filename', () => {
return client
.logoDetection({
image: {
source: {filename: IMAGES.logo},
},
})
.then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
return client.logoDetection(IMAGES.logo).then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
});

it('should detect from a Buffer', () => {
var buffer = fs.readFileSync(IMAGES.logo);
return client
.logoDetection({
image: {
content: buffer,
},
})
.then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
return client.logoDetection(buffer).then(responses => {
var response = responses[0];
assert.deepEqual(response.logoAnnotations[0].description, 'Google');
});
});

describe('single image', () => {
Expand Down
Loading

0 comments on commit 3eaa0fe

Please sign in to comment.