Experimentation I - Introduction to PsychoPy I
Contents
Experimentation I - Introduction to PsychoPy I¶
Michael Ernst
Phd student - Fiebach Lab, Neurocognitive Psychology at Goethe-University Frankfurt
Peer Herholz (he/him)
Research affiliate - NeuroDataScience lab at MNI/McGill
Member - BIDS, ReproNim, Brainhack, Neuromod, OHBM SEA-SIG, UNIQUE
@peerherholz
This lecture was prepared by Peer Herholz and further adapted and expanded for this course by Michael Ernst.
Objectives 📍¶
get to know the PsychoPy library
learn basic and efficient usage of its
module
s andfunction
s to create simple experiments
Important note¶
The main content of this section will be presented via a mixture of slides and VScode which is further outlined in the respective section of the course website and slides. As noted there, jupyter notebooks
aren’t the best way to work on and run experiments using PsychoPy
, instead we need to switch to IDE
s for this part of the course. Specifically, we will use VScode
for this.
This notebook
is thus not intended as the main resource and you shouldn’t try to test/run the experiment via the here included code cells
. Rather, this is meant to be an add-on resource that presents some of the content and especially code
in a more condensed form. We hope it will be useful/helpful for you.
Outline¶
Within this notebook we will go through the basic and required steps to create a new experiment using PsychoPy
, including:
Prerequisites
1.1 Computing environment
1.2 Folders & FilesPsychoPy
basics
2.1 The general idea
2.2 Input viadialog boxes
2.3 Presenting instructionsPsychoPy
’s working principles
3.1draw
ing &flip
ping
3.2trial
sInput/output
4.1 collecting responses
4.2 saving dataA very simple experiment
Prerequisites¶
Starting new experiments in PsychoPy
follows the same guidelines as starting new projects in general. This includes the following:
create and store everything in a dedicated place on your machine
create and use a dedicated computing environment
document everything or at least as much as possible
test and save things in very short intervals, basically after every change
Computing environments¶
As addressed during the first weeks of the course, computing environments
are essential when programming, not only in Python
. This refers to reproducibility, transfer/sharing of code and many other factors. Lucky for us Python
makes it easy to create
and manage
computing environments
, for example using conda.
We can thus also use it to create a new computing environment
specifically dedicated to creating and running a new experiment using PsychoPy
. Here we will name it psychopy
and include/install a few dependencies we already know.
Note: Now for you wsl-folks this probably won’t work as intended, therefore we had you download the standalone version of Psychopy
, so you can safely skipt the cell below.
%%bash
conda create -n psychopy psychopy
conda activate psychopy
pip install jedi psychtoolbox pygame pyo pyobjc
python-vlc ujson
With these few steps we have our (initial) computing environment
ready to go!
Let’s continue with creating folders
and files
we need.
Folders & files¶
As mentioned above, it’s a good idea to keep things handy and organized. Obviously, this also holds true for running experiments using PsychoPy
. While there are several ways we could do this, at the minimum we need a dedicated folder
or directory
somewhere on our machine within which we will store all corresponding information and files
. Creating a new directory
is no biggie using bash
. We can simply use mkdir
and specify the wanted path
and name
. For the sake of simplicity, let’s put everything in a folder called psychopy_experiment
on our Desktop
s.
%%bash
mkdir /path/to/your/Desktop/psychopy_environment
If you’d prefer to just stick with Python you could instead make use of the os
module, like so:
import os
os.makedirs('/path/to/your/Desktop/psychopy_environment')
But it’s a good exercise to get aquainted with bash, so let’s continue with that.
Now we will change our current working directory
to this new folder
%%bash
cd /path/to/your/Desktop/psychopy_environment
and once there, create a new python script
, i.e. an empty python file
. For this we can use bash
s touch
function
followed by the desired filename
. Keeping things simple again, we will name it experiment.py
(notice the file extension .py
).
%%bash
touch experiment.py
Within this, currently empty, python file
/script
we will put our python code
needed to run
the experiment
.
VScode setup¶
Once more: please remember that we’re switching to an IDE
for this part of the course, specifically VScode
, as jupyter notebooks
aren’t the most feasible way to implement/test/run experiment
s via PsychoPy
. Therefore, please open VScode
and within it open the folder we just created (File
-> Open Folder
). Next, click on the experiment.py
file which should open in the editor
window.
With that your setup is ready for our PsychoPy
adventure and should look roughly like below:
PsychoPy
basics¶
It’s already time to talk about PsychoPy
, one of the python libraries
intended to run experiments
and acquire data
. For more information regarding different software and options, their advantages and drawbacks, please consult the slides.
Make sure to check the PsychoPy
website, documentation and forum.
What is PsychoPy
¶
Psychology software in
Python
, i.e. aPython library
, i.e. completely written inPython
2002-2003: Jon Pierce began work on this for his own lab (visual neuroscience)
2003-2017: a purely volunteer-driven, evenings and weekends project
2017-now: still open source and free to install but with professional support
Idea/goals of PsychoPy
¶
allow scientists to run as wide a range of experiments as possible, easily and via standard computer hardware
precise enough for psychophysics
intuitive enough for (undergraduate) psychology (no offence)
flexible enough for everything else
capable of running studies in the lab or online
can work with pure python flows, but adds additional functionality on top of that
Things to check/evaluate¶
computer hardware settings & interfaces
rapid software development
always check version
set
version
inexperiment
use
environments
forexperiments
don’t change version in running
experiments
First things first: do you have a working PsychoPy
installation?
We can simply check that via starting an ipython
session from our terminal
:
%%bash
ipython
and from within there then try import
ing PsychoPy
:
import psychopy
Which shouldn’t work for you folks, as we’ve opted to download the standalone installation. Instead you can open
psychopy, navigate to the coder
view, create a new file and try out the above import
statement.
The general idea¶
If you don’t get an import error
there, at least the basic installation
should be ok!
Cool, we are now ready to actually do some coding
! As said before we will do that in our experiment.py
script
. While the transition from jupyter notebooks
to python scripts
might seem harsh at first, it’s actually straight-forward: the steps we conducted/commands
we run
in an incremental fashion will also be indicated/run
in an incremental fashion here, just within one python script
line-by-line.
So, what’s the first thing we usually do? That’s right: import
ing modules
and function
s we need. Comparably to jupyter notebook
, we will do that at the beginning of our script
.
Please note, that we will go through a realistic example of coding
experiments
in python
and thus might not import all modules
/functions
we will actually need from the start. Thus, we will add them to the the beginning of our scripts as we go along.
However, we actually haven’t checked out what modules
/functions
PsychoPy
has. Let’s do that first.
psychopy.core: various basic functions, including timing & experiment termination
psychopy.gui: various basic functions, including timing & experiment termination
psychopy.event: handling of keyboard/mouse/other input from user
psychopy.visual/sound: presentation of stimuli of various types (e.g. images, sounds, etc.)
psychopy.data: handling of condition parameters, response registration, trial order, etc.
many more …: we unfortunately can’t check out due to time constraints
Nice, looks like a decent collection of useful/feasible modules
/functions
. The question now is: which ones do we need to implement to run
our experiment
?
“What experiment are we talking about?” you might ask. Well…, let’s make up our minds on that.
Let’s assume we have gotten our hands on some data regarding favorite artists
, snacks
and animals
from a group of fantastic students and now want to test how each respectively provided item is perceived
/evaluated
by our sample: how would we do that?
As you can see, we need all of them!
Input via dialog boxes¶
Many experiments
start with a GUI dialog box
that allow us to input certain information, for example participant id
, session
, group
, data storage path
, etc. . We can implement this crucial aspect via the psychopy.gui module. Initially, we need to import
it and thus need to start a new section in our python script
and after that, we can define the GUI dialog box
we want to create via a dictionary
with respective key-value pairs
.
Please note: at this we will start populating our experiment.py
script
. Thus, you should copy paste the respective content of the code cells
into your experiment.py
script
you opened in VScode
. As we will go step-by-step, the code
/script
will get longer and longer.
Btw can you think of other uses such a dialog box may have for psychological research?
#===============
# Import modules
#===============
from psychopy import gui, core # import psychopy modules/functions
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
That’s actually all we need to test our GUI dialog box
. In order to do that, we need to run
/execute
our python script
called experiment.py
.
Simply open your psychopy
installation, navigate to the coder view
, open the experiment.py
file via file -> open file
and hit the run
button (green arrow).
Usually this is achieved via typing python experiment.py
in the VScode terminal
and pressing enter
this will run
/execute
the python script
experiment.py
via the python
installed in our conda environment
called psychopy
. Again, please don’t run
/execute
code
in this jupyter notebook
!
Note: You can also try out the create_dialogue_box.py
script in the /lecture/experiment/
folder, which does the same basic thing
python experiment.py
If everything works/is set correctly you should see a GUI dialog box
appearing on your screen asking for the information we indicated in our experiment.py
python script
(chances are the layout on your end looks a bit different than mine, that’s no biggie). After entering all requested information and clicking ok
the GUI dialog box
should close and no errors should appear.
E.g.
Data Handling
As we want to log whatever info is collected in the experiment, the next aspect we should take care of is the data handling
, i.e. defining a data filename
and path
where it should be saved.
We can make use of the exp_info dictionary
right away and extract important information from there, for example, the experiment
name and participant ID
. Additionally, we will obtain the date
and time
via the psychopy.core module
.
We will also create a unique filename
for the resulting data
and check if the set data path
works out via the os
module
.
#===============
# Import modules
#===============
from psychopy import gui, core, data # import psychopy modules/functions
import os # import os module
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# Get date and time
exp_info['date'] = data.getDateStr() # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name no dict
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory to terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
Presenting instructions¶
After having set some crucial backbones of our experiment
, it’s time to actually start it. Quite often, experiments
start with several messages of instructions
that explain the experiment
to the participant
. Thus, we will add a few here as well, starting with a common “welcome” text message
.
To display things in general but also text, the psychopy.visual module is the way to go. What we need to do now is define a general experiment window
to utilize during the entire experiment
and a text
to be displayed on it.
We can do the first via the function
win = visual.Window()
Which takes some keyword arguments
, such as the size (e.g. size=(800,600)
) of the window we want to display, it’s backgroundcolor (e.g. color='gray'
) as inputs. For a full-list of specifications check out the visual.Windows() API
#===============
# Import modules
#===============
from psychopy import gui, core, data, visual, event # import psychopy modules/functions
import os # import os module
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
# Get date and time
exp_info['date'] = data.getDateStr() # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name no dict
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory to terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press the spacebar to continue.",
color='black', height=40)
As you see, we can create messages
by using the function
visual.TextStim()
to which we provide as an positional argument
our newly created window, the text
we want to provide as a string
and some further specifications for readability.
welcome_message = visual.TextStim(win, text="Welcome to the experiment.")
Check-out the visual.TextStim() API for more ways to customize your messages
.
PsychoPy working principles¶
Now we come across one of PsychoPy
’s core working principles: we need a general experiment window
, i.e. a place we can display/present something on.
You can define a variety of different windows
based on different screens
/monitors
which should however be adapted to the setup
and experiment
at hand (e.g. size
, background color
, etc.). Basically all experiments
you will set up will require you to define a general experiment window
as without it no visual stimuli
(e.g. images
, text
, movies
, etc.) can be displayed/presented or how PsychoPy
would say it: drawn
Speaking of which: this is the next core working principle we are going to see and explore is the difference between draw
ing something and showing it.
Draw
ing & flip
ping¶
In PsychoPy
(and many other comparable software) there’s a big difference between draw
ing and showing something.
While we need to draw
something on/in a window
, to actually add
the component
to our window
, that alone won’t actually show it.
This is because PsychoPy
internally uses “two screens”
one background
or buffer
screen
which is not seen (yet) and one front screen
which is (currently) seen. When you draw
something it’s always going to be draw
n on the background
/buffer
screen
, thus “invisible” and you need to flip
it to the front screen
to be “visible”.
Why does PsychoPy
(and other comparable software) work like that? The idea/aim is always the same: increase performance and minimize delays (as addressed in the slides).
Draw
ing something might take a long time, depending on the stimulus at hand, but flip
ping something already drawn from the buffer
to the front screen
is fast(er). It can thus ensure better and more precise timing. This can work comparably for images
, sounds
, movies
, etc. where things are set/draw
n/pre-loaded and presented exactly when needed.
#===============
# Import modules
#===============
from psychopy import gui, core, data, visual, event, data # import psychopy modules/functions
import os # import os module
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
# Get date and time
exp_info['date'] = data.getDateStr() # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name no dict
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory to terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press the spacebar to continue.",
color='black', height=40)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
As you might have noticed the draw
function is applied to the text_stimulus
to load it onto our buffer screen
, while the .flip
function is applied afterwards to our window
instead.
Let’s give it a try via python experiment.py
. If everything works/is set correctly you should see the GUI dialog box
again but this time after clicking OK
, the text
we defined as a welcome message should appear next.
Waiting for Input¶
However, the text only appears very briefly and in contrast to our GUI dialog box
doesn’t wait for us to press anything in advance.
This is because we didn’t tell PsychoPy
that we want to wait
for a distinct key press
before we advance further, we need the psychopy.event module.
Through its .waitKeys()
function
we can define that nothing should happen/we shouldn’t advancing unless a certain key
is pressed. We can Specify the respective keys
we want to allow as input, by providing them as elements
of a list
to the function.
keys = event.waitKeys(keyList=['space', 'escape'])
We also assign this function by convention to a variable, which would allow us to track the buttons participants have pressed and their reaction times.
While we are at it, let’s add a few more messages to our experiment
. One will be presented right after the welcome message and explain very generally what will happen in the experiment
. Another one will be presented at the end of the experiment and display a general “that’s it, thanks for taking part” message. The syntax for creating, draw
ing and presenting these message is identical to the one we just explored, we only need to change the text
(and assign them to new variables, of course).
Note: Something you’ll also frequently encounter is the psychopy.core.wait() function which can be used to present stimuli or text for a certain period of time
#===============
# Import modules
#===============
from psychopy import gui, core, visual, event, data # import psychopy modules/functions
import os # import os module
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory in terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press spacebar to continue.",
color='black', height=40)
# Define trial start text
start_message = visual.TextStim(win,
text="In this experiment you will rate different musical artists, snacks and animals on a scale from 1 to 7. Please press the spacebar to start.",
color='black', height=40)
# Define experiment end text
end_message = visual.TextStim(win,
text="You have reached the end of the experiment, thanks for participating.",
color='black', height=40)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# display start message
start_message.draw() # draw start message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
#======================
# End of the experiment
#======================
# Display end message
end_message.draw() # draw end message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
#print('Experiment ended' + data.getDateStr(format="%Y-%m-%d_%H_M"))
Let’s give it a try via python experiment.py
. If everything works/is set correctly you should see the GUI dialog box
and after clicking OK
, the text
we defined as a welcome message should appear next, followed by the start message and finally the end message. In all cases, the experiment
should only advance if you press spacebar
or quit when you press escape
.
Having this rough frame of our experiment
, it’s actually time to add the experiment
itself: the evaluation
of our artists
, snacks
and animals
.
trials
¶
Quick reminder: our experiment
should collect responses from participants
regarding our list
s of artists
, snacks
and animals
, specifically their respective rating
. Thus we need to add/implement two aspects in our experiment
: the presentation of stimuli and their rating/
Starting with the presentation of stimuli
, we will keep it simple for now and present them via text
. However, any ideas how we could begin working on this? That’s right: we need to define list
s with our stimuli!
artists = [‘Dvne’, ‘Mac Miller’, ‘Taylor Swift’, …]
We can already think about the next step:
Quite often experiments
shuffle
the order of stimuli
across participants to avoid sequence/order effects.
We will do the same and implement that via the numpy.random module, specifically its .shuffle() function
which will allow us to randomly shuffle
our previously created list
.
Caution: This will shuffle your list in place
, i.e make a copy of it beforehand, if you value the initial order.
rnd.shuffle(artists)
would then result in something like this:
print(artists)
[‘Mac Miller’, ‘Dvne’, ‘Taylor Swift’, …]
After that we could bring our
shuffle
dstimuli list
into the format required byPsychoPy
. Specifically, this refers to the definition ofexperiment trials
, i.e.trials
that will be presented during theexperiment
, including their properties (e.g.content
,order
,repetition
, etc.). InPsychoPy
this is achieved via the data.TrialHandler()function
for which we need to convert ourshuffle
dstimuli list
into alist
ofdictionaries
of the form“stimulus”: value
.
stim_order = []
for stim in movies: stim_order.append({‘stimulus’: stim})
stim_order
[{‘stimulus’:‘Interstellar’}, {‘stimulus’:‘Love Actually’}, {‘stimulus’:‘Forrest Gump’},…]
The result is then provided as
input
for the data.TrialHandler()function
.
With that we can simply
loop
over thetrials
in thetrials object
and during eachiteration
draw
andflip
the respectivevalue
of thedictionary key
“stimulus”
to present the stimuli of ourlist
“movies” one-by-one after one another.
for trial in trials: visual.TextStim(win, text=trial['stimulus'], bold=True, pos=[0, 30], height=40).draw()
But rebels that we are, we’re not going to do all that. Our stimuli
are already contained in a list
so Python makes it easy for us to iteratively present each stimulus
.
Enter the for loop
for artist in artists:
visual.TextStim(win, text=artist, bold=True, pos=[0, 30], height=40).draw()
Notice, that we are just providing our element
, i.e. the artist variable
as the text keyword argument
for the TextStim
function.
Additionally, we want to display the question “How much do you like the following?”
above the respective stimulus to remind participant about the task.
Within each iteration
of our for-loop
we will also allow participants to quit
the experiment
by pressing “escape”
via the event.getKeys()
function
.
#===============
# Import modules
#===============
from psychopy import gui, core, visual, event, data # import psychopy modules/functions
import os # import os module
import numpy.random as rnd # import random module from numpy
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory in terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press spacebar to continue.",
color='black', height=40)
# Define trial start text
start_message = visual.TextStim(win,
text="In this experiment you will rate different musical artists, snacks and animals on a scale from 1 to 7. Please press the spacebar to start.",
color='black', height=40)
# Define experiment end text
end_message = visual.TextStim(win,
text="You have reached the end of the experiment, thanks for participating.",
color='black', height=40)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# display start message
start_message.draw() # draw start message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
#==========================
# Define the trial sequence
#==========================
# Define a list of trials with their properties:
# define stimuli to be presented
artists = [
'Queen Adreena', 'Mac Miller', 'Nirvana', 'Álvaro Soler', 'Kanye West', 'Taylor Swift',
'BTS', 'SYML', 'Ed Sheeran', 'Betterov', 'Mark Medlock',
'Drake', 'NF', 'Lauv', 'Arctic Monkeys', 'Alfa Mist', 'Nepumuk',
'Reezy', 'Cat Burns', 'Aurora', 'Godspeed You! Black Emperor'
]
# randomize order of presented stimuli
rnd.shuffle(artists)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# display start message
start_message.draw() # draw start message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# Run through the trials, stimulus by stimulus
for artist in artists:
# display/draw task question to remind participants
visual.TextStim(win, text='How much do you like the following?', pos=[0, 90], italic=True).draw()
# display/draw respective stimulus within each iteration, notice how the stimulus is set "on the fly"
visual.TextStim(win, text=artist, bold=True, pos=[0, 30], height=40).draw()
win.flip()
core.wait(0.5) # specify that psychopy should wait for .5 secs, so people can read the stimulus
# if participants press `escape`, stop the experiment
if event.getKeys(['escape']):
core.quit()
#======================
# End of the experiment
#======================
# Display end message
end_message.draw() # draw end message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
Let’s give it a try via python experiment.py
. If everything works/is set correctly you should see the GUI dialog box
and after clicking OK
, the text
we defined as a welcome message should appear next, followed by the start message. Subsequently, you should see all of our stimuli
one after another and the same question above them every trial
. Finally, you should see the end message.
While this is already great, the same thing as during our earlier tests happened: the text
, i.e. our stimuli
, is only very briefly shown on screen (although the core.wait(0.5) statement slowed that down a bit).
As we didn’t specify that we want to collect responses
the script is simply moving on to the next stimulus
. We could now use the event.waitKeys()
function as before and allow people to use the keys 1 - 7, but we instead will add a proper ratingscale
for each loop-iteration to our experiment
.
Input/output¶
PsychoPy
offers quite a bit of possible options to collect responses
: simple yes/no questions, rating scales, visual analog scales, voice recordings, etc. and store outputs (files
, different levels of detail, etc.).
Collecting responses¶
For the experiment
at hand a simple rating scale
yes, a Likert scale to make it Psychology, also recently know as “feeling integers”) should be sufficient. As with the other components we have explored so far, we need to implement/add this via two steps: defining
/creating
a rating scale
and draw
ing/presenting it.
We can easily define and tweak a rating scale via PsychoPy
’s visual.RatingScale() function
which allows us to set the range
of values
, labels
, size
, etc..
ratingScale = visual.RatingScale(win,
scale = None,
low = 1,
high = 7,
showAccept = True,
markerStart = 4,
labels = ['1 - Not at all', '7 - A lot'],
pos = [0, -80])
We then need to draw
it and indicate that we want to wait
until a rating
was conducted before we advance to the next trial
. Which is done by calling a while loop
that “asks” wether a response
has not been given yet. We don’t need to write this out, as the ratingScale
object already includes this functionaliyt via ratingScale.noResponse
.
while ratingScale.noResponse:
Additionally, we are going to display a small helpful message describing the rating
and make sure that the rating scale
is reset back to its default status
before the next trial
starts.
ratingScale.reset()
Even though participants
could already perform the rating
of the stimuli
, we don’t track and collect the respective responses
yet. These need to be obtained from the rating scale
before we reset
before the next trial
.
As indicated before visual.RatingScale()
creates an object
/class
/data type
with many inbuilt functions
, this includes .getRating()
and .getRT()
to collect the provided rating
and corresponding response time
:
rating = ratingScale.getRating()
rt = ratingScale.getRT()
We can then store both values
per trial
in the simplest way possible by first creating a dictionary
and adding (appending
) the values following each loop-iteration for each.
Importantly, this dictionary
has to be created outside
and before
our for-loop
, otherwise it would get overwritten in eacht iteration.
This would look something like this:
data_info = {
'stimulus':[],
'rating_rt':[],
'rating':[],
'trial_num':[]
}
Next we consecutively append
the collected values for each variable of interest to our dictionary
by providing the respective keys
:
# write trial data into dict
data_info['stimulus'].append(artist)
data_info['rating'].append(rating)
data_info['rating_rt'].append(rt)
data_info['trial_num'].append(trial_counter)
Note the level of indentation of these two code blocks
#===============
# Import modules
#===============
from psychopy import gui, core, visual, event, data # import psychopy modules/functions
import os # import os module
import numpy.random as rnd # import random module from numpy
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory in terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press spacebar to continue.",
color='black', height=40)
# Define trial start text
start_message = visual.TextStim(win,
text="In this experiment you will rate different musical artists, snacks and animals on a scale from 1 to 7. Please press the spacebar to start.",
color='black', height=40)
# Define experiment end text
end_message = visual.TextStim(win,
text="You have reached the end of the experiment, thanks for participating.",
color='black', height=40)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# display start message
start_message.draw() # draw start message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
#==========================
# Define the trial sequence
#==========================
# define stimuli to be presented
artists = [
'Queen Adreena', 'Mac Miller', 'Nirvana', 'Álvaro Soler', 'Kanye West', 'Taylor Swift',
'BTS', 'SYML', 'Ed Sheeran', 'Betterov', 'Mark Medlock',
'Drake', 'NF', 'Lauv', 'Arctic Monkeys', 'Alfa Mist', 'Nepumuk',
'Reezy', 'Cat Burns', 'Aurora', 'Godspeed You! Black Emperor'
]
# randomize order of presented stimuli
rnd.shuffle(artists)
#================================
# Define a rating scale
#================================
ratingScale = visual.RatingScale(win,
scale = None, # This makes sure there's no subdivision on the scale
low = 1, # This is the minimum value I want the scale to have
high = 7, # This is the maximum value of the scale
showAccept = True, # This shows the user's chosen value in a window below the scale
markerStart = 4, # This sets the rating scale to have its marker start on 5
labels = ['1 - Not at all', '7 - A lot'], # This creates the labels
pos = [0, -80]) # set the position of the rating scale within the window
# define a dictionary to save responses in
data_info = {
'stimulus':[],
'rating_rt':[],
'rating':[],
'trial_num':[]
}
# Run through the trials, stimulus by stimulus
for artist in artists:
ratingScale.reset()
while ratingScale.noResponse: # show & update until a response has been made
# display/draw task question to remind participants
visual.TextStim(win, text='How much do you like the following?', pos=[0, 90], italic=True).draw()
# display/draw respective stimulus within each iteration, notice how the stimulus is set "on the fly"
visual.TextStim(win, text=artist, bold=True, pos=[0, 30], height=40).draw()
# display/draw help message regarding rating scale
visual.TextStim(win, text='(Move the marker along the line and click "enter" to indicate your rating from 1 to 7.)',
pos=[0,-200], height=14).draw()
# display/draw the rating scale
ratingScale.draw()
# after everything is drawn, flip it to the front screen
win.flip()
# if participants press `escape`, stop the experiment
if event.getKeys(['escape']):
core.quit()
# get the current rating
rating = ratingScale.getRating()
# get the response time of the current rating
rt = ratingScale.getRT()
# write trial data into dict
data_info['stimulus'].append(artist)
data_info['rating'].append(rating)
data_info['rating_rt'].append(rt)
# if participants press `escape`, stop the experiment
if event.getKeys(['escape']):
core.quit()
#======================
# End of the experiment
#======================
# Display end message
end_message.draw() # draw end message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
Let’s give it a try via python experiment.py
. If everything works/is set correctly you should see the GUI dialog box
and after clicking OK
, the text
we defined as a welcome message should appear next, followed by the start message. Subsequently, you should see all of our stimuli
one after another and the same question above them every trial
, this time not advancing until you provided a rating
. Finally, you should see the end message.
Our experiment
works as expected but we don’t get any output files
. The reason is once again simple: we actually didn’t tell PsychoPy
that we would like to save
our data
to an output file
. Our data
is uo until now only stored “internally” in our data_info
dictionary.
Saving data¶
Because things work like a charm and we’re using Python
-based tools, we can make use of the Pandas
module, which is great for data handling.
To demonstrate let’s quickly simulate some data. Don’t worry about the following syntax, we’ll just create some lists to populate our dictionary with.
import numpy.random as rnd
# define a dictionary to save responses in
data_info = {
'stimulus':[],
'rating_rt':[],
'rating':[]
}
# Let's generate some lists to add to our dictionary
artists = [
'Queen Adreena', 'Mac Miller', 'Nirvana', 'Álvaro Soler', 'Kanye West', 'Taylor Swift',
'BTS', 'SYML', 'Ed Sheeran', 'Betterov', 'Mark Medlock',
'Drake', 'NF', 'Lauv', 'Arctic Monkeys', 'Alfa Mist', 'Nepumuk',
'Reezy', 'Cat Burns', 'Aurora', 'Godspeed You! Black Emperor'
]
rand_ratings = rnd.choice(range(0,7), 21)
rand_rating_rts = rnd.default_rng().uniform(low=0, high=7, size=21)
for idx, artist in enumerate(artists):
data_info['stimulus'].append(artist)
data_info['rating'].append(rand_ratings[idx])
data_info['rating_rt'].append(rand_rating_rts[idx])
Now we can check out the dictionary which we’ve populated with our simulated data
data_info
{'stimulus': ['Queen Adreena',
'Mac Miller',
'Nirvana',
'Álvaro Soler',
'Kanye West',
'Taylor Swift',
'BTS',
'SYML',
'Ed Sheeran',
'Betterov',
'Mark Medlock',
'Drake',
'NF',
'Lauv',
'Arctic Monkeys',
'Alfa Mist',
'Nepumuk',
'Reezy',
'Cat Burns',
'Aurora',
'Godspeed You! Black Emperor'],
'rating_rt': [3.2772442481808426,
0.656837546073622,
5.305188597615572,
3.7109678433592337,
4.5633028309846795,
2.7402306429166403,
2.0620945387131058,
2.140196245882997,
0.1112713456839105,
2.3765529324792376,
3.2461004928422685,
1.3123651689988316,
1.4878501154676924,
3.8586029309303678,
4.628222461212935,
2.965178742457285,
0.07369319191654722,
2.6676446688048783,
2.6950791124336906,
0.3516332742629801,
2.416779223648422],
'rating': [4, 6, 4, 4, 4, 2, 0, 0, 5, 4, 5, 0, 2, 3, 1, 1, 4, 4, 5, 4, 1]}
And from that we can create a pandas DataFrame
object via the DataFrame.from_dict
function. Which not only looks way better, but allows for powerful data handling tools to be applied to our data directly. Check-out the Pandas for Data-Science learning path to explore this point further.
import pandas as pd
df_data = pd.DataFrame.from_dict(data_info)
df_data
stimulus | rating_rt | rating | |
---|---|---|---|
0 | Queen Adreena | 3.277244 | 4 |
1 | Mac Miller | 0.656838 | 6 |
2 | Nirvana | 5.305189 | 4 |
3 | Álvaro Soler | 3.710968 | 4 |
4 | Kanye West | 4.563303 | 4 |
5 | Taylor Swift | 2.740231 | 2 |
6 | BTS | 2.062095 | 0 |
7 | SYML | 2.140196 | 0 |
8 | Ed Sheeran | 0.111271 | 5 |
9 | Betterov | 2.376553 | 4 |
10 | Mark Medlock | 3.246100 | 5 |
11 | Drake | 1.312365 | 0 |
12 | NF | 1.487850 | 2 |
13 | Lauv | 3.858603 | 3 |
14 | Arctic Monkeys | 4.628222 | 1 |
15 | Alfa Mist | 2.965179 | 1 |
16 | Nepumuk | 0.073693 | 4 |
17 | Reezy | 2.667645 | 4 |
18 | Cat Burns | 2.695079 | 5 |
19 | Aurora | 0.351633 | 4 |
20 | Godspeed You! Black Emperor | 2.416779 | 1 |
We can also simply add lists or elements to our DataFrame as we would do with a dictionary:
# Also add data from experiment info gui into DataFrame
exp_name = ['pfp_2022']*21
df_data['exp_name'] = exp_name
df_data
stimulus | rating_rt | rating | exp_name | |
---|---|---|---|---|
0 | Queen Adreena | 3.277244 | 4 | pfp_2022 |
1 | Mac Miller | 0.656838 | 6 | pfp_2022 |
2 | Nirvana | 5.305189 | 4 | pfp_2022 |
3 | Álvaro Soler | 3.710968 | 4 | pfp_2022 |
4 | Kanye West | 4.563303 | 4 | pfp_2022 |
5 | Taylor Swift | 2.740231 | 2 | pfp_2022 |
6 | BTS | 2.062095 | 0 | pfp_2022 |
7 | SYML | 2.140196 | 0 | pfp_2022 |
8 | Ed Sheeran | 0.111271 | 5 | pfp_2022 |
9 | Betterov | 2.376553 | 4 | pfp_2022 |
10 | Mark Medlock | 3.246100 | 5 | pfp_2022 |
11 | Drake | 1.312365 | 0 | pfp_2022 |
12 | NF | 1.487850 | 2 | pfp_2022 |
13 | Lauv | 3.858603 | 3 | pfp_2022 |
14 | Arctic Monkeys | 4.628222 | 1 | pfp_2022 |
15 | Alfa Mist | 2.965179 | 1 | pfp_2022 |
16 | Nepumuk | 0.073693 | 4 | pfp_2022 |
17 | Reezy | 2.667645 | 4 | pfp_2022 |
18 | Cat Burns | 2.695079 | 5 | pfp_2022 |
19 | Aurora | 0.351633 | 4 | pfp_2022 |
20 | Godspeed You! Black Emperor | 2.416779 | 1 | pfp_2022 |
if we provoide only a single value
for a column key
pandas does this work for us:
# Also add data from experiment info gui into DataFrame
df_data['subject'] = 'sub_01'
df_data
stimulus | rating_rt | rating | exp_name | subject | |
---|---|---|---|---|---|
0 | Queen Adreena | 3.277244 | 4 | pfp_2022 | sub_01 |
1 | Mac Miller | 0.656838 | 6 | pfp_2022 | sub_01 |
2 | Nirvana | 5.305189 | 4 | pfp_2022 | sub_01 |
3 | Álvaro Soler | 3.710968 | 4 | pfp_2022 | sub_01 |
4 | Kanye West | 4.563303 | 4 | pfp_2022 | sub_01 |
5 | Taylor Swift | 2.740231 | 2 | pfp_2022 | sub_01 |
6 | BTS | 2.062095 | 0 | pfp_2022 | sub_01 |
7 | SYML | 2.140196 | 0 | pfp_2022 | sub_01 |
8 | Ed Sheeran | 0.111271 | 5 | pfp_2022 | sub_01 |
9 | Betterov | 2.376553 | 4 | pfp_2022 | sub_01 |
10 | Mark Medlock | 3.246100 | 5 | pfp_2022 | sub_01 |
11 | Drake | 1.312365 | 0 | pfp_2022 | sub_01 |
12 | NF | 1.487850 | 2 | pfp_2022 | sub_01 |
13 | Lauv | 3.858603 | 3 | pfp_2022 | sub_01 |
14 | Arctic Monkeys | 4.628222 | 1 | pfp_2022 | sub_01 |
15 | Alfa Mist | 2.965179 | 1 | pfp_2022 | sub_01 |
16 | Nepumuk | 0.073693 | 4 | pfp_2022 | sub_01 |
17 | Reezy | 2.667645 | 4 | pfp_2022 | sub_01 |
18 | Cat Burns | 2.695079 | 5 | pfp_2022 | sub_01 |
19 | Aurora | 0.351633 | 4 | pfp_2022 | sub_01 |
20 | Godspeed You! Black Emperor | 2.416779 | 1 | pfp_2022 | sub_01 |
But let’s get to actually saving our data. For that we use the .to_csv()
method of the DataFrame
object. We have to further specify
where our file should be stored/how it’s called
the file extension, e.g. .csv/.txt etc.
a delimiter (to separate chunks of data)
We can also specify a lot more, such as the exclusion of a nummeric index column that pandas otherwise adds to our file via:
index=False
Check this tutorial for more info on what else this function can do
Let’s try that
First define a name for your file.
Importantly this name will be handeld in respect to your current working directory, so if you are in your Desktop
directory right now, it will be created there. Therefore if you’re calling your file /Path/to/Desktop/file_name you will get an error, as such an folder doesn’t exist starting from your desktop. I.e. python would look for /Path/to/Desktop/Path/to/Desktop/file_name.
data_fname = 'test_simulated data'
Now we use the .to_csv
function to save our data, provided our specification
df_data.to_csv(data_fname + '.csv', sep = ',', encoding='utf-8', index=False)
And a quick ls
shows us that we’ve just created a new file in our current working directory
ls
create_dialogue_box.py psyhopy_while_loop.py
display_instructions.py rating_artists.py
experiments.md rating_for_loop.py
flanker_task_no_comments.py rating_pictures.py
intro_psychopy_I.ipynb rating_task.py
psychopy_for_loop.py 'test_simulated data.csv'
Now let’s put it all together
#===============
# Import modules
#===============
from psychopy import gui, core, visual, event, data # import psychopy modules/functions
import os # import os module
import pandas as pd
import numpy.random as rnd # import random module from numpy
#========================================
# Create GUI dialog box for user input
#========================================
# Get subject name, age, handedness and other information through a dialog box
exp_info = {
'participant': '', # participant name as string
'age': '', # age name as string
'left-handed':False, # handedness as boolean
'like this course':('yes', 'no') # course feedback as tuple
}
# define name of experiment
exp_name = 'pfp_2022' # set experiment name
# create GUI dialog box from dictionary
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)
# If 'Cancel' is pressed, quit experiment
if dlg.OK == False:
core.quit()
#=================================================
# Data storage: basic information, filename & path
#=================================================
# get current working directory for easier creation/saving of files
cwd = os.getcwd()
# Get date and time
exp_info['date'] = data.getDateStr(format="%Y-%m-%d_%H_%M") # get date and time via data module
exp_info['exp_name'] = exp_name # add experiment name to info dict
# Check if set data path exists, if not create it
data_path = os.path.join(cwd, exp_info['exp_name'], exp_info['participant'])
if not os.path.isdir(data_path):
os.makedirs(data_path) # create subject directory
print(data_path) # print subject_directory in terminal
# Create a unique filename for the experiment data
data_fname = exp_info['participant'] + '_' + exp_info['date'] # create initial file name from participant ID and date/time
data_fname = os.path.join(data_path, data_fname) # add path from GUI dialog box
#===============================
# Creation of window and messages
#===============================
# Open a window
win = visual.Window(size=(800,600), color='gray', units='pix', fullscr=False) # set size, background color, etc. of window
# Define experiment start text
welcome_message = visual.TextStim(win,
text="Welcome to the experiment. Please press spacebar to continue.",
color='black', height=40)
# Define trial start text
start_message = visual.TextStim(win,
text="In this experiment you will rate different musical artists, snacks and animals on a scale from 1 to 7. Please press the spacebar to start.",
color='black', height=40)
# Define experiment end text
end_message = visual.TextStim(win,
text="You have reached the end of the experiment, thanks for participating.",
color='black', height=40)
#=====================
# Start the experiment
#=====================
# display welcome message
welcome_message.draw() # draw welcome message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
# display start message
start_message.draw() # draw start message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
#==========================
# Define the trial sequence
#==========================
# define stimuli to be presented
artists = [
'Queen Adreena', 'Mac Miller', 'Nirvana', 'Álvaro Soler', 'Kanye West', 'Taylor Swift',
'BTS', 'SYML', 'Ed Sheeran', 'Betterov', 'Mark Medlock',
'Drake', 'NF', 'Lauv', 'Arctic Monkeys', 'Alfa Mist', 'Nepumuk',
'Reezy', 'Cat Burns', 'Aurora', 'Godspeed You! Black Emperor'
]
# randomize order of presented stimuli
rnd.shuffle(artists)
#================================
# Define a rating scale
#================================
ratingScale = visual.RatingScale(win,
scale = None, # This makes sure there's no subdivision on the scale
low = 1, # This is the minimum value I want the scale to have
high = 7, # This is the maximum value of the scale
showAccept = True, # This shows the user's chosen value in a window below the scale
markerStart = 4, # This sets the rating scale to have its marker start on 5
labels = ['1 - Not at all', '7 - A lot'], # This creates the labels
pos = [0, -80]) # set the position of the rating scale within the window
trial_counter = 0
# define a dictionary to save responses in
data_info = {
'stimulus':[],
'rating_rt':[],
'rating':[],
'trial_num':[]
}
# Run through the trials, stimulus by stimulus
for artist in artists:
ratingScale.reset()
while ratingScale.noResponse: # show & update until a response has been made
# display/draw task question to remind participants
visual.TextStim(win, text='How much do you like the following?', pos=[0, 90], italic=True).draw()
# display/draw respective stimulus within each iteration, notice how the stimulus is set "on the fly"
visual.TextStim(win, text=artist, bold=True, pos=[0, 30], height=40).draw()
# display/draw help message regarding rating scale
visual.TextStim(win, text='(Move the marker along the line and click "enter" to indicate your rating from 1 to 7.)',
pos=[0,-200], height=14).draw()
# display/draw the rating scale
ratingScale.draw()
# after everything is drawn, flip it to the front screen
win.flip()
# if participants press `escape`, stop the experiment
if event.getKeys(['escape']):
core.quit()
# get the current rating
rating = ratingScale.getRating()
# get the response time of the current rating
rt = ratingScale.getRT()
# write trial data into dict
data_info['stimulus'].append(artist)
data_info['rating'].append(rating)
data_info['rating_rt'].append(rt)
data_info['trial_num'].append(trial_counter)
# if participants press `escape`, stop the experiment
if event.getKeys(['escape']):
core.quit()
trial_counter += 1
#======================
# End of the experiment
#======================
# Display end message
end_message.draw() # draw end message to buffer screen
win.flip() # flip it to the front screen
keys = event.waitKeys(keyList=['space', 'escape']) # wait for spacebar key press before advancing or quit when escape is pressed
print('Experiment ended' + data.getDateStr(format="%Y-%m-%d_%H_M"))
#======================
# Save your Data
#======================
# create a "DataFrame" for easier file handling
df_data = pd.DataFrame.from_dict(data_info)
# Also add data from experiment info gui into DataFrame
df_data['subject'] = exp_info['participant']
df_data['date'] = exp_info['date']
df_data['left_handed'] = exp_info['left-handed']
df_data['exp_name'] = exp_info['exp_name']
# save Dataframe as .csv
print(df_data)
df_data.to_csv(data_fname + '.csv', sep = ',', encoding='utf-8', index=False)
If you’d now try it again, everything should work as before but after finishing the experiment
you should see a new file
within the indicated data path
containing all information
stored about the experiment
: trials
, stimuli
, responses
, reaction times
, etc. .
A very simple experiment¶
Believe it or not folks, with that we already created our first working PsychoPy
experiment
. Using only a small amount of, very readable, code
, we can obtain ratings
for our stimuli
. Obviously, this is a very simple experiment
but nevertheless a good start, showcasing a lot of the core things you should know to start using PsychoPy
for experiments
. All the things addressed here are usually also part of much more complex experiments
, as well as build their basis. For an overview on how to add to this (e.g. our snacks and animals list) head over to the slides or the rating_artists.py
script. For an overview on how the same framweork can be used for e.g. a reaction-time task look into the [flanker_task.py
] script. If you consider using psychopy for your experiments (which you definetly should!) it’s always a good idea to check google for possible implementations of experiment ideas close to whatever you’re trying to accomplish.
Outro¶
As usually: awesome work folks! The transition from basic Python
aspects to applied together is definitely no cake-walk, especially when simultaneously switching to python scripts
from jupyter notebooks
but you did a great job!
Feel free to explore the other aspects of the coures website for further insights on what you can do with Python.
Thank so much for sticking with us throughout this!
Homework assignment #6¶
Your sixth and last homework assignment will entail the generation of a “new” psychopy experiment
You are free to copy any of the existing experiments, in the experiments folder or that were presented in the lesson
but at least change the instruction, stimuli and file/experiment name
you get full marks, if the experiment runs without crashes, collects participant input in a gui dialogue, logs responses and outputs a .csv file which contains the collected data
Bonus: Use a different stimulus modality
Save it as a .py file and e-mail it to ernst@psych.uni-frankfurt.de
Deadline: 23/12/2023, 11:59 PM EST