use core::iter;
use core::slice;
use arrayvec::ArrayVec;
use embedded_hal::blocking::i2c;
#[cfg_attr(feature = "std", allow(unused_imports))]
use num_traits::Float;
use crate::common::*;
use crate::error::{Error, LibraryError};
use crate::expose_member;
use crate::register::{AccessPattern, Subpage};
use crate::util::{i16_from_bits, Buffer};
use super::address::EepromAddress;
use super::Mlx90640;
const NUM_CORNER_TEMPERATURES: usize = 4;
const WORD_SIZE: usize = 16 / 8;
#[derive(Clone, Debug, PartialEq)]
pub struct Mlx90640Calibration {
k_v_dd: i16,
v_dd_25: i16,
resolution: u8,
k_v_ptat: f32,
k_t_ptat: f32,
v_ptat_25: f32,
alpha_ptat: f32,
gain: f32,
k_s_ta: f32,
corner_temperatures: [i16; NUM_CORNER_TEMPERATURES],
k_s_to: [f32; NUM_CORNER_TEMPERATURES],
alpha_correction: [f32; NUM_CORNER_TEMPERATURES],
alpha_pixels: [f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
alpha_cp: [f32; 2],
offset_reference_pixels: [i16; <Self as CalibrationData>::Camera::NUM_PIXELS],
offset_reference_cp: [i16; 2],
k_v_pattern: [f32; 4],
k_v_cp: f32,
k_ta_pixels: [f32; <Self as CalibrationData>::Camera::NUM_PIXELS],
k_ta_cp: f32,
temperature_gradient_coefficient: Option<f32>,
interleave_correction_pixels: [f32; 6],
interleave_correction_cp: f32,
}
impl Mlx90640Calibration {
fn calculate_bulk_pixel_calibration(
data: &mut &[u8],
) -> ([i16; <Self as CalibrationData>::Camera::NUM_PIXELS], u8, u8) {
let (extra_value, row_scale, column_scale, remainder_scale) = {
let scales = word_to_u4s(data);
(scales[0], scales[1], scales[2], scales[3])
};
let offset_average = data.get_i16();
let mut pixel_calibration = [offset_average; Mlx90640::NUM_PIXELS];
const VALUES_PER_DATA_ROW: usize = 4;
for row_chunks in pixel_calibration.chunks_exact_mut(Mlx90640::WIDTH * VALUES_PER_DATA_ROW)
{
let rows_coefficients = word_to_i4s(data);
let rows_coefficients = core::array::IntoIter::new(rows_coefficients)
.map(i16::from)
.map(|coeff| coeff << row_scale)
.rev();
for (row, coefficient) in row_chunks
.chunks_exact_mut(Mlx90640::WIDTH)
.zip(rows_coefficients)
{
row.iter_mut().for_each(|element| *element += coefficient);
}
}
for column_chunk_index in 0..(Mlx90640::WIDTH / VALUES_PER_DATA_ROW) {
let column_coefficients = word_to_i4s(data);
let column_coefficients = column_coefficients
.iter()
.copied()
.map(i16::from)
.map(|coeff| coeff << column_scale)
.rev();
for row in pixel_calibration.chunks_exact_mut(Mlx90640::WIDTH) {
let start_index = column_chunk_index * VALUES_PER_DATA_ROW;
let row_range = start_index..(start_index + VALUES_PER_DATA_ROW);
row[row_range]
.iter_mut()
.zip(column_coefficients.clone())
.for_each(|(element, coefficient)| *element += coefficient);
}
}
(pixel_calibration, remainder_scale, extra_value)
}
fn repeat_chessboard<T>(source_values: [i8; 4]) -> impl Iterator<Item = T>
where
T: From<i8> + core::fmt::Debug,
{
let row_even_col_even = source_values[0];
let row_odd_col_even = source_values[1];
let row_even_col_odd = source_values[2];
let row_odd_col_odd = source_values[3];
let even_row_pattern = core::array::IntoIter::new([row_even_col_even, row_even_col_odd]);
let odd_row_pattern = core::array::IntoIter::new([row_odd_col_even, row_odd_col_odd]);
let even_row = even_row_pattern.cycle().take(Mlx90640::WIDTH).map(T::from);
let odd_row = odd_row_pattern.cycle().take(Mlx90640::WIDTH).map(T::from);
let repeating_rows = even_row.chain(odd_row).cycle();
repeating_rows.take(Mlx90640::NUM_PIXELS)
}
fn generate_k_ta_pixels(data: &mut &[u8]) -> impl Iterator<Item = i16> {
let source_data: ArrayVec<i8, 4> = (0..4).map(|_| data.get_i8()).collect();
let source_data = source_data.into_inner().unwrap();
Self::repeat_chessboard(source_data)
}
fn get_access_pattern_corrections(data: &mut &[u8]) -> (f32, [f32; 6]) {
let word = data.get_u16();
let chess_1_raw = i16_from_bits(&(word & 0x003F).to_be_bytes(), 6);
let chess_2_raw = i16_from_bits(&((word & 0x07C0) >> 6).to_be_bytes(), 5);
let chess_3_raw = i16_from_bits(&((word & 0xF800) >> 11).to_be_bytes(), 5);
let chess_1 = f32::from(chess_1_raw) / 16f32;
let chess_2 = f32::from(chess_2_raw) / 2f32;
let chess_3 = f32::from(chess_3_raw) / 8f32;
let b = chess_2 - chess_3;
let c = chess_2 + chess_3;
let pattern = [-chess_3, b, -c, chess_3, -b, c];
(chess_1, pattern)
}
pub fn from_data(data: &[u8]) -> Result<Self, LibraryError> {
let mut buf = data;
let eeprom_length = usize::from(EepromAddress::End - EepromAddress::Base);
if buf.len() < eeprom_length {
return Err(LibraryError::Other(
"Not enough space left in buffer to be a full EEPROM dump",
));
}
buf.advance(WORD_SIZE * 16);
let (mut offset_reference_pixels, offset_correction_remainder_scale, alpha_ptat) =
Self::calculate_bulk_pixel_calibration(&mut buf);
let alpha_ptat = alpha_ptat / 4 + 8;
let (alpha_pixels, alpha_correction_remainder_scale, alpha_scale_exp) =
Self::calculate_bulk_pixel_calibration(&mut buf);
let alpha_pixels: ArrayVec<f32, { Mlx90640::NUM_PIXELS }> =
core::array::IntoIter::new(alpha_pixels)
.map(f32::from)
.collect();
let mut alpha_pixels = alpha_pixels.into_inner().unwrap();
let alpha_scale = f32::from(alpha_scale_exp + 30).exp2();
let gain = buf.get_i16();
let v_ptat_25 = buf.get_i16();
let (k_v_ptat, kt_ptat_bytes) = word_6_10_split(&mut buf);
let k_v_ptat = f32::from(k_v_ptat) / 4096f32;
let k_t_ptat = f32::from(i16_from_bits(&kt_ptat_bytes, 10)) / 8f32;
let k_v_dd = (buf.get_i8() as i16) << 5;
let v_dd_25 = ((buf.get_u8() as i16) - 256) * (1 << 5) - (1 << 13);
let k_v_avg = word_to_i4s(&mut buf);
let (interleave_correction_cp, interleave_correction_pixels) =
Self::get_access_pattern_corrections(&mut buf);
let lazy_k_ta_pixels = Self::generate_k_ta_pixels(&mut buf);
let unpacked_scales = word_to_u4s(&mut buf);
let resolution = unpacked_scales[0] & 0x3;
let k_v_scale = f32::from(unpacked_scales[1]).exp2();
let k_ta_scale1 = f32::from(unpacked_scales[2] + 8).exp2();
let k_ta_scale2_exp = unpacked_scales[3];
let k_v_pattern: ArrayVec<f32, 4> = core::array::IntoIter::new(k_v_avg)
.map(|v| f32::from(v) / k_v_scale)
.collect();
let k_v_pattern = k_v_pattern.into_inner().unwrap();
let alpha_cp = {
let (alpha_cp_ratio, alpha_cp_bytes) = word_6_10_split(&mut buf);
let alpha_cp_ratio = f32::from(alpha_cp_ratio) / 7f32.exp2();
let alpha_scale_cp = f32::from(alpha_scale_exp + 27).exp2();
let alpha_cp0: f32 = f32::from(u16::from_be_bytes(alpha_cp_bytes)) / alpha_scale_cp;
[alpha_cp0, alpha_cp0 * (1f32 + alpha_cp_ratio)]
};
let offset_reference_cp = {
let (offset_cp_delta, offset_cp_bytes) = word_6_10_split(&mut buf);
let offset_cp0 = i16_from_bits(&offset_cp_bytes, 10);
[offset_cp0, offset_cp0 + i16::from(offset_cp_delta)]
};
let k_v_cp = f32::from(buf.get_i8()) / k_v_scale;
let k_ta_cp = f32::from(buf.get_i8()) / k_ta_scale1;
let k_s_ta = f32::from(buf.get_i8()) / 13f32.exp2();
let temperature_gradient_coefficient = match buf.get_i8() {
0 => None,
n => Some(f32::from(n) / 5f32.exp2()),
};
let mut k_s_to_ranges: ArrayVec<f32, 4> = (0..4).map(|_| f32::from(buf.get_i8())).collect();
k_s_to_ranges.swap(0, 1);
k_s_to_ranges.swap(2, 3);
let mut k_s_to = k_s_to_ranges.into_inner().unwrap();
let unpacked_corner_temps = word_to_u4s(&mut buf);
let corner_temperature_step = i16::from(unpacked_corner_temps[0] & 0x3) * 10;
let ct2 = i16::from(unpacked_corner_temps[2]) * corner_temperature_step;
let ct3 = i16::from(unpacked_corner_temps[1]) * corner_temperature_step + ct2;
let k_s_to_scale = f32::from(unpacked_corner_temps[3] + 8).exp2();
let corner_temperatures = [-40i16, 0, ct2, ct3];
k_s_to.iter_mut().for_each(|k_s_to| *k_s_to /= k_s_to_scale);
let basic_range = <Self as CalibrationData>::Camera::BASIC_TEMPERATURE_RANGE;
let alpha_correction =
alpha_correction_coefficients(basic_range, &corner_temperatures, &k_s_to);
let mut k_ta_pixels = [0f32; Mlx90640::NUM_PIXELS];
offset_reference_pixels
.iter_mut()
.zip(alpha_pixels.iter_mut())
.zip(lazy_k_ta_pixels)
.zip(k_ta_pixels.iter_mut())
.for_each(|(((offset, alpha), k_ta_rc), k_ta)| {
let high = buf.get_u8();
let low = buf.get_u8();
let offset_remainder = i16::from(i8::from_ne_bytes([high & 0xFC]) >> 2);
*offset += offset_remainder << offset_correction_remainder_scale;
let alpha_remainder = (i16::from_be_bytes([high & 0x03, low]) << 6) >> 10;
*alpha += f32::from(alpha_remainder << alpha_correction_remainder_scale);
*alpha /= alpha_scale;
let k_ta_remainder = i16::from(i8::from_ne_bytes([low & 0x0E]) << 4 >> 5);
let k_ta_numerator = f32::from(k_ta_rc + (k_ta_remainder << k_ta_scale2_exp));
*k_ta = k_ta_numerator / k_ta_scale1;
});
Ok(Self {
k_v_dd,
v_dd_25,
resolution,
k_v_ptat,
k_t_ptat,
v_ptat_25: v_ptat_25.into(),
alpha_ptat: alpha_ptat.into(),
gain: gain.into(),
k_s_ta,
corner_temperatures,
k_s_to,
alpha_correction,
alpha_pixels,
alpha_cp,
offset_reference_pixels,
offset_reference_cp,
k_v_pattern,
k_v_cp,
k_ta_pixels,
k_ta_cp,
temperature_gradient_coefficient,
interleave_correction_pixels,
interleave_correction_cp,
})
}
}
impl<I2C> FromI2C<I2C> for Mlx90640Calibration
where
I2C: i2c::WriteRead + i2c::Write,
{
type Error = Error<I2C>;
type Ok = Self;
fn from_i2c(bus: &mut I2C, i2c_address: u8) -> Result<Self, Error<I2C>> {
const EEPROM_LENGTH: usize =
(EepromAddress::End as usize - EepromAddress::Base as usize + 1) * 2;
let mut eeprom_buf = [0u8; EEPROM_LENGTH];
let eeprom_base: Address = EepromAddress::Base.into();
bus.write_read(i2c_address, &eeprom_base.as_bytes(), &mut eeprom_buf)
.map_err(Error::I2cWriteReadError)?;
Ok(Self::from_data(&eeprom_buf)?)
}
}
impl<'a> CalibrationData<'a> for Mlx90640Calibration {
type Camera = Mlx90640;
expose_member!(k_v_dd, i16);
expose_member!(v_dd_25, i16);
expose_member!(resolution, u8);
expose_member!(k_v_ptat, f32);
expose_member!(k_t_ptat, f32);
expose_member!(v_ptat_25, f32);
expose_member!(alpha_ptat, f32);
expose_member!(gain, f32);
expose_member!(k_s_ta, f32);
expose_member!(&corner_temperatures, [i16]);
expose_member!(&k_s_to, [f32]);
expose_member!(&alpha_correction, [f32]);
type OffsetReferenceIterator = slice::Iter<'a, i16>;
fn offset_reference_pixels(&'a self, _subpage: Subpage) -> Self::OffsetReferenceIterator {
self.offset_reference_pixels.iter()
}
fn offset_reference_cp(&self, subpage: Subpage) -> i16 {
self.offset_reference_cp[subpage as usize]
}
type AlphaIterator = slice::Iter<'a, f32>;
fn alpha_pixels(&'a self, _subpage: Subpage) -> Self::AlphaIterator {
self.alpha_pixels.iter()
}
fn alpha_cp(&self, subpage: Subpage) -> f32 {
self.alpha_cp[subpage as usize]
}
type KvIterator = ChessboardIter<'a, f32>;
fn k_v_pixels(&'a self, _subpage: Subpage) -> Self::KvIterator {
ChessboardIter::new(&self.k_v_pattern)
}
fn k_v_cp(&self, _subpage: Subpage) -> f32 {
self.k_v_cp
}
type KtaIterator = slice::Iter<'a, f32>;
fn k_ta_pixels(&'a self, _subpage: Subpage) -> Self::KtaIterator {
self.k_ta_pixels.iter()
}
fn k_ta_cp(&self, _subpage: Subpage) -> f32 {
self.k_ta_cp
}
expose_member!(temperature_gradient_coefficient, Option<f32>);
type AccessPatternCompensation = PixelAccessPatternCompensation<'a>;
fn access_pattern_compensation_pixels(
&'a self,
access_pattern: AccessPattern,
) -> Self::AccessPatternCompensation {
PixelAccessPatternCompensation::new(access_pattern, &self.interleave_correction_pixels[..])
}
fn access_pattern_compensation_cp(
&self,
subpage: Subpage,
access_pattern: AccessPattern,
) -> Option<f32> {
match (subpage, access_pattern) {
(Subpage::One, AccessPattern::Interleave) => Some(self.interleave_correction_cp),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChessboardIter<'a, T: 'a> {
index: usize,
source: &'a [T],
}
impl<'a, T: 'a> ChessboardIter<'a, T> {
fn new(source: &'a [T]) -> Self {
Self { index: 0, source }
}
}
impl<'a, T: 'a> Iterator for ChessboardIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < Mlx90640::NUM_PIXELS {
let row = self.index / Mlx90640::WIDTH;
let column = self.index % Mlx90640::WIDTH;
self.index += 1;
Some(match (row % 2 == 0, column % 2 == 0) {
(true, true) => &self.source[0],
(false, true) => &self.source[1],
(true, false) => &self.source[2],
(false, false) => &self.source[3],
})
} else {
None
}
}
}
#[doc = include_str!("../katex.html")]
#[derive(Clone, Debug)]
pub enum PixelAccessPatternCompensation<'a> {
Chess(iter::Take<iter::Repeat<Option<&'a f32>>>),
Interleave {
index: usize,
patterns: &'a [f32],
},
}
impl<'a> PixelAccessPatternCompensation<'a> {
fn new(access_pattern: AccessPattern, patterns: &'a [f32]) -> Self {
match access_pattern {
AccessPattern::Chess => Self::Chess(iter::repeat(None).take(Mlx90640::NUM_PIXELS)),
AccessPattern::Interleave => Self::Interleave { index: 0, patterns },
}
}
}
impl<'a> Iterator for PixelAccessPatternCompensation<'a> {
type Item = Option<&'a f32>;
fn next(&mut self) -> Option<Self::Item> {
match self {
PixelAccessPatternCompensation::Chess(inner) => inner.next(),
PixelAccessPatternCompensation::Interleave { index, patterns } => {
if *index < Mlx90640::NUM_PIXELS {
let row_sign = (*index / Mlx90640::WIDTH) % 2;
let column_index = match *index % 4 {
0 | 2 => 0,
1 => 1,
3 => 2,
_ => unreachable!(),
};
let pattern_index = row_sign * 3 + column_index;
*index += 1;
Some(Some(&patterns[pattern_index]))
} else {
None
}
}
}
}
}
fn word_6_10_split(data: &mut &[u8]) -> (i8, [u8; 2]) {
let mut word = [data.get_u8(), data.get_u8()];
let six_bit = i8::from_ne_bytes([word[0]]) >> 2;
word[0] &= 0x03;
(six_bit, word)
}
fn word_to_u4s(data: &mut &[u8]) -> [u8; 4] {
let high = data.get_u8();
let low = data.get_u8();
[(high & 0xF0) >> 4, high & 0xF, (low & 0xF0) >> 4, low & 0xF]
}
fn u8_to_i4s(byte: u8) -> [i8; 2] {
let high = byte & 0xF0;
let low = (byte & 0xF) << 4;
let high = i8::from_ne_bytes([high]);
let low = i8::from_ne_bytes([low]);
[high >> 4, low >> 4]
}
fn word_to_i4s(data: &mut &[u8]) -> [i8; 4] {
let high = data.get_u8();
let low = data.get_u8();
let high = u8_to_i4s(high);
let low = u8_to_i4s(low);
[high[0], high[1], low[0], low[1]]
}
#[cfg(test)]
#[allow(clippy::excessive_precision)]
pub(crate) mod test {
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::{print, println};
use arrayvec::ArrayVec;
use float_cmp::{assert_approx_eq, ApproxEq};
use crate::common::{CalibrationData, MelexisCamera};
use crate::mlx90640::Mlx90640;
use crate::register::{AccessPattern, Subpage};
use crate::test::{mlx90640_datasheet_eeprom, mlx90640_example_data};
use super::Mlx90640Calibration;
fn datasheet_eeprom() -> Mlx90640Calibration {
let eeprom_bytes = mlx90640_datasheet_eeprom();
Mlx90640Calibration::from_data(&eeprom_bytes).expect("The EEPROM data to be parsed.")
}
fn example_eeprom() -> Mlx90640Calibration {
Mlx90640Calibration::from_data(mlx90640_example_data::EEPROM_DATA)
.expect("The example data should be parseable")
}
#[test]
fn word_6_10_split() {
fn check(mut data: &[u8], little: i8, remainder: [u8; 2]) {
let split = super::word_6_10_split(&mut data);
assert_eq!(
split.0, little,
"word_6_10_split failed to split the upper 6 bits off"
);
assert_eq!(
split.1, remainder,
"word_6_10_split failed to split the lower 10 bits off"
);
}
check(b"\xfc\x00", -1, [0x00, 0x00]);
check(b"\x03\xff", 0, [0x03, 0xFF]);
check(b"\x83\xff", -32, [0x03, 0xFF]);
}
#[test]
fn word_to_u4s() {
let mut sequence: &[u8] = b"\x12\x34";
assert_eq!(super::word_to_u4s(&mut sequence), [1, 2, 3, 4]);
let mut max_min: &[u8] = b"\xf0\xf0";
assert_eq!(super::word_to_u4s(&mut max_min), [0xF, 0, 0xF, 0]);
let mut min_max: &[u8] = b"\x0f\x0f";
assert_eq!(super::word_to_u4s(&mut min_max), [0, 0xF, 0, 0xF]);
}
#[test]
fn u8_to_i4s() {
assert_eq!(super::u8_to_i4s(0x44), [4, 4]);
assert_eq!(super::u8_to_i4s(0x88), [-8, -8]);
assert_eq!(super::u8_to_i4s(0x48), [4, -8]);
assert_eq!(super::u8_to_i4s(0x84), [-8, 4]);
}
#[test]
fn repeat_chessboard() {
let pattern = [1, 2, 3, 4];
let test_pattern: ArrayVec<i8, { Mlx90640::NUM_PIXELS }> =
Mlx90640Calibration::repeat_chessboard(pattern).collect();
#[cfg(feature = "std")]
for row in 0..Mlx90640::HEIGHT {
for column in 0..Mlx90640::WIDTH {
let index = row * Mlx90640::WIDTH + column;
print!("{} ", test_pattern[index]);
}
println!();
}
for column in 0..Mlx90640::WIDTH {
for row in 0..Mlx90640::HEIGHT {
let index = row * Mlx90640::WIDTH + column;
let expected = match (row % 2, column % 2) {
(0, 0) => 1,
(1, 0) => 2,
(0, 1) => 3,
(1, 1) => 4,
(_, _) => unreachable!(),
};
assert_eq!(
test_pattern[index], expected,
"pattern incorrect at index {}",
index
);
}
}
}
#[test]
fn smoke() {
datasheet_eeprom();
example_eeprom();
}
#[test]
fn resolution() {
assert_eq!(datasheet_eeprom().resolution(), 2);
assert_eq!(
example_eeprom().resolution(),
mlx90640_example_data::RESOLUTION_EE
);
}
#[test]
fn k_v_dd() {
assert_eq!(datasheet_eeprom().k_v_dd(), -3168);
assert_eq!(example_eeprom().k_v_dd(), mlx90640_example_data::K_V_DD);
}
#[test]
fn v_dd_25() {
assert_eq!(datasheet_eeprom().v_dd_25(), -13056);
assert_eq!(example_eeprom().v_dd_25(), mlx90640_example_data::V_DD_25);
}
#[test]
fn v_dd_0() {
assert_eq!(datasheet_eeprom().v_dd_0(), 3.3);
assert_eq!(example_eeprom().v_dd_0(), 3.3);
}
#[test]
fn k_v_ptat() {
assert_approx_eq!(
f32,
datasheet_eeprom().k_v_ptat(),
0.005371094,
epsilon = 0.000000001
);
assert_approx_eq!(
f32,
example_eeprom().k_v_ptat(),
mlx90640_example_data::K_V_PTAT,
epsilon = 0.00001
);
}
#[test]
fn k_t_ptat() {
assert_eq!(datasheet_eeprom().k_t_ptat(), 42.25);
assert_eq!(example_eeprom().k_t_ptat(), mlx90640_example_data::K_T_PTAT);
}
#[test]
fn v_ptat_25() {
assert_eq!(datasheet_eeprom().v_ptat_25(), 12273f32);
assert_eq!(
example_eeprom().v_ptat_25(),
mlx90640_example_data::V_PTAT_25
);
}
#[test]
fn alpha_ptat() {
assert_eq!(datasheet_eeprom().alpha_ptat(), 9f32);
assert_eq!(
example_eeprom().alpha_ptat(),
mlx90640_example_data::ALPHA_PTAT
);
}
#[test]
fn gain() {
assert_eq!(datasheet_eeprom().gain(), 6383f32);
assert_eq!(example_eeprom().gain(), mlx90640_example_data::GAIN_EE);
}
fn test_pixels_common<T: PartialEq + core::fmt::Debug + core::fmt::Display + Copy>(
datasheet_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
example_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
datasheet_expected: T,
example_expected: &[T; Mlx90640::NUM_PIXELS],
subpage: Option<Subpage>,
check: &dyn Fn(T, T) -> bool,
) {
if subpage.is_none() {
assert_eq!(datasheet_data[0], datasheet_data[1]);
assert_eq!(example_data[0], example_data[1]);
}
let datasheet_index = 11 * Mlx90640::WIDTH + 15;
let subpage_index: usize = subpage.unwrap_or(Subpage::Zero).into();
let pixel = datasheet_data[subpage_index][datasheet_index];
assert!(
check(pixel, datasheet_expected),
"[datasheet pixel]:\n{:>10}: `{:?}`,\n{:>10}: `{:?}`,",
"expected",
datasheet_expected,
"actual",
pixel
);
let offset_pairs = example_data[subpage_index]
.iter()
.zip(example_expected.iter());
for (index, (actual, expected)) in offset_pairs.enumerate() {
assert!(
check(*actual, *expected),
"[pixel {:?}]:\n{:>10}: `{:?}`,\n{:>10}: `{:?}`,",
index,
"expected",
expected,
"actual",
actual
);
}
}
fn test_pixels_approx<T>(
datasheet_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
example_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
datasheet_expected: T,
example_expected: &[T; Mlx90640::NUM_PIXELS],
subpage: Option<Subpage>,
margin: Option<<T as ApproxEq>::Margin>,
) where
T: ApproxEq + PartialEq + core::fmt::Debug + core::fmt::Display + Copy,
{
let check = |actual: T, expected: T| actual.approx_eq(expected, margin.unwrap_or_default());
test_pixels_common(
datasheet_data,
example_data,
datasheet_expected,
example_expected,
subpage,
&check,
);
}
fn test_pixels<T: PartialEq + core::fmt::Debug + core::fmt::Display + Copy>(
datasheet_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
example_data: [ArrayVec<T, { Mlx90640::NUM_PIXELS }>; 2],
datasheet_expected: T,
example_expected: &[T; Mlx90640::NUM_PIXELS],
subpage: Option<Subpage>,
) {
let check = |actual: T, expected: T| actual == expected;
test_pixels_common(
datasheet_data,
example_data,
datasheet_expected,
example_expected,
subpage,
&check,
);
}
#[test]
fn pixel_offset() {
let datasheet = datasheet_eeprom();
let datasheet_offsets: [ArrayVec<i16, { Mlx90640::NUM_PIXELS }>; 2] = [
datasheet
.offset_reference_pixels(Subpage::Zero)
.copied()
.collect(),
datasheet
.offset_reference_pixels(Subpage::One)
.copied()
.collect(),
];
let example = example_eeprom();
let example_offsets: [ArrayVec<i16, { Mlx90640::NUM_PIXELS }>; 2] = [
example
.offset_reference_pixels(Subpage::Zero)
.copied()
.collect(),
example
.offset_reference_pixels(Subpage::One)
.copied()
.collect(),
];
test_pixels(
datasheet_offsets,
example_offsets,
-75,
&mlx90640_example_data::OFFSET_REFERENCE_PIXELS,
None,
);
}
#[test]
fn pixel_k_ta() {
let datasheet = datasheet_eeprom();
let datasheet_k_ta: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
datasheet.k_ta_pixels(Subpage::Zero).copied().collect(),
datasheet.k_ta_pixels(Subpage::One).copied().collect(),
];
let example = example_eeprom();
let example_k_ta: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
example.k_ta_pixels(Subpage::Zero).copied().collect(),
example.k_ta_pixels(Subpage::One).copied().collect(),
];
test_pixels_approx(
datasheet_k_ta,
example_k_ta,
0.005126953125,
&mlx90640_example_data::K_TA_PIXELS,
None,
Some((10E-8, 2).into()),
);
}
#[test]
fn k_v_pixels() {
let datasheet = datasheet_eeprom();
let datasheet_k_v: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
datasheet.k_v_pixels(Subpage::Zero).copied().collect(),
datasheet.k_v_pixels(Subpage::One).copied().collect(),
];
let example = example_eeprom();
let example_k_v: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
example.k_v_pixels(Subpage::Zero).copied().collect(),
example.k_v_pixels(Subpage::One).copied().collect(),
];
test_pixels_approx(
datasheet_k_v,
example_k_v,
0.5f32,
&mlx90640_example_data::K_V_PIXELS,
None,
None,
);
}
#[test]
fn emissivity() {
assert_eq!(datasheet_eeprom().emissivity(), None);
assert_eq!(example_eeprom().emissivity(), None);
}
#[test]
fn offset_reference_cp() {
let datasheet = datasheet_eeprom();
assert_eq!(datasheet.offset_reference_cp(Subpage::Zero), -75);
assert_eq!(datasheet.offset_reference_cp(Subpage::One), -77);
let example = example_eeprom();
assert_eq!(
example.offset_reference_cp(Subpage::Zero),
mlx90640_example_data::OFFSET_CP[0],
);
assert_eq!(
example.offset_reference_cp(Subpage::One),
mlx90640_example_data::OFFSET_CP[1],
);
}
#[test]
fn k_ta_cp() {
let datasheet = datasheet_eeprom();
let expected = 0.00457763671875;
assert_approx_eq!(f32, datasheet.k_ta_cp(Subpage::Zero), expected);
assert_approx_eq!(f32, datasheet.k_ta_cp(Subpage::One), expected);
let example = example_eeprom();
assert_approx_eq!(
f32,
example.k_ta_cp(Subpage::Zero),
mlx90640_example_data::K_TA_CP,
epsilon = 10E-6
);
assert_approx_eq!(
f32,
example.k_ta_cp(Subpage::One),
mlx90640_example_data::K_TA_CP,
epsilon = 10E-6
);
}
#[test]
fn k_v_cp() {
let datasheet = datasheet_eeprom();
let expected = 0.5;
assert_eq!(datasheet.k_v_cp(Subpage::Zero), expected);
assert_eq!(datasheet.k_v_cp(Subpage::One), expected);
let example = example_eeprom();
assert_eq!(example.k_v_cp(Subpage::Zero), mlx90640_example_data::K_V_CP);
assert_eq!(example.k_v_cp(Subpage::One), mlx90640_example_data::K_V_CP);
}
#[test]
fn temperature_gradient_coefficient() {
assert_eq!(
datasheet_eeprom().temperature_gradient_coefficient(),
Some(1f32)
);
assert_eq!(
example_eeprom().temperature_gradient_coefficient(),
None,
);
}
#[test]
fn alpha_cp() {
let datasheet = datasheet_eeprom();
assert_approx_eq!(
f32,
datasheet.alpha_cp(Subpage::Zero),
4.07453626394272E-9,
epsilon = 10E-11
);
assert_approx_eq!(
f32,
datasheet.alpha_cp(Subpage::One),
3.851710062200835E-9,
epsilon = 10E-11
);
let example = example_eeprom();
assert_approx_eq!(
f32,
example.alpha_cp(Subpage::Zero),
mlx90640_example_data::ALPHA_CP[0],
epsilon = 10E-19
);
assert_approx_eq!(
f32,
example.alpha_cp(Subpage::One),
mlx90640_example_data::ALPHA_CP[1],
epsilon = 10E-19
);
}
#[test]
fn k_s_ta() {
assert_approx_eq!(
f32,
datasheet_eeprom().k_s_ta(),
-0.001953125,
epsilon = 10E-9
);
assert_approx_eq!(
f32,
example_eeprom().k_s_ta(),
mlx90640_example_data::K_S_TA,
epsilon = 10E-6
);
}
#[test]
fn pixel_alpha() {
let datasheet = datasheet_eeprom();
let datasheet_alpha: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
datasheet.alpha_pixels(Subpage::Zero).copied().collect(),
datasheet.alpha_pixels(Subpage::One).copied().collect(),
];
let example = example_eeprom();
let example_alpha: [ArrayVec<f32, { Mlx90640::NUM_PIXELS }>; 2] = [
example.alpha_pixels(Subpage::Zero).copied().collect(),
example.alpha_pixels(Subpage::One).copied().collect(),
];
test_pixels_approx(
datasheet_alpha,
example_alpha,
1.262233122690854E-7,
&mlx90640_example_data::ALPHA_PIXELS,
None,
Some((10E-19, 2).into()),
);
}
#[test]
fn k_s_to() {
assert_approx_eq!(
f32,
datasheet_eeprom().k_s_to()[1],
-0.00080108642578125,
epsilon = 10E-17
);
let example = example_eeprom();
let example_pairs = example
.k_s_to()
.iter()
.zip(mlx90640_example_data::K_S_TO.iter());
for (actual, expected) in example_pairs {
assert_approx_eq!(
f32,
*actual,
*expected,
epsilon = 10E-6
);
}
}
#[test]
fn corner_temperatures() {
let e = datasheet_eeprom();
let ct = e.corner_temperatures();
assert_eq!(ct[0], -40);
assert_eq!(ct[1], 0);
assert_eq!(ct[2], 160);
assert_eq!(ct[3], 320);
assert_eq!(
example_eeprom().corner_temperatures(),
mlx90640_example_data::CORNER_TEMPERATURES
);
}
#[test]
fn access_pattern_compensation_chess() {
let e = datasheet_eeprom();
for compensation in e.access_pattern_compensation_pixels(AccessPattern::Chess) {
assert!(
compensation.is_none(),
"There is no access pattern compensation in chess mode"
);
}
}
#[test]
fn access_pattern_compensation_interleave() {
let e = datasheet_eeprom();
const IL_CHESS_3: f32 = 0.125;
const IL_CHESS_2: f32 = 3.0;
const A: f32 = IL_CHESS_3;
const B: f32 = IL_CHESS_2 - IL_CHESS_3;
const C: f32 = IL_CHESS_2 + IL_CHESS_3;
let compensation_iter = e
.access_pattern_compensation_pixels(AccessPattern::Interleave)
.enumerate();
let mut count = 0;
for (pixel_index, compensation) in compensation_iter {
let row = pixel_index / Mlx90640::WIDTH;
let column = pixel_index % Mlx90640::WIDTH;
let expected = if column % 2 == 0 {
-A
} else if column % 4 == 3 {
-C
} else {
B
};
let expected = if row % 2 == 0 { expected } else { -expected };
assert_eq!(
Some(&expected),
compensation,
"[pixel {:?}]:\n{:>10}: `{:?}`,\n{:>10}: `{:?}`,",
pixel_index,
"expected",
Some(expected),
"actual",
compensation
);
count += 1;
}
assert_eq!(count, Mlx90640::NUM_PIXELS);
}
}