Files
lightcraft/crates/raw/tests/sparse_corpus.rs
T
ingolfteetzandClaude Opus 5.5 bbd973f321 LRC-LIB-CATALOG-IMPORT: describe raws from their headers without reading pixel data
`lightcraft_raw::sparse::header_plan` says which byte ranges of a raw file the header
probe (`probe_info`, `lightcraft_meta::extract`) reads, given a buffer of the file's length
in which only some ranges are loaded: everything but the bulk sample data (large TIFF
strips/tiles minus the first chunk's coding headers, a CR3's image tracks minus the raw
track's CRX headers and QP maps, a RAF's CFA strip, an RW2's raw data). `preview_plan`
also reads what `embedded_preview` needs, for raws shown from their preview. NEF, ORF and
PEF (their header probe looks at samples) and anything else are read whole.

The corpus test checks, for every raw.pixls.us sample the plan describes, that probing the
partly loaded buffer gives exactly what the whole file gives, with the skipped bytes zero and
again 0xFF: CR3 2.7 %, DNG 2.4 %, CR2 3 %, RAF 6 %, RW2 7 %, ARW 8 % of their bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:25:57 +02:00

109 lines
5.3 KiB
Rust

//! [`lightcraft_raw::sparse::header_plan`] over `corpus/raw/**` (git-ignored CC0 samples from
//! raw.pixls.us, fetched with `cargo xtask corpus --download`; `LIGHTCRAFT_CORPUS` overrides the
//! corpus root). Skips cleanly when absent.
//!
//! For every file the plan reaches `Ready` on, probing the partly loaded buffer must give exactly
//! what probing the whole file gives — with the skipped bytes zero and again with them 0xFF, so a
//! reader that looked at skipped data would be caught. CR3 and DNG files must be described from a
//! small part of the file (`cargo test -p lightcraft-raw --release --test sparse_corpus -- --nocapture`
//! prints what each format reads).
use lightcraft_raw::sparse::{HeaderPlan, Ranges, header_plan, preview_plan};
use lightcraft_raw::{embedded_preview, probe, probe_info};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
fn corpus_root() -> PathBuf {
std::env::var_os("LIGHTCRAFT_CORPUS").map(PathBuf::from).unwrap_or_else(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus"))
}
/// Run the plan (with the embedded preview when `preview`), loading from `file` what it asks for
/// into a buffer whose other bytes are `fill`.
fn run(file: &[u8], fill: u8, preview: bool) -> (HeaderPlan, Vec<u8>, Ranges) {
let mut buf = vec![fill; file.len()];
let mut loaded = Ranges::new();
let prefix = file.len().min(512 * 1024);
buf[..prefix].copy_from_slice(&file[..prefix]);
loaded.insert(0, prefix as u64);
for _ in 0..32 {
match if preview { preview_plan(&buf, &loaded) } else { header_plan(&buf, &loaded) } {
HeaderPlan::Need(r) => {
for &(a, b) in r.spans() {
assert!(!loaded.overlaps(a, b), "asked again for {a}..{b}");
buf[a as usize..b as usize].copy_from_slice(&file[a as usize..b as usize]);
loaded.insert(a, b);
}
}
done => return (done, buf, loaded),
}
}
panic!("no answer after 32 rounds");
}
/// What an import learns from a file: the raw description and the metadata.
fn describe(bytes: &[u8]) -> String {
let e = lightcraft_meta::embedded(bytes);
format!("{:?}\n{:?}\n{:?}", probe_info(bytes), lightcraft_meta::extract(bytes), e.xmp)
}
#[test]
fn partial_reads_describe_raws_as_whole_files_do() {
let dir = corpus_root().join("raw");
let mut paths: Vec<PathBuf> = std::fs::read_dir(corpus_root().join("leica"))
.into_iter()
.chain(std::fs::read_dir(&dir))
.flatten()
.flatten()
.map(|e| e.path())
.filter(|p| p.is_file() && !p.extension().is_some_and(|e| e == "part" || e == "txt" || e == "md"))
.collect();
if paths.is_empty() {
eprintln!("skip: {} absent", dir.display());
return;
}
paths.sort();
// format → (files ready, bytes read, bytes total, files read whole)
let mut stats: BTreeMap<String, (usize, u64, u64, usize)> = BTreeMap::new();
for p in paths {
let name = p.file_name().unwrap().to_string_lossy().to_string();
let file = std::fs::read(&p).unwrap();
let format = probe(&file).map_or("?".to_string(), |f| format!("{f:?}"));
let (plan, zeros, loaded) = run(&file, 0, false);
let entry = stats.entry(format.clone()).or_default();
match plan {
HeaderPlan::Ready => {
let (plan_ff, ones, loaded_ff) = run(&file, 0xff, false);
assert_eq!((plan_ff, &loaded_ff), (HeaderPlan::Ready, &loaded), "{name}: the plan read skipped bytes");
let whole = describe(&file);
assert_eq!(describe(&zeros), whole, "{name}: differs from the whole file (skipped bytes 0)");
assert_eq!(describe(&ones), whole, "{name}: differs from the whole file (skipped bytes 0xFF)");
entry.0 += 1;
entry.1 += loaded.total();
entry.2 += file.len() as u64;
// with the preview (for a raw shown from it), the preview is what the whole file gives too
let preview = embedded_preview(&file);
for fill in [0, 0xff] {
let (plan, buf, _) = run(&file, fill, true);
assert_eq!(plan, HeaderPlan::Ready, "{name}: preview plan");
assert_eq!(describe(&buf), whole, "{name}: preview plan, skipped bytes {fill}");
assert!(embedded_preview(&buf) == preview, "{name}: a different embedded preview (skipped bytes {fill})");
}
if loaded.total() * 4 > file.len() as u64 {
eprintln!("{name}: read {:.0} %", loaded.total() as f64 * 100.0 / file.len() as f64);
}
if matches!(format.as_str(), "Cr3" | "Dng") && file.len() > 8 << 20 {
assert!(loaded.total() * 4 < file.len() as u64, "{name}: read {} of {} bytes", loaded.total(), file.len());
}
}
HeaderPlan::Whole(why) => {
entry.3 += 1;
eprintln!("{name}: whole ({why})");
}
HeaderPlan::Need(_) => unreachable!(),
}
}
for (format, (ready, read, total, whole)) in stats {
eprintln!("{format:>10}: {ready} described from {:.1} % of their bytes, {whole} read whole", read as f64 * 100.0 / total.max(1) as f64);
}
}