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.
83 lines
4.6 KiB
Markdown
83 lines
4.6 KiB
Markdown
# DarkRoom — Sensor health: a dated defect map per body
|
||
|
||
**Status:** Spike · 2026-10-04 · not built
|
||
**Companion to:** [requirements.md](requirements.md) FR-RAW-3, [catalog.md](catalog.md)
|
||
|
||
A sensor gains defective photosites as it ages, and a photosite that has gone bad does not recover.
|
||
This records, per camera body, which photosites are defective and since when, so that the library
|
||
can show how a sensor has aged and the hot-pixel repair can fix the defects a body is known to have
|
||
rather than only those that stand out in the frame at hand.
|
||
|
||
What exists is the measuring tool: `Demosaicer::find_hot_pixels` (the photosites the repair pass
|
||
would replace, without replacing them) and `core/dr-gpu/examples/sensor_scan.rs`, which prints them
|
||
per frame and, with `--probe`, reads a list of coordinates back out of each frame. The rest of this
|
||
document is what a spike with them on the 6D found, and the design it argues for.
|
||
|
||
---
|
||
|
||
## 1. What is wanted
|
||
|
||
- **Settings → Bodies**, one entry per body, with a graph of the defective share over time in two
|
||
series: photosites (the raw mosaic) and 2×2 cells holding at least one defective photosite (what
|
||
reaches a pixel of the output).
|
||
- **A dated defect map** per body, synced with the library like any other catalog data, and
|
||
cumulative: an entry is never removed.
|
||
- **The repair reads the map** whose date is nearest the frame's, and fixes every defect the body had
|
||
by then, whether or not it stands out in that frame.
|
||
- One body per model for now: the catalog stores `make model`, not a body serial.
|
||
|
||
## 2. What the spike found (6D, 2026-10-03)
|
||
|
||
53 CR2s, up to four per quarter at the highest ISO of a day, 2015 to 2026. The library holds almost
|
||
no 6D raws from 2016–2022, so onsets in that span are dated to the span, not the year. `sensor_scan`
|
||
ran at 0.8 s per frame, decode included.
|
||
|
||
**Persistence separates the sensor from the scene.** 4,179 photosites were flagged at least once;
|
||
3,554 on one day only (stars, glints, noise). A defect is a photosite that keeps coming back.
|
||
|
||
**A frame that does not flag a photosite is not evidence it was clean.** The repair's test is
|
||
relative to the neighbourhood, so a defect in a lit area does not stand out. Confirmed defects were
|
||
flagged in a median 20 % of the frames after their first sighting. Only a frame whose neighbourhood
|
||
at that photosite is dark counts, either way.
|
||
|
||
**The 6D hides some defects itself at high ISO.** (2517, 3172) reads 8,000–13,000 over neighbours
|
||
near 200 at ISO 2000–5000, and does not stand out at all at ISO 6400–12800 (82 over 149 on
|
||
2025-03-15). The camera appears to map out photosites it knows at those gains. So evidence for this
|
||
body comes from ISO ≤ 5000; the cut-off must be learned per body, not fixed.
|
||
|
||
**Long exposures light everything.** A 9.8 s frame saturated every candidate; it confirms, it does
|
||
not date.
|
||
|
||
**The curve.** Counting a photosite as defective from the first frame where it stands out, provided
|
||
it stands out in at least 60 % of the observable frames after that (32 defects; 31 with a clean
|
||
observable frame before onset to bracket it):
|
||
|
||
| Year | Defects | Share of photosites |
|
||
|---|---|---|
|
||
| 2015 | 2 | 0.1 ppm |
|
||
| 2016–2021 | 2 | 0.1 ppm |
|
||
| 2022 | 7 | 0.3 ppm |
|
||
| 2023 | 26 | 1.3 ppm |
|
||
| 2026 | 32 | 1.6 ppm |
|
||
|
||
(2517, 3172) is the shape every entry should have: clean at ISO 800–1000 in 2015 and at ISO 100–200
|
||
in 2016, then 338 over 72 at ISO 100 on 2022-08-13 and in every comparable frame since.
|
||
|
||
Weak defects exist too, about twice their neighbours ((1814, 3039)); the blind repair misses them in
|
||
most frames. A known map would catch them.
|
||
|
||
## 3. Design it argues for
|
||
|
||
- **Evidence per frame, per known photosite**: observable (dark neighbourhood, ISO inside the
|
||
body's band) and, if so, lit or clean. Not just the frame's flagged list.
|
||
- **A map entry is a bracket**: last clean observation, first lit observation, strength, kind. Onset
|
||
lies between the two; the graph plots it at the first, and can show the bracket.
|
||
- **The repair**: every entry whose first lit date is on or before the frame's capture date; for an
|
||
entry whose bracket contains the date, probe the photosite in the frame itself.
|
||
- **Storage and sync**: catalog tables created on first use (as `albums` does), so no schema bump
|
||
breaks an older peer. They travel in the snapshot by default. Merge is a set union of photosites
|
||
per body, the earlier first-lit and the later last-clean winning, which makes it commutative and
|
||
keeps the map cumulative.
|
||
- **Work**: a sample, not the library. Frames are picked for what they can reveal (dark, mid ISO,
|
||
long exposures), a few per body per month, and after the first pass only new imports are read.
|