Table of Contents

How to cite use of mgl in a manuscript

Please see here.

How to end the experiment

In general, the easiest way to code the stimulus is to have it continue indefinitely until the scanner stops scanning. After the scan is finished and you want to stop the stimulus you hit the ESC key. This way you never have the stimulus stop before the scanner does, and it doesn't hurt to keep having the stimulus go past the end of the scan.

If instead you want to only collect a specific number of blocks of trials and stop, then you would set:

task{1}.numBlocks = 4;

say, to run for 4 blocks of trials and then stop. Or if you want to run for a specific number of trials and stop, then you can do:

task{1}.numTrials = 17;

which would run for 17 trials and stop. These variables default to inf so that the experiment only stops when the user hits ESC.

How to make a grating

You may want to look at the code mglTestTex. Here is sample code. (For mgl version 1.5, use makeGrating and makeGaussian instead of mglMakeGrating and mglMakeGaussian)

mglOpen;
mglVisualAngleCoordinates(57,[16 12]);
mglClearScreen(0.5);
grating = mglMakeGrating(10,10,1.5,45,0);
gaussian = mglMakeGaussian(10,10,1,1); 
gabor = 255*(grating.*gaussian+1)/2;
tex = mglCreateTexture(gabor);
mglBltTexture(tex,[0 0]);
mglFlush;

Here are some variations. Run the code above first for these example.

Here is a sharp bordered patch (set to be 2 std of the gaussian)

mglClearScreen(0.5);
gabor = 255*(grating.*(gaussian>exp(-2))+1)/2;
tex = mglCreateTexture(gabor);
mglBltTexture(tex,[0 0]);
mglFlush;

A square wave grating:

mglClearScreen(0.5);
gabor = 255*(sign(grating).*gaussian+1)/2;
tex = mglCreateTexture(gabor);
mglBltTexture(tex,[0 0]);
mglFlush;

A plaid

mglClearScreen(0.5);
grating1 = mglMakeGrating(10,10,1.5,45,0);
grating2 = mglMakeGrating(10,10,1.5,135,0);
gabor = 255*((grating1/2+grating2/2).*gaussian+1)/2;
tex = mglCreateTexture(gabor);
mglBltTexture(tex,[0 0]);
mglFlush;

A checkerboard:

mglClearScreen(0.5);
grating1 = mglMakeGrating(10,10,1.5,45,0);
grating2 = mglMakeGrating(10,10,1.5,135,0);
gabor = 255*(sign(grating1/2+grating2/2).*gaussian+1)/2;
tex = mglCreateTexture(gabor);
mglBltTexture(tex,[0 0]);
mglFlush;

Flickering checkerboard:

mglClearScreen(0.5);
grating1 = mglMakeGrating(10,10,1.5,45,0);
grating2 = mglMakeGrating(10,10,1.5,135,0);
gabor1 = 255*(sign(grating1/2+grating2/2).*gaussian+1)/2;
gabor2 = 255*(sign(-grating1/2-grating2/2).*gaussian+1)/2;
tex1 = mglCreateTexture(gabor1);
tex2 = mglCreateTexture(gabor2);
for i = 1:5
  mglBltTexture(tex1,[0 0]);
  mglFlush;
  mglWaitSecs(0.1)
  mglBltTexture(tex2,[0 0]);
  mglFlush;
  mglWaitSecs(0.1)
end

Drifting grating

phases = [0:10:359];
for i = 1:length(phases)
  grating = mglMakeGrating(10,10,1.5,135,phases(i));
  gabor = 255*(grating.*gaussian+1)/2;
  tex(i) = mglCreateTexture(gabor);
end
for i = 1:length(phases)*5
  mglClearScreen(0.5);
  mglBltTexture(tex(rem(i,length(phases))+1),[0 0]);
  mglFlush;
end

How to use 10-bit contrast

If you want to use 10-bits so as to be able to display finer contrast gradations, you need to remap the usual 8-bit contrast steps (0:255) into a subset of the larger 10-bit (1024) contrast table. This can be done using a piece of code called setGammaTable that can be included in your code as a subfunction (written by JG and FP and found at ~shani/matlab/MGLexpts/setGammaTable.m), but there are some details to be careful of.

First, you will want to ‘reserve’ some colors that you will want to be able to use and leave unaffected by the resetting of the gamma table. This allows you to show, for example, a high-contrast fixation cross at the same time that you’re showing a low-contrast target. If you don’t reserve some colors, you won’t be able to have anything high-contrast at the same time as you use the 10-bit capacity. See example code taskTemplateContrast10bit.m where four colors are saved, and a low-contrast target is shown (written by SO and found at mgl/task/taskTemplateContrast10bit.m).

How to run a dual task

If you want to run two tasks at once, for example, an RSVP task at fixation and a detection task in the periphery, you will create two tasks and call one from within the other. You should construct it so one task (e.g. detection) is the main task and the other task (e.g. fixation-RSVP) is the subsidiary task.

The subsidiary task needs to be constructed like a regular task, with its own initialization and callbacks, but without the updateTask loop. It will be updated from within the main task.

The main task will be constructed as usual, but an extra line will appear to set the subsidiary task and to update it. For example, to set the fixation task as the subsidiary, you will add a line in the main task like this:

task{2} = fixationTask(myscreen);

Then, the update loop of the main task will look like this:

phaseNum = 1;
while (phaseNum <= length(task{1})) && ~myscreen.userHitEsc
 % update the task
 [task{1} myscreen phaseNum] = updateTask(task{1},myscreen,phaseNum);
 [task{2} myscreen] = updateTask(task{2},myscreen,1);
 % flip screen
 myscreen = tickScreen(myscreen,task);
end
% if we got here, we are at the end of the experiment
myscreen = endTask(myscreen,task);

The key to getting this to work is to control the timing. One way to do this is to have the main task set some variables which tell the subsidiary task whether or not to run. In order to do this, have the stimulus variable set as a global variable in both tasks. Set two stimulus subfields as flags, e.g. stimulus.startSubsidiary and stimulus.endSubsidiary, in order to control the subsidiary task. Then have the subsidiary task check the status of these flags, and start or stop accordingly.

In order to get the subsidiary task to start and stop when the appropriate flags are set, you will need to do the following:

Set the first segment of the subsidiary task to have infinite length. That makes the subsidiary wait in the first segment until the main task calls it. When the main task wants to start the subsidiary task, it will set the stimulus.startSubsidary flag to 1, and this will cause the subsidiary to jump to the next segment as follows:

In the screenUpdate callback of the subsidiary task, have a loop that checks to see whether the stimulus.startSubsidiary flag is set to 1. (This should be done in screenUpdate so that it can check all the time.) Have an if-loop that tells the task to skip ahead to the next segment as soon as the flag == 1. (It’s a good idea to reset the flag to 0):

if(stimulus.startSubsidiary == 1)
 stimulus.startSubsidiary = 0;
 task = jumpSegment(task);
end

When you’re ready to end the subsidiary task, have the main task set the stimulus.endSubsidiary flag to 1, and have the following if-loop in the subsidiary’s screenUpdate callback:

if(stimulus.endSubsidiary == 1)
 stimulus.endSubsidiary = 0;
 task = jumpSegment(task,inf);
end

The ‘inf’ argument in the jumpSegment function call tells the task to jump to the end of all the segments and start the next trial. This puts the subsidiary task back into the state of being in the infinite first segment, waiting for the start flag to be reset to 1 by the main task.

Example code can be found in taskTemplateDualMain.m and taskTemplateDualSubsidiary.m

How to calibrate the monitor

See instructions on using moncalib.

How to run an experiment with the same random sequence as a previous one

You can do this by calling initScreen with the randstate of the previous experiment

initScreen([],previousMyscreen.randstate);

This will insure that all the parameters, randVars and segment times are generated with the same random sequence as the previous experiment.

Alternatively, you can run both experiments starting with the same randstate (which can be an integer value). For example

initScreen([],11);

Will run the experiment with exactly the same randomization sequence every time.

How to change the back tick code

Different scanners and set up have different keyboard codes for the backtick sent out at each volume (e.g., back tick is '`' at NYU and '\' at Columbia). If you are waiting for or synching to the back tick you need to change the backtick character (actually the keyboard code of such character) that task is waiting for. This can be done by setting the backtick character in mglEditScreenParams. If instead you would like to change this programmatically, you can do the following after calling initScreen.

% 1. choose a back tick character
backTickCharacter = '\';
 
% 2. get its keyboard code
backTickCode = mglCharToKeycode({backTickCharacter})
 
% 3. save the code into the myscreen structure during its initialization
myscreen.keyboard.backtick = backTickCode;

The keyboard code is set also for other basic characters in mgl (i.e., the 'escape' key that exit a task loop, the 'return' key and the number keys used to get button responses). All these keys are stored in the field 'keyboard' of 'myscreen' (e.g., myscreen.keyboard.num). A good way to check the codes of several keys is to use the program mglTestKeys.m, which returns the code for characters typed in the matlab prompt.

How to set up a National Instruments card digital I/O card

You can use a National Instruments card for digital I/O with mgl by doing the following:

  1. Download NI-DAQmx Base. You may need to make a free account with NI.
  2. Make sure that the device (NI USB-6501) has up-to-date firmware, by running FWUpdate (included in Ni-DAQmx Base/bin)
  3. Restart matlab if you have already run readDigPort (the program has to reinit the driver)
  4. Documentation is installed and should be available from:
    file:///Applications/National%20Instruments/NI-DAQmx%20Base/documentation/docsource/daqmxbasecfuncindex.htm
  5. More information is available in the discussion of digital I/O.

You can set up to read digial pulses by setting digin using mglEditScreenParams.

How to open a small mgl-window in the current display

If you would like to test your code and stimuli by opening a small mgl window while keeping the rest of your desktop available you can do two things:

  1. Set up a 'debug' display using mglEditScreenParams
    1. Type mglEditScreenParams in matlab.
    2. Click to Add a new display on the first dialog box that appears.
    3. Hit Ok to edit that display.
    4. After that set its displayName to “debug”.
    5. Set screenNumber to 0 (this will set to a mgl window instead of a full screen). Set screenWidth and screenHeight to your choice of size for the window e.g. 400 and 200. Set the displayPos to the desired x and y position where the window should display.
    6. You can test the settings by hitting the “Test screen params” button.
    7. Now when running your experimental code for debugging open the screen using the command: initScreen('debug'); This will automatically load the settings for the windowed display and the stimuli will appear in a small mgl window.
    8. If you want to use the full screen settings, call initScreen without any arguments, or set the displayName for your full screen context in mglEditScreenParams to something like 'experiment' and open the screen in your program with initScreen('experiment');
  1. Manually code settings
    1. set the myscreen.screenNumer field to 0;
    2. choose the size and position of the mgl window in the current display
    3. set those in the variable myscreen
    4. init the screen using initScreen(myscreen);
  % get the resolution of main display
  res = mglResolution(1);
 
  % set the parameters of the mgl window
  myscreen.screenNumber = 0;  % tell mgl to open a small window instead of a full-screen one
  myscreen.screenWidth  = 200;% choose the horizontal size of the window
  myscreen.screenHeight = 150;% choose the vertical size of the window
 
  % now set the position of the window relative to the size of the main display.
  myscreen.displayPos = [res.screenWidth - myscreen.screenWidth res.screenHeight - myscreen.screenHeight];
 
  % init screen and open up the window
  myscreen = initScreen(myscreen);
 

How to add a new variable after you have run your experiment

Sometimes after you run an experiment you may want to add a new variable. For instance, you may have run a signal detection experiment and you want to add a new variable which tracks whether each trial was a hit, miss, false alarm or correct reject. Or maybe you want to mark certain trials as junk trials (for example when the subject does not response or there is an error trial) so that they will not be included with the good data in a deconvolution analysis (you should always still include these trials in your design, you just can later ignore the computed responses for these junk trials). If you add a new variable, then you can do deconvolution from the MLR menu item based on the new computed variable.

For example, you may want to mark a few trials in an experiment with different orientation stimuli as being junk. So, for example you start out with a variable with three orientation settings.

task{1}.parameters.orientation = [0 90 180];

After you run the experiment, you have different trials with different orientations:

>> e = getTaskParameters('090217_stim02');
>> e{1}(1).parameter.orientation
  [90 0 180 90 180 0 0 180 90 180 90 0]

Say you know the trial 3 and 7 are trials that you want to avoid in your deconvolution analysis. Then you should set them to have some dummy value, say -1

 >> newOrient = e{1}(1).parameter.orientation;
 >> newOrient(3) = -1;
 >> newOrient(7) = -1;
 >> newOrient
 [90 0 -1 90 180 0 -1 180 90 180 90 0];

And you can save this new variable back to your stimfile:

>> addCalculatedVar('newOrient',newOrient,'090217_stim02','taskNum=1','phaseNum=1')
(addCalculatedVar) Saving variable newOrient into 090217_stim02.mat for taskNum=1 phaseNum=1

This will change your stimfile to include the changed variable, you can check that it worked:

>> e = getTaskParameters('090217_stim02');
>> e{1}(1).randVars

ans = 

    newOrient: [90 0 -1 90 180 0 -1 180 90 180 90 0];

Now when you do your deconvolution analysis, you should be able to deconvolution based on the variable 'newOrient' and you will get an extra trial type that is the deconvolved responses for all the trials that you set to be -1. If these are junk trials then you can just ignore that response.

See also here for more info.

How to save info into a trace for keeping fine time resolution data

You may want to save information at a fine time resolution (and not just for each trial). If you just need to save a value that changes for each trial, you should use a calculated var, however sometimes it is necessary to save information that may change during a trial for later access. To do this, you can save the variable into a “trace”. This is in fact how mgl saves all task info. The trace is stored into a special field in the myscreen variable and you can later reconstruct a time record of what happened. To do this, you first need to add the trace to your task variable (do this after you call initScreen but before calling initTask).

[task{2}{1} myscreen] = addTraces(task{2}{1},myscreen,'someData');

The above example is for a task with two tasks and one phase, but substitute whatever task variable you have. The name of the trace ('someData' in the example above) should be something that won't interfere with other task variable fields so pick something distinct. A field task[2}{1}.someDataTrace will be added. This will carry the number of the trace which is used for adding data.

Then when you want to save new data (and this can be done at anytime - in updateScreenCallback or startSegmentCallback, etc. You do the following

myscreen = writeTrace(someData,task.someDataTrace,myscreen);

Where someData is a scalar value. Note that if the value someData is the same as the last value saved, nothing will actually be saved (to save memory). Only changes in the value are recorded and the trace is later reconstructed. If you need to have the data stored, set force:

myscreen = writeTrace(someData,task.someDataTrace,myscreen,1);

Note that we every call to writeTrace the current volume number and exact time are recorded.

When the stimulus program has been run, you can recover a trace of your variable, by creating the traces

myscreen = makeTraces(myscreen);

Then there will be a field 'traces' added which will contain all the traces. The trace you want to look at is the one encoded in the row defined as task.someDataTrace. The time points are defined by the field myscreen.time. So you can plot the value by doing:

plot(myscreen.time,myscreen.traces(task.someDataTrace,:);

How to save data into a subject specific directory

See subject ID.

How to display images / textures faster

The easiest way to create a texture and display is something like this:

mglOpen;
mglClearScreen
mglScreenCoordinates
texture = mglCreateTexture(round(rand(100,100,3)*255));
mglBltTexture(texture,[mglGetParam('screenWidth')/2 mglGetParam('screenHeight')/2]);
mglFlush;
mglDeleteTexture(texture);

Note that this can be slow due to mglCreateTexture needing to convert the matlab matrix into the texture format that OpenGL expects. One way to get around this is to precompute your textures in an initialization routine (for example if you have a small set of images you need to display). And then when you want to display you just call mglBltTexture which is very fast. Another is that you may be able to use a 1D texture (like if you just want to show a grating). See mglTestTex for examples of these strategies.

But, you may need to create images every screen refresh (for example, if you are showing some random noise stimulus which updates every frame refresh). To do this, you might want to try to format your matlab array in exactly the format that OpenGL expects and pass that to mglCreateTexture. The format is unsigned byte RGBA x imageWidth x imageHeight:

mglOpen;
mglClearScreen
mglScreenCoordinates
r = uint8(floor(rand(3,100,100)*256));
r(4,:,:) = 255;
texture = mglCreateTexture(r);
mglBltTexture(texture,[mglGetParam('screenWidth')/2 mglGetParam('screenHeight')/2]);
mglFlush;
mglDeleteTexture(texture);

Note that uint8 specifies unsigned byte format and that the color values for each pixel are the first dimension (rather than the last dimension). The fourth dimension is the alpha channel which in this case is set to 255 so that there is no alpha blending. This can speed things up considerably. For a largish image, say 600×600 mglCreateTexture will take (on a fast machine) under 2ms with this version, but could be 10s of ms with the regular version above. Remember also, to always delete textures once you are done using them.

Another trick that you might be able to use is to use openGL to scale an image rather than scaling it yourself. OpenGl has different modes of image scaling, for example linear interpolation or nearest neighbor. We default to using linear interpolation, but if you want nearest-neighbor, you can set that when your call mglCreateTexture:

mglOpen;
mglClearScreen
mglScreenCoordinates
r = uint8(floor(rand(3,100,100)*256));
r(4,:,:) = 255;
texture = mglCreateTexture(r,[],0,{'GL_TEXTURE_MAG_FILTER','GL_NEAREST'});
mglBltTexture(texture,[mglGetParam('screenWidth')/2 mglGetParam('screenHeight')/2 800 800]);
mglFlush;
mglDeleteTexture(texture);

There is also a way to just change the data buffer that a texture refers to without using mglCreateTexture multiple times. See help on mglBindTexture and mglTestTexFast.

Another trick that can be used for grating stimuli is to note that they are actually one dimensional and that you don't need to compute a full 2D texture. Instead you can make a 1D texture and let BltTexture repeat that sinusoid in the other space dimension. See mglTestTex for examples of this.

How to add a fixation task

We often use a fixation task which is a 2AFC fixation dimming task in which the fixation cross dims twice and the subject has to respond with either a button press on 1 or 2 to indicate which interval was dimmer. This is run on a staircase so that the difficulty is adjusted throughout a scan to maintain vigilance and attention level. To add this task to your code requires only a few lines of code. First, you need to initialize the fix task:

[fixTask myscreen] = fixStairInitTask(myscreen);

Then in your display loop, make sure to update the fix task. Note that in any call that requires sending the task variable, you should send both the main task and fixTask by putting them into a cell array - see the call to tickScreen below:

% while the main task still has a phase to complete
while (phaseNum <= length(task)) && ~myscreen.userHitEsc
  % update main task
  [task myscreen phaseNum] = updateTask(task,myscreen,phaseNum);
  % update the fixation task
  [fixTask myscreen] = updateTask(fixTask,myscreen,1);
  % flip screen
  myscreen = tickScreen(myscreen,{fixTask task});
end

After this, you just want to make sure that in the call to endTask you send the fixTask so that the subjects performance gets saved correctly:

myscreen = endTask(myscreen,{task fixTask});

Note that you will not have to draw any fixation crosses in your own code since this will be handled by the fixTask. For examples of code that use the fixation task you can look at mglRetinotopy or testExperiment. Also, there is a task which can be used to train subjects on the task (makes the fixation cross really big with words describing what to do):

fixStairTest

Also, if you want to change parameters of the fixation task, for example the timing of the intervals, the size of the cross, etc., you can set fields in the global fixStimulus before calling fixStairInitTask - for example:

global fixStimulus
fixStimulus.diskSize = 5;
fixStimulus.fixWidth = 10;
fixStimulus.fixLineWidth = 8;
fixStimulus.stimTime = 2;
fixStimulus.interTime = 1;
fixStimulus.responseTime = 5;

You should check the code of fixStairInitTask for a full listing of parameters you can set in this way.

How to make an exact experiment length

You may want to have your experiment take a fixed amount of time - even though the individual trials have randomized lengths. For instance, you may want to run an experiment in the scanner for a certain number of volumes but have randomized inter-trial intervals. To do this, you can simply set seglenPrecompute in your task variable to true:

task{1}.seglenPrecompute = 1;

Then make sure that you have set either numTrials or numBlocks:

task[1}.numTrials = 15;

For example, try running the test experiment

taskTemplateSeglenPrecompute

And you will see a message like this

(initTask) Computing 10 trials with average length 6.500000
(initTask:seglenPrecompute) Total length: 64.43 Desired length: 65.00 Diff: -0.57

Note that there is a slight difference between the desired length and the actual length. What seglenPrecompute is doing is that it computes a randomized set of trial lengths which should come out close to the average length but due to random variation will not be the right length. So, it then computes a new trial length and chooses a random trial. If replacing that random trial with the new trial gets the total closer to the desired length it exchanges the old trial for the new trial (it also does this with some small probability regardless of whether it makes things better or worse so as not to get stuck in a local minimum). After a while it will get a sequence that is close to the desired length (which is the average length of trial x number of trials). But, only to within some tolerance. If you want it more exact, set:

task{1}.seglenPrecomputeSettings.idealDiffFromIdeal = 0.1;

If you make it too strict, it might not be able to find a good sequence - you might need to increase the number of trials it tries to replace:

task{1}.seglenPrecomputeSettings.maxTries = 1000;

The code computes the average length of trial. This computation can be a bit complicated if you have multiple segments that have randomized trial lengths. In particular, if you have more than 2 randomized trial segments before a synchToVol then we have not yet implemented a computation that can compute the average length. If this is your case, you can either implement it (and let us know so we can include your solution) or just pass your average trial length:

task{1}.seglenPrecomputeSettings.averageLen = 15;

The seglenPrecompute feature can handle what happens when you synchToVol. But it needs then to know how long it takes to acquire each volume (often - but not necessarily - your TR). So you set that in the framePeriod field:

task{1}.seglenPrecomputeSettings.framePeriod = 1.5;

You can try it by running the test program. The test program requires 25 volumes to run, so test it by doing the following:

mglSimulateRun(1.5,25,5);
taskTemplateSeglenPrecompute(2);

This will print out information about what trials have been computed:

(initTask) Computing 5 trials with average length 7.500000
(initTask:seglenPrecompute) Total length: 37.50 Desired length: 37.50 Diff: 0.00
(initTask:seglenPrecompute) 25 volumes needed
(initTask:seglenPrecompute) trialLen: 6.00 freq: 0.40 (2/5, 0.33 expected)
(initTask:seglenPrecompute) trialLen: 7.50 freq: 0.20 (1/5, 0.33 expected)
(initTask:seglenPrecompute) trialLen: 9.00 freq: 0.40 (2/5, 0.33 expected)

Note that the expected frequencies and the actual frequencies don't match. That's ok. For so few trials it shouldn't and that is not what we are trying to match anyway. We are just trying to match the overall length of the task.

You can also set it up so that a segment has a set of durations that it can run for with associated probabilities and get trial sequence with a fixed length:

task{1}.segdur{3} = [1 3 8];
task{1}.segprob{3} = [0.2 0.5 0.3];

This then makes segment 3 have durations either 1, 3 or 8 seconds long. The probabilities for each duration is then 0.2, 0.5 and 0.3 respectively. Note that if you omit segprob then the probabilities will be equal for each duration. you can test how this works by running:

taskTemplateSeglenPrecompute(3);

You should the following:

(initTask) Computing 20 trials with average length 12.800000
(initTask:seglenPrecompute) Total length: 256.00 Desired length: 256.00 Diff: 0.00
(initTask:seglenPrecompute) trialLen: 10.00 freq: 0.20 (4/20, 0.20 expected)
(initTask:seglenPrecompute) trialLen: 12.00 freq: 0.50 (10/20, 0.50 expected)
(initTask:seglenPrecompute) trialLen: 16.00 freq: 0.30 (6/20, 0.30 expected)

Note that in the case above, the proportion of trials happens to come out just as expected, but this is not necessarily the case. The code is trying to keep the total experiment length the same. It is *not* trying to get the proportion of trials the same each run. In fact, randomly you should have some times in which you have more or less than the expected probability. For example, another run gives:

(initTask) Computing 20 trials with average length 12.800000
(initTask:seglenPrecompute) Total length: 256.00 Desired length: 256.00 Diff: 0.00
(initTask:seglenPrecompute) trialLen: 10.00 freq: 0.10 (2/20, 0.20 expected)
(initTask:seglenPrecompute) trialLen: 12.00 freq: 0.65 (13/20, 0.50 expected)
(initTask:seglenPrecompute) trialLen: 16.00 freq: 0.25 (5/20, 0.30 expected)

If you want to see even more (or less) verbose information about the precompute, you can set the verbose parameter:

task{1}.seglenPrecomputeSettings.verbose = 2;

How to get complete control of trial lengths

If you want to have complete control of the segment lengths of individual trials, you can precompute them yourselves. For example, you could precompute the four following trials:

task{1}.seglenPrecompute.seglen{1} = [1 10];
task{1}.seglenPrecompute.seglen{2} = [2 1];
task{1}.seglenPrecompute.seglen{3} = [3 4];
task{1}.seglenPrecompute.seglen{4} = [2 1 5];

And they will be played in that order. Note that if you run for more than four trials in this case, you will cycle back to the first trial in the list. Also note that not all the trials have to have the same number of segments. You can do whatever makes sense to you. Also, you can add any variable you like to be computed on a trial by trial basis. This allows you to associate it each seglen with some variable that has meaning to you. For example:

task{1}.seglenPrecompute.myVar = {'huh','wow','uhm','yowsa'};

Then the variable myVar shows up with the matching value in task.thistrial.myVar. To test this run:

taskTemplateSeglenPrecompute(4);