#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct TextureDescriptor {
pub energy: f32,
pub entropy: f32,
pub homogeneity: f32,
pub contrast: f32,
pub correlation: f32,
}
impl TextureDescriptor {
#[must_use]
pub fn is_uniform(&self, threshold: f32) -> bool {
self.energy >= threshold
}
#[must_use]
pub fn is_complex(&self) -> bool {
self.entropy > 3.0 && self.contrast > 10.0
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GlcmMatrix {
pub matrix: Vec<Vec<u32>>,
pub levels: usize,
}
impl GlcmMatrix {
#[must_use]
pub fn new(levels: usize) -> Self {
Self {
matrix: vec![vec![0u32; levels]; levels],
levels,
}
}
pub fn add(&mut self, i: usize, j: usize) {
if i < self.levels && j < self.levels {
self.matrix[i][j] = self.matrix[i][j].saturating_add(1);
}
}
#[must_use]
pub fn normalize(&self) -> Vec<Vec<f32>> {
let total = self.total_count();
if total == 0 {
return vec![vec![0.0_f32; self.levels]; self.levels];
}
let total_f = total as f32;
self.matrix
.iter()
.map(|row| row.iter().map(|&v| v as f32 / total_f).collect())
.collect()
}
#[must_use]
pub fn total_count(&self) -> u32 {
self.matrix.iter().flat_map(|row| row.iter()).sum()
}
}
#[must_use]
pub fn compute_glcm(
pixels: &[u8],
width: usize,
height: usize,
dx: i32,
dy: i32,
levels: usize,
) -> GlcmMatrix {
let mut glcm = GlcmMatrix::new(levels);
if levels == 0 || width == 0 || height == 0 {
return glcm;
}
let scale = levels as f32 / 256.0;
for y in 0..height {
for x in 0..width {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
continue;
}
let src = pixels[y * width + x];
let dst = pixels[ny as usize * width + nx as usize];
let i = ((src as f32 * scale) as usize).min(levels - 1);
let j = ((dst as f32 * scale) as usize).min(levels - 1);
glcm.add(i, j);
glcm.add(j, i);
}
}
glcm
}
#[must_use]
pub fn compute_texture_descriptor(glcm: &GlcmMatrix) -> TextureDescriptor {
let norm = glcm.normalize();
let n = glcm.levels;
let mut energy = 0.0_f32;
let mut entropy = 0.0_f32;
let mut homogeneity = 0.0_f32;
let mut contrast = 0.0_f32;
let mut mean_i = 0.0_f32;
let mut mean_j = 0.0_f32;
let mut std_i = 0.0_f32;
let mut std_j = 0.0_f32;
let mut correlation = 0.0_f32;
for i in 0..n {
for j in 0..n {
let p = norm[i][j];
if p == 0.0 {
continue;
}
energy += p * p;
entropy -= p * p.ln();
let diff = (i as f32 - j as f32).abs();
homogeneity += p / (1.0 + diff);
contrast += diff * diff * p;
mean_i += i as f32 * p;
mean_j += j as f32 * p;
}
}
for i in 0..n {
for j in 0..n {
let p = norm[i][j];
std_i += (i as f32 - mean_i).powi(2) * p;
std_j += (j as f32 - mean_j).powi(2) * p;
}
}
std_i = std_i.sqrt();
std_j = std_j.sqrt();
if std_i > 1e-10 && std_j > 1e-10 {
for i in 0..n {
for j in 0..n {
let p = norm[i][j];
correlation += (i as f32 - mean_i) * (j as f32 - mean_j) * p / (std_i * std_j);
}
}
}
TextureDescriptor {
energy,
entropy,
homogeneity,
contrast,
correlation,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_uniform_true() {
let td = TextureDescriptor {
energy: 0.9,
entropy: 0.1,
homogeneity: 0.95,
contrast: 0.5,
correlation: 0.1,
};
assert!(td.is_uniform(0.8));
}
#[test]
fn test_is_uniform_false() {
let td = TextureDescriptor {
energy: 0.3,
entropy: 2.5,
homogeneity: 0.6,
contrast: 5.0,
correlation: -0.2,
};
assert!(!td.is_uniform(0.5));
}
#[test]
fn test_is_complex_true() {
let td = TextureDescriptor {
energy: 0.01,
entropy: 5.0,
homogeneity: 0.2,
contrast: 50.0,
correlation: 0.0,
};
assert!(td.is_complex());
}
#[test]
fn test_is_complex_false_low_entropy() {
let td = TextureDescriptor {
energy: 0.8,
entropy: 1.0,
homogeneity: 0.9,
contrast: 50.0,
correlation: 0.8,
};
assert!(!td.is_complex());
}
#[test]
fn test_is_complex_false_low_contrast() {
let td = TextureDescriptor {
energy: 0.01,
entropy: 5.0,
homogeneity: 0.2,
contrast: 5.0,
correlation: 0.0,
};
assert!(!td.is_complex());
}
#[test]
fn test_glcm_new_zeroed() {
let glcm = GlcmMatrix::new(4);
assert_eq!(glcm.total_count(), 0);
assert_eq!(glcm.levels, 4);
}
#[test]
fn test_glcm_add_and_total() {
let mut glcm = GlcmMatrix::new(4);
glcm.add(0, 1);
glcm.add(0, 1);
glcm.add(2, 3);
assert_eq!(glcm.total_count(), 3);
assert_eq!(glcm.matrix[0][1], 2);
assert_eq!(glcm.matrix[2][3], 1);
}
#[test]
fn test_glcm_add_out_of_bounds_ignored() {
let mut glcm = GlcmMatrix::new(4);
glcm.add(10, 0); assert_eq!(glcm.total_count(), 0);
}
#[test]
fn test_glcm_normalize_sums_to_one() {
let mut glcm = GlcmMatrix::new(4);
for i in 0..4 {
for j in 0..4 {
glcm.add(i, j);
}
}
let norm = glcm.normalize();
let sum: f32 = norm.iter().flat_map(|row| row.iter()).sum();
assert!((sum - 1.0).abs() < 1e-5);
}
#[test]
fn test_glcm_normalize_empty_returns_zeros() {
let glcm = GlcmMatrix::new(3);
let norm = glcm.normalize();
let sum: f32 = norm.iter().flat_map(|row| row.iter()).sum();
assert!(sum.abs() < 1e-10);
}
#[test]
fn test_compute_glcm_uniform_image() {
let pixels = vec![128u8; 16];
let glcm = compute_glcm(&pixels, 4, 4, 1, 0, 8);
assert!(glcm.total_count() > 0);
}
#[test]
fn test_compute_glcm_zero_levels_returns_empty() {
let pixels = vec![128u8; 16];
let glcm = compute_glcm(&pixels, 4, 4, 1, 0, 0);
assert_eq!(glcm.total_count(), 0);
}
#[test]
fn test_compute_glcm_symmetric_counts() {
let pixels = vec![0u8, 255u8, 0u8, 255u8]; let glcm = compute_glcm(&pixels, 2, 2, 1, 0, 2);
let l = glcm.levels;
for i in 0..l {
for j in 0..l {
assert_eq!(glcm.matrix[i][j], glcm.matrix[j][i]);
}
}
}
#[test]
fn test_texture_descriptor_uniform_image() {
let pixels = vec![100u8; 100]; let glcm = compute_glcm(&pixels, 10, 10, 1, 0, 8);
let td = compute_texture_descriptor(&glcm);
assert!(td.energy > 0.5, "energy={}", td.energy);
assert!(td.contrast < 1.0, "contrast={}", td.contrast);
}
#[test]
fn test_texture_descriptor_energy_in_range() {
let pixels: Vec<u8> = (0..64).map(|i| (i * 4) as u8).collect();
let glcm = compute_glcm(&pixels, 8, 8, 1, 0, 8);
let td = compute_texture_descriptor(&glcm);
assert!(td.energy >= 0.0 && td.energy <= 1.0);
}
#[test]
fn test_texture_descriptor_homogeneity_positive() {
let pixels = vec![50u8; 25]; let glcm = compute_glcm(&pixels, 5, 5, 0, 1, 8);
let td = compute_texture_descriptor(&glcm);
assert!(td.homogeneity > 0.0);
}
#[test]
fn test_texture_descriptor_empty_glcm() {
let glcm = GlcmMatrix::new(8);
let td = compute_texture_descriptor(&glcm);
assert_eq!(td.energy, 0.0);
assert_eq!(td.entropy, 0.0);
assert_eq!(td.contrast, 0.0);
}
}