1use crate::error::{AlgorithmError, Result};
36
37#[allow(clippy::too_many_arguments)]
56pub fn glcm_construct_simd(
57 quantized: &[u8],
58 glcm: &mut [f32],
59 width: usize,
60 height: usize,
61 gray_levels: usize,
62 dx: i64,
63 dy: i64,
64) -> Result<()> {
65 if width == 0 || height == 0 {
66 return Err(AlgorithmError::InvalidParameter {
67 parameter: "dimensions",
68 message: "Width and height must be greater than zero".to_string(),
69 });
70 }
71
72 if gray_levels == 0 || gray_levels > 256 {
73 return Err(AlgorithmError::InvalidParameter {
74 parameter: "gray_levels",
75 message: "Gray levels must be between 1 and 256".to_string(),
76 });
77 }
78
79 if quantized.len() != width * height {
80 return Err(AlgorithmError::InvalidParameter {
81 parameter: "quantized",
82 message: "Quantized data size must match width * height".to_string(),
83 });
84 }
85
86 if glcm.len() != gray_levels * gray_levels {
87 return Err(AlgorithmError::InvalidParameter {
88 parameter: "glcm",
89 message: "GLCM size must be gray_levels * gray_levels".to_string(),
90 });
91 }
92
93 const LANES: usize = 8;
95 let chunks = glcm.len() / LANES;
96
97 for i in 0..chunks {
98 let start = i * LANES;
99 let end = start + LANES;
100 for j in start..end {
101 glcm[j] = 0.0;
102 }
103 }
104
105 let remainder_start = chunks * LANES;
106 for i in remainder_start..glcm.len() {
107 glcm[i] = 0.0;
108 }
109
110 for y in 0..height {
112 let ny = (y as i64 + dy) as usize;
113 if ny >= height {
114 continue;
115 }
116
117 for x in 0..width {
118 let nx = (x as i64 + dx) as usize;
119 if nx >= width {
120 continue;
121 }
122
123 let i = quantized[y * width + x] as usize;
124 let j = quantized[ny * width + nx] as usize;
125
126 if i < gray_levels && j < gray_levels {
127 glcm[i * gray_levels + j] += 1.0;
128 }
129 }
130 }
131
132 Ok(())
133}
134
135pub fn glcm_normalize_simd(glcm: &mut [f32], gray_levels: usize) -> Result<()> {
148 if glcm.len() != gray_levels * gray_levels {
149 return Err(AlgorithmError::InvalidParameter {
150 parameter: "glcm",
151 message: "GLCM size must be gray_levels * gray_levels".to_string(),
152 });
153 }
154
155 let mut sum = 0.0_f32;
157 const LANES: usize = 8;
158 let chunks = glcm.len() / LANES;
159
160 for i in 0..chunks {
161 let start = i * LANES;
162 let end = start + LANES;
163
164 for j in start..end {
165 sum += glcm[j];
166 }
167 }
168
169 let remainder_start = chunks * LANES;
170 for i in remainder_start..glcm.len() {
171 sum += glcm[i];
172 }
173
174 if sum == 0.0 {
175 return Ok(()); }
177
178 for i in 0..chunks {
180 let start = i * LANES;
181 let end = start + LANES;
182
183 for j in start..end {
184 glcm[j] /= sum;
185 }
186 }
187
188 for i in remainder_start..glcm.len() {
189 glcm[i] /= sum;
190 }
191
192 Ok(())
193}
194
195pub fn texture_contrast_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
208 if glcm.len() != gray_levels * gray_levels {
209 return Err(AlgorithmError::InvalidParameter {
210 parameter: "glcm",
211 message: "GLCM size must be gray_levels * gray_levels".to_string(),
212 });
213 }
214
215 let mut contrast = 0.0_f32;
216
217 for i in 0..gray_levels {
219 let row_offset = i * gray_levels;
220
221 const LANES: usize = 8;
223 let chunks = gray_levels / LANES;
224
225 for chunk in 0..chunks {
226 let j_start = chunk * LANES;
227 let j_end = j_start + LANES;
228
229 for j in j_start..j_end {
230 let diff = (i as i64 - j as i64) as f32;
231 contrast += diff * diff * glcm[row_offset + j];
232 }
233 }
234
235 let remainder_start = chunks * LANES;
237 for j in remainder_start..gray_levels {
238 let diff = (i as i64 - j as i64) as f32;
239 contrast += diff * diff * glcm[row_offset + j];
240 }
241 }
242
243 Ok(contrast)
244}
245
246pub fn texture_energy_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
259 if glcm.len() != gray_levels * gray_levels {
260 return Err(AlgorithmError::InvalidParameter {
261 parameter: "glcm",
262 message: "GLCM size must be gray_levels * gray_levels".to_string(),
263 });
264 }
265
266 let mut energy = 0.0_f32;
267
268 const LANES: usize = 8;
270 let chunks = glcm.len() / LANES;
271
272 for i in 0..chunks {
273 let start = i * LANES;
274 let end = start + LANES;
275
276 for j in start..end {
277 energy += glcm[j] * glcm[j];
278 }
279 }
280
281 let remainder_start = chunks * LANES;
282 for i in remainder_start..glcm.len() {
283 energy += glcm[i] * glcm[i];
284 }
285
286 Ok(energy)
287}
288
289pub fn texture_entropy_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
302 if glcm.len() != gray_levels * gray_levels {
303 return Err(AlgorithmError::InvalidParameter {
304 parameter: "glcm",
305 message: "GLCM size must be gray_levels * gray_levels".to_string(),
306 });
307 }
308
309 let mut entropy = 0.0_f32;
310
311 const LANES: usize = 8;
313 let chunks = glcm.len() / LANES;
314
315 for i in 0..chunks {
316 let start = i * LANES;
317 let end = start + LANES;
318
319 for j in start..end {
320 let p = glcm[j];
321 if p > 0.0 {
322 entropy -= p * p.ln();
323 }
324 }
325 }
326
327 let remainder_start = chunks * LANES;
328 for i in remainder_start..glcm.len() {
329 let p = glcm[i];
330 if p > 0.0 {
331 entropy -= p * p.ln();
332 }
333 }
334
335 Ok(entropy)
336}
337
338pub fn texture_homogeneity_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
351 if glcm.len() != gray_levels * gray_levels {
352 return Err(AlgorithmError::InvalidParameter {
353 parameter: "glcm",
354 message: "GLCM size must be gray_levels * gray_levels".to_string(),
355 });
356 }
357
358 let mut homogeneity = 0.0_f32;
359
360 for i in 0..gray_levels {
362 let row_offset = i * gray_levels;
363
364 const LANES: usize = 8;
365 let chunks = gray_levels / LANES;
366
367 for chunk in 0..chunks {
368 let j_start = chunk * LANES;
369 let j_end = j_start + LANES;
370
371 for j in j_start..j_end {
372 let diff = (i as i64 - j as i64) as f32;
373 homogeneity += glcm[row_offset + j] / (1.0 + diff * diff);
374 }
375 }
376
377 let remainder_start = chunks * LANES;
378 for j in remainder_start..gray_levels {
379 let diff = (i as i64 - j as i64) as f32;
380 homogeneity += glcm[row_offset + j] / (1.0 + diff * diff);
381 }
382 }
383
384 Ok(homogeneity)
385}
386
387pub fn texture_correlation_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
400 if glcm.len() != gray_levels * gray_levels {
401 return Err(AlgorithmError::InvalidParameter {
402 parameter: "glcm",
403 message: "GLCM size must be gray_levels * gray_levels".to_string(),
404 });
405 }
406
407 let mut px = vec![0.0_f32; gray_levels];
409 let mut py = vec![0.0_f32; gray_levels];
410
411 for i in 0..gray_levels {
412 let row_offset = i * gray_levels;
413
414 const LANES: usize = 8;
415 let chunks = gray_levels / LANES;
416
417 for chunk in 0..chunks {
418 let j_start = chunk * LANES;
419 let j_end = j_start + LANES;
420
421 for j in j_start..j_end {
422 let val = glcm[row_offset + j];
423 px[i] += val;
424 py[j] += val;
425 }
426 }
427
428 let remainder_start = chunks * LANES;
429 for j in remainder_start..gray_levels {
430 let val = glcm[row_offset + j];
431 px[i] += val;
432 py[j] += val;
433 }
434 }
435
436 let mut mu_x = 0.0_f32;
438 let mut mu_y = 0.0_f32;
439
440 for i in 0..gray_levels {
441 mu_x += i as f32 * px[i];
442 mu_y += i as f32 * py[i];
443 }
444
445 let mut sigma_x = 0.0_f32;
447 let mut sigma_y = 0.0_f32;
448
449 for i in 0..gray_levels {
450 let dx = i as f32 - mu_x;
451 let dy = i as f32 - mu_y;
452 sigma_x += dx * dx * px[i];
453 sigma_y += dy * dy * py[i];
454 }
455
456 sigma_x = sigma_x.sqrt();
457 sigma_y = sigma_y.sqrt();
458
459 if sigma_x == 0.0 || sigma_y == 0.0 {
460 return Ok(0.0);
461 }
462
463 let mut correlation = 0.0_f32;
465
466 for i in 0..gray_levels {
467 let row_offset = i * gray_levels;
468
469 const LANES: usize = 8;
470 let chunks = gray_levels / LANES;
471
472 for chunk in 0..chunks {
473 let j_start = chunk * LANES;
474 let j_end = j_start + LANES;
475
476 for j in j_start..j_end {
477 let term = ((i as f32 - mu_x) * (j as f32 - mu_y) * glcm[row_offset + j])
478 / (sigma_x * sigma_y);
479 correlation += term;
480 }
481 }
482
483 let remainder_start = chunks * LANES;
484 for j in remainder_start..gray_levels {
485 let term = ((i as f32 - mu_x) * (j as f32 - mu_y) * glcm[row_offset + j])
486 / (sigma_x * sigma_y);
487 correlation += term;
488 }
489 }
490
491 Ok(correlation)
492}
493
494pub fn texture_dissimilarity_simd(glcm: &[f32], gray_levels: usize) -> Result<f32> {
507 if glcm.len() != gray_levels * gray_levels {
508 return Err(AlgorithmError::InvalidParameter {
509 parameter: "glcm",
510 message: "GLCM size must be gray_levels * gray_levels".to_string(),
511 });
512 }
513
514 let mut dissimilarity = 0.0_f32;
515
516 for i in 0..gray_levels {
518 let row_offset = i * gray_levels;
519
520 const LANES: usize = 8;
521 let chunks = gray_levels / LANES;
522
523 for chunk in 0..chunks {
524 let j_start = chunk * LANES;
525 let j_end = j_start + LANES;
526
527 for j in j_start..j_end {
528 let diff = (i as i64 - j as i64).abs() as f32;
529 dissimilarity += diff * glcm[row_offset + j];
530 }
531 }
532
533 let remainder_start = chunks * LANES;
534 for j in remainder_start..gray_levels {
535 let diff = (i as i64 - j as i64).abs() as f32;
536 dissimilarity += diff * glcm[row_offset + j];
537 }
538 }
539
540 Ok(dissimilarity)
541}
542
543#[derive(Debug, Clone, Default)]
545pub struct HaralickFeaturesSIMD {
546 pub contrast: f32,
548 pub correlation: f32,
550 pub energy: f32,
552 pub homogeneity: f32,
554 pub entropy: f32,
556 pub dissimilarity: f32,
558}
559
560pub fn compute_haralick_features_simd(
571 glcm: &[f32],
572 gray_levels: usize,
573) -> Result<HaralickFeaturesSIMD> {
574 Ok(HaralickFeaturesSIMD {
575 contrast: texture_contrast_simd(glcm, gray_levels)?,
576 correlation: texture_correlation_simd(glcm, gray_levels)?,
577 energy: texture_energy_simd(glcm, gray_levels)?,
578 homogeneity: texture_homogeneity_simd(glcm, gray_levels)?,
579 entropy: texture_entropy_simd(glcm, gray_levels)?,
580 dissimilarity: texture_dissimilarity_simd(glcm, gray_levels)?,
581 })
582}
583
584#[allow(clippy::too_many_arguments)]
602pub fn compute_glcm_simd(
603 data: &[u8],
604 glcm: &mut [f32],
605 width: usize,
606 height: usize,
607 distance: i64,
608 angle: i64,
609 num_levels: usize,
610) -> Result<()> {
611 if data.len() != width * height {
612 return Err(AlgorithmError::InvalidParameter {
613 parameter: "data",
614 message: "Data length must match width * height".to_string(),
615 });
616 }
617
618 if glcm.len() != num_levels * num_levels {
619 return Err(AlgorithmError::InvalidParameter {
620 parameter: "glcm",
621 message: "GLCM must be num_levels x num_levels".to_string(),
622 });
623 }
624
625 if num_levels == 0 {
626 return Err(AlgorithmError::InvalidParameter {
627 parameter: "num_levels",
628 message: "Number of levels must be positive".to_string(),
629 });
630 }
631
632 let (dx, dy) = match angle {
634 0 => (distance, 0),
635 45 => (distance, distance),
636 90 => (0, distance),
637 135 => (-distance, distance),
638 _ => (distance, 0), };
640
641 let scale = 256.0 / num_levels as f32;
642
643 for val in glcm.iter_mut() {
645 *val = 0.0;
646 }
647
648 for y in 0..height as i64 {
650 for x in 0..width as i64 {
651 let nx = x + dx;
652 let ny = y + dy;
653
654 if nx >= 0 && nx < width as i64 && ny >= 0 && ny < height as i64 {
655 let i = (data[(y as usize) * width + (x as usize)] as f32 / scale) as usize;
656 let j = (data[(ny as usize) * width + (nx as usize)] as f32 / scale) as usize;
657
658 let i = i.min(num_levels - 1);
659 let j = j.min(num_levels - 1);
660
661 glcm[i * num_levels + j] += 1.0;
662 }
663 }
664 }
665
666 let sum: f32 = glcm.iter().sum();
668 if sum > 0.0 {
669 for val in glcm.iter_mut() {
670 *val /= sum;
671 }
672 }
673
674 Ok(())
675}
676
677pub fn compute_glcm_multidirectional_simd(
694 data: &[u8],
695 glcm: &mut [f32],
696 width: usize,
697 height: usize,
698 distance: i64,
699 num_levels: usize,
700) -> Result<()> {
701 if data.len() != width * height {
702 return Err(AlgorithmError::InvalidParameter {
703 parameter: "data",
704 message: "Data length must match width * height".to_string(),
705 });
706 }
707
708 if glcm.len() != num_levels * num_levels {
709 return Err(AlgorithmError::InvalidParameter {
710 parameter: "glcm",
711 message: "GLCM must be num_levels x num_levels".to_string(),
712 });
713 }
714
715 if num_levels == 0 {
716 return Err(AlgorithmError::InvalidParameter {
717 parameter: "num_levels",
718 message: "Number of levels must be positive".to_string(),
719 });
720 }
721
722 for val in glcm.iter_mut() {
724 *val = 0.0;
725 }
726
727 let directions: [(i64, i64); 4] = [
729 (distance, 0), (distance, distance), (0, distance), (-distance, distance), ];
734
735 let scale = 256.0 / num_levels as f32;
736
737 for (dx, dy) in &directions {
738 for y in 0..height as i64 {
739 for x in 0..width as i64 {
740 let nx = x + dx;
741 let ny = y + dy;
742
743 if nx >= 0 && nx < width as i64 && ny >= 0 && ny < height as i64 {
744 let i = (data[(y as usize) * width + (x as usize)] as f32 / scale) as usize;
745 let j = (data[(ny as usize) * width + (nx as usize)] as f32 / scale) as usize;
746
747 let i = i.min(num_levels - 1);
748 let j = j.min(num_levels - 1);
749
750 glcm[i * num_levels + j] += 1.0;
752 glcm[j * num_levels + i] += 1.0;
753 }
754 }
755 }
756 }
757
758 let sum: f32 = glcm.iter().sum();
760 if sum > 0.0 {
761 for val in glcm.iter_mut() {
762 *val /= sum;
763 }
764 }
765
766 Ok(())
767}
768
769#[derive(Debug, Clone, Copy, Default)]
771pub struct TextureFeatures {
772 pub contrast: f32,
774 pub dissimilarity: f32,
776 pub homogeneity: f32,
778 pub asm: f32,
780 pub energy: f32,
782 pub entropy: f32,
784 pub correlation: f32,
786 pub mean: f32,
788 pub variance: f32,
790}
791
792pub fn compute_all_texture_features_simd(
809 glcm: &[f32],
810 num_levels: usize,
811) -> Result<TextureFeatures> {
812 if glcm.len() != num_levels * num_levels {
813 return Err(AlgorithmError::InvalidParameter {
814 parameter: "glcm",
815 message: "GLCM must be num_levels x num_levels".to_string(),
816 });
817 }
818
819 let mut contrast = 0.0_f32;
820 let mut dissimilarity = 0.0_f32;
821 let mut homogeneity = 0.0_f32;
822 let mut asm = 0.0_f32;
823 let mut entropy = 0.0_f32;
824
825 let mut mean_i = 0.0_f32;
827 let mut mean_j = 0.0_f32;
828
829 for i in 0..num_levels {
830 for j in 0..num_levels {
831 let p = glcm[i * num_levels + j];
832 mean_i += p * (i as f32);
833 mean_j += p * (j as f32);
834 }
835 }
836
837 let mut std_i = 0.0_f32;
838 let mut std_j = 0.0_f32;
839
840 for i in 0..num_levels {
841 for j in 0..num_levels {
842 let p = glcm[i * num_levels + j];
843 std_i += p * (i as f32 - mean_i).powi(2);
844 std_j += p * (j as f32 - mean_j).powi(2);
845 }
846 }
847
848 std_i = std_i.sqrt();
849 std_j = std_j.sqrt();
850
851 let mut correlation = 0.0_f32;
853
854 for i in 0..num_levels {
856 for j in 0..num_levels {
857 let p = glcm[i * num_levels + j];
858 let diff = (i as i32 - j as i32).abs() as f32;
859
860 contrast += p * diff * diff;
861 dissimilarity += p * diff;
862 homogeneity += p / (1.0 + diff);
863 asm += p * p;
864
865 if p > 0.0 {
866 entropy -= p * p.ln();
867 }
868
869 if std_i > 0.0 && std_j > 0.0 {
870 correlation += p * (i as f32 - mean_i) * (j as f32 - mean_j) / (std_i * std_j);
871 }
872 }
873 }
874
875 Ok(TextureFeatures {
876 contrast,
877 dissimilarity,
878 homogeneity,
879 asm,
880 energy: asm.sqrt(),
881 entropy,
882 correlation,
883 mean: (mean_i + mean_j) / 2.0,
884 variance: (std_i * std_i + std_j * std_j) / 2.0,
885 })
886}
887
888#[derive(Debug, Clone, Copy, PartialEq, Eq)]
890pub enum TextureFeatureType {
891 Contrast,
893 Dissimilarity,
895 Homogeneity,
897 ASM,
899 Energy,
901 Entropy,
903 Correlation,
905}
906
907#[allow(clippy::too_many_arguments)]
926pub fn compute_texture_feature_image_simd(
927 data: &[u8],
928 output: &mut [f32],
929 width: usize,
930 height: usize,
931 window_size: usize,
932 distance: i64,
933 num_levels: usize,
934 feature: TextureFeatureType,
935) -> Result<()> {
936 if data.len() != width * height {
937 return Err(AlgorithmError::InvalidParameter {
938 parameter: "data",
939 message: "Data length must match width * height".to_string(),
940 });
941 }
942
943 if output.len() != width * height {
944 return Err(AlgorithmError::InvalidParameter {
945 parameter: "output",
946 message: "Output length must match width * height".to_string(),
947 });
948 }
949
950 if window_size % 2 == 0 || window_size < 3 {
951 return Err(AlgorithmError::InvalidParameter {
952 parameter: "window_size",
953 message: "Window size must be odd and at least 3".to_string(),
954 });
955 }
956
957 if num_levels == 0 {
958 return Err(AlgorithmError::InvalidParameter {
959 parameter: "num_levels",
960 message: "Number of levels must be positive".to_string(),
961 });
962 }
963
964 let half_window = window_size / 2;
965 let scale = 256.0 / num_levels as f32;
966
967 let mut glcm = vec![0.0_f32; num_levels * num_levels];
969
970 for y in 0..height {
972 for x in 0..width {
973 for val in glcm.iter_mut() {
975 *val = 0.0;
976 }
977
978 let y_start = y.saturating_sub(half_window);
980 let y_end = (y + half_window + 1).min(height);
981 let x_start = x.saturating_sub(half_window);
982 let x_end = (x + half_window + 1).min(width);
983
984 let mut count = 0_usize;
985
986 for wy in y_start..y_end {
987 for wx in x_start..x_end {
988 let nx = wx as i64 + distance;
989 let ny = wy as i64;
990
991 if nx >= x_start as i64 && nx < x_end as i64 {
992 let i = (data[wy * width + wx] as f32 / scale) as usize;
993 let j = (data[ny as usize * width + nx as usize] as f32 / scale) as usize;
994
995 let i = i.min(num_levels - 1);
996 let j = j.min(num_levels - 1);
997
998 glcm[i * num_levels + j] += 1.0;
999 glcm[j * num_levels + i] += 1.0;
1000 count += 2;
1001 }
1002 }
1003 }
1004
1005 if count > 0 {
1007 for val in glcm.iter_mut() {
1008 *val /= count as f32;
1009 }
1010 }
1011
1012 output[y * width + x] = compute_single_texture_feature(&glcm, num_levels, feature);
1014 }
1015 }
1016
1017 Ok(())
1018}
1019
1020fn compute_single_texture_feature(
1022 glcm: &[f32],
1023 num_levels: usize,
1024 feature: TextureFeatureType,
1025) -> f32 {
1026 match feature {
1027 TextureFeatureType::Contrast => {
1028 let mut val = 0.0_f32;
1029 for i in 0..num_levels {
1030 for j in 0..num_levels {
1031 let diff = (i as i32 - j as i32).abs() as f32;
1032 val += glcm[i * num_levels + j] * diff * diff;
1033 }
1034 }
1035 val
1036 }
1037 TextureFeatureType::Dissimilarity => {
1038 let mut val = 0.0_f32;
1039 for i in 0..num_levels {
1040 for j in 0..num_levels {
1041 let diff = (i as i32 - j as i32).abs() as f32;
1042 val += glcm[i * num_levels + j] * diff;
1043 }
1044 }
1045 val
1046 }
1047 TextureFeatureType::Homogeneity => {
1048 let mut val = 0.0_f32;
1049 for i in 0..num_levels {
1050 for j in 0..num_levels {
1051 let diff = (i as i32 - j as i32).abs() as f32;
1052 val += glcm[i * num_levels + j] / (1.0 + diff);
1053 }
1054 }
1055 val
1056 }
1057 TextureFeatureType::ASM => {
1058 let mut val = 0.0_f32;
1059 for p in glcm {
1060 val += p * p;
1061 }
1062 val
1063 }
1064 TextureFeatureType::Energy => {
1065 let asm: f32 = glcm.iter().map(|p| p * p).sum();
1066 asm.sqrt()
1067 }
1068 TextureFeatureType::Entropy => {
1069 let mut val = 0.0_f32;
1070 for &p in glcm {
1071 if p > 0.0 {
1072 val -= p * p.ln();
1073 }
1074 }
1075 val
1076 }
1077 TextureFeatureType::Correlation => {
1078 let mut mean_i = 0.0_f32;
1079 let mut mean_j = 0.0_f32;
1080
1081 for i in 0..num_levels {
1082 for j in 0..num_levels {
1083 let p = glcm[i * num_levels + j];
1084 mean_i += p * (i as f32);
1085 mean_j += p * (j as f32);
1086 }
1087 }
1088
1089 let mut std_i = 0.0_f32;
1090 let mut std_j = 0.0_f32;
1091
1092 for i in 0..num_levels {
1093 for j in 0..num_levels {
1094 let p = glcm[i * num_levels + j];
1095 std_i += p * (i as f32 - mean_i).powi(2);
1096 std_j += p * (j as f32 - mean_j).powi(2);
1097 }
1098 }
1099
1100 std_i = std_i.sqrt();
1101 std_j = std_j.sqrt();
1102
1103 if std_i > 0.0 && std_j > 0.0 {
1104 let mut correlation = 0.0_f32;
1105 for i in 0..num_levels {
1106 for j in 0..num_levels {
1107 let p = glcm[i * num_levels + j];
1108 correlation +=
1109 p * (i as f32 - mean_i) * (j as f32 - mean_j) / (std_i * std_j);
1110 }
1111 }
1112 correlation
1113 } else {
1114 0.0
1115 }
1116 }
1117 }
1118}
1119
1120pub fn compute_lbp_simd(data: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
1135 if data.len() != width * height {
1136 return Err(AlgorithmError::InvalidParameter {
1137 parameter: "data",
1138 message: "Data length must match width * height".to_string(),
1139 });
1140 }
1141
1142 if output.len() != width * height {
1143 return Err(AlgorithmError::InvalidParameter {
1144 parameter: "output",
1145 message: "Output length must match width * height".to_string(),
1146 });
1147 }
1148
1149 if width < 3 || height < 3 {
1150 return Err(AlgorithmError::InvalidParameter {
1151 parameter: "dimensions",
1152 message: "Width and height must be at least 3".to_string(),
1153 });
1154 }
1155
1156 for x in 0..width {
1158 output[x] = 0;
1159 output[(height - 1) * width + x] = 0;
1160 }
1161
1162 for y in 0..height {
1163 output[y * width] = 0;
1164 output[y * width + width - 1] = 0;
1165 }
1166
1167 for y in 1..(height - 1) {
1169 let prev_row = (y - 1) * width;
1170 let curr_row = y * width;
1171 let next_row = (y + 1) * width;
1172
1173 for x in 1..(width - 1) {
1174 let center = data[curr_row + x];
1175 let mut lbp: u8 = 0;
1176
1177 if data[prev_row + x - 1] >= center {
1179 lbp |= 1 << 0;
1180 }
1181 if data[prev_row + x] >= center {
1182 lbp |= 1 << 1;
1183 }
1184 if data[prev_row + x + 1] >= center {
1185 lbp |= 1 << 2;
1186 }
1187 if data[curr_row + x + 1] >= center {
1188 lbp |= 1 << 3;
1189 }
1190 if data[next_row + x + 1] >= center {
1191 lbp |= 1 << 4;
1192 }
1193 if data[next_row + x] >= center {
1194 lbp |= 1 << 5;
1195 }
1196 if data[next_row + x - 1] >= center {
1197 lbp |= 1 << 6;
1198 }
1199 if data[curr_row + x - 1] >= center {
1200 lbp |= 1 << 7;
1201 }
1202
1203 output[curr_row + x] = lbp;
1204 }
1205 }
1206
1207 Ok(())
1208}
1209
1210#[cfg(test)]
1211mod tests {
1212 use super::*;
1213 use approx::assert_abs_diff_eq;
1214
1215 #[test]
1216 fn test_glcm_construct() {
1217 let quantized = vec![0_u8; 100]; let mut glcm = vec![0.0_f32; 4 * 4]; glcm_construct_simd(&quantized, &mut glcm, 10, 10, 4, 1, 0)
1221 .expect("Failed to construct GLCM for uniform image");
1222
1223 assert!(glcm[0] > 0.0);
1225 }
1226
1227 #[test]
1228 fn test_glcm_normalize() {
1229 let mut glcm = vec![2.0_f32; 16]; glcm_normalize_simd(&mut glcm, 4).expect("Failed to normalize GLCM");
1232
1233 let sum: f32 = glcm.iter().sum();
1235 assert_abs_diff_eq!(sum, 1.0, epsilon = 0.001);
1236 }
1237
1238 #[test]
1239 fn test_texture_energy() {
1240 let mut glcm = vec![0.0_f32; 16]; glcm[0] = 1.0; let energy = texture_energy_simd(&glcm, 4).expect("Failed to compute texture energy");
1244
1245 assert_abs_diff_eq!(energy, 1.0, epsilon = 0.001);
1247 }
1248
1249 #[test]
1250 fn test_texture_contrast_uniform() {
1251 let mut glcm = vec![0.0_f32; 16]; glcm[0] = 0.25;
1255 glcm[5] = 0.25;
1256 glcm[10] = 0.25;
1257 glcm[15] = 0.25;
1258
1259 let contrast = texture_contrast_simd(&glcm, 4).expect("Failed to compute texture contrast");
1260
1261 assert_abs_diff_eq!(contrast, 0.0, epsilon = 0.001);
1263 }
1264
1265 #[test]
1266 fn test_texture_entropy() {
1267 let glcm = vec![1.0 / 16.0_f32; 16]; let entropy = texture_entropy_simd(&glcm, 4).expect("Failed to compute texture entropy");
1270
1271 assert_abs_diff_eq!(entropy, 16.0_f32.ln(), epsilon = 0.01);
1274 }
1275
1276 #[test]
1277 fn test_haralick_features() {
1278 let mut glcm = vec![0.0_f32; 16]; glcm[0] = 0.5;
1280 glcm[5] = 0.3;
1281 glcm[10] = 0.2;
1282
1283 let features =
1284 compute_haralick_features_simd(&glcm, 4).expect("Failed to compute Haralick features");
1285
1286 assert!(features.contrast.is_finite());
1288 assert!(features.correlation.is_finite());
1289 assert!(features.energy.is_finite());
1290 assert!(features.homogeneity.is_finite());
1291 assert!(features.entropy.is_finite());
1292 assert!(features.dissimilarity.is_finite());
1293 }
1294
1295 #[test]
1296 fn test_invalid_gray_levels() {
1297 let quantized = vec![0_u8; 100];
1298 let mut glcm = vec![0.0_f32; 16];
1299
1300 let result = glcm_construct_simd(&quantized, &mut glcm, 10, 10, 0, 1, 0);
1302 assert!(result.is_err());
1303
1304 let result = glcm_construct_simd(&quantized, &mut glcm, 10, 10, 257, 1, 0);
1306 assert!(result.is_err());
1307 }
1308
1309 #[test]
1310 fn test_texture_homogeneity() {
1311 let mut glcm = vec![0.0_f32; 16]; glcm[0] = 0.25;
1315 glcm[5] = 0.25;
1316 glcm[10] = 0.25;
1317 glcm[15] = 0.25;
1318
1319 let homogeneity =
1320 texture_homogeneity_simd(&glcm, 4).expect("Failed to compute texture homogeneity");
1321
1322 assert_abs_diff_eq!(homogeneity, 1.0, epsilon = 0.001);
1324 }
1325
1326 #[test]
1327 fn test_glcm_uniform() {
1328 let data = vec![5u8; 100]; let mut glcm = vec![0.0_f32; 256 * 256];
1330
1331 compute_glcm_simd(&data, &mut glcm, 10, 10, 1, 0, 256)
1332 .expect("Failed to compute GLCM for uniform image");
1333
1334 let max_val = glcm.iter().copied().fold(f32::NEG_INFINITY, f32::max);
1336 assert!(max_val > 0.0);
1337 }
1338
1339 #[test]
1340 fn test_glcm_multidirectional() {
1341 let data = vec![128u8; 100]; let mut glcm = vec![0.0_f32; 16 * 16];
1343
1344 compute_glcm_multidirectional_simd(&data, &mut glcm, 10, 10, 1, 16)
1345 .expect("Failed to compute multidirectional GLCM");
1346
1347 let sum: f32 = glcm.iter().sum();
1349 assert_abs_diff_eq!(sum, 1.0, epsilon = 0.001);
1350 }
1351
1352 #[test]
1353 fn test_all_texture_features() {
1354 let mut glcm = vec![0.0_f32; 16];
1355 glcm[0] = 0.5;
1356 glcm[5] = 0.3;
1357 glcm[10] = 0.2;
1358
1359 let features = compute_all_texture_features_simd(&glcm, 4)
1360 .expect("Failed to compute all texture features");
1361
1362 assert!(features.contrast.is_finite());
1363 assert!(features.energy.is_finite());
1364 assert!(features.homogeneity.is_finite());
1365 }
1366
1367 #[test]
1368 fn test_texture_feature_image() {
1369 let data = vec![128u8; 100];
1370 let mut output = vec![0.0_f32; 100];
1371
1372 compute_texture_feature_image_simd(
1373 &data,
1374 &mut output,
1375 10,
1376 10,
1377 3,
1378 1,
1379 8,
1380 TextureFeatureType::Contrast,
1381 )
1382 .expect("Failed to compute texture feature image");
1383
1384 for &val in &output {
1386 assert_abs_diff_eq!(val, 0.0, epsilon = 0.01);
1387 }
1388 }
1389
1390 #[test]
1391 fn test_lbp_uniform() {
1392 let data = vec![128u8; 100]; let mut output = vec![0u8; 100];
1394
1395 compute_lbp_simd(&data, &mut output, 10, 10)
1396 .expect("Failed to compute LBP for uniform image");
1397
1398 for y in 1..9 {
1400 for x in 1..9 {
1401 assert_eq!(output[y * 10 + x], 255);
1402 }
1403 }
1404 }
1405}