use ndarray::{ArrayBase, ArrayD, ArrayView1, AsArray, Dimension, Ix1, Ix2, IxDyn, ViewRepr};
use rayon::prelude::*;
use crate::prelude::*;
#[inline]
pub fn gaussian_metaballs<'a, T, A, B>(
centers: A,
radii: B,
intensities: B,
falloffs: B,
background: T,
shape: &[usize],
threads: Option<usize>,
) -> Result<ArrayD<f64>, ImgalError>
where
A: AsArray<'a, T, Ix2>,
B: AsArray<'a, T, Ix1>,
T: 'a + AsNumeric,
{
let centers: ArrayBase<ViewRepr<&'a T>, Ix2> = centers.into();
let radii: ArrayBase<ViewRepr<&'a T>, Ix1> = radii.into();
let intensities: ArrayBase<ViewRepr<&'a T>, Ix1> = intensities.into();
let falloffs: ArrayBase<ViewRepr<&'a T>, Ix1> = falloffs.into();
let background = background.to_f64();
let (n_blobs, n_dims) = centers.dim();
if n_blobs != radii.len() {
return Err(ImgalError::MismatchedArrayLengths {
a_arr_name: "centers",
a_arr_len: n_blobs,
b_arr_name: "radii",
b_arr_len: radii.len(),
});
};
if n_blobs != intensities.len() {
return Err(ImgalError::MismatchedArrayLengths {
a_arr_name: "centers",
a_arr_len: n_blobs,
b_arr_name: "radii",
b_arr_len: intensities.len(),
});
}
if n_dims != shape.len() {
return Err(ImgalError::MismatchedDimensionLengths {
a_name: "centers",
a_dim_len: n_dims,
b_name: "shape",
b_dim_len: shape.len(),
});
}
let gauss_contrib_calc = |p: IxDyn| {
(0..n_blobs).fold(background, |acc, i| {
acc.max(gaussian_contribution(
p.as_array_view(),
centers.row(i),
radii[i],
intensities[i],
falloffs[i].to_f64(),
))
})
};
let mut blobs_arr = ArrayD::from_elem(shape, background);
par!(threads,
seq_exp: blobs_arr.indexed_iter_mut()
.for_each(|(p, v)| *v = gauss_contrib_calc(p)),
par_exp: blobs_arr.indexed_iter_mut()
.par_bridge()
.for_each(|(p, v)| *v = gauss_contrib_calc(p)));
Ok(blobs_arr)
}
#[inline]
pub fn logistic_metaballs<'a, T, A, B>(
centers: A,
radii: B,
intensities: B,
falloffs: B,
background: T,
shape: &[usize],
threads: Option<usize>,
) -> Result<ArrayD<f64>, ImgalError>
where
A: AsArray<'a, T, Ix2>,
B: AsArray<'a, T, Ix1>,
T: 'a + AsNumeric,
{
let centers: ArrayBase<ViewRepr<&'a T>, Ix2> = centers.into();
let radii: ArrayBase<ViewRepr<&'a T>, Ix1> = radii.into();
let intensities: ArrayBase<ViewRepr<&'a T>, Ix1> = intensities.into();
let falloffs: ArrayBase<ViewRepr<&'a T>, Ix1> = falloffs.into();
let background = background.to_f64();
let (n_blobs, n_dims) = centers.dim();
if n_blobs != radii.len() {
return Err(ImgalError::MismatchedArrayLengths {
a_arr_name: "centers",
a_arr_len: n_blobs,
b_arr_name: "radii",
b_arr_len: radii.len(),
});
};
if n_blobs != intensities.len() {
return Err(ImgalError::MismatchedArrayLengths {
a_arr_name: "centers",
a_arr_len: n_blobs,
b_arr_name: "radii",
b_arr_len: intensities.len(),
});
}
if n_dims != shape.len() {
return Err(ImgalError::MismatchedDimensionLengths {
a_name: "centers",
a_dim_len: n_dims,
b_name: "shape",
b_dim_len: shape.len(),
});
}
let logi_contrib_calc = |p: IxDyn| {
(0..n_blobs).fold(background, |acc, i| {
acc.max(logistic_contribution(
p.as_array_view(),
centers.row(i),
radii[i],
intensities[i],
falloffs[i].to_f64(),
))
})
};
let mut blobs_arr = ArrayD::from_elem(shape, background);
par!(threads,
seq_exp: blobs_arr.indexed_iter_mut()
.for_each(|(p, v)| *v = logi_contrib_calc(p)),
par_exp: blobs_arr.indexed_iter_mut()
.par_bridge()
.for_each(|(p, v)| *v = logi_contrib_calc(p)));
Ok(blobs_arr)
}
fn gaussian_contribution<T>(
current_pnt: ArrayView1<usize>,
center_pnt: ArrayView1<T>,
radius: T,
intensity: T,
falloff: f64,
) -> f64
where
T: AsNumeric,
{
let dist_sq = current_pnt
.iter()
.zip(center_pnt.iter())
.map(|(&cur, &cen)| {
let diff = cur as f64 - cen.to_f64();
diff * diff
})
.sum::<f64>();
let sigma_sq = (radius * radius).to_f64();
intensity.to_f64() * (-dist_sq / (falloff * sigma_sq)).exp()
}
fn logistic_contribution<T>(
current_pnt: ArrayView1<usize>,
center_pnt: ArrayView1<T>,
radius: T,
intensity: T,
falloff: f64,
) -> f64
where
T: AsNumeric,
{
let dist: f64 = current_pnt
.iter()
.zip(center_pnt.iter())
.map(|(&cur, &cen)| {
let diff = cur as f64 - cen.to_f64();
diff * diff
})
.sum::<f64>()
.sqrt();
let k = falloff.max(1e-12);
let expo = ((dist - radius.to_f64()) / k).exp();
let soft = 1.0 / (1.0 + expo);
intensity.to_f64() * soft
}