use crate::spectrum::SpectralData;
pub fn sprague_interpolate(
wavelengths: &[f32],
intensities: &[f32],
output_wavelengths: &[f32],
) -> Vec<f32> {
if wavelengths.len() < 4 {
return linear_interpolate(wavelengths, intensities, output_wavelengths);
}
let mut results = Vec::with_capacity(output_wavelengths.len());
for &out_wl in output_wavelengths {
let idx = wavelengths
.binary_search_by(|w| w.partial_cmp(&out_wl).unwrap_or(std::cmp::Ordering::Equal));
let result = match idx {
Ok(i) => {
intensities[i]
}
Err(i) => {
if i == 0 || i >= wavelengths.len() {
if i == 0 {
intensities[0]
} else {
intensities[wavelengths.len() - 1]
}
} else {
let i0 = i.saturating_sub(2);
let i1 = i.saturating_sub(1);
let i2 = i;
let i3 = if i < wavelengths.len() - 1 {
i + 1
} else {
wavelengths.len() - 1
};
let y0 = intensities[i0];
let y1 = intensities[i1];
let y2 = intensities[i2];
let y3 = intensities[i3];
let h = wavelengths[i2] - wavelengths[i1];
let t = (out_wl - wavelengths[i1]) / h;
sprague_value(t, y0, y1, y2, y3)
}
}
};
results.push(result);
}
results
}
fn sprague_value(t: f32, y0: f32, y1: f32, y2: f32, y3: f32) -> f32 {
let t2 = t * t;
let t3 = t2 * t;
let a0 = y1;
let a1 = -y0 / 6.0 + y2 / 2.0 - y3 / 3.0;
let a2 = y0 / 2.0 - y1 + y2 / 2.0;
let a3 = -y0 / 6.0 + y1 / 2.0 - y2 / 2.0 + y3 / 6.0;
a0 + a1 * t + a2 * t2 + a3 * t3
}
fn linear_interpolate(
wavelengths: &[f32],
intensities: &[f32],
output_wavelengths: &[f32],
) -> Vec<f32> {
let mut results = Vec::with_capacity(output_wavelengths.len());
for &out_wl in output_wavelengths {
let idx = wavelengths
.binary_search_by(|w| w.partial_cmp(&out_wl).unwrap_or(std::cmp::Ordering::Equal));
let value = match idx {
Ok(i) => intensities[i],
Err(i) => {
if i == 0 {
intensities[0]
} else if i >= wavelengths.len() {
intensities[wavelengths.len() - 1]
} else {
let w1 = wavelengths[i - 1];
let w2 = wavelengths[i];
let y1 = intensities[i - 1];
let y2 = intensities[i];
let t = (out_wl - w1) / (w2 - w1);
y1 + t * (y2 - y1)
}
}
};
results.push(value);
}
results
}
pub fn reconstruct_spectrum(spd: &SpectralData, output_resolution: f32) -> Vec<f32> {
let wl_start = 380.0;
let wl_end = 780.0;
let num_samples = ((wl_end - wl_start) / output_resolution) as usize + 1;
let mut output_wls = Vec::with_capacity(num_samples);
for i in 0..num_samples {
output_wls.push(wl_start + i as f32 * output_resolution);
}
let (wavelengths, intensities) = spd.get_wavelength_data();
sprague_interpolate(&wavelengths, &intensities, &output_wls)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sprague_smoothness() {
let wls = vec![380.0, 390.0, 400.0, 410.0, 420.0];
let data = vec![10.0, 20.0, 15.0, 25.0, 30.0];
let interp = sprague_interpolate(&wls, &data, &wls);
for (orig, interp_val) in data.iter().zip(interp.iter()) {
assert!(
(orig - interp_val).abs() < 0.1,
"Mismatch at original wavelength"
);
}
}
#[test]
fn test_sprague_intermediate() {
let wls = vec![380.0, 390.0, 400.0];
let data = vec![10.0, 20.0, 15.0];
let interp = sprague_interpolate(&wls, &data, &[385.0]);
assert!(interp[0] > 10.0 && interp[0] < 20.0);
}
}