Skip to content
This repository has been archived by the owner on May 21, 2019. It is now read-only.

Commit

Permalink
Merge pull request #62 from watson-developer-cloud/alchemy-async
Browse files Browse the repository at this point in the history
Alchemy async
  • Loading branch information
germanattanasio committed Mar 23, 2016
2 parents 4137331 + d1ed867 commit 1e551c8
Show file tree
Hide file tree
Showing 20 changed files with 375 additions and 2,091 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ uploads/*
!uploads/README.md
VCAP_SERVICES.json
run.sh

.env.js
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Visual Recognition Nodejs Starter Application

The [IBM Watson Visual Recognition service][visual_recognition_service] analyzes the visual content of images to understand the scene without any input text describing
[Visual Recognition][visual_recognition_service] partnered with [Alchemy Vision](www.alchemyapi.com/products/alchemyvision), allows you to derive insights from an image based on its visual content. You can organize image libraries, understand an individual image, and create custom classifiers for specific results that are tailored to your needs.

Give it a try! Click the button below to fork into IBM DevOps Services and deploy your own copy of this application on Bluemix.

Expand Down Expand Up @@ -37,21 +37,26 @@ applications:
$ cf create-service visual_recognition free visual-recognition-service
```

6. Push it live!
6. Create the Alchemy service in Bluemix or copy your existing key into `ALCHEMY_KEY` in `app.js`
```sh
$ cf create-service alchemy_api free alchemy-service
```

7. Push it live!
```sh
$ cf push
```

7. Optional Security and Performance enhancements

Set `NODE_ENV=production` to enable view caching and other performance enhancements:

```sh
$ cf set-env <application-name> NODE_ENV production
```

Set `SECURE_EXPRESS=1` to enable rate-limiting, HTTPS-only, and several other security features:

```sh
$ cf set-env <application-name> SECURE_EXPRESS 1
```
Expand Down
106 changes: 90 additions & 16 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@

'use strict';

try {
var env = require('./.env.js');
console.log('loading .env.js');
for (var key in env) {
if (!(key in process.env))
process.env[key] = env[key];
}
} catch(ex) {
console.log('.env.js not found');
}

var express = require('express'),
app = express(),
fs = require('fs'),
Expand All @@ -26,6 +37,7 @@ var express = require('express'),
request = require('request'),
datasets = require('./public/data/datasets.json'),
zipUtils = require('./config/zip-utils'),
uuid = require('uuid'),
watson = require('watson-developer-cloud');


Expand All @@ -42,8 +54,24 @@ var visualRecognition = watson.visual_recognition({
version_date:'2015-12-02'
});

var alchemyVision = watson.alchemy_vision({
api_key: process.env.ALCHEMY_KEY || '<alchemy-key>'
});

app.get('/', function(req, res) {
res.render('index', datasets);
res.render('use', datasets);
});

app.get('/use', function(req, res) {
res.render('use', datasets);
});

app.get('/train', function(req, res) {
res.render('train', datasets);
});

app.get('/test', function(req, res) {
res.render('test', datasets);
});

/**
Expand All @@ -67,6 +95,36 @@ function filterUserCreatedClassifier(result, classifier_ids) {
}
return result;
}

/**
* Normalize Alchemy Vision results
* @param {Object} Alchemy vision result
* @return {Object} Visual Recognition result
*/
function normalizeResult(item) {
var result = {
name: item.text || 'Unknown',
score: parseFloat(item.score || '0')
};
return result;
}

function noTags(tag) {
return tag.name !== 'NO_TAGS';
}
/**
* Formats Alchemy Vision results to match the Watson Vision format
* @param {Object} result The result of calling 'classify()'
* @return {Object} The formatted 'result'
*/
function formatAlchemyVisionResults(results) {
return {
images: [{
scores: results.imageKeywords.map(normalizeResult).filter(noTags)
}]
};
}

/**
* Creates a classifier
* @param req.body.positives Array of base64 or relative images
Expand Down Expand Up @@ -138,6 +196,12 @@ app.post('/api/classify', app.upload.single('images_file'), function(req, res, n
if (req.file) {
// file image
file = fs.createReadStream(req.file.path);
} else if (req.body.image_data) {
// write the base64 image to a temp file
var resource = zipUtils.parseBase64Image(req.body.image_data);
var temp = './uploads/' + uuid.v1() + '.' + resource.type;
fs.writeFileSync(temp, resource.data);
file = fs.createReadStream(temp);
} else if (req.body.url && validator.isURL(req.body.url)) {
// web image
file = request(req.body.url.split('?')[0]);
Expand All @@ -149,23 +213,33 @@ app.post('/api/classify', app.upload.single('images_file'), function(req, res, n
return next({ error: 'Malformed URL', code: 400 });
}

var params = {
images_file: file
};
if (req.query.classifier_id) {
var vparams = {
images_file: file,
classifier_ids: [req.query.classifier_id]
};

if (req.query.classifier_id)
params.classifier_ids = [req.query.classifier_id];
visualRecognition.classify(vparams, function(err, results) {
if (req.file || req.body.image_data) // delete the recognized file
fs.unlink(file.path);

visualRecognition.classify(params, function(err, results) {
// delete the recognized file
if (req.file)
fs.unlink(file.path);

if (err)
return next(err);
else
res.json(filterUserCreatedClassifier(results, params.classifier_ids));
});
if (err)
return next(err);
else
res.json(filterUserCreatedClassifier(results, vparams.classifier_ids));
});
} else {
alchemyVision.getImageKeywords({ image: file}, function (err, results) {
// delete the recognized file
if (req.file || req.body.image_data)
fs.unlink(file.path);

if (err)
return next(err);
else
res.json(formatAlchemyVisionResults(results));
});
}
});

// error-handler settings
Expand Down
2 changes: 1 addition & 1 deletion config/security.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module.exports = function (app) {
app.use(secure());

// 2. helmet with defaults
app.use(helmet());
app.use(helmet({ cacheControl: false }));

// 3. rate-limit to /api/
app.use('/api/', rateLimit({
Expand Down
2 changes: 1 addition & 1 deletion config/zip-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function parseBase64Image(imageString) {
resource.data = new Buffer(matches[2], 'base64');
return resource;
}
module.exports.parseBase64Image = parseBase64Image;

/**
* Archives an image uning the @param archive module
Expand All @@ -61,7 +62,6 @@ function archiveImage(archive, image) {
}
}


/**
* Creates a zip file with the images array
* @param images The images to zip
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@
"start": "node app.js"
},
"dependencies": {
"archiver": "^0.16.0",
"archiver": "^0.21.0",
"async": "^1.5.0",
"body-parser": "^1.13.2",
"express": "^4.12.2",
"express-rate-limit": "^2.0.2",
"body-parser": "^1.15.0",
"express": "^4.13.4",
"express-rate-limit": "^2.1.0",
"express-secure-only": "^0.2.1",
"find-remove": "^0.2.8",
"find-remove": "^0.2.10",
"helmet": "^1.3.0",
"jade": "^1.11.0",
"multer": "^1.1.0",
"request": "^2.65.0",
"request": "^2.69.0",
"uuid": "^2.0.1",
"validator": "^4.7.0",
"watson-developer-cloud": "^1.0.6"
"validator": "^5.1.0",
"watson-developer-cloud": "^1.3.1"
}
}
Loading

0 comments on commit 1e551c8

Please sign in to comment.