noest 0.1.1

Tool to estimate noise in a video and output the results, optionally as ISO for use with photon noise in AV1 encoding.
use std::{sync::{mpsc::{Receiver}, Arc, Mutex}, path::Path};

use av_metrics_decoders::{Frame, Pixel, Decoder, Plane, VapoursynthDecoder};
#[inline(always)]
fn round_power_of_two(value: isize, n: isize) -> isize {
    ((value) + ((1 << (n)) >> 1)) >> (n)
}

#[inline(always)]
fn run_plane<D: Pixel>(plane: &Plane<D>, depth: usize) -> f64 {
    let threshold = 50;

    let (width, height, stride) = 
        (plane.cfg.width, plane.cfg.height, plane.cfg.stride);

    let (mut count, mut accum) = (0, 0);
    let plane_data = plane.data_origin();

    for i in 1..height-1 {
        for j in 1..width-1 {
            // Set up 3x3 Matrix
            let mut mat: [[isize; 3]; 3] = Default::default();
            let center_idx = i * stride + j;

            for ii in -1..=1_isize {
                for jj in -1..=1_isize {
                    let idx = center_idx + (ii as usize) * stride + (jj as usize);

                    mat[(ii + 1) as usize][(jj + 1) as usize] = plane_data[idx].to_isize().unwrap();
                }
            }

            // Compute Sobel gradients
            let g_x = (mat[0][0] - mat[0][2]) + (mat[2][0] - mat[2][2]) +
                2 * (mat[1][0] - mat[1][2]);

            let g_y = (mat[0][0] - mat[2][0]) + (mat[0][2] - mat[2][2]) +
                2 * (mat[0][1] - mat[2][1]);

            let g_a = round_power_of_two(g_x.abs() + g_y.abs(), depth as isize - 8);

            if g_a < threshold { // Only count smooth pixels
                let v = 4 * mat[1][1] -
                    2 * (mat[0][1] + mat[2][1] + mat[1][0] + mat[1][2]) +
                    (mat[0][0] + mat[0][2] + mat[2][0] + mat[2][2]);

                accum += round_power_of_two(v.abs(), depth as isize - 8);
                count += 1;
            }
        }
    }

    if count < 16 { -1.0 } else {
        accum as f64 / (6.0 * count as f64) * 1.25331413732
    }
}

fn run_frame<D: Pixel>(frame: &Frame<D>, depth: usize) -> [f64; 3] {
    let planes = &frame.planes;

    let mut scores: [f64; 3] = [-1.0, -1.0, -1.0];

    scores[0] = run_plane(&planes[0], depth);
    scores[1] = run_plane(&planes[1], depth);
    scores[2] = run_plane(&planes[2], depth);

    scores
}

pub fn run(source: &Path, threads: usize) -> (Option<usize>, Receiver<[f64; 3]>) {
    let decoder = VapoursynthDecoder::new_from_video(source)
        .expect("Failed to open input file!");

    let frame_count = decoder.get_frame_count();

    let depth = decoder.get_bit_depth();

    let decoders = Arc::new(Mutex::new(decoder));
    let (tx, rx) = std::sync::mpsc::channel();

    for _ in 0..threads {
        let decoders = Arc::clone(&decoders);
        let res_tx = tx.clone();

        std::thread::spawn(move || {
            match depth {
                8 => {
                    loop {
                        let frame = {
                            let mut guard = decoders.lock().unwrap();
                            guard.read_video_frame::<u8>()
                        };

                        match frame {
                            Some(f) => {
                                res_tx.send(run_frame(&f, 8)).unwrap();
                            }
                            None => {
                                break;
                            },
                        }
                    }
                },
                _ => {
                    loop {
                        let frame = {
                            let mut guard = decoders.lock().unwrap();
                            guard.read_video_frame::<u16>()
                        };
            
                        match frame {
                            Some(f) => {
                                res_tx.send(run_frame(&f, depth)).unwrap();
                            }
                            None => {
                                break;
                            },
                        }
                    }
                }
            }
        });
    }

    drop(tx);
    (frame_count.ok(), rx)
}