diff --git a/README.md b/README.md index 1961fe75..88c62d3c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ aka "Consequencer" ## Why -Image Sequencer is different from other image processing systems in that it's non-destructive: instead of modifying the original image, it creates a new image at each step in a sequence. This is because it: +Image Sequencer is different from other image processing systems in that it's non-destructive: instead of modifying the original image, it creates a new image at each step in a sequence. This is because it: * produces a legible trail of operations, to "show your work" for evidential, educational, or reproducibility reasons * makes the creation of new tools or "modules" simpler -- each must accept an input image, and produce an output image @@ -28,9 +28,78 @@ Examples: * [Basic example](https://jywarren.github.io/image-sequencer/) * [NDVI example](https://jywarren.github.io/image-sequencer/examples/ndvi/) - related to [Infragram.org](http://infragram.org) +Using the library: +The Image Sequencer Library exports a function ImageSequencer which initializes a sequencer. +```js +var sequencer = ImageSequencer(); +``` +Image Sequencer has an array of images which gets stored in `sequencer.images` in this case. +Images can be loaded into this array by the method `loadImages`. +loadImages accepts 1, 2, or 3 parameters. +3/2 parameters : +```js +sequencer.loadImages(image_name,image_src,optional_callback); +``` +1/2 parameters (JSON) : +```js +sequencer.loadImages({ + image_name_1: image_src, + image_name_2: image_src, + ... +}, optional_callback); +``` + +After loading the image, we can add modules to the image using the addSteps method. +The options argument (object) is an optional parameter to pass in arguments to the module. +In all the following examples, `image_name` and `module_name` may be a string or an array of strings. +```js +sequencer.addSteps(image_name,module_name,optional_options); +``` +If no Image Name is specified, the module is added to **all** images. +```js +sequencer.addSteps(module_name,optional_options); +``` +All this can be passed in as JSON: +```js +sequencer.addSteps({ + image_name: {name: module_name, o: optional_options}, + image_name: {name: module_name, o: optional_options}, + ... +}); +``` + +After adding the steps, now we must generate output for each of the step via the `run` method. +The `run` method accepts parameters `image` and `from`. `from` is the index from where the function starts generating output. By default, it will run across all the steps. (from = 1) If no image is specified, the sequencer will be run over all the images. +```js +sequencer.run(); //All images from first step +``` +```js +sequencer.run(image,from); //Image 'image' from 'from' +``` +image may either be an array or a string. +An optional callback may also be passed. + + +Steps can be removed using the `removeSteps` method. It accepts `image` and `index` as parameters. +Either, both, or none of them can be an array. JSON input is also accepted. This method automatically triggers the `run` method as a callback. +```js +sequencer.removeSteps("image",[steps]); +``` +```js +sequencer.removeSteps("image",step); +``` +```js +sequencer.removeSteps({ + image: [steps], + image: [steps], + ... +}); +``` + + ## Contributing -Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`. +Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`. ### Contributing modules @@ -42,7 +111,7 @@ To add a module to Image Sequencer, it must have the following method; you can w * `module.draw(image)` -The `draw()` method should accept an `image` parameter, which will be a native JavaScript image object (i.e. `new Image()`). +The `draw()` method should accept an `image` parameter, which will be a native JavaScript image object (i.e. `new Image()`). The draw method must, when it is complete, pass the output image to the method `options.output(image)`, which will send the output to the next module in the chain. For example: @@ -220,8 +289,3 @@ Possible web-based commandline interface: https://hyper.is/? * vectorize * edge detect * direction find (vectorize and colorize) - - - - - diff --git a/index.js b/index.js index a327a0c2..aa744e7f 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,8 @@ console.log('\x1b[31m%s\x1b[0m',"This is the output of the module"); require('./src/ImageSequencer'); sequencer = ImageSequencer(); sequencer.loadImages({red:'examples/red.jpg'},function(){ - sequencer.addSteps(['do-nothing','do-nothing-pix','do-nothing']); + sequencer.addSteps(['do-nothing','do-nothing-pix','do-nothing','do-nothing']); + sequencer.addSteps(['do-nothing','do-nothing-pix','do-nothing','do-nothing']); sequencer.run(); sequencer.removeSteps(1); sequencer.insertSteps(1,'do-nothing'); diff --git a/src/ImageSequencer.js b/src/ImageSequencer.js index 34143106..d61c2634 100644 --- a/src/ImageSequencer.js +++ b/src/ImageSequencer.js @@ -28,6 +28,17 @@ ImageSequencer = function ImageSequencer(options) { } } + function copy(a) { + if (!typeof(a) == "object") return a; + if (objTypeOf(a) == "Array") return a.slice(); + if (objTypeOf(a) == "Object") return JSON.parse(JSON.stringify(a)); + return a; + } + + function makeArray(input) { + return (objTypeOf(input)=="Array")?input:[input]; + } + var image, steps = [], modules = require('./Modules'), @@ -37,14 +48,14 @@ ImageSequencer = function ImageSequencer(options) { // if (options.imageSelect || options.inBrowser) addStep('image-select'); // else if (options.imageUrl) loadImage(imageUrl); - function addStep(image, name, o) { + function addStep(image, name, o_) { log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".'); - o = o || {}; + o = {}; o.id = options.sequencerCounter++; //Gives a Unique ID to each step - o.name = o.name || name; - o.selector = o.selector || 'ismod-' + name; - o.container = o.container || options.selector; + o.name = o_.name || name; + o.selector = o_.selector || 'ismod-' + name; + o.container = o_.container || options.selector; o.image = image; var module = modules[name](o); @@ -61,38 +72,52 @@ ImageSequencer = function ImageSequencer(options) { else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?) // tell the UI that a step has been added. - return true; } function addSteps(){ - argtype = []; + args = []; json_q = {}; for (i in images) { lastimage = i; } for (var arg in arguments) { - argtype.push(objTypeOf(arguments[arg])); + args.push(copy(arguments[arg])); } - if (arguments.length == 1) { - if(argtype[0] == "Object") + if (args.length == 1) { + if(objTypeOf(args[0]) == "Object") //addSteps(JSON) json_q = arguments[0]; - else - for (i in images) - json_q[i] = (argtype[0]=="Array")?arguments[0]:[arguments[0]]; + else { //addSteps(name) => addSteps([image],[name]) + args.splice(0,0,[]); + for (img in images) args[0].push(img); + } } - else if (arguments.length == 2) { - if(argtype[1]=="String") arguments[1] = [arguments[1]]; - if(argtype[0]=="String") - json_q[arguments[0]] = arguments[1]; - else if(argtype[0]=="Array") - for (var i in arguments[0]) { - json_q[arguments[0][i]] = arguments[1]; + if (args.length == 2) { + //addSteps(name,o) => addSteps([image],[name],o) + if (objTypeOf(args[1])=="Object") { + args.splice(0,0,[]); + for (img in images) args[0].push(img); + } + else { //addSteps(image,name) => addSteps([image],[name],o) + args[2] = {}; + } + } + if (args.length == 3) { //addSteps(image,name,o) => addSteps(JSON) + args[0] = makeArray(args[0]); + args[1] = makeArray(args[1]); + for (img in args[0]) { + json_q[args[0][img]] = []; + for (step in args[1]) { + json_q[args[0][img]].push({ + name: args[1][step], + o: args[2] + }); } + } } for (i in json_q) for (j in json_q[i]) - addStep.call(this,i,json_q[i][j]); + addStep.call(this,i,json_q[i][j].name,json_q[i][j].o); } @@ -109,32 +134,32 @@ ImageSequencer = function ImageSequencer(options) { function removeSteps(image,index) { run = {}; this_ = this; + args = []; + for(var arg in arguments) args.push(copy(arguments[arg])); + json_q = {}; - if(arguments.length==2) { - removeStep(image,index); - run[image] = index; - } - else if(arguments.length==1) { - if (typeof(arguments[0])=="number" || objTypeOf(arguments[0])=="Array") { - indx = arguments[0]; - arguments[0] = {}; - for (img in this_.images) arguments[0][img] = indx; + if(args.length==1) { + if (objTypeOf(args[0])=="Object") { //removeSteps(JSON) + json_q = args[0]; + } + else { //removeSteps(index) => removeSteps([image],[index]) + args.splice(0,0,[]); + for(img in images) args[0].push(img); } - if (objTypeOf(arguments[0])=='Object') { - for (img in arguments[0]) { - var indexes = arguments[0][img]; - if (typeof(indexes) == "number") - {removeStep(img,indexes); run[img]=indexes;} - else if (objTypeOf(indexes) == "Array") { - indexes = indexes.sort(function(a,b){return b-a}); - run[img] = indexes[indexes.length-1]; - for (i in indexes) - removeStep(img,indexes[i]); - } - } - } // end if argument is object } - + if(args.length==2) { //removeSteps(image,index) => removeSteps(JSON) + args[0] = makeArray(args[0]); + args[1] = makeArray(args[1]); + for(img in args[0]) { + json_q[args[0][img]] = args[1]; + } + } + for (img in json_q) { + indices = json_q[img].sort(function(a,b){return b-a}); + run[img] = indices[indices.length-1]; + for (i in indices) + removeStep(img,indices[i]); + } this.run(run) } @@ -229,10 +254,16 @@ ImageSequencer = function ImageSequencer(options) { this_ = this; runimg = {}; json_q = {}; + args = []; + for (var arg in arguments) args.push(copy(arguments[arg])); + for (var arg in args) + if(objTypeOf(args[arg]) == "Function") + var callback = args.splice(arg,1)[0]; for (image in images) { runimg[image] = 0; } function drawStep(drawarray,pos) { + if(pos==drawarray.length) if(objTypeOf(callback)=='Function') callback(); if(pos>=drawarray.length) return true; image = drawarray[pos].image; i = drawarray[pos].i; diff --git a/test/image-sequencer.js b/test/image-sequencer.js index 8b69db98..4788963f 100644 --- a/test/image-sequencer.js +++ b/test/image-sequencer.js @@ -29,30 +29,52 @@ test('loadImages loads a step', function (t){ t.end(); }); -test('addStep adds a step', function (t) { +test('addSteps("image","name") adds a step', function (t) { sequencer.addSteps('test','do-nothing'); t.equal(sequencer.images.test.steps[1].options.name, "do-nothing"); t.equal(sequencer.images.test.steps.length, 2, "It Does!") t.end(); }); +test('addSteps("name") adds a step', function (t) { + sequencer.addSteps('do-nothing'); + t.equal(sequencer.images.test.steps[2].options.name, "do-nothing"); + t.equal(sequencer.images.test.steps.length, 3, "It Does!") + t.end(); +}); + +test('addSteps(["name"]) adds a step', function (t) { + sequencer.addSteps(['do-nothing','do-nothing-pix']); + t.equal(sequencer.images.test.steps[3].options.name, "do-nothing"); + t.equal(sequencer.images.test.steps[4].options.name, "do-nothing-pix"); + t.equal(sequencer.images.test.steps.length, 5, "It Does!") + t.end(); +}); + +test('addSteps("name",o) adds a step', function (t) { + sequencer.addSteps('do-nothing',{}); + t.equal(sequencer.images.test.steps[5].options.name, "do-nothing"); + t.equal(sequencer.images.test.steps.length, 6, "It Does!") + t.end(); +}); + +test('addSteps("image","name",o) adds a step', function (t) { + sequencer.addSteps('test','do-nothing',{}); + t.equal(sequencer.images.test.steps[6].options.name, "do-nothing"); + t.equal(sequencer.images.test.steps.length, 7, "It Does!") + t.end(); +}); + + test('removeSteps removes a step', function (t) { sequencer.removeSteps('test',1); - t.equal(sequencer.images.test.steps.length, 1, "It Does!"); + t.equal(sequencer.images.test.steps.length, 6, "It Does!"); t.end(); }); test('insertStep inserts a step', function (t) { sequencer.insertSteps('test',1,'do-nothing'); t.equal(sequencer.images.test.steps[1].options.name, "do-nothing"); - t.equal(sequencer.images.test.steps.length, 2, "It Does!"); - t.end(); -}); - -test('run creates output of steps', function (t) { - sequencer.run(); - var steps = sequencer.images.test.steps - var type = typeof(steps[steps.length-1].output) - t.equal(type,"object"); + t.equal(sequencer.images.test.steps.length, 7, "It Does!"); t.end(); });