Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GCI] Standardised Edge Detect module code comments #1346

Merged
merged 10 commits into from
Jan 1, 2020
90 changes: 69 additions & 21 deletions src/modules/EdgeDetect/EdgeUtils.js
Original file line number Diff line number Diff line change
@@ -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],
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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;
Expand All @@ -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));

Expand All @@ -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]);
Expand Down Expand Up @@ -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
SidharthBansal marked this conversation as resolved.
Show resolved Hide resolved
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++) {
Expand All @@ -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],
Expand Down
12 changes: 7 additions & 5 deletions src/modules/EdgeDetect/Module.js
Original file line number Diff line number Diff line change
@@ -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) {

Expand All @@ -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];
}
Expand Down Expand Up @@ -64,4 +66,4 @@ module.exports = function edgeDetect(options, UI) {
output: output,
UI: UI
};
};
};
2 changes: 1 addition & 1 deletion src/modules/EdgeDetect/info.json
Original file line number Diff line number Diff line change
@@ -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.<a href='https://en.wikipedia.org/wiki/Canny_edge_detector'> Read more. </a>",
"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",
Expand Down