Let the user choose which SCRFD finds their faces
faces.md §12.3 measured what the cheapest detector costs: the small faces in every group shot, and a dog embedded a dozen times. Which trade is right depends on the machine doing the sweep — a desktop left overnight and a tablet on a battery want different answers — so the detector is now a per-device setting, Fast / Balanced / Thorough on the settings page beside the indexing button, persisted with the rest of the settings file. A detector is half of a model id. Every face, marker, shard and calibration is keyed on faces.model_id precisely so that a model change is a new id and a re-index rather than a silent change under existing data, and a detector change is a model change: it decides which faces exist and where the landmarks that align them land. So each choice names its own pipeline. 500M keeps the bare "w600k_mbf" every existing library was written under, so an upgrade disturbs nothing; the others are qualified. Choosing one restarts coverage from zero under the new id, the sweep re-detects, confirmed names carry across by box overlap, and the sync shards are keyed by the same id so a peer on another setting neither adopts nor pollutes them. The library controller carries the id into the sync the same way it carries the cache budget, because the sync starts from places that have no settings in reach. All three shape-fixed exports ship — APK, Arch, Flatpak — since a tablet has no other way to obtain the one it was not installed with; the APK grows by twenty megabytes for the choice.
This commit is contained in:
+44
-11
@@ -1358,9 +1358,10 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// and their absence is the ordinary state of a fresh install.
|
||||
{
|
||||
let lib = library.clone();
|
||||
let settings = settings.clone();
|
||||
move || {
|
||||
let conn = lib.session()?;
|
||||
library::face_models(&conn.account)
|
||||
library::face_models(&conn.account, settings.snapshot().faces.detector)
|
||||
}
|
||||
},
|
||||
// The sweep opens its own connection on its own thread, so it
|
||||
@@ -1504,6 +1505,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
library.set_cache_budget(stored.cache.original_budget_bytes);
|
||||
library.set_keep_opened_originals(stored.cache.keep_opened_originals);
|
||||
library.set_timeline_bars(stored.library.timeline_bars);
|
||||
library.set_face_model_id(stored.faces.detector.model_id());
|
||||
}
|
||||
|
||||
// --- the export folder picker ----------------------------------------
|
||||
@@ -1648,6 +1650,16 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// The pipeline the next sync and the next sweep run under, and
|
||||
// the two lines on the page that describe it: which detector
|
||||
// file is installed, and how much of the library that
|
||||
// pipeline has covered — which for a freshly chosen one is
|
||||
// nothing, and saying so is the point.
|
||||
lib.set_face_model_id(s.faces.detector.model_id());
|
||||
if let Some(w) = weak.upgrade() {
|
||||
refresh_face_status(&w, &lib, s.faces.detector);
|
||||
}
|
||||
|
||||
// Lowering the ceiling evicts, so the figure beside it has just
|
||||
// changed — leaving the old one would show the cache still over a
|
||||
// limit that was enforced a moment ago.
|
||||
@@ -1662,18 +1674,9 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// through here rather than being kept up to date continuously —
|
||||
// they are only ever looked at while this page is on screen.
|
||||
let lib = library.clone();
|
||||
let catalog = library.catalog();
|
||||
let ctl = settings.clone();
|
||||
move |w: &AppWindow| {
|
||||
let store = lib.session().and_then(|c| {
|
||||
dr_thumbs::ThumbStore::open(&library::thumbs_dir(&c.account)).ok()
|
||||
});
|
||||
identity_ui::refresh_coverage(w, &catalog, store.as_ref());
|
||||
w.set_identity_model_missing(
|
||||
lib.session()
|
||||
.and_then(|c| library::face_models(&c.account))
|
||||
.is_none(),
|
||||
);
|
||||
refresh_face_status(w, &lib, ctl.snapshot().faces.detector);
|
||||
// Here rather than at startup, on exactly the reasoning
|
||||
// above: the catalog this reads is opened on a worker now,
|
||||
// so a launch has nothing to describe, and the figure is
|
||||
@@ -3588,6 +3591,36 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
/// the pinned figure is disk the user asked for and no ceiling will reclaim.
|
||||
/// One combined number would make the budget look wrong whenever a large
|
||||
/// collection was pinned.
|
||||
/// TRACES: FR-CULL-8
|
||||
/// The two face lines on the settings page: whether the chosen detector's file
|
||||
/// is installed, and how far the chosen pipeline has got through the library.
|
||||
///
|
||||
/// One function because they move together. A detector picked from the list
|
||||
/// changes which file has to exist *and* which `model_id` the coverage is
|
||||
/// counted under, and a page that updated one without the other would report
|
||||
/// "no model installed" over a coverage figure for a different model.
|
||||
fn refresh_face_status(
|
||||
window: &AppWindow,
|
||||
library: &Rc<library_ui::LibraryController>,
|
||||
detector: dr_types::FaceDetector,
|
||||
) {
|
||||
let store = library
|
||||
.session()
|
||||
.and_then(|c| dr_thumbs::ThumbStore::open(&library::thumbs_dir(&c.account)).ok());
|
||||
identity_ui::refresh_coverage(
|
||||
window,
|
||||
&library.catalog(),
|
||||
store.as_ref(),
|
||||
detector.model_id(),
|
||||
);
|
||||
window.set_identity_model_missing(
|
||||
library
|
||||
.session()
|
||||
.and_then(|c| library::face_models(&c.account, detector))
|
||||
.is_none(),
|
||||
);
|
||||
}
|
||||
|
||||
fn describe_cache_usage(library: &Rc<library_ui::LibraryController>) -> String {
|
||||
let Some(cache) = library.cache() else {
|
||||
return String::new();
|
||||
|
||||
Reference in New Issue
Block a user