Specify face indexing at native resolution, and say what the proxy cost
FR-CULL-8 said detection runs against the thumbnail or proxy tier and never a full decode, and faces.md §5 said the aligned crop is sampled from that same proxy. Both are wrong in the same place: they treat detection and cropping as one resolution problem when they are two, with opposite answers. Detection does not care. §4.1 fixes the graph's input at 640x640 and letterboxes whatever arrives, so a face filling 2% of the frame reaches the model at 12px whether the buffer handed over is 1024px or 6000px. Every pixel above the detector's own input is discarded before inference. The crop cares about nothing else. §5's warp produces the fixed 112x112 ArcFace sees, so source resolution converts directly into whether those 112 pixels were photographed or interpolated. Reading crop_px across the 18,671 faces the proxy-tier implementation stored: 47.3% were upsampled to reach the embedder, 314 of them by more than 2x, the smallest from 34 source pixels. An upsampled crop does not fail loudly -- it yields a confident embedding of detail that was never there, and the damage appears three stages later as clusters that will not separate. So FR-CULL-8 now specifies four stages with the resolutions named separately: render native through FR-EXP-9's pipeline, downscale for the detector, map boxes and landmarks back to native, crop and align from the native render. The affordability the old rule bought is met instead by when the pass runs -- background, preempted, resumable -- and the requirement says plainly what it now costs on a remote library: the original rather than FR-NC-3's byte range, 412 GB across the reference library's 19,107 images, so a whole-library pass is a transfer under FR-NC-6 rather than something that may start on its own. MIN_CROP_EDGE replaces the MIN_DETECT_EDGE this branch briefly had. Same number, guarding the quantity that turned out to matter. faces.md §7b records both measurements, and marks the second as unexplained rather than dressing it as a finding. Grouped by the buffer detection ran against, faces per image was 0.078 at 1024 or below and 1.82 at 2048 or better, controlled for file type and size. That gap is real and reproducible and I cannot account for it, because the letterbox above says detector input should not matter. M4 is where it gets settled. The crop measurement does not depend on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,14 @@ pub struct IdentityController {
|
||||
/// Images visited so far in the current sweep, and the total it announced.
|
||||
progress: std::cell::Cell<(usize, usize)>,
|
||||
faces_found: std::cell::Cell<usize>,
|
||||
/// Images the running sweep could not get through, and images it refused
|
||||
/// for want of a big enough proxy.
|
||||
///
|
||||
/// On screen because a pass that fails everything used to be
|
||||
/// indistinguishable from one that succeeded at everything: both ended
|
||||
/// with a tidy "0 face(s) in 0 image(s)".
|
||||
sweep_failed: std::cell::Cell<usize>,
|
||||
sweep_too_small: std::cell::Cell<usize>,
|
||||
/// Whether opening this screen left the library rather than develop.
|
||||
///
|
||||
/// Recorded so leaving puts the user back where they were. The screen is
|
||||
@@ -1035,6 +1043,8 @@ pub fn wire<S, M, P>(
|
||||
|
||||
ctl.progress.set((0, 0));
|
||||
ctl.faces_found.set(0);
|
||||
ctl.sweep_failed.set(0);
|
||||
ctl.sweep_too_small.set(0);
|
||||
*ctl.activity.borrow_mut() =
|
||||
Some(activity.begin(crate::activity::Kind::Index, "Indexing faces"));
|
||||
*ctl.sweep.borrow_mut() = Some(crate::library::spawn_face_sweep(
|
||||
@@ -1079,19 +1089,42 @@ pub fn wire<S, M, P>(
|
||||
ctl_tick.progress.set((seen + 1, total));
|
||||
ctl_tick.faces_found.set(ctl_tick.faces_found.get() + faces);
|
||||
}
|
||||
FaceSweepMessage::Finished { .. } => done = true,
|
||||
FaceSweepMessage::Failed { images } => {
|
||||
// Advances the same counter. Work that
|
||||
// failed is still work the pass got
|
||||
// through, and a bar that only moves on
|
||||
// success reports a run of pure failure as
|
||||
// no progress at all.
|
||||
let (seen, total) = ctl_tick.progress.get();
|
||||
ctl_tick.progress.set((seen + images, total));
|
||||
ctl_tick
|
||||
.sweep_failed
|
||||
.set(ctl_tick.sweep_failed.get() + images);
|
||||
}
|
||||
FaceSweepMessage::Finished {
|
||||
failed, too_small, ..
|
||||
} => {
|
||||
// The sweep's own tallies win over the
|
||||
// running ones: a cancelled or offline
|
||||
// pass can end without every batch having
|
||||
// been reported.
|
||||
ctl_tick.sweep_failed.set(failed);
|
||||
ctl_tick.sweep_too_small.set(too_small);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (seen, total) = ctl_tick.progress.get();
|
||||
w.set_identity_indexing_status(
|
||||
format!(
|
||||
"indexing {seen}/{total} — {} face(s) found",
|
||||
ctl_tick.faces_found.get()
|
||||
)
|
||||
.into(),
|
||||
let mut status = format!(
|
||||
"indexing {seen}/{total} — {} face(s) found",
|
||||
ctl_tick.faces_found.get()
|
||||
);
|
||||
if ctl_tick.sweep_failed.get() > 0 {
|
||||
status.push_str(&format!(", {} failed", ctl_tick.sweep_failed.get()));
|
||||
}
|
||||
w.set_identity_indexing_status(status.into());
|
||||
if let Some(a) = ctl_tick.activity.borrow().as_ref() {
|
||||
a.progress(seen, total);
|
||||
}
|
||||
@@ -1099,10 +1132,28 @@ pub fn wire<S, M, P>(
|
||||
if done {
|
||||
*ctl_tick.sweep.borrow_mut() = None;
|
||||
if let Some(a) = ctl_tick.activity.borrow_mut().take() {
|
||||
a.finish(format!(
|
||||
// Say what did not work. A pass that failed every
|
||||
// image used to finish with the same sentence as
|
||||
// one that succeeded at every image, which is how
|
||||
// 169 consecutive failures went unnoticed for a
|
||||
// day.
|
||||
let mut msg = format!(
|
||||
"{} face(s) in {seen} image(s)",
|
||||
ctl_tick.faces_found.get()
|
||||
));
|
||||
);
|
||||
if ctl_tick.sweep_failed.get() > 0 {
|
||||
msg.push_str(&format!(
|
||||
", {} could not be read",
|
||||
ctl_tick.sweep_failed.get()
|
||||
));
|
||||
}
|
||||
if ctl_tick.sweep_too_small.get() > 0 {
|
||||
msg.push_str(&format!(
|
||||
", {} with no proxy big enough",
|
||||
ctl_tick.sweep_too_small.get()
|
||||
));
|
||||
}
|
||||
a.finish(msg);
|
||||
}
|
||||
w.set_identity_indexing(false);
|
||||
// Newly indexed faces belong to nobody until they are
|
||||
|
||||
Reference in New Issue
Block a user