Skip to content

Commit

Permalink
[GCI] Standardised Edge Detect module code comments (#1346)
Browse files Browse the repository at this point in the history
* standardise comments

* more fixes

* fix

* Apply suggestions from code review

Co-authored-by: Jeffrey Warren <jeff@unterbahn.com>
  • Loading branch information
harshkhandeparkar and jywarren committed Jan 1, 2020
1 parent 1af9655 commit 51ce1c5
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 27 deletions.
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
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

0 comments on commit 51ce1c5

Please sign in to comment.