use serde_derive::*;
use crate::flir::FlirCameraParams;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ThermalSettings {
#[serde(
rename = "RelativeHumidity",
deserialize_with = "serde_helpers::float_with_suffix"
)]
relative_humidity_percentage: f64,
emissivity: f64,
#[serde(deserialize_with = "serde_helpers::float_with_suffix")]
reflected_apparent_temperature: f64,
#[serde(
rename = "IRWindowTemperature",
deserialize_with = "serde_helpers::float_with_suffix"
)]
ir_window_temperature: f64,
#[serde(rename = "IRWindowTransmission")]
ir_window_transmission: f64,
planck_r1: f64,
planck_b: f64,
planck_f: f64,
planck_o: f64,
planck_r2: f64,
#[serde(deserialize_with = "serde_helpers::float_with_suffix")]
atmospheric_temperature: f64,
#[serde(rename = "AtmosphericTransAlpha1")]
atmospheric_transmission_alpha_1: f64,
#[serde(rename = "AtmosphericTransAlpha2")]
atmospheric_transmission_alpha_2: f64,
#[serde(rename = "AtmosphericTransBeta1")]
atmospheric_transmission_beta_1: f64,
#[serde(rename = "AtmosphericTransBeta2")]
atmospheric_transmission_beta_2: f64,
#[serde(rename = "AtmosphericTransX")]
atmospheric_transmission_x: f64,
}
const CELICIUS_OFFSET: f64 = 273.15;
impl ThermalSettings {
fn planck_temp_to_raw(&self, temp: f64) -> f64 {
self.planck_r1
/ (self.planck_r2 * ((self.planck_b / (temp + CELICIUS_OFFSET)).exp() - self.planck_f))
- self.planck_o
}
fn planck_raw_to_temp(&self, raw: f64) -> f64 {
self.planck_b
/ (self.planck_r1 / (self.planck_r2 * (raw + self.planck_o)) + self.planck_f).ln()
- CELICIUS_OFFSET
}
fn atmospheric_affine1(&self, val: f64) -> f64 {
self.atmospheric_transmission_alpha_1 + self.atmospheric_transmission_beta_1 * val
}
fn atmospheric_affine2(&self, val: f64) -> f64 {
self.atmospheric_transmission_alpha_2 + self.atmospheric_transmission_beta_2 * val
}
fn atmospheric_interpolate(&self, val1: f64, val2: f64) -> f64 {
self.atmospheric_transmission_x * val1 + (1. - self.atmospheric_transmission_x) * val2
}
pub fn raw_transform(&self, distance: f64) -> impl Fn(f64) -> f64 {
let emiss_wind = 1. - self.ir_window_transmission;
let refl_wind = 0.;
const ATMOSPHERIC_SERIES: [f64; 4] = [1.5587, 0.06939, -0.00027816, 0.00000068455];
let h2o = (self.relative_humidity_percentage / 100.)
* power_series_at(&ATMOSPHERIC_SERIES, self.atmospheric_temperature).exp();
let h2o_sqrt = h2o.sqrt();
let dist_factor = (distance as f64 / 2.).sqrt();
let tau = self.atmospheric_interpolate(
(-dist_factor * self.atmospheric_affine1(h2o_sqrt)).exp(),
(-dist_factor * self.atmospheric_affine2(h2o_sqrt)).exp(),
);
let refl1 = self.planck_temp_to_raw(self.reflected_apparent_temperature);
let refl1_attn = (1. - self.emissivity) / self.emissivity * refl1;
let atm1 = self.planck_temp_to_raw(self.atmospheric_temperature);
let atm1_attn = (1. - tau) / tau / self.emissivity * atm1;
let wind = self.planck_temp_to_raw(self.ir_window_temperature);
let wind_attn = emiss_wind / self.emissivity / tau / self.ir_window_transmission * wind;
let refl2 = self.planck_temp_to_raw(self.reflected_apparent_temperature);
let refl2_attn = refl_wind / self.emissivity / tau / self.ir_window_transmission * refl2;
let atm2 = self.planck_temp_to_raw(self.atmospheric_temperature);
let atm2_attn =
(1. - tau) / self.emissivity / tau / self.ir_window_transmission / tau * atm2;
let coeffs = [
-atm1_attn - atm2_attn - wind_attn - refl1_attn - refl2_attn,
1. / self.emissivity / tau / self.ir_window_transmission / tau,
];
move |raw| power_series_at(&coeffs, raw)
}
pub fn temperature_transform(&self, distance: f64) -> impl Fn(f64) -> f64 + '_ {
let t = self.raw_transform(distance);
move |raw| {
let raw = t(raw);
self.planck_raw_to_temp(raw)
}
}
pub fn raw_to_temp(&self, distance: f64, raw: f64) -> f64 {
self.temperature_transform(distance)(raw)
}
}
impl From<FlirCameraParams> for ThermalSettings {
fn from(params: FlirCameraParams) -> Self {
let FlirCameraParams {
temperature_params,
extra_params,
..
} = params;
ThermalSettings {
relative_humidity_percentage: temperature_params.relative_humidity as f64 * 100.,
emissivity: temperature_params.emissivity as f64,
reflected_apparent_temperature: temperature_params.reflected_apparent_temperature
as f64
- CELICIUS_OFFSET,
ir_window_temperature: temperature_params.ir_window_temperature as f64
- CELICIUS_OFFSET,
ir_window_transmission: temperature_params.ir_window_transmission as f64,
planck_r1: temperature_params.planck_r1 as f64,
planck_b: temperature_params.planck_b as f64,
planck_f: temperature_params.planck_f as f64,
planck_o: extra_params.planck_o as f64,
planck_r2: extra_params.planck_r2 as f64,
atmospheric_temperature: temperature_params.atmospheric_temperature as f64
- CELICIUS_OFFSET,
atmospheric_transmission_alpha_1: temperature_params.atmospheric_transmission_alpha_1
as f64,
atmospheric_transmission_alpha_2: temperature_params.atmospheric_transmission_alpha_2
as f64,
atmospheric_transmission_beta_1: temperature_params.atmospheric_transmission_beta_1
as f64,
atmospheric_transmission_beta_2: temperature_params.atmospheric_transmission_beta_2
as f64,
atmospheric_transmission_x: temperature_params.atmospheric_transmission_x as f64,
}
}
}
#[inline]
fn power_series_at(coeffs: &[f64], x: f64) -> f64 {
let mut pow = 1.;
let mut sum = 0.;
for coeff in coeffs.iter() {
sum += pow * coeff;
pow *= x;
}
sum
}
mod serde_helpers {
use lazy_static::lazy_static;
use regex::Regex;
use serde::*;
lazy_static! {
static ref RE: Regex = Regex::new(r"^\d*.\d*").unwrap();
}
pub fn float_with_suffix<'de, D>(de: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let str_rep = <String as Deserialize>::deserialize(de)?;
let val = RE
.find(&str_rep)
.ok_or(Error::custom("unexpected format: must begin with float"))?
.as_str()
.parse()
.map_err(Error::custom)?;
Ok(val)
}
}