1use crate::error::{IrisError, Result};
2use crate::image::Image;
3use burn::tensor::{Tensor, TensorData, backend::Backend};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum ThresholdType {
8 Binary,
9 BinaryInv,
10 Trunc,
11 ToZero,
12 ToZeroInv,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq)]
17pub enum AdaptiveMethod {
18 MeanC,
20 GaussianC,
22}
23
24impl<B: Backend> Image<B> {
25 pub fn threshold(&self, thresh: f32, maxval: f32, thresh_type: ThresholdType) -> Result<Self> {
27 let dims = self.tensor.dims();
28 let c = dims[0];
29 let h = dims[1];
30 let w = dims[2];
31
32 let device = self.tensor.device();
33 let tensor_data = self.tensor.clone().into_data();
34 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
35 let mut out_vals = vec![0.0f32; c * h * w];
36
37 {
38 use rayon::prelude::*;
39 out_vals
40 .par_iter_mut()
41 .enumerate()
42 .for_each(|(i, out_val)| {
43 let val = flat_vals[i];
44 *out_val = match thresh_type {
45 ThresholdType::Binary => {
46 if val > thresh {
47 maxval
48 } else {
49 0.0
50 }
51 }
52 ThresholdType::BinaryInv => {
53 if val > thresh {
54 0.0
55 } else {
56 maxval
57 }
58 }
59 ThresholdType::Trunc => {
60 if val > thresh {
61 thresh
62 } else {
63 val
64 }
65 }
66 ThresholdType::ToZero => {
67 if val > thresh {
68 val
69 } else {
70 0.0
71 }
72 }
73 ThresholdType::ToZeroInv => {
74 if val > thresh {
75 0.0
76 } else {
77 val
78 }
79 }
80 };
81 });
82 }
83
84 let new_data = TensorData::new(out_vals, [c, h, w]);
85 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
86 Ok(Image::new(new_tensor))
87 }
88
89 pub fn threshold_otsu(&self, maxval: f32) -> Result<Self> {
91 let gray = self.grayscale()?;
92 let dims = gray.tensor.dims();
93 let h = dims[1];
94 let w = dims[2];
95
96 let tensor_data = gray.tensor.clone().into_data();
97 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
98
99 let mut hist = [0u32; 256];
100 for &val in &flat_vals {
101 let bin = (val.clamp(0.0, 1.0) * 255.0) as usize;
102 hist[bin] += 1;
103 }
104
105 let total = (h * w) as f32;
106 let mut sum = 0.0f32;
107 for (i, &count) in hist.iter().enumerate() {
108 sum += (i as f32) * (count as f32);
109 }
110
111 let mut sum_b = 0.0f32;
112 let mut w_b = 0.0f32;
113 let mut max_var = 0.0f32;
114 let mut threshold = 0;
115
116 for (t, &count) in hist.iter().enumerate() {
117 w_b += count as f32;
118 if w_b == 0.0 {
119 continue;
120 }
121 let w_f = total - w_b;
122 if w_f == 0.0 {
123 break;
124 }
125
126 sum_b += (t as f32) * (count as f32);
127 let m_b = sum_b / w_b;
128 let m_f = (sum - sum_b) / w_f;
129
130 let var_between = w_b * w_f * (m_b - m_f) * (m_b - m_f);
131 if var_between > max_var {
132 max_var = var_between;
133 threshold = t;
134 }
135 }
136
137 let thresh_float = (threshold as f32) / 255.0;
138 self.threshold(thresh_float, maxval, ThresholdType::Binary)
139 }
140
141 pub fn threshold_triangle(&self, maxval: f32) -> Result<Self> {
146 let gray = self.grayscale()?;
147 let dims = gray.tensor.dims();
148 let _h = dims[1];
149 let _w = dims[2];
150
151 let tensor_data = gray.tensor.clone().into_data();
152 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
153
154 let mut hist = [0u32; 256];
155 for &val in &flat_vals {
156 let bin = (val.clamp(0.0, 1.0) * 255.0) as usize;
157 hist[bin] += 1;
158 }
159
160 let mut peak = 0;
162 let mut max_count = 0u32;
163 for (i, &count) in hist.iter().enumerate() {
164 if count > max_count {
165 max_count = count;
166 peak = i;
167 }
168 }
169
170 let mut last = 255;
172 for i in (0..256).rev() {
173 if hist[i] > 0 {
174 last = i;
175 break;
176 }
177 }
178
179 if peak == last {
180 let thresh_float = (peak as f32) / 255.0;
181 return self.threshold(thresh_float, maxval, ThresholdType::Binary);
182 }
183
184 let dx = (last as f64) - (peak as f64);
186 let dy = 0.0 - (max_count as f64);
187 let line_len = (dx * dx + dy * dy).sqrt();
188
189 let mut max_dist = 0.0f64;
191 let mut threshold = peak;
192
193 for i in peak..=last {
194 let px = i as f64;
195 let py = hist[i] as f64;
196 let dist = ((dy * px - dx * py + (last as f64) * (max_count as f64)
198 - (peak as f64) * 0.0)
199 / line_len)
200 .abs();
201 if dist > max_dist {
202 max_dist = dist;
203 threshold = i;
204 }
205 }
206
207 let thresh_float = (threshold as f32) / 255.0;
208 self.threshold(thresh_float, maxval, ThresholdType::Binary)
209 }
210
211 pub fn adaptive_threshold(
214 &self,
215 maxval: f32,
216 method: AdaptiveMethod,
217 block_size: usize,
218 c: f32,
219 ) -> Result<Self> {
220 if block_size == 0 || block_size.is_multiple_of(2) {
221 return Err(IrisError::InvalidParameter(
222 "block_size must be a positive odd number".into(),
223 ));
224 }
225
226 let gray = self.grayscale()?;
227 let dims = gray.tensor.dims();
228 let h = dims[1];
229 let w = dims[2];
230
231 let tensor_data = gray.tensor.clone().into_data();
232 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
233 let mut out_vals = vec![0.0f32; h * w];
234
235 let half = block_size / 2;
236
237 let mut integral = vec![0.0f64; (h + 1) * (w + 1)];
239 for y in 0..h {
240 let mut row_sum = 0.0f64;
241 for x in 0..w {
242 row_sum += flat_vals[y * w + x] as f64;
243 integral[(y + 1) * (w + 1) + (x + 1)] = integral[y * (w + 1) + (x + 1)] + row_sum;
244 }
245 }
246
247 let gaussian_kernel = if method == AdaptiveMethod::GaussianC {
249 let sigma = (block_size as f64) / 6.0;
250 let mut kernel = Vec::with_capacity(block_size * block_size);
251 for ky in 0..block_size {
252 for kx in 0..block_size {
253 let dy = (ky as f64) - (half as f64);
254 let dx = (kx as f64) - (half as f64);
255 let weight = (-(dx * dx + dy * dy) / (2.0 * sigma * sigma)).exp();
256 kernel.push(weight);
257 }
258 }
259 let sum: f64 = kernel.iter().sum();
260 for k in &mut kernel {
261 *k /= sum;
262 }
263 Some(kernel)
264 } else {
265 None
266 };
267
268 let _total_pixels = block_size * block_size;
269
270 for y in 0..h {
271 for x in 0..w {
272 let y1 = y.saturating_sub(half).min(h - 1);
273 let y2 = (y + half).min(h - 1);
274 let x1 = x.saturating_sub(half).min(w - 1);
275 let x2 = (x + half).min(w - 1);
276
277 let mean = if let Some(ref kernel) = gaussian_kernel {
278 let mut weighted_sum = 0.0f64;
280 let mut ki = 0;
281 for ky in y1..=y2 {
282 for kx in x1..=x2 {
283 weighted_sum += flat_vals[ky * w + kx] as f64 * kernel[ki];
284 ki += 1;
285 }
286 }
287 weighted_sum
288 } else {
289 let area = integral[(y2 + 1) * (w + 1) + (x2 + 1)]
291 - integral[y1 * (w + 1) + (x2 + 1)]
292 - integral[(y2 + 1) * (w + 1) + x1]
293 + integral[y1 * (w + 1) + x1];
294 let count = ((y2 - y1 + 1) * (x2 - x1 + 1)) as f64;
295 area / count
296 };
297
298 let pixel = flat_vals[y * w + x];
299 if pixel > (mean as f32) - c {
300 out_vals[y * w + x] = maxval;
301 } else {
302 out_vals[y * w + x] = 0.0;
303 }
304 }
305 }
306
307 let new_data = TensorData::new(out_vals, [1, h, w]);
308 let new_tensor = Tensor::<B, 3>::from_data(new_data, &gray.tensor.device());
309 Ok(Image::new(new_tensor))
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::test_helpers::{TestBackend, test_device};
317
318 #[test]
319 fn test_threshold() {
320 let device = test_device();
321 let flat_data = vec![0.5f32; 3 * 8 * 8];
322 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
323 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
324
325 let thresh = img.threshold(0.4, 1.0, ThresholdType::Binary).unwrap();
326 assert_eq!(thresh.shape(), [3, 8, 8]);
327
328 let otsu = img.threshold_otsu(1.0).unwrap();
329 assert_eq!(otsu.shape(), [3, 8, 8]);
330 }
331
332 #[test]
333 fn test_triangle_threshold() {
334 let device = test_device();
335 let mut flat_data = vec![0.0f32; 16 * 16];
336 for y in 0..16 {
338 for x in 0..16 {
339 if x < 8 {
340 flat_data[y * 16 + x] = 0.2;
341 } else {
342 flat_data[y * 16 + x] = 0.8;
343 }
344 }
345 }
346 let tensor =
347 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 16, 16]), &device);
348 let img = Image::new(tensor);
349 let result = img.threshold_triangle(1.0).unwrap();
350 assert_eq!(result.shape(), [1, 16, 16]);
351 }
352
353 #[test]
354 fn test_adaptive_threshold() {
355 let device = test_device();
356 let mut flat_data = vec![0.0f32; 16 * 16];
357 for y in 0..16 {
358 for x in 0..16 {
359 flat_data[y * 16 + x] = (x as f32) / 16.0;
360 }
361 }
362 let tensor =
363 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [1, 16, 16]), &device);
364 let img = Image::new(tensor);
365
366 let result = img
367 .adaptive_threshold(1.0, AdaptiveMethod::MeanC, 3, 0.05)
368 .unwrap();
369 assert_eq!(result.shape(), [1, 16, 16]);
370
371 let result_gauss = img
372 .adaptive_threshold(1.0, AdaptiveMethod::GaussianC, 5, 0.05)
373 .unwrap();
374 assert_eq!(result_gauss.shape(), [1, 16, 16]);
375 }
376}