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
impl Denoiser
Sourcepub fn create(
accelerators: &[Accelerator],
device: &Device,
width: u32,
height: u32,
options: DenoiserOptions,
) -> Result<Denoiser, DenoiserError>
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 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") };
}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 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.
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.
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.
Sourcepub fn recv_frame(&mut self) -> Result<Option<Vec<f32>>, DenoiserError>
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.
Sourcepub fn try_recv_frame(&mut self) -> Result<Option<Vec<f32>>, DenoiserError>
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.
Sourcepub fn flush(&mut self, sink: impl FnMut(Vec<f32>)) -> Result<(), DenoiserError>
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.