Skip to content

Commit

Permalink
Merge pull request #3 from danvk/classify-images
Browse files Browse the repository at this point in the history
Script to classify images
  • Loading branch information
danvk committed Feb 20, 2015
2 parents 6a56ab6 + 303e9f6 commit ba8cc29
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 13 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,26 @@ Then you'd make an HTML template for the task:

Finally, you'd start up the Local Turk server:

$ node localturk.js path/to/template.html path/to/tasks.csv path/to/output.csv
$ localturk path/to/template.html path/to/tasks.csv path/to/output.csv

Now you can visit http://localhost:4321/ to complete each task. When you're done, the output.csv file will contain

image_url,has_button
http://example.com/image_with_red_ball.png,yes
http://example.com/image_without_red_ball.png,no

Image Classification
--------------------

The use case described above (classifying images) is an extremely common one.

To expedite this, localturk provides a separate script for doing image
classification. The example above could be written as:


classify-images --labels 'Has a red ball,Does not have a red ball' *.png

This will bring up a web server with a UI for assigning one of those two labels
to each image on your local file system. The results will go in `output.csv`.

For more details, run `classify-images --help`.
82 changes: 82 additions & 0 deletions classify-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env node

/**
* This is an optimization for a common use case of localturk: classifying
* images. When you use this script, you can skip creating a CSV file of inputs
* and an HTML template.
*
* Usage:
*
* classify-images -o labels.csv --labels Yes,No,Maybe *.jpg
*
* This will present a web UI for classifying each image and put the output in
* labels.csv.
*/

var child_process = require('child_process'),
escape = require('escape-html'),
fs = require('fs'),
program = require('commander'),
temp = require('temp').track();

function list(val) {
return val.split(',');
}

program
.version('1.1.0')
.usage('[options] /path/to/images/*.jpg')
.option('-o, --output <file>',
'Path to output CSV file (default output.csv)', 'output.csv')
.option('-l, --labels <csv>',
'Comma-separated list of choices of labels', list, ['Yes', 'No'])
.option('-w, --max_width <pixels>',
'Make the images this width when displaying in-browser', parseInt)
.parse(process.argv)

if (program.args.length == 0) {
console.error('You must specify at least one image file!\n');
program.help(); // exits
}

if (fs.existsSync(program.output)) {
console.warn('Output file ' + program.output + ' already exists.');
console.warn('Its contents will be assumed to be previously-generated labels.');
console.warn('If you want to start from scratch, either delete this file,');
console.warn('rename it or specify a different output via --output.\n');
}

var csvInfo = temp.openSync({suffix: '.csv'}),
templateInfo = temp.openSync({suffix: '.html'});

fs.writeSync(csvInfo.fd, 'path\n' + program.args.join('\n') + '\n');
fs.closeSync(csvInfo.fd);

var buttonsHtml = program.labels.map(function(label, idx) {
var buttonText = label + ' (' + (1 + idx) + ')';
return '<button type="submit" id=' + (1+idx) + ' name="label" value="' + label + '">' + escape(buttonText) + '</button>'
}).join('&nbsp;');
var widthHtml = program.max_width ? ' width="' + program.max_width + '"' : '';
var html = buttonsHtml + '\n<p><img src="${path}" ' + widthHtml + '></p>';

// Add keyboard shortcuts. 1=first button, etc.
html += [
'<script>',
'window.addEventListener("keydown", function(e) {',
' var code = e.keyCode;',
' if (code < 48 || code > 57) return;',
' var el = document.getElementById(String.fromCharCode(code));',
' if (el) {',
' e.preventDefault();',
' el.click();',
' }',
'});',
'</script>'
].join('\n');

fs.writeSync(templateInfo.fd, html);
fs.closeSync(templateInfo.fd);

var args = ['localturk.js', '-q', '--static_dir', '.', templateInfo.path, csvInfo.path, program.output];
console.log('Running ', args.join(' '));
child_process.spawn(args[0], args.slice(1), {stdio: 'inherit'});
12 changes: 8 additions & 4 deletions localturk.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ var assert = require('assert'),
express = require('express'),
bodyParser = require('body-parser'),
errorhandler = require('errorhandler'),
// methodOverride = require('method-override'),
path = require('path'),
program = require('commander')
program = require('commander'),
open = require('open')
;

program
.version('1.0.0')
.version('1.1.0')
.usage('[options] template.html tasks.csv outputs.csv')
.option('-s, --static_dir <dir>', 'Serve static content from this directory')
.option('-p, --port <n>', 'Run on this port (default 4321)', parseInt)
.option('-q, --quit_on_done', 'Quit when done with all tasks.')
.parse(process.argv);

var args = program.args;
Expand Down Expand Up @@ -241,7 +242,6 @@ if (!fs.existsSync(outputs_file)) {
var app = express();
app.use(bodyParser.urlencoded({extended: false}))
app.set('views', __dirname);
// app.use(methodOverride());
app.set("view options", {layout: false});
app.use(errorhandler({
dumpExceptions:true,
Expand All @@ -267,6 +267,9 @@ app.get("/", function(req, res) {
});
}, function() {
res.send('DONE');
if (program.quit_on_done) {
process.exit(0);
}
});
});

Expand All @@ -284,3 +287,4 @@ app.post("/submit", function(req, res) {

app.listen(port);
console.log('Running local turk on http://localhost:' + port)
open('http://localhost:' + port + '/');
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
{
"name": "localturk",
"version": "1.0.0",
"version": "1.1.0",
"main": "localturk",
"bin": {
"localturk": "localturk.js"
"localturk": "localturk.js",
"classify-images": "classify-images.js"
},
"repository" : {
"type" : "git",
"url" : "http://github.com/danvk/localturk.git"
"repository": {
"type": "git",
"url": "http://github.com/danvk/localturk.git"
},
"dependencies": {
"errorhandler": "~1.2",
"express": "~4",
"body-parser": "~1.8",
"commander": "~2.3",
"csv": "~0.4",
"csv-parse": "~0",
"method-override": "~2.2"
"errorhandler": "~1.2",
"escape-html": "^1.0.1",
"express": "~4",
"method-override": "~2.2",
"open": "0.0.5",
"temp": "^0.8.1"
}
}

0 comments on commit ba8cc29

Please sign in to comment.