Store seven eye numbers per face rather than three

Per eye P(open), the pixels across its box and the sharpness of the
patch; and P(sunglasses). The verdict — open, closed, sunglasses,
unclear — stays a rule in dr_face::eyes so the floors can move without
re-measuring twenty thousand faces. Shards carry the same seven, and a
peer's shard from before any of them is still read.
This commit is contained in:
2026-09-19 14:04:08 +02:00
parent f5956707e7
commit 54b543fb77
3 changed files with 134 additions and 58 deletions
+26 -11
View File
@@ -163,11 +163,19 @@ pub fn migrate(conn: &Connection) -> Result<i64, CatalogError> {
Ok(from)
}
/// The three columns V16 adds to `faces`, in the order the readers name them.
/// The seven columns V16 adds to `faces`, in the order the readers name them.
///
/// Named once because three places have to agree on them: this migration,
/// [`for_attached`], and the face shard's own catch-up (`face_shard`).
pub const EYE_COLUMNS: [&str; 3] = ["eye_right", "eye_left", "sunglasses"];
pub const EYE_COLUMNS: [&str; 7] = [
"eye_right",
"eye_right_px",
"eye_right_sharp",
"eye_left",
"eye_left_px",
"eye_left_sharp",
"sunglasses",
];
/// Recompute columns a migration added, for rows that predate it.
///
@@ -328,7 +336,11 @@ pub fn for_attached(schema_name: &str) -> String {
ALTER TABLE {schema_name}.faces ADD COLUMN crop BLOB;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN quality REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_right REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_right_px REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_right_sharp REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_left REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_left_px REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN eye_left_sharp REAL;\n\
ALTER TABLE {schema_name}.faces ADD COLUMN sunglasses REAL;",
rewrite_for_attached(V1, schema_name),
rewrite_for_attached(V6, schema_name),
@@ -734,16 +746,19 @@ CREATE TABLE IF NOT EXISTS xmp_conflicts (
// V16 -- TRACES: FR-CULL-13
//
// What each face's eyes are doing: P(open) for the subject's right and left
// eye, and P(sunglasses) for the head, each a sigmoid from a classifier run
// over the aligned crop at indexing time (dr_face::classify). Three numbers
// rather than a verdict, because the verdict is a rule with thresholds in it
// (dr_face::eyes::EyeReading::state) and a rule belongs in code that can be
// changed, not in rows that would have to be re-measured.
// What each face's eyes are doing: for each eye P(open), the source pixels
// across its box and the sharpness of the patch the classifier saw; and
// P(sunglasses) for the head. Seven numbers rather than a verdict, because
// the verdict is a rule with thresholds in it (dr_face::eyes::EyeReading::
// state) and a rule belongs in code that can be changed, not in rows that
// would have to be re-measured.
//
// Sunglasses are a column of their own and not folded into the eye numbers,
// because the eye classifier answers confidently over dark glass and its
// answer means nothing there. A filter for "eyes open" reads all three.
// The pixels and the sharpness are what stop a smear reading as a blink: an
// eye too small or too soft to read is not asked, and a face with no
// readable eye is "unclear", which no filter drops. Sunglasses are a column
// of their own for the same kind of reason — the eye classifier answers
// confidently over dark glass, and its answer means nothing there. A filter
// for "eyes open" reads all seven.
//
// NULL means "never measured" -- a face indexed before this version, or on a
// device without the eye models -- and a NULL is left alone by every filter