Keep the dense landmarks behind each eye reading, packed

The 106 points the eye boxes were cut from, stored beside the reading as
16-bit fixed point over the frame: 424 bytes a face, a seventh of a pixel
on a 6000-pixel frame, where f16 at the same size would have been six.
Derived data like the embedding, kept for the same reason — it cost a
fetch and a model run, and the next per-face pass should run from the
catalog. Shards carry it; a peer's shard from before it is still read.
This commit is contained in:
2026-09-19 14:24:15 +02:00
parent 85cc2b1dcc
commit facb44cb55
10 changed files with 264 additions and 99 deletions
+33 -5
View File
@@ -127,6 +127,11 @@ pub struct DetectedFace {
/// models were not present at indexing, or the face came from a peer
/// that had none; the measuring pass fills it in (schema V16).
pub eyes: Option<EyeReading>,
/// The 106 dense landmarks the eyes were read from, as
/// `dr_face::Landmarks::to_packed_bytes` — 424 bytes, or empty where
/// the face was never read (schema V18). Kept so a later per-face pass
/// need not fetch the original again.
pub landmarks_dense: Vec<u8>,
/// Which model produced the embedding. Comparing across models is the one
/// mistake that yields plausible garbage rather than an error.
pub model_id: String,
@@ -161,6 +166,8 @@ pub struct Face {
pub quality: Option<f32>,
/// See [`DetectedFace::eyes`]. `None` for a face never read.
pub eyes: Option<EyeReading>,
/// See [`DetectedFace::landmarks_dense`]. Empty for a face never read.
pub landmarks_dense: Vec<u8>,
pub model_id: String,
/// `None` when the face belongs to no one yet.
pub person: Option<PersonId>,
@@ -267,9 +274,9 @@ pub fn record_detections(
(image_id, x, y, w, h, landmarks, detector_confidence,
embedding, crop_px, model_id, detected_at, crop, quality,
eye_right, eye_right_px, eye_right_sharp,
eye_left, eye_left_px, eye_left_sharp, sunglasses)
eye_left, eye_left_px, eye_left_sharp, sunglasses, landmarks_dense)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13,
?14, ?15, ?16, ?17, ?18, ?19, ?20)",
?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21)",
rusqlite::params![
image_id.0 as i64,
f.x as f64,
@@ -294,6 +301,7 @@ pub fn record_detections(
f.eyes.map(|e| f64::from(e.left.px)),
f.eyes.map(|e| f64::from(e.left.sharpness)),
f.eyes.map(|e| f64::from(e.sunglasses)),
(!f.landmarks_dense.is_empty()).then_some(f.landmarks_dense.as_slice()),
],
)?;
let id = FaceId(tx.last_insert_rowid() as u64);
@@ -354,6 +362,8 @@ pub struct Measurement {
/// See [`DetectedFace::eyes`]. `None` where the measuring device has no
/// eye models, in which case the stored reading is left as it was.
pub eyes: Option<EyeReading>,
/// See [`DetectedFace::landmarks_dense`]; written with `eyes`.
pub landmarks_dense: Vec<u8>,
}
/// Write fresh embeddings over faces that were found before their quality was
@@ -397,7 +407,7 @@ pub fn record_measurements(
"UPDATE faces
SET eye_right = ?2, eye_right_px = ?3, eye_right_sharp = ?4,
eye_left = ?5, eye_left_px = ?6, eye_left_sharp = ?7,
sunglasses = ?8
sunglasses = ?8, landmarks_dense = ?9
WHERE id = ?1",
rusqlite::params![
m.face.0 as i64,
@@ -408,6 +418,7 @@ pub fn record_measurements(
f64::from(e.left.px),
f64::from(e.left.sharpness),
f64::from(e.sunglasses),
(!m.landmarks_dense.is_empty()).then_some(m.landmarks_dense.as_slice()),
],
)?;
}
@@ -619,7 +630,8 @@ pub fn for_image(conn: &Connection, image_id: ImageId) -> Result<Vec<Face>, Cata
f.detector_confidence, f.crop_px, f.model_id,
fp.person_id, fp.probability, fp.confirmed, f.quality,
f.eye_right, f.eye_right_px, f.eye_right_sharp,
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses,
f.landmarks_dense
FROM faces f
LEFT JOIN face_person fp ON fp.face_id = f.id
WHERE f.image_id = ?1
@@ -914,7 +926,8 @@ pub fn for_person(
f.detector_confidence, f.crop_px, f.model_id,
fp.person_id, fp.probability, fp.confirmed, f.quality,
f.eye_right, f.eye_right_px, f.eye_right_sharp,
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses,
f.landmarks_dense
FROM faces f
JOIN face_person fp ON fp.face_id = f.id
WHERE fp.person_id = ?1 AND (?2 OR fp.confirmed = 1)
@@ -1175,6 +1188,7 @@ fn read_face(r: &rusqlite::Row<'_>) -> rusqlite::Result<Face> {
crop_px: r.get::<_, f64>(8)? as f32,
quality: r.get::<_, Option<f64>>(13)?.map(|q| q as f32),
eyes: read_eyes(r, 14)?,
landmarks_dense: r.get::<_, Option<Vec<u8>>>(21)?.unwrap_or_default(),
model_id: r.get(9)?,
person: person.map(|p| PersonId(p as u64)),
probability: r.get::<_, Option<f64>>(11)?.unwrap_or(0.0) as f32,
@@ -1361,6 +1375,7 @@ mod tests {
},
sunglasses: 0.1,
}),
landmarks_dense: vec![7; 424],
model_id: "w600k_mbf".into(),
crop: Vec::new(),
}
@@ -1398,6 +1413,11 @@ mod tests {
// And the eyes, as one reading.
assert_eq!(got[0].eyes.map(|e| e.left.open), Some(0.8));
assert_eq!(got[0].eyes.map(|e| e.right.sharpness), Some(0.2));
assert_eq!(
got[0].landmarks_dense.len(),
424,
"the dense landmarks ride along"
);
assert_eq!(
got[0].eyes.map(|e| e.state()),
Some(dr_face::EyeState::Open)
@@ -1442,6 +1462,7 @@ mod tests {
embedding: vec![9; 1024],
quality: 21.5,
eyes: None,
landmarks_dense: Vec::new(),
}],
&[],
)
@@ -1471,11 +1492,17 @@ mod tests {
},
sunglasses: 0.0,
}),
landmarks_dense: vec![9; 424],
}],
&[],
)
.unwrap();
assert_eq!(faces_unmeasured(&c, "w600k_mbf", true).unwrap(), 0);
assert_eq!(
for_image(&c, img).unwrap()[0].landmarks_dense.len(),
424,
"written with the reading"
);
let got = for_image(&c, img).unwrap();
let read = got.iter().find(|f| f.id == ids[0]).unwrap();
assert_eq!(
@@ -1527,6 +1554,7 @@ mod tests {
embedding: vec![9; 1024],
quality: 21.5,
eyes: None,
landmarks_dense: Vec::new(),
}],
&[ids[1]],
)