Withhold the empty groups that fill the People rail
`load_people` returned every live person, and on the reference library that was 14,268 rows of which 11,739 held no faces at all — 85 per cent of the rail naming nobody and able to do nothing. They are not a mystery. A regrouping pass creates a person per cluster; the next pass moves those faces elsewhere and leaves the person it emptied behind. `faces::prune_empty_unnamed` exists for exactly this and runs only at the end of a pass, so nothing clears what accumulates between them, and the Identity screen never prunes at all. Each one cost a `for_person` query and a built row on every reload. This withholds precisely the set the prune already treats as disposable — empty, unnamed, not set aside — and no more. Filtered rather than deleted: a screen is being drawn, not a catalog repaired. Nothing is lost, a sync cannot resurrect what was never removed, and the prune stays the one place that decides these can go. An empty group with a *name* still shows. That one is not debris but the symptom of a real failure — a named person whose faces were regrouped out from under them — and hiding it would take away the only way to merge them back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -34,7 +34,7 @@ A face is either the system's guess or the user's judgement, and the two are nev
|
|||||||
|
|
||||||
This is the point of having identified anybody. Without it the screen is a filing cabinet with no drawer handles.
|
This is the point of having identified anybody. Without it the screen is a filing cabinet with no drawer handles.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/identity.slint:552`</sub>
|
<sub>`ui/dr-ui/ui/identity.slint:571`</sub>
|
||||||
|
|
||||||
### Change how faces are grouped
|
### Change how faces are grouped
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ This is the point of having identified anybody. Without it the screen is a filin
|
|||||||
|
|
||||||
The right match confidence is a property of your library, not of the model. "What would this do?" answers for this library without writing anything; names, confirmations and the groups you have set aside are kept whatever the dials say.
|
The right match confidence is a property of your library, not of the model. "What would this do?" answers for this library without writing anything; names, confirmations and the groups you have set aside are kept whatever the dials say.
|
||||||
|
|
||||||
<sub>`ui/dr-ui/ui/identity.slint:589`</sub>
|
<sub>`ui/dr-ui/ui/identity.slint:608`</sub>
|
||||||
|
|
||||||
## Library grid
|
## Library grid
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,25 @@ pub struct IdentityView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read the people rail.
|
/// Read the people rail.
|
||||||
|
///
|
||||||
|
/// **A group holding no faces is not a group, and does not appear.** Only the
|
||||||
|
/// unnamed ones, which is exactly the set `faces::prune_empty_unnamed` treats
|
||||||
|
/// as debris: a pass creates a person per cluster, the next pass regroups those
|
||||||
|
/// faces elsewhere, and the person it emptied survives until something prunes
|
||||||
|
/// it. Nothing prunes on this path, so they accumulate — the reference library
|
||||||
|
/// reached 11,739 of them against 2,525 real people, and the rail was 85 per
|
||||||
|
/// cent rows that named nobody and could do nothing.
|
||||||
|
///
|
||||||
|
/// Filtered rather than deleted, because this is a screen being drawn and not a
|
||||||
|
/// catalog being repaired. A row is withheld; nothing is lost, a sync cannot
|
||||||
|
/// resurrect what was never removed, and the prune stays the one place that
|
||||||
|
/// decides these are disposable.
|
||||||
|
///
|
||||||
|
/// An empty group with a *name* still shows. That one is not debris, it is the
|
||||||
|
/// symptom of a real failure — a named person whose faces were regrouped out
|
||||||
|
/// from under them (see `Population::read` on why naming has to anchor) — and
|
||||||
|
/// the user cannot merge it back into the group that took them if the rail has
|
||||||
|
/// hidden it.
|
||||||
pub fn load_people(
|
pub fn load_people(
|
||||||
catalog: &Catalog,
|
catalog: &Catalog,
|
||||||
model_id: &str,
|
model_id: &str,
|
||||||
@@ -163,6 +182,9 @@ pub fn load_people(
|
|||||||
let conn = catalog.connection();
|
let conn = catalog.connection();
|
||||||
let people = faces::people(conn)?
|
let people = faces::people(conn)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|p| {
|
||||||
|
p.confirmed_faces + p.suggested_faces > 0 || !p.name.trim().is_empty() || p.ignored
|
||||||
|
})
|
||||||
.map(|p| PersonRow {
|
.map(|p| PersonRow {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
@@ -812,6 +834,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The rail withholds the debris a regrouping pass leaves behind, and only
|
||||||
|
/// that: the same rows `prune_empty_unnamed` treats as disposable.
|
||||||
|
///
|
||||||
|
/// Not a nicety. Every pass creates a person per cluster and the next pass
|
||||||
|
/// may empty it, nothing on this path prunes, and the reference library
|
||||||
|
/// reached 11,739 of them against 2,525 real people — a rail that was 85
|
||||||
|
/// per cent rows naming nobody, each one costing a query and a built row.
|
||||||
|
#[test]
|
||||||
|
fn the_rail_withholds_empty_unnamed_groups_and_nothing_else() {
|
||||||
|
let c = catalog();
|
||||||
|
let i1 = image(&c, 1);
|
||||||
|
let held =
|
||||||
|
faces::record_detections(c.connection(), i1, "w600k_mbf", 1024, &[face(1)]).unwrap();
|
||||||
|
|
||||||
|
let with_faces = faces::create_person(c.connection(), "Anna").unwrap();
|
||||||
|
faces::confirm(c.connection(), held[0], with_faces).unwrap();
|
||||||
|
|
||||||
|
// The three that hold nothing. Only the first is debris.
|
||||||
|
faces::create_person(c.connection(), "").unwrap();
|
||||||
|
let named = faces::create_person(c.connection(), "Bob").unwrap();
|
||||||
|
let set_aside = faces::create_person(c.connection(), "").unwrap();
|
||||||
|
faces::set_ignored(c.connection(), set_aside, true).unwrap();
|
||||||
|
|
||||||
|
let shown: Vec<_> = load_people(&c, "w600k_mbf")
|
||||||
|
.unwrap()
|
||||||
|
.people
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.id)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(shown.contains(&with_faces), "a group with faces is a group");
|
||||||
|
assert!(
|
||||||
|
shown.contains(&named),
|
||||||
|
"an empty group with a name is the visible symptom of a regroup that \
|
||||||
|
emptied it, and hiding it takes away the only way to merge it back"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
shown.contains(&set_aside),
|
||||||
|
"set aside is a judgement the user made, and the prune spares it too"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
shown.len(),
|
||||||
|
3,
|
||||||
|
"the empty unnamed group is the one that goes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn confirm_all_promotes_every_suggestion_and_nothing_else() {
|
fn confirm_all_promotes_every_suggestion_and_nothing_else() {
|
||||||
let c = catalog();
|
let c = catalog();
|
||||||
|
|||||||
Reference in New Issue
Block a user