use cubecl::prelude::*;
use super::helpers::{R, make_client};
use crate::nl4d::kernels::nl4d_mv_regularise;
use crate::nlmeans::motion::THSAD_PIXEL;
const BLKSIZE: u32 = 16;
const STEP: u32 = 8;
fn run(
w: u32,
h: u32,
centre: &[f32],
neighbour: &[f32],
mv_in: &[i32],
lambda: f32,
) -> (Vec<i32>, Vec<f32>) {
let client = make_client();
let blocks_x = w.div_ceil(STEP);
let blocks_y = h.div_ceil(STEP);
let blocks = (blocks_x * blocks_y) as usize;
assert_eq!(mv_in.len(), 2 * blocks);
let centre_buf = client.create_from_slice(f32::as_bytes(centre));
let neighbour_buf = client.create_from_slice(f32::as_bytes(neighbour));
let mv_in_buf = client.create_from_slice(i32::as_bytes(mv_in));
let mv_out = client.empty(2 * blocks * size_of::<i32>());
let conf_out = client.empty(blocks * size_of::<f32>());
let thsad = (BLKSIZE * BLKSIZE) as f32 * THSAD_PIXEL;
unsafe {
nl4d_mv_regularise::launch_unchecked::<R>(
&client,
CubeCount::new_2d(blocks_x, blocks_y),
CubeDim::new_2d(8, 8),
ArrayArg::from_raw_parts(centre_buf, centre.len()),
ArrayArg::from_raw_parts(neighbour_buf, neighbour.len()),
ArrayArg::from_raw_parts(mv_in_buf, 2 * blocks),
ArrayArg::from_raw_parts(mv_out.clone(), 2 * blocks),
ArrayArg::from_raw_parts(conf_out.clone(), blocks),
lambda * (BLKSIZE * BLKSIZE) as f32 * THSAD_PIXEL,
0.0,
thsad,
w,
h,
BLKSIZE,
STEP,
blocks_x,
blocks_y,
);
}
let mv = i32::from_bytes(&client.read_one(mv_out).expect("mv readback"))[..2 * blocks].to_vec();
let conf = f32::from_bytes(&client.read_one(conf_out).expect("conf readback"))[..blocks].to_vec();
(mv, conf)
}
fn textured(w: u32, h: u32, seed: u32) -> Vec<f32> {
(0..w * h)
.map(|i| {
let mut x = i
.wrapping_mul(2654435761)
.wrapping_add(seed.wrapping_mul(0x9E37_79B9));
x ^= x >> 15;
x = x.wrapping_mul(0x85EB_CA6B);
x ^= x >> 13;
0.2 + 0.6 * (x as f32 / u32::MAX as f32)
})
.collect()
}
fn shifted(centre: &[f32], w: u32, h: u32, dx: i32, dy: i32) -> Vec<f32> {
let mut out = vec![0.0f32; (w * h) as usize];
for y in 0..h as i32 {
for x in 0..w as i32 {
let sx = (x - dx).clamp(0, w as i32 - 1) as u32;
let sy = (y - dy).clamp(0, h as i32 - 1) as u32;
out[(y as u32 * w + x as u32) as usize] = centre[(sy * w + sx) as usize];
}
}
out
}
fn uniform_field(blocks: usize, v: [i32; 2]) -> Vec<i32> {
let mut f = Vec::with_capacity(2 * blocks);
for _ in 0..blocks {
f.push(v[0]);
f.push(v[1]);
}
f
}
#[test]
fn an_outlier_in_a_flat_region_moves_to_the_median() {
let (w, h) = (64u32, 64u32);
let blocks_x = w.div_ceil(STEP);
let blocks = (blocks_x * h.div_ceil(STEP)) as usize;
let flat = vec![0.5f32; (w * h) as usize];
let mut field = uniform_field(blocks, [3, 1]);
let outlier = (4 * blocks_x + 4) as usize;
field[2 * outlier] = -6;
field[2 * outlier + 1] = 5;
let (out, _) = run(w, h, &flat, &flat, &field, 1.0);
assert_eq!([out[2 * outlier], out[2 * outlier + 1]], [3, 1]);
for b in 0..blocks {
if b != outlier {
assert_eq!([out[2 * b], out[2 * b + 1]], [3, 1], "block {b} moved");
}
}
}
#[test]
fn a_zero_penalty_keeps_the_input_field_on_ties() {
let (w, h) = (64u32, 64u32);
let blocks_x = w.div_ceil(STEP);
let blocks = (blocks_x * h.div_ceil(STEP)) as usize;
let flat = vec![0.5f32; (w * h) as usize];
let mut field = uniform_field(blocks, [3, 1]);
let outlier = (4 * blocks_x + 4) as usize;
field[2 * outlier] = -6;
field[2 * outlier + 1] = 5;
let (out, _) = run(w, h, &flat, &flat, &field, 0.0);
assert_eq!(out, field);
}
#[test]
fn a_true_boundary_block_keeps_its_vector_when_the_sad_margin_wins() {
let (w, h) = (64u32, 64u32);
let blocks_x = w.div_ceil(STEP);
let blocks = (blocks_x * h.div_ceil(STEP)) as usize;
let centre = textured(w, h, 1);
let neighbour = shifted(¢re, w, h, 2, 0);
let mut field = uniform_field(blocks, [0, 0]);
let truthful = (4 * blocks_x + 4) as usize;
field[2 * truthful] = 2;
let (out, conf) = run(w, h, ¢re, &neighbour, &field, 1.0);
assert_eq!([out[2 * truthful], out[2 * truthful + 1]], [2, 0]);
assert!(
conf[truthful] > 0.9,
"an exact match scores a high confidence, got {}",
conf[truthful]
);
let right = truthful + 1;
assert_eq!([out[2 * right], out[2 * right + 1]], [2, 0]);
}
#[test]
fn the_median_rule_picks_the_lower_of_two_middle_values() {
let (w, h) = (24u32, 24u32);
let blocks_x = w.div_ceil(STEP);
let blocks_y = h.div_ceil(STEP);
assert_eq!((blocks_x, blocks_y), (3, 3));
let flat = vec![0.5f32; (w * h) as usize];
#[rustfmt::skip]
let field: Vec<i32> = vec![
-1, 0, -1, 0, -1, 0,
-1, 0, 99, 99, 2, 0,
2, 0, 2, 0, 2, 0,
];
let (out, _) = run(w, h, &flat, &flat, &field, 1.0);
let centre = (blocks_x + 1) as usize;
assert_eq!(
[out[2 * centre], out[2 * centre + 1]],
[-1, 0],
"the centre block must take the lower median, not the upper one"
);
let corner = 0usize;
assert_eq!(
[out[2 * corner], out[2 * corner + 1]],
[-1, 0],
"the corner block's three-member median must resolve too"
);
}
#[test]
fn confidence_follows_the_winning_vector() {
let (w, h) = (64u32, 64u32);
let blocks_x = w.div_ceil(STEP);
let blocks = (blocks_x * h.div_ceil(STEP)) as usize;
let centre = textured(w, h, 2);
let neighbour = shifted(¢re, w, h, 1, 1);
let field = uniform_field(blocks, [1, 1]);
let (_, conf) = run(w, h, ¢re, &neighbour, &field, 1.0);
let interior = (3 * blocks_x + 3) as usize;
assert!(
conf[interior] > 0.99,
"a perfect match must score ~1, got {}",
conf[interior]
);
let wrong = uniform_field(blocks, [-3, -3]);
let (_, conf) = run(w, h, ¢re, &neighbour, &wrong, 0.0);
assert!(
conf[interior] < 0.5,
"a wrong vector on texture must score low, got {}",
conf[interior]
);
let boundary_centre = textured(w, h, 1);
let boundary_neighbour = shifted(&boundary_centre, w, h, 2, 0);
let mut boundary_field = uniform_field(blocks, [0, 0]);
let truthful = (4 * blocks_x + 4) as usize;
boundary_field[2 * truthful] = 2;
let right = truthful + 1;
let (out, conf) = run(w, h, &boundary_centre, &boundary_neighbour, &boundary_field, 1.0);
assert_eq!([out[2 * right], out[2 * right + 1]], [2, 0]);
assert!(
conf[right] > 0.9,
"right wins its neighbour's exact match (candidate 2), so confidence must be high, got {}",
conf[right]
);
}