diff --git a/src/opencv_ops.rs b/src/opencv_ops.rs index 8d7fbac..921dd89 100644 --- a/src/opencv_ops.rs +++ b/src/opencv_ops.rs @@ -4,7 +4,7 @@ use ndarray::{Array3, ArrayView3}; #[cfg(feature = "opencv")] use opencv::{ core::{AlgorithmHint, Mat}, - imgproc::{cvt_color, resize, COLOR_RGB2GRAY, INTER_LANCZOS4}, + imgproc::{cvt_color, resize, COLOR_RGB2GRAY, INTER_LANCZOS4, INTER_LINEAR}, prelude::*, }; @@ -30,6 +30,15 @@ impl OpenCVBatchProcessor { &self, images: &[ArrayView3], target_sizes: &[(u32, u32)], // (width, height) + ) -> Result>> { + self.batch_resize_images_with_interpolation(images, target_sizes, INTER_LINEAR) + } + + fn batch_resize_images_with_interpolation( + &self, + images: &[ArrayView3], + target_sizes: &[(u32, u32)], // (width, height) + interpolation: i32, ) -> Result>> { if images.len() != target_sizes.len() { anyhow::bail!("Number of images and target sizes must match"); @@ -39,7 +48,7 @@ impl OpenCVBatchProcessor { .iter() .zip(target_sizes.iter()) .map(|(image, &(target_width, target_height))| { - self.resize_single_opencv(image, target_width, target_height) + self.resize_single_opencv(image, target_width, target_height, interpolation) }) .collect::>>()?; @@ -111,8 +120,7 @@ impl OpenCVBatchProcessor { images: &[ArrayView3], target_sizes: &[(u32, u32)], // (width, height) ) -> Result>> { - // Use the same implementation as batch_resize_images but with Lanczos4 - self.batch_resize_images(images, target_sizes) + self.batch_resize_images_with_interpolation(images, target_sizes, INTER_LANCZOS4) } /// Single image resize using OpenCV @@ -121,6 +129,7 @@ impl OpenCVBatchProcessor { image: &ArrayView3, target_width: u32, target_height: u32, + interpolation: i32, ) -> Result> { let (_height, _width, channels) = image.dim(); @@ -139,7 +148,7 @@ impl OpenCVBatchProcessor { opencv::core::Size::new(target_width as i32, target_height as i32), 0.0, 0.0, - INTER_LANCZOS4, + interpolation, )?; // Convert back to ndarray @@ -243,7 +252,7 @@ pub fn resize_bilinear_opencv( target_height: u32, ) -> Result> { let processor = OpenCVBatchProcessor::new(); - processor.resize_single_opencv(image, target_width, target_height) + processor.resize_single_opencv(image, target_width, target_height, INTER_LINEAR) } #[cfg(feature = "opencv")] @@ -253,7 +262,7 @@ pub fn resize_lanczos4_opencv( target_height: u32, ) -> Result> { let processor = OpenCVBatchProcessor::new(); - processor.resize_single_opencv(image, target_width, target_height) + processor.resize_single_opencv(image, target_width, target_height, INTER_LANCZOS4) } #[cfg(not(feature = "opencv"))] diff --git a/src/python_bindings.rs b/src/python_bindings.rs index 79b2526..869e030 100644 --- a/src/python_bindings.rs +++ b/src/python_bindings.rs @@ -952,17 +952,20 @@ pub fn batch_random_crop_images<'py>( #[cfg(feature = "python-bindings")] #[pyfunction] -pub fn batch_calculate_luminance(images: Vec>) -> PyResult> { - // Keep original sequential implementation for fair comparison - let mut luminances = Vec::with_capacity(images.len()); +pub fn batch_calculate_luminance( + py: Python<'_>, + images: Vec>, +) -> PyResult> { + use rayon::prelude::*; - for image in images.iter() { - let img_view = image.as_array(); - let luminance = crate::luminance::calculate_luminance_array(&img_view); - luminances.push(luminance); - } + let image_views: Vec<_> = images.iter().map(|image| image.as_array()).collect(); - Ok(luminances) + Ok(py.allow_threads(|| { + image_views + .par_iter() + .map(crate::luminance::calculate_luminance_array_sequential) + .collect() + })) } // TSR FORMAT CONVERSION OPERATIONS (BENCHMARK WINNERS) diff --git a/src/tests.rs b/src/tests.rs index 44e0619..395f5ba 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -337,7 +337,7 @@ mod opencv_tests { use super::*; #[cfg(feature = "opencv")] - use crate::opencv_ops::OpenCVBatchProcessor; + use crate::opencv_ops::{resize_bilinear_opencv, resize_lanczos4_opencv, OpenCVBatchProcessor}; #[test] #[cfg(feature = "opencv")] @@ -356,6 +356,40 @@ mod opencv_tests { assert_eq!(resized[0].dim(), (128, 128, 3)); } + #[test] + #[cfg(feature = "opencv")] + fn test_opencv_resize_interpolation_contract() { + let processor = OpenCVBatchProcessor::new(); + let high_frequency = + Array3::from_shape_fn( + (4, 4, 3), + |(y, x, c)| { + if (x + y + c) % 2 == 0 { + 0 + } else { + 255 + } + }, + ); + + let bilinear = resize_bilinear_opencv(&high_frequency.view(), 7, 7).unwrap(); + let lanczos = resize_lanczos4_opencv(&high_frequency.view(), 7, 7).unwrap(); + assert_ne!( + bilinear, lanczos, + "bilinear and lanczos paths should use different OpenCV interpolation modes" + ); + + let batch_linear = processor + .batch_resize_images(&[high_frequency.view()], &[(7, 7)]) + .unwrap(); + let batch_lanczos = processor + .batch_resize_lanczos4(&[high_frequency.view()], &[(7, 7)]) + .unwrap(); + + assert_eq!(batch_linear[0], bilinear); + assert_eq!(batch_lanczos[0], lanczos); + } + #[test] #[cfg(feature = "opencv")] fn test_opencv_batch_luminance() {