Merge branch 'master' into gh-pages

This commit is contained in:
jywarren
2017-01-09 18:06:50 -05:00
11 changed files with 11933 additions and 1998 deletions

View File

@@ -5,45 +5,31 @@ module.exports = function(grunt) {
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: { pkg: grunt.file.readJSON('package.json'),
options : {
livereload: true
},
source: {
files: [
'src/*.js',
'src/*/*.js',
'Gruntfile.js'
],
tasks: [ 'build:js' ]
}
},
browserify: { watch: {
dist: { options : {
src: [ livereload: true
'src/ImageBoard.js' },
], source: {
dest: 'dist/imageboard.js' files: [
} 'src/*.js',
}, 'src/*/*.js',
'Gruntfile.js'
/* ],
jasmine: { tasks: [ 'build:js' ]
imageboard: {
src: 'dist/*.js',
options: {
specs: 'spec/javascripts/*spec.js',
vendor: [
'node_modules/jquery/dist/jquery.min.js',
'node_modules/jasmine-jquery/lib/jasmine-jquery.js'
]
}
} }
} },
*/
browserify: {
dist: {
src: [
'src/ImageSequencer.js'
],
dest: 'dist/image-sequencer.js'
}
}
}); });
@@ -54,6 +40,4 @@ module.exports = function(grunt) {
'browserify:dist' 'browserify:dist'
]); ]);
// grunt.loadNpmTasks('grunt-contrib-jasmine');
}; };

117
README.md
View File

@@ -1,13 +1,68 @@
ImageBoard Image Sequencer
ImageFlow ====
======== aka "Consequencer"
* [ ] steps don't run on last step; they run on initial image ## Why
Image Sequencer is different from other image processing systems in that instead of modifying the original image, it creates a new image at each step. This is because it:
* produces a legible trail of operations, to "show your work" for evidential, educational, or reproducibility reasons
* makes the creation of new tools or "modules" simpler -- each must accept an input image, and produce an output image
* allows many images to be run through the same sequence of steps
It is also for exploring some other related ideas:
* test-based image processing -- the ability to create a sequence of steps that do the same task as some other image processing tool, provable with example before/after images to compare with
* logging of each step to produce an evidentiary record of modifications to an original image
* cascading changes -- change an earlier step's settings, and see those changes affect later steps
* "small modules"-based extensibility: see [Contributing](#contributing), below
// add createUserInterface() which is set up by default to draw on ImageBoardUI, but could be swapped for nothing, or an equiv. lib ## Contributing
// it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
**This is a draft proposal: currently, onComplete assignment is done through `module.options.onComplete` -- clearly non-ideal.**
To add a module to Image Sequencer, it must have the following method; you can wrap an existing module to add them:
* `module.draw(onComplete)`
The `draw()` method should accept two paramters, `image` and `onComplete`. The `onComplete` parameter will be a function with one parameter, and will be set to the `draw()` method of the next step; for example:
```js
function(image, onComplete) {
// do some stuff with the image
}
```
> No, let's instead do: `module.draw()` and `module.setOutput(fn)` or `module.setNext(fn)`
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.
* setup()
****
## Development
Notes on development next steps:
Make available as browserified OR `require()` includable...
### 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 * [ ] spinners before panels are complete
* [ ] ability to start running at any point -- already works? * [ ] ability to start running at any point -- already works?
@@ -16,12 +71,11 @@ ImageFlow
* [ ] figure out UI/functional separation -- ui is in module wrapper? * [ ] figure out UI/functional separation -- ui is in module wrapper?
* [ ] is there a module for generating forms from parameters? * [ ] is there a module for generating forms from parameters?
* [ ] commandline runnability? * [ ] commandline runnability?
* [ ]
* [ ] tests - modules headless; unit tests * [ ] tests - modules headless; unit tests
* [ ] comparisons with diff * [ ] 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
* [ ] standardize panel addition with submodule that offers Panel.display(image) * [ ] standardize panel addition with submodule that offers Panel.display(image)
https://www.npmjs.com/package/histogram
* [ ] make an Infragram module that accepts a math expression * [ ] make an Infragram module that accepts a math expression
* [ ] click to expand for all images * [ ] click to expand for all images
* [ ] "add a new step" menu * [ ] "add a new step" menu
@@ -36,25 +90,48 @@ https://www.npmjs.com/package/histogram
**** ****
## Why ## Module Candidates
How can Scratch/others do what a scientific tool does? * 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`?)
* if it passes the same tests, it's empirically equivalent ## Ideas
Competitive with program X? Build bridges * 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
Show your work: Collins ### Referencing earlier states
Activities: teachability -- each step Complex sequences with masking could require accessing previous states (or nonlinearity):
Evidentiary: Chain of custody * flood-fill an area
* select only the flooded area
Store each previous step, log, in metadata -- like shapefiles * 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 } })`
**** ****
Ideas: **Notes:**
https://github.com/vicapow/jsqrcode `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();
```

82
dist/image-sequencer.css vendored Normal file
View File

@@ -0,0 +1,82 @@
/* 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;
max-width: 700px;
background: #f8f8fa;
}
h1 {
font-family: 'League Spartan';
color: #445;
}
.header {
text-align: center;
}
.panels {
}
.panel {
background: #f8f8fa;
padding: 20px 0;
margin-bottom: 20px;
border-bottom: 1px dashed #ccc;
}
.panel img {
max-width: 100%;
}
.instructions {
color: #aaa;
}
.log h4 {
text-align: center;
}
.log {
margin-top: 20px;
padding: 10px;
font-size: 9px;
font-family: monospace;
color: #666;
background: #efeff6;
border-radius: 4px;
}
/* ImageSelect styles */
.mod-image-select #drop {
border-radius: 4px;
background: #efeff6;
color: #aaa;
padding: 20px;
width: 100%;
margin-bottom: 10px;
border: 4px dashed #ccc;
text-align: center;
}
.mod-image-select #drop.hover {
background: #ddd;
}
.mod-image-select #drop img {
width: 100%;
}

File diff suppressed because one or more lines are too long

60
dist/imageboard.css vendored
View File

@@ -1,60 +0,0 @@
body {
padding: 20px;
margin: 0 auto;
max-width: 700px;
}
.header {
text-align: center;
}
.panels {
}
.panel {
padding: 20px 0;
margin-bottom: 20px;
border-bottom: 1px dashed #ccc;
}
.panel img {
max-width: 100%;
}
.instructions {
color: #aaa;
}
.log h4 {
text-align: center;
}
.log {
margin-top: 20px;
padding: 10px;
font-size: 9px;
font-family: monospace;
color: #666;
background: #eee;
}
/* ImageSelect styles */
.mod-image-select #drop {
background: #efefef;
color: #aaa;
padding: 20px;
width: 100%;
margin-bottom: 10px;
border: 4px dashed #ccc;
text-align: center;
}
.mod-image-select #drop.hover {
background: #ddd;
}
.mod-image-select #drop img {
width: 100%;
}

View File

@@ -2,18 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>ImageBoard</title> <title>Image Sequencer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=UTF8"> <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/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet"> <link href="node_modules/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="dist/imageboard.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/jquery/dist/jquery.min.js"></script>
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script> <script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="dist/imageboard.js"></script> <script src="dist/image-sequencer.js"></script>
</head> </head>
@@ -21,7 +21,7 @@
<div class="header"> <div class="header">
<h1>ImageBoard</h1> <h1>Image Sequencer</h1>
</div> </div>
@@ -40,7 +40,7 @@
<div class="mod-new-panel"> <div class="mod-new-panel">
<form class="mod-new-panel"> <form class="mod-new-panel">
<p class="instructions">Add a new step</p> <p class="instructions">Add a new step</p>
<select class="select-module form-control"> <select class="select-module form-control" style="margin-bottom:6px;">
<option value="ndvi-red">NDVI with red filter</option> <option value="ndvi-red">NDVI with red filter</option>
<option value="green-channel">Green channel</option> <option value="green-channel">Green channel</option>
<option value="plot">Plot with colorbar</option> <option value="plot">Plot with colorbar</option>
@@ -56,27 +56,23 @@
<script> <script>
var imageboard; var sequencer;
jQuery(document).ready(function($) { jQuery(document).ready(function($) {
imageboard = ImageBoard(); sequencer = ImageSequencer();
// imageboard.loadImage('examples/grid.png', function() { // sequencer.loadImage('examples/grid.png');
// $('body').append(imageboard.run()); sequencer.addStep('ndvi-red');
sequencer.addStep('image-threshold');
// }); //sequencer.addStep('plot');
imageboard.addStep('ndvi-red');
imageboard.addStep('image-threshold');
//imageboard.addStep('plot');
$('.add-step').click(function(e) { $('.add-step').click(function(e) {
e.preventDefault(); e.preventDefault();
imageboard.addStep($('.select-module').val()); sequencer.addStep($('.select-module').val());
imageboard.run(); sequencer.run();
}); });

View File

@@ -1,12 +1,14 @@
{ {
"name": "imageboard", "name": "image-sequencer",
"version": "0.0.1", "version": "0.0.1",
"description": "A modular JavaScript image manipulation library modeled on a storyboard.", "description": "A modular JavaScript image manipulation library modeled on a storyboard.",
"main": "dist/imageboard.js", "main": "dist/image-sequencer.js",
"scripts": {}, "scripts": {
"test": "tape test/*.js"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/jywarren/imageboard.git" "url": "git+https://github.com/jywarren/image-sequencer.git"
}, },
"keywords": [ "keywords": [
"images", "images",
@@ -15,7 +17,7 @@
"author": "Public Lab", "author": "Public Lab",
"license": "GPL-3.0", "license": "GPL-3.0",
"bugs": { "bugs": {
"url": "https://github.com/jywarren/imageboard/issues" "url": "https://github.com/jywarren/image-sequencer/issues"
}, },
"dependencies": { "dependencies": {
"font-awesome": "~4.5.0", "font-awesome": "~4.5.0",
@@ -23,15 +25,15 @@
"jquery": "~2" "jquery": "~2"
}, },
"devDependencies": { "devDependencies": {
"get-pixels": "^3.3.0", "get-pixels": "~3.3.0",
"save-pixels": "^2.3.4", "save-pixels": "~2.3.4",
"base64-stream": "^0.1.3", "base64-stream": "~0.1.3",
"buffer": "^5.0.2", "buffer": "~5.0.2",
"plotly.js": "^1.21.2", "plotly.js": "~1.21.2",
"image-filter-threshold": "~1.0.0",
"image-filter-threshold": "git+https://github.com/canastro/image-filter-threshold#prebundle", "image-filter-core": "~1.0.0",
"image-filter-core": "git+https://github.com/canastro/image-filter-core#prebundle",
"tape": "^3.5.0",
"browserify": "13.0.0", "browserify": "13.0.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-browserify": "^5.0.0", "grunt-browserify": "^5.0.0",
@@ -39,5 +41,5 @@
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"matchdep": "^0.3.0" "matchdep": "^0.3.0"
}, },
"homepage": "https://github.com/jywarren/imageboard" "homepage": "https://github.com/jywarren/image-sequencer"
} }

View File

@@ -1,4 +1,6 @@
ImageBoard = function ImageBoard(options) { window.$ = window.jQuery = require('jquery');
ImageSequencer = function ImageSequencer(options) {
options = options || {}; options = options || {};
options.defaultSteps = options.defaultSteps || function defaultSteps() { options.defaultSteps = options.defaultSteps || function defaultSteps() {
@@ -7,8 +9,9 @@ ImageBoard = function ImageBoard(options) {
var image, var image,
steps = [], steps = [],
modules = require('./Modules'), modules = require('./Modules');
ui = require('./UserInterface')();
options.ui = options.ui || require('./UserInterface')();
options.defaultSteps(); options.defaultSteps();
@@ -17,7 +20,7 @@ ImageBoard = function ImageBoard(options) {
o = o || {}; o = o || {};
o.container = o.container || options.selector; o.container = o.container || options.selector;
o.createUserInterface = o.createUserInterface || ui.create; o.createUserInterface = o.createUserInterface || options.ui.create;
var module = modules[name](o); var module = modules[name](o);
@@ -81,7 +84,9 @@ ImageBoard = function ImageBoard(options) {
run: run, run: run,
modules: modules, modules: modules,
steps: steps, steps: steps,
ui: ui ui: options.ui
} }
} }
module.exports = ImageSequencer;

View File

@@ -14,11 +14,11 @@ module.exports = function GreenChannel(options) {
} }
function draw(_image) { function draw(_image) {
// PixelManipulation returns an image
require('./PixelManipulation.js')(_image, { require('./PixelManipulation.js')(_image, {
onComplete: options.onComplete, onComplete: options.onComplete,
changePixel: changePixel changePixel: changePixel
}); });
} }
function changePixel(r, g, b, a) { function changePixel(r, g, b, a) {

View File

@@ -4,6 +4,8 @@
*/ */
module.exports = function ImageSelect(options) { module.exports = function ImageSelect(options) {
//window.$ = window.jQuery = require('jquery');
options = options || {}; options = options || {};
options.selector = options.selector || "#drop"; options.selector = options.selector || "#drop";
options.inputSelector = options.inputSelector || "#file-select"; options.inputSelector = options.inputSelector || "#file-select";
@@ -13,6 +15,8 @@ module.exports = function ImageSelect(options) {
var image, var image,
el = options.ui.el; el = options.ui.el;
console.log(el,$('body'));
function setup() { function setup() {
// CSS UI // CSS UI

27
test/image-sequencer.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict';
var fs = require('fs');
var test = require('tape');
// We should only test headless code here.
// http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document#25622933
var imageSequencer = require('../dist/image-sequencer')({
defaultSteps: function() {
console.log('defaults');
}
});
function read (file) {
return fs.readFileSync('./test/fixtures/' + file, 'utf8').trim();
}
function write (file, data) { /* jshint ignore:line */
return fs.writeFileSync('./test/fixtures/' + file, data + '\n', 'utf8');
}
test.skip('Image Sequencer has tests', function (t) {
// read('something.html')
t.equal(true, true);
t.end();
});