//! 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. Two networks that trade time for quality /// (docs/dev/denoise.md §15) and the classical demosaic, which is no network /// at all. /// /// Until 0.24 there were four — Bilinear, Fast, Medium, Best — and the /// sidecar keeps their numbers: 2, which was Medium, is now Best, and 3, /// which was Best, is past the end and reads as the default, which is /// Best. Both land on the network that replaced them, with no migration. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Method { /// The classical demosaic: the noise stays. Bilinear, /// The smallest student: a quarter of Best's work. Fast, /// One network of the first release's size, taught by the mixture of /// experts it replaced: the mixture's edges at a third of its work. Best, } impl Method { pub const ALL: [Method; 3] = [Method::Bilinear, Method::Fast, 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> = 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.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 { DESCRIPTOR.clone() } #[cfg(test)] mod tests { use super::*; /// An edit saved before 0.24 stored Medium as 2 and Best as 3. Both /// now name the network that replaced them, and nothing reads as Fast /// or Bilinear that did not before. #[test] fn the_retired_methods_read_as_best() { assert_eq!(Method::from_index(0.0), Method::Bilinear); assert_eq!(Method::from_index(1.0), Method::Fast); assert_eq!(Method::from_index(2.0), Method::Best, "Medium, before 0.24"); assert_eq!(Method::from_index(3.0), Method::Best, "Best, before 0.24"); assert_eq!(Method::Best.index(), 2.0); } }