use super::{Jp2Error, Jp2Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WaveletKind {
Reversible53,
Irreversible97,
}
const CDF97_ALPHA: f64 = -1.586_134_342_059_924;
const CDF97_BETA: f64 = -0.052_980_118_572_961;
const CDF97_GAMMA: f64 = 0.882_911_075_530_934;
const CDF97_DELTA: f64 = 0.443_506_852_043_971;
const CDF97_K: f64 = 1.230_174_104_914_001;
const CDF97_K_INV: f64 = 0.812_893_066_115_961;
#[derive(Debug, Clone)]
pub struct SubbandLevel {
pub hl: Vec<i32>,
pub lh: Vec<i32>,
pub hh: Vec<i32>,
pub width: usize,
pub height: usize,
}
#[derive(Debug, Clone)]
pub struct SubbandTree {
pub ll: Vec<i32>,
pub ll_width: usize,
pub ll_height: usize,
pub levels: Vec<SubbandLevel>,
}
#[must_use]
pub fn forward_53(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
let n = signal.len();
if n == 0 {
return (Vec::new(), Vec::new());
}
if n == 1 {
return (vec![signal[0]], Vec::new());
}
let n_l = (n + 1) / 2;
let n_h = n / 2;
let mut low: Vec<i32> = signal.iter().step_by(2).copied().collect();
let mut high: Vec<i32> = signal.iter().skip(1).step_by(2).copied().collect();
for n in 0..n_h {
let l_n = low[n];
let l_n1 = if n + 1 < n_l {
low[n + 1]
} else {
low[n_l - 1]
};
high[n] -= (l_n + l_n1) >> 1;
}
for n in 0..n_l {
let h_prev = if n > 0 {
high[n - 1]
} else {
if n_h > 0 {
high[0]
} else {
0
}
};
let h_n = if n < n_h {
high[n]
} else {
if n_h > 0 {
high[n_h - 1]
} else {
0
}
};
low[n] += (h_prev + h_n + 2) >> 2;
}
(low, high)
}
#[must_use]
pub fn inverse_wavelet_1d(low: &[i32], high: &[i32]) -> Vec<i32> {
let n_l = low.len();
let n_h = high.len();
if n_l == 0 {
return Vec::new();
}
let mut s = low.to_vec(); let mut d = high.to_vec();
for n in 0..n_l {
let d_prev = if n > 0 {
d[n - 1]
} else if n_h > 0 {
d[0]
} else {
0
};
let d_n = if n < n_h {
d[n]
} else if n_h > 0 {
d[n_h - 1]
} else {
0
};
s[n] -= (d_prev + d_n + 2) >> 2;
}
for n in 0..n_h {
let s_n = s[n];
let s_n1 = if n + 1 < n_l { s[n + 1] } else { s[n_l - 1] };
d[n] += (s_n + s_n1) >> 1;
}
let total = n_l + n_h;
let mut result = vec![0i32; total];
for n in 0..n_l {
result[2 * n] = s[n];
}
for n in 0..n_h {
result[2 * n + 1] = d[n];
}
result
}
pub fn inverse_wavelet_2d(
ll: &[i32],
hl: &[i32],
lh: &[i32],
hh: &[i32],
width: usize,
height: usize,
) -> Jp2Result<Vec<i32>> {
let n_l_h = (width + 1) / 2; let n_h_h = width / 2; let n_l_v = (height + 1) / 2; let n_h_v = height / 2;
let expected_ll = n_l_v * n_l_h;
let expected_hl = n_l_v * n_h_h;
let expected_lh = n_h_v * n_l_h;
let expected_hh = n_h_v * n_h_h;
if ll.len() < expected_ll {
return Err(Jp2Error::InternalError(format!(
"LL subband too small: expected {expected_ll}, got {}",
ll.len()
)));
}
if hl.len() < expected_hl {
return Err(Jp2Error::InternalError(format!(
"HL subband too small: expected {expected_hl}, got {}",
hl.len()
)));
}
if lh.len() < expected_lh {
return Err(Jp2Error::InternalError(format!(
"LH subband too small: expected {expected_lh}, got {}",
lh.len()
)));
}
if hh.len() < expected_hh {
return Err(Jp2Error::InternalError(format!(
"HH subband too small: expected {expected_hh}, got {}",
hh.len()
)));
}
let mut low_rows: Vec<i32> = vec![0; n_l_v * width]; for row in 0..n_l_v {
let l = &ll[row * n_l_h..(row + 1) * n_l_h];
let h = &hl[row * n_h_h..(row + 1) * n_h_h];
let reconstructed = inverse_wavelet_1d(l, h);
let out_len = reconstructed.len().min(width);
low_rows[row * width..row * width + out_len].copy_from_slice(&reconstructed[..out_len]);
}
let mut high_rows: Vec<i32> = vec![0; n_h_v * width]; for row in 0..n_h_v {
let l = &lh[row * n_l_h..(row + 1) * n_l_h];
let h = &hh[row * n_h_h..(row + 1) * n_h_h];
let reconstructed = inverse_wavelet_1d(l, h);
let out_len = reconstructed.len().min(width);
high_rows[row * width..row * width + out_len].copy_from_slice(&reconstructed[..out_len]);
}
let mut output = vec![0i32; height * width];
for col in 0..width {
let low_col: Vec<i32> = (0..n_l_v).map(|r| low_rows[r * width + col]).collect();
let high_col: Vec<i32> = (0..n_h_v).map(|r| high_rows[r * width + col]).collect();
let col_out = inverse_wavelet_1d(&low_col, &high_col);
let out_len = col_out.len().min(height);
for (row, &val) in col_out[..out_len].iter().enumerate() {
output[row * width + col] = val;
}
}
Ok(output)
}
#[inline]
fn sym_ext(buf: &[f64], i: i64) -> f64 {
let n = buf.len() as i64;
if n == 0 {
return 0.0;
}
if n == 1 {
return buf[0];
}
let period = 2 * (n - 1);
let mut j = i.rem_euclid(period);
if j > n - 1 {
j = period - j;
}
buf[j as usize]
}
#[must_use]
pub fn inverse_wavelet_1d_97(low: &[f64], high: &[f64]) -> Vec<f64> {
let n_l = low.len();
let n_h = high.len();
if n_l == 0 {
return Vec::new();
}
let mut s: Vec<f64> = low.iter().map(|&v| v * CDF97_K).collect();
let mut d: Vec<f64> = high.iter().map(|&v| v * CDF97_K_INV).collect();
for n in 0..n_l {
let d_prev = sym_ext(&d, n as i64 - 1);
let d_n = sym_ext(&d, n as i64);
s[n] -= CDF97_DELTA * (d_prev + d_n);
}
for n in 0..n_h {
let s_n = sym_ext(&s, n as i64);
let s_n1 = sym_ext(&s, n as i64 + 1);
d[n] -= CDF97_GAMMA * (s_n + s_n1);
}
for n in 0..n_l {
let d_prev = sym_ext(&d, n as i64 - 1);
let d_n = sym_ext(&d, n as i64);
s[n] -= CDF97_BETA * (d_prev + d_n);
}
for n in 0..n_h {
let s_n = sym_ext(&s, n as i64);
let s_n1 = sym_ext(&s, n as i64 + 1);
d[n] -= CDF97_ALPHA * (s_n + s_n1);
}
let total = n_l + n_h;
let mut result = vec![0.0f64; total];
for n in 0..n_l {
result[2 * n] = s[n];
}
for n in 0..n_h {
result[2 * n + 1] = d[n];
}
result
}
pub fn inverse_wavelet_2d_97(
ll: &[f64],
hl: &[f64],
lh: &[f64],
hh: &[f64],
width: usize,
height: usize,
) -> Jp2Result<Vec<f64>> {
let n_l_h = (width + 1) / 2; let n_h_h = width / 2; let n_l_v = (height + 1) / 2; let n_h_v = height / 2;
let expected_ll = n_l_v * n_l_h;
let expected_hl = n_l_v * n_h_h;
let expected_lh = n_h_v * n_l_h;
let expected_hh = n_h_v * n_h_h;
if ll.len() < expected_ll {
return Err(Jp2Error::InternalError(format!(
"LL subband too small for 9-7: expected {expected_ll}, got {}",
ll.len()
)));
}
if hl.len() < expected_hl {
return Err(Jp2Error::InternalError(format!(
"HL subband too small for 9-7: expected {expected_hl}, got {}",
hl.len()
)));
}
if lh.len() < expected_lh {
return Err(Jp2Error::InternalError(format!(
"LH subband too small for 9-7: expected {expected_lh}, got {}",
lh.len()
)));
}
if hh.len() < expected_hh {
return Err(Jp2Error::InternalError(format!(
"HH subband too small for 9-7: expected {expected_hh}, got {}",
hh.len()
)));
}
let mut low_rows: Vec<f64> = vec![0.0; n_l_v * width];
for row in 0..n_l_v {
let l = &ll[row * n_l_h..(row + 1) * n_l_h];
let h = &hl[row * n_h_h..(row + 1) * n_h_h];
let reconstructed = inverse_wavelet_1d_97(l, h);
let out_len = reconstructed.len().min(width);
low_rows[row * width..row * width + out_len].copy_from_slice(&reconstructed[..out_len]);
}
let mut high_rows: Vec<f64> = vec![0.0; n_h_v * width];
for row in 0..n_h_v {
let l = &lh[row * n_l_h..(row + 1) * n_l_h];
let h = &hh[row * n_h_h..(row + 1) * n_h_h];
let reconstructed = inverse_wavelet_1d_97(l, h);
let out_len = reconstructed.len().min(width);
high_rows[row * width..row * width + out_len].copy_from_slice(&reconstructed[..out_len]);
}
let mut output = vec![0.0f64; height * width];
for col in 0..width {
let low_col: Vec<f64> = (0..n_l_v).map(|r| low_rows[r * width + col]).collect();
let high_col: Vec<f64> = (0..n_h_v).map(|r| high_rows[r * width + col]).collect();
let col_out = inverse_wavelet_1d_97(&low_col, &high_col);
let out_len = col_out.len().min(height);
for (row, &val) in col_out[..out_len].iter().enumerate() {
output[row * width + col] = val;
}
}
Ok(output)
}
#[derive(Debug, Clone)]
pub struct SubbandLevel97 {
pub hl: Vec<f64>,
pub lh: Vec<f64>,
pub hh: Vec<f64>,
pub width: usize,
pub height: usize,
}
#[derive(Debug, Clone)]
pub struct SubbandTree97 {
pub ll: Vec<f64>,
pub ll_width: usize,
pub ll_height: usize,
pub levels: Vec<SubbandLevel97>,
}
pub fn reconstruct_levels_97(
subbands: &SubbandTree97,
num_levels: usize,
width: usize,
height: usize,
) -> Jp2Result<Vec<f64>> {
if subbands.levels.len() < num_levels {
return Err(Jp2Error::InternalError(format!(
"SubbandTree97 has {} levels, need {num_levels}",
subbands.levels.len()
)));
}
let mut current_ll = subbands.ll.clone();
let mut current_w = subbands.ll_width;
let mut current_h = subbands.ll_height;
for level_idx in 0..num_levels {
let level = &subbands.levels[level_idx];
let out_w = level.width * 2;
let out_h = level.height * 2;
let target_w = if level_idx == num_levels - 1 {
width
} else {
out_w
};
let target_h = if level_idx == num_levels - 1 {
height
} else {
out_h
};
current_ll = inverse_wavelet_2d_97(
¤t_ll,
&level.hl,
&level.lh,
&level.hh,
target_w,
target_h,
)?;
current_w = target_w;
current_h = target_h;
}
let _ = current_w;
let _ = current_h;
Ok(current_ll)
}
#[must_use]
pub fn forward_wavelet_1d_97(signal: &[f64]) -> (Vec<f64>, Vec<f64>) {
let n = signal.len();
if n == 0 {
return (Vec::new(), Vec::new());
}
if n == 1 {
return (vec![signal[0]], Vec::new());
}
let n_l = (n + 1) / 2;
let n_h = n / 2;
let mut s: Vec<f64> = signal.iter().step_by(2).copied().collect();
let mut d: Vec<f64> = signal.iter().skip(1).step_by(2).copied().collect();
for n in 0..n_h {
let s_n = sym_ext(&s, n as i64);
let s_n1 = sym_ext(&s, n as i64 + 1);
d[n] += CDF97_ALPHA * (s_n + s_n1);
}
for n in 0..n_l {
let d_prev = sym_ext(&d, n as i64 - 1);
let d_n = sym_ext(&d, n as i64);
s[n] += CDF97_BETA * (d_prev + d_n);
}
for n in 0..n_h {
let s_n = sym_ext(&s, n as i64);
let s_n1 = sym_ext(&s, n as i64 + 1);
d[n] += CDF97_GAMMA * (s_n + s_n1);
}
for n in 0..n_l {
let d_prev = sym_ext(&d, n as i64 - 1);
let d_n = sym_ext(&d, n as i64);
s[n] += CDF97_DELTA * (d_prev + d_n);
}
let s_scaled: Vec<f64> = s.iter().map(|&v| v / CDF97_K).collect();
let d_scaled: Vec<f64> = d.iter().map(|&v| v / CDF97_K_INV).collect();
(s_scaled, d_scaled)
}
pub fn forward_wavelet_2d_97(
input: &[f64],
width: usize,
height: usize,
) -> Jp2Result<(Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>)> {
if input.len() < width * height {
return Err(Jp2Error::InternalError(format!(
"forward_wavelet_2d_97 input too small: expected {}, got {}",
width * height,
input.len()
)));
}
let n_l_h = width.div_ceil(2); let n_h_h = width / 2; let n_l_v = height.div_ceil(2); let n_h_v = height / 2;
let mut low_rows = vec![0.0f64; n_l_v * width];
let mut high_rows = vec![0.0f64; n_h_v * width];
let mut col_vals = vec![0.0f64; height];
for col in 0..width {
for (row, slot) in col_vals.iter_mut().enumerate() {
*slot = input[row * width + col];
}
let (low, high) = forward_wavelet_1d_97(&col_vals);
for (r, &v) in low.iter().enumerate() {
low_rows[r * width + col] = v;
}
for (r, &v) in high.iter().enumerate() {
high_rows[r * width + col] = v;
}
}
let mut ll = vec![0.0f64; n_l_v * n_l_h];
let mut hl = vec![0.0f64; n_l_v * n_h_h];
for row in 0..n_l_v {
let (low, high) = forward_wavelet_1d_97(&low_rows[row * width..(row + 1) * width]);
let l_len = low.len().min(n_l_h);
ll[row * n_l_h..row * n_l_h + l_len].copy_from_slice(&low[..l_len]);
let h_len = high.len().min(n_h_h);
if n_h_h > 0 {
hl[row * n_h_h..row * n_h_h + h_len].copy_from_slice(&high[..h_len]);
}
}
let mut lh = vec![0.0f64; n_h_v * n_l_h];
let mut hh = vec![0.0f64; n_h_v * n_h_h];
for row in 0..n_h_v {
let (low, high) = forward_wavelet_1d_97(&high_rows[row * width..(row + 1) * width]);
let l_len = low.len().min(n_l_h);
lh[row * n_l_h..row * n_l_h + l_len].copy_from_slice(&low[..l_len]);
let h_len = high.len().min(n_h_h);
if n_h_h > 0 {
hh[row * n_h_h..row * n_h_h + h_len].copy_from_slice(&high[..h_len]);
}
}
Ok((ll, hl, lh, hh))
}
pub fn decompose_levels_97(
image: &[f64],
width: usize,
height: usize,
num_levels: usize,
) -> Jp2Result<SubbandTree97> {
if image.len() < width * height {
return Err(Jp2Error::InternalError(format!(
"decompose_levels_97 image too small: expected {}, got {}",
width * height,
image.len()
)));
}
let mut current = image[..width * height].to_vec();
let mut cur_w = width;
let mut cur_h = height;
let mut levels_fine_first: Vec<SubbandLevel97> = Vec::with_capacity(num_levels);
for _ in 0..num_levels {
let (ll, hl, lh, hh) = forward_wavelet_2d_97(¤t, cur_w, cur_h)?;
let detail_w = cur_w / 2;
let detail_h = cur_h / 2;
levels_fine_first.push(SubbandLevel97 {
hl,
lh,
hh,
width: detail_w,
height: detail_h,
});
current = ll;
cur_w = cur_w.div_ceil(2);
cur_h = cur_h.div_ceil(2);
}
levels_fine_first.reverse();
Ok(SubbandTree97 {
ll: current,
ll_width: cur_w,
ll_height: cur_h,
levels: levels_fine_first,
})
}
pub fn forward_wavelet_2d(
input: &[i32],
width: usize,
height: usize,
) -> Jp2Result<(Vec<i32>, Vec<i32>, Vec<i32>, Vec<i32>)> {
if input.len() < width * height {
return Err(Jp2Error::InternalError(format!(
"forward_wavelet_2d input too small: expected {}, got {}",
width * height,
input.len()
)));
}
let n_l_h = width.div_ceil(2); let n_h_h = width / 2; let n_l_v = height.div_ceil(2); let n_h_v = height / 2;
let mut low_rows = vec![0i32; n_l_v * width];
let mut high_rows = vec![0i32; n_h_v * width];
let mut col_vals = vec![0i32; height];
for col in 0..width {
for (row, slot) in col_vals.iter_mut().enumerate() {
*slot = input[row * width + col];
}
let (low, high) = forward_53(&col_vals);
for (r, &v) in low.iter().enumerate() {
low_rows[r * width + col] = v;
}
for (r, &v) in high.iter().enumerate() {
high_rows[r * width + col] = v;
}
}
let mut ll = vec![0i32; n_l_v * n_l_h];
let mut hl = vec![0i32; n_l_v * n_h_h];
for row in 0..n_l_v {
let (low, high) = forward_53(&low_rows[row * width..(row + 1) * width]);
let l_len = low.len().min(n_l_h);
ll[row * n_l_h..row * n_l_h + l_len].copy_from_slice(&low[..l_len]);
let h_len = high.len().min(n_h_h);
if n_h_h > 0 {
hl[row * n_h_h..row * n_h_h + h_len].copy_from_slice(&high[..h_len]);
}
}
let mut lh = vec![0i32; n_h_v * n_l_h];
let mut hh = vec![0i32; n_h_v * n_h_h];
for row in 0..n_h_v {
let (low, high) = forward_53(&high_rows[row * width..(row + 1) * width]);
let l_len = low.len().min(n_l_h);
lh[row * n_l_h..row * n_l_h + l_len].copy_from_slice(&low[..l_len]);
let h_len = high.len().min(n_h_h);
if n_h_h > 0 {
hh[row * n_h_h..row * n_h_h + h_len].copy_from_slice(&high[..h_len]);
}
}
Ok((ll, hl, lh, hh))
}
pub fn decompose_levels(
image: &[i32],
width: usize,
height: usize,
num_levels: usize,
) -> Jp2Result<SubbandTree> {
if image.len() < width * height {
return Err(Jp2Error::InternalError(format!(
"decompose_levels image too small: expected {}, got {}",
width * height,
image.len()
)));
}
let mut current = image[..width * height].to_vec();
let mut cur_w = width;
let mut cur_h = height;
let mut levels_fine_first: Vec<SubbandLevel> = Vec::with_capacity(num_levels);
for _ in 0..num_levels {
let (ll, hl, lh, hh) = forward_wavelet_2d(¤t, cur_w, cur_h)?;
let detail_w = cur_w / 2;
let detail_h = cur_h / 2;
levels_fine_first.push(SubbandLevel {
hl,
lh,
hh,
width: detail_w,
height: detail_h,
});
current = ll;
cur_w = cur_w.div_ceil(2);
cur_h = cur_h.div_ceil(2);
}
levels_fine_first.reverse();
Ok(SubbandTree {
ll: current,
ll_width: cur_w,
ll_height: cur_h,
levels: levels_fine_first,
})
}
pub fn reconstruct_levels(
subbands: &SubbandTree,
num_levels: usize,
width: usize,
height: usize,
) -> Jp2Result<Vec<i32>> {
if subbands.levels.len() < num_levels {
return Err(Jp2Error::InternalError(format!(
"SubbandTree has {} levels, need {num_levels}",
subbands.levels.len()
)));
}
let mut current_ll = subbands.ll.clone();
let mut current_w = subbands.ll_width;
let mut current_h = subbands.ll_height;
for level_idx in 0..num_levels {
let level = &subbands.levels[level_idx];
let out_w = level.width * 2;
let out_h = level.height * 2;
let target_w = if level_idx == num_levels - 1 {
width
} else {
out_w
};
let target_h = if level_idx == num_levels - 1 {
height
} else {
out_h
};
current_ll = inverse_wavelet_2d(
¤t_ll,
&level.hl,
&level.lh,
&level.hh,
target_w,
target_h,
)?;
current_w = target_w;
current_h = target_h;
}
let _ = current_w;
let _ = current_h;
Ok(current_ll)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_1d_even_length() {
let original = vec![10i32, 20, 30, 40, 50, 60, 70, 80];
let (low, high) = forward_53(&original);
let recovered = inverse_wavelet_1d(&low, &high);
assert_eq!(
recovered, original,
"1D round-trip failed for even-length signal"
);
}
#[test]
fn roundtrip_1d_odd_length() {
let original = vec![3i32, 1, 4, 1, 5, 9, 2];
let (low, high) = forward_53(&original);
let recovered = inverse_wavelet_1d(&low, &high);
assert_eq!(
recovered, original,
"1D round-trip failed for odd-length signal"
);
}
#[test]
fn roundtrip_1d_single_element() {
let original = vec![42i32];
let (low, high) = forward_53(&original);
assert_eq!(high.len(), 0);
let recovered = inverse_wavelet_1d(&low, &high);
assert_eq!(recovered, original);
}
#[test]
fn roundtrip_1d_two_elements() {
let original = vec![100i32, 200];
let (low, high) = forward_53(&original);
let recovered = inverse_wavelet_1d(&low, &high);
assert_eq!(recovered, original);
}
#[test]
fn constant_signal_1d() {
let original = vec![128i32; 8];
let (low, high) = forward_53(&original);
for &h in &high {
assert_eq!(
h, 0,
"Detail coefficients should be zero for constant signal"
);
}
let recovered = inverse_wavelet_1d(&low, &high);
assert_eq!(recovered, original);
}
#[test]
fn roundtrip_2d_constant_image() {
let width = 4;
let height = 4;
let constant_val = 128i32;
let n_l_h = (width + 1) / 2; let n_l_v = (height + 1) / 2; let n_h_h = width / 2; let n_h_v = height / 2;
let ll = vec![constant_val; n_l_v * n_l_h];
let hl = vec![0i32; n_l_v * n_h_h];
let lh = vec![0i32; n_h_v * n_l_h];
let hh = vec![0i32; n_h_v * n_h_h];
let image: Vec<i32> = vec![constant_val; width * height];
let mut row_transformed = vec![0i32; width * height];
for row in 0..height {
let (l, h) = forward_53(&image[row * width..(row + 1) * width]);
for (i, &v) in l.iter().enumerate() {
row_transformed[row * width + i] = v;
}
for (i, &v) in h.iter().enumerate() {
row_transformed[row * width + n_l_h + i] = v;
}
}
let mut ll_forward = vec![0i32; n_l_v * n_l_h];
for col in 0..n_l_h {
let col_vals: Vec<i32> = (0..height)
.map(|r| row_transformed[r * width + col])
.collect();
let (l, _h) = forward_53(&col_vals);
for (r, &v) in l.iter().enumerate() {
ll_forward[r * n_l_h + col] = v;
}
}
let output = inverse_wavelet_2d(&ll_forward, &hl, &lh, &hh, width, height)
.expect("inverse_wavelet_2d");
for (i, &v) in output.iter().enumerate() {
assert_eq!(
v, constant_val,
"Sample {i} should be {constant_val}, got {v}"
);
}
}
#[test]
fn reconstruct_levels_with_zero_details() {
let width = 4;
let height = 4;
let val = 64i32;
let n_l_h = (width + 1) / 2;
let n_l_v = (height + 1) / 2;
let n_h_h = width / 2;
let n_h_v = height / 2;
let ll = vec![val; n_l_v * n_l_h];
let hl = vec![0i32; n_l_v * n_h_h];
let lh = vec![0i32; n_h_v * n_l_h];
let hh = vec![0i32; n_h_v * n_h_h];
let tree = SubbandTree {
ll: ll.clone(),
ll_width: n_l_h,
ll_height: n_l_v,
levels: vec![SubbandLevel {
hl,
lh,
hh,
width: n_l_h,
height: n_l_v,
}],
};
let output = reconstruct_levels(&tree, 1, width, height).expect("reconstruct");
assert_eq!(output.len(), width * height);
for &v in &output {
assert_eq!(v, val, "Expected all samples to be {val}");
}
}
#[test]
fn cdf97_inverse_roundtrip_8_samples() {
let signal: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let (low, high) = forward_wavelet_1d_97(&signal);
let recovered = inverse_wavelet_1d_97(&low, &high);
assert_eq!(recovered.len(), signal.len(), "Recovered length mismatch");
for (i, (&orig, &rec)) in signal.iter().zip(recovered.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-4,
"Sample {i}: original {orig}, recovered {rec}, diff {}",
(orig - rec).abs()
);
}
}
#[test]
fn cdf97_inverse_roundtrip_constant_signal() {
let signal = vec![128.0f64; 8];
let (low, high) = forward_wavelet_1d_97(&signal);
let recovered = inverse_wavelet_1d_97(&low, &high);
assert_eq!(recovered.len(), signal.len());
for (i, (&orig, &rec)) in signal.iter().zip(recovered.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-4,
"Constant signal sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn inverse_wavelet_97_constant_signal() {
let n = 8usize;
let n_l_h = (n + 1) / 2;
let n_h_h = n / 2;
let n_l_v = (n + 1) / 2;
let n_h_v = n / 2;
let image = vec![128.0f64; n * n];
let mut ll_h = vec![0.0f64; n_l_h * n];
let mut hl_h = vec![0.0f64; n_h_h * n];
for row in 0..n {
let (l, h) = forward_wavelet_1d_97(&image[row * n..(row + 1) * n]);
for (c, &v) in l.iter().enumerate() {
ll_h[row * n_l_h + c] = v;
}
for (c, &v) in h.iter().enumerate() {
hl_h[row * n_h_h + c] = v;
}
}
let mut ll = vec![0.0f64; n_l_v * n_l_h];
let mut lh = vec![0.0f64; n_h_v * n_l_h];
for col in 0..n_l_h {
let col_vals: Vec<f64> = (0..n).map(|r| ll_h[r * n_l_h + col]).collect();
let (l, h) = forward_wavelet_1d_97(&col_vals);
for (r, &v) in l.iter().enumerate() {
ll[r * n_l_h + col] = v;
}
for (r, &v) in h.iter().enumerate() {
lh[r * n_l_h + col] = v;
}
}
let mut hl = vec![0.0f64; n_l_v * n_h_h];
let mut hh = vec![0.0f64; n_h_v * n_h_h];
for col in 0..n_h_h {
let col_vals: Vec<f64> = (0..n).map(|r| hl_h[r * n_h_h + col]).collect();
let (l, h) = forward_wavelet_1d_97(&col_vals);
for (r, &v) in l.iter().enumerate() {
hl[r * n_h_h + col] = v;
}
for (r, &v) in h.iter().enumerate() {
hh[r * n_h_h + col] = v;
}
}
let result = inverse_wavelet_2d_97(&ll, &hl, &lh, &hh, n, n).unwrap();
assert_eq!(result.len(), n * n);
for (i, v) in result.iter().enumerate() {
assert!(
(v - 128.0).abs() < 1e-4,
"Sample {i}: expected ~128.0, got {v}"
);
}
}
#[test]
fn reconstruct_levels_97_constant_signal() {
let n = 8usize;
let n_l_h = (n + 1) / 2;
let n_h_h = n / 2;
let n_l_v = (n + 1) / 2;
let n_h_v = n / 2;
let image = vec![64.0f64; n * n];
let mut ll_h = vec![0.0f64; n_l_h * n];
let mut hl_h = vec![0.0f64; n_h_h * n];
for row in 0..n {
let (l, h) = forward_wavelet_1d_97(&image[row * n..(row + 1) * n]);
for (c, &v) in l.iter().enumerate() {
ll_h[row * n_l_h + c] = v;
}
for (c, &v) in h.iter().enumerate() {
hl_h[row * n_h_h + c] = v;
}
}
let mut ll = vec![0.0f64; n_l_v * n_l_h];
let mut lh = vec![0.0f64; n_h_v * n_l_h];
for col in 0..n_l_h {
let col_vals: Vec<f64> = (0..n).map(|r| ll_h[r * n_l_h + col]).collect();
let (l, h) = forward_wavelet_1d_97(&col_vals);
for (r, &v) in l.iter().enumerate() {
ll[r * n_l_h + col] = v;
}
for (r, &v) in h.iter().enumerate() {
lh[r * n_l_h + col] = v;
}
}
let mut hl = vec![0.0f64; n_l_v * n_h_h];
let mut hh = vec![0.0f64; n_h_v * n_h_h];
for col in 0..n_h_h {
let col_vals: Vec<f64> = (0..n).map(|r| hl_h[r * n_h_h + col]).collect();
let (l, h) = forward_wavelet_1d_97(&col_vals);
for (r, &v) in l.iter().enumerate() {
hl[r * n_h_h + col] = v;
}
for (r, &v) in h.iter().enumerate() {
hh[r * n_h_h + col] = v;
}
}
let tree = SubbandTree97 {
ll,
ll_width: n_l_h,
ll_height: n_l_v,
levels: vec![SubbandLevel97 {
hl,
lh,
hh,
width: n_l_h,
height: n_l_v,
}],
};
let output = reconstruct_levels_97(&tree, 1, n, n).expect("reconstruct_97");
assert_eq!(output.len(), n * n);
for (i, &v) in output.iter().enumerate() {
assert!(
(v - 64.0).abs() < 1e-4,
"Sample {i}: expected ~64.0, got {v}"
);
}
}
#[test]
fn cdf97_forward_inverse_identity_8() {
let signal: Vec<f64> = (0..8).map(|i| (i as f64) * 11.0 + 3.0).collect();
let (low, high) = forward_wavelet_1d_97(&signal);
let recovered = inverse_wavelet_1d_97(&low, &high);
assert_eq!(recovered.len(), signal.len());
for (i, (&orig, &rec)) in signal.iter().zip(recovered.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-6,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn cdf97_forward_inverse_identity_16() {
let signal: Vec<f64> = (0..16).map(|i| (i as f64) * 2.5 - 7.0).collect();
let (low, high) = forward_wavelet_1d_97(&signal);
let recovered = inverse_wavelet_1d_97(&low, &high);
for (i, (&orig, &rec)) in signal.iter().zip(recovered.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-6,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn cdf97_forward_inverse_identity_32() {
let signal: Vec<f64> = (0..32).map(|i| ((i * 7) % 31) as f64).collect();
let (low, high) = forward_wavelet_1d_97(&signal);
let recovered = inverse_wavelet_1d_97(&low, &high);
for (i, (&orig, &rec)) in signal.iter().zip(recovered.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-6,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn decompose_reconstruct_97_identity_1_level() {
let w = 16usize;
let h = 16usize;
let image: Vec<f64> = (0..w * h).map(|i| (i as f64) % 53.0).collect();
let tree = decompose_levels_97(&image, w, h, 1).expect("decompose_97");
let recon = reconstruct_levels_97(&tree, 1, w, h).expect("reconstruct_97");
assert_eq!(recon.len(), w * h);
for (i, (&orig, &rec)) in image.iter().zip(recon.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-4,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn decompose_reconstruct_97_identity_2_levels() {
let w = 16usize;
let h = 16usize;
let image: Vec<f64> = (0..w * h)
.map(|i| (((i % w) + (i / w)) as f64) * 5.0)
.collect();
let tree = decompose_levels_97(&image, w, h, 2).expect("decompose_97");
let recon = reconstruct_levels_97(&tree, 2, w, h).expect("reconstruct_97");
assert_eq!(recon.len(), w * h);
for (i, (&orig, &rec)) in image.iter().zip(recon.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-3,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
#[test]
fn decompose_reconstruct_97_identity_3_levels() {
let w = 32usize;
let h = 32usize;
let image: Vec<f64> = (0..w * h).map(|i| ((i * 13) % 200) as f64).collect();
let tree = decompose_levels_97(&image, w, h, 3).expect("decompose_97");
let recon = reconstruct_levels_97(&tree, 3, w, h).expect("reconstruct_97");
assert_eq!(recon.len(), w * h);
for (i, (&orig, &rec)) in image.iter().zip(recon.iter()).enumerate() {
assert!(
(orig - rec).abs() < 1e-2,
"Sample {i}: orig {orig}, rec {rec}"
);
}
}
}