Make a sweep that fails everything say so

A run over 169 images failed all 169, in fourteen seconds, and reported
"0 face(s) in 0 image(s)" -- the same sentence a run that indexed
nothing because there was nothing to index produces. Three separate
places dropped the information on its way to the screen.

The progress count only moved on success. FaceSweepMessage had no
failure variant at all, so a pass where every image failed sat at 0/169
from the first tick to the last: the receiver was told the total, told
nothing, and told the pass had ended. That is indistinguishable from a
hung job, and it is what it was taken for.

Finished already carried a failed count and identity_ui matched it with
`Finished { .. }`, throwing the number away and printing the tidy
success line regardless.

And the reason each image failed was logged at debug, which is off, so
169 consecutive failures left no trace of why anywhere.

Failed { images } now carries the count back per lane batch, the
progress counter advances on it, and both the running status line and
the finishing activity row say how many could not be read. A batch
rather than one message per image because failures come back lane-sized
and the useful number is how many.

Also renames the store sweep's guard to MIN_CROP_EDGE with the rest of
that constant's move, since the two touch the same lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 19:41:20 +02:00
co-authored by Claude Opus 5
parent e7b526c550
commit 9ddc1273c0
3 changed files with 20 additions and 40 deletions
+8 -11
View File
@@ -163,18 +163,17 @@ fn main() {
// Say it here rather than letting the pass return an empty result and
// print "done: 0 image(s)". That is the shape of report this whole change
// exists to stop producing.
if faces::FACE_TIER.edge() < dr_face::MIN_DETECT_EDGE {
if faces::FACE_TIER.edge() < dr_face::MIN_CROP_EDGE {
println!(
"\n--run cannot index from the local store any more.\n\
\n\
Detection needs a proxy longer than {}px and the store's largest\n\
tier is {}px, so every image here would be refused. The measurement\n\
behind that floor is docs/faces.md §7c.\n\
Face crops must be sampled from a buffer longer than {}px and the\n\
store's largest tier is {}px, so every image here would be refused.\n\
The measurement behind that floor is docs/faces.md §7b.\n\
\n\
Index from the app's Identity screen instead: that pass fetches the\n\
embedded preview at full size, which is the resolution detection\n\
needs.",
dr_face::MIN_DETECT_EDGE - 1,
Index from the app's Identity screen instead: that pass renders at\n\
native resolution, which is what the crop needs.",
dr_face::MIN_CROP_EDGE - 1,
faces::FACE_TIER.edge(),
);
return;
@@ -214,11 +213,9 @@ fn main() {
images,
faces,
failed,
too_small,
} => {
println!(
"\ndone: {images} image(s), {faces} face(s), {failed} failed, \
{too_small} with no proxy big enough, in {:.0}s",
"\ndone: {images} image(s), {faces} face(s), {failed} failed, in {:.0}s",
start.elapsed().as_secs_f64()
);
}
+10 -15
View File
@@ -35,12 +35,13 @@ use dr_types::ImageId;
/// The tier a stored face crop is cut from. See the module note.
///
/// **No longer the tier faces are *found* on**, and the distinction is the
/// whole of `docs/faces.md` §7c: 1024 is below [`dr_face::MIN_DETECT_EDGE`], so
/// detection refuses it. Cutting a crop out of a proxy this size is fine — the
/// face has already been located, and the crop only has to be looked at.
/// Finding one at this size is what returned 0.078 faces per image where the
/// same photographs returned 1.82.
/// **No longer a tier faces are found or cropped at**, and the distinction is
/// the whole of `docs/faces.md` §7: cutting an already-located face out of a
/// stored 1024px proxy so the People screen can draw a thumbnail of it is
/// fine, because that crop is only ever looked at. Sampling the *embedder's*
/// 112×112 from a buffer this size is what left 47% of the reference library's
/// faces upsampled, and FR-CULL-8 now requires that crop to come from the
/// native render.
pub const FACE_TIER: ThumbSize = ThumbSize::Large;
/// Progress from an indexing sweep.
@@ -69,10 +70,6 @@ pub enum FaceSweepMessage {
images: usize,
faces: usize,
failed: usize,
/// Refused for want of a big enough proxy rather than failed; see
/// `docs/faces.md` §7c. Counted apart because a library of small
/// scans is not a broken network and must not read as one.
too_small: usize,
},
}
@@ -369,7 +366,6 @@ pub fn spawn_store_face_sweep(
images: 0,
faces: 0,
failed: 0,
too_small: 0,
});
};
@@ -422,12 +418,12 @@ pub fn spawn_store_face_sweep(
// same thing. The pass is not repairable here either, because there is
// no larger tier for it to read; the pixels it needs have to come off
// the server, which is `library::spawn_face_sweep`'s job.
if (FACE_TIER.edge()) < dr_face::MIN_DETECT_EDGE {
if FACE_TIER.edge() < dr_face::MIN_CROP_EDGE {
log::warn!(
"face sweep: the local store's largest tier is {}px, below the {}px \
detection floor — use the library pass, which fetches its own pixels",
crop floor — use the library pass, which renders at native resolution",
FACE_TIER.edge(),
dr_face::MIN_DETECT_EDGE,
dr_face::MIN_CROP_EDGE,
);
finish_empty(&tx);
return;
@@ -527,7 +523,6 @@ pub fn spawn_store_face_sweep(
images,
faces: found,
failed,
too_small: 0,
});
});
+2 -14
View File
@@ -43,14 +43,12 @@ 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.
/// Images the running sweep could not get through.
///
/// 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
@@ -1044,7 +1042,6 @@ 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(
@@ -1101,15 +1098,12 @@ pub fn wire<S, M, P>(
.sweep_failed
.set(ctl_tick.sweep_failed.get() + images);
}
FaceSweepMessage::Finished {
failed, too_small, ..
} => {
FaceSweepMessage::Finished { failed, .. } => {
// 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;
}
}
@@ -1147,12 +1141,6 @@ pub fn wire<S, M, P>(
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);