Faces: nothing is bundled, so YuNet is not reported as installed

The model list marked the YuNet detector as bundled with LightCraft and installed, but no model ships
inside LightCraft (the weights are opt-in downloads or files the user adds). Drop the notion: a
model is installed when its folder is, it can be removed like any other, and the licence notice no
longer says it is bundled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Voshart
2026-10-08 21:59:02 -04:00
co-authored by Claude Sonnet 5.5
parent cb34bb4842
commit 7845d515e8
4 changed files with 11 additions and 26 deletions
+2 -10
View File
@@ -88,7 +88,6 @@ fn installed_models(dir: &Path) -> Vec<Installed> {
}
fn row(m: &ModelManifest, installed: bool, selected: bool, accepted: &Value) -> Value {
let bundled = known::BUNDLED.contains(&m.id.as_str());
json!({
"id": m.id,
"name": m.name,
@@ -99,8 +98,7 @@ fn row(m: &ModelManifest, installed: bool, selected: bool, accepted: &Value) ->
"sizeBytes": m.size_bytes,
"sha256": m.sha256,
"known": known::all().iter().any(|k| k.id == m.id),
"bundled": bundled,
"installed": installed || bundled,
"installed": installed,
"selected": selected,
"accepted": accepted,
})
@@ -192,9 +190,6 @@ fn install(s: &mut Session, p: &Value) -> Result<Value> {
Suggestion::Known(m) | Suggestion::Draft { manifest: m, .. } => m,
Suggestion::Unsupported(why) => return Err(bad(C, format!("this model cannot be used yet: {why}"))),
};
if known::BUNDLED.contains(&m.id.as_str()) {
return Err(bad(C, "this model is already included with LightCraft"));
}
if m.role != Role::Embedder {
return Err(bad(C, "only face recognition models can be installed for now"));
}
@@ -228,9 +223,6 @@ fn remove(s: &mut Session, p: &Value) -> Result<Value> {
if !manifest::valid_id(id) {
return Err(bad(C, "not a model id"));
}
if known::BUNDLED.contains(&id) {
return Err(bad(C, "this model is part of LightCraft and cannot be removed"));
}
let home = dir.join(id);
// only a folder that is a model (it has a manifest) is ever deleted
if !home.join("face-model.json").is_file() {
@@ -274,7 +266,7 @@ fn enable(s: &mut Session, p: &Value) -> Result<Value> {
pub fn specs() -> Vec<CommandSpec> {
vec![
cmd!(query "faces.models.list", "Face Models", [], None, "{} → {dir, enabled, embedder, runtime, models: [{id, name, role, licence{name, commercial, url, notice}, provenance, source, sizeBytes, known, bundled, installed, selected}]}", always, list),
cmd!(query "faces.models.list", "Face Models", [], None, "{} → {dir, enabled, embedder, runtime, models: [{id, name, role, licence{name, commercial, url, notice}, provenance, source, sizeBytes, known, installed, selected}]}", always, list),
cmd!(query "faces.models.inspect", "Inspect Face Model File", [], None, "{path} → what a .onnx file is: {kind: known | draft | unsupported, model, assumptions, reason, alreadyInstalled}; installs nothing", always, inspect),
cmd!(query "faces.models.install", "Install Face Model", [], None, "{path, acknowledged: true} — copy a .onnx face recognition model into the models folder. `acknowledged` must be true: the user has been shown its licence (see inspect) and accepted it", always, install),
cmd!(query "faces.models.remove", "Remove Face Model", [], None, "{id} — delete an installed model", always, remove),
+1 -2
View File
@@ -37,8 +37,7 @@ fn the_list_knows_the_known_models_and_is_honest_about_the_runtime() {
let l = s.execute("faces.models.list", &json!({})).unwrap();
assert_eq!(l["enabled"], false);
assert_eq!(l["runtime"], false);
assert_eq!(find(&l, "yunet-2023mar")["bundled"], true);
assert_eq!(find(&l, "yunet-2023mar")["installed"], true);
assert_eq!(find(&l, "yunet-2023mar")["installed"], false, "nothing ships with LightCraft");
assert_eq!(find(&l, "auraface-v1")["installed"], false);
assert_eq!(find(&l, "auraface-v1")["licence"]["commercial"], "yes");
assert_eq!(find(&l, "sface-2021dec")["licence"]["commercial"], "unknown");
+3 -7
View File
@@ -1,17 +1,13 @@
//! The models LightCraft recognises by their SHA-256, with what is honestly known about each.
//!
//! The licence and provenance texts here are what the user reads before enabling a model, so they say what
//! is *not* known too. Weights are never bundled except where the bundling decision says so (the YuNet
//! detector, 232 KB, MIT); everything else is an opt-in the user installs themselves.
//! is *not* known too. Weights are never part of LightCraft: every model is an opt-in the user installs.
use crate::manifest::{Colour, Commercial, InputSpec, Licence, ModelManifest, OutputSpec, Resize, Role, Thresholds};
/// Detector output decoders built into LightCraft.
pub const DECODERS: &[&str] = &["yunet-v2"];
/// Models that ship inside LightCraft (they are not installed by the user).
pub const BUNDLED: &[&str] = &["yunet-2023mar"];
pub const YUNET_SHA256: &str = "8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4";
pub const SFACE_SHA256: &str = "0ba9fbfa01b5270c96627c4ef784da859931e02f04419c829e83484087c34e79";
pub const AURAFACE_SHA256: &str = "a7933ea5330113b01c9b60351d8f4c33003f145d8470ac5f0e52ee2effe25c60";
@@ -20,7 +16,7 @@ fn licence(name: &str, commercial: Commercial, url: &str, notice: &str) -> Licen
Licence { name: name.into(), commercial, url: Some(url.into()), notice: notice.into() }
}
/// YuNet 2023mar (OpenCV Zoo): the face detector LightCraft bundles.
/// YuNet 2023mar (OpenCV Zoo): a small face detector.
pub fn yunet() -> ModelManifest {
ModelManifest {
id: "yunet-2023mar".into(),
@@ -31,7 +27,7 @@ pub fn yunet() -> ModelManifest {
"MIT",
Commercial::Yes,
"https://github.com/opencv/opencv_zoo/blob/main/models/face_detection_yunet/LICENSE",
"MIT licence, copyright Shiqi Yu. Bundled with LightCraft.",
"MIT licence, copyright Shiqi Yu.",
),
source: Some("https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet".into()),
sha256: Some(YUNET_SHA256.into()),
+5 -7
View File
@@ -145,13 +145,11 @@ fn model_row(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens, m: &Value)
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if installed {
if m["bundled"] != true {
let r = ui.button("Remove");
register(ui.ctx(), format!("faces:remove:{id}"), r.rect);
if r.clicked() {
let _ = app.run("faces.models.remove", json!({"id": id}));
app.caches.faces_epoch += 1;
}
let r = ui.button("Remove");
register(ui.ctx(), format!("faces:remove:{id}"), r.rect);
if r.clicked() {
let _ = app.run("faces.models.remove", json!({"id": id}));
app.caches.faces_epoch += 1;
}
if !selected {
let r = ui.button("Use");