diff --git a/core/dr-decode/examples/rawinfo.rs b/core/dr-decode/examples/rawinfo.rs index 2e1376f..8a54489 100644 --- a/core/dr-decode/examples/rawinfo.rs +++ b/core/dr-decode/examples/rawinfo.rs @@ -32,6 +32,10 @@ fn main() { println!("black {:?}", raw.black_level); println!("white {}", raw.white_level); println!("wb_coeffs {:?}", raw.wb_coeffs); + match dr_decode::noise_profile(&bytes) { + Some(p) => println!("noise profile {p:?} ((S, O) per plane)"), + None => println!("noise profile none"), + } match raw.color_matrix { Some(m) => { diff --git a/core/dr-decode/src/lib.rs b/core/dr-decode/src/lib.rs index def7f02..6fdc375 100644 --- a/core/dr-decode/src/lib.rs +++ b/core/dr-decode/src/lib.rs @@ -544,6 +544,17 @@ pub(crate) fn parse_exif_offset(s: &str) -> Option { Some(sign * (h * 60 + m)) } +/// TRACES: FR-DEV-3g +/// The DNG `NoiseProfile` of a file, if it carries one: `(S, O)` per CFA +/// colour plane, variance `S·x + O` in black-to-white normalised units. See +/// [`profile::read_noise_profile`]. Reads the header, not the image. +pub fn noise_profile(bytes: &[u8]) -> Option> { + use rawler::rawsource::RawSource; + let source = RawSource::new_from_slice(bytes); + let decoder = rawler::get_decoder(&source).ok()?; + profile::read_noise_profile(decoder.as_ref()) +} + /// TRACES: FR-RAW-3 | FR-EXP-9 /// Fully decode sensor data. /// diff --git a/core/dr-decode/src/profile.rs b/core/dr-decode/src/profile.rs index 10349ac..f4da951 100644 --- a/core/dr-decode/src/profile.rs +++ b/core/dr-decode/src/profile.rs @@ -740,6 +740,35 @@ pub fn read_dng_matrices(decoder: &dyn rawler::decoders::Decoder) -> DngMatrices } } +/// TRACES: FR-DEV-3g +/// The DNG `NoiseProfile` tag (51041): the converter's measured noise for +/// this body at this ISO, as `(S, O)` per CFA colour plane, so that a +/// photosite's variance is `S·x + O` with `x` normalised black-to-white. +/// +/// One pair means all planes share it. `None` where the file has no such +/// tag — every proprietary raw, and DNGs from converters that do not measure +/// — or where a value is not a finite non-negative number. The learned +/// denoise's second-best noise source (denoise.md §3.3), after a measured +/// table for the body. +pub fn read_noise_profile(decoder: &dyn rawler::decoders::Decoder) -> Option> { + use rawler::decoders::WellKnownIFD; + use rawler::tags::DngTag; + + let ifd = decoder.ifd(WellKnownIFD::Root).ok()??; + let entry = ifd.get_entry_recursive(DngTag::NoiseProfile)?; + let n = entry.count() as usize; + if n < 2 || n % 2 != 0 { + return None; + } + let pairs: Vec<(f32, f32)> = (0..n / 2) + .map(|i| (entry.force_f32(2 * i), entry.force_f32(2 * i + 1))) + .collect(); + pairs + .iter() + .all(|(s, o)| s.is_finite() && o.is_finite() && *s >= 0.0 && *o >= 0.0) + .then_some(pairs) +} + #[cfg(test)] mod tests { use super::*;