mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-11 10:49:59 +01:00
Merge branch 'master' into gh-pages
This commit is contained in:
60
Gruntfile.js
60
Gruntfile.js
@@ -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
117
README.md
@@ -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
82
dist/image-sequencer.css
vendored
Normal 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%;
|
||||
}
|
||||
13504
dist/imageboard.js → dist/image-sequencer.js
vendored
13504
dist/imageboard.js → dist/image-sequencer.js
vendored
File diff suppressed because one or more lines are too long
60
dist/imageboard.css
vendored
60
dist/imageboard.css
vendored
@@ -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%;
|
||||
}
|
||||
30
index.html
30
index.html
@@ -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();
|
||||
|
||||
});
|
||||
|
||||
|
||||
30
package.json
30
package.json
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
27
test/image-sequencer.js
Normal 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();
|
||||
});
|
||||
Reference in New Issue
Block a user