gaitsetpy.features.physionet_features

PhysioNet VGRF Feature Extractor. Maintainer: @aharshit123456

This module contains feature extractors specific to the PhysioNet VGRF dataset, including Local Binary Pattern (LBP) and Fourier series analysis.

  1'''
  2PhysioNet VGRF Feature Extractor.
  3Maintainer: @aharshit123456
  4
  5This module contains feature extractors specific to the PhysioNet VGRF dataset,
  6including Local Binary Pattern (LBP) and Fourier series analysis.
  7'''
  8
  9from typing import List, Dict, Any
 10import numpy as np
 11import pandas as pd
 12from scipy.integrate import simpson
 13from scipy.signal import find_peaks
 14from scipy.optimize import curve_fit
 15from scipy import fftpack
 16import logging
 17from tqdm import tqdm
 18from ..core.base_classes import BaseFeatureExtractor
 19
 20# Set up logging
 21logging.basicConfig(level=logging.INFO)
 22logger = logging.getLogger(__name__)
 23
 24
 25class LBPFeatureExtractor(BaseFeatureExtractor):
 26    """
 27    Local Binary Pattern (LBP) feature extractor for VGRF data.
 28    
 29    This extractor converts time-series data into LBP codes and extracts
 30    histogram features from the LBP representation.
 31    """
 32    
 33    def __init__(self, verbose: bool = True):
 34        super().__init__(
 35            name="lbp_features",
 36            description="Local Binary Pattern feature extractor for VGRF time-series data"
 37        )
 38        self.verbose = verbose
 39        self.config = {
 40            'radius': 2,  # LBP radius (number of neighbors)
 41            'n_bins': 256,  # Number of histogram bins
 42            'normalize': True  # Normalize histogram
 43        }
 44        
 45        if self.verbose:
 46            print("🔍 LBP Feature Extractor initialized!")
 47    
 48    def lbp_1d(self, data: np.ndarray, radius: int = 2) -> str:
 49        """
 50        Compute 1D Local Binary Pattern for time-series data.
 51        
 52        Args:
 53            data: Input time-series data
 54            radius: Radius for LBP computation
 55            
 56        Returns:
 57            LBP code as binary string
 58        """
 59        n = len(data)
 60        lbp_code = ''
 61        
 62        for i in range(n):
 63            pattern = ''
 64            for j in range(i - radius, i + radius + 1):
 65                if j < 0 or j >= n:
 66                    pattern += '0'
 67                elif data[j] >= data[i]:
 68                    pattern += '1'
 69                else:
 70                    pattern += '0'
 71            lbp_code += pattern
 72        
 73        return lbp_code
 74    
 75    def lbp_to_histogram(self, lbp_code: str, n_bins: int = 256, normalize: bool = True) -> np.ndarray:
 76        """
 77        Convert LBP code to histogram features.
 78        
 79        Args:
 80            lbp_code: Binary LBP code string
 81            n_bins: Number of histogram bins
 82            normalize: Whether to normalize histogram
 83            
 84        Returns:
 85            Histogram features as numpy array
 86        """
 87        # Convert LBP code to integer values
 88        if len(lbp_code) == 0:
 89            return np.zeros(n_bins)
 90        
 91        # Process LBP code in chunks of 8 bits (or smaller)
 92        chunk_size = 8
 93        lbp_values = []
 94        
 95        for i in range(0, len(lbp_code), chunk_size):
 96            chunk = lbp_code[i:i + chunk_size]
 97            if len(chunk) > 0:
 98                # Convert binary string to integer
 99                try:
100                    value = int(chunk, 2)
101                    lbp_values.append(value % n_bins)  # Ensure within bin range
102                except ValueError:
103                    continue
104        
105        if len(lbp_values) == 0:
106            return np.zeros(n_bins)
107        
108        # Create histogram
109        hist, _ = np.histogram(lbp_values, bins=n_bins, range=(0, n_bins))
110        
111        if normalize and np.sum(hist) > 0:
112            hist = hist / np.sum(hist)
113        
114        return hist
115    
116    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
117        """
118        Extract LBP features from sliding windows.
119        
120        Args:
121            windows: List of sliding window dictionaries
122            fs: Sampling frequency (unused for LBP)
123            **kwargs: Additional arguments
124            
125        Returns:
126            List of feature dictionaries
127        """
128        # Update config with any passed arguments
129        radius = kwargs.get('radius', self.config['radius'])
130        n_bins = kwargs.get('n_bins', self.config['n_bins'])
131        normalize = kwargs.get('normalize', self.config['normalize'])
132        
133        if self.verbose:
134            print(f"\n🔍 LBP Feature Extraction")
135            print(f"📊 Radius: {radius}, Bins: {n_bins}, Normalize: {normalize}")
136        
137        features = []
138        
139        for window_dict in tqdm(windows, desc="Processing LBP features", disable=not self.verbose):
140            sensor_name = window_dict['name']
141            window_data = window_dict['data']
142            
143            # Skip annotation windows
144            if sensor_name == 'annotations':
145                continue
146            
147            sensor_features = {'name': sensor_name, 'features': {}}
148            
149            # Extract LBP features for each window
150            lbp_histograms = []
151            lbp_means = []
152            lbp_stds = []
153            
154            for window in window_data:
155                # Ensure window is numpy array
156                if hasattr(window, 'values'):
157                    window = window.values
158                
159                # Compute LBP
160                lbp_code = self.lbp_1d(window, radius)
161                
162                # Convert to histogram
163                hist = self.lbp_to_histogram(lbp_code, n_bins, normalize)
164                lbp_histograms.append(hist)
165                
166                # Extract summary statistics
167                lbp_means.append(np.mean(hist))
168                lbp_stds.append(np.std(hist))
169            
170            # Store features
171            sensor_features['features'] = {
172                'lbp_histograms': lbp_histograms,
173                'lbp_mean': lbp_means,
174                'lbp_std': lbp_stds,
175                'lbp_energy': [np.sum(hist**2) for hist in lbp_histograms],
176                'lbp_entropy': [self._calculate_entropy(hist) for hist in lbp_histograms]
177            }
178            
179            features.append(sensor_features)
180        
181        return features
182    
183    def _calculate_entropy(self, hist: np.ndarray) -> float:
184        """Calculate entropy of histogram."""
185        # Avoid log(0) by adding small value
186        hist = hist + 1e-10
187        return -np.sum(hist * np.log2(hist))
188    
189    def get_feature_names(self) -> List[str]:
190        """Get names of LBP features."""
191        return [
192            'lbp_histograms', 'lbp_mean', 'lbp_std', 
193            'lbp_energy', 'lbp_entropy'
194        ]
195
196
197class FourierSeriesFeatureExtractor(BaseFeatureExtractor):
198    """
199    Fourier Series feature extractor for VGRF data.
200    
201    This extractor fits Fourier series to time-series data and extracts
202    coefficients and reconstruction features.
203    """
204    
205    def __init__(self, verbose: bool = True):
206        super().__init__(
207            name="fourier_features",
208            description="Fourier series feature extractor for VGRF time-series data"
209        )
210        self.verbose = verbose
211        self.config = {
212            'n_terms': 10,  # Number of Fourier terms
213            'period': 3.0,  # Period for Fourier series
214            'extract_coefficients': True,
215            'extract_reconstruction_error': True
216        }
217        
218        if self.verbose:
219            print("🌊 Fourier Series Feature Extractor initialized!")
220    
221    def fit_fourier_series(self, signal: np.ndarray, time_points: np.ndarray, 
222                          period: float = 3.0, n_terms: int = 10) -> Dict[str, Any]:
223        """
224        Fit Fourier series to signal.
225        
226        Args:
227            signal: Input signal
228            time_points: Time points
229            period: Period of the Fourier series
230            n_terms: Number of Fourier terms
231            
232        Returns:
233            Dictionary containing Fourier series parameters
234        """
235        try:
236            # Calculate Fourier coefficients
237            L = period
238            
239            # Calculate a0 (DC component)
240            a0 = 2/L * simpson(signal, time_points)
241            
242            # Calculate an and bn coefficients
243            an = []
244            bn = []
245            
246            for n in range(1, n_terms + 1):
247                # Calculate an coefficient
248                an_val = 2.0/L * simpson(signal * np.cos(2.*np.pi*n*time_points/L), time_points)
249                an.append(an_val)
250                
251                # Calculate bn coefficient
252                bn_val = 2.0/L * simpson(signal * np.sin(2.*np.pi*n*time_points/L), time_points)
253                bn.append(bn_val)
254            
255            # Reconstruct signal
256            reconstructed = np.full_like(time_points, a0/2)
257            for n in range(n_terms):
258                reconstructed += an[n] * np.cos(2.*np.pi*(n+1)*time_points/L)
259                reconstructed += bn[n] * np.sin(2.*np.pi*(n+1)*time_points/L)
260            
261            # Calculate reconstruction error
262            reconstruction_error = np.mean((signal - reconstructed)**2)
263            
264            return {
265                'a0': a0,
266                'an': an,
267                'bn': bn,
268                'reconstructed': reconstructed,
269                'reconstruction_error': reconstruction_error,
270                'fourier_energy': a0**2 + 2*np.sum(np.array(an)**2 + np.array(bn)**2)
271            }
272            
273        except Exception as e:
274            if self.verbose:
275                print(f"Error in Fourier series fitting: {e}")
276            return {
277                'a0': 0,
278                'an': [0] * n_terms,
279                'bn': [0] * n_terms,
280                'reconstructed': np.zeros_like(time_points),
281                'reconstruction_error': float('inf'),
282                'fourier_energy': 0
283            }
284    
285    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
286        """
287        Extract Fourier series features from sliding windows.
288        
289        Args:
290            windows: List of sliding window dictionaries
291            fs: Sampling frequency
292            **kwargs: Additional arguments
293            
294        Returns:
295            List of feature dictionaries
296        """
297        # Update config with any passed arguments
298        n_terms = kwargs.get('n_terms', self.config['n_terms'])
299        period = kwargs.get('period', self.config['period'])
300        
301        if self.verbose:
302            print(f"\n🌊 Fourier Series Feature Extraction")
303            print(f"📊 Terms: {n_terms}, Period: {period}")
304        
305        features = []
306        
307        for window_dict in tqdm(windows, desc="Processing Fourier features", disable=not self.verbose):
308            sensor_name = window_dict['name']
309            window_data = window_dict['data']
310            
311            # Skip annotation windows
312            if sensor_name == 'annotations':
313                continue
314            
315            sensor_features = {'name': sensor_name, 'features': {}}
316            
317            # Extract Fourier features for each window
318            a0_values = []
319            an_values = []
320            bn_values = []
321            reconstruction_errors = []
322            fourier_energies = []
323            
324            for window in window_data:
325                # Ensure window is numpy array
326                if hasattr(window, 'values'):
327                    window = window.values
328                
329                # Create time points
330                time_points = np.linspace(0, period, len(window))
331                
332                # Fit Fourier series
333                fourier_result = self.fit_fourier_series(window, time_points, period, n_terms)
334                
335                # Store results
336                a0_values.append(fourier_result['a0'])
337                an_values.append(fourier_result['an'])
338                bn_values.append(fourier_result['bn'])
339                reconstruction_errors.append(fourier_result['reconstruction_error'])
340                fourier_energies.append(fourier_result['fourier_energy'])
341            
342            # Store features
343            sensor_features['features'] = {
344                'fourier_a0': a0_values,
345                'fourier_an': an_values,
346                'fourier_bn': bn_values,
347                'fourier_reconstruction_error': reconstruction_errors,
348                'fourier_energy': fourier_energies,
349                'fourier_an_mean': [np.mean(an) for an in an_values],
350                'fourier_bn_mean': [np.mean(bn) for bn in bn_values],
351                'fourier_an_std': [np.std(an) for an in an_values],
352                'fourier_bn_std': [np.std(bn) for bn in bn_values]
353            }
354            
355            features.append(sensor_features)
356        
357        return features
358    
359    def get_feature_names(self) -> List[str]:
360        """Get names of Fourier series features."""
361        return [
362            'fourier_a0', 'fourier_an', 'fourier_bn', 
363            'fourier_reconstruction_error', 'fourier_energy',
364            'fourier_an_mean', 'fourier_bn_mean',
365            'fourier_an_std', 'fourier_bn_std'
366        ]
367
368
369class PhysioNetFeatureExtractor(BaseFeatureExtractor):
370    """
371    Combined feature extractor for PhysioNet VGRF data.
372    
373    This extractor combines LBP and Fourier series features along with
374    basic statistical features specific to VGRF data.
375    """
376    
377    def __init__(self, verbose: bool = True):
378        super().__init__(
379            name="physionet_features",
380            description="Combined feature extractor for PhysioNet VGRF data including LBP and Fourier features"
381        )
382        self.verbose = verbose
383        self.lbp_extractor = LBPFeatureExtractor(verbose=False)
384        self.fourier_extractor = FourierSeriesFeatureExtractor(verbose=False)
385        
386        if self.verbose:
387            print("🚀 PhysioNet Feature Extractor initialized!")
388    
389    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
390        """
391        Extract combined features from sliding windows.
392        
393        Args:
394            windows: List of sliding window dictionaries
395            fs: Sampling frequency
396            **kwargs: Additional arguments
397            
398        Returns:
399            List of feature dictionaries
400        """
401        # Extract features from each extractor
402        extract_lbp = kwargs.get('extract_lbp', True)
403        extract_fourier = kwargs.get('extract_fourier', True)
404        extract_statistical = kwargs.get('extract_statistical', True)
405        
406        if self.verbose:
407            print(f"\n🔍 PhysioNet Feature Extraction")
408            print(f"📊 LBP: {extract_lbp}, Fourier: {extract_fourier}, Statistical: {extract_statistical}")
409        
410        features = []
411        
412        # Extract LBP features
413        if extract_lbp:
414            lbp_features = self.lbp_extractor.extract_features(windows, fs, **kwargs)
415        else:
416            lbp_features = []
417        
418        # Extract Fourier features
419        if extract_fourier:
420            fourier_features = self.fourier_extractor.extract_features(windows, fs, **kwargs)
421        else:
422            fourier_features = []
423        
424        # Extract statistical features
425        if extract_statistical:
426            statistical_features = self._extract_statistical_features(windows)
427        else:
428            statistical_features = []
429        
430        # Combine features
431        for i, window_dict in enumerate(windows):
432            sensor_name = window_dict['name']
433            
434            # Skip annotation windows
435            if sensor_name == 'annotations':
436                continue
437            
438            combined_features = {'name': sensor_name, 'features': {}}
439            
440            # Add LBP features
441            if extract_lbp and i < len(lbp_features):
442                combined_features['features'].update(lbp_features[i]['features'])
443            
444            # Add Fourier features
445            if extract_fourier and i < len(fourier_features):
446                combined_features['features'].update(fourier_features[i]['features'])
447            
448            # Add statistical features
449            if extract_statistical and i < len(statistical_features):
450                combined_features['features'].update(statistical_features[i]['features'])
451            
452            features.append(combined_features)
453        
454        return features
455    
456    def _extract_statistical_features(self, windows: List[Dict]) -> List[Dict]:
457        """Extract basic statistical features."""
458        features = []
459        
460        for window_dict in windows:
461            sensor_name = window_dict['name']
462            window_data = window_dict['data']
463            
464            # Skip annotation windows
465            if sensor_name == 'annotations':
466                continue
467            
468            sensor_features = {'name': sensor_name, 'features': {}}
469            
470            # Extract statistical features for each window
471            means = []
472            stds = []
473            maxs = []
474            mins = []
475            ranges = []
476            
477            for window in window_data:
478                # Ensure window is numpy array
479                if hasattr(window, 'values'):
480                    window = window.values
481                
482                means.append(np.mean(window))
483                stds.append(np.std(window))
484                maxs.append(np.max(window))
485                mins.append(np.min(window))
486                ranges.append(np.max(window) - np.min(window))
487            
488            # Store features
489            sensor_features['features'] = {
490                'vgrf_mean': means,
491                'vgrf_std': stds,
492                'vgrf_max': maxs,
493                'vgrf_min': mins,
494                'vgrf_range': ranges
495            }
496            
497            features.append(sensor_features)
498        
499        return features
500    
501    def get_feature_names(self) -> List[str]:
502        """Get names of all features."""
503        feature_names = []
504        feature_names.extend(self.lbp_extractor.get_feature_names())
505        feature_names.extend(self.fourier_extractor.get_feature_names())
506        feature_names.extend(['vgrf_mean', 'vgrf_std', 'vgrf_max', 'vgrf_min', 'vgrf_range'])
507        return feature_names
508
509
510# Legacy functions for backward compatibility
511def extract_lbp_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
512    """
513    Legacy function to extract LBP features.
514    
515    Args:
516        windows: List of sliding window dictionaries
517        fs: Sampling frequency
518        **kwargs: Additional arguments
519        
520    Returns:
521        List of feature dictionaries
522    """
523    extractor = LBPFeatureExtractor(verbose=kwargs.get('verbose', True))
524    return extractor.extract_features(windows, fs, **kwargs)
525
526
527def extract_fourier_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
528    """
529    Legacy function to extract Fourier series features.
530    
531    Args:
532        windows: List of sliding window dictionaries
533        fs: Sampling frequency
534        **kwargs: Additional arguments
535        
536    Returns:
537        List of feature dictionaries
538    """
539    extractor = FourierSeriesFeatureExtractor(verbose=kwargs.get('verbose', True))
540    return extractor.extract_features(windows, fs, **kwargs)
541
542
543def extract_physionet_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
544    """
545    Legacy function to extract combined PhysioNet features.
546    
547    Args:
548        windows: List of sliding window dictionaries
549        fs: Sampling frequency
550        **kwargs: Additional arguments
551        
552    Returns:
553        List of feature dictionaries
554    """
555    extractor = PhysioNetFeatureExtractor(verbose=kwargs.get('verbose', True))
556    return extractor.extract_features(windows, fs, **kwargs) 
logger = <Logger gaitsetpy.features.physionet_features (INFO)>
class LBPFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
 26class LBPFeatureExtractor(BaseFeatureExtractor):
 27    """
 28    Local Binary Pattern (LBP) feature extractor for VGRF data.
 29    
 30    This extractor converts time-series data into LBP codes and extracts
 31    histogram features from the LBP representation.
 32    """
 33    
 34    def __init__(self, verbose: bool = True):
 35        super().__init__(
 36            name="lbp_features",
 37            description="Local Binary Pattern feature extractor for VGRF time-series data"
 38        )
 39        self.verbose = verbose
 40        self.config = {
 41            'radius': 2,  # LBP radius (number of neighbors)
 42            'n_bins': 256,  # Number of histogram bins
 43            'normalize': True  # Normalize histogram
 44        }
 45        
 46        if self.verbose:
 47            print("🔍 LBP Feature Extractor initialized!")
 48    
 49    def lbp_1d(self, data: np.ndarray, radius: int = 2) -> str:
 50        """
 51        Compute 1D Local Binary Pattern for time-series data.
 52        
 53        Args:
 54            data: Input time-series data
 55            radius: Radius for LBP computation
 56            
 57        Returns:
 58            LBP code as binary string
 59        """
 60        n = len(data)
 61        lbp_code = ''
 62        
 63        for i in range(n):
 64            pattern = ''
 65            for j in range(i - radius, i + radius + 1):
 66                if j < 0 or j >= n:
 67                    pattern += '0'
 68                elif data[j] >= data[i]:
 69                    pattern += '1'
 70                else:
 71                    pattern += '0'
 72            lbp_code += pattern
 73        
 74        return lbp_code
 75    
 76    def lbp_to_histogram(self, lbp_code: str, n_bins: int = 256, normalize: bool = True) -> np.ndarray:
 77        """
 78        Convert LBP code to histogram features.
 79        
 80        Args:
 81            lbp_code: Binary LBP code string
 82            n_bins: Number of histogram bins
 83            normalize: Whether to normalize histogram
 84            
 85        Returns:
 86            Histogram features as numpy array
 87        """
 88        # Convert LBP code to integer values
 89        if len(lbp_code) == 0:
 90            return np.zeros(n_bins)
 91        
 92        # Process LBP code in chunks of 8 bits (or smaller)
 93        chunk_size = 8
 94        lbp_values = []
 95        
 96        for i in range(0, len(lbp_code), chunk_size):
 97            chunk = lbp_code[i:i + chunk_size]
 98            if len(chunk) > 0:
 99                # Convert binary string to integer
100                try:
101                    value = int(chunk, 2)
102                    lbp_values.append(value % n_bins)  # Ensure within bin range
103                except ValueError:
104                    continue
105        
106        if len(lbp_values) == 0:
107            return np.zeros(n_bins)
108        
109        # Create histogram
110        hist, _ = np.histogram(lbp_values, bins=n_bins, range=(0, n_bins))
111        
112        if normalize and np.sum(hist) > 0:
113            hist = hist / np.sum(hist)
114        
115        return hist
116    
117    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
118        """
119        Extract LBP features from sliding windows.
120        
121        Args:
122            windows: List of sliding window dictionaries
123            fs: Sampling frequency (unused for LBP)
124            **kwargs: Additional arguments
125            
126        Returns:
127            List of feature dictionaries
128        """
129        # Update config with any passed arguments
130        radius = kwargs.get('radius', self.config['radius'])
131        n_bins = kwargs.get('n_bins', self.config['n_bins'])
132        normalize = kwargs.get('normalize', self.config['normalize'])
133        
134        if self.verbose:
135            print(f"\n🔍 LBP Feature Extraction")
136            print(f"📊 Radius: {radius}, Bins: {n_bins}, Normalize: {normalize}")
137        
138        features = []
139        
140        for window_dict in tqdm(windows, desc="Processing LBP features", disable=not self.verbose):
141            sensor_name = window_dict['name']
142            window_data = window_dict['data']
143            
144            # Skip annotation windows
145            if sensor_name == 'annotations':
146                continue
147            
148            sensor_features = {'name': sensor_name, 'features': {}}
149            
150            # Extract LBP features for each window
151            lbp_histograms = []
152            lbp_means = []
153            lbp_stds = []
154            
155            for window in window_data:
156                # Ensure window is numpy array
157                if hasattr(window, 'values'):
158                    window = window.values
159                
160                # Compute LBP
161                lbp_code = self.lbp_1d(window, radius)
162                
163                # Convert to histogram
164                hist = self.lbp_to_histogram(lbp_code, n_bins, normalize)
165                lbp_histograms.append(hist)
166                
167                # Extract summary statistics
168                lbp_means.append(np.mean(hist))
169                lbp_stds.append(np.std(hist))
170            
171            # Store features
172            sensor_features['features'] = {
173                'lbp_histograms': lbp_histograms,
174                'lbp_mean': lbp_means,
175                'lbp_std': lbp_stds,
176                'lbp_energy': [np.sum(hist**2) for hist in lbp_histograms],
177                'lbp_entropy': [self._calculate_entropy(hist) for hist in lbp_histograms]
178            }
179            
180            features.append(sensor_features)
181        
182        return features
183    
184    def _calculate_entropy(self, hist: np.ndarray) -> float:
185        """Calculate entropy of histogram."""
186        # Avoid log(0) by adding small value
187        hist = hist + 1e-10
188        return -np.sum(hist * np.log2(hist))
189    
190    def get_feature_names(self) -> List[str]:
191        """Get names of LBP features."""
192        return [
193            'lbp_histograms', 'lbp_mean', 'lbp_std', 
194            'lbp_energy', 'lbp_entropy'
195        ]

Local Binary Pattern (LBP) feature extractor for VGRF data.

This extractor converts time-series data into LBP codes and extracts histogram features from the LBP representation.

LBPFeatureExtractor(verbose: bool = True)
34    def __init__(self, verbose: bool = True):
35        super().__init__(
36            name="lbp_features",
37            description="Local Binary Pattern feature extractor for VGRF time-series data"
38        )
39        self.verbose = verbose
40        self.config = {
41            'radius': 2,  # LBP radius (number of neighbors)
42            'n_bins': 256,  # Number of histogram bins
43            'normalize': True  # Normalize histogram
44        }
45        
46        if self.verbose:
47            print("🔍 LBP Feature Extractor initialized!")

Initialize the feature extractor.

Args: name: Name of the feature extractor description: Description of the feature extractor

verbose
config
def lbp_1d(self, data: numpy.ndarray, radius: int = 2) -> str:
49    def lbp_1d(self, data: np.ndarray, radius: int = 2) -> str:
50        """
51        Compute 1D Local Binary Pattern for time-series data.
52        
53        Args:
54            data: Input time-series data
55            radius: Radius for LBP computation
56            
57        Returns:
58            LBP code as binary string
59        """
60        n = len(data)
61        lbp_code = ''
62        
63        for i in range(n):
64            pattern = ''
65            for j in range(i - radius, i + radius + 1):
66                if j < 0 or j >= n:
67                    pattern += '0'
68                elif data[j] >= data[i]:
69                    pattern += '1'
70                else:
71                    pattern += '0'
72            lbp_code += pattern
73        
74        return lbp_code

Compute 1D Local Binary Pattern for time-series data.

Args: data: Input time-series data radius: Radius for LBP computation

Returns: LBP code as binary string

def lbp_to_histogram( self, lbp_code: str, n_bins: int = 256, normalize: bool = True) -> numpy.ndarray:
 76    def lbp_to_histogram(self, lbp_code: str, n_bins: int = 256, normalize: bool = True) -> np.ndarray:
 77        """
 78        Convert LBP code to histogram features.
 79        
 80        Args:
 81            lbp_code: Binary LBP code string
 82            n_bins: Number of histogram bins
 83            normalize: Whether to normalize histogram
 84            
 85        Returns:
 86            Histogram features as numpy array
 87        """
 88        # Convert LBP code to integer values
 89        if len(lbp_code) == 0:
 90            return np.zeros(n_bins)
 91        
 92        # Process LBP code in chunks of 8 bits (or smaller)
 93        chunk_size = 8
 94        lbp_values = []
 95        
 96        for i in range(0, len(lbp_code), chunk_size):
 97            chunk = lbp_code[i:i + chunk_size]
 98            if len(chunk) > 0:
 99                # Convert binary string to integer
100                try:
101                    value = int(chunk, 2)
102                    lbp_values.append(value % n_bins)  # Ensure within bin range
103                except ValueError:
104                    continue
105        
106        if len(lbp_values) == 0:
107            return np.zeros(n_bins)
108        
109        # Create histogram
110        hist, _ = np.histogram(lbp_values, bins=n_bins, range=(0, n_bins))
111        
112        if normalize and np.sum(hist) > 0:
113            hist = hist / np.sum(hist)
114        
115        return hist

Convert LBP code to histogram features.

Args: lbp_code: Binary LBP code string n_bins: Number of histogram bins normalize: Whether to normalize histogram

Returns: Histogram features as numpy array

def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
117    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
118        """
119        Extract LBP features from sliding windows.
120        
121        Args:
122            windows: List of sliding window dictionaries
123            fs: Sampling frequency (unused for LBP)
124            **kwargs: Additional arguments
125            
126        Returns:
127            List of feature dictionaries
128        """
129        # Update config with any passed arguments
130        radius = kwargs.get('radius', self.config['radius'])
131        n_bins = kwargs.get('n_bins', self.config['n_bins'])
132        normalize = kwargs.get('normalize', self.config['normalize'])
133        
134        if self.verbose:
135            print(f"\n🔍 LBP Feature Extraction")
136            print(f"📊 Radius: {radius}, Bins: {n_bins}, Normalize: {normalize}")
137        
138        features = []
139        
140        for window_dict in tqdm(windows, desc="Processing LBP features", disable=not self.verbose):
141            sensor_name = window_dict['name']
142            window_data = window_dict['data']
143            
144            # Skip annotation windows
145            if sensor_name == 'annotations':
146                continue
147            
148            sensor_features = {'name': sensor_name, 'features': {}}
149            
150            # Extract LBP features for each window
151            lbp_histograms = []
152            lbp_means = []
153            lbp_stds = []
154            
155            for window in window_data:
156                # Ensure window is numpy array
157                if hasattr(window, 'values'):
158                    window = window.values
159                
160                # Compute LBP
161                lbp_code = self.lbp_1d(window, radius)
162                
163                # Convert to histogram
164                hist = self.lbp_to_histogram(lbp_code, n_bins, normalize)
165                lbp_histograms.append(hist)
166                
167                # Extract summary statistics
168                lbp_means.append(np.mean(hist))
169                lbp_stds.append(np.std(hist))
170            
171            # Store features
172            sensor_features['features'] = {
173                'lbp_histograms': lbp_histograms,
174                'lbp_mean': lbp_means,
175                'lbp_std': lbp_stds,
176                'lbp_energy': [np.sum(hist**2) for hist in lbp_histograms],
177                'lbp_entropy': [self._calculate_entropy(hist) for hist in lbp_histograms]
178            }
179            
180            features.append(sensor_features)
181        
182        return features

Extract LBP features from sliding windows.

Args: windows: List of sliding window dictionaries fs: Sampling frequency (unused for LBP) **kwargs: Additional arguments

Returns: List of feature dictionaries

def get_feature_names(self) -> List[str]:
190    def get_feature_names(self) -> List[str]:
191        """Get names of LBP features."""
192        return [
193            'lbp_histograms', 'lbp_mean', 'lbp_std', 
194            'lbp_energy', 'lbp_entropy'
195        ]

Get names of LBP features.

class FourierSeriesFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
198class FourierSeriesFeatureExtractor(BaseFeatureExtractor):
199    """
200    Fourier Series feature extractor for VGRF data.
201    
202    This extractor fits Fourier series to time-series data and extracts
203    coefficients and reconstruction features.
204    """
205    
206    def __init__(self, verbose: bool = True):
207        super().__init__(
208            name="fourier_features",
209            description="Fourier series feature extractor for VGRF time-series data"
210        )
211        self.verbose = verbose
212        self.config = {
213            'n_terms': 10,  # Number of Fourier terms
214            'period': 3.0,  # Period for Fourier series
215            'extract_coefficients': True,
216            'extract_reconstruction_error': True
217        }
218        
219        if self.verbose:
220            print("🌊 Fourier Series Feature Extractor initialized!")
221    
222    def fit_fourier_series(self, signal: np.ndarray, time_points: np.ndarray, 
223                          period: float = 3.0, n_terms: int = 10) -> Dict[str, Any]:
224        """
225        Fit Fourier series to signal.
226        
227        Args:
228            signal: Input signal
229            time_points: Time points
230            period: Period of the Fourier series
231            n_terms: Number of Fourier terms
232            
233        Returns:
234            Dictionary containing Fourier series parameters
235        """
236        try:
237            # Calculate Fourier coefficients
238            L = period
239            
240            # Calculate a0 (DC component)
241            a0 = 2/L * simpson(signal, time_points)
242            
243            # Calculate an and bn coefficients
244            an = []
245            bn = []
246            
247            for n in range(1, n_terms + 1):
248                # Calculate an coefficient
249                an_val = 2.0/L * simpson(signal * np.cos(2.*np.pi*n*time_points/L), time_points)
250                an.append(an_val)
251                
252                # Calculate bn coefficient
253                bn_val = 2.0/L * simpson(signal * np.sin(2.*np.pi*n*time_points/L), time_points)
254                bn.append(bn_val)
255            
256            # Reconstruct signal
257            reconstructed = np.full_like(time_points, a0/2)
258            for n in range(n_terms):
259                reconstructed += an[n] * np.cos(2.*np.pi*(n+1)*time_points/L)
260                reconstructed += bn[n] * np.sin(2.*np.pi*(n+1)*time_points/L)
261            
262            # Calculate reconstruction error
263            reconstruction_error = np.mean((signal - reconstructed)**2)
264            
265            return {
266                'a0': a0,
267                'an': an,
268                'bn': bn,
269                'reconstructed': reconstructed,
270                'reconstruction_error': reconstruction_error,
271                'fourier_energy': a0**2 + 2*np.sum(np.array(an)**2 + np.array(bn)**2)
272            }
273            
274        except Exception as e:
275            if self.verbose:
276                print(f"Error in Fourier series fitting: {e}")
277            return {
278                'a0': 0,
279                'an': [0] * n_terms,
280                'bn': [0] * n_terms,
281                'reconstructed': np.zeros_like(time_points),
282                'reconstruction_error': float('inf'),
283                'fourier_energy': 0
284            }
285    
286    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
287        """
288        Extract Fourier series features from sliding windows.
289        
290        Args:
291            windows: List of sliding window dictionaries
292            fs: Sampling frequency
293            **kwargs: Additional arguments
294            
295        Returns:
296            List of feature dictionaries
297        """
298        # Update config with any passed arguments
299        n_terms = kwargs.get('n_terms', self.config['n_terms'])
300        period = kwargs.get('period', self.config['period'])
301        
302        if self.verbose:
303            print(f"\n🌊 Fourier Series Feature Extraction")
304            print(f"📊 Terms: {n_terms}, Period: {period}")
305        
306        features = []
307        
308        for window_dict in tqdm(windows, desc="Processing Fourier features", disable=not self.verbose):
309            sensor_name = window_dict['name']
310            window_data = window_dict['data']
311            
312            # Skip annotation windows
313            if sensor_name == 'annotations':
314                continue
315            
316            sensor_features = {'name': sensor_name, 'features': {}}
317            
318            # Extract Fourier features for each window
319            a0_values = []
320            an_values = []
321            bn_values = []
322            reconstruction_errors = []
323            fourier_energies = []
324            
325            for window in window_data:
326                # Ensure window is numpy array
327                if hasattr(window, 'values'):
328                    window = window.values
329                
330                # Create time points
331                time_points = np.linspace(0, period, len(window))
332                
333                # Fit Fourier series
334                fourier_result = self.fit_fourier_series(window, time_points, period, n_terms)
335                
336                # Store results
337                a0_values.append(fourier_result['a0'])
338                an_values.append(fourier_result['an'])
339                bn_values.append(fourier_result['bn'])
340                reconstruction_errors.append(fourier_result['reconstruction_error'])
341                fourier_energies.append(fourier_result['fourier_energy'])
342            
343            # Store features
344            sensor_features['features'] = {
345                'fourier_a0': a0_values,
346                'fourier_an': an_values,
347                'fourier_bn': bn_values,
348                'fourier_reconstruction_error': reconstruction_errors,
349                'fourier_energy': fourier_energies,
350                'fourier_an_mean': [np.mean(an) for an in an_values],
351                'fourier_bn_mean': [np.mean(bn) for bn in bn_values],
352                'fourier_an_std': [np.std(an) for an in an_values],
353                'fourier_bn_std': [np.std(bn) for bn in bn_values]
354            }
355            
356            features.append(sensor_features)
357        
358        return features
359    
360    def get_feature_names(self) -> List[str]:
361        """Get names of Fourier series features."""
362        return [
363            'fourier_a0', 'fourier_an', 'fourier_bn', 
364            'fourier_reconstruction_error', 'fourier_energy',
365            'fourier_an_mean', 'fourier_bn_mean',
366            'fourier_an_std', 'fourier_bn_std'
367        ]

Fourier Series feature extractor for VGRF data.

This extractor fits Fourier series to time-series data and extracts coefficients and reconstruction features.

FourierSeriesFeatureExtractor(verbose: bool = True)
206    def __init__(self, verbose: bool = True):
207        super().__init__(
208            name="fourier_features",
209            description="Fourier series feature extractor for VGRF time-series data"
210        )
211        self.verbose = verbose
212        self.config = {
213            'n_terms': 10,  # Number of Fourier terms
214            'period': 3.0,  # Period for Fourier series
215            'extract_coefficients': True,
216            'extract_reconstruction_error': True
217        }
218        
219        if self.verbose:
220            print("🌊 Fourier Series Feature Extractor initialized!")

Initialize the feature extractor.

Args: name: Name of the feature extractor description: Description of the feature extractor

verbose
config
def fit_fourier_series( self, signal: numpy.ndarray, time_points: numpy.ndarray, period: float = 3.0, n_terms: int = 10) -> Dict[str, Any]:
222    def fit_fourier_series(self, signal: np.ndarray, time_points: np.ndarray, 
223                          period: float = 3.0, n_terms: int = 10) -> Dict[str, Any]:
224        """
225        Fit Fourier series to signal.
226        
227        Args:
228            signal: Input signal
229            time_points: Time points
230            period: Period of the Fourier series
231            n_terms: Number of Fourier terms
232            
233        Returns:
234            Dictionary containing Fourier series parameters
235        """
236        try:
237            # Calculate Fourier coefficients
238            L = period
239            
240            # Calculate a0 (DC component)
241            a0 = 2/L * simpson(signal, time_points)
242            
243            # Calculate an and bn coefficients
244            an = []
245            bn = []
246            
247            for n in range(1, n_terms + 1):
248                # Calculate an coefficient
249                an_val = 2.0/L * simpson(signal * np.cos(2.*np.pi*n*time_points/L), time_points)
250                an.append(an_val)
251                
252                # Calculate bn coefficient
253                bn_val = 2.0/L * simpson(signal * np.sin(2.*np.pi*n*time_points/L), time_points)
254                bn.append(bn_val)
255            
256            # Reconstruct signal
257            reconstructed = np.full_like(time_points, a0/2)
258            for n in range(n_terms):
259                reconstructed += an[n] * np.cos(2.*np.pi*(n+1)*time_points/L)
260                reconstructed += bn[n] * np.sin(2.*np.pi*(n+1)*time_points/L)
261            
262            # Calculate reconstruction error
263            reconstruction_error = np.mean((signal - reconstructed)**2)
264            
265            return {
266                'a0': a0,
267                'an': an,
268                'bn': bn,
269                'reconstructed': reconstructed,
270                'reconstruction_error': reconstruction_error,
271                'fourier_energy': a0**2 + 2*np.sum(np.array(an)**2 + np.array(bn)**2)
272            }
273            
274        except Exception as e:
275            if self.verbose:
276                print(f"Error in Fourier series fitting: {e}")
277            return {
278                'a0': 0,
279                'an': [0] * n_terms,
280                'bn': [0] * n_terms,
281                'reconstructed': np.zeros_like(time_points),
282                'reconstruction_error': float('inf'),
283                'fourier_energy': 0
284            }

Fit Fourier series to signal.

Args: signal: Input signal time_points: Time points period: Period of the Fourier series n_terms: Number of Fourier terms

Returns: Dictionary containing Fourier series parameters

def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
286    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
287        """
288        Extract Fourier series features from sliding windows.
289        
290        Args:
291            windows: List of sliding window dictionaries
292            fs: Sampling frequency
293            **kwargs: Additional arguments
294            
295        Returns:
296            List of feature dictionaries
297        """
298        # Update config with any passed arguments
299        n_terms = kwargs.get('n_terms', self.config['n_terms'])
300        period = kwargs.get('period', self.config['period'])
301        
302        if self.verbose:
303            print(f"\n🌊 Fourier Series Feature Extraction")
304            print(f"📊 Terms: {n_terms}, Period: {period}")
305        
306        features = []
307        
308        for window_dict in tqdm(windows, desc="Processing Fourier features", disable=not self.verbose):
309            sensor_name = window_dict['name']
310            window_data = window_dict['data']
311            
312            # Skip annotation windows
313            if sensor_name == 'annotations':
314                continue
315            
316            sensor_features = {'name': sensor_name, 'features': {}}
317            
318            # Extract Fourier features for each window
319            a0_values = []
320            an_values = []
321            bn_values = []
322            reconstruction_errors = []
323            fourier_energies = []
324            
325            for window in window_data:
326                # Ensure window is numpy array
327                if hasattr(window, 'values'):
328                    window = window.values
329                
330                # Create time points
331                time_points = np.linspace(0, period, len(window))
332                
333                # Fit Fourier series
334                fourier_result = self.fit_fourier_series(window, time_points, period, n_terms)
335                
336                # Store results
337                a0_values.append(fourier_result['a0'])
338                an_values.append(fourier_result['an'])
339                bn_values.append(fourier_result['bn'])
340                reconstruction_errors.append(fourier_result['reconstruction_error'])
341                fourier_energies.append(fourier_result['fourier_energy'])
342            
343            # Store features
344            sensor_features['features'] = {
345                'fourier_a0': a0_values,
346                'fourier_an': an_values,
347                'fourier_bn': bn_values,
348                'fourier_reconstruction_error': reconstruction_errors,
349                'fourier_energy': fourier_energies,
350                'fourier_an_mean': [np.mean(an) for an in an_values],
351                'fourier_bn_mean': [np.mean(bn) for bn in bn_values],
352                'fourier_an_std': [np.std(an) for an in an_values],
353                'fourier_bn_std': [np.std(bn) for bn in bn_values]
354            }
355            
356            features.append(sensor_features)
357        
358        return features

Extract Fourier series features from sliding windows.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments

Returns: List of feature dictionaries

def get_feature_names(self) -> List[str]:
360    def get_feature_names(self) -> List[str]:
361        """Get names of Fourier series features."""
362        return [
363            'fourier_a0', 'fourier_an', 'fourier_bn', 
364            'fourier_reconstruction_error', 'fourier_energy',
365            'fourier_an_mean', 'fourier_bn_mean',
366            'fourier_an_std', 'fourier_bn_std'
367        ]

Get names of Fourier series features.

class PhysioNetFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
370class PhysioNetFeatureExtractor(BaseFeatureExtractor):
371    """
372    Combined feature extractor for PhysioNet VGRF data.
373    
374    This extractor combines LBP and Fourier series features along with
375    basic statistical features specific to VGRF data.
376    """
377    
378    def __init__(self, verbose: bool = True):
379        super().__init__(
380            name="physionet_features",
381            description="Combined feature extractor for PhysioNet VGRF data including LBP and Fourier features"
382        )
383        self.verbose = verbose
384        self.lbp_extractor = LBPFeatureExtractor(verbose=False)
385        self.fourier_extractor = FourierSeriesFeatureExtractor(verbose=False)
386        
387        if self.verbose:
388            print("🚀 PhysioNet Feature Extractor initialized!")
389    
390    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
391        """
392        Extract combined features from sliding windows.
393        
394        Args:
395            windows: List of sliding window dictionaries
396            fs: Sampling frequency
397            **kwargs: Additional arguments
398            
399        Returns:
400            List of feature dictionaries
401        """
402        # Extract features from each extractor
403        extract_lbp = kwargs.get('extract_lbp', True)
404        extract_fourier = kwargs.get('extract_fourier', True)
405        extract_statistical = kwargs.get('extract_statistical', True)
406        
407        if self.verbose:
408            print(f"\n🔍 PhysioNet Feature Extraction")
409            print(f"📊 LBP: {extract_lbp}, Fourier: {extract_fourier}, Statistical: {extract_statistical}")
410        
411        features = []
412        
413        # Extract LBP features
414        if extract_lbp:
415            lbp_features = self.lbp_extractor.extract_features(windows, fs, **kwargs)
416        else:
417            lbp_features = []
418        
419        # Extract Fourier features
420        if extract_fourier:
421            fourier_features = self.fourier_extractor.extract_features(windows, fs, **kwargs)
422        else:
423            fourier_features = []
424        
425        # Extract statistical features
426        if extract_statistical:
427            statistical_features = self._extract_statistical_features(windows)
428        else:
429            statistical_features = []
430        
431        # Combine features
432        for i, window_dict in enumerate(windows):
433            sensor_name = window_dict['name']
434            
435            # Skip annotation windows
436            if sensor_name == 'annotations':
437                continue
438            
439            combined_features = {'name': sensor_name, 'features': {}}
440            
441            # Add LBP features
442            if extract_lbp and i < len(lbp_features):
443                combined_features['features'].update(lbp_features[i]['features'])
444            
445            # Add Fourier features
446            if extract_fourier and i < len(fourier_features):
447                combined_features['features'].update(fourier_features[i]['features'])
448            
449            # Add statistical features
450            if extract_statistical and i < len(statistical_features):
451                combined_features['features'].update(statistical_features[i]['features'])
452            
453            features.append(combined_features)
454        
455        return features
456    
457    def _extract_statistical_features(self, windows: List[Dict]) -> List[Dict]:
458        """Extract basic statistical features."""
459        features = []
460        
461        for window_dict in windows:
462            sensor_name = window_dict['name']
463            window_data = window_dict['data']
464            
465            # Skip annotation windows
466            if sensor_name == 'annotations':
467                continue
468            
469            sensor_features = {'name': sensor_name, 'features': {}}
470            
471            # Extract statistical features for each window
472            means = []
473            stds = []
474            maxs = []
475            mins = []
476            ranges = []
477            
478            for window in window_data:
479                # Ensure window is numpy array
480                if hasattr(window, 'values'):
481                    window = window.values
482                
483                means.append(np.mean(window))
484                stds.append(np.std(window))
485                maxs.append(np.max(window))
486                mins.append(np.min(window))
487                ranges.append(np.max(window) - np.min(window))
488            
489            # Store features
490            sensor_features['features'] = {
491                'vgrf_mean': means,
492                'vgrf_std': stds,
493                'vgrf_max': maxs,
494                'vgrf_min': mins,
495                'vgrf_range': ranges
496            }
497            
498            features.append(sensor_features)
499        
500        return features
501    
502    def get_feature_names(self) -> List[str]:
503        """Get names of all features."""
504        feature_names = []
505        feature_names.extend(self.lbp_extractor.get_feature_names())
506        feature_names.extend(self.fourier_extractor.get_feature_names())
507        feature_names.extend(['vgrf_mean', 'vgrf_std', 'vgrf_max', 'vgrf_min', 'vgrf_range'])
508        return feature_names

Combined feature extractor for PhysioNet VGRF data.

This extractor combines LBP and Fourier series features along with basic statistical features specific to VGRF data.

PhysioNetFeatureExtractor(verbose: bool = True)
378    def __init__(self, verbose: bool = True):
379        super().__init__(
380            name="physionet_features",
381            description="Combined feature extractor for PhysioNet VGRF data including LBP and Fourier features"
382        )
383        self.verbose = verbose
384        self.lbp_extractor = LBPFeatureExtractor(verbose=False)
385        self.fourier_extractor = FourierSeriesFeatureExtractor(verbose=False)
386        
387        if self.verbose:
388            print("🚀 PhysioNet Feature Extractor initialized!")

Initialize the feature extractor.

Args: name: Name of the feature extractor description: Description of the feature extractor

verbose
lbp_extractor
fourier_extractor
def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
390    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
391        """
392        Extract combined features from sliding windows.
393        
394        Args:
395            windows: List of sliding window dictionaries
396            fs: Sampling frequency
397            **kwargs: Additional arguments
398            
399        Returns:
400            List of feature dictionaries
401        """
402        # Extract features from each extractor
403        extract_lbp = kwargs.get('extract_lbp', True)
404        extract_fourier = kwargs.get('extract_fourier', True)
405        extract_statistical = kwargs.get('extract_statistical', True)
406        
407        if self.verbose:
408            print(f"\n🔍 PhysioNet Feature Extraction")
409            print(f"📊 LBP: {extract_lbp}, Fourier: {extract_fourier}, Statistical: {extract_statistical}")
410        
411        features = []
412        
413        # Extract LBP features
414        if extract_lbp:
415            lbp_features = self.lbp_extractor.extract_features(windows, fs, **kwargs)
416        else:
417            lbp_features = []
418        
419        # Extract Fourier features
420        if extract_fourier:
421            fourier_features = self.fourier_extractor.extract_features(windows, fs, **kwargs)
422        else:
423            fourier_features = []
424        
425        # Extract statistical features
426        if extract_statistical:
427            statistical_features = self._extract_statistical_features(windows)
428        else:
429            statistical_features = []
430        
431        # Combine features
432        for i, window_dict in enumerate(windows):
433            sensor_name = window_dict['name']
434            
435            # Skip annotation windows
436            if sensor_name == 'annotations':
437                continue
438            
439            combined_features = {'name': sensor_name, 'features': {}}
440            
441            # Add LBP features
442            if extract_lbp and i < len(lbp_features):
443                combined_features['features'].update(lbp_features[i]['features'])
444            
445            # Add Fourier features
446            if extract_fourier and i < len(fourier_features):
447                combined_features['features'].update(fourier_features[i]['features'])
448            
449            # Add statistical features
450            if extract_statistical and i < len(statistical_features):
451                combined_features['features'].update(statistical_features[i]['features'])
452            
453            features.append(combined_features)
454        
455        return features

Extract combined features from sliding windows.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments

Returns: List of feature dictionaries

def get_feature_names(self) -> List[str]:
502    def get_feature_names(self) -> List[str]:
503        """Get names of all features."""
504        feature_names = []
505        feature_names.extend(self.lbp_extractor.get_feature_names())
506        feature_names.extend(self.fourier_extractor.get_feature_names())
507        feature_names.extend(['vgrf_mean', 'vgrf_std', 'vgrf_max', 'vgrf_min', 'vgrf_range'])
508        return feature_names

Get names of all features.

def extract_lbp_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
512def extract_lbp_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
513    """
514    Legacy function to extract LBP features.
515    
516    Args:
517        windows: List of sliding window dictionaries
518        fs: Sampling frequency
519        **kwargs: Additional arguments
520        
521    Returns:
522        List of feature dictionaries
523    """
524    extractor = LBPFeatureExtractor(verbose=kwargs.get('verbose', True))
525    return extractor.extract_features(windows, fs, **kwargs)

Legacy function to extract LBP features.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments

Returns: List of feature dictionaries

def extract_fourier_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
528def extract_fourier_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
529    """
530    Legacy function to extract Fourier series features.
531    
532    Args:
533        windows: List of sliding window dictionaries
534        fs: Sampling frequency
535        **kwargs: Additional arguments
536        
537    Returns:
538        List of feature dictionaries
539    """
540    extractor = FourierSeriesFeatureExtractor(verbose=kwargs.get('verbose', True))
541    return extractor.extract_features(windows, fs, **kwargs)

Legacy function to extract Fourier series features.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments

Returns: List of feature dictionaries

def extract_physionet_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
544def extract_physionet_features(windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
545    """
546    Legacy function to extract combined PhysioNet features.
547    
548    Args:
549        windows: List of sliding window dictionaries
550        fs: Sampling frequency
551        **kwargs: Additional arguments
552        
553    Returns:
554        List of feature dictionaries
555    """
556    extractor = PhysioNetFeatureExtractor(verbose=kwargs.get('verbose', True))
557    return extractor.extract_features(windows, fs, **kwargs) 

Legacy function to extract combined PhysioNet features.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments

Returns: List of feature dictionaries