Name the eye pass on the People screen

Once every image has been through the detector and only readings are
left — the state an already-indexed library is in the day the eye models
arrive — the button reads "Read eye state" rather than promising to
index, and the coverage line says what the faces are waiting for.
This commit is contained in:
2026-09-19 14:24:16 +02:00
parent facb44cb55
commit f79a76f2d5
6 changed files with 47 additions and 15 deletions
+30 -14
View File
@@ -200,7 +200,10 @@ impl IndexAudit {
s.push_str(&format!("; {outstanding} to index"));
}
if self.unmeasured > 0 {
s.push_str(&format!("; {} face(s) to measure", self.unmeasured));
s.push_str(&format!(
"; {} face(s) to read for quality or eye state",
self.unmeasured
));
}
s
}
@@ -284,6 +287,10 @@ pub fn audit(
/// the models, which is the ordinary state of one that has not been given
/// them, and `None` — logged — where the models refuse: a face the embedder
/// could use is not lost for the want of an eye reading.
///
/// Returns the reading and the dense landmarks it was read from, the
/// latter already packed for the catalog (`landmarks_dense`, normalised by
/// `long_edge` like the five points).
fn read_eyes(
models: Option<&mut EyeModels>,
px: dr_face::Pixels<'_>,
@@ -291,13 +298,17 @@ fn read_eyes(
height: usize,
bbox: (f32, f32, f32, f32),
landmarks: &[(f32, f32); 5],
) -> Option<EyeReading> {
let models = models?;
long_edge: f32,
) -> (Option<EyeReading>, Vec<u8>) {
let Some(models) = models else {
return (None, Vec::new());
};
match models.read(px, width, height, bbox, landmarks) {
Ok(r) => r,
Ok(Some((reading, dense))) => (Some(reading), dense.to_packed_bytes(long_edge)),
Ok(None) => (None, Vec::new()),
Err(e) => {
log::debug!("eye reading failed: {e}");
None
(None, Vec::new())
}
}
}
@@ -370,13 +381,14 @@ pub fn index_proxy(
}
let embedded = embedder.embed(&aligned)?;
let eyes = read_eyes(
let (eyes, landmarks_dense) = read_eyes(
eye_models.as_deref_mut(),
dr_face::Pixels::RgbF32(rgb),
width,
height,
d.bbox,
&d.landmarks,
long_edge,
);
out.push(DetectedFace {
@@ -390,6 +402,7 @@ pub fn index_proxy(
crop_px: aligned.source_px(),
quality: Some(embedded.quality),
eyes,
landmarks_dense,
model_id: embedder.model().as_str().to_string(),
// Cut here, while the buffer is still in hand. This is the only
// moment in the whole pipeline where the pixels are free.
@@ -499,13 +512,14 @@ pub fn index_native(
// From the native buffer, box and landmarks scaled like the crop's
// — the eye is a fortieth of the face, and it is here that the
// native render pays for itself twice.
let eyes = read_eyes(
let (eyes, landmarks_dense) = read_eyes(
eye_models.as_deref_mut(),
native,
width,
height,
(bx, by, bx + bw, by + bh),
&landmarks,
long_edge,
);
out.push(DetectedFace {
@@ -519,6 +533,7 @@ pub fn index_native(
crop_px: aligned.source_px(),
quality: Some(embedded.quality),
eyes,
landmarks_dense,
model_id: embedder.model().as_str().to_string(),
crop: cut_crop_native(native, width, height, (bx, by, bw, bh)).unwrap_or_default(),
});
@@ -582,8 +597,8 @@ pub fn measure_native(
continue;
};
let embedded = embedder.embed(&aligned)?;
let eyes = if f.eyes.is_some() {
None
let (eyes, landmarks_dense) = if f.eyes.is_some() {
(None, Vec::new())
} else {
let bbox = (
f.x * long_edge,
@@ -598,6 +613,7 @@ pub fn measure_native(
height,
bbox,
&landmarks,
long_edge,
)
};
out.measured.push(faces::Measurement {
@@ -605,6 +621,7 @@ pub fn measure_native(
embedding: embedded.to_f16_bytes(),
quality: embedded.quality,
eyes,
landmarks_dense,
});
}
Ok(out)
@@ -1610,6 +1627,7 @@ mod tests {
crop_px: 280.0,
quality: None,
eyes: None,
landmarks_dense: Vec::new(),
model_id: "w600k_mbf".into(),
person: None,
probability: 0.0,
@@ -1718,11 +1736,7 @@ mod tests {
};
assert!(a.coverage.is_complete());
assert!(!a.is_complete());
assert!(
a.summary().contains("4 face(s) to measure"),
"{}",
a.summary()
);
assert!(a.summary().contains("4 face(s) to read"), "{}", a.summary());
}
/// The figure the real library actually produced: 110 of 23,528 rounds to
@@ -1807,6 +1821,7 @@ mod tests {
crop_px: 120.0,
quality: None,
eyes: None,
landmarks_dense: Vec::new(),
model_id: "w600k_mbf".into(),
person: None,
probability: 0.0,
@@ -1926,6 +1941,7 @@ mod tests {
crop_px: 150.0,
quality: None,
eyes: None,
landmarks_dense: Vec::new(),
model_id: TEST_MODEL.to_string(),
crop: Vec::new(),
};