#![doc = include_str!("katex.html")]
use core::convert::TryInto;
use embedded_hal::blocking::i2c;
#[cfg_attr(feature = "std", allow(unused_imports))]
use num_traits::Float;
use crate::common::{Address, CalibrationData, MelexisCamera};
use crate::register::Subpage;
use crate::AccessPattern;
const KELVINS_TO_CELSIUS: f32 = 273.15;
#[doc = include_str!("katex.html")]
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())
}
#[doc = include_str!("katex.html")]
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()
}
#[doc = include_str!("katex.html")]
pub fn v_ptat_art<'a, Clb: CalibrationData<'a>>(
calibration: &'a Clb,
t_a_ptat: i16,
t_a_v_be: i16,
) -> f32 {
let denom = f32::from(t_a_ptat) * calibration.alpha_ptat() + f32::from(t_a_v_be);
f32::from(t_a_ptat) / denom * 18f32.exp2()
}
#[doc = include_str!("katex.html")]
pub fn ambient_temperature<'a, Clb: CalibrationData<'a>>(
calibration: &'a Clb,
v_ptat_art: f32,
delta_v: f32,
) -> f32 {
let v_ptat_25 = calibration.v_ptat_25();
let numerator = (v_ptat_art / (1f32 + calibration.k_v_ptat() * delta_v)) - v_ptat_25;
numerator / calibration.k_t_ptat() + 25f32
}
#[doc = include_str!("katex.html")]
#[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,
}
#[doc = include_str!("katex.html")]
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,
})
}
}
#[doc = include_str!("katex.html")]
#[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 = calibration.gain() / f32::from(ram.gain);
Self {
gain,
v_dd,
emissivity,
t_a,
}
}
}
#[doc = include_str!("katex.html")]
#[inline]
pub fn per_pixel_v_ir(
pixel_data: i16,
common: &CommonIrData,
reference_offset: i16,
k_v: f32,
k_ta: f32,
access_mode_compensation: Option<f32>,
) -> f32 {
let pixel_gain = f32::from(pixel_data) * common.gain;
let mut pixel_offset = pixel_gain
+ access_mode_compensation.unwrap_or_default()
- 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,
access_pattern: AccessPattern,
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),
calibration.access_pattern_compensation_cp(subpage, access_pattern),
);
tgc * compensation_pixel_offset
});
let access_mode_compensation = calibration
.access_pattern_compensation_pixels(access_pattern)
.map(|o| o.copied());
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(access_mode_compensation)
.filter(|_| valid_pixels.next().unwrap_or_default())
.for_each(
|(
((((output, pixel_slice), reference_offset), k_v), k_ta),
access_mode_compensation,
)| {
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,
access_mode_compensation,
);
if let Some(compensation_pixel_offset) = compensation_pixel_offset {
pixel_offset -= compensation_pixel_offset;
}
*output = pixel_offset;
},
);
common.t_a
}
#[doc = include_str!("katex.html")]
#[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);
#[allow(clippy::float_cmp)]
if emissivity == 1.0 {
t_a_k4
} else {
let t_r_k4 = (t_r + KELVINS_TO_CELSIUS).powi(4);
t_r_k4 - ((t_r_k4 - t_a_k4) / emissivity)
}
}
#[doc = include_str!("katex.html")]
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)
}
#[doc = include_str!("katex.html")]
#[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
}
#[doc = include_str!("katex.html")]
pub fn raw_ir_to_temperatures<'a, Clb, Px>(
calibration: &'a Clb,
emissivity: f32,
t_a: f32,
t_r: Option<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 k_s_to_basic = calibration.k_s_to()[Clb::Camera::BASIC_TEMPERATURE_RANGE];
let alpha_coefficient = sensitivity_correction_coefficient(calibration, t_a);
let t_r = t_r.unwrap_or_else(|| t_a - Clb::Camera::SELF_HEATING);
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,
t_r: Option<f32>,
resolution_correction: f32,
pixel_data: &[u8],
ram: RamData,
subpage: Subpage,
access_pattern: AccessPattern,
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),
calibration.access_pattern_compensation_cp(subpage, access_pattern),
);
tgc * compensation_pixel_offset
});
let alpha_compensation_pixel = calibration
.temperature_gradient_coefficient()
.map(|tgc| calibration.alpha_cp(subpage) * tgc);
let k_s_to_basic = calibration.k_s_to()[Clb::Camera::BASIC_TEMPERATURE_RANGE];
let alpha_coefficient = sensitivity_correction_coefficient(calibration, common.t_a);
let t_r = t_r.unwrap_or_else(|| common.t_a - Clb::Camera::SELF_HEATING);
let t_ar = t_ar(common.t_a, t_r, emissivity);
let access_mode_compensation = calibration
.access_pattern_compensation_pixels(access_pattern)
.map(|o| o.copied());
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))
.zip(access_mode_compensation)
.filter(|_| valid_pixels.next().unwrap_or_default())
.for_each(
|(
(((((output, pixel_slice), reference_offset), k_v), k_ta), alpha),
access_mode_compensation,
)| {
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,
access_mode_compensation,
);
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)]
#[allow(clippy::excessive_precision)]
mod test {
use float_cmp::assert_approx_eq;
use crate::test::{mlx90640_datasheet_eeprom, mlx90641_datasheet_eeprom};
use crate::{mlx90640, mlx90641, CalibrationData, MelexisCamera, Subpage};
fn mlx90640_calibration() -> mlx90640::Mlx90640Calibration {
let eeprom_data = mlx90640_datasheet_eeprom();
mlx90640::Mlx90640Calibration::from_data(&eeprom_data)
.expect("Mlx90640Calibration should be able to be created from the example data")
}
fn mlx90641_calibration() -> mlx90641::Mlx90641Calibration {
let eeprom_data = mlx90641_datasheet_eeprom();
mlx90641::Mlx90641Calibration::from_data(&eeprom_data)
.expect("Mlx90641Calibration should be able to be created from the example data")
}
#[test]
fn delta_v() {
let clb_640 = mlx90640_calibration();
let clb_641 = mlx90641_calibration();
assert_eq!(super::delta_v(&clb_640, -13115), 59.0 / 3168.0);
assert_eq!(super::delta_v(&clb_641, -13430), 138.0 / -3136.0);
}
#[test]
fn v_dd() {
let clb_640 = mlx90640_calibration();
let clb_641 = mlx90641_calibration();
let resolution_correction = 1.0;
assert_approx_eq!(
f32,
super::v_dd(&clb_640, resolution_correction, 0.018623737),
3.319,
epsilon = 0.001
);
assert_approx_eq!(
f32,
super::v_dd(&clb_641, resolution_correction, -0.0440051),
3.25599,
epsilon = 0.00001
);
}
#[test]
fn v_ptat_art() {
let clb_640 = mlx90640_calibration();
let clb_641 = mlx90641_calibration();
assert_approx_eq!(
f32,
super::v_ptat_art(&clb_640, 1711, 19442),
12873.57952,
epsilon = 0.00001
);
assert_approx_eq!(
f32,
super::v_ptat_art(&clb_641, 1752, 19540),
13007.71,
epsilon = 0.01
);
}
#[test]
fn t_a() {
let clb_640 = mlx90640_calibration();
let clb_641 = mlx90641_calibration();
let delta_v_640 = super::delta_v(&clb_640, -13115);
let delta_v_641 = super::delta_v(&clb_641, -13430);
let v_ptat_art_640 = super::v_ptat_art(&clb_640, 1711, 19442);
let v_ptat_art_641 = super::v_ptat_art(&clb_641, 1752, 19540);
assert_approx_eq!(
f32,
super::ambient_temperature(&clb_640, v_ptat_art_640, delta_v_640),
39.18442383,
epsilon = 0.00000001
);
assert_approx_eq!(
f32,
super::ambient_temperature(&clb_641, v_ptat_art_641, delta_v_641),
42.09766048,
epsilon = 0.001
);
}
#[test]
fn v_ir_640() {
let clb = mlx90640_calibration();
let pixel_index = 11 * mlx90640::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, None);
assert_approx_eq!(f32, v_ir, 700.8974671440075625);
}
#[test]
fn v_ir_641() {
let clb = mlx90641_calibration();
let pixel_index = 5 * mlx90641::Mlx90641::WIDTH + 8;
let offset = clb
.offset_reference_pixels(Subpage::Zero)
.nth(pixel_index)
.unwrap();
let k_v = clb.k_v_pixels(Subpage::Zero).nth(pixel_index).unwrap();
let k_ta = clb.k_ta_pixels(Subpage::Zero).nth(pixel_index).unwrap();
let common = super::CommonIrData {
gain: 1.02445038,
v_dd: 3.25599,
emissivity: 0.949218,
t_a: 42.022,
};
let raw_pixel: i16 = 972;
let v_ir = super::per_pixel_v_ir(raw_pixel, &common, *offset, *k_v, *k_ta, None);
assert_approx_eq!(f32, v_ir, 1784.78049, epsilon = 0.01);
}
#[test]
fn t_ar_640() {
let t_a = 39.184;
let t_r = 31.0;
let emissivity = 1.0;
let t_ar = super::t_ar(t_a, t_r, emissivity);
assert_approx_eq!(f32, t_ar, 9516495632.56);
}
#[test]
fn pixel_temperature_640() {
let clb = mlx90640_calibration();
let basic_range =
<mlx90640::Mlx90640Calibration as CalibrationData>::Camera::BASIC_TEMPERATURE_RANGE;
let k_s_to = clb.k_s_to()[basic_range];
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);
}
#[test]
fn pixel_temperature_641() {
let clb = mlx90641_calibration();
let basic_range =
<mlx90641::Mlx90641Calibration as CalibrationData>::Camera::BASIC_TEMPERATURE_RANGE;
let k_s_to = clb.k_s_to()[basic_range];
let alpha = 3.32641806639731E-7;
let v_ir = 1785f32;
let t_ar = 9899175739.92;
let t_o = super::per_pixel_temperature(v_ir, alpha, t_ar, k_s_to);
assert_approx_eq!(f32, t_o, 80.129812);
}
}