1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
use std::cmp::{Ordering, PartialOrd};
use std::collections::HashMap;

use ndarray::*;

use crate::palettes;

pub trait ThermogramTrait {
    fn thermal(&self) -> &Array<f32, Ix2>;
    fn optical(&self) -> Option<&Array<u8, Ix3>>;
    fn identifier(&self) -> &str;
    fn path(&self) -> Option<&str>;

    fn render_defaults(&self) -> Array<u8, Ix3> {
        self.render(1.0, 10.0, palettes::TURBO)
    }

    fn render_clip_percentiles(&self, _min_p: u8, _max_p: u8, palette: [[f32; 3]; 256]) -> Array<u8, Ix3> {
        self.render(self.min_temp(), self.max_temp(), palette)
    }

    fn render(&self, min_temp: f32, max_temp: f32, palette: [[f32; 3]; 256]) -> Array<u8, Ix3> {
        let num_bands = 3;
        let map_color = |v: &f32| {
            let idx = match (min_temp.partial_cmp(v), max_temp.partial_cmp(v)) {
                (Some(Ordering::Greater), _) => 0,
                (_, Some(Ordering::Less)) => 255,
                (_, _) => ((v - min_temp) / (max_temp - min_temp) * 255f32) as usize,
            };

            let to_u8 = |f| (f * 255.0) as u8;
            let color = [
                // Create color array sized [u8; num_bands]
                to_u8(palette[idx][0]),
                to_u8(palette[idx][1]),
                to_u8(palette[idx][2]),
            ];

            // Create iterator out of the array so we can use this in flat_map
            (0..num_bands).map(move |i| color[i])
        };

        // Convert thermal array into a color array by iterating over all values,
        // converting thermal values to RGB arrays, flattening the result into a
        // single vector of u8s. Lastly we recreate an ndarray with the shape
        // (height, width, num_bands) from this vector.
        // FIXME don't hardcode color map
        let colored_array: Vec<u8> = self.thermal().iter().flat_map(map_color).collect();

        let width = self.thermal().ncols();
        let height = self.thermal().nrows();
        Array::from_shape_vec((height, width, num_bands), colored_array).unwrap()
    }

    fn thermal_shape(&self) -> [usize; 2] {
        let thermal = self.thermal();
        [thermal.nrows(), thermal.ncols()]
    }

    fn metadata(&self) -> HashMap<String, String> {
        HashMap::new() // TODO
    }

    fn as_base64(&self) -> String {
        "".to_string() // TODO
    }

    fn positionally_annotated(&self) -> bool {
        false // TODO
    }

    fn position(&self) -> [f32; 2] {
        [0.0, 0.0] // TODO
    }

    fn direction(&self) -> f32 {
        0.0 // TODO
    }

    fn angle(&self) -> f32 {
        0.0 // TODO
    }

    fn time_stamp(&self) -> u8 {
        0 // TODO
    }

    fn has_optical(&self) -> bool {
        self.optical() == None // TODO
    }

    fn min_temp(&self) -> f32 {
        self.thermal().fold(f32::MAX, |acc, elem| acc.min(*elem))
    }

    fn max_temp(&self) -> f32 {
        self.thermal().fold(f32::MIN, |acc, elem| acc.max(*elem))
    }

    fn normalized_minmax(&self) -> Array<f32, Ix2> {
        let thermal = self.thermal();
        let max_temp = self.max_temp();
        let divider = match max_temp == 0.0 {
            true => self.min_temp() + 0.0000000001,
            false => max_temp,
        };
        (thermal - self.min_temp()) / divider
    }
}