Keep each face's quality, and never compare against a poor one
The embedder's raw output has a length, and the length is a reading of how recognisable the crop was: a blur, an occlusion or a hard profile comes out short. Normalising threw it away. A short vector sits near the middle of the sphere and matches a little of everyone, which is how one bad crop bridges two people in a grouping pass. So the length is kept — the store now holds the raw vector, re-normalised on load, with the length beside it as `faces.quality` — and a face under MIN_GALLERY_QUALITY (14) is a probe: measured against the gallery and placed where it fits, but never what another face is measured against. Two probes are never paired, and a probe is nobody's evidence for a confidence. The People screen shows the number as "Quality 17.3", dimmed below the floor. Faces indexed before this stored unit vectors and have no reading; they are admitted to the gallery, and schema V14 forgets the run marker of every image holding one so the next indexing pass measures them. A peer's unmeasured shard faces are not adopted, or a sync would write that marker back.
This commit is contained in:
@@ -117,6 +117,17 @@ pub struct Faces<'a> {
|
||||
/// Which photograph each face came from. Two faces in one frame are not
|
||||
/// the same person, so those pairs are never returned (docs/faces.md §9).
|
||||
pub images: &'a [u64],
|
||||
/// Which faces may be compared *against* — the gallery
|
||||
/// ([`crate::embedding::MIN_GALLERY_QUALITY`]).
|
||||
///
|
||||
/// A pair needs at least one gallery side: a probe measured against a
|
||||
/// reference is a comparison, two short vectors measured against each
|
||||
/// other is noise agreeing with noise, and those pairs are never returned.
|
||||
/// Filtered here rather than by the caller for the same reason
|
||||
/// co-occurrence is: what this module leaves out of the list stays out of
|
||||
/// the graph, the components and the merge order, so nothing downstream
|
||||
/// has to remember the rule.
|
||||
pub gallery: &'a [bool],
|
||||
}
|
||||
|
||||
impl Faces<'_> {
|
||||
@@ -272,8 +283,9 @@ fn scan_rows(scan: &Scan, from: usize, to: usize, out: &mut Vec<Pair>) {
|
||||
let a = faces.row(i);
|
||||
let crop_a = faces.crop_px[i];
|
||||
let image_a = faces.images[i];
|
||||
let gallery_a = faces.gallery[i];
|
||||
for j in start..tile_end {
|
||||
if image_a == faces.images[j] {
|
||||
if image_a == faces.images[j] || !(gallery_a || faces.gallery[j]) {
|
||||
continue;
|
||||
}
|
||||
let cos = dot(a, faces.row(j));
|
||||
@@ -552,6 +564,7 @@ mod tests {
|
||||
embeddings: Vec<f32>,
|
||||
crop_px: Vec<f32>,
|
||||
images: Vec<u64>,
|
||||
gallery: Vec<bool>,
|
||||
}
|
||||
|
||||
impl Set {
|
||||
@@ -561,6 +574,7 @@ mod tests {
|
||||
dim: DIM,
|
||||
crop_px: &self.crop_px,
|
||||
images: &self.images,
|
||||
gallery: &self.gallery,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,10 +602,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
let crop_px = vec![150.0; embeddings.len()];
|
||||
let gallery = vec![true; embeddings.len()];
|
||||
Set {
|
||||
embeddings: embeddings.concat(),
|
||||
crop_px,
|
||||
images,
|
||||
gallery,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,7 +617,7 @@ mod tests {
|
||||
let mut out = Vec::new();
|
||||
for i in 0..n {
|
||||
for j in i + 1..n {
|
||||
if faces.images[i] == faces.images[j] {
|
||||
if faces.images[i] == faces.images[j] || !(faces.gallery[i] || faces.gallery[j]) {
|
||||
continue;
|
||||
}
|
||||
let cos: f32 = faces
|
||||
@@ -750,6 +766,35 @@ mod tests {
|
||||
assert!(above_threshold(&s.faces(), &cal(), 0.9).is_empty());
|
||||
}
|
||||
|
||||
/// A probe against a reference is a comparison; two probes against each
|
||||
/// other is not. The rule lives here so that nothing downstream sees the
|
||||
/// pair at all.
|
||||
#[test]
|
||||
fn two_faces_outside_the_gallery_are_never_paired() {
|
||||
let mut s = population(1, 3, 1.0);
|
||||
s.gallery = vec![false, false, true];
|
||||
let pairs = above_threshold(&s.faces(), &cal(), 0.9);
|
||||
assert!(
|
||||
!pairs.iter().any(|p| p.i == 0 && p.j == 1),
|
||||
"two probes were paired with each other"
|
||||
);
|
||||
// Each probe is still measured against the one reference.
|
||||
assert!(pairs.iter().any(|p| p.i == 0 && p.j == 2));
|
||||
assert!(pairs.iter().any(|p| p.i == 1 && p.j == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_gallery_rule_matches_the_reference_at_scale() {
|
||||
let mut s = population(60, 8, 0.97);
|
||||
for (i, g) in s.gallery.iter_mut().enumerate() {
|
||||
*g = i % 3 != 0;
|
||||
}
|
||||
let f = s.faces();
|
||||
let got = above_threshold(&f, &cal(), 0.9);
|
||||
let want = reference(&f, &cal(), 0.9);
|
||||
assert!(same_pairs(&got, &want), "{} vs {}", got.len(), want.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pairs_come_back_in_index_order() {
|
||||
let s = population(300, 8, 0.97);
|
||||
|
||||
Reference in New Issue
Block a user