Merge remote-tracking branch 'origin/main' into pr-2676

This commit is contained in:
FredApps
2026-11-04 12:23:28 +01:00
2 changed files with 49 additions and 1 deletions
+20 -1
View File
@@ -328,7 +328,9 @@ fn maker_matches(a: &[String], b: &[String]) -> bool {
}
/// How well a database name matches the EXIF name, 0..1. Every number in the database name (focal lengths,
/// apertures) must appear in the EXIF name; the maker's words are ignored on both sides (EXIF often omits them).
/// apertures) must appear in the EXIF name and vice versa; the maker's words are ignored on both sides (EXIF
/// often omits them). Extra EXIF words only lower the score; they name a different lens only when they mark
/// a teleconverter, a different optical system than the bare lens.
fn name_score(db: &[String], exif: &[String], maker: &[String]) -> f64 {
let db: Vec<&String> = db.iter().filter(|t| !maker.contains(t)).collect();
let exif: Vec<&String> = exif.iter().filter(|t| !maker.contains(t)).collect();
@@ -338,10 +340,27 @@ fn name_score(db: &[String], exif: &[String], maker: &[String]) -> f64 {
if db.iter().any(|t| is_number(t) && !exif.contains(t)) || exif.iter().any(|t| is_number(t) && !db.contains(t)) {
return 0.0;
}
// A teleconverter combo is a different optical system from the bare lens:
// reject it whichever side carries the marker, independent of which tokens
// the names drop or add.
let db_tc = db.iter().any(|t| is_teleconverter_marker(t.as_str()));
let exif_tc = exif.iter().any(|t| is_teleconverter_marker(t.as_str()));
if db_tc != exif_tc {
return 0.0;
}
let common = db.iter().filter(|t| exif.contains(t)).count() as f64;
common / db.len().max(exif.len()) as f64
}
/// Teleconverter markers in a lens name. A name carrying one names a different
/// optical system than the bare lens, so a match whose sides disagree on the
/// marker is rejected before scoring. Converter factors such as `1.4x`
/// tokenise into a number the base profile lacks, so the numeric check above
/// already rejects them.
fn is_teleconverter_marker(t: &str) -> bool {
t == "tc" || t == "teleconverter"
}
fn camera(n: &xml::Node) -> Option<Camera> {
Some(Camera {
maker: n.kid_text("maker")?.to_string(),
+29
View File
@@ -543,3 +543,32 @@ fn hostile_numbers_give_finite_corrections_or_none() {
assert!(db.find_camera("", "").is_none());
assert!(db.find_lens(None, " ", None).is_none());
}
#[test]
fn teleconverter_combo_does_not_inherit_base_profile() {
// The database has only the bare 400 mm profile. A teleconverter combo is a
// different optical system, so find_lens must not return the base profile --
// not even when the converter is a digit-free suffix such as "Z TC".
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<lensdatabase version="2">
<lens>
<maker>Nikon</maker>
<model>NIKKOR Z 400mm f/4.5 VR S</model>
<mount>Z</mount>
<calibration>
<distortion model="poly3" focal="400" k1="0.001" />
</calibration>
</lens>
</lensdatabase>"#;
let db = LensDb::parse(xml).unwrap();
assert!(db.find_lens(Some("Nikon"), "NIKKOR Z 400mm f/4.5 VR S", None).is_some(), "the base profile matches its own name");
// A digit-free teleconverter suffix must not inherit the 400 mm profile:
// name_score finds every base-profile number token present and would score 0.78.
assert!(db.find_lens(Some("Nikon"), "NIKKOR Z 400mm f/4.5 VR S Z TC", None).is_none(), "teleconverter combos do not inherit the base profile");
// A converter factor ("1.4") is a numeric token the base profile lacks, so the
// combo matches nothing either.
assert!(
db.find_lens(Some("Nikon"), "NIKKOR Z 400mm f/4.5 VR S Z TC-1.4x", None).is_none(),
"teleconverter combos do not inherit the base profile",
);
}