Skip to content

Commit 5d0c765

Browse files
kalwaltclaude
andcommitted
test(kpm): add dual-mode test for DoG detector with find_orientation=true
The existing `test_dog_keypoints_match_cpp_count` uses `find_orientation = false`, which means `detect()` skips the OrientationAssignment step entirely. The test was therefore insensitive to changes in the orientation gradient histogram, the SMOOTH_KERNEL constant, or the parabolic peak refinement — anything in `orientation.rs` could regress silently. Add a companion test `test_dog_keypoints_match_cpp_count_with_orientation` that runs the same image (`found.jpg`, 3 octaves) with `find_orientation = true`. This exercises every step of the detector pipeline including the orientation assignment that the M8-2 SMOOTH_KERNEL fidelity fix touched. Empirical local result on Windows MSVC (clean rebuild): |diff| = 0 on both variants. The Rust port matches C++ keypoint counts EXACTLY both with and without orientation, contrary to the speculative concern that the 1-ULP kernel literal change might shift peak detection at the 0.8 · max_height threshold boundary. Tolerances set to allow cross-platform FP variance: - orientation-off: 5 (unchanged) - orientation-on: 10 (orientation step adds more FP ops; can be tightened if CI shows consistent agreement) Doc-test comments record the empirical 0-divergence baseline so future regressions are easy to spot. refs #125 #128 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent bc1c7fa commit 5d0c765

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

crates/core/src/kpm/freak/detector.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,11 @@ mod tests {
11961196
fn test_dog_keypoints_match_cpp_count() {
11971197
// Tolerance covers sort tie-breaking and bucket ordering variance
11981198
// only. Any algorithm-level error would dwarf this.
1199+
//
1200+
// Empirical local result (Windows MSVC, clean rebuild): |diff| = 0.
1201+
// The tolerance is kept at 5 to absorb potential cross-platform
1202+
// FP variance (Linux GCC / macOS Apple clang) before tightening
1203+
// to exact match in a follow-up if CI shows consistent equality.
11991204
const MAX_TIE_DIVERGENCE: i32 = 5;
12001205

12011206
let img = load_grayscale("../../benchmarks/data/found.jpg");
@@ -1229,4 +1234,66 @@ mod tests {
12291234
"keypoint count divergence: rust={rust_count}, cpp={cpp_count}, |diff|={diff} > {MAX_TIE_DIVERGENCE}"
12301235
);
12311236
}
1237+
1238+
#[test]
1239+
#[cfg(feature = "dual-mode")]
1240+
fn test_dog_keypoints_match_cpp_count_with_orientation() {
1241+
// Companion to `test_dog_keypoints_match_cpp_count` with
1242+
// `find_orientation = true`, exercising the OrientationAssignment
1243+
// gradient-histogram step.
1244+
//
1245+
// Why the tolerance is wider (10 vs 5):
1246+
// 1. Each keypoint can produce *multiple* DoGFeaturePoint copies
1247+
// (one per dominant orientation peak >= 0.8 · max_height).
1248+
// Tie-breaking at the peak threshold boundary multiplies any
1249+
// f32 rounding variance.
1250+
// 2. The orientation smoothing kernel
1251+
// `[0.274068619061197, 0.451862761877606, ...]` sums to
1252+
// exactly 1.0 in f64 but its f32 representation may differ
1253+
// by 1 ULP between platforms. Five smoothing iterations
1254+
// compound that variance.
1255+
// 3. Bucket pruning runs before orientation in C++, so the
1256+
// pre-orientation count is bounded by max_pts. Post-
1257+
// orientation, the count can exceed max_pts (each keypoint
1258+
// contributing 1..N orientations).
1259+
//
1260+
// Empirical local result (Windows MSVC, clean rebuild): |diff| = 0.
1261+
// The orientation step did NOT introduce any divergence on this
1262+
// image, contrary to the speculative concern that the 1-ULP
1263+
// SMOOTH_KERNEL change might shift peak detection. The tolerance
1264+
// is set to 10 (vs 5 for orientation-off) to absorb potential
1265+
// cross-platform variance in the orientation gradient histogram.
1266+
const MAX_TIE_DIVERGENCE: i32 = 10;
1267+
1268+
let img = load_grayscale("../../benchmarks/data/found.jpg");
1269+
let num_octaves = 3;
1270+
let laplacian_threshold = 0.0;
1271+
let edge_threshold = 10.0;
1272+
let max_pts = 5000;
1273+
let find_orientation = true;
1274+
1275+
let pyr = build_test_pyramid(&img, num_octaves);
1276+
let det = DoGScaleInvariantDetector::new(
1277+
laplacian_threshold,
1278+
edge_threshold,
1279+
max_pts,
1280+
find_orientation,
1281+
);
1282+
let rust_count = det.detect(&pyr).len() as i32;
1283+
1284+
let cpp_count = cpp_detect_count(
1285+
&img,
1286+
num_octaves,
1287+
laplacian_threshold,
1288+
edge_threshold,
1289+
max_pts,
1290+
find_orientation,
1291+
);
1292+
1293+
let diff = (rust_count - cpp_count).abs();
1294+
assert!(
1295+
diff <= MAX_TIE_DIVERGENCE,
1296+
"with-orientation keypoint count divergence: rust={rust_count}, cpp={cpp_count}, |diff|={diff} > {MAX_TIE_DIVERGENCE}"
1297+
);
1298+
}
12321299
}

0 commit comments

Comments
 (0)