visualhaar/src/heatmap.rs

225 lines
7.8 KiB
Rust

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<nd::Array2<i32>> for Heatmap {
// fn from(array: nd::Array2<i32>) -> Self {
// }
// }
// impl From<image::DynamicImage> 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<u8>{
let step_size = 1./N as f64;
let mut prev_color: Option<(f64, f64, f64)> = None;
let mut lut = Vec::<u8>::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
}
}