mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-11 19:00:00 +01:00
Merge branch 'master' into gh-pages
This commit is contained in:
31
README.md
31
README.md
@@ -5,6 +5,10 @@ ImageFlow
|
||||
|
||||
* [ ] steps don't run on last step; they run on initial image
|
||||
|
||||
|
||||
// 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?
|
||||
* [ ] setNextStep()?
|
||||
@@ -16,6 +20,8 @@ ImageFlow
|
||||
* [ ] comparisons with 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
|
||||
@@ -27,3 +33,28 @@ ImageFlow
|
||||
* we should make defaults a config of the first module
|
||||
|
||||
* [ ] output in animated Gif?
|
||||
|
||||
****
|
||||
|
||||
## Why
|
||||
|
||||
How can Scratch/others do what a scientific tool does?
|
||||
|
||||
* if it passes the same tests, it's empirically equivalent
|
||||
|
||||
Competitive with program X? Build bridges
|
||||
|
||||
Show your work: Collins
|
||||
|
||||
Activities: teachability -- each step
|
||||
|
||||
Evidentiary: Chain of custody
|
||||
|
||||
Store each previous step, log, in metadata -- like shapefiles
|
||||
|
||||
****
|
||||
|
||||
Ideas:
|
||||
|
||||
https://github.com/vicapow/jsqrcode
|
||||
|
||||
|
||||
5655
dist/imageboard.js
vendored
5655
dist/imageboard.js
vendored
File diff suppressed because one or more lines are too long
@@ -44,6 +44,7 @@
|
||||
<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>
|
||||
@@ -68,7 +69,8 @@
|
||||
// });
|
||||
|
||||
imageboard.addStep('ndvi-red');
|
||||
imageboard.addStep('plot');
|
||||
imageboard.addStep('image-threshold');
|
||||
//imageboard.addStep('plot');
|
||||
|
||||
$('.add-step').click(function(e) {
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
"buffer": "^5.0.2",
|
||||
"plotly.js": "^1.21.2",
|
||||
|
||||
"image-filter-threshold": "^0.0.8",
|
||||
"babelify": "^7.2.0",
|
||||
"image-filter-threshold": "git+https://github.com/canastro/image-filter-threshold#prebundle",
|
||||
"image-filter-core": "git+https://github.com/canastro/image-filter-core#prebundle",
|
||||
|
||||
"browserify": "13.0.0",
|
||||
"grunt": "^0.4.5",
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
ImageBoard = function ImageBoard(options) {
|
||||
|
||||
options = options || {};
|
||||
options.container = options.container || '.panels';
|
||||
|
||||
var image;
|
||||
var modules = require('./Modules');
|
||||
var steps = [];
|
||||
|
||||
function addStep(name, stepOptions) {
|
||||
steps.push({
|
||||
module: modules[name]({
|
||||
container: options.container // this is a bit redundant
|
||||
}),
|
||||
options: stepOptions
|
||||
});
|
||||
steps[steps.length - 1].module.setup();
|
||||
options.defaultSteps = options.defaultSteps || function defaultSteps() {
|
||||
addStep('image-select');
|
||||
}
|
||||
|
||||
// by default, always begin with an ImageSelect module
|
||||
addStep('image-select');
|
||||
var image,
|
||||
steps = [],
|
||||
modules = require('./Modules'),
|
||||
ui = require('./UserInterface')();
|
||||
|
||||
function setup() {
|
||||
options.defaultSteps();
|
||||
|
||||
steps.forEach(function forEachStep(step, index) {
|
||||
function addStep(name, o) {
|
||||
console.log('adding step "' + name + '"');
|
||||
|
||||
// different behavior for first step:
|
||||
var onComplete = (index !== 0) ? false : function (image) {
|
||||
run(image); // begin run on image selection
|
||||
o = o || {};
|
||||
o.container = o.container || options.selector;
|
||||
o.createUserInterface = o.createUserInterface || ui.create;
|
||||
|
||||
var module = modules[name](o);
|
||||
|
||||
steps.push(module);
|
||||
|
||||
if (steps.length > 1) {
|
||||
|
||||
if (module.setup) module.setup();
|
||||
|
||||
var lastStep = steps[steps.length - 2];
|
||||
|
||||
// connect last step to input of this step
|
||||
lastStep.options.onComplete = function onComplete(_image) {
|
||||
log('running module "' + name + '"');
|
||||
if (lastStep.options.ui) lastStep.options.ui.el.html(_image);
|
||||
module.draw(_image);
|
||||
}
|
||||
|
||||
if (step.module.setup) step.module.setup(onComplete);
|
||||
module.options.onComplete = function onComplete(_image) {
|
||||
if (module.options.ui) module.options.ui.el.html(_image);
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
|
||||
module.setup(); // just set up initial ImageSelect
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
setup();
|
||||
|
||||
function log(msg) {
|
||||
$('.log').append(msg + ' at ' + new Date());
|
||||
console.log(msg);
|
||||
@@ -44,34 +54,21 @@ ImageBoard = function ImageBoard(options) {
|
||||
|
||||
function run() {
|
||||
|
||||
var lastImage;
|
||||
|
||||
// THIS MUST BE EVENT BASED OR CALLBACKED -- ITS ASYNCHRONOUS
|
||||
steps.forEach(function forEachStep(step) {
|
||||
|
||||
step.module.run(lastImage, function onComplete(image) {
|
||||
lastImage = image;
|
||||
log('completed step "' + step.module.title + '"');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return lastImage;
|
||||
steps[0].draw();
|
||||
|
||||
}
|
||||
|
||||
// load default starting image
|
||||
// i.e. from parameter
|
||||
// this could send the image to ImageSelect, or something?
|
||||
// not currently working
|
||||
function loadImage(src, callback) {
|
||||
|
||||
image = new Image();
|
||||
|
||||
image.onload = function() {
|
||||
|
||||
run();
|
||||
|
||||
if (callback) callback(image);
|
||||
|
||||
}
|
||||
|
||||
image.src = src;
|
||||
@@ -83,7 +80,8 @@ ImageBoard = function ImageBoard(options) {
|
||||
addStep: addStep,
|
||||
run: run,
|
||||
modules: modules,
|
||||
steps: steps
|
||||
steps: steps,
|
||||
ui: ui
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,71 +1,12 @@
|
||||
/*
|
||||
* Core modules; externalized these wrapper modules with:
|
||||
* 'image-select': require('./modules/ImageSelect.js'),
|
||||
* Core modules
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
// How much of this wrapper is necessary?
|
||||
// Could it be for UI, and the actual module is for functionality?
|
||||
// But 'image-select' is not set up that way; it's UI. But it's special.
|
||||
'image-select': function ImageSelect() {
|
||||
'image-select': require('./modules/ImageSelect'),
|
||||
'green-channel': require('./modules/GreenChannel'),
|
||||
'ndvi-red': require('./modules/NdviRed'),
|
||||
'plot': require('./modules/Plot'),
|
||||
'image-threshold': require('./modules/ImageThreshold')
|
||||
|
||||
var imageselect;
|
||||
|
||||
function setup(onComplete) {
|
||||
imageselect = require('./modules/ImageSelect.js')({
|
||||
output: onComplete,
|
||||
selector: '#drop'
|
||||
});
|
||||
}
|
||||
|
||||
function run(image, onComplete) {
|
||||
if (onComplete) onComplete(get());
|
||||
}
|
||||
|
||||
function get() {
|
||||
return imageselect.getImage();
|
||||
}
|
||||
|
||||
return {
|
||||
title: "Select image",
|
||||
run: run,
|
||||
setup: setup,
|
||||
get: get
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'green-channel': require('./modules/GreenChannel.js'),
|
||||
'ndvi-red': require('./modules/NdviRed.js'),
|
||||
'plot': require('./modules/Plot.js'),
|
||||
|
||||
/*
|
||||
'image-threshold': {
|
||||
name: "Threshold image",
|
||||
run: function imageThreshold(image, onComplete, options) {
|
||||
|
||||
options = options || {};
|
||||
options.threshold = options.threshold || 30;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0 );
|
||||
var imageData = context.getImageData(0, 0, element.width, element.height);
|
||||
|
||||
var imageThreshold = require('../node_modules/image-filter-threshold/src/index.js');
|
||||
|
||||
var result = imageThreshold({
|
||||
data: imageData,
|
||||
threshold: options.threshold
|
||||
}).then(function (result) {
|
||||
var image = new Image();
|
||||
image.onload = function onLoad() {
|
||||
onComplete(image);
|
||||
}
|
||||
image.src = result;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
26
src/UserInterface.js
Normal file
26
src/UserInterface.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Default UI for ImageBoard
|
||||
*/
|
||||
module.exports = function UserInterface(options) {
|
||||
|
||||
options = options || {};
|
||||
options.container = options.container || ".panels";
|
||||
|
||||
// method to create a UI for a given module
|
||||
function create(o) {
|
||||
o.random = o.random || parseInt(Math.random() * (new Date()).getTime() / 1000000);
|
||||
o.uniqueSelector = o.uniqueSelector || o.selector + '-' + o.random;
|
||||
$(options.container).append('<div class="panel ' + o.selector + ' ' + o.uniqueSelector + '"></div>');
|
||||
o.el = o.el || $('.' + o.uniqueSelector);
|
||||
return {
|
||||
el: o.el,
|
||||
uniqueSelector: o.uniqueSelector,
|
||||
selector: o.selector
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
create: create
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,26 +5,17 @@ module.exports = function GreenChannel(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var image,
|
||||
selector = 'mod-green-channel',
|
||||
random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000),
|
||||
uniqueSelector = selector + '-' + random,
|
||||
el;
|
||||
var image;
|
||||
|
||||
// should we just run setup on constructor?
|
||||
function setup() {
|
||||
|
||||
$(options.container).append('<div class="panel ' + selector + ' ' + uniqueSelector + '"></div>');
|
||||
el = $('.' + uniqueSelector);
|
||||
|
||||
options.ui = options.createUserInterface({
|
||||
selector: 'mod-green-channel'
|
||||
});
|
||||
}
|
||||
|
||||
function run(_image, onComplete, options) {
|
||||
function draw(_image) {
|
||||
require('./PixelManipulation.js')(_image, {
|
||||
onComplete: function displayImage(image) {
|
||||
el.html(image);
|
||||
onComplete(image);
|
||||
},
|
||||
onComplete: options.onComplete,
|
||||
changePixel: changePixel
|
||||
});
|
||||
|
||||
@@ -36,8 +27,8 @@ module.exports = function GreenChannel(options) {
|
||||
|
||||
return {
|
||||
title: "Green channel only",
|
||||
run: run,
|
||||
setup: setup,
|
||||
image: image
|
||||
options: options,
|
||||
draw: draw,
|
||||
setup: setup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,68 +7,78 @@ module.exports = function ImageSelect(options) {
|
||||
options = options || {};
|
||||
options.selector = options.selector || "#drop";
|
||||
options.inputSelector = options.inputSelector || "#file-select";
|
||||
options.output = options.output || function output(image) {
|
||||
return image;
|
||||
}
|
||||
options.ui = options.ui || {};
|
||||
options.ui.el = options.ui.el || $(options.selector);
|
||||
|
||||
var image;
|
||||
var image,
|
||||
el = options.ui.el;
|
||||
|
||||
// CSS UI
|
||||
function setup() {
|
||||
|
||||
$(options.selector).on('dragenter', function(e) {
|
||||
$(options.selector).addClass('hover');
|
||||
});
|
||||
// CSS UI
|
||||
el.on('dragenter', function(e) {
|
||||
el.addClass('hover');
|
||||
});
|
||||
|
||||
$(options.selector).on('dragleave', function(e) {
|
||||
$(options.selector).removeClass('hover');
|
||||
});
|
||||
el.on('dragleave', function(e) {
|
||||
el.removeClass('hover');
|
||||
});
|
||||
|
||||
// Drag & Drop behavior
|
||||
// Drag & Drop behavior
|
||||
function onImage(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // stops the browser from redirecting.
|
||||
|
||||
var onImage = function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // stops the browser from redirecting.
|
||||
var files;
|
||||
if (e.target && e.target.files) files = e.target.files;
|
||||
else files = e.dataTransfer.files;
|
||||
|
||||
var files;
|
||||
if (e.target && e.target.files) files = e.target.files;
|
||||
else files = e.dataTransfer.files;
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
// Read the File objects in this FileList.
|
||||
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
// Read the File objects in this FileList.
|
||||
reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
// we should trigger "load" event here
|
||||
|
||||
reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
// we should trigger "load" event here
|
||||
image = new Image();
|
||||
image.src = event.target.result;
|
||||
|
||||
image = new Image();
|
||||
image.src = event.target.result;
|
||||
el.html(image); // may be redundant
|
||||
|
||||
$(options.selector).html(image);
|
||||
// this is done once per image:
|
||||
options.onComplete(image);
|
||||
}
|
||||
reader.readAsDataURL(f);
|
||||
|
||||
// this is done once per image:
|
||||
options.output(image);
|
||||
}
|
||||
reader.readAsDataURL(f);
|
||||
|
||||
}
|
||||
|
||||
function onDragOver(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
}
|
||||
|
||||
el.on('dragover', onDragOver, false);
|
||||
el[0].addEventListener('drop', onImage, false);
|
||||
$(options.inputSelector).change(onImage);
|
||||
|
||||
}
|
||||
|
||||
function onDragOver(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
function draw(_image) {
|
||||
if (options.onComplete) options.onComplete(image);
|
||||
}
|
||||
|
||||
$(options.selector).on('dragover', onDragOver, false);
|
||||
$(options.selector)[0].addEventListener('drop', onImage, false);
|
||||
$(options.inputSelector).change(onImage);
|
||||
|
||||
function getImage() {
|
||||
function get() {
|
||||
return image;
|
||||
}
|
||||
|
||||
return {
|
||||
getImage: getImage
|
||||
title: "Select image",
|
||||
options: options,
|
||||
draw: draw,
|
||||
setup: setup,
|
||||
get: get
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
51
src/modules/ImageThreshold.js
Normal file
51
src/modules/ImageThreshold.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Image thresholding with 'image-filter-threshold'
|
||||
*/
|
||||
module.exports = function ImageThreshold(options) {
|
||||
|
||||
options = options || {};
|
||||
options.threshold = options.threshold || 30;
|
||||
|
||||
var image;
|
||||
|
||||
function setup() {
|
||||
options.ui = options.createUserInterface({
|
||||
selector: 'mod-image-threshold'
|
||||
});
|
||||
}
|
||||
|
||||
function draw(_image) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = _image.width;
|
||||
canvas.height = _image.height;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(_image, 0, 0 );
|
||||
var imageData = context.getImageData(0, 0, _image.width, _image.height);
|
||||
|
||||
var imageThreshold = require('image-filter-threshold');
|
||||
var imageFilterCore = require('image-filter-core');
|
||||
|
||||
var result = imageThreshold({
|
||||
data: imageData,
|
||||
threshold: options.threshold
|
||||
}).then(function (imageData) {
|
||||
image = new Image();
|
||||
image.onload = function onLoad() {
|
||||
if (options.onComplete) options.onComplete(image);
|
||||
}
|
||||
image.src = imageFilterCore.convertImageDataToCanvasURL(imageData);
|
||||
});
|
||||
}
|
||||
|
||||
function get() {
|
||||
return image;
|
||||
}
|
||||
|
||||
return {
|
||||
title: "Threshold image",
|
||||
options: options,
|
||||
setup: setup,
|
||||
draw: draw,
|
||||
get: get
|
||||
}
|
||||
}
|
||||
@@ -5,29 +5,19 @@ module.exports = function NdviRed(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var image,
|
||||
selector = 'mod-ndvi-red',
|
||||
random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000),
|
||||
uniqueSelector = selector + '-' + random,
|
||||
el;
|
||||
var image;
|
||||
|
||||
// should we just run setup on constructor?
|
||||
function setup() {
|
||||
|
||||
$(options.container).append('<div class="panel ' + selector + ' ' + uniqueSelector + '"></div>');
|
||||
el = $('.' + uniqueSelector);
|
||||
|
||||
options.ui = options.createUserInterface({
|
||||
selector: 'mod-ndvi-red'
|
||||
});
|
||||
}
|
||||
|
||||
function run(_image, onComplete, options) {
|
||||
function draw(_image) {
|
||||
require('./PixelManipulation.js')(_image, {
|
||||
onComplete: function displayImage(image) {
|
||||
el.html(image);
|
||||
onComplete(image);
|
||||
},
|
||||
onComplete: options.onComplete,
|
||||
changePixel: changePixel
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function changePixel(r, g, b, a) {
|
||||
@@ -37,8 +27,8 @@ module.exports = function NdviRed(options) {
|
||||
|
||||
return {
|
||||
title: "NDVI for red-filtered cameras (blue is infrared)",
|
||||
run: run,
|
||||
setup: setup,
|
||||
image: image
|
||||
options: options,
|
||||
draw: draw,
|
||||
setup: setup
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ module.exports = function PixelManipulation(image, options) {
|
||||
savePixels(pixels, options.format)
|
||||
.on('end', function() {
|
||||
|
||||
var image = new Image();
|
||||
var img = new Image();
|
||||
|
||||
if (options.onComplete) options.onComplete(image);
|
||||
img.src = 'data:image/' + options.format + ';base64,' + buffer.read().toString();
|
||||
|
||||
image.src = 'data:image/' + options.format + ';base64,' + buffer.read().toString();
|
||||
if (options.onComplete) options.onComplete(img);
|
||||
|
||||
}).pipe(buffer);
|
||||
|
||||
|
||||
@@ -4,26 +4,20 @@
|
||||
module.exports = function Plot(options) {
|
||||
|
||||
options = options || {};
|
||||
options.colorscale = options.colorscale || 'Jet',//'RdBu';
|
||||
options.type = options.type || 'contour'; // or 'heatmap'
|
||||
|
||||
var image,
|
||||
selector = 'mod-plot',
|
||||
random = options.random || parseInt(Math.random() * (new Date()).getTime() / 1000000),
|
||||
uniqueSelector = selector + '-' + random,
|
||||
el;
|
||||
var image;
|
||||
|
||||
// should we just run setup on constructor?
|
||||
function setup() {
|
||||
|
||||
$(options.container).append('<div class="panel ' + selector + ' ' + uniqueSelector + '"></div>');
|
||||
el = $('.' + uniqueSelector);
|
||||
options.ui = options.createUserInterface({
|
||||
selector: 'mod-plot'
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function run(_image, onComplete, options) {
|
||||
|
||||
options = options || {};
|
||||
options.colorscale = options.colorscale || 'Jet',//'RdBu';
|
||||
options.type = options.type || 'contour'; // or 'heatmap'
|
||||
function draw(_image) {
|
||||
|
||||
/* https://plot.ly/javascript/heatmap-and-contour-colorscales/#custom-colorscale
|
||||
type: 'contour',
|
||||
@@ -41,7 +35,7 @@ module.exports = function Plot(options) {
|
||||
var data = [{
|
||||
z: [],
|
||||
colorscale: options.colorscale,
|
||||
type: 'heatmap'
|
||||
type: options.type
|
||||
}];
|
||||
|
||||
getPixels(_image.src, function(err, pixels) {
|
||||
@@ -61,19 +55,28 @@ module.exports = function Plot(options) {
|
||||
}
|
||||
|
||||
var layout = { title: '' };
|
||||
el.append('<div id="plot-' + random + '"></div>');
|
||||
Plotly.newPlot('plot-' + random, data, layout);
|
||||
random = parseInt(Math.random() * (new Date()).getTime() / 1000000);
|
||||
|
||||
// return Plotly.toImage(gd,{format:'jpeg',height:400,width:400});
|
||||
options.ui.el.append('<div id="plot-' + random + '"></div>');
|
||||
Plotly.newPlot('plot-' + random, data, layout)
|
||||
/* .then(function afterPlot(graphData) {
|
||||
|
||||
options.onComplete(Plotly.toImage(graphData, {
|
||||
format: 'jpeg',
|
||||
height: _image.height,
|
||||
width: _image.width
|
||||
}));
|
||||
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
title: "Plot with colorbar",
|
||||
run: run,
|
||||
setup: setup,
|
||||
image: image
|
||||
options: options,
|
||||
draw: draw,
|
||||
setup: setup
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user