====== Typical EMG Processing ====== **EMG** stands for **electromyography**, which is the study of electrical signals from active muscles that are receiving input from the central nervous system. This wiki includes an overview of [[visual3d:documentation:emg:emg_overview|how to process EMG data in Visual3D]]. More information on EMG can be found in most good biomechanics and motor control textbooks, and on [[http://en.wikipedia.org/wiki/Electromyography|Wikipedia]]. Additional information on EMG processing requirements for the [[http://www.isek-online.org/standards.html|International Society of Electrophysiology and Kinesiology]]. You will also find other useful information on EMG signal processing posted by [[http://noraxon.com/downloads/educational.php3|Noraxon]]. ===== Overview ===== This tutorial will show the basic steps for processing EMG data into Visual3D. Files used in this tutorial can be found here: [[https://www.has-motion.com/download/examples/EMG_Tutorial.zip|EMG Tutorial]] ===== Visual 3D Implementation ===== Visual3D expects the EMG signals to be stored in the C3D file as ANALOG data, not unlike force platform data. One point of importance is that EMG typically has very high frequency content, which means that it must be sampled at high data sampling rates. As of Visual3D Version 5.0, EMG signals are stored as integer multiples of the motion capture sampling rate (e.g. consistent with the common use of the C3D file format). One of the nuisances caused by this requirement is that force platform data must be sampled at a very high rate in order to match the EMG sampling rate. All of Visual3D's [[visual3d:documentation:pipeline:signal_commands:signal_commands_overview|pipeline commands for signal processing]] can be used with EMG data. ===== Processing EMG Signals with the Visual3D Pipeline ===== The tutorial will go through a step by step process using [[visual3d:documentation:pipeline:general_information:pipeline_commands_reference|Visual3D pipeline commands]] to process the example EMG data. It will do the following: - Open Files and Tags - Create Pipeline Parameter "EMG Signals" - Apply a Band Pass Filter - Compute an Envelope - Normalize based off of 4 different methods ==== Open Files and Tag ==== This section will walk you through pipeline commands that will open files and automatically assign tags to motion files for **Movement** and **MVIC** trials. !-------------------------------------------------------------------------------------- ! Open files and automatically assign tag to motion files for Movement and MVIC trials ! -------------------------------------------------------------------------------------- ! Fresh Workspace File_New ; !Open files File_Open ! /FILE_NAME= ; !Assign Tags to MVC_A file Assign_Tags_To_File /MOTION_FILE_NAMES=*MVC_A.C3D ! /QUERY= /TAGS=MVC_A ; !Assign Tags to MVC_B file Assign_Tags_To_File /MOTION_FILE_NAMES=*MVC_B.C3D ! /QUERY= /TAGS=MVC_B ; !Assign Tags to MVC_C file Assign_Tags_To_File /MOTION_FILE_NAMES=*MVC_C.C3D ! /QUERY= /TAGS=MVC_C ; !Assign Tags to walking files Assign_Tags_To_File /MOTION_FILE_NAMES=*trial*.C3D !/QUERY= /TAGS=MOVEMENT ; ==== Create Pipeline Parameter "EMG Signals" ==== Use the command **Set_Pipeline_Parameter**, to set up a pipeline parameter to contain the EMG signal names. Set_Pipeline_Parameter /PARAMETER_NAME=EMG_SIGNALS /PARAMETER_VALUE=EMG_A+EMG_B+EMG_C ; ==== Apply a Band Pass Filter ==== Apply a band pass filter to the EMG data with the commands **Highpass_Filter** and **Lowpass_Filter**. Surface EMG signals have a frequency content between 20 and 500 Hz. **Note:** that the highpass filter often has a cutoff frequency anywhere between 20 and 50 Hz. The purpose of this high pass filter is to remove movement artifact (low frequency content). !------------------------------------------------------------- ! Apply a band pass filter - highpass and lowpass filter !------------------------------------------------------------- ! Select active file ALL_FILES Select_Active_File /FILE_NAME=ALL_FILES ! /QUERY= ; ! Apply highpass filter with 50 Hz cutoff Highpass_Filter /Signal_Types=ANALOG /SIgnal_Names=::EMG_SIGNALS /Signal_Folder=ORIGINAL ! /Result_Suffix= ! /Result_Folder=PROCESSED ! /Filter_Class=BUTTERWORTH /Frequency_Cutoff=50 /Num_Reflected= 0 /Total_Buffer_Size=100 /Num_Bidirectional_Passes=1 ; ! Apply lowpass filter with 500 Hz cutoff Lowpass_Filter /Signal_Types=ANALOG /SIgnal_Names=::EMG_SIGNALS /Signal_Folder=PROCESSED ! /Result_Suffix= ! /Result_Folder=PROCESSED ! /Filter_Class=BUTTERWORTH /Frequency_Cutoff=500 /Num_Reflected= 0 /Total_Buffer_Size=100 /Num_Bidirectional_Passes=1 ; ==== Compute an Envelope ==== Compute the linear envelope of the EMG signal by calculating the RMS value using the command **Moving_RMS** for short successive time periods (the time period is described as a moving window). More information on calculating the RMS can be found at the [[Visual3D:Documentation:Pipeline:Signal_Commands:Moving_RMS|Moving RMS]] page. The command below will apply a moving RMS with a 100ms window !------------------------------------------- ! Apply a moving RMS with a 100ms window !------------------------------------------- Moving_RMS /SIGNAL_TYPES=ANALOG /SIGNAL_NAMES=::EMG_SIGNALS /SIGNAL_FOLDER=PROCESSED ! /RESULT_SUFFIX= /RESULT_FOLDER=RMS /NUM_WINDOW_FRAMES=1+0.1*PARAMETER::ANALOG::RATE ; ==== Create Normalization Factor and Scale ==== There are four alternative normalization methods describes in this tutorial. - [[Visual3D:Documentation:EMG:Filtering:EMG_Envelope_Version_3|Normalize EMG to MVC]] - [[Visual3D:Documentation:EMG:Processing:Normalize_EMG_to_Generic_Global_Variable|Normalize EMG to Generic Global Variable]] - [[Visual3D:Documentation:EMG:Filtering:Removing_DC_Bias|Normalize to Maximum 30 second interval]] - [[Visual3D:Documentation:EMG:Processing:Normalize_EMG_to_MVC|Normalize to the Maximum within each Gait Cycle]] The beginning of the pipeline for all methods is as follows. !-------------------------------------------------------------------------------------------------- ! There are often artifacts in the EMG signal related to filtering, so we want to ignore the first ! and last 50 frames of data (at POINT rate) when we create the normalization (scale) factors !-------------------------------------------------------------------------------------------------- Event_Explicit /EVENT_NAME=START /FRAME=50 ! /TIME= ; Event_Explicit /EVENT_NAME=END /FRAME=250 ! /TIME= ; !--------------------------------------------------- ! Set Pipeline parameters for START and END events !--------------------------------------------------- Set_Pipeline_Parameter /PARAMETER_NAME=START_AT_EVENT /PARAMETER_VALUE=START ; Set_Pipeline_Parameter /PARAMETER_NAME=END_AT_EVENT /PARAMETER_VALUE=END ; The following for loop will loop through each of the signals and first create a global event for the maximum and mean. !--------------------------------------------------- ! Loop to calculate scale to normalize EMG signals !--------------------------------------------------- For_Each /ITERATION_PARAMETER_NAME=INDEX /ITEMS=::EMG_SIGNALS ; ! Create an event at the maximum value Event_Global_Maximum /SIGNAL_TYPES=ANALOG /SIGNAL_NAMES=::INDEX /SIGNAL_FOLDER=RMS /EVENT_NAME=EMG_MAX /SELECT_X=TRUE ! /SELECT_Y=FALSE ! /SELECT_Z=FALSE /START_AT_EVENT=::START_AT_EVENT /END_AT_EVENT=::END_AT_EVENT ; ! Create an event MEAN_START 15 frames prior to the max event Event_Copy /EVENT_NAME=EMG_MAX ! /EVENT_INSTANCE=0 ! /START_AT_EVENT= ! /END_AT_EVENT= /NEW_EVENT_NAME=MEAN_START /FRAME_OFFSET=-15 ; ! Create an event MEAN_END 15 frames after the max event Event_Copy /EVENT_NAME=EMG_MAX ! /EVENT_INSTANCE=0 ! /START_AT_EVENT= ! /END_AT_EVENT= /NEW_EVENT_NAME=MEAN_END /FRAME_OFFSET=15 ; The mean of the signal and the maximum of the signal will then be computed. ! Calculate mean of EMG signal between MEAN_START and MEAN_END Metric_Mean /RESULT_METRIC_NAME=::INDEX&_SCALE ! /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=FALSE /RESULT_METRIC_FOLDER=EMG_SCALE /SIGNAL_TYPES=ANALOG /SIGNAL_NAMES=::INDEX /SIGNAL_FOLDER=RMS /SIGNAL_COMPONENTS=0 /EVENT_SEQUENCE=MEAN_START+MEAN_END /EXCLUDE_EVENTS= /GENERATE_MEAN_AND_STDDEV=FALSE ! /APPEND_TO_EXISTING_VALUES=FALSE ; ! calculate the global maximum of the mean signal Metric_Maximum /RESULT_METRIC_NAME=_MAX /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=TRUE /RESULT_METRIC_FOLDER=PROCESSED /SIGNAL_TYPES=METRIC /SIGNAL_NAMES=::INDEX&_SCALE /SIGNAL_FOLDER=EMG_SCALE ! /SIGNAL_COMPONENTS=ALL_COMPONENTS /EVENT_SEQUENCE= /EXCLUDE_EVENTS= ! /GENERATE_MEAN_AND_STDDEV=TRUE ! /APPEND_TO_EXISTING_VALUES=FALSE /CREATE_GLOBAL_MAXIMUM=FALSE ; ! Clean up some temporary signals Event_Delete /EVENT_NAME=EMG_MAX+MEAN_START+MEAN_END ! /EVENT_SEQUENCE= ! /EXCLUDE_EVENTS= ! /TIME= ; Remove_Signals /SIGNAL_TYPES=METRIC /SIGNAL_NAMES=::INDEX&_SCALE_MAX /SIGNAL_FOLDER=PROCESSED ; End_For_Each /ITERATION_PARAMETER_NAME=INDEX ; ==== Approach 1: Normalize EMG to MVC ==== This pipeline normalizes the EMG signal by dividing the EMG signal by the maximum value of the EMG signal from a separate trial (e.g. a trial that elicits a maximum voluntary contraction (MVC) from the muscle). ! ------------------------------------------------------------- ! Normalize ! ------------------------------------------------------------- ! For each EMG signal, divide by it's maximum value For_Each /Iteration_Parameter_Name=CURRENT_SIGNAL /Items=EMG_A+EMG_B+EMG_C ;  ! Divide the EMG signal by it's maximum value, which was stored automatically in the GLOBAL workspace Evaluate_Expression /EXPRESSION= ANALOG::ORIGINAL::&::CURRENT_SIGNAL& / GLOBAL::METRIC::MVC_MAX::&::CURRENT_SIGNAL&_MAX  ! /SIGNAL_TYPES=  ! /SIGNAL_FOLDER=ORIGINAL  ! /SIGNAL_NAMES= /RESULT_TYPES=ANALOG /RESULT_FOLDERS=NORMALIZED /RESULT_NAME=::CURRENT_SIGNAL  ! /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=FALSE  ; End_For_Each /Iteration_Parameter_Name= CURRENT_SIGNAL ; ==== Approach 2: Normalize EMG to Generic Global Variable ==== Typically the scaling factors for EMG signals are the result of other data trials (such as MVIC trials). Individual files in Visual3D cannot access the data from other files, so the only way signals can be shared is through the Global Workspace. This pipeline normalizes the EMG signal to a global variable. ! ------------------------------------------------------------- ! Normalize ! ------------------------------------------------------------- ! For each EMG signal, divide by it's maximum value For_Each /Iteration_Parameter_Name=CURRENT_SIGNAL /Items=EMG_A+EMG_B+EMG_C ;  ! Divide the EMG signal by it's maximum value, which was stored in the GLOBAL workspace Evaluate_Expression /EXPRESSION= ANALOG::ORIGINAL::&::CURRENT_SIGNAL& / GLOBAL::METRIC::MVC_MAX::&::CURRENT_SIGNAL&_MAX  ! /SIGNAL_TYPES=  ! /SIGNAL_FOLDER=ORIGINAL  ! /SIGNAL_NAMES= /RESULT_TYPES=ANALOG /RESULT_FOLDERS=NORMALIZED /RESULT_NAME=::CURRENT_SIGNAL  ! /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=FALSE  ; End_For_Each /Iteration_Parameter_Name= CURRENT_SIGNAL ; ==== Approach 3: Normalize to Maximum 30 second interval ==== This pipeline normalizes to the greatest ½ second activity during a 5 second trial. A window of twenty-five .02 second intervals of integrated EMG is moved one interval at a time across the 5 seconds of data to find the greatest EMG. The average integrated EMG during the ½ second is used to compute the normalization factor. It does the following: * Compute a normalization factor * Normalize to greatest ½ second during 5 second trial. This is accomplished by using an average filter (see the bottom of the page) * A window of twenty-five .02 second intervals of integrated EMG is moved one interval at a time across the 5 seconds of data to find the greatest EMG. * The average integrated EMG during the ½ second is used to compute the normalization factor. ! --------------------------------------------------------------- ! Normalize ! --------------------------------------------------------------- For_Each /ITERATION_PARAMETER_NAME=INDEX /ITEMS=::EMG_SIGNALS ; Evaluate_Expression /EXPRESSION=ANALOG::RMS&:&:&::INDEX&/&GLOBAL::METRIC::PROCESSED&:&:&::INDEX&_SCALE_MAX_MEAN /RESULT_NAME=::INDEX /RESULT_TYPE=ANALOG /RESULT_FOLDER=RESULT ; End_For_Each /ITERATION_PARAMETER_NAME=INDEX ; ==== Approach 4: Normalize to the Maximum within Each Gait Cycle ==== For this example, the goal is as follows: * Compute the maximum of the EMG signal during each gait cycle * Normalize the EMG signal to the maximum during each gait cycle The beginning of this pipeline are separating the left and right legs and identifying specific gait events depending on the leg. Set_Pipeline_Parameter /PARAMETER_NAME=R_EMG_SIGNAL_LIST /PARAMETER_VALUE=EMG_A+EMG_B+EMG_C ! /PARAMETER_VALUE_SEARCH_FOR= ! /PARAMETER_VALUE_REPLACE_WITH= ! /PARAMETER_VALUE_PREFIX= ! /PARAMETER_VALUE_APPEND= ! /MULTI_PASS=FALSE ; Set_Pipeline_Parameter /PARAMETER_NAME=L_EMG_SIGNAL_LIST /PARAMETER_VALUE=EMG_A+EMG_B+EMG_C ! /PARAMETER_VALUE_SEARCH_FOR= ! /PARAMETER_VALUE_REPLACE_WITH= ! /PARAMETER_VALUE_PREFIX= ! /PARAMETER_VALUE_APPEND= ! /MULTI_PASS=FALSE ; Select_Active_File /FILE_NAME=ALL_FILES ! /QUERY= ; Event_Define_Event_Sequence /EVENT_SEQUENCE_NAME=RCYCLE_SEQUENCE /EVENT_SEQUENCE=RHS+RTO ! /EXCLUDE_EVENTS= ! /INSIDE_OF_SEQUENCE= ! /OFFSET_FROM_START= ! /OFFSET_FROM_END= ! /OFFSET_BY=PERCENT /EVENT_SEQUENCE_INSTANCE=0 ; Event_Define_Event_Sequence /EVENT_SEQUENCE_NAME=LCYCLE_SEQUENCE /EVENT_SEQUENCE=LHS+LTO ! /EXCLUDE_EVENTS= ! /INSIDE_OF_SEQUENCE= ! /OFFSET_FROM_START= ! /OFFSET_FROM_END= ! /OFFSET_BY=PERCENT /EVENT_SEQUENCE_INSTANCE=0 ; A for loop will be used to loop through both the right and left leg. For_Each /ITERATION_PARAMETER_NAME=SIDE ! /ITERATION_PARAMETER_COUNT_NAME= /ITEMS=R+L ; Set_Pipeline_Parameter /PARAMETER_NAME=EMG_SIGNAL_LIST /PARAMETER_VALUE=:&:&::SIDE&_EMG_SIGNAL_LIST ! /PARAMETER_VALUE_SEARCH_FOR= ! /PARAMETER_VALUE_REPLACE_WITH= ! /PARAMETER_VALUE_PREFIX= ! /PARAMETER_VALUE_APPEND= /MULTI_PASS=TRUE ; We're going to then loop over every sequence in the trial, so we first calculate the maximum number of sequences in the longest trial. ! --------------------------------------------------------------------- ! Get the maximum number of cycles in a trial the entire CMZ file ! --------------------------------------------------------------------- ! Count the number of cycles in each trial Metric_Event_Sequence_Count /RESULT_METRIC_FOLDER=EMG_NORM /RESULT_METRIC_NAME=&::SIDE&Cycle_Count ! /TIME_INTERVAL= /EVENT_SEQUENCE=&::SIDE&CYCLE_SEQUENCE /EXCLUDE_EVENTS= /GENERATE_COUNT_TOTAL_IN_GLOBAL=FALSE ; ! Calculate the maximum number of cycles across all trials Metric_Maximum /RESULT_METRIC_FOLDER=EMG_NORM /RESULT_METRIC_NAME=&::SIDE&Cycle_Count_Global ! /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=FALSE /SIGNAL_TYPES=METRIC /SIGNAL_FOLDER=EMG_NORM /SIGNAL_NAMES=&::SIDE&Cycle_Count /COMPONENT_SEQUENCE=ALL /EVENT_SEQUENCE= /EXCLUDE_EVENTS= /SEQUENCE_PERCENT_START= /SEQUENCE_PERCENT_END= /GENERATE_MEAN_AND_STDDEV=FALSE ! /APPEND_TO_EXISTING_VALUES=FALSE /CREATE_GLOBAL_MAXIMUM=TRUE ; ! Create a list from 1 to the maximum event sequence in steps of 1 Set_Pipeline_Parameter_From_For_Loop /PARAMETER_NAME=SEQUENCE_COUNT /PARAMETER_INDEX_START=1 /PARAMETER_INDEX_END=GLOBAL::METRIC::EMG_NORM::&::SIDE&Cycle_Count_Global_Max /PARAMETER_INDEX_STEP=1 /PARAMETER_INDEX_TYPE=INTEGER ; We will then create a temporary signal to normalize the EMG signal with, which will contain the maximum of the EMG Signal of the current cycle. ! --------------------------------------------------------------------- ! Create Normalize Signal ! --------------------------------------------------------------------- ! Create a temporary signal which contains zeros at all frames Evaluate_Expression /EXPRESSION=CURRENT_SIGNAL * 0 /SIGNAL_TYPES=ANALOG /SIGNAL_FOLDER=ENVELOPE /SIGNAL_NAMES=::EMG_SIGNAL_LIST /RESULT_TYPES=DERIVED /RESULT_FOLDERS=EMG_NORM_TEMP /RESULT_NAME= /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=TRUE ; ! Loop over every sequence For_Each /ITERATION_PARAMETER_NAME=SEQUENCE_CURRENT ! /ITERATION_PARAMETER_COUNT_NAME= /ITEMS=::SEQUENCE_COUNT ;  ! Define the "current" sequence as TEMP_CYCLE Event_Define_Event_Sequence /EVENT_SEQUENCE_NAME=TEMP_CYCLE /EVENT_SEQUENCE=&::SIDE&CYCLE_SEQUENCE  ! /EXCLUDE_EVENTS=  ! /INSIDE_OF_SEQUENCE=  ! /OFFSET_FROM_START=  ! /OFFSET_FROM_END=  ! /OFFSET_BY=PERCENT /EVENT_SEQUENCE_INSTANCE=::SEQUENCE_CURRENT  ;  ! Loop over every EMG Signal For_Each /ITERATION_PARAMETER_NAME=EMG_SIGNAL_CURRENT  ! /ITERATION_PARAMETER_COUNT_NAME= /ITEMS=::EMG_SIGNAL_LIST  ;  ! For all frames during the current cycle,  ! set the values to the maximum of the EMG signal Set_Data_To_New_Values /SIGNAL_TYPES=DERIVED /SIGNAL_FOLDER=EMG_NORM_TEMP /SIGNAL_NAMES=::EMG_SIGNAL_CURRENT  ! /SIGNAL_COMPONENTS=ALL_COMPONENTS /RESULT_TYPES=DERIVED /RESULT_FOLDERS=EMG_NORM_TEMP /RESULT_SUFFIX= /EVENT_SEQUENCE=TEMP_CYCLE  ! /EXCLUDE_EVENTS=  ! /START_FRAME=  ! /END_FRAME=  ! /USE_POINT_RATE=TRUE /REPLACEMENT_VALUES=METRIC_MAXIMUM(ANALOG::ENVELOPE::&::EMG_SIGNAL_CURRENT&,EVENT_LABEL::SEQUENCE::TEMP_CYCLE)  ! /THRESHOLD_HIGH=NO_DATA  ! /THRESHOLD_LOW=NO_DATA  ! /SET_DATA_AT_SINGLE_EVENT=FALSE  ; End_For_Each /ITERATION_PARAMETER_NAME=EMG_SIGNAL_CURRENT  ; End_For_Each /ITERATION_PARAMETER_NAME=SEQUENCE_CURRENT ; We can then normalize the EMG signal by dividing it by the temporary signal above. ! --------------------------------------------------------------------- ! Normalize EMG Signal ! --------------------------------------------------------------------- ! Divide the EMG signals by the temporary signal above For_Each /ITERATION_PARAMETER_NAME=EMG_SIGNAL_CURRENT ! /ITERATION_PARAMETER_COUNT_NAME= /ITEMS=::EMG_SIGNAL_LIST ; Evaluate_Expression /EXPRESSION=ANALOG::ENVELOPE::&::EMG_SIGNAL_CURRENT& /DERIVED::EMG_NORM_TEMP::&::EMG_SIGNAL_CURRENT&  ! /SIGNAL_TYPES=  ! /SIGNAL_FOLDER=  ! /SIGNAL_NAMES= /RESULT_TYPES=DERIVED /RESULT_FOLDERS=EMG_NORM /RESULT_NAME=&::EMG_SIGNAL_CURRENT&  ! /APPLY_AS_SUFFIX_TO_SIGNAL_NAME=FALSE  ; End_For_Each /ITERATION_PARAMETER_NAME=EMG_SIGNAL_CURRENT ; End_For_Each /ITERATION_PARAMETER_NAME=SIDE ;