Documentation Site Map Main Page Reference List Motion Capture Visual3D Overview Visual3D Installation License Activation Getting Started Visual3D Documentation Overview Pipeline Commands Reference Expressions Overview CalTester Mode Overview List of Tutorials Visual3D Examples Overview Troubleshooting Sift Sift Overview Installation Getting Started Sift Documentation Overview Knowledge Discovery for Biomechanical Data Tutorial Overview Troubleshooting Inspect3D Inspect3D Overview Inspect3D Installation Overview Inspect3D Getting Started Overview Inspect3D Documentation Overview Knowledge Discovery in Inspect3D Inspect3D Tutorials Overview Troubleshooting DSX Suite DSX Overview DSX Definitions DSX Suite Installation DSX Tutorials DSX Release Notes xManager Overview PlanDSX Overview Surface3D Overview Orient3D Overview CalibrateDSX Overview Locate3D Overview X4D Overview
This is an old revision of the document!
tutorial emg]], we can look at automatically detecting periods of low muscle activity to help us auto populate emg onset/offset detection. in this tutorial we will: - load emg data into visual 3d - perform a series of signal filtering commands including: - bandpass filter - full-wave rectify - moving root mean squared (rms) - teager-kaiser energy operator (tkeo) - automatically detect low muscle activity - generate tkeo threshold to perform onset offset detection - visualize the event detection and demonstrate the effects of changing the meta parameters ===== preparing for the tutorial ===== **tutorial data download**: files used in this tutorial can be found here: [[[https://www.has-motion.com/download/examples/emg_tutorial.zip|emg tutorial]]] the following pipeline has been provided as a **meta_function**, and you may copy and paste it into your **visual3d x64 > plugins > meta-commands** folder. for more information on meta-commands and how they work, follow see [[visual3d:documentation:pipeline:meta_commands:meta_commands_overview|here]]. **meta-commands download**: meta-command file found here: [[[https://www.has-motion.com/download/examples/emg/automatic_emg_events.v3m|automatic_emg_events]]] ===== loading data and calling the meta-function ===== open the **trial_1.c3d** file from the tutorial download into visual3d. to begin the automatic detection, we first need to follow the typical processing procedure for emg signals, going through all signals individually. we will work through the pipeline of **automatic_emg_events**. you may look through the meta-command file along with this description, or use this pipeline to help build your own. to call the meta-command in the pipeline, use the following: <code> automatic_emg_events /signal_names=emg_a+emg_b+emg_c /thresholdpercent=0.2 ; </code> we can go through the meta-command pipeline itself to see what is being done. we begin by initializing the pipeline as a meta-command. this command will take two parameters, **signals_names** and **thresholdpercent**. the signal names are associated with your emgs and associated .c3d files. the threshold percent determines what percent of the maximum signal voltage you want to consider as noise or low muscle activity. <code> ! begin_meta ! meta_cmd_name=automatic_emg_events ! meta_param= signal_names:string::yes ! meta_param= thresholdpercent:string::no ! meta_highpass= ! meta_lowpass= ! end_meta </code> ===== processing and filtering raw emg signals ===== this meta command can be used to process emg data in two ways; first, through the **moving rms** filtering to remove noise from the analog signal while maintaining overall power, amplitude and energy, muscle activation patterns, and fatigue properties [**1**]. second, **tkeo** will be used on the raw signals to perform a more rigorous filtration that will allow for onset/offset contraction detection. the **tkeo** will reduce power and amplitude but is more accurate when finding exact timing of events [**2**]. we initialize a for loop to go through all emg signals in the open file. <code> for_each /iteration_parameter_name= signal /items= ::signal_names ; </code> now we will look at filtering only at emg data, if you wish to find grfs or **model_based_data**, run a different pipeline before or after this one. first, a **bandpass filter** (high and lowpass) will be performed on raw analog signals. the **highpass filter** removes high frequency noise, the cutoff may need to be adjusted to suit your raw signals and how much noise there is. that is the **thresholdpercent** parameter, and is expressed as a decimal. in the **analog** folder, several sub-folders will be generated in this section of the pipeline, as we go through the different filtering methods. the **processed** data folder is going to contain the data that has gone through a highpass and lowpass filter. the **envelope** data folder will contain filtered data in order of highpass, rectify, lowpass - this will be used later to help us find a resting period of the signal, with the **thresholdpercent** parameter. the **processed_rms** data folder will take the **processed** data and perform moving rms. the **processed_tkeo** data folder will take the **processed** data and perform tkeo. <code> ! highpass filter removes large frquency noise from signal highpass_filter /signal_types=analog /signal_folder=original /signal_names=::signal /result_folder=processed ! /result_suffix= ! /filter_class=butterworth /frequency_cutoff=50 /num_reflected=0 ! /num_extrapolated=0 /total_buffer_size=100 ! /num_bidirectional_passes=1 ; </code> <code> ! rectify and lowpass for envelop development (difference freq cutoff in lowpass filter) rectify /signal_types=analog /signal_names=::signal /signal_folder=processed ! /result_names= ! /result_types= /result_folder=rectify ! /result_suffix= ; </code> <code> lowpass_filter /signal_types=analog /signal_folder=rectify /signal_names=::signal /result_folder=envelope ! /result_suffix= ! /filter_class=butterworth /frequency_cutoff=10 /num_reflected=0 ! /num_extrapolated=0 /total_buffer_size=100 ! /num_bidirectional_passes=1 ; </code> next, lowpass filter will be applied to the original processed data for bandpass filtering. the lowpass filter removes any baseline drift, and again may need to be adjusted for your data. for more details on these filtering functions, see [[visual3d:documentation:emg:processing:onset_based_on_tko|emg processing tools]]. <code> lowpass_filter /signal_types=analog /signal_folder=processed /signal_names=::signal /result_folder=processed ! /result_suffix= ! /filter_class=butterworth /frequency_cutoff=80 /num_reflected=0 ! /num_extrapolated=0 /total_buffer_size=100 ! /num_bidirectional_passes=1 ; </code> <code> ! rms filtering: putting results in a new processed_rms folder to identify which ! filtering process was used. using the processed data from the bandpass filter ! to perform rms on. moving_rms /signal_types=analog /signal_folder=processed /signal_names=::signal ! /result_types= /result_folders=processed_rms ! /result_name= ! /apply_as_suffix_to_signal_name= ! /num_window_frames=3 ! /if_even_frames_increment_1=false ; </code> <code> ! tkeo filtering: putting results in a new processed_tkeo folder to identify which ! filtering process was used. using the processed data from the bandpass filter ! to perform rms on. teager_kaiser_energy /signal_types=analog /signal_folder=processed /signal_names=::signal !/signal_components= /result_folder=processed_tkeo ! /event_sequence= ! /exclude_events= ; </code> ===== detecting low activity signal regions ===== envelope.png this section will essentially perform on/off detection of the **envelope** signal with an arbitrary baseline that we define with a user-given parameter. event detection using analog signal envelop method: creates events that define the resting baseline of the muscle. we will find the resting period based on an arbitrary value that the user can change. for this example, we use 20% of the maximum signal of the **envelope** (which is a highly smoothed signal). after we find the rest period, we compute the median and standard deviation to find an appropriate baseline for on/off detection of muscle activation that can be used on **tkeo** signals. when we run this pipeline section, you can open your **envelope** analog signals and highlight the **ascent** and **descent** events that are associated with each signal. here, you can determine if the **thresholdpercent** parameter is at an appropriate level for your signals. increasing the **thresholdpercent** will consider more of the signal to be “rest”, or noise, and lowering the **thresholdpercent** will decrease the noise that you include in your “resting” data. find maximum for each signal <code> metric_maximum /result_metric_folder=processed /result_metric_name=::signal&max /apply_as_suffix_to_signal_name=false /signal_types= analog /signal_folder=envelope /signal_names=::signal ! /component_sequence= !/event_sequence= !/exclude_events= ! /generate_mean_and_stddev=true ! /append_to_existing_values=false ! /create_global_maximum=false ! /create_trial_maximum=false ; </code> find events when signal crosses threshold that is some specified % of metric max, to find "activation" and "rest" <code> ! set threshold set_pipeline_parameter_from_expression /parameter_name= thresh /expression=(metric::processed::&::signal&max)*&::thresholdpercent /as_integer=false ; ! find when signal "ascents" above threshold metric_time_of_threshold_from_event /result_event_name=::signal&ascent /signal_types=analog /signal_names=::signal /signal_folder=envelope /signal_components=x ! /frame_offset=0 ! /time_offset= ! /event_sequence= ! /exclude_events= !/event_sequence_instance=0 ! /event_subsequence= ! /subsequence_exclude_events= ! /event_subsequence_instance=0 /event_instance=0 /select_x=true ! /select_y= ! /select_z= ! /select_residual= ! /start_at_event= ! /end_at_event= /threshold=::thresh /on_ascent=true !/on_descent= ! /frame_window=8 ! /ensure_frames_before= ! /ensure_frames_after= ; ! find when signal "descents" below threshold metric_time_of_threshold_from_event /result_event_name=::signal&descent /signal_types=analog /signal_names=::signal /signal_folder=envelope /signal_components=x ! /frame_offset=0 ! /time_offset= ! /event_sequence= ! /exclude_events= !/event_sequence_instance=0 ! /event_subsequence= ! /subsequence_exclude_events= ! /event_subsequence_instance=0 /event_instance=0 /select_x=true ! /select_y= ! /select_z= ! /select_residual= ! /start_at_event= ! /end_at_event= /threshold=::thresh /on_ascent=false /on_descent=true ! /frame_window=8 ! /ensure_frames_before= ! /ensure_frames_after= ; </code> now define events at the times of "ascent" and "descent". a period of time between descent to ascent should be an area of low activity and just noise. <code> event_explicit /event_name=::signal&ascent !/frame=1 /time=metric::processed::&::signal&ascent ; event_explicit /event_name=::signal&descent !/frame=150 /time=metric::processed::&::signal&descent ; </code> ===== onset/offset detection ===== now that we have **ascent** and **descent** events of our emg envelopes, we can consider low activity or “rest” periods to be between descent - ascent events. the reason we cannot officially define these events as **onset** or **offset** of the muscle, is because it is more correct to use the **tkeo** signal with a threshold of **median + 3*stdv** of the “resting” signal. so now we have identified the resting periods of each signal and can find our official **onset** and **offset** thresholds. here we compute the median and standard deviation of the **tkeo** envelope during defined "rest". we have the events to define a period of rest, and we can define these times for the **tkeo** signal. tkeo is used to define **on/off** for muscle activity by identify that the signal is on based on a threshold crossing of the **median + 3 * standard deviation** (from tkeo filtered data specifically. for more detail, [[visual3d:documentation:emg:processing:onset|emg onset based on tko]] or reference **[3]**). <code> metric_median /result_metric_name=_med /apply_as_suffix_to_signal_name=true /result_metric_folder=emg_tkeo /signal_types=analog /signal_names=::signal /signal_folder=processed_tkeo ! /signal_components=all_components /event_sequence=::signal&descent+::signal&ascent /exclude_events= /generate_mean_and_stddev=true ! /append_to_existing_values=false ; metric_stddev /result_metric_name=_sd /apply_as_suffix_to_signal_name=true /result_metric_folder=emg_tkeo /signal_types=analog /signal_names=::signal /signal_folder=processed_tkeo ! /signal_components=all_components /event_sequence=::signal&descent+::signal&ascent /exclude_events= /generate_mean_and_stddev=true ! /append_to_existing_values=false ; </code> identify the event emg_c_on based on a threshold crossing <code> !of the median + 3 * standard deviation set_pipeline_parameter_from_expression /parameter_name= thresh2 /expression=(metric::emg_tkeo::&::signal&_med_mean)+3*(metric::emg_tkeo::&::signal&_sd_mean) /as_integer=false ; ! create threshold line vector for plotting purposes create_target /signal_names=::signal&_threshold ! /signal_description= /expression=vector(&::thresh2&,0,0) ! /include_motionfile=true ! /include_calfile=false ; </code> defining signal off based on descending through the new threshold <code> metric_time_of_threshold_from_event /result_event_name=::signal&off /signal_types=analog /signal_names=::signal /signal_folder=processed_tkeo /signal_components=x ! /frame_offset=0 ! /time_offset= ! /event_sequence= ! /exclude_events= !/event_sequence_instance=0 ! /event_subsequence= ! /subsequence_exclude_events= ! /event_subsequence_instance=0 /event_instance=0 /select_x=true ! /select_y= ! /select_z= ! /select_residual= ! /start_at_event= ! /end_at_event= /threshold=::thresh2 /on_ascent=false /on_descent=true ! /frame_window=8 ! /ensure_frames_before= ! /ensure_frames_after= ; </code> creating an event for the off detection <code> event_explicit /event_name=::signal&off !/frame=1 /time=metric::processed::&::signal&off ; </code> defining signal on based on ascending through the new threshold <code> metric_time_of_threshold_from_event /result_event_name=::signal&on /signal_types=analog /signal_names=::signal /signal_folder=processed_tkeo /signal_components=x ! /frame_offset=0 ! /time_offset= ! /event_sequence= ! /exclude_events= !/event_sequence_instance=0 ! /event_subsequence= ! /subsequence_exclude_events= ! /event_subsequence_instance=0 /event_instance=0 /select_x=true ! /select_y= ! /select_z= ! /select_residual= ! /start_at_event= ! /end_at_event= /threshold=::thresh2 /on_ascent=true /on_descent=false ! /frame_window=8 ! /ensure_frames_before= ! /ensure_frames_after= ; </code> creating an event for the on detection <code> event_explicit /event_name=::signal&on !/frame=1 /time=metric::processed::&::signal&on ; </code> ===== visualizing event detection ===== there we go! we have automatically found onset and offset for all signals, and can look at these events in our data. before this, we end the for loop for the meta-function. <code> end_for_each /iteration_parameter_name=signal ; </code> tkeo_new.png zoomed.png we can now take a look at our onset and offset detection. select a **tkeo** processed signal from the **analog > processed_tkeo** folder and plot a new graph. under **event_label > original**, select the '**signalname'on** and '**signalname'off** events for the signal plotted. you can see on the signal there are faint pink lines all across the signal, that do not all get highlighted when the events are selected. this is because event assignment happens at then file level, not at the signal level. so these events are being assigned to the entire motion file each time we define an event for one of the signals. this is why it is important that you highlight only the events associated with the signal you are looking at. we can see from these figures that there are a lot of **on** and **off** detections for the **tkeo** signal. this is due to a lot of fluctuation and noise in the signal, and the threshold is passed through many times where there is large muscle activity. if you find the automatic detection is identify data fluctuation that is too low in amplitude for your liking, we can adjust the **thresholdpercent** parameter to change this. for example, this meta-command was re-run on this data using **thresholdpercent = 0.6**, and the following results were found. we can see that only muscle activity of high magnitude were noted as **on/off** events. thresh60.png ===== references ===== 1. [[https://www.sciencedirect.com/science/article/abs/pii/s0966636208000994|amedeo troiano, francesco naddeo, erik sosso, gianfranco camarota, roberto merletti, luca mesin, assessment of force and fatigue in isometric contractions of the upper trapezius muscle by surface emg signal and perceived exertion scale, gait & posture 2018]] 2. [[https://ieeexplore.ieee.org/abstract/document/7096455|josé a. biurrun manresa, carsten d. mørch, and ole k. andersen, teager-kaiser energy operator improves the detection and quantification of nociceptive withdrawal reflexes from surface electromyography, ieee]] 3. [solnik s, devita p, rider p, long b, and hortobágyi t (2008) teager–kaiser operator improves the accuracy of emg onset detection independent of signal-to-noise ratio, acta bioeng biomech. 2008 ; 10(2): 65–68] }}}}}}