1use crate::error::{IrisError, Result};
2use crate::image::Image;
3use burn::tensor::{Tensor, TensorData, backend::Backend};
4use std::cmp::Reverse;
5use std::collections::BinaryHeap;
6
7const INFINITY: f32 = 1.0e10;
8
9#[derive(Clone, Copy, PartialEq)]
11struct OrdF32(f32);
12
13impl Eq for OrdF32 {}
14
15impl PartialOrd for OrdF32 {
16 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
17 Some(self.cmp(other))
18 }
19}
20
21impl Ord for OrdF32 {
22 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
23 self.0
24 .partial_cmp(&other.0)
25 .unwrap_or(std::cmp::Ordering::Equal)
26 }
27}
28
29pub fn inpaint<B: Backend>(image: &Image<B>, mask: &Image<B>, radius: f32) -> Result<Image<B>> {
38 let img_dims = image.tensor.dims();
39 let mask_dims = mask.tensor.dims();
40
41 if img_dims.len() != 3 || mask_dims.len() != 3 {
42 return Err(IrisError::InvalidParameter(
43 "image must be [C,H,W] and mask must be [1,H,W]".into(),
44 ));
45 }
46 if img_dims[1] != mask_dims[1] || img_dims[2] != mask_dims[2] {
47 return Err(IrisError::DimensionMismatch {
48 expected: vec![img_dims[0], mask_dims[1], mask_dims[2]],
49 actual: img_dims.to_vec(),
50 });
51 }
52 if mask_dims[0] != 1 {
53 return Err(IrisError::InvalidParameter(
54 "mask must have exactly 1 channel".into(),
55 ));
56 }
57
58 let c = img_dims[0];
59 let h = img_dims[1];
60 let w = img_dims[2];
61 let pixels = h * w;
62
63 let img_data = image.tensor.clone().into_data();
64 let img_vals: Vec<f32> = img_data.iter::<f32>().collect();
65 let mask_data = mask.tensor.clone().into_data();
66 let mask_vals: Vec<f32> = mask_data.iter::<f32>().collect();
67
68 let mut inpaint_buf: Vec<Vec<f32>> = (0..c)
69 .map(|ch| img_vals[ch * pixels..(ch + 1) * pixels].to_vec())
70 .collect();
71
72 #[derive(Clone, Copy, PartialEq, Eq)]
73 enum State {
74 Known,
75 Band,
76 Unknown,
77 }
78
79 let mut state = vec![State::Unknown; pixels];
80 let mut dist = vec![INFINITY; pixels];
81
82 let neighbours = |y: usize, x: usize| -> [(isize, isize); 4] {
83 [
84 (y as isize - 1, x as isize),
85 (y as isize + 1, x as isize),
86 (y as isize, x as isize - 1),
87 (y as isize, x as isize + 1),
88 ]
89 };
90
91 let mut heap: BinaryHeap<(Reverse<OrdF32>, usize)> = BinaryHeap::new();
92
93 for y in 0..h {
94 for x in 0..w {
95 let idx = y * w + x;
96 if mask_vals[idx] == 0.0 {
97 state[idx] = State::Known;
98 dist[idx] = 0.0;
99 for (ny, nx) in neighbours(y, x) {
100 if ny >= 0 && ny < h as isize && nx >= 0 && nx < w as isize {
101 let ni = ny as usize * w + nx as usize;
102 if state[ni] == State::Unknown {
103 state[ni] = State::Band;
104 dist[ni] = 1.0;
105 heap.push((Reverse(OrdF32(1.0)), ni));
106 }
107 }
108 }
109 }
110 }
111 }
112
113 let band_neighbours = |y: usize, x: usize, st: &[State], w_: usize, h_: usize| -> Vec<usize> {
115 let mut result = Vec::with_capacity(4);
116 for (ny, nx) in neighbours(y, x) {
117 if ny < 0 || ny >= h_ as isize || nx < 0 || nx >= w_ as isize {
118 continue;
119 }
120 let ni = ny as usize * w_ + nx as usize;
121 if st[ni] != State::Unknown {
122 result.push(ni);
123 }
124 }
125 result
126 };
127
128 while let Some((Reverse(OrdF32(d)), idx)) = heap.pop() {
129 if state[idx] == State::Known {
130 continue;
131 }
132 if d > radius {
133 break;
134 }
135
136 let y = idx / w;
137 let x = idx % w;
138
139 let known_nbrs = band_neighbours(y, x, &state, w, h);
141
142 let mut sum_weight = 0.0f64;
143 let mut sum_vals: Vec<f64> = vec![0.0; c];
144
145 let mut grad_mag: f64 = 0.0;
148 for &ni in &known_nbrs {
149 let ny = ni / w;
150 let nx = ni % w;
151 let dy_dir = ny as f64 - y as f64;
152 let dx_dir = nx as f64 - x as f64;
153 let spatial_dist = (dy_dir * dy_dir + dx_dir * dx_dir).sqrt().max(1.0e-6);
154
155 let mut g_magnitude: f64 = 0.0;
157 let nnbrs = band_neighbours(ny, nx, &state, w, h);
158 for &nni in &nnbrs {
159 let nny = nni / w;
160 let nnx = nni % w;
161 let ddy = nny as f64 - ny as f64;
162 let ddx = nnx as f64 - nx as f64;
163 let ndist = (ddy * ddy + ddx * ddx).sqrt().max(1.0e-6);
164 for ch in 0..c {
165 let diff = inpaint_buf[ch][nni] as f64 - inpaint_buf[ch][ni] as f64;
166 g_magnitude += diff * diff;
167 }
168 let _ = ndist;
169 }
170 grad_mag = grad_mag.max(g_magnitude.sqrt());
171
172 let w_spatial = 1.0 / (spatial_dist * spatial_dist);
174
175 let beta = if grad_mag > 1.0e-6 {
180 let mut gx: f64 = 0.0;
182 let mut gy: f64 = 0.0;
183 for &nni in &nnbrs {
184 let nny = nni / w;
185 let nnx = nni % w;
186 let ddy = nny as f64 - ny as f64;
187 let ddx = nnx as f64 - nx as f64;
188 for ch in 0..c {
189 let diff = inpaint_buf[ch][nni] as f64 - inpaint_buf[ch][ni] as f64;
190 gx += diff * ddx;
191 gy += diff * ddy;
192 }
193 }
194 let gnorm = (gx * gx + gy * gy).sqrt().max(1.0e-12);
195 gx /= gnorm;
196 gy /= gnorm;
197
198 let px = x as f64 - nx as f64;
200 let py = y as f64 - ny as f64;
201 let pnorm = (px * px + py * py).sqrt().max(1.0e-12);
202 let pxn = px / pnorm;
203 let pyn = py / pnorm;
204
205 (gx * pyn - gy * pxn).abs()
208 } else {
209 0.5 };
211
212 let weight = w_spatial * (1.0 + beta);
213
214 for ch in 0..c {
215 sum_vals[ch] += inpaint_buf[ch][ni] as f64 * weight;
216 }
217 sum_weight += weight;
218 }
219
220 if sum_weight > 1.0e-12 {
221 for ch in 0..c {
222 inpaint_buf[ch][idx] = (sum_vals[ch] / sum_weight) as f32;
223 }
224 }
225
226 state[idx] = State::Known;
227
228 for (ny, nx) in neighbours(y, x) {
229 if ny < 0 || ny >= h as isize || nx < 0 || nx >= w as isize {
230 continue;
231 }
232 let ni = ny as usize * w + nx as usize;
233 if state[ni] == State::Known {
234 continue;
235 }
236 let new_dist = dist[idx] + 1.0;
237 if new_dist < dist[ni] {
238 dist[ni] = new_dist;
239 }
240 if state[ni] == State::Unknown {
241 state[ni] = State::Band;
242 }
243 heap.push((Reverse(OrdF32(dist[ni])), ni));
244 }
245 }
246
247 for idx in 0..pixels {
248 if state[idx] != State::Known {
249 for ch in 0..c {
250 inpaint_buf[ch][idx] = img_vals[ch * pixels + idx];
251 }
252 }
253 }
254
255 let mut flat = vec![0.0f32; c * pixels];
256 for ch in 0..c {
257 flat[ch * pixels..(ch + 1) * pixels].copy_from_slice(&inpaint_buf[ch]);
258 }
259
260 let device = image.tensor.device();
261 let new_data = TensorData::new(flat, [c, h, w]);
262 let new_tensor = Tensor::<B, 3>::from_data(new_data, &device);
263 Ok(Image::new(new_tensor))
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::test_helpers::{TestBackend, test_device};
270 use burn::tensor::{Tensor, TensorData};
271
272 #[test]
273 fn test_inpaint_no_mask() {
274 let device = test_device();
275 let data = vec![0.25f32; 3 * 8 * 8];
276 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
277 TensorData::new(data, [3, 8, 8]),
278 &device,
279 ));
280 let mask = Image::new(Tensor::<TestBackend, 3>::from_data(
281 TensorData::new(vec![0.0f32; 8 * 8], [1, 8, 8]),
282 &device,
283 ));
284
285 let result = inpaint(&img, &mask, 5.0).unwrap();
286 assert_eq!(result.shape(), [3, 8, 8]);
287 let vals: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
288 for v in vals {
289 assert!((v - 0.25).abs() < 1e-6);
290 }
291 }
292
293 #[test]
294 fn test_inpaint_center_region() {
295 let device = test_device();
296 let h = 10usize;
297 let w = 10usize;
298
299 let mut img_vals = vec![0.0f32; h * w];
300 for y in 0..h {
301 for x in 0..w {
302 img_vals[y * w + x] = if x < w / 2 { 0.0 } else { 1.0 };
303 }
304 }
305 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
306 TensorData::new(img_vals.clone(), [1, h, w]),
307 &device,
308 ));
309
310 let mut mask_vals = vec![0.0f32; h * w];
311 mask_vals[4 * w + 4] = 1.0;
312 mask_vals[4 * w + 5] = 1.0;
313 mask_vals[5 * w + 4] = 1.0;
314 mask_vals[5 * w + 5] = 1.0;
315 let mask = Image::new(Tensor::<TestBackend, 3>::from_data(
316 TensorData::new(mask_vals, [1, h, w]),
317 &device,
318 ));
319
320 let result = inpaint(&img, &mask, 5.0).unwrap();
321 assert_eq!(result.shape(), [1, h, w]);
322
323 let out: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
324
325 for y in 4..=5 {
326 for x in 4..=5 {
327 let v = out[y * w + x];
328 assert!((0.0..=1.0).contains(&v), "pixel ({},{}) = {}", x, y, v);
329 }
330 }
331 assert!((out[4 * w + 3] - img_vals[4 * w + 3]).abs() < 1e-6);
332 assert!((out[5 * w + 6] - img_vals[5 * w + 6]).abs() < 1e-6);
333 }
334
335 #[test]
336 fn test_inpaint_rgb_channel_independence() {
337 let device = test_device();
338 let h = 6usize;
339 let w = 6usize;
340
341 let mut img_vals = vec![0.0f32; 3 * h * w];
342 for y in 0..h {
343 for x in 0..w {
344 img_vals[y * w + x] = 0.1;
345 img_vals[h * w + y * w + x] = 0.5;
346 img_vals[2 * h * w + y * w + x] = 0.9;
347 }
348 }
349 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
350 TensorData::new(img_vals, [3, h, w]),
351 &device,
352 ));
353
354 let mut mask_vals = vec![0.0f32; h * w];
355 mask_vals[3 * w + 3] = 1.0;
356 let mask = Image::new(Tensor::<TestBackend, 3>::from_data(
357 TensorData::new(mask_vals, [1, h, w]),
358 &device,
359 ));
360
361 let result = inpaint(&img, &mask, 10.0).unwrap();
362 let out: Vec<f32> = result.tensor.into_data().iter::<f32>().collect();
363
364 let r = out[3 * w + 3];
365 let g = out[h * w + 3 * w + 3];
366 let b = out[2 * h * w + 3 * w + 3];
367 assert!((r - 0.1).abs() < 0.01, "R={}", r);
368 assert!((g - 0.5).abs() < 0.01, "G={}", g);
369 assert!((b - 0.9).abs() < 0.01, "B={}", b);
370 }
371
372 #[test]
373 fn test_inpaint_dimension_mismatch() {
374 let device = test_device();
375 let img = Image::new(Tensor::<TestBackend, 3>::from_data(
376 TensorData::new(vec![0.5f32; 3 * 8 * 8], [3, 8, 8]),
377 &device,
378 ));
379 let mask = Image::new(Tensor::<TestBackend, 3>::from_data(
380 TensorData::new(vec![0.0f32; 6 * 6], [1, 6, 6]),
381 &device,
382 ));
383 let result = inpaint(&img, &mask, 5.0);
384 assert!(result.is_err());
385 }
386}