use crate::*;
use scirs2_core::ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, ArrayView3, ArrayViewMut1};
use scirs2_core::simd_ops::SimdUnifiedOps;
pub mod simd_operations {
use super::*;
pub fn simd_reconstruct_from_patches_2d(
patches: &ArrayView3<Float>,
image_size: (usize, usize),
) -> SklResult<Array2<Float>> {
let (height, width) = image_size;
if patches.is_empty() {
return Ok(Array2::zeros((height, width)));
}
let (n_patches, patch_height, patch_width) = patches.dim();
if n_patches == 0 || patch_height == 0 || patch_width == 0 {
return Ok(Array2::zeros((height, width)));
}
let mut reconstructed = Array2::<Float>::zeros((height, width));
let mut overlap_counts = Array2::<Float>::zeros((height, width));
let max_row = height.saturating_sub(patch_height).saturating_add(1);
let max_col = width.saturating_sub(patch_width).saturating_add(1);
if patch_height > height || patch_width > width {
return Err(SklearsError::InvalidInput(format!(
"Patch size ({}, {}) cannot be larger than image size ({}, {})",
patch_height, patch_width, height, width
)));
}
let total_positions = max_row * max_col;
let step = if n_patches < total_positions {
total_positions / n_patches
} else {
1
};
let mut patch_idx = 0;
#[allow(clippy::explicit_counter_loop)] for i in (0..total_positions).step_by(step.max(1)) {
if patch_idx >= n_patches {
break;
}
let row = i / max_col;
let col = i % max_col;
let valid_width = width.saturating_sub(col).min(patch_width);
if valid_width > 0 {
let ones = Array1::<Float>::from_elem(valid_width, 1.0);
for py in 0..patch_height {
let img_y = row + py;
if img_y >= height {
continue;
}
let patch_row = patches.slice(s![patch_idx, py, 0..valid_width]);
let mut recon_row = reconstructed.slice_mut(s![img_y, col..col + valid_width]);
let summed = Float::simd_add(&recon_row.view(), &patch_row);
recon_row.assign(&summed);
let mut count_row = overlap_counts.slice_mut(s![img_y, col..col + valid_width]);
let incremented = Float::simd_add(&count_row.view(), &ones.view());
count_row.assign(&incremented);
}
}
patch_idx += 1;
}
let ones_row = Array1::<Float>::from_elem(width, 1.0);
for y in 0..height {
let safe_counts = Float::simd_max(&overlap_counts.row(y), &ones_row.view());
let divided = Float::simd_div(&reconstructed.row(y), &safe_counts.view());
reconstructed.row_mut(y).assign(÷d);
}
Ok(reconstructed)
}
pub fn simd_array_subtraction(
a: &ArrayView2<Float>,
b: &ArrayView2<Float>,
) -> SklResult<Array2<Float>> {
if a.dim() != b.dim() {
return Err(SklearsError::InvalidInput(
"Array dimensions must match for subtraction".to_string(),
));
}
let (height, width) = a.dim();
let mut result = Array2::<Float>::zeros((height, width));
for row in 0..height {
let diff = Float::simd_sub(&a.row(row), &b.row(row));
result.row_mut(row).assign(&diff);
}
Ok(result)
}
pub fn simd_detect_extrema(
below: &ArrayView2<Float>,
center: &ArrayView2<Float>,
above: &ArrayView2<Float>,
threshold: Float,
) -> Vec<(usize, usize, bool)> {
let mut extrema = Vec::new();
let (height, width) = center.dim();
if height < 3 || width < 3 {
return extrema;
}
let below_matches = below.dim() == center.dim();
let above_matches = above.dim() == center.dim();
let mut neighbors: Vec<Float> = Vec::with_capacity(24);
for y in 1..height - 1 {
for x in 1..width - 1 {
let center_val = center[[y, x]];
if center_val.abs() < threshold {
continue;
}
neighbors.clear();
for dy in 0..3 {
for dx in 0..3 {
if dy == 1 && dx == 1 {
continue;
}
let ny = y + dy - 1;
let nx = x + dx - 1;
neighbors.push(center[[ny, nx]]);
if below_matches {
neighbors.push(below[[ny, nx]]);
}
if above_matches {
neighbors.push(above[[ny, nx]]);
}
}
}
let neighbor_view = ArrayView1::from(neighbors.as_slice());
let max_neighbor = Float::simd_max_element(&neighbor_view);
let min_neighbor = Float::simd_min_element(&neighbor_view);
let is_maximum = center_val > max_neighbor;
let is_minimum = center_val < min_neighbor;
if is_maximum || is_minimum {
extrema.push((x, y, is_maximum));
}
}
}
extrema
}
pub fn simd_normalize_descriptor(descriptor: &mut ArrayViewMut1<Float>, threshold: Float) {
let n = descriptor.len();
if n == 0 {
return;
}
let norm = Float::simd_norm(&descriptor.view());
if norm > Float::EPSILON {
let norm_arr = Array1::<Float>::from_elem(n, norm);
let normalized = Float::simd_div(&descriptor.view(), &norm_arr.view());
descriptor.assign(&normalized);
}
let clamp = Array1::<Float>::from_elem(n, threshold);
let clipped = Float::simd_min(&descriptor.view(), &clamp.view());
descriptor.assign(&clipped);
let norm2 = Float::simd_norm(&descriptor.view());
if norm2 > Float::EPSILON {
let norm2_arr = Array1::<Float>::from_elem(n, norm2);
let renormalized = Float::simd_div(&descriptor.view(), &norm2_arr.view());
descriptor.assign(&renormalized);
}
}
pub fn simd_gaussian_blur(
image: &ArrayView2<Float>,
_kernel: &[Float],
sigma: Float,
) -> Array2<Float> {
let (height, width) = image.dim();
if height == 0 || width == 0 {
return Array2::zeros((height, width));
}
let kernel_size = (6.0 * sigma).ceil() as usize | 1; let half_size = kernel_size / 2;
let mut blurred = Array2::zeros((height, width));
for y in 0..height {
let lo_y = y.saturating_sub(half_size);
let hi_y = (y + half_size).min(height - 1);
for x in 0..width {
let lo_x = x.saturating_sub(half_size);
let hi_x = (x + half_size).min(width - 1);
let row_span = hi_x - lo_x + 1;
let mut sum = 0.0;
let mut count = 0usize;
for src_y in lo_y..=hi_y {
let row_segment = image.slice(s![src_y, lo_x..=hi_x]);
sum += Float::simd_sum(&row_segment);
count += row_span;
}
blurred[[y, x]] = if count > 0 { sum / count as Float } else { 0.0 };
}
}
blurred
}
pub fn simd_compute_integral_image(image: &ArrayView2<Float>) -> Array2<Float> {
let (height, width) = image.dim();
if height == 0 || width == 0 {
return Array2::zeros((height, width));
}
let mut row_prefix = Array2::<Float>::zeros((height, width));
for y in 0..height {
let cs = Float::simd_cumsum(&image.row(y));
row_prefix.row_mut(y).assign(&cs);
}
let transposed = Float::simd_transpose_blocked(&row_prefix.view());
let mut col_prefix_t = Array2::<Float>::zeros((width, height));
for x in 0..width {
let cs = Float::simd_cumsum(&transposed.row(x));
col_prefix_t.row_mut(x).assign(&cs);
}
Float::simd_transpose_blocked(&col_prefix_t.view())
}
pub fn simd_haar_x_response(
integral: &ArrayView2<Float>,
x: usize,
y: usize,
size: usize,
) -> Float {
let (height, width) = integral.dim();
let half_size = size / 2;
if x < half_size || y < half_size || x + half_size >= width || y + half_size >= height {
return 0.0;
}
let left = box_filter_sum(integral, x - half_size, y - half_size, half_size, size);
let right = box_filter_sum(integral, x, y - half_size, half_size, size);
right - left
}
pub fn simd_haar_y_response(
integral: &ArrayView2<Float>,
x: usize,
y: usize,
size: usize,
) -> Float {
let (height, width) = integral.dim();
let half_size = size / 2;
if x < half_size || y < half_size || x + half_size >= width || y + half_size >= height {
return 0.0;
}
let top = box_filter_sum(integral, x - half_size, y - half_size, size, half_size);
let bottom = box_filter_sum(integral, x - half_size, y, size, half_size);
bottom - top
}
pub fn simd_compute_kurtosis(values: &[Float], mean: Float, std_dev: Float) -> Float {
if std_dev == 0.0 {
return 0.0;
}
let view = ArrayView1::from(values);
let n = values.len() as Float;
let mean_arr = Array1::<Float>::from_elem(values.len(), mean);
let std_arr = Array1::<Float>::from_elem(values.len(), std_dev);
let centered = Float::simd_sub(&view, &mean_arr.view());
let z_scores = Float::simd_div(¢ered.view(), &std_arr.view());
let fourth_powers = Float::simd_powi(&z_scores.view(), 4);
let sum_fourth = Float::simd_sum(&fourth_powers.view());
(sum_fourth / n) - 3.0 }
}
pub use simd_operations::*;
fn box_filter_sum(integral: &ArrayView2<Float>, x: usize, y: usize, w: usize, h: usize) -> Float {
let (height, width) = integral.dim();
let x1 = x.saturating_sub(1);
let y1 = y.saturating_sub(1);
let x2 = (x + w).min(width - 1);
let y2 = (y + h).min(height - 1);
if x2 >= width || y2 >= height {
return 0.0;
}
let values = [
integral[[y2, x2]],
integral[[y1, x1]],
integral[[y1, x2]],
integral[[y2, x1]],
];
let signs = [1.0, 1.0, -1.0, -1.0];
Float::simd_dot(
&ArrayView1::from(&values[..]),
&ArrayView1::from(&signs[..]),
)
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::{Array2, Array3};
#[test]
fn test_array_subtraction() {
let a = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
.expect("operation should succeed");
let b = Array2::from_shape_vec((2, 2), vec![0.5, 1.0, 1.5, 2.0])
.expect("operation should succeed");
let result =
simd_array_subtraction(&a.view(), &b.view()).expect("operation should succeed");
let expected = Array2::from_shape_vec((2, 2), vec![0.5, 1.0, 1.5, 2.0])
.expect("operation should succeed");
assert!((result - expected)
.map(|x| x.abs())
.iter()
.all(|&x| x < 1e-10));
}
#[test]
fn test_integral_image() {
let image =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("operation should succeed");
let integral = simd_compute_integral_image(&image.view());
assert_eq!(integral[[0, 0]], 1.0);
assert_eq!(integral[[0, 2]], 6.0); assert_eq!(integral[[2, 2]], 45.0); }
#[test]
fn test_extrema_detection() {
let below =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0, 1.0])
.expect("operation should succeed");
let center = Array2::from_shape_vec(
(3, 3),
vec![
2.0, 3.0, 2.0, 3.0, 5.0, 3.0, 2.0, 3.0, 2.0,
],
)
.expect("operation should succeed");
let above =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0, 1.0])
.expect("operation should succeed");
let extrema = simd_detect_extrema(&below.view(), ¢er.view(), &above.view(), 1.0);
assert!(!extrema.is_empty());
assert!(extrema
.iter()
.any(|(x, y, is_max)| *x == 1 && *y == 1 && *is_max));
}
fn assert_array2_close(actual: &Array2<Float>, expected: &Array2<Float>, epsilon: Float) {
assert_eq!(actual.dim(), expected.dim(), "shape mismatch");
for ((y, x), &e) in expected.indexed_iter() {
let a = actual[[y, x]];
assert!(
(a - e).abs() <= epsilon,
"mismatch at ({y},{x}): actual={a}, expected={e}"
);
}
}
fn assert_array1_close(actual: &Array1<Float>, expected: &[Float], epsilon: Float) {
assert_eq!(actual.len(), expected.len(), "length mismatch");
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!(
(a - e).abs() <= epsilon,
"mismatch at [{i}]: actual={a}, expected={e}"
);
}
}
#[test]
fn test_reconstruct_from_patches_2d_matches_reference() {
let patches =
Array3::from_shape_vec((2, 2, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
.expect("operation should succeed");
let recon = simd_reconstruct_from_patches_2d(&patches.view(), (3, 3))
.expect("operation should succeed");
let expected =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 0.0, 4.0, 5.0, 0.0, 7.0, 8.0, 0.0])
.expect("operation should succeed");
assert_array2_close(&recon, &expected, 1e-9);
}
#[test]
fn test_reconstruct_from_patches_2d_overlap_matches_reference() {
let patches = Array3::from_shape_vec(
(4, 2, 2),
vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, ],
)
.expect("operation should succeed");
let recon = simd_reconstruct_from_patches_2d(&patches.view(), (4, 4))
.expect("operation should succeed");
let expected = Array2::from_shape_vec(
(4, 4),
vec![
1.0, 2.0, 5.0, 6.0, 3.0, 6.5, 8.5, 8.0, 13.0, 12.5, 12.0, 0.0, 15.0, 16.0, 0.0, 0.0,
],
)
.expect("operation should succeed");
assert_array2_close(&recon, &expected, 1e-9);
}
#[test]
fn test_array_subtraction_matches_reference() {
let a = Array2::from_shape_vec((2, 3), vec![1.0, -2.5, 3.0, 4.25, -5.0, 6.75])
.expect("operation should succeed");
let b = Array2::from_shape_vec((2, 3), vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
.expect("operation should succeed");
let result =
simd_array_subtraction(&a.view(), &b.view()).expect("operation should succeed");
let expected = Array2::from_shape_vec((2, 3), vec![0.9, -2.7, 2.7, 3.85, -5.5, 6.15])
.expect("operation should succeed");
assert_array2_close(&result, &expected, 1e-9);
}
#[test]
fn test_detect_extrema_matches_reference() {
let center =
Array2::from_shape_vec((3, 3), vec![2.0, 3.0, 2.0, 3.0, 9.0, 3.0, 2.0, 3.0, 2.0])
.expect("operation should succeed");
let below3 =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0, 1.0])
.expect("operation should succeed");
let above3 = below3.clone();
let no_dims: Array2<Float> = Array2::zeros((0, 0));
let extrema_nodims =
simd_detect_extrema(&no_dims.view(), ¢er.view(), &no_dims.view(), 1.0);
assert_eq!(extrema_nodims, vec![(1, 1, true)]);
let extrema_full = simd_detect_extrema(&below3.view(), ¢er.view(), &above3.view(), 1.0);
assert_eq!(extrema_full, vec![(1, 1, true)]);
let center_min =
Array2::from_shape_vec((3, 3), vec![5.0, 5.0, 5.0, 5.0, 0.5, 5.0, 5.0, 5.0, 5.0])
.expect("operation should succeed");
let extrema_min =
simd_detect_extrema(&below3.view(), ¢er_min.view(), &above3.view(), 0.1);
assert_eq!(extrema_min, vec![(1, 1, false)]);
}
#[test]
fn test_normalize_descriptor_matches_reference() {
let mut desc = Array1::from_vec(vec![3.0, 4.0, 0.0, 0.0]);
simd_normalize_descriptor(&mut desc.view_mut(), 0.7);
assert_array1_close(
&desc,
&[0.6507913734559686, 0.7592566023652966, 0.0, 0.0],
1e-9,
);
let mut desc2 = Array1::from_vec(vec![1.0, -2.0, 2.0, -1.0, 0.5]);
simd_normalize_descriptor(&mut desc2.view_mut(), 10.0); assert_array1_close(
&desc2,
&[
0.31234752377721214,
-0.6246950475544243,
0.6246950475544243,
-0.31234752377721214,
0.15617376188860607,
],
1e-9,
);
}
#[test]
fn test_haar_responses_match_reference() {
let integral = Array2::from_shape_vec(
(6, 6),
(0..6)
.flat_map(|r: i64| (0..6).map(move |c: i64| (r * c + 2 * r + 3 * c + 1) as f64))
.collect(),
)
.expect("operation should succeed");
assert_abs_diff_eq!(
simd_haar_x_response(&integral.view(), 3, 3, 2),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_y_response(&integral.view(), 3, 3, 2),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_x_response(&integral.view(), 2, 2, 4),
4.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_y_response(&integral.view(), 2, 2, 4),
4.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_x_response(&integral.view(), 4, 3, 3),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_y_response(&integral.view(), 4, 3, 3),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_x_response(&integral.view(), 0, 0, 4),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_x_response(&integral.view(), 4, 4, 2),
0.0,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_haar_y_response(&integral.view(), 4, 4, 2),
0.0,
epsilon = 1e-9
);
}
#[test]
fn test_compute_integral_image_matches_reference() {
let img2x4 = Array2::from_shape_vec((2, 4), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
.expect("operation should succeed");
let integ2x4 = simd_compute_integral_image(&img2x4.view());
let expected2x4 =
Array2::from_shape_vec((2, 4), vec![1.0, 3.0, 6.0, 10.0, 6.0, 14.0, 24.0, 36.0])
.expect("operation should succeed");
assert_array2_close(&integ2x4, &expected2x4, 1e-9);
let img3x3 =
Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("operation should succeed");
let integ3x3 = simd_compute_integral_image(&img3x3.view());
let expected3x3 = Array2::from_shape_vec(
(3, 3),
vec![1.0, 3.0, 6.0, 5.0, 12.0, 21.0, 12.0, 27.0, 45.0],
)
.expect("operation should succeed");
assert_array2_close(&integ3x3, &expected3x3, 1e-9);
}
#[test]
fn test_compute_kurtosis_matches_reference() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_abs_diff_eq!(
simd_compute_kurtosis(&values, 3.0, 1.41),
-1.279588004134889,
epsilon = 1e-9
);
let values2 = vec![2.0, 8.0, 0.0, 4.0, 4.0, 4.0, 4.0, 6.0, 10.0, 4.0];
let mean2 = values2.iter().sum::<Float>() / values2.len() as Float;
let var2 = values2
.iter()
.map(|v| (v - mean2) * (v - mean2))
.sum::<Float>()
/ values2.len() as Float;
let std2 = var2.sqrt();
assert_abs_diff_eq!(mean2, 4.6, epsilon = 1e-9);
assert_abs_diff_eq!(std2, 2.690724809414742, epsilon = 1e-9);
assert_abs_diff_eq!(
simd_compute_kurtosis(&values2, mean2, std2),
-0.17294954366472215,
epsilon = 1e-9
);
assert_abs_diff_eq!(
simd_compute_kurtosis(&values, 3.0, 0.0),
0.0,
epsilon = 1e-9
);
}
#[test]
fn test_gaussian_blur_matches_reference() {
let img4 = Array2::from_shape_vec((4, 4), (0..16).map(|v| v as Float).collect())
.expect("operation should succeed");
let blurred_wide = simd_gaussian_blur(&img4.view(), &[], 1.0);
let expected_wide = Array2::from_elem((4, 4), 7.5);
assert_array2_close(&blurred_wide, &expected_wide, 1e-9);
let img5 = Array2::from_shape_vec(
(5, 5),
vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0,
],
)
.expect("operation should succeed");
let blurred_narrow = simd_gaussian_blur(&img5.view(), &[], 0.3);
let expected_narrow = Array2::from_shape_vec(
(5, 5),
vec![
4.0, 4.5, 5.5, 6.5, 7.0, 6.5, 7.0, 8.0, 9.0, 9.5, 11.5, 12.0, 13.0, 14.0, 14.5, 16.5, 17.0, 18.0, 19.0, 19.5, 19.0, 19.5, 20.5, 21.5, 22.0,
],
)
.expect("operation should succeed");
assert_array2_close(&blurred_narrow, &expected_narrow, 1e-9);
}
}