// Public-input validation and compact snapshots for integration tests and
// application telemetry. No file or network I/O is hidden in these helpers.
///|
pub(all) struct FeatureSnapshot {
keypoints : Int
descriptors : Int
descriptor_bytes : Int
mean_response : Double
bounds : Rect
fingerprint : Int
}
///|
pub(all) struct SnapshotDelta {
keypoints : Int
descriptors : Int
descriptor_bytes : Int
response_delta : Double
same_shape : Bool
}
///|
pub fn snapshot_features(
keypoints : Array[Keypoint],
descriptors : Array[Descriptor],
) -> FeatureSnapshot {
let keypoint_summary = summarize_keypoints(keypoints)
let descriptor_summary = summarize_descriptors(descriptors)
let mut fingerprint = 29
fingerprint = (fingerprint * 31 + keypoints.length()) & 0x7FFFFFFF
fingerprint = (fingerprint * 31 + descriptors.length()) & 0x7FFFFFFF
let mut i = 0
while i < descriptors.length() {
fingerprint = (fingerprint * 31 + descriptor_checksum(descriptors[i])) &
0x7FFFFFFF
i = i + 1
}
{
keypoints: keypoints.length(),
descriptors: descriptors.length(),
descriptor_bytes: descriptor_summary.bytes,
mean_response: keypoint_summary.mean_response,
bounds: keypoint_summary.bounds,
fingerprint,
}
}
///|
pub fn compare_snapshots(
left : FeatureSnapshot,
right : FeatureSnapshot,
) -> SnapshotDelta {
{
keypoints: right.keypoints - left.keypoints,
descriptors: right.descriptors - left.descriptors,
descriptor_bytes: right.descriptor_bytes - left.descriptor_bytes,
response_delta: right.mean_response - left.mean_response,
same_shape: left.keypoints == right.keypoints &&
left.descriptors == right.descriptors,
}
}
///|
pub fn format_feature_snapshot(snapshot : FeatureSnapshot) -> String {
"keypoints=\{snapshot.keypoints} descriptors=\{snapshot.descriptors} bytes=\{snapshot.descriptor_bytes} mean_response=\{snapshot.mean_response} fingerprint=\{snapshot.fingerprint}"
}
///|
pub fn validate_image_shape(img : GrayImage) -> Bool {
img.width >= 0 &&
img.height >= 0 &&
img.data.length() == img.width * img.height
}
///|
pub fn validate_image_bytes(img : GrayImage) -> Bool {
let mut i = 0
while i < img.data.length() {
if img.data[i] < 0 || img.data[i] > 255 {
return false
}
i = i + 1
}
true
}
///|
pub fn validate_keypoints(
keypoints : Array[Keypoint],
width : Int,
height : Int,
) -> Bool {
let mut i = 0
while i < keypoints.length() {
if keypoints[i].x < 0 ||
keypoints[i].x >= width ||
keypoints[i].y < 0 ||
keypoints[i].y >= height ||
keypoints[i].size < 0 {
return false
}
i = i + 1
}
true
}
///|
pub fn validate_descriptor_lengths(descriptors : Array[Descriptor]) -> Bool {
if descriptors.length() < 2 {
return true
}
let length = descriptors[0].data.length()
let mut i = 1
while i < descriptors.length() {
if descriptors[i].data.length() != length {
return false
}
i = i + 1
}
true
}
///|
pub fn validate_pipeline_result(result : PipelineResult) -> Bool {
result.keypoints1.length() == result.descriptors1.length() &&
result.keypoints2.length() == result.descriptors2.length() &&
validate_match_indices(
result.matches,
result.descriptors1.length(),
result.descriptors2.length(),
) &&
validate_descriptor_lengths(result.descriptors1) &&
validate_descriptor_lengths(result.descriptors2)
}
///|
pub fn pipeline_invariant_report(result : PipelineResult) -> Array[String] {
let issues : Array[String] = []
if result.keypoints1.length() != result.descriptors1.length() {
issues.push("image1 feature/descriptor count mismatch")
}
if result.keypoints2.length() != result.descriptors2.length() {
issues.push("image2 feature/descriptor count mismatch")
}
if !validate_match_indices(
result.matches,
result.descriptors1.length(),
result.descriptors2.length(),
) {
issues.push("match index out of range")
}
if !validate_descriptor_lengths(result.descriptors1) {
issues.push("image1 descriptor lengths differ")
}
if !validate_descriptor_lengths(result.descriptors2) {
issues.push("image2 descriptor lengths differ")
}
if result.homography.inlier_ratio < 0.0 ||
result.homography.inlier_ratio > 1.0 {
issues.push("homography ratio outside [0,1]")
}
issues
}
///|
pub fn format_snapshot_delta(delta : SnapshotDelta) -> String {
"delta_keypoints=\{delta.keypoints} delta_descriptors=\{delta.descriptors} delta_bytes=\{delta.descriptor_bytes} delta_response=\{delta.response_delta} same_shape=\{delta.same_shape}"
}
///|
pub fn safe_match_ratio(matches : Array[Match], query_count : Int) -> Double {
if query_count <= 0 {
0.0
} else {
let ratio = Double::from_int(matches.length()) /
Double::from_int(query_count)
if ratio > 1.0 {
1.0
} else if ratio < 0.0 {
0.0
} else {
ratio
}
}
}
///|
pub fn safe_inlier_ratio(inliers : Int, matches : Int) -> Double {
if matches <= 0 || inliers <= 0 {
0.0
} else {
let ratio = Double::from_int(inliers) / Double::from_int(matches)
if ratio > 1.0 {
1.0
} else {
ratio
}
}
}
///|
pub fn feature_count_difference(
left : Array[Keypoint],
right : Array[Keypoint],
) -> Int {
left.length() - right.length()
}
///|
pub fn descriptor_count_difference(
left : Array[Descriptor],
right : Array[Descriptor],
) -> Int {
left.length() - right.length()
}
///|
pub fn stable_descriptor_bytes(descriptors : Array[Descriptor]) -> Int {
let mut bytes = 0
let mut i = 0
while i < descriptors.length() {
if descriptors[i].data.length() > bytes {
bytes = descriptors[i].data.length()
}
i = i + 1
}
bytes
}
///|
pub fn result_fingerprint(result : PipelineResult) -> Int {
let snapshot1 = snapshot_features(result.keypoints1, result.descriptors1)
let snapshot2 = snapshot_features(result.keypoints2, result.descriptors2)
let mut fingerprint = snapshot1.fingerprint
fingerprint = (fingerprint * 31 + snapshot2.fingerprint) & 0x7FFFFFFF
fingerprint = (fingerprint * 31 + result.matches.length()) & 0x7FFFFFFF
fingerprint = (fingerprint * 31 + result.homography.inliers.length()) &
0x7FFFFFFF
fingerprint
}
///|
pub fn results_have_same_shape(
left : PipelineResult,
right : PipelineResult,
) -> Bool {
left.keypoints1.length() == right.keypoints1.length() &&
left.keypoints2.length() == right.keypoints2.length() &&
left.descriptors1.length() == right.descriptors1.length() &&
left.descriptors2.length() == right.descriptors2.length() &&
left.matches.length() == right.matches.length()
}
///|
pub fn result_delta_summary(
left : PipelineResult,
right : PipelineResult,
) -> String {
let first = snapshot_features(left.keypoints1, left.descriptors1)
let second = snapshot_features(right.keypoints1, right.descriptors1)
format_snapshot_delta(compare_snapshots(first, second))
}
///|
pub fn format_rect(rect : Rect) -> String {
"rect=\{rect.x0},\{rect.y0},\{rect.x1},\{rect.y1}"
}
///|
pub fn format_point(point : FloatPoint2) -> String {
"point=\{point.x},\{point.y}"
}
///|
pub fn format_keypoint(keypoint : Keypoint) -> String {
"keypoint=\{keypoint.x},\{keypoint.y} response=\{keypoint.response} angle=\{keypoint.angle} octave=\{keypoint.octave} size=\{keypoint.size}"
}
///|
pub fn validate_regions(
regions : Array[RegionStats],
width : Int,
height : Int,
) -> Bool {
let mut i = 0
while i < regions.length() {
let region = regions[i]
if region.label <= 0 ||
region.area <= 0 ||
region.bounds.x0 < 0 ||
region.bounds.y0 < 0 ||
region.bounds.x1 > width ||
region.bounds.y1 > height ||
region.bounds.x1 <= region.bounds.x0 ||
region.bounds.y1 <= region.bounds.y0 {
return false
}
i = i + 1
}
true
}
///|
pub fn validate_tracks(tracks : Array[FeatureTrack]) -> Bool {
let mut i = 0
while i < tracks.length() {
if tracks[i].id < 0 ||
tracks[i].age < 0 ||
tracks[i].missed < 0 ||
tracks[i].keypoints.length() != tracks[i].descriptors.length() {
return false
}
i = i + 1
}
true
}
///|
pub fn report_is_empty(report : PipelineReport) -> Bool {
report.features1.count == 0 &&
report.features2.count == 0 &&
report.matches.match_count == 0
}
///|
pub fn dashboard_is_valid(dashboard : QualityDashboard) -> Bool {
dashboard.features1.count >= 0 &&
dashboard.features2.count >= 0 &&
dashboard.descriptors1.count >= 0 &&
dashboard.descriptors2.count >= 0 &&
dashboard.match_graph.match_count >= 0 &&
dashboard.gate.inlier_ratio >= 0.0 &&
dashboard.gate.inlier_ratio <= 1.0
}
///|
pub fn ratio_difference(left : Double, right : Double) -> Double {
if left > right {
left - right
} else {
right - left
}
}
///|
pub fn counts_are_nonnegative(values : Array[Int]) -> Bool {
let mut i = 0
while i < values.length() {
if values[i] < 0 {
return false
}
i = i + 1
}
true
}
///|
pub fn bounds_are_ordered(rect : Rect) -> Bool {
rect.x1 >= rect.x0 && rect.y1 >= rect.y0
}
///|
pub fn image_area(img : GrayImage) -> Int {
if img.width <= 0 || img.height <= 0 {
0
} else {
img.width * img.height
}
}
///|
pub fn image_has_pixels(img : GrayImage) -> Bool {
image_area(img) > 0 && img.data.length() > 0
}