Skip to main content

Denoiser

Struct Denoiser 

Source
pub struct Denoiser { /* private fields */ }
Expand description

A stateful denoiser that cleans a stream of frames.

Push frames in order with push_frame and collect the cleaned ones with recv_frame or try_recv_frame.

At the end of the stream call flush to drain whatever temporal context is left.

Input frames are f32 values in [0, 1], laid out as width * height * channels. Output comes back as a FrameOutput in whichever OutputFormat the options named.

use av_denoise_core::accelerate::Accelerator;
use av_denoise_core::{ChannelMode, Denoiser, DenoiserOptions, DenoisingMode, Device};

let options = DenoiserOptions::builder()
    .channel_mode(ChannelMode::Luma)
    .mode(DenoisingMode::Temporal { radius: 2 })
    .build();

let mut denoiser = Denoiser::create(
    &[Accelerator::Vulkan],
    &Device::Default,
    1920,
    1080,
    options,
)?;

let frames: Vec<Vec<f32>> = read_my_frames();
let mut cleaned: Vec<Vec<f32>> = Vec::new();

for frame in &frames {
    denoiser.push_frame(frame)?;

    // Temporal denoising runs a few frames behind the input, so
    // there is not always one ready to collect.
    if let Some(out) = denoiser.recv_frame()? {
        cleaned.push(out.into_f32().expect("built for f32 output"));
    }
}

// Drain the frames still inside the temporal window.
denoiser.flush(|out| cleaned.push(out.into_f32().expect("built for f32 output")))?;

Implementations§

Source§

impl Denoiser

Source

pub fn create( accelerators: &[Accelerator], device: &Device, width: u32, height: u32, options: DenoiserOptions, ) -> Result<Denoiser, DenoiserError>

Tries each accelerator in accelerators in order and builds a denoiser on the first one that works.

device picks a non-default device on the chosen runtime.

§Thread stack size

cubecl spawns its own per-device worker thread, named DS{U,D}-…, and runs GPU kernel codegen on it. That thread gets Rust’s default stack, which is RUST_MIN_STACK or 2 MiB when that is unset.

The windowed NLM kernels unroll their body (2 * search_radius + 1)^2 times, so a search_radius of about 5 or more can overflow the 2 MiB default and abort the process.

Callers using a search_radius above 4 should call crate::raise_codegen_stack_limit before any cubecl thread spawns, usually right at the top of main.

Source

pub fn selected_accelerator(&self) -> Accelerator

The accelerator sniff_best_accelerator picked.

Source

pub fn width(&self) -> u32

The width passed at construction.

Source

pub fn height(&self) -> u32

The height passed at construction.

Source

pub fn temporal_radius(&self) -> u32

The temporal radius the resolved parameters run at.

Source

pub fn output_format(&self) -> OutputFormat

The format every collected frame comes back in.

Source

pub fn window_span(&self) -> WindowSpan

How many frames behind and ahead of a target frame this denoiser needs pushed, in order, to produce that frame’s output through PlanarDenoiser::reseed.

Both NLM algorithms only ever need their own 2 * radius + 1 sliding window, symmetric around the target frame: WindowSpan { behind: radius, ahead: radius }.

nl4d’s cross-frame accumulator scatters every pass’s contribution across the 2 * radius + 1 frames the pass reaches, and a frame’s own region only starts collecting once the pass that first reaches it, the one centred radius frames behind it, has run. That earliest pass is itself only real once the front end’s own window is full at that centre, which needs radius more frames behind it again. So nl4d needs the target’s own radius-wide neighbourhood doubled on both sides: WindowSpan { behind: 2 * radius, ahead: 2 * radius }.

Source

pub fn push_frame(&mut self, frame: &[f32]) -> Result<(), DenoiserError>

Uploads one frame into the temporal window.

frame holds width * height * channels f32 values in [0, 1].

Once the window is full and the pipeline has room, this also starts the kernels for the next denoised frame.

Up to MAX_PENDING outputs can be in flight at once, so the GPU runs one frame’s kernels while the previous frame’s readback is still travelling. At that ceiling this returns DenoiserError::QueueFull, and the caller has to drain a frame with Self::recv_frame before pushing more.

Any other failure poisons the denoiser, so every further call returns DenoiserError::Poisoned until Self::reset_stream clears it. QueueFull does not poison, since it is the documented retry signal above.

Source

pub fn push_frame_wire( &mut self, planes: &[&[u8]], depth: Depth, ) -> Result<(), DenoiserError>

Uploads one frame held as wire bytes into the temporal window.

planes holds one width * height plane per channel at depth, which the GPU normalises and interleaves. The planes run Y, U, V for a fused frame and U, V for a chroma pair.

Queueing, poisoning, and the QueueFull retry signal work exactly as they do for Self::push_frame.

Source

pub fn push_frame_wire_priming( &mut self, planes: &[&[u8]], depth: Depth, ) -> Result<(), DenoiserError>

Uploads one frame held as wire bytes into the temporal window without starting a denoise.

The wire counterpart of Self::push_frame_priming.

Source

pub fn push_frame_priming(&mut self, frame: &[f32]) -> Result<(), DenoiserError>

Uploads one frame into the temporal window without starting a denoise.

The ring advances exactly as it does for Self::push_frame, so the window still fills, but no kernels are submitted and no output is queued. This is how a caller that can hand over a whole window at once, rather than a strictly ordered stream, fills the window in one go and lets only the last push in it submit.

A failure elsewhere poisons the denoiser, so this refuses to run until Self::reset_stream clears it.

Source

pub fn reset_stream(&mut self)

Drops the current stream and returns to the state a fresh denoiser starts in, keeping every GPU allocation.

Anything still in flight is discarded. This also clears the poison an earlier failure left, so it is the recovery path for DenoiserError::Poisoned.

Source

pub fn recv_frame(&mut self) -> Result<Option<FrameOutput>, DenoiserError>

Blocks until the in-flight denoise finishes and returns the cleaned frame.

Returns Ok(None) when nothing is in flight, which happens while the temporal window is still filling up.

A failure poisons the denoiser, so every further call returns DenoiserError::Poisoned until Self::reset_stream clears it.

Source

pub fn try_recv_frame(&mut self) -> Result<Option<FrameOutput>, DenoiserError>

Polls the in-flight denoise once.

Returns Ok(None) both when nothing is in flight and when the in-flight readback has not landed yet, so None alone does not tell those two cases apart.

A caller that needs the frame rather than just checking on it should use Self::recv_frame instead.

This only avoids blocking on the wgpu backends, meaning Vulkan and Metal. On CUDA and ROCm the readback completes synchronously on its first poll, so this call blocks until the readback lands there, the same as recv_frame.

A failure poisons the denoiser, so every further call returns DenoiserError::Poisoned until Self::reset_stream clears it.

Source

pub fn flush( &mut self, sink: impl FnMut(FrameOutput), ) -> Result<(), DenoiserError>

Drains the in-flight frames and the trailing temporal tail, handing each frame it produces to sink.

The tail is padded by repeating the last pushed frame.

On success the denoiser is ready for a fresh, unrelated stream of the same size and parameters. Pushing again after a flush starts a new temporal window from scratch, and flushing more than once is fine.

A failure poisons the denoiser, so every further call returns DenoiserError::Poisoned until Self::reset_stream clears it.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoComptime for T

Source§

fn comptime(self) -> Self

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more