<commit_before>//! Functions for manipulating the contrast of images.

use std::cmp::{min, max};
use image::{GrayImage, ImageBuffer, Luma};
use definitions::{HasBlack, HasWhite};
use integralimage::{integral_image, sum_image_pixels};
use rayon::prelude::*;

/// Applies an adaptive threshold to an image.
///
/// This algorithm compares each pixel's brightness with the average brightness of the pixels
/// in the (2 * `block_radius` + 1) square block centered on it. If the pixel if at least as bright
/// as the threshold then it will have a value of 255 in the output image, otherwise 0.
pub fn adaptive_threshold(image: &GrayImage, block_radius: u32) -> GrayImage {
     assert!(block_radius > 0);
     let integral = integral_image(image);
     let mut out = ImageBuffer::from_pixel(image.width(), image.height(), Luma::black());
     for y in 0..image.height() {
         for x in 0..image.width() {
             let current_pixel = image.get_pixel(x, y);
             // Traverse all neighbors in (2 * block_radius + 1) x (2 * block_radius + 1)
             let (y_low, y_high) = (max(0, y as i32 - (block_radius as i32)) as u32,
                                    min(image.height() - 1, y + block_radius));
             let (x_low, x_high) = (max(0, x as i32 - (block_radius as i32)) as u32,
                                    min(image.width() - 1, x + block_radius));

             // Number of pixels in the block, adjusted for edge cases.
             let w = (y_high - y_low + 1) * (x_high - x_low + 1);
             let mean = sum_image_pixels(&integral, x_low, y_low, x_high, y_high) / w;

             if current_pixel[0] as u32 >= mean as u32 {
                 out.put_pixel(x, y, Luma::white());
             }
         }
     }
     out
}

/// Returns the [Otsu threshold level] of an 8bpp image.
///
/// [Otsu threshold level]: https://en.wikipedia.org/wiki/Otsu%27s_method
pub fn otsu_level(image: &GrayImage) -> u8 {
    let hist = histogram(image);
    let (width, height) = image.dimensions();
    let total_weight = width * height;

    // Sum of all pixel intensities, to use when calculating means.
    let total_pixel_sum = hist
        .iter()
        .enumerate()
        .fold(0f64, |sum, (t, h)| sum + (t as u32 * h) as f64);

    // Sum of all pixel intensities in the background class.
    let mut background_pixel_sum = 0f64;

    // The weight of a class (background or foreground) is
    // the number of pixels which belong to that class at
    // the current threshold.
    let mut background_weight = 0u32;
    let mut foreground_weight;

    let mut largest_variance = 0f64;
    let mut best_threshold = 0u8;

    for (threshold, hist_count) in hist.iter().enumerate() {
        background_weight = background_weight + hist_count;
        if background_weight == 0 {
            continue
        };

        foreground_weight = total_weight - background_weight;
        if foreground_weight == 0 {
            break
        };

        background_pixel_sum += (threshold as u32 * hist_count) as f64;
        let foreground_pixel_sum = total_pixel_sum - background_pixel_sum;

        let background_mean = background_pixel_sum / (background_weight as f64);
        let foreground_mean = foreground_pixel_sum / (foreground_weight as f64);

        let mean_diff_squared = (background_mean - foreground_mean).powi(2);
        let intra_class_variance =
            (background_weight as f64) * (foreground_weight as f64) * mean_diff_squared;

        if intra_class_variance > largest_variance {
            largest_variance = intra_class_variance;
            best_threshold = threshold as u8;
        }
    }

    best_threshold
}

/// Returns a binarized image from an input 8bpp grayscale image
/// obtained by applying the given threshold. Pixels with intensity
/// equal to the threshold are assigned to the background.
pub fn threshold(image: &GrayImage, thresh: u8) -> GrayImage {
    let mut out = image.clone();
    threshold_mut(&mut out, thresh);
    out
}

/// Mutates given image to form a binarized version produced by applying
/// the given threshold. Pixels with intensity
/// equal to the threshold are assigned to the background.
pub fn threshold_mut(image: &mut GrayImage, thresh: u8) {
    for p in image.iter_mut() {
        *p = if *p <= thresh { 0 } else { 255 };
    }
}

/// Returns the histogram of grayscale values in an 8bpp
/// grayscale image.<commit_msg>Add examples for threshold and threshold_mut<commit_after>//! Functions for manipulating the contrast of images.

use std::cmp::{min, max};
use image::{GrayImage, ImageBuffer, Luma};
use definitions::{HasBlack, HasWhite};
use integralimage::{integral_image, sum_image_pixels};
use rayon::prelude::*;

/// Applies an adaptive threshold to an image.
///
/// This algorithm compares each pixel's brightness with the average brightness of the pixels
/// in the (2 * `block_radius` + 1) square block centered on it. If the pixel if at least as bright
/// as the threshold then it will have a value of 255 in the output image, otherwise 0.
pub fn adaptive_threshold(image: &GrayImage, block_radius: u32) -> GrayImage {
     assert!(block_radius > 0);
     let integral = integral_image(image);
     let mut out = ImageBuffer::from_pixel(image.width(), image.height(), Luma::black());
     for y in 0..image.height() {
         for x in 0..image.width() {
             let current_pixel = image.get_pixel(x, y);
             // Traverse all neighbors in (2 * block_radius + 1) x (2 * block_radius + 1)
             let (y_low, y_high) = (max(0, y as i32 - (block_radius as i32)) as u32,
                                    min(image.height() - 1, y + block_radius));
             let (x_low, x_high) = (max(0, x as i32 - (block_radius as i32)) as u32,
                                    min(image.width() - 1, x + block_radius));

             // Number of pixels in the block, adjusted for edge cases.
             let w = (y_high - y_low + 1) * (x_high - x_low + 1);
             let mean = sum_image_pixels(&integral, x_low, y_low, x_high, y_high) / w;

             if current_pixel[0] as u32 >= mean as u32 {
                 out.put_pixel(x, y, Luma::white());
             }
         }
     }
     out
}

/// Returns the [Otsu threshold level] of an 8bpp image.
///
/// [Otsu threshold level]: https://en.wikipedia.org/wiki/Otsu%27s_method
pub fn otsu_level(image: &GrayImage) -> u8 {
    let hist = histogram(image);
    let (width, height) = image.dimensions();
    let total_weight = width * height;

    // Sum of all pixel intensities, to use when calculating means.
    let total_pixel_sum = hist
        .iter()
        .enumerate()
        .fold(0f64, |sum, (t, h)| sum + (t as u32 * h) as f64);

    // Sum of all pixel intensities in the background class.
    let mut background_pixel_sum = 0f64;

    // The weight of a class (background or foreground) is
    // the number of pixels which belong to that class at
    // the current threshold.
    let mut background_weight = 0u32;
    let mut foreground_weight;

    let mut largest_variance = 0f64;
    let mut best_threshold = 0u8;

    for (threshold, hist_count) in hist.iter().enumerate() {
        background_weight = background_weight + hist_count;
        if background_weight == 0 {
            continue
        };

        foreground_weight = total_weight - background_weight;
        if foreground_weight == 0 {
            break
        };

        background_pixel_sum += (threshold as u32 * hist_count) as f64;
        let foreground_pixel_sum = total_pixel_sum - background_pixel_sum;

        let background_mean = background_pixel_sum / (background_weight as f64);
        let foreground_mean = foreground_pixel_sum / (foreground_weight as f64);

        let mean_diff_squared = (background_mean - foreground_mean).powi(2);
        let intra_class_variance =
            (background_weight as f64) * (foreground_weight as f64) * mean_diff_squared;

        if intra_class_variance > largest_variance {
            largest_variance = intra_class_variance;
            best_threshold = threshold as u8;
        }
    }

    best_threshold
}

/// Returns a binarized image from an input 8bpp grayscale image
/// obtained by applying the given threshold. Pixels with intensity
/// equal to the threshold are assigned to the background.
///
/// # Examples
/// ```
/// # extern crate image;
/// # #[macro_use]
/// # extern crate imageproc;
/// # fn main() {
/// use image::Rgb;
/// use imageproc::contrast::threshold;
///
/// let image = gray_image!(
///     10, 80, 20;
///     50, 90, 70);
///
/// let thresholded = gray_image!(
///     0, 255,   0;
///     0, 255, 255);
///
/// assert_pixels_eq!(threshold(&image, 50), thresholded);
/// # }
/// ```
pub fn threshold(image: &GrayImage, thresh: u8) -> GrayImage {
    let mut out = image.clone();
    threshold_mut(&mut out, thresh);
    out
}

/// Mutates given image to form a binarized version produced by applying
/// the given threshold. Pixels with intensity
/// equal to the threshold are assigned to the background.
///
/// ```
/// # extern crate image;
/// # #[macro_use]
/// # extern crate imageproc;
/// # fn main() {
/// use image::Rgb;
/// use imageproc::contrast::threshold_mut;
///
/// let mut image = gray_image!(
///     10, 80, 20;
///     50, 90, 70);
///
/// let thresholded = gray_image!(
///     0, 255,   0;
///     0, 255, 255);
///
/// threshold_mut(&mut image, 50);
///
/// assert_pixels_eq!(image, thresholded);
/// # }
/// ```
pub fn threshold_mut(image: &mut GrayImage, thresh: u8) {
    for p in image.iter_mut() {
        *p = if *p <= thresh { 0 } else { 255 };
    }
}

/// Returns the histogram of grayscale values in an 8bpp
/// grayscale image.
{'fraction_non_alphanumeric': 0.11802773497688752, 'fraction_numerical': 0.022598870056497175, 'mean_word_length': 2.44881331916401, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 15, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '457062', 'n_tokens_mistral': 3123, 'n_tokens_neox': 2892, 'n_words': 988}