use image::{DynamicImage, GenericImageView, GrayImage, Luma}; use sift::keypoints::KeyPoint;
use sift::sift::{load_image_dyn, Sift, DEFAULT_NUM_INTERVALS};
use std::path::Path;
const EXPECTED_DESCRIPTOR_LEN: usize = 128;
const TEST_IMAGE_PATH: &str = "data/1.jpg";
fn check_keypoint_properties(keypoints: &[KeyPoint], img_width: u32, img_height: u32) {
let img_width_f = img_width as f32;
let img_height_f = img_height as f32;
for (i, kp) in keypoints.iter().enumerate() {
assert!(
kp.x >= -1.0 && kp.x <= img_width_f + 1.0,
"KP[{}]: x coordinate out of bounds: {}",
i,
kp.x
);
assert!(
kp.y >= -1.0 && kp.y <= img_height_f + 1.0,
"KP[{}]: y coordinate out of bounds: {}",
i,
kp.y
);
assert!(
kp.size > 0.0,
"KP[{}]: size (sigma) is not positive: {}",
i,
kp.size
);
assert!(
kp.angle >= -std::f32::consts::PI && kp.angle <= std::f32::consts::PI,
"KP[{}]: angle out of bounds: {}",
i,
kp.angle
);
assert!(
kp.octave >= 0,
"KP[{}]: octave is negative: {}",
i,
kp.octave
);
assert!(
kp.layer >= -2 && kp.layer <= (DEFAULT_NUM_INTERVALS as i32 + 4),
"KP[{}]: layer is out of reasonable range: {}",
i,
kp.layer
);
}
}
#[test]
fn test_sift_on_real_image() {
let image_path = Path::new(TEST_IMAGE_PATH);
assert!(
image_path.exists(),
"Test image file not found at '{}'",
TEST_IMAGE_PATH
);
let img_dyn = load_image_dyn(TEST_IMAGE_PATH).expect("Failed to load test image.");
let (width, height) = img_dyn.dimensions();
let sift = Sift::default();
let (keypoints, descriptors) = sift.detect_and_compute(&img_dyn);
assert!(
!keypoints.is_empty(),
"No keypoints found on {}",
TEST_IMAGE_PATH
);
assert!(
!descriptors.is_empty(),
"No descriptors computed for {}",
TEST_IMAGE_PATH
);
assert_eq!(keypoints.len(), descriptors.len(), "KP/Desc count mismatch");
println!("Found {} keypoints on {}", keypoints.len(), TEST_IMAGE_PATH);
for (i, desc) in descriptors.iter().enumerate() {
assert_eq!(
desc.len(),
EXPECTED_DESCRIPTOR_LEN,
"Desc[{}] length incorrect",
i
);
}
check_keypoint_properties(&keypoints, width, height);
}
#[test]
fn test_sift_determinism() {
let image_path = Path::new(TEST_IMAGE_PATH);
assert!(
image_path.exists(),
"Test image file not found at '{}'",
TEST_IMAGE_PATH
);
let img_dyn = load_image_dyn(TEST_IMAGE_PATH).expect("Failed to load test image.");
let sift = Sift::default();
let (keypoints1, descriptors1) = sift.detect_and_compute(&img_dyn);
let (keypoints2, descriptors2) = sift.detect_and_compute(&img_dyn);
assert_eq!(
keypoints1.len(),
keypoints2.len(),
"Keypoint counts differ between runs."
);
assert_eq!(
descriptors1.len(),
descriptors2.len(),
"Descriptor counts differ between runs."
);
for i in 0..keypoints1.len() {
assert_eq!(
keypoints1[i], keypoints2[i],
"Keypoint at index {} differs between runs.",
i
);
assert_eq!(
descriptors1[i].len(),
descriptors2[i].len(),
"Descriptor[{}] lengths differ",
i
);
for j in 0..descriptors1[i].len() {
assert!(
(descriptors1[i][j] - descriptors2[i][j]).abs() < 1e-6,
"Descriptor[{}][{}] differs: {} vs {}",
i,
j,
descriptors1[i][j],
descriptors2[i][j]
);
}
}
println!("SIFT results are deterministic on {}", TEST_IMAGE_PATH);
}
#[test]
fn test_sift_on_small_image() {
let width = 20;
let height = 15;
let small_gray_img = GrayImage::from_fn(width, height, |x, _| Luma([(x * 10) as u8]));
let img_dyn = DynamicImage::ImageLuma8(small_gray_img);
let sift = Sift::default();
let (keypoints, descriptors) = sift.detect_and_compute(&img_dyn);
if !keypoints.is_empty() {
println!("Found {} keypoints on small image.", keypoints.len());
assert_eq!(
keypoints.len(),
descriptors.len(),
"KP/Desc count mismatch on small image"
);
for (i, desc) in descriptors.iter().enumerate() {
assert_eq!(
desc.len(),
EXPECTED_DESCRIPTOR_LEN,
"Desc[{}] length incorrect on small image",
i
);
}
check_keypoint_properties(&keypoints, width, height);
} else {
println!("No keypoints found on small image (which might be expected).");
assert!(
descriptors.is_empty(),
"Found descriptors without keypoints on small image"
);
}
}
#[test]
fn test_sift_on_no_feature_image() {
let width = 100;
let height = 100;
let uniform_gray: GrayImage = GrayImage::from_pixel(width, height, Luma([128u8]));
let img_dyn = DynamicImage::ImageLuma8(uniform_gray);
let sift = Sift::default();
let (keypoints, descriptors) = sift.detect_and_compute(&img_dyn);
assert!(
keypoints.is_empty(),
"Keypoints found on a uniform color image."
);
assert!(
descriptors.is_empty(),
"Descriptors found on a uniform color image."
);
println!("Correctly found no keypoints on uniform image.");
}
#[test]
fn test_sift_high_contrast_threshold() {
let img_dyn = load_image_dyn(TEST_IMAGE_PATH).expect("Failed to load test image.");
let sift_default = Sift::default();
let (keypoints_default, _) = sift_default.detect_and_compute(&img_dyn);
let default_count = keypoints_default.len();
assert!(
default_count > 0,
"Default SIFT found no keypoints, cannot test high threshold effect."
);
let high_contrast_threshold = 1.0; let sift_high_thresh = Sift::new(
sift_default.sigma, sift_default.num_octaves, sift_default.num_intervals, sift_default.assumed_blur, high_contrast_threshold, sift_default.edge_threshold, );
let (keypoints_high, descriptors_high) = sift_high_thresh.detect_and_compute(&img_dyn);
let high_thresh_count = keypoints_high.len();
println!(
"Default threshold found {} keypoints, High threshold found {}.",
default_count, high_thresh_count
);
assert!(
high_thresh_count < default_count / 2 || high_thresh_count == 0,
"High contrast threshold did not significantly reduce keypoint count ({} vs default {}).",
high_thresh_count,
default_count
);
assert_eq!(
keypoints_high.len(),
descriptors_high.len(),
"KP/Desc count mismatch (high thresh)"
);
if !keypoints_high.is_empty() {
let (width, height) = img_dyn.dimensions();
check_keypoint_properties(&keypoints_high, width, height);
for desc in descriptors_high {
assert_eq!(
desc.len(),
EXPECTED_DESCRIPTOR_LEN,
"Desc length incorrect (high thresh)"
);
}
}
}