From 1f266a447845f664395872c6cd7bb7bf3688c9c3 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 2 Oct 2026 22:15:18 -0400 Subject: [PATCH] Dump RAW mosaics for training the learned denoise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit denoise.md §4.4 requires the training repo to read photosites through dr-decode, not LibRaw, so black and white levels, the active area and the CFA phase match what the app will feed the network. mosaic_dump reads `inputprefix` lines and writes the whole readout as .npy plus a JSON of what decode and metadata report. The masked border is kept: its optically black photosites are a free dark frame for the noise profile. --- core/dr-decode/examples/mosaic_dump.rs | 99 ++++++++++++++++++++++++++ docs/dev/traceability.md | 2 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 core/dr-decode/examples/mosaic_dump.rs diff --git a/core/dr-decode/examples/mosaic_dump.rs b/core/dr-decode/examples/mosaic_dump.rs new file mode 100644 index 0000000..0c1114a --- /dev/null +++ b/core/dr-decode/examples/mosaic_dump.rs @@ -0,0 +1,99 @@ +//! Dump RAW files' mosaics for training the learned denoise (FR-DEV-3g). +//! +//! The training repo must read photosites the way the app reads them — +//! same black and white levels, same active area, same CFA phase — or a +//! network trained on one phase runs on another and paints moiré everywhere +//! (denoise.md §4.4). So it reads this, not LibRaw. +//! +//! Reads `inputoutput-prefix` lines on stdin and writes, per line, +//! `prefix.npy` (the whole readout, masked border included, `u16`, row-major) +//! and `prefix.json` (what `decode` and `metadata` say about it). The border +//! is kept because its optically black photosites are a dark frame for free: +//! read noise and row noise at that ISO. +//! +//! ```sh +//! printf 'IMG_0001.CR2\tout/IMG_0001\n' | +//! cargo run --release -p dr-decode --example mosaic_dump +//! ``` + +use std::io::{BufRead, Write}; + +fn main() { + let mut failed = 0; + for line in std::io::stdin().lock().lines() { + let line = line.expect("stdin"); + let Some((input, prefix)) = line.split_once('\t') else { + continue; + }; + match dump(input, prefix) { + Ok(()) => println!("ok\t{input}"), + Err(e) => { + failed += 1; + println!("fail\t{input}\t{e}"); + } + } + std::io::stdout().flush().ok(); + } + std::process::exit(if failed > 0 { 1 } else { 0 }); +} + +fn dump(input: &str, prefix: &str) -> Result<(), String> { + let bytes = std::fs::read(input).map_err(|e| e.to_string())?; + let raw = dr_decode::decode(&bytes).map_err(|e| e.to_string())?; + if raw.samples_per_pixel != 1 { + return Err("linear DNG: no photosites".into()); + } + let meta = dr_decode::metadata(&bytes).map_err(|e| e.to_string())?; + + let mut npy = Vec::with_capacity(raw.data.len() * 2 + 128); + let mut header = format!( + "{{'descr': '| v.map_or("null".to_string(), |v| v.to_string()); + let matrix = raw + .color_matrix + .map_or("null".to_string(), |m| format!("{m:?}")); + let json = format!( + concat!( + "{{\"source\": {:?}, \"make\": {:?}, \"model\": {:?}, ", + "\"width\": {}, \"height\": {}, ", + "\"crop\": [{}, {}, {}, {}], \"cfa\": {:?}, ", + "\"black\": {:?}, \"white\": {}, \"wb\": {:?}, \"cam_to_srgb\": {}, ", + "\"iso\": {}, \"shutter\": {}, \"aperture\": {}, \"captured_at\": {}}}\n" + ), + input, + raw.make, + raw.model, + raw.width, + raw.height, + raw.crop.x, + raw.crop.y, + raw.crop.width, + raw.crop.height, + format!("{:?}", raw.cfa_pattern), + raw.black_level, + raw.white_level, + raw.wb_coeffs, + matrix, + meta.iso.map_or("null".to_string(), |v| v.to_string()), + opt(meta.shutter), + opt(meta.aperture), + meta.captured_at + .map_or("null".to_string(), |v| v.to_string()), + ); + std::fs::write(format!("{prefix}.json"), json).map_err(|e| e.to_string()) +} diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index ec2b97c..0c24a03 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -9,7 +9,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 486 | +| Source files scanned | 487 | | TRACES tags found | 2033 | | Requirements defined | 193 | | Requirements deferred (post-v1) | 24 |