plotpoint 0.1.9

A library to convert a point cloud into a (differentiable) image
Documentation
mod affected;

use std::{marker::PhantomData, ops::Mul};

use image::{Rgba, RgbaImage};
use nalgebra::{Affine2, DMatrix, Point2, RealField};
use num_dual::{DualNum, DualNumFloat};
use num_traits::FromPrimitive;

pub struct Image<F, T: DualNum<F, Inner = F>>(pub DMatrix<T>, PhantomData<F>);

impl<F: DualNumFloat, T: DualNum<F, Inner = F> + RealField> Image<F, T>
where
    T::Inner: DualNumFloat + FromPrimitive,
    for<'a> &'a T: Mul<&'a T, Output = T>,
    // for<'a> &'a T: Mul<F, Output = T>,
{
    pub fn new(image: DMatrix<T>) -> Self {
        Self(image, PhantomData)
    }
    // todo: fn_from real image
    pub fn from_points(
        point_weights: &[(Point2<T>, T)],
        pix_to_point: &Affine2<T>,
        background: &DMatrix<F>,
    ) -> Self {
        let mut img = background.map(|f| T::from_inner(f));
        for (point, weight) in point_weights.iter() {
            let p = pix_to_point.transform_point(point);
            let affected = affected::affected(&p.coords);
            for affected in affected {
                let square = affected.square();
                if square.x >= 0
                    && (square.x as usize) < background.shape().1
                    && square.y >= 0
                    && (square.y as usize) < background.shape().0
                {
                    img[(square.y as usize, square.x as usize)] -= affected.val().clone() * weight;
                }
            }
        }
        Self(img, PhantomData)
    }

    pub fn to_image(&self) -> RgbaImage {
        let (rows, cols) = self.0.shape();
        let mut img = RgbaImage::new(cols as u32, rows as u32);
        for (x, y, pixel) in img.enumerate_pixels_mut() {
            let val = self.0[(y as usize, x as usize)].re();
            let g = (val * F::from(u8::MAX as f64).unwrap())
                .round()
                .to_i16()
                .unwrap()
                .clamp(u8::MIN as i16, u8::MAX as i16) as u8;
            *pixel = Rgba([g, g, g, u8::MAX]);
        }
        img
    }
}

#[cfg(test)]
mod tests {
    use nalgebra::DVector;
    use num_dual::{Derivative, DualVec};

    use super::*;

    #[test]
    fn test_image() {
        let input = Image::new(DMatrix::from_column_slice(
            2,
            2,
            &[
                DualVec::new(
                    0.0,
                    Derivative::new(Some(DVector::from_column_slice(&[0.0]))),
                ),
                DualVec::new(
                    0.1,
                    Derivative::new(Some(DVector::from_column_slice(&[1.0]))),
                ),
                DualVec::new(
                    0.9,
                    Derivative::new(Some(DVector::from_column_slice(&[2.0]))),
                ),
                DualVec::new(
                    1.0,
                    Derivative::new(Some(DVector::from_column_slice(&[3.0]))),
                ),
            ],
        ));
        let output = input.to_image();
        assert_eq!(output.get_pixel(0, 0), &image::Rgba([0, 0, 0, 255]));
        assert_eq!(output.get_pixel(1, 0), &image::Rgba([230, 230, 230, 255]));
        assert_eq!(output.get_pixel(0, 1), &image::Rgba([26, 26, 26, 255]));
        assert_eq!(output.get_pixel(1, 1), &image::Rgba([255, 255, 255, 255]));
    }

    #[test]
    fn from_point() {
        let background = DMatrix::from_element(3, 3, 1.0);
        let point = [Point2::new(1.75, 1.25)];
        let pix_to_point = Affine2::identity();
        let weight = 0.5;
        let point_weights = point.iter().map(|p| (*p, weight)).collect::<Vec<_>>();
        let img = Image::from_points(&point_weights, &pix_to_point, &background);
        let output = img.to_image();
        assert_eq!(output.get_pixel(0, 0), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(0, 1), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(0, 2), &image::Rgba([255, 255, 255, 255]));
        let tl = (255.0_f64 - (0.25 * 0.75 * weight * 255.0)).round() as u8;
        let bl = (255.0_f64 - (0.75 * 0.75 * weight * 255.0)).round() as u8;
        let tr = (255.0_f64 - (0.25 * 0.25 * weight * 255.0)).round() as u8;
        let br = (255.0_f64 - (0.75 * 0.25 * weight * 255.0)).round() as u8;
        assert_eq!(output.get_pixel(1, 0), &image::Rgba([tl, tl, tl, 255])); // top left
        assert_eq!(output.get_pixel(1, 1), &image::Rgba([bl, bl, bl, 255])); // bot left
        assert_eq!(output.get_pixel(1, 2), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(2, 0), &image::Rgba([tr, tr, tr, 255])); // top right
        assert_eq!(output.get_pixel(2, 1), &image::Rgba([br, br, br, 255])); // bot right
        assert_eq!(output.get_pixel(2, 2), &image::Rgba([255, 255, 255, 255]));
    }
    #[test]
    fn from_points_2_identical() {
        let background = DMatrix::from_element(3, 3, 1.0);
        let point = [Point2::new(1.75, 1.25); 2];
        let pix_to_point = Affine2::identity();
        let weight = 0.5;
        let point_weights = point.iter().map(|p| (*p, weight)).collect::<Vec<_>>();
        let img = Image::from_points(&point_weights, &pix_to_point, &background);
        let output = img.to_image();
        assert_eq!(output.get_pixel(0, 0), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(0, 1), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(0, 2), &image::Rgba([255, 255, 255, 255]));
        let tl = (255.0_f64 - 2.0 * (0.25 * 0.75 * weight * 255.0)).round() as u8;
        let bl = (255.0_f64 - 2.0 * (0.75 * 0.75 * weight * 255.0)).round() as u8;
        let tr = (255.0_f64 - 2.0 * (0.25 * 0.25 * weight * 255.0)).round() as u8;
        let br = (255.0_f64 - 2.0 * (0.75 * 0.25 * weight * 255.0)).round() as u8;
        assert_eq!(output.get_pixel(1, 0), &image::Rgba([tl, tl, tl, 255])); // top left
        assert_eq!(output.get_pixel(1, 1), &image::Rgba([bl, bl, bl, 255])); // bot left
        assert_eq!(output.get_pixel(1, 2), &image::Rgba([255, 255, 255, 255]));
        assert_eq!(output.get_pixel(2, 0), &image::Rgba([tr, tr, tr, 255])); // top right
        assert_eq!(output.get_pixel(2, 1), &image::Rgba([br, br, br, 255])); // bot right
        assert_eq!(output.get_pixel(2, 2), &image::Rgba([255, 255, 255, 255]));
    }
}