Skip to content

Commit

Permalink
Introduce _parallel variants of filter functions
Browse files Browse the repository at this point in the history
This refactors the previous commit by keeping the original versions of the
filter functions as-is and adding the _parallel variants only if the rayon
feature is enabled. There is some code duplication here now subject to
further refactoring.
  • Loading branch information
sunsided committed May 19, 2024
1 parent c42cf58 commit e3efaa4
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 10 deletions.
65 changes: 60 additions & 5 deletions src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ impl<'a, K: Num + Copy + 'a> Kernel<'a, K> {

/// Returns 2d correlation of an image. Intermediate calculations are performed
/// at type K, and the results converted to pixel Q via f. Pads by continuity.
#[cfg(not(feature = "rayon"))]
pub fn filter<P, F, Q>(&self, image: &Image<P>, mut f: F) -> Image<Q>
where
P: Pixel,
Expand Down Expand Up @@ -282,14 +281,14 @@ impl<'a, K: Num + Copy + 'a> Kernel<'a, K> {
/// Returns 2d correlation of an image. Intermediate calculations are performed
/// at type K, and the results converted to pixel Q via f. Pads by continuity.
#[cfg(feature = "rayon")]
pub fn filter<P, F, Q>(&self, image: &Image<P>, f: F) -> Image<Q>
pub fn filter_parallel<P, F, Q>(&self, image: &Image<P>, f: F) -> Image<Q>
where
P: Pixel + Sync,
<P as Pixel>::Subpixel: Into<K> + Send + Sync,
Q: Pixel + Send + Sync,
<Q as Pixel>::Subpixel: Send,
K: Sync,
F: Fn(&mut Q::Subpixel, K) + Send + Sync, // TODO: Had to lift FnMut to Fn
F: Fn(&mut Q::Subpixel, K) + Send + Sync,
{
let (width, height) = image.dimensions();
let num_channels = P::CHANNEL_COUNT as usize;
Expand Down Expand Up @@ -406,6 +405,21 @@ where
/// performed at type K, and the results clamped to subpixel type S. Pads by continuity.
#[must_use = "the function does not modify the original image"]
pub fn filter3x3<P, K, S>(image: &Image<P>, kernel: &[K]) -> Image<ChannelMap<P, S>>
where
P::Subpixel: Into<K>,
S: Clamp<K> + Primitive,
P: WithChannel<S>,
K: Num + Copy,
{
let kernel = Kernel::new(kernel, 3, 3);
kernel.filter(image, |channel, acc| *channel = S::clamp(acc))
}

/// Returns 2d correlation of an image with a 3x3 row-major kernel. Intermediate calculations are
/// performed at type K, and the results clamped to subpixel type S. Pads by continuity.
#[must_use = "the function does not modify the original image"]
#[cfg(feature = "rayon")]
pub fn filter3x3_parallel<P, K, S>(image: &Image<P>, kernel: &[K]) -> Image<ChannelMap<P, S>>
where
P::Subpixel: Into<K> + Send + Sync,
S: Clamp<K> + Primitive + Send + Sync,
Expand All @@ -414,7 +428,7 @@ where
K: Num + Copy + Send + Sync,
{
let kernel = Kernel::new(kernel, 3, 3);
kernel.filter(image, |channel, acc| *channel = S::clamp(acc))
kernel.filter_parallel(image, |channel, acc| *channel = S::clamp(acc))
}

/// Returns horizontal correlations between an image and a 1d kernel.
Expand Down Expand Up @@ -647,6 +661,21 @@ pub fn laplacian_filter(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &kernel)
}

/// Calculates the Laplacian of an image.
///
/// The Laplacian is computed by filtering the image using the following 3x3 kernel:
/// ```notrust
/// 0, 1, 0,
/// 1, -4, 1,
/// 0, 1, 0
/// ```
#[must_use = "the function does not modify the original image"]
#[cfg(feature = "rayon")]
pub fn laplacian_filter_parallel(image: &GrayImage) -> Image<Luma<i16>> {
let kernel: [i16; 9] = [0, 1, 0, 1, -4, 1, 0, 1, 0];
filter3x3_parallel(image, &kernel)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -797,7 +826,7 @@ mod tests {
#[test]
fn $test_name() {
// I think the interesting edge cases here are determined entirely
// by the relative sizes of the kernel and the image side length, so
// by the relative sizes of the kernel and the image side length, soz
// I'm just enumerating over small values instead of generating random
// examples via quickcheck.
for height in 0..5 {
Expand Down Expand Up @@ -1064,6 +1093,15 @@ mod proptests {
assert_eq!(out.dimensions(), img.dimensions());
}

#[test]
fn proptest_filter3x3_parallel(
img in arbitrary_image::<Luma<u8>>(0..50, 0..50),
ker in proptest::collection::vec(any::<f32>(), 9),
) {
let out: Image<Luma<f32>> = filter3x3_parallel(&img, &ker);
assert_eq!(out.dimensions(), img.dimensions());
}

#[test]
fn proptest_horizontal_filter_luma_f32(
img in arbitrary_image::<Luma<f32>>(0..50, 0..50),
Expand Down Expand Up @@ -1168,6 +1206,23 @@ mod benches {
});
}

#[bench]
fn bench_filter3x3_parallel_i32_filter(b: &mut Bencher) {
let image = gray_bench_image(500, 500);
#[rustfmt::skip]
let kernel: Vec<i32> = vec![
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
];

b.iter(|| {
let filtered: ImageBuffer<Luma<i16>, Vec<i16>> =
filter3x3_parallel::<_, _, i16>(&image, &kernel);
black_box(filtered);
});
}

/// Baseline implementation of Gaussian blur is that provided by image::imageops.
/// We can also use this to validate correctness of any implementations we add here.
fn gaussian_baseline_rgb<I>(image: &I, stdev: f32) -> Image<Rgb<u8>>
Expand Down
10 changes: 10 additions & 0 deletions src/filter/sharpen.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "rayon")]
use super::filter3x3_parallel;
use super::{filter3x3, gaussian_blur_f32};
use crate::{
definitions::{Clamp, Image},
Expand All @@ -12,6 +14,14 @@ pub fn sharpen3x3(image: &GrayImage) -> GrayImage {
filter3x3(image, &identity_minus_laplacian)
}

/// Sharpens a grayscale image by applying a 3x3 approximation to the Laplacian.
#[must_use = "the function does not modify the original image"]
#[cfg(feature = "rayon")]
pub fn sharpen3x3_parallel(image: &GrayImage) -> GrayImage {
let identity_minus_laplacian = [0, -1, 0, -1, 5, -1, 0, -1, 0];
filter3x3_parallel(image, &identity_minus_laplacian)
}

/// Sharpens a grayscale image using a Gaussian as a low-pass filter.
///
/// * `sigma` is the standard deviation of the Gaussian filter used.
Expand Down
Loading

0 comments on commit e3efaa4

Please sign in to comment.