Skip to main content

Simple, fast IMU+EMG feature extraction

Project description

biomech-features

A Python library for extracting biomechanical features from IMU (Inertial Measurement Unit) and EMG (Electromyography) sensor data. This library provides simple, fast feature extraction with filtering, segmentation (fixed windows or repetition-based), and feature computation into a tidy pandas DataFrame.


Features

  • Multi-modal sensor support: EMG and IMU (accelerometer + gyroscope) data processing
  • Flexible segmentation: Fixed time windows or repetition-based segmentation
  • Comprehensive feature extraction: 100+ features across multiple domains (time, frequency, statistical)
  • Easy integration: Simple API with pandas DataFrame output
  • Extensible: Modular design for adding custom features

Installation

Requirements

  • Python ≥ 3.9
  • NumPy
  • SciPy
  • Pandas

Install from source

git clone <https://github.com/Qirtas/BiomechanicalFeatures.git>
cd BiomechanicalFeatures
pip install -e .

Install from Test PyPI

The package is currently available on Test PyPI for team review and testing:

pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ biomech-features```

Quick Start

Basic Usage

from biomechfe import extract_features, load_record_from_hierarchy

# Load your data
rec = load_record_from_hierarchy(
    root_path, subject_id, trial_id, 
    load_emg=True, load_imu=True
)

# Extract features from IMU data
df_imu = extract_features(
    {"imu": rec["imu"]},
    fs_imu=rec["fs"]["imu"],
    window_s=2.0,
    step_s=1.0
)

# Extract features from EMG data
df_emg = extract_features(
    {"emg": rec["emg"]},
    fs_emg=rec["fs"]["emg"],
    muscle_names=["deltoid", "biceps"],
    window_s=1.0,
    step_s=0.5
)

Repetition-based Segmentation

# Segment data based on movement repetitions
df = extract_features(
    {"imu": rec["imu"]},
    fs_imu=rec["fs"]["imu"],
    segmentation={
        "mode": "reps",
        "rep_mode": "zero_between_valley_peak",
        "axis": "x",
        "min_prominence": 0.25,
        "min_distance_s": 0.8,
        "smooth_s": 0.20,
    }
)

Single Muscle Analysis

# Extract features from specific muscle
target_muscle = "emg_deltoideus_posterior"
emg_channels = rec["meta"]["emg_channels"]
muscle_idx = emg_channels.index(target_muscle)

single_muscle_emg = rec["emg"][muscle_idx:muscle_idx + 1, :]
muscle_name = target_muscle.replace('emg_', '')

df = extract_features(
    {"emg": single_muscle_emg},
    fs_emg=rec["fs"]["emg"],
    muscle_names=[muscle_name],
    window_s=3.0,
    step_s=3.0
)

Feature Categories

EMG Features

Category Description Sub-features
Integrated Features Time-domain integration measures Integrated EMG (iEMG), Modified iEMG
Mean Absolute Value (MAV) Amplitude-based features MAV, MAV1, MAV2, Enhanced MAV
Statistical Features Statistical descriptors RMS, Variance, Standard Deviation, Skewness, Kurtosis
Frequency Features (STFT) Short-time Fourier transform Mean/Median Power Frequency, Spectral Moments, Power Ratios
Wavelength Features Signal complexity measures Waveform Length, Average Wavelength
Zero Crossing (ZC) Signal crossing analysis Zero Crossings, Slope Sign Changes, Willison Amplitude

IMU Accelerometer Features

Category Description Sub-features
3-Axis Features Basic acceleration measures Mean, RMS, Standard Deviation, Range per axis
Magnitude Features Vector magnitude analysis Mean/RMS/Std of magnitude, Peak acceleration
Jerk Features Rate of acceleration change Jerk magnitude, Jerk RMS, Peak jerk
Movement Frequency Frequency domain analysis Dominant frequencies, Power spectral density
MPSD Features Motion-specific power spectral density Frequency band power ratios
RT Variability Real-time variability measures Coefficient of variation, Variability indices

IMU Gyroscope Features

Category Description Sub-features
3-Axis Angular Features Angular velocity measures Mean, RMS, Standard Deviation, Range per axis
Range of Motion (ROM) Movement extent analysis Angular displacement, Total ROM
Movement Frequency Rotational frequency analysis Dominant frequencies, Spectral characteristics
MPSD Features Angular motion power analysis Frequency band distributions
RT Variability Angular velocity variability Smoothness indices, Consistency measures

API Reference

Main Function

extract_features(data, **kwargs)

Extract features from sensor data.

Parameters:

  • data (dict): Dictionary containing sensor data
    • "emg": EMG data array (n_channels, n_samples)
    • "imu": IMU data dict with "acc" and "gyr" keys
  • fs_emg (float, optional): EMG sampling frequency
  • fs_imu (float, optional): IMU sampling frequency
  • muscle_names (list, optional): Names of EMG channels
  • imu_site (str, optional): IMU sensor location name
  • window_s (float, default=2.0): Window size in seconds
  • step_s (float, default=1.0): Step size in seconds
  • extended_acc (bool, default=False): Use extended accelerometer features
  • segmentation (dict, optional): Repetition-based segmentation parameters

Returns:

  • pandas.DataFrame: Features with metadata (window indices, timing, etc.)

Segmentation Modes

Fixed Windows

# Default: fixed-size sliding windows
extract_features(data, window_s=2.0, step_s=1.0)

Repetition-based

segmentation = {
    "mode": "reps",
    "rep_mode": "zero_between_valley_peak",  # or "zero_between_peak_valley"
    "axis": "x",
    "min_prominence": 0.25,
    "min_distance_s": 0.8,
    "smooth_s": 0.20,
}

Data Format

Expected File Structure

The library expects a hierarchical folder structure for automatic data loading:

<root>/
├── <movement>/
│   ├── emg_<muscle_name>/
│   │   └── *Subject_<id>*.csv
│   ├── <imu_site>/
│   │   ├── acc/
│   │   │   └── Subject_<id>_<site>_acc.csv
│   │   └── gyr/
│   │       └── Subject_<id>_<site>_gyr.csv

Example:

Dataset/
├── processed_data_35_i/
│   ├── emg_deltoideus_anterior/
│   │   └── Subject_1_deltoideus_anterior.csv
│   ├── emg_deltoideus_posterior/
│   │   └── Subject_1_deltoideus_posterior.csv
│   ├── Shoulder/
│   │   ├── acc/
│   │   │   └── Subject_1_Shoulder_acc.csv
│   │   └── gyr/
│   │       └── Subject_1_Shoulder_gyr.csv

CSV File Format Requirements

EMG Files

EMG CSV files should contain a single column with the signal data:

  • Preferred column names: "EMG", "emg", "Signal", "signal", "value", "amplitude"
  • Fallback: First numeric column will be used
  • Data type: Numeric values (float)
EMG
0.1234
0.1456
0.1123
...

IMU Files

IMU CSV files should contain 3-axis data (X, Y, Z):

  • Preferred column names:
    • Accelerometer: "Acc_X", "Acc_Y", "Acc_Z"
    • Gyroscope: "Gyr_X", "Gyr_Y", "Gyr_Z"
  • Fallback options: "X", "Y", "Z" or "x", "y", "z"
  • Data type: Numeric values (float)
Acc_X,Acc_Y,Acc_Z
9.81,0.12,-0.34
9.85,0.15,-0.31
9.79,0.09,-0.37
...

Input Data Structure (In-Memory)

# EMG data: (n_channels, n_samples)
emg_data = np.array([[...], [...]])  # 2D array

# IMU data: dictionary with accelerometer and gyroscope
imu_data = {
    "acc": np.array([[...], [...], [...]]),  # (3, n_samples) - x,y,z
    "gyr": np.array([[...], [...], [...]]),  # (3, n_samples) - x,y,z
}

# Complete data structure
data = {
    "emg": emg_data,
    "imu": imu_data
}

Adapting Your Data Format

If your data doesn't match the expected format, here are common adaptation strategies:

1. Different File Structure

import pandas as pd
import numpy as np
from biomechfe import extract_features

# Load your custom format
def load_custom_format(file_path):
    df = pd.read_csv(file_path)
    
    # Example: Convert single file with multiple EMG channels
    emg_columns = ["muscle1", "muscle2", "muscle3"]
    emg_data = df[emg_columns].values.T  # Transpose to (channels, samples)
    
    # Example: Convert IMU data with different column names
    imu_data = {
        "acc": df[["accel_x", "accel_y", "accel_z"]].values.T,
        "gyr": df[["gyro_x", "gyro_y", "gyro_z"]].values.T
    }
    
    return {"emg": emg_data, "imu": imu_data}

# Use with feature extraction
data = load_custom_format("your_file.csv")
df = extract_features(data, fs_emg=1000, fs_imu=100)

2. Different Column Names

def adapt_column_names(df, format_type="emg"):
    """Adapt your column names to library expectations"""
    
    if format_type == "emg":
        # Map your column names to expected ones
        column_mapping = {
            "signal_amplitude": "EMG",
            "muscle_activity": "EMG"
        }
        df = df.rename(columns=column_mapping)
    
    elif format_type == "imu_acc":
        column_mapping = {
            "acceleration_x": "Acc_X",
            "acceleration_y": "Acc_Y", 
            "acceleration_z": "Acc_Z"
        }
        df = df.rename(columns=column_mapping)
    
    elif format_type == "imu_gyr":
        column_mapping = {
            "angular_vel_x": "Gyr_X",
            "angular_vel_y": "Gyr_Y",
            "angular_vel_z": "Gyr_Z"
        }
        df = df.rename(columns=column_mapping)
    
    return df

3. Single File with Multiple Modalities

def split_multimodal_file(file_path):
    """Split a single file containing both EMG and IMU data"""
    df = pd.read_csv(file_path)
    
    # Extract EMG channels
    emg_cols = [col for col in df.columns if 'emg' in col.lower()]
    emg_data = df[emg_cols].values.T
    
    # Extract IMU channels
    acc_cols = [col for col in df.columns if 'acc' in col.lower()]
    gyr_cols = [col for col in df.columns if 'gyr' in col.lower()]
    
    imu_data = {
        "acc": df[acc_cols].values.T,
        "gyr": df[gyr_cols].values.T
    }
    
    return {
        "emg": emg_data,
        "imu": imu_data
    }

4. Different Sampling Rates

from scipy import signal

def resample_data(data, original_fs, target_fs):
    """Resample data to match expected sampling rate"""
    if original_fs == target_fs:
        return data
    
    # Calculate resampling ratio
    num_samples = int(data.shape[-1] * target_fs / original_fs)
    
    if data.ndim == 1:
        return signal.resample(data, num_samples)
    else:
        # For multi-channel data
        resampled = np.zeros((data.shape[0], num_samples))
        for i in range(data.shape[0]):
            resampled[i] = signal.resample(data[i], num_samples)
        return resampled

# Example usage
emg_resampled = resample_data(emg_data, original_fs=2000, target_fs=1000)

5. Custom Data Loader Function

def create_custom_loader(base_path, subject_id, trial_name):
    """
    Create a custom loader for your specific data format
    Returns data in library-compatible format
    """
    
    # Load EMG data (example: single file with multiple muscles)
    emg_file = f"{base_path}/{trial_name}/emg_data_subject_{subject_id}.csv"
    emg_df = pd.read_csv(emg_file)
    
    # Assuming columns: timestamp, muscle1, muscle2, muscle3
    emg_data = emg_df[["muscle1", "muscle2", "muscle3"]].values.T
    
    # Load IMU data (example: separate files for acc and gyr)
    acc_file = f"{base_path}/{trial_name}/accelerometer_subject_{subject_id}.csv"
    gyr_file = f"{base_path}/{trial_name}/gyroscope_subject_{subject_id}.csv"
    
    acc_df = pd.read_csv(acc_file)
    gyr_df = pd.read_csv(gyr_file)
    
    imu_data = {
        "acc": acc_df[["x", "y", "z"]].values.T,
        "gyr": gyr_df[["x", "y", "z"]].values.T
    }
    
    return {
        "emg": emg_data,
        "imu": imu_data,
        "fs": {"emg": 1000, "imu": 100},
        "meta": {
            "subject": subject_id,
            "trial": trial_name,
            "emg_channels": ["muscle1", "muscle2", "muscle3"]
        }
    }

# Usage
data = create_custom_loader("path/to/data", subject_id=1, trial_name="trial1")
df = extract_features(data, fs_emg=data["fs"]["emg"], fs_imu=data["fs"]["imu"])

Output DataFrame

The output DataFrame includes:

  • Feature columns: All computed features with descriptive names
  • Metadata columns:
    • window_idx: Window/segment index
    • start_time: Window start time (seconds)
    • end_time: Window end time (seconds)
    • duration: Window duration (seconds)

Examples

Complete Workflow Example

import numpy as np
import pandas as pd
from biomechfe import extract_features

# Load your data (example with synthetic data)
fs_emg = 1000  # Hz
fs_imu = 100   # Hz
duration = 10  # seconds

# Create synthetic EMG data (2 channels)
emg = np.random.randn(2, fs_emg * duration)

# Create synthetic IMU data
imu = {
    "acc": np.random.randn(3, fs_imu * duration),
    "gyr": np.random.randn(3, fs_imu * duration)
}

# Extract features
df = extract_features(
    {"emg": emg, "imu": imu},
    fs_emg=fs_emg,
    fs_imu=fs_imu,
    muscle_names=["biceps", "triceps"],
    imu_site="wrist",
    window_s=2.0,
    step_s=1.0,
    extended_acc=True
)

# Save results
df.to_csv("extracted_features.csv", index=False)
print(f"Extracted {len(df.columns)} features from {len(df)} windows")

Processing Multiple Subjects

results = []

for subject_id in subject_list:
    for trial_id in trial_list:
        # Load data
        rec = load_record_from_hierarchy(
            root_path, subject_id, trial_id,
            load_emg=True, load_imu=True
        )
        
        # Extract features
        df = extract_features(
            {"emg": rec["emg"], "imu": rec["imu"]},
            fs_emg=rec["fs"]["emg"],
            fs_imu=rec["fs"]["imu"],
            muscle_names=rec["meta"]["emg_channels"],
            window_s=2.0
        )
        
        # Add metadata
        df["subject_id"] = subject_id
        df["trial_id"] = trial_id
        results.append(df)

# Combine all results
final_df = pd.concat(results, ignore_index=True)

Development Setup

git clone <https://github.com/Qirtas/BiomechanicalFeatures.git>
cd biomech-features
pip install -e ".[dev]"

Citation

If you use this library in your research, please cite:


Contact

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

biomech_features-0.1.1.tar.gz (58.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

biomech_features-0.1.1-py3-none-any.whl (78.2 kB view details)

Uploaded Python 3

File details

Details for the file biomech_features-0.1.1.tar.gz.

File metadata

  • Download URL: biomech_features-0.1.1.tar.gz
  • Upload date:
  • Size: 58.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.10

File hashes

Hashes for biomech_features-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3d2a6a24bce4678c8f18a07bfb031441a386ce2a753b44a46df5b2db0d7b2548
MD5 8e4668fffd7b760ad1a6b4000c041ba6
BLAKE2b-256 4a019971a3e8cf576023d9428b245180b946c363e8f84db0a55f757a9fe0fab2

See more details on using hashes here.

File details

Details for the file biomech_features-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for biomech_features-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 eae8eb4761cda6897383456e80424c016a50e348f03299d1dcdf0b209ce84f06
MD5 01fe1115bede053ea2ddc791b613618c
BLAKE2b-256 30a468f6c32747ed58680686505656f2817ca6cc2c6f875c32313316d32a29d6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page