use ndarray as nd; use image; pub enum ColorMaps{ Binary, NipySpectral, TraficLight, } #[derive(Debug)] pub struct ColorMap{ pub red: Vec<(f64, f64, f64)>, pub green: Vec<(f64, f64, f64)>, pub blue: Vec<(f64, f64, f64)>, } #[derive(Debug)] pub struct Heatmap{ pub cm: ColorMap } impl Heatmap{ pub fn new(cm: ColorMaps) -> Self{ Self{ cm: ColorMap::new(cm) } } pub fn convert_image(&self, img: image::DynamicImage) -> image::RgbImage { let gray_img: image::GrayImage = match img { image::DynamicImage::ImageLuma8(gray_image) => { gray_image } _ => { img.to_luma() } }; let mut heatmap_img = image::RgbImage::new(gray_img.width(), gray_img.height()); let lut_size = 256;// * 256 * 256; let lut = self.cm.generate_lut(lut_size); // info!("LUT: {:?}", lut); for pixel in gray_img.enumerate_pixels() { let l = pixel.2; let p = image::Rgb(lut[l.0[0] as usize]); heatmap_img.put_pixel(pixel.0, pixel.1, p); } return heatmap_img; } } // impl From> for Heatmap { // fn from(array: nd::Array2) -> Self { // } // } // impl From for Heatmap { // fn from(image: image::DynamicImage) -> Self { // Self{ // cm: // } // } // } impl ColorMap{ pub fn new(m: ColorMaps) -> Self { let cm = match m { ColorMaps::Binary => { Self{ red: vec![ (0., 0., 0.), (1., 1., 1.) ], green: vec![ (0., 0., 0.), (1., 1., 1.) ], blue: vec![ (0., 0., 0.), (1., 1., 1.) ], } } ColorMaps::TraficLight => { Self{ red: vec![ (0., 0., 0.), (0.5, 1., 1.), (1., 1., 1.) ], green: vec![ (0., 0., 0.), (0.5, 1., 1.), (1., 0., 0.) ], blue: vec![ (0., 0., 1.), (0.5, 0., 0.), (1., 0., 0.) ], } } ColorMaps::NipySpectral => { Self{ red: vec![(0.0, 0.0, 0.0), (0.05, 0.4667, 0.4667), (0.10, 0.5333, 0.5333), (0.15, 0.0, 0.0), (0.20, 0.0, 0.0), (0.25, 0.0, 0.0), (0.30, 0.0, 0.0), (0.35, 0.0, 0.0), (0.40, 0.0, 0.0), (0.45, 0.0, 0.0), (0.50, 0.0, 0.0), (0.55, 0.0, 0.0), (0.60, 0.0, 0.0), (0.65, 0.7333, 0.7333), (0.70, 0.9333, 0.9333), (0.75, 1.0, 1.0), (0.80, 1.0, 1.0), (0.85, 1.0, 1.0), (0.90, 0.8667, 0.8667), (0.95, 0.80, 0.80), (1.0, 0.80, 0.80)], green: vec![(0.0, 0.0, 0.0), (0.05, 0.0, 0.0), (0.10, 0.0, 0.0), (0.15, 0.0, 0.0), (0.20, 0.0, 0.0), (0.25, 0.4667, 0.4667), (0.30, 0.6000, 0.6000), (0.35, 0.6667, 0.6667), (0.40, 0.6667, 0.6667), (0.45, 0.6000, 0.6000), (0.50, 0.7333, 0.7333), (0.55, 0.8667, 0.8667), (0.60, 1.0, 1.0), (0.65, 1.0, 1.0), (0.70, 0.9333, 0.9333), (0.75, 0.8000, 0.8000), (0.80, 0.6000, 0.6000), (0.85, 0.0, 0.0), (0.90, 0.0, 0.0), (0.95, 0.0, 0.0), (1.0, 0.80, 0.80)], blue: vec![(0.0, 0.0, 0.0), (0.05, 0.5333, 0.5333), (0.10, 0.6000, 0.6000), (0.15, 0.6667, 0.6667), (0.20, 0.8667, 0.8667), (0.25, 0.8667, 0.8667), (0.30, 0.8667, 0.8667), (0.35, 0.6667, 0.6667), (0.40, 0.5333, 0.5333), (0.45, 0.0, 0.0), (0.5, 0.0, 0.0), (0.55, 0.0, 0.0), (0.60, 0.0, 0.0), (0.65, 0.0, 0.0), (0.70, 0.0, 0.0), (0.75, 0.0, 0.0), (0.80, 0.0, 0.0), (0.85, 0.0, 0.0), (0.90, 0.0, 0.0), (0.95, 0.0, 0.0), (1.0, 0.80, 0.80)], } } }; return cm; } /// Similar to MatplotLib LinearSegmentedColormap /// @see https://github.com/matplotlib/matplotlib/blob/13e3573b721210d84865d148aab7f63cc2fc95a6/lib/matplotlib/colors.py /// """ /// Create color map from linear mapping segments /// segmentdata argument is a dictionary with a red, green and blue /// entries. Each entry should be a list of *x*, *y0*, *y1* tuples, /// forming rows in a table. Entries for alpha are optional. /// Example: suppose you want red to increase from 0 to 1 over /// the bottom half, green to do the same over the middle half, /// and blue over the top half. Then you would use:: /// cdict = {'red': [(0.0, 0.0, 0.0), /// (0.5, 1.0, 1.0), /// (1.0, 1.0, 1.0)], /// 'green': [(0.0, 0.0, 0.0), /// (0.25, 0.0, 0.0), /// (0.75, 1.0, 1.0), /// (1.0, 1.0, 1.0)], /// 'blue': [(0.0, 0.0, 0.0), /// (0.5, 0.0, 0.0), /// (1.0, 1.0, 1.0)]} /// Each row in the table for a given color is a sequence of /// *x*, *y0*, *y1* tuples. In each sequence, *x* must increase /// monotonically from 0 to 1. For any input value *z* falling /// between *x[i]* and *x[i+1]*, the output value of a given color /// will be linearly interpolated between *y1[i]* and *y0[i+1]*:: /// row i: x y0 y1 /// / /// / /// row i+1: x y0 y1 /// Hence y0 in the first row and y1 in the last row are never used. /// See Also /// -------- /// LinearSegmentedColormap.from_list /// Static method; factory function for generating a smoothly-varying /// LinearSegmentedColormap. /// """ pub fn generate_lut(&self, N: usize) -> Vec<[u8; 3]> { let r = Self::interpolate_color(&self.red, N); let g = Self::interpolate_color(&self.green, N); let b = Self::interpolate_color(&self.blue, N); let mut lut = Vec::<[u8;3]>::new(); for i in 0..N { lut.push([r[i], g[i], b[i]]); } lut } pub fn interpolate_color(colors: &Vec<(f64, f64, f64)>, N: usize) -> Vec{ let step_size = 1./N as f64; let mut prev_color: Option<(f64, f64, f64)> = None; let mut lut = Vec::::new(); for color in colors { match prev_color { Some(prev) => { let steps = (color.0/step_size) as usize - lut.len(); for i in 0..steps { let factor = (i + 1) as f64 / steps as f64; let c = ((prev.2 * (1. - factor) + color.1 * factor) * (N as f64 - 1.)) as u8; lut.push(c); } } None => { let steps = (color.0/step_size) as usize; for i in 0..steps{ lut.push((color.2 * (N as f64 - 1.)) as u8); } } } prev_color = Some(color.clone()); } // now fill the last bit of the lut with the last color for pos in lut.len()..N { lut.push(lut.last().unwrap().clone()); } lut } }