use super::convolve::{horizontal_row_symmetric, reflect};
use super::simd::{square_and_cross, sum, vertical_taps_combine};
const PLANES: usize = 5;
pub(crate) struct Workspace {
ring: Vec<f32>,
products: Vec<f32>,
rows: Vec<usize>,
width: usize,
taps: usize,
}
impl Workspace {
pub(crate) fn new(width: usize, taps: usize) -> Self {
Self {
ring: vec![0f32; PLANES * taps * width],
products: vec![0f32; 3 * width],
rows: vec![0usize; taps],
width,
taps,
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn scale_statistics_into(
plane1: &[f32],
plane2: &[f32],
width: usize,
height: usize,
window: &[f32],
c1: f32,
c2: f32,
workspace: &mut Workspace,
map: &mut [f32],
cs_map: &mut [f32],
) -> (f64, f64) {
let taps = window.len();
let pad = taps / 2;
debug_assert!(width > 0 && height > 0);
debug_assert!(workspace.width >= width && workspace.taps >= taps);
debug_assert!(map.len() >= width * height && cs_map.len() >= width * height);
let Workspace {
ring,
products,
rows,
..
} = workspace;
let products = &mut products[..3 * width];
let rows = &mut rows[..taps];
let mut filled = 0usize;
for y in 0..height {
let horizon = (y + pad).min(height - 1);
while filled <= horizon {
fill_ring_row(plane1, plane2, width, filled, taps, window, ring, products);
filled += 1;
}
for (tap, row) in rows.iter_mut().enumerate() {
let slot = reflect(y as isize + tap as isize - pad as isize, height) % taps;
*row = slot * width;
}
vertical_taps_combine(
ring,
taps * width,
rows,
window,
c1,
c2,
&mut map[y * width..][..width],
&mut cs_map[y * width..][..width],
);
}
let len = width * height;
(sum(&map[..len]), sum(&cs_map[..len]))
}
#[allow(clippy::too_many_arguments)]
fn fill_ring_row(
plane1: &[f32],
plane2: &[f32],
width: usize,
row: usize,
taps: usize,
window: &[f32],
ring: &mut [f32],
products: &mut [f32],
) {
let first = &plane1[row * width..][..width];
let second = &plane2[row * width..][..width];
let (squared1, rest) = products.split_at_mut(width);
let (squared2, cross) = rest.split_at_mut(width);
square_and_cross(first, second, squared1, squared2, cross);
let slot = row % taps;
let sources: [&[f32]; PLANES] = [first, second, squared1, squared2, cross];
for (plane, source) in sources.into_iter().enumerate() {
let base = (plane * taps + slot) * width;
horizontal_row_symmetric(source, &mut ring[base..base + width], window);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convolve::convolve_same_symmetric;
use crate::gaussian::{window_1d, SIGMA};
use crate::simd::ssim_combine_split;
fn reference(
plane1: &[f32],
plane2: &[f32],
width: usize,
height: usize,
window: &[f32],
c1: f32,
c2: f32,
) -> (Vec<f32>, Vec<f32>) {
let len = width * height;
let mut scratch = vec![0f32; len];
let mut mu1 = vec![0f32; len];
let mut mu2 = vec![0f32; len];
convolve_same_symmetric(plane1, &mut mu1, &mut scratch, width, height, window);
convolve_same_symmetric(plane2, &mut mu2, &mut scratch, width, height, window);
let mut squared1 = vec![0f32; len];
let mut squared2 = vec![0f32; len];
let mut cross = vec![0f32; len];
square_and_cross(plane1, plane2, &mut squared1, &mut squared2, &mut cross);
let mut sigma1 = vec![0f32; len];
let mut sigma2 = vec![0f32; len];
let mut sigma12 = vec![0f32; len];
convolve_same_symmetric(&squared1, &mut sigma1, &mut scratch, width, height, window);
convolve_same_symmetric(&squared2, &mut sigma2, &mut scratch, width, height, window);
convolve_same_symmetric(&cross, &mut sigma12, &mut scratch, width, height, window);
let mut map = vec![0f32; len];
let mut cs_map = vec![0f32; len];
ssim_combine_split(
&mu1,
&mu2,
&sigma1,
&sigma2,
&sigma12,
c1,
c2,
&mut map,
&mut cs_map,
);
(map, cs_map)
}
#[test]
fn streaming_matches_the_unfused_pipeline_bit_for_bit() {
let window = window_1d(11, SIGMA);
for (width, height) in [(64usize, 48usize), (11, 11), (37, 13), (128, 12)] {
let plane1: Vec<f32> = (0..width * height)
.map(|i| ((i * 37) % 251) as f32)
.collect();
let plane2: Vec<f32> = (0..width * height)
.map(|i| (((i * 53) % 241) as f32) + 0.5)
.collect();
let (expected_map, expected_cs) =
reference(&plane1, &plane2, width, height, &window, 6.5025, 58.5225);
let mut workspace = Workspace::new(width, window.len());
let mut map = vec![0f32; width * height];
let mut cs_map = vec![0f32; width * height];
let (ssim_sum, cs_sum) = scale_statistics_into(
&plane1,
&plane2,
width,
height,
&window,
6.5025,
58.5225,
&mut workspace,
&mut map,
&mut cs_map,
);
assert_eq!(map, expected_map, "{width}x{height} ssim map");
assert_eq!(cs_map, expected_cs, "{width}x{height} cs map");
assert_eq!(ssim_sum, sum(&expected_map));
assert_eq!(cs_sum, sum(&expected_cs));
}
}
#[test]
fn a_workspace_sized_for_a_larger_level_still_matches() {
let window = window_1d(11, SIGMA);
let (width, height) = (24usize, 19usize);
let plane1: Vec<f32> = (0..width * height).map(|i| (i % 97) as f32).collect();
let plane2: Vec<f32> = (0..width * height).map(|i| ((i * 3) % 89) as f32).collect();
let (expected_map, _) = reference(&plane1, &plane2, width, height, &window, 6.5, 58.5);
let mut workspace = Workspace::new(256, window.len());
let mut map = vec![0f32; 256 * 256];
let mut cs_map = vec![0f32; 256 * 256];
scale_statistics_into(
&plane1,
&plane2,
width,
height,
&window,
6.5,
58.5,
&mut workspace,
&mut map,
&mut cs_map,
);
assert_eq!(&map[..width * height], &expected_map[..]);
}
}