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]
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.
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
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
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
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
Inherited Members
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.
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
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
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
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
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.
Inherited Members
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.
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
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
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
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.
Inherited Members
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.
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
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
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.
Inherited Members
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.
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
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
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
Inherited Members
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
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
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
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
Inherited Members
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
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
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
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
133def calculate_mean(signal): 134 """Calculate the mean of the signal.""" 135 return np.mean(signal)
Calculate the mean of the 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.
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.
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.
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.
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.
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.
145def calculate_median(signal): 146 """Calculate the median of the signal.""" 147 return np.median(signal)
Calculate the median of the 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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