LR-CROP-AUTO: the analysis cache tells lenses apart

From CodeRabbit's review of #2809: the analysis cache was keyed by the source pixels and the
optics and orientation settings, not by `info.lens`, which the analysis image is corrected for.
Auto straighten reads the horizon from a fresh image and the lines from the cache, so a hit for
another lens mixed two frames. `analysis_key` now includes the lens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ingolfteetz
2026-10-25 11:29:16 +01:00
co-authored by Claude Opus 5.5
parent 583deac156
commit 89391f03fc
2 changed files with 28 additions and 2 deletions
+16
View File
@@ -597,3 +597,19 @@ fn auto_straighten_takes_the_roll_from_converging_verticals() {
let deg = auto_level(&line_drawing(&lines)).expect("the vanishing point");
assert!((deg + 3.0).abs() < 0.3, "{deg}");
}
/// The cached analysis tells lenses apart: the analysis image is corrected for `info.lens`, and
/// Auto straighten reads the horizon from a fresh image and the lines from the cache, so a hit
/// for another lens would mix two frames (review of #2809).
#[test]
fn the_analysis_cache_tells_lenses_apart() {
let src = tilted_scene(2.0, 30.0);
let s = DevelopSettings::default();
let plain = SourceInfo::default();
let vignette = lightcraft_develop::EmbeddedVignette::default();
let lens =
SourceInfo { lens: Some(lightcraft_develop::EmbeddedLens { vignette: Some(vignette), ..Default::default() }), ..SourceInfo::default() };
let key = crate::upright::analysis_key;
assert_eq!(key(&src, &plain, &s), key(&src, &plain.clone(), &s), "the same source, settings and lens");
assert_ne!(key(&src, &plain, &s), key(&src, &lens, &s), "another lens");
}
+12 -2
View File
@@ -553,8 +553,7 @@ pub fn analyze_source(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> A
/// cache miss instead of building it again.
fn analyze_cached(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings, plane: Option<&Plane>) -> Analysis {
static CACHE: Mutex<Vec<(u64, Analysis)>> = Mutex::new(Vec::new());
let key = crate::optics::fingerprint(src)
^ lightcraft_develop::DevelopSettings { optics: s.optics, orientation: s.orientation, ..Default::default() }.hash64().rotate_left(17);
let key = analysis_key(src, info, s);
if let Ok(c) = CACHE.lock()
&& let Some((_, a)) = c.iter().find(|(k, _)| *k == key)
{
@@ -573,6 +572,17 @@ fn analyze_cached(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings, plane: O
a
}
/// What an analysis depends on: the source's pixels, the optics and orientation settings, and the
/// lens the frame is corrected for (`info.lens`, which [`analysis_plane`] reads through the frame).
pub(crate) fn analysis_key(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> u64 {
use std::hash::{Hash, Hasher};
let mut lens = std::collections::hash_map::DefaultHasher::new();
format!("{:?}", info.lens).hash(&mut lens);
crate::optics::fingerprint(src)
^ lightcraft_develop::DevelopSettings { optics: s.optics, orientation: s.orientation, ..Default::default() }.hash64().rotate_left(17)
^ lens.finish().rotate_left(29)
}
/// The image an analysis reads: the lens-corrected source (optics applied, no perspective or crop)
/// at [`ANALYSIS_LONG`] px, as perceptual lightness normalized to its bright end.
pub(crate) fn analysis_plane(src: &Rgb32f, info: &SourceInfo, s: &DevelopSettings) -> Plane {