LR-EDIT-OPTICS-PROFILE: find the profile named by a lens specification behind a tie

Another maker's lens with the same specification ("Otherco 18-55mm f/3.5-5.6")
scores as high as the profile aliased "18-55mm f/3.5-5.6"; when it came first,
find_lens kept it and the exact-name check then refused it, so the alias was
never used. LensDb::find_lens_named ranks only the profiles named exactly so,
with the same maker and mount preferences (review on #2217).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nerdislb
2026-10-18 18:42:50 +02:00
co-authored by Claude Opus 5.5
parent 067f4fdf1e
commit cb61588e7d
3 changed files with 35 additions and 2 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn lookup(db: &LensDb, m: &lightcraft_meta::Metadata, w: f64, h: f64, orient
// (a user's alias), since the specification fits other makers' lenses too
None => {
let name = lightcraft_lensdb::spec_name(m.lens_spec?);
db.find_lens(m.lens_make.as_deref(), &name, camera).filter(|l| l.names_exactly(&name))?
db.find_lens_named(m.lens_make.as_deref(), &name, camera)?
}
};
let crop = frame_crop_factor(camera.map(|c| c.crop_factor), focal, m.focal_length_35mm);
+12 -1
View File
@@ -169,13 +169,24 @@ impl LensDb {
/// The best profile for an EXIF lens model (and lens make, when the file has one), preferring lenses for the
/// camera's mount. `None` when no name is a convincing match.
pub fn find_lens(&self, lens_make: Option<&str>, lens_model: &str, camera: Option<&Camera>) -> Option<&Profile> {
self.best_lens(lens_make, lens_model, camera, |_| true)
}
/// [`find_lens`](Self::find_lens) among the profiles one of whose names is exactly `name`
/// ([`Profile::names_exactly`]), for names that carry no maker, such as [`spec_name`]s: another maker's lens with
/// the same specification scores as high, and must neither win nor, by coming first, hide the profile named so.
pub fn find_lens_named(&self, lens_make: Option<&str>, name: &str, camera: Option<&Camera>) -> Option<&Profile> {
self.best_lens(lens_make, name, camera, |l| l.names_exactly(name))
}
fn best_lens(&self, lens_make: Option<&str>, lens_model: &str, camera: Option<&Camera>, eligible: impl Fn(&Profile) -> bool) -> Option<&Profile> {
let exif = tokens(lens_model);
if exif.is_empty() {
return None;
}
let make = lens_make.map(tokens).unwrap_or_default();
let mut best: Option<(f64, &Profile)> = None;
for lens in &self.lenses {
for lens in self.lenses.iter().filter(|l| eligible(l)) {
let maker = tokens(&lens.maker);
if !make.is_empty() && !maker.is_empty() && !maker_matches(&maker, &make) {
continue;
+22
View File
@@ -265,3 +265,25 @@ fn spec_names_need_an_exact_profile_name() {
assert!(aliased.names_exactly(&spec_name([24.0, 70.0, 2.8, 2.8])));
assert!(!aliased.names_exactly("24-105mm f/4"));
}
/// Another maker's lens with the same specification scores as high as the aliased one; coming first in the
/// database it must not hide the profile named for the specification.
#[test]
fn a_named_profile_is_found_behind_a_tie() {
let lens = |maker: &str, models: &str| {
format!(
r#"<lens><maker>{maker}</maker>{models}<mount>Test Mount</mount><cropfactor>1</cropfactor>
<calibration><distortion model="poly3" focal="18" k1="-0.01" /></calibration></lens>"#
)
};
let xml = format!(
r#"<lensdatabase version="2">{}{}</lensdatabase>"#,
lens("Otherco", "<model>Otherco 18-55mm f/3.5-5.6</model>"),
lens("Testcam", "<model>Testcam 18-55mm f/3.5-5.6 VR</model><model>18-55mm f/3.5-5.6</model>"),
);
let db = LensDb::parse(&xml).unwrap();
let name = spec_name([18.0, 55.0, 3.5, 5.6]);
assert_eq!(db.find_lens(None, &name, None).unwrap().maker, "Otherco", "a plain lookup keeps the first of a tie");
assert_eq!(db.find_lens_named(None, &name, None).unwrap().maker, "Testcam");
assert!(db.find_lens_named(None, "70-200mm f/4", None).is_none());
}