Files
DarkRoom/core/dr-pipeline/src/learned_denoise.rs
T
dtourolle 06422a07db Offer three denoise networks and a method to choose between them
AI Denoise's Apply switch becomes Method: Bilinear, Fast, Medium, Best,
default Best, so an untouched raw writes nothing and develops through the
mixture. `apply` is still read and never written: 0 is Bilinear, 1 keeps
a network already chosen.

- Best is the mixture of a flat and an edge expert with a learned gate;
  Medium and Fast are students distilled from it. 2.48 s, 0.79 s and
  0.57 s for a 20 MP frame on TensorRT fp16.
- Each network carries its own tile border (256 for the mixture, 192 for
  the students) through `dr_denoise::Shipped` and `TileNet::halo`.
- The file is hashed once at open and each network keys its own cached
  result; Bilinear keeps the result in memory for the way back.
- Each has an .a16w16 sibling for the Hexagon: 0.00 dB on the 6D gate,
  at most 0.11 dB with the noise scaled x0.5 to x4.
- APK BUNDLED 19 -> 23; the PKGBUILD installs all three.
2026-10-04 08:02:25 -04:00

120 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! TRACES: FR-DEV-3g
//! The learned denoise's settings: which network develops the photograph,
//! if any, and how much grain to keep.
//!
//! Not an [`crate::operation::Operation`]: the learned stage replaces the
//! demosaic and runs once per photograph, off the render path
//! (docs/dev/denoise.md §2, §7), and the grain is a blend of its result with
//! the classical one, done where the source is chosen. But what a
//! photographer sets travels the one road every setting travels — the
//! capability list feeds the panel, [`crate::Preset`] captures it, the
//! sidecar stores it, the undo stack replays it (FR-DEV-3c) — so it is
//! published as a capability, like the lens profile switch.
use std::sync::{Arc, LazyLock};
use crate::descriptor::{Attribute, LocalizedKey, OpDescriptor, ParamDescriptor, Scale, Unit};
use crate::{OpId, ParamId};
pub const ID: OpId = OpId("learned_denoise");
/// TRACES: FR-DEV-3g
/// Which demosaic develops the photograph, a [`Method`] by index.
pub const METHOD: ParamId = ParamId("method");
/// What 0.21 and 0.22 stored instead of [`METHOD`]: on or off. Still read —
/// off is [`Method::Bilinear`], on is the default network — so an edit saved
/// by those releases keeps its look; never written, and not offered.
pub const APPLY: ParamId = ParamId("apply");
/// TRACES: FR-DEV-3g
/// How strongly to denoise, 0–100: 100 is the network's result as it is, and
/// lower puts the removed noise's brightness back as grain.
pub const STRENGTH: ParamId = ParamId("strength");
/// What 0.21.0 stored instead of [`STRENGTH`]: the grain kept, its inverse.
/// Still read, so an edit saved by that release keeps its look; never
/// written, and not offered as a control.
pub const GRAIN: ParamId = ParamId("grain");
/// TRACES: FR-DEV-3g
/// The demosaics a photograph can be developed with, in the order the
/// sidecar numbers them. Three networks that trade time for quality — the
/// same training, distilled into smaller students (docs/dev/denoise.md §13)
/// — and the classical demosaic, which is no network at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Method {
/// The classical demosaic: the noise stays.
Bilinear,
/// The smallest student: a quarter of the medium network's work.
Fast,
/// One network the size of the first release's.
Medium,
/// Two experts, one for flat areas and one for edges, and a gate.
Best,
}
impl Method {
pub const ALL: [Method; 4] = [Method::Bilinear, Method::Fast, Method::Medium, Method::Best];
pub const DEFAULT: Method = Method::Best;
/// The sidecar's number for it.
pub fn index(self) -> f32 {
Self::ALL.iter().position(|m| *m == self).unwrap_or(0) as f32
}
/// The method a stored number names; out of range is the default, as
/// from a newer build with more of them.
pub fn from_index(value: f32) -> Method {
let i = value.round();
if i >= 0.0 && (i as usize) < Self::ALL.len() {
Self::ALL[i as usize]
} else {
Self::DEFAULT
}
}
/// Whether a network runs at all.
pub fn learned(self) -> bool {
self != Method::Bilinear
}
}
/// The best network by default, at full strength: every Bayer raw is
/// developed from the learned demosaic, and the choice and the slider are
/// there to take it back, trade it for time, or ease it off. It costs seconds per photograph the first time, while
/// the classical demosaic shows; the result is cached, so a photograph
/// reopened or exported does not pay again (docs/dev/denoise.md §7).
pub(crate) static DESCRIPTOR: LazyLock<Arc<OpDescriptor>> = LazyLock::new(|| {
Arc::new(OpDescriptor {
id: ID,
label: LocalizedKey("op.learned_denoise"),
params: vec![
ParamDescriptor::choice(
"method",
"param.learned_denoise.method",
vec![
LocalizedKey("param.learned_denoise.method.bilinear"),
LocalizedKey("param.learned_denoise.method.fast"),
LocalizedKey("param.learned_denoise.method.medium"),
LocalizedKey("param.learned_denoise.method.best"),
],
)
.with_default(Method::DEFAULT.index()),
ParamDescriptor::scalar(
"strength",
"param.learned_denoise.strength",
0.0,
100.0,
100.0,
Unit::Percent,
Scale::Linear,
0,
),
],
// With the classical noise reduction, which is what a photographer
// looks for it beside.
attributes: vec![Attribute::Detail],
})
});
pub fn descriptor() -> Arc<OpDescriptor> {
DESCRIPTOR.clone()
}