Find hot photosites without repairing them, and measure a sensor's aging

The hot-pixel pass could only repair: it returned how many photosites it
changed and threw away which. find_hot_pixels runs the same pass and
returns them as sensor coordinates, leaving the frame alone, so a sensor's
defects can be tracked across frames.

sensor_scan prints each frame's candidates, and with --probe reads a list
of coordinates back out of every frame. Run over 53 6D raws from 2015 to
2026, it found 32 persistent defects, 2 in 2015 and 32 by 2026, and showed
what a defect map has to account for: a frame that does not flag a
photosite proves nothing unless its neighbourhood is dark, and the 6D
hides some of its defects itself above ISO 5000. docs/dev/sensor-health.md
records the findings and the design they argue for.
This commit is contained in:
2026-10-04 02:38:48 -04:00
parent 83f0461ce7
commit 8f9e59b9fa
6 changed files with 305 additions and 14 deletions
+41 -1
View File
@@ -7,7 +7,7 @@
//! see of a defect it missed is the coloured cross the demosaic makes of it.
use dr_decode::{CfaPattern, CropRect, RawImage};
use dr_gpu::{AdjustPass, Demosaicer, GpuContext};
use dr_gpu::{AdjustPass, Demosaicer, GpuContext, Photosite};
use dr_pipeline::EditGraph;
const SIZE: u32 = 36;
@@ -171,3 +171,43 @@ fn the_repaired_mosaic_reads_back_with_only_the_defect_changed() {
let others = (0..raw.data.len()).filter(|&i| i != at);
assert!(others.into_iter().all(|i| raw.data[i] == before.data[i]));
}
/// Finding without repairing (docs/dev/sensor-health.md): the same verdict as
/// the repair, as sensor coordinates, with the frame left as it was. The
/// sensor health record builds on this, so it must name exactly the
/// photosites the repair would change — the hot one and the dead one, and
/// not the star.
#[test]
fn finding_names_what_the_repair_would_change_and_changes_nothing() {
let Some(ctx) = ctx() else {
eprintln!("no GPU adapter; skipping");
return;
};
let d = Demosaicer::new(&ctx).expect("demosaicer");
let mut set = vec![(MIDDLE, MIDDLE, WHITE), (9, 25, 0)];
for dy in 0..3 {
for dx in 0..3 {
set.push((4 + dx, 4 + dy, WHITE));
}
}
let raw = frame(CfaPattern::Rggb, 1600, &set);
let mut found = d.find_hot_pixels(&raw).expect("find");
found.sort_by_key(|p| (p.y, p.x));
assert_eq!(
found,
vec![
Photosite {
x: MIDDLE,
y: MIDDLE,
hot: true
},
Photosite {
x: 9,
y: 25,
hot: false
},
]
);
let mut repaired = raw.clone();
assert_eq!(d.repair_hot_pixels(&mut repaired).expect("repair"), 2);
}