#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
pub fn laplacian_variance(img: &[u8], w: u32, h: u32) -> f32 {
let w = w as usize;
let h = h as usize;
if w < 3 || h < 3 || img.len() < w * h {
return 0.0;
}
let n = (w - 2) * (h - 2); if n == 0 {
return 0.0;
}
let mut sum: f64 = 0.0;
let mut sum_sq: f64 = 0.0;
for row in 1..(h - 1) {
for col in 1..(w - 1) {
let center = img[row * w + col] as f64;
let top = img[(row - 1) * w + col] as f64;
let bottom = img[(row + 1) * w + col] as f64;
let left = img[row * w + (col - 1)] as f64;
let right = img[row * w + (col + 1)] as f64;
let lap = top + bottom + left + right - 4.0 * center;
sum += lap;
sum_sq += lap * lap;
}
}
let mean = sum / n as f64;
let variance = (sum_sq / n as f64) - mean * mean;
variance.max(0.0) as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flat_image_zero_variance() {
let img = vec![128u8; 16 * 16];
let v = laplacian_variance(&img, 16, 16);
assert!(v.abs() < 1e-5, "flat image variance should be ~0, got {v}");
}
#[test]
fn test_edge_image_nonzero_variance() {
let w = 8usize;
let h = 8usize;
let mut img = vec![0u8; w * h];
for row in 0..h {
for col in (w / 2)..w {
img[row * w + col] = 255;
}
}
let v = laplacian_variance(&img, w as u32, h as u32);
assert!(v > 0.0, "step-function edge should give positive variance");
}
#[test]
fn test_too_small_returns_zero() {
assert_eq!(laplacian_variance(&[1, 2, 3, 4], 2, 2), 0.0);
assert_eq!(laplacian_variance(&[], 0, 0), 0.0);
assert_eq!(laplacian_variance(&[100u8; 9], 3, 3).is_nan(), false);
}
#[test]
fn test_checker_sharper_than_ramp() {
let w = 8u32;
let h = 8u32;
let checker: Vec<u8> = (0..(w * h) as usize)
.map(|i| {
let row = i / w as usize;
let col = i % w as usize;
if (row + col) % 2 == 0 {
0
} else {
255
}
})
.collect();
let ramp: Vec<u8> = (0..(w * h) as usize)
.map(|i| {
let col = i % w as usize;
((col as f32 / (w - 1) as f32) * 255.0) as u8
})
.collect();
let v_checker = laplacian_variance(&checker, w, h);
let v_ramp = laplacian_variance(&ramp, w, h);
assert!(
v_checker > v_ramp,
"checker ({v_checker}) should be sharper than ramp ({v_ramp})"
);
}
#[test]
fn test_short_buffer_returns_zero() {
let short = vec![0u8; 5];
assert_eq!(laplacian_variance(&short, 4, 4), 0.0);
}
}