doc_quad/edge/
threshold.rs1use ndarray::ArrayView2;
3use std::time::Instant;
4
5pub struct AdaptiveThreshold;
7
8impl AdaptiveThreshold {
9 pub fn calculate(view: &ArrayView2<'_, u8>, sigma: f32) -> (f32, f32) {
26 let start = Instant::now();
27
28 let mut hist = [0u32; 256];
30 for &pixel in view.iter() {
31 hist[pixel as usize] += 1;
32 }
33
34 let total = view.len() as u32;
35
36 let mut count = 0u32;
38 let mut median = 127u8;
39 for (i, &c) in hist.iter().enumerate() {
40 count += c;
41 if count >= total / 2 {
42 median = i as u8;
43 break;
44 }
45 }
46
47 let (low, high) = if median <= 10 {
48 let p70 = Self::percentile(&hist, total, 70);
53 let p85 = Self::percentile(&hist, total, 85);
54
55 let low = if p70 > 0 {
57 (p70 as f32 * (1.0 - sigma)).max(1.0)
58 } else {
59 10.0
60 };
61 let high = if p85 > 0 {
62 (p85 as f32).max(low + 1.0)
63 } else {
64 30.0
65 };
66
67 log::debug!(
68 "[Edge::Threshold] - Sparse scene (median={}), percentile method: \
69 p70={}, p85={}, low={:.2}, high={:.2}",
70 median, p70, p85, low, high
71 );
72
73 (low, high)
74 } else if median > 150 {
75 let p10 = Self::percentile(&hist, total, 10);
85 let p25 = Self::percentile(&hist, total, 25);
86 let p75 = Self::percentile(&hist, total, 75);
87 let p90 = Self::percentile(&hist, total, 90);
88
89 let dynamic_range = p90 as i32 - p10 as i32;
90 let highlight_spread = p90 as i32 - p75 as i32;
92
93 let (low, high) = if dynamic_range < 30 || highlight_spread < 10 {
94 let low = 5.0f32;
98 let high = 20.0f32;
99
100 log::debug!(
101 "[Edge::Threshold] - Extreme highlight scene (median={}, dynamic_range={}, \
102 highlight_spread={}), using fixed low thresholds: low={:.2}, high={:.2}",
103 median, dynamic_range, highlight_spread, low, high
104 );
105
106 (low, high)
107 } else {
108 let low = (p25 as f32 * (1.0 - sigma)).max(1.0).min(60.0);
111 let high = (p75 as f32 * (1.0 - sigma * 0.5))
112 .max(low + 5.0)
113 .min(150.0);
114
115 log::debug!(
116 "[Edge::Threshold] - Bright background scene (median={}, dynamic_range={}), \
117 p25/p75 method: p25={}, p75={}, low={:.2}, high={:.2}",
118 median, dynamic_range, p25, p75, low, high
119 );
120
121 (low, high)
122 };
123
124 (low, high)
125 } else {
126 let low = (0.0f32.max((1.0 - sigma) * median as f32)).min(255.0);
131 let high = (255.0f32.min((1.0 + sigma) * median as f32))
132 .max(low + 1.0)
133 .min(200.0); (low, high)
136 };
137
138 log::debug!(
139 "[Edge::Threshold] - Thresholds: low={:.2}, high={:.2}, median={}, \
140 sigma={:.2}. Elapsed: {}µs",
141 low,
142 high,
143 median,
144 sigma,
145 start.elapsed().as_micros()
146 );
147
148 (low, high)
149 }
150
151 fn percentile(hist: &[u32; 256], total: u32, percent: u32) -> u8 {
158 let target = (total as u64 * percent as u64 / 100) as u32;
160 let mut cumulative = 0u32;
161 for (i, &c) in hist.iter().enumerate() {
162 cumulative += c;
163 if cumulative >= target {
164 return i as u8;
165 }
166 }
167 255
168 }
169
170 pub fn fast_median(view: &ndarray::ArrayView2<'_, u8>) -> u8 {
174 let sample_step = 4;
175 let mut hist = [0u32; 256];
176 let mut count = 0u32;
177
178 for row in view.rows().into_iter().step_by(sample_step) {
179 for &pixel in row.iter().step_by(sample_step) {
180 hist[pixel as usize] += 1;
181 count += 1;
182 }
183 }
184
185 if count == 0 {
186 return 127;
187 }
188
189 let mut cumulative = 0u32;
190 for (i, &c) in hist.iter().enumerate() {
191 cumulative += c;
192 if cumulative >= count / 2 {
193 return i as u8;
194 }
195 }
196 127
197 }
198}