#[cfg(test)]
mod tests;
use alloc::vec::Vec;
use crate::real::Real;
use crate::shape::{RuntimeShape3, Shape3};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Downsample {
Decimate,
Mean,
Tent,
Min,
}
const RADIUS: i64 = 1;
impl Downsample {
const fn weights(self) -> Option<[f64; 3]> {
match self {
Self::Decimate => Some([0.0, 1.0, 0.0]),
Self::Mean => Some([1.0, 1.0, 1.0]),
Self::Tent => Some([1.0, 2.0, 1.0]),
Self::Min => None,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Decimate => "decimate",
Self::Mean => "mean",
Self::Tent => "tent",
Self::Min => "min",
}
}
pub const ALL: [Self; 4] = [Self::Decimate, Self::Mean, Self::Tent, Self::Min];
}
pub fn downsample<R: Real>(
fine: &[R],
shape: &impl Shape3,
op: Downsample,
) -> crate::Result<(Vec<R>, RuntimeShape3)> {
let size = shape.size();
for n in size {
if n < 3 || !(n - 1).is_power_of_two() {
return Err(crate::Error::GridTooSmall { size });
}
}
if fine.len() != shape.element_count() {
return Err(crate::Error::ShapeOverflow {
size,
product: fine.len() as u64,
});
}
let coarse_size = [size[0] / 2 + 1, size[1] / 2 + 1, size[2] / 2 + 1];
let coarse_shape = RuntimeShape3::new(coarse_size)?;
let (nx, ny, nz) = (size[0] as i64, size[1] as i64, size[2] as i64);
let at = |x: i64, y: i64, z: i64| -> R {
let c = |v: i64, n: i64| v.clamp(0, n - 1);
fine[((c(z, nz) * ny + c(y, ny)) * nx + c(x, nx)) as usize]
};
let weights = op.weights();
let mut out = Vec::with_capacity(coarse_shape.element_count());
for cz in 0..coarse_size[2] as i64 {
for cy in 0..coarse_size[1] as i64 {
for cx in 0..coarse_size[0] as i64 {
let (fx, fy, fz) = (cx * 2, cy * 2, cz * 2);
let value = match weights {
Some(w) => {
let mut sum = R::ZERO;
let mut total = R::ZERO;
for dz in -RADIUS..=RADIUS {
for dy in -RADIUS..=RADIUS {
for dx in -RADIUS..=RADIUS {
let k = w[(dx + RADIUS) as usize]
* w[(dy + RADIUS) as usize]
* w[(dz + RADIUS) as usize];
if k == 0.0 {
continue;
}
let k = R::from_f64(k);
sum += at(fx + dx, fy + dy, fz + dz) * k;
total += k;
}
}
}
sum / total
}
None => {
let mut best = at(fx, fy, fz);
for dz in -RADIUS..=RADIUS {
for dy in -RADIUS..=RADIUS {
for dx in -RADIUS..=RADIUS {
let v = at(fx + dx, fy + dy, fz + dz);
if v < best {
best = v;
}
}
}
}
best
}
};
out.push(value);
}
}
}
Ok((out, coarse_shape))
}