Merge branch 'master' into gh-pages

This commit is contained in:
jywarren
2017-01-06 01:39:26 -05:00
13 changed files with 3763 additions and 2355 deletions

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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
}
}

View File

@@ -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
View 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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View 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
}
}

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -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
}
}