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:
@@ -58,7 +58,7 @@ use rusqlite::{Connection, OptionalExtension};
|
||||
use dr_types::ImageId;
|
||||
|
||||
use crate::error::CatalogError;
|
||||
use dr_face::EyeReading;
|
||||
use dr_face::{Eye, EyeReading};
|
||||
|
||||
/// The embedder half of a model id: what makes two faces comparable.
|
||||
///
|
||||
@@ -266,9 +266,10 @@ pub fn record_detections(
|
||||
"INSERT INTO faces
|
||||
(image_id, x, y, w, h, landmarks, detector_confidence,
|
||||
embedding, crop_px, model_id, detected_at, crop, quality,
|
||||
eye_right, eye_left, sunglasses)
|
||||
eye_right, eye_right_px, eye_right_sharp,
|
||||
eye_left, eye_left_px, eye_left_sharp, sunglasses)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13,
|
||||
?14, ?15, ?16)",
|
||||
?14, ?15, ?16, ?17, ?18, ?19, ?20)",
|
||||
rusqlite::params![
|
||||
image_id.0 as i64,
|
||||
f.x as f64,
|
||||
@@ -286,8 +287,12 @@ pub fn record_detections(
|
||||
// about.
|
||||
(!f.crop.is_empty()).then_some(f.crop.as_slice()),
|
||||
f.quality.map(f64::from),
|
||||
f.eyes.map(|e| f64::from(e.right_open)),
|
||||
f.eyes.map(|e| f64::from(e.left_open)),
|
||||
f.eyes.map(|e| f64::from(e.right.open)),
|
||||
f.eyes.map(|e| f64::from(e.right.px)),
|
||||
f.eyes.map(|e| f64::from(e.right.sharpness)),
|
||||
f.eyes.map(|e| f64::from(e.left.open)),
|
||||
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)),
|
||||
],
|
||||
)?;
|
||||
@@ -389,12 +394,20 @@ pub fn record_measurements(
|
||||
// a face a peer had already read must not blank the reading.
|
||||
if let Some(e) = m.eyes {
|
||||
tx.execute(
|
||||
"UPDATE faces SET eye_right = ?2, eye_left = ?3, sunglasses = ?4 WHERE id = ?1",
|
||||
"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
|
||||
WHERE id = ?1",
|
||||
rusqlite::params![
|
||||
m.face.0 as i64,
|
||||
f64::from(e.right_open),
|
||||
f64::from(e.left_open),
|
||||
f64::from(e.sunglasses)
|
||||
f64::from(e.right.open),
|
||||
f64::from(e.right.px),
|
||||
f64::from(e.right.sharpness),
|
||||
f64::from(e.left.open),
|
||||
f64::from(e.left.px),
|
||||
f64::from(e.left.sharpness),
|
||||
f64::from(e.sunglasses),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
@@ -605,7 +618,8 @@ pub fn for_image(conn: &Connection, image_id: ImageId) -> Result<Vec<Face>, Cata
|
||||
"SELECT f.id, f.image_id, f.x, f.y, f.w, f.h, f.landmarks,
|
||||
f.detector_confidence, f.crop_px, f.model_id,
|
||||
fp.person_id, fp.probability, fp.confirmed, f.quality,
|
||||
f.eye_right, f.eye_left, f.sunglasses
|
||||
f.eye_right, f.eye_right_px, f.eye_right_sharp,
|
||||
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses
|
||||
FROM faces f
|
||||
LEFT JOIN face_person fp ON fp.face_id = f.id
|
||||
WHERE f.image_id = ?1
|
||||
@@ -899,7 +913,8 @@ pub fn for_person(
|
||||
"SELECT f.id, f.image_id, f.x, f.y, f.w, f.h, f.landmarks,
|
||||
f.detector_confidence, f.crop_px, f.model_id,
|
||||
fp.person_id, fp.probability, fp.confirmed, f.quality,
|
||||
f.eye_right, f.eye_left, f.sunglasses
|
||||
f.eye_right, f.eye_right_px, f.eye_right_sharp,
|
||||
f.eye_left, f.eye_left_px, f.eye_left_sharp, f.sunglasses
|
||||
FROM faces f
|
||||
JOIN face_person fp ON fp.face_id = f.id
|
||||
WHERE fp.person_id = ?1 AND (?2 OR fp.confirmed = 1)
|
||||
@@ -1167,26 +1182,38 @@ fn read_face(r: &rusqlite::Row<'_>) -> rusqlite::Result<Face> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The three eye columns at `first`, `first + 1`, `first + 2`, as one reading.
|
||||
/// The seven eye columns at `first` .. `first + 6`, in [`EYE_COLUMNS`]'s
|
||||
/// order, as one reading.
|
||||
///
|
||||
/// All three or none: they are written together, and a row with one of them
|
||||
/// NULL is a row nothing in this crate produced. Read as absent rather than
|
||||
/// invented, which is what a reader of a half-written row deserves.
|
||||
/// All seven or none: they are written together, and a row with one of
|
||||
/// them NULL is a row nothing in this crate produced. Read as absent rather
|
||||
/// than invented, which is what a reader of a half-written row deserves.
|
||||
///
|
||||
/// [`EYE_COLUMNS`]: crate::schema::EYE_COLUMNS
|
||||
pub(crate) fn read_eyes(
|
||||
r: &rusqlite::Row<'_>,
|
||||
first: usize,
|
||||
) -> rusqlite::Result<Option<EyeReading>> {
|
||||
let right: Option<f64> = r.get(first)?;
|
||||
let left: Option<f64> = r.get(first + 1)?;
|
||||
let sunglasses: Option<f64> = r.get(first + 2)?;
|
||||
Ok(match (right, left, sunglasses) {
|
||||
(Some(right), Some(left), Some(sunglasses)) => Some(EyeReading {
|
||||
right_open: right as f32,
|
||||
left_open: left as f32,
|
||||
sunglasses: sunglasses as f32,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
let mut v = [0.0_f32; 7];
|
||||
for (i, slot) in v.iter_mut().enumerate() {
|
||||
match r.get::<_, Option<f64>>(first + i)? {
|
||||
Some(x) => *slot = x as f32,
|
||||
None => return Ok(None),
|
||||
}
|
||||
}
|
||||
Ok(Some(EyeReading {
|
||||
right: Eye {
|
||||
open: v[0],
|
||||
px: v[1],
|
||||
sharpness: v[2],
|
||||
},
|
||||
left: Eye {
|
||||
open: v[3],
|
||||
px: v[4],
|
||||
sharpness: v[5],
|
||||
},
|
||||
sunglasses: v[6],
|
||||
}))
|
||||
}
|
||||
|
||||
/// The predicate "this face still needs measuring", over `faces` aliased as
|
||||
@@ -1322,8 +1349,16 @@ mod tests {
|
||||
crop_px: 180.0,
|
||||
quality: Some(10.0 + f32::from(seed)),
|
||||
eyes: Some(EyeReading {
|
||||
right_open: 0.9,
|
||||
left_open: 0.8,
|
||||
right: Eye {
|
||||
open: 0.9,
|
||||
px: 40.0,
|
||||
sharpness: 0.2,
|
||||
},
|
||||
left: Eye {
|
||||
open: 0.8,
|
||||
px: 38.0,
|
||||
sharpness: 0.3,
|
||||
},
|
||||
sunglasses: 0.1,
|
||||
}),
|
||||
model_id: "w600k_mbf".into(),
|
||||
@@ -1361,14 +1396,15 @@ mod tests {
|
||||
assert_eq!(stored[0].quality, Some(11.0), "oldest first");
|
||||
assert_eq!(stored[1].quality, Some(12.0));
|
||||
// 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.left.open), Some(0.8));
|
||||
assert_eq!(got[0].eyes.map(|e| e.right.sharpness), Some(0.2));
|
||||
assert_eq!(
|
||||
got[0].eyes.map(|e| e.state()),
|
||||
Some(dr_face::EyeState::Open)
|
||||
);
|
||||
}
|
||||
|
||||
/// The three eye columns are one fact: a face with none of them reads as
|
||||
/// The seven eye columns are one fact: a face with none of them reads as
|
||||
/// unread, and the measuring pass is what fills them.
|
||||
#[test]
|
||||
fn eyes_are_measured_only_where_the_device_can_read_them() {
|
||||
@@ -1423,8 +1459,16 @@ mod tests {
|
||||
embedding: vec![9; 1024],
|
||||
quality: 21.5,
|
||||
eyes: Some(EyeReading {
|
||||
right_open: 0.2,
|
||||
left_open: 0.9,
|
||||
right: Eye {
|
||||
open: 0.2,
|
||||
px: 40.0,
|
||||
sharpness: 0.2,
|
||||
},
|
||||
left: Eye {
|
||||
open: 0.9,
|
||||
px: 40.0,
|
||||
sharpness: 0.2,
|
||||
},
|
||||
sunglasses: 0.0,
|
||||
}),
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user