diff --git a/README.md b/README.md index 35ac3c2..7f44396 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/classify-images.js b/classify-images.js new file mode 100755 index 0000000..d396ae1 --- /dev/null +++ b/classify-images.js @@ -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 ', + 'Path to output CSV file (default output.csv)', 'output.csv') + .option('-l, --labels ', + 'Comma-separated list of choices of labels', list, ['Yes', 'No']) + .option('-w, --max_width ', + '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 '' +}).join(' '); +var widthHtml = program.max_width ? ' width="' + program.max_width + '"' : ''; +var html = buttonsHtml + '\n

'; + +// Add keyboard shortcuts. 1=first button, etc. +html += [ + '' +].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'}); diff --git a/localturk.js b/localturk.js index 4ad760f..28a052e 100755 --- a/localturk.js +++ b/localturk.js @@ -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 ', 'Serve static content from this directory') .option('-p, --port ', '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; @@ -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, @@ -267,6 +267,9 @@ app.get("/", function(req, res) { }); }, function() { res.send('DONE'); + if (program.quit_on_done) { + process.exit(0); + } }); }); @@ -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 + '/'); diff --git a/package.json b/package.json index 4bf96a0..e3d3a91 100644 --- a/package.json +++ b/package.json @@ -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" } }