From 5392ce8540337291339626d68f99c0672b9cad1e Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sat, 15 Apr 2023 16:21:08 -0300 Subject: [PATCH 1/7] Add Laplacian edge detection using builder pattern, customizable masks, and documentation --- src/filter/mod.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index eacdc29a..ef8048e6 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -564,6 +564,51 @@ where } } +// Laplacian filter with basic mask and diagonal adjustment mask + +/// This implementation of the Laplacian algorithm provides a builder pattern for easy customization. +/// The default Laplacian mask can be used, or an optional diagonal adjustment mask can be selected. +/// The chosen mask is convolved with the input image to produce an output image with the edges highlighted. + +/// A builder for applying Laplacian filter to a grayscale image. +pub struct Laplacian<'a> { + image: &'a GrayImage, + mask: [i32; 9], +} + +impl<'a> Laplacian<'a> { + /// Creates a new Laplacian with the default mask. + /// + /// # Arguments + /// * image - A reference to the grayscale image to which the Laplacian filter will be applied. + /// + /// # Returns + /// A new Laplacian instance. + pub fn new(image: &'a GrayImage) -> Self { + Self { + image, + mask: [1, 1, 1, 1, -8, 1, 1, 1, 1], + } + } + + /// Sets the mask to the diagonal adjustment mask. + /// + /// # Returns + /// The modified Laplacian instance. + pub fn diagonal_adjustment(mut self) -> Self { + self.mask = [0, 1, 0, 1, -4, 1, 0, 1, 0]; + self + } + + /// Applies the Laplacian filter with the selected mask to the image. + /// + /// # Returns + /// An ImageBuffer containing the filtered image. + pub fn apply(self) -> ImageBuffer, Vec> { + filter3x3(self.image, &self.mask) + } +} + #[cfg(test)] mod tests { use super::*; From be81dc55c40cb555a46b2163bfad7c702846534a Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sat, 15 Apr 2023 17:30:01 -0300 Subject: [PATCH 2/7] Add PhantomData in Laplacian builder --- src/filter/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index ef8048e6..35114126 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -574,6 +574,7 @@ where pub struct Laplacian<'a> { image: &'a GrayImage, mask: [i32; 9], + _marker: std::marker::PhantomData<&'a ()>, } impl<'a> Laplacian<'a> { @@ -588,6 +589,7 @@ impl<'a> Laplacian<'a> { Self { image, mask: [1, 1, 1, 1, -8, 1, 1, 1, 1], + _marker: std::marker::PhantomData, } } From fb6dc5c26f55cab396a52de3169ed46db9613da1 Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sat, 15 Apr 2023 18:50:18 -0300 Subject: [PATCH 3/7] Revert "Add PhantomData in Laplacian builder" This reverts commit be81dc55c40cb555a46b2163bfad7c702846534a. --- src/filter/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 35114126..ef8048e6 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -574,7 +574,6 @@ where pub struct Laplacian<'a> { image: &'a GrayImage, mask: [i32; 9], - _marker: std::marker::PhantomData<&'a ()>, } impl<'a> Laplacian<'a> { @@ -589,7 +588,6 @@ impl<'a> Laplacian<'a> { Self { image, mask: [1, 1, 1, 1, -8, 1, 1, 1, 1], - _marker: std::marker::PhantomData, } } From 7506295520285b7f76bd3f55bf0152f2348f9a4c Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sat, 15 Apr 2023 18:51:43 -0300 Subject: [PATCH 4/7] Add LaplacianEdgeDetector builder --- src/edges.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/edges.rs b/src/edges.rs index 65db00c0..36ccf69e 100644 --- a/src/edges.rs +++ b/src/edges.rs @@ -1,7 +1,8 @@ //! Functions for detecting edges in images. +use crate::contrast; use crate::definitions::{HasBlack, HasWhite}; -use crate::filter::gaussian_blur_f32; +use crate::filter::{gaussian_blur_f32, Laplacian}; use crate::gradients::{horizontal_sobel, vertical_sobel}; use image::{GenericImageView, GrayImage, ImageBuffer, Luma}; use std::f32; @@ -157,6 +158,80 @@ fn hysteresis( out } +/// A builder for the Laplacian edge detection algorithm. +/// This LaplacianEdgeDetector builder provides a flexible and convenient way to perform edge detection +/// on grayscale images using the Laplacian filter. It supports optional parameters such as custom +/// Gaussian blur sigma, diagonal adjustment for the Laplacian kernel, and a custom threshold for edge +/// detection. The builder pattern makes it easy to configure and apply the edge detection algorithm +/// with a fluent and readable syntax. +pub struct LaplacianEdgeDetector<'a> { + image: &'a GrayImage, + sigma: Option, + diagonal_adjustment: bool, + threshold: Option, +} + +impl<'a> LaplacianEdgeDetector<'a> { + /// Creates a new LaplacianEdgeDetector with the specified grayscale image. + /// + /// # Arguments + /// * image - A reference to the grayscale image on which edge detection will be performed. + pub fn new(image: &'a GrayImage) -> Self { + Self { + image, + sigma: None, + diagonal_adjustment: false, + threshold: None, + } + } + + /// Sets the standard deviation (sigma) of the Gaussian blur applied before edge detection. + /// + /// # Arguments + /// * sigma - The standard deviation of the Gaussian blur. + pub fn sigma(mut self, sigma: f32) -> Self { + self.sigma = Some(sigma); + self + } + + /// Enables diagonal adjustment for the Laplacian kernel. + pub fn diagonal_adjustment(mut self) -> Self { + self.diagonal_adjustment = true; + self + } + + /// Sets a custom threshold for edge detection. + /// + /// # Arguments + /// * threshold - The custom threshold value. + pub fn threshold(mut self, threshold: u8) -> Self { + self.threshold = Some(threshold); + self + } + + /// Applies the Laplacian edge detection algorithm and returns the resulting image. + /// + /// # Returns + /// * An ImageBuffer containing the result of the edge detection. + pub fn apply(self) -> ImageBuffer, Vec> { + let sigma = self.sigma.unwrap_or(1.4); + let blurred_img = gaussian_blur_f32(self.image, sigma); + + let laplacian_img = if self.diagonal_adjustment { + Laplacian::new(&blurred_img).diagonal_adjustment().apply() + } else { + Laplacian::new(&blurred_img).apply() + }; + + if let Some(threshold) = self.threshold { + contrast::threshold(&laplacian_img, threshold) + } else { + let otsu_threshold = contrast::otsu_level(&laplacian_img); + contrast::threshold(&laplacian_img, otsu_threshold) + } + } +} + #[cfg(test)] mod tests { use super::canny; From 0f54b7b077a8e4c7f1320dd3e3795a0d2be6b25a Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sun, 28 Apr 2024 18:33:25 -0300 Subject: [PATCH 5/7] Replacing Laplace constructor with a Laplace filter function --- src/filter/mod.rs | 45 ++------------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index ef8048e6..a466260d 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -564,49 +564,8 @@ where } } -// Laplacian filter with basic mask and diagonal adjustment mask - -/// This implementation of the Laplacian algorithm provides a builder pattern for easy customization. -/// The default Laplacian mask can be used, or an optional diagonal adjustment mask can be selected. -/// The chosen mask is convolved with the input image to produce an output image with the edges highlighted. - -/// A builder for applying Laplacian filter to a grayscale image. -pub struct Laplacian<'a> { - image: &'a GrayImage, - mask: [i32; 9], -} - -impl<'a> Laplacian<'a> { - /// Creates a new Laplacian with the default mask. - /// - /// # Arguments - /// * image - A reference to the grayscale image to which the Laplacian filter will be applied. - /// - /// # Returns - /// A new Laplacian instance. - pub fn new(image: &'a GrayImage) -> Self { - Self { - image, - mask: [1, 1, 1, 1, -8, 1, 1, 1, 1], - } - } - - /// Sets the mask to the diagonal adjustment mask. - /// - /// # Returns - /// The modified Laplacian instance. - pub fn diagonal_adjustment(mut self) -> Self { - self.mask = [0, 1, 0, 1, -4, 1, 0, 1, 0]; - self - } - - /// Applies the Laplacian filter with the selected mask to the image. - /// - /// # Returns - /// An ImageBuffer containing the filtered image. - pub fn apply(self) -> ImageBuffer, Vec> { - filter3x3(self.image, &self.mask) - } +fn laplacian_filter(image: &GrayImage) -> ImageBuffer, Vec> { + filter3x3(image, &[1, 1, 1, 1, -8, 1, 1, 1, 1]) } #[cfg(test)] From 0a752d6f8dd3bfa9f520c6b2d0e1f915d9de23c0 Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sun, 28 Apr 2024 18:35:14 -0300 Subject: [PATCH 6/7] Adding comment in the laplacian filter function --- src/filter/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index a466260d..fddddf1c 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -564,7 +564,9 @@ where } } -fn laplacian_filter(image: &GrayImage) -> ImageBuffer, Vec> { +/// Apply a Laplacian filter to an image. +#[must_use = "the function does not modify the original image"] +pub fn laplacian_filter(image: &GrayImage) -> ImageBuffer, Vec> { filter3x3(image, &[1, 1, 1, 1, -8, 1, 1, 1, 1]) } From 6259ad14729d32eea24616271822f8042d265ae3 Mon Sep 17 00:00:00 2001 From: Gustavo Michels de Camargo Date: Sun, 28 Apr 2024 18:46:15 -0300 Subject: [PATCH 7/7] Replacing the Laplacian Edge Detection constructor with a function named laplacian_edge_detector --- src/edges.rs | 89 ++++++++++++---------------------------------------- 1 file changed, 20 insertions(+), 69 deletions(-) diff --git a/src/edges.rs b/src/edges.rs index 36ccf69e..ceb3ca10 100644 --- a/src/edges.rs +++ b/src/edges.rs @@ -2,7 +2,7 @@ use crate::contrast; use crate::definitions::{HasBlack, HasWhite}; -use crate::filter::{gaussian_blur_f32, Laplacian}; +use crate::filter::{gaussian_blur_f32, laplacian_filter}; use crate::gradients::{horizontal_sobel, vertical_sobel}; use image::{GenericImageView, GrayImage, ImageBuffer, Luma}; use std::f32; @@ -158,78 +158,29 @@ fn hysteresis( out } -/// A builder for the Laplacian edge detection algorithm. -/// This LaplacianEdgeDetector builder provides a flexible and convenient way to perform edge detection -/// on grayscale images using the Laplacian filter. It supports optional parameters such as custom -/// Gaussian blur sigma, diagonal adjustment for the Laplacian kernel, and a custom threshold for edge -/// detection. The builder pattern makes it easy to configure and apply the edge detection algorithm -/// with a fluent and readable syntax. -pub struct LaplacianEdgeDetector<'a> { - image: &'a GrayImage, - sigma: Option, - diagonal_adjustment: bool, - threshold: Option, -} - -impl<'a> LaplacianEdgeDetector<'a> { - /// Creates a new LaplacianEdgeDetector with the specified grayscale image. - /// - /// # Arguments - /// * image - A reference to the grayscale image on which edge detection will be performed. - pub fn new(image: &'a GrayImage) -> Self { - Self { - image, - sigma: None, - diagonal_adjustment: false, - threshold: None, - } - } - - /// Sets the standard deviation (sigma) of the Gaussian blur applied before edge detection. - /// - /// # Arguments - /// * sigma - The standard deviation of the Gaussian blur. - pub fn sigma(mut self, sigma: f32) -> Self { - self.sigma = Some(sigma); - self - } - - /// Enables diagonal adjustment for the Laplacian kernel. - pub fn diagonal_adjustment(mut self) -> Self { - self.diagonal_adjustment = true; - self - } +/// Detects edges in a grayscale image using a Laplacian edge detector with Gaussian smoothing and Otsu thresholding. +/// +/// # Arguments +/// +/// * `image` - The grayscale image to be processed. +/// +/// # Return value +/// +/// A binary grayscale image where pixels belonging to edges are white and pixels not belonging to edges are black. +/// +/// # Details +/// +/// The Laplacian edge detector is applied to the image smoothed with a Gaussian filter with standard deviation `sigma`. The threshold for binarization is calculated using Otsu's method, which maximizes the variance between pixel intensity classes. +pub fn laplacian_edge_detector(image: &GrayImage) -> ImageBuffer, Vec> { + let signma = 1.4; - /// Sets a custom threshold for edge detection. - /// - /// # Arguments - /// * threshold - The custom threshold value. - pub fn threshold(mut self, threshold: u8) -> Self { - self.threshold = Some(threshold); - self - } + let blurred_img = gaussian_blur_f32(image, signma); - /// Applies the Laplacian edge detection algorithm and returns the resulting image. - /// - /// # Returns - /// * An ImageBuffer containing the result of the edge detection. - pub fn apply(self) -> ImageBuffer, Vec> { - let sigma = self.sigma.unwrap_or(1.4); - let blurred_img = gaussian_blur_f32(self.image, sigma); + let laplacian_img = laplacian_filter(&blurred_img); - let laplacian_img = if self.diagonal_adjustment { - Laplacian::new(&blurred_img).diagonal_adjustment().apply() - } else { - Laplacian::new(&blurred_img).apply() - }; + let otsu_threshold = contrast::otsu_level(&laplacian_img); - if let Some(threshold) = self.threshold { - contrast::threshold(&laplacian_img, threshold) - } else { - let otsu_threshold = contrast::otsu_level(&laplacian_img); - contrast::threshold(&laplacian_img, otsu_threshold) - } - } + contrast::threshold(&laplacian_img, otsu_threshold) } #[cfg(test)]