diff --git a/src/modules/EdgeDetect/EdgeUtils.js b/src/modules/EdgeDetect/EdgeUtils.js index 1a6965431a..6bc46720be 100644 --- a/src/modules/EdgeDetect/EdgeUtils.js +++ b/src/modules/EdgeDetect/EdgeUtils.js @@ -1,6 +1,8 @@ -// Define kernels for the sobel filter +// Read More: https://en.wikipedia.org/wiki/Canny_edge_detector const pixelSetter = require('../../util/pixelSetter.js'); + +// Define kernels for the sobel filter. const kernelx = [ [-1, 0, 1], [-2, 0, 2], @@ -21,7 +23,7 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst grads.push([]); angles.push([]); for (var y = 0; y < pixels.shape[1]; y++) { - var result = sobelFilter( + var result = sobelFilter( // Convolves the sobel filter on every pixel pixels, x, y @@ -32,28 +34,47 @@ module.exports = function(pixels, highThresholdRatio, lowThresholdRatio, useHyst angles.slice(-1)[0].push(result.angle); } } - nonMaxSupress(pixels, grads, angles); - doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels); - if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels); + nonMaxSupress(pixels, grads, angles); // Non Maximum Suppression: Filter fine edges. + doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels); // Double Threshold: Categorizes edges into strong and weak edges based on two thresholds. + if(useHysteresis.toLowerCase() == 'true') hysteresis(strongEdgePixels, weakEdgePixels); // Optional Hysteresis (very slow) to minimize edges generated due to noise. - strongEdgePixels.forEach(pixel => preserve(pixels, pixel)); - weakEdgePixels.forEach(pixel => supress(pixels, pixel)); - pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel)); + strongEdgePixels.forEach(pixel => preserve(pixels, pixel)); // Makes the strong edges White. + weakEdgePixels.forEach(pixel => supress(pixels, pixel)); // Makes the weak edges black(bg color) after filtering. + pixelsToBeSupressed.forEach(pixel => supress(pixels, pixel)); // Makes the rest of the image black. return pixels; }; +/** + * @method supress + * @description Supresses (fills with background color) the specified (non-edge)pixel. + * @param {Object} pixels ndarry of pixels + * @param {Float32Array} pixel Pixel coordinates + * @returns {Null} + */ function supress(pixels, pixel) { pixelSetter(pixel[0], pixel[1], [0, 0, 0, 255], pixels); - } +/** + * @method preserve + * @description Preserve the specified pixel(of an edge). + * @param {Object} pixels ndarray of pixels + * @param {*} pixel Pixel coordinates + * @returns {Null} + */ function preserve(pixels, pixel) { pixelSetter(pixel[0], pixel[1], [255, 255, 255, 255], pixels); - } -// sobelFilter function that convolves sobel kernel over every pixel +/** + * @method sobelFiler + * @description Runs the sobel filter on the specified and neighbouring pixels. + * @param {Object} pixels ndarray of pixels + * @param {Number} x x-coordinate of the pixel + * @param {Number} y y-coordinate of the pixel + * @returns {Object} Object containing the gradient and angle. + */ function sobelFilter(pixels, x, y) { let val = pixels.get(x, y, 0), gradX = 0.0, @@ -65,8 +86,8 @@ function sobelFilter(pixels, x, y) { let xn = x + a - 1, yn = y + b - 1; - if (isOutOfBounds(pixels, xn, yn)) { - gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b]; + if (isOutOfBounds(pixels, xn, yn)) { // Fallback for coordinates which lie outside the image. + gradX += pixels.get(xn + 1, yn + 1, 0) * kernelx[a][b]; // Fallback to nearest pixel gradY += pixels.get(xn + 1, yn + 1, 0) * kernely[a][b]; } else { @@ -84,6 +105,12 @@ function sobelFilter(pixels, x, y) { }; } +/** + * @method categorizeAngle + * @description Categorizes the given angle into 4 catagories according to the Category Map given below. + * @param {Number} angle Angle in degrees + * @returns {Number} Category number of the given angle + */ function categorizeAngle(angle){ if ((angle >= -22.5 && angle <= 22.5) || (angle < -157.5 && angle >= -180)) return 1; else if ((angle >= 22.5 && angle <= 67.5) || (angle < -112.5 && angle >= -157.5)) return 2; @@ -98,17 +125,25 @@ function categorizeAngle(angle){ */ } +/** + * @method isOutOfBounds + * @description Checks whether the given coordinates lie outside the bounds of the image. Used for error handling in convolution. + * @param {Object} pixels ndarray of pixels + * @param {*} x x-coordinate of the pixel + * @param {*} y y-coordinate of the pixel + * @returns {Boolean} True if the given coordinates are out of bounds. + */ function isOutOfBounds(pixels, x, y){ return ((x < 0) || (y < 0) || (x >= pixels.shape[0]) || (y >= pixels.shape[1])); } -const removeElem = (arr = [], elem) => { +const removeElem = (arr = [], elem) => { // Removes the specified element from the given array. return arr = arr.filter((arrelem) => { return arrelem !== elem; }); }; -// Non Maximum Supression without interpolation +// Non Maximum Supression without interpolation. function nonMaxSupress(pixels, grads, angles) { angles = angles.map((arr) => arr.map(convertToDegrees)); @@ -118,7 +153,7 @@ function nonMaxSupress(pixels, grads, angles) { let angleCategory = categorizeAngle(angles[x][y]); if (!isOutOfBounds(pixels, x - 1, y - 1) && !isOutOfBounds(pixels, x + 1, y + 1)){ - switch (angleCategory){ + switch (angleCategory){ // Non maximum suppression according to angle category case 1: if (!((grads[x][y] >= grads[x][y + 1]) && (grads[x][y] >= grads[x][y - 1]))) { pixelsToBeSupressed.push([x, y]); @@ -147,17 +182,24 @@ function nonMaxSupress(pixels, grads, angles) { } } } -// Converts radians to degrees + + +/** + * @method convertToDegrees + * @description Converts the given angle(in radians) to degrees. + * @param {Number} radians Angle in radians + * @returns {Number} Angle in degrees + */ var convertToDegrees = radians => (radians * 180) / Math.PI; -// Finds the max value in a 2d array like grads +// Finds the max value in a 2d array like grads. var findMaxInMatrix = arr => Math.max(...arr.map(el => el.map(val => val ? val : 0)).map(el => Math.max(...el))); -// Applies the double threshold to the image +// Applies the double threshold to the image. function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, strongEdgePixels, weakEdgePixels) { - const highThreshold = findMaxInMatrix(grads) * highThresholdRatio, - lowThreshold = highThreshold * lowThresholdRatio; + const highThreshold = findMaxInMatrix(grads) * highThresholdRatio, // High Threshold relative to the strongest edge + lowThreshold = highThreshold * lowThresholdRatio; // Low threshold relative to high threshold for (let x = 0; x < pixels.shape[0]; x++) { for (let y = 0; y < pixels.shape[1]; y++) { @@ -178,6 +220,12 @@ function doubleThreshold(pixels, highThresholdRatio, lowThresholdRatio, grads, s } } +/** + * @method hysteresis + * @description Filters weak edge pixels that are not connected to a strong edge pixel. + * @param {Float32array} strongEdgePixels 2D array of strong edge pixel coordinates + * @param {*} weakEdgePixels 2D array of weak edge pixel coordinated + */ function hysteresis(strongEdgePixels, weakEdgePixels){ strongEdgePixels.forEach(pixel => { let x = pixel[0], diff --git a/src/modules/EdgeDetect/Module.js b/src/modules/EdgeDetect/Module.js index 404f688ef2..a90f676495 100644 --- a/src/modules/EdgeDetect/Module.js +++ b/src/modules/EdgeDetect/Module.js @@ -1,5 +1,7 @@ -/* +/** * Detect Edges in an Image +* Uses Canny method for the same +* Read more: https://en.wikipedia.org/wiki/Canny_edge_detector */ module.exports = function edgeDetect(options, UI) { @@ -19,17 +21,17 @@ module.exports = function edgeDetect(options, UI) { var step = this; - // Blur the image + // Blur the image. const internalSequencer = ImageSequencer({ inBrowser: false, ui: false }); return internalSequencer.loadImage(input.src, function() { - internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]); + internalSequencer.importJSON([{ 'name': 'blur', 'options': { blur: options.blur } }]); // Blurs the image before detecting edges to reduce noise. 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 + // 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]; } @@ -64,4 +66,4 @@ module.exports = function edgeDetect(options, UI) { output: output, UI: UI }; -}; +}; \ No newline at end of file diff --git a/src/modules/EdgeDetect/info.json b/src/modules/EdgeDetect/info.json index eb89263329..0678645c76 100644 --- a/src/modules/EdgeDetect/info.json +++ b/src/modules/EdgeDetect/info.json @@ -1,6 +1,6 @@ { "name": "edge-detect", - "description": "This module detects edges using the Canny method, which first Gaussian blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. Read more. ", + "description": "Edge Detect module detects edges using the Canny method, which first blurs the image to reduce noise (amount of blur configurable in settings as `options.blur`), then applies a number of steps to highlight edges, resulting in a greyscale image where the brighter the pixel, the stronger the detected edge. [Read more](https://en.wikipedia.org/wiki/Canny_edge_detector)", "inputs": { "blur": { "type": "float",