| 1 | use anyhow::{ensure, Result}; |
| 2 | use num_rational::Rational64; |
| 3 | use v_frame::{frame::Frame, pixel::Pixel}; |
| 4 | |
| 5 | use self::solver::{FlatBlockFinder, NoiseModel}; |
| 6 | use crate::{util::frame_into_u8, GrainTableSegment}; |
| 7 | |
| 8 | mod solver; |
| 9 | |
| 10 | const BLOCK_SIZE: usize = 32; |
| 11 | const BLOCK_SIZE_SQUARED: usize = BLOCK_SIZE * BLOCK_SIZE; |
| 12 | |
| 13 | pub struct DiffGenerator { |
| 14 | fps: Rational64, |
| 15 | source_bit_depth: usize, |
| 16 | denoised_bit_depth: usize, |
| 17 | frame_count: usize, |
| 18 | prev_timestamp: u64, |
| 19 | flat_block_finder: FlatBlockFinder, |
| 20 | noise_model: NoiseModel, |
| 21 | grain_table: Vec<GrainTableSegment>, |
| 22 | } |
| 23 | |
| 24 | impl DiffGenerator { |
| 25 | #[must_use ] |
| 26 | pub fn new(fps: Rational64, source_bit_depth: usize, denoised_bit_depth: usize) -> Self { |
| 27 | Self { |
| 28 | frame_count: 0, |
| 29 | fps, |
| 30 | flat_block_finder: FlatBlockFinder::new(), |
| 31 | noise_model: NoiseModel::new(), |
| 32 | grain_table: Vec::new(), |
| 33 | prev_timestamp: 0, |
| 34 | source_bit_depth, |
| 35 | denoised_bit_depth, |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | /// Processes the next frame and adds the results to the state of this |
| 40 | /// `DiffGenerator`. |
| 41 | /// |
| 42 | /// # Errors |
| 43 | /// - If the frames do not have the same resolution |
| 44 | /// - If the frames do not have the same chroma subsampling |
| 45 | pub fn diff_frame<T: Pixel, U: Pixel>( |
| 46 | &mut self, |
| 47 | source: &Frame<T>, |
| 48 | denoised: &Frame<U>, |
| 49 | ) -> Result<()> { |
| 50 | self.diff_frame_internal( |
| 51 | &frame_into_u8(source, self.source_bit_depth), |
| 52 | &frame_into_u8(denoised, self.denoised_bit_depth), |
| 53 | ) |
| 54 | } |
| 55 | |
| 56 | /// Finalize the state of this `DiffGenerator` and return the resulting |
| 57 | /// grain table segments. |
| 58 | #[must_use ] |
| 59 | pub fn finish(mut self) -> Vec<GrainTableSegment> { |
| 60 | log::debug!("Updating final parameters" ); |
| 61 | self.grain_table.push( |
| 62 | self.noise_model |
| 63 | .get_grain_parameters(self.prev_timestamp, i64::MAX as u64), |
| 64 | ); |
| 65 | |
| 66 | self.grain_table |
| 67 | } |
| 68 | |
| 69 | fn diff_frame_internal(&mut self, source: &Frame<u8>, denoised: &Frame<u8>) -> Result<()> { |
| 70 | verify_dimensions_match(source, denoised)?; |
| 71 | |
| 72 | let (flat_blocks, num_flat_blocks) = self.flat_block_finder.run(&source.planes[0]); |
| 73 | log::debug!("Num flat blocks: {}" , num_flat_blocks); |
| 74 | |
| 75 | log::debug!("Updating noise model" ); |
| 76 | let status = self.noise_model.update(source, denoised, &flat_blocks); |
| 77 | |
| 78 | if status == NoiseStatus::DifferentType { |
| 79 | let cur_timestamp = self.frame_count as u64 * 10_000_000u64 * *self.fps.denom() as u64 |
| 80 | / *self.fps.numer() as u64; |
| 81 | log::debug!( |
| 82 | "Updating parameters for times {} to {}" , |
| 83 | self.prev_timestamp, |
| 84 | cur_timestamp |
| 85 | ); |
| 86 | self.grain_table.push( |
| 87 | self.noise_model |
| 88 | .get_grain_parameters(self.prev_timestamp, cur_timestamp), |
| 89 | ); |
| 90 | self.noise_model.save_latest(); |
| 91 | self.prev_timestamp = cur_timestamp; |
| 92 | } |
| 93 | log::debug!("Noise model updated for frame {}" , self.frame_count); |
| 94 | self.frame_count += 1; |
| 95 | |
| 96 | Ok(()) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | #[derive (Debug)] |
| 101 | enum NoiseStatus { |
| 102 | Ok, |
| 103 | DifferentType, |
| 104 | Error(anyhow::Error), |
| 105 | } |
| 106 | |
| 107 | impl PartialEq for NoiseStatus { |
| 108 | fn eq(&self, other: &Self) -> bool { |
| 109 | match (self, other) { |
| 110 | (&Self::Error(_), &Self::Error(_)) => true, |
| 111 | _ => core::mem::discriminant(self) == core::mem::discriminant(other), |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | fn verify_dimensions_match(source: &Frame<u8>, denoised: &Frame<u8>) -> Result<()> { |
| 117 | let res_1 = (source.planes[0].cfg.width, source.planes[0].cfg.height); |
| 118 | let res_2 = (denoised.planes[0].cfg.width, denoised.planes[0].cfg.height); |
| 119 | ensure!( |
| 120 | res_1 == res_2, |
| 121 | "Luma resolutions were not equal, {}x {} != {}x {}" , |
| 122 | res_1.0, |
| 123 | res_1.1, |
| 124 | res_2.0, |
| 125 | res_2.1 |
| 126 | ); |
| 127 | |
| 128 | let res_1 = (source.planes[1].cfg.width, source.planes[1].cfg.height); |
| 129 | let res_2 = (denoised.planes[1].cfg.width, denoised.planes[1].cfg.height); |
| 130 | ensure!( |
| 131 | res_1 == res_2, |
| 132 | "Chroma resolutions were not equal, {}x {} != {}x {}" , |
| 133 | res_1.0, |
| 134 | res_1.1, |
| 135 | res_2.0, |
| 136 | res_2.1 |
| 137 | ); |
| 138 | |
| 139 | Ok(()) |
| 140 | } |
| 141 | |