mirror of
https://github.com/publiclab/image-sequencer.git
synced 2025-12-11 19:00:00 +01:00
GPU Acceleration achieved! (#1038)
* add gpu.js 2.0.0-rc.7 * add gpuUtils * add gpuUtil convolve * add convolution to edgeDetect * bench it * bench change * newline * pipeline * revert edge-detect * gpu accelerate gaussian blur * edgeDetect use blur * tweak values * remove ndarray-gaussian-filter * audit * turn on previews * remove oldPix * fix travis * Travis fix * Try fixing travis * Fix * Retry * tweaks * convolution module on GPU * use babelify * trial * remove logs * Update .travis.yml * Update .travis.yml * bump version * bump to rc.9 * rc.10 * rc.11 * Update package.json * convolution fix * unit test gpuUtils * tests changed, fixed * new fix * more obvious parseFloat * remove old commented code
This commit is contained in:
committed by
Jeffrey Warren
parent
43a8d0f2c1
commit
b0096a13f4
@@ -1,3 +1,4 @@
|
||||
sudo: required
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
@@ -26,6 +27,10 @@ addons:
|
||||
packages:
|
||||
- g++-4.8
|
||||
- xvfb # for tape-run
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install xserver-xorg-dev libxext-dev libxi-dev
|
||||
- sudo apt-get install -y build-essential libxi-dev libglu1-mesa-dev libglew-dev pkg-config libglu1-mesa-dev freeglut3-dev mesa-common-dev
|
||||
install:
|
||||
- export DISPLAY=':99.0' # for tape-run
|
||||
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & # for tape-run
|
||||
|
||||
1727
package-lock.json
generated
1727
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -5,7 +5,7 @@
|
||||
"main": "src/ImageSequencer.js",
|
||||
"scripts": {
|
||||
"debug": "TEST=true node ./index.js -i ./examples/images/monarch.png -s invert",
|
||||
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/canvas-resize.js test/core/modules/QR.js test/core/modules/crop.js | tap-spec; browserify test/core/modules/image-sequencer.js test/core/modules/chain.js test/core/modules/meta-modules.js test/core/modules/replace.js test/core/modules/import-export.js test/core/modules/run.js test/core/modules/dynamic-imports.js test/core/util/parse-input.js test/core/modules/benchmark.js| tape-run --render=\"tap-spec\"",
|
||||
"test": "TEST=true istanbul cover tape test/core/*.js test/core/ui/user-interface.js test/core/modules/canvas-resize.js test/core/modules/QR.js test/core/modules/crop.js | tap-spec; browserify test/core/modules/image-sequencer.js test/core/modules/chain.js test/core/modules/meta-modules.js test/core/modules/replace.js test/core/modules/import-export.js test/core/modules/run.js test/core/modules/dynamic-imports.js test/core/util/*.js test/core/modules/benchmark.js| tape-run --render=\"tap-spec\"",
|
||||
"test-ui": "jasmine test/spec/*.js",
|
||||
"setup": "npm i && npm i -g grunt grunt-cli && grunt build",
|
||||
"start": "grunt serve"
|
||||
@@ -24,6 +24,7 @@
|
||||
"url": "https://github.com/publiclab/image-sequencer/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-img": "^1.0.4",
|
||||
"bootstrap": "~3.4.0",
|
||||
"buffer": "~5.2.1",
|
||||
"commander": "^2.11.0",
|
||||
@@ -35,6 +36,7 @@
|
||||
"get-pixels": "~3.3.0",
|
||||
"gifshot": "^0.4.5",
|
||||
"glfx": "0.0.4",
|
||||
"gpu.js": "^2.0.0-rc.12",
|
||||
"image-sequencer-invert": "^1.0.0",
|
||||
"imagejs": "0.0.9",
|
||||
"imgareaselect": "git://github.com/jywarren/imgareaselect.git#v1.0.0-rc.2",
|
||||
@@ -44,7 +46,6 @@
|
||||
"jsqr": "^1.1.1",
|
||||
"lodash": "^4.17.11",
|
||||
"ndarray": "^1.0.18",
|
||||
"ndarray-gaussian-filter": "^1.0.0",
|
||||
"ora": "^3.0.0",
|
||||
"pace": "0.0.4",
|
||||
"puppeteer": "^1.14.0",
|
||||
@@ -56,7 +57,10 @@
|
||||
"webgl-distort": "0.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"base64-img": "^1.0.4",
|
||||
"@babel/core": "^7.4.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.4.3",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.2.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "16.2.3",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-browser-sync": "^2.2.0",
|
||||
|
||||
@@ -1,84 +1,62 @@
|
||||
module.exports = exports = function(pixels, blur) {
|
||||
let kernel = kernelGenerator(blur, 1), oldpix = require('lodash').cloneDeep(pixels);
|
||||
kernel = flipKernel(kernel);
|
||||
|
||||
for (let i = 0; i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < pixels.shape[1]; j++) {
|
||||
let neighboutPos = getNeighbouringPixelPositions([i, j]);
|
||||
let acc = [0.0, 0.0, 0.0, 0.0];
|
||||
for (let a = 0; a < kernel.length; a++) {
|
||||
for (let b = 0; b < kernel.length; b++) {
|
||||
acc[0] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 0) * kernel[a][b]);
|
||||
acc[1] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 1) * kernel[a][b]);
|
||||
acc[2] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 2) * kernel[a][b]);
|
||||
acc[3] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 3) * kernel[a][b]);
|
||||
}
|
||||
}
|
||||
pixels.set(i, j, 0, acc[0]);
|
||||
pixels.set(i, j, 1, acc[1]);
|
||||
pixels.set(i, j, 2, acc[2]);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
|
||||
|
||||
|
||||
//Generates a 3x3 Gaussian kernel
|
||||
function kernelGenerator(sigma, size) {
|
||||
|
||||
/*
|
||||
Trying out a variable radius kernel not working as of now
|
||||
*/
|
||||
// const coeff = (1.0/(2.0*Math.PI*sigma*sigma))
|
||||
// const expCoeff = -1 * (1.0/2.0 * sigma * sigma)
|
||||
// let e = Math.E
|
||||
// let result = []
|
||||
// for(let i = -1 * size;i<=size;i++){
|
||||
// let arr = []
|
||||
// for(let j= -1 * size;j<=size;j++){
|
||||
// arr.push(coeff * Math.pow(e,expCoeff * ((i * i) + (j*j))))
|
||||
// }
|
||||
// result.push(arr)
|
||||
// }
|
||||
// let sum = result.reduce((sum,val)=>{
|
||||
// return val.reduce((sumInner,valInner)=>{
|
||||
// return sumInner+valInner
|
||||
// })
|
||||
// })
|
||||
// result = result.map(arr=>arr.map(val=>(val + 0.0)/(sum + 0.0)))
|
||||
|
||||
// return result
|
||||
|
||||
return [
|
||||
[2.0 / 159.0, 4.0 / 159.0, 5.0 / 159.0, 4.0 / 159.0, 2.0 / 159.0],
|
||||
[4.0 / 159.0, 9.0 / 159.0, 12.0 / 159.0, 9.0 / 159.0, 4.0 / 159.0],
|
||||
[5.0 / 159.0, 12.0 / 159.0, 15.0 / 159.0, 12.0 / 159.0, 5.0 / 159.0],
|
||||
[4.0 / 159.0, 9.0 / 159.0, 12.0 / 159.0, 9.0 / 159.0, 4.0 / 159.0],
|
||||
[2.0 / 159.0, 4.0 / 159.0, 5.0 / 159.0, 4.0 / 159.0, 2.0 / 159.0]
|
||||
];
|
||||
}
|
||||
function getNeighbouringPixelPositions(pixelPosition) {
|
||||
let x = pixelPosition[0], y = pixelPosition[1], result = [];
|
||||
|
||||
for (let i = -2; i <= 2; i++) {
|
||||
let arr = [];
|
||||
for (let j = -2; j <= 2; j++)
|
||||
arr.push([x + i, y + j]);
|
||||
|
||||
result.push(arr);
|
||||
}
|
||||
return result;
|
||||
let kernel = kernelGenerator(blur),
|
||||
pixs = {
|
||||
r: [],
|
||||
g: [],
|
||||
b: [],
|
||||
}
|
||||
|
||||
function flipKernel(kernel) {
|
||||
let result = [];
|
||||
for (let i = kernel.length - 1; i >= 0; i--) {
|
||||
let arr = [];
|
||||
for (let j = kernel[i].length - 1; j >= 0; j--) {
|
||||
arr.push(kernel[i][j]);
|
||||
}
|
||||
result.push(arr);
|
||||
}
|
||||
return result;
|
||||
for (let y = 0; y < pixels.shape[1]; y++){
|
||||
pixs.r.push([])
|
||||
pixs.g.push([])
|
||||
pixs.b.push([])
|
||||
|
||||
for (let x = 0; x < pixels.shape[0]; x++){
|
||||
pixs.r[y].push(pixels.get(x, y, 0))
|
||||
pixs.g[y].push(pixels.get(x, y, 1))
|
||||
pixs.b[y].push(pixels.get(x, y, 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const convolve = require('../_nomodule/gpuUtils').convolve
|
||||
|
||||
const conPix = convolve([pixs.r, pixs.g, pixs.b], kernel)
|
||||
|
||||
for (let y = 0; y < pixels.shape[1]; y++){
|
||||
for (let x = 0; x < pixels.shape[0]; x++){
|
||||
pixels.set(x, y, 0, Math.max(0, Math.min(conPix[0][y][x], 255)))
|
||||
pixels.set(x, y, 1, Math.max(0, Math.min(conPix[1][y][x], 255)))
|
||||
pixels.set(x, y, 2, Math.max(0, Math.min(conPix[2][y][x], 255)))
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
|
||||
//Generates a 5x5 Gaussian kernel
|
||||
function kernelGenerator(sigma = 1) {
|
||||
|
||||
let kernel = [],
|
||||
sum = 0;
|
||||
|
||||
if (sigma == 0) sigma += 0.05
|
||||
|
||||
const s = 2 * Math.pow(sigma, 2);
|
||||
|
||||
for (let y = -2; y <= 2; y++) {
|
||||
kernel.push([])
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
let r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
|
||||
kernel[y + 2].push(Math.exp(-(r / s)))
|
||||
sum += kernel[y + 2][x + 2]
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < 5; x++){
|
||||
for (let y = 0; y < 5; y++){
|
||||
kernel[y][x] = (kernel[y][x] / sum)
|
||||
}
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = function Blur(options, UI) {
|
||||
|
||||
var defaults = require('./../../util/getDefaults.js')(require('./info.json'));
|
||||
options.blur = options.blur || defaults.blur;
|
||||
options.blur = parseFloat(options.blur);
|
||||
var output;
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"default": 2,
|
||||
"min": 0,
|
||||
"max": 5,
|
||||
"step": 0.25
|
||||
"step": 0.05
|
||||
}
|
||||
},
|
||||
"docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blur-module"
|
||||
|
||||
@@ -1,71 +1,50 @@
|
||||
var _ = require('lodash');
|
||||
module.exports = exports = function(pixels, constantFactor, kernelValues) {
|
||||
let kernel = kernelGenerator(constantFactor, kernelValues), oldpix = _.cloneDeep(pixels);
|
||||
kernel = flipKernel(kernel);
|
||||
|
||||
for (let i = 0; i < pixels.shape[0]; i++) {
|
||||
for (let j = 0; j < pixels.shape[1]; j++) {
|
||||
let neighboutPos = getNeighbouringPixelPositions([i, j]);
|
||||
let acc = [0.0, 0.0, 0.0, 0.0];
|
||||
for (let a = 0; a < kernel.length; a++) {
|
||||
for (let b = 0; b < kernel.length; b++) {
|
||||
acc[0] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 0) * kernel[a][b]);
|
||||
acc[1] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 1) * kernel[a][b]);
|
||||
acc[2] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 2) * kernel[a][b]);
|
||||
acc[3] += (oldpix.get(neighboutPos[a][b][0], neighboutPos[a][b][1], 3) * kernel[a][b]);
|
||||
}
|
||||
}
|
||||
acc[0] = Math.min(acc[0], 255);
|
||||
acc[1] = Math.min(acc[1], 255);
|
||||
acc[2] = Math.min(acc[2], 255);
|
||||
pixels.set(i, j, 0, acc[0]);
|
||||
pixels.set(i, j, 1, acc[1]);
|
||||
pixels.set(i, j, 2, acc[2]);
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
|
||||
|
||||
function kernelGenerator(constantFactor, kernelValues) {
|
||||
kernelValues = kernelValues.split(" ");
|
||||
for (i = 0; i < 9; i++) {
|
||||
kernelValues[i] = Number(kernelValues[i]) * constantFactor;
|
||||
}
|
||||
let k = 0;
|
||||
let arr = [];
|
||||
for (i = 0; i < 3; i++) {
|
||||
let columns = [];
|
||||
for (j = 0; j < 3; j++) {
|
||||
columns.push(kernelValues[k]);
|
||||
k += 1;
|
||||
}
|
||||
arr.push(columns);
|
||||
}
|
||||
return arr;
|
||||
module.exports = exports = function(pixels, constantFactor, kernelValues, texMode) {
|
||||
let kernel = kernelGenerator(constantFactor, kernelValues),
|
||||
pixs = {
|
||||
r: [],
|
||||
g: [],
|
||||
b: [],
|
||||
}
|
||||
|
||||
function getNeighbouringPixelPositions(pixelPosition) {
|
||||
let x = pixelPosition[0], y = pixelPosition[1], result = [];
|
||||
for (let y = 0; y < pixels.shape[1]; y++){
|
||||
pixs.r.push([])
|
||||
pixs.g.push([])
|
||||
pixs.b.push([])
|
||||
|
||||
for (let i = -1; i <= 1; i++) {
|
||||
let arr = [];
|
||||
for (let j = -1; j <= 1; j++)
|
||||
arr.push([x + i, y + j]);
|
||||
|
||||
result.push(arr);
|
||||
}
|
||||
return result;
|
||||
for (let x = 0; x < pixels.shape[0]; x++){
|
||||
pixs.r[y].push(pixels.get(x, y, 0))
|
||||
pixs.g[y].push(pixels.get(x, y, 1))
|
||||
pixs.b[y].push(pixels.get(x, y, 2))
|
||||
}
|
||||
}
|
||||
|
||||
function flipKernel(kernel) {
|
||||
let result = [];
|
||||
for (let i = kernel.length - 1; i >= 0; i--) {
|
||||
let arr = [];
|
||||
for (let j = kernel[i].length - 1; j >= 0; j--) {
|
||||
arr.push(kernel[i][j]);
|
||||
}
|
||||
result.push(arr);
|
||||
}
|
||||
return result;
|
||||
const convolve = require('../_nomodule/gpuUtils').convolve;
|
||||
const conPix = convolve([pixs.r, pixs.g, pixs.b], kernel, (pixels.shape[0] * pixels.shape[1]) < 400000 ? true : false)
|
||||
|
||||
for (let y = 0; y < pixels.shape[1]; y++){
|
||||
for (let x = 0; x < pixels.shape[0]; x++){
|
||||
pixels.set(x, y, 0, Math.max(0, Math.min(conPix[0][y][x], 255)))
|
||||
pixels.set(x, y, 1, Math.max(0, Math.min(conPix[1][y][x], 255)))
|
||||
pixels.set(x, y, 2, Math.max(0, Math.min(conPix[2][y][x], 255)))
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
function kernelGenerator(constantFactor, kernelValues) {
|
||||
kernelValues = kernelValues.split(" ");
|
||||
for (i = 0; i < 9; i++) {
|
||||
kernelValues[i] = Number(kernelValues[i]) * constantFactor;
|
||||
}
|
||||
let k = 0;
|
||||
let arr = [];
|
||||
for (y = 0; y < 3; y++) {
|
||||
arr.push([])
|
||||
for (x = 0; x < 3; x++) {
|
||||
arr[y].push(kernelValues[k]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ module.exports = function Convolution(options, UI) {
|
||||
|
||||
options.kernelValues = options.kernelValues || defaults.kernelValues;
|
||||
options.constantFactor = options.constantFactor || defaults.constantFactor;
|
||||
options.texMode = options.texMode || defaults.texMode;
|
||||
var output;
|
||||
|
||||
function draw(input, callback, progressObj) {
|
||||
@@ -14,7 +15,7 @@ module.exports = function Convolution(options, UI) {
|
||||
var step = this;
|
||||
|
||||
function extraManipulation(pixels) {
|
||||
pixels = require('./Convolution')(pixels, options.constantFactor, options.kernelValues);
|
||||
pixels = require('./Convolution')(pixels, options.constantFactor, options.kernelValues, options.texMode);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
"constantFactor":{
|
||||
"type": "float",
|
||||
"desc": "a constant factor, multiplies all the kernel values by that factor",
|
||||
"default": 0.1111,
|
||||
"placeholder": 0.1111
|
||||
"default": 0.111,
|
||||
"min": 0.001,
|
||||
"max": 2,
|
||||
"step": 0.001
|
||||
},
|
||||
|
||||
"kernelValues": {
|
||||
"type": "string",
|
||||
"desc": "nine space separated numbers representing the kernel values in left to right and top to bottom format.",
|
||||
|
||||
@@ -12,8 +12,9 @@ kernely = [
|
||||
|
||||
let pixelsToBeSupressed = [];
|
||||
|
||||
module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, hysteresis) {
|
||||
module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHysteresis) {
|
||||
let angles = [], grads = [], strongEdgePixels = [], weakEdgePixels = [];
|
||||
|
||||
for (var x = 0; x < pixels.shape[0]; x++) {
|
||||
grads.push([]);
|
||||
angles.push([]);
|
||||
@@ -31,7 +32,7 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, hystere
|
||||
}
|
||||
nonMaxSupress(pixels, grads, angles);
|
||||
doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels);
|
||||
if(hysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels);
|
||||
if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels);
|
||||
|
||||
strongEdgePixels.forEach(pixel => preserve(pixels, pixel));
|
||||
weakEdgePixels.forEach(pixel => supress(pixels, pixel));
|
||||
@@ -83,7 +84,7 @@ function sobelFilter(pixels, x, y) {
|
||||
return {
|
||||
pixel: [val, val, val, grad],
|
||||
angle: angle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function categorizeAngle(angle){
|
||||
|
||||
@@ -19,30 +19,40 @@ module.exports = function edgeDetect(options, UI) {
|
||||
|
||||
var step = this;
|
||||
|
||||
// Blur the image
|
||||
const internalSequencer = ImageSequencer({ inBrowser: false, ui: false });
|
||||
return internalSequencer.loadImage(input.src, function () {
|
||||
internalSequencer.importJSON([{ 'name': 'blur', 'options': {blur: options.blur} }]);
|
||||
return internalSequencer.run(function onCallback(internalOutput) {
|
||||
require('get-pixels')(internalOutput, function(err, blurPixels){
|
||||
if (err){
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
|
||||
}
|
||||
// Extra Manipulation function used as an enveloper for applying gaussian blur and Convolution
|
||||
function changePixel(r, g, b, a) {
|
||||
return [(r + g + b) / 3, (r + g + b) / 3, (r + g + b) / 3, a];
|
||||
}
|
||||
|
||||
function extraManipulation(pixels) {
|
||||
pixels = require('ndarray-gaussian-filter')(pixels, options.blur);
|
||||
pixels = require('./EdgeUtils')(pixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
|
||||
return pixels;
|
||||
}
|
||||
function extraManipulation(){
|
||||
return require('./EdgeUtils')(blurPixels, options.highThresholdRatio, options.lowThresholdRatio, options.hysteresis);
|
||||
}
|
||||
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
function output(image, datauri, mimetype) {
|
||||
step.output = { src: datauri, format: mimetype };
|
||||
}
|
||||
|
||||
return require('../_nomodule/PixelManipulation.js')(input, {
|
||||
output: output,
|
||||
changePixel: changePixel,
|
||||
extraManipulation: extraManipulation,
|
||||
format: input.format,
|
||||
image: options.image,
|
||||
inBrowser: options.inBrowser,
|
||||
callback: callback
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
"type": "float",
|
||||
"desc": "Amount of gaussian blur(Less blur gives more detail, typically 0-5)",
|
||||
"default": 2,
|
||||
"min": 0,
|
||||
"min": 0.05,
|
||||
"max": 5,
|
||||
"step": 0.25
|
||||
"step": 0.05
|
||||
},
|
||||
"highThresholdRatio":{
|
||||
"type": "float",
|
||||
@@ -16,7 +16,7 @@
|
||||
"default": 0.2,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.25
|
||||
"step": 0.01
|
||||
},
|
||||
"lowThresholdRatio": {
|
||||
"type": "float",
|
||||
@@ -24,7 +24,7 @@
|
||||
"default": 0.15,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.05
|
||||
"step": 0.01
|
||||
},
|
||||
"hysteresis": {
|
||||
"type": "select",
|
||||
|
||||
94
src/modules/_nomodule/gpuUtils.js
Normal file
94
src/modules/_nomodule/gpuUtils.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const GPU = require('gpu.js').GPU
|
||||
|
||||
/**
|
||||
* @method convolve
|
||||
* @param {Float32Array|Unit8Array|Float64Array} arrays Array of matrices all of the same size.
|
||||
* @param {Float32Array|Unit8Array|Float64Array} kernel kernelto be convolved on all the matrices.
|
||||
* @param {Boolean} pipeMode Whether to save the output to a texture.
|
||||
* @param {Boolean} normalize Whether to normailize the output by dividing it by the total value of the kernel.
|
||||
* @returns {Float32Array}
|
||||
*/
|
||||
const convolve = (arrays, kernel, options = {}) => {
|
||||
const pipeMode = options.pipeMode || false,
|
||||
mode = options.mode || 'gpu'
|
||||
|
||||
const gpu = new GPU(mode != 'gpu' ? {mode} : {})
|
||||
|
||||
const arrayX = arrays[0][0].length,
|
||||
arrayY = arrays[0].length,
|
||||
kernelX = kernel[0].length,
|
||||
kernelY = kernel.length,
|
||||
paddingX = Math.floor(kernelX / 2),
|
||||
paddingY = Math.floor(kernelY / 2);
|
||||
|
||||
const matConvFunc = `function (array, kernel) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < ${kernelX}; i++){
|
||||
for (let j = 0; j < ${kernelY}; j++){
|
||||
sum += kernel[j][i] * array[this.thread.y + j][this.thread.x + i];
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}`;
|
||||
|
||||
const padIt = (array) => {
|
||||
let out = []
|
||||
|
||||
for (var y = 0; y < array.length + paddingY * 2; y++){
|
||||
out.push([])
|
||||
for (var x = 0; x < array[0].length + paddingX * 2; x++){
|
||||
const positionX = Math.min(Math.max(x - paddingX, 0), array[0].length - 1);
|
||||
const positionY = Math.min(Math.max(y - paddingY, 0), array.length - 1);
|
||||
|
||||
out[y].push(array[positionY][positionX])
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
const convolveKernel = gpu.createKernel(matConvFunc, {
|
||||
output: [arrayX, arrayY],
|
||||
pipeline: pipeMode
|
||||
})
|
||||
|
||||
let outs = [];
|
||||
for (var i = 0; i < arrays.length; i++){
|
||||
const paddedArray = padIt(arrays[i])
|
||||
|
||||
const outArr = convolveKernel(paddedArray, kernel)
|
||||
|
||||
if (pipeMode) outs.push(outArr.toArray())
|
||||
else outs.push(outArr)
|
||||
}
|
||||
|
||||
return outs
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Float32Array|'Object'} outputSize Output size of the compute function.
|
||||
* @param {Function} computeFunc The compute function. Cannot be an arrow function.
|
||||
* @param {'Object'} constants Constants to be passed to the function. Can be accessed inside the compute function using `this.constants`.
|
||||
* @param {Boolean} pipeMode Whether to save output array to a texture.
|
||||
* @returns {Float32Array}
|
||||
*/
|
||||
const compute = (outputSize, computeFunc, constants, pipeMode) => {
|
||||
computeFunc = computeFunc.toString()
|
||||
|
||||
const compute = gpu.createKernel(computeFunc, {
|
||||
output: outputSize,
|
||||
constants,
|
||||
pipeline: pipeMode
|
||||
})
|
||||
|
||||
compute.build()
|
||||
|
||||
if (pipeMode) return compute().toArray()
|
||||
else return compute()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
convolve,
|
||||
compute
|
||||
}
|
||||
96
test/core/util/gpuUtils.js
Normal file
96
test/core/util/gpuUtils.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const test = require('tape');
|
||||
const { convolve, compute } = require('../../../src/modules/_nomodule/gpuUtils')
|
||||
|
||||
test('convolve works with 1x1 array', t => {
|
||||
const array = [[1]],
|
||||
kernel = [
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]
|
||||
],
|
||||
expectedOut = [
|
||||
[9]
|
||||
]
|
||||
|
||||
const out = convolve([array], kernel);
|
||||
|
||||
t.equal(out.length, 1, 'convolve returns a single output array')
|
||||
t.equal(out[0][0].length, 1, 'ouput array width is correct')
|
||||
t.equal(out[0].length, 1, 'ouput array height is correct')
|
||||
t.deepEqual(out[0], expectedOut, 'convolve outputs correct array')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('convolve works with 3x4 array', t => {
|
||||
const array = [
|
||||
[1, 2, 3],
|
||||
[1, 2, 4],
|
||||
[1, 3, 3],
|
||||
[1, 2, 3]
|
||||
],
|
||||
kernel = [
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]
|
||||
],
|
||||
expectedOut = [
|
||||
[12, 19, 26],
|
||||
[13, 20, 27],
|
||||
[13, 20, 27],
|
||||
[13, 19, 25]
|
||||
]
|
||||
|
||||
const out = convolve([array], kernel);
|
||||
|
||||
t.equal(out.length, 1, 'convolve returns a single output array')
|
||||
t.equal(out[0][0].length, 3, 'ouput array width is correct')
|
||||
t.equal(out[0].length, 4, 'ouput array height is correct')
|
||||
t.deepEqual(out[0], expectedOut, 'convolve outputs correct array')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('convolve works with multiple 3x4 arrays', t => {
|
||||
const array1 = [
|
||||
[1, 2, 3],
|
||||
[1, 2, 4],
|
||||
[1, 3, 3],
|
||||
[1, 2, 3]
|
||||
],
|
||||
array2 = [
|
||||
[1, 2, 4],
|
||||
[2, 2, 1],
|
||||
[1, 0, 0],
|
||||
[2, 3, 1]
|
||||
],
|
||||
kernel = [
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]
|
||||
],
|
||||
expectedOut1 = [
|
||||
[12, 19, 26],
|
||||
[13, 20, 27],
|
||||
[13, 20, 27],
|
||||
[13, 19, 25]
|
||||
],
|
||||
expectedOut2 = [
|
||||
[14, 19, 24],
|
||||
[12, 13, 14],
|
||||
[15, 12, 9],
|
||||
[16, 13, 10]
|
||||
]
|
||||
|
||||
const out = convolve([array1, array2], kernel);
|
||||
|
||||
t.equal(out.length, 2, 'convolve returns 2 output array')
|
||||
|
||||
t.equal(out[0][0].length, 3, 'ouput array1 width is correct')
|
||||
t.equal(out[0].length, 4, 'ouput array1 height is correct')
|
||||
|
||||
t.equal(out[1][0].length, 3, 'ouput array2 width is correct')
|
||||
t.equal(out[1].length, 4, 'ouput array2 height is correct')
|
||||
|
||||
t.deepEqual(out[0], expectedOut1, 'convolve outputs correct array1')
|
||||
t.deepEqual(out[1], expectedOut2, 'convolve outputs correct array2')
|
||||
t.end()
|
||||
})
|
||||
Reference in New Issue
Block a user