A quick overview

The task structure can be used to help code experiments, it is completely separate from the basic mgl libarary that is used to display to the screen (in that you do not have to use the task code to use the basic mgl functions).

The structure for these experiments involves three main variables:

myscreen: Holds information about the screen parameters like resolution, etc.
task: Holds info about the block/trial/segment structure of the experiment
stimulus: Holds structures associated with the stimulus.

To create and run an experiment, your program will do the following:

  1. Initialize the screen.
  2. Set up the task structure. The task structure holds information about the parameters you want to randomize over and the timing of your experiment.
  3. Initialize the stimulus. Here you will create all the necessary bitmaps or display structures that you will need to display your stimulus.
  4. Create callback functions. These functions will run at various times in the experiment like at the beginning of a trial or when the subject responds with a keypress or before each display refresh. They are the main way that you program how your stimulus will display and what to do when you get subject responses etc.
  5. Create a display loop. This is the part that actually runs your experiment. Essentially all you have to do is call updateTask which handles all the hard work of running your task.

The basic idea of how to set up your experiment with these structures requires defining some terms. Going from the largest organization down to the smallest:

  • Task: Task refers to the overall experiment. The task is the top level structure. It contains all the parameters that you are testing as well as the information about how the trials are to be run. A task might be the parameters for a set of trials in which you show different visual stimuli. Or a set of trials that run a psychophysical staircase. Note that in some cases you might have more than one task running at the same time. For example, if you are running a retinotopy scan, you may want to have the retinotopic stimuli as one task and a staircased fixation task as the second task.
  • Phases: Tasks may sometimes have more than one phase. For example you may want to show an adaptation stimulus for 30 seconds at the beginning of your experiment in one phase, and then go on to the next phase of the experiment in which you will have randomized trials.
  • Blocks: A block is a set of trials in which each combination of parameters is presented in one trial. The code takes care of properly randomizing your trials so that in each block of trials each stimulus type is presented once. (You can also choose not to randomize).
  • Trials: A single trial of an experiment.
  • Segments: Segments divide up the time in a trial. For example you may have one segment with a fixation cross, another segment where the stimulus is presented and a final segment where the subject responds. What each segment does, how many you have and how long they last are all up to you and define how a trial works.

A simple example experiment can be found in mgl/task:



The code for textExperiment is a good starting place for creating a new experiment since it contains all the essential elements for using these functions.

Let's start by briefly going through each one of the steps above in reference to the function testExperiment. Note that when you actually want to program your own task, you can either start by editing testExperiment.m or use the function taskTemplate.m (be sure to copy these to a new name). taskTemplate.m is an even more stripped down version of testExperiment.m that contains only the necessary essentials to start using the code (and everywhere there is a comment that begins with fix: you will need to make changes to customize for your experiment). There are also some more templates that can be used as starting places:

  • taskTemplateStaticStaircase: This is a task that implements a simple staircase task where the stimuli are static and don't need to be updated every screen refresh.
  • taskTemplateFlashingStaircase: This is a task that implements a simple staircase task where the stimuli are flashing and need to be updated every screen refresh.
  • taskTemplateReactionTime: A simple reaction time task that shows you how to get the most accurate reaction time.
  • taskTemplateContrast10bit: Shows you how to use the 10-bit capacity for fine contrast steps
  • taskTemplateDualMain: This is an example of the main task in a dual task pair, to show how to run dual tasks.
  • taskTemplateDualSubsidiary: This is an example of the subsidiary task in a dual task pair, to show how to run dual tasks.

Initialize the screen

This can be done very simply just by calling

% initalize the screen
myscreen = initScreen;

This call will handle opening up of the screen with appropriate parameters and setting the gamma table.

If you want to add specific parameters for your computer add a line like the following:

myscreen.screenParams{1} = {'yoyodyne.cns.nyu.edu',[],2,1280,1024,57,[31 23],60,1,1,1.8,'calibFilename.mat',[0 0]}; 
myscreen = initScreen(myscreen);

This will set parameters for your screen. The parameters in order are

  • computerName
  • displayName (optional–for computers with multiple displays like lcd and projector)
  • displayNumber
  • screenWidth (in pixels)
  • screenHeight (in pixels)
  • displayDistances (in cm)
  • displaySize (in cm)
  • framesPerSecond (in Hz)
  • autoCloseScreen (1 to close screen at end of experiment, 0 to leave it open)
  • saveData (1 to save data file, 0 not to save data file,n>1 saves a data file only if you exceed n number of volumes)
  • monitorGamma (The monitor gamma to correct for if you do not have a calibration file. Macs are supposed to have a gamma of 1.8)
  • calibFilename (the name of the calibration file–usually just the computer name–see below under moncalib)
  • flipHV (Whether to flip the screen horizontally and/or vertically–an array of length two 0=no flip, 1 = flip)

Setup the task structure

In the testExperiment, the task structure is a cell array that actually contains two separate tasks that will be run in the course of the experiment.

This sets the first task to be the fixation staircase task. If you don't want to use the fixation task then you can omit this part:

% set the first task to be the fixation staircase task
[task{1} myscreen] = fixStairInitTask(myscreen);

This is the first “phase” of our task. Not all tasks need to have different phases, but in this case we want the experiment to start with dots moving incoherently for 10 seconds and then we want trials to run in the next phase.

% set our task to have two phases. 
% one starts out with dots moving for incohrently for 10 seconds
task{2}{1}.waitForBacktick = 1;
task{2}{1}.seglen = 10;
task{2}{1}.numBlocks = 1;
task{2}{1}.parameter.dir = 0;
task{2}{1}.parameter.coherence = 0;

Each one of the fields in the task set the behavior of that phase of the task.

  • waitForBacktick=1: The task phase will only start running after we receive a keyboard backtick (`).
  • seglen = 10: The segment will run for 10 seconds.
  • numBlocks = 1: There will be one block of trials before we run on to the next phase of the task.
  • paramater.dir = 0: We set the parameter dir to have a value of 0.
  • parameter.coherence = 0: We set the parameter coherence to have a value of 0.

The next phase of the task will be the one that actually runs the trials.

% the second phase has 2 second bursts of directions, followed by  
% a top-up period of the same direction
task{2}{2}.segmin = [2 6];
task{2}{2}.segmax = [2 10];
task{2}{2}.parameter.dir = 0:60:360;
task{2}{2}.parameter.coherence = 1;
task{2}{2}.random = 1;

In this task, we have a block of trials in which we will show trials with different motion directions. You set what parameters you want to use in the “parameter” part of your task. Note that you can use any name for parameters that you like. Here we call them dir for direction and coherence for motion coherence. Note that we have only one value of motion coherence so all trials will be run with a motion coherence of 1.

task{2}{2}.parameter.dir = 0:60:360;
task{2}{2}.parameter.coherence = 1;

We also have to decide the order in which parameters will be presented in a block of trials. The default is to run them sequentially (in this case directions 0 then 60 then 120 etc). To randomize the order, we set:

task{2}{2}.random = 1;

Our trial will have two segments, a 2 second segment in which the stimulus is presented and a 6-10 second long intertrial interval:

task{2}{2}.segmin = [2 6];
task{2}{2}.segmax = [2 10];

The task code will automatically keep track of the variables in the parameter field, so that you can later access them to find out which direction of motion was shown on what trial. You will be able to do this by using the function getTaskParameters.

Initialize the stimulus.

The stimulus is kept in a global variable so that if the variable is very large, we don't incur overhead with passing it around all the time. If you want to have the stimulus variable saved at the end of the experiment, you can call the function initStimulus as below. Note that you do not need to call initStimulus if you do not want to save the stimulus structure.

% init the stimulus
global stimulus; 
myscreen = initStimulus('stimulus',myscreen);
stimulus = initDots(stimulus,myscreen);

The function initDots is specific for creating the dots stimulus for this test experiment, you will substitute your own function for creating your stimulus.

Create callback functions

Callbacks are the way that you control what happens on different portions of the trial and what gets drawn to the screen. A callback is simply a function that gets called at a specific time. You write the function and you let updateTask handle when that function needs to be called.

There are two required callbacks:

The first required callback that is used in this program is the one that gets called every time a segment starts.

function [task myscreen] = startSegmentCallback(task, myscreen)
global stimulus;
if (task.thistrial.thisseg == 1)
  stimulus.dots.coherence = task.thistrial.coherence;
  stimulus.dots.coherence = 0;
stimulus.dots.dir = task.thistrial.dir;

What it does is it looks in the “thistrial” structure for what segment we are on, if we are not in segment one (i.e. the intertrial interval) it sets the motion coherence to 0, otherwise it sets it to whatever the parameter coherence is set to (defined in the task.parameter.coherence field). It also sets the direction of motion of the dots.

The second (and most important) callback is the one used to draw the stimulus to the screen:

function [task myscreen] = screenUpdateCallback(task, myscreen)
global stimulus 
stimulus = updateDots(stimulus,myscreen);

You can put your stimulus drawing routines in here. In this program, we simply clear the screen and draw the dots. This function gets called every display refresh.

Once these functions are defined in your file, you tell the programs to use these callbacks by using initTask to register the callbacks.

% initialize our task with only the two required callbacks
for phaseNum = 1:length(task{1})
  [task{1}{phaseNum} myscreen] = initTask(task{1}{phaseNum},myscreen,@startSegmentCallback,@screenUpdateCallback);

NOTE: It is necessary to register the callbacks in a specific order. The correct order for registering callbacks is: startSegmentCallback, screenUpdateCallback, getResponseCallback, startTrialCallback, endTrialCallback, startBlockCallback

It doesn't matter exactly how you name the callbacks, what matters is what order you call them in. If there is a callback that you are not defining, you can enter it as [] in the initTask call, or leave it out:

for example,

[task myscreen] = initTask(task,myscreen,@startSegment, @screenUpdate, @getResponse, [],[], @startBlock);


[task myscreen] = initTask(task,myscreen, @startSegment, @screenUpdate, @getResponse);

More details can be found in the callbacks section.

Create a display loop

Now that everything is setup to run your experiment all you need is a display loop that calls updateTask to run each one of the tasks that are being displayed. Then to flip the front and back buffer of the display to show your stimulus, you call tickScreen. This is the main loop in which your program is run.

phaseNum = 1;
while (phaseNum <= length(task{2})) && ~myscreen.userHitEsc
  % update the dots
  [task{2} myscreen phaseNum] = updateTask(task{2},myscreen,phaseNum);
  % update the fixation task
  [task{1} myscreen] = updateTask(task{1},myscreen,1);
  % flip screen
  myscreen = tickScreen(myscreen,task);

At the very end you end the task which will save out information about your experiment.

myscreen = endTask(myscreen,task);
software/mgl/taskreferenceoverview.txt · Last modified: 2009/05/01 13:32 (external edit)
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0