//! 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. //! //! The photosites are those the demosaic reads: hot and dead ones repaired by //! the app's own pass ([`Demosaicer::repair_hot_pixels`], the same shader //! `run` dispatches), because the learned stage replaces the demosaic and //! takes its input (denoise.md §2). `--unrepaired` skips it. //! //! 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, and the repair never touches it, 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-gpu --example mosaic_dump //! ``` use std::io::{BufRead, Write}; use dr_gpu::{Demosaicer, GpuContext}; fn main() { let repair = !std::env::args().any(|a| a == "--unrepaired"); let ctx = pollster::block_on(GpuContext::new_headless()).expect("a GPU for the hot-pixel pass"); let demosaicer = Demosaicer::new(&ctx).expect("demosaicer"); 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, repair.then_some(&demosaicer)) { 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, repair: Option<&Demosaicer>) -> Result<(), String> { let bytes = std::fs::read(input).map_err(|e| e.to_string())?; let mut 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 repaired = match repair { Some(d) => d.repair_hot_pixels(&mut raw).map_err(|e| e.to_string())? as i64, None => -1, }; 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\": {}, ", "\"hot_repaired\": {}}}\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()), repaired, ); std::fs::write(format!("{prefix}.json"), json).map_err(|e| e.to_string()) }