Skip to content

Commit 2fcefa2

Browse files
kalwaltclaude
andauthored
test(kpm): raise M9 coverage — exclude examples + edge tests (#177) (#218)
* refactor(kpm): factor VisualDatabase::query matching loop + add query_from_keyframe (#147) Recover part of the C++ query onion. Step 1 (no public API change): extract the per-keyframe matching loop into `match_against_database` and the per-query reset into `reset_query_state`. Step 2: expose `query_from_keyframe(Keyframe)` — runs only the matching loop against the database (skips pyramid build + feature extraction), for deterministic testing and callers that already hold a `Keyframe`. Mirrors C++ `query(const keyframe_t*)`. `query(&Matrix<u8>)` is now a thin wrapper (build keyframe → match → stash), behaviorally identical (existing query tests unchanged + green). Skipped per YAGNI: `query_from_pyramid` — KPM uses GaussianScaleSpacePyramid while AR2 uses BoxFilterPyramid8u, so there is no possible cross-pipeline caller (documented in #147). Test: query_from_keyframe self-matches an identical keyframe. Closes #147. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(kpm): raise M9 coverage — exclude examples + edge tests (#177) - coverage: exclude `examples/**` from tarpaulin (the 30-line simple_nft_dual.rs diagnostic at 0% was the single biggest gap; examples are demos, not test code — the issue's preferred fix). - clustering: add a degenerate-input test (BHC build over all-identical descriptors → zero-distance k-medoids split + query). - visual_database: add a no-match test via query_from_keyframe (#147) — maximally-distant descriptors short-circuit try_match_one. Codecov on this PR confirms the resulting M9 module coverage; any residual per-file gaps (rust_backend DualFreakMatcher divergence, hough 1-liner) are quick follow-ups. Refs #177. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 5b6fbee commit 2fcefa2

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

.github/workflows/coverage.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
--output-dir coverage \
3434
--timeout 300 \
3535
--exclude-files "**/tests/*" \
36+
--exclude-files "**/examples/*" \
3637
--ignore-panics
3738
3839
- name: Upload coverage reports to Codecov

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,22 @@ mod tests {
876876
assert!(bhc.query(&feat).is_err());
877877
}
878878

879+
#[test]
880+
fn test_bhc_build_all_identical() {
881+
// Degenerate input: many identical descriptors. k-medoids distances are
882+
// all zero, so the split is degenerate — exercise that path (#177).
883+
let feat = [42u8; 96];
884+
let owned: Vec<[u8; 96]> = (0..20).map(|_| feat).collect();
885+
let features: Vec<&[u8; 96]> = owned.iter().collect();
886+
let mut bhc = BinaryHierarchicalClustering::new().unwrap();
887+
bhc.build(&features).expect("build over identical features");
888+
let res = bhc.query(&feat).expect("query");
889+
assert!(
890+
!res.is_empty(),
891+
"query of an identical feature must return the leaf members"
892+
);
893+
}
894+
879895
#[test]
880896
fn test_bhc_roundtrip() {
881897
let mut bhc = BinaryHierarchicalClustering::new().unwrap();

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,38 @@ mod tests {
11291129
);
11301130
}
11311131

1132+
#[test]
1133+
fn test_query_from_keyframe_no_match() {
1134+
// Deterministic, no image: a db keyframe with all-0x00 descriptors vs a
1135+
// query keyframe with all-0xFF descriptors — maximally distant, so the
1136+
// ratio test rejects every candidate and try_match_one short-circuits
1137+
// (no-match path, #177).
1138+
let mk = |desc: u8, n: usize| -> Keyframe {
1139+
let mut kf = Keyframe::new(640, 480).unwrap();
1140+
for i in 0..n {
1141+
let p = FeaturePoint {
1142+
x: i as f32,
1143+
y: i as f32,
1144+
angle: 0.0,
1145+
scale: 1.0,
1146+
maxima: true,
1147+
};
1148+
kf.store.add(p, &[desc; 96]).unwrap();
1149+
}
1150+
kf
1151+
};
1152+
1153+
let mut db = VisualDatabase::new().expect("new");
1154+
db.add_keyframe(mk(0x00, 12), 0).expect("add_keyframe");
1155+
1156+
let matched = db
1157+
.query_from_keyframe(mk(0xFF, 12))
1158+
.expect("query_from_keyframe");
1159+
assert!(!matched, "maximally-distant descriptors must not match");
1160+
assert_eq!(db.matched_db_id(), -1);
1161+
assert!(db.inliers().is_empty());
1162+
}
1163+
11321164
#[test]
11331165
fn test_facade_parity_accessors() {
11341166
// Build a tiny keyframe directly (no image pipeline) so the getters are

0 commit comments

Comments
 (0)