Tutorial 1: ERP Experiment#

Corresponding file for this tutorial: TMSiPlugins/PsychoPy/experiment_psychopy_erp_experiment_apex.py

An ERP experiment consists of two phases which are data collection (stimulus presentation occurs at this phase) and data analysis. This tutorial covers the first phase only. The second phase is explained in Tutorial ERP Analysis.

This tutorial is a step-by-step guide on running the example_psychopy_erp_experiment_apex.py located inside the TMSiPlugins/PsychoPy folder. This example contains all you need to run an auditory oddball experiment in Psychopy using TMSi APEX amplifier. Before reading this tutorial it’s highly recommended to have a basic knowledge about electroencephalography (EEG), TMSi Python interface, and TMSi APEX amplifier. More information about Event Related Potentials (ERPs) can be found on TMSi’s website. Before getting started, please ensure that you have Python 3.8 installed on your PC as this is a requirement of PsychoPy. Furthermore, please ensure you have a USB-TTL module (REF 95-8650-0001-0) available.

Adding the correct directory of files#

To use the TMSi Python interface folder, which is necessary for running the examples, your Python interpreter needs to know the corresponding path. Therefore, the first lines of code add this information. If you wish to store the recordings in a different folder than the default one, you can change the directory specified in the highlighted code line 38 below.

34import sys
35from os.path import join, dirname, realpath
36Plugin_dir = dirname(realpath(__file__)) # directory of this file
37modules_dir = join(Plugin_dir, '..', '..') # directory with all modules
38measurements_dir = join(Plugin_dir, '../../measurements') # directory with all measurements
39configs_dir = join(Plugin_dir, '../../TMSiSDK\\tmsi_resources') # directory with configurations
40sys.path.append(modules_dir)

Importing the required classes and functions#

The next lines import all the libraries which are required for the example script to work. Line 43 is needed so that PsychoPy uses the correct graphics library. They are used for purposes such as handling errors, writing recording files on PC, interacting with the USB-TTL module, and using the PsychoPy package.

42import os
43os.environ["PYQTGRAPH_QT_LIB"] = 'PySide2'
44
45from TMSiSDK.tmsi_sdk import TMSiSDK, DeviceType, DeviceInterfaceType, DeviceState
46from TMSiSDK.tmsi_errors.error import TMSiError
47
48from PySide2.QtWidgets import *
49from TMSiGui.gui import Gui
50from TMSiPlotterHelpers.impedance_plotter_helper import ImpedancePlotterHelper
51from TMSiPlotterHelpers.signal_plotter_helper import SignalPlotterHelper
52from TMSiFileFormats.file_writer import FileWriter, FileFormat
53from TMSiPlugins.PsychoPy.experiment_psychopy import PsychopyExperimentSetup
54from TMSiPlugins.PsychoPy.erp_training_routine import PsychopyTrainingSetup
55from TMSiPlugins.external_devices.usb_ttl_device import TTLError
56from TMSiSDK.device.tmsi_device_enums import MeasurementType
57import time
58from threading import Thread
59import easygui 
60from PIL import Image

Defining a function to set the background image#

Using an auditory oddball paradigm, it’s common to display an image on the screen so that the participant remains focused. This section of the example script defines a function which gets the directory of an image as an input (line 62) and then shows that image on the screen. This function is defined at the beginning as it will be called in several steps of the experiment.

62def display_background_image(image_path):
63    # Open and display the image using PIL
64    image = Image.open(image_path)
65    image.show()
66    easygui.msgbox("Click 'OK' to continue once you see the image.")

Checking the sound level#

As sound intensity is important for auditory oddball paradigms to be reproducible, it should be set before starting the experiment. Therefore, this section of the script is implemented to check if the sound level of user’s PC is set to the desired volume (in this case 40 as it corresponds to 60 dB, please check the paradigm settings in the P300 blog).

This is done by displaying a dialogue box. Please note that the displayed message and the text shown on both buttons can be changed (line 71). The same block of code can be used if more user confirmations need to be defined during the experiment.

69# Check whether the sound level on laptop is set to 40 ~ corresponding to 60 dB
70while True:
71    sound_choice = easygui.buttonbox("Is the laptop sound turned on at volume 40?", choices=["Yes","No"])
72    
73    if sound_choice == "Yes":
74        easygui.msgbox("Well done, you can continue")
75        break
76    elif sound_choice == "No":
77        easygui.msgbox("Adjust the volume please")
78        continue
79    else:
80        break        

Finding the paired device for Bluetooth connection#

As it’s possible to have more than one device connected, it’s necessary to specify the type of connected device as well as the interface via which the device is connected to the PC (line 84). The interface type for APEX can be set to either usb or bluetooth (line 84). When choosing bluetooth, it’s required to assign a variable (dongle) to the Bluetooth dongle that is connected to the PC (line 85). Next, a list of connected devices is stored inside the variable discoveryList.

The next line of code (line 89) is a check to ensure that at least one device has been discovered. When one or multiple devices are found, the device which is paired to the Bluetooth dongle is selected. The handle to this discovered device is stored in the variable dev and a connection to the device is opened (line 97). The dongle ID is used to ensure that the connection is made to the device which is paired to that specific dongle.

83    # Execute a device discovery. This returns a list of device-objects for every discovered device.
84    TMSiSDK().discover(DeviceType.apex, DeviceInterfaceType.bluetooth)
85    dongle = TMSiSDK().get_dongle_list(DeviceType.apex)[0]
86    discoveryList = TMSiSDK().get_device_list(DeviceType.apex)
87
88    # Set up device
89    if (len(discoveryList) > 0):
90        # Get the handle to the paired device.
91        for i, dev_i in enumerate(discoveryList):
92            if dev_i.get_dongle_serial_number() == dongle.get_serial_number():
93                dev_id = i   
94        dev = discoveryList[dev_id]
95        
96        # Open a connection to the APEX-system
97        dev.open(dongle_id = dongle.get_id())

Loading desired device configuration#

The pre-defined configuration files are located inside the configs folder of the TMSi Python interface (TMSiSDK/tmsi_resources). It is also possible to create custom configurations and save these as .xml files. In this example, a 32-channel configuration with EEG labeling is loaded to the device (line 101). Obviously, other configuration files could be imported given the corresponding directory.

 99        # Load the EEG channel set and configuration (in this case for 32 channels)
100        print("load EEG config")
101        dev.import_configuration(join(configs_dir, 'APEX_config_EEG32.xml'))

Initializing PsychoPy experiment#

The first two lines of this section are meant to give feedback to the end-user. Next, settings used for the experiment are defined. These settings include number of trials, interval between stimulus presentation, tone duration, and probability of target stimulus occurrence. Users can set these values based on the desired auditory paradigm. This part of the code is where the experiment can be tailored to the user’s liking (assuming an auditory odd-ball experiment is desired).

103        # Initialize PsychoPy Experiment, for arguments description see class
104        print('\n Initializing PsychoPy experiment and TTL module \n')
105        print('\n  Please check if red and green LEDs are turned on ... \n')
106
107        # Experiment settings
108        n_trials = 60
109        interval = 1.5 
110        duration = 0.05
111        probability = 0.2

Once the settings are defined, they will need to be passed on to the PsychopyExperimentSetup class that is created by TMSi to set up the experiment properties needed to use the PsychoPy library and to initialize the trigger setup for an oddball experiment.

Please note, when APEX is used available input options for both target and non-target values are only even numbers between 2 and 30. These numbers are assigned to ensure that the user can distinguish target and non-target values when looking at the STATUS channel of APEX.

It’s important to specify the correct COM_port on which the USB-TTL module is to be found. This should be checked prior to the experiment, which can be done via Windows’ Device manager - Ports (COM & LPT).

115        experiment = PsychopyExperimentSetup(TMSiDevice="APEX", COM_port = 'COM5', n_trials = n_trials, target_value = 16, nontarget_value= 2,
116                                             interval = interval, probability = probability, duration = duration)

Initializing impedance plotter#

In most EEG measurements, it is common to monitor impedances prior to signal acquisition. Therefore, once the device configuration is set, a real-time plotter is initialized. As shown, the required graphical user interface (GUI) and plotter helper were initially imported from TMSiGui and TMSiPlotterHelpers. The plotter helper defines which plotter is shown by the GUI (for more information about the plotters, please read the Tutorial Plotters). In this case, the ImpedancePlotterHelper is initialized in line 126. Three parameters can be specified for the impedance plotter. The first parameter is the device that is passed to the Helper. The second variable, the layout variable, defines if the electrode impedances should be visualized as a head (for EEG) or in a Grid form (for HD-EMG). Finally, the impedances can be stored in a file: the third parameter defines the location and name of the file.

118        # Check if there is already a plotter application in existence
119        app = QApplication.instance()
120        
121        # Initialise the plotter application if there is no other plotter application
122        if not app:
123            app = QApplication(sys.argv)
124        
125        # Initialise the helper
126        plotter_helper = ImpedancePlotterHelper(device=dev,
127                                                is_head_layout=True, 
128                                                file_storage = join(measurements_dir,"Example_PsychoPy_ERP_experiment"))
129        # Define the GUI object and show it 
130        gui = Gui(plotter_helper = plotter_helper)
131        # Enter the event loop
132        app.exec_()

Once the impedance plotter is closed by the user, a training round of the ERP experiment will start.

Start the training round#

To familiarize the participant with the oddball paradigm, a training round is designed which can be run more than one time if required. In the beginning, line 138 defines the directory that contains the background image (in this case a black cross). Next, a while loop is defined so that the participant can practice the experiment as long as he/she clicks on yes on the dialogue boxes shown. During this practice session, no data acquisition takes place.

Please note that the display_background_image function which was defined earlier is used in this section (line 147).

137        # Set up background image 
138        background_image_path = join(Plugin_dir, 'psychopy_resources', 'cross.png')
139
140        while True:
141            # are you ready for the training?
142            ready_choice = easygui.buttonbox("Are you ready for the training?", choices=["Yes", "No"])
143            
144            if ready_choice == "Yes":
145                
146                # Display the background image
147                display_background_image(background_image_path)
148        
149                # Create an instance of the PsychopyTrainingSetup class with the background image
150                training_setup = PsychopyTrainingSetup(background_image_path)
151                
152                # Run the training
153                training_setup.runTraining()
154            
155                # Ask the participant if they want to do it again or continue
156                choice = easygui.buttonbox("Do you want to do the training again?", choices=["Yes", "No"])
157
158                if choice == "No":
159                    break  # Exit the loop if the participant chooses "No"
160            elif ready_choice == "No":
161                continue
162            else:
163                break

Running the auditory oddball experiment#

Before running the experiment, a file writer ensures that the acquired data will be saved to disk. Here, it is also possible to specify the directory in which the recordings are stored. The default is set to the measurement_dir (defined earlier) located inside the TMSi Python interface folder (line 166). Next a while loop is defined to check whether the participant is ready to start the experiment. The dialogue boxes which appear on the screen are self-explanatory.

165        # Initialise a file-writer class (Xdf-format) and state its file path
166        file_writer = FileWriter(FileFormat.xdf, join(measurements_dir,"Example_PsychoPy_ERP_experiment.xdf"))
167        
168        # Check to see if the participant is ready for the experiment
169        while True:
170            exp_choice = easygui.buttonbox("Are you ready for the experiment?", choices=["Yes","No"])
171            
172            if exp_choice == "Yes":
173                easygui.msgbox("Alright, the experiment will start in a bit. \n\nPlease make the image fullscreen after clicking 'OK' ")
174                break
175            elif exp_choice == "No":
176                easygui.msgbox("Alright take your time")
177                continue
178            else:
179                break   

It is required for the file_writer to get the handle to the device specified earlier in the discoveryList (line 182). Further, a thread (a separate flow of execution) is defined to run the oddball experiment. As soon as the measurement starts on APEX (line 188), the experiment will be started (line 191). The experiment runs until a certain time that is given according to the defined settings (line 194). Please note, some additional time (in this example 10 seconds) is added at the end of the experiment.

No pause is included in this experiment. Therefore, it is advised to keep the number of trials limited or to edit the code to include such a pause.

Finally, the measurement will be stopped, the file_writer will be closed, and the connection to APEX will be closed too. The data will be saved to the measurements directory which is specified in the very beginning.

181        # Define the handle to the device
182        file_writer.open(dev)
183        
184        # Define thread to run the experiment
185        thread = Thread(target=experiment.runExperiment)
186        
187        # Start a measurement on APEX
188        dev.start_measurement(MeasurementType.APEX_SIGNAL)       
189        
190        # Start the PsychoPy thread
191        thread.start()
192        
193        # Acquisition time, based on experiment timing (with some additional time at the end)
194        time.sleep(n_trials * (interval + duration) + 10)
195        
196        # Stop the measurement on APEX
197        dev.stop_measurement()
198
199        # Close the file writer after GUI termination
200        file_writer.close()
201        
202        # Close the connection to APEX
203        dev.close()

To learn how to analyze your ERP data, please refer to the tutorial for example_erp_analysis.py