sklears_feature_extraction/signal_processing/
transform_methods.rs1use scirs2_core::ndarray::{Array1, ArrayView1};
10use sklears_core::{error::Result as SklResult, prelude::SklearsError};
11
12#[derive(Debug, Clone)]
17pub struct WaveletTransform {
18 wavelet: String,
19 levels: usize,
20 mode: String,
21}
22
23impl WaveletTransform {
24 pub fn new() -> Self {
26 Self {
27 wavelet: "db4".to_string(),
28 levels: 4,
29 mode: "symmetric".to_string(),
30 }
31 }
32
33 pub fn wavelet(mut self, wavelet: String) -> Self {
41 self.wavelet = wavelet;
42 self
43 }
44
45 pub fn wavelet_type(mut self, wavelet_type: String) -> Self {
47 self.wavelet = wavelet_type;
48 self
49 }
50
51 pub fn levels(mut self, levels: usize) -> Self {
56 self.levels = levels;
57 self
58 }
59
60 pub fn mode(mut self, mode: String) -> Self {
67 self.mode = mode;
68 self
69 }
70
71 pub fn extract_features(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
77 if signal.is_empty() {
78 return Err(SklearsError::InvalidInput("Empty signal".to_string()));
79 }
80
81 self.validate_decomposition_levels(signal.len())?;
82
83 let mut features = Vec::new();
85 let mut current_signal = signal.to_owned();
86
87 for _level in 0..self.levels {
88 let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
89
90 let detail_energy = high.iter().map(|x| x * x).sum::<f64>();
92 features.push(detail_energy);
93
94 current_signal = low;
95
96 if current_signal.len() < 2 {
97 break;
98 }
99 }
100
101 let approx_energy = current_signal.iter().map(|x| x * x).sum::<f64>();
103 features.push(approx_energy);
104
105 Ok(Array1::from_vec(features))
106 }
107
108 pub fn decompose(
113 &self,
114 signal: &ArrayView1<f64>,
115 ) -> SklResult<Vec<(Array1<f64>, Array1<f64>)>> {
116 if signal.is_empty() {
117 return Err(SklearsError::InvalidInput("Empty signal".to_string()));
118 }
119
120 self.validate_decomposition_levels(signal.len())?;
121
122 let mut result = Vec::new();
123 let mut current_signal = signal.to_owned();
124
125 for _level in 0..self.levels {
126 let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
127
128 result.push((low.clone(), high));
129 current_signal = low;
130
131 if current_signal.len() < 2 {
132 break;
133 }
134 }
135
136 Ok(result)
137 }
138
139 pub fn approximation_coefficients(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
141 if signal.is_empty() {
142 return Err(SklearsError::InvalidInput("Empty signal".to_string()));
143 }
144
145 self.validate_decomposition_levels(signal.len())?;
146
147 let mut current_signal = signal.to_owned();
148
149 for _level in 0..self.levels {
150 let (low, _high) = self.wavelet_decompose(¤t_signal.view())?;
151 current_signal = low;
152
153 if current_signal.len() < 2 {
154 break;
155 }
156 }
157
158 Ok(current_signal)
159 }
160
161 pub fn detail_coefficients_at_level(
163 &self,
164 signal: &ArrayView1<f64>,
165 target_level: usize,
166 ) -> SklResult<Array1<f64>> {
167 if signal.is_empty() {
168 return Err(SklearsError::InvalidInput("Empty signal".to_string()));
169 }
170
171 self.validate_decomposition_levels(signal.len())?;
172
173 if target_level >= self.levels {
174 return Err(SklearsError::InvalidInput(
175 "Target level exceeds configured levels".to_string(),
176 ));
177 }
178
179 let mut current_signal = signal.to_owned();
180
181 for level in 0..=target_level {
182 let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
183
184 if level == target_level {
185 return Ok(high);
186 }
187
188 current_signal = low;
189
190 if current_signal.len() < 2 {
191 return Err(SklearsError::InvalidInput(
192 "Signal too short for requested level".to_string(),
193 ));
194 }
195 }
196
197 Err(SklearsError::InvalidInput(
198 "Failed to reach target level".to_string(),
199 ))
200 }
201
202 fn wavelet_decompose(&self, signal: &ArrayView1<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
204 let n = signal.len();
205 let _half_n = n / 2;
206
207 match self.wavelet.as_str() {
208 "haar" => self.haar_decompose(signal),
209 "db4" => self.daubechies4_decompose(signal),
210 "db8" => self.daubechies8_decompose(signal),
211 _ => {
212 self.haar_decompose(signal)
214 }
215 }
216 }
217
218 fn haar_decompose(&self, signal: &ArrayView1<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
220 let n = signal.len();
221 let half_n = n / 2;
222
223 let mut low = Array1::zeros(half_n);
224 let mut high = Array1::zeros(half_n);
225
226 for i in 0..half_n {
227 let idx = i * 2;
228 if idx + 1 < n {
229 low[i] = (signal[idx] + signal[idx + 1]) / 2.0_f64.sqrt();
230 high[i] = (signal[idx] - signal[idx + 1]) / 2.0_f64.sqrt();
231 }
232 }
233
234 Ok((low, high))
235 }
236
237 fn daubechies4_decompose(
239 &self,
240 signal: &ArrayView1<f64>,
241 ) -> SklResult<(Array1<f64>, Array1<f64>)> {
242 let h0 = (1.0 + 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
244 let h1 = (3.0 + 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
245 let h2 = (3.0 - 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
246 let h3 = (1.0 - 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
247
248 let low_pass = [h0, h1, h2, h3];
249 let high_pass = [h3, -h2, h1, -h0]; self.filter_decompose(signal, &low_pass, &high_pass)
252 }
253
254 fn daubechies8_decompose(
256 &self,
257 signal: &ArrayView1<f64>,
258 ) -> SklResult<(Array1<f64>, Array1<f64>)> {
259 let low_pass = [
261 0.23037781,
262 0.71484657,
263 0.63088076,
264 -0.02798376,
265 -0.18703481,
266 0.03084138,
267 0.03288301,
268 -0.01059740,
269 ];
270 let high_pass = [
271 -0.01059740,
272 -0.03288301,
273 0.03084138,
274 0.18703481,
275 -0.02798376,
276 -0.63088076,
277 0.71484657,
278 -0.23037781,
279 ];
280
281 self.filter_decompose(signal, &low_pass, &high_pass)
282 }
283
284 fn filter_decompose(
286 &self,
287 signal: &ArrayView1<f64>,
288 low_filter: &[f64],
289 high_filter: &[f64],
290 ) -> SklResult<(Array1<f64>, Array1<f64>)> {
291 let n = signal.len();
292 let filter_len = low_filter.len();
293 let output_len = n / 2;
294
295 let mut low = Array1::zeros(output_len);
296 let mut high = Array1::zeros(output_len);
297
298 for i in 0..output_len {
299 let mut low_sum = 0.0;
300 let mut high_sum = 0.0;
301
302 for k in 0..filter_len {
303 let idx = (2 * i + k) % n; low_sum += signal[idx] * low_filter[k];
305 high_sum += signal[idx] * high_filter[k];
306 }
307
308 low[i] = low_sum;
309 high[i] = high_sum;
310 }
311
312 Ok((low, high))
313 }
314
315 pub fn energy_distribution(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
317 let features = self.extract_features(signal)?;
318 let total_energy: f64 = features.sum();
319
320 if total_energy > 0.0 {
321 Ok(features.mapv(|x| x / total_energy))
322 } else {
323 Ok(features)
324 }
325 }
326
327 pub fn wavelet_entropy(&self, signal: &ArrayView1<f64>) -> SklResult<f64> {
329 let energy_dist = self.energy_distribution(signal)?;
330
331 let mut entropy = 0.0;
332 for &energy in energy_dist.iter() {
333 if energy > 0.0 {
334 entropy -= energy * energy.ln();
335 }
336 }
337
338 Ok(entropy)
339 }
340
341 pub fn transform(&self, signal: &ArrayView1<f64>) -> SklResult<Vec<f64>> {
345 let decomposition = self.decompose(signal)?;
347 let mut coefficients = Vec::new();
348
349 for (_approx, detail) in decomposition.iter() {
350 coefficients.extend(detail.iter());
351 }
352
353 if let Some((approx, _)) = decomposition.last() {
355 coefficients.extend(approx.iter());
356 }
357
358 Ok(coefficients)
359 }
360
361 pub fn inverse_transform(&self, _coefficients: &[f64]) -> SklResult<Array1<f64>> {
365 Ok(Array1::from_vec(vec![0.0; _coefficients.len()]))
367 }
368
369 fn validate_decomposition_levels(&self, signal_len: usize) -> SklResult<()> {
370 if self.levels == 0 {
371 return Ok(());
372 }
373
374 let mut remaining = signal_len;
375 let mut max_levels = 0;
376
377 while remaining >= 2 {
378 max_levels += 1;
379 remaining /= 2;
380 }
381
382 if self.levels > max_levels {
383 return Err(SklearsError::InvalidInput(format!(
384 "Signal length {} insufficient for {} decomposition levels (max {}).",
385 signal_len, self.levels, max_levels
386 )));
387 }
388
389 Ok(())
390 }
391}
392
393impl Default for WaveletTransform {
394 fn default() -> Self {
395 Self::new()
396 }
397}