Control from R

Contents:

Installation

Download mPsy with the sample experiment in R from here.

In the following the R environment is used only to define stimulus parameters and the timeline of the experiment.

Presentation of stimuli requires a working Python installation as described in Tutorials. Once Python is installed, extract the downloaded mPsy package into an empty directory.

The bridge between mPsy and R uses two R packages: rjson and ttpRequest. Make sure you have them.

Sample experiment

In this experiment R is used to remotely control the experiment run in Python.

The following Python program “listens” to R commands and it presents no stimuli.

from stimuli import *

def main():
    fullscreen = False
    kwargs = {}
    if not fullscreen:
        kwargs = dict(width=1024,height=768)
    win = ExpWindow(fullscreen,**kwargs)

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

    # tell mPsy about remote control
    import web
    remote = web.Remote(5000)
    win.set_remote(remote)
    # capture event by the remote control
    win.set_logger(remote.event)
    
    # run experiment
    # mPsy takes Trials from the begining of the trials list
    # if the trials list is empty mPsy waits
    # ESCAPE stops the experiment at any time
    run()

if __name__ == "__main__":
    main()

The remote control is enabled by:

    # tell mPsy about remote control
    import web
    remote = web.Remote(5000)
    win.set_remote(remote)
    # capture event by the remote control
    win.set_logger(remote.event)

When the Python experiment is already running, we use R to define what stimuli appear on the screen and when. mPsy will take care of stimulus presentation and interaction with the subject (i.e., it will collect responses and send this information back to R).

Here is the code in R:

source('mPsy.r')

# EXPERIMENT
win <- mpsy_info()
cx <- round(win[1]/2)
cy <- round(win[2]/2)

# prepare grating configuration
p_lattice =  Params(ap_fs=0.0, ap_sigma=130.0, ap_edge=4.0,
                    gamma=pi/2, theta=0.0, dx=60.0, r=1.5,
                    dot_size=100.0, dot_sigma=0.3, dot_fs=0.0,
                    dot_edge=10.0, dot_c=0.4, dot_phi=0.2)

# setup presentation screens
stimuli_dots = list( DotLattice(c(cx,cy),p_lattice) )

# this time response screen is different for every trial
# we make a function that will generate proper response choice
response_screen = function(t1,t2,t3,t4)
{
    list( Pill(c(cx+100,cy+100),Params(th=t1,R=55,d=5)),
          Pill(c(cx+100,cy-100),Params(th=t2,R=55,d=5)),
          Pill(c(cx-100,cy-100),Params(th=t3,R=55,d=5)),
          Pill(c(cx-100,cy+100),Params(th=t4,R=55,d=5)) )
}

theta = function(p)
{
    t = as.double(p['theta'])
    g = as.double(p['gamma'])
    bvec = r*exp(1i*g)
    c(t,t+g,t+angle(bvec+1),t+angle(bvec-1))
}

welcome_line  = list( Text(c(cx,cy-50),Params(msg='Press SPACE when ready.')) )
farewell_line = list( Text(c(cx,cy-50),Params(msg='Thank you for participation!')) )
dot = list( Dot(c(cx,cy),Params(c=0.5)) )

keys_none     = list()
keys_space    = list(key.SPACE)
keys_response = list(key.NUM_1,key.NUM_2,key.NUM_3)
# key names are listed at http://www.pyglet.org/doc/api/pyglet.window.key-module.html

# add a message screen, wait until button press (duration=0 means wait)
trials <- list( Trial('Start',welcome_line,0,keys_space) )

# prepare 6*5 ratios
ratios = c()
for (i in 1:1) ratios = c(ratios,c(1.0,1.1,1.2,1.3,1.4))
# and shuffle them
ratios = sample(ratios)
gamma = rads(90.0)

# add three Trials each consisting of a Fixation, Stimulus and a Response screen
for (r in ratios)
{
    # generate a new lattice, rotated by a random angle
    th = 2*pi*runif(1)
    new_params = copy_params(p_lattice,r=r,gamma=gamma,theta=th)
    lattice = list( DotLattice(c(cx,cy),new_params) )
    # create response button that correspond to presented stimuli
    options = theta(new_params)
    # shuffle them
    options = sample(options)
    responses = do.call(response_screen, as.list(options))
    # add the trial
    trial = list( Trial('Fixation',       dot, 1000, keys_none),
                  Trial('Lattices',   lattice,  300, keys_space),
                  Trial('Blank'   ,    list(),  300, keys_none),
                  Trial('Response', responses,    0, keys_response, mouse=1) )
    trials = c(trials,trial)
}

trials <- c( trials, list(Trial('End',farewell_line,0,keys_none)) )

# tell mPsy about trials
set_trials(trials)

# watch the events for 10 seconds
for (i in 1:10)
{
    Sys.sleep(1.0)
    events = mpsy_events()
    if (length(events) > 0)
        for (j in 1:length(events))
        {
            ev = events[[j]]
            if (ev[[1]]=='TRIAL' && ev[[3]]['name']=='Lattices')
            {
                th = ev[[3]][['stimuli']][[1]]['params'][[1]]['theta']
                cat(sprintf("Event: \"%s\" triggered at %0.3f, theta = %0.2f\n",
                            ev[[1]],ev[[2]],th))
            }
            else
                cat(sprintf("Event: \"%s\" triggered at %0.3f, response '%s'\n",
                            ev[[1]],ev[[2]],toJSON(ev[[4]])))
        }
}

To run this demonstration, download the file and run exp_tut_webremote.py by

  • either double-clicking the file name
  • or executing python exp_tut_webremote.py in the Terminal.

A successfully executed Python program will create an empty black screen or an empty black window if the fullscreen option is off.

At this point you can go to R, change the working directory to the one containing the file dots_tut3.r and run source('dots_tut3.r').

The syntax of the sample experiment in R is modelled after the sample example in Python. See main Tutorials for an explanation of how to control the parameters of stimuli and the timeline.

Recording responses

The events triggered by mPsy or the subject are described in the main Python tutorial Control from Python.

To catch events in R we regularly check for messages accumulating in the Python presentation program:

# watch the events for 10 seconds
for (i in 1:10)
{
    Sys.sleep(1.0)
    events = mpsy_events()
    if (length(events) > 0)
        for (j in 1:length(events))
        {
            ev = events[[j]]
            if (ev[[1]]=='TRIAL' && ev[[3]]['name']=='Lattices')
            {
                th = ev[[3]][['stimuli']][[1]]['params'][[1]]['theta']
                cat(sprintf("Event: \"%s\" triggered at %0.3f, theta = %0.2f\n",
                            ev[[1]],ev[[2]],th))
            }
            else
                cat(sprintf("Event: \"%s\" triggered at %0.3f, response '%s'\n",
                            ev[[1]],ev[[2]],toJSON(ev[[4]])))
        }
}