gaitsetpy.features

Features module for gait analysis and feature extraction.

This module provides both the new class-based feature extractors and legacy function-based API. All feature extractors inherit from BaseFeatureExtractor and are registered with the FeatureManager.

Maintainer: @aharshit123456

  1'''
  2Features module for gait analysis and feature extraction.
  3
  4This module provides both the new class-based feature extractors and legacy function-based API.
  5All feature extractors inherit from BaseFeatureExtractor and are registered with the FeatureManager.
  6
  7Maintainer: @aharshit123456
  8'''
  9
 10# Import the new class-based feature extractors
 11from .gait_features import GaitFeatureExtractor
 12from .physionet_features import LBPFeatureExtractor, FourierSeriesFeatureExtractor, PhysioNetFeatureExtractor
 13from .harup_features import HARUPFeatureExtractor
 14from .urfall_features import UrFallMediaFeatureExtractor
 15
 16# Import legacy functions for backward compatibility
 17from .physionet_features import extract_lbp_features, extract_fourier_features, extract_physionet_features
 18from .harup_features import extract_harup_features
 19from .utils import (
 20    calculate_mean,
 21    calculate_standard_deviation,
 22    calculate_variance,
 23    calculate_skewness,
 24    calculate_kurtosis,
 25    calculate_root_mean_square,
 26    calculate_range,
 27    calculate_median,
 28    calculate_mode,
 29    calculate_mean_absolute_value,
 30    calculate_median_absolute_deviation,
 31    calculate_peak_height,
 32    calculate_stride_times,
 33    calculate_step_time,
 34    calculate_cadence,
 35    calculate_freezing_index,
 36    calculate_dominant_frequency,
 37    calculate_peak_frequency,
 38    calculate_power_spectral_entropy,
 39    calculate_principal_harmonic_frequency,
 40    calculate_entropy,
 41    calculate_interquartile_range,
 42    calculate_correlation,
 43    calculate_auto_regression_coefficients
 44)
 45
 46from .gait_features import (
 47    get_mean_for_windows,
 48    get_standard_deviation_for_windows,
 49    get_variance_for_windows,
 50    get_skewness_for_windows,
 51    get_kurtosis_for_windows,
 52    get_root_mean_square_for_windows,
 53    get_range_for_windows,
 54    get_median_for_windows,
 55    get_mode_for_windows,
 56    get_mean_absolute_value_for_windows,
 57    get_median_absolute_deviation_for_windows,
 58    get_peak_height_for_windows,
 59    get_stride_times_for_windows,
 60    get_step_times_for_windows,
 61    get_cadence_for_windows,
 62    get_freezing_index_for_windows,
 63    get_dominant_frequency_for_windows,
 64    get_peak_frequency_for_windows,
 65    get_power_spectral_entropy_for_windows,
 66    get_principal_harmonic_frequency_for_windows,
 67    get_entropy_for_windows,
 68    get_interquartile_range_for_windows,
 69    get_correlation_for_windows,
 70    get_auto_regression_coefficients_for_windows,
 71    extract_gait_features
 72)
 73
 74# Import managers
 75from ..core.managers import FeatureManager
 76
 77# Register all feature extractors with the manager
 78def _register_extractors():
 79    """Register all available feature extractors with the FeatureManager."""
 80    manager = FeatureManager()
 81    manager.register_extractor("gait_features", GaitFeatureExtractor)
 82    manager.register_extractor("lbp_features", LBPFeatureExtractor)
 83    manager.register_extractor("fourier_features", FourierSeriesFeatureExtractor)
 84    manager.register_extractor("physionet_features", PhysioNetFeatureExtractor)
 85    manager.register_extractor("harup_features", HARUPFeatureExtractor)
 86    manager.register_extractor("urfall_media", UrFallMediaFeatureExtractor)
 87
 88# Auto-register extractors when module is imported
 89_register_extractors()
 90
 91# Convenient access to the feature manager
 92def get_feature_manager():
 93    """Get the singleton FeatureManager instance."""
 94    return FeatureManager()
 95
 96# Helper function to get available extractors
 97def get_available_extractors():
 98    """Get list of available feature extractor names."""
 99    return FeatureManager().get_available_components()
100
101# Helper function to extract features using manager
102def extract_features(extractor_name: str, windows, fs: int, **kwargs):
103    """
104    Extract features using the FeatureManager.
105    
106    Args:
107        extractor_name: Name of the feature extractor
108        windows: List of sliding window dictionaries
109        fs: Sampling frequency
110        **kwargs: Additional arguments for feature extraction
111        
112    Returns:
113        List of feature dictionaries
114    """
115    return FeatureManager().extract_features(extractor_name, windows, fs, **kwargs)
116
117__all__ = [
118    # New class-based feature extractors
119    'GaitFeatureExtractor',
120    'LBPFeatureExtractor',
121    'FourierSeriesFeatureExtractor',
122    'PhysioNetFeatureExtractor',
123    'HARUPFeatureExtractor',
124    'UrFallMediaFeatureExtractor',
125    # Legacy functions
126    'extract_lbp_features',
127    'extract_fourier_features',
128    'extract_physionet_features',
129    'extract_harup_features',
130    # Utility exports
131    'calculate_mean',
132    'calculate_standard_deviation',
133    'calculate_variance',
134    'calculate_skewness',
135    'calculate_kurtosis',
136    'calculate_root_mean_square',
137    'calculate_range',
138    'calculate_median',
139    'calculate_mode',
140    'calculate_mean_absolute_value',
141    'calculate_median_absolute_deviation',
142    'calculate_peak_height',
143    'calculate_stride_times',
144    'calculate_step_time',
145    'calculate_cadence',
146    'calculate_freezing_index',
147    'calculate_dominant_frequency',
148    'calculate_peak_frequency',
149    'calculate_power_spectral_entropy',
150    'calculate_principal_harmonic_frequency',
151    'calculate_entropy',
152    'calculate_interquartile_range',
153    'calculate_correlation',
154    'calculate_auto_regression_coefficients',
155    # Gait feature convenience
156    'get_mean_for_windows',
157    'get_standard_deviation_for_windows',
158    'get_variance_for_windows',
159    'get_skewness_for_windows',
160    'get_kurtosis_for_windows',
161    'get_root_mean_square_for_windows',
162    'get_range_for_windows',
163    'get_median_for_windows',
164    'get_mode_for_windows',
165    'get_mean_absolute_value_for_windows',
166    'get_median_absolute_deviation_for_windows',
167    'get_peak_height_for_windows',
168    'get_stride_times_for_windows',
169    'get_step_times_for_windows',
170    'get_cadence_for_windows',
171    'get_freezing_index_for_windows',
172    'get_dominant_frequency_for_windows',
173    'get_peak_frequency_for_windows',
174    'get_power_spectral_entropy_for_windows',
175    'get_principal_harmonic_frequency_for_windows',
176    'get_entropy_for_windows',
177    'get_interquartile_range_for_windows',
178    'get_correlation_for_windows',
179    'get_auto_regression_coefficients_for_windows',
180    'extract_gait_features',
181]
class GaitFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
 49class GaitFeatureExtractor(BaseFeatureExtractor):
 50    """
 51    Comprehensive gait feature extractor class.
 52    
 53    This class extracts various time-domain, frequency-domain, and statistical features
 54    from gait data sliding windows.
 55    """
 56    
 57    def __init__(self, verbose: bool = True):
 58        super().__init__(
 59            name="gait_features",
 60            description="Comprehensive gait feature extractor for time-domain, frequency-domain, and statistical features"
 61        )
 62        self.verbose = verbose
 63        self.config = {
 64            'time_domain': True,
 65            'frequency_domain': True,
 66            'statistical': True,
 67            'ar_order': 3  # Order for auto-regression coefficients
 68        }
 69        
 70        if self.verbose:
 71            print("🚀 GaitFeatureExtractor initialized successfully!")
 72            print(f"📊 Default configuration: {self.config}")
 73    
 74    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 75        """
 76        Extract gait features from sliding windows.
 77        
 78        Args:
 79            windows: List of sliding window dictionaries
 80            fs: Sampling frequency
 81            **kwargs: Additional arguments including time_domain, frequency_domain, statistical flags
 82            
 83        Returns:
 84            List of feature dictionaries for each sensor
 85        """
 86        # Update config with any passed arguments
 87        time_domain = kwargs.get('time_domain', self.config['time_domain'])
 88        frequency_domain = kwargs.get('frequency_domain', self.config['frequency_domain'])
 89        statistical = kwargs.get('statistical', self.config['statistical'])
 90        ar_order = kwargs.get('ar_order', self.config['ar_order'])
 91        
 92        if self.verbose:
 93            print("\n" + "="*60)
 94            print("🔍 STARTING GAIT FEATURE EXTRACTION")
 95            print("="*60)
 96            print(f"📈 Total sensors/windows to process: {len(windows)}")
 97            print(f"🔊 Sampling frequency: {fs} Hz")
 98            print(f"⏱️  Time domain features: {'✅' if time_domain else '❌'}")
 99            print(f"🌊 Frequency domain features: {'✅' if frequency_domain else '❌'}")
100            print(f"📊 Statistical features: {'✅' if statistical else '❌'}")
101            print(f"🔄 Auto-regression order: {ar_order}")
102            print("-"*60)
103        
104        features = []
105        
106        # Main progress bar for processing all windows
107        main_pbar = tqdm(
108            windows, 
109            desc="🔍 Processing Sensors", 
110            unit="sensor",
111            disable=not self.verbose
112        )
113        
114        for i, window_dict in enumerate(main_pbar):
115            sensor_name = window_dict['name']
116            window_data = window_dict['data']
117            
118            if self.verbose:
119                main_pbar.set_postfix({
120                    'Current': sensor_name,
121                    'Windows': len(window_data) if isinstance(window_data, list) else 1
122                })
123            
124            # Skip annotation windows
125            if sensor_name == 'annotations':
126                if self.verbose:
127                    logger.info(f"📝 Processing annotation data for {sensor_name}")
128                
129                features.append({
130                    'name': sensor_name,
131                    'features': {},
132                    'annotations': [self._extract_annotation_labels(window) for window in window_data]
133                })
134                continue
135            
136            if self.verbose:
137                logger.info(f"🎯 Processing sensor: {sensor_name}")
138                logger.info(f"📦 Number of windows: {len(window_data)}")
139            
140            sensor_features = {'name': sensor_name, 'features': {}}
141            
142            # Time domain features
143            if time_domain:
144                if self.verbose:
145                    print(f"  ⏱️  Extracting time domain features for {sensor_name}...")
146                
147                time_features = self._extract_time_domain_features(window_data)
148                sensor_features['features'].update(time_features)
149                
150                if self.verbose:
151                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in time_features.values())
152                    print(f"  ✅ Time domain: {len(time_features)} feature types, {feature_count} total features")
153            
154            # Frequency domain features
155            if frequency_domain:
156                if self.verbose:
157                    print(f"  🌊 Extracting frequency domain features for {sensor_name}...")
158                
159                freq_features = self._extract_frequency_domain_features(window_data, fs)
160                sensor_features['features'].update(freq_features)
161                
162                if self.verbose:
163                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in freq_features.values())
164                    print(f"  ✅ Frequency domain: {len(freq_features)} feature types, {feature_count} total features")
165            
166            # Statistical features
167            if statistical:
168                if self.verbose:
169                    print(f"  📊 Extracting statistical features for {sensor_name}...")
170                
171                stat_features = self._extract_statistical_features(window_data)
172                sensor_features['features'].update(stat_features)
173                
174                if self.verbose:
175                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in stat_features.values())
176                    print(f"  ✅ Statistical: {len(stat_features)} feature types, {feature_count} total features")
177            
178            # Auto-regression coefficients
179            if self.verbose:
180                print(f"  🔄 Extracting auto-regression coefficients for {sensor_name}...")
181            
182            ar_features = self._extract_ar_coefficients(window_data, ar_order)
183            sensor_features['features'].update(ar_features)
184            
185            if self.verbose:
186                feature_count = sum(len(v) if isinstance(v, list) else 1 for v in ar_features.values())
187                print(f"  ✅ Auto-regression: {len(ar_features)} feature types, {feature_count} total features")
188            
189            # Calculate total features for this sensor
190            total_features = sum(
191                len(v) if isinstance(v, list) else 1 
192                for v in sensor_features['features'].values()
193            )
194            
195            if self.verbose:
196                print(f"  🎯 Total features extracted for {sensor_name}: {total_features}")
197                print(f"  📋 Feature types: {list(sensor_features['features'].keys())}")
198                print("-"*40)
199            
200            features.append(sensor_features)
201        
202        if self.verbose:
203            print("\n" + "="*60)
204            print("🎉 FEATURE EXTRACTION COMPLETED!")
205            print("="*60)
206            print(f"📊 Total sensors processed: {len(features)}")
207            
208            # Calculate overall statistics
209            total_feature_count = 0
210            for feature_dict in features:
211                if 'features' in feature_dict:
212                    total_feature_count += sum(
213                        len(v) if isinstance(v, list) else 1 
214                        for v in feature_dict['features'].values()
215                    )
216            
217            print(f"🔢 Total features extracted: {total_feature_count}")
218            print(f"📈 Average features per sensor: {total_feature_count / len(features):.1f}")
219            print("="*60)
220        
221        return features
222    
223    def _extract_time_domain_features(self, windows: List) -> Dict[str, List]:
224        """Extract time domain features from windows."""
225        if self.verbose:
226            print("    🔍 Computing time domain features...")
227        
228        time_features = {}
229        
230        # Define time domain feature functions
231        time_domain_funcs = {
232            'mean': calculate_mean,
233            'std': calculate_standard_deviation,
234            'variance': calculate_variance,
235            'rms': calculate_root_mean_square,
236            'range': calculate_range,
237            'median': calculate_median,
238            'mode': calculate_mode,
239            'mean_absolute_value': calculate_mean_absolute_value,
240            'median_absolute_deviation': calculate_median_absolute_deviation,
241            'peak_height': calculate_peak_height,
242            'zero_crossing_rate': calculate_zero_crossing_rate,
243            'energy': calculate_energy,
244        }
245        
246        # Progress bar for time domain features
247        feature_pbar = tqdm(
248            time_domain_funcs.items(), 
249            desc="    ⏱️  Time features", 
250            unit="feature",
251            leave=False,
252            disable=not self.verbose
253        )
254        
255        for feature_name, func in feature_pbar:
256            if self.verbose:
257                feature_pbar.set_postfix({'Computing': feature_name})
258            
259            time_features[feature_name] = [
260                func(self._ensure_numpy_array(window)) for window in windows
261            ]
262        
263        return time_features
264    
265    def _ensure_numpy_array(self, signal):
266        """Convert pandas Series to numpy array if needed."""
267        if hasattr(signal, 'values'):
268            return signal.values
269        return signal
270    
271    def _extract_frequency_domain_features(self, windows: List, fs: int) -> Dict[str, List]:
272        """Extract frequency domain features from windows."""
273        if self.verbose:
274            print("    🔍 Computing frequency domain features...")
275        
276        freq_features = {}
277        
278        # Define frequency domain feature functions
279        freq_domain_funcs = {
280            'dominant_frequency': lambda w: calculate_dominant_frequency(w, fs),
281            'peak_frequency': lambda w: calculate_peak_frequency(w, fs),
282            'power_spectral_entropy': lambda w: calculate_power_spectral_entropy(w, fs),
283            'principal_harmonic_frequency': lambda w: calculate_principal_harmonic_frequency(w, fs),
284            'stride_times': lambda w: calculate_stride_times(w, fs),
285            'step_time': lambda w: calculate_step_time(w, fs),
286            'cadence': lambda w: calculate_cadence(w, fs),
287            'freezing_index': lambda w: calculate_freezing_index(w, fs),
288        }
289        
290        # Progress bar for frequency domain features
291        feature_pbar = tqdm(
292            freq_domain_funcs.items(), 
293            desc="    🌊 Freq features", 
294            unit="feature",
295            leave=False,
296            disable=not self.verbose
297        )
298        
299        for feature_name, func in feature_pbar:
300            if self.verbose:
301                feature_pbar.set_postfix({'Computing': feature_name})
302            
303            freq_features[feature_name] = [
304                func(self._ensure_numpy_array(window)) for window in windows
305            ]
306        
307        return freq_features
308    
309    def _extract_statistical_features(self, windows: List) -> Dict[str, List]:
310        """Extract statistical features from windows."""
311        if self.verbose:
312            print("    🔍 Computing statistical features...")
313        
314        stat_features = {}
315        
316        # Define statistical feature functions
317        stat_funcs = {
318            'skewness': calculate_skewness,
319            'kurtosis': calculate_kurtosis,
320            'entropy': calculate_entropy,
321            'interquartile_range': calculate_interquartile_range,
322        }
323        
324        # Progress bar for statistical features
325        feature_pbar = tqdm(
326            stat_funcs.items(), 
327            desc="    📊 Stat features", 
328            unit="feature",
329            leave=False,
330            disable=not self.verbose
331        )
332        
333        for feature_name, func in feature_pbar:
334            if self.verbose:
335                feature_pbar.set_postfix({'Computing': feature_name})
336            
337            stat_features[feature_name] = [
338                func(self._ensure_numpy_array(window)) for window in windows
339            ]
340        
341        # Handle correlation separately (needs two signals)
342        if self.verbose:
343            print("      🔗 Computing correlation features...")
344        
345        stat_features['correlation'] = [
346            calculate_correlation(
347                self._ensure_numpy_array(window)[:-1], 
348                self._ensure_numpy_array(window)[1:]
349            ) if len(window) > 1 else 0 
350            for window in windows
351        ]
352        
353        return stat_features
354    
355    def _extract_ar_coefficients(self, windows: List, order: int) -> Dict[str, List]:
356        """Extract auto-regression coefficients from windows."""
357        if self.verbose:
358            print(f"    🔍 Computing auto-regression coefficients (order={order})...")
359        
360        # Progress bar for AR coefficients
361        ar_pbar = tqdm(
362            windows, 
363            desc="    🔄 AR coeffs", 
364            unit="window",
365            leave=False,
366            disable=not self.verbose
367        )
368        
369        ar_coeffs = []
370        for window in ar_pbar:
371            coeffs = calculate_auto_regression_coefficients(
372                self._ensure_numpy_array(window), order
373            )
374            ar_coeffs.append(coeffs)
375        
376        return {'ar_coefficients': ar_coeffs}
377    
378    def _extract_annotation_labels(self, window) -> int:
379        """Extract the most common annotation label from a window."""
380        if hasattr(window, 'mode'):
381            return window.mode().iloc[0] if len(window.mode()) > 0 else 0
382        else:
383            # For numpy arrays or other types
384            unique, counts = np.unique(window, return_counts=True)
385            return unique[np.argmax(counts)]
386    
387    def get_feature_names(self) -> List[str]:
388        """
389        Get names of all features that can be extracted.
390        
391        Returns:
392            List of feature names
393        """
394        time_domain_features = [
395            'mean', 'std', 'variance', 'rms', 'range', 'median', 'mode',
396            'mean_absolute_value', 'median_absolute_deviation', 'peak_height',
397            'zero_crossing_rate', 'energy'
398        ]
399        
400        frequency_domain_features = [
401            'dominant_frequency', 'peak_frequency', 'power_spectral_entropy',
402            'principal_harmonic_frequency', 'stride_times', 'step_time',
403            'cadence', 'freezing_index'
404        ]
405        
406        statistical_features = [
407            'skewness', 'kurtosis', 'entropy', 'interquartile_range', 'correlation'
408        ]
409        
410        other_features = ['ar_coefficients']
411        
412        return time_domain_features + frequency_domain_features + statistical_features + other_features
413
414    def print_extraction_summary(self, features: List[Dict]) -> None:
415        """
416        Print a detailed summary of extracted features.
417        
418        Args:
419            features: List of feature dictionaries returned by extract_features
420        """
421        print("\n" + "="*80)
422        print("📊 FEATURE EXTRACTION SUMMARY")
423        print("="*80)
424        
425        for i, feature_dict in enumerate(features):
426            sensor_name = feature_dict['name']
427            print(f"\n🎯 Sensor {i+1}: {sensor_name}")
428            print("-" * 40)
429            
430            if 'features' in feature_dict and feature_dict['features']:
431                for feature_type, feature_values in feature_dict['features'].items():
432                    if isinstance(feature_values, list):
433                        print(f"  📈 {feature_type}: {len(feature_values)} values")
434                        if feature_values:
435                            sample_value = feature_values[0]
436                            if isinstance(sample_value, (list, np.ndarray)):
437                                print(f"    └── Shape per window: {np.array(sample_value).shape}")
438                            else:
439                                print(f"    └── Sample value: {sample_value:.4f}")
440                    else:
441                        print(f"  📈 {feature_type}: {feature_values}")
442            
443            if 'annotations' in feature_dict:
444                print(f"  📝 Annotations: {len(feature_dict['annotations'])} windows")
445        
446        print("\n" + "="*80)

Comprehensive gait feature extractor class.

This class extracts various time-domain, frequency-domain, and statistical features from gait data sliding windows.

GaitFeatureExtractor(verbose: bool = True)
57    def __init__(self, verbose: bool = True):
58        super().__init__(
59            name="gait_features",
60            description="Comprehensive gait feature extractor for time-domain, frequency-domain, and statistical features"
61        )
62        self.verbose = verbose
63        self.config = {
64            'time_domain': True,
65            'frequency_domain': True,
66            'statistical': True,
67            'ar_order': 3  # Order for auto-regression coefficients
68        }
69        
70        if self.verbose:
71            print("🚀 GaitFeatureExtractor initialized successfully!")
72            print(f"📊 Default configuration: {self.config}")

Initialize the feature extractor.

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

verbose
config
def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 74    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 75        """
 76        Extract gait features from sliding windows.
 77        
 78        Args:
 79            windows: List of sliding window dictionaries
 80            fs: Sampling frequency
 81            **kwargs: Additional arguments including time_domain, frequency_domain, statistical flags
 82            
 83        Returns:
 84            List of feature dictionaries for each sensor
 85        """
 86        # Update config with any passed arguments
 87        time_domain = kwargs.get('time_domain', self.config['time_domain'])
 88        frequency_domain = kwargs.get('frequency_domain', self.config['frequency_domain'])
 89        statistical = kwargs.get('statistical', self.config['statistical'])
 90        ar_order = kwargs.get('ar_order', self.config['ar_order'])
 91        
 92        if self.verbose:
 93            print("\n" + "="*60)
 94            print("🔍 STARTING GAIT FEATURE EXTRACTION")
 95            print("="*60)
 96            print(f"📈 Total sensors/windows to process: {len(windows)}")
 97            print(f"🔊 Sampling frequency: {fs} Hz")
 98            print(f"⏱️  Time domain features: {'✅' if time_domain else '❌'}")
 99            print(f"🌊 Frequency domain features: {'✅' if frequency_domain else '❌'}")
100            print(f"📊 Statistical features: {'✅' if statistical else '❌'}")
101            print(f"🔄 Auto-regression order: {ar_order}")
102            print("-"*60)
103        
104        features = []
105        
106        # Main progress bar for processing all windows
107        main_pbar = tqdm(
108            windows, 
109            desc="🔍 Processing Sensors", 
110            unit="sensor",
111            disable=not self.verbose
112        )
113        
114        for i, window_dict in enumerate(main_pbar):
115            sensor_name = window_dict['name']
116            window_data = window_dict['data']
117            
118            if self.verbose:
119                main_pbar.set_postfix({
120                    'Current': sensor_name,
121                    'Windows': len(window_data) if isinstance(window_data, list) else 1
122                })
123            
124            # Skip annotation windows
125            if sensor_name == 'annotations':
126                if self.verbose:
127                    logger.info(f"📝 Processing annotation data for {sensor_name}")
128                
129                features.append({
130                    'name': sensor_name,
131                    'features': {},
132                    'annotations': [self._extract_annotation_labels(window) for window in window_data]
133                })
134                continue
135            
136            if self.verbose:
137                logger.info(f"🎯 Processing sensor: {sensor_name}")
138                logger.info(f"📦 Number of windows: {len(window_data)}")
139            
140            sensor_features = {'name': sensor_name, 'features': {}}
141            
142            # Time domain features
143            if time_domain:
144                if self.verbose:
145                    print(f"  ⏱️  Extracting time domain features for {sensor_name}...")
146                
147                time_features = self._extract_time_domain_features(window_data)
148                sensor_features['features'].update(time_features)
149                
150                if self.verbose:
151                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in time_features.values())
152                    print(f"  ✅ Time domain: {len(time_features)} feature types, {feature_count} total features")
153            
154            # Frequency domain features
155            if frequency_domain:
156                if self.verbose:
157                    print(f"  🌊 Extracting frequency domain features for {sensor_name}...")
158                
159                freq_features = self._extract_frequency_domain_features(window_data, fs)
160                sensor_features['features'].update(freq_features)
161                
162                if self.verbose:
163                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in freq_features.values())
164                    print(f"  ✅ Frequency domain: {len(freq_features)} feature types, {feature_count} total features")
165            
166            # Statistical features
167            if statistical:
168                if self.verbose:
169                    print(f"  📊 Extracting statistical features for {sensor_name}...")
170                
171                stat_features = self._extract_statistical_features(window_data)
172                sensor_features['features'].update(stat_features)
173                
174                if self.verbose:
175                    feature_count = sum(len(v) if isinstance(v, list) else 1 for v in stat_features.values())
176                    print(f"  ✅ Statistical: {len(stat_features)} feature types, {feature_count} total features")
177            
178            # Auto-regression coefficients
179            if self.verbose:
180                print(f"  🔄 Extracting auto-regression coefficients for {sensor_name}...")
181            
182            ar_features = self._extract_ar_coefficients(window_data, ar_order)
183            sensor_features['features'].update(ar_features)
184            
185            if self.verbose:
186                feature_count = sum(len(v) if isinstance(v, list) else 1 for v in ar_features.values())
187                print(f"  ✅ Auto-regression: {len(ar_features)} feature types, {feature_count} total features")
188            
189            # Calculate total features for this sensor
190            total_features = sum(
191                len(v) if isinstance(v, list) else 1 
192                for v in sensor_features['features'].values()
193            )
194            
195            if self.verbose:
196                print(f"  🎯 Total features extracted for {sensor_name}: {total_features}")
197                print(f"  📋 Feature types: {list(sensor_features['features'].keys())}")
198                print("-"*40)
199            
200            features.append(sensor_features)
201        
202        if self.verbose:
203            print("\n" + "="*60)
204            print("🎉 FEATURE EXTRACTION COMPLETED!")
205            print("="*60)
206            print(f"📊 Total sensors processed: {len(features)}")
207            
208            # Calculate overall statistics
209            total_feature_count = 0
210            for feature_dict in features:
211                if 'features' in feature_dict:
212                    total_feature_count += sum(
213                        len(v) if isinstance(v, list) else 1 
214                        for v in feature_dict['features'].values()
215                    )
216            
217            print(f"🔢 Total features extracted: {total_feature_count}")
218            print(f"📈 Average features per sensor: {total_feature_count / len(features):.1f}")
219            print("="*60)
220        
221        return features

Extract gait features from sliding windows.

Args: windows: List of sliding window dictionaries fs: Sampling frequency **kwargs: Additional arguments including time_domain, frequency_domain, statistical flags

Returns: List of feature dictionaries for each sensor

def get_feature_names(self) -> List[str]:
387    def get_feature_names(self) -> List[str]:
388        """
389        Get names of all features that can be extracted.
390        
391        Returns:
392            List of feature names
393        """
394        time_domain_features = [
395            'mean', 'std', 'variance', 'rms', 'range', 'median', 'mode',
396            'mean_absolute_value', 'median_absolute_deviation', 'peak_height',
397            'zero_crossing_rate', 'energy'
398        ]
399        
400        frequency_domain_features = [
401            'dominant_frequency', 'peak_frequency', 'power_spectral_entropy',
402            'principal_harmonic_frequency', 'stride_times', 'step_time',
403            'cadence', 'freezing_index'
404        ]
405        
406        statistical_features = [
407            'skewness', 'kurtosis', 'entropy', 'interquartile_range', 'correlation'
408        ]
409        
410        other_features = ['ar_coefficients']
411        
412        return time_domain_features + frequency_domain_features + statistical_features + other_features

Get names of all features that can be extracted.

Returns: List of feature names

def print_extraction_summary(self, features: List[Dict]) -> None:
414    def print_extraction_summary(self, features: List[Dict]) -> None:
415        """
416        Print a detailed summary of extracted features.
417        
418        Args:
419            features: List of feature dictionaries returned by extract_features
420        """
421        print("\n" + "="*80)
422        print("📊 FEATURE EXTRACTION SUMMARY")
423        print("="*80)
424        
425        for i, feature_dict in enumerate(features):
426            sensor_name = feature_dict['name']
427            print(f"\n🎯 Sensor {i+1}: {sensor_name}")
428            print("-" * 40)
429            
430            if 'features' in feature_dict and feature_dict['features']:
431                for feature_type, feature_values in feature_dict['features'].items():
432                    if isinstance(feature_values, list):
433                        print(f"  📈 {feature_type}: {len(feature_values)} values")
434                        if feature_values:
435                            sample_value = feature_values[0]
436                            if isinstance(sample_value, (list, np.ndarray)):
437                                print(f"    └── Shape per window: {np.array(sample_value).shape}")
438                            else:
439                                print(f"    └── Sample value: {sample_value:.4f}")
440                    else:
441                        print(f"  📈 {feature_type}: {feature_values}")
442            
443            if 'annotations' in feature_dict:
444                print(f"  📝 Annotations: {len(feature_dict['annotations'])} windows")
445        
446        print("\n" + "="*80)

Print a detailed summary of extracted features.

Args: features: List of feature dictionaries returned by extract_features

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.

class HARUPFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
 21class HARUPFeatureExtractor(BaseFeatureExtractor):
 22    """
 23    HAR-UP feature extractor class.
 24    
 25    This class implements the feature extraction methods used in the HAR-UP project.
 26    It extracts both time-domain and frequency-domain features from sensor data.
 27    """
 28    
 29    def __init__(self, verbose: bool = False):
 30        """
 31        Initialize the HAR-UP feature extractor.
 32        
 33        Args:
 34            verbose: Whether to print progress information
 35        """
 36        super().__init__(
 37            name="harup",
 38            description="HAR-UP Feature Extractor - Extracts features used in the HAR-UP project"
 39        )
 40        self.config = {
 41            'time_domain': True,
 42            'frequency_domain': True,
 43            'verbose': verbose
 44        }
 45        
 46        # Define the features to extract
 47        self.time_domain_features = [
 48            'mean', 'std', 'rms', 'max_amp', 'min_amp', 'median',
 49            'zero_crossings', 'skewness', 'kurtosis', 'q1', 'q3', 'autocorr'
 50        ]
 51        
 52        self.freq_domain_features = [
 53            'energy'
 54        ]
 55    
 56    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 57        """
 58        Extract features from sliding windows.
 59        
 60        Args:
 61            windows: List of sliding window dictionaries
 62            fs: Sampling frequency
 63            **kwargs: Additional arguments for feature extraction
 64            
 65        Returns:
 66            List of feature dictionaries
 67        """
 68        # Update config with kwargs
 69        self.config.update(kwargs)
 70        
 71        all_features = []
 72        
 73        # Skip label and activity_id windows
 74        sensor_windows = [w for w in windows if w["name"] not in ["labels", "activity_id"]]
 75        
 76        if self.config['verbose']:
 77            print(f"Extracting features from {len(sensor_windows)} sensor windows")
 78        
 79        # Process each sensor window
 80        for window in sensor_windows:
 81            sensor_name = window["name"]
 82            sensor_data = window["data"]
 83            
 84            if self.config['verbose']:
 85                print(f"Processing {sensor_name} with {len(sensor_data)} windows")
 86            
 87            # For each window of this sensor
 88            for i, window_data in enumerate(sensor_data):
 89                features = {}
 90                features["sensor"] = sensor_name
 91                
 92                # Time domain features
 93                if self.config['time_domain']:
 94                    self._extract_time_domain_features(window_data, features)
 95                
 96                # Frequency domain features
 97                if self.config['frequency_domain']:
 98                    self._extract_freq_domain_features(window_data, features)
 99                
100                all_features.append(features)
101        
102        return all_features
103    
104    def _extract_time_domain_features(self, window_data: np.ndarray, features: Dict[str, Any]):
105        """
106        Extract time domain features from a window.
107        
108        Args:
109            window_data: Window data
110            features: Dictionary to store the extracted features
111        """
112        # Basic statistical features
113        features["mean"] = np.mean(window_data)
114        features["std"] = np.std(window_data)
115        features["rms"] = np.sqrt(np.mean(window_data**2))
116        features["max_amp"] = np.max(np.abs(window_data))
117        features["min_amp"] = np.min(np.abs(window_data))
118        features["median"] = np.median(window_data)
119        
120        # Zero crossings
121        zero_crossings = np.where(np.diff(np.signbit(window_data)))[0]
122        features["zero_crossings"] = len(zero_crossings)
123        
124        # Higher-order statistics
125        features["skewness"] = skew(window_data)
126        features["kurtosis"] = kurtosis(window_data)
127        
128        # Quartiles
129        features["q1"] = np.percentile(window_data, 25)
130        features["q3"] = np.percentile(window_data, 75)
131        
132        # Autocorrelation
133        autocorr = np.correlate(window_data, window_data, mode='full')
134        features["autocorr"] = np.median(autocorr)
135    
136    def _extract_freq_domain_features(self, window_data: np.ndarray, features: Dict[str, Any]):
137        """
138        Extract frequency domain features from a window.
139        
140        Args:
141            window_data: Window data
142            features: Dictionary to store the extracted features
143        """
144        # FFT
145        fft_values = abs(rfft(np.asarray(window_data)))
146        
147        # Energy
148        features["energy"] = np.sum(fft_values**2)
149    
150    def get_feature_names(self) -> List[str]:
151        """
152        Get names of features extracted by this extractor.
153        
154        Returns:
155            List of feature names
156        """
157        feature_names = []
158        
159        if self.config['time_domain']:
160            feature_names.extend(self.time_domain_features)
161        
162        if self.config['frequency_domain']:
163            feature_names.extend(self.freq_domain_features)
164        
165        return feature_names

HAR-UP feature extractor class.

This class implements the feature extraction methods used in the HAR-UP project. It extracts both time-domain and frequency-domain features from sensor data.

HARUPFeatureExtractor(verbose: bool = False)
29    def __init__(self, verbose: bool = False):
30        """
31        Initialize the HAR-UP feature extractor.
32        
33        Args:
34            verbose: Whether to print progress information
35        """
36        super().__init__(
37            name="harup",
38            description="HAR-UP Feature Extractor - Extracts features used in the HAR-UP project"
39        )
40        self.config = {
41            'time_domain': True,
42            'frequency_domain': True,
43            'verbose': verbose
44        }
45        
46        # Define the features to extract
47        self.time_domain_features = [
48            'mean', 'std', 'rms', 'max_amp', 'min_amp', 'median',
49            'zero_crossings', 'skewness', 'kurtosis', 'q1', 'q3', 'autocorr'
50        ]
51        
52        self.freq_domain_features = [
53            'energy'
54        ]

Initialize the HAR-UP feature extractor.

Args: verbose: Whether to print progress information

config
time_domain_features
freq_domain_features
def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 56    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
 57        """
 58        Extract features from sliding windows.
 59        
 60        Args:
 61            windows: List of sliding window dictionaries
 62            fs: Sampling frequency
 63            **kwargs: Additional arguments for feature extraction
 64            
 65        Returns:
 66            List of feature dictionaries
 67        """
 68        # Update config with kwargs
 69        self.config.update(kwargs)
 70        
 71        all_features = []
 72        
 73        # Skip label and activity_id windows
 74        sensor_windows = [w for w in windows if w["name"] not in ["labels", "activity_id"]]
 75        
 76        if self.config['verbose']:
 77            print(f"Extracting features from {len(sensor_windows)} sensor windows")
 78        
 79        # Process each sensor window
 80        for window in sensor_windows:
 81            sensor_name = window["name"]
 82            sensor_data = window["data"]
 83            
 84            if self.config['verbose']:
 85                print(f"Processing {sensor_name} with {len(sensor_data)} windows")
 86            
 87            # For each window of this sensor
 88            for i, window_data in enumerate(sensor_data):
 89                features = {}
 90                features["sensor"] = sensor_name
 91                
 92                # Time domain features
 93                if self.config['time_domain']:
 94                    self._extract_time_domain_features(window_data, features)
 95                
 96                # Frequency domain features
 97                if self.config['frequency_domain']:
 98                    self._extract_freq_domain_features(window_data, features)
 99                
100                all_features.append(features)
101        
102        return all_features

Extract features from sliding windows.

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

Returns: List of feature dictionaries

def get_feature_names(self) -> List[str]:
150    def get_feature_names(self) -> List[str]:
151        """
152        Get names of features extracted by this extractor.
153        
154        Returns:
155            List of feature names
156        """
157        feature_names = []
158        
159        if self.config['time_domain']:
160            feature_names.extend(self.time_domain_features)
161        
162        if self.config['frequency_domain']:
163            feature_names.extend(self.freq_domain_features)
164        
165        return feature_names

Get names of features extracted by this extractor.

Returns: List of feature names

class UrFallMediaFeatureExtractor(gaitsetpy.core.base_classes.BaseFeatureExtractor):
15class UrFallMediaFeatureExtractor(BaseFeatureExtractor):
16    """
17    UrFall image/video feature extractor.
18
19    Extracts per-window features from sequences of images or decoded video frames:
20    - mean_intensity
21    - std_intensity
22    - motion_mean (if pairwise differences are available)
23    - motion_std
24    """
25
26    def __init__(self, verbose: bool = False):
27        super().__init__(
28            name="urfall_media",
29            description="Lightweight feature extractor for UrFall depth/RGB/video modalities"
30        )
31        self.config = {
32            'verbose': verbose,
33            'use_motion': True,
34            'grayscale': True,  # for RGB convert to gray before stats
35        }
36
37    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
38        self.config.update(kwargs)
39        features: List[Dict[str, Any]] = []
40        for window in windows:
41            name = window.get('name', 'unknown')
42            frames = window.get('data', [])
43            if not isinstance(frames, list) or len(frames) == 0:
44                continue
45            # frames is a list of numpy arrays (HxW) or (HxWxC)
46            intensities = []
47            motions = []
48            prev_gray = None
49            for f in frames:
50                arr = np.array(f)
51                if arr.ndim == 3 and self.config['grayscale']:
52                    arr = arr.mean(axis=2)
53                intensities.append(float(np.mean(arr)))
54                if self.config['use_motion']:
55                    if prev_gray is not None:
56                        diff = np.abs(arr.astype(np.float32) - prev_gray.astype(np.float32))
57                        motions.append(float(np.mean(diff)))
58                    prev_gray = arr
59            fdict: Dict[str, Any] = {'name': name, 'features': {}}
60            if intensities:
61                fdict['features']['mean_intensity'] = float(np.mean(intensities))
62                fdict['features']['std_intensity'] = float(np.std(intensities))
63            if motions:
64                fdict['features']['motion_mean'] = float(np.mean(motions))
65                fdict['features']['motion_std'] = float(np.std(motions))
66            features.append(fdict)
67        return features
68
69    def get_feature_names(self) -> List[str]:
70        return ['mean_intensity', 'std_intensity', 'motion_mean', 'motion_std'] 

UrFall image/video feature extractor.

Extracts per-window features from sequences of images or decoded video frames:

  • mean_intensity
  • std_intensity
  • motion_mean (if pairwise differences are available)
  • motion_std
UrFallMediaFeatureExtractor(verbose: bool = False)
26    def __init__(self, verbose: bool = False):
27        super().__init__(
28            name="urfall_media",
29            description="Lightweight feature extractor for UrFall depth/RGB/video modalities"
30        )
31        self.config = {
32            'verbose': verbose,
33            'use_motion': True,
34            'grayscale': True,  # for RGB convert to gray before stats
35        }

Initialize the feature extractor.

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

config
def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
37    def extract_features(self, windows: List[Dict], fs: int, **kwargs) -> List[Dict]:
38        self.config.update(kwargs)
39        features: List[Dict[str, Any]] = []
40        for window in windows:
41            name = window.get('name', 'unknown')
42            frames = window.get('data', [])
43            if not isinstance(frames, list) or len(frames) == 0:
44                continue
45            # frames is a list of numpy arrays (HxW) or (HxWxC)
46            intensities = []
47            motions = []
48            prev_gray = None
49            for f in frames:
50                arr = np.array(f)
51                if arr.ndim == 3 and self.config['grayscale']:
52                    arr = arr.mean(axis=2)
53                intensities.append(float(np.mean(arr)))
54                if self.config['use_motion']:
55                    if prev_gray is not None:
56                        diff = np.abs(arr.astype(np.float32) - prev_gray.astype(np.float32))
57                        motions.append(float(np.mean(diff)))
58                    prev_gray = arr
59            fdict: Dict[str, Any] = {'name': name, 'features': {}}
60            if intensities:
61                fdict['features']['mean_intensity'] = float(np.mean(intensities))
62                fdict['features']['std_intensity'] = float(np.std(intensities))
63            if motions:
64                fdict['features']['motion_mean'] = float(np.mean(motions))
65                fdict['features']['motion_std'] = float(np.std(motions))
66            features.append(fdict)
67        return features

Extract features from sliding windows.

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

Returns: List of feature dictionaries

def get_feature_names(self) -> List[str]:
69    def get_feature_names(self) -> List[str]:
70        return ['mean_intensity', 'std_intensity', 'motion_mean', 'motion_std'] 

Get names of features extracted by this extractor.

Returns: List of feature names

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

def extract_harup_features( windows: List[Dict], fs: int = 100, time_domain: bool = True, freq_domain: bool = True, verbose: bool = False) -> List[Dict]:
169def extract_harup_features(windows: List[Dict], fs: int = 100, 
170                         time_domain: bool = True, freq_domain: bool = True, 
171                         verbose: bool = False) -> List[Dict]:
172    """
173    Legacy function for extracting HAR-UP features.
174    
175    Args:
176        windows: List of sliding window dictionaries
177        fs: Sampling frequency (default: 100Hz)
178        time_domain: Whether to extract time domain features
179        freq_domain: Whether to extract frequency domain features
180        verbose: Whether to print progress information
181        
182    Returns:
183        List of feature dictionaries
184    """
185    extractor = HARUPFeatureExtractor(verbose=verbose)
186    return extractor.extract_features(
187        windows, 
188        fs=fs, 
189        time_domain=time_domain, 
190        frequency_domain=freq_domain
191    )

Legacy function for extracting HAR-UP features.

Args: windows: List of sliding window dictionaries fs: Sampling frequency (default: 100Hz) time_domain: Whether to extract time domain features freq_domain: Whether to extract frequency domain features verbose: Whether to print progress information

Returns: List of feature dictionaries

def calculate_mean(signal):
133def calculate_mean(signal):
134    """Calculate the mean of the signal."""
135    return np.mean(signal)

Calculate the mean of the signal.

def calculate_standard_deviation(signal):
64def calculate_standard_deviation(signal):
65    """
66    Calculate the standard deviation of a signal.
67    Args:
68        signal (np.array): Input signal.
69    Returns:
70        std_dev (float): Standard deviation.
71    """
72    return np.std(signal)

Calculate the standard deviation of a signal. Args: signal (np.array): Input signal. Returns: std_dev (float): Standard deviation.

def calculate_variance(signal):
 96def calculate_variance(signal):
 97    """
 98    Calculate the variance of a signal.
 99    Args:
100        signal (np.array): Input signal.
101    Returns:
102        variance (float): Variance.
103    """
104    return np.var(signal)

Calculate the variance of a signal. Args: signal (np.array): Input signal. Returns: variance (float): Variance.

def calculate_skewness(signal):
149def calculate_skewness(signal):
150    """Calculate the skewness of the signal."""
151    try:
152        return skew(signal)
153    except Exception as e:
154        print(f"An error occurred in skewness: {e}")
155        return 0

Calculate the skewness of the signal.

def calculate_kurtosis(signal):
106def calculate_kurtosis(signal):
107    """
108    Calculate the kurtosis of a signal.
109    Args:
110        signal (np.array): Input signal.
111    Returns:
112        kurtosis_value (float): Kurtosis.
113    """
114    try:
115        return kurtosis(signal, fisher=False)
116    except Exception as e:
117        print(f"An error occurred in feature 'kurtosis': {e}")
118        return 0

Calculate the kurtosis of a signal. Args: signal (np.array): Input signal. Returns: kurtosis_value (float): Kurtosis.

def calculate_root_mean_square(signal):
157def calculate_root_mean_square(signal):
158    """Calculate the root mean square of the signal."""
159    return np.sqrt(np.mean(np.square(signal)))

Calculate the root mean square of the signal.

def calculate_range(signal):
161def calculate_range(signal):
162    """Calculate the range of the signal."""
163    return np.max(signal) - np.min(signal)

Calculate the range of the signal.

def calculate_median(signal):
145def calculate_median(signal):
146    """Calculate the median of the signal."""
147    return np.median(signal)

Calculate the median of the signal.

def calculate_mode(signal):
194def calculate_mode(signal):
195    """Calculate the mode of the signal."""
196    values, counts = np.unique(signal, return_counts=True)
197    return values[np.argmax(counts)]

Calculate the mode of the signal.

def calculate_mean_absolute_value(signal):
206def calculate_mean_absolute_value(signal):
207    """Calculate the mean absolute value of the signal."""
208    return np.mean(np.abs(signal))

Calculate the mean absolute value of the signal.

def calculate_median_absolute_deviation(signal):
210def calculate_median_absolute_deviation(signal):
211    """Calculate the median absolute deviation of the signal."""
212    return np.median(np.abs(signal - np.median(signal)))

Calculate the median absolute deviation of the signal.

def calculate_peak_height(signal):
180def calculate_peak_height(signal):
181    """Calculate the peak height of the signal."""
182    peaks, _ = find_peaks(signal)
183    return np.max(signal[peaks]) if len(peaks) > 0 else 0

Calculate the peak height of the signal.

def calculate_stride_times(signal, fs):
 9def calculate_stride_times(signal, fs):
10    """
11    Calculate stride times from a signal using peak detection.
12    Args:
13        signal (np.array): Input signal.
14        fs (int): Sampling frequency.
15    Returns:
16        avg_stride_time (float): Average stride time.
17    """
18    peaks, _ = find_peaks(signal)
19    stride_times = np.diff(peaks) / fs
20    avg_stride_time = np.mean(stride_times) if len(stride_times) > 0 else 0
21    return avg_stride_time

Calculate stride times from a signal using peak detection. Args: signal (np.array): Input signal. fs (int): Sampling frequency. Returns: avg_stride_time (float): Average stride time.

def calculate_step_time(signal, fs):
120def calculate_step_time(signal, fs):
121    """
122    Calculate step times from a signal using peak detection.
123    Args:
124        signal (np.array): Input signal.
125        fs (int): Sampling frequency.
126    Returns:
127        step_times (np.array): Array of step times.
128    """
129    peaks, _ = find_peaks(signal)
130    step_times = np.diff(peaks) / fs
131    return step_times

Calculate step times from a signal using peak detection. Args: signal (np.array): Input signal. fs (int): Sampling frequency. Returns: step_times (np.array): Array of step times.

def calculate_cadence(signal, fs):
199def calculate_cadence(signal, fs):
200    """Calculate the cadence (steps per minute) of the signal."""
201    peaks, _ = find_peaks(signal)
202    step_count = len(peaks)
203    duration = len(signal) / fs
204    return (step_count / duration) * 60

Calculate the cadence (steps per minute) of the signal.

def calculate_freezing_index(signal, fs):
50def calculate_freezing_index(signal, fs):
51    """
52    Calculate the freezing index of a signal.
53    Args:
54        signal (np.array): Input signal.
55        fs (int): Sampling frequency.
56    Returns:
57        freezing_index (float): Freezing index.
58    """
59    power_3_8 = calculate_power(signal, fs, (3, 8))
60    power_0_5_3 = calculate_power(signal, fs, (0.5, 3))
61    freezing_index = power_3_8 / power_0_5_3 if power_0_5_3 != 0 else 0
62    return freezing_index

Calculate the freezing index of a signal. Args: signal (np.array): Input signal. fs (int): Sampling frequency. Returns: freezing_index (float): Freezing index.

def calculate_dominant_frequency(signal, fs):
169def calculate_dominant_frequency(signal, fs):
170    """Calculate the dominant frequency of the signal."""
171    try:
172        fft_values = np.abs(fft(signal))
173        freqs = np.fft.fftfreq(len(signal), 1 / fs)
174        dominant_freq = freqs[np.argmax(fft_values)]
175        return dominant_freq
176    except Exception as e:
177        print(f"An error occurred: {e}")
178        return 0

Calculate the dominant frequency of the signal.

def calculate_peak_frequency(signal, fs):
214def calculate_peak_frequency(signal, fs):
215    """Calculate the peak frequency of the signal."""
216    try:
217        f, Pxx = welch(signal, fs=fs, nperseg=min(len(signal), 192))  # Ensure nperseg ≤ length
218        return f[np.argmax(Pxx)]
219    except Exception as e:
220        print(f"An error occurred in feature 'peak_frequency': {e}")
221        return 0

Calculate the peak frequency of the signal.

def calculate_power_spectral_entropy(signal, fs):
233def calculate_power_spectral_entropy(signal, fs):
234    """Calculate the power spectral entropy of the signal."""
235    try:
236        f, Pxx = welch(signal, fs=fs, nperseg=min(len(signal), 192))  # Ensure nperseg ≤ length
237        Pxx_norm = Pxx / np.sum(Pxx)
238        return -np.sum(Pxx_norm * np.log2(Pxx_norm + np.finfo(float).eps))
239    except Exception as e:
240        print(f"An error occurred in feature 'power spectral entropy': {e}")
241        return 0

Calculate the power spectral entropy of the signal.

def calculate_principal_harmonic_frequency(signal, fs):
243def calculate_principal_harmonic_frequency(signal, fs):
244    """Calculate the principal harmonic frequency of the signal."""
245    try:
246        fft_values = np.abs(fft(signal))
247        freqs = np.fft.fftfreq(len(signal), 1 / fs)
248        return freqs[np.argmax(fft_values)]
249    except Exception as e:
250        print(f"An error occurred in feature 'principal_harmonic_frequency': {e}")
251        return 0

Calculate the principal harmonic frequency of the signal.

def calculate_entropy(signal):
74def calculate_entropy(signal):
75    """
76    Calculate the entropy of a signal.
77    Args:
78        signal (np.array): Input signal.
79    Returns:
80        entropy_value (float): Entropy.
81    """
82    value, counts = np.unique(signal, return_counts=True)
83    probabilities = counts / len(signal)
84    return entropy(probabilities, base=2)

Calculate the entropy of a signal. Args: signal (np.array): Input signal. Returns: entropy_value (float): Entropy.

def calculate_interquartile_range(signal):
185def calculate_interquartile_range(signal):
186    """Calculate the interquartile range of the signal."""
187    try:
188        q75, q25 = np.percentile(signal, [75, 25])
189        return q75 - q25
190    except Exception as e:
191        print(f"An error occurred in feature 'interquartile_range': {e}")
192        return 0

Calculate the interquartile range of the signal.

def calculate_correlation(signal1, signal2):
165def calculate_correlation(signal1, signal2):
166    """Calculate the correlation between two signals."""
167    return np.corrcoef(signal1, signal2)[0, 1]

Calculate the correlation between two signals.

def calculate_auto_regression_coefficients(signal, order=3):
253def calculate_auto_regression_coefficients(signal, order=3):
254    """Calculate the auto-regression coefficients of the signal."""
255    try:
256        model = AutoReg(signal, lags=order)
257        results = model.fit()
258        return results.params
259    except Exception as e:
260        print(f"An error occurred in feature 'auto_regression_coefficients': {e}")
261        return 0

Calculate the auto-regression coefficients of the signal.

def get_mean_for_windows(windows):
486def get_mean_for_windows(windows):
487    return [calculate_mean(window) for window in windows]
def get_standard_deviation_for_windows(windows):
489def get_standard_deviation_for_windows(windows):
490    return [calculate_standard_deviation(window) for window in windows]
def get_variance_for_windows(windows):
492def get_variance_for_windows(windows):
493    return [calculate_variance(window) for window in windows]
def get_skewness_for_windows(windows):
495def get_skewness_for_windows(windows):
496    return [calculate_skewness(window) for window in windows]
def get_kurtosis_for_windows(windows):
478def get_kurtosis_for_windows(windows):
479    """Calculate kurtosis values for all windows in the input."""
480    return [calculate_kurtosis(window) for window in windows]

Calculate kurtosis values for all windows in the input.

def get_root_mean_square_for_windows(windows):
498def get_root_mean_square_for_windows(windows):
499    return [calculate_root_mean_square(window) for window in windows]
def get_range_for_windows(windows):
501def get_range_for_windows(windows):
502    return [calculate_range(window) for window in windows]
def get_median_for_windows(windows):
504def get_median_for_windows(windows):
505    return [calculate_median(window) for window in windows]
def get_mode_for_windows(windows):
507def get_mode_for_windows(windows):
508    return [calculate_mode(window) for window in windows]
def get_mean_absolute_value_for_windows(windows):
510def get_mean_absolute_value_for_windows(windows):
511    return [calculate_mean_absolute_value(window) for window in windows]
def get_median_absolute_deviation_for_windows(windows):
513def get_median_absolute_deviation_for_windows(windows):
514    return [calculate_median_absolute_deviation(window) for window in windows]
def get_peak_height_for_windows(windows):
516def get_peak_height_for_windows(windows):
517    return [calculate_peak_height(window) for window in windows]
def get_stride_times_for_windows(windows, fs):
450def get_stride_times_for_windows(windows, fs):
451    """Calculate stride times for all windows in the input."""
452    return [calculate_stride_times(window, fs) for window in windows]

Calculate stride times for all windows in the input.

def get_step_times_for_windows(windows, fs):
482def get_step_times_for_windows(windows, fs):
483    """Calculate step times for all windows in the input."""
484    return [calculate_step_time(window, fs) for window in windows]

Calculate step times for all windows in the input.

def get_cadence_for_windows(windows, fs):
519def get_cadence_for_windows(windows, fs):
520    return [calculate_cadence(window, fs) for window in windows]
def get_freezing_index_for_windows(windows, fs):
522def get_freezing_index_for_windows(windows, fs):
523    return [calculate_freezing_index(window, fs) for window in windows]
def get_dominant_frequency_for_windows(windows, fs):
525def get_dominant_frequency_for_windows(windows, fs):
526    return [calculate_dominant_frequency(window, fs) for window in windows]
def get_peak_frequency_for_windows(windows, fs):
528def get_peak_frequency_for_windows(windows, fs):
529    return [calculate_peak_frequency(window, fs) for window in windows]
def get_power_spectral_entropy_for_windows(windows, fs):
531def get_power_spectral_entropy_for_windows(windows, fs):
532    return [calculate_power_spectral_entropy(window, fs) for window in windows]
def get_principal_harmonic_frequency_for_windows(windows, fs):
534def get_principal_harmonic_frequency_for_windows(windows, fs):
535    return [calculate_principal_harmonic_frequency(window, fs) for window in windows]
def get_entropy_for_windows(windows):
537def get_entropy_for_windows(windows):
538    return [calculate_entropy(window) for window in windows]
def get_interquartile_range_for_windows(windows):
540def get_interquartile_range_for_windows(windows):
541    return [calculate_interquartile_range(window) for window in windows]
def get_correlation_for_windows(windows):
543def get_correlation_for_windows(windows):
544    # For correlation, we need to handle it differently since it needs two signals
545    # We'll calculate autocorrelation for each window
546    return [calculate_correlation(window[:-1], window[1:]) if len(window) > 1 else 0 for window in windows]
def get_auto_regression_coefficients_for_windows(windows, order=3):
548def get_auto_regression_coefficients_for_windows(windows, order=3):
549    return [calculate_auto_regression_coefficients(window, order) for window in windows]
def extract_gait_features( daphnet_windows, fs, time_domain=True, frequency_domain=True, statistical=True, verbose=True):
551def extract_gait_features(daphnet_windows, fs, time_domain=True, frequency_domain=True, statistical=True, verbose=True):
552    """
553    Legacy function for extracting gait features.
554    
555    Args:
556        daphnet_windows: List of sliding window dictionaries
557        fs: Sampling frequency
558        time_domain: Whether to extract time domain features
559        frequency_domain: Whether to extract frequency domain features
560        statistical: Whether to extract statistical features
561        verbose: Whether to show verbose output and progress bars
562        
563    Returns:
564        List of feature dictionaries
565    """
566    extractor = GaitFeatureExtractor(verbose=verbose)
567    return extractor.extract_features(
568        daphnet_windows, fs, 
569        time_domain=time_domain, 
570        frequency_domain=frequency_domain, 
571        statistical=statistical
572    )

Legacy function for extracting gait features.

Args: daphnet_windows: List of sliding window dictionaries fs: Sampling frequency time_domain: Whether to extract time domain features frequency_domain: Whether to extract frequency domain features statistical: Whether to extract statistical features verbose: Whether to show verbose output and progress bars

Returns: List of feature dictionaries