1#![allow(unsafe_code)]
42
43use crate::error::{AlgorithmError, Result};
44
45#[cfg(target_arch = "aarch64")]
48mod neon_impl {
49 use std::arch::aarch64::*;
50
51 #[target_feature(enable = "neon")]
54 pub(crate) unsafe fn binary(
55 input: &[u8],
56 output: &mut [u8],
57 threshold: u8,
58 max_value: u8,
59 min_value: u8,
60 ) {
61 unsafe {
62 let len = input.len();
63 let chunks = len / 16;
64 let ip = input.as_ptr();
65 let op = output.as_mut_ptr();
66 let vt = vdupq_n_u8(threshold);
67 let vmax = vdupq_n_u8(max_value);
68 let vmin = vdupq_n_u8(min_value);
69 for i in 0..chunks {
70 let off = i * 16;
71 let v = vld1q_u8(ip.add(off));
72 let mask = vcgeq_u8(v, vt); vst1q_u8(op.add(off), vbslq_u8(mask, vmax, vmin));
74 }
75 for i in (chunks * 16)..len {
76 *op.add(i) = if *ip.add(i) >= threshold {
77 max_value
78 } else {
79 min_value
80 };
81 }
82 }
83 }
84
85 #[target_feature(enable = "neon")]
88 pub(crate) unsafe fn to_zero(input: &[u8], output: &mut [u8], threshold: u8) {
89 unsafe {
90 let len = input.len();
91 let chunks = len / 16;
92 let ip = input.as_ptr();
93 let op = output.as_mut_ptr();
94 let vt = vdupq_n_u8(threshold);
95 for i in 0..chunks {
96 let off = i * 16;
97 let v = vld1q_u8(ip.add(off));
98 let mask = vcgeq_u8(v, vt);
99 vst1q_u8(op.add(off), vandq_u8(mask, v));
100 }
101 for i in (chunks * 16)..len {
102 let x = *ip.add(i);
103 *op.add(i) = if x >= threshold { x } else { 0 };
104 }
105 }
106 }
107
108 #[target_feature(enable = "neon")]
111 pub(crate) unsafe fn truncate(input: &[u8], output: &mut [u8], threshold: u8) {
112 unsafe {
113 let len = input.len();
114 let chunks = len / 16;
115 let ip = input.as_ptr();
116 let op = output.as_mut_ptr();
117 let vt = vdupq_n_u8(threshold);
118 for i in 0..chunks {
119 let off = i * 16;
120 let v = vld1q_u8(ip.add(off));
121 vst1q_u8(op.add(off), vminq_u8(v, vt));
122 }
123 for i in (chunks * 16)..len {
124 *op.add(i) = (*ip.add(i)).min(threshold);
125 }
126 }
127 }
128
129 #[target_feature(enable = "neon")]
132 pub(crate) unsafe fn range(input: &[u8], output: &mut [u8], low: u8, high: u8) {
133 unsafe {
134 let len = input.len();
135 let chunks = len / 16;
136 let ip = input.as_ptr();
137 let op = output.as_mut_ptr();
138 let vlo = vdupq_n_u8(low);
139 let vhi = vdupq_n_u8(high);
140 for i in 0..chunks {
141 let off = i * 16;
142 let v = vld1q_u8(ip.add(off));
143 let mask = vandq_u8(vcgeq_u8(v, vlo), vcleq_u8(v, vhi));
144 vst1q_u8(op.add(off), vandq_u8(mask, v));
145 }
146 for i in (chunks * 16)..len {
147 let x = *ip.add(i);
148 *op.add(i) = if x >= low && x <= high { x } else { 0 };
149 }
150 }
151 }
152}
153
154pub fn binary_threshold(
168 input: &[u8],
169 output: &mut [u8],
170 threshold: u8,
171 max_value: u8,
172 min_value: u8,
173) -> Result<()> {
174 if input.len() != output.len() {
175 return Err(AlgorithmError::InvalidParameter {
176 parameter: "buffers",
177 message: format!(
178 "Buffer size mismatch: input={}, output={}",
179 input.len(),
180 output.len()
181 ),
182 });
183 }
184
185 #[cfg(target_arch = "aarch64")]
186 {
187 unsafe {
189 neon_impl::binary(input, output, threshold, max_value, min_value);
190 }
191 }
192 #[cfg(not(target_arch = "aarch64"))]
193 {
194 for i in 0..input.len() {
195 output[i] = if input[i] >= threshold {
196 max_value
197 } else {
198 min_value
199 };
200 }
201 }
202
203 Ok(())
204}
205
206pub fn threshold_to_zero(input: &[u8], output: &mut [u8], threshold: u8) -> Result<()> {
214 if input.len() != output.len() {
215 return Err(AlgorithmError::InvalidParameter {
216 parameter: "buffers",
217 message: format!(
218 "Buffer size mismatch: input={}, output={}",
219 input.len(),
220 output.len()
221 ),
222 });
223 }
224
225 #[cfg(target_arch = "aarch64")]
226 {
227 unsafe {
229 neon_impl::to_zero(input, output, threshold);
230 }
231 }
232 #[cfg(not(target_arch = "aarch64"))]
233 {
234 for i in 0..input.len() {
235 output[i] = if input[i] >= threshold { input[i] } else { 0 };
236 }
237 }
238
239 Ok(())
240}
241
242pub fn threshold_truncate(input: &[u8], output: &mut [u8], threshold: u8) -> Result<()> {
250 if input.len() != output.len() {
251 return Err(AlgorithmError::InvalidParameter {
252 parameter: "buffers",
253 message: format!(
254 "Buffer size mismatch: input={}, output={}",
255 input.len(),
256 output.len()
257 ),
258 });
259 }
260
261 #[cfg(target_arch = "aarch64")]
262 {
263 unsafe {
265 neon_impl::truncate(input, output, threshold);
266 }
267 }
268 #[cfg(not(target_arch = "aarch64"))]
269 {
270 for i in 0..input.len() {
271 output[i] = input[i].min(threshold);
272 }
273 }
274
275 Ok(())
276}
277
278pub fn threshold_range(
286 input: &[u8],
287 output: &mut [u8],
288 low_threshold: u8,
289 high_threshold: u8,
290) -> Result<()> {
291 if input.len() != output.len() {
292 return Err(AlgorithmError::InvalidParameter {
293 parameter: "buffers",
294 message: format!(
295 "Buffer size mismatch: input={}, output={}",
296 input.len(),
297 output.len()
298 ),
299 });
300 }
301
302 if low_threshold > high_threshold {
303 return Err(AlgorithmError::InvalidParameter {
304 parameter: "thresholds",
305 message: format!("Invalid range: low={low_threshold}, high={high_threshold}"),
306 });
307 }
308
309 #[cfg(target_arch = "aarch64")]
310 {
311 unsafe {
313 neon_impl::range(input, output, low_threshold, high_threshold);
314 }
315 }
316 #[cfg(not(target_arch = "aarch64"))]
317 {
318 for i in 0..input.len() {
319 let val = input[i];
320 output[i] = if val >= low_threshold && val <= high_threshold {
321 val
322 } else {
323 0
324 };
325 }
326 }
327
328 Ok(())
329}
330
331pub fn otsu_threshold(data: &[u8]) -> Result<u8> {
340 if data.is_empty() {
341 return Err(AlgorithmError::EmptyInput {
342 operation: "otsu_threshold",
343 });
344 }
345
346 let mut histogram = [0u32; 256];
348 for &value in data {
349 histogram[value as usize] += 1;
350 }
351
352 let total_pixels = data.len() as f64;
353
354 let mut total_mean = 0.0;
356 for (i, &count) in histogram.iter().enumerate() {
357 total_mean += i as f64 * f64::from(count);
358 }
359 total_mean /= total_pixels;
360
361 let mut max_variance = 0.0;
362 let mut optimal_threshold = 0u8;
363
364 let mut weight_background = 0.0;
365 let mut sum_background = 0.0;
366
367 for (t, &count) in histogram.iter().enumerate() {
368 weight_background += f64::from(count) / total_pixels;
369 sum_background += t as f64 * f64::from(count);
370
371 if weight_background < 1e-10 || (1.0 - weight_background) < 1e-10 {
372 continue;
373 }
374
375 let mean_background = sum_background / (weight_background * total_pixels);
376 let mean_foreground = (total_mean * total_pixels - sum_background)
377 / ((1.0 - weight_background) * total_pixels);
378
379 let variance = weight_background
380 * (1.0 - weight_background)
381 * (mean_background - mean_foreground).powi(2);
382
383 if variance >= max_variance {
385 max_variance = variance;
386 optimal_threshold = t as u8;
387 }
388 }
389
390 Ok(optimal_threshold)
391}
392
393pub fn adaptive_threshold_mean(
408 input: &[u8],
409 output: &mut [u8],
410 width: usize,
411 height: usize,
412 window_size: usize,
413 c: i16,
414) -> Result<()> {
415 if input.len() != width * height || output.len() != width * height {
416 return Err(AlgorithmError::InvalidParameter {
417 parameter: "buffers",
418 message: format!(
419 "Buffer size mismatch: input={}, output={}, expected={}",
420 input.len(),
421 output.len(),
422 width * height
423 ),
424 });
425 }
426
427 if window_size == 0 || window_size.is_multiple_of(2) {
428 return Err(AlgorithmError::InvalidParameter {
429 parameter: "window_size",
430 message: format!("Window size must be odd and positive, got {window_size}"),
431 });
432 }
433
434 let half_window = window_size / 2;
435
436 for y in 0..height {
437 for x in 0..width {
438 let mut sum = 0u32;
440 let mut count = 0u32;
441
442 let y_start = y.saturating_sub(half_window);
443 let y_end = (y + half_window + 1).min(height);
444 let x_start = x.saturating_sub(half_window);
445 let x_end = (x + half_window + 1).min(width);
446
447 for py in y_start..y_end {
448 for px in x_start..x_end {
449 sum += u32::from(input[py * width + px]);
450 count += 1;
451 }
452 }
453
454 let mean = sum.checked_div(count).unwrap_or(0);
455 let threshold = (mean as i32 - i32::from(c)).max(0) as u8;
456
457 let idx = y * width + x;
458 output[idx] = if input[idx] >= threshold { 255 } else { 0 };
459 }
460 }
461
462 Ok(())
463}
464
465pub fn adaptive_threshold_gaussian(
473 input: &[u8],
474 output: &mut [u8],
475 width: usize,
476 height: usize,
477 window_size: usize,
478 c: i16,
479) -> Result<()> {
480 if input.len() != width * height || output.len() != width * height {
481 return Err(AlgorithmError::InvalidParameter {
482 parameter: "buffers",
483 message: format!(
484 "Buffer size mismatch: input={}, output={}, expected={}",
485 input.len(),
486 output.len(),
487 width * height
488 ),
489 });
490 }
491
492 if window_size == 0 || window_size.is_multiple_of(2) {
493 return Err(AlgorithmError::InvalidParameter {
494 parameter: "window_size",
495 message: format!("Window size must be odd and positive, got {window_size}"),
496 });
497 }
498
499 let half_window = window_size / 2;
500 let sigma = window_size as f32 / 6.0;
501
502 let mut weights = vec![vec![0.0f32; window_size]; window_size];
504 let mut weight_sum = 0.0f32;
505
506 for wy in 0..window_size {
507 for wx in 0..window_size {
508 let dy = wy as f32 - half_window as f32;
509 let dx = wx as f32 - half_window as f32;
510 let weight = (-((dx * dx + dy * dy) / (2.0 * sigma * sigma))).exp();
511 weights[wy][wx] = weight;
512 weight_sum += weight;
513 }
514 }
515
516 for row in &mut weights {
518 for w in row {
519 *w /= weight_sum;
520 }
521 }
522
523 for y in 0..height {
524 for x in 0..width {
525 let mut weighted_sum = 0.0f32;
526
527 let y_start = y.saturating_sub(half_window);
528 let y_end = (y + half_window + 1).min(height);
529 let x_start = x.saturating_sub(half_window);
530 let x_end = (x + half_window + 1).min(width);
531
532 for py in y_start..y_end {
533 for px in x_start..x_end {
534 let wy = py - y + half_window;
535 let wx = px - x + half_window;
536 if wy < window_size && wx < window_size {
537 weighted_sum += f32::from(input[py * width + px]) * weights[wy][wx];
538 }
539 }
540 }
541
542 let threshold = (weighted_sum - f32::from(c)).max(0.0) as u8;
543
544 let idx = y * width + x;
545 output[idx] = if input[idx] >= threshold { 255 } else { 0 };
546 }
547 }
548
549 Ok(())
550}
551
552pub fn hysteresis_threshold(
562 input: &[u8],
563 output: &mut [u8],
564 width: usize,
565 height: usize,
566 low_threshold: u8,
567 high_threshold: u8,
568) -> Result<()> {
569 if input.len() != width * height || output.len() != width * height {
570 return Err(AlgorithmError::InvalidParameter {
571 parameter: "buffers",
572 message: format!(
573 "Buffer size mismatch: input={}, output={}, expected={}",
574 input.len(),
575 output.len(),
576 width * height
577 ),
578 });
579 }
580
581 if low_threshold >= high_threshold {
582 return Err(AlgorithmError::InvalidParameter {
583 parameter: "thresholds",
584 message: format!(
585 "low_threshold must be < high_threshold: {low_threshold} >= {high_threshold}"
586 ),
587 });
588 }
589
590 output.fill(0);
592
593 for i in 0..input.len() {
595 if input[i] >= high_threshold {
596 output[i] = 255;
597 }
598 }
599
600 let mut changed = true;
602 while changed {
603 changed = false;
604
605 for y in 1..(height - 1) {
606 for x in 1..(width - 1) {
607 let idx = y * width + x;
608
609 if input[idx] >= low_threshold && input[idx] < high_threshold && output[idx] == 0 {
611 let mut connected = false;
613 for dy in 0..3 {
614 for dx in 0..3 {
615 if dx == 1 && dy == 1 {
616 continue;
617 }
618 let ny = y + dy - 1;
619 let nx = x + dx - 1;
620 if output[ny * width + nx] == 255 {
621 connected = true;
622 break;
623 }
624 }
625 if connected {
626 break;
627 }
628 }
629
630 if connected {
631 output[idx] = 255;
632 changed = true;
633 }
634 }
635 }
636 }
637 }
638
639 Ok(())
640}
641
642pub fn multi_threshold(
657 input: &[u8],
658 output: &mut [u8],
659 thresholds: &[u8],
660 levels: &[u8],
661) -> Result<()> {
662 if input.len() != output.len() {
663 return Err(AlgorithmError::InvalidParameter {
664 parameter: "buffers",
665 message: format!(
666 "Buffer size mismatch: input={}, output={}",
667 input.len(),
668 output.len()
669 ),
670 });
671 }
672
673 if levels.len() != thresholds.len() + 1 {
674 return Err(AlgorithmError::InvalidParameter {
675 parameter: "levels",
676 message: format!(
677 "Levels length must be thresholds.len() + 1: {} != {}",
678 levels.len(),
679 thresholds.len() + 1
680 ),
681 });
682 }
683
684 const LANES: usize = 16;
685 let chunks = input.len() / LANES;
686
687 for i in 0..chunks {
688 let start = i * LANES;
689 let end = start + LANES;
690
691 for j in start..end {
692 let val = input[j];
693 let mut level_idx = 0;
694
695 for (t_idx, &threshold) in thresholds.iter().enumerate() {
696 if val >= threshold {
697 level_idx = t_idx + 1;
698 } else {
699 break;
700 }
701 }
702
703 output[j] = levels[level_idx];
704 }
705 }
706
707 let remainder_start = chunks * LANES;
708 for i in remainder_start..input.len() {
709 let val = input[i];
710 let mut level_idx = 0;
711
712 for (t_idx, &threshold) in thresholds.iter().enumerate() {
713 if val >= threshold {
714 level_idx = t_idx + 1;
715 } else {
716 break;
717 }
718 }
719
720 output[i] = levels[level_idx];
721 }
722
723 Ok(())
724}
725
726#[cfg(test)]
727mod tests {
728 use super::*;
729
730 #[test]
731 fn test_binary_threshold() {
732 let input = vec![50, 100, 150, 200, 250];
733 let mut output = vec![0; 5];
734
735 binary_threshold(&input, &mut output, 128, 255, 0)
736 .expect("binary threshold should succeed");
737
738 assert_eq!(output, vec![0, 0, 255, 255, 255]);
739 }
740
741 #[test]
742 fn test_threshold_to_zero() {
743 let input = vec![50, 100, 150, 200, 250];
744 let mut output = vec![0; 5];
745
746 threshold_to_zero(&input, &mut output, 128).expect("threshold to zero should succeed");
747
748 assert_eq!(output, vec![0, 0, 150, 200, 250]);
749 }
750
751 #[test]
752 fn test_threshold_truncate() {
753 let input = vec![50, 100, 150, 200, 250];
754 let mut output = vec![0; 5];
755
756 threshold_truncate(&input, &mut output, 128).expect("threshold truncate should succeed");
757
758 assert_eq!(output, vec![50, 100, 128, 128, 128]);
759 }
760
761 #[test]
762 fn test_threshold_range() {
763 let input = vec![50, 100, 150, 200, 250];
764 let mut output = vec![0; 5];
765
766 threshold_range(&input, &mut output, 100, 200).expect("threshold range should succeed");
767
768 assert_eq!(output, vec![0, 100, 150, 200, 0]);
769 }
770
771 #[test]
772 fn test_otsu_threshold() {
773 let mut data = vec![50u8; 500];
775 data.extend(vec![200u8; 500]);
776
777 let threshold = otsu_threshold(&data).expect("Otsu threshold calculation should succeed");
778
779 assert!(threshold > 50 && threshold < 200);
781 }
782
783 #[test]
784 fn test_adaptive_threshold_mean() {
785 let width = 10;
786 let height = 10;
787 let input = vec![128u8; width * height];
788 let mut output = vec![0u8; width * height];
789
790 adaptive_threshold_mean(&input, &mut output, width, height, 3, 10)
791 .expect("adaptive threshold mean should succeed");
792
793 assert!(output.iter().filter(|&&x| x == 255).count() > 50);
795 }
796
797 #[test]
798 fn test_multi_threshold() {
799 let input = vec![10, 50, 100, 150, 200, 250];
800 let mut output = vec![0; 6];
801 let thresholds = vec![64, 128, 192];
802 let levels = vec![0, 85, 170, 255];
803
804 multi_threshold(&input, &mut output, &thresholds, &levels)
805 .expect("multi-level threshold should succeed");
806
807 assert_eq!(output[0], 0); assert_eq!(output[1], 0); assert_eq!(output[2], 85); assert_eq!(output[3], 170); assert_eq!(output[4], 255); assert_eq!(output[5], 255); }
814
815 #[test]
816 fn test_hysteresis_threshold() {
817 let width = 5;
818 let height = 5;
819 let mut input = vec![0u8; width * height];
820
821 input[2 * width + 2] = 200;
823 input[2 * width + 1] = 80;
825 input[width + 2] = 80;
826 input[4 * width + 4] = 80;
828
829 let mut output = vec![0u8; width * height];
830 hysteresis_threshold(&input, &mut output, width, height, 50, 150)
831 .expect("hysteresis threshold should succeed");
832
833 assert_eq!(output[2 * width + 2], 255);
835 assert_eq!(output[2 * width + 1], 255);
837 assert_eq!(output[width + 2], 255);
838 assert_eq!(output[4 * width + 4], 0);
840 }
841
842 #[test]
843 fn test_buffer_size_mismatch() {
844 let input = vec![0u8; 10];
845 let mut output = vec![0u8; 5]; let result = binary_threshold(&input, &mut output, 128, 255, 0);
848 assert!(result.is_err());
849 }
850
851 #[test]
852 fn test_invalid_range() {
853 let input = vec![0u8; 10];
854 let mut output = vec![0u8; 10];
855
856 let result = threshold_range(&input, &mut output, 200, 100);
857 assert!(result.is_err());
858 }
859
860 fn make_image(len: usize, seed: u64) -> Vec<u8> {
861 let mut state = seed;
862 (0..len)
863 .map(|_| {
864 state = state
865 .wrapping_mul(6364136223846793005)
866 .wrapping_add(1442695040888963407);
867 (state >> 33) as u8
868 })
869 .collect()
870 }
871
872 #[test]
873 fn test_pointwise_thresholds_match_scalar_reference() {
874 for len in [0usize, 1, 15, 16, 17, 31, 33, 100, 256, 257] {
876 let input = make_image(len, 0xDEAD ^ len as u64);
877 let (thr, lo, hi) = (100u8, 60u8, 190u8);
878
879 let mut out = vec![0u8; len];
881 binary_threshold(&input, &mut out, thr, 255, 0).expect("binary ok");
882 for i in 0..len {
883 let want = if input[i] >= thr { 255 } else { 0 };
884 assert_eq!(out[i], want, "binary mismatch len={len} i={i}");
885 }
886
887 threshold_to_zero(&input, &mut out, thr).expect("to_zero ok");
889 for i in 0..len {
890 let want = if input[i] >= thr { input[i] } else { 0 };
891 assert_eq!(out[i], want, "to_zero mismatch len={len} i={i}");
892 }
893
894 threshold_truncate(&input, &mut out, thr).expect("truncate ok");
896 for i in 0..len {
897 assert_eq!(
898 out[i],
899 input[i].min(thr),
900 "truncate mismatch len={len} i={i}"
901 );
902 }
903
904 threshold_range(&input, &mut out, lo, hi).expect("range ok");
906 for i in 0..len {
907 let v = input[i];
908 let want = if v >= lo && v <= hi { v } else { 0 };
909 assert_eq!(out[i], want, "range mismatch len={len} i={i}");
910 }
911 }
912 }
913}