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