Meta modules (#308)

* meta-modules in node

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* meta-modules in browser

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* update docs

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* more doc

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fix typo

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* update docs and sample meta-module

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add dynamic modules CLI from npm

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* update

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* meta-modules and sequence saving completed

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* add docs

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* update docs

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>

* fixed test

Signed-off-by: tech4GT <varun.gupta1798@gmail.com>
This commit is contained in:
Varun Gupta
2018-08-04 19:37:20 +05:30
committed by Jeffrey Warren
parent 10e47656a3
commit 5ab018fb84
22 changed files with 664 additions and 306 deletions

View File

@@ -265,3 +265,70 @@ See existing module `green-channel` for an example: https://github.com/publiclab
The `green-channel` module is included into the core modules here: https://github.com/publiclab/image-sequencer/blob/master/src/Modules.js#L5-L7
For help integrating, please open an issue.
## Meta Module
IMAGE SEQUENCER supports "meta modules" -- modules made of other modules. The syntax and structure of these meta modules is very similar to standard modules. Sequencer can also genarate meta modules dynamically with the function `createMetaModule` which can be called in the following ways
```js
// stepsString is a stringified sequence
sequencer.createMetaModule(stepsString,info)
/* stepsArray is the array of objects in this format
* [
* {name: "moduleName",options: {}},
* {name: "moduleName",options: {}}
* ]
*/
sequencer.createMetaModule(stepsArray,info)
```
A Meta module can also be contributed like a normal module with an info and a Module.js. A basic Meta module shall follow the following format
```js
// Module.js
module.exports = function metaModuleFun(){
this.expandSteps([
{ 'name': 'module-name', 'options': {} },
{ 'name': 'module-name', options: {} }
]);
}
```
```json
// Info
{
"name": "meta-moduleName",
"description": "",
"length": //Integer representing number of steps in the metaModule
}
```
```js
//index.js
module.exports = [
require('./Module.js'),
require('./info.json')
]
```
All of the above can also be combined together to form a single file module.
```js
// MetaModule.js
module.export = [
function (){
this.expandSteps([
{ 'name': 'module-name', 'options': {} },
{ 'name': 'module-name', options: {} }
]);
},
{
"name": "meta-moduleName",
"description": "",
"length": //Integer representing number of steps in the metaModule
}
]
```
The length is absolutely required for a meta-module, since sequencer is optimized to re-run minimum number of steps when a step is added in the UI which is 1 in the case of normal modules, if the added step is a meta-module the length of the sequence governs the number of steps to re-run.

View File

@@ -104,6 +104,8 @@ Image Sequencer also provides a CLI for applying operations to local files. The
-b | --basic | Basic mode only outputs the final image
-o | --output [PATH] | Directory where output will be stored. (optional)
-c | --config {object} | Options for the step. (optional)
--save-sequence [string] | Name space separated with Stringified sequence to save
--install-module [string] | Module name space seaprated npm package name
The basic format for using the CLI is as follows:
@@ -130,6 +132,15 @@ Or the values can be given through terminal prompt like
<img width="1436" alt="screen shot 2018-02-14 at 5 18 50 pm" src="https://user-images.githubusercontent.com/25617855/36202790-3c6e8204-11ab-11e8-9e17-7f3387ab0158.png">
`save-sequence` option can be used to save a sequence and the associated options for later usage. You should provide a string which contains a name of the sequence space separated from the sequence of steps which constitute it.
```shell
sequencer --save-sequence "invert-colormap invert(),colormap()"
```
`install-module` option can be used to install new modules from npm. You can register this module in your sequencer with a custom name space sepated with the npm package name. Below is an example for the `image-sequencer-invert` module.
```shell
sequencer --install-module "invert image-sequencer-invert"
```
The CLI is also chainable with other commands using `&&`
@@ -461,6 +472,20 @@ sequencer.insertSteps({
```
return value: **`sequencer`** (To allow method chaining)
## Saving Sequences
IMAGE SEQUENCER supports saving a sequence of modules and their associated settings in a simple string syntax. These sequences can be saved in the local storage inside the browser and inside a json file in node.js. sequences can be saved in node context using the CLI option
```shell
--save-sequence "name stringified-sequence"
```
In Node and the browser the following function can be used
```js
sequencer.saveSequence(name,sequenceString)
```
The function `sequencer.loadModules()` reloads the modules and the saved sequences into `sequencer.modules` and `sequencer.sequences`
## Creating a User Interface

View File

@@ -47602,6 +47602,7 @@ ImageSequencer = function ImageSequencer(options) {
var image,
steps = [],
modules = require('./Modules'),
sequences = require('./SavedSequences.json'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [],
@@ -47614,6 +47615,11 @@ ImageSequencer = function ImageSequencer(options) {
for (o in sequencer) {
modules[o] = sequencer[o];
}
sequences = JSON.parse(window.localStorage.getItem('sequences'));
if (!sequences) {
sequences = {};
window.localStorage.setItem('sequences', JSON.stringify(sequences));
}
}
// if in browser, prompt for an image
@@ -47770,11 +47776,20 @@ ImageSequencer = function ImageSequencer(options) {
function modulesInfo(name) {
var modulesdata = {}
if (name == "load-image") return {};
if (arguments.length == 0)
for (var modulename in modules) {
if (arguments.length == 0) {
for (var modulename in this.modules) {
modulesdata[modulename] = modules[modulename][1];
}
else modulesdata = modules[name][1];
for (var sequencename in this.sequences) {
modulesdata[sequencename] = { name: sequencename, steps: sequences[sequencename] };
}
}
else {
if (modules[name])
modulesdata = modules[name][1];
else
modulesdata = { 'inputs': sequences[name]['options'] };
}
return modulesdata;
}
@@ -47808,7 +47823,11 @@ ImageSequencer = function ImageSequencer(options) {
// Coverts stringified sequence into an array of JSON steps
function stringToJSON(str) {
let steps = str.split(',');
let steps;
if (str.includes(','))
steps = str.split(',');
else
steps = [str];
return steps.map(stringToJSONstep);
}
@@ -47866,15 +47885,17 @@ ImageSequencer = function ImageSequencer(options) {
if (!options) {
return this;
} else if (Array.isArray(options)) {
// contains the array of module and info
this.module[name] = options;
this.modules[name] = options;
} else if (options.func && options.info) {
// passed in options object
this.modules[name] = [
options.func, options.info
];
} else if (options.path && !this.inBrowser) {
// load from path(only in node)
const module = [
@@ -47886,12 +47907,62 @@ ImageSequencer = function ImageSequencer(options) {
return this;
}
function saveNewModule(name, path) {
if (options.inBrowser) {
// Not for browser context
return;
}
var mods = fs.readFileSync('./src/Modules.js').toString();
mods = mods.substr(0, mods.length - 1) + " '" + name + "': require('" + path + "'),\n}";
fs.writeFileSync('./src/Modules.js', mods);
}
function createMetaModule(stepsCollection, info) {
var stepsArr = stepsCollection;
if (typeof stepsCollection === 'string')
stepsArr = stringToJSON(stepsCollection);
var metaMod = function() {
this.expandSteps(stepsArr);
return {
isMeta: true
}
}
return [metaMod, info];
}
function saveSequence(name, sequenceString) {
const sequence = stringToJSON(sequenceString);
// Save the given sequence string as a module
if (options.inBrowser) {
// Inside the browser we save the meta-modules using the Web Storage API
var sequences = JSON.parse(window.localStorage.getItem('sequences'));
sequences[name] = sequence;
window.localStorage.setItem('sequences', JSON.stringify(sequences));
}
else {
// In node we save the sequences in the json file SavedSequences.json
var sequences = require('./SavedSequences.json');
sequences[name] = sequence;
fs.writeFileSync('./src/SavedSequences.json', JSON.stringify(sequences));
}
}
function loadModules() {
// This function loads the modules and saved sequences
this.modules = require('./Modules');
if (options.inBrowser)
this.sequences = JSON.parse(window.localStorage.getItem('sequences'));
else
this.sequences = require('./SavedSequences.json');
}
return {
//literals and objects
name: "ImageSequencer",
options: options,
inputlog: inputlog,
modules: modules,
sequences: sequences,
images: images,
events: events,
@@ -47914,6 +47985,10 @@ ImageSequencer = function ImageSequencer(options) {
importString: importString,
importJSON: importJSON,
loadNewModule: loadNewModule,
saveNewModule: saveNewModule,
createMetaModule: createMetaModule,
saveSequence: saveSequence,
loadModules: loadModules,
//other functions
log: log,
@@ -47926,15 +48001,20 @@ ImageSequencer = function ImageSequencer(options) {
}
module.exports = ImageSequencer;
},{"./AddStep":136,"./ExportBin":137,"./FormatInput":138,"./InsertStep":140,"./Modules":141,"./ReplaceImage":142,"./Run":143,"./ui/LoadImage":194,"./ui/SetInputStep":195,"./ui/UserInterface":196,"./util/getStep.js":198,"fs":42}],140:[function(require,module,exports){
},{"./AddStep":136,"./ExportBin":137,"./FormatInput":138,"./InsertStep":140,"./Modules":141,"./ReplaceImage":142,"./Run":143,"./SavedSequences.json":145,"./ui/LoadImage":198,"./ui/SetInputStep":199,"./ui/UserInterface":200,"./util/getStep.js":202,"fs":42}],140:[function(require,module,exports){
const getStepUtils = require('./util/getStep.js');
// insert one or more steps at a given index in the sequencer
function InsertStep(ref, image, index, name, o) {
if (ref.sequences[name]) {
return ref.importJSON(ref.sequences[name]);
}
function insertStep(image, index, name, o_) {
if (ref.modules[name]) var moduleInfo = ref.modules[name][1];
else console.log('Module ' + name + ' not found.');
else {
console.log('Module ' + name + ' not found.');
}
var o = ref.copy(o_);
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
@@ -47961,7 +48041,13 @@ function InsertStep(ref, image, index, name, o) {
// Tell UI that a step has been set up.
o = o || {};
UI.onSetup(o.step);
ref.modules[name].expandSteps = function expandSteps(stepsArray) {
for (var step of stepsArray) {
ref.addSteps(step['name'], step['options']);
}
}
var module = ref.modules[name][0](o, UI);
if (!module.isMeta)
ref.images[image].steps.splice(index, 0, module);
return true;
@@ -47973,7 +48059,7 @@ function InsertStep(ref, image, index, name, o) {
}
module.exports = InsertStep;
},{"./util/getStep.js":198}],141:[function(require,module,exports){
},{"./util/getStep.js":202}],141:[function(require,module,exports){
/*
* Core modules and their info files
*/
@@ -47992,10 +48078,10 @@ module.exports = {
'average': require('./modules/Average'),
'blend': require('./modules/Blend'),
'import-image': require('./modules/ImportImage'),
'invert': require('image-sequencer-invert')
'invert': require('image-sequencer-invert'),
'ndvi-colormap': require('./modules/NdviColormap'),
}
},{"./modules/Average":146,"./modules/Blend":149,"./modules/Blur":153,"./modules/Brightness":156,"./modules/Channel":159,"./modules/Colormap":163,"./modules/Crop":168,"./modules/DecodeQr":171,"./modules/Dynamic":174,"./modules/EdgeDetect":178,"./modules/FisheyeGl":181,"./modules/ImportImage":185,"./modules/Ndvi":188,"./modules/Saturation":191,"image-sequencer-invert":56}],142:[function(require,module,exports){
},{"./modules/Average":147,"./modules/Blend":150,"./modules/Blur":154,"./modules/Brightness":157,"./modules/Channel":160,"./modules/Colormap":164,"./modules/Crop":169,"./modules/DecodeQr":172,"./modules/Dynamic":175,"./modules/EdgeDetect":179,"./modules/FisheyeGl":182,"./modules/ImportImage":186,"./modules/Ndvi":189,"./modules/NdviColormap":192,"./modules/Saturation":195,"image-sequencer-invert":56}],142:[function(require,module,exports){
// Uses a given image as input and replaces it with the output.
// Works only in the browser.
function ReplaceImage(ref,selector,steps,options) {
@@ -48151,7 +48237,7 @@ function Run(ref, json_q, callback, ind, progressObj) {
}
module.exports = Run;
},{"./RunToolkit":144,"./util/getStep.js":198}],144:[function(require,module,exports){
},{"./RunToolkit":144,"./util/getStep.js":202}],144:[function(require,module,exports){
const getPixels = require('get-pixels');
const pixelManipulation = require('./modules/_nomodule/PixelManipulation');
const lodash = require('lodash');
@@ -48166,7 +48252,9 @@ module.exports = function(input) {
input.savePixels = savePixels;
return input;
}
},{"./modules/_nomodule/PixelManipulation":193,"data-uri-to-buffer":13,"get-pixels":23,"lodash":62,"save-pixels":111}],145:[function(require,module,exports){
},{"./modules/_nomodule/PixelManipulation":197,"data-uri-to-buffer":13,"get-pixels":23,"lodash":62,"save-pixels":111}],145:[function(require,module,exports){
module.exports={"sample":[{"name":"invert","options":{}},{"name":"channel","options":{"channel":"red"}},{"name":"blur","options":{"blur":"5"}}]}
},{}],146:[function(require,module,exports){
/*
* Average all pixel colors
*/
@@ -48244,12 +48332,12 @@ module.exports = function Average(options, UI){
}
}
},{"../_nomodule/PixelManipulation.js":193}],146:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],147:[function(require,module,exports){
module.exports = [
require('./Module'),
require('./info.json')
]
},{"./Module":145,"./info.json":147}],147:[function(require,module,exports){
},{"./Module":146,"./info.json":148}],148:[function(require,module,exports){
module.exports={
"name": "Average",
"description": "Average all pixel color",
@@ -48257,7 +48345,7 @@ module.exports={
}
}
},{}],148:[function(require,module,exports){
},{}],149:[function(require,module,exports){
module.exports = function Dynamic(options, UI, util) {
options.func = options.func || "function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }";
@@ -48322,9 +48410,9 @@ module.exports = function Dynamic(options, UI, util) {
}
}
},{"../_nomodule/PixelManipulation.js":193,"get-pixels":23}],149:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":148,"./info.json":150,"dup":146}],150:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197,"get-pixels":23}],150:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":149,"./info.json":151,"dup":147}],151:[function(require,module,exports){
module.exports={
"name": "Blend",
"description": "Blend the past two image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2. Easier to use interfaces coming soon!",
@@ -48337,7 +48425,7 @@ module.exports={
}
}
},{}],151:[function(require,module,exports){
},{}],152:[function(require,module,exports){
module.exports = exports = function(pixels,blur){
let kernel = kernelGenerator(blur,1)
kernel = flipKernel(kernel)
@@ -48423,14 +48511,13 @@ function flipKernel(kernel){
return result
}
}
},{}],152:[function(require,module,exports){
},{}],153:[function(require,module,exports){
/*
* Blur an Image
*/
module.exports = function Blur(options, UI) {
options.blur = options.blur || 2
var output;
function draw(input, callback, progressObj) {
@@ -48474,9 +48561,9 @@ module.exports = function Blur(options,UI){
}
}
},{"../_nomodule/PixelManipulation.js":193,"./Blur":151}],153:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":152,"./info.json":154,"dup":146}],154:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197,"./Blur":152}],154:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":153,"./info.json":155,"dup":147}],155:[function(require,module,exports){
module.exports={
"name": "Blur",
"description": "Gaussian blur an image by a given value, typically 0-5",
@@ -48489,7 +48576,7 @@ module.exports={
}
}
},{}],155:[function(require,module,exports){
},{}],156:[function(require,module,exports){
/*
* Changes the Image Brightness
*/
@@ -48545,9 +48632,9 @@ module.exports = function Brightness(options,UI){
}
}
},{"../_nomodule/PixelManipulation.js":193}],156:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":155,"./info.json":157,"dup":146}],157:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],157:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":156,"./info.json":158,"dup":147}],158:[function(require,module,exports){
module.exports={
"name": "Brightness",
"description": "Change the brightness of the image by given percent value",
@@ -48560,7 +48647,7 @@ module.exports={
}
}
},{}],158:[function(require,module,exports){
},{}],159:[function(require,module,exports){
/*
* Display only one color channel
*/
@@ -48610,9 +48697,9 @@ module.exports = function Channel(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193}],159:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":158,"./info.json":160,"dup":146}],160:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],160:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":159,"./info.json":161,"dup":147}],161:[function(require,module,exports){
module.exports={
"name": "Channel",
"description": "Displays only one color channel of an image -- default is green",
@@ -48626,7 +48713,7 @@ module.exports={
}
}
},{}],161:[function(require,module,exports){
},{}],162:[function(require,module,exports){
/*
* Accepts a value from 0-255 and returns the new color-mapped pixel
* from a lookup table, which can be specified as an array of [begin, end]
@@ -48715,7 +48802,7 @@ var colormaps = {
])
}
},{}],162:[function(require,module,exports){
},{}],163:[function(require,module,exports){
module.exports = function Colormap(options,UI) {
var output;
@@ -48759,9 +48846,9 @@ module.exports = function Colormap(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193,"./Colormap":161}],163:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":162,"./info.json":164,"dup":146}],164:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197,"./Colormap":162}],164:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":163,"./info.json":165,"dup":147}],165:[function(require,module,exports){
module.exports={
"name": "Colormap",
"description": "Maps brightness values (average of red, green & blue) to a given color lookup table, made up of a set of one more color gradients.\n\nFor example, 'cooler' colors like blue could represent low values, while 'hot' colors like red could represent high values.",
@@ -48775,7 +48862,7 @@ module.exports={
}
}
},{}],165:[function(require,module,exports){
},{}],166:[function(require,module,exports){
(function (Buffer){
module.exports = function Crop(input,options,callback) {
@@ -48821,7 +48908,7 @@ module.exports = function Crop(input,options,callback) {
};
}).call(this,require("buffer").Buffer)
},{"buffer":4,"get-pixels":23,"save-pixels":111}],166:[function(require,module,exports){
},{"buffer":4,"get-pixels":23,"save-pixels":111}],167:[function(require,module,exports){
/*
* Image Cropping module
* Usage:
@@ -48893,7 +48980,7 @@ module.exports = function CropModule(options, UI) {
}
}
},{"./Crop":165,"./Ui.js":167}],167:[function(require,module,exports){
},{"./Crop":166,"./Ui.js":168}],168:[function(require,module,exports){
// hide on save
module.exports = function CropModuleUi(step, ui) {
@@ -48992,9 +49079,9 @@ module.exports = function CropModuleUi(step, ui) {
}
}
},{}],168:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":166,"./info.json":169,"dup":146}],169:[function(require,module,exports){
},{}],169:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":167,"./info.json":170,"dup":147}],170:[function(require,module,exports){
module.exports={
"name": "Crop",
"description": "Crop image to given x, y, w, h in pixels, measured from top left",
@@ -49022,7 +49109,7 @@ module.exports={
}
}
}
},{}],170:[function(require,module,exports){
},{}],171:[function(require,module,exports){
/*
* Decodes QR from a given image.
*/
@@ -49065,9 +49152,9 @@ module.exports = function DoNothing(options,UI) {
}
}
},{"get-pixels":23,"jsqr":61}],171:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":170,"./info.json":172,"dup":146}],172:[function(require,module,exports){
},{"get-pixels":23,"jsqr":61}],172:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":171,"./info.json":173,"dup":147}],173:[function(require,module,exports){
module.exports={
"name": "Decode QR",
"description": "Search for and decode a QR code in the image",
@@ -49080,7 +49167,7 @@ module.exports={
}
}
},{}],173:[function(require,module,exports){
},{}],174:[function(require,module,exports){
module.exports = function Dynamic(options,UI) {
var output;
@@ -49165,9 +49252,9 @@ module.exports = function Dynamic(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193}],174:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":173,"./info.json":175,"dup":146}],175:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],175:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":174,"./info.json":176,"dup":147}],176:[function(require,module,exports){
module.exports={
"name": "Dynamic",
"description": "A module which accepts JavaScript math expressions to produce each color channel based on the original image's color. See <a href='https://publiclab.org/wiki/infragram-sandbox'>Infragrammar</a>.",
@@ -49195,7 +49282,7 @@ module.exports={
}
}
},{}],176:[function(require,module,exports){
},{}],177:[function(require,module,exports){
const _ = require('lodash')
//define kernels for the sobel filter
@@ -49376,7 +49463,7 @@ function hysteresis(pixels){
},{"lodash":62}],177:[function(require,module,exports){
},{"lodash":62}],178:[function(require,module,exports){
/*
* Detect Edges in an Image
*/
@@ -49434,9 +49521,9 @@ module.exports = function edgeDetect(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193,"./EdgeUtils":176,"ndarray-gaussian-filter":67}],178:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":177,"./info.json":179,"dup":146}],179:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197,"./EdgeUtils":177,"ndarray-gaussian-filter":67}],179:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":178,"./info.json":180,"dup":147}],180:[function(require,module,exports){
module.exports={
"name": "Detect Edges",
"description": "this module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. Read more at: https://en.wikipedia.org/wiki/Canny_edge_detector",
@@ -49459,7 +49546,7 @@ module.exports={
}
}
},{}],180:[function(require,module,exports){
},{}],181:[function(require,module,exports){
/*
* Resolves Fisheye Effect
*/
@@ -49531,9 +49618,9 @@ module.exports = function DoNothing(options,UI) {
}
}
},{"fisheyegl":15}],181:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":180,"./info.json":182,"dup":146}],182:[function(require,module,exports){
},{"fisheyegl":15}],182:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":181,"./info.json":183,"dup":147}],183:[function(require,module,exports){
module.exports={
"name": "Fisheye GL",
"description": "Correct fisheye, or barrel distortion, in images (with WebGL -- adapted from fisheye-correction-webgl by @bluemir).",
@@ -49601,7 +49688,7 @@ module.exports={
}
}
},{}],183:[function(require,module,exports){
},{}],184:[function(require,module,exports){
/*
* Import Image module; this fetches a given remote or local image via URL
* or data-url, and overwrites the current one. It saves the original as
@@ -49661,7 +49748,7 @@ module.exports = function ImportImageModule(options, UI) {
}
}
},{"../../util/GetFormat":197,"./Ui.js":184}],184:[function(require,module,exports){
},{"../../util/GetFormat":201,"./Ui.js":185}],185:[function(require,module,exports){
// hide on save
module.exports = function ImportImageModuleUi(step, ui) {
@@ -49717,9 +49804,9 @@ module.exports = function ImportImageModuleUi(step, ui) {
}
}
},{}],185:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":183,"./info.json":186,"dup":146}],186:[function(require,module,exports){
},{}],186:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":184,"./info.json":187,"dup":147}],187:[function(require,module,exports){
module.exports={
"name": "Import Image",
"description": "Import a new image and replace the original with it. Future versions may enable a blend mode. Specify an image by URL or by file selector.",
@@ -49732,7 +49819,7 @@ module.exports={
}
}
}
},{}],187:[function(require,module,exports){
},{}],188:[function(require,module,exports){
/*
* NDVI with red filter (blue channel is infrared)
*/
@@ -49783,9 +49870,9 @@ module.exports = function Ndvi(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193}],188:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":187,"./info.json":189,"dup":146}],189:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],189:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":188,"./info.json":190,"dup":147}],190:[function(require,module,exports){
module.exports={
"name": "NDVI",
"description": "Normalized Difference Vegetation Index, or NDVI, is an image analysis technique used with aerial photography. It's a way to visualize the amounts of infrared and other wavelengths of light reflected from vegetation by comparing ratios of blue and red light absorbed versus green and IR light reflected. NDVI is used to evaluate the health of vegetation in satellite imagery, where it correlates with how much photosynthesis is happening. This is helpful in assessing vegetative health or stress. <a href='https://publiclab.org/ndvi'>Read more</a>.<br /><br/>This is designed for use with red-filtered single camera <a href='http://publiclab.org/infragram'>DIY Infragram cameras</a>; change to 'blue' for blue filters",
@@ -49799,7 +49886,26 @@ module.exports={
}
}
},{}],190:[function(require,module,exports){
},{}],191:[function(require,module,exports){
/*
* Sample Meta Module for demonstration purpose only
*/
module.exports = function NdviColormapfunction() {
this.expandSteps([{ 'name': 'ndvi', 'options': {} }, { 'name': 'colormap', options: {} }]);
return {
isMeta: true
}
}
},{}],192:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":191,"./info.json":193,"dup":147}],193:[function(require,module,exports){
module.exports={
"name": "NDVI-Colormap",
"description": "Sequentially Applies NDVI and Colormap steps",
"inputs": {},
"length": 2
}
},{}],194:[function(require,module,exports){
/*
* Saturate an image with a value from 0 to 1
*/
@@ -49857,9 +49963,9 @@ module.exports = function Saturation(options,UI) {
}
}
},{"../_nomodule/PixelManipulation.js":193}],191:[function(require,module,exports){
arguments[4][146][0].apply(exports,arguments)
},{"./Module":190,"./info.json":192,"dup":146}],192:[function(require,module,exports){
},{"../_nomodule/PixelManipulation.js":197}],195:[function(require,module,exports){
arguments[4][147][0].apply(exports,arguments)
},{"./Module":194,"./info.json":196,"dup":147}],196:[function(require,module,exports){
module.exports={
"name": "Saturation",
"description": "Change the saturation of the image by given value, from 0-1, with 1 being 100% saturated.",
@@ -49872,7 +49978,7 @@ module.exports={
}
}
},{}],193:[function(require,module,exports){
},{}],197:[function(require,module,exports){
(function (process,Buffer){
/*
* General purpose per-pixel manipulation
@@ -49973,7 +50079,7 @@ module.exports = function PixelManipulation(image, options) {
};
}).call(this,require('_process'),require("buffer").Buffer)
},{"_process":98,"buffer":4,"get-pixels":23,"pace":74,"save-pixels":111}],194:[function(require,module,exports){
},{"_process":98,"buffer":4,"get-pixels":23,"pace":74,"save-pixels":111}],198:[function(require,module,exports){
// special module to load an image into the start of the sequence; used in the HTML UI
function LoadImage(ref, name, src, main_callback) {
function makeImage(datauri) {
@@ -50080,7 +50186,7 @@ function LoadImage(ref, name, src, main_callback) {
module.exports = LoadImage;
},{"urify":132}],195:[function(require,module,exports){
},{"urify":132}],199:[function(require,module,exports){
// TODO: potentially move this into ImportImage module
function setInputStepInit() {
@@ -50132,7 +50238,7 @@ function setInputStepInit() {
}
module.exports = setInputStepInit;
},{}],196:[function(require,module,exports){
},{}],200:[function(require,module,exports){
/*
* User Interface Handling Module
*/
@@ -50192,7 +50298,7 @@ module.exports = function UserInterface(events = {}) {
}
},{}],197:[function(require,module,exports){
},{}],201:[function(require,module,exports){
/*
* Determine format from a URL or data-url, return "jpg" "png" "gif" etc
* TODO: write a test for this using the examples
@@ -50234,7 +50340,7 @@ module.exports = function GetFormat(src) {
}
},{}],198:[function(require,module,exports){
},{}],202:[function(require,module,exports){
module.exports = {
getPreviousStep: function() {
return this.getStep(-1);

File diff suppressed because one or more lines are too long

View File

@@ -107,3 +107,7 @@ h1 {
text-align: center;
margin: 10px;
}
#save-seq {
display: block;
margin: 0 auto;
}

View File

@@ -1,15 +1,21 @@
window.onload = function() {
sequencer = ImageSequencer();
function refreshOptions() {
// Load information of all modules (Name, Inputs, Outputs)
var modulesInfo = sequencer.modulesInfo();
var addStepSelect = $("#addStep select");
addStepSelect.html("");
// Add modules to the addStep dropdown
for (var m in modulesInfo) {
$("#addStep select").append(
addStepSelect.append(
'<option value="' + m + '">' + modulesInfo[m].name + "</option>"
);
}
}
refreshOptions();
// UI for each step:
sequencer.setUI(DefaultHtmlStepUi(sequencer));
@@ -22,6 +28,11 @@ window.onload = function() {
$("#addStep select").on("change", ui.selectNewStepUi);
$("#addStep button").on("click", ui.addStepUi);
$('body').on('click', 'button.remove', ui.removeStepUi);
$('#save-seq').click(() => {
sequencer.saveSequence(window.prompt("Please give a name to your sequence..."), sequencer.toString());
sequencer.loadModules();
refreshOptions();
});
// image selection and drag/drop handling from examples/lib/imageSelection.js
sequencer.setInputStep({

View File

@@ -81,6 +81,7 @@
</div>
</div>
</section>
<button class="btn btn-primary btn-lg" name="save-sequence" id="save-seq">Save Sequence</button>
</div>

View File

@@ -7,6 +7,7 @@ function DefaultHtmlSequencerUi(_sequencer, options) {
function onLoad() {
importStepsFromUrlHash();
if (!$('#selectStep').val())
$(addStepSel + " button").prop("disabled", true);
}
@@ -44,9 +45,15 @@ function DefaultHtmlSequencerUi(_sequencer, options) {
* and since loadImage is not a part of the drawarray the step lies at current
* length - 2 of the drawarray
*/
var sequenceLength = 1;
if (sequencer.sequences[newStepName]) {
sequenceLength = sequencer.sequences[newStepName].length;
} else if (sequencer.modules[newStepName][1]["length"]) {
sequenceLength = sequencer.modules[newStepName][1]["length"];
}
_sequencer
.addSteps(newStepName, options)
.run({index: _sequencer.images.image1.steps.length - 2});
.run({ index: _sequencer.images.image1.steps.length - sequenceLength - 1 });
// add to URL hash too
setUrlHashParameter("steps", _sequencer.toString());

View File

@@ -19,8 +19,21 @@ program
.option("-o, --output [PATH]", "Directory where output will be stored.")
.option("-b, --basic", "Basic mode outputs only final image")
.option("-c, --config [Object]", "Options for the step")
.option("--save-sequence [string]", "Name space separated with Stringified sequence")
.option('--install-module [string]', "Module name space seaprated npm package name")
.parse(process.argv);
if (program.saveSequence) {
var params = program.saveSequence.split(' ');
sequencer.saveSequence(params[0], params[1]);
} else if (program.installModule) {
var params = program.installModule.split(' ');
var spinner = Spinner("Now Installing...").start();
require('child_process').execSync(`npm i ${params[1]}`)
sequencer.saveNewModule(params[0], params[1]);
sequencer.loadNewModule(params[0], require(params[1]));
spinner.stop();
} else {
// Parse step into an array to allow for multiple steps.
if (!program.step) exit("No steps passed");
program.step = program.step.split(" ");
@@ -188,3 +201,5 @@ function validateConfig(config_, options_) {
return false;
else return true;
}
}

View File

@@ -5,7 +5,7 @@
"main": "src/ImageSequencer.js",
"scripts": {
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
"test": "TEST=true tape test/**/*.js test/*.js | tap-spec; browserify test/modules/image-sequencer.js test/modules/chain.js test/modules/replace.js test/modules/import-export.js test/modules/run.js test/modules/dynamic-imports.js | tape-run --render=\"tap-spec\"",
"test": "TEST=true tape test/**/*.js test/*.js | tap-spec; browserify test/modules/image-sequencer.js test/modules/chain.js test/modules/meta-modules.js test/modules/replace.js test/modules/import-export.js test/modules/run.js test/modules/dynamic-imports.js | tape-run --render=\"tap-spec\"",
"start": "grunt serve"
},
"repository": {

View File

@@ -40,6 +40,7 @@ ImageSequencer = function ImageSequencer(options) {
var image,
steps = [],
modules = require('./Modules'),
sequences = require('./SavedSequences.json'),
formatInput = require('./FormatInput'),
images = {},
inputlog = [],
@@ -52,6 +53,11 @@ ImageSequencer = function ImageSequencer(options) {
for (o in sequencer) {
modules[o] = sequencer[o];
}
sequences = JSON.parse(window.localStorage.getItem('sequences'));
if (!sequences) {
sequences = {};
window.localStorage.setItem('sequences', JSON.stringify(sequences));
}
}
// if in browser, prompt for an image
@@ -208,11 +214,20 @@ ImageSequencer = function ImageSequencer(options) {
function modulesInfo(name) {
var modulesdata = {}
if (name == "load-image") return {};
if (arguments.length == 0)
for (var modulename in modules) {
if (arguments.length == 0) {
for (var modulename in this.modules) {
modulesdata[modulename] = modules[modulename][1];
}
else modulesdata = modules[name][1];
for (var sequencename in this.sequences) {
modulesdata[sequencename] = { name: sequencename, steps: sequences[sequencename] };
}
}
else {
if (modules[name])
modulesdata = modules[name][1];
else
modulesdata = { 'inputs': sequences[name]['options'] };
}
return modulesdata;
}
@@ -246,7 +261,11 @@ ImageSequencer = function ImageSequencer(options) {
// Coverts stringified sequence into an array of JSON steps
function stringToJSON(str) {
let steps = str.split(',');
let steps;
if (str.includes(','))
steps = str.split(',');
else
steps = [str];
return steps.map(stringToJSONstep);
}
@@ -304,15 +323,17 @@ ImageSequencer = function ImageSequencer(options) {
if (!options) {
return this;
} else if (Array.isArray(options)) {
// contains the array of module and info
this.module[name] = options;
this.modules[name] = options;
} else if (options.func && options.info) {
// passed in options object
this.modules[name] = [
options.func, options.info
];
} else if (options.path && !this.inBrowser) {
// load from path(only in node)
const module = [
@@ -324,12 +345,62 @@ ImageSequencer = function ImageSequencer(options) {
return this;
}
function saveNewModule(name, path) {
if (options.inBrowser) {
// Not for browser context
return;
}
var mods = fs.readFileSync('./src/Modules.js').toString();
mods = mods.substr(0, mods.length - 1) + " '" + name + "': require('" + path + "'),\n}";
fs.writeFileSync('./src/Modules.js', mods);
}
function createMetaModule(stepsCollection, info) {
var stepsArr = stepsCollection;
if (typeof stepsCollection === 'string')
stepsArr = stringToJSON(stepsCollection);
var metaMod = function() {
this.expandSteps(stepsArr);
return {
isMeta: true
}
}
return [metaMod, info];
}
function saveSequence(name, sequenceString) {
const sequence = stringToJSON(sequenceString);
// Save the given sequence string as a module
if (options.inBrowser) {
// Inside the browser we save the meta-modules using the Web Storage API
var sequences = JSON.parse(window.localStorage.getItem('sequences'));
sequences[name] = sequence;
window.localStorage.setItem('sequences', JSON.stringify(sequences));
}
else {
// In node we save the sequences in the json file SavedSequences.json
var sequences = require('./SavedSequences.json');
sequences[name] = sequence;
fs.writeFileSync('./src/SavedSequences.json', JSON.stringify(sequences));
}
}
function loadModules() {
// This function loads the modules and saved sequences
this.modules = require('./Modules');
if (options.inBrowser)
this.sequences = JSON.parse(window.localStorage.getItem('sequences'));
else
this.sequences = require('./SavedSequences.json');
}
return {
//literals and objects
name: "ImageSequencer",
options: options,
inputlog: inputlog,
modules: modules,
sequences: sequences,
images: images,
events: events,
@@ -352,6 +423,10 @@ ImageSequencer = function ImageSequencer(options) {
importString: importString,
importJSON: importJSON,
loadNewModule: loadNewModule,
saveNewModule: saveNewModule,
createMetaModule: createMetaModule,
saveSequence: saveSequence,
loadModules: loadModules,
//other functions
log: log,

View File

@@ -2,10 +2,15 @@ const getStepUtils = require('./util/getStep.js');
// insert one or more steps at a given index in the sequencer
function InsertStep(ref, image, index, name, o) {
if (ref.sequences[name]) {
return ref.importJSON(ref.sequences[name]);
}
function insertStep(image, index, name, o_) {
if (ref.modules[name]) var moduleInfo = ref.modules[name][1];
else console.log('Module ' + name + ' not found.');
else {
console.log('Module ' + name + ' not found.');
}
var o = ref.copy(o_);
o.number = ref.options.sequencerCounter++; //Gives a Unique ID to each step
@@ -32,7 +37,13 @@ function InsertStep(ref, image, index, name, o) {
// Tell UI that a step has been set up.
o = o || {};
UI.onSetup(o.step);
ref.modules[name].expandSteps = function expandSteps(stepsArray) {
for (var step of stepsArray) {
ref.addSteps(step['name'], step['options']);
}
}
var module = ref.modules[name][0](o, UI);
if (!module.isMeta)
ref.images[image].steps.splice(index, 0, module);
return true;

View File

@@ -16,5 +16,6 @@ module.exports = {
'average': require('./modules/Average'),
'blend': require('./modules/Blend'),
'import-image': require('./modules/ImportImage'),
'invert': require('image-sequencer-invert')
'invert': require('image-sequencer-invert'),
'ndvi-colormap': require('./modules/NdviColormap'),
}

1
src/SavedSequences.json Normal file
View File

@@ -0,0 +1 @@
{"sample":[{"name":"invert","options":{}},{"name":"channel","options":{"channel":"red"}},{"name":"blur","options":{"blur":"5"}}]}

View File

@@ -4,7 +4,6 @@
module.exports = function Blur(options, UI) {
options.blur = options.blur || 2
var output;
function draw(input, callback, progressObj) {

View File

@@ -0,0 +1,9 @@
/*
* Sample Meta Module for demonstration purpose only
*/
module.exports = function NdviColormapfunction() {
this.expandSteps([{ 'name': 'ndvi', 'options': {} }, { 'name': 'colormap', options: {} }]);
return {
isMeta: true
}
}

View File

@@ -0,0 +1,4 @@
module.exports = [
require('./Module'),
require('./info.json')
]

View File

@@ -0,0 +1,6 @@
{
"name": "NDVI-Colormap",
"description": "Sequentially Applies NDVI and Colormap steps",
"inputs": {},
"length": 2
}

View File

@@ -0,0 +1,16 @@
var test = require('tape');
require('../../src/ImageSequencer.js');
var sequencer1 = ImageSequencer({ ui: false });
var sequencer2 = ImageSequencer({ ui: false });
var red = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
test('Load ndvi-colormap meta module', function(t) {
sequencer1.loadImages('image1', red);
sequencer2.loadImages('image1', red);
sequencer1.addSteps('ndvi-colormap');
sequencer2.addSteps(['ndvi', 'colormap']);
t.equal(sequencer1.images.image1.steps[0].options.name, sequencer2.steps[0].options.name, "First step OK");
t.equal(sequencer1.images.image1.steps[1].options.name, sequencer2.steps[1].options.name, "Second step OK");
t.end();
});