Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
tiffany1618 committed Mar 4, 2021
2 parents 6d16831 + b034191 commit bca9826
Show file tree
Hide file tree
Showing 18 changed files with 485 additions and 331 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "imgproc-rs"
version = "0.2.0"
version = "0.2.1"
edition = "2018"
license = "MIT"
description = "Image processing library for Rust"
Expand All @@ -18,5 +18,9 @@ categories = ["multimedia::images"]

[dependencies]
image = "0.23.12"
rayon = { version = "1.5.0", optional = true }
rulinalg = "0.4.2"
rayon = "1.5.0"

[features]
# Enables multithreading
parallel = ["rayon"]
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
A Rust image processing library.

## Features
* Multithreading support for some functions (indicated by the `_par` suffix in the function name) via
[rayon](https://github.com/rayon-rs/rayon)
* Multithreading support for some functions via [rayon](https://github.com/rayon-rs/rayon) (see
[Enabling Multithreading](#enabling-multithreading) for more information)

## Supported Image Formats

`imgproc-rs` uses the i/o functions provided in the [`image`](https://github.com/image-rs/image) crate. A list of
supported image formats can be found [here](https://docs.rs/image/0.23.12/image/codecs/index.html#supported-formats).

## Notes
Running with the `release` profile greatly increases performance:
```
cargo run --release
```

## Examples

### Reading and Writing Images
Expand Down Expand Up @@ -114,4 +120,32 @@ fn main() {
// Get an image pixel using 2D coordinates
let pixel_2d = img.get_pixel(1, 1);
}
```
```

## Enabling Multithreading

To enable multithreading, include the `parallel` feature in your `Cargo.toml`:

```toml
[dependencies.imgproc-rs]
version = "0.2.1"
default-features = false
features = ["parallel"]
```

Alternatively, pass the features flag to `cargo run`:

```
cargo run --features parallel
```

### Image processing functions that support multithreading:
* `transform` module
* `crop`
* `scale`
* `scale_lanczos`
* All functions in the `filter` module, except:
* `threshold`
* `residual`
* `median_filter`
* `alpha_trimmed_mean_filter`
8 changes: 4 additions & 4 deletions src/colorspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn xyz_to_srgb_lin(input: &Image<f64>) -> Image<f64> {

/// Converts an image from CIE XYZ to CIELAB
// Input: CIEXYZ range [0, 1]
// Output: CIELAB with L* channel range [0, 1]
// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
pub fn xyz_to_lab(input: &Image<f64>, ref_white: &White) -> Image<f64> {
let (x_n, y_n, z_n) = util::generate_xyz_tristimulus_vals(ref_white);

Expand All @@ -97,7 +97,7 @@ pub fn xyz_to_lab(input: &Image<f64>, ref_white: &White) -> Image<f64> {
}

/// Converts an image from CIELAB to CIE XYZ
// Input: CIELAB with L* channel range [0, 1]
// Input: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
// Output: CIEXYZ range [0, 1]
pub fn lab_to_xyz(input: &Image<f64>, ref_white: &White) -> Image<f64> {
let (x_n, y_n, z_n) = util::generate_xyz_tristimulus_vals(ref_white);
Expand Down Expand Up @@ -195,14 +195,14 @@ pub fn xyz_to_srgb(input: &Image<f64>) -> Image<u8> {

/// Converts an image from sRGB to CIELAB
// Input: sRGB range [0, 255] unlinearized
// Output: CIELAB
// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
pub fn srgb_to_lab(input: &Image<u8>, ref_white: &White) -> Image<f64> {
let xyz = srgb_to_xyz(input);
xyz_to_lab(&xyz, ref_white)
}

/// Converts an image from CIELAB to sRGB
// Input: CIELAB
// Input: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
// Output: sRGB range [0, 255] unlinearized
pub fn lab_to_srgb(input: &Image<f64>, ref_white: &White) -> Image<u8> {
let xyz = lab_to_xyz(input, ref_white);
Expand Down
4 changes: 2 additions & 2 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ pub enum Tone {
/// Tone operation should be carried out using an RGB channels
Rgb,

/// Tone operation should be carried out using XYZ channels
Xyz,
/// Tone operation should be carried out using CIELAB channels
Lab,
}

/// An enum for reference white values
Expand Down
7 changes: 5 additions & 2 deletions src/filter/bilateral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::enums::{Bilateral, White};
use crate::image::{Image, BaseImage};
use crate::error::ImgProcResult;

#[cfg(feature = "rayon")]
use rayon::prelude::*;

/// Applies a bilateral filter using CIE LAB
#[cfg(not(feature = "rayon"))]
pub fn bilateral_filter(input: &Image<u8>, range: f64, spatial: f64, algorithm: Bilateral)
-> ImgProcResult<Image<u8>> {
error::check_non_neg(range, "range")?;
Expand All @@ -32,8 +34,9 @@ pub fn bilateral_filter(input: &Image<u8>, range: f64, spatial: f64, algorithm:
Ok(colorspace::lab_to_srgb(&output, &White::D65))
}

/// (Parallel) Applies a bilateral filter using CIE LAB
pub fn bilateral_filter_par(input: &Image<u8>, range: f64, spatial: f64, algorithm: Bilateral)
/// Applies a bilateral filter using CIE LAB
#[cfg(feature = "rayon")]
pub fn bilateral_filter(input: &Image<u8>, range: f64, spatial: f64, algorithm: Bilateral)
-> ImgProcResult<Image<u8>> {
error::check_non_neg(range, "range")?;
error::check_non_neg(spatial, "spatial")?;
Expand Down
125 changes: 10 additions & 115 deletions src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::util::constants::{K_SOBEL_1D_VERT, K_SOBEL_1D_HORZ, K_UNSHARP_MASKING
use crate::enums::Thresh;
use crate::error::ImgProcResult;

#[cfg(feature = "rayon")]
use rayon::prelude::*;

/////////////////////
Expand All @@ -20,6 +21,7 @@ use rayon::prelude::*;

/// Applies a 1D filter. If `is_vert` is true, applies `kernel`
/// as a vertical filter; otherwise applies `kernel` as a horizontal filter
#[cfg(not(feature = "rayon"))]
pub fn filter_1d(input: &Image<f64>, kernel: &[f64], is_vert: bool) -> ImgProcResult<Image<f64>> {
error::check_odd(kernel.len(), "kernel length")?;

Expand All @@ -37,9 +39,10 @@ pub fn filter_1d(input: &Image<f64>, kernel: &[f64], is_vert: bool) -> ImgProcRe
Ok(output)
}

/// (Parallel) Applies a 1D filter. If `is_vert` is true, applies `kernel`
/// Applies a 1D filter. If `is_vert` is true, applies `kernel`
/// as a vertical filter; otherwise applies `kernel` as a horizontal filter
pub fn filter_1d_par(input: &Image<f64>, kernel: &[f64], is_vert: bool) -> ImgProcResult<Image<f64>> {
#[cfg(feature = "rayon")]
pub fn filter_1d(input: &Image<f64>, kernel: &[f64], is_vert: bool) -> ImgProcResult<Image<f64>> {
error::check_odd(kernel.len(), "kernel length")?;

let (width, height, channels, alpha) = input.info().whca();
Expand All @@ -65,17 +68,8 @@ pub fn separable_filter(input: &Image<f64>, vert_kernel: &[f64], horz_kernel: &[
Ok(filter_1d(&vertical, horz_kernel, false)?)
}

/// (Parallel) Applies a separable linear filter by first applying `vert_kernel` and then `horz_kernel`
pub fn separable_filter_par(input: &Image<f64>, vert_kernel: &[f64], horz_kernel: &[f64]) -> ImgProcResult<Image<f64>> {
error::check_odd(vert_kernel.len(), "vert_kernel length")?;
error::check_odd(horz_kernel.len(), "horz_kernel length")?;
error::check_equal(vert_kernel.len(), horz_kernel.len(), "kernel lengths")?;

let vertical = filter_1d_par(input, vert_kernel, true)?;
Ok(filter_1d_par(&vertical, horz_kernel, false)?)
}

/// Applies an unseparable linear filter
#[cfg(not(feature = "rayon"))]
pub fn unseparable_filter(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<Image<f64>> {
error::check_odd(kernel.len(), "kernel length")?;
error::check_square(kernel.len() as f64, "kernel length")?;
Expand All @@ -94,8 +88,9 @@ pub fn unseparable_filter(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<I
Ok(output)
}

/// (Parallel) Applies an unseparable linear filter
pub fn unseparable_filter_par(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<Image<f64>> {
/// Applies an unseparable linear filter
#[cfg(feature = "rayon")]
pub fn unseparable_filter(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<Image<f64>> {
error::check_odd(kernel.len(), "kernel length")?;
error::check_square(kernel.len() as f64, "kernel length")?;

Expand Down Expand Up @@ -125,44 +120,12 @@ pub fn linear_filter(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<Image<
}
}

/// (Parallel) Applies a linear filter using the 2D `kernel`
pub fn linear_filter_par(input: &Image<f64>, kernel: &[f64]) -> ImgProcResult<Image<f64>> {
error::check_odd(kernel.len(), "kernel length")?;
error::check_square(kernel.len() as f64, "kernel length")?;

let separable = math::separate_kernel(kernel);
match separable {
Some((vert, horz)) => Ok(separable_filter_par(input, &vert, &horz)?),
None => Ok(unseparable_filter_par(input, &kernel)?)
}
}

//////////////
// Blurring
//////////////

/// Applies a box filter using a `size x size` kernel
pub fn box_filter(input: &Image<f64>, size: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;

let len = (size * size) as usize;
let kernel = vec![1.0; len];

Ok(separable_filter(input, &kernel, &kernel)?)
}

/// (Parallel) Applies a box filter using a `size x size` kernel
pub fn box_filter_par(input: &Image<f64>, size: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;

let len = (size * size) as usize;
let kernel = vec![1.0; len];

Ok(separable_filter_par(input, &kernel, &kernel)?)
}

/// Applies a normalized box filter using a `size x size` kernel
pub fn box_filter_normalized(input: &Image<f64>, size: u32) -> ImgProcResult<Image<f64>> {
pub fn box_filter(input: &Image<f64>, size: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;

let len = (size * size) as usize;
Expand All @@ -171,16 +134,6 @@ pub fn box_filter_normalized(input: &Image<f64>, size: u32) -> ImgProcResult<Ima
Ok(separable_filter(input, &kernel, &kernel)?)
}

/// (Parallel) Applies a normalized box filter using a `size x size` kernel
pub fn box_filter_normalized_par(input: &Image<f64>, size: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;

let len = (size * size) as usize;
let kernel = vec![1.0 / ((size * size) as f64); len];

Ok(separable_filter_par(input, &kernel, &kernel)?)
}

/// Applies a weighted average filter using a `size x size` kernel with a center weight of `weight`
pub fn weighted_avg_filter(input: &Image<f64>, size: u32, weight: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;
Expand All @@ -193,30 +146,12 @@ pub fn weighted_avg_filter(input: &Image<f64>, size: u32, weight: u32) -> ImgPro
Ok(unseparable_filter(input, &kernel)?)
}

/// (Parallel) Applies a weighted average filter using a `size x size` kernel with a center weight of `weight`
pub fn weighted_avg_filter_par(input: &Image<f64>, size: u32, weight: u32) -> ImgProcResult<Image<f64>> {
error::check_odd(size, "size")?;

let sum = (size * size) - 1 + weight;
let center = (size / 2) * size + (size / 2);
let mut kernel = vec![1.0 / (sum as f64); (size * size) as usize];
kernel[center as usize] = (weight as f64) / (sum as f64);

Ok(unseparable_filter_par(input, &kernel)?)
}

/// Applies a Gaussian blur using a `size x size` kernel
pub fn gaussian_blur(input: &Image<f64>, size: u32, std_dev: f64) -> ImgProcResult<Image<f64>> {
let kernel = util::generate_gaussian_kernel(size, std_dev)?;
Ok(linear_filter(input, &kernel)?)
}

/// (Parallel) Applies a Gaussian blur using a `size x size` kernel
pub fn gaussian_blur_par(input: &Image<f64>, size: u32, std_dev: f64) -> ImgProcResult<Image<f64>> {
let kernel = util::generate_gaussian_kernel(size, std_dev)?;
Ok(linear_filter_par(input, &kernel)?)
}

////////////////
// Sharpening
////////////////
Expand All @@ -226,21 +161,11 @@ pub fn sharpen(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(unseparable_filter(input, &K_SHARPEN)?)
}

/// (Parallel) Sharpens image
pub fn sharpen_par(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(unseparable_filter_par(input, &K_SHARPEN)?)
}

/// Sharpens image by applying the unsharp masking kernel
pub fn unsharp_masking(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(unseparable_filter(input, &K_UNSHARP_MASKING)?)
}

/// (Parallel) Sharpens image by applying the unsharp masking kernel
pub fn unsharp_masking_par(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(unseparable_filter_par(input, &K_UNSHARP_MASKING)?)
}

////////////////////
// Edge detection
////////////////////
Expand All @@ -260,52 +185,22 @@ pub fn derivative_mask(input: &Image<f64>, vert_kernel: &[f64], horz_kernel: &[f
Ok(output)
}

/// (Parallel) Applies a separable derivative mask; first converts `input` to grayscale
pub fn derivative_mask_par(input: &Image<f64>, vert_kernel: &[f64], horz_kernel: &[f64]) -> ImgProcResult<Image<f64>> {
let gray = colorspace::rgb_to_grayscale_f64(input);
let img_x = separable_filter_par(&gray, &vert_kernel, &horz_kernel)?;
let img_y = separable_filter_par(&gray, &horz_kernel, &vert_kernel)?;
let mut output = Image::blank(gray.info());

for i in 0..(output.info().full_size() as usize) {
output.set_pixel_indexed(i, &[(img_x[i][0].powf(2.0) + img_y[i][0].powf(2.0)).sqrt()]);
}

Ok(output)
}

/// Applies the Prewitt operator
pub fn prewitt(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(derivative_mask(input, &K_PREWITT_1D_VERT, &K_PREWITT_1D_HORZ)?)
}

/// (Parallel) Applies the Prewitt operator
pub fn prewitt_par(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(derivative_mask_par(input, &K_PREWITT_1D_VERT, &K_PREWITT_1D_HORZ)?)
}

/// Applies the Sobel operator
pub fn sobel(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(derivative_mask(input, &K_SOBEL_1D_VERT, &K_SOBEL_1D_HORZ)?)
}

/// (Parallel) Applies the Sobel operator
pub fn sobel_par(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(derivative_mask_par(input, &K_SOBEL_1D_VERT, &K_SOBEL_1D_HORZ)?)
}

/// Applies a Sobel operator with weight `weight`
pub fn sobel_weighted(input: &Image<f64>, weight: u32) -> ImgProcResult<Image<f64>> {
let vert_kernel = vec![1.0, weight as f64, 1.0];
Ok(derivative_mask(input, &vert_kernel, &K_SOBEL_1D_HORZ)?)
}

/// (Parallel) Applies a Sobel operator with weight `weight`
pub fn sobel_weighted_par(input: &Image<f64>, weight: u32) -> ImgProcResult<Image<f64>> {
let vert_kernel = vec![1.0, weight as f64, 1.0];
Ok(derivative_mask_par(input, &vert_kernel, &K_SOBEL_1D_HORZ)?)
}

//////////////////
// Thresholding
//////////////////
Expand Down
2 changes: 1 addition & 1 deletion src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ impl<T: Number> Image<T> {
/// Applies function `f` to each pixel
pub fn map_pixels<S: Number, F>(&self, f: F) -> Image<S>
where F: Fn(&[T]) -> Vec<S> {
let mut data= Vec::new();
let mut data= Vec::new();

for i in 0..(self.info.size() as usize) {
data.append(&mut f(&self[i]));
Expand Down
Loading

0 comments on commit bca9826

Please sign in to comment.