use crate::{SignalError, convolution::validate_real_signal};
const SQRT_2: f64 = std::f64::consts::SQRT_2;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BoundaryMode {
#[default]
Periodic,
Symmetric,
Zero,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Wavelet {
#[default]
Haar,
LeGall53,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WaveletPlan {
pub wavelet: Wavelet,
pub levels: usize,
pub boundary: BoundaryMode,
}
impl WaveletPlan {
pub const fn new(wavelet: Wavelet, levels: usize) -> Self {
Self {
wavelet,
levels,
boundary: BoundaryMode::Periodic,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct WaveletLevel {
pub input_len: usize,
pub detail: Vec<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WaveletCoefficients {
pub approximation: Vec<f64>,
pub levels: Vec<WaveletLevel>,
pub wavelet: Wavelet,
pub boundary: BoundaryMode,
pub original_len: usize,
}
pub fn dwt(signal: &[f64], plan: &WaveletPlan) -> Result<WaveletCoefficients, SignalError> {
validate_real_signal(signal)?;
validate_plan(signal.len(), plan)?;
let mut approximation = signal.to_vec();
let mut levels = Vec::with_capacity(plan.levels);
for _ in 0..plan.levels {
let input_len = approximation.len();
let (next, detail) = match plan.wavelet {
Wavelet::Haar => haar_analysis(&approximation),
Wavelet::LeGall53 => legall_analysis(&approximation, plan.boundary),
};
validate_real_signal(&next)?;
validate_real_signal(&detail)?;
approximation = next;
levels.push(WaveletLevel { input_len, detail });
}
Ok(WaveletCoefficients {
approximation,
levels,
wavelet: plan.wavelet,
boundary: plan.boundary,
original_len: signal.len(),
})
}
pub fn idwt(
coefficients: &WaveletCoefficients,
plan: &WaveletPlan,
) -> Result<Vec<f64>, SignalError> {
if coefficients.wavelet != plan.wavelet || coefficients.boundary != plan.boundary {
return Err(SignalError::InvalidPolicy {
policy: "wavelet synthesis",
reason: "the synthesis plan must match the coefficient policy",
});
}
if coefficients.levels.len() != plan.levels {
return Err(SignalError::LengthMismatch {
expected: plan.levels,
actual: coefficients.levels.len(),
});
}
validate_real_signal(&coefficients.approximation)?;
let mut approximation = coefficients.approximation.clone();
for level in coefficients.levels.iter().rev() {
validate_real_signal(&level.detail)?;
approximation = match plan.wavelet {
Wavelet::Haar => haar_synthesis(&approximation, &level.detail, level.input_len)?,
Wavelet::LeGall53 => legall_synthesis(
&approximation,
&level.detail,
level.input_len,
plan.boundary,
)?,
};
}
if approximation.len() != coefficients.original_len {
return Err(SignalError::LengthMismatch {
expected: coefficients.original_len,
actual: approximation.len(),
});
}
validate_real_signal(&approximation)?;
Ok(approximation)
}
fn validate_plan(len: usize, plan: &WaveletPlan) -> Result<(), SignalError> {
if len < 2 {
return Err(SignalError::InvalidLength {
len,
reason: "a wavelet transform requires at least two samples",
});
}
if plan.levels == 0 {
return Err(SignalError::InvalidPolicy {
policy: "wavelet levels",
reason: "at least one decomposition level is required",
});
}
let mut available = len;
for _ in 0..plan.levels {
if available < 2 {
return Err(SignalError::InvalidPolicy {
policy: "wavelet levels",
reason: "the requested depth exceeds the available approximation samples",
});
}
available = available.div_ceil(2);
}
Ok(())
}
fn haar_analysis(input: &[f64]) -> (Vec<f64>, Vec<f64>) {
let mut approximation = Vec::with_capacity(input.len().div_ceil(2));
let mut detail = Vec::with_capacity(input.len() / 2);
let pairs = input.len() / 2;
for pair in 0..pairs {
let even = input[2 * pair];
let odd = input[2 * pair + 1];
approximation.push((even + odd) / SQRT_2);
detail.push((even - odd) / SQRT_2);
}
if input.len() % 2 == 1 {
approximation.push(input[input.len() - 1]);
}
(approximation, detail)
}
fn haar_synthesis(
approximation: &[f64],
detail: &[f64],
input_len: usize,
) -> Result<Vec<f64>, SignalError> {
let expected_approximation = input_len.div_ceil(2);
let expected_detail = input_len / 2;
validate_level_lengths(
approximation,
detail,
expected_approximation,
expected_detail,
)?;
let mut output = Vec::with_capacity(input_len);
for (&approximation, &detail) in approximation.iter().zip(detail) {
output.push((approximation + detail) / SQRT_2);
output.push((approximation - detail) / SQRT_2);
}
if input_len % 2 == 1 {
output.push(approximation[expected_approximation - 1]);
}
Ok(output)
}
fn legall_analysis(input: &[f64], boundary: BoundaryMode) -> (Vec<f64>, Vec<f64>) {
let mut approximation = input.iter().step_by(2).copied().collect::<Vec<_>>();
let mut detail = input.iter().skip(1).step_by(2).copied().collect::<Vec<_>>();
for index in 0..detail.len() {
detail[index] -=
0.5 * (approximation[index] + extended(&approximation, index as isize + 1, boundary));
}
if !detail.is_empty() {
for (index, value) in approximation.iter_mut().enumerate() {
*value += 0.25
* (extended(&detail, index as isize - 1, boundary)
+ extended(&detail, index as isize, boundary));
}
}
(approximation, detail)
}
fn legall_synthesis(
approximation: &[f64],
detail: &[f64],
input_len: usize,
boundary: BoundaryMode,
) -> Result<Vec<f64>, SignalError> {
validate_level_lengths(approximation, detail, input_len.div_ceil(2), input_len / 2)?;
let mut even = approximation.to_vec();
let mut odd = detail.to_vec();
if !odd.is_empty() {
for (index, value) in even.iter_mut().enumerate() {
*value -= 0.25
* (extended(&odd, index as isize - 1, boundary)
+ extended(&odd, index as isize, boundary));
}
}
for (index, value) in odd.iter_mut().enumerate() {
*value += 0.5 * (even[index] + extended(&even, index as isize + 1, boundary));
}
let mut output = Vec::with_capacity(input_len);
for (index, value) in even.iter().enumerate() {
output.push(*value);
if let Some(value) = odd.get(index) {
output.push(*value);
}
}
Ok(output)
}
fn validate_level_lengths(
approximation: &[f64],
detail: &[f64],
expected_approximation: usize,
expected_detail: usize,
) -> Result<(), SignalError> {
if approximation.len() != expected_approximation {
return Err(SignalError::LengthMismatch {
expected: expected_approximation,
actual: approximation.len(),
});
}
if detail.len() != expected_detail {
return Err(SignalError::LengthMismatch {
expected: expected_detail,
actual: detail.len(),
});
}
Ok(())
}
pub(crate) fn extended(values: &[f64], index: isize, boundary: BoundaryMode) -> f64 {
if index >= 0 && (index as usize) < values.len() {
return values[index as usize];
}
match boundary {
BoundaryMode::Zero => 0.0,
BoundaryMode::Periodic => {
let len = values.len() as isize;
values[index.rem_euclid(len) as usize]
}
BoundaryMode::Symmetric => {
let len = values.len() as isize;
let reflected = index.rem_euclid(2 * len);
let reflected = if reflected < len {
reflected
} else {
2 * len - 1 - reflected
};
values[reflected as usize]
}
}
}