rawshift_image/transforms/
denoise.rs1use crate::core::image::RgbImage;
9
10pub fn apply_bilateral_filter(
28 image: &mut RgbImage,
29 spatial_sigma: f32,
30 range_sigma: f32,
31 radius: u32,
32) {
33 let width = image.width() as usize;
34 let height = image.height() as usize;
35
36 if width == 0 || height == 0 {
37 return;
38 }
39
40 let r = radius as usize;
41
42 let ksize = 2 * r + 1;
44 let two_ss_sq = 2.0_f32 * spatial_sigma * spatial_sigma;
45 let two_rs_sq = 2.0_f32 * range_sigma * range_sigma;
46
47 let mut spatial_lut = vec![0.0_f32; ksize * ksize];
48 for dy in 0..ksize {
49 for dx in 0..ksize {
50 let fx = dx as f32 - r as f32;
51 let fy = dy as f32 - r as f32;
52 let d2 = fx * fx + fy * fy;
53 spatial_lut[dy * ksize + dx] = (-d2 / two_ss_sq).exp();
54 }
55 }
56
57 let input = image.data.clone();
58
59 for y in 0..height {
60 for x in 0..width {
61 let center_idx = (y * width + x) * 3;
62
63 for c in 0..3usize {
64 let center_val = input[center_idx + c] as f32;
65 let mut numerator = 0.0_f32;
66 let mut denominator = 0.0_f32;
67
68 let y_min = y.saturating_sub(r);
69 let y_max = (y + r).min(height - 1);
70 let x_min = x.saturating_sub(r);
71 let x_max = (x + r).min(width - 1);
72
73 for ny in y_min..=y_max {
74 for nx in x_min..=x_max {
75 let lut_dy = (ny as isize - y as isize + r as isize) as usize;
76 let lut_dx = (nx as isize - x as isize + r as isize) as usize;
77 let spatial_w = spatial_lut[lut_dy * ksize + lut_dx];
78
79 let neighbor_val = input[(ny * width + nx) * 3 + c] as f32;
80 let diff = center_val - neighbor_val;
81 let range_w = (-diff * diff / two_rs_sq).exp();
82
83 let w = spatial_w * range_w;
84 numerator += neighbor_val * w;
85 denominator += w;
86 }
87 }
88
89 let result = if denominator > 0.0 {
90 (numerator / denominator).round() as u16
91 } else {
92 input[center_idx + c]
93 };
94
95 image.data[center_idx + c] = result;
96 }
97 }
98 }
99}
100
101pub fn apply_gaussian_blur(image: &mut RgbImage, sigma: f32, radius: u32) {
112 let width = image.width() as usize;
113 let height = image.height() as usize;
114
115 if width == 0 || height == 0 {
116 return;
117 }
118
119 let r = radius as usize;
120 let ksize = 2 * r + 1;
121 let two_sq = 2.0_f32 * sigma * sigma;
122
123 let mut kernel = vec![0.0_f32; ksize];
125 for (i, v) in kernel.iter_mut().enumerate() {
126 let x = i as f32 - r as f32;
127 *v = (-x * x / two_sq).exp();
128 }
129 let k_sum: f32 = kernel.iter().sum();
130 for v in kernel.iter_mut() {
131 *v /= k_sum;
132 }
133
134 let mut tmp = image.data.clone();
136 for y in 0..height {
137 for x in 0..width {
138 for c in 0..3usize {
139 let mut acc = 0.0_f32;
140 let mut wsum = 0.0_f32;
141 let x_min = x.saturating_sub(r);
142 let x_max = (x + r).min(width - 1);
143 for nx in x_min..=x_max {
144 let ki = (nx as isize - x as isize + r as isize) as usize;
145 let w = kernel[ki];
146 acc += image.data[(y * width + nx) * 3 + c] as f32 * w;
147 wsum += w;
148 }
149 tmp[(y * width + x) * 3 + c] = if wsum > 0.0 {
150 (acc / wsum).round() as u16
151 } else {
152 image.data[(y * width + x) * 3 + c]
153 };
154 }
155 }
156 }
157
158 for y in 0..height {
160 for x in 0..width {
161 for c in 0..3usize {
162 let mut acc = 0.0_f32;
163 let mut wsum = 0.0_f32;
164 let y_min = y.saturating_sub(r);
165 let y_max = (y + r).min(height - 1);
166 for ny in y_min..=y_max {
167 let ki = (ny as isize - y as isize + r as isize) as usize;
168 let w = kernel[ki];
169 acc += tmp[(ny * width + x) * 3 + c] as f32 * w;
170 wsum += w;
171 }
172 image.data[(y * width + x) * 3 + c] = if wsum > 0.0 {
173 (acc / wsum).round() as u16
174 } else {
175 tmp[(y * width + x) * 3 + c]
176 };
177 }
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 fn make_uniform(width: u32, height: u32, value: u16) -> RgbImage {
188 let n = (width as usize) * (height as usize) * 3;
189 RgbImage::new(width, height, vec![value; n])
190 }
191
192 fn channel_variance(image: &RgbImage, channel: usize) -> f64 {
194 let vals: Vec<f64> = image
195 .data
196 .chunks_exact(3)
197 .map(|px| px[channel] as f64)
198 .collect();
199 let mean = vals.iter().sum::<f64>() / vals.len() as f64;
200 vals.iter().map(|&v| (v - mean).powi(2)).sum::<f64>() / vals.len() as f64
201 }
202
203 #[test]
204 fn test_bilateral_uniform_image_unchanged() {
205 let mut img = make_uniform(8, 8, 1000);
207 apply_bilateral_filter(&mut img, 2.0, 2000.0, 2);
208 assert!(img.data.iter().all(|&v| v == 1000));
209 }
210
211 #[test]
212 fn test_bilateral_reduces_noise() {
213 let w = 16u32;
215 let h = 16u32;
216 let n = (w as usize) * (h as usize);
217 let mut data = Vec::with_capacity(n * 3);
218 for i in 0..n {
219 let v = if i % 2 == 0 { 1000u16 } else { 2000u16 };
220 data.extend_from_slice(&[v, v, v]);
221 }
222 let mut img = RgbImage::new(w, h, data.clone());
223 let var_before = channel_variance(&img, 0);
224 apply_bilateral_filter(&mut img, 3.0, 5000.0, 3);
225 let var_after = channel_variance(&img, 0);
226 assert!(
227 var_after < var_before,
228 "variance should decrease: before={var_before}, after={var_after}"
229 );
230 }
231
232 #[test]
233 fn test_bilateral_preserves_edges() {
234 let w = 16u32;
237 let h = 8u32;
238 let n = (w as usize) * (h as usize);
239 let mut data = Vec::with_capacity(n * 3);
240 for _y in 0..h {
241 for x in 0..w {
242 let v = if x < w / 2 { 0u16 } else { 60000u16 };
243 data.extend_from_slice(&[v, v, v]);
244 }
245 }
246 let mut img = RgbImage::new(w, h, data);
247 apply_bilateral_filter(&mut img, 2.0, 1000.0, 2);
248
249 let left_px = img.data[((4 * w as usize) + 2) * 3];
251 let right_px = img.data[((4 * w as usize) + 13) * 3];
253 assert!(left_px < 10000, "left edge should stay dark, got {left_px}");
254 assert!(
255 right_px > 50000,
256 "right edge should stay bright, got {right_px}"
257 );
258 }
259
260 #[test]
261 fn test_gaussian_blur_uniform_unchanged() {
262 let mut img = make_uniform(8, 8, 5000);
263 apply_gaussian_blur(&mut img, 1.5, 2);
264 assert!(img.data.iter().all(|&v| v == 5000));
266 }
267
268 #[test]
269 fn test_filter_small_image() {
270 let mut img1 = make_uniform(1, 1, 100);
272 apply_bilateral_filter(&mut img1, 2.0, 1000.0, 2);
273
274 let mut img2 = make_uniform(2, 2, 200);
275 apply_gaussian_blur(&mut img2, 1.0, 2);
276 }
277
278 #[test]
279 fn test_gaussian_blur_reduces_noise() {
280 let w = 16u32;
281 let h = 16u32;
282 let n = (w as usize) * (h as usize);
283 let mut data = Vec::with_capacity(n * 3);
284 for i in 0..n {
285 let v: u16 = if i % 2 == 0 { 1000 } else { 3000 };
286 data.extend_from_slice(&[v, v, v]);
287 }
288 let mut img = RgbImage::new(w, h, data);
289 let var_before = channel_variance(&img, 0);
290 apply_gaussian_blur(&mut img, 2.0, 3);
291 let var_after = channel_variance(&img, 0);
292 assert!(
293 var_after < var_before,
294 "Gaussian blur should reduce variance: before={var_before}, after={var_after}"
295 );
296 }
297}