Skip to content

Commit ba8cc29

Browse files
committed
Merge pull request #3 from danvk/classify-images
Script to classify images
2 parents 6a56ab6 + 303e9f6 commit ba8cc29

File tree

4 files changed

+119
-13
lines changed

4 files changed

+119
-13
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,26 @@ Then you'd make an HTML template for the task:
5050

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

53-
$ node localturk.js path/to/template.html path/to/tasks.csv path/to/output.csv
53+
$ localturk path/to/template.html path/to/tasks.csv path/to/output.csv
5454

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

5757
image_url,has_button
5858
http://example.com/image_with_red_ball.png,yes
5959
http://example.com/image_without_red_ball.png,no
60+
61+
Image Classification
62+
--------------------
63+
64+
The use case described above (classifying images) is an extremely common one.
65+
66+
To expedite this, localturk provides a separate script for doing image
67+
classification. The example above could be written as:
68+
69+
70+
classify-images --labels 'Has a red ball,Does not have a red ball' *.png
71+
72+
This will bring up a web server with a UI for assigning one of those two labels
73+
to each image on your local file system. The results will go in `output.csv`.
74+
75+
For more details, run `classify-images --help`.

classify-images.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* This is an optimization for a common use case of localturk: classifying
5+
* images. When you use this script, you can skip creating a CSV file of inputs
6+
* and an HTML template.
7+
*
8+
* Usage:
9+
*
10+
* classify-images -o labels.csv --labels Yes,No,Maybe *.jpg
11+
*
12+
* This will present a web UI for classifying each image and put the output in
13+
* labels.csv.
14+
*/
15+
16+
var child_process = require('child_process'),
17+
escape = require('escape-html'),
18+
fs = require('fs'),
19+
program = require('commander'),
20+
temp = require('temp').track();
21+
22+
function list(val) {
23+
return val.split(',');
24+
}
25+
26+
program
27+
.version('1.1.0')
28+
.usage('[options] /path/to/images/*.jpg')
29+
.option('-o, --output <file>',
30+
'Path to output CSV file (default output.csv)', 'output.csv')
31+
.option('-l, --labels <csv>',
32+
'Comma-separated list of choices of labels', list, ['Yes', 'No'])
33+
.option('-w, --max_width <pixels>',
34+
'Make the images this width when displaying in-browser', parseInt)
35+
.parse(process.argv)
36+
37+
if (program.args.length == 0) {
38+
console.error('You must specify at least one image file!\n');
39+
program.help(); // exits
40+
}
41+
42+
if (fs.existsSync(program.output)) {
43+
console.warn('Output file ' + program.output + ' already exists.');
44+
console.warn('Its contents will be assumed to be previously-generated labels.');
45+
console.warn('If you want to start from scratch, either delete this file,');
46+
console.warn('rename it or specify a different output via --output.\n');
47+
}
48+
49+
var csvInfo = temp.openSync({suffix: '.csv'}),
50+
templateInfo = temp.openSync({suffix: '.html'});
51+
52+
fs.writeSync(csvInfo.fd, 'path\n' + program.args.join('\n') + '\n');
53+
fs.closeSync(csvInfo.fd);
54+
55+
var buttonsHtml = program.labels.map(function(label, idx) {
56+
var buttonText = label + ' (' + (1 + idx) + ')';
57+
return '<button type="submit" id=' + (1+idx) + ' name="label" value="' + label + '">' + escape(buttonText) + '</button>'
58+
}).join('&nbsp;');
59+
var widthHtml = program.max_width ? ' width="' + program.max_width + '"' : '';
60+
var html = buttonsHtml + '\n<p><img src="${path}" ' + widthHtml + '></p>';
61+
62+
// Add keyboard shortcuts. 1=first button, etc.
63+
html += [
64+
'<script>',
65+
'window.addEventListener("keydown", function(e) {',
66+
' var code = e.keyCode;',
67+
' if (code < 48 || code > 57) return;',
68+
' var el = document.getElementById(String.fromCharCode(code));',
69+
' if (el) {',
70+
' e.preventDefault();',
71+
' el.click();',
72+
' }',
73+
'});',
74+
'</script>'
75+
].join('\n');
76+
77+
fs.writeSync(templateInfo.fd, html);
78+
fs.closeSync(templateInfo.fd);
79+
80+
var args = ['localturk.js', '-q', '--static_dir', '.', templateInfo.path, csvInfo.path, program.output];
81+
console.log('Running ', args.join(' '));
82+
child_process.spawn(args[0], args.slice(1), {stdio: 'inherit'});

localturk.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ var assert = require('assert'),
1313
express = require('express'),
1414
bodyParser = require('body-parser'),
1515
errorhandler = require('errorhandler'),
16-
// methodOverride = require('method-override'),
1716
path = require('path'),
18-
program = require('commander')
17+
program = require('commander'),
18+
open = require('open')
1919
;
2020

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

2829
var args = program.args;
@@ -241,7 +242,6 @@ if (!fs.existsSync(outputs_file)) {
241242
var app = express();
242243
app.use(bodyParser.urlencoded({extended: false}))
243244
app.set('views', __dirname);
244-
// app.use(methodOverride());
245245
app.set("view options", {layout: false});
246246
app.use(errorhandler({
247247
dumpExceptions:true,
@@ -267,6 +267,9 @@ app.get("/", function(req, res) {
267267
});
268268
}, function() {
269269
res.send('DONE');
270+
if (program.quit_on_done) {
271+
process.exit(0);
272+
}
270273
});
271274
});
272275

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

285288
app.listen(port);
286289
console.log('Running local turk on http://localhost:' + port)
290+
open('http://localhost:' + port + '/');

package.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
{
22
"name": "localturk",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"main": "localturk",
55
"bin": {
6-
"localturk": "localturk.js"
6+
"localturk": "localturk.js",
7+
"classify-images": "classify-images.js"
78
},
8-
"repository" : {
9-
"type" : "git",
10-
"url" : "http://github.com/danvk/localturk.git"
9+
"repository": {
10+
"type": "git",
11+
"url": "http://github.com/danvk/localturk.git"
1112
},
1213
"dependencies": {
13-
"errorhandler": "~1.2",
14-
"express": "~4",
1514
"body-parser": "~1.8",
1615
"commander": "~2.3",
1716
"csv": "~0.4",
1817
"csv-parse": "~0",
19-
"method-override": "~2.2"
18+
"errorhandler": "~1.2",
19+
"escape-html": "^1.0.1",
20+
"express": "~4",
21+
"method-override": "~2.2",
22+
"open": "0.0.5",
23+
"temp": "^0.8.1"
2024
}
2125
}

0 commit comments

Comments
 (0)