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);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
options : {
livereload: true
},
source: {
files: [
'src/*.js',
'src/*/*.js',
'Gruntfile.js'
],
tasks: [ 'build:js' ]
}
},
pkg: grunt.file.readJSON('package.json'),
browserify: {
dist: {
src: [
'src/ImageBoard.js'
],
dest: 'dist/imageboard.js'
}
},
/*
jasmine: {
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'
]
}
watch: {
options : {
livereload: true
},
source: {
files: [
'src/*.js',
'src/*/*.js',
'Gruntfile.js'
],
tasks: [ 'build:js' ]
}
}
*/
},
browserify: {
dist: {
src: [
'src/ImageSequencer.js'
],
dest: 'dist/image-sequencer.js'
}
}
});
@@ -54,6 +40,4 @@ module.exports = function(grunt) {
'browserify:dist'
]);
// grunt.loadNpmTasks('grunt-contrib-jasmine');
};

117
README.md
View File

@@ -1,13 +1,68 @@
ImageBoard
ImageFlow
Image Sequencer
====
========
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
// it could create the interface and use event listeners like module.on('draw', fn()); to update the interface
## Contributing
**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
* [ ] ability to start running at any point -- already works?
@@ -16,12 +71,11 @@ ImageFlow
* [ ] figure out UI/functional separation -- ui is in module wrapper?
* [ ] is there a module for generating forms from parameters?
* [ ] commandline runnability?
* [ ]
* [ ] 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
* [ ] 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
* [ ] click to expand for all images
* [ ] "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
Store each previous step, log, in metadata -- like shapefiles
* 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 } })`
****
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">
<head>
<title>ImageBoard</title>
<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/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/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="dist/imageboard.js"></script>
<script src="dist/image-sequencer.js"></script>
</head>
@@ -21,7 +21,7 @@
<div class="header">
<h1>ImageBoard</h1>
<h1>Image Sequencer</h1>
</div>
@@ -40,7 +40,7 @@
<div class="mod-new-panel">
<form class="mod-new-panel">
<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="green-channel">Green channel</option>
<option value="plot">Plot with colorbar</option>
@@ -56,27 +56,23 @@
<script>
var imageboard;
var sequencer;
jQuery(document).ready(function($) {
imageboard = ImageBoard();
sequencer = ImageSequencer();
// imageboard.loadImage('examples/grid.png', function() {
// sequencer.loadImage('examples/grid.png');
// $('body').append(imageboard.run());
// });
imageboard.addStep('ndvi-red');
imageboard.addStep('image-threshold');
//imageboard.addStep('plot');
sequencer.addStep('ndvi-red');
sequencer.addStep('image-threshold');
//sequencer.addStep('plot');
$('.add-step').click(function(e) {
e.preventDefault();
imageboard.addStep($('.select-module').val());
imageboard.run();
sequencer.addStep($('.select-module').val());
sequencer.run();
});

View File

@@ -1,12 +1,14 @@
{
"name": "imageboard",
"name": "image-sequencer",
"version": "0.0.1",
"description": "A modular JavaScript image manipulation library modeled on a storyboard.",
"main": "dist/imageboard.js",
"scripts": {},
"main": "dist/image-sequencer.js",
"scripts": {
"test": "tape test/*.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jywarren/imageboard.git"
"url": "git+https://github.com/jywarren/image-sequencer.git"
},
"keywords": [
"images",
@@ -15,7 +17,7 @@
"author": "Public Lab",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/jywarren/imageboard/issues"
"url": "https://github.com/jywarren/image-sequencer/issues"
},
"dependencies": {
"font-awesome": "~4.5.0",
@@ -23,15 +25,15 @@
"jquery": "~2"
},
"devDependencies": {
"get-pixels": "^3.3.0",
"save-pixels": "^2.3.4",
"base64-stream": "^0.1.3",
"buffer": "^5.0.2",
"plotly.js": "^1.21.2",
"image-filter-threshold": "git+https://github.com/canastro/image-filter-threshold#prebundle",
"image-filter-core": "git+https://github.com/canastro/image-filter-core#prebundle",
"get-pixels": "~3.3.0",
"save-pixels": "~2.3.4",
"base64-stream": "~0.1.3",
"buffer": "~5.0.2",
"plotly.js": "~1.21.2",
"image-filter-threshold": "~1.0.0",
"image-filter-core": "~1.0.0",
"tape": "^3.5.0",
"browserify": "13.0.0",
"grunt": "^0.4.5",
"grunt-browserify": "^5.0.0",
@@ -39,5 +41,5 @@
"grunt-contrib-watch": "^0.6.1",
"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.defaultSteps = options.defaultSteps || function defaultSteps() {
@@ -7,8 +9,9 @@ ImageBoard = function ImageBoard(options) {
var image,
steps = [],
modules = require('./Modules'),
ui = require('./UserInterface')();
modules = require('./Modules');
options.ui = options.ui || require('./UserInterface')();
options.defaultSteps();
@@ -17,7 +20,7 @@ ImageBoard = function ImageBoard(options) {
o = o || {};
o.container = o.container || options.selector;
o.createUserInterface = o.createUserInterface || ui.create;
o.createUserInterface = o.createUserInterface || options.ui.create;
var module = modules[name](o);
@@ -81,7 +84,9 @@ ImageBoard = function ImageBoard(options) {
run: run,
modules: modules,
steps: steps,
ui: ui
ui: options.ui
}
}
module.exports = ImageSequencer;

View File

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

View File

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