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
impl Denoiser
Sourcepub fn create(
accelerators: &[Accelerator],
device: &Device,
width: u32,
height: u32,
options: DenoiserOptions,
) -> Result<Self, DenoiserError>
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 call
crate::raise_codegen_stack_limit before any cubecl thread
spawns, usually right at the top of main.
Sourcepub fn selected_accelerator(&self) -> Accelerator
pub fn selected_accelerator(&self) -> Accelerator
The accelerator sniff_best_accelerator picked.
Sourcepub fn temporal_radius(&self) -> u32
pub fn temporal_radius(&self) -> u32
The temporal radius the resolved parameters run at.
Sourcepub fn output_format(&self) -> OutputFormat
pub fn output_format(&self) -> OutputFormat
The format every collected frame comes back in.
Sourcepub fn window_span(&self) -> WindowSpan
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 }.
Sourcepub fn push_frame(&mut self, frame: &[f32]) -> Result<(), DenoiserError>
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.
Sourcepub fn push_frame_wire(
&mut self,
planes: &[&[u8]],
depth: Depth,
) -> Result<(), DenoiserError>
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.
Sourcepub fn push_frame_wire_priming(
&mut self,
planes: &[&[u8]],
depth: Depth,
) -> Result<(), DenoiserError>
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.
Sourcepub fn push_frame_priming(&mut self, frame: &[f32]) -> Result<(), DenoiserError>
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.
Sourcepub fn reset_stream(&mut self)
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.
Sourcepub fn recv_frame(&mut self) -> Result<Option<FrameOutput>, DenoiserError>
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.
Sourcepub fn try_recv_frame(&mut self) -> Result<Option<FrameOutput>, DenoiserError>
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.
Sourcepub fn flush(
&mut self,
sink: impl FnMut(FrameOutput),
) -> Result<(), DenoiserError>
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.