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 9, 2021
2 parents bca9826 + 44b2c86 commit 6779711
Show file tree
Hide file tree
Showing 21 changed files with 413 additions and 224 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "imgproc-rs"
version = "0.2.1"
version = "0.2.2"
edition = "2018"
license = "MIT"
description = "Image processing library for Rust"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ To enable multithreading, include the `parallel` feature in your `Cargo.toml`:

```toml
[dependencies.imgproc-rs]
version = "0.2.1"
version = "0.2.2"
default-features = false
features = ["parallel"]
```
Expand Down
80 changes: 46 additions & 34 deletions src/colorspace.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! A module for image colorspace conversion operations

use crate::{util, math};
use crate::util::constants::{GAMMA, SRGB_TO_XYZ_MAT, XYZ_TO_SRGB_MAT};
use crate::image::Image;
use crate::enums::White;

use std::cmp;

use crate::enums::White;
use crate::image::Image;
use crate::util;
use crate::util::constants::{GAMMA, SRGB_TO_XYZ_MAT, XYZ_TO_SRGB_MAT};

/// Converts an image from RGB to Grayscale
pub fn rgb_to_grayscale(input: &Image<u8>) -> Image<u8> {
input.map_pixels_if_alpha(|channels| {
Expand All @@ -32,11 +32,12 @@ pub fn rgb_to_grayscale_f64(input: &Image<f64>) -> Image<f64> {
}

/// Linearizes an sRGB image
// Input: sRGB range [0, 255]
// Output: sRGB range [0, 1] linearized
///
/// * Input: sRGB image with channels in range [0, 255]
/// * Output: linearized sRGB image with channels in range [0, 1]
pub fn linearize_srgb(input: &Image<u8>) -> Image<f64> {
let mut lookup_table: [f64; 256] = [0.0; 256];
util::create_lookup_table(&mut lookup_table, |i| {
util::generate_lookup_table(&mut lookup_table, |i| {
let val = i as f64;
if val <= 10.0 {
val / 3294.0
Expand All @@ -49,8 +50,9 @@ pub fn linearize_srgb(input: &Image<u8>) -> Image<f64> {
}

/// "Unlinearizes" a previously linearized sRGB image
// Input: sRGB range [0, 1] linearized
// Output: sRGB range [0, 255]
///
/// * Input: linearized sRGB image with channels in range [0, 1]
/// * Output: sRGB image with channels in range [0, 255]
pub fn unlinearize_srgb(input: &Image<f64>) -> Image<u8> {
input.map_channels_if_alpha(|num| {
if num <= 0.0031308 {
Expand All @@ -62,28 +64,31 @@ pub fn unlinearize_srgb(input: &Image<f64>) -> Image<u8> {
}

/// Converts an image from linearized sRGB to CIE XYZ
// Input: sRGB range [0, 1] linearized
// Output: CIE XYZ range [0, 1]
///
/// * Input: linearized sRGB image with channels in range [0, 1]
/// * Output: CIE XYZ image with channels in range [0, 1]
pub fn srgb_lin_to_xyz(input: &Image<f64>) -> Image<f64> {
input.map_pixels_if_alpha(|channels| {
math::vector_mul(&SRGB_TO_XYZ_MAT, channels).unwrap()
util::vector_mul(&SRGB_TO_XYZ_MAT, channels).unwrap()
}, |a| a)
}

/// Converts an image from CIE XYZ to linearized sRGB
// Input: CIE XYZ range [0, 1]
// Output: sRGB range [0, 1] linearized
///
/// * Input: CIE XYZ image with channels in range [0, 1]
/// * Output: linearized sRGB image with channels in range [0, 1]
pub fn xyz_to_srgb_lin(input: &Image<f64>) -> Image<f64> {
input.map_pixels_if_alpha(|channels| {
math::vector_mul(&XYZ_TO_SRGB_MAT, channels).unwrap()
util::vector_mul(&XYZ_TO_SRGB_MAT, channels).unwrap()
}, |a| a)
}

/// Converts an image from CIE XYZ to CIELAB
// Input: CIEXYZ range [0, 1]
// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
///
/// * Input: CIE XYZ image with channels in range [0, 1]
/// * Output: CIELAB image 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);
let (x_n, y_n, z_n) = util::xyz_tristimulus_vals(ref_white);

input.map_pixels_if_alpha(|channels| {
let x = util::xyz_to_lab_fn(channels[0] * 100.0 / x_n);
Expand All @@ -97,10 +102,11 @@ 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, 100] and a*, b* channels range [-128,127]
// Output: CIEXYZ range [0, 1]
///
/// * Input: CIELAB image with L* channel range [0, 100] and a*, b* channels range [-128, 127]
/// * Output: CIE XYZ image with channels in 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);
let (x_n, y_n, z_n) = util::xyz_tristimulus_vals(ref_white);

input.map_pixels_if_alpha(|channels| {
let n = (channels[0] + 16.0) / 116.0;
Expand All @@ -112,8 +118,9 @@ pub fn lab_to_xyz(input: &Image<f64>, ref_white: &White) -> Image<f64> {
}

/// Converts an image from RGB to HSV
// Input: RGB range [0, 255]
// Output: HSV range [0, 1]
///
/// * Input: RGB image with channels in range [0, 255]
/// * Output: HSV image with channels in range [0, 1]
pub fn rgb_to_hsv(input: &Image<u8>) -> Image<f64> {
input.map_pixels_if_alpha(|channels| {
let max: u8 = cmp::max(cmp::max(channels[0], channels[1]), channels[2]);
Expand Down Expand Up @@ -150,8 +157,9 @@ pub fn rgb_to_hsv(input: &Image<u8>) -> Image<f64> {
}

/// Converts an image from HSV to RGB
// Input: HSV range [0, 1]
// Output: RGB range [0, 255]
///
/// * Input: HSV image with channels in range [0, 1]
/// * Output: RGB image with channels in range [0, 255]
pub fn hsv_to_rgb(input: &Image<f64>) -> Image<u8> {
input.map_pixels_if_alpha(|channels| {
if channels[1] == 0.0 {
Expand All @@ -178,32 +186,36 @@ pub fn hsv_to_rgb(input: &Image<f64>) -> Image<u8> {
}

/// Converts an image from sRGB to CIE XYZ
// Input: sRGB range [0, 255] unlinearized
// Output: CIEXYZ range [0, 1]
///
/// * Input: sRGB image with channels in range [0, 255]
/// * Output: CIE XYZ image with channels in range [0, 1]
pub fn srgb_to_xyz(input: &Image<u8>) -> Image<f64> {
let linearized = linearize_srgb(input);
srgb_lin_to_xyz(&linearized)
}

/// Converts an image from CIE XYZ to sRGB
// Input: CIEXYZ range [0, 1]
// Output: sRGB range [0, 255] unlinearized
///
/// * Input: CIE XYZ image with channels in range [0, 1]
/// * Output: sRGB image with channels in range [0, 255]
pub fn xyz_to_srgb(input: &Image<f64>) -> Image<u8> {
let srgb = xyz_to_srgb_lin(input);
unlinearize_srgb(&srgb)
}

/// Converts an image from sRGB to CIELAB
// Input: sRGB range [0, 255] unlinearized
// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127]
///
/// * Input: sRGB image with channels in range [0, 255]
/// * Output: CIELAB image 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 with L* channel range [0, 100] and a*, b* channels range [-128,127]
// Output: sRGB range [0, 255] unlinearized
///
/// * Input: CIELAB image with L* channel range [0, 100] and a*, b* channels range [-128,127]
/// * Output: sRGB image with channels in range [0, 255]
pub fn lab_to_srgb(input: &Image<f64>, ref_white: &White) -> Image<u8> {
let xyz = lab_to_xyz(input, ref_white);
xyz_to_srgb(&xyz)
Expand Down
10 changes: 4 additions & 6 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

use crate::image::Image;
use crate::error::ImgProcResult;
use crate::error;

/// Scales channels from range 0.0 to `current_max` to range 0.0 to `scaled_max`
pub fn scale_channels(input: &Image<f64>, current_max: f64, scaled_max: f64) -> ImgProcResult<Image<f64>> {
error::check_non_neg(current_max, "current_max")?;
error::check_non_neg(scaled_max, "scaled_max")?;

Ok(input.map_channels(|channel| (channel / current_max * scaled_max)))
pub fn scale_channels(input: &Image<f64>, current_min: f64, scaled_min: f64, current_max: f64, scaled_max: f64) -> ImgProcResult<Image<f64>> {
Ok(input.map_channels(|channel| {
(channel - current_min) / (current_max - current_min) * (scaled_max - scaled_min) + scaled_min
}))
}

/// Converts an `Image<f64>` with channels in range 0 to `scale` to an `Image<u8>` with channels
Expand Down
14 changes: 11 additions & 3 deletions src/error/messages.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::{ImgProcResult, ImgProcError};
use crate::image::Number;
use crate::image::{Number, Image, BaseImage};

pub(crate) fn check_channels(channels: u8, len: usize) {
if channels != len as u8 {
Expand Down Expand Up @@ -57,8 +57,16 @@ pub(crate) fn check_square(val: f64, name: &str) -> ImgProcResult<()> {
Ok(())
}

pub(crate) fn check_grayscale(channels: u8, alpha: bool) -> ImgProcResult<()> {
if (alpha && channels != 2) || (!alpha && channels != 1) {
pub(crate) fn check_in_range<T: Number>(val: T, min: T, max: T, name: &str) -> ImgProcResult<()> {
if val < min || val > max {
return Err(ImgProcError::InvalidArgError(format!("{} must be between {} and {} (inclusive)", name, min, max)));
}

Ok(())
}

pub(crate) fn check_grayscale<T: Number>(input: &Image<T>) -> ImgProcResult<()> {
if (input.info().alpha && input.info().channels != 2) || (!input.info().alpha && input.info().channels != 1) {
return Err(ImgProcError::InvalidArgError("input is not a grayscale image".to_string()));
}

Expand Down
12 changes: 6 additions & 6 deletions src/filter/bilateral.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{error, util, colorspace, math};
use crate::enums::{Bilateral, White};
use crate::image::{Image, BaseImage};
use crate::error::ImgProcResult;

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

use crate::{colorspace, error, util};
use crate::enums::{Bilateral, White};
use crate::error::ImgProcResult;
use crate::image::{BaseImage, Image};

/// Applies a bilateral filter using CIE LAB
#[cfg(not(feature = "rayon"))]
pub fn bilateral_filter(input: &Image<u8>, range: f64, spatial: f64, algorithm: Bilateral)
Expand Down Expand Up @@ -73,7 +73,7 @@ fn bilateral_direct_pixel(input: &Image<f64>, range: f64, spatial_mat: &[f64], s
let mut p_curr = 0.0;

for i in 0..((size * size) as usize) {
let g_r = math::gaussian_fn((p_in[c] - p_n[i][c]).abs(), range).unwrap();
let g_r = util::gaussian_fn((p_in[c] - p_n[i][c]).abs(), range).unwrap();
let weight = spatial_mat[i] * g_r;

p_curr += weight * p_n[i][c];
Expand Down
64 changes: 64 additions & 0 deletions src/filter/edge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
////////////////////
// Edge detection
////////////////////

use crate::{filter, error, util, convert};
use crate::image::{Image, BaseImage};
use crate::error::ImgProcResult;
use crate::util::constants::{K_PREWITT_1D_VERT, K_PREWITT_1D_HORZ, K_SOBEL_1D_VERT, K_SOBEL_1D_HORZ, K_LAPLACIAN};

/// Applies a separable derivative mask to a grayscale image
pub fn derivative_mask(input: &Image<f64>, vert_kernel: &[f64], horz_kernel: &[f64]) -> ImgProcResult<Image<f64>> {
error::check_grayscale(input)?;

let img_x = filter::separable_filter(&input, &vert_kernel, &horz_kernel)?;
let img_y = filter::separable_filter(&input, &horz_kernel, &vert_kernel)?;

let mut output = Image::blank(input.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 to a grayscale image
pub fn prewitt(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(derivative_mask(input, &K_PREWITT_1D_VERT, &K_PREWITT_1D_HORZ)?)
}

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

/// Applies a Sobel operator with weight `weight` to a grayscale image
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)?)
}

/// Applies the Laplacian operator to a grayscale image. Output contains positive
/// and negative values - use [`normalize_laplacian()`](fn.normalize_laplacian.html) for visualization
pub fn laplacian(input: &Image<f64>) -> ImgProcResult<Image<f64>> {
Ok(filter::unseparable_filter(input, &K_LAPLACIAN)?)
}

/// Applies the Laplacian of Gaussian operator using a `size x size` kernel to a grayscale image.
/// Output contains positive and negative values - use
/// [`normalize_laplacian()`](fn.normalize_laplacian.html) for visualization
pub fn laplacian_of_gaussian(input: &Image<f64>, size: u32, sigma: f64) -> ImgProcResult<Image<f64>> {
let kernel = util::generate_log_kernel(size, sigma)?;
Ok(filter::unseparable_filter(input, &kernel)?)
}

/// Normalizes the result of a Laplacian or Laplacian of Gaussian operator to the range [0, 255]
pub fn normalize_laplacian(input: &Image<f64>) -> ImgProcResult<Image<u8>> {
error::check_grayscale(input)?;

let min = *input.data().iter().min_by(|x, y| x.partial_cmp(y).unwrap()).unwrap();
let max = *input.data().iter().max_by(|x, y| x.partial_cmp(y).unwrap()).unwrap();

Ok(convert::scale_channels(&input, min, 0.0, max, 255.0)?.into())
}
Loading

0 comments on commit 6779711

Please sign in to comment.