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

[FEAT] Add dilate and erode operations to Tensor #1136

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

BritishWerewolf
Copy link
Contributor

@BritishWerewolf BritishWerewolf commented Jan 4, 2025

This PR is inspired by the functionality of OpenCV.

With this, we can create a kernel that is operated over each element of the data array and performs some calculation.
For this PR, we only have dilate and erode.

I decided to set these functions to async, despite that making the chaining a bit clunkier, since these functions are often used together.


Explanation

kernel

This is an array of 0s and 1s that will be applied to each element of the data array, 1 by 1.

There are multiple shapes supported in this PR, they are: RECT, CROSS, ELLIPSE.
Consider a 5x5 kernel for each shape:

  • RECT:
    1 1 1 1 1
    1 1 1 1 1
    1 1 1 1 1
    1 1 1 1 1
    1 1 1 1 1
    
  • CROSS:
    0 0 1 0 0
    0 0 1 0 0
    1 1 1 1 1
    0 0 1 0 0 
    0 0 1 0 0 
    
  • ELLIPSE:
    0 0 1 0 0
    0 1 1 1 0
    1 1 1 1 1
    0 1 1 1 0 
    0 0 1 0 0 
    

The element we are checking will be in the centre, and then it will apply this kernel to all those values around it.
Any value of 1 will be included and 0 will be discarded.

dilate

Once we have a list of values for that element, we will apply a Math.max to return the biggest value of these.

Consider this Tensor.

 1  2  3  4  5
 6  7  8  9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25

If we were to apply CROSS with a kernel size of 3x3, then the first iteration would return the values of [1, 2, 6]. This is because the top and left side of the kernel are off the edge.

When the loop gets to the value of 13, the extracted values would be [8, 12, 13, 14, 18].

Finally, the value in each instance will be replaced with the maximum value.
Again, in the case of the first element, that would be 6 and for the centre element, that would be replaced with 18.

erode

This works in the exact same way as dilate, however it will apply the Math.min to the elements instead.

Comment on lines +902 to +909
// Only include values where the kernel has a value
// of 1.
// Rather than multiply against this value, we use
// the if check to reduce the size of the array.
const kernelValue = kernel[kernelRowOffset + paddingSize.height][kernelColOffset + paddingSize.width];
if (kernelValue === 1) {
kernelValues.push(data[neighborIndex] * kernelValue);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I was not using the if statement, and utilising the fact that a kernel contains either 0 or 1, however that meant that kernelValues array would be bigger than it needs to be.

Also, erode wouldn't work properly because 0 is always the minimum value.

Comment on lines +1769 to +1789
function validateKernel(kernelSize) {
let kernel;
if (typeof kernelSize === 'object' && 'width' in kernelSize && 'height' in kernelSize) {
// This is a Size object, so no conversion required.
kernel = kernelSize;
} else if (typeof kernelSize === 'number' && Number.isInteger(kernelSize)) {
// A single whole number is assumed as the width and height.
kernel = { width: kernelSize, height: kernelSize };
} else if (Array.isArray(kernelSize) && kernelSize.length === 2 && kernelSize.every(Number.isInteger)) {
// An array of two values is assumed as width then height.
kernel = { width: kernelSize[0], height: kernelSize[1] };
} else {
throw new Error("Invalid kernel size.");
}

if (kernel.width % 2 === 0 || kernel.height % 2 === 0) {
throw new Error("Kernel size must be odd");
}

return kernel;
}
Copy link
Contributor Author

@BritishWerewolf BritishWerewolf Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of this function is to help users with passing in kernel values.

To get a 3x3 kernel to the erode function we can do this:

  • tensor.erode(3)
  • tensor.erode([3, 3])
  • tensor.erode({ width: 3, height: 3 })

Most likely we want a symmetrical kernel, and it's easier to just pass in a single number.

@BritishWerewolf BritishWerewolf marked this pull request as ready for review January 4, 2025 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant