Put the grouping dials where the regrouping is
The merge probability was `dr_face`'s constant and the smallest group was a bare `< 2` in the clustering pass. Both were tuned on one library — 1,813 faces of one photographer's family — and the quantity they optimise is a property of the population, not of the model. A household at close family resemblance and two thousand strangers at a wedding want different answers, and neither of them is the reference library. The doc comment already conceded the point and pointed at `face_index --tune`; a photographer does not have a terminal. So they are `FaceSettings` now, saved per device beside the cache budgets and edited from the People screen — beside the Regroup button that applies them and the rail that shows what they did, because a value changed three screens away from its effect is one nobody can tune. Moving them is safe by construction, which is why nothing asks for confirmation: a regroup writes only the suggested half, and confirmations, names and ignores enter as anchors and come back unchanged. The smallest-group rule is applied only to groups the system invented — a group the user named or set aside survives it whatever its size, because a display preference does not overrule a judgement. **Withdrawal, without which the setting does nothing visible.** Raising the smallest group stops the pass creating small groups; it does not remove the ones a previous pass made, because those still hold their suggestions, so they are not empty, so the prune leaves them. The pass now releases every unanchored face it did not place before pruning. And a dial you cannot see the effect of is not a dial. "What would this do?" runs the same population through the clusterer without opening a transaction and reports groups, faces grouped and largest group — one row of `--tune`'s table, on the user's own library, on a worker thread. The line leads with the group count because that is the number that says which side of the right setting you are on: it climbs as fragments are gathered into people and falls as separate people start being welded, while the grouped-face count rises straight through both. The preview parks its poll timer in a slot of its own. A preview and a regroup are allowed to be in flight together, and sharing the sweep's single slot would have the second to start drop the first's timer — visible as a Regroup that finished on its worker and never said so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Theme } from "theme.slint";
|
||||
import { Panel, Button, IconButton, Field } from "widgets.slint";
|
||||
import { SliderRow } from "controls.slint";
|
||||
import { Icon } from "icons.slint";
|
||||
|
||||
export struct IdentityPerson {
|
||||
@@ -235,6 +236,11 @@ export component IdentityScreen inherits Rectangle {
|
||||
/// live and has to say what it is doing rather than simply stopping.
|
||||
in property <bool> regrouping: false;
|
||||
in property <string> regroup-status;
|
||||
/// Whether the grouping dials are open.
|
||||
///
|
||||
/// Private to the screen: nothing in Rust needs to know, and a disclosure
|
||||
/// state that round-tripped through a callback would flicker.
|
||||
property <bool> grouping-open: false;
|
||||
/// People the user has set aside, and whether the rail is showing them.
|
||||
in property <int> ignored-count: 0;
|
||||
in property <bool> show-ignored: false;
|
||||
@@ -251,6 +257,21 @@ export component IdentityScreen inherits Rectangle {
|
||||
/// "and also" button only appears when there is something to add to.
|
||||
in property <bool> photos-filtered: false;
|
||||
callback recluster();
|
||||
/// TRACES: FR-CULL-9
|
||||
/// How sure the pass has to be before it calls two groups one person, as a
|
||||
/// percentage — the same units every confidence on this screen is printed
|
||||
/// in, because a control in *cosines* would be the one place in the
|
||||
/// subsystem that thresholds a bare similarity.
|
||||
in property <float> merge-probability: 80;
|
||||
/// The smallest group the pass will make a person out of.
|
||||
in property <int> min-group-size: 2;
|
||||
callback merge-probability-changed(float);
|
||||
callback min-group-size-changed(int);
|
||||
/// Ask what the dials would do, without doing it.
|
||||
callback preview-grouping();
|
||||
in property <bool> previewing: false;
|
||||
/// The answer, in words. Empty until one has been asked for.
|
||||
in property <string> grouping-preview;
|
||||
callback index-faces();
|
||||
callback stop-indexing();
|
||||
callback check-coverage();
|
||||
@@ -530,6 +551,106 @@ export component IdentityScreen inherits Rectangle {
|
||||
enabled: !root.regrouping;
|
||||
clicked => { root.recluster(); }
|
||||
}
|
||||
// The dials Regroup turns, immediately beside it. They belong
|
||||
// here and not on the settings page: they are only meaningful
|
||||
// next to the button that applies them and the rail that shows
|
||||
// what they did, and a value changed three screens away from
|
||||
// its effect is a value nobody can tune.
|
||||
Button {
|
||||
text: "Grouping…";
|
||||
active: root.grouping-open;
|
||||
clicked => { root.grouping-open = !root.grouping-open; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- the grouping dials ---------------------------------------
|
||||
//
|
||||
// Opened inline rather than in a popup, the way the develop
|
||||
// column's film picker is: this screen already scrolls as one, and
|
||||
// a second overlay to dismiss is a second gesture to lose.
|
||||
if root.grouping-open: Rectangle {
|
||||
border-radius: Theme.radius;
|
||||
background: Theme.surface-raised;
|
||||
// Stated, because the layout above it is a VerticalLayout that
|
||||
// would otherwise stretch this block over the faces grid.
|
||||
height: dials.preferred-height + 2 * Theme.gap;
|
||||
|
||||
dials := VerticalLayout {
|
||||
x: Theme.gap;
|
||||
y: Theme.gap;
|
||||
width: parent.width - 2 * Theme.gap;
|
||||
spacing: Theme.gap-sm;
|
||||
|
||||
// **The hints are two words each, and that is deliberate.**
|
||||
// `FieldRow` draws a hint as a non-wrapping elided
|
||||
// `Caption`, whose *minimum* width is still its whole text
|
||||
// — and a layout cannot be narrower than its children's
|
||||
// minimums, so a sentence here would set the minimum width
|
||||
// of this pane and, on a phone, of the screen. The
|
||||
// sentences go in the wrapping paragraph below instead,
|
||||
// where they cost nothing.
|
||||
SliderRow {
|
||||
label: "Match confidence";
|
||||
hint: "%";
|
||||
value: root.merge-probability;
|
||||
default-value: 80;
|
||||
minimum: 50;
|
||||
maximum: 95;
|
||||
precision: 0;
|
||||
enabled: !root.regrouping;
|
||||
changed(v) => { root.merge-probability-changed(v); }
|
||||
reset => { root.merge-probability-changed(80); }
|
||||
}
|
||||
|
||||
SliderRow {
|
||||
label: "Smallest group";
|
||||
hint: "faces";
|
||||
value: root.min-group-size;
|
||||
default-value: 2;
|
||||
minimum: 1;
|
||||
maximum: 12;
|
||||
precision: 0;
|
||||
enabled: !root.regrouping;
|
||||
changed(v) => { root.min-group-size-changed(v); }
|
||||
reset => { root.min-group-size-changed(2); }
|
||||
}
|
||||
|
||||
// The dial is otherwise blind: nothing on the screen says
|
||||
// what 78% means for *this* library until the user commits
|
||||
// to a pass and reads the rail. `dr_face`'s tuning table is
|
||||
// the right answer to that question and it is measured on
|
||||
// somebody else's photographs, so this runs it here — one
|
||||
// row of it, read-only, for the value actually set.
|
||||
HorizontalLayout {
|
||||
spacing: Theme.gap-sm;
|
||||
alignment: start;
|
||||
|
||||
Button {
|
||||
text: root.previewing ? "Working…" : "What would this do?";
|
||||
enabled: !root.previewing && !root.regrouping;
|
||||
clicked => { root.preview-grouping(); }
|
||||
}
|
||||
if root.grouping-preview != "": Text {
|
||||
text: root.grouping-preview;
|
||||
color: Theme.ink-dim;
|
||||
font-size: Theme.text-sm;
|
||||
vertical-alignment: center;
|
||||
wrap: word-wrap;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// What the two dials mean, and — said plainly, because
|
||||
// they look destructive and are not — what they cannot
|
||||
// touch: they move the *suggested* half, which this pass
|
||||
// owns and may revise as often as it likes.
|
||||
Text {
|
||||
text: "Lower confidence gathers more of each person together; too low and different people are welded into one. A bigger smallest group keeps one-off strangers off the rail.\n\nPress Regroup to apply. Names, confirmations and the groups you have set aside are kept whatever the dials say.";
|
||||
color: Theme.ink-faint;
|
||||
font-size: Theme.text-sm;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user