scirs2_signal/dwt/
multiscale.rs

1// Multi-level wavelet transform functions
2//
3// This module provides functions for multi-level/multi-resolution wavelet analysis,
4// including decomposition and reconstruction of signals.
5
6use super::transform::{dwt_decompose, dwt_reconstruct};
7use crate::dwt::Wavelet;
8use crate::error::{SignalError, SignalResult};
9use scirs2_core::numeric::{Float, NumCast};
10use std::fmt::Debug;
11
12#[allow(unused_imports)]
13/// Perform multi-level wavelet decomposition
14///
15/// # Arguments
16///
17/// * `data` - Input signal
18/// * `wavelet` - Wavelet to use for decomposition
19/// * `level` - Number of decomposition levels (default: maximum possible)
20/// * `mode` - Signal extension mode (default: "symmetric")
21///
22/// # Returns
23///
24/// A vector of coefficient arrays: [approximation_n, detail_n, detail_n-1, ..., detail_1]
25/// where n is the decomposition level.
26///
27/// # Examples
28///
29/// ```
30/// use scirs2_signal::dwt::{wavedec, Wavelet};
31///
32/// let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
33/// let coeffs = wavedec(&signal, Wavelet::DB(4), Some(2), None).unwrap();
34///
35/// // coeffs[0] contains level-2 approximation
36/// // coeffs[1] contains level-2 detail
37/// // coeffs[2] contains level-1 detail
38/// ```
39#[allow(dead_code)]
40pub fn wavedec<T>(
41    data: &[T],
42    wavelet: Wavelet,
43    level: Option<usize>,
44    mode: Option<&str>,
45) -> SignalResult<Vec<Vec<f64>>>
46where
47    T: Float + NumCast + Debug,
48{
49    if data.is_empty() {
50        return Err(SignalError::ValueError("Input array is empty".to_string()));
51    }
52
53    // Convert data to f64
54    let data_f64: Vec<f64> = data
55        .iter()
56        .map(|&v| {
57            NumCast::from(v).ok_or_else(|| {
58                SignalError::ValueError(format!("Failed to convert value {:?} to f64", v))
59            })
60        })
61        .collect::<SignalResult<Vec<f64>>>()?;
62
63    // Calculate maximum possible decomposition level
64    let data_len = data_f64.len();
65    let filters = wavelet.filters()?;
66    let filter_len = filters.dec_lo.len();
67
68    // Each level of decomposition approximately halves the signal length
69    // and requires at least filter_len samples
70    let min_length = if let Wavelet::Haar = wavelet {
71        2
72    } else {
73        filter_len
74    };
75    let max_level = (data_len as f64 / min_length as f64).log2().floor() as usize;
76    let decomp_level = level.unwrap_or(max_level).min(max_level);
77
78    if decomp_level == 0 {
79        // No decomposition, just return the original signal
80        return Ok(vec![data_f64]);
81    }
82
83    // Initialize coefficient arrays
84    let mut coeffs = Vec::with_capacity(decomp_level + 1);
85
86    // Start with the original signal
87    let mut approx = data_f64;
88
89    // Perform decomposition for each level
90    for _ in 0..decomp_level {
91        // Decompose current approximation
92        let (next_approx, detail) = dwt_decompose(&approx, wavelet, mode)?;
93
94        // Store detail coefficients
95        coeffs.push(detail);
96
97        // Update approximation for next level
98        approx = next_approx;
99    }
100
101    // Add final approximation (level 'n')
102    coeffs.push(approx);
103
104    // Reverse to get [a_n, d_n, d_n-1, ..., d_1]
105    coeffs.reverse();
106
107    Ok(coeffs)
108}
109
110/// Perform multi-level inverse wavelet reconstruction
111///
112/// # Arguments
113///
114/// * `coeffs` - Wavelet coefficients from wavedec [a_n, d_n, d_n-1, ..., d_1]
115/// * `wavelet` - Wavelet to use for reconstruction
116///
117/// # Returns
118///
119/// The reconstructed signal
120///
121/// # Examples
122///
123/// ```
124/// use scirs2_signal::dwt::{wavedec, waverec, Wavelet};
125///
126/// let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
127/// let coeffs = wavedec(&signal, Wavelet::DB(4), Some(2), None).unwrap();
128///
129/// // Reconstruct the signal
130/// let reconstructed = waverec(&coeffs, Wavelet::DB(4)).unwrap();
131///
132/// // Check that reconstructed signal is close to original
133/// for i in 0..signal.len() {
134///     assert!((signal[i] - reconstructed[i]).abs() < 1e-10);
135/// }
136/// ```
137#[allow(dead_code)]
138pub fn waverec(coeffs: &[Vec<f64>], wavelet: Wavelet) -> SignalResult<Vec<f64>> {
139    if coeffs.is_empty() {
140        return Err(SignalError::ValueError(
141            "Coefficients array is empty".to_string(),
142        ));
143    }
144
145    // Case of no transform (just the signal)
146    if coeffs.len() == 1 {
147        return Ok(coeffs[0].clone());
148    }
149
150    // Start with the coarsest approximation
151    let mut approx = coeffs[0].clone();
152
153    // Number of reconstruction levels
154    let n_levels = coeffs.len() - 1;
155
156    // Reconstruct each level
157    for i in 0..n_levels {
158        let detail = &coeffs[i + 1];
159
160        // In some cases, approximation and detail coefficients might be off by 1 or 2
161        // elements due to boundary handling and padding. We'll adjust them to make them equal.
162        if approx.len() != detail.len() {
163            // If the lengths are very different, that's a real error
164            if (approx.len() as isize - detail.len() as isize).abs() > 4 {
165                return Err(SignalError::ValueError(format!(
166                    "Significantly mismatched coefficient lengths at level {}: approx={}, detail={}",
167                    i,
168                    approx.len(),
169                    detail.len()
170                )));
171            }
172
173            // Otherwise, adjust to the smaller length
174            let min_len = approx.len().min(detail.len());
175            if approx.len() > min_len {
176                approx.truncate(min_len);
177            }
178            let detail = if detail.len() > min_len {
179                detail[0..min_len].to_vec()
180            } else {
181                detail.clone()
182            };
183
184            // Now reconstruct with the adjusted arrays
185            approx = dwt_reconstruct(&approx, &detail, wavelet)?;
186        } else {
187            // Normal case - reconstruct with the original arrays
188            approx = dwt_reconstruct(&approx, detail, wavelet)?;
189        }
190    }
191
192    Ok(approx)
193}
194
195// Compatibility wrapper functions for old API style
196
197/// Compatibility wrapper for wavedec with 3 parameters (old API)
198pub fn wavedec_compat<T>(data: &[T], wavelet: Wavelet, level: usize) -> SignalResult<Vec<Vec<f64>>>
199where
200    T: Float + NumCast + Debug,
201{
202    wavedec(data, wavelet, Some(level), None)
203}
204
205/// Compatibility wrapper for waverec with DecompositionResult input
206pub fn waverec_compat(coeffs: &[Vec<f64>], wavelet: Wavelet) -> SignalResult<Vec<f64>> {
207    waverec(coeffs, wavelet)
208}