#[cfg(feature = "no-std")]
use core::f32::consts;
#[cfg(not(feature = "no-std"))]
use std::f32::consts;
#[cfg(feature = "no-std")]
use core::cmp::Ordering;
#[cfg(not(feature = "no-std"))]
use std::cmp::Ordering;
pub mod convolution {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::{vec, vec::Vec};
pub fn convolve_2d(
image: &[f32],
width: usize,
height: usize,
kernel: &[f32],
kernel_size: usize,
) -> Vec<f32> {
assert_eq!(image.len(), width * height);
assert_eq!(kernel.len(), kernel_size * kernel_size);
assert!(kernel_size % 2 == 1, "Kernel size must be odd");
let mut output = vec![0.0; width * height];
let half_kernel = kernel_size / 2;
for y in 0..height {
for x in 0..width {
let mut sum = 0.0;
for ky in 0..kernel_size {
for kx in 0..kernel_size {
let img_y = y as i32 + ky as i32 - half_kernel as i32;
let img_x = x as i32 + kx as i32 - half_kernel as i32;
if img_y >= 0 && img_y < height as i32 && img_x >= 0 && img_x < width as i32
{
let img_idx = img_y as usize * width + img_x as usize;
let kernel_idx = ky * kernel_size + kx;
sum += image[img_idx] * kernel[kernel_idx];
}
}
}
output[y * width + x] = sum;
}
}
output
}
pub fn separable_convolve_2d(
image: &[f32],
width: usize,
height: usize,
kernel_x: &[f32],
kernel_y: &[f32],
) -> Vec<f32> {
let temp = convolve_horizontal(image, width, height, kernel_x);
convolve_vertical(&temp, width, height, kernel_y)
}
fn convolve_horizontal(image: &[f32], width: usize, height: usize, kernel: &[f32]) -> Vec<f32> {
let mut output = vec![0.0; width * height];
let half_kernel = kernel.len() / 2;
for y in 0..height {
for x in 0..width {
let mut sum = 0.0;
for (k, &kernel_val) in kernel.iter().enumerate() {
let img_x = x as i32 + k as i32 - half_kernel as i32;
if img_x >= 0 && img_x < width as i32 {
let img_idx = y * width + img_x as usize;
sum += image[img_idx] * kernel_val;
}
}
output[y * width + x] = sum;
}
}
output
}
fn convolve_vertical(image: &[f32], width: usize, height: usize, kernel: &[f32]) -> Vec<f32> {
let mut output = vec![0.0; width * height];
let half_kernel = kernel.len() / 2;
for y in 0..height {
for x in 0..width {
let mut sum = 0.0;
for (k, &kernel_val) in kernel.iter().enumerate() {
let img_y = y as i32 + k as i32 - half_kernel as i32;
if img_y >= 0 && img_y < height as i32 {
let img_idx = img_y as usize * width + x;
sum += image[img_idx] * kernel_val;
}
}
output[y * width + x] = sum;
}
}
output
}
}
pub mod edge_detection {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::{vec, vec::Vec};
use super::*;
pub fn sobel(image: &[f32], width: usize, height: usize) -> Vec<f32> {
let sobel_x = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
let sobel_y = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
let grad_x = convolution::convolve_2d(image, width, height, &sobel_x, 3);
let grad_y = convolution::convolve_2d(image, width, height, &sobel_y, 3);
grad_x
.iter()
.zip(grad_y.iter())
.map(|(&gx, &gy)| (gx * gx + gy * gy).sqrt())
.collect()
}
pub fn prewitt(image: &[f32], width: usize, height: usize) -> Vec<f32> {
let prewitt_x = vec![-1.0, 0.0, 1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0];
let prewitt_y = vec![-1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
let grad_x = convolution::convolve_2d(image, width, height, &prewitt_x, 3);
let grad_y = convolution::convolve_2d(image, width, height, &prewitt_y, 3);
grad_x
.iter()
.zip(grad_y.iter())
.map(|(&gx, &gy)| (gx * gx + gy * gy).sqrt())
.collect()
}
pub fn laplacian(image: &[f32], width: usize, height: usize) -> Vec<f32> {
let laplacian_kernel = vec![0.0, -1.0, 0.0, -1.0, 4.0, -1.0, 0.0, -1.0, 0.0];
convolution::convolve_2d(image, width, height, &laplacian_kernel, 3)
}
pub fn canny(
image: &[f32],
width: usize,
height: usize,
low_threshold: f32,
high_threshold: f32,
) -> Vec<f32> {
let gaussian_kernel = [1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0];
let gaussian_sum: f32 = gaussian_kernel.iter().sum();
let normalized_gaussian: Vec<f32> =
gaussian_kernel.iter().map(|&x| x / gaussian_sum).collect();
let blurred = convolution::convolve_2d(image, width, height, &normalized_gaussian, 3);
let edges = sobel(&blurred, width, height);
edges
.iter()
.map(|&magnitude| {
if magnitude >= high_threshold {
255.0
} else if magnitude >= low_threshold {
128.0
} else {
0.0
}
})
.collect()
}
}
pub mod filters {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::{vec, vec::Vec};
use super::*;
pub fn gaussian_blur(image: &[f32], width: usize, height: usize, sigma: f32) -> Vec<f32> {
let kernel_size = (6.0 * sigma).ceil() as usize | 1; let half_size = kernel_size / 2;
let mut kernel = Vec::with_capacity(kernel_size);
let sigma_sq_2 = 2.0 * sigma * sigma;
let norm_factor = 1.0 / (sigma * (2.0 * consts::PI).sqrt());
for i in 0..kernel_size {
let x = (i as i32 - half_size as i32) as f32;
let value = norm_factor * (-x * x / sigma_sq_2).exp();
kernel.push(value);
}
let kernel_sum: f32 = kernel.iter().sum();
for k in &mut kernel {
*k /= kernel_sum;
}
convolution::separable_convolve_2d(image, width, height, &kernel, &kernel)
}
pub fn box_blur(image: &[f32], width: usize, height: usize, kernel_size: usize) -> Vec<f32> {
let kernel_val = 1.0 / (kernel_size * kernel_size) as f32;
let kernel = vec![kernel_val; kernel_size * kernel_size];
convolution::convolve_2d(image, width, height, &kernel, kernel_size)
}
pub fn median_filter(
image: &[f32],
width: usize,
height: usize,
kernel_size: usize,
) -> Vec<f32> {
assert!(kernel_size % 2 == 1, "Kernel size must be odd");
let mut output = vec![0.0; width * height];
let half_kernel = kernel_size / 2;
for y in 0..height {
for x in 0..width {
let mut window = Vec::new();
for ky in 0..kernel_size {
for kx in 0..kernel_size {
let img_y = y as i32 + ky as i32 - half_kernel as i32;
let img_x = x as i32 + kx as i32 - half_kernel as i32;
if img_y >= 0 && img_y < height as i32 && img_x >= 0 && img_x < width as i32
{
let img_idx = img_y as usize * width + img_x as usize;
window.push(image[img_idx]);
}
}
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let median = if window.len() % 2 == 0 && !window.is_empty() {
(window[window.len() / 2 - 1] + window[window.len() / 2]) / 2.0
} else if !window.is_empty() {
window[window.len() / 2]
} else {
0.0
};
output[y * width + x] = median;
}
}
output
}
pub fn unsharp_mask(
image: &[f32],
width: usize,
height: usize,
amount: f32,
sigma: f32,
) -> Vec<f32> {
let blurred = gaussian_blur(image, width, height, sigma);
image
.iter()
.zip(blurred.iter())
.map(|(&original, &blur)| {
let detail = original - blur;
original + amount * detail
})
.collect()
}
}
pub mod morphology {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::{vec, vec::Vec};
pub fn erosion(
image: &[f32],
width: usize,
height: usize,
structuring_element: &[bool],
se_size: usize,
) -> Vec<f32> {
assert_eq!(structuring_element.len(), se_size * se_size);
assert!(se_size % 2 == 1, "Structuring element size must be odd");
let mut output = vec![0.0; width * height];
let half_se = se_size / 2;
for y in 0..height {
for x in 0..width {
let mut min_val = f32::INFINITY;
for sy in 0..se_size {
for sx in 0..se_size {
if structuring_element[sy * se_size + sx] {
let img_y = y as i32 + sy as i32 - half_se as i32;
let img_x = x as i32 + sx as i32 - half_se as i32;
if img_y >= 0
&& img_y < height as i32
&& img_x >= 0
&& img_x < width as i32
{
let img_idx = img_y as usize * width + img_x as usize;
min_val = min_val.min(image[img_idx]);
}
}
}
}
output[y * width + x] = if min_val == f32::INFINITY {
0.0
} else {
min_val
};
}
}
output
}
pub fn dilation(
image: &[f32],
width: usize,
height: usize,
structuring_element: &[bool],
se_size: usize,
) -> Vec<f32> {
assert_eq!(structuring_element.len(), se_size * se_size);
assert!(se_size % 2 == 1, "Structuring element size must be odd");
let mut output = vec![0.0; width * height];
let half_se = se_size / 2;
for y in 0..height {
for x in 0..width {
let mut max_val = f32::NEG_INFINITY;
for sy in 0..se_size {
for sx in 0..se_size {
if structuring_element[sy * se_size + sx] {
let img_y = y as i32 + sy as i32 - half_se as i32;
let img_x = x as i32 + sx as i32 - half_se as i32;
if img_y >= 0
&& img_y < height as i32
&& img_x >= 0
&& img_x < width as i32
{
let img_idx = img_y as usize * width + img_x as usize;
max_val = max_val.max(image[img_idx]);
}
}
}
}
output[y * width + x] = if max_val == f32::NEG_INFINITY {
0.0
} else {
max_val
};
}
}
output
}
pub fn opening(
image: &[f32],
width: usize,
height: usize,
structuring_element: &[bool],
se_size: usize,
) -> Vec<f32> {
let eroded = erosion(image, width, height, structuring_element, se_size);
dilation(&eroded, width, height, structuring_element, se_size)
}
pub fn closing(
image: &[f32],
width: usize,
height: usize,
structuring_element: &[bool],
se_size: usize,
) -> Vec<f32> {
let dilated = dilation(image, width, height, structuring_element, se_size);
erosion(&dilated, width, height, structuring_element, se_size)
}
pub fn circular_structuring_element(radius: usize) -> (Vec<bool>, usize) {
let size = 2 * radius + 1;
let mut element = vec![false; size * size];
let center = radius as i32;
for y in 0..size {
for x in 0..size {
let dy = y as i32 - center;
let dx = x as i32 - center;
let distance = ((dx * dx + dy * dy) as f32).sqrt();
if distance <= radius as f32 {
element[y * size + x] = true;
}
}
}
(element, size)
}
pub fn square_structuring_element(size: usize) -> (Vec<bool>, usize) {
(vec![true; size * size], size)
}
}
pub mod features {
#[cfg(feature = "no-std")]
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "no-std"))]
use std::{vec, vec::Vec};
use super::*;
pub fn local_binary_pattern(
image: &[f32],
width: usize,
height: usize,
radius: usize,
num_points: usize,
) -> Vec<u8> {
let mut output = vec![0u8; width * height];
for y in radius..height - radius {
for x in radius..width - radius {
let center_val = image[y * width + x];
let mut lbp_code = 0u8;
for p in 0..num_points {
let angle = 2.0 * consts::PI * p as f32 / num_points as f32;
let dy = (radius as f32 * angle.sin()).round() as i32;
let dx = (radius as f32 * angle.cos()).round() as i32;
let ny = y as i32 + dy;
let nx = x as i32 + dx;
if ny >= 0 && ny < height as i32 && nx >= 0 && nx < width as i32 {
let neighbor_val = image[ny as usize * width + nx as usize];
if neighbor_val >= center_val {
lbp_code |= 1 << p;
}
}
}
output[y * width + x] = lbp_code;
}
}
output
}
pub fn harris_corners(
image: &[f32],
width: usize,
height: usize,
k: f32,
threshold: f32,
) -> Vec<(usize, usize)> {
let sobel_x = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
let sobel_y = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
let grad_x = convolution::convolve_2d(image, width, height, &sobel_x, 3);
let grad_y = convolution::convolve_2d(image, width, height, &sobel_y, 3);
let mut ixx = vec![0.0; width * height];
let mut iyy = vec![0.0; width * height];
let mut ixy = vec![0.0; width * height];
for i in 0..width * height {
ixx[i] = grad_x[i] * grad_x[i];
iyy[i] = grad_y[i] * grad_y[i];
ixy[i] = grad_x[i] * grad_y[i];
}
let gaussian_kernel = [1.0, 2.0, 1.0];
let gaussian_sum: f32 = gaussian_kernel.iter().sum();
let normalized_gaussian: Vec<f32> =
gaussian_kernel.iter().map(|&x| x / gaussian_sum).collect();
ixx = convolution::separable_convolve_2d(
&ixx,
width,
height,
&normalized_gaussian,
&normalized_gaussian,
);
iyy = convolution::separable_convolve_2d(
&iyy,
width,
height,
&normalized_gaussian,
&normalized_gaussian,
);
ixy = convolution::separable_convolve_2d(
&ixy,
width,
height,
&normalized_gaussian,
&normalized_gaussian,
);
let mut corners = Vec::new();
for y in 1..height - 1 {
for x in 1..width - 1 {
let idx = y * width + x;
let det = ixx[idx] * iyy[idx] - ixy[idx] * ixy[idx];
let trace = ixx[idx] + iyy[idx];
let harris_response = det - k * trace * trace;
if harris_response > threshold {
corners.push((x, y));
}
}
}
corners
}
}
#[allow(non_snake_case)]
#[cfg(all(test, not(feature = "no-std")))]
mod tests {
use super::*;
#[cfg(feature = "no-std")]
use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
#[test]
fn test_2d_convolution() {
let image = vec![0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let kernel = vec![0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0];
let result = convolution::convolve_2d(&image, 3, 3, &kernel, 3);
assert!(result[4] > result[0]);
assert!(result[4] > result[8]);
}
#[test]
fn test_sobel_edge_detection() {
let image = vec![
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
];
let edges = edge_detection::sobel(&image, 4, 4);
assert!(edges[4] > 0.0 || edges[5] > 0.0 || edges[6] > 0.0 || edges[7] > 0.0);
}
#[test]
fn test_gaussian_blur() {
let image = vec![0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let blurred = filters::gaussian_blur(&image, 3, 3, 1.0);
assert!(blurred[4] < 1.0);
assert!(blurred[1] > 0.0);
assert!(blurred[3] > 0.0);
}
#[test]
fn test_median_filter() {
let image = vec![
1.0, 1.0, 1.0, 1.0, 9.0, 1.0, 1.0, 1.0, 1.0,
];
let filtered = filters::median_filter(&image, 3, 3, 3);
assert!(filtered[4] < 9.0);
assert!(filtered[4] <= 1.0);
}
#[test]
fn test_erosion_dilation() {
let image = vec![
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
let (se, se_size) = morphology::square_structuring_element(3);
let eroded = morphology::erosion(&image, 5, 5, &se, se_size);
let dilated = morphology::dilation(&image, 5, 5, &se, se_size);
assert!(eroded.iter().sum::<f32>() <= image.iter().sum::<f32>());
assert!(dilated.iter().sum::<f32>() >= image.iter().sum::<f32>());
}
#[test]
fn test_circular_structuring_element() {
let (se, size) = morphology::circular_structuring_element(2);
assert_eq!(size, 5);
assert!(se[2 * 5 + 2]);
assert!(!se[0]);
assert!(!se[4]);
assert!(!se[20]);
assert!(!se[24]);
}
#[test]
fn test_local_binary_pattern() {
let image = 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,
];
let lbp = features::local_binary_pattern(&image, 4, 4, 1, 8);
assert!(lbp[5] > 0 || lbp[6] > 0);
}
#[test]
fn test_harris_corners() {
let image = vec![
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
let _corners = features::harris_corners(&image, 5, 5, 0.04, 0.01);
}
#[test]
fn test_unsharp_mask() {
let image = vec![0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let sharpened = filters::unsharp_mask(&image, 3, 3, 1.0, 1.0);
assert!(sharpened[4] >= image[4]);
}
}