lux-rs 0.1.1

Pure Rust lighting and color science library inspired by LuxPy
Documentation
use crate::color::TristimulusObserver;
use crate::error::{LuxError, LuxResult};
use crate::spectrum::Spectrum;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerType {
    Radiometric,
    Photometric,
    Quantal,
}

const PLANCK_CONSTANT: f64 = 6.626_070_15e-34;
const SPEED_OF_LIGHT: f64 = 299_792_458.0;

pub fn spd_to_xyz(
    spectrum: &Spectrum,
    observer: &TristimulusObserver,
    relative: bool,
) -> LuxResult<[f64; 3]> {
    let wavelengths = spectrum.wavelengths();
    let x_bar = observer.x_bar_spectrum()?.interpolate_linear(wavelengths)?;
    let y_bar = observer.vl_spectrum()?.interpolate_linear(wavelengths)?;
    let z_bar = observer.z_bar_spectrum()?.interpolate_linear(wavelengths)?;
    integrate_xyz(
        spectrum,
        x_bar.values(),
        y_bar.values(),
        z_bar.values(),
        observer.k,
        relative,
    )
}

pub fn spd_to_ler(spectrum: &Spectrum, observer: &TristimulusObserver) -> LuxResult<f64> {
    let photometric = spd_to_power(spectrum, PowerType::Photometric, Some(observer))?;
    let radiometric = spd_to_power(spectrum, PowerType::Radiometric, None)?;
    Ok(photometric / radiometric)
}

pub(crate) fn integrate_xyz(
    spectrum: &Spectrum,
    x_bar: &[f64],
    y_bar: &[f64],
    z_bar: &[f64],
    k: f64,
    relative: bool,
) -> LuxResult<[f64; 3]> {
    let spacing = spectrum.spacing()?;
    let values = spectrum.values();

    let x: f64 = values
        .iter()
        .zip(&spacing)
        .zip(x_bar.iter())
        .map(|((value, dl), x_bar)| value * dl * x_bar)
        .sum();
    let y: f64 = values
        .iter()
        .zip(&spacing)
        .zip(y_bar.iter())
        .map(|((value, dl), y_bar)| value * dl * y_bar)
        .sum();
    let z: f64 = values
        .iter()
        .zip(&spacing)
        .zip(z_bar.iter())
        .map(|((value, dl), z_bar)| value * dl * z_bar)
        .sum();

    let scale = if relative { 100.0 / y } else { k };
    Ok([x * scale, y * scale, z * scale])
}

pub fn spd_to_power(
    spectrum: &Spectrum,
    power_type: PowerType,
    observer: Option<&TristimulusObserver>,
) -> LuxResult<f64> {
    let spacing = spectrum.spacing()?;
    let wavelengths = spectrum.wavelengths();
    let values = spectrum.values();

    let power = match power_type {
        PowerType::Radiometric => values
            .iter()
            .zip(spacing.iter())
            .map(|(value, dl)| value * dl)
            .sum(),
        PowerType::Photometric => {
            let observer = observer.ok_or(LuxError::MissingObserver)?;
            let vl = observer.vl_spectrum()?.interpolate_linear(wavelengths)?;
            values
                .iter()
                .zip(spacing.iter())
                .zip(vl.values().iter())
                .map(|((value, dl), v_lambda)| observer.k * value * dl * v_lambda)
                .sum()
        }
        PowerType::Quantal => {
            let factor = 1e-9 / (PLANCK_CONSTANT * SPEED_OF_LIGHT);
            values
                .iter()
                .zip(spacing.iter())
                .zip(wavelengths.iter())
                .map(|((value, dl), wavelength)| factor * value * dl * wavelength)
                .sum()
        }
    };

    Ok(power)
}