use super::helpers::*;
use crate::nlmeans::*;
fn frame_with_square(
w: u32,
h: u32,
background: f32,
square_x: u32,
square_y: u32,
square_size: u32,
square_val: f32,
) -> Vec<f32> {
let mut frame = vec![background; (w * h) as usize];
for dy in 0..square_size {
for dx in 0..square_size {
let x = square_x + dx;
let y = square_y + dy;
if x < w && y < h {
frame[(y * w + x) as usize] = square_val;
}
}
}
frame
}
#[test]
fn motion_compensation_uniform_passthrough() {
let client = make_client();
let w = 32;
let h = 32;
let frame = make_uniform_frame(w, h, 1, 0.5);
let params = NlmParams {
temporal_radius: 1,
search_radius: 2,
patch_radius: 2,
strength: 1.2,
self_weight: 1.0,
channels: ChannelMode::Luma,
prefilter: PrefilterMode::None,
motion_compensation: MotionCompensationMode::Mvtools {
blksize: 8,
overlap: 4,
search_radius: 2,
pyramid_levels: 2,
},
};
let mut d = NlmDenoiser::<R>::new(&client, params, w, h);
d.push_frame(&frame);
d.push_frame(&frame);
d.push_frame(&frame);
let result = d.denoise().unwrap().unwrap().to_vec();
assert_eq!(result.len(), (w * h) as usize);
for (i, &v) in result.iter().enumerate() {
assert!(v.is_finite(), "pixel {i}: non-finite output {v}");
assert!(
(v - 0.5).abs() < 1e-3,
"pixel {i}: expected 0.5 (uniform input passthrough), got {v}"
);
}
}
#[test]
fn motion_compensation_with_bilateral_finite() {
let client = make_client();
let w = 32;
let h = 32;
let frame = make_uniform_frame(w, h, 1, 0.5);
let params = NlmParams {
temporal_radius: 1,
search_radius: 2,
patch_radius: 2,
strength: 1.2,
self_weight: 1.0,
channels: ChannelMode::Luma,
prefilter: PrefilterMode::Bilateral {
sigma_s: 1.0,
sigma_r: 0.1,
},
motion_compensation: MotionCompensationMode::Mvtools {
blksize: 8,
overlap: 4,
search_radius: 2,
pyramid_levels: 2,
},
};
let mut d = NlmDenoiser::<R>::new(&client, params, w, h);
d.push_frame(&frame);
d.push_frame(&frame);
d.push_frame(&frame);
let result = d.denoise().unwrap().unwrap().to_vec();
assert_eq!(result.len(), (w * h) as usize);
for (i, &v) in result.iter().enumerate() {
assert!(v.is_finite(), "pixel {i}: non-finite output {v}");
assert!((-0.01..=1.01).contains(&v), "pixel {i}: out-of-range output {v}");
}
}
#[test]
fn motion_compensation_translating_square_preserves_centre() {
let client = make_client();
let w = 32u32;
let h = 32u32;
let bg = 0.3;
let sq_val = 0.8;
let sq_size = 4u32;
let f0 = frame_with_square(w, h, bg, 12, 12, sq_size, sq_val);
let f1 = frame_with_square(w, h, bg, 14, 14, sq_size, sq_val);
let f2 = frame_with_square(w, h, bg, 16, 16, sq_size, sq_val);
let params = NlmParams {
temporal_radius: 1,
search_radius: 2,
patch_radius: 2,
strength: 1.2,
self_weight: 1.0,
channels: ChannelMode::Luma,
prefilter: PrefilterMode::None,
motion_compensation: MotionCompensationMode::Mvtools {
blksize: 8,
overlap: 4,
search_radius: 2,
pyramid_levels: 2,
},
};
let mut d = NlmDenoiser::<R>::new(&client, params, w, h);
d.push_frame(&f0);
d.push_frame(&f1);
d.push_frame(&f2);
let result = d.denoise().unwrap().unwrap().to_vec();
assert_eq!(result.len(), (w * h) as usize);
for (i, &v) in result.iter().enumerate() {
assert!(v.is_finite(), "pixel {i}: non-finite output {v}");
assert!((-0.01..=1.01).contains(&v), "pixel {i}: out-of-range output {v}");
}
let halfway = (bg + sq_val) * 0.5;
let centre_val = result[(15 * w + 15) as usize];
assert!(
centre_val > halfway,
"centre of moving square should remain above halfway between bg ({bg}) \
and sq_val ({sq_val}) (= {halfway}), got {centre_val}",
);
let bg_val = result[(2 * w + 2) as usize];
assert!(
(bg_val - bg).abs() < 0.05,
"background pixel (2, 2) should stay near {bg}, got {bg_val} \
(MC may be warping neighbour squares into the background region)",
);
}