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