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

logo logo   @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 modules and functions 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 IDEs for this part of the course. Specifically, we will use VScode for this.

logo

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:

  1. Prerequisites
    1.1 Computing environment
    1.2 Folders & Files

  2. PsychoPy basics
    2.1 The general idea
    2.2 Input via dialog boxes
    2.3 Presenting instructions

  3. PsychoPy’s working principles
    3.1 drawing & flipping
    3.2 trials

  4. Input/output
    4.1 collecting responses
    4.2 saving data

  5. A 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

logo

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 Desktops.

%%bash
mkdir /path/to/your/Desktop/psychopy_environment

If you’d prefer to just stick with Python you could instead make use of the osmodule, 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 bashs 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 experiments 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:

logo

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.

logo

Make sure to check the PsychoPy website, documentation and forum.

What is PsychoPy¶

  • Psychology software in Python, i.e. a Python library, i.e. completely written in Python

  • 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 in experiment

  • use environments for experiments

  • 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 importing 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: importing modules and functions 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?

logo

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. logo

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 drawing something and showing it.

Drawing & flipping¶

In PsychoPy (and many other comparable software) there’s a big difference between drawing 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 drawn on the background/buffer screen, thus “invisible” and you need to flip it to the front screen to be “visible”.

logo

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).

Drawing something might take a long time, depending on the stimulus at hand, but flipping 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/drawn/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, drawing 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 lists 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 lists 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 shuffled stimuli list into the format required by PsychoPy. Specifically, this refers to the definition of experiment trials, i.e. trials that will be presented during the experiment, including their properties (e.g. content, order, repetition, etc.). In PsychoPy this is achieved via the data.TrialHandler() function for which we need to convert our shuffled stimuli list into a list of dictionaries 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 the trials in the trials object and during each iteration draw and flip the respective value of the dictionary key “stimulus” to present the stimuli of our list “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 drawing/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.

logo

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!

via GIPHY

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