use crate::Rgb;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UniformityReport {
pub grid_size: usize,
pub luminance_measurements: Vec<f64>,
pub color_measurements: Vec<Rgb>,
pub max_luminance_deviation: f64,
pub avg_luminance_deviation: f64,
pub max_color_deviation: f64,
pub avg_color_deviation: f64,
pub center_luminance: f64,
}
impl UniformityReport {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
grid_size: usize,
luminance_measurements: Vec<f64>,
color_measurements: Vec<Rgb>,
max_luminance_deviation: f64,
avg_luminance_deviation: f64,
max_color_deviation: f64,
avg_color_deviation: f64,
center_luminance: f64,
) -> Self {
Self {
grid_size,
luminance_measurements,
color_measurements,
max_luminance_deviation,
avg_luminance_deviation,
max_color_deviation,
avg_color_deviation,
center_luminance,
}
}
#[must_use]
pub fn is_acceptable(&self, max_luminance_deviation: f64, max_color_deviation: f64) -> bool {
self.max_luminance_deviation <= max_luminance_deviation
&& self.max_color_deviation <= max_color_deviation
}
#[must_use]
pub fn grade(&self) -> char {
if self.max_luminance_deviation < 5.0 {
'A'
} else if self.max_luminance_deviation < 10.0 {
'B'
} else if self.max_luminance_deviation < 15.0 {
'C'
} else if self.max_luminance_deviation < 20.0 {
'D'
} else if self.max_luminance_deviation < 25.0 {
'E'
} else {
'F'
}
}
}
pub struct UniformityTest {
grid_size: usize,
}
impl UniformityTest {
#[must_use]
pub fn new(grid_size: usize) -> Self {
Self { grid_size }
}
#[must_use]
pub fn analyze(
&self,
luminance_measurements: Vec<f64>,
color_measurements: Vec<Rgb>,
) -> UniformityReport {
let center_index = self.grid_size * self.grid_size / 2;
let center_luminance = luminance_measurements
.get(center_index)
.copied()
.unwrap_or(100.0);
let (max_lum_dev, avg_lum_dev) =
self.calculate_luminance_deviation(&luminance_measurements, center_luminance);
let center_color = color_measurements
.get(center_index)
.copied()
.unwrap_or([0.5, 0.5, 0.5]);
let (max_color_dev, avg_color_dev) =
self.calculate_color_deviation(&color_measurements, ¢er_color);
UniformityReport::new(
self.grid_size,
luminance_measurements,
color_measurements,
max_lum_dev,
avg_lum_dev,
max_color_dev,
avg_color_dev,
center_luminance,
)
}
fn calculate_luminance_deviation(&self, measurements: &[f64], reference: f64) -> (f64, f64) {
if measurements.is_empty() || reference <= 0.0 {
return (0.0, 0.0);
}
let mut max_deviation: f64 = 0.0;
let mut sum_deviation = 0.0;
for &lum in measurements {
let deviation = ((lum - reference).abs() / reference) * 100.0;
max_deviation = max_deviation.max(deviation);
sum_deviation += deviation;
}
let avg_deviation = sum_deviation / measurements.len() as f64;
(max_deviation, avg_deviation)
}
fn calculate_color_deviation(&self, measurements: &[Rgb], reference: &Rgb) -> (f64, f64) {
if measurements.is_empty() {
return (0.0, 0.0);
}
let mut max_deviation: f64 = 0.0;
let mut sum_deviation = 0.0;
for color in measurements {
let delta_e = Self::calculate_delta_e(color, reference);
max_deviation = max_deviation.max(delta_e);
sum_deviation += delta_e;
}
let avg_deviation = sum_deviation / measurements.len() as f64;
(max_deviation, avg_deviation)
}
fn calculate_delta_e(color1: &Rgb, color2: &Rgb) -> f64 {
let dr = color1[0] - color2[0];
let dg = color1[1] - color2[1];
let db = color1[2] - color2[2];
(dr * dr + dg * dg + db * db).sqrt() * 100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uniformity_report_new() {
let report = UniformityReport::new(
3,
vec![100.0; 9],
vec![[0.5, 0.5, 0.5]; 9],
5.0,
2.5,
3.0,
1.5,
100.0,
);
assert_eq!(report.grid_size, 3);
assert_eq!(report.luminance_measurements.len(), 9);
assert_eq!(report.color_measurements.len(), 9);
}
#[test]
fn test_uniformity_report_is_acceptable() {
let report = UniformityReport::new(
3,
vec![100.0; 9],
vec![[0.5, 0.5, 0.5]; 9],
5.0,
2.5,
3.0,
1.5,
100.0,
);
assert!(report.is_acceptable(10.0, 5.0));
assert!(!report.is_acceptable(3.0, 5.0));
assert!(!report.is_acceptable(10.0, 2.0));
}
#[test]
fn test_uniformity_report_grade() {
let report_a = UniformityReport::new(
3,
vec![100.0; 9],
vec![[0.5, 0.5, 0.5]; 9],
4.0,
2.0,
3.0,
1.5,
100.0,
);
assert_eq!(report_a.grade(), 'A');
let report_b = UniformityReport::new(
3,
vec![100.0; 9],
vec![[0.5, 0.5, 0.5]; 9],
8.0,
4.0,
3.0,
1.5,
100.0,
);
assert_eq!(report_b.grade(), 'B');
let report_f = UniformityReport::new(
3,
vec![100.0; 9],
vec![[0.5, 0.5, 0.5]; 9],
30.0,
15.0,
3.0,
1.5,
100.0,
);
assert_eq!(report_f.grade(), 'F');
}
#[test]
fn test_uniformity_test_new() {
let test = UniformityTest::new(3);
assert_eq!(test.grid_size, 3);
}
#[test]
fn test_uniformity_test_analyze_perfect() {
let test = UniformityTest::new(3);
let luminance = vec![100.0; 9];
let colors = vec![[0.5, 0.5, 0.5]; 9];
let report = test.analyze(luminance, colors);
assert!((report.max_luminance_deviation - 0.0).abs() < 1e-10);
assert!((report.avg_luminance_deviation - 0.0).abs() < 1e-10);
assert!((report.max_color_deviation - 0.0).abs() < 1e-10);
assert!((report.avg_color_deviation - 0.0).abs() < 1e-10);
assert_eq!(report.grade(), 'A');
}
#[test]
fn test_uniformity_test_analyze_variation() {
let test = UniformityTest::new(3);
let luminance = vec![100.0, 105.0, 95.0, 100.0, 100.0, 100.0, 98.0, 102.0, 100.0];
let colors = vec![[0.5, 0.5, 0.5]; 9];
let report = test.analyze(luminance, colors);
assert!((report.max_luminance_deviation - 5.0).abs() < 0.1);
assert_eq!(report.grade(), 'B');
}
#[test]
fn test_calculate_delta_e() {
let color1 = [0.5, 0.5, 0.5];
let color2 = [0.5, 0.5, 0.5];
let delta_e = UniformityTest::calculate_delta_e(&color1, &color2);
assert!((delta_e - 0.0).abs() < 1e-10);
let color3 = [0.6, 0.5, 0.5];
let delta_e2 = UniformityTest::calculate_delta_e(&color1, &color3);
assert!(delta_e2 > 0.0);
}
}