Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 376c264168 | |||
| 1f3000b7f9 | |||
| dc9827049a | |||
| f69f8461a7 | |||
| 5b860dee83 | |||
| 959f5db6df | |||
| 237a96247a | |||
| 2e4255a5ca | |||
| fe3ec335fe | |||
| eafc639555 | |||
| 7da59aed0c | |||
| d759680869 | |||
| 4c662d908e | |||
| c5d62e5529 | |||
| d466afb90d | |||
| 72435eb9a4 | |||
| c7e4ec7c4a | |||
| c28c958414 | |||
| 38975791ca | |||
| dbc0ad6e69 | |||
| c3c6713ec1 | |||
| 873492a456 | |||
| ca044b1b01 | |||
| 0fa0684076 | |||
| c655b62813 | |||
| 69c656fbd5 | |||
| ee467f2a7b | |||
| 21796bb58c | |||
| cc308748c8 | |||
| 0095462384 | |||
| 54c7393417 | |||
| 555ea9b945 | |||
| f879f31e4f | |||
| 782e60269e | |||
| 963129d513 | |||
| d207c147fe | |||
| a9e0068628 | |||
| 5b7e47dd79 | |||
| 95ffa0453b | |||
| 76ffb6a013 | |||
| 78fb43a8af | |||
| 04e2ee7903 | |||
| 06af6007a2 | |||
| 42425bcf69 | |||
| 3b73c6d700 | |||
| 889c751399 | |||
| 26538891a3 | |||
| 1510043a50 | |||
| d34130b791 | |||
| 8579cfafa7 | |||
| 2f117c1f35 | |||
| 14b1152e5a | |||
| c90bf3fa0a | |||
| 802648ae69 | |||
| 812cd808c7 | |||
| 45328999f3 | |||
| 88f31c9665 | |||
| 235b2428ee | |||
| 3354071548 | |||
| 02ec74bad3 | |||
| b8ec24c185 | |||
| 187f66429a | |||
| 8f5e9f0c40 | |||
| e6f1ff3243 | |||
| b9797a0329 | |||
| 61bd95355b | |||
| 9208216f99 | |||
| 3421714c2b | |||
| 93941b1280 | |||
| 6eea7b8bd6 | |||
| d1b47a73fe | |||
| 31e9b3ec9d | |||
| 1b7dadf41f | |||
| f1f3574468 | |||
| 8610ab1ff8 | |||
| 2888c2d735 | |||
| 53a8963b72 | |||
| 6e136e6c2f | |||
| 467fe1d2e5 | |||
| b59383ce53 | |||
| b1c9949757 | |||
| f3f3f5f615 | |||
| 7f17f825ab | |||
| ec4459309b | |||
| d80a587bf3 | |||
| d7eb5a2f0c | |||
| 03d9ff1ea2 | |||
| 2e284560e2 | |||
| f5c21e7c82 | |||
| fab35c5efb | |||
| 29cb36dd31 | |||
| d90c61f85d | |||
| 21334ff5d8 | |||
| 1117279d44 | |||
| 6baed92ec6 | |||
| e9f045f5cd | |||
| ec657a6735 | |||
| 246130c8e6 | |||
| 82bb274cfb | |||
| 78beae48d9 | |||
| 7584b1ec45 | |||
| cfdc4c509e | |||
| 867792927e | |||
| 555e37190b | |||
| a74f67ef06 | |||
| b963dfd297 | |||
| 10dadeb63b | |||
| c4c1f5bd2d | |||
| 86d475d3e7 | |||
| 52e8e652c3 | |||
| 73930849a7 | |||
| dc9b9bcde3 | |||
| dd595f371c | |||
| 1404d455fc | |||
| 5994963253 | |||
| e6a96d723d | |||
| 43d55d2781 | |||
| 8d8cf33d23 | |||
| 09c0a180ec | |||
| 51e6c70fc8 | |||
| 41c392d838 | |||
| 368d11230c | |||
| a4e7470a5e | |||
| 3112816afc | |||
| 2eb41b20f9 | |||
| ec6cc396e4 | |||
| 10dc6c0a7c | |||
| f6225422b1 | |||
| 5dfbc9d28b | |||
| 74eb686604 | |||
| fedccd59a1 | |||
| f1551abb7f | |||
| 7c1228fca9 | |||
| 1bf58351b4 |
@@ -25,6 +25,7 @@ build/Release
|
||||
|
||||
# Dependency directory
|
||||
node_modules
|
||||
node_modules/*
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
@@ -34,3 +35,8 @@ node_modules
|
||||
|
||||
*.swp
|
||||
todo.txt
|
||||
test.js
|
||||
output.txt
|
||||
output/
|
||||
node_modules/
|
||||
node_modules/*
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
Contributing to Image Sequencer
|
||||
===
|
||||
|
||||
Happily accepting pull requests; to edit the core library, modify files in `./src/`. To build, run `npm install` followed by `grunt build`.
|
||||
|
||||
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
|
||||
|
||||
## Contributing modules
|
||||
|
||||
Any module must look like this :
|
||||
|
||||
```js
|
||||
module.exports = function ModuleName(options,UI) {
|
||||
options = options || {};
|
||||
options.title = "Title of the Module";
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var output = /*do something with the input*/ ;
|
||||
|
||||
this.output = output;
|
||||
options.step.output = output.src;
|
||||
callback();
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### options
|
||||
|
||||
The object `options` stores some important information. This is how you can accept
|
||||
input from users. If you require a variable "x" from the user and the user passes
|
||||
it in, you will be able to access it as `options.x`.
|
||||
|
||||
Options also has some in-built properties. The `options.inBrowser` boolean denotes
|
||||
whether your module is being run on a browser.
|
||||
|
||||
### draw()
|
||||
|
||||
The draw method is run every time the step is `run` using `sequencer.run()`.
|
||||
So any calculations must go **into** the `draw()` method's definition.
|
||||
|
||||
What is not in the draw method, but is in the `module.exports` is executed only
|
||||
when the step is added. So whatever external npm modules are to be loaded, or
|
||||
constant definitions must be done **outside** the `draw()` method's definition.
|
||||
|
||||
`draw()` receives two arguments - `input` and `callback` :
|
||||
* `input` is an object which is essentially the output of the previous step.
|
||||
```js
|
||||
input = {
|
||||
src: "<$DataURL>",
|
||||
format: "<png|jpeg|gif>"
|
||||
}
|
||||
```
|
||||
* `callback` is a function which is responsible to tell the sequencer that the
|
||||
step has been "drawn".
|
||||
|
||||
When you have done your calculations and produced an image output, you are required
|
||||
to set `this.output` to an object similar to what the input object was, call
|
||||
`callback()`, and set `options.step.output` equal to the output DataURL
|
||||
|
||||
### UI Methods
|
||||
|
||||
The module is responsible to emit various events for the UI to capture. There are
|
||||
four events in all:
|
||||
|
||||
* `UI.onSetup(options.step)` must be emitted when the module is added. So it must be emitted outside the draw method's definition as shown above.
|
||||
* `UI.onDraw(options.step)` must be emitted whenever the `draw()` method is called. So it should ideally be the first line of the definition of the `draw` method.
|
||||
* `UI.onComplete(options.step)` must be emitted whenever the output of a draw call
|
||||
is ready. An argument, that is the DataURL of the output image must be passed in.
|
||||
* `UI.onRemove(options.step)` is emitted automatically and the module should not emit it.
|
||||
|
||||
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
|
||||
|
||||
* `module.draw()`
|
||||
|
||||
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
|
||||
|
||||
```js
|
||||
input = {
|
||||
src: "datauri here",
|
||||
format: "jpeg/png/etc"
|
||||
}
|
||||
```
|
||||
|
||||
## options.title
|
||||
|
||||
For display in the web-based UI, each module may also have a title `options.title`.
|
||||
|
||||
## Info file
|
||||
|
||||
All module folders must have an `info.json` file which looks like the following:
|
||||
```json
|
||||
{
|
||||
"name": "Name of Module to be displayed",
|
||||
"inputs": {
|
||||
"var1": {
|
||||
"type": "text",
|
||||
"default": "default value"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"out1": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Types may be one of "text", "integer", "float", "select".
|
||||
Integer and Float types should also specify minimum and maximum values like this:
|
||||
|
||||
```json
|
||||
"var1": {
|
||||
"type": "integer",
|
||||
"min": 0,
|
||||
"max": 4,
|
||||
"default": 1
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, "Select" type inputs should have a `values` array.
|
||||
|
||||
Also, A module may have output values. These must be defined as shown above.
|
||||
|
||||
### Module example
|
||||
|
||||
See existing module `green-channel` for an example: https://github.com/publiclab/image-sequencer/tree/master/src/modules/GreenChannel/Module.js
|
||||
|
||||
For help integrating, please open an issue.
|
||||
|
||||
****
|
||||
|
||||
## Development
|
||||
|
||||
Notes on development next steps:
|
||||
|
||||
### UI
|
||||
|
||||
* [ ] add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib
|
||||
* [ ] it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
|
||||
|
||||
* [ ] spinners before panels are complete
|
||||
* [ ] is there a module for generating forms from parameters?
|
||||
* [ ] click to expand for all images
|
||||
* [ ] `ImageSequencer.Renderer` class to manage image output formats and adapters
|
||||
* [ ] remove step
|
||||
|
||||
* [ ] output besides an image -- like `message(txt)` to display to the step's UI
|
||||
|
||||
|
||||
### Modularization
|
||||
|
||||
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
|
||||
* [x] ability to start running at any point -- already works?
|
||||
* [x] commandline runnability?
|
||||
* [x] Make available as browserified OR `require()` includable...
|
||||
* [ ] standardize panel addition with submodule that offers Panel.display(image)
|
||||
* [ ] allow passing data as data-uri or Image object, or stream, or ndarray or ImageData array, if both of neighboring pair has ability?
|
||||
* see https://github.com/jywarren/image-sequencer/issues/1
|
||||
* [ ] ...could we directly include package.json for module descriptions? At least as a fallback.
|
||||
* [ ] (for node-and-line style UIs) non-linear sequences with Y-splitters
|
||||
* [ ] `sequencer.addModule('path/to/module.js')` style module addition -- also to avoid browserifying all of Plotly :-P
|
||||
* [x] remove step
|
||||
|
||||
### Testing
|
||||
|
||||
* [x] tests - modules headless; unit tests
|
||||
* [ ] comparisons with diff
|
||||
* [ ] testing a module's promised functionality: each module could offer before/after images as part of their API; by running the module on the before image, you should get exactly the after image, comparing with an image diff
|
||||
|
||||
### Use cases
|
||||
|
||||
* [ ] make an Infragram module that accepts a math expression
|
||||
|
||||
### Bugs
|
||||
|
||||
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
|
||||
* we should make defaults a config of the first module
|
||||
|
||||
****
|
||||
|
||||
## Module Candidates
|
||||
|
||||
* https://github.com/linuxenko/rextract.js
|
||||
* https://www.npmjs.com/package/histogram
|
||||
* https://github.com/hughsk/flood-fill
|
||||
* https://www.npmjs.com/package/blink-diff
|
||||
* smaller and faster: https://www.npmjs.com/package/@schornio/pixelmatch
|
||||
* https://github.com/yahoo/pngjs-image has lots of useful general-purpose image getters like `image.getLuminosityAtIndex(idx)`
|
||||
* some way to add in a new image (respecting alpha) -- `add-image` (with blend mode, default `normal`?)
|
||||
* https://github.com/yuta1984/CannyJS - edge detection
|
||||
* http://codepen.io/taylorcoffelt/pen/EsCcr - more edge detection
|
||||
* https://github.com/jywarren/webgl-distort/ - an image distortion module
|
||||
|
||||
## Ideas
|
||||
|
||||
* https://github.com/vicapow/jsqrcode
|
||||
* https://github.com/jadnco/whirl - scrubbable image sequence player
|
||||
* non graphics card GL functions could be shimmed with https://github.com/Overv/JSGL
|
||||
* or this: https://github.com/stackgl/headless-gl
|
||||
* https://github.com/mattdesl/fontpath-simple-renderer
|
||||
* output in animated Gif? as a module
|
||||
|
||||
### Referencing earlier states
|
||||
|
||||
Complex sequences with masking could require accessing previous states (or nonlinearity):
|
||||
|
||||
* flood-fill an area
|
||||
* select only the flooded area
|
||||
* roundabout: lighten everything to <50%, then flood-fill with black? Not 100% reliable.
|
||||
* roundabout 2: `flood fill`, then `blink-diff` with original
|
||||
* then add step which recovers original image, repeat `flood-fill`/`blink-diff` for second region
|
||||
* reference above masked states in a `mask` module, with `maskModule.draw(image, { getMask: function() { return maskImg } })`
|
||||
|
||||
****
|
||||
|
||||
**Notes:**
|
||||
|
||||
`pattern-fill` module to use patterns in JS canvas:
|
||||
|
||||
```js
|
||||
var c=document.getElementById("myCanvas");
|
||||
var ctx=c.getContext("2d");
|
||||
var img=document.getElementById("lamp");
|
||||
var pat=ctx.createPattern(img,"repeat");
|
||||
ctx.rect(0,0,150,100);
|
||||
ctx.fillStyle=pat;
|
||||
ctx.fill();
|
||||
```
|
||||
|
||||
Masking:
|
||||
|
||||
```js
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(160, 600);
|
||||
ctx.rect(0, 0, 160, 600);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(img, 0, 0);
|
||||
ctx.restore();
|
||||
```
|
||||
|
||||
****
|
||||
|
||||
## UI notes:
|
||||
|
||||
* visual nodes-and-lines UI: https://github.com/flowhub/the-graph
|
||||
* https://flowhub.github.io/the-graph/examples/demo-simple.html
|
||||
|
||||
|
||||
|
||||
```js
|
||||
|
||||
settings: {
|
||||
'threshold': {
|
||||
type: 'slider',
|
||||
label: 'Threshold',
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100
|
||||
},
|
||||
'colors': {
|
||||
type: 'select',
|
||||
label: 'Colors',
|
||||
options: [
|
||||
{ name: '0', value: '0', default: true },
|
||||
{ name: '1', value: '1' },
|
||||
{ name: '2', value: '2' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Possible web-based commandline interface: https://hyper.is/?
|
||||
|
||||
|
||||
### Path cutting
|
||||
|
||||
* threshold
|
||||
* vectorize
|
||||
* edge detect
|
||||
* direction find (vectorize and colorize)
|
||||
@@ -26,6 +26,14 @@ It is also for prototyping some other related ideas:
|
||||
* [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)
|
||||
|
||||
## Jump to:
|
||||
|
||||
* [Quick Usage](#quick-usage)
|
||||
* [Classic Usage](#classic-usage)
|
||||
* [Method Chaining](#method-chaining)
|
||||
* [Multiple Images](#multiple-images)
|
||||
* [Creating a User Interface](#creating-a-user-interface)
|
||||
|
||||
## Quick Usage
|
||||
|
||||
Image Sequencer can be used to run modules on an HTML Image Element using the
|
||||
@@ -49,6 +57,24 @@ For example:
|
||||
sequencer.replaceImage('#photo',['invert','ndvi-red']);
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
Image Sequencer also provides a CLI for applying operations to local files. The CLI takes the following arguments:
|
||||
|
||||
-i | --image [PATH/URL] | Input image URL. (required)
|
||||
-s | --step [step-name] | Name of the step to be added. (required)
|
||||
-o | --output [PATH] | Directory where output will be stored. (optional)
|
||||
-op | --opions {object} | Options for the step. (optional)
|
||||
|
||||
The basic format for using the CLI is as follows:
|
||||
|
||||
$ ./index.js -i [PATH] -s step-name
|
||||
|
||||
*NOTE:* On Windows you'll have to use `node index.js` instead of `./index.js`.
|
||||
|
||||
The CLI also can take multiple steps at once, like so:
|
||||
|
||||
$ ./index.js -i [PATH] -s "step-name-1 step-name-2 ..."
|
||||
|
||||
## Classic Usage
|
||||
|
||||
@@ -68,11 +94,26 @@ a name and an image. The method also accepts an optional callback.
|
||||
```js
|
||||
sequencer.loadImage(image_src,optional_callback);
|
||||
```
|
||||
On `Node.js` the `image_src` may be a DataURI or a local path. On browsers, it
|
||||
must be a DatURI (or 'selector to image' -- Work in Progress)
|
||||
On `Node.js` the `image_src` may be a DataURI or a local path or a URL.
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
On browsers, it may be a DatURI, a local image or a URL (Unless this violates
|
||||
CORS Restrictions). To sum up, these are accepted:
|
||||
* Images in the same domain (or directory - for a local implementation)
|
||||
* CORS-Proof images in another domain.
|
||||
* DataURLs
|
||||
|
||||
return value: **none** (A callback should be used to ensure the image gets loaded)
|
||||
The callback is called within the scope of a the sequencer. For example:
|
||||
(addSteps is defined later)
|
||||
|
||||
```js
|
||||
sequencer.loadImage('SRC',function(){
|
||||
this.addSteps('module-name');
|
||||
});
|
||||
```
|
||||
|
||||
The `this` refers to all the images added in the parent `loadImages` function only.
|
||||
In this case, only `'SRC'`.
|
||||
|
||||
### Adding steps to the image
|
||||
|
||||
@@ -156,6 +197,7 @@ return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
## Method Chaining
|
||||
Methods can be chained on the Image Sequencer:
|
||||
* loadImage()/loadImages() can only terminate a chain.
|
||||
* run() can not be in the middle of the chain.
|
||||
* If the chain starts with loadImage() or loadImages(), the following methods are
|
||||
applied only to the newly loaded images.
|
||||
@@ -164,9 +206,11 @@ be of the form "image<number>". For ex: "image1", "image2", "image3", etc.
|
||||
|
||||
Valid Chains:
|
||||
```js
|
||||
sequencer.loadImage('red').addSteps('invert').run(function(out){
|
||||
//do something with otuput.
|
||||
});
|
||||
sequencer.loadImage('red',function(){
|
||||
this.addSteps('invert').run(function(out){
|
||||
//do something with ouptut.
|
||||
});
|
||||
})
|
||||
sequencer.addSteps(['ndvi-red','invert']).run();
|
||||
et cetra.
|
||||
```
|
||||
@@ -209,7 +253,7 @@ with each image. This is a string literal.
|
||||
});
|
||||
```
|
||||
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
return value: **none**
|
||||
|
||||
|
||||
### Adding Steps on Multiple Images
|
||||
@@ -325,204 +369,75 @@ sequencer.insertSteps({
|
||||
return value: **`sequencer`** (To allow method chaining)
|
||||
|
||||
|
||||
## Contributing
|
||||
## Creating a User Interface
|
||||
|
||||
Happily accepting pull requests; to edit the core library, modify files in `/src/`. To build, run `npm install` and `grunt build`.
|
||||
Image Sequencer provides the following events which can be used to generate a UI:
|
||||
|
||||
### Contributing modules
|
||||
* `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.
|
||||
|
||||
Most contribution (we imagine) would be in the form of API-compatible modules, which need not be directly included.
|
||||
|
||||
#### draw()
|
||||
|
||||
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
|
||||
|
||||
* `module.draw()`
|
||||
|
||||
The `draw(input,callback)` method should accept an `input` parameter, which will be an object of the form:
|
||||
How to define these functions:
|
||||
|
||||
```js
|
||||
input = {
|
||||
src: "datauri here",
|
||||
format: "jpeg/png/etc"
|
||||
}
|
||||
sequencer.setUI({
|
||||
onSetup: function(step) {},
|
||||
onDraw: function(step) {},
|
||||
onComplete: function(step) {},
|
||||
onRemove: function(step) {}
|
||||
});
|
||||
```
|
||||
|
||||
The `image` object is essentially the output of the previous step.
|
||||
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 draw method must, when it is complete, pass the output image to the method `this.output = modified_input`, which will send the output to the next module in the chain. For example:
|
||||
The `onComplete` event is passed on the output of the module.
|
||||
|
||||
Image Sequencer provides a namespace `step` for the purpose of UI Creation in
|
||||
the scope of these definable function. This namespace has the following
|
||||
predefined properties:
|
||||
|
||||
* `step.name` : (String) Name of the step
|
||||
* `step.ID` : (Number) An ID given to every step of the sequencer, unique throughout.
|
||||
* `step.imageName` : (String) Name of the image the step is applied to.
|
||||
* `step.output` : (DataURL String) Output of the step.
|
||||
* `step.inBrowser` : (Boolean) Whether the client is a browser or not
|
||||
|
||||
In addition to these, one might define their own properties, which shall be
|
||||
accessible across all the event scopes of that step.
|
||||
|
||||
For example :
|
||||
|
||||
```js
|
||||
function draw(image) {
|
||||
|
||||
// do some stuff with the image
|
||||
|
||||
this.output = image;
|
||||
callback();
|
||||
}
|
||||
```
|
||||
|
||||
#### Title
|
||||
|
||||
For display in the web-based UI, each module may also have a title like `options.title`.
|
||||
|
||||
#### Module example
|
||||
|
||||
See existing module `green-channel` for an example: https://github.com/jywarren/image-sequencer/tree/master/src/modules/GreenChannel.js
|
||||
|
||||
For help integrating, please open an issue.
|
||||
|
||||
****
|
||||
|
||||
## Development
|
||||
|
||||
Notes on development next steps:
|
||||
|
||||
### UI
|
||||
|
||||
* [ ] add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib
|
||||
* [ ] it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
|
||||
|
||||
* [ ] spinners before panels are complete
|
||||
* [ ] is there a module for generating forms from parameters?
|
||||
* [ ] click to expand for all images
|
||||
* [ ] `ImageSequencer.Renderer` class to manage image output formats and adapters
|
||||
* [ ] remove step
|
||||
|
||||
* [ ] output besides an image -- like `message(txt)` to display to the step's UI
|
||||
|
||||
|
||||
### Modularization
|
||||
|
||||
* [ ] remotely includable modules, not compiled in -- see plugin structures in other libs
|
||||
* [x] ability to start running at any point -- already works?
|
||||
* [x] commandline runnability?
|
||||
* [x] Make available as browserified OR `require()` includable...
|
||||
* [ ] standardize panel addition with submodule that offers Panel.display(image)
|
||||
* [ ] allow passing data as data-uri or Image object, or stream, or ndarray or ImageData array, if both of neighboring pair has ability?
|
||||
* see https://github.com/jywarren/image-sequencer/issues/1
|
||||
* [ ] ...could we directly include package.json for module descriptions? At least as a fallback.
|
||||
* [ ] (for node-and-line style UIs) non-linear sequences with Y-splitters
|
||||
* [ ] `sequencer.addModule('path/to/module.js')` style module addition -- also to avoid browserifying all of Plotly :-P
|
||||
* [x] remove step
|
||||
|
||||
### Testing
|
||||
|
||||
* [ ] tests - modules headless; unit tests
|
||||
* [ ] comparisons with diff
|
||||
* [ ] testing a module's promised functionality: each module could offer before/after images as part of their API; by running the module on the before image, you should get exactly the after image, comparing with an image diff
|
||||
|
||||
### Use cases
|
||||
|
||||
* [ ] make an Infragram module that accepts a math expression
|
||||
|
||||
### Bugs
|
||||
|
||||
* [x] BUG: this doesn't work for defaults: imageboard.loadImage('examples/grid.png', function() {});
|
||||
* we should make defaults a config of the first module
|
||||
|
||||
****
|
||||
|
||||
## Module Candidates
|
||||
|
||||
* https://github.com/linuxenko/rextract.js
|
||||
* https://www.npmjs.com/package/histogram
|
||||
* https://github.com/hughsk/flood-fill
|
||||
* https://www.npmjs.com/package/blink-diff
|
||||
* smaller and faster: https://www.npmjs.com/package/@schornio/pixelmatch
|
||||
* https://github.com/yahoo/pngjs-image has lots of useful general-purpose image getters like `image.getLuminosityAtIndex(idx)`
|
||||
* some way to add in a new image (respecting alpha) -- `add-image` (with blend mode, default `normal`?)
|
||||
* https://github.com/yuta1984/CannyJS - edge detection
|
||||
* http://codepen.io/taylorcoffelt/pen/EsCcr - more edge detection
|
||||
|
||||
## Ideas
|
||||
|
||||
* https://github.com/vicapow/jsqrcode
|
||||
* https://github.com/jadnco/whirl - scrubbable image sequence player
|
||||
* non graphics card GL functions could be shimmed with https://github.com/Overv/JSGL
|
||||
* or this: https://github.com/stackgl/headless-gl
|
||||
* https://github.com/mattdesl/fontpath-simple-renderer
|
||||
* output in animated Gif? as a module
|
||||
|
||||
### Referencing earlier states
|
||||
|
||||
Complex sequences with masking could require accessing previous states (or nonlinearity):
|
||||
|
||||
* flood-fill an area
|
||||
* select only the flooded area
|
||||
* roundabout: lighten everything to <50%, then flood-fill with black? Not 100% reliable.
|
||||
* roundabout 2: `flood fill`, then `blink-diff` with original
|
||||
* then add step which recovers original image, repeat `flood-fill`/`blink-diff` for second region
|
||||
* reference above masked states in a `mask` module, with `maskModule.draw(image, { getMask: function() { return maskImg } })`
|
||||
|
||||
****
|
||||
|
||||
**Notes:**
|
||||
|
||||
`pattern-fill` module to use patterns in JS canvas:
|
||||
|
||||
```js
|
||||
var c=document.getElementById("myCanvas");
|
||||
var ctx=c.getContext("2d");
|
||||
var img=document.getElementById("lamp");
|
||||
var pat=ctx.createPattern(img,"repeat");
|
||||
ctx.rect(0,0,150,100);
|
||||
ctx.fillStyle=pat;
|
||||
ctx.fill();
|
||||
```
|
||||
|
||||
Masking:
|
||||
|
||||
```js
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(160, 600);
|
||||
ctx.rect(0, 0, 160, 600);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(img, 0, 0);
|
||||
ctx.restore();
|
||||
```
|
||||
|
||||
****
|
||||
|
||||
## UI notes:
|
||||
|
||||
* visual nodes-and-lines UI: https://github.com/flowhub/the-graph
|
||||
* https://flowhub.github.io/the-graph/examples/demo-simple.html
|
||||
|
||||
|
||||
|
||||
```js
|
||||
|
||||
settings: {
|
||||
'threshold': {
|
||||
type: 'slider',
|
||||
label: 'Threshold',
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100
|
||||
sequencer.setUI({
|
||||
onSetup: function(step){
|
||||
// Create new property "step.image"
|
||||
step.image = document.createElement('img');
|
||||
document.body.append(step.image);
|
||||
},
|
||||
'colors': {
|
||||
type: 'select',
|
||||
label: 'Colors',
|
||||
options: [
|
||||
{ name: '0', value: '0', default: true },
|
||||
{ name: '1', value: '1' },
|
||||
{ name: '2', value: '2' }
|
||||
]
|
||||
onComplete: function(step){
|
||||
// Access predefined "step.output" and user-defined "step.image"
|
||||
step.image.src = step.output;
|
||||
},
|
||||
onRemove: function(step){
|
||||
// Access user-defined "step.image"
|
||||
step.image.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
Possible web-based commandline interface: https://hyper.is/?
|
||||
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.
|
||||
|
||||
|
||||
### Path cutting
|
||||
|
||||
* threshold
|
||||
* vectorize
|
||||
* edge detect
|
||||
* direction find (vectorize and colorize)
|
||||
Details of all modules can be sought using `sequencer.modulesInfo()`.
|
||||
This method returns an object which defines the name and inputs of the modules. If a module name (hyphenated) is passed in the method, then only the details of that module are returned.
|
||||
|
||||
@@ -47,3 +47,24 @@ where `options` is an object with the property `colormap`. `options.colormap` ca
|
||||
* "fastie" : [[0, [255, 255, 255], [0, 0, 0]], [0.167, [0, 0, 0], [255, 255, 255]], [0.33, [255, 255, 255], [0, 0, 0]], [0.5, [0, 0, 0], [140, 140, 255]], [0.55, [140, 140, 255], [0, 255, 0]], [0.63, [0, 255, 0], [255, 255, 0]], [0.75, [255, 255, 0], [255, 0, 0]], [0.95, [255, 0, 0], [255, 0, 255]]]
|
||||
|
||||
* A custom array.
|
||||
|
||||
## FisheyeGl (fisheye-gl)
|
||||
|
||||
This module is used for correcting Fisheye or Lens Distortion
|
||||
|
||||
#### Usage
|
||||
|
||||
```js
|
||||
sequencer.loadImage('PATH')
|
||||
.addSteps('fisheye-gl',options)
|
||||
.run()
|
||||
```
|
||||
|
||||
where `options` is an object with the following properties:
|
||||
* a : a correction (0 to 4; default 1)
|
||||
* b : b correction (0 to 4; default 1)
|
||||
* Fx : x correction (0 to 4; default 1)
|
||||
* Fy : y correction (0 to 4; default 1)
|
||||
* scale : The ratio to which the original image is to be scaled (0 to 20; default 1.5)
|
||||
* x : Field of View x (0 to 2; default 1)
|
||||
* y : Field of View y (0 to 2; default 1)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/* https://github.com/theleagueof/league-spartan */
|
||||
@font-face {
|
||||
font-family: 'League Spartan';
|
||||
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot');
|
||||
src: url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff2') format('woff2'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.woff') format('woff'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.ttf') format('truetype'),
|
||||
url('https://raw.githubusercontent.com/theleagueof/league-spartan/master/_webfonts/leaguespartan-bold.svg#league_spartanbold') format('svg');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 20px;
|
||||
margin: 0 auto 200px;
|
||||
max-width: 1000px;
|
||||
background: #f8f8fa;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'League Spartan';
|
||||
color: #445;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#imageSelect {
|
||||
padding: 30px;
|
||||
margin: 0 20% 30px;
|
||||
border: 4px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#imageSelect input {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.step .img-thumbnail {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.details {
|
||||
border-top: 3px solid #444;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.details h3 {
|
||||
font-family: monospace;
|
||||
margin-top: 12px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.det {
|
||||
padding: 10px 16px;
|
||||
text-decoration: italic;
|
||||
}
|
||||
|
||||
.tools {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#addStep {
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
#addStep .labels {
|
||||
text-align: right;
|
||||
padding: 6px;
|
||||
margin: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width:768px) {
|
||||
#addStep .labels {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
#addStep .add {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<title>Fisheye Removal | Image Sequencer</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<style media="screen">
|
||||
.r.18 {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
.m {
|
||||
margin: 0 5px;
|
||||
}
|
||||
div.c.m {
|
||||
width: 100px;
|
||||
}
|
||||
.r span {
|
||||
width: 30px !important;
|
||||
}
|
||||
@media all and (max-width: 1000px) {
|
||||
div#main {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../dist/image-sequencer.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h2>Image Sequencer</h2>
|
||||
<h5>FisheyeGl Demo</h5>
|
||||
</header>
|
||||
|
||||
<section class="rh">
|
||||
<div class="r">
|
||||
Drag the Slider to adjust values
|
||||
</div>
|
||||
<div class="r">
|
||||
Select an Image <input type="file" id="file" value="">
|
||||
</div>
|
||||
<div class="r" id="main">
|
||||
<div class="c">
|
||||
<img id="fisheye" src="" alt="">
|
||||
</div>
|
||||
<div class="c rh">
|
||||
<div class="r 18">
|
||||
<div class="c m 30">Fx:</div>
|
||||
<input class="c" name="Fx" type="range" min="0" max="2" step="0.01" value="0" />
|
||||
<span>0</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">Fy:</div>
|
||||
<input class="c" name="Fy" type="range" min="0" max="2" step="0.01" value="0" />
|
||||
<span>0</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">a:</div>
|
||||
<input class="c" name="a" type="range" min="0" max="4" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">b:</div>
|
||||
<input class="c" name="b" type="range" min="0" max="4" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">scale:</div>
|
||||
<input class="c" name="scale" type="range" min="0" max="20" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">FOV x:</div>
|
||||
<input class="c" name="x" type="range" min="0" max="2" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="r 18">
|
||||
<div class="c m 30">FOV y:</div>
|
||||
<input class="c" name="y" type="range" min="0" max="2" step="0.01" value="1" />
|
||||
<span>1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer> <a href="https://github.com/publiclab/image-sequencer">View on GitHub</a> </footer>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
sequencer = ImageSequencer();
|
||||
var image = document.querySelector('#fisheye');
|
||||
|
||||
sequencer.setUI({
|
||||
onComplete: function(step){
|
||||
image.src = step.output;
|
||||
}
|
||||
});
|
||||
|
||||
sequencer.loadImage('fisheye','images/grid.png',function(){
|
||||
this.addSteps('fisheye-gl').run();
|
||||
});
|
||||
|
||||
var inputs = document.querySelectorAll('input[type="range"]')
|
||||
for(i in inputs)
|
||||
inputs[i].oninput = function(e) {
|
||||
e.target.nextSibling.nextSibling.innerHTML = e.target.value;
|
||||
sequencer.images.fisheye.steps[1].options[e.target.name] = e.target.value;
|
||||
sequencer.run(1);
|
||||
}
|
||||
|
||||
document.querySelector('#file').onchange = function(e) {
|
||||
var file = e.target.files[0];
|
||||
|
||||
if(!file) return;
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
sequencer.images.fisheye.steps[0].output.src = reader.result;
|
||||
sequencer.run(0);
|
||||
}
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 277 B |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,221 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="../dist/image-sequencer.js" charset="utf-8"></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<header class="text-center">
|
||||
<h1>Image Sequencer</h1>
|
||||
<h3>
|
||||
<a href="https://github.com/publiclab/image-sequencer"><i class="fa fa-github"></i></a>
|
||||
</h3>
|
||||
</header>
|
||||
|
||||
<div id="imageSelect">
|
||||
<p><i>Select an image to start with!</i></p>
|
||||
<center><input type="file" id="file" value=""></center>
|
||||
</div>
|
||||
|
||||
<section id="steps" class="row"></section>
|
||||
|
||||
<section id="addStep">
|
||||
<div class="row">
|
||||
<select class="form-control">
|
||||
<option value="none" disabled selected>Select a new step...</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div id="options"></div>
|
||||
</div>
|
||||
<div class="row add">
|
||||
<button class="btn btn-success btn-lg" name="add">Add Step</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// Initialize Image Sequencer
|
||||
var sequencer = ImageSequencer();
|
||||
|
||||
window.onload = function() {
|
||||
|
||||
// Load information of all modules (Name, Inputs, Outputs)
|
||||
var modulesInfo = sequencer.modulesInfo();
|
||||
|
||||
// Add modules to the addStep dropdown
|
||||
for(var m in modulesInfo) {
|
||||
$('#addStep select').append(
|
||||
'<option value="'+m+'">'+modulesInfo[m].name+'</option>'
|
||||
);
|
||||
}
|
||||
|
||||
// Initial definitions
|
||||
var steps = document.querySelector('#steps');
|
||||
var parser = new DOMParser();
|
||||
var reader = new FileReader();
|
||||
initial = "images/grid.png";
|
||||
|
||||
// Set the UI in sequencer. This Will generate HTML based on
|
||||
// Image Sequencer events :
|
||||
// onSetup : Called every time a step is added
|
||||
// onDraw : Called every time a step starts draw
|
||||
// onComplete : Called every time a step finishes drawing
|
||||
// onRemove : Called everytime a step is removed
|
||||
// The variable 'step' stores useful data like input and
|
||||
// output values, step information.
|
||||
// See documetation for more details.
|
||||
sequencer.setUI({
|
||||
|
||||
onSetup: function(step) {
|
||||
|
||||
step.ui = '\
|
||||
<div class="row step">\
|
||||
<div class="col-md-4 details">\
|
||||
<h3>'+step.name+'</h3>\
|
||||
<p><i>'+(step.description || '')+'</i></p>\
|
||||
</div>\
|
||||
<div class="col-md-8">\
|
||||
<img alt="" class="img-thumbnail"/>\
|
||||
</div>\
|
||||
</div>\
|
||||
';
|
||||
|
||||
var tools =
|
||||
'<div class="tools btn-group">\
|
||||
<button confirm="Are you sure?" class="remove btn btn-xs btn-default">\
|
||||
<i class="fa fa-trash"></i>\
|
||||
</button>\
|
||||
</div>';
|
||||
|
||||
step.ui = parser.parseFromString(step.ui,'text/html');
|
||||
step.ui = step.ui.querySelector('div.row');
|
||||
step.imgElement = step.ui.querySelector('img');
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
var io = Object.assign(inputs,outputs);
|
||||
for (var i in io) {
|
||||
var div = document.createElement('div');
|
||||
div.className = "row";
|
||||
div.setAttribute('name',i);
|
||||
div.innerHTML = "<div class='det'>" + i + ": " + "<span></span></div>";
|
||||
step.ui.querySelector('div.details').appendChild(div);
|
||||
}
|
||||
}
|
||||
|
||||
if(step.name != "load-image")
|
||||
step.ui.querySelector('div.details').appendChild(
|
||||
parser.parseFromString(tools,'text/html')
|
||||
.querySelector('div')
|
||||
);
|
||||
|
||||
steps.appendChild(step.ui);
|
||||
},
|
||||
|
||||
onComplete: function(step) {
|
||||
|
||||
step.imgElement.src = step.output;
|
||||
|
||||
if(sequencer.modulesInfo().hasOwnProperty(step.name)) {
|
||||
var inputs = sequencer.modulesInfo(step.name).inputs;
|
||||
var outputs = sequencer.modulesInfo(step.name).outputs;
|
||||
for (var i in inputs) {
|
||||
step.ui.querySelector('div[name="'+i+'"] span')
|
||||
.innerHTML = step.options[i];
|
||||
}
|
||||
for (var i in outputs) {
|
||||
step.ui.querySelector('div[name="'+i+'"] span')
|
||||
.innerHTML = step[i];
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onRemove: function(step) {
|
||||
step.ui.remove();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
sequencer.loadImage('images/grid.png',function(){
|
||||
this.addSteps('invert').run();
|
||||
});
|
||||
|
||||
$('#addStep select').on('change', function(){
|
||||
$('#options').html('');
|
||||
var m = $('#addStep select').val();
|
||||
for(var input in modulesInfo[m].inputs) {
|
||||
$('#options').append(
|
||||
'<div class="row">\
|
||||
<div class="col-md-5 labels">\
|
||||
'+input+':\
|
||||
</div>\
|
||||
<div class="col-md-5">\
|
||||
<input class="form-control" type="text" name="'+input+'" value="" placeholder="'+
|
||||
modulesInfo[m].inputs[input].default+'"/>\
|
||||
</div>\
|
||||
</div>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$('#addStep button').on('click',function(){
|
||||
var options = {};
|
||||
var inputs = $('#options input');
|
||||
$.each(inputs, function() {
|
||||
options[this.name] = $(this).val();
|
||||
});
|
||||
if($('#addStep select').val()=="none") return;
|
||||
sequencer.addSteps($('#addStep select').val(),options).run();
|
||||
});
|
||||
|
||||
$('body').on('click','button.remove',function(){
|
||||
var index = $('button.remove').index(this) + 1;
|
||||
sequencer.removeSteps(index).run();
|
||||
});
|
||||
|
||||
$('#file').on('change',function(e){
|
||||
var file = e.target.files[0];
|
||||
if(!file) return;
|
||||
|
||||
reader.onload = function(){
|
||||
var loadStep = sequencer.images.image1.steps[0];
|
||||
loadStep.output.src = reader.result;
|
||||
sequencer.run(0);
|
||||
loadStep.options.step.imgElement.src = reader.result;
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="../dist/image-sequencer.css" rel="stylesheet">
|
||||
<link href="demo-old.css" rel="stylesheet">
|
||||
|
||||
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="panels">
|
||||
|
||||
<div class="panel ismod-image-select" style="display:flex;justify-content:center">
|
||||
<img src="replace.jpg" id="pencils" style="cursor:pointer">
|
||||
<img src="images/replace.jpg" id="pencils" style="cursor:pointer">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,87 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<title>Image Sequencer</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF8">
|
||||
|
||||
<link href="node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="dist/image-sequencer.css" rel="stylesheet">
|
||||
|
||||
<script src="node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="dist/image-sequencer.js"></script>
|
||||
|
||||
<meta http-equiv="refresh" content="0; url=examples/" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
|
||||
<h1>Image Sequencer</h1>
|
||||
<h3>
|
||||
<a href="https://github.com/jywarren/image-sequencer"><i class="fa fa-github"></i></a>
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
|
||||
<p style="display:none;" class="spinner"><i class="fa fa-spinner fa-spin"></i></p>
|
||||
|
||||
<div class="panels">
|
||||
|
||||
<div class="panel ismod-image-select">
|
||||
<div class="mod-drop">Drag image here</div>
|
||||
<p class="instructions">Select or drop an image here to begin.</p>
|
||||
<input id="file-select" type="file" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mod-new-panel">
|
||||
<form class="mod-new-panel">
|
||||
<p class="instructions">Add a new step</p>
|
||||
<select class="select-module form-control" style="margin-bottom:6px;">
|
||||
<option value="ndvi-red">NDVI with red filter</option>
|
||||
<option value="green-channel">Green channel</option>
|
||||
<option value="plot">Plot with colorbar</option>
|
||||
<option value="image-threshold">Threshold image</option>
|
||||
</select>
|
||||
<p><button class="btn btn-default add-step">Add step</button></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="log">
|
||||
<h4>Log</h4>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
var sequencer;
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
sequencer = ImageSequencer();
|
||||
|
||||
sequencer.loadImage('examples/grid.png');
|
||||
|
||||
sequencer.addStep('ndvi-red');
|
||||
sequencer.addStep('image-threshold');
|
||||
sequencer.addStep('crop');
|
||||
//sequencer.addStep('plot');
|
||||
|
||||
$('.add-step').click(function(e) {
|
||||
e.preventDefault();
|
||||
sequencer.addStep($('.select-module').val());
|
||||
sequencer.run(sequencer.options.initialImage); // later we might only run this step, if we can fetch the image output from the previous
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,111 @@
|
||||
console.log('\x1b[31m%s\x1b[0m',"This is the output of the module");
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('./src/ImageSequencer');
|
||||
sequencer = ImageSequencer();
|
||||
sequencer.loadImages({images:{red:'examples/red.jpg'},callback:function(){
|
||||
sequencer.addSteps(['do-nothing-pix','ndvi-red','invert']);
|
||||
sequencer.removeSteps(1);
|
||||
sequencer.insertSteps({
|
||||
red: [{index: -1, name: 'do-nothing-pix', o:{}}]
|
||||
sequencer = ImageSequencer({ui: false});
|
||||
|
||||
var program = require('commander');
|
||||
var readlineSync = require('readline-sync');
|
||||
|
||||
function exit(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
program
|
||||
.version('0.1.0')
|
||||
.option('-i, --image [PATH/URL]', 'Input image URL')
|
||||
.option('-s, --step [step-name]', 'Name of the step to be added.')
|
||||
.option('-o, --output [PATH]', 'Directory where output will be stored.')
|
||||
.option('-op, --opions {object}', 'Options for the step')
|
||||
.parse(process.argv);
|
||||
|
||||
// Parse step into an array to allow for multiple steps.
|
||||
program.step = program.step.split(" ");
|
||||
|
||||
// User must input an image.
|
||||
if(!program.image) exit("Can't read file.")
|
||||
|
||||
// User must input an image.
|
||||
require('fs').access(program.image, function(err){
|
||||
if(err) exit("Can't read file.")
|
||||
});
|
||||
|
||||
// User must input a step. If steps exist, check that every step is a valid step.
|
||||
if(!program.step || !validateSteps(program.step))
|
||||
exit("Please ensure all steps are valid.");
|
||||
|
||||
// If there's no user defined output directory, select a default directory.
|
||||
program.output = program.output || "./output/";
|
||||
|
||||
// Set sequencer to log module outputs, if any.
|
||||
sequencer.setUI({
|
||||
|
||||
onComplete: function(step) {
|
||||
|
||||
// Get information of outputs.
|
||||
step.info = sequencer.modulesInfo(step.name);
|
||||
|
||||
for (var output in step.info.outputs) {
|
||||
console.log("["+program.step+"]: "+output+" = "+step[output]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Finally, if everything is alright, load the image, add the steps and run the sequencer.
|
||||
sequencer.loadImages(program.image,function(){
|
||||
|
||||
// Iterate through the steps and retrieve their inputs.
|
||||
program.step.forEach(function(step){
|
||||
var options = Object.assign({}, sequencer.modulesInfo(step).inputs);
|
||||
|
||||
// If inputs exists, print to console.
|
||||
if (Object.keys(options).length) {
|
||||
console.log("[" + step + "]: Inputs");
|
||||
}
|
||||
|
||||
// If inputs exists, print them out with descriptions.
|
||||
Object.keys(options).forEach(function(input) {
|
||||
// The array below creates a variable number of spaces. This is done with (length + 1).
|
||||
// The extra 4 that makes it (length + 5) is to account for the []: characters
|
||||
console.log(new Array(step.length + 5).join(' ') + input + ": " + options[input].desc);
|
||||
});
|
||||
|
||||
// If inputs exist, iterate through them and prompt for values.
|
||||
Object.keys(options).forEach(function(input) {
|
||||
var value = readlineSync.question("[" + step + "]: Enter a value for " + input + " (" + options[input].type + ", default: " + options[input].default + "): ");
|
||||
options[input] = value;
|
||||
});
|
||||
|
||||
// Add the step and its inputs to the sequencer.
|
||||
sequencer.addSteps(step, options);
|
||||
});
|
||||
sequencer.run();
|
||||
}});
|
||||
|
||||
// Run the sequencer.
|
||||
sequencer.run(function(){
|
||||
|
||||
// Export all images as binary files.
|
||||
sequencer.exportBin(program.output);
|
||||
|
||||
console.log("Files will be exported to \""+program.output+"\"");
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Takes an array of steps and checks if they are valid steps for the sequencer.
|
||||
function validateSteps(steps) {
|
||||
|
||||
// Assume all are valid in the beginning.
|
||||
var valid = true;
|
||||
steps.forEach(function(step) {
|
||||
// If any step in the array is not valid (not a property of modulesInfo), set valid to false.
|
||||
if (!sequencer.modulesInfo().hasOwnProperty(step)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Return valid. (If all of the steps are valid properties, valid will have remained true).
|
||||
return valid;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "image-sequencer",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.0",
|
||||
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
|
||||
"main": "dist/image-sequencer.js",
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"test": "tape test/*.js | tap-spec; browserify test/image-sequencer.js test/chain.js | tape-run --render=\"tap-spec\""
|
||||
},
|
||||
@@ -21,14 +21,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "~3.2.0",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"fisheyegl": "^0.1.2",
|
||||
"font-awesome": "~4.5.0",
|
||||
"jquery": "~2",
|
||||
"jsqr": "^0.2.2",
|
||||
"readline-sync": "^1.4.7",
|
||||
"urify": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"base64-stream": "~0.1.3",
|
||||
"browserify": "13.0.0",
|
||||
"buffer": "~5.0.2",
|
||||
"data-uri-to-buffer": "^2.0.0",
|
||||
"get-pixels": "~3.3.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
@@ -36,6 +40,7 @@
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"image-filter-core": "~1.0.0",
|
||||
"image-filter-threshold": "~1.0.0",
|
||||
"looks-same": "^3.2.1",
|
||||
"matchdep": "^0.3.0",
|
||||
"plotly.js": "~1.21.2",
|
||||
"save-pixels": "~2.3.4",
|
||||
@@ -43,5 +48,8 @@
|
||||
"tape": ">=4.7.0",
|
||||
"tape-run": "^3.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/publiclab/image-sequencer"
|
||||
"homepage": "https://github.com/publiclab/image-sequencer",
|
||||
"bin": {
|
||||
"sequencer": "./index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
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;
|
||||
o.inBrowser = ref.options.inBrowser;
|
||||
|
||||
var module = ref.modules[name].call(ref.images,o);
|
||||
o.step = {
|
||||
name: o.name,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
var fs = require('fs');
|
||||
var getDirectories = function(rootDir, cb) {
|
||||
fs.readdir(rootDir, function(err, files) {
|
||||
var dirs = [];
|
||||
if(typeof(files)=="undefined") {
|
||||
cb(dirs);
|
||||
return [];
|
||||
}
|
||||
for (var index = 0; index < files.length; ++index) {
|
||||
var file = files[index];
|
||||
if (file[0] !== '.') {
|
||||
var filePath = rootDir + '/' + file;
|
||||
fs.stat(filePath, function(err, stat) {
|
||||
if (stat.isDirectory()) {
|
||||
dirs.push(this.file);
|
||||
}
|
||||
if (files.length === (this.index + 1)) {
|
||||
return cb(dirs);
|
||||
}
|
||||
}.bind({index: index, file: file}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function ExportBin(dir = "./output/",ref) {
|
||||
dir = (dir[dir.length-1]=="/") ? dir : dir + "/";
|
||||
if(ref.options.inBrowser) return false;
|
||||
fs.access(dir, function(err){
|
||||
if(err) fs.mkdir(dir, function() {});
|
||||
});
|
||||
getDirectories(dir,function(dirs){
|
||||
var num = 1;
|
||||
for(var d in dirs){
|
||||
if(dirs[d].match(/^sequencer(.*)$/)==null) continue;
|
||||
var n = parseInt(dirs[d].match(/^sequencer(.*)$/)[1]);
|
||||
num = (n>=num)?(n+1):num;
|
||||
}
|
||||
fs.mkdir(dir+'sequencer'+num,function(){
|
||||
var root = dir+'sequencer'+num+'/';
|
||||
for(var image in ref.images) {
|
||||
var steps = ref.images[image].steps;
|
||||
for(var i in steps) {
|
||||
var datauri = steps[i].output.src;
|
||||
var ext = steps[i].output.format;
|
||||
var buffer = require('data-uri-to-buffer')(datauri);
|
||||
fs.writeFile(root+image+"_"+i+"."+ext,buffer,function(){
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -41,14 +41,16 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
modules = require('./Modules'),
|
||||
formatInput = require('./FormatInput'),
|
||||
images = {},
|
||||
inputlog = [];
|
||||
inputlog = [],
|
||||
events = require('./UserInterface')(),
|
||||
fs = require('fs');
|
||||
|
||||
// if in browser, prompt for an image
|
||||
// if (options.imageSelect || options.inBrowser) addStep('image-select');
|
||||
// else if (options.imageUrl) loadImage(imageUrl);
|
||||
|
||||
function addSteps(){
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
var json_q = {};
|
||||
for(var arg in arguments){args.push(copy(arguments[arg]));}
|
||||
@@ -66,7 +68,8 @@ 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);
|
||||
thisStep = images[image].steps[index];
|
||||
thisStep.UI.onRemove(thisStep.options.step);
|
||||
images[image].steps.splice(index,1);
|
||||
}
|
||||
//tell the UI a step has been removed
|
||||
@@ -74,7 +77,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
function removeSteps(image,index) {
|
||||
var run = {}, indices;
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
for(var arg in arguments) args.push(copy(arguments[arg]));
|
||||
|
||||
@@ -93,7 +96,7 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
function insertSteps(image, index, name, o) {
|
||||
var run = {};
|
||||
const this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
for (var arg in arguments) args.push(arguments[arg]);
|
||||
|
||||
@@ -112,8 +115,7 @@ 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 this_ = (this.name == "ImageSequencer")?this:this.sequencer;
|
||||
var args = (this.name == "ImageSequencer")?[]:[this.images];
|
||||
for (var arg in arguments) args.push(copy(arguments[arg]));
|
||||
|
||||
@@ -131,25 +133,38 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
|
||||
function loadImages() {
|
||||
var args = [];
|
||||
var sequencer = this;
|
||||
for (var arg in arguments) args.push(copy(arguments[arg]));
|
||||
var json_q = formatInput.call(this,args,"l");
|
||||
|
||||
inputlog.push({method:"loadImages", json_q:copy(json_q)});
|
||||
var loadedimages = this.copy(json_q.loadedimages);
|
||||
// require('./LoadImage')(this,i,json_q.images[i]);
|
||||
|
||||
for (var i in json_q.images)
|
||||
require('./LoadImage')(this,i,json_q.images[i])
|
||||
|
||||
json_q.callback();
|
||||
return {
|
||||
var ret = {
|
||||
name: "ImageSequencer Wrapper",
|
||||
sequencer: this,
|
||||
addSteps: this.addSteps,
|
||||
removeSteps: this.removeSteps,
|
||||
insertSteps: this.insertSteps,
|
||||
run: this.run,
|
||||
UI: this.UI,
|
||||
setUI: this.setUI,
|
||||
images: loadedimages
|
||||
};
|
||||
|
||||
function load(i) {
|
||||
if(i==loadedimages.length) {
|
||||
json_q.callback.call(ret);
|
||||
return;
|
||||
}
|
||||
var img = loadedimages[i];
|
||||
require('./LoadImage')(sequencer,img,json_q.images[img],function(){
|
||||
load(++i);
|
||||
});
|
||||
}
|
||||
|
||||
load(0);
|
||||
}
|
||||
|
||||
function replaceImage(selector,steps,options) {
|
||||
@@ -157,9 +172,35 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
return require('./ReplaceImage')(this,selector,steps);
|
||||
}
|
||||
|
||||
function setUI(UI) {
|
||||
this.events = require('./UserInterface')(UI);
|
||||
}
|
||||
|
||||
var exportBin = function(dir) {
|
||||
return require('./ExportBin')(dir,this);
|
||||
}
|
||||
|
||||
function modulesInfo(name) {
|
||||
var modulesdata = {}
|
||||
if(name == "load-image") return {};
|
||||
if(arguments.length==0)
|
||||
for (var modulename in modules) {
|
||||
modulesdata[modulename] = modules[modulename][1];
|
||||
}
|
||||
else modulesdata = modules[name][1];
|
||||
return modulesdata;
|
||||
}
|
||||
|
||||
return {
|
||||
//literals and objects
|
||||
name: "ImageSequencer",
|
||||
options: options,
|
||||
inputlog: inputlog,
|
||||
modules: modules,
|
||||
images: images,
|
||||
events: events,
|
||||
|
||||
//user functions
|
||||
loadImages: loadImages,
|
||||
loadImage: loadImages,
|
||||
addSteps: addSteps,
|
||||
@@ -167,10 +208,11 @@ ImageSequencer = function ImageSequencer(options) {
|
||||
insertSteps: insertSteps,
|
||||
replaceImage: replaceImage,
|
||||
run: run,
|
||||
inputlog: inputlog,
|
||||
modules: modules,
|
||||
images: images,
|
||||
ui: options.ui,
|
||||
setUI: setUI,
|
||||
exportBin: exportBin,
|
||||
modulesInfo: modulesInfo,
|
||||
|
||||
//other functions
|
||||
log: log,
|
||||
objTypeOf: objTypeOf,
|
||||
copy: copy
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
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.
|
||||
o.step = {
|
||||
name: o.name,
|
||||
ID: o.number,
|
||||
imageName: o.image,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui,
|
||||
options: o
|
||||
};
|
||||
var UI = ref.events;
|
||||
var module = ref.modules[name][0](o,UI);
|
||||
ref.images[image].steps.splice(index,0,module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,99 @@
|
||||
function LoadImage(ref, name, src) {
|
||||
function CImage(src) {
|
||||
var datauri = (ref.options.inBrowser || src.substring(0,11) == "data:image/")?(src):require('urify')(src);
|
||||
function LoadImage(ref, name, src, main_callback) {
|
||||
function makeImage(datauri) {
|
||||
var image = {
|
||||
src: datauri,
|
||||
format: datauri.split(':')[1].split(';')[0].split('/')[1]
|
||||
}
|
||||
return image;
|
||||
}
|
||||
function CImage(src, callback) {
|
||||
var datauri;
|
||||
if (!!src.match(/^data:/i)) {
|
||||
datauri = src;
|
||||
callback(datauri);
|
||||
}
|
||||
else if (!ref.options.inBrowser && !!src.match(/^https?:\/\//i)) {
|
||||
require( src.match(/^(https?):\/\//i)[1] ).get(src,function(res){
|
||||
var data = '';
|
||||
var contentType = res.headers['content-type'];
|
||||
res.setEncoding('base64');
|
||||
res.on('data',function(chunk) {data += chunk;});
|
||||
res.on('end',function() {
|
||||
callback("data:"+contentType+";base64,"+data);
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (ref.options.inBrowser) {
|
||||
var ext = src.split('.').pop();
|
||||
var image = document.createElement('img');
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
image.onload = function() {
|
||||
canvas.width = image.naturalWidth;
|
||||
canvas.height = image.naturalHeight;
|
||||
context.drawImage(image,0,0);
|
||||
datauri = canvas.toDataURL(ext);
|
||||
callback(datauri);
|
||||
}
|
||||
image.src = src;
|
||||
}
|
||||
else {
|
||||
datauri = require('urify')(src);
|
||||
callback(datauri);
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage(name, src) {
|
||||
var step = {
|
||||
name: "load-image",
|
||||
ID: ref.options.sequencerCounter++,
|
||||
imageName: name,
|
||||
inBrowser: ref.options.inBrowser,
|
||||
ui: ref.options.ui
|
||||
};
|
||||
|
||||
var image = {
|
||||
src: src,
|
||||
steps: [{
|
||||
options: {
|
||||
id: ref.options.sequencerCounter++,
|
||||
id: step.ID,
|
||||
name: "load-image",
|
||||
title: "Load Image"
|
||||
title: "Load Image",
|
||||
step: step
|
||||
},
|
||||
UI: ref.events,
|
||||
draw: function() {
|
||||
UI.onDraw(options.step);
|
||||
if(arguments.length==1){
|
||||
this.output = CImage(arguments[0]);
|
||||
options.step.output = this.output;
|
||||
UI.onComplete(options.step);
|
||||
return true;
|
||||
}
|
||||
else if(arguments.length==2) {
|
||||
this.output = CImage(arguments[0]);
|
||||
options.step.output = this.output;
|
||||
arguments[1]();
|
||||
UI.onComplete(options.step);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
output: CImage(src)
|
||||
}]
|
||||
};
|
||||
ref.images[name] = image;
|
||||
CImage(src, function(datauri) {
|
||||
var output = makeImage(datauri);
|
||||
ref.images[name] = image;
|
||||
var loadImageStep = ref.images[name].steps[0];
|
||||
loadImageStep.output = output;
|
||||
loadImageStep.options.step.output = loadImageStep.output.src;
|
||||
loadImageStep.UI.onSetup(loadImageStep.options.step);
|
||||
loadImageStep.UI.onDraw(loadImageStep.options.step);
|
||||
loadImageStep.UI.onComplete(loadImageStep.options.step);
|
||||
|
||||
main_callback();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return loadImage(name,src);
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
/*
|
||||
* Core modules
|
||||
* Core modules and their info files
|
||||
*/
|
||||
module.exports = {
|
||||
'do-nothing': require('./modules/DoNothing/Module'),
|
||||
'green-channel': require('./modules/GreenChannel/Module'),
|
||||
'ndvi-red': require('./modules/NdviRed/Module'),
|
||||
'do-nothing-pix': require('./modules/DoNothingPix/Module.js'),
|
||||
'invert': require('./modules/Invert/Module'),
|
||||
'crop': require('./modules/Crop/Module'),
|
||||
'segmented-colormap': require('./modules/SegmentedColormap/Module')
|
||||
'green-channel': [
|
||||
require('./modules/GreenChannel/Module'),require('./modules/GreenChannel/info')
|
||||
],
|
||||
'ndvi-red': [
|
||||
require('./modules/NdviRed/Module'),require('./modules/NdviRed/info')
|
||||
],
|
||||
'invert': [
|
||||
require('./modules/Invert/Module'),require('./modules/Invert/info')
|
||||
],
|
||||
'crop': [
|
||||
require('./modules/Crop/Module'),require('./modules/Crop/info')
|
||||
],
|
||||
'segmented-colormap': [
|
||||
require('./modules/SegmentedColormap/Module'),require('./modules/SegmentedColormap/info')
|
||||
],
|
||||
'decode-qr': [
|
||||
require('./modules/DecodeQr/Module'),require('./modules/DecodeQr/info')
|
||||
],
|
||||
'fisheye-gl': [
|
||||
require('./modules/FisheyeGl/Module'),require('./modules/FisheyeGl/info')
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,41 +1,66 @@
|
||||
/*
|
||||
* Default UI for each image-sequencer module
|
||||
* User Interface Handling Module
|
||||
*/
|
||||
module.exports = function UserInterface(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);
|
||||
module.exports = function UserInterface(events = {}) {
|
||||
|
||||
// method to remove the UI for a given method, and remove the step
|
||||
function display(image) {
|
||||
options.el.find('.image').html(image);
|
||||
events.onSetup = events.onSetup || function(step) {
|
||||
if(step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
// Create and append an HTML Element
|
||||
console.log("Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
// Create a NodeJS Object
|
||||
console.log('\x1b[36m%s\x1b[0m',"Added Step \""+step.name+"\" to \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
// method to remove the UI for a given method, and remove the step
|
||||
function remove() {
|
||||
$('div#sequencer-'+options.id).remove();
|
||||
events.onDraw = events.onDraw || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
// Overlay a loading spinner
|
||||
console.log("Drawing Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
// Don't do anything
|
||||
console.log('\x1b[33m%s\x1b[0m',"Drawing Step \""+step.name+"\" on \""+step.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>');
|
||||
events.onComplete = events.onComplete || function(step) {
|
||||
if (step.ui == false) {
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
// Update the DIV Element
|
||||
// Hide the laoding spinner
|
||||
console.log("Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
// Update the NodeJS Object
|
||||
console.log('\x1b[32m%s\x1b[0m',"Drawn Step \""+step.name+"\" on \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
el: options.el,
|
||||
uniqueSelector: options.uniqueSelector,
|
||||
selector: options.selector,
|
||||
display: display,
|
||||
remove: remove
|
||||
events.onRemove = events.onRemove || function(step) {
|
||||
if(step.ui == false){
|
||||
// No UI
|
||||
}
|
||||
else if(step.inBrowser) {
|
||||
// Remove the DIV Element
|
||||
console.log("Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
}
|
||||
else {
|
||||
// Delete the NodeJS Object
|
||||
console.log('\x1b[31m%s\x1b[0m',"Removing Step \""+step.name+"\" of \""+step.imageName+"\".");
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
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');
|
||||
|
||||
options.x = parseInt(options.x) || 0;
|
||||
options.y = parseInt(options.y) || 0;
|
||||
|
||||
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]);
|
||||
options.w = parseInt(options.w) || Math.floor(0.5*pixels.shape[0]);
|
||||
options.h = parseInt(options.h) || Math.floor(0.5*pixels.shape[1]);
|
||||
var ox = options.x;
|
||||
var oy = options.y;
|
||||
var w = options.w;
|
||||
var h = options.h;
|
||||
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 +22,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);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,30 +13,47 @@
|
||||
* 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;
|
||||
var output
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is caled everytime the step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
const this_ = this;
|
||||
// Tell the UI that the step has been triggered
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
require('./Crop')(input,options,function(out,format){
|
||||
this_.output = {
|
||||
|
||||
// This output is accessible to Image Sequencer
|
||||
step.output = {
|
||||
src: out,
|
||||
format: format
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = out;
|
||||
|
||||
// Tell the UI that the step has been drawn
|
||||
UI.onComplete(options.step);
|
||||
|
||||
// Tell Image Sequencer that step has been drawn
|
||||
callback();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "Crop",
|
||||
"description": "Crop image to given x, y, w, h",
|
||||
"url": "https://github.com/publiclab/image-sequencer/tree/master/MODULES.md",
|
||||
"inputs": {
|
||||
"x": {
|
||||
"type": "integer",
|
||||
"desc": "X-position (measured from left) from where cropping starts",
|
||||
"default": 0
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"desc": "Y-position (measured from top) from where cropping starts",
|
||||
"default": 0
|
||||
},
|
||||
"w": {
|
||||
"type": "integer",
|
||||
"desc": "Width of crop",
|
||||
"default": "(50%)"
|
||||
},
|
||||
"h": {
|
||||
"type": "integer",
|
||||
"desc": "Height of crop",
|
||||
"default": "(50%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Decodes QR from a given image.
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Decode QR Code";
|
||||
|
||||
// Tell the UI that a step has been added
|
||||
UI.onSetup(options.step);
|
||||
|
||||
var output;
|
||||
var jsQR = require('jsqr');
|
||||
var getPixels = require('get-pixels');
|
||||
|
||||
// This function is called everytime a step has to be redrawn
|
||||
function draw(input,callback) {
|
||||
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var 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 is accessible to Image Sequencer
|
||||
step.output = input;
|
||||
step.output.data = decoded;
|
||||
|
||||
// Tell Image Sequencer that this step is complete
|
||||
callback();
|
||||
|
||||
// These values are accessible to the UI
|
||||
options.step.output = input.src;
|
||||
options.step.qrval = decoded;
|
||||
|
||||
// Tell the UI that the step is complete and output is set
|
||||
UI.onComplete(options.step);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Decode QR",
|
||||
"inputs": {
|
||||
},
|
||||
"outputs": {
|
||||
"qrval": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Demo Module. Does nothing. Adds a step where output is equal to input.
|
||||
*/
|
||||
module.exports = function DoNothing(options) {
|
||||
options = options || {};
|
||||
options.title = "Do Nothing";
|
||||
var this_ = this;
|
||||
var output
|
||||
|
||||
function draw(input,callback) {
|
||||
this.output = input;
|
||||
callback();
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This module extracts pixels and saves them as it is.
|
||||
*/
|
||||
module.exports = function DoNothingPix(options) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Do Nothing with pixels";
|
||||
var output;
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
function changePixel(r, g, b, a) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Resolves Fisheye Effect
|
||||
*/
|
||||
module.exports = function DoNothing(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Fisheye GL";
|
||||
|
||||
var output;
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
require('fisheyegl');
|
||||
|
||||
function draw(input,callback) {
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var step = this;
|
||||
|
||||
if (!options.inBrowser) { // This module is only for browser
|
||||
this.output = input;
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
// Create a canvas, if it doesn't already exist.
|
||||
if (!document.querySelector('#image-sequencer-canvas')) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.style.display = "none";
|
||||
canvas.setAttribute('id','image-sequencer-canvas');
|
||||
document.body.append(canvas);
|
||||
}
|
||||
else var canvas = document.querySelector('#image-sequencer-canvas');
|
||||
|
||||
distorter = FisheyeGl({
|
||||
selector: "#image-sequencer-canvas"
|
||||
});
|
||||
|
||||
// Parse the inputs
|
||||
options.a = parseFloat(options.a) || distorter.lens.a;
|
||||
options.b = parseFloat(options.b) || distorter.lens.b;
|
||||
options.Fx = parseFloat(options.Fx) || distorter.lens.Fx;
|
||||
options.Fy = parseFloat(options.Fy) || distorter.lens.Fy;
|
||||
options.scale = parseFloat(options.scale) || distorter.lens.scale;
|
||||
options.x = parseFloat(options.x) || distorter.fov.x;
|
||||
options.y = parseFloat(options.y) || distorter.fov.y;
|
||||
|
||||
// Set fisheyegl inputs
|
||||
distorter.lens.a = options.a;
|
||||
distorter.lens.b = options.b;
|
||||
distorter.lens.Fx = options.Fx;
|
||||
distorter.lens.Fy = options.Fy;
|
||||
distorter.lens.scale = options.scale;
|
||||
distorter.fov.x = options.x;
|
||||
distorter.fov.y = options.y;
|
||||
|
||||
// generate fisheyegl output
|
||||
distorter.setImage(input.src,function(){
|
||||
|
||||
// this output is accessible to Image Sequencer
|
||||
step.output = {src: canvas.toDataURL(), format: input.format};
|
||||
|
||||
// This output is accessible to the UI
|
||||
options.step.output = step.output.src;
|
||||
|
||||
// Tell Image Sequencer and UI that step has been drawn
|
||||
callback();
|
||||
UI.onComplete(options.step);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "Fisheye GL",
|
||||
"inputs": {
|
||||
"a": {
|
||||
"type": "float",
|
||||
"desc": "a parameter",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4
|
||||
},
|
||||
"b": {
|
||||
"type": "float",
|
||||
"desc": "b parameter",
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4
|
||||
},
|
||||
"Fx": {
|
||||
"type": "float",
|
||||
"desc": "Fx parameter",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4
|
||||
},
|
||||
"Fy": {
|
||||
"type": "float",
|
||||
"desc": "Fy parameter",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 4
|
||||
},
|
||||
"scale": {
|
||||
"type": "float",
|
||||
"desc": "Image Scaling",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"x": {
|
||||
"type": "float",
|
||||
"desc": "FOV x parameter",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"y": {
|
||||
"type": "float",
|
||||
"desc": "FOV y parameter",
|
||||
"default": 1.5,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"fragmentSrc": {
|
||||
"type": "PATH",
|
||||
"desc": "Patht to a WebGL fragment shader file",
|
||||
"default": "(inbuilt)"
|
||||
},
|
||||
"vertexSrc": {
|
||||
"type": "PATH",
|
||||
"desc": "Patht to a WebGL vertex shader file",
|
||||
"default": "(inbuilt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,38 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
// Tell UI that a step has been set up
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
//function setup() {} // optional
|
||||
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
|
||||
// Tell UI that a step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
return [0, g, 0, a];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
|
||||
// This output is accesible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step ahs been drawn
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
@@ -25,12 +40,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Green Channel",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,40 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
// Tell UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
//function setup() {} // optional
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
|
||||
// Tell UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
|
||||
var 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}
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell UI that step has been drawn.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
@@ -25,12 +42,13 @@ module.exports = function GreenChannel(options) {
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
//setup: setup, // optional
|
||||
draw: draw,
|
||||
output: output
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Invert",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,40 @@
|
||||
/*
|
||||
* 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)";
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// The function which is called on every draw.
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
|
||||
// Tell the UI that a step is being drawn.
|
||||
UI.onDraw(options.step);
|
||||
var 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}
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by the UI.
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell the UI that step has been drawn succesfully.
|
||||
UI.onComplete(options.step);
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
@@ -24,10 +42,13 @@ module.exports = function NdviRed(options) {
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw
|
||||
draw: draw,
|
||||
output: output,
|
||||
UI:UI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "NDVI Red",
|
||||
"inputs": {
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,37 @@
|
||||
module.exports = function SegmentedColormap(options) {
|
||||
module.exports = function SegmentedColormap(options,UI) {
|
||||
|
||||
options = options || {};
|
||||
options.title = "Segmented Colormap";
|
||||
|
||||
// Tell the UI that a step has been set up.
|
||||
UI.onSetup(options.step);
|
||||
var output;
|
||||
|
||||
// This function is called on every draw.
|
||||
function draw(input,callback) {
|
||||
var this_ = this;
|
||||
|
||||
// Tell the UI that the step is being drawn
|
||||
UI.onDraw(options.step);
|
||||
var step = this;
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
var ndvi = (b - r) / (r + b);
|
||||
var normalized = (ndvi + 1) / 2;
|
||||
var res = require('./SegmentedColormap')(normalized,options);
|
||||
return [res[0], res[1], res[2], 255];
|
||||
}
|
||||
|
||||
function output(image,datauri,mimetype){
|
||||
this_.output = {src:datauri,format:mimetype}
|
||||
|
||||
// This output is accessible by Image Sequencer
|
||||
step.output = {src:datauri,format:mimetype};
|
||||
|
||||
// This output is accessible by the UI
|
||||
options.step.output = datauri;
|
||||
|
||||
// Tell the UI that the draw is complete
|
||||
UI.onComplete(options.step);
|
||||
|
||||
}
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
@@ -22,11 +40,13 @@ module.exports = function SegmentedColormap(options) {
|
||||
image: options.image,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
options: options,
|
||||
draw: draw,
|
||||
output: output
|
||||
output: output,
|
||||
UI: UI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Segmented Colormap",
|
||||
"inputs": {
|
||||
"colormap": {
|
||||
"type": "select",
|
||||
"desc": "Name of the Colormap",
|
||||
"default": "default",
|
||||
"values": ["default","greyscale","stretched","fastie"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 r = savePixels(pixels, options.format);
|
||||
r.pipe(w).on('finish',function(){
|
||||
data = w.read().toString();
|
||||
datauri = 'data:image/' + options.format + ';base64,' + data;
|
||||
var chunks = [];
|
||||
var totalLength = 0;
|
||||
var r = savePixels(pixels, options.format, {quality: 100});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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){
|
||||
@@ -17,22 +17,27 @@ test('loadImages/loadImage has a name generator.', function (t){
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages/loadImage returns a wrapper.', function (t){
|
||||
var returnval = sequencer.loadImage(red);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image2","Image scope is defined");
|
||||
t.end();
|
||||
test('loadImages/loadImage returns a wrapper in the callback.', function (t){
|
||||
sequencer.loadImage(red, function() {
|
||||
var returnval = this;
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image2","Image scope is defined");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).addSteps('invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image3","Image scope is defined");
|
||||
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
|
||||
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
|
||||
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
|
||||
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
|
||||
t.end();
|
||||
sequencer.loadImage(red, function(){
|
||||
var returnval = this;
|
||||
this.addSteps('invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image3","Image scope is defined");
|
||||
t.equal(sequencer.images.image3.steps.length,2,"Loaded image is affected");
|
||||
t.equal(sequencer.images.image3.steps[1].options.name,"invert","Correct Step Added");
|
||||
t.equal(sequencer.images.image2.steps.length,1,"Other images are not affected");
|
||||
t.equal(sequencer.images.image1.steps.length,1,"Other images are not affected");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('addSteps is two-way chainable without loadImages.', function (t){
|
||||
@@ -44,11 +49,14 @@ test('addSteps is two-way chainable without loadImages.', function (t){
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).addSteps('invert').removeSteps(1);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image4","Image scope is defined");
|
||||
t.equal(sequencer.images.image4.steps.length,1);
|
||||
t.end();
|
||||
sequencer.loadImage(red,function(){
|
||||
var returnval = this;
|
||||
this.addSteps('invert').removeSteps(1);
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper", "Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image4","Image scope is defined");
|
||||
t.equal(sequencer.images.image4.steps.length,1);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('removeSteps is two-way chainable without loadImages.', function (t){
|
||||
@@ -59,12 +67,15 @@ test('removeSteps is two-way chainable without loadImages.', function (t){
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable.', function (t){
|
||||
var returnval = sequencer.loadImage(red).insertSteps(1,'invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image5","Image scope is defined");
|
||||
t.equal(sequencer.images.image5.steps.length,2);
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
|
||||
t.end();
|
||||
sequencer.loadImage(red,function() {
|
||||
var returnval = this;
|
||||
this.insertSteps(1,'invert');
|
||||
t.equal(returnval.name,"ImageSequencer Wrapper","Wrapper is returned");
|
||||
t.equal(returnval.images[0],"image5","Image scope is defined");
|
||||
t.equal(sequencer.images.image5.steps.length,2);
|
||||
t.equal(sequencer.images.image5.steps[1].options.name,"invert","Correct Step Inserrted");
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('insertSteps is two-way chainable without loadImages.', function (t){
|
||||
|
||||
@@ -1,25 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var looksSame = require('looks-same');
|
||||
var DataURItoBuffer = require('data-uri-to-buffer');
|
||||
|
||||
// We should only test headless code here.
|
||||
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
|
||||
|
||||
require('../src/ImageSequencer.js');
|
||||
|
||||
var sequencer = ImageSequencer({ ui: "none" });
|
||||
var image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAABgj/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABykX//Z";
|
||||
sequencer.loadImages(image);
|
||||
//require image files as DataURLs so they can be tested alike on browser and Node.
|
||||
var sequencer = ImageSequencer({ ui: false });
|
||||
|
||||
sequencer.addSteps(['do-nothing-pix','invert','invert']);
|
||||
sequencer.run();
|
||||
var qr = require('./images/IS-QR.js');
|
||||
var test_png = require('./images/test.png.js');
|
||||
var test_gif = require('./images/test.gif.js');
|
||||
|
||||
sequencer.loadImages(test_png);
|
||||
sequencer.addSteps(['invert','invert']);
|
||||
|
||||
test("Preload", function(t) {
|
||||
sequencer.run(function(){
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Inverted image isn't identical", function (t) {
|
||||
t.notEqual(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[2].output.src);
|
||||
t.end();
|
||||
var step1 = sequencer.images.image1.steps[0].output.src;
|
||||
var step2 = sequencer.images.image1.steps[1].output.src;
|
||||
step1 = DataURItoBuffer(step1);
|
||||
step2 = DataURItoBuffer(step2);
|
||||
looksSame(step1,step2,function(err,res){
|
||||
if(err) console.log(err);
|
||||
t.equal(res,false);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Twice inverted image is identical to original image", function (t) {
|
||||
t.equal(sequencer.images.image1.steps[1].output.src, sequencer.images.image1.steps[3].output.src);
|
||||
var step1 = sequencer.images.image1.steps[0].output.src;
|
||||
var step3 = sequencer.images.image1.steps[2].output.src;
|
||||
step1 = DataURItoBuffer(step1);
|
||||
step3 = DataURItoBuffer(step3);
|
||||
looksSame(step1,step3,function(err,res){
|
||||
if(err) console.log(err);
|
||||
t.equal(res,true);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test("Decode QR module works properly :: setup", function (t) {
|
||||
sequencer.loadImage(qr,function(){
|
||||
this.addSteps('decode-qr').run(function(){
|
||||
t.end();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
test("Decode QR module works properly :: teardown", function (t) {
|
||||
t.equal("http://github.com/publiclab/image-sequencer",sequencer.images.image2.steps[1].output.data);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test("PixelManipulation works for PNG images", function (t) {
|
||||
sequencer.loadImages(test_png,function(){
|
||||
this.addSteps('invert').run(function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("PixelManipulation works for GIF images", function (t) {
|
||||
sequencer.loadImages(test_gif,function(){
|
||||
this.addSteps('invert').run(function(out){
|
||||
t.equal(1,1)
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
@@ -40,18 +40,30 @@ test('loadImages loads a DataURL image and creates a step.', function (t){
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('loadImages loads a PATH image and creates a step. (NodeJS)', function (t){
|
||||
if(sequencer.options.inBrowser){
|
||||
t.equal("not applicable","not applicable","Not applicable for Browser");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImages(red);
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImage loads an image from URL and creates a step. (NodeJS)', function (t){
|
||||
require('dns').resolve('www.github.com', function(err) {
|
||||
if (err) {
|
||||
console.log("Test aborted due to no internet");
|
||||
t.end();
|
||||
}
|
||||
else {
|
||||
sequencer.loadImage('URL','https://ccpandhare.github.io/image-sequencer/examples/images/red.jpg', function(){
|
||||
t.equal(sequencer.images.URL.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.URL.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if(!sequencer.options.inBrowser)
|
||||
test('loadImages loads an image from PATH and creates a step. (NodeJS)', function (t){
|
||||
sequencer.loadImages('examples/images/red.jpg');
|
||||
t.equal(sequencer.images.image1.steps.length, 1, "Initial Step Created");
|
||||
t.equal(typeof(sequencer.images.image1.steps[0].output.src), "string", "Initial output exists");
|
||||
t.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('loadImage works too.', function (t){
|
||||
sequencer.loadImage('test2',red);
|
||||
@@ -61,38 +73,38 @@ test('loadImage works too.', function (t){
|
||||
});
|
||||
|
||||
test('addSteps("image","name") adds a step', function (t) {
|
||||
sequencer.addSteps('test','do-nothing');
|
||||
sequencer.addSteps('test','green-channel');
|
||||
t.equal(sequencer.images.test.steps.length, 2, "Length of steps increased")
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("name") adds a step', function (t) {
|
||||
sequencer.addSteps('do-nothing');
|
||||
sequencer.addSteps('green-channel');
|
||||
t.equal(sequencer.images.test.steps.length, 3, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[2].options.name, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[2].options.name, "green-channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps(["name"]) adds a step', function (t) {
|
||||
sequencer.addSteps(['do-nothing','do-nothing-pix']);
|
||||
sequencer.addSteps(['green-channel','invert']);
|
||||
t.equal(sequencer.images.test.steps.length, 5, "Length of steps increased by two")
|
||||
t.equal(sequencer.images.test.steps[3].options.name, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[4].options.name, "do-nothing-pix", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[3].options.name, "green-channel", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[4].options.name, "invert", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("name",o) adds a step', function (t) {
|
||||
sequencer.addSteps('do-nothing',{});
|
||||
sequencer.addSteps('green-channel',{});
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[5].options.name, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[5].options.name, "green-channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('addSteps("image","name",o) adds a step', function (t) {
|
||||
sequencer.addSteps('test','do-nothing',{});
|
||||
sequencer.addSteps('test','green-channel',{});
|
||||
t.equal(sequencer.images.test.steps.length, 7, "Length of steps increased");
|
||||
t.equal(sequencer.images.test.steps[6].options.name, "do-nothing", "Correct Step Added");
|
||||
t.equal(sequencer.images.test.steps[6].options.name, "green-channel", "Correct Step Added");
|
||||
t.end();
|
||||
});
|
||||
|
||||
@@ -115,30 +127,30 @@ test('removeSteps(position) removes steps', function (t) {
|
||||
});
|
||||
|
||||
test('insertSteps("image",position,"module",options) inserts a step', function (t) {
|
||||
sequencer.insertSteps('test',1,'do-nothing',{});
|
||||
sequencer.insertSteps('test',1,'green-channel',{});
|
||||
t.equal(sequencer.images.test.steps.length, 3, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps("image",position,"module") inserts a step', function (t) {
|
||||
sequencer.insertSteps('test',1,'do-nothing');
|
||||
sequencer.insertSteps('test',1,'green-channel');
|
||||
t.equal(sequencer.images.test.steps.length, 4, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps(position,"module") inserts a step', function (t) {
|
||||
sequencer.insertSteps(1,'do-nothing');
|
||||
sequencer.insertSteps(1,'green-channel');
|
||||
t.equal(sequencer.images.test.steps.length, 5, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('insertSteps({image: {index: index, name: "module", o: options} }) inserts a step', function (t) {
|
||||
sequencer.insertSteps({test: {index:1, name:'do-nothing', o:{} } });
|
||||
sequencer.insertSteps({test: {index:1, name:'green-channel', o:{} } });
|
||||
t.equal(sequencer.images.test.steps.length, 6, "Length of Steps increased");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "do-nothing", "Correct Step Inserted");
|
||||
t.equal(sequencer.images.test.steps[1].options.name, "green-channel", "Correct Step Inserted");
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||