use scirs2_core::ndarray::{Array2, ArrayView2, Dimension, Zip};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::simd_ops::SimdUnifiedOps;
use std::fmt::Debug;
use crate::error::{NdimageError, NdimageResult};
use crate::filters::{convolve, BorderMode};
use crate::utils::safe_f64_to_float;
#[allow(dead_code)]
fn safe_to_usize<T: Float>(value: T) -> NdimageResult<usize> {
value.to_usize().ok_or_else(|| {
NdimageError::ComputationError("Failed to convert _value to usize".to_string())
})
}
#[allow(dead_code)]
fn safe_to_f64<T: Float>(value: T) -> NdimageResult<f64> {
value
.to_f64()
.ok_or_else(|| NdimageError::ComputationError("Failed to convert value to f64".to_string()))
}
#[allow(dead_code)]
fn safe_isize_to_float<T: Float + FromPrimitive>(value: isize) -> NdimageResult<T> {
T::from_isize(value).ok_or_else(|| {
NdimageError::ComputationError(format!("Failed to convert isize {} to float type", value))
})
}
#[allow(dead_code)]
fn safe_usize_to_float<T: Float + FromPrimitive>(value: usize) -> NdimageResult<T> {
T::from_usize(value).ok_or_else(|| {
NdimageError::ComputationError(format!("Failed to convert usize {} to float type", value))
})
}
#[derive(Debug, Clone)]
pub struct GaborParams<T> {
pub wavelength: T,
pub orientation: T,
pub sigma_x: T,
pub sigma_y: T,
pub phase: T,
pub aspect_ratio: Option<T>,
}
impl<T> Default for GaborParams<T>
where
T: Float + FromPrimitive,
{
fn default() -> Self {
Self {
wavelength: safe_f64_to_float::<T>(4.0)
.unwrap_or_else(|_| T::from_f64(4.0).unwrap_or_else(|| T::one())),
orientation: T::zero(),
sigma_x: safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one())),
sigma_y: safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one())),
phase: T::zero(),
aspect_ratio: None,
}
}
}
#[allow(dead_code)]
pub fn gabor_filter<T>(
input: &ArrayView2<T>,
params: &GaborParams<T>,
kernel_size: Option<usize>,
mode: Option<BorderMode>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
{
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let sigma_max = if let Some(aspect) = params.aspect_ratio {
params.sigma_x.max(params.sigma_x * aspect)
} else {
params.sigma_x.max(params.sigma_y)
};
let auto_size = (sigma_max
* safe_f64_to_float::<T>(6.0)
.unwrap_or_else(|_| T::from_f64(6.0).unwrap_or_else(|| T::one())))
.to_usize()
.unwrap_or(21);
let _size = kernel_size.unwrap_or(auto_size);
let _size = if _size % 2 == 0 { _size + 1 } else { _size };
let kernel = generate_gabor_kernel(_size, params)?;
convolve(&input.to_owned(), &kernel, Some(border_mode))
}
#[allow(dead_code)]
fn generate_gabor_kernel<T>(size: usize, params: &GaborParams<T>) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug,
{
let mut kernel = Array2::zeros((size, size));
let center = (size / 2) as isize;
let cos_theta = params.orientation.cos();
let sin_theta = params.orientation.sin();
let sigma_x = params.sigma_x;
let sigma_y = if let Some(aspect) = params.aspect_ratio {
sigma_x * aspect
} else {
params.sigma_y
};
let lambda = params.wavelength;
let phase = params.phase;
let sigma_x_sq = sigma_x * sigma_x;
let sigma_y_sq = sigma_y * sigma_y;
let two_pi = safe_f64_to_float::<T>(2.0 * std::f64::consts::PI)?;
let two = safe_f64_to_float::<T>(2.0)?;
for i in 0..size {
for j in 0..size {
let x: T = safe_isize_to_float(j as isize - center)?;
let y: T = safe_isize_to_float(i as isize - center)?;
let x_rot = x * cos_theta + y * sin_theta;
let y_rot = -x * sin_theta + y * cos_theta;
let gauss_exp =
-(x_rot * x_rot / (two * sigma_x_sq) + y_rot * y_rot / (two * sigma_y_sq));
let gauss = gauss_exp.exp();
let wave_arg = two_pi * x_rot / lambda + phase;
let wave = wave_arg.cos();
kernel[[i, j]] = gauss * wave;
}
}
Ok(kernel)
}
#[allow(dead_code)]
pub fn gabor_filter_bank<T>(
input: &ArrayView2<T>,
base_params: &GaborParams<T>,
num_orientations: usize,
kernel_size: Option<usize>,
mode: Option<BorderMode>,
) -> NdimageResult<Vec<Array2<T>>>
where
T: Float
+ FromPrimitive
+ Debug
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
{
let mut results = Vec::with_capacity(num_orientations);
let pi = safe_f64_to_float::<T>(std::f64::consts::PI)?;
let angle_step = pi / safe_usize_to_float::<T>(num_orientations)?;
for i in 0..num_orientations {
let mut params = base_params.clone();
params.orientation = safe_usize_to_float::<T>(i)? * angle_step;
let filtered = gabor_filter(input, ¶ms, kernel_size, mode)?;
results.push(filtered);
}
Ok(results)
}
#[allow(dead_code)]
pub fn log_gabor_filter<T>(
input: &ArrayView2<T>,
center_freq: T,
bandwidth: T,
orientation: T,
angular_bandwidth: T,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static + SimdUnifiedOps,
{
let (height, width) = input.dim();
let center_freq_f64 = safe_to_f64(center_freq)?;
if center_freq_f64 <= 0.0 || center_freq_f64 >= 0.5 {
return Err(NdimageError::InvalidInput(
"Center frequency must be between 0.0 and 0.5".into(),
));
}
let input_f64: Array2<f64> = input.mapv(|x| safe_to_f64(x).unwrap_or(0.0));
let fft_result = apply_2d_fft(&input_f64)?;
let filter = create_log_gabor_frequency_filter(
height,
width,
center_freq_f64,
safe_to_f64(bandwidth)?,
safe_to_f64(orientation)?,
safe_to_f64(angular_bandwidth)?,
)?;
let filtered_fft = apply_frequency_filter(&fft_result, &filter)?;
let result_f64 = apply_2d_ifft(&filtered_fft)?;
let output: Array2<T> =
result_f64.mapv(|x| safe_f64_to_float::<T>(x.re).unwrap_or_else(|_| T::zero()));
Ok(output)
}
#[derive(Debug, Clone, Copy)]
struct Complex64 {
re: f64,
im: f64,
}
impl Complex64 {
fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
fn zero() -> Self {
Self { re: 0.0, im: 0.0 }
}
fn mul(self, other: Self) -> Self {
Self {
re: self.re * other.re - self.im * other.im,
im: self.re * other.im + self.im * other.re,
}
}
fn magnitude_squared(self) -> f64 {
self.re * self.re + self.im * self.im
}
}
#[allow(dead_code)]
fn apply_2d_fft(input: &Array2<f64>) -> NdimageResult<Array2<Complex64>> {
let (height, width) = input.dim();
let mut result = Array2::from_elem((height, width), Complex64::zero());
for ((i, j), &val) in input.indexed_iter() {
result[[i, j]] = Complex64::new(val, 0.0);
}
for i in 0..height {
let mut row: Vec<Complex64> = (0..width).map(|j| result[[i, j]]).collect();
fft_1d_radix2(&mut row);
for (j, val) in row.into_iter().enumerate() {
result[[i, j]] = val;
}
}
for j in 0..width {
let mut col: Vec<Complex64> = (0..height).map(|i| result[[i, j]]).collect();
fft_1d_radix2(&mut col);
for (i, val) in col.into_iter().enumerate() {
result[[i, j]] = val;
}
}
Ok(result)
}
#[allow(dead_code)]
fn apply_2d_ifft(input: &Array2<Complex64>) -> NdimageResult<Array2<Complex64>> {
let (height, width) = input.dim();
let mut result = input.clone();
for i in 0..height {
let mut row: Vec<Complex64> = (0..width).map(|j| result[[i, j]]).collect();
ifft_1d_radix2(&mut row);
for (j, val) in row.into_iter().enumerate() {
result[[i, j]] = val;
}
}
for j in 0..width {
let mut col: Vec<Complex64> = (0..height).map(|i| result[[i, j]]).collect();
ifft_1d_radix2(&mut col);
for (i, val) in col.into_iter().enumerate() {
result[[i, j]] = val;
}
}
Ok(result)
}
#[allow(dead_code)]
fn fft_1d_radix2(data: &mut [Complex64]) {
let n = data.len();
if n <= 1 {
return;
}
let mut j = 0;
for i in 1..n {
let mut bit = n >> 1;
while j & bit != 0 {
j ^= bit;
bit >>= 1;
}
j ^= bit;
if i < j {
data.swap(i, j);
}
}
let mut length = 2;
while length <= n {
let angle = -2.0 * std::f64::consts::PI / length as f64;
let wlen = Complex64::new(angle.cos(), angle.sin());
for i in (0..n).step_by(length) {
let mut w = Complex64::new(1.0, 0.0);
for j in 0..length / 2 {
let u = data[i + j];
let v = data[i + j + length / 2].mul(w);
data[i + j] = Complex64::new(u.re + v.re, u.im + v.im);
data[i + j + length / 2] = Complex64::new(u.re - v.re, u.im - v.im);
w = w.mul(wlen);
}
}
length <<= 1;
}
}
#[allow(dead_code)]
fn ifft_1d_radix2(data: &mut [Complex64]) {
let n = data.len();
for item in data.iter_mut() {
item.im = -item.im;
}
fft_1d_radix2(data);
for item in data.iter_mut() {
item.im = -item.im;
item.re /= n as f64;
item.im /= n as f64;
}
}
#[allow(dead_code)]
fn create_log_gabor_frequency_filter(
height: usize,
width: usize,
center_freq: f64,
bandwidth: f64,
orientation: f64,
angular_bandwidth: f64,
) -> NdimageResult<Array2<f64>> {
let mut filter = Array2::zeros((height, width));
let _center_y = height as f64 / 2.0;
let _center_x = width as f64 / 2.0;
for i in 0..height {
for j in 0..width {
let fy = if i < height / 2 {
i as f64 / height as f64
} else {
(i as f64 - height as f64) / height as f64
};
let fx = if j < width / 2 {
j as f64 / width as f64
} else {
(j as f64 - width as f64) / width as f64
};
let radius = (fx * fx + fy * fy).sqrt();
let angle = fy.atan2(fx);
let radial_response = if radius > 1e-10 {
let log_radius = radius.log2();
let log_center = center_freq.log2();
let radial_diff = log_radius - log_center;
(-radial_diff * radial_diff / (2.0 * bandwidth * bandwidth)).exp()
} else {
0.0 };
let angle_diff = (angle - orientation).abs();
let angle_diff_wrapped = angle_diff.min(2.0 * std::f64::consts::PI - angle_diff);
let angular_response = (-angle_diff_wrapped * angle_diff_wrapped
/ (2.0 * angular_bandwidth * angular_bandwidth))
.exp();
filter[[i, j]] = radial_response * angular_response;
}
}
Ok(filter)
}
#[allow(dead_code)]
fn apply_frequency_filter(
fft_data: &Array2<Complex64>,
filter: &Array2<f64>,
) -> NdimageResult<Array2<Complex64>> {
if fft_data.dim() != filter.dim() {
return Err(NdimageError::DimensionError(
"FFT _data and filter must have the same dimensions".into(),
));
}
let result = Zip::from(fft_data)
.and(filter)
.map_collect(|&complex_val, &filter_val| {
Complex64::new(complex_val.re * filter_val, complex_val.im * filter_val)
});
Ok(result)
}
#[allow(dead_code)]
pub fn steerable_filter<T>(
input: &ArrayView2<T>,
filter_order: usize,
orientation: T,
sigma: T,
mode: Option<BorderMode>,
) -> NdimageResult<Array2<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Clone
+ Send
+ Sync
+ std::ops::AddAssign
+ std::ops::DivAssign
+ 'static,
{
if filter_order == 0 || filter_order > 3 {
return Err(NdimageError::InvalidInput(
"Steerable filter _order must be 1, 2, or 3".into(),
));
}
let border_mode = mode.unwrap_or(BorderMode::Reflect);
let basis_filters = generate_steerable_basis(filter_order, sigma)?;
let coeffs = compute_steering_coefficients(filter_order, orientation);
let mut result = Array2::zeros(input.dim());
for (basis_filter, coeff) in basis_filters.iter().zip(coeffs.iter()) {
let filtered = convolve(&input.to_owned(), basis_filter, Some(border_mode))?;
result = &result + &(filtered.mapv(|x| x * *coeff));
}
Ok(result)
}
#[allow(dead_code)]
fn generate_steerable_basis<T>(order: usize, sigma: T) -> NdimageResult<Vec<Array2<T>>>
where
T: Float + FromPrimitive + Debug,
{
let size = (sigma
* safe_f64_to_float::<T>(6.0)
.unwrap_or_else(|_| T::from_f64(6.0).unwrap_or_else(|| T::one())))
.to_usize()
.unwrap_or(15);
let size = if size % 2 == 0 { size + 1 } else { size };
let center = (size / 2) as isize;
let mut basis_filters = Vec::new();
match order {
1 => {
for angle in [0.0, std::f64::consts::PI / 2.0] {
let mut kernel = Array2::zeros((size, size));
let cos_theta = safe_f64_to_float::<T>(angle.cos())?;
let sin_theta = safe_f64_to_float::<T>(angle.sin())?;
for i in 0..size {
for j in 0..size {
let x: T = safe_isize_to_float(j as isize - center)?;
let y: T = safe_isize_to_float(i as isize - center)?;
let r_sq: T = x * x + y * y;
let gauss = (-r_sq / (safe_f64_to_float::<T>(2.0)? * sigma * sigma)).exp();
let deriv = if angle == 0.0 { x } else { y };
kernel[[i, j]] = -deriv * gauss / (sigma * sigma);
}
}
basis_filters.push(kernel);
}
}
2 => {
for angle in [0.0, std::f64::consts::PI / 4.0, std::f64::consts::PI / 2.0] {
let mut kernel = Array2::zeros((size, size));
for i in 0..size {
for j in 0..size {
let x: T = safe_isize_to_float(j as isize - center)?;
let y: T = safe_isize_to_float(i as isize - center)?;
let r_sq: T = x * x + y * y;
let gauss = (-r_sq / (safe_f64_to_float::<T>(2.0)? * sigma * sigma)).exp();
let sigma_sq = sigma * sigma;
let value = match angle as i32 {
0 => (x * x - sigma_sq) * gauss / (sigma_sq * sigma_sq), 1 => {
(safe_f64_to_float::<T>(2.0)? * x * y) * gauss
/ (sigma_sq * sigma_sq)
} _ => (y * y - sigma_sq) * gauss / (sigma_sq * sigma_sq), };
kernel[[i, j]] = value;
}
}
basis_filters.push(kernel);
}
}
3 => {
for angle_idx in 0..4 {
let angle = angle_idx as f64 * std::f64::consts::PI / 4.0; let mut kernel = Array2::zeros((size, size));
for i in 0..size {
for j in 0..size {
let x: T = safe_isize_to_float(j as isize - center)?;
let y: T = safe_isize_to_float(i as isize - center)?;
let r_sq: T = x * x + y * y;
let gauss = (-r_sq / (safe_f64_to_float::<T>(2.0)? * sigma * sigma)).exp();
let sigma_cubed = sigma * sigma * sigma;
let sigma_sq = sigma * sigma;
let value = match angle_idx {
0 => {
let x3 = x * x * x;
let term1 = x3 / (sigma_cubed * sigma_cubed);
let term2 = safe_f64_to_float::<T>(3.0)? * x / (sigma_cubed);
(term1 - term2) * gauss
}
1 => {
let x2y = x * x * y;
let term1 = x2y / (sigma_cubed * sigma_cubed);
let term2 = y / (sigma_cubed);
(term1 - term2) * gauss
}
2 => {
let xy2 = x * y * y;
let term1 = xy2 / (sigma_cubed * sigma_cubed);
let term2 = x / (sigma_cubed);
(term1 - term2) * gauss
}
_ => {
let y3 = y * y * y;
let term1 = y3 / (sigma_cubed * sigma_cubed);
let term2 = safe_f64_to_float::<T>(3.0)? * y / (sigma_cubed);
(term1 - term2) * gauss
}
};
kernel[[i, j]] = value;
}
}
basis_filters.push(kernel);
}
}
_ => unreachable!(),
}
Ok(basis_filters)
}
#[allow(dead_code)]
fn compute_steering_coefficients<T>(order: usize, orientation: T) -> Vec<T>
where
T: Float + FromPrimitive,
{
let cos_theta = orientation.cos();
let sin_theta = orientation.sin();
match order {
1 => vec![cos_theta, sin_theta],
2 => {
let cos2 = cos_theta * cos_theta;
let sin2 = sin_theta * sin_theta;
let cos_sin = cos_theta * sin_theta;
vec![
cos2,
safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()))
* cos_sin,
sin2,
]
}
3 => {
let cos3 = cos_theta * cos_theta * cos_theta;
let sin3 = sin_theta * sin_theta * sin_theta;
let cos2_sin = cos_theta * cos_theta * sin_theta;
let cos_sin2 = cos_theta * sin_theta * sin_theta;
vec![
cos3,
safe_f64_to_float::<T>(3.0)
.unwrap_or_else(|_| T::from_f64(3.0).unwrap_or_else(|| T::one()))
* cos2_sin,
safe_f64_to_float::<T>(3.0)
.unwrap_or_else(|_| T::from_f64(3.0).unwrap_or_else(|| T::one()))
* cos_sin2,
sin3,
]
}
_ => vec![T::one()], }
}
#[allow(dead_code)]
pub fn bilateral_gradient_filter<T>(
input: &ArrayView2<T>,
spatial_sigma: T,
range_sigma: T,
direction: char,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static + SimdUnifiedOps,
{
let (height, width) = input.dim();
let mut output = Array2::zeros((height, width));
let window_size = (spatial_sigma
* safe_f64_to_float::<T>(6.0)
.unwrap_or_else(|_| T::from_f64(6.0).unwrap_or_else(|| T::one())))
.to_usize()
.unwrap_or(7);
let window_size = if window_size % 2 == 0 {
window_size + 1
} else {
window_size
};
let half_window = window_size / 2;
let spatial_factor = -safe_f64_to_float::<T>(0.5)
.unwrap_or_else(|_| T::from_f64(0.5).unwrap_or_else(|| T::one()))
/ (spatial_sigma * spatial_sigma);
let range_factor = -safe_f64_to_float::<T>(0.5)
.unwrap_or_else(|_| T::from_f64(0.5).unwrap_or_else(|| T::one()))
/ (range_sigma * range_sigma);
for i in half_window..height - half_window {
for j in half_window..width - half_window {
let center_val = input[[i, j]];
let mut weighted_sum = T::zero();
let mut weight_sum = T::zero();
for di in 0..window_size {
for dj in 0..window_size {
let ni = i - half_window + di;
let nj = j - half_window + dj;
let neighbor_val = input[[ni, nj]];
let dx = safe_isize_to_float((dj as isize) - (half_window as isize))
.unwrap_or_else(|_| T::zero());
let dy = safe_isize_to_float((di as isize) - (half_window as isize))
.unwrap_or_else(|_| T::zero());
let spatial_dist = dx * dx + dy * dy;
let spatial_weight = (spatial_dist * spatial_factor).exp();
let range_diff = neighbor_val - center_val;
let range_weight = (range_diff * range_diff * range_factor).exp();
let total_weight = spatial_weight * range_weight;
let gradient_val = match direction {
'x' => dx * neighbor_val,
'y' => dy * neighbor_val,
_ => (dx * dx + dy * dy).sqrt() * neighbor_val, };
weighted_sum = weighted_sum + total_weight * gradient_val;
weight_sum = weight_sum + total_weight;
}
}
output[[i, j]] = if weight_sum > T::zero() {
weighted_sum / weight_sum
} else {
T::zero()
};
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn anisotropic_diffusion<T>(
input: &ArrayView2<T>,
num_iterations: usize,
kappa: T,
gamma: T,
option: usize,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static,
{
if option != 1 && option != 2 {
return Err(NdimageError::InvalidInput(
"Diffusion option must be 1 or 2".into(),
));
}
let (height, width) = input.dim();
let mut image = input.to_owned();
let gamma_quarter = gamma
/ safe_f64_to_float::<T>(4.0)
.unwrap_or_else(|_| T::from_f64(4.0).unwrap_or_else(|| T::one()));
for _ in 0..num_iterations {
let mut newimage = image.clone();
for i in 1..height - 1 {
for j in 1..width - 1 {
let grad_n = image[[i - 1, j]] - image[[i, j]];
let grad_s = image[[i + 1, j]] - image[[i, j]];
let grad_e = image[[i, j + 1]] - image[[i, j]];
let grad_w = image[[i, j - 1]] - image[[i, j]];
let c_n = diffusion_function(grad_n, kappa, option);
let c_s = diffusion_function(grad_s, kappa, option);
let c_e = diffusion_function(grad_e, kappa, option);
let c_w = diffusion_function(grad_w, kappa, option);
let update =
gamma_quarter * (c_n * grad_n + c_s * grad_s + c_e * grad_e + c_w * grad_w);
newimage[[i, j]] = image[[i, j]] + update;
}
}
image = newimage;
}
Ok(image)
}
#[allow(dead_code)]
fn diffusion_function<T>(gradient: T, kappa: T, option: usize) -> T
where
T: Float + FromPrimitive,
{
match option {
1 => {
let ratio = gradient / kappa;
(-ratio * ratio).exp()
}
2 => {
let ratio = gradient / kappa;
T::one() / (T::one() + ratio * ratio)
}
_ => T::one(),
}
}
#[allow(dead_code)]
pub fn non_local_means<T>(
input: &ArrayView2<T>,
h: T,
patch_size: usize,
search_window: usize,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static + SimdUnifiedOps,
{
let (height, width) = input.dim();
let mut output = Array2::zeros((height, width));
let patch_radius = patch_size / 2;
let search_radius = search_window / 2;
let h_squared = h * h;
let patch_area = safe_usize_to_float(patch_size * patch_size).unwrap_or_else(|_| T::one());
for i in patch_radius..height - patch_radius {
for j in patch_radius..width - patch_radius {
let mut weighted_sum = T::zero();
let mut weight_sum = T::zero();
let search_i_min = (i.saturating_sub(search_radius)).max(patch_radius);
let search_i_max = (i + search_radius + 1).min(height - patch_radius);
let search_j_min = (j.saturating_sub(search_radius)).max(patch_radius);
let search_j_max = (j + search_radius + 1).min(width - patch_radius);
for search_i in search_i_min..search_i_max {
for search_j in search_j_min..search_j_max {
let mut patch_distance = T::zero();
for di in 0..patch_size {
for dj in 0..patch_size {
let pi = i - patch_radius + di;
let pj = j - patch_radius + dj;
let si = search_i - patch_radius + di;
let sj = search_j - patch_radius + dj;
if pi < height && pj < width && si < height && sj < width {
let diff = input[[pi, pj]] - input[[si, sj]];
patch_distance = patch_distance + diff * diff;
}
}
}
patch_distance = patch_distance / patch_area;
let weight = (-patch_distance.max(T::zero()) / h_squared).exp();
weighted_sum = weighted_sum + weight * input[[search_i, search_j]];
weight_sum = weight_sum + weight;
}
}
output[[i, j]] = if weight_sum > T::zero() {
weighted_sum / weight_sum
} else {
input[[i, j]]
};
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn adaptive_wiener_filter<T>(
input: &ArrayView2<T>,
noise_variance: T,
window_size: usize,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static,
{
let (height, width) = input.dim();
let mut output = Array2::zeros((height, width));
let half_window = window_size / 2;
for i in half_window..height - half_window {
for j in half_window..width - half_window {
let mut local_values = Vec::with_capacity(window_size * window_size);
for di in 0..window_size {
for dj in 0..window_size {
let y = i - half_window + di;
let x = j - half_window + dj;
local_values.push(input[[y, x]]);
}
}
let local_mean = local_values
.iter()
.cloned()
.fold(T::zero(), |acc, x| acc + x)
/ safe_usize_to_float(local_values.len()).unwrap_or_else(|_| T::one());
let local_variance = local_values
.iter()
.map(|&x| (x - local_mean) * (x - local_mean))
.fold(T::zero(), |acc, x| acc + x)
/ safe_usize_to_float(local_values.len()).unwrap_or_else(|_| T::one());
let signal_variance = (local_variance - noise_variance).max(T::zero());
let wiener_gain = if local_variance > T::zero() {
signal_variance / local_variance
} else {
T::zero()
};
let center_value = input[[i, j]];
output[[i, j]] = local_mean + wiener_gain * (center_value - local_mean);
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn shock_filter<T>(
input: &ArrayView2<T>,
num_iterations: usize,
dt: T,
sigma: T,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static,
{
let mut image = input.to_owned();
for _ in 0..num_iterations {
let newimage = shock_filter_iteration(&image.view(), dt, sigma)?;
image = newimage;
}
Ok(image)
}
#[allow(dead_code)]
fn shock_filter_iteration<T>(input: &ArrayView2<T>, dt: T, sigma: T) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static,
{
let (height, width) = input.dim();
let mut output = input.to_owned();
let input_f64 = input.mapv(|x| x.to_f64().unwrap_or(0.0));
let sigma_f64 = sigma.to_f64().unwrap_or(1.0);
let smoothed_f64 = crate::filters::gaussian_filter(&input_f64, sigma_f64, None, None)?;
let smoothed = smoothed_f64.mapv(|x| T::from_f64(x).unwrap_or_else(|| T::zero()));
for i in 1..height - 1 {
for j in 1..width - 1 {
let grad_x = (smoothed[[i, j + 1]] - smoothed[[i, j - 1]])
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let grad_y = (smoothed[[i + 1, j]] - smoothed[[i - 1, j]])
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let grad_magnitude = (grad_x * grad_x + grad_y * grad_y).sqrt();
if grad_magnitude > T::zero() {
let grad_x_norm = grad_x / grad_magnitude;
let grad_y_norm = grad_y / grad_magnitude;
let uxx = input[[i, j + 1]]
- safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()))
* input[[i, j]]
+ input[[i, j - 1]];
let uyy = input[[i + 1, j]]
- safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()))
* input[[i, j]]
+ input[[i - 1, j]];
let uxy = (input[[i + 1, j + 1]] - input[[i + 1, j - 1]] - input[[i - 1, j + 1]]
+ input[[i - 1, j - 1]])
/ safe_f64_to_float::<T>(4.0)
.unwrap_or_else(|_| T::from_f64(4.0).unwrap_or_else(|| T::one()));
let directional_curvature = grad_x_norm * grad_x_norm * uxx
+ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()))
* grad_x_norm
* grad_y_norm
* uxy
+ grad_y_norm * grad_y_norm * uyy;
let sign = if directional_curvature < T::zero() {
-T::one()
} else {
T::one()
};
let update = dt * sign * grad_magnitude;
output[[i, j]] = input[[i, j]] + update;
}
}
}
Ok(output)
}
#[allow(dead_code)]
pub fn coherence_enhancing_diffusion<T>(
input: &ArrayView2<T>,
num_iterations: usize,
alpha: T,
sigma: T,
c: T,
) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone + Send + Sync + 'static,
{
let mut image = input.to_owned();
let image_f64 = image.mapv(|x| x.to_f64().unwrap_or(0.0));
let sigma_f64 = sigma.to_f64().unwrap_or(1.0);
let smoothed_f64 = crate::filters::gaussian_filter(&image_f64, sigma_f64, None, None)?;
image = smoothed_f64.mapv(|x| T::from_f64(x).unwrap_or_else(|| T::zero()));
for _ in 0..num_iterations {
let newimage = ced_iteration(&image.view(), alpha, c)?;
image = newimage;
}
Ok(image)
}
#[allow(dead_code)]
fn ced_iteration<T>(input: &ArrayView2<T>, alpha: T, c: T) -> NdimageResult<Array2<T>>
where
T: Float + FromPrimitive + Debug + Clone,
{
let (height, width) = input.dim();
let mut output = input.to_owned();
let dt = safe_f64_to_float::<T>(0.25)
.unwrap_or_else(|_| T::from_f64(0.25).unwrap_or_else(|| T::one()));
for i in 1..height - 1 {
for j in 1..width - 1 {
let grad_x = (input[[i, j + 1]] - input[[i, j - 1]])
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let grad_y = (input[[i + 1, j]] - input[[i - 1, j]])
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let j11 = grad_x * grad_x;
let j12 = grad_x * grad_y;
let j22 = grad_y * grad_y;
let trace = j11 + j22;
let det = j11 * j22 - j12 * j12;
let discriminant = (trace * trace
- safe_f64_to_float::<T>(4.0)
.unwrap_or_else(|_| T::from_f64(4.0).unwrap_or_else(|| T::one()))
* det)
.max(T::zero())
.sqrt();
let lambda1 = (trace + discriminant)
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let lambda2 = (trace - discriminant)
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
let coherence = if lambda1 > T::zero() {
(lambda1 - lambda2) / lambda1
} else {
T::zero()
};
let d1 = alpha;
let d2 = alpha + (T::one() - alpha) * (-(coherence * coherence / (c * c))).exp();
let laplacian =
input[[i + 1, j]] + input[[i - 1, j]] + input[[i, j + 1]] + input[[i, j - 1]]
- safe_f64_to_float::<T>(4.0)
.unwrap_or_else(|_| T::from_f64(4.0).unwrap_or_else(|| T::one()))
* input[[i, j]];
let avg_diffusivity = (d1 + d2)
/ safe_f64_to_float::<T>(2.0)
.unwrap_or_else(|_| T::from_f64(2.0).unwrap_or_else(|| T::one()));
output[[i, j]] = input[[i, j]] + dt * avg_diffusivity * laplacian;
}
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_gabor_filter() {
let input = array![
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0]
];
let params = GaborParams {
wavelength: 4.0,
orientation: 0.0,
sigma_x: 1.0,
sigma_y: 1.0,
phase: 0.0,
aspect_ratio: None,
};
let result = gabor_filter(&input.view(), ¶ms, Some(5), None).expect("Operation failed");
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_gabor_filter_bank() {
let input = array![[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 1.0]];
let params = GaborParams::default();
let results =
gabor_filter_bank(&input.view(), ¶ms, 4, Some(3), None).expect("Operation failed");
assert_eq!(results.len(), 4);
for result in results {
assert_eq!(result.shape(), input.shape());
}
}
#[test]
fn test_steerable_filter() {
let input = array![[0.0, 1.0, 0.0], [1.0, 2.0, 1.0], [0.0, 1.0, 0.0]];
let result = steerable_filter(&input.view(), 1, std::f64::consts::PI / 4.0, 1.0, None)
.expect("Operation failed");
assert_eq!(result.shape(), input.shape());
}
#[test]
fn test_bilateral_gradient_filter() {
let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let result =
bilateral_gradient_filter(&input.view(), 1.0, 2.0, 'x').expect("Operation failed");
assert_eq!(result.shape(), input.shape());
}
}