diff --git a/docs/faces.md b/docs/faces.md index 0a99b95..11a6ec0 100644 --- a/docs/faces.md +++ b/docs/faces.md @@ -556,6 +556,26 @@ as 34 — a 3.3× enlargement — against a mean of 178. An upsampled crop does produces a confident 512-d embedding describing detail that was interpolated rather than photographed, and the damage appears three stages later as clusters that will not separate. +### Measured again, against the implementation + +The figures above are read out of a catalog after the fact, so they describe what the old code did +rather than what the new code does. `examples/face_native.rs` renders one file and indexes it both +ways, so the difference can be attributed to the resolution and nothing else. Fourteen originals +from the reference library — 5472×3648 Canon CR2 and DNG — each rendered once and indexed twice: + +| Path | Faces found | Mean `crop_px` | +|---|---|---| +| Native (this specification) | 9 | **287** | +| Everything from a 1024 proxy | 5 | **75** | + +Crops 3.8× larger, and on the right side of the line that matters: 75 px is *below* [`ALIGNED_EDGE`] +so the proxy path was upsampling into the embedder on average, where the native path downsamples +into it. + +**Sample of fourteen images and nine faces.** Enough to show the direction and to catch a wrong +landmark scaling, which is what it was written for; not enough to quote a ratio as the library-wide +figure. §12's M4 is still where the recall curve gets established. + A second effect, recorded here because it was measured and because the mechanism is **not** established. Grouping the same runs by `face_index.source_edge` — the buffer detection ran against — gives 0.078 faces per image at 1024 or below, against 1.82 at 2048 or better. Controlled @@ -567,6 +587,13 @@ itself a downscale of a larger preview, so the detector sees a twice-resampled i larger buffer is resampled once — but that is a hypothesis, not a finding, and §12's M4 is where it should be settled. **The crop measurement above stands on its own and does not depend on it.** +The A/B is evidence for that hypothesis without settling it. Native found nine faces where the +1024 path found five, on four files where the proxy path found none at all — so detector input +*does* affect recall, which the letterbox says it should not. The two paths differ in their +resampling as well as their size (one box filter and one letterbox against a downscale and a +letterbox), and this experiment does not separate those. Isolating it means holding the chain fixed +and varying only the buffer, which is M4's job. + --- ## 8. Calibration — cosine to probability diff --git a/ui/dr-ui/src/faces.rs b/ui/dr-ui/src/faces.rs index 79f66be..5182445 100644 --- a/ui/dr-ui/src/faces.rs +++ b/ui/dr-ui/src/faces.rs @@ -1395,7 +1395,7 @@ mod tests { } #[test] - fn an_audit_summary_names_both_kinds_of_outstanding() { + fn an_audit_summary_counts_the_outstanding_as_one_number() { let a = IndexAudit { coverage: faces::Coverage { images: 100, @@ -1410,11 +1410,13 @@ mod tests { assert!(s.contains("60/100"), "{s}"); assert!(s.contains("60%"), "{s}"); assert!(!s.contains("60.0%"), "whole numbers above ten percent: {s}"); - assert!(s.contains("30 ready"), "{s}"); - // "to fetch", not "awaiting a proxy": the whole-library pass fetches - // these rather than being blocked by them, and the line must not send - // the user off to run the thumbnail sweep first. - assert!(s.contains("10 to fetch"), "{s}"); + // One number, not two. The split described a pass that could index + // whatever already had a proxy; detection now refuses that proxy's + // size (§7), so both halves cost the same fetch and "30 ready, 10 to + // fetch" would imply a distinction that decides nothing. + assert!(s.contains("40 to index"), "{s}"); + assert!(!s.contains("ready"), "the split should be gone: {s}"); + assert!(!s.contains("to fetch"), "the split should be gone: {s}"); } #[test]