Measure the native path against the one it replaces

Everything argued for this change so far was read out of a catalog
after the fact: crop_px across 18,671 faces the old code had already
stored. That is evidence about what the previous implementation did. It
is not evidence that the new one does better, and the difference matters
because a landmark left in the detector's coordinates, or a box filter
with an off-by-one in its source span, would both produce faces that
look entirely plausible until somebody counted the pixels behind them.

So examples/face_native.rs renders one file and indexes it twice, native
and from a 1024 proxy, changing nothing else. Fourteen originals from
the reference library, 5472x3648 CR2 and DNG:

    native      9 faces, mean crop 287px
    1024 proxy  5 faces, mean crop  75px

Crops 3.8x larger, and across the line that decides whether the crop is
photographed or interpolated: 75px is below ALIGNED_EDGE, so the proxy
path was upsampling into the embedder on average where this one
downsamples into it. Fourteen images and nine faces is enough to show a
direction and to catch a wrong scaling; §7b says so rather than quoting
the ratio as a library-wide figure.

It also corrects something §7b asserted two commits ago. I wrote that
detector input resolution cannot affect recall, because §4.1 letterboxes
everything to 640. Native found nine faces to the proxy's five,
including four on files where the proxy found none, so it plainly can.
The two paths differ in their resampling as well as their size, and this
experiment does not separate those, so §7b now records the result as
evidence for the double-resampling hypothesis rather than as its proof.
M4 still owns settling it.

The audit-summary test went stale when the ready/to-fetch split was
collapsed and is updated to assert the single number, including that the
old wording is gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 19:41:30 +02:00
co-authored by Claude Opus 5
parent 4af3b93dfa
commit a1e361e35a
2 changed files with 35 additions and 6 deletions
+8 -6
View File
@@ -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]