use scirs2_core::ndarray::{Array, Array1, Array2, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive, NumAssign};
use std::fmt::Debug;
use crate::error::{NdimageError, NdimageResult};
use crate::utils::safe_usize_to_float;
#[allow(dead_code)]
pub fn center_of_mass<T, D>(input: &Array<T, D>) -> NdimageResult<Vec<T>>
where
T: Float + FromPrimitive + Debug + NumAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if input.is_empty() {
return Err(NdimageError::InvalidInput("Input array is empty".into()));
}
let ndim = input.ndim();
let shape = input.shape();
let total_mass = input.sum();
if total_mass == T::zero() {
let center: Result<Vec<T>, NdimageError> = shape
.iter()
.map(|&dim| safe_usize_to_float::<T>(dim).map(|dim_t| dim_t / (T::one() + T::one())))
.collect();
let center = center?;
return Ok(center);
}
let mut center_of_mass = vec![T::zero(); ndim];
let input_dyn = input.clone().into_dyn();
for (idx, &value) in input_dyn.indexed_iter() {
if value != T::zero() {
for (dim, &coord) in idx.as_array_view().iter().enumerate() {
let coord_t = safe_usize_to_float::<T>(coord)?;
center_of_mass[dim] += coord_t * value;
}
}
}
for coord in center_of_mass.iter_mut() {
*coord /= total_mass;
}
Ok(center_of_mass)
}
#[allow(dead_code)]
pub fn moments_inertia_tensor<T, D>(
input: &Array<T, D>,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix2>>
where
T: Float + FromPrimitive + Debug + NumAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
let dim = input.ndim();
Ok(Array2::<T>::zeros((dim, dim)))
}
#[allow(dead_code)]
pub fn moments<T, D>(
input: &Array<T, D>,
order: usize,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix1>>
where
T: Float + FromPrimitive + Debug + NumAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
let ndim = input.ndim();
if ndim == 2 {
let mut moments_vec = Vec::new();
let input_2d = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| NdimageError::DimensionError("Expected 2D array for 2D moments".into()))?;
for p in 0..=order {
for q in 0..=order {
let mut moment = T::zero();
for (row, col) in scirs2_core::ndarray::indices(input_2d.dim()) {
let value = input_2d[[row, col]];
if value != T::zero() {
let x = safe_usize_to_float::<T>(col)?;
let y = safe_usize_to_float::<T>(row)?;
let x_power = if p == 0 { T::one() } else { x.powi(p as i32) };
let y_power = if q == 0 { T::one() } else { y.powi(q as i32) };
moment += x_power * y_power * value;
}
}
moments_vec.push(moment);
}
}
let total_moments = (order + 1) * (order + 1);
Array1::<T>::from_vec(moments_vec)
.into_shape_with_order((total_moments,))
.map_err(|_| NdimageError::ComputationError("Failed to reshape moments array".into()))
} else {
let mut moments_vec = Vec::new();
moments_vec.push(input.sum());
let center = center_of_mass(input)?;
let total_mass = input.sum();
for dim in 0..ndim {
moments_vec.push(center[dim] * total_mass);
}
let expected_size = (order + 1).pow(ndim as u32);
while moments_vec.len() < expected_size {
moments_vec.push(T::zero());
}
Array1::<T>::from_vec(moments_vec)
.into_shape_with_order((expected_size,))
.map_err(|_| NdimageError::ComputationError("Failed to reshape moments array".into()))
}
}
#[allow(dead_code)]
pub fn central_moments<T, D>(
input: &Array<T, D>,
order: usize,
center: Option<&[T]>,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix1>>
where
T: Float + FromPrimitive + Debug + NumAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
if let Some(c) = center {
if c.len() != input.ndim() {
return Err(NdimageError::DimensionError(format!(
"Center must have same length as input dimensions (got {} expected {})",
c.len(),
input.ndim()
)));
}
}
let ndim = input.ndim();
let center_coords = if let Some(c) = center {
c.to_vec()
} else {
center_of_mass(input)?
};
if ndim == 2 {
let mut central_moments_vec = Vec::new();
let input_2d = input
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|_| {
NdimageError::DimensionError("Expected 2D array for 2D central moments".into())
})?;
let cx = center_coords[1]; let cy = center_coords[0];
for p in 0..=order {
for q in 0..=order {
let mut moment = T::zero();
for (row, col) in scirs2_core::ndarray::indices(input_2d.dim()) {
let value = input_2d[[row, col]];
if value != T::zero() {
let x = safe_usize_to_float::<T>(col)?;
let y = safe_usize_to_float::<T>(row)?;
let dx = x - cx;
let dy = y - cy;
let x_power = if p == 0 { T::one() } else { dx.powi(p as i32) };
let y_power = if q == 0 { T::one() } else { dy.powi(q as i32) };
moment += x_power * y_power * value;
}
}
central_moments_vec.push(moment);
}
}
let total_moments = (order + 1) * (order + 1);
Array1::<T>::from_vec(central_moments_vec)
.into_shape_with_order((total_moments,))
.map_err(|_| {
NdimageError::ComputationError("Failed to reshape central moments array".into())
})
} else {
let mut central_moments_vec = Vec::new();
central_moments_vec.push(input.sum());
for _ in 0..ndim {
central_moments_vec.push(T::zero());
}
if order >= 2 {
let _total_mass = input.sum();
let input_dyn = input.clone().into_dyn();
for dim1 in 0..ndim {
for dim2 in dim1..ndim {
let mut moment = T::zero();
for (idx, &value) in input_dyn.indexed_iter() {
if value != T::zero() {
let coord1 = safe_usize_to_float::<T>(idx.as_array_view()[dim1])?;
let coord2 = safe_usize_to_float::<T>(idx.as_array_view()[dim2])?;
let dc1 = coord1 - center_coords[dim1];
let dc2 = coord2 - center_coords[dim2];
moment += dc1 * dc2 * value;
}
}
central_moments_vec.push(moment);
}
}
}
let expected_size = (order + 1).pow(ndim as u32);
while central_moments_vec.len() < expected_size {
central_moments_vec.push(T::zero());
}
Array1::<T>::from_vec(central_moments_vec)
.into_shape_with_order((expected_size,))
.map_err(|_| {
NdimageError::ComputationError("Failed to reshape central moments array".into())
})
}
}
#[allow(dead_code)]
pub fn normalized_moments<T, D>(
input: &Array<T, D>,
order: usize,
) -> NdimageResult<Array<T, scirs2_core::ndarray::Ix1>>
where
T: Float + FromPrimitive + Debug + NumAssign + std::ops::DivAssign + 'static,
D: Dimension + 'static,
{
if input.ndim() == 0 {
return Err(NdimageError::InvalidInput(
"Input array cannot be 0-dimensional".into(),
));
}
let central_moments = central_moments(input, order, None)?;
let ndim = input.ndim();
if ndim == 2 {
let mut normalized_moments_vec = Vec::new();
let mu_00 = central_moments[0];
if mu_00 == T::zero() {
let total_moments = (order + 1) * (order + 1);
return Ok(Array1::<T>::zeros(total_moments));
}
for p in 0..=order {
for q in 0..=order {
let moment_idx = p * (order + 1) + q;
let mu_pq = central_moments[moment_idx];
if p == 0 && q == 0 {
normalized_moments_vec.push(T::one());
} else {
let gamma = (p + q) as f64 / 2.0 + 1.0;
let gamma_t = T::from_f64(gamma).ok_or_else(|| {
NdimageError::ComputationError(
"Failed to convert gamma to float type".into(),
)
})?;
let normalizer = mu_00.powf(gamma_t);
let eta_pq = if normalizer != T::zero() {
mu_pq / normalizer
} else {
T::zero()
};
normalized_moments_vec.push(eta_pq);
}
}
}
let total_moments = (order + 1) * (order + 1);
Array1::<T>::from_vec(normalized_moments_vec)
.into_shape_with_order((total_moments,))
.map_err(|_| {
NdimageError::ComputationError("Failed to reshape normalized moments array".into())
})
} else {
let mut normalized_moments_vec = Vec::new();
let mu_00 = central_moments[0];
if mu_00 == T::zero() {
let expected_size = (order + 1).pow(ndim as u32);
return Ok(Array1::<T>::zeros(expected_size));
}
normalized_moments_vec.push(T::one());
for _ in 0..ndim {
normalized_moments_vec.push(T::zero());
}
for i in (ndim + 1)..central_moments.len() {
let mu_i = central_moments[i];
let eta_i = if mu_00 != T::zero() {
mu_i / (mu_00 * mu_00) } else {
T::zero()
};
normalized_moments_vec.push(eta_i);
}
let expected_size = (order + 1).pow(ndim as u32);
while normalized_moments_vec.len() < expected_size {
normalized_moments_vec.push(T::zero());
}
Array1::<T>::from_vec(normalized_moments_vec)
.into_shape_with_order((expected_size,))
.map_err(|_| {
NdimageError::ComputationError("Failed to reshape normalized moments array".into())
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_center_of_mass() {
let input: Array2<f64> = Array2::eye(3);
let com = center_of_mass(&input).expect("center_of_mass should succeed for test");
assert_eq!(com.len(), input.ndim());
}
#[test]
fn test_moments_inertia_tensor() {
let input: Array2<f64> = Array2::eye(3);
let tensor =
moments_inertia_tensor(&input).expect("moments_inertia_tensor should succeed for test");
assert_eq!(tensor.shape(), &[input.ndim(), input.ndim()]);
}
#[test]
fn test_moments() {
let input: Array2<f64> = Array2::eye(3);
let order = 2;
let mom = moments(&input, order).expect("moments should succeed for test");
assert!(!mom.is_empty());
}
}