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.

Frames are f32 values in [0, 1], laid out as width * height * channels.

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);
    }
}

// Drain the frames still inside the temporal window.
denoiser.flush(|out| cleaned.push(out))?;

Implementations§

Source§

impl Denoiser

Source

pub fn create( accelerators: &[Accelerator], device: &Device, width: u32, height: u32, options: DenoiserOptions, ) -> Result<Self, 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 set RUST_MIN_STACK to at least 16 MiB before any cubecl thread spawns, usually right at the top of main.

if std::env::var_os("RUST_MIN_STACK").is_none() {
    // SAFETY: single-threaded at startup.
    unsafe { std::env::set_var("RUST_MIN_STACK", "16777216") };
}
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 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.

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.

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.

Source

pub fn recv_frame(&mut self) -> Result<Option<Vec<f32>>, 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.

Source

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

Collects the in-flight denoise if one is ready.

This can still block for a moment while the runtime confirms the readback has landed. When the kernels have already finished the wait is effectively nothing.

Source

pub fn flush(&mut self, sink: impl FnMut(Vec<f32>)) -> 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.

If flush returns Err the denoiser is in an undefined state and should be dropped rather than reused.

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, <T as TryFrom<U>>::Error>

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