deconvolution 0.1.0

Rust image deconvolution and restoration library.
Documentation
use image::{DynamicImage, GrayImage, Luma};
use ndarray::{Array2, Array3, Axis};

use crate::core::color::{sample_from_f32, sample_to_f32};
use crate::{Error, Kernel2D, Result};

pub(crate) fn array2_to_dynamic(input: &Array2<f32>) -> Result<DynamicImage> {
    let gray = array2_to_gray(input)?;
    Ok(DynamicImage::ImageLuma8(gray))
}

pub(crate) fn dynamic_to_array2(image: &DynamicImage) -> Result<Array2<f32>> {
    match image {
        DynamicImage::ImageLuma8(gray) => gray_to_array2(gray),
        DynamicImage::ImageLumaA8(gray_alpha) => {
            let width =
                usize::try_from(gray_alpha.width()).map_err(|_| Error::DimensionMismatch)?;
            let height =
                usize::try_from(gray_alpha.height()).map_err(|_| Error::DimensionMismatch)?;
            if width == 0 || height == 0 {
                return Err(Error::EmptyImage);
            }

            let mut output = Array2::zeros((height, width));
            for y in 0..height {
                let y_u32 = u32::try_from(y).map_err(|_| Error::DimensionMismatch)?;
                for x in 0..width {
                    let x_u32 = u32::try_from(x).map_err(|_| Error::DimensionMismatch)?;
                    output[[y, x]] = sample_to_f32(gray_alpha.get_pixel(x_u32, y_u32)[0]) / 255.0;
                }
            }
            Ok(output)
        }
        _ => Err(Error::UnsupportedPixelType),
    }
}

pub(crate) fn gray_to_array2(image: &GrayImage) -> Result<Array2<f32>> {
    let width = usize::try_from(image.width()).map_err(|_| Error::DimensionMismatch)?;
    let height = usize::try_from(image.height()).map_err(|_| Error::DimensionMismatch)?;
    if width == 0 || height == 0 {
        return Err(Error::EmptyImage);
    }

    let mut output = Array2::zeros((height, width));
    for y in 0..height {
        let y_u32 = u32::try_from(y).map_err(|_| Error::DimensionMismatch)?;
        for x in 0..width {
            let x_u32 = u32::try_from(x).map_err(|_| Error::DimensionMismatch)?;
            output[[y, x]] = sample_to_f32(image.get_pixel(x_u32, y_u32)[0]) / 255.0;
        }
    }
    Ok(output)
}

pub(crate) fn array2_to_gray(input: &Array2<f32>) -> Result<GrayImage> {
    validate_array2(input)?;
    let (height, width) = input.dim();
    let width_u32 = u32::try_from(width).map_err(|_| Error::DimensionMismatch)?;
    let height_u32 = u32::try_from(height).map_err(|_| Error::DimensionMismatch)?;
    let mut image = GrayImage::new(width_u32, height_u32);

    for y in 0..height {
        let y_u32 = u32::try_from(y).map_err(|_| Error::DimensionMismatch)?;
        for x in 0..width {
            let x_u32 = u32::try_from(x).map_err(|_| Error::DimensionMismatch)?;
            let scaled = input[[y, x]].clamp(0.0, 1.0) * 255.0;
            let value = sample_from_f32(scaled)?;
            image.put_pixel(x_u32, y_u32, Luma([value]));
        }
    }

    Ok(image)
}

pub(crate) fn kernel2_from_array(input: &Array2<f32>) -> Result<Kernel2D> {
    validate_array2(input)?;
    Kernel2D::new(input.as_standard_layout().to_owned())
}

pub(crate) fn kernel3_to_projected_kernel2(input: &Array3<f32>) -> Result<Kernel2D> {
    validate_array3(input)?;
    let mut projected = input.sum_axis(Axis(0));
    let sum = projected.sum();
    if !sum.is_finite() || sum.abs() <= f32::EPSILON {
        return Err(Error::InvalidPsf);
    }
    for value in &mut projected {
        *value /= sum;
    }
    Kernel2D::new(projected)
}

pub(crate) fn validate_array2(input: &Array2<f32>) -> Result<()> {
    if input.is_empty() {
        return Err(Error::EmptyImage);
    }
    if input.iter().any(|value| !value.is_finite()) {
        return Err(Error::NonFiniteInput);
    }
    Ok(())
}

pub(crate) fn validate_array3(input: &Array3<f32>) -> Result<()> {
    if input.is_empty() {
        return Err(Error::EmptyImage);
    }
    if input.iter().any(|value| !value.is_finite()) {
        return Err(Error::NonFiniteInput);
    }
    Ok(())
}