use core::convert::TryInto;
use embedded_hal::blocking::i2c;
use crate::common::{Address, CalibrationData, MelexisCamera};
use crate::register::Subpage;
const KELVINS_TO_CELSIUS: f32 = 273.15;
pub fn delta_v<'a, Clb: CalibrationData<'a>>(calibration: &'a Clb, v_dd_pixel: i16) -> f32 {
f32::from(v_dd_pixel - calibration.v_dd_25()) / f32::from(calibration.k_v_dd())
}
pub fn v_dd<'a, Clb: CalibrationData<'a>>(
calibration: &'a Clb,
resolution_correction: f32,
delta_v: f32,
) -> f32 {
delta_v * resolution_correction + calibration.v_dd_0()
}
pub fn v_ptat_art<'a, Clb: CalibrationData<'a>>(
calibration: &'a Clb,
t_a_ptat: i16,
t_a_v_be: i16,
) -> f32 {
let denom = t_a_ptat as i32 * calibration.alpha_ptat() as i32 + t_a_v_be as i32;
f32::from(t_a_ptat) / denom as f32 * 18f32.exp2()
}
pub fn ambient_temperature<'a, Clb: CalibrationData<'a>>(
calibration: &'a Clb,
v_ptat_art: f32,
delta_v: f32,
) -> f32 {
let k_v_ptat = f32::from(calibration.k_v_ptat()) / 12f32.exp2();
let v_ptat_25 = f32::from(calibration.v_ptat_25());
let numerator = (v_ptat_art / (1f32 + k_v_ptat * delta_v)) - v_ptat_25;
let k_t_ptat = f32::from(calibration.k_t_ptat()) / 3f32.exp2();
numerator / k_t_ptat + 25f32
}
#[derive(Clone, Copy, Debug)]
pub struct RamData {
pub t_a_v_be: i16,
pub t_a_ptat: i16,
pub v_dd_pixel: i16,
pub gain: i16,
pub compensation_pixel: i16,
}
impl RamData {
fn read_ram_value<I2C>(
bus: &mut I2C,
i2c_address: u8,
ram_address: Address,
) -> Result<i16, I2C::Error>
where
I2C: i2c::WriteRead,
{
let address_bytes = ram_address.as_bytes();
let mut scratch = [0u8; 2];
bus.write_read(i2c_address, &address_bytes[..], &mut scratch[..])?;
Ok(i16::from_be_bytes(scratch))
}
pub fn from_i2c<I2C, Cam>(
bus: &mut I2C,
i2c_address: u8,
subpage: Subpage,
) -> Result<Self, I2C::Error>
where
I2C: i2c::WriteRead,
Cam: MelexisCamera,
{
let t_a_v_be = Self::read_ram_value(bus, i2c_address, Cam::t_a_v_be())?;
let t_a_ptat = Self::read_ram_value(bus, i2c_address, Cam::t_a_ptat())?;
let v_dd_pixel = Self::read_ram_value(bus, i2c_address, Cam::v_dd_pixel())?;
let gain = Self::read_ram_value(bus, i2c_address, Cam::gain())?;
let compensation_pixel =
Self::read_ram_value(bus, i2c_address, Cam::compensation_pixel(subpage))?;
Ok(Self {
t_a_v_be,
t_a_ptat,
v_dd_pixel,
gain,
compensation_pixel,
})
}
}
#[derive(Debug, PartialEq)]
pub struct CommonIrData {
pub gain: f32,
pub v_dd: f32,
pub emissivity: f32,
pub t_a: f32,
}
impl CommonIrData {
pub fn new<'a, Clb>(
resolution_correction: f32,
emissivity: f32,
calibration: &'a Clb,
ram: &RamData,
) -> Self
where
Clb: CalibrationData<'a>,
{
let delta_v = delta_v(calibration, ram.v_dd_pixel);
let v_dd = v_dd(calibration, resolution_correction, delta_v);
let v_ptat_art = v_ptat_art(calibration, ram.t_a_ptat, ram.t_a_v_be);
let t_a = ambient_temperature(calibration, v_ptat_art, delta_v);
let gain = f32::from(calibration.gain()) / f32::from(ram.gain);
Self {
gain,
v_dd,
emissivity,
t_a,
}
}
}
#[inline]
pub fn per_pixel_v_ir(
pixel_data: i16,
common: &CommonIrData,
reference_offset: i16,
k_v: f32,
k_ta: f32,
) -> f32 {
let pixel_gain = f32::from(pixel_data) * common.gain;
let mut pixel_offset = pixel_gain
- f32::from(reference_offset)
* (1f32 + k_ta * (common.t_a - 25f32))
* (1f32 + k_v * (common.v_dd - 3.3f32));
pixel_offset /= common.emissivity;
pixel_offset
}
#[allow(clippy::too_many_arguments)]
pub fn raw_pixels_to_ir_data<'a, Clb, Px>(
calibration: &'a Clb,
emissivity: f32,
resolution_correction: f32,
pixel_data: &[u8],
ram: RamData,
subpage: Subpage,
valid_pixels: &mut Px,
destination: &mut [f32],
) -> f32
where
Clb: CalibrationData<'a>,
Px: Iterator<Item = bool>,
{
let common = CommonIrData::new(resolution_correction, emissivity, calibration, &ram);
let compensation_pixel_offset = calibration.temperature_gradient_coefficient().map(|tgc| {
let compensation_pixel_offset = per_pixel_v_ir(
ram.compensation_pixel,
&common,
calibration.offset_reference_cp(subpage),
calibration.k_v_cp(subpage),
calibration.k_ta_cp(subpage),
);
tgc * compensation_pixel_offset
});
destination
.iter_mut()
.zip(pixel_data.chunks_exact(2))
.zip(calibration.offset_reference_pixels(subpage))
.zip(calibration.k_v_pixels(subpage))
.zip(calibration.k_ta_pixels(subpage))
.filter(|_| valid_pixels.next().unwrap_or_default())
.for_each(|((((output, pixel_slice), reference_offset), k_v), k_ta)| {
let pixel_bytes: [u8; 2] = pixel_slice.try_into().unwrap();
let pixel_data = i16::from_be_bytes(pixel_bytes);
let mut pixel_offset =
per_pixel_v_ir(pixel_data, &common, *reference_offset, *k_v, *k_ta);
if let Some(compensation_pixel_offset) = compensation_pixel_offset {
pixel_offset -= compensation_pixel_offset;
}
*output = pixel_offset;
});
common.t_a
}
#[inline]
pub fn t_ar(t_a: f32, t_r: f32, emissivity: f32) -> f32 {
let t_a_k4 = (t_a + KELVINS_TO_CELSIUS).powi(4);
let t_r_k4 = (t_r + KELVINS_TO_CELSIUS).powi(4);
t_r_k4 - ((t_r_k4 - t_a_k4) / emissivity)
}
pub fn sensitivity_correction_coefficient<'a, Clb>(calibration: &'a Clb, t_a: f32) -> f32
where
Clb: CalibrationData<'a>,
{
let k_s_ta = calibration.k_s_ta();
let t_a0 = 25f32;
1f32 + k_s_ta * (t_a - t_a0)
}
#[inline]
pub fn per_pixel_temperature(v_ir: f32, alpha: f32, t_ar: f32, k_s_to: f32) -> f32 {
let s_x = k_s_to * (alpha.powi(3) * v_ir + alpha.powi(4) * t_ar).powf(0.25);
let t_o_root = (v_ir / (alpha * (1f32 - k_s_to * KELVINS_TO_CELSIUS) + s_x) + t_ar).powf(0.25);
t_o_root - KELVINS_TO_CELSIUS
}
pub fn raw_ir_to_temperatures<'a, Clb, Px>(
calibration: &'a Clb,
emissivity: f32,
t_a: f32,
subpage: Subpage,
valid_pixels: &mut Px,
destination: &mut [f32],
) where
Clb: CalibrationData<'a>,
Px: Iterator<Item = bool>,
{
let alpha_compensation_pixel = calibration
.temperature_gradient_coefficient()
.map(|tgc| calibration.alpha_cp(subpage) * tgc);
let basic_index = calibration.basic_range();
let k_s_to_basic = calibration.k_s_to()[basic_index];
let alpha_coefficient = sensitivity_correction_coefficient(calibration, t_a);
let t_r = t_a - 8.0;
let t_ar = t_ar(t_a, t_r, emissivity);
destination
.iter_mut()
.zip(calibration.alpha_pixels(subpage))
.filter(|_| valid_pixels.next().unwrap_or_default())
.for_each(|(output, alpha)| {
let v_ir = *output;
let compensated_alpha = match alpha_compensation_pixel {
Some(alpha_compensation_pixel) => alpha - alpha_compensation_pixel,
None => *alpha,
} * alpha_coefficient;
*output = per_pixel_temperature(v_ir, compensated_alpha, t_ar, k_s_to_basic);
});
}
#[allow(clippy::too_many_arguments)]
pub fn raw_pixels_to_temperatures<'a, Clb, Px>(
calibration: &'a Clb,
emissivity: f32,
resolution_correction: f32,
pixel_data: &[u8],
ram: RamData,
subpage: Subpage,
valid_pixels: &mut Px,
destination: &mut [f32],
) -> f32
where
Clb: CalibrationData<'a>,
Px: Iterator<Item = bool>,
{
let common = CommonIrData::new(resolution_correction, emissivity, calibration, &ram);
let compensation_pixel_offset = calibration.temperature_gradient_coefficient().map(|tgc| {
let compensation_pixel_offset = per_pixel_v_ir(
ram.compensation_pixel,
&common,
calibration.offset_reference_cp(subpage),
calibration.k_v_cp(subpage),
calibration.k_ta_cp(subpage),
);
tgc * compensation_pixel_offset
});
let alpha_compensation_pixel = calibration
.temperature_gradient_coefficient()
.map(|tgc| calibration.alpha_cp(subpage) * tgc);
let basic_index = calibration.basic_range();
let k_s_to_basic = calibration.k_s_to()[basic_index];
let alpha_coefficient = sensitivity_correction_coefficient(calibration, common.t_a);
let t_r = common.t_a - 8.0;
let t_ar = t_ar(common.t_a, t_r, emissivity);
destination
.iter_mut()
.zip(pixel_data.chunks_exact(2))
.zip(calibration.offset_reference_pixels(subpage))
.zip(calibration.k_v_pixels(subpage))
.zip(calibration.k_ta_pixels(subpage))
.zip(calibration.alpha_pixels(subpage))
.filter(|_| valid_pixels.next().unwrap_or_default())
.for_each(
|(((((output, pixel_slice), reference_offset), k_v), k_ta), alpha)| {
let pixel_bytes: [u8; 2] = pixel_slice.try_into().unwrap();
let pixel_data = i16::from_be_bytes(pixel_bytes);
let mut v_ir = per_pixel_v_ir(pixel_data, &common, *reference_offset, *k_v, *k_ta);
if let Some(compensation_pixel_offset) = compensation_pixel_offset {
v_ir -= compensation_pixel_offset;
}
let compensated_alpha = match alpha_compensation_pixel {
Some(alpha_compensation_pixel) => alpha - alpha_compensation_pixel,
None => *alpha,
} * alpha_coefficient;
*output = per_pixel_temperature(v_ir, compensated_alpha, t_ar, k_s_to_basic);
},
);
common.t_a
}
#[cfg(test)]
mod test {
use float_cmp::{approx_eq, F32Margin};
use crate::test::mlx90640_eeprom_data;
use crate::{mlx90640, CalibrationData, Subpage};
fn mlx90640_calibration() -> mlx90640::Mlx90640Calibration {
let eeprom_data = mlx90640_eeprom_data();
mlx90640::Mlx90640Calibration::from_data(&eeprom_data)
.expect("Mlx90640Calibration should be able to be created from the example data")
}
#[test]
fn delta_v() {
let clb = mlx90640_calibration();
assert_eq!(super::delta_v(&clb, -13115), 0.018623737)
}
#[test]
fn v_dd() {
let clb = mlx90640_calibration();
let resolution_correction = 0.5;
approx_eq!(
f32,
super::v_dd(&clb, resolution_correction, 0.018623737),
3.3186,
F32Margin::default()
);
}
#[test]
fn v_ptat_art() {
let clb = mlx90640_calibration();
assert_eq!(super::v_ptat_art(&clb, 1711, 19442), 12873.57952);
}
#[test]
fn t_a() {
let clb = mlx90640_calibration();
let delta_v = super::delta_v(&clb, -13115);
let v_ptat_art = super::v_ptat_art(&clb, 1711, 19442);
approx_eq!(
f32,
super::ambient_temperature(&clb, v_ptat_art, delta_v),
39.18440152,
epsilon = 0.000002
);
}
#[test]
fn v_ir() {
let clb = mlx90640_calibration();
let pixel_index = 11 * mlx90640::WIDTH + 15;
let offset = clb
.offset_reference_pixels(Subpage::One)
.nth(pixel_index)
.unwrap();
let k_v = clb.k_v_pixels(Subpage::One).nth(pixel_index).unwrap();
let k_ta = clb.k_ta_pixels(Subpage::One).nth(pixel_index).unwrap();
let common = super::CommonIrData {
gain: 1.01753546947234,
v_dd: 3.319,
emissivity: 1.0,
t_a: 39.18440152,
};
let raw_pixel: i16 = 609;
let v_ir = super::per_pixel_v_ir(raw_pixel, &common, *offset, *k_v, *k_ta);
approx_eq!(f32, v_ir, 700.882495690866, epsilon = 0.01);
}
#[test]
fn pixel_temperature() {
let clb = mlx90640_calibration();
let basic_index = clb.basic_range();
let k_s_to = clb.k_s_to()[basic_index];
let alpha = 1.1876487360496E-7;
let v_ir = 679.250909123826;
let t_ar = 9516495632.56;
let t_o = super::per_pixel_temperature(v_ir, alpha, t_ar, k_s_to);
assert_eq!(t_o, 80.36331);
}
}