Control from Python

Contents:

Sample experiment

We start with a simple example that uses Dot Lattice as the stimulus. Let us design a short experiment of only three trials. On every trial a fixation dot is presented first, followed by a stimulus and response.

A complete code of this experiment is:

from stimuli import *

fullscreen = True #False
win = ExpWindow(fullscreen)

cx, cy = win.width //2, win.height //2

# prepare lattice configuration
p_lattice =  Params(ap_fs=3.0, ap_sigma=100.0, ap_edge=4.0,
                    gamma=70.0, theta=10.0, dx=40.0, r=1.5,
                    dot_size=150.0, dot_sigma=0.25, dot_fs=0.0,
                    dot_edge=10.0, dot_c=0.4, dot_phi=0.2)

# setup presentation screens
stimuli_dots = [ DotLattice((cx,cy),p_lattice) ]

response_screen = [ Pill((cx-100,cy),Params(th=0.1,R=30,d=3)),
                    Pill((cx    ,cy),Params(th=0.5,R=30,d=3)),
                    Pill((cx+100,cy),Params(th=1.5,R=30,d=3)) ]

welcome_line  = [Text((cx,cy-50),Params(msg='Press SPACE when ready.'))]
farewell_line = [Text((cx,cy-50),Params(msg='Game over!'))]
response_line = [Text((cx,cy),Params(msg='Which was faster\nLeft or Right'))]
dot = [Dot((cx,cy),Params(c=0.5))]

# setup trials
trials = []

keys_none    = []
keys_default = [key.SPACE,key.C,key.N,key.LEFT,key.RIGHT]

# add a message screen, wait until button press (duration=0 means wait)
trials += [ Trial('Start',welcome_line,0,keys_default) ]
# duration 0 means wait for response

# add three Trials each consisting of a Fixation, Stimulus and a Response screem
for i in xrange(3):
    trials += [Trial('Fixation',             dot, 1000, keys_none),
               Trial('Lattices',    stimuli_dots,  500, keys_none),
               Trial('Response', response_screen,    0, keys_none, mouse=True)] 

trials += [Trial('End',farewell_line,0,keys_none)]

# tell mPsy about trials
win.set_trials(trials)

# run experiment
# mPsy takes Trials from the begining of the trials list
# if the trials list is eempty mPsy waits
# ESCAPE stops the experiment at any time
run()

You can run this experiment by downloading the file and executing it.

If you are new to Python, try creating and running the script from scratch. Paste the code from the box above into your text editor, save it as dots_tut1.py. To run it, double click the file name or execute it by typing python dots_tut1.py in the Terminal.

Code in detail

Now let us walk through this code. The first few lines initialize the library (line 1), initialize the window (lines 3-4) and create Python variables cx, cy that will hold the coordinates of screen center:

from stimuli import *

fullscreen = True #False
win = ExpWindow(fullscreen)

cx, cy = win.width //2, win.height //2

Next we create presentation screens:

# prepare lattice configuration
p_lattice =  Params(ap_fs=3.0, ap_sigma=100.0, ap_edge=4.0,
                    gamma=70.0, theta=10.0, dx=40.0, r=1.5,
                    dot_size=150.0, dot_sigma=0.25, dot_fs=0.0,
                    dot_edge=10.0, dot_c=0.4, dot_phi=0.2)

Recording responses

We will build on the previous example, by leaving the stimuli intact and adding the code that enables tracking various events in the experiment.

Events are generated by the mPsy presentation module or by the stimuli. Three types of events are generated by default:

  • TRIAL - when a trial starts
  • KEY - when a key is pressed
  • MOUSE - when a mouse button is pressed

To catch an event we need a function that will be called every time an event is triggered:

# create a function that will be triggered by events TRIAL, MOUSE, KEY ...
log = open('dots2.log','wb')

def record_event(event,time,trial,args):
    print >>log, event, time, trial.name, args

# tell mPsy about logging function
win.set_logger(record_event)

Function record_event() could have any name and its parameters could be called differently. But the number of parameters and their order must be the same. These parameters are:

  • event - text containing the name of the event
  • time - numerical value in seconds from experiment onset
  • trial - trial object: an important object in mPsy; it contains information about the stimuli and their parameters
  • args - contain properties of the event: - for KEY event the name of the key pressed, - for MOUSE event the coordinate and the button clicked.

When you execute this experiment, file dots2.log is created with the contents that look like this:

TRIAL 0.575153048996 Start []
KEY 1.31466873576 Start SPACE
TRIAL 1.3156867604 Fixation []
TRIAL 2.33855819112 Lattices []
TRIAL 3.86717378322 Response []
MOUSE 5.86993089944 Response [814, 453, 1]
TRIAL 5.87124100698 Fixation []
TRIAL 6.89199839131 Lattices []
TRIAL 8.42179287708 Response []
MOUSE 9.6018394316 Response [627, 428, 1]
TRIAL 9.60313044609 Fixation []
TRIAL 10.6309067569 Lattices []
TRIAL 12.1618943546 Response []
MOUSE 15.1811783249 Response [814, 456, 1]
TRIAL 15.1816048716 End []

Suppose we want to only keep the response. Then we change the record_event() function:

def record_event(event,time,trial,args):
    if event == 'MOUSE' and trial.name == 'Response':
        print >>log, 'RESP', args[0], args[1]

yielding this record:

RESP 814 453
RESP 627 428
RESP 814 456

(In the forthcoming tutorials we will compute whether the response is correct prior to logging.)

Stimulus randomization

To randomize stimulus parameters we can use Python lists. Two operations are needed for this: shuffling and concatenation.

The following code creates a list of values, which are then shuffled using Python function shuffle(). It is an example of shuffling “in-place” where we modify the original list rather than creating a new variable.

>> ratios = [1.0,1.1,1.2,1.3,1.4,1.5]
>> shuffle(ratios)
>> ratios
[1.0, 1.3, 1.4, 1.1, 1.2, 1.5]

To create a more complicated set of parameters we can concatenate list using the + operator or make multiple copies of a list with the * operator:

>> ratios = [1.0,1.1,1.2]
>> ratios + ratios
[1.0, 1.1, 1.2, 1.0, 1.1, 1.2]
>> ratios*3
[1.0, 1.1, 1.2, 1.0, 1.1, 1.2, 1.0, 1.1, 1.2]
>> [ratios,ratios]*2
[[1.0, 1.1, 1.2], [1.0, 1.1, 1.2], [1.0, 1.1, 1.2], [1.0, 1.1, 1.2]]

To create three blocks of shuffled trials:

trials = []
for i in xrange(4):
    ratios = [1.0,1.1,1.2]
    shuffle(ratios)
    trials.append(ratios)

Then

>> trials
[[1.0, 1.2, 1.1],[1.0, 1.1, 1.2],[1.2, 1.1, 1.0],[1.0, 1.2, 1.1]]

In list of blocks can be shuffled as well:

>> shuffle(trials)
>> trials
[[1.0, 1.1, 1.2], [1.0, 1.1, 1.2], [1.2, 1.0, 1.1], [1.0, 1.2, 1.1]]