How-Tos
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
Moncalib
To calibrate a monitor, you can use the program moncalib.m in the utils directory. It is set up to work with the PhotoResearch PR650 photometer/colorimeter (which the Lennie lab has) and a serial port adaptor (use the one from the Carrasco lab it is a white Keyspan USA-28 and says Carrasco Lab on it–the one that is in the bag with the photometer is a white translucent Keyspan USA-28X B and doesn't seem to work properly). The serial port interface for matlab is included in the mgl distribution but can also be found on the Mathworks website [1]. To use the Keyspan USA-28 adaptor you will need to download a driver from [2].
- Tricky–When using the automated calibration via the serial port, the program will ask you to turn on the PR650 and then press 'return' within 5 secs. You might not want to press 'return' right away, or you may get something like this on the photometer:
PR650 REMOTE MODE (XFER) s/w ver 1.02 CMD 51 NAK
This indicates that you pressed the return while the photometer is waiting for a transfer signal (not sure what it is), and hence entered the XFER mode. If you wait another 2 secs or so it will enter the control mode, now press 'return' you should see this:
PR650 REMOTE MODE (CTRL) s/w ver 1.19 CMD B
Basically there is about 2-3 secs time window you should press 'return' to get to this state.
- Tricky2–When doing the automated calibration, turn off screensavers and energysaver, otherwise the screen will go blank after a while and you'll be measuring luminance of blank sreens.
If you cannot install the serial port interface or don't want to automatically calibrate using the USB cable you can also use the program to run manually with any photometer by typing in the luminance measurements yourself.
The program moncalib will save a calibration file in the local directory. For you to use this calibration file, you can store it in one of two places. Either in your own program directory under a directory called displays:
./displays
Or you can store it in the general displays directory
mgl/task/displays
InitScreen should automatically find the correct table by checking your computer name and looking for the file in these two places. If you do not use the standard filename, or have multiple calibrations for the same computer (like if you have multiple monitors calibrated), you can use a specific file by setting myscreen.calibFilename
myscreen.calibFilename = 'mycalibrationfile.mat'; myscreen = initScreen(myscreen);
Note that the calibFilename can be a literal filename as in the above, or you can specify a portion of the name that will get matched in a file from the displays directory (e.g. computername_displayname would matcha any file in the displays directory that looks like *computername_displayname*.mat).
The name of the file usually created by moncalib will be:
xxxx_computername_yymmdd.mat
Where xxxx is a sequential number starting at 0001 and yymmdd is the date of the calibration. This stores a variable called calib which contains all the information about the calibration. You can quickly plot the data in calib by doing:
load 0001_stimulus-g5_LCD_061004 moncalib(calib);
The most important field of calib is the table field which holds the inverse lookup table to linearize the monitor.
10 bit gamma tables
The NVIDIA GeForce series of video cards have 10 bit gamma tables (these are the only ones we have tested):
- NVIDIA GeForce FX 6600 (In the G5 in the magnet room)
- NVIDIA GeForce FX 7300 GT (brownie Mike Landy's psychophysics room)
- NVIDIA GeForce FX ????? (Jackson the G5 in the psychophysics room)
ATI 10 bit cards:
- any Randeon card for desktop computers above series 7000 has 10-bits DAC resolution (laptop cards don't have it necessarely or drivers do not access it)
It is always the best to use the bit test in moncalib because some drivers do not allow 10-bit control on 10-bit DAC cards. You can also query the display card to see if it says that it supports a 10 bit gamma:
displayInfo = mglDescribeDisplays
Check the field gammaTableWidth to see if it is 10.
Calibration devices
Note that there are some commercially available devices to calibrate monitor screens which create color profiling information (e.g. [5] [6] [7]. We have tested one of these called Spyder2Pro which allows you to linearize the monitor output but found that is not yet suitable for psychophysics purposes. The calibration program crashes when you use the default settings to linearize the monitor (an email to the tech support confirmed this is a bug in their software). Using advanced settings it worked but it could only test luminance at 5 output levels. The linearization that it achieved was not accurate enough when tested with the PR650 (it looked like they are doing some sort of spline fit of the points and the luminance as a function of monitor output level looked like a wavy line around the ideal).
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 in the following way:
% 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
You can use a National Instruments card for digital I/O with mgl by doing the following:
- Download NI-DAQmx Base. You may need to make a free account with NI.
- Make sure that the device (NI USB-6501) has up-to-date firmware, by running FWUpdate (included in Ni-DAQmx Base/bin)
- Restart matlab if you have already run readDigPort (the program has to reinit the driver)
- Documentation is installed and should be available from:
file:///Applications/National%20Instruments/NI-DAQmx%20Base/documentation/docsource/daqmxbasecfuncindex.htm
You can set up to read digial pulses by setting digin using mglEditScreenParams.
How to open a small mgl-widow in the current display
The following code gives the basic steps necessary to open a small mgl window in the main display of a computer. This is useful when testing mgl code on a computer that does not have a secondary display. There are two simple steps necessary to achieve this:
- set the myscreen.screenNumer field to 0;
- choose the size and position of the mgl window in the current display
- init the screen.
% 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);
The previous lines should show a small white window in the upper right corner of the screen.