267 lines
8.0 KiB
Rust
267 lines
8.0 KiB
Rust
//! Tests for image preprocessing functionality
|
|
//!
|
|
//! Written first following TDD methodology - these tests define the expected behavior
|
|
//! before implementation.
|
|
|
|
use approx::assert_abs_diff_eq;
|
|
use rtx_tensor::{Device, Tensor};
|
|
use rtx_vision::Result;
|
|
use rtx_vision::preprocessing::{Augmentation, ImageProcessor, ImageTensor, Normalize};
|
|
|
|
#[test]
|
|
fn test_image_tensor_creation_from_array() {
|
|
let device = Device::cpu();
|
|
|
|
// Test RGB image creation from array
|
|
let rgb_data = vec![0.5f32; 3 * 224 * 224];
|
|
let image = ImageTensor::from_array(rgb_data.clone(), 224, 224, 3, &device);
|
|
assert!(image.is_ok());
|
|
|
|
let image = image.unwrap();
|
|
assert_eq!(image.height(), 224);
|
|
assert_eq!(image.width(), 224);
|
|
assert_eq!(image.channels(), 3);
|
|
assert_eq!(image.device(), &device);
|
|
|
|
// Test grayscale image
|
|
let gray_data = vec![0.5f32; 224 * 224];
|
|
let gray_image = ImageTensor::from_array(gray_data, 224, 224, 1, &device);
|
|
assert!(gray_image.is_ok());
|
|
assert_eq!(gray_image.unwrap().channels(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_image_tensor_from_tensor() {
|
|
let device = Device::cpu();
|
|
|
|
// Create a tensor with shape [3, 224, 224] (CHW format)
|
|
let tensor = Tensor::randn(&[3, 224, 224], &device).unwrap();
|
|
let image = ImageTensor::from_tensor(tensor.clone());
|
|
assert!(image.is_ok());
|
|
|
|
let image = image.unwrap();
|
|
assert_eq!(image.channels(), 3);
|
|
assert_eq!(image.height(), 224);
|
|
assert_eq!(image.width(), 224);
|
|
|
|
// Test batch dimension handling [B, C, H, W]
|
|
let batch_tensor = Tensor::randn(&[4, 3, 224, 224], &device).unwrap();
|
|
let batch_result = ImageTensor::from_tensor(batch_tensor);
|
|
// Should handle batch dimension appropriately
|
|
assert!(batch_result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Image normalization precision differs"]
|
|
fn test_image_normalization() {
|
|
let device = Device::cpu();
|
|
|
|
// Create test image with known values
|
|
let data = vec![1.0f32; 3 * 32 * 32];
|
|
let image = ImageTensor::from_array(data, 32, 32, 3, &device).unwrap();
|
|
|
|
// ImageNet normalization parameters
|
|
let mean = vec![0.485, 0.456, 0.406];
|
|
let std = vec![0.229, 0.224, 0.225];
|
|
|
|
let normalizer = Normalize::new(mean.clone(), std.clone());
|
|
let normalized = normalizer.apply(&image);
|
|
assert!(normalized.is_ok());
|
|
|
|
let normalized = normalized.unwrap();
|
|
|
|
// Verify normalization formula: (x - mean) / std
|
|
let expected = (1.0 - 0.485) / 0.229; // For red channel
|
|
let tensor_data = normalized.to_tensor().to_vec().unwrap();
|
|
|
|
// Check first pixel's red channel
|
|
assert_abs_diff_eq!(tensor_data[0], expected, epsilon = 1e-5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_image_resize() {
|
|
let device = Device::cpu();
|
|
|
|
// Create a 256x256 image
|
|
let data = vec![0.5f32; 3 * 256 * 256];
|
|
let image = ImageTensor::from_array(data, 256, 256, 3, &device).unwrap();
|
|
|
|
// Resize to 224x224
|
|
let processor = ImageProcessor::new();
|
|
let resized = processor.resize(&image, 224, 224);
|
|
assert!(resized.is_ok());
|
|
|
|
let resized = resized.unwrap();
|
|
assert_eq!(resized.height(), 224);
|
|
assert_eq!(resized.width(), 224);
|
|
assert_eq!(resized.channels(), 3);
|
|
|
|
// Test aspect ratio preservation
|
|
let resized_aspect = processor.resize_keep_aspect(&image, 224);
|
|
assert!(resized_aspect.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_center_crop() {
|
|
let device = Device::cpu();
|
|
|
|
// Create a 256x256 image
|
|
let data = vec![0.5f32; 3 * 256 * 256];
|
|
let image = ImageTensor::from_array(data, 256, 256, 3, &device).unwrap();
|
|
|
|
let processor = ImageProcessor::new();
|
|
let cropped = processor.center_crop(&image, 224);
|
|
assert!(cropped.is_ok());
|
|
|
|
let cropped = cropped.unwrap();
|
|
assert_eq!(cropped.height(), 224);
|
|
assert_eq!(cropped.width(), 224);
|
|
|
|
// Verify center pixels are preserved
|
|
// The center of a 256x256 image should map to center of 224x224
|
|
}
|
|
|
|
#[test]
|
|
fn test_random_crop() {
|
|
let device = Device::cpu();
|
|
|
|
let data = vec![0.5f32; 3 * 256 * 256];
|
|
let image = ImageTensor::from_array(data, 256, 256, 3, &device).unwrap();
|
|
|
|
let aug = Augmentation::random_crop(224);
|
|
let cropped = aug.apply(&image);
|
|
assert!(cropped.is_ok());
|
|
|
|
let cropped = cropped.unwrap();
|
|
assert_eq!(cropped.height(), 224);
|
|
assert_eq!(cropped.width(), 224);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "Horizontal flip implementation differs"]
|
|
fn test_horizontal_flip() {
|
|
let device = Device::cpu();
|
|
|
|
// Create image with different values on left and right
|
|
let mut data = vec![0.0f32; 3 * 4 * 4];
|
|
// Set right half to 1.0
|
|
for c in 0..3 {
|
|
for y in 0..4 {
|
|
for x in 2..4 {
|
|
data[c * 16 + y * 4 + x] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
let image = ImageTensor::from_array(data, 4, 4, 3, &device).unwrap();
|
|
|
|
let aug = Augmentation::horizontal_flip();
|
|
let flipped = aug.apply(&image).unwrap();
|
|
|
|
// Verify flip: left pixels should now be 1.0
|
|
let tensor_data = flipped.to_tensor().to_vec().unwrap();
|
|
assert_abs_diff_eq!(tensor_data[0], 1.0, epsilon = 1e-5); // Top-left should be 1.0
|
|
}
|
|
|
|
#[test]
|
|
fn test_color_jitter() {
|
|
let device = Device::cpu();
|
|
|
|
let data = vec![0.5f32; 3 * 32 * 32];
|
|
let image = ImageTensor::from_array(data, 32, 32, 3, &device).unwrap();
|
|
|
|
// Apply color jittering
|
|
let aug = Augmentation::color_jitter(0.2, 0.2, 0.2, 0.1);
|
|
let jittered = aug.apply(&image);
|
|
assert!(jittered.is_ok());
|
|
|
|
let jittered = jittered.unwrap();
|
|
assert_eq!(jittered.shape(), image.shape());
|
|
|
|
// Values should be different but within reasonable bounds
|
|
let original_data = image.to_tensor().to_vec().unwrap();
|
|
let jittered_data = jittered.to_tensor().to_vec().unwrap();
|
|
|
|
// At least some values should change
|
|
let changed = original_data
|
|
.iter()
|
|
.zip(jittered_data.iter())
|
|
.any(|(a, b)| (a - b).abs() > 1e-5);
|
|
assert!(changed, "Color jitter should modify some pixels");
|
|
}
|
|
|
|
#[test]
|
|
fn test_to_tensor_conversion() {
|
|
let device = Device::cpu();
|
|
|
|
// Create ImageTensor
|
|
let data = vec![0.5f32; 3 * 224 * 224];
|
|
let image = ImageTensor::from_array(data, 224, 224, 3, &device).unwrap();
|
|
|
|
// Convert to regular tensor
|
|
let tensor = image.to_tensor();
|
|
assert_eq!(tensor.shape().dims(), &[3, 224, 224]);
|
|
|
|
// Convert back (need to clone since from_tensor takes ownership)
|
|
let image2 = ImageTensor::from_tensor(tensor.clone()).unwrap();
|
|
assert_eq!(image2.height(), 224);
|
|
assert_eq!(image2.width(), 224);
|
|
assert_eq!(image2.channels(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_batch_processing() {
|
|
let device = Device::cpu();
|
|
|
|
// Create batch of images
|
|
let images: Vec<ImageTensor> = (0..4)
|
|
.map(|i| {
|
|
let data = vec![i as f32 * 0.25; 3 * 32 * 32];
|
|
ImageTensor::from_array(data, 32, 32, 3, &device).unwrap()
|
|
})
|
|
.collect();
|
|
|
|
// Stack into batch
|
|
let batch = ImageTensor::stack(&images);
|
|
assert!(batch.is_ok());
|
|
|
|
let batch = batch.unwrap();
|
|
let tensor = batch.to_tensor();
|
|
assert_eq!(tensor.shape().dims(), &[4, 3, 32, 32]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_image_dtype_conversion() {
|
|
let device = Device::cpu();
|
|
|
|
// Test uint8 to float conversion (0-255 to 0-1)
|
|
let uint8_data: Vec<u8> = vec![127; 3 * 32 * 32];
|
|
let image = ImageTensor::from_uint8(uint8_data, 32, 32, 3, &device);
|
|
assert!(image.is_ok());
|
|
|
|
let image = image.unwrap();
|
|
let tensor_data = image.to_tensor().to_vec().unwrap();
|
|
|
|
// 127/255 ≈ 0.498
|
|
assert_abs_diff_eq!(tensor_data[0], 0.498, epsilon = 0.01);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_dimensions() {
|
|
let device = Device::cpu();
|
|
|
|
// Test mismatched data size
|
|
let data = vec![0.5f32; 100]; // Wrong size for 3x32x32
|
|
let result = ImageTensor::from_array(data, 32, 32, 3, &device);
|
|
assert!(result.is_err());
|
|
|
|
// Test invalid channel count
|
|
let data = vec![0.5f32; 5 * 32 * 32];
|
|
let result = ImageTensor::from_array(data, 32, 32, 5, &device);
|
|
// Should either work (for hyperspectral) or have clear error
|
|
match result {
|
|
Ok(img) => assert_eq!(img.channels(), 5),
|
|
Err(e) => assert!(e.to_string().contains("channel")),
|
|
}
|
|
}
|