Skip to main content

sklears_feature_extraction/
wavelet_features.rs

1//! Wavelet and Time-Frequency Feature Extraction
2//!
3//! This module provides comprehensive wavelet-based and time-frequency feature extraction:
4//!
5//! - `TimeSeriesWaveletExtractor`: Discrete wavelet transform features for 1D time series
6//! - `TimeFrequencyExtractor`: Combined time-frequency analysis with STFT and wavelets
7//!
8//! These extractors enable time-frequency analysis and multi-resolution signal decomposition.
9
10use crate::{Float, SklResult, SklearsError};
11use scirs2_core::ndarray::{s, Array1, ArrayView1};
12
13/// Wavelet types for time series analysis
14#[derive(Debug, Clone, Copy)]
15pub enum TimeSeriesWaveletType {
16    /// Haar
17    Haar,
18    /// Daubechies4
19    Daubechies4,
20    /// Daubechies8
21    Daubechies8,
22    /// Biorthogonal
23    Biorthogonal,
24    /// Coiflets
25    Coiflets,
26}
27
28/// Feature types to extract from wavelet coefficients
29#[derive(Debug, Clone, Copy)]
30pub enum WaveletFeatureType {
31    /// Mean
32    Mean,
33    /// Variance
34    Variance,
35    /// Energy
36    Energy,
37    /// Entropy
38    Entropy,
39    /// Skewness
40    Skewness,
41    /// Kurtosis
42    Kurtosis,
43    /// MaxCoeff
44    MaxCoeff,
45    /// MinCoeff
46    MinCoeff,
47}
48
49/// Wavelet decomposition result
50struct WaveletDecomposition {
51    approximation: Array1<Float>,
52    details: Vec<Array1<Float>>,
53}
54
55/// Time series wavelet transform feature extractor
56///
57/// Extracts features from time series using discrete wavelet transform (DWT).
58/// This is different from the image wavelet features - it's specifically designed
59/// for 1D time series analysis and includes time-frequency decomposition.
60pub struct TimeSeriesWaveletExtractor {
61    wavelet_type: TimeSeriesWaveletType,
62    num_levels: usize,
63    feature_types: Vec<WaveletFeatureType>,
64    include_scaleogram: bool,
65    include_energy_ratios: bool,
66}
67
68impl TimeSeriesWaveletExtractor {
69    /// Create a new time series wavelet extractor
70    pub fn new() -> Self {
71        Self {
72            wavelet_type: TimeSeriesWaveletType::Haar,
73            num_levels: 4,
74            feature_types: vec![
75                WaveletFeatureType::Mean,
76                WaveletFeatureType::Variance,
77                WaveletFeatureType::Energy,
78                WaveletFeatureType::Entropy,
79            ],
80            include_scaleogram: false,
81            include_energy_ratios: true,
82        }
83    }
84
85    /// Set wavelet type
86    pub fn wavelet_type(mut self, wavelet_type: TimeSeriesWaveletType) -> Self {
87        self.wavelet_type = wavelet_type;
88        self
89    }
90
91    /// Set number of decomposition levels
92    pub fn num_levels(mut self, levels: usize) -> Self {
93        self.num_levels = levels;
94        self
95    }
96
97    /// Set feature types to extract
98    pub fn feature_types(mut self, feature_types: Vec<WaveletFeatureType>) -> Self {
99        self.feature_types = feature_types;
100        self
101    }
102
103    /// Include scaleogram features (time-frequency representation)
104    pub fn include_scaleogram(mut self, include: bool) -> Self {
105        self.include_scaleogram = include;
106        self
107    }
108
109    /// Include energy ratios between levels
110    pub fn include_energy_ratios(mut self, include: bool) -> Self {
111        self.include_energy_ratios = include;
112        self
113    }
114
115    /// Extract wavelet features from time series
116    pub fn extract_features(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
117        if ts.len() < 4 {
118            return Err(SklearsError::InvalidInput(
119                "Time series too short for wavelet analysis".to_string(),
120            ));
121        }
122
123        // Pad to power of 2 for efficient computation
124        let padded_ts = self.pad_to_power_of_2(ts)?;
125
126        // Perform wavelet decomposition
127        let decomposition = self.wavelet_decompose(&padded_ts)?;
128
129        let mut features = Vec::new();
130
131        // Extract features from each level
132        for level in 0..self.num_levels {
133            if level < decomposition.details.len() {
134                let detail_features = self.extract_level_features(&decomposition.details[level])?;
135                features.extend(detail_features);
136            }
137        }
138
139        // Extract features from approximation coefficients
140        let approx_features = self.extract_level_features(&decomposition.approximation)?;
141        features.extend(approx_features);
142
143        // Energy ratios between levels
144        if self.include_energy_ratios {
145            let energy_ratios = self.calculate_energy_ratios(&decomposition)?;
146            features.extend(energy_ratios);
147        }
148
149        // Scaleogram features (time-frequency analysis)
150        if self.include_scaleogram {
151            let scaleogram_features = self.extract_scaleogram_features(&padded_ts)?;
152            features.extend(scaleogram_features);
153        }
154
155        Ok(Array1::from_vec(features))
156    }
157
158    /// Pad time series to next power of 2
159    fn pad_to_power_of_2(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
160        let len = ts.len();
161        let next_power = (len as Float).log2().ceil() as usize;
162        let padded_len = 2_usize.pow(next_power as u32);
163
164        let mut padded = Array1::zeros(padded_len);
165        padded.slice_mut(s![..len]).assign(ts);
166
167        // Pad with zeros or replicate the boundary values
168        for i in len..padded_len {
169            padded[i] = ts[len - 1]; // Replicate last value
170        }
171
172        Ok(padded)
173    }
174
175    /// Perform discrete wavelet transform decomposition
176    fn wavelet_decompose(&self, signal: &Array1<Float>) -> SklResult<WaveletDecomposition> {
177        let (low_pass, high_pass) = self.get_wavelet_filters()?;
178
179        let mut approximation = signal.clone();
180        let mut details = Vec::new();
181
182        for _ in 0..self.num_levels {
183            if approximation.len() < 2 {
184                break;
185            }
186
187            let (approx, detail) = self.single_level_dwt(&approximation, &low_pass, &high_pass)?;
188            details.push(detail);
189            approximation = approx;
190        }
191
192        Ok(WaveletDecomposition {
193            approximation,
194            details,
195        })
196    }
197
198    /// Get wavelet filter coefficients
199    fn get_wavelet_filters(&self) -> SklResult<(Vec<Float>, Vec<Float>)> {
200        match self.wavelet_type {
201            TimeSeriesWaveletType::Haar => {
202                let low_pass = vec![
203                    std::f64::consts::FRAC_1_SQRT_2,
204                    std::f64::consts::FRAC_1_SQRT_2,
205                ];
206                let high_pass = vec![
207                    -std::f64::consts::FRAC_1_SQRT_2,
208                    std::f64::consts::FRAC_1_SQRT_2,
209                ];
210                Ok((low_pass, high_pass))
211            }
212            TimeSeriesWaveletType::Daubechies4 => {
213                let low_pass = vec![
214                    0.48296291314469025,
215                    0.8365163037378079,
216                    0.2241438680420134,
217                    -0.12940952255092145,
218                ];
219                let high_pass = vec![
220                    -0.12940952255092145,
221                    -0.2241438680420134,
222                    0.8365163037378079,
223                    -0.48296291314469025,
224                ];
225                Ok((low_pass, high_pass))
226            }
227            TimeSeriesWaveletType::Daubechies8 => {
228                let low_pass = vec![
229                    0.23037781330885523,
230                    0.7148465705525415,
231                    0.6308807679295904,
232                    -0.02798376941698385,
233                    -0.18703481171888114,
234                    0.030841381835986965,
235                    0.032883011666982945,
236                    -0.010597401784997278,
237                ];
238                let high_pass = vec![
239                    -0.010597401784997278,
240                    -0.032883011666982945,
241                    0.030841381835986965,
242                    0.18703481171888114,
243                    -0.02798376941698385,
244                    -0.6308807679295904,
245                    0.7148465705525415,
246                    -0.23037781330885523,
247                ];
248                Ok((low_pass, high_pass))
249            }
250            TimeSeriesWaveletType::Biorthogonal => {
251                // Biorthogonal 2.2 wavelet filters
252                let low_pass = vec![
253                    -0.1767766952966369,
254                    0.3535533905932738,
255                    1.0606601717798214,
256                    0.3535533905932738,
257                    -0.1767766952966369,
258                ];
259                let high_pass = vec![0.0, -0.5, 1.0, -0.5, 0.0];
260                Ok((low_pass, high_pass))
261            }
262            TimeSeriesWaveletType::Coiflets => {
263                // Coiflets 2 (6-tap) wavelet filters
264                let low_pass = vec![
265                    -0.051429864679368207,
266                    0.23389032745436126,
267                    0.6054328720162073,
268                    0.6054328720162073,
269                    0.23389032745436126,
270                    -0.051429864679368207,
271                ];
272                let high_pass = vec![
273                    -0.051429864679368207,
274                    -0.23389032745436126,
275                    0.6054328720162073,
276                    -0.6054328720162073,
277                    0.23389032745436126,
278                    0.051429864679368207,
279                ];
280                Ok((low_pass, high_pass))
281            }
282        }
283    }
284
285    /// Single level discrete wavelet transform
286    fn single_level_dwt(
287        &self,
288        signal: &Array1<Float>,
289        low_pass: &[Float],
290        high_pass: &[Float],
291    ) -> SklResult<(Array1<Float>, Array1<Float>)> {
292        let n = signal.len();
293        let filter_len = low_pass.len();
294
295        // Convolution and downsampling
296        let mut approx = Vec::new();
297        let mut detail = Vec::new();
298
299        for i in (0..n).step_by(2) {
300            let mut low_sum = 0.0;
301            let mut high_sum = 0.0;
302
303            for j in 0..filter_len {
304                let idx = (i + j) % n; // Circular boundary conditions
305                low_sum += signal[idx] * low_pass[j];
306                high_sum += signal[idx] * high_pass[j];
307            }
308
309            approx.push(low_sum);
310            detail.push(high_sum);
311        }
312
313        Ok((Array1::from_vec(approx), Array1::from_vec(detail)))
314    }
315
316    /// Extract features from wavelet coefficients
317    fn extract_level_features(&self, coeffs: &Array1<Float>) -> SklResult<Vec<Float>> {
318        let mut features = Vec::new();
319
320        for &feature_type in &self.feature_types {
321            let feature_value = match feature_type {
322                WaveletFeatureType::Mean => coeffs.mean().unwrap_or(0.0),
323                WaveletFeatureType::Variance => coeffs.var(0.0),
324                WaveletFeatureType::Energy => coeffs.iter().map(|x| x * x).sum::<Float>(),
325                WaveletFeatureType::Entropy => self.calculate_wavelet_entropy(coeffs)?,
326                WaveletFeatureType::Skewness => self.calculate_skewness(coeffs),
327                WaveletFeatureType::Kurtosis => self.calculate_kurtosis(coeffs),
328                WaveletFeatureType::MaxCoeff => {
329                    coeffs.iter().cloned().fold(Float::NEG_INFINITY, Float::max)
330                }
331                WaveletFeatureType::MinCoeff => {
332                    coeffs.iter().cloned().fold(Float::INFINITY, Float::min)
333                }
334            };
335
336            features.push(feature_value);
337        }
338
339        Ok(features)
340    }
341
342    /// Calculate wavelet entropy
343    fn calculate_wavelet_entropy(&self, coeffs: &Array1<Float>) -> SklResult<Float> {
344        let energy: Float = coeffs.iter().map(|x| x * x).sum();
345
346        if energy < 1e-10 {
347            return Ok(0.0);
348        }
349
350        let mut entropy = 0.0;
351        for &coeff in coeffs.iter() {
352            let prob = (coeff * coeff) / energy;
353            if prob > 1e-10 {
354                entropy -= prob * prob.log2();
355            }
356        }
357
358        Ok(entropy)
359    }
360
361    /// Calculate skewness of coefficients
362    fn calculate_skewness(&self, coeffs: &Array1<Float>) -> Float {
363        let n = coeffs.len() as Float;
364        if n < 3.0 {
365            return 0.0;
366        }
367
368        let mean = coeffs.mean().unwrap_or(0.0);
369        let variance = coeffs.var(0.0);
370
371        if variance < 1e-10 {
372            return 0.0;
373        }
374
375        let std_dev = variance.sqrt();
376        let third_moment: Float = coeffs
377            .iter()
378            .map(|x| ((x - mean) / std_dev).powi(3))
379            .sum::<Float>()
380            / n;
381
382        third_moment
383    }
384
385    /// Calculate kurtosis of coefficients
386    fn calculate_kurtosis(&self, coeffs: &Array1<Float>) -> Float {
387        let n = coeffs.len() as Float;
388        if n < 4.0 {
389            return 0.0;
390        }
391
392        let mean = coeffs.mean().unwrap_or(0.0);
393        let variance = coeffs.var(0.0);
394
395        if variance < 1e-10 {
396            return 0.0;
397        }
398
399        let std_dev = variance.sqrt();
400        let fourth_moment: Float = coeffs
401            .iter()
402            .map(|x| ((x - mean) / std_dev).powi(4))
403            .sum::<Float>()
404            / n;
405
406        fourth_moment - 3.0 // Excess kurtosis
407    }
408
409    /// Calculate energy ratios between decomposition levels
410    fn calculate_energy_ratios(
411        &self,
412        decomposition: &WaveletDecomposition,
413    ) -> SklResult<Vec<Float>> {
414        let mut energies = Vec::new();
415
416        // Energy of approximation
417        let approx_energy: Float = decomposition.approximation.iter().map(|x| x * x).sum();
418        energies.push(approx_energy);
419
420        // Energy of details
421        for detail in &decomposition.details {
422            let detail_energy: Float = detail.iter().map(|x| x * x).sum();
423            energies.push(detail_energy);
424        }
425
426        let total_energy: Float = energies.iter().sum();
427
428        let mut ratios = Vec::new();
429        for energy in energies {
430            let ratio = if total_energy > 1e-10 {
431                energy / total_energy
432            } else {
433                0.0
434            };
435            ratios.push(ratio);
436        }
437
438        Ok(ratios)
439    }
440
441    /// Extract scaleogram features (time-frequency representation)
442    fn extract_scaleogram_features(&self, signal: &Array1<Float>) -> SklResult<Vec<Float>> {
443        let mut features = Vec::new();
444
445        // Continuous wavelet transform approximation using scales
446        let scales = vec![1.0, 2.0, 4.0, 8.0, 16.0];
447
448        for scale in scales {
449            let scaled_coeffs = self.continuous_wavelet_transform(signal, scale)?;
450
451            // Extract statistical features from each scale
452            let mean = scaled_coeffs.mean().unwrap_or(0.0);
453            let variance = scaled_coeffs.var(0.0);
454            let max_val = scaled_coeffs
455                .iter()
456                .cloned()
457                .fold(Float::NEG_INFINITY, Float::max);
458
459            features.extend_from_slice(&[mean, variance, max_val]);
460        }
461
462        Ok(features)
463    }
464
465    /// Simplified continuous wavelet transform for a single scale
466    fn continuous_wavelet_transform(
467        &self,
468        signal: &Array1<Float>,
469        scale: Float,
470    ) -> SklResult<Array1<Float>> {
471        let n = signal.len();
472        let mut coeffs = Array1::zeros(n);
473
474        // Morlet wavelet approximation
475        for i in 0..n {
476            let mut sum = 0.0;
477            for j in 0..n {
478                let t = (j as Float - i as Float) / scale;
479                let wavelet_val = (t * t / 2.0).exp() * (5.0 * t).cos(); // Simplified Morlet
480                sum += signal[j] * wavelet_val;
481            }
482            coeffs[i] = sum / scale.sqrt();
483        }
484
485        Ok(coeffs)
486    }
487}
488
489impl Default for TimeSeriesWaveletExtractor {
490    fn default() -> Self {
491        Self::new()
492    }
493}
494
495/// Time-frequency feature extractor
496///
497/// Combines multiple time-frequency analysis methods including
498/// wavelets, spectrograms, and other time-frequency representations.
499pub struct TimeFrequencyExtractor {
500    window_size: usize,
501    hop_size: usize,
502    num_frequency_bins: usize,
503    include_spectral_features: bool,
504    include_wavelet_features: bool,
505    include_instantaneous_features: bool,
506}
507
508impl TimeFrequencyExtractor {
509    /// Create a new time-frequency extractor
510    pub fn new() -> Self {
511        Self {
512            window_size: 256,
513            hop_size: 128,
514            num_frequency_bins: 64,
515            include_spectral_features: true,
516            include_wavelet_features: true,
517            include_instantaneous_features: false,
518        }
519    }
520
521    /// Set window size for STFT
522    pub fn window_size(mut self, size: usize) -> Self {
523        self.window_size = size;
524        self
525    }
526
527    /// Set hop size for STFT
528    pub fn hop_size(mut self, size: usize) -> Self {
529        self.hop_size = size;
530        self
531    }
532
533    /// Set number of frequency bins
534    pub fn num_frequency_bins(mut self, bins: usize) -> Self {
535        self.num_frequency_bins = bins;
536        self
537    }
538
539    /// Include spectral features from STFT
540    pub fn include_spectral_features(mut self, include: bool) -> Self {
541        self.include_spectral_features = include;
542        self
543    }
544
545    /// Include wavelet-based features
546    pub fn include_wavelet_features(mut self, include: bool) -> Self {
547        self.include_wavelet_features = include;
548        self
549    }
550
551    /// Include instantaneous frequency and phase features
552    pub fn include_instantaneous_features(mut self, include: bool) -> Self {
553        self.include_instantaneous_features = include;
554        self
555    }
556
557    /// Extract time-frequency features
558    pub fn extract_features(&self, ts: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
559        if ts.len() < self.window_size {
560            return Err(SklearsError::InvalidInput(
561                "Time series too short for time-frequency analysis".to_string(),
562            ));
563        }
564
565        let mut features = Vec::new();
566
567        // Spectral features from STFT
568        if self.include_spectral_features {
569            let spectral_features = self.extract_spectral_features(ts)?;
570            features.extend(spectral_features);
571        }
572
573        // Wavelet-based time-frequency features
574        if self.include_wavelet_features {
575            let wavelet_extractor = TimeSeriesWaveletExtractor::new()
576                .wavelet_type(TimeSeriesWaveletType::Daubechies4)
577                .num_levels(3)
578                .include_scaleogram(true);
579
580            let wavelet_features = wavelet_extractor.extract_features(ts)?;
581            features.extend(wavelet_features.to_vec());
582        }
583
584        // Instantaneous features
585        if self.include_instantaneous_features {
586            let instantaneous_features = self.extract_instantaneous_features(ts)?;
587            features.extend(instantaneous_features);
588        }
589
590        Ok(Array1::from_vec(features))
591    }
592
593    /// Extract spectral features from short-time Fourier transform
594    fn extract_spectral_features(&self, ts: &ArrayView1<Float>) -> SklResult<Vec<Float>> {
595        let num_windows = (ts.len() - self.window_size) / self.hop_size + 1;
596        let mut spectral_centroids = Vec::new();
597        let mut spectral_bandwidths = Vec::new();
598        let mut spectral_rolloffs = Vec::new();
599
600        for i in 0..num_windows {
601            let start = i * self.hop_size;
602            let end = start + self.window_size;
603
604            if end <= ts.len() {
605                let window = ts.slice(s![start..end]);
606                let windowed = self.apply_hanning_window(&window.to_owned())?;
607
608                // Compute FFT for this window
609                let fft_result = self.compute_fft(&windowed)?;
610                let max_freq = fft_result.len() / 2; // Nyquist frequency
611
612                // Spectral centroid
613                let mut weighted_sum = 0.0;
614                let mut total_power = 0.0;
615
616                for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
617                    let power = real * real + imag * imag;
618                    weighted_sum += i as Float * power;
619                    total_power += power;
620                }
621
622                let centroid = if total_power > 1e-10 {
623                    weighted_sum / total_power
624                } else {
625                    0.0
626                };
627                spectral_centroids.push(centroid);
628
629                // Spectral bandwidth
630                let mut bandwidth_sum = 0.0;
631                for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
632                    let power = real * real + imag * imag;
633                    bandwidth_sum += (i as Float - centroid).powi(2) * power;
634                }
635
636                let bandwidth = if total_power > 1e-10 {
637                    (bandwidth_sum / total_power).sqrt()
638                } else {
639                    0.0
640                };
641                spectral_bandwidths.push(bandwidth);
642
643                // Spectral rolloff
644                let target_energy = 0.85 * total_power;
645                let mut cumulative_energy = 0.0;
646                let mut rolloff = 0.0;
647
648                for (i, &(real, imag)) in fft_result.iter().take(max_freq).enumerate() {
649                    let power = real * real + imag * imag;
650                    cumulative_energy += power;
651                    if cumulative_energy >= target_energy {
652                        rolloff = i as Float;
653                        break;
654                    }
655                }
656                spectral_rolloffs.push(rolloff);
657            }
658        }
659
660        let mut features = Vec::new();
661
662        // Statistical features of spectral measures across time
663        features.push(spectral_centroids.iter().sum::<Float>() / spectral_centroids.len() as Float);
664        features
665            .push(spectral_bandwidths.iter().sum::<Float>() / spectral_bandwidths.len() as Float);
666        features.push(spectral_rolloffs.iter().sum::<Float>() / spectral_rolloffs.len() as Float);
667
668        // Variance of spectral measures
669        let centroid_mean = features[features.len() - 3];
670        let bandwidth_mean = features[features.len() - 2];
671        let rolloff_mean = features[features.len() - 1];
672
673        let centroid_var = spectral_centroids
674            .iter()
675            .map(|x| (x - centroid_mean).powi(2))
676            .sum::<Float>()
677            / spectral_centroids.len() as Float;
678        let bandwidth_var = spectral_bandwidths
679            .iter()
680            .map(|x| (x - bandwidth_mean).powi(2))
681            .sum::<Float>()
682            / spectral_bandwidths.len() as Float;
683        let rolloff_var = spectral_rolloffs
684            .iter()
685            .map(|x| (x - rolloff_mean).powi(2))
686            .sum::<Float>()
687            / spectral_rolloffs.len() as Float;
688
689        features.extend_from_slice(&[centroid_var, bandwidth_var, rolloff_var]);
690
691        Ok(features)
692    }
693
694    /// Extract instantaneous frequency and phase features
695    fn extract_instantaneous_features(&self, ts: &ArrayView1<Float>) -> SklResult<Vec<Float>> {
696        // Hilbert transform approximation for analytic signal
697        let analytic_signal = self.hilbert_transform(ts)?;
698
699        let mut instantaneous_freqs = Vec::new();
700        let mut instantaneous_phases = Vec::new();
701
702        for i in 1..analytic_signal.len() {
703            let (real_prev, imag_prev) = analytic_signal[i - 1];
704            let (real_curr, imag_curr) = analytic_signal[i];
705
706            // Instantaneous phase
707            let phase_prev = imag_prev.atan2(real_prev);
708            let phase_curr = imag_curr.atan2(real_curr);
709
710            // Instantaneous frequency (derivative of phase)
711            let mut freq = phase_curr - phase_prev;
712
713            // Unwrap phase
714            if freq > std::f64::consts::PI {
715                freq -= 2.0 * std::f64::consts::PI;
716            } else if freq < -std::f64::consts::PI {
717                freq += 2.0 * std::f64::consts::PI;
718            }
719
720            instantaneous_freqs.push(freq);
721            instantaneous_phases.push(phase_curr);
722        }
723
724        let mut features = Vec::new();
725
726        // Statistical features of instantaneous frequency
727        if !instantaneous_freqs.is_empty() {
728            let mean_freq =
729                instantaneous_freqs.iter().sum::<Float>() / instantaneous_freqs.len() as Float;
730            let var_freq = instantaneous_freqs
731                .iter()
732                .map(|x| (x - mean_freq).powi(2))
733                .sum::<Float>()
734                / instantaneous_freqs.len() as Float;
735
736            features.extend_from_slice(&[mean_freq, var_freq]);
737        }
738
739        // Phase-based features
740        if !instantaneous_phases.is_empty() {
741            let phase_variance = instantaneous_phases
742                .iter()
743                .map(|x| x.powi(2))
744                .sum::<Float>()
745                / instantaneous_phases.len() as Float;
746
747            features.push(phase_variance);
748        }
749
750        Ok(features)
751    }
752
753    /// Apply Hanning window
754    fn apply_hanning_window(&self, signal: &Array1<Float>) -> SklResult<Array1<Float>> {
755        let n = signal.len();
756        let mut windowed = Array1::zeros(n);
757
758        for i in 0..n {
759            let window_val =
760                0.5 * (1.0 - (2.0 * std::f64::consts::PI * i as Float / (n - 1) as Float).cos());
761            windowed[i] = signal[i] * window_val;
762        }
763
764        Ok(windowed)
765    }
766
767    /// Simplified FFT computation
768    fn compute_fft(&self, signal: &Array1<Float>) -> SklResult<Vec<(Float, Float)>> {
769        let n = signal.len();
770        let mut result = vec![(0.0, 0.0); n];
771
772        // Simple DFT (for production, use a proper FFT implementation)
773        // k used in trig angle computation k*j, not just indexing
774        #[allow(clippy::needless_range_loop)]
775        for k in 0..n {
776            let mut real_sum = 0.0;
777            let mut imag_sum = 0.0;
778
779            for j in 0..n {
780                let angle = -2.0 * std::f64::consts::PI * (k * j) as Float / n as Float;
781                real_sum += signal[j] * angle.cos();
782                imag_sum += signal[j] * angle.sin();
783            }
784
785            result[k] = (real_sum, imag_sum);
786        }
787
788        Ok(result)
789    }
790
791    /// Simplified Hilbert transform
792    fn hilbert_transform(&self, signal: &ArrayView1<Float>) -> SklResult<Vec<(Float, Float)>> {
793        let n = signal.len();
794        let mut result = Vec::with_capacity(n);
795
796        // Simple Hilbert transform approximation
797        for i in 0..n {
798            let mut hilbert_val = 0.0;
799
800            for j in 0..n {
801                if i != j {
802                    hilbert_val += signal[j] / (std::f64::consts::PI * (i as Float - j as Float));
803                }
804            }
805
806            result.push((signal[i], hilbert_val));
807        }
808
809        Ok(result)
810    }
811}
812
813impl Default for TimeFrequencyExtractor {
814    fn default() -> Self {
815        Self::new()
816    }
817}