This commit is contained in:
Chinmay Pandhare
2017-07-26 23:26:18 +05:30
21 changed files with 649 additions and 559 deletions

View File

@@ -325,6 +325,54 @@ sequencer.insertSteps({
return value: **`sequencer`** (To allow method chaining)
## Creating a User Interface
Image Sequencer provides the following events which can be used to generate a UI:
* `onSetup` : this event is triggered when a new module is set up. This can be used,
for instance, to generate a DIV element to store the generated image for that step.
* `onDraw` : This event is triggered when Image Sequencer starts drawing the output
for a module. This can be used, for instance, to overlay a loading GIF over the DIV
generated above.
* `onComplete` : This event is triggered when Image Sequencer has drawn the output
for a module. This can be used, for instance, to update the DIV with the new image
and remove the loading GIF generated above.
* `onRemove` : This event is triggered when a module is removed. This can be used,
for instance, to remove the DIV generated above.
How to define these functions:
```js
sequencer.setUI({
onSetup: function() {},
onDraw: function() {},
onComplete: function(output) {},
onRemove: function() {}
});
```
These methods can be defined and re-defined at any time, but it is advisable to
set them before any module is added and not change it thereafter. This is because
the `setUI` method will only affect the modules added after `setUI` is called.
The `onComplete` event is passed on the output of the module.
In the scope of all these events, the following variables are present, which
may be used in generating the UI:
* The object `identity`
```
identity = {
stepName: "Name of the Step",
stepID: "A unique ID given to the step",
imageName: "The name of the image to which the step is added."
}
```
* The variable `options.inBrowser` which is a Boolean and is `true` if the client is a browser and `false` otherwise.
Note: `identity.imageName` is the "name" of that particular image. This name can be specified
while loading the image via `sequencer.loadImage("name","SRC")`. If not specified,
the name of a loaded image defaults to a name like "image1", "image2", et cetra.
## Contributing
Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`.

File diff suppressed because one or more lines are too long

View File

@@ -27,7 +27,6 @@
"urify": "^2.1.0"
},
"devDependencies": {
"base64-stream": "~0.1.3",
"browserify": "13.0.0",
"buffer": "~5.0.2",
"get-pixels": "~3.3.0",

View File

@@ -1,30 +1,21 @@
function AddStep(ref, image, name, o) {
function addStep(image, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','adding step \"' + name + '\" to \"' + image + '\".');
var o = ref.copy(o_);
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o_.name || name;
o.selector = o_.selector || 'ismod-' + name;
o.container = o_.container || ref.options.selector;
o.image = image;
var module = ref.modules[name].call(ref.images,o);
var UI = ref.UI({
stepName: o.name,
stepID: o.number,
imageName: o.image
});
var module = ref.modules[name](o,UI);
ref.images[image].steps.push(module);
function defaultSetupModule() {
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
selector: o.selector,
title: module.options.title,
id: o.id
});
}
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
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;
}

View File

@@ -41,7 +41,10 @@ ImageSequencer = function ImageSequencer(options) {
modules = require('./Modules'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [];
inputlog = [],
UI;
setUI();
// if in browser, prompt for an image
// if (options.imageSelect || options.inBrowser) addStep('image-select');
@@ -66,7 +69,7 @@ ImageSequencer = function ImageSequencer(options) {
function removeStep(image,index) {
//remove the step from images[image].steps and redraw remaining images
if(index>0) {
log('\x1b[31m%s\x1b[0m',"Removing "+index+" from "+image);
images[image].steps[index].UI.onRemove();
images[image].steps.splice(index,1);
}
//tell the UI a step has been removed
@@ -112,7 +115,6 @@ ImageSequencer = function ImageSequencer(options) {
}
function run(t_image,t_from) {
log('\x1b[32m%s\x1b[0m',"Running the Sequencer!");
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
var args = (this.name == "ImageSequencer")?[]:[this.images];
for (var arg in arguments) args.push(copy(arguments[arg]));
@@ -148,6 +150,8 @@ ImageSequencer = function ImageSequencer(options) {
removeSteps: this.removeSteps,
insertSteps: this.insertSteps,
run: this.run,
UI: this.UI,
setUI: this.setUI,
images: loadedimages
};
}
@@ -157,9 +161,21 @@ ImageSequencer = function ImageSequencer(options) {
return require('./ReplaceImage')(this,selector,steps);
}
function setUI(_UI) {
UI = require('./UserInterface')(_UI,options);
return UI;
}
return {
//literals and objects
name: "ImageSequencer",
options: options,
inputlog: inputlog,
modules: modules,
images: images,
UI: UI,
//user functions
loadImages: loadImages,
loadImage: loadImages,
addSteps: addSteps,
@@ -167,10 +183,9 @@ ImageSequencer = function ImageSequencer(options) {
insertSteps: insertSteps,
replaceImage: replaceImage,
run: run,
inputlog: inputlog,
modules: modules,
images: images,
ui: options.ui,
setUI: setUI,
//other functions
log: log,
objTypeOf: objTypeOf,
copy: copy

View File

@@ -1,31 +1,22 @@
function InsertStep(ref, image, index, name, o) {
function insertStep(image, index, name, o_) {
ref.log('\x1b[36m%s\x1b[0m','inserting step \"' + name + '\" to \"' + image + '\" at \"'+index+'\".');
var o = ref.copy(o_);
o.id = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o.name || name;
o.selector = o.selector || 'ismod-' + name;
o.container = o.container || ref.options.selector;
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
o.name = o_.name || name;
o.selector = o_.selector || 'ismod-' + name;
o.container = o_.container || ref.options.selector;
o.image = image;
if(index==-1) index = ref.images[image].steps.length;
var module = ref.modules[name](o);
ref.images[image].steps.splice(index, 0, module);
function defaultSetupModule() {
if (ref.options.ui && ref.options.ui!="none") module.options.ui = ref.options.ui({
selector: o.selector,
title: module.options.title,
id: o.id
var UI = ref.UI({
stepName: o.name,
stepID: o.number,
imageName: o.image
});
}
if (module.hasOwnProperty('setup')) module.setup(); // add a default UI, unless the module has one specified
else defaultSetupModule.apply(module); // run default setup() in scope of module (is this right?)
// tell the UI that a step has been inserted.
var module = ref.modules[name](o,UI);
ref.images[image].steps.splice(index,0,module);
return true;
}

View File

@@ -17,6 +17,11 @@ function LoadImage(ref, name, src) {
name: "load-image",
title: "Load Image"
},
UI: ref.UI({
stepName: "load-image",
stepID: ref.options.sequencerCounter++,
imageName: name
}),
draw: function() {
if(arguments.length==1){
this.output = CImage(arguments[0]);
@@ -33,6 +38,9 @@ function LoadImage(ref, name, src) {
}]
};
ref.images[name] = image;
ref.images[name].steps[0].UI.onSetup();
ref.images[name].steps[0].UI.onDraw();
ref.images[name].steps[0].UI.onComplete(image.steps[0].output.src);
}
return loadImage(name,src);

View File

@@ -1,41 +1,72 @@
/*
* Default UI for each image-sequencer module
*/
module.exports = function UserInterface(options) {
module.exports = function UserInterface(UI,options) {
options = options || {};
options.container = options.container || ".panels";
options.id = options.id;
options.instanceName = options.instanceName;
options.random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000);
options.uniqueSelector = options.uniqueSelector || options.selector + '-' + options.random;
$(options.container).append('<div class="panel ' + options.selector + ' ' + options.uniqueSelector + '" id="sequencer-'+options.id+'"><div class="image"></div></div>');
options.el = options.el || $('.' + options.uniqueSelector);
createLabel(options.el);
return function userInterface(identity) {
// method to remove the UI for a given method, and remove the step
function display(image) {
options.el.find('.image').html(image);
var UI = UI || {};
UI.onSetup = UI.onSetup || function() {
if(options.ui == false) {
// No UI
}
else if(options.inBrowser) {
// Create and append an HTML Element
console.log("Added Step \""+identity.stepName+"\" to \""+identity.imageName+"\".");
}
else {
// Create a NodeJS Object
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+identity.stepName+"\" to \""+identity.imageName+"\".");
}
}
// method to remove the UI for a given method, and remove the step
function remove() {
$('div#sequencer-'+options.id).remove();
UI.onDraw = UI.onDraw || function() {
if (options.ui == false) {
// No UI
}
else if(options.inBrowser) {
// Overlay a loading spinner
console.log("Drawing Step \""+identity.stepName+"\" on \""+identity.imageName+"\".");
}
else {
// Don't do anything
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+identity.stepName+"\" on \""+identity.imageName+"\".");
}
}
// method to reorder steps, and update the UI
//function move() {}
function createLabel(el) {
if (options.title) el.prepend('<h3 class="title">' + options.title + '</h3> <button class="btn btn-default" onclick="'+options.instanceName+'.removeStep('+options.id+')">Remove Step</button>');
UI.onComplete = UI.onComplete || function(output) {
if (options.ui == false) {
// No UI
}
else if(options.inBrowser) {
// Update the DIV Element
// Hide the laoding spinner
console.log("Drawn Step \""+identity.stepName+"\" on \""+identity.imageName+"\".");
}
else {
// Update the NodeJS Object
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+identity.stepName+"\" on \""+identity.imageName+"\".");
}
}
return {
el: options.el,
uniqueSelector: options.uniqueSelector,
selector: options.selector,
display: display,
remove: remove
UI.onRemove = UI.onRemove || function(callback) {
if(options.ui == false){
// No UI
}
else if(options.inBrowser) {
// Remove the DIV Element
console.log("Removing Step \""+identity.stepName+"\" of \""+identity.imageName+"\".");
}
else {
// Delete the NodeJS Object
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+identity.stepName+"\" of \""+identity.imageName+"\".");
}
}
return UI;
}
}

View File

@@ -1,17 +1,15 @@
module.exports = function Crop(input,options,callback) {
var getPixels = require("get-pixels"),
savePixels = require("save-pixels"),
base64 = require('base64-stream');
var getPixels = require('get-pixels'),
savePixels = require('save-pixels');
getPixels(input.src,function(err,pixels){
var newdata = [];
var ox = options.x || 0;
var oy = options.y || 0;
var w = options.w || Math.floor(0.5*pixels.shape[0]);
var h = options.h || Math.floor(0.5*pixels.shape[1]);
var iw = pixels.shape[0]; //Width of Original Image
newarray = new Uint8Array(4*w*h);
var newarray = new Uint8Array(4*w*h);
for (var n = oy; n < oy + h; n++) {
newarray.set(pixels.data.slice(n*4*iw + ox, n*4*iw + ox + 4*w),4*w*(n-oy));
}
@@ -19,15 +17,21 @@ module.exports = function Crop(input,options,callback) {
pixels.shape = [w,h,4];
pixels.stride[1] = 4*w;
options.format = "jpeg";
options.format = input.format;
w = base64.encode();
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, options.format);
r.pipe(w).on('finish',function(){
data = w.read().toString();
datauri = 'data:image/' + options.format + ';base64,' + data;
r.on('data', function(chunk){
totalLength += chunk.length;
chunks.push(chunk);
});
r.on('end', function(){
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + options.format + ';base64,' + data;
callback(datauri,options.format);
});
});
}
};

View File

@@ -13,21 +13,23 @@
* y = options.y
* y = options.y + options.h
*/
module.exports = function CropModule(options) {
module.exports = function CropModule(options,UI) {
options = options || {};
options.title = "Crop Image";
var this_ = this;
UI.onSetup();
var output
function draw(input,callback) {
const this_ = this;
UI.onDraw();
const step = this;
require('./Crop')(input,options,function(out,format){
this_.output = {
step.output = {
src: out,
format: format
}
UI.onComplete(out);
callback();
});
@@ -37,6 +39,7 @@
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -1,23 +1,28 @@
/*
* Decodes QR from a given image.
*/
module.exports = function DoNothing(options) {
module.exports = function DoNothing(options,UI) {
options = options || {};
options.title = "Decode QR Code";
UI.onSetup();
var output;
var jsQR = require('jsqr');
var getPixels = require('get-pixels');
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
getPixels(input.src,function(err,pixels){
if(err) throw err;
var w = pixels.shape[0];
var h = pixels.shape[1];
var decoded = jsQR.decodeQRFromImage(pixels.data,w,h);
this_.output = input;
this_.output.data = decoded;
step.output = input;
step.output.data = decoded;
callback();
UI.onComplete(input.src);
});
}

View File

@@ -1,20 +1,23 @@
/*
* Demo Module. Does nothing. Adds a step where output is equal to input.
*/
module.exports = function DoNothing(options) {
module.exports = function DoNothing(options,UI) {
options = options || {};
options.title = "Do Nothing";
var this_ = this;
var output
UI.onSetup();
var output;
function draw(input,callback) {
UI.onDraw();
this.output = input;
callback();
UI.onComplete(this.output.src);
}
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -1,19 +1,24 @@
/*
* This module extracts pixels and saves them as it is.
*/
module.exports = function DoNothingPix(options) {
module.exports = function DoNothingPix(options,UI) {
options = options || {};
options.title = "Do Nothing with pixels";
UI.onSetup();
var output;
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
function changePixel(r, g, b, a) {
return [r, g, b, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
step.output = {src:datauri,format:mimetype}
UI.onComplete(datauri);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
@@ -22,11 +27,13 @@ module.exports = function DoNothingPix(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -1,22 +1,25 @@
/*
* Display only the green channel
*/
module.exports = function GreenChannel(options) {
module.exports = function GreenChannel(options,UI) {
options = options || {};
options.title = "Green channel only";
options.description = "Displays only the green channel of an image";
UI.onSetup();
var output;
//function setup() {} // optional
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
function changePixel(r, g, b, a) {
return [0, g, 0, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
@@ -25,12 +28,14 @@ module.exports = function GreenChannel(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -1,22 +1,27 @@
/*
* Display only the green channel
*/
module.exports = function GreenChannel(options) {
module.exports = function GreenChannel(options,UI) {
options = options || {};
options.title = "Invert Colors";
options.description = "Inverts the colors of the image";
UI.onSetup();
var output;
//function setup() {} // optional
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
function changePixel(r, g, b, a) {
return [255-r, 255-g, 255-b, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
@@ -25,12 +30,14 @@ module.exports = function GreenChannel(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
//setup: setup, // optional
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -1,21 +1,26 @@
/*
* NDVI with red filter (blue channel is infrared)
*/
module.exports = function NdviRed(options) {
module.exports = function NdviRed(options,UI) {
options = options || {};
options.title = "NDVI for red-filtered cameras (blue is infrared)";
UI.onSetup();
var output;
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
function changePixel(r, g, b, a) {
var ndvi = (b - r) / (1.00 * b + r);
var x = 255 * (ndvi + 1) / 2;
return [x, x, x, a];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
@@ -24,10 +29,13 @@ module.exports = function NdviRed(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw
draw: draw,
output: output,
UI:UI
}
}

View File

@@ -1,11 +1,15 @@
module.exports = function SegmentedColormap(options) {
module.exports = function SegmentedColormap(options,UI) {
options = options || {};
options.title = "Segmented Colormap";
UI.onSetup();
var output;
function draw(input,callback) {
var this_ = this;
UI.onDraw();
const step = this;
function changePixel(r, g, b, a) {
var ndvi = (b - r) / (r + b);
var normalized = (ndvi + 1) / 2;
@@ -13,7 +17,8 @@ module.exports = function SegmentedColormap(options) {
return [res[0], res[1], res[2], 255];
}
function output(image,datauri,mimetype){
this_.output = {src:datauri,format:mimetype}
step.output = {src:datauri,format:mimetype};
UI.onComplete(datauri);
}
return require('../_nomodule/PixelManipulation.js')(input, {
output: output,
@@ -22,11 +27,13 @@ module.exports = function SegmentedColormap(options) {
image: options.image,
callback: callback
});
}
return {
options: options,
draw: draw,
output: output
output: output,
UI: UI
}
}

View File

@@ -7,16 +7,15 @@ module.exports = function PixelManipulation(image, options) {
options = options || {};
options.changePixel = options.changePixel || function changePixel(r, g, b, a) {
return [r, g, b, a];
}
var getPixels = require("get-pixels"),
savePixels = require("save-pixels"),
base64 = require('base64-stream');
};
var getPixels = require('get-pixels'),
savePixels = require('save-pixels');
getPixels(image.src, function(err, pixels) {
if(err) {
console.log("Bad image path")
return
console.log('Bad image path');
return;
}
// iterate through pixels;
@@ -25,7 +24,7 @@ module.exports = function PixelManipulation(image, options) {
for(var x = 0; x < pixels.shape[0]; x++) {
for(var y = 0; y < pixels.shape[1]; y++) {
pixel = options.changePixel(
var pixel = options.changePixel(
pixels.get(x, y, 0),
pixels.get(x, y, 1),
pixels.get(x, y, 2),
@@ -40,19 +39,22 @@ module.exports = function PixelManipulation(image, options) {
}
}
options.format = "jpeg";
// there may be a more efficient means to encode an image object,
// but node modules and their documentation are essentially arcane on this point
w = base64.encode();
var chunks = [];
var totalLength = 0;
var r = savePixels(pixels, options.format);
r.pipe(w).on('finish',function(){
data = w.read().toString();
datauri = 'data:image/' + options.format + ';base64,' + data;
r.on('data', function(chunk){
totalLength += chunk.length;
chunks.push(chunk);
});
r.on('end', function(){
var data = Buffer.concat(chunks, totalLength).toString('base64');
var datauri = 'data:image/' + options.format + ';base64,' + data;
if (options.output) options.output(options.image,datauri,options.format);
if (options.callback) options.callback();
});
});
}
};

View File

@@ -8,7 +8,7 @@ var test = require('tape');
require('../src/ImageSequencer.js');
var sequencer = ImageSequencer({ ui: "none" });
var sequencer = ImageSequencer({ ui: false });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('loadImages/loadImage has a name generator.', function (t){

View File

@@ -7,7 +7,7 @@ var test = require('tape');
require('../src/ImageSequencer.js');
var sequencer = ImageSequencer({ ui: "none" });
var sequencer = ImageSequencer({ ui: false });
var image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
var qr = require('../examples/IS-QR.js');
sequencer.loadImages(image);

View File

@@ -25,7 +25,7 @@ function copy(g,a) {
var parent = (typeof(global)==="undefined")?window:global;
var global1 = copy(true,parent);
var sequencer = ImageSequencer({ ui: "none" });
var sequencer = ImageSequencer({ ui: false });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('Image Sequencer has tests', function (t) {