From 41c655c176e7485002543ece1acc8cf2e25be458 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 30 Aug 2026 18:17:01 +0200 Subject: [PATCH] Stop the local-store pass pretending it can still index spawn_store_face_sweep detects on what the thumbnail store holds, and the store's largest tier is FACE_TIER -- 1024, which the floor now refuses. Left alone it would list every outstanding image, decode every proxy it had, and report every single one as failed: twenty thousand refusals all saying the same thing, with the real explanation buried at debug level. There is no repair available inside this pass. It has no larger tier to read; the pixels the detector needs have to come off the server, which is spawn_face_sweep's job and always was. So the honest behaviour is to check the tier against the floor once, say plainly which pass to use instead, and stop. It is only reached from examples/face_index.rs, so this costs the tool its --run mode and no shipped behaviour. FACE_TIER keeps its value and loses its meaning. It is now the tier a stored crop is *cut from*, which 1024 is entirely adequate for -- the face has already been located and the crop only has to be looked at -- and no longer the tier faces are *found* on, which is the thing that was returning 0.078 faces per image. IndexAudit's ready/awaiting_proxy split goes the same way. It existed because one of the two passes could only do images that already had a proxy; now that detection refuses that proxy's size, both halves cost the same fetch, and a status line reading "169 ready to index" implies a distinction that no longer decides anything. One number. Co-Authored-By: Claude Opus 5 (1M context) --- ui/dr-ui/src/faces.rs | 56 +++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/ui/dr-ui/src/faces.rs b/ui/dr-ui/src/faces.rs index a323b3a..6efc3a6 100644 --- a/ui/dr-ui/src/faces.rs +++ b/ui/dr-ui/src/faces.rs @@ -33,7 +33,14 @@ use dr_thumbs::{ThumbSize, ThumbStore}; use dr_types::settings::FaceSettings; use dr_types::ImageId; -/// The tier faces are found on. See the module note. +/// 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. pub const FACE_TIER: ThumbSize = ThumbSize::Large; /// Progress from an indexing sweep. @@ -120,11 +127,15 @@ pub fn faces_outstanding( #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct IndexAudit { pub coverage: faces::Coverage, - /// Outstanding and ready: the proxy exists, so a sweep would do these now. + /// Outstanding, with a proxy already on disk. + /// + /// **Not "ready to index".** It was, when a pass existed that detected on + /// the stored proxy; that proxy is 1024 and detection now refuses it + /// (§7c), so these need a fetch exactly like the others do. Kept as a + /// separate number only because it says something true about the cache. pub ready: u64, - /// Outstanding with no proxy on disk, so the whole-library pass will fetch - /// one. Still reported separately because it is the expensive half — these - /// cost a range request each, and the ones above cost nothing. + /// Outstanding with no proxy on disk. Costs a range request, same as the + /// ones above now do. pub awaiting_proxy: u64, } @@ -151,13 +162,14 @@ impl IndexAudit { "{}/{} images indexed ({shown}), {} face(s), {} image(s) with none", c.indexed, c.images, c.faces, c.without_faces, ); - if self.ready > 0 { - s.push_str(&format!("; {} ready to index", self.ready)); - } - if self.awaiting_proxy > 0 { - // Not a blocker any more, and it must not read like one: the - // whole-library pass fetches these rather than skipping them. - s.push_str(&format!("; {} to fetch", self.awaiting_proxy)); + // One number, not two. The split used to matter because one of the + // two passes could only do the images that already had a proxy; now + // that detection refuses that proxy's size, both halves cost the same + // fetch and telling the user "169 ready, 0 to fetch" only implies a + // distinction that no longer decides anything. + let outstanding = self.ready + self.awaiting_proxy; + if outstanding > 0 { + s.push_str(&format!("; {outstanding} to index")); } s } @@ -382,6 +394,26 @@ pub fn spawn_store_face_sweep( } }; + // Nothing below this point can succeed, so do not pretend to try. + // + // This pass detects on what the store holds, and the store's largest + // tier is `FACE_TIER` — 1024, which is below the floor. Left to run it + // would fetch nothing, decode every proxy it has, and report every + // single image as failed: twenty thousand refusals that all say the + // 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 { + 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", + FACE_TIER.edge(), + dr_face::MIN_DETECT_EDGE, + ); + finish_empty(&tx); + return; + } + let wanted = match faces_outstanding(&catalog, &store, &model_id) { Ok(w) => w, Err(e) => {