iris/filters/
bilateral.rs1use crate::error::Result;
2use crate::image::Image;
3use burn::tensor::{Tensor, TensorData, backend::Backend};
4
5impl<B: Backend> Image<B> {
6 pub fn bilateral_filter(&self, d: isize, sigma_color: f64, sigma_space: f64) -> Result<Self> {
8 let dims = self.tensor.dims();
9 let c = dims[0];
10 let h = dims[1];
11 let w = dims[2];
12
13 let device = self.tensor.device();
14 let tensor_data = self.tensor.clone().into_data();
15 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
16 let mut out_vals = vec![0.0f32; c * h * w];
17
18 let rad = if d <= 0 {
19 (sigma_space * 3.0).round() as isize
21 } else {
22 d / 2
23 };
24
25 let space_coeff = -1.0 / (2.0 * sigma_space * sigma_space);
26 let color_coeff = -1.0 / (2.0 * sigma_color * sigma_color);
27
28 {
29 use rayon::prelude::*;
30 out_vals
31 .par_chunks_exact_mut(w)
32 .enumerate()
33 .for_each(|(idx, row)| {
34 let ch = idx / h;
35 let y = idx % h;
36
37 for x in 0..w {
38 let mut sum_vals = 0.0f64;
39 let mut sum_weights = 0.0f64;
40 let center_val = f64::from(flat_vals[ch * h * w + y * w + x]);
41
42 for ky in -rad..=rad {
43 let py = y as isize + ky;
44 if py >= 0 && py < h as isize {
45 for kx in -rad..=rad {
46 let px = x as isize + kx;
47 if px >= 0 && px < w as isize {
48 let neighbor_val = f64::from(
49 flat_vals
50 [ch * h * w + (py as usize) * w + (px as usize)],
51 );
52
53 let r2 = (kx * kx + ky * ky) as f64;
55 let diff = neighbor_val - center_val;
57 let diff2 = diff * diff;
58
59 let space_weight = (r2 * space_coeff).exp();
60 let color_weight = (diff2 * color_coeff).exp();
61 let weight = space_weight * color_weight;
62
63 sum_vals += neighbor_val * weight;
64 sum_weights += weight;
65 }
66 }
67 }
68 }
69
70 if sum_weights > 0.0 {
71 row[x] = (sum_vals / sum_weights) as f32;
72 } else {
73 row[x] = center_val as f32;
74 }
75 }
76 });
77 }
78
79 let new_data = TensorData::new(out_vals, [c, h, w]);
80 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
81 Ok(Image::new(new_tensor))
82 }
83
84 pub fn sep_filter_2d(&self, kernel_x: &[f32], kernel_y: &[f32]) -> Result<Self> {
87 let dims = self.tensor.dims();
88 let c = dims[0];
89 let h = dims[1];
90 let w = dims[2];
91
92 let device = self.tensor.device();
93 let tensor_data = self.tensor.clone().into_data();
94 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
95
96 let mut temp_vals = vec![0.0f32; c * h * w];
98 let rad_x = (kernel_x.len() / 2) as isize;
99
100 {
101 use rayon::prelude::*;
102 temp_vals
103 .par_chunks_exact_mut(w)
104 .enumerate()
105 .for_each(|(idx, row)| {
106 let ch = idx / h;
107 let y = idx % h;
108 for x in 0..w {
109 let mut sum = 0.0f64;
110 for kx in -rad_x..=rad_x {
111 let px = x as isize + kx;
112 let px_clamped = px.clamp(0, w as isize - 1) as usize;
113 let weight = f64::from(kernel_x[(kx + rad_x) as usize]);
114 sum += f64::from(flat_vals[ch * h * w + y * w + px_clamped]) * weight;
115 }
116 row[x] = sum as f32;
117 }
118 });
119 }
120
121 let mut out_vals = vec![0.0f32; c * h * w];
123 let rad_y = (kernel_y.len() / 2) as isize;
124
125 {
126 use rayon::prelude::*;
127 out_vals
128 .par_chunks_exact_mut(w)
129 .enumerate()
130 .for_each(|(idx, row)| {
131 let ch = idx / h;
132 let y = idx % h;
133 for x in 0..w {
134 let mut sum = 0.0f64;
135 for ky in -rad_y..=rad_y {
136 let py = y as isize + ky;
137 let py_clamped = py.clamp(0, h as isize - 1) as usize;
138 let weight = f64::from(kernel_y[(ky + rad_y) as usize]);
139 sum += f64::from(temp_vals[ch * h * w + py_clamped * w + x]) * weight;
140 }
141 row[x] = sum as f32;
142 }
143 });
144 }
145
146 let new_data = TensorData::new(out_vals, [c, h, w]);
147 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
148 Ok(Image::new(new_tensor))
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::test_helpers::{TestBackend, test_device};
156
157 #[test]
158 fn test_bilateral_and_separable() {
159 let device = test_device();
160 let flat_data = vec![0.5f32; 3 * 8 * 8];
161 let tensor_data = TensorData::new(flat_data, [3, 8, 8]);
162 let img = Image::new(Tensor::<TestBackend, 3>::from_data(tensor_data, &device));
163
164 let bilateral = img.bilateral_filter(3, 0.1, 1.0).unwrap();
165 assert_eq!(bilateral.shape(), [3, 8, 8]);
166
167 let kernel_x = vec![1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0];
168 let kernel_y = vec![1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0];
169 let sep = img.sep_filter_2d(&kernel_x, &kernel_y).unwrap();
170 assert_eq!(sep.shape(), [3, 8, 8]);
171 }
172}