Skip to content

Commit 03fd9d7

Browse files
kalwaltclaude
andcommitted
feat(kpm): facade-parity convenience accessors on VisualDatabase + FeatureStore slices (#148)
Mirror `VisualDatabaseFacade`'s Group-A accessors so the Rust port is a drop-in for the C++ facade surface, and callers don't have to loop `0..num_features()` element-by-element. Pure additive getters — no behavior change. - `FeatureStore`: add slice accessors `points() -> &[FeaturePoint]` and `descriptors() -> &[u8]` (counterparts to per-element `point(i)`/`descriptor(i)`). - `VisualDatabase`: add `get_feature_points(id)`, `get_descriptors(id)`, `get_query_feature_points()`, `get_query_descriptors()`, `get_width(id)`, `get_height(id)` (Option-returning, 1-liners over the slice accessors). - Tests for both (deterministic, no image pipeline). Group B (3D feature points — RustFreakMatcher/NFT domain) and Group C (computeFreakFeaturesAndDescriptors — no caller) are out of scope. Refs #148. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d511d00 commit 03fd9d7

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,22 @@ impl FeatureStore {
133133
pub fn bytes_per_feature(&self) -> usize {
134134
self.bytes_per_feature
135135
}
136+
137+
/// Returns all feature points as a slice.
138+
///
139+
/// Slice counterpart to [`point`](Self::point) — avoids forcing callers to
140+
/// loop `0..num_features()` element-by-element (facade parity, #148).
141+
pub fn points(&self) -> &[FeaturePoint] {
142+
&self.points
143+
}
144+
145+
/// Returns the flat descriptor buffer (`num_features() * bytes_per_feature()`
146+
/// bytes, descriptor `i` at `[i * bpf .. (i+1) * bpf]`).
147+
///
148+
/// Slice counterpart to [`descriptor`](Self::descriptor) (#148).
149+
pub fn descriptors(&self) -> &[u8] {
150+
&self.descriptors
151+
}
136152
}
137153

138154
// ============================================================================
@@ -583,6 +599,27 @@ mod tests {
583599
assert!(!s.point(2).maxima);
584600
}
585601

602+
#[test]
603+
fn test_feature_store_slice_accessors() {
604+
let mut s = FeatureStore::new(96).unwrap();
605+
let d0 = make_descriptor(0);
606+
let d1 = make_descriptor(1);
607+
s.add(fp(0.0, 0.0, true), &d0).unwrap();
608+
s.add(fp(1.0, 1.0, false), &d1).unwrap();
609+
610+
// points() slice agrees with per-element point(i).
611+
let pts = s.points();
612+
assert_eq!(pts.len(), 2);
613+
assert_eq!(pts[0].x, s.point(0).x);
614+
assert!(!pts[1].maxima);
615+
616+
// descriptors() flat buffer agrees with per-element descriptor(i).
617+
let descs = s.descriptors();
618+
assert_eq!(descs.len(), 2 * 96);
619+
assert_eq!(&descs[0..96], s.descriptor(0));
620+
assert_eq!(&descs[96..192], s.descriptor(1));
621+
}
622+
586623
#[test]
587624
fn test_feature_store_add_wrong_size() {
588625
let mut s = FeatureStore::new(96).unwrap();

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,54 @@ impl VisualDatabase {
667667
pub fn keyframe(&self, id: usize) -> Option<&Keyframe> {
668668
self.keyframes.get(&id)
669669
}
670+
671+
// ── Facade-parity convenience accessors (#148, Group A) ──────────────
672+
// Thin getters mirroring `VisualDatabaseFacade`, so callers don't have to
673+
// reach through `keyframe(id).store` and loop element-by-element.
674+
675+
/// Feature points of the reference keyframe stored under `id`.
676+
///
677+
/// C equivalent: `getFeaturePoints(id)`.
678+
pub fn get_feature_points(&self, id: usize) -> Option<&[FeaturePoint]> {
679+
self.keyframes.get(&id).map(|kf| kf.store.points())
680+
}
681+
682+
/// Flat descriptor buffer of the reference keyframe stored under `id`.
683+
///
684+
/// C equivalent: `getDescriptors(id)`.
685+
pub fn get_descriptors(&self, id: usize) -> Option<&[u8]> {
686+
self.keyframes.get(&id).map(|kf| kf.store.descriptors())
687+
}
688+
689+
/// Feature points of the most recent query frame (see [`query`](Self::query)).
690+
///
691+
/// C equivalent: `getQueryFeaturePoints()`.
692+
pub fn get_query_feature_points(&self) -> Option<&[FeaturePoint]> {
693+
self.query_keyframe.as_ref().map(|kf| kf.store.points())
694+
}
695+
696+
/// Flat descriptor buffer of the most recent query frame.
697+
///
698+
/// C equivalent: `getQueryDescriptors()`.
699+
pub fn get_query_descriptors(&self) -> Option<&[u8]> {
700+
self.query_keyframe
701+
.as_ref()
702+
.map(|kf| kf.store.descriptors())
703+
}
704+
705+
/// Width of the reference image stored under `id`.
706+
///
707+
/// C equivalent: `getWidth(id)`.
708+
pub fn get_width(&self, id: usize) -> Option<i32> {
709+
self.keyframes.get(&id).map(|kf| kf.width)
710+
}
711+
712+
/// Height of the reference image stored under `id`.
713+
///
714+
/// C equivalent: `getHeight(id)`.
715+
pub fn get_height(&self, id: usize) -> Option<i32> {
716+
self.keyframes.get(&id).map(|kf| kf.height)
717+
}
670718
}
671719

672720
impl Default for VisualDatabase {
@@ -1019,6 +1067,49 @@ mod tests {
10191067
assert!(db.keyframe(0).unwrap().index().is_some());
10201068
}
10211069

1070+
#[test]
1071+
fn test_facade_parity_accessors() {
1072+
// Build a tiny keyframe directly (no image pipeline) so the getters are
1073+
// exercised deterministically and fast (#148, Group A).
1074+
let mut kf = Keyframe::new(640, 480).unwrap();
1075+
let p0 = FeaturePoint {
1076+
x: 1.0,
1077+
y: 2.0,
1078+
angle: 0.0,
1079+
scale: 1.0,
1080+
maxima: true,
1081+
};
1082+
let p1 = FeaturePoint {
1083+
x: 3.0,
1084+
y: 4.0,
1085+
angle: 0.0,
1086+
scale: 1.0,
1087+
maxima: false,
1088+
};
1089+
kf.store.add(p0, &[7u8; 96]).unwrap();
1090+
kf.store.add(p1, &[9u8; 96]).unwrap();
1091+
1092+
let mut db = VisualDatabase::new().expect("new");
1093+
db.add_keyframe(kf, 0).expect("add_keyframe");
1094+
1095+
// Reference accessors for the stored id.
1096+
assert_eq!(db.get_width(0), Some(640));
1097+
assert_eq!(db.get_height(0), Some(480));
1098+
assert_eq!(db.get_feature_points(0).unwrap().len(), 2);
1099+
assert_eq!(db.get_feature_points(0).unwrap()[0].x, 1.0);
1100+
let descs = db.get_descriptors(0).unwrap();
1101+
assert_eq!(descs.len(), 2 * 96);
1102+
assert_eq!(&descs[0..96], &[7u8; 96]);
1103+
1104+
// Missing id → None.
1105+
assert!(db.get_feature_points(99).is_none());
1106+
assert!(db.get_width(99).is_none());
1107+
1108+
// No query run yet → query accessors are None.
1109+
assert!(db.get_query_feature_points().is_none());
1110+
assert!(db.get_query_descriptors().is_none());
1111+
}
1112+
10221113
// -----------------------------------------------------------------
10231114
// Dual-mode parity test (M9-1 gate per #140, closed by M9 #146)
10241115
// -----------------------------------------------------------------

0 commit comments

Comments
 (0)