Read a DNG's NoiseProfile
The converter's measured noise for the body at that ISO, (S, O) per CFA plane: the learned denoise's best source for a body with no table of its own (denoise.md §3.3). Read from the header beside the colour tags, and printed by rawinfo. Checked against tifffile on a 6D DNG at ISO 5000: all six values agree.
This commit is contained in:
@@ -32,6 +32,10 @@ fn main() {
|
|||||||
println!("black {:?}", raw.black_level);
|
println!("black {:?}", raw.black_level);
|
||||||
println!("white {}", raw.white_level);
|
println!("white {}", raw.white_level);
|
||||||
println!("wb_coeffs {:?}", raw.wb_coeffs);
|
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 {
|
match raw.color_matrix {
|
||||||
Some(m) => {
|
Some(m) => {
|
||||||
|
|||||||
@@ -544,6 +544,17 @@ pub(crate) fn parse_exif_offset(s: &str) -> Option<i32> {
|
|||||||
Some(sign * (h * 60 + m))
|
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<Vec<(f32, f32)>> {
|
||||||
|
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
|
/// TRACES: FR-RAW-3 | FR-EXP-9
|
||||||
/// Fully decode sensor data.
|
/// Fully decode sensor data.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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<Vec<(f32, f32)>> {
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user