MGL
Mgl is a suite of mex/m files for displaying visual psychophysics stimuli and writing experimental programs in Matlab. Runs on Mac OS X (G4/5 and Intel 32 and Intel 64 bit OS Versions 10.5-10.7) Version 2.0. An older version 1.5 runs on Linux.
A quick overview
mgl is a set of matlab functions for dispalying full screen visual stimuli from matlab. It is based on OpenGL functions, but abstracts these into more simple functions that can be used to code various kinds of visual stimuli. It can be used on Mac OS X systems (a Windows beta is in the works and an older but fully usable version (1.5) exists for Linux).
- mgl contains a set of higher-level routines for helping you write stimulus programs which takes care of parameter randomization, timing of trials, synching with MRI scanners, collection of eye data etc.
- Stimuli can be displayed full screen or in a window (helpful for debugging on a system that only has one display).
- With a single command that specifies the distance to and size of a monitor, the coordinate system can be specified in degrees of visual angle, thus obviating the need to explicitly convert from the natural coordinate frame of psychophysics experiments into pixels.
- You can read and write digital signals with National Instruments digital I/O boards (see here).
- You can set the gamma table for example to linearize the output of your monitor.
- You can calibrate monitors automatically through serial connection to a PhotoResearch PR650, Minolta or Topcon photometer (see here).
- You can get accurate keyboard and mouse event information.
- You can play sounds.
- You can play quicktime movies.
- You can control multiple screens for example to control stereo displays.
- mgl is 64-bit compliant, except for the National Instruments code which relies on 32 bit libraries provided by the manufactures.
- mgl works with the latest versions of Mac OS X (10.5-10.7) and with the latest versions of Matlab (7.4-7.12) as of 10/21/2011. In general, we stay reasonably up to date with releases of Mac OS and Matlab. If we encounter any compatibility issues, that information will generally be posted here.
The best way to see whether it will be useful to you is to try out the mglTest programs and also the sample experiment testExperiment. A basic “hello world” program can be written in four lines:
% Open the screen, 0 specifies to draw in a window. % 1 would be full screen in the main display % 2 would be full screen in a secondary display, etc... >> mglOpen(0); % Select the coordinate frame for drawing % (e.g. for a monitor 57 cm away, which has width and height of 16 and 12 cm). >> mglVisualAngleCoordinates(57,[16 12]); % Draw the text in the center (i.e. at 0,0) >> mglTextDraw('Hello World!',[0 0]); % The above is drawn on the back-buffer of the double-buffered display % To make it show up you flush the display. % This function will wait till the end of the screen refresh >> mglFlush;
When finished, with displaying the stimuli, you simply close the screen:
>> mglClose;
Download
The latest version of mgl (2.0) can be retrieved using subversion:
svn checkout http://gru.brain.riken.jp/svn/mgl/trunk mgl
We recommend using subversion (see here for a quick primer) if at all possible. For Mac users, OS 10.5 comes with subversion preinstalled. You should probably have the latest Xcode installed. Otherwise, download directly from the subversion website. If you are really unable to get subversion, you can download mgl.tar.gz which contains a current version of the code. It should just open by clicking on it. If not, from a command line, you should be able to do:
gunzip mgl.tar.gz tar xfv mgl.tar
Once you have downloaded, see Initial setup for installation instructions.
Problems downloading with SVN
If you have problems downloading with svn because your institution has a proxy server, you might have to let subversion know about it. Have a look at download_with_proxy-server for details on how to set this up.
Older versions of mgl
You can access a stable read-only version of mgl (version 1.5) by using subversion:
svn checkout http://gru.brain.riken.jp/svn/mgl/branches/v1_5 mgl
We are no longer making updates to version 1.5
Initial setup
- Add the mgl directory to your Matlab path. In Matlab:
>> addpath(genpath('MYPATH/mgl'));where MYPATH should be replaced by the path to your version of mgl.
- Make sure to enable access for assistive devices so that you can use the function mglGetKeyEvent, mglGetMouseEvent, mglPostEvent, mglSimulateRun and mglEatKeys.
- There are a few functions (mglEditScreenParams and mglDoRetinotopy) that require the mrTools GUI functions to be installed. If you are not already using mrTools, you can download the following:
svn checkout http://cbi.nyu.edu/svn/mrTools/trunk/mrUtilities/MatlabUtilities mrToolsUtilities
and add that to your MATLAB path (with MYPATH replaced with the path to your version of the mrToolsUtilities):
>> addpath(genpath('MYPATH/mrToolsUtilities'));
Test your setup
You can see what functions are available by doing (in matlab):
>> help mgl
After downloading, you may wish to try the mglTest programs (e.g. mglTestDots, mglTestTex, etc….).
If these functions don't work or you are running on Linux (version 1.5 only), then you may need to recompile. This may especially be necessary if you are running an older version of matlab (we run Matlab version >= 7.3 on Mac OS >= 10.4.8). We have found that mex files created on Matlab 7.3 do not run on matlab 14.1 for instance (if you run -nojvm you will see that it complains that it cannot find a dynamic link library for the mex functions – if you run with the matlab desktop it will just crash the system). If this happens to you simply recompile and you should be good to go.
What is in the mgl distribution
- mgl/mgllib: The main distribution that has all functions for displaying to the screen.
- mgl/task: A set of higher level routines that set up a structure for running tasks and trials. Relies on functions in mgl/mgllib. You do no need to use any of these functions if you just want to use this library for drawing to the screen.
- mgl/utils: Various utility functions.
GNU General Public License
These programs are free to distribute under the GNU General Public License. See the file mgl/COPYING for details.
Recompiling mgl
If you are running into an obvious error like a segmentation fault, then you may want to recompile. The command to recompile is:
» mglMake(1);
If you run into any problems with mglMake, you may want to restart matlab and try again.
The mglMake command simply runs mex on all the .c files in the mgl/mgllib directory – you can do this by hand (e.g. mex mglPrivateOpen.c), if you prefer.
Note that this requires mex to be setup properly on your machine. On a Mac OS X machine, at a minimum you will need to have the apple developer tools installed (XCode) http://developer.apple.com/tools/. On linux, you will need a compatible version of gcc (older versions of gcc can be found here).
See here for more help on compiling.
If all else fails, how to get back control over the display?
If you can't do mglClose, you can always press:
option-command-esc
this will quit your matlab session as well.
Also, some of our test programs will run until you hit the <ESC> key (e.g. testExperiment), so it is worth trying that as well.
Can I get access to all OpenGL functions?
We have only exposed parts of the OpenGL functionality. If you need to dig deeper to code your stimulus, consider writing your own mex file. This will allow you to use the full functionality of the OpenGL library. To do this, you could start by modifying one of our mex functions (e.g. mglClearScreen.c) and add your own GL code to do what you want and compile.
Printing the wiki help pages
You can print out all the wiki help pages at once, by using this link.
Compatibility with latest MAC OS and Matlab versions
MGL is compatible with Mac OS 10.6 and Matlab 10.12 32 and 64bit. So far, everything compiles and works on Mac OS 10.7.1, but I have not yet tested the Eyelink and Digital I/O code.
Recompiling MGL
We (Apple developers) run the latest Mac OS (10.7.1 as of 10/21/2011) with the latest version of Matlab (7.12) or (Linux developers) Ubuntu 64-bit (Gutsy) and 32-bit (Feisty) with Matlab 7.4 and the binaries are created to run on these systems. As noted above, some older versions (notably Matlab 14.1) are not able to use these mex files and crash when you try to run mglOpen. If this happens, then all you need to do is recompile MGL using mglMake(1).
License manager timing glitch
The Matlab license manager checks every 30 seconds for the license. This can cause there to be an apparent frame glitch in your stimulus code, especially if you are using a network license (on our machines it can take ~200 ms to check for the license). The only known workaround to this is to run on a machine that has a local copy of the license. You can check this for yourself by seeing how long it takes to do screen refreshes:
mglOpen;
global MGL;
checkTime = 30*MGL.frameRate;
timeTaken = zeros(1,checkTime);
mglFlush;
for i = 1:checkTime
flushStart = mglGetSecs;
mglFlush;
timeTaken(i) = mglGetSecs(flushStart);
end
mglClose;
plot((1:checkTime)/MGL.frameRate,timeTaken);
zoom on;xlabel('seconds');ylabel('Frame refresh time (seconds)');
If you have the same problem, you should see one large spike in the time course like this:
This one shows it taking about 65 ms. Note that you may see small deviations in which one frame takes longer and then the following frame takes shorter than the mean. These are normal flucations persumably due to multi-tasking and other events that are intermittently taking up time. As long as these are shorter than a frame refresh interval minus the time it takes you to process the stimuli for your display, you will not drop any frames. Note that in the above code, if you change mglFlush to any other command, such as WaitSecs(1/frameRate);, you will still see the big spike for the license manager check–confirming that this has nothing to do with drawing to the screen.
Function not supported on Linux
Note that only version 1.5 supports linux, version 2.0 and beyond do not have linux support at the moment (actually could be supported with a little bit of work, if someone is interested in putting in the effort).
The list of funcitons not supported yet on linux for version 1.5 are:
- mglText
- mglTextDraw
- mglPlaySound
- mglInstallSound
- mglDescribeDisplays
- mglSwitchDisplay
- mglListener
If you want to use text under the linux operating system, you can use mglStrokeText.
Here is a more recent update from Jonas about the Linux version:
I am in the process of upgrading the Linux version of MGL to run under Ubuntu (64-bit and 32-bit) with NVIDIA and ATI graphics cards. Although the upgrade is still incomplete, most functions work equally well under Linux at this stage. Some differences that will remain between the platforms are listed below.
- no support for font-based text - this needs to be upgraded, I started looking into using FreeType for this which is widely available and would be easy to implement in the same texture-based way that the Mac code relies on. Care must be taken to ensure that the code is maximally portable across platforms, so it may be that some Mac-specific idiosyncracies need to be modified
- note that the stroke text works perfectly under Linux, so unless you are very enamoured with a specific font this is a perfectly usable workaround (though some symbols, eg %, are missing currently)
- some differences in the way you specify special keys, but this is generally to Linux' advantage - eg you can use ESC, BACKSPACE etc as names (relies on the XKeySymDef.h or sth like that)
- timing is in general more accurate on Linux, since the clock rate is much higher on modern systems (100-500Hz vs 60Hz on the Mac)
- no parallel port interface yet so you can't use Justin's code for calibration
- no sound - need to decide on a standard to use that is most widely available
- the syncing with OpenGL is idiosyncratic and depends on the graphics card. I have implemented this to use both the SGI video sync extension and the environment variable (both NVIDIA and ATI provide this option). The former is not supported by all OpenGL cards (though most modern ones) and can interact with the environment variable option, so I will make the latter the default, with an option to use the SGI extension when compiling only.
For the time being, only NVIDIA and ATI cards will be supported (because I only have access to those two machines).
- setting screen size and resolution requires the XRandR extension, which is supported on recent X distributions (Xorg 7.0 and later). Older X servers (eg Apple's X11) won't work.
- you need to reuse the X display or you run into memory problems, and the code for doing this needs to be checked for consistency. This is similar to the Mac window situation and relates to the uneasy relationship between Matlab and X. When you open a window, the MGL global variable will contain a window and display pointer that is used on subsequent calls; care must be taken not to clear the MGL variable between calls (once you do so, running MGL is likely to crash Matlab, even if you run clear all, which correctly closes the display).
Opening in a window with matlab desktop (Mac only)
This issue had been resolved, but if you find you are still having problems, here is what the issue was and how to resolve it:
On Mac OS X there seems to be some interaction with having mutliple threads in the workspace that causes working within a window (i.e. mglOpen(0) as opposed to fullscreen) to be unstable. The workaround for now is not to close the window once it is opened. This seems to work fairly well. When one is completely finished working with the window, one can call mglPrivateClose to close the window. But after that, calling mglOpen(0) is likely to crash.
mglOpen(0) works fine if running matlab -nojvm or -nodesktop.
Precise timing of key press events
This has been resolved. On Mac OS X if you want to get key press events, you can get them with a system (nanosecond precision) time stamp in mgl 2.0. For the previous version of mgl you could only get them with a resolution of 1/60 second. If you are willing to sit in a loop testing the keyboard, you can use mglGetKeys and get acceptable time resolution (your time resolution will depend on how fast you poll the keyboard status). On Linux, the time resolution depends on the kernel's hard-coded HZ setting, which is usually between 100-500; the value can be retrieved (at least on Ubuntu) by typing cat /boot/config-`uname -r` | grep '^CONFIG_HZ=' The time resolution is 1/HZ sec; so on the current developer system, with HZ=250, key presses can be timed with a resolution of 1/250=4msec.
64 bit Matlab on Mac OS X
The default version of mgl runs on 64 bit Matlab. An older version (v1.5) will only run on 32-bit Matlab.
If you have Matlab 7.9 or greater installed, matlab defaults to running in 64 bit mode, but you can set it to run in 32 bit mode - though with the current version of mgl you do not need to do this:
- Finder Go find the Matlab executable (somewhere like: /Applications/MATLAB_R2009b.app/bin/matlab) then click on it and “Get Info” by selecting and hitting “Command-I”. Then click “Start in 32-bit mode”.
- Command line Run using a flag: matlab -maci
Problems compiling
If you are trying to compile on Mac OS X and are running into problems, try the following. First, make sure that you have Xcode properly installed. Next make sure your software is up-to-date, we generally compile with the latest version of Mac OS X (10.6.6 as of this writing 2/4/2011) and the latest version of Matlab (7.11 64bit). Next, check that you have the SDK for 10.5 installed properly (this should be installed when you install Xcode):
/Developer/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/
If you only have the 10.6.sdk installed, you may be able to edit the “mexopts.sh” file in the mgl/mgllib directory to link to 10.6 instead of 10.5. Change the following lines appropriately to link to 10.6:
SDKROOT='/Developer/SDKs/MacOSX10.5.sdk' MACOSX_DEPLOYMENT_TARGET='10.5'
Note that mglMake should now work on Lion 10.7. To do so, we have added functionality to mglMake which swaps out the mexopts.sh file so that we use the new SDK 10.7.sdk, properly on Lion but not on older platforms.
SVN problems
If you have problems updating with SVN because you have changed files, you might be running into file or tree conflicts. See here for more info.
I have run into some unresolvable conflicts with the following error:
svn: GET of '/svn/mgl/!svn/bc/946/trunk/mgllib/mglEyelink/mglPrivateEyelinkReadEDF.c': Could not read response body: connection was closed by server (http://gru.brain.riken.jp)
You may try to svn checkout the whole repository again (remember to make a copy of your current repository and then copy back the files you have changed). It seems that this can be caused by Apache timing out for long updates, but that doesn't seem to be the reason why we are getting that (we have set out timeout very long and this happens for single files and always at the same place in certain revisions). I suspect that it is some problem with the Mac Apache svn/PHP module, but have not been able to figure it out. A workaround is to use svn+ssh for accessing the repository, but this is only for developers who have an account on our server gru.brain.riken.jp (email to justin if you think you need this). Also, if someone knows what the problem is and can provide a fix, let us know.
Vertical blanking using ATI Videocards (Radeon HD 5XXX)
An issue about syncing to the vertical sync using ATI videocards reported by Romesh Kumbhani and Gauri Wable:
The issue isn't an mgl specific issue, but is true with any program that uses OpenGL on recent Macs with ATI videocards (Radeon HD 5xxxx). We believe the problem is that video card driver isn't reporting back to OpenGL when a Vertical Blank has occurred. Obviously the video card is generating the VBLs (or the display would be desynchronized), but this information isn't getting back to OpenGL. Instead of OpenGL flushing the buffer and waiting for the VBL signal to occur, it's returning as soon as it can.
As far as we can tell people are having issues with mgl, psychophysics toolbox, expo, etc.. (all which rely on OpenGL working properly).
We have implemented one work around to this which is to spoof the vertical blank time by waiting the appropriate amount of time after mglFlush (this is implemented with the function mglFlushAndWait). You can set this for your display (if you are using the task code) by setting simulateVerticalBlank to true in mglEditScreenParams.
Another possible work around is to use an older video card (which probably calls on older drivers). We're currently using ATI Radeon HD 2600 XTs in our MacPros. We haven't tested the HD 3xxx series or the HD 4xxxx series, but the HD 5xxx series seems to be broken. We also haven't tested any NVidia cards.
My guess is that OpenGL issues the command to wait for the vertical refresh signal but returns immediately because the video card either isn't communicating back when the screen has refreshed, or the drivers have a bug; it's hard to tell which one it is.
We're using:
Model Name: Mac Pro Model Identifier: MacPro5,1 Processor Name: Quad-Core Intel Xeon Processor Speed: 2.8 GHz Memory: 6 GB System Version: Mac OS X 10.6.8 (10K549) Kernel Version: Darwin 10.8.0 Boot Mode: Normal Secure Virtual Memory: Enabled 64-bit Kernel and Extensions: Yes Graphics/Displays: Chipset Model: ATI Radeon HD 5770 Type: GPU Bus: PCIe PCIe Lane Width: x16 VRAM (Total): 1024 MB Vendor: ATI (0x1002) Device ID: 0x68b8 Revision ID: 0x0000 ROM Revision: 113-C0160C-155 EFI Driver Version: 01.00.436
We've been using either Eizos or Iiyama CRT monitors running 1280×960@120Hz for display our stimuli. They're connected to the DVI port of the 5770 via a DVI-VGA connector made by ATI.
When we use an HD 2600 XT card (specs below), we dont' have any issues.
ATI Radeon HD 2600 XT:
Chipset Model: ATI Radeon HD 2600 Type: GPU Bus: PCIe Slot: Slot-2 PCIe Lane Width: x16 VRAM (Total): 256 MB Vendor: ATI (0x1002) Device ID: 0x9588 Revision ID: 0x0000 ROM Revision: 113-B1480A-252 EFI Driver Version: 01.00.252 Displays: VGA Display: Resolution: 1280 x 960 @ 120 Hz Pixel Depth: 32-Bit Color (ARGB8888) Mirror: Off Online: Yes Rotation: Supported Display Connector: Status: No Display Connected
Also, we tried using an iMac i7 connected to an Iiyama CRT via Apple's mini-DisplayPort to VGA connector and had the same issues in mgl and expo (our software). It seems like cards that natively generate an analog signal seem to work great. Those that rely on DisplayPort→Analog converters (either internally on the card or externally via an adaptor) have issues.
Main screen functions
mglOpen: Opens the screen
usage: mglOpen(whichScreen, <screenWidth>, <screenHeight>, <frameRate>, <bitDepth>)
purpose: Opens an openGL window
| argument | value |
|---|---|
| whichScreen | 0=Window, 1=primary screen, 2=secondary screen, etc |
| <screenWidth> | [width] in pixels |
| <screenHeight> | [height] in pixels |
| <frameRate> | [frameRate] in hertz |
| <bitDepth> | [bitDepth] this is usually 32 bits |
Open last monitor in list with current window settings
mglOpen
Open with resolution 800×600 60Hz 32bit fullscreen
mglOpen(1,800,600,60,32);
Open in a window
mglOpen(0);
Note that mglOpen hides the mouse cursor as a default. If you want to show the mouse cursor, you should call mglDisplayCursor after opening the screen.
mglFlush: Flips front and back buffer
purpose: swap front and back buffer (waits for one frame tick)
usage: mglFlush
N.B. mglFlush has to be called after each operation that involves drawing or erasing from the currently open window
mglClose: Closes the screen
purpose: close OpenGL screen
usage: mglClose
Other screen functions
mglResolution: Get and set display resolution
availability: mgl 2.0
usage: mglResolution(<whichScreen>, <screenWidth>, <screenHeight>, <frameRate>, <bitDepth>)
purpose: Gets or sets the resolution of a display
usage: To get the resolution of the default display:
mglResolution
To get the resolution of display 1:
mglResolution(1)
To set the resolution of display 2 to 1440×900:
mglResolution(2,1440,900)
mglSwitchDisplay: Switch between multiple monitors
usage: mglSwitchDisplay(<displayID>)
purpose: If you are using multiple monitors to display stimuli (like for a dichoptic presentation), you can open up multiple displays using this function.
| argument | value |
|---|---|
| <displayID> | which display to switch to |
You can use this to open up two separate screens and control them independently. For example, say you have two monitors 1 and 2. You open the first in the usual way:
mglOpen(1)
Then you switch monitors so that you can open up the other one
mglSwitchDisplay mglOpen(2)
Now if you want draw to the first display, you can do
mglSwitchDisplay(1) mglClearScreen(0.5); mglFlush;
Similarly, to draw to the second display
mglSwitchDisplay(2); mglClearScreen(1); mglFlush;
You can check the status of all open displays with:
mglSwitchDisplay(-2);
If you want to close all displays at once, you can do:
mglSwitchDisplay(-1);
mglMoveWindow: Moves windows created by mglOpen(0)
usage: mglMoveWindow(leftPos,topPos)
purpose: Moves a window created by mglOpen(0)
| argument | value |
|---|---|
| leftPos | Left position of where the window will be moved to |
| topPos | Top position of where the window will be moved to |
mglOpen(0); mglMoveWindow(100,100);
mglDescribeDisplays: Get information about your monitor and computer system
usage: [displayInfo computerInfo] = mglDescribeDisplays()
purpose: Gets information about your displays (as an array of structs with values for each monitor). As well as a single struct with info about your computer.
mglFrameGrab: Frame grab to a matlab matrix
usage: mglFrameGrab(<frameRect>)
purpose: Does a frame grab of the current mgl screen and returns it as a matrix of dimensions widthxheightx3
| argument | value |
|---|---|
| frameRect | Optional argument that is a 1×4 array specifying a rectangular part of the frame to grab in the format [x y width height] |
mglOpen();
mglScreenCoordinates;
mglClearScreen([0 0 0]);
mglPoints2(mglGetParam('screenWidth')*rand(5000,1),mglGetParam('screenHeight')*rand(5000,1));
mglPolygon([0 0 mglGetParam('screenWidth') mglGetParam('screenWidth')],[mglGetParam('screenHeight')/3 mglGetParam('screenHeight')*2/3 mglGetParam('screenHeight')*2/3 mglGetParam('screenHeight')/3],0);
mglTextSet('Helvetica',32,[1 1 1]);
mglTextDraw('Frame Grab',[mglGetParam('screenWidth')/2 mglGetParam('screenHeight')/2]);
frame = mglFrameGrab;
imagesc(mean(frame,3)');colormap('gray')
mglFlush
Functions to adjust the coordinate frame
mglVisualAngleCoordinates: Visual angle coordinates
purpose: Sets view transformation to correspond to visual angles (in degrees) given size and distance of display. Display must be open and have valid width and height (defined in MGL variable)
usage: mglVisualAngleCoordinates(physicalDistance,physicalSize);
| argument | value |
|---|---|
| physicalDistance | [distance] in cm |
| physicalSize | [width height] in cm |
mglOpen mglVisualAngleCoordinates(57,[16 12]);
mglScreenCoordinates: Pixel coordinate frame
purpose: Set coordinate frame so that it is in pixels with 0,0 in the top left hand corrner
usage: mglScreenCoordinates()
mglTransform: Low-level function to adjust transforms
purpose: applies view transformations
usage: mglTransform(whichMatrix, whichTransform, [whichParameters])
| argument | value |
|---|---|
| whichMatrix | 'GL_MODELVIEW', 'GL_PROJECTION', or 'GL_TEXTURE' |
| whichTransform | 'glRotate', 'glTranslate', 'glScale','glMultMatrix', 'glFrustum', 'glOrtho','glLoadMatrix', 'glLoadIdentity', 'glPushMatrix','glPopMatrix', 'glDepthRange', or 'glViewport' |
| whichParameters | function-specific; see OpenGL documentation |
You can also specifiy one of GL_MODELVIEW, GL_PROJECTION, or GL_TEXTURE and a return variable current matrix values. If two outputs are specified, the result of the computation will be returned.
This function is usually not called directly, but called by mglVisualAngleCoordinates or mglScreenCoordinates to set the transforms
mglHFlip: Horizontally flip coordinates
purpose: flips coordinate frame horizontally, useful for when the display is viewed through a mirror
usage: mglHFlip()
mglOpen
mglVisualAngleCoordinates(57,[16 12]);
mglHFlip
mglTextSet('Helvetica',32,1,0,0,0,0,0,0,0);
mglTextDraw('Mirror reversed',[0 0]);
mglFlush;
mglVFlip: Vertically flip coordinates
purpose: flips coordinate frame vertically
usage: mglVFlip
mglOpen
mglVisualAngleCoordinates(57,[16 12]);
mglVFlip
mglTextSet('Helvetica',32,[1 1 1],0,0,0,0,0,0,0);
mglTextDraw('Vertically flipped',[0 0]);
mglFlush;
Texture functions used for displaying images
mglCreateTexture: Create a texture from a matrix
purpose: Create a texture for display on the screen with mglBltTexture image can either be grayscale nxm, color nxmx3 or color+alpha nxmx4
usage: texture = mglCreateTexture(image,axis)
| argument | value |
|---|---|
| image | nxm matrix of grayscale values from 0 to 255, or nxmx3 matrix of RGB values or nxmx4 of RGBA values |
| axis | 'xy' rows are x and columns are y dimension (default–matlab oriented matrix, i.e. will give you the same results as using imagesc), 'yx' rows are y and columns are x dimension |
| OUTPUT: texture | a texture structure that can be drawn to the screen using mglBltTexture |
mglOpen; mglClearScreen mglScreenCoordinates texture = mglCreateTexture(round(rand(100,100)*255)); mglBltTexture(texture,[0 0]); mglFlush;
When you are done using a texture, you may want to free its memory using mglDeleteTexture.
mglBltTexture: Draw the texture to the screen
purpose: Draw a texture to the screen in desired position.
usage: mglBltTexture(texture,position,<hAlignment>,<vAlignment>,<rotation>)
| argument | value |
|---|---|
| texture | A texture structure created by mglCreateTexture or mglText. |
| position | Either a 2-vector [xpos ypos] or 4-vector [xpos ypos width height]. units are degrees. |
| hAlignment | -1 = left, 0 = center, 1 = right (defaults to center) |
| vAlignment | -1 = top, 0 = center, 1 = bottom (defaults to center) |
| rotation | rotation in degrees, defaults to 0 |
To display several textures at once, texture can be an array of n textures, position is nx2, or nx4 and hAlignment, vAlignment and rotation are either a single value or an array of n.
multiple textures:
mglOpen; mglVisualAngleCoordinates(57,[16 12]); image = rand(200,200)*255; imageTex = mglCreateTexture(image); mglBltTexture([imageTex imageTex],[-3 0;3 0],0,0,[-15 15]); mglFlush;
single textures
mglOpen; mglVisualAngleCoordinates(57,[16 12]); image = rand(200,200)*255; imageTex = mglCreateTexture(image); mglBltTexture(imageTex,[0 0]); mglFlush;
mglDeleteTexture: Delete a texture
purpose: Deletes a texture. This will free up memory for textures that will not be drawn again. Note that when you call mglClose texture memory is freed up. You only need to call this if you are running out of memory and have textures that you do not need to use anymore.
usage: mglDeleteTexture(tex)
| argument | value |
|---|---|
| tex | The texture created by mglCreateTexture that you want to delete |
mglOpen; mglClearScreen mglScreenCoordinates texture = mglCreateTexture(round(rand(100,100)*255)); mglBltTexture(texture,[0 0]); mglFlush; mglDeleteTexture(texture);
Drawing text
mglTextSet: Set parameters for drawing text
purpose: Set text properties for mglText
usage: mglTextSet(fontName,<fontSize>,<fontColor>,<fontVFlip>,<fontHFlip>,<fontRotation>,<fontBold>,<fontItalic>,<fontUnderline>,<fontStrikeThrough>)
| argument | value |
|---|---|
| fontName | 'fontName' (defaults to 'Times Roman') |
| fontSize | font point size (defaults to 36) |
| fontColor | [r g b] where r, g and b are values between 0 and 1 (defaults to [1 1 1]) |
| fontVFlip | 0 = no vertical flip, 1 = vertical flip (defaults to 0) |
| fontHFlip | 0 = no horizontal flip, 1 = horizontal flip (defaults to 0) |
| fontRotation | rotation in degrees (defaults to 0) |
| fontBold | 0 for normal, 1 for bold (defaults to 0) |
| fontItalic | 0 for normal, 1 for italic (defaults to 0) |
| fontUnderline | 0 for normal, 1 for underline (defaults to 0) |
| fontStrikethrough | 0 for normal, 1 for strikethrough (defaults to 0) |
mglOpen;
mglVisualAngleCoordinates(57,[16 12]);
mglTextSet('Helvetica',32,[0 0.5 1 1],0,0,0,0,0,0,0);
mglTextDraw('Hello There',[0 0]);
mglFlush;
mglText: Create a texture from a string
purpose: Creates a texture from a string.
usage: tex = mglText('string')
| argument | value |
|---|---|
| string | The string you want to draw |
mglOpen;
mglVisualAngleCoordinates(57,[16 12]);
mglTextSet('Helvetica',32,[0 0.5 1 1],0,0,0,0,0,0,0);
thisText = mglText('hello')
mglBltTexture(thisText,[0 0],'left','top');
mglFlush;
Normally you will only set one output argument which is a texture usable by mglBltTexture. But if you have two output arguments
[tex texMatrix] = mglText('hello');
texMatrix will contain a 2D matlab array that has a rendering of the text (i.e. it will have values from 0-255 that represent the string). You can modify this matrix as you want and then use mglCreateTexture to create it into a texture that can be displayed by mglBltTexture
mglTextDraw: Draws text to screen (simple but slow)
purpose: wrapper around mglText and mglBltTexture to draw some text on the screen. If you need to draw text more quickly, you will have to pre-make the text textures with mglText and then use mglBltTexture when you want it. Otherwise, for non time-critical things this functions should be used.
usage: mglTextDraw(str,pos,<hAlignment>,<vAlignment>)
| argument | value |
|---|---|
| str | desired string |
| pos | [x y] position on screen |
| hAlignment | -1 = left, 0 = center, 1 = right (defaults to center) |
| vAlignment | -1 = top, 0 = center, 1 = bottom (defaults to center) |
mglOpen;
mglVisualAngleCoordinates(57,[16 12]);
mglTextSet('Helvetica',32,[0 0.5 1 1],0,0,0,0,0,0,0);
mglTextDraw('Hello There',[0 0]);
mglFlush;
mglStrokeText: Fast no-frills line-based text drawing (does not use texture memory)
purpose: Draws a stroked fixed-width character or string on MGL display. Default width is 1, default height 1.8 (in current screen coordinates)
usage: [x,y]=mglStrokeText( string, x, y, scalex, scaley, linewidth, color, rotation );
| argument | value |
|---|---|
| string | text string. Unsupported characters are printed as # |
| x,y | center coordinates of first character (current screen coordinates) |
| scalex | scale factor in x dimension (relative to character) (current screen coordinates). Note that text can be mirrored by setting this factor to a negative value. |
| scaley | scale factor in y dimension (relative to character) (current screen coordinates). Optional, defaults to scalex. |
| linewidth | width of line used to draw text. Default 1. |
| color | text color. Default [1 1 1] |
| rotation | in radians. Default 0. |
| OUTPUT x,y | position after last letter (for subsequent calls) [optional] |
mglOpen;
mglVisualAngleCoordinates(57,[16 12]);
mglStrokeText('Hello',0,0);
mglFlush;
Drawing functions
mglClearScreen
purpose: sets the background color
usage: mglClearScreen(<color>)
| argument | value |
|---|---|
| color | color to set background to, can be a grayscale value or an [r g b] value |
set to the level of gray (0-1)
mglClearScreen(gray)
set to the given [r g b]
mglClearScreen([r g b])
full example
mglOpen; mglClearScreen([0.7 0.2 0.5]); mglFlush();
mglPoints2: 2D points
purpose: plot 2D points on an OpenGL screen opened with mglOpen. For round dots, use mglGluDisk.
usage: mglPoints2(x,y,size,color)
| argument | value |
|---|---|
| x,y | position of dots on screen |
| size | size of dots (height of square in pixels) |
| color | color of dots |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); mglPoints2(16*rand(500,1)-8,12*rand(500,1)-6,2,1); mglFlush
mglPoints3: 3D points
purpose: plot 3D points on an OpenGL screen opened with mglOpen
usage: mglPoints2(x,y,z,size,color)
| argument | value |
|---|---|
| x,y,z | position of dots on screen |
| size | size of dots (in pixels) |
| color | color of dots |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); mglPoints3(16*rand(500,1)-8,12*rand(500,1)-6,zeros(500,1),2,1); mglFlush
mglLines2: 2D lines
purpose: mex function to plot lines on an OpenGL screen opened with glopen
usage: mglLines(x0, y0, x1, y1,size,color)
| argument | value |
|---|---|
| x0,y0 | initial position of line |
| x1,y1 | end position of line |
| size | size of line (in pixels) |
| color | color of line |
mglOpen mglVisualAngleCoordinates(57,[16 12]); mglLines2(-4, -4, 4, 4, 2, [1 0.6 1]); mglFlush
mglFillOval: Ovals
purpose: draw filled oval(s) centered at x,y with size [xsize ysize] and color [rgb]. the function is vectorized, so if you provide many x/y coordinates (identical) ovals will be plotted at all those locations.
usage: mglFillOval(x,y, size, color)
| argument | value |
|---|---|
| x,y | center position of oval |
| size | [width height] of oval |
| color | color of oval |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); x = [-1 -4 -3 0 3 4 1]; y = [-1 -4 -3 0 3 4 1]; sz = [1 1]; mglFillOval(x, y, sz, [1 0 0]); mglFlush();
mglFillRect: Rectangles
purpose: draw filled rectangles(s) centered at x,y with size [xsize ysize] and color [rgb]. the function is vectorized, so if you provide many x/y coordinates (identical) ovals will be plotted at all those locations.
usage: [ ] = mglFillRect(x,y, size, color)
| argument | value |
|---|---|
| x,y | center position of rectangle |
| size | [width height] of rectangle |
| color | color of rectangle |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); x = [-1 -4 -3 0 3 4 1]; y = [-1 -4 -3 0 3 4 1]; sz = [1 1]; mglFillRect(x, y, sz, [1 1 0]); mglFlush();
mglFixationCross: Cross
purpose: draws a fixation cross. With no arguments, draws a fixation cross at origin (default width 0.2 with linewidth 1 in white at [0,0])
usage: mglFixationCross([width], [linewidth], [color], [origin]);
alternate usage: mglFixationCross( params )
| argument | value |
|---|---|
| params | [width linewidth r g b x y] |
| width | width in degrees of fixation cross |
| linewidth | width in pixels on line |
| color | color of fixation cross |
| origin | center position of fixation (defaults to [0 0]) |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); mglFixationCross; mglFlush;
mglGluAnnulus: Annuli, rings
purpose: for annuli and rings, e.g. for retinotopic stimuli. The function is vectorized, such that multiple annuli can be rendered in one call. In this case, x,y, isize, and osize need to have the same number of elements. Color is also vectorized.
usage: [ ] = mglGluAnnulus( x, y, isize, osize, color, [nslices], [nloops] )
| argument | value |
|---|---|
| x,y | position of circle from which annulus/annuli is/are derived |
| isize | inner radius/radii |
| osize | outer radius/radii |
| color | color of annuli, either [], 3-vector, or 3-row by n-column matrix |
| nslices | number of wedges used in polygon→circle approximation [default 8] |
| nloops | number of annuli used in polygon→circle approximation [default 1] |
mglOpen(0); mglVisualAngleCoordinates(57,[16 12]); x = zeros(4, 1); y = zeros(4, 1); isize = linspace(1, 8, 4); osize = isize+linspace(0.1, 2, 4); colors = jet(4)'; mglGluAnnulus(x, y, isize, osize,colors , 60, 2); mglFlush();
mglGluDisk: Circular dots
purpose: for plotting circular (rather than square dots), use this function. on slower machines, large number of dots may lead to dropped frames. there may be a way to speed this up a bit in future.
usage: [ ] = mglGluDisk( x, y, size, color, [nslices], [nloops] )
| argument | value |
|---|---|
| x,y | position of dots |
| size | size of dots (radius) |
| color | color of dots |
| nslices | number of wedges used in polygon→circle approximation [default 8] |
| nloops | number of annuli used in polygon→circle approximation [default 1] |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); x = 16*rand(100,1)-8; y = 12*rand(100,1)-6; mglGluDisk(x, y, 0.1, [0.1 0.6 1], 24, 2); mglFlush();
mglGluPartialDisk: Segments, wedges
purpose: for segments and wedges, e.g. for retinotopic stimuli. The function is vectorized, such that multiple segments can be rendered in one call. In this case, x,y, isize, osize, startAngles, and sweepAngles need to have the same number of elements. Color is also vectorized (see mglGluAnnulus and the example below).
usage: [ ] = mglGluPartialDisk( x, y, isize, osize, startAngles, sweepAngles, color, [nslices], [nloops] )
| argument | value |
|---|---|
| x,y | center position of circle from which segment is derived |
| isize | innter radius of segment |
| osize | outer radius of segment |
| startAngles | angle at which segment(s) start |
| sweepAngles | angle each segment(s) sweep(s) out |
| color | color of segment |
| nslices | number of wedges used in polygon→circle approximation [default 8] |
| nloops | number of annuli used in polygon→circle approximation [default 2] |
mglOpen(0); mglVisualAngleCoordinates(57,[16 12]); x = zeros(10, 1); y = zeros(10, 1); isize = linspace(1, 5, 10); osize = 3+isize; startAngles = linspace(0,180, 10) sweepAngles = ones(1,10).*10; colors = jet(10)'; mglGluPartialDisk(x, y, isize, osize, startAngles, sweepAngles, colors, 60, 2); mglFlush();
mglPolygon: Polygons
purpose: mex function to draw a polygon in an OpenGL screen opened with mglOpen. x and y can be vectors (the polygon will be closed)
usage: mglPolygon(x, y, [color])
| argument | value |
|---|---|
| x,y | position of vertices |
| color | color of polygon |
mglOpen; mglVisualAngleCoordinates(57,[16 12]); x = [-5 -6 -3 4 5]; y = [ 5 1 -4 -2 3]; mglPolygon(x, y, [1 0 0]); mglFlush();
mglQuads: Quads
usage: mglQuad( vX, vY, rgbColor, [antiAliasFlag] );
purpose: mex function to draw a quad in an OpenGL screen opened with mglOpen
| argument | value |
|---|---|
| vX | 4 row by N column matrix of 'X' coordinates |
| vY | 4 row by N column matrix of 'Y' coordinates |
| rgbColors | 3 row by N column of r-g-b specifing the color of each quad |
| antiAliasFlag | turns on antialiasing to smooth the edges |
mglOpen; mglScreenCoordinates mglQuad([100; 600; 600; 100], [100; 200; 600; 100], [1; 1; 1], 1); mglFlush();
Gamma tables
mglSetGammaTable: Sets the display card gamma table
purpose: Set the gamma table
usage: There are a number of ways of calling this function explained below.
Setting a redMin, redMax, redGamma, greenMin, etc.
mglSetGammaTable(0,1,0.8,0,1,0.9,0,1,0.75);
or with a vector of length 9:
mglSetGammaTable([0 1 0.8 0 1 0.9 0 1 0.75]);
or set with a single table for all there colors. Note that the table values go from 0 to 1 (i.e. 0 is the darkest value and 1 is the brightest value). If you have a 10 bit gamma table (most cards do–see section on monitor calibration for a list), then the intermediate values will be interpreted with 10 bits of resolution.
gammaTable = ((0:1/255:1).^0.8)'; mglSetGammaTable(gammaTable);
or set all three colors with differnet tables
redGammaTable = (0:1/255:1).^0.8; greenGammaTable = (0:1/255:1).^0.9; blueGammaTable = (0:1/255:1).^0.75; mglSetGammaTable(redGammaTable,greenGammaTable,blueGammaTable);
can also be called with an nx3 table
gammaTable(:,1) = (0:1/255:1).^0.8; gammaTable(:,2) = (0:1/255:1).^0.9; gammaTable(:,3) = (0:1/255:1).^0.75; mglSetGammaTable(gammaTable);
can also be called with the structure returned by mglGetGammaTable
mglSetGammaTable(mglGetGammaTable);
Note that the gamma table will be restored to the original after mglClose.
Timing. The setting of the gamma table is done by the OS in a way that seems to be asynchronous with mglFlush. For instance, the following code gives unexpected results:
mglOpen(1); mglClearScreen(1); % set back buffer to white mglWaitSecs(2); % now set the gamma table to all black, this should insure that nothing will be displayed mglSetGammaTable(zeros(1,256)); mglFlush; % now the flush will bring the value 255, set by the mglClearScreen above, % to the front buffer, but because the gamma table is set to black, % nothing should be displayed mglWaitSecs(2); mglClose;
This should keep the screen black, but on my machine, the screen temporarily flashes white. Presumably this is because the mglSetGammaTable happens after the mglFlush. It is recommended that you change the gamma while there is nothing displayed on the screen and wait for at least one screen refresh before assuming that the gamma table has actually changed.
mglGetGammaTable: Gets the current gamma table
purpose: returns what the gamma table is set to usage: table = mglGetGammaTable()
mglOpen; gammaTable = mglGetGammaTable
Stencils to control drawing only to specific parts of screen
Here is a demonstration of how to use stencils using these these functions:
mglOpen; mglScreenCoordinates;
%Draw an oval stencil mglStencilCreateBegin(1); mglFillOval(300,400,[100 100]); mglStencilCreateEnd; mglClearScreen;
% now draw some dots, masked by the oval stencil mglStencilSelect(1); mglPoints2(rand(1,5000)*500,rand(1,5000)*500); mglFlush; mglStencilSelect(0);
mglStencilCreateBegin: Start drawing a stencil
purpose: Begin drawing to stencil. Until mglStencilCreateEnd is called, all drawing operations will also draw to the stencil. Check MGL.stencilBits to see how many stencil planes there are. If invert is set to one, then the inverse stencil is made
usage: mglStencilCreateBegin(stencilNumber,invert)
| argument | value |
|---|---|
| stencilNumber | stencil number, usualy 1-8 but look at the global variable MGL.stencilBits to see how many stencil planes there are. |
| invert | 1 or 0 to invert the stencil that is made |
see example above.
mglStencilCreateEnd: End drawing a stencil
purpose: Ends drawing to stencil usage: mglStencilCreateEnd
see example above.
mglStencilSelect: Select a stencil
purpose: Sets which stencil to use, 0 for no stencil usage: mglStencilSelect(stencilNumber)
| argument | value |
|---|---|
| stencilNumber | number of stencil to use |
See example above.
Keyboard and mouse functions
mglDisplayCursor: Hide or display the mouse cursor
purpose: Hide or display the mouse cursor
usage: mglDisplayCursor(<display>)
| argument | value |
|---|---|
| display | 1 or 0 to display or hide the cursor |
When you call mglOpen the mouse cursor is hidden by default. You can get it to come back by doing:
mglOpen mglDisplayCursor
mglGetKeys: Get keyboard state
purpose: returns the status of the keyboard (regardless of whether the focus is on the mgl window)
usage: mglGetKeys(<keys>)
| argument | value |
|---|---|
| keys | array of keycodes. In this case mglGetKeys will only return thestatus of those keys, for example: mglGetKeys([61 46]) will return the values of key 61 and 46. |
mglGetMouse: Get mouse state
usage: mglGetMouse()
purpose: returns the status of the mouse buttons (regardless of whether the focuse is on the mgl window
mglGetKeyEvent: Get a key down event off of queue
purpose: returns a key down event waitTicks specifies how long to wait for a key press event in seconds. Note that the timing precision is system-dependent:
- Mac OS X: nanosecond (gets timestamps using a background thread mglListener)
- Linux: 1/HZ s where HZ is the system kernel tick frequency (HZ=100 on older systems, HZ=250 or 500 on more modern systems)
The default wait time is 0, which will return immediately and if no keypress event is found, will return an empty array []. The return structure contains the character (ASCII) code of the pressed key, the system-specific keycode, a keyboard identifier (on Linux, this is the keyboard state, or modifier field), and and the time (in secs) of the key press event. NOTE that to get a key event the focus *MUST* be on the mgl window. For faster timing, try mglGetKeys
usage: mglGetKeyEvent(waitTicks)
| argument | value |
|---|---|
| waitTicks | Ticks to wait for before giving up and returning empty event |
mglOpen mglGetKeyEvent(0.5)
mglGetMouseEvent: Get a mouse button down event off of queue
usage: mglGetMouseEvent(waitTicks)
purpose: returns a mouse down event waitTicks specifies how long to wait for a mouse event in seconds. Note that the timing precision is system-dependent:
- Mac OS X: nanosecond (gets timestamp from background thread mglListener)
- Linux: 1/HZ s where HZ is the system kernel tick frequency (HZ=100 on older systems, HZ=250 or 500 on more modern systems)
The default wait time is 0, which will return immediately with the mouse position regardless of button state. The return structure contains the x,y coordinates of the mouse, the button identifier if pressed (on the button-challenged Mac this is always 1) and 0 otherwise, and the time (in secs) of the mouse event. NOTE that the mouse down event has to be *ON* the mgl window for this to work with waitTicks not equal to 0
| argument | value |
|---|---|
| waitTicks | Ticks to wait for before giving up and returning empty event |
mglOpen mglGetMouseEvent(0.5)
mglCharToKeycode: Returns keycode of char
Purpose: Returns the keycodes of a (list of) keynames
Usage: keycode=mglCharToKeycode(keyname)
Note on special keys: On Linux (X), special keys and function keys have unique names, e.g., 'Escape', 'F1', etc., so obtaining the keycodes for these is done by mglCharToKeycode({'Escape','F1'}) etc. On Macs, this is not possible; instead, test for the keycode and name of a key using the mglShowKey function.
The keycodes match those used by mglGetKeys and mglGetKeyEvent
| argument | value |
|---|---|
| keyname | cell array where each entry is a key name string e.g. keyname = {'h','g' '1'} |
| OUTPUT:keycode | vector of integer keycodes for each keyname entry e.g. for the above example, keyname=[44 43 11] (on Linux) |
Example: testing for specific keypresses:
keycodes=mglCharToKeycode({'1','2' '3'}) % keys 1-3 on main keyboard
while (1); k=mglGetKeys(keycodes); if (any(k)),break;end;end
Technical note: the returned keycodes are identical to system keycodes+1
mglKeycodeToChar: Returns char of keycode
Purpose: Returns the keynames of a (list of) keycodes
Usage: keyname=mglKeycodeToChar(keycode)
Note on special keys: This repeats the above entry, deleted.
| argument | value |
|---|---|
| keycode | vector of integer keycodes for each keyname entry for example, keyname=[44 43 11] |
| OUTPUT:keyname | cell array where each entry is a key name string for the above example on linux keyname = {'h','g' '1'} |
Example: testing which keys were pressed:
while (1); k=mglGetKeys; if (any(k)),break;end;end keycodes=find(k); keynames=mglKeycodeToChar(keycodes)
Technical note: keycodes are identical to system keycodes+1
mglSimulateRun: Generates simulated key presses
availability: mgl 2.0 Mac OS X only
usage: mglSimulateRun(framePeriod,numFrames,<startDelay>,<char>)
purpose: This function simulates a run (currently only available on Mac) it does this by posting periodic backtick keypresses using mglPostEvent. For example if you want to have 375 backticks occur spaced 1.5 seconds apart:
mglSimulateRun(1.5,375);
Also, you can delay the start of the backticks, by doing
mglSimulateRun(1.5,375,10);
Instead of sending the backtick character, send the character 'a'
mglSimulateRun(1.5,375,0,'a');
Note that if you want to stop the keyboard events in the middle, you need to do:
mglPostEvent('quit');
mglEatKeys: Keep key presses from entering buffer
availability: mgl 2.0 Mac OS X only
usage: mglEatKeys(keyCodes)
purpose: Starts eating keypresses (i.e. the sent in keycodes will no longer be sent to the terminal window. This can be useful if you don't want to fill your text buffer with extraneous keyboard backticks and subject responses). keyCodes can also be a char array or the myscreen variable (if the myscreen variable it will eat all keys that initScreen is using for backticks and response keys). Note that if you press any key that is not being eaten, then key eating will stop. Usually, you do not need to call this function directly, but you have initScreen call it, by setting eatKeys in mglEditScreenParams.
Timing functions
mglGetSecs: Get time in seconds
purpose: Get current or elapsed time
usage: mglGetSecs(<t0>)
| argument | value |
|---|---|
| t0 | start time from which to compute elapsed time |
To get current time
t=mglGetSecs
Get elapsed time since t0
t0 = mglGetSecs; elapsedTime=mglGetSecs(t0)
mglWaitSecs: Wait for a time in seconds
purpose: Wait for some time
usage: mglWaitSecs(waitTime)
| argument | value |
|---|---|
| waitTime | time to wait for in seconds |
Wait 3.3 seconds:
mglWaitSecs(3.3);
Sound functions
mglInstallSound: Install an .aiff file for playing with mglPlaySound
purpose: Install an .aiff file for playing with mglPlaySound
usage: mglInstallSound(soundName)
| argument | value |
|---|---|
| soundName | aiff filename |
This will install sounds to be played using mglPlaySound. Note that if you just want to use systems sounds then you do not need to call this function directly, it will be called by mglOpen to install all your system sounds. Once the sound is installed you can play it with mglPlaySound
soundNum = mglInstallSound('/System/Library/Sounds/Submarine.aiff');
mglPlaySound(soundNum);
With no arguments, mglInstallSound uninstalls all sounds
mglInstallSound
Version 2.0
You can also specify a directory of sound files to install:
mgInstallSound('soundDir');
which will install all sounds named *.aif or *.aiff. You can play the sounds by specifying the name of the file (without the file extension):
mglPlaySound('soundFileName');
mglPlaySound: Play a system sound
purpose: Play a sound
usage: mglPlaySound(soundNum)
| argument | value |
|---|---|
| soundNum | number of sound |
Plays a system sound. After calling mglOpen, all of the system sounds will be installed and you can play a specific one as follows:
mglOpen; global MGL; mglPlaySound(find(strcmp(MGL.soundNames,'Submarine')));
With no arguments mglPlaySound plays the system alert sound
mglPlaySound
Note that this function returns right after it starts playing the sound (it does not wait until the sound finishes playing).
mglMovie
availability: mgl 2.0 Mac OS X 64-bit Matlab only
usage: movieStruct = mglMovie(filename,position) or mglMovie(movieStruct,command,<argument>);
purpose: Used to display quicktime movies. (This *only* works on 64 bit Mac. There is some issue with the quicktime library QTKit and threads which does not seem to be a problem on 64 bit). You also need to be using a cocoa window, so make sure to set mglSetParam('movieMode',1) before running mglOpen.
Also, note that the movies will play in front of the openGL buffer. Thus you can't draw on top of the movie and mglFrameGrab won't grab the movie frame – you can grab movie frames with mglMovie(m,'getFrame');
To init the movie, you open with a filename, and an optional position array [x y width height] and save the returned structure.
mglSetParam('movieMode',1);
mglOpen;
m = mglMovie('movie.mov');
Then you can run commands on the movie:
mglMovie(m,'play');
If no position is specified, the movie will be made to fill the display. Then pass in the structure with any of the following commands:
| command | purpose |
|---|---|
| 0:'close” | Close the movie. After you run this, you will no longer be able to play the movieStruct again since the memory will have been released. |
| 1:'play' | Play the movie |
| 2:'pause' | Pause the movie |
| 3:'gotoBeginning' | Goto the beginning of the movie |
| 4:'gotoEnd' | Goto the end of the movie |
| 5:'stepForward' | Step one frame forward in the movie |
| 6:'stepBackward' | Step one frame backward in the movie |
| 7:'hide' | Hide the movie. This does not close the movie, so you will be able to show the movieStruct again by using show. |
| 8:'show' | Show the movie after it has been hidden |
| 9:'getDuration' | Get a string that represents the length of the movie |
| 10:'getCurrentTime' | Gets the current time of the movie |
| 11:'setCurrentTime' | Sets the current time of the movie to the string\passed in. Make sure the string is one returned from getCurrentTime |
| 12:'getFrame' | Returns a nxmx3 matrix containing RGB data for current frame |
Here is a sample quicktime movie that should work with mglMovie: bosque.mov
Digital I/O
Mgl has some functions to handle digital I/O with a National Instruments card (e.g. NI USB 6501) that can be used to read and write digital I/O signals. These can be useful to synch to an MR scanner or control an external eye tracker. These functions all live in the directory:
mgl/utils/readDigPort
These need to be compiled specially, in mgl 2.0 by running (earlier versions, just go mex by hand):
mglMake('digio');
You will need to have downloaded the National Instruments drivers (see next section) to compile and use these functions. Note that these functions only work for 32 bit matlab due to the fact that the NI-DAQmx Base library is currently (10/26/2011) available only in 32-bit. Currently we are using NI-DAQmx Base version 3.4.5.
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 in mgl 2.0 by setting digin using mglEditScreenParams.
mglDigIO
availability: mgl 2.0 Mac OS X only
usage: mglDigIO(command,<arg1>,<arg2>)
purpose: This is a mac specific command that is used to control a NI digital IO board. It has been tested with the NI USB 6501. It runs as a thread that reads digital port 2 and logs any change in state (either up or down). It can also be used to set digital lines on port 1 at a time of your choosing. It is used by the task code if you set mglEditScreenParams to use digin. It is the preferred way of dealing with digital I/O since it keeps excellent timing. Note that if you are trying to read events faster than about 250Hz (e.g. a square wave of 250Hz), you should be able to read all events without fail. Faster than that at around 500Hz you will likely start dropping events (this is likely due to how fast the NI-DAQ mxBase driver can pool the device). To use this function, you will need to compile it using mglMake('digin');
Here are thte commands it accepts:
| command | purpose |
|---|---|
| 1:'init' | Init the digIO thread. You need to run this before anything else will work. You can optional specify input and output ports which default to 1 and 2 respectively: mglDigIO('init',inputPortNum,outputPortNum); You can call init with different port numbers to reset what ports you want to listen/write to/from without calling quit inbetween. |
| 2:'digin' | Returns all changes on the input digital port |
| 3:'digout' | Set the output digital port a time of your choosing. This takes 2 other values. The time in seconds that you want the digital port to be set. And the value you want it to be set too. Time can be either an absolute time returned by mglGetSecs or it can be relative to now if it is a negative value: mglDigIO('digout',-5,0) → Sets the output port to 0 five secs from now. |
| 4:'list' | Lists status and all pending digout events |
| 0:'quit' | Closes the nidaq ports, after this you won't be able to run other commands. Note that this does not shutdown the digIO thread. The reason for this is that the NIDAQ library is not thread safe, so you can only call its functions from one thread, so to be able to keep starting and stopping reading from the card, the thread is set to continue to run, and quit simply shuts down the nidaq tasks and stops logging events. After you call quit, you can use init again to restart reading/writing. If you need to shutdown the thread, use 'shutdown' |
| -1:'shutdown' | Quits the digIO thread if it is running, after this you won't be able to run other commands. If you plan on starting and stopping digIO collection, you should use init and quit rather than shutdown |
writeDigPort
usage: writeDigPort(portNum,val);
purpose: write an ouput to the National Instruments board. portNum defaults to 2, to write from Dev1/port2. The first time you read it needs to open the port to the NI device which can take some time. Subsequent calls will be faster. Note that you can only open one port at a time, so if you need to read from two different ports it will always be closing and reopening the ports which will cause a performance hit (consider rewriting the code to keep multiple ports open if you need this). Also, if you want to switch between reading and writing on a single port, you will need to manually close the port in between read/write calls by setting portNum = -1 (see below).
portNum can also be set to:
| -1 | closes any open port |
| -2 | displays which port (if any) is open. |
Note that in the distribution, writeDigPort is not compiled. It always returns 0. To use it to read your NI card, you will need to mex readDigPort.c, this requires you to install the NI-DAQmx Base Frameworks.
readDigPort
usage: readDigPort(<portNum>)
purpose: read the National Instruments board digital input. portNum defaults to 1, to read from Dev1/port1. The first time you read it needs to open the port to the NI device which can take some time. Subsequent calls will be faster. Note that you can only open one port at a time, so if you need to read from two different ports it will always be closing and reopening the ports which will cause a performance hit (consider rewriting the code to keep multiple ports open if you need this). Also, if you want to switch between reading and writing on a single port, you will need to manually close the port in between read/write calls by setting portNum = -1 (see below).
portNum can also be set to:
| -1 | closes any open port |
| -2 | displays which port (if any) is open. |
Note that: in the distribution, readDigPort is not compiled. It always returns 0. To use it to read your NI card, you will need to mex readDigPort.c, this requires you to install the NI-DAQmx Base Frameworks.
Testing USB-7204
It is from the company, Mearsurement Computing and 64 bit library for Mac is available. Download the driver from here.
Manual for the hardware is PDFhardware while software manual is PDFsoftware
EyeLink Eyetracker functions
Overview
We have functions to interface Matlab with the EyeLink scanner. These functions allow you to calibrate the scanner and get current eye position information, etc. These are all referenced below. NOTE these functions only work with 32 bit Matlab. This is a limitation of the Eyelink library that SR Research provides. They are working on a 64 bit version, but as of this writing (12/3/2010) they are not available.64 bit for OSX is available, but the installation is not straight forward as of 07/05/2011. Please follow the instruction here. To use the mgl Eyelink functions, you will need to compile them with:
mglMake('Eyelink')
You will need to have the Eyelink Frameworks installed. This can be downloaded from Eyelink CD. The ones you will need are called eyelink_core.framework (used for interfacing with the eye tracker) and edfapi.framework (used for reading the edf files that the tracker makes). These frameworks will appear as directories in Library/Frameworks (either in the root directory or your home directory). There is also an eyelink_core_graphics.framework, but this framework should not be necessary.
Follow the Eyelink instructions for setting up your Eyelink computer and connecting to it. But, essentially, you need to do at least the following:
- Connect the Eyelink computer to your stimulus computer with an Ethernet cross cable (should be provided by SR Research).
- Set your Network settings correctly. Usually you will have plugged the Eyelink computer into the second network adaptor. So you will then need to go to System Preferences/Network and set “Ethernet 2” to the following
Configure IPv4: Manually IP Address: 100.1.1.2 Subnet Mask: 255.255.255.0 Router, DNS Server and Search Domains are irrelevant (I think)
- Boot up the Eyelink computer.
- Run the command mglEyelinkOpen in matlab on the stimulus computer. You should see the green bar in the top right hand corner of the Eyelink computer change to say: TCP/IP Link Open
- Run the command mglEyelinkClose and it should change back to: Link Closed
If you use the mgl task code, you do not need to call any of the following functions explicitly.
mglEyelinkReadEDF
purpose: Reads an Eyelink file into matlab
usage: mglEyelinkReadEDF(filename,<verbose>)
| argument | value |
|---|---|
| filename | Name of EDF file you want to read |
| verbose | Set to 1 to display verbose information, defaults to 0 |
mglEyelinkOpen
purpose: Opens a TCP/IP link between matlab and the EyeLink eyetracker.
usage: mglEyelinkOpen(ip,conntype);
| argument | value |
|---|---|
| ip | The IP address of the eyewink eye tracker, defaults to 100.1.1.1 |
| conntype | 0, open up a direct link, 1 initializes a dummy connection useful for debugging. |
% open the link
% calls mglPrivateEyelinkOpen, default ip is '100.1.1.1', default conntype is 0
try
mglEyelinkOpen('100.1.1.1', 0);
catch err
mglEyelinkOpen('100.1.1.1', 1);
disp(sprintf('(mglEyelinkOpen) Establishing a dummy connection with the EyeLink'));
end
mglEyelinkCMDPrintF
purpose: Sends a command to the eyetracker
usage: mglEyelinkCMDPrintF('an EyeLink command');
| argument | value |
|---|---|
| a string | anything recognized by EyeLink (see the manual) |
% set up some variables
mglEyelinkCMDPrintF('screen_pixel_coords = 0 0 %d %d', mglGetParam('screenWidth'), mglGetParam('screenHeight'));
mglEyelinkCMDPrintF('calibration_type = HV9');
mglEyelinkCMDPrintF('file_event_filter = RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON');
mglEyelinkCMDPrintF('file_sample_data = RIGHT,GAZE,AREA,GAZERES,STATUS');
mglEyelinkCMDPrintF('sample_rate = 500');
mglEyelinkSetup
This function switches the Eyelink Software into the 'setup' state where you can adjust thresholds, change settings and run the calibration routine. You can use any of the keyboard commands that are available from the Eyelink software (on the Eyelink computer keyboard). This function returns when you exit the Eyelink setup state (via the ESC key), usually after you have successfully calibrated or validated. The keys you can use are listed below, for more information refer to the Eyelink Users Manual.
The basic keys are
Setup Screen
- RETURN : display the eye image in the primary MGL context
- LEFT/RIGHT ARROW : switch the selected eye image
- A : Auto threshold the eye
- C : Enter the calibration routine (ENTER to accept a calibration)
- V : Enter the validation routine (ENTER to accept a validation)
- UP/DOWN ARROW : adjust the pupil reflection threshold
- +/- : adjust the corneal reflection threshold
All Keyboard Commands
Setup Screen
- ESC : Go to the Offline screen or exit Camera Setup
- ENTER : Toggles sending images over link
- C : Go to the Calibration screen
- V : Go to the Validate screen
- D : Go to the Drift correction/check screen
- O : Go to the Output screen
- S : Go to Set Options page
- Ctrl + Alt + Q : Exit the EyeLink Host application
- Page Up and ⇑ : Increase pupil threshold/bias
- Page Down : Decrease pupil threshold/bias
- EyeLink 1000 User Manual version (9/13/2007) © 2005-2007 SR Research Ltd. 23 : and ⇓
- + and - : Set corneal reflection threshold/bias
- ⇐ and ⇒ : Select Eye, Global or zoomed view for link
- A : Auto threshold selected imageTower MountDesktop Mount; Additionally, for the EyeLink Remote, realigns the search limit box on top of the current eye position
- E : Cycle through eye(s) to track.
- L : Select left eye for recording
- R : Select Right eye for recording
- B : Select both eyes for recording
- P : Toggle Pupil only or Pupil-CR mode selection (may be locked)
- Q : Toggle Ellipse and Centroid pupil center position algorithm
- F : Select sampling rate of EyeLink recording
- U : Toggle search limit box on or off
- SHIFT and cursor keys (⇐, ⇒, ⇑, or ⇓) : If search limits are enabled, these keys can be used to move the position of the search limits.
- ALT and cursor keys (⇐, ⇒, ⇑, or ⇓) : If search limits are enabled, these keys can be used to adjust the size and shape of the search limits.
- M : Toggle Mouse-click Autothreshold on or off
- X : Toggle crosshair display
- T : Toggle threshold coloring display
- I : Change illuminator power (hardware dependent)
- K : Perform camera position autodetect (mirror mount); Toggle “lock tracked eye” button (Desktop Mount).
Video Overlay Only
- W : Video overlay configuration.
Calibration Screen
- ESC : Camera setup
- A : Auto calibration set to the pacing selected in Set Options menu. (Auto trigger ON). EyeLink accepts current fixation if it is stable.
During Calibration
- ENTER or Spacebar : Begins calibration sequence or accepts calibration value given. After first point, also selects manual calibration mode.
- ESC : Terminates calibration sequence.
- M : Manual calibration (Auto trigger turned off.)
- A : Auto calibration set to the pacing selected in Set Options menu. (Auto trigger ON). EyeLink accepts current fixation if it is stable.
- Backspace : Repeats previous calibration target.
After Calibration
- ENTER : Accept calibration values
- V : Validate calibration values
- ESC : Discard calibration values
- Backspace : Repeats last calibration target.
Validation Screen
- ESC : Camera setup
- A : Auto calibration set to the pacing selected in Set Options menu. (Auto trigger ON). EyeLink accepts current fixation if it is stable.
During Validation
- ESC : (First Point) Exit to Camera Setup (Following Points) Restart Calibration.
- ENTER or Spacebar : Begins calibration sequence or accepts calibration value given. After first point, also selects manual calibration mode.
- M : Manual validation (Auto trigger turned off.)
- A : Auto validation set to the pacing selected in Set Options menu. (Auto trigger ON). EyeLink accepts current fixation if it is stable.
- Backspace : Repeats previous calibration target.
After Validation
- ENTER : Accept validation values
- ESC : Discard validation values
mglEyelinkOpenEDF
purpose: Open up a new datafile to store the eye data
usage: mglEyelinkOpenEDF(filename)
mglEyelinkRecordingStart
purpose: Tell the eyetracker to start recording samples
usage: mglEyelinkRecordingStart(startvector)
| argument | value |
|---|---|
| 1 0 0 0 | edf-sample (record eye samples in the current eyelink .edf file) |
| 0 1 0 0 | edf-event (record events in the current eyelink .edf file) |
| 0 0 1 0 | link-sample (send eye position samples back to matlab) |
| 0 0 0 1 | link-event (send events back to matlab) |
Pass in either a vector for recording state: e.g. [1 0 0 0] where the elements are [file-sample file-event link-sample link-event] or up to four string arguments that set the recording state
mglEyelinkRecordingStart('file-sample','file-event', 'link-sample','link-event');
mglEyelinkEDFPrintF
purpose: Insert a message into the recorded datastream
usage: mglEyelinkEDFPrintF(message)
| argument | value |
|---|---|
| meessage | text string that you want to insert |
mglEyelinkGetCurrentEyePos
purpose: Get the X and Y coordinate of the current eye position
usage: pos = mglEyelinkGetCurrentEyePos()
| argument | value |
|---|---|
| pos | returns the eye position in the current device coordinates - e.g. visual angle if visual angle coordinates are set. |
mglEyelinkClose
purpose: Close the link between matlab and the eyetracker
usage: mglEyelinkClose
Test/Demo programs
Run these test programs without any parameters and they should display on your second monitor. With an optional single argument you can pass the number of the display you want to display on.
- mglTestAlignment: Alignment of textures
- mglTestDots: Draws dots
- mglTestGamma: GUI controlled gamma
- mglTestLUTAnimation: Gamma LUT animation
- mglTestStencil: Demonstrates stencil functions
- mglTestTex: Draws a gabor
- mglTestTexMulti: Draws many small images to screen
- mglTestText: Draws text
- mglTestKeys: Returns keyboard codes
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:
- Initialize the screen.
- Set up the task structure. The task structure holds information about the parameters you want to randomize over and the timing of your experiment.
- Initialize the stimulus. Here you will create all the necessary bitmaps or display structures that you will need to display your stimulus.
- 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.
- 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:
testExperiment
testExperiment
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 in mgl 2.0 just use mglEditScreenParams. In earlier versions of mgl you 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; else stimulus.dots.coherence = 0; end 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 mglClearScreen; 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); end
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);
or
[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. It also checks for whether the user hit the <ESC> key, and ends the program when it has been hit.
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); end
At the very end you end the task which will save out information about your experiment.
myscreen = endTask(myscreen,task);
Integration with an eye tracker
The task structure also provides easy integration with an eye tracker. The basic functionality is handled by a set of callback functions that handle interacting with the eye tracker. Currently, support for the SR Research (http://www.sr-research.com/) EyeLink trackers is fully supported. The current eye position is also available for constructing simple gaze contingent displays. See here.
Experimental parameters
Basics
For your experiment you can choose what parameters you have and what values they can take on. You do this by adding parameters (of your choosing) into the parameter part of a task variable:
task.parameter.myParameter1 = [1 3 5 10]; task.parameter.myParameter2 = [-1 1];
You can add any number of parameters that you want. updateTask will chose a value on each trial and put those values into the thistrial structure:
task.thistrial.myParameter1 task.thistrial.myParameter2
would equal the setting on that particular trial. In each block every combination of parameters will be presented. You can randomize the order of the parameters by setting:
task.random = 1;
Note that parameter should really just be used for the parameters over which you want to randomize your experiment. For example, you may be testing several contrasts in your experiment, that should be coded as a parameter. You may also have some random variables, things like which segment that target should be presented in for example–things that need to be randomized, but are not a crucial parameter you are testing. For these types of variables, you should use randVars instead of parameter (see below).
What if I have parameters that are not single numbers
You may have a parameter that is an array rather than a single number, for example a string:
task.parameter.strings = {'string1','string2','string3'};
The variable strings will be set in task.thistrial:
task.thistrial.strings
What if I have a group of parameters
You may have stimuli in which the parameters are grouped into different sets. For example you might want to show two types of grating patches. One tilted to the left with a high contrast and low spatial frequency and the other tilted to the right with low contrast and high spatial frequency.
Then you could do
task.parameter.groupNum = [1 2];
task.private.group{1}.orientation = -10;
task.private.group{1}.contrast = 1;
task.private.group{1}.sf = 0.2;
task.private.group{2}.orientation = 10;
task.private.group{2}.contrast = 0.1;
task.private.group{2}.sf = 4;
On each trial, you get the parameters by doing
task.thistrial.thisgroup = task.private.group{task.thistrial.groupNum};
randVars
For variables that you just want to have some randomization over, you can declare them as randVars. For example, you might want to specify a target interval which should be either 1 or 2 on any given trial, but you don't want that to be block randomized. Then you can declare that variable as a uniform randomization:
task.randVars.uniform.targetInterval = [1 2];
This variable will then be available in task.thistrial.targetInterval.
You may also want to have the variable block randomized, like a parameter, but the blocks should be independent of the main parameter:
task.randVars.block.blockedVar = [-1 0 1];
This will guarantee that on every three trials, blockedVar will be set to each one of the possible values -1,0 and 1.
Note that with randVars the randomization is chosen at the beginning of the experiment and by default 250 trials are randomized after which you will cycle back through the variables. If you need more than 250 trials, you can set:
task.randVars.len_ = 500;
Storing variables calculated during a trial
If you want to store a value calculated during a trial (e.g. a user entered value or a calculated value) you can use the 'calculated' type of randVar. The variables defined in the calculated struct array are initialized to the value(s) specified. The variables are then made available in the 'task.thistrial' as with other parameters and randVars. However, the all variables that were defined as 'calculated' are saved back to the randVars variable array at the end of the trial. These calculated values will then be available when you extract the parameters using getTaskParameters. For example, you can declare in your task variable:
task{1}.randVars.calculated.myVar = nan;
Note that the default value (if you don't set it in a callback, will be the one set above, i.e. nan). Next in any callback, you can set that variable. For example in the response callback:
task.thistrial.myVar = 15;
After the experiment is done that variable can be accessed through getTaskParameters just like any other parameter or randVar. Note that if you set the variable to something other than a scalar, the calculated variable will be stored as a cell array.
You may also (optionally) specify all the values that your calculated variable may take on. This is useful if during any given run you won't necessarily encounter all possible values. To do this set the variable name with an underscore after it:
task{1}.randVars.calculated.myVar_ = 1:15;
Using your own random sequence
You might have your own randomization routine and want to use that to randomize parameters. You can do that with randVars:
task.randVars.myRandomParameter = [...];
Then myRandomParameter will be available in task.thistrial.myRandomParameter in the order you specify in the array.
Creating a parameter sequence after running the stimulus program
Sometimes you may want to compute a parameter sequence after you have run the stimulus program. For example, if you want to compute a new variable sequence to do event-related processing based on the existing data within your stim file. To do so, you can use the function addCalculatedVar. You run this on a stimfile. For example, say you have a stimfile with 5 trials, and you wanted to add the variable 'myCalcVar' with values [0 3 2 4 1] for those trials:
addCalculatedVar('myCalcVar',[0 3 2 4 1],'100727_stim01');
After running this, the variable myCalcVar will appear when you run getTaskParameters and you can use it to do event-related analysis from the mrLoadRet GUI. Note that addCalculatedVar will overwrite the stimfile 100727_stim01.mat, but will save an original copy called 100727_stim01_original.mat.
You may also (optionally) specify all the values that your calculated variable may take on. This is useful if during any given run you won't necessarily encounter all possible values. To do this set the variable name with an underscore after it:
addCalculatedVar('myCalcVar',[0 3 2 4 1],'100727_stim01','allval',0:5);
See also this how-to.
Segment times
How to setup segment times
Each trial can be divided into multiple segments where different things happen, like for instance you might have a stimulus segment and response segment that you want to have occur for 1.3 and 2.4 seconds respectively:
task.seglen = [1.3 2.4];
At the beginning of each segment the callback startSegment will be called and you can find out which segment is being run by looking at:
task.thistrial.thisseg
How to randomize the length of segments
If you want to randomize the length of segments over a uniform distribution, like for instance when you want the first segment to be exactly 1.3 seconds and the second segments to be randomized over the interval 2-2.5 seconds:
task.segmin = [1.3 2]; task.segmax = [1.3 2.5];
In this case, do not specify task.seglen.
If you want the second interval to be randomized over the interval 2-2.5 seconds in intervals of 0.1 seconds (i.e. you want it to be either 2,2.1,2.2,2.3,2.4 or 2.5:
task.segmin = [1.3 2]; task.segmax = [1.3 2.5]; task.segquant = [0 0.1];
You can also have a segment wait until a backtick happens, so that you can easily synch to volumes, for example:
task.segmin = [1.3 2]; task.segmax = [1.3 2.5]; task.synchToVol = [0 1];
This will cause the second segment to last a random amount of time between 2 and 2.5 seconds and then wait until a backtick occurs before going on to the next trial. Note that when using synchToVol it is a good idea to make the segment for which you are waiting for a volume acquisition to happen slightly shorter than you actually want. This way the segment time will be finished and it will be waiting for the volume acquisition to continue.
How to wait for user input before moving to next segment
Sometimes you will want to wait for user input to decide when to end a segment of the trial, rather than pre-set a time. To do this, you need to: (1) set the segment length to inf, (2) take user input for that segment, and (3) in the responseCallback, end the segment when the subject responds. [Note that if you want to limit how much time the user has to respond, but still wait for input, you can set the segment length to something less than inf, e.g. 5 seconds; this means that the segment will end either when the subject responds, or when 5 seconds have elapsed, whichever comes first.]
An example of how this might be implemented, in the case when the second of three segments waits for subject input before terminating:
% in the main task body: task.seglen = [.5 inf 2]; task.getresponse = [0 1 0];
% At the end of the responseCallback function: task = jumpsegment(task);
For other uses of jumpsegment, and for how to use jumpsegment(task, inf), see how to program a dual task.
Keeping time in seconds, volumes or refreshes
Trial segments can keep time in either seconds (default), volumes or monitor refreshes.
To change timing to use volumes:
task.timeInVols = 1;
To change timing to use monitor refreshes (note that is probably not a great idea to keep time in monitor refreshes since if you drop a frame, your timing will be altered).
task.timeInTicks = 1;
With timeInVols or timeInTicks, your segment times should now be integer values that specify time in Vols or monitor refreshes (e.g.):
task.seglen = [3 2];
Note, that the default (time in seconds) adjusts for segment overruns that might occur when you drop monitor frames, but the timeInTicks will not and is therefore usually less accurate.
Callbacks
Callbacks are the way that you control what happens on different portions of the trial and what gets drawn to the screen. They are simply functions that get called at specific times in the experiment.
It doesn't matter exactly what you call them, but it does matter exactly what order you register them in.
There are two required callbacks, and the rest are optional. If for some reason you don't need one of the required callbacks, you can just leave it empty, but you must still define it.
Callbacks are also discussed in the overview.
Registering callbacks
You must register your callbacks with the initTask function, in the following order:
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,@getResponseCallback,@startTrialCallback,@endTrialCallback,@startBlockCallback);
You do not need to specify all the callbacks, only startSegmentCallback and screenUpdateCallback. To omit any of the callbacks, either don't pass it in to initTask or set the appropriate argument to []. Make sure that you return task and myscreen.
For example, you might have
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,[],@startTrialCallback,[],@startBlockCallback);
or
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,@getResponseCallback);
screenUpdateCallback (required)
function [task myscreen] = screenUpdateCallback(task, myscreen) % do your draw functions in here.
Note that you will normally declare a global variable named stimulus that contains any textures or information about the stimulus and use that in here. Remember that screenUpdateCallback gets called every frame update. For a refresh rate of 60 Hz that means it definitely has to run within 1/60 th of a second, or else the program will start to drop frames and become slow. You should therefore make this function as simple as possible. For example, if you are using textures, call mglCreateTexture in your myInitStimulus function and only use the precomputed texture here in an mglBltTexture function.
Another option that you can consider is that for many types of stimulus you don't have to update the screen every frame refresh. For something like moving dots or a drifting gabor you will need to update the frame every screen refresh, but if you just want to show a static gabor for a full segment, you can use the flushMode=1 feature that is described below in startSegmentCallback.
startSegmentCallback (required)
The other mandatory callback is the one that is called at the beginning of each segment:
function [task myscreen] = startSegmentCallback(task, myscreen)
The variable task.thistrial will have fields set to what the parameters are for that trial. For instance if you have dir as one of your parameters, then you will have the field task.thistrial.dir set to one of the directions (chosen by updateTask).
If you are only drawing to the screen at the start of every segment, then you can use the flushMode=1 feature. Say for example you want to clear the screen and draw your texture to the screen and that is all that will happen in the segment then you can do something like:
mglClearScreen; mglBltTexture(stimulus.tex,[4 0]); myscreen.flushMode = 1;
Note that in this case you do not do any drawing in the screenUpdateCallback (this function will be empty). You only do drawing in the startSegmentCallback. This assumes that the only time the screen changes is when you start a new segment of your trial.
getResponseCallback (optional)
You can (optionally) define a callback for when the subject hits a response key:
function [task myscreen] = getResponseCallback(task,myscreen)
If you don't have subject responses in your experiment, you can just put this one line in with nothing after it.
There is a field called
task.thistrial.whichButton
This will get filled with which button was pressed (a number from 1-9). Note that if two keys are pressed down at the same time, it will only return the first in the list (e.g. if 1 and 2 are simultaneously pressed, it will return 1).
If you want to get all the keys that are pressed, you can look at
task.thistrial.buttonState
This will be an array where each element will have 0 or 1 depending on whether the key was pressed or not.
Note that the getResponseCallback will only be called if in the task structure you have set the appropriate segment of the getResponse variable. For example, if you have a two segment trial, and you want to get subject responses in the second segment of the trial you would do:
task.getResponse = [0 1];
You may also set a getResponse segment to 2. What this does is similar to setting myscreen.flushMode = 1. It prevents mglFlush from being called to update the screen while you are waiting for a keyboard press. This will get much more accurate keyboard timing, but will not allow the screen to update while you are waiting (i.e. you have to have a static display–no moving dots or flickering gratings or anything).
task.getResponse = [0 2];
If you want to get other keys, rather than the defined keys 1-9, for example if you want the keypad numbers, you can override which keys will be checked with:
myscreen.keyboard.nums = [84 85]; myscreen = initScreen(myscreen);
This is called at the beginning of your program. Note that to get the keycodes that correspond to a key, you can either use:
mglCharToKeycode({'a' 'b' 'c'})
or, for keys that you can't write like the keypad numbers or the esc key, run the program:
mglTestKeys
and type the keys you want and it will print out the correct keycode.
The getResponseCallback will get called every time the subject presses a button, so if the subject presses two buttons one after the other during the response period, getResponseCallback will be called twice. If you want to ignore the 2nd button press you can do:
if task.thistrial.gotResponse == 0 %your response code here end
task.thistrial.gotResponse will be set to 1 the second time the subject presses a key.
startTrialCallback (optional)
You can (optionally) define a callback that gets called at the beginning of each trial
function [task myscreen] = startTrialCallback(task,myscreen);
endTrialCallback (optional)
You can (optionally) define a callback that gets called at the end of each trial
function [task myscreen] = endTrialCallback(task,myscreen);
startBlockCallback (optional)
You can (optionally) define a callback that gets called at the beginning of a block
[task myscreen] = startBlockCallback(task,myscreen)
Saving data into a stimfile
Stimfiles
After you have run an experiment, all three variables (myscreen, task and your stimulus variable) will get saved into a file called
yymmdd_stimnn.mat
Where yymmdd is the current date, and nn is a sequential number starting at 01. This file will be stored in the current directory or in the directory ~/data if you have one.
After these get saved, you can access all the variables for your experiment by using
e = getTaskParameters(myscreen,task);
This will return a structure that contains the starting volume of each trial, what each variable was set to, the response of the subject and reaction time, among other things. For most purposes this should contain all the information you need to reconstruct what was presented on what trial and what the subject's response was.
Note that there is a variable called myscreen.saveData which tells the task structure whether to save the stim file or not. The default on your computer is probably set not to save the stim file. When you run on the computer in the scanner room, it will save the file automatically. For debugging purposes this is usually what you want so that you don't save unnecessary stim files every time you test your program. However if you want to save the stim file on your test computer to look at, you can add the following to your code where you call initScreen:
myscreen.saveData = 1; myscreen = initScreen(myscreen);
The variables stored in the stim file contain all the information you should need to recreate what happened in your experiment. In fact, it even contains a full listing of the file you used when running the experiment. This is useful since often you might make minor changes to the program and forget what version you were using when you ran an experiment. You can access a listing from the task variable:
task{1}{1}.taskFileListing
Directory to save stimfiles in
By default, mgl will save the data in ~/data if that directory exists, and in the current directory if ~/data doesn't exist. To save data to a specific directory instead of to these defaults, set
myscreen.datadir = datadirname;
where datadirname is the full path of the desired directory.
Retrieving data from stimfiles
getTaskParameters
usage: e = getTaskParameters(myscreen,task);
purpose: Gets all the info about your task and its parameters
| argument | value |
|---|---|
| myscreen | The myscreen variable saved in your stimfile |
| task | The task variable saved in your stimfile. This can be a cell array (task with multiple phases) or a cell array of cell arrays (multiple tasks with multiple phases) or a structure (single task, single phase). |
| return argument | value |
|---|---|
| e | A structure or cell array of structures that contains information about the task you run, including how each parameter and randVar was set on each trial, the stimvol for each trial, reaction times for each trial etc. |
See also here
getStimvol
usage: [stimvol stimNames var] = getStimvol(v,'varname',<taskNum=?>,<phaseNum=?>,<segmentNum=?>);
purpose: Gets the stimvols for a variable. This function is available in mrTools. It will call getStimvolFromVarname on each stimfile for the scan and put all the stimvols together. This is useful for a concatenated scan that has multiple stimfiles since it will create the stimvols correctly for the concatenation – honoring junked frames for instance.
| argument | value |
|---|---|
| v | A view variable returned by newView in mrTools. Make sure to set its curGroup and curScan to the group and scan that you want to get the stimvols for. |
| varname | The name of the variable you want to get stimvols for (see getStimvolFromVarname for all the options that you can use to select subsets of values of the variable |
| taskNum | This is an optional argument for when you have multiple tasks, select which task you want by passing in an argument 'taskNum=2' for instance. |
| phaseNum | This is an optional argument for when you have multiple phases within a task, select which phase you want by passing in an argument 'phaseNum=3' for instance. |
| segmentNum | This is an optional argument for when you have want to get volumes relative to a specific segment of a trial rather than from the beginning of the trial. Select which segment you want by passing in an argument 'segmentNum=2' for instance. |
| return argument | value |
|---|---|
| stimvol | A cell array of arrays. The cell array has one array for each setting of the variable. Each array contains the volume numbers for the times at which the variable in question was set to that particular value. |
| stimNames | A cell array of strings that contains a description of what that particular stimulus setting is. |
| var | A structure that contains the varname, taskNum etc. that you used to get the stimvols |
An example:
v = newView; v = viewSet(v,'curGroup',3); v = viewSet(v,'curScan',1); [stimvol stimNames var] = getStimvol(v,'varname','taskNum=2','phaseNum=2');
getStimvolFromVarname
usage:[stimvol stimNames] = getStimvolFromVarname(varnameIn,myscreen,task,taskNum,phaseNum,segmentNum);
purpose: Gets a cell array that contains the stimulus volumes for a particuar variable name
| argument | value |
|---|---|
| varnameIn | The variable name that you want to get stimvols for. See below for more information on how this value can be set. |
| myscreen | The myscreen variable saved in your stimfile |
| task | The task variable from your stimfile |
| taskNum | This is an optional argument for when you have multiple tasks. Set to the number of the task you want. |
| phaseNum | This is an optional argument for when you have multiple phases within a task. Set to the number of the phase you want. |
| segmentNum | This is an optional argument for when you have want to get volumes relative to a specific segment of a trial rather than from the beginning of the trial. Set to the number of the segment you want. |
| return argument | value |
|---|---|
| stimvol | A cell array of arrays. The cell array has one array for each setting of the variable. Each array contains the volume numbers for the times at which the variable in question was set to that particular value. |
| stimNames | A cell array of strings that contains a description of what that particular stimulus setting is. |
varnameIn can be the name of a parameter or randVar. e.g.:
getStimvolFromVarname('dir',myscreen,task,2,2);
It can also be of the form varname(indexVar). For when you have used a parameterCode and an index variable. e.g.:
getStimvolFromVarname('localDir(dirIndex)',myscreen,task);
Or it can be _all_ which returns all trial numbers regardless of stimulus type:
getStimvolFromVarname('_all_',myscreen,task);
If varnameIn is a cell array, then you can specify a set of matching conditions. For example, the following would return the stimvols for when var1 = 1 *and* var2 = either 2 or 3:
{'var1=[1]','var2=[2 3]'}
If you want to return multiple sets of stimvols with matching conditions, you can make a cell array of cell arrays of the type form above. For example:
{{'var1=[1]','var2=[2 3]'},{'var1=[2]','var2=[1]'}}
getTaskVarnames
usage:varnames = getTaskVarnames(task);
purpose: Gets a cell array of the variables names in your task. This function can also accept an MLR view instead of the task variable.
| argument | value |
|---|---|
| task | The task variable from your stimfile |
| return argument | value |
|---|---|
| varnames | A cell array of strings containing the names of parameters and randVars from your task |
getParameterTrace
usage: trace = getParameterTrace(myscreen,task,'varname');
purpose: Gets a trace of the variable called for. The time base for the trace is in screen refreshes.
e.g.: plot(getParameterTrace(myscreen,task,'dirnum'));
| argument | value |
|---|---|
| myscreen | The myscreen variable saved in your stimfile |
| task | The task variable from your stimfile |
| varname | The variable that you want to create a trace for |
| return argument | value |
|---|---|
| trace | A vector containing the value of the variable as a function of time in screen refreshes |
getVarFromParameters
usage: [varval taskNum phaseNum] = getVarFromParameters('varname',e);
purpose: Gets the variable settings for each trial
| argument | value |
|---|---|
| varname | The name of the variable of interest |
| e | A structure retruned from getTaskParameters |
| return argument | value |
|---|---|
| varval | An array of what the particular variable was set to on each trial |
| taskNum | The number of the task in which the variable was defined. If defined in multiple tasks will return the last task that it was defined in. To get another task, useg getTaskParameters to select which task to get information from. |
| phaseNum | The number of the phase in which the variable was defined |
makeTraces
For most people, using getTaskParameters is the easiest way to get what happened on each trial. But there is another mechanism that allows you to see the specific timing of events as traces. This is saved in the traces field of the myscreen variable. This field stores when each volume was collected and what stimulus was presented. Using this information you can reconstruct the volume when each stimulus occurred. It is set up so the first row contains an array which has a one every time a volume was acquired (i.e. whenever a backtick was received) and zeros elsewhere. The timebase for the array is in monitor refreshes, so every 60 elements shouls be one second. Take a look at what this trace has by doing:
myscreen = makeTraces(myscreen); plot(myscreen.traces(1,:));
You can also plot in seconds, relative to the beginning of the experiment:
plot(myscreen.time,myscreen.traces(1,:));
The other important trace is the one corresponding to myscreen.stimtrace:
plot(myscreen.traces(myscreen.stimtrace,:));
This will contain the information about which trial was presented as long as you have set the writeTrace variable correctly (see next section).
Integrating eye tracking with a task
There is a generic interface to the eye tracking functionality. As long as the appropriate callback functions are written for an eye tracker, no code needs to be changed in your task.
To configure MGL to use an eye tracker you must initialize the eye tracker support. The eye tracker configuration is specified in the eyetracker field of myscreen.
You should start by specifying whether to save the data (in a file) and what data you need.
myscreen.eyetracker.savedata = true; myscreen.eyetracker.data = [1 1 1 0]; % don't need link events
You also need to specify one of your tasks (or which of your phases) you want to use to control the eye data timing. This task will define what is a block, trial, etc.
task{1}.collectEyeData = true;
Next you initialize MGL's eye tracker support for your eye tracker.
myscreen = initEyeTracker(myscreen, 'Eyelink');
And finally you need to run a calibration.
myscreen = calibrateEyeTracker(myscreen);
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). It can also be used with a Minolta or TopCon photometer/colorimeter. 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].
There are a few points that you should pay special attention to:
- 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.
- 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
Moncalib can talk directly through the USB/serial port to the PhotoResearch PR650, Topcon SR-3A, Minolta CS-100a or Minolta LS-100. If you want to use another photometer you can input the readings manually or consider writing a few lines of code in moncalib to support your photometer type.
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 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:
- 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-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:
- Set up a 'debug' display using mglEditScreenParams
- Type mglEditScreenParams in matlab.
- Click to Add a new display on the first dialog box that appears.
- Hit Ok to edit that display.
- After that set its displayName to “debug”.
- 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.
- You can test the settings by hitting the “Test screen params” button.
- 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.
- 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');
- Manually code settings
- set the myscreen.screenNumer field to 0;
- choose the size and position of the mgl window in the current display
- set those in the variable myscreen
- 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
Before calling initScreen, you can set the myscreen variable to have a subjectID:
myscreen.subjectID = 's001'; myscreen = initScreen(myscreen);
When this runs it will create a directory:
~/data/taskprogramname/subjectID
in which the data will be saved. Make sure that subjectID and your task program name are valid directory names. If subjectID=[], then the data will be saved to:
~/data/taskprogramname
You can also specify a folder name underneath the subjectID if you want, by adding:
myscreen.subjectFolder = 'folderName';
before running initScreen. This will make the dataDir:
~/data/taskprogramname/subjectId/subjectFolder
You can also load the latest stimfile from that directory (if you want to continue a staircase for example), by doing:
s = getLastStimfile(myscreen);
Task function reference
mglEditScreenParams
availability: mgl 2.0
You can now use a GUI to set all of your screen parameters so that when you call initScreen everything is customized for your system. The settings will be stored across matlab sessions in a file called .mglScreenParams in your home directory. If you had been passing in a screenParams field to initScreen in your mgl program, you should convert to using mglEditScreenParams. To set the parameters you need to have mrTools installed (see Getting Started).
Start by setting your parameters:
mglEditScreenParams
This will bring up a dialog that looks like this:
This first dialog box allows you to manage all of the displays that you have for your computer. You can keep multiple different screen parameters if you have multiple displays on your computer for instance (one for each display). You may also want to keep different display settings for different uses, for example if you like to debug your code with a windowed context, then create another display parameter for the windowed context and set the displayName to say, debug. Then when you run initScreen you can select which display settings to use by passing in the displayName. For example, initScreen('debug').
In this dialog box you can add, delete or restore default settings for each display setting.
Next, you should edit the screen parameters for your desired display by selecting the display and clicking OK. You should see a dialog like this:
Here you can set all of your screen parameters (including the displayName which is how you access the settings with initScreen). You can test the settings using the “testSettings” button. The help button gives you information about all the settings. Here is a list of what the settings do:
| Name | Function |
|---|---|
| computerName | The name of the computer for these screen parameters |
| displayName | The display name for these settings. This can be left blank if there is only one display on this computer for which you will be using mgl. If instead there are multiple displays, then you will need screen parameters for each display, and you should name them appropriately. You then call initScreen(displayName) to get the settings for the correct display |
| useCustomScreenSettings | If you leave this unchecked then mgl will open up with default screen settings (i.e. the display will be chosen as the last display in the list and the screenWidth and ScreenHeight will be whatever the current settings are). This is sometimes useful for when you are on a development computer – rather than the one you are running experiments on |
| screenNumber | The screen number to use on this display. 0 is for a windowed contex. Less than 1 gives a transparent windowed context |
| screenWidth | The width in pixels of the screen |
| screenHeight | The height in pixels of the screen |
| framesPerSecond | Refresh rate of monitor |
| displayDistance | Distance in cm to the display from the eyes. This is used for figuring out how many pixels correspond to a degree of visual angle |
| displaySize | Width and height of display in cm. This is used for figuring out how many pixels correspond to a degree of visual angle |
| displayPos | This is only relevant if you are using a windowed context (e.g. screenNumber=0). It will set the position of the display in pixels where 0,0 is the bottom left corner of your display. |
| autoCloseScreen | Check if you want endScreen to automatically do mglClose at the end of your experiment. |
| flipHorizontal | Click if you want initScreen to set the coordinates so that the screen is horizontally flipped. This may be useful if you are viewing the screen through mirrors |
| flipVertical | Click if you want initScreen to set the coordinates so that the screen is vertically flipped. This may be useful if you are viewing the screen through mirrors |
| hideCursor | Click if you want initScreen to hide the mouse for this display. |
| backtickChar | Set the keyboard character that is used a synch pulse from the scanner. At NYU this is the backtick character. If you use a different character enter it here. If you enter a number then this will be interpreted as a character code (see mglCharToKeycode). |
| responseKeys | Sets which keys you want to use for response keys. This should be a space delimited string, for example: 1 2 3 4 5 6 7 8 9 is the default, which uses the number keys. If you want to use keycodes, for example to use the numberic key pad, you can do: k84 k85 k86 k87 k88 k89 k90 k92 k93 |
| eatKeys | Sets whether to eat keys. That is, any key that initScreen uses, for example the response keys and the backtickChar will not be sent to the terminal. See the function mglEatKeys for more details. |
| saveData | Sets whether you want to save an stim file which stores all the parameters of your experiment. You will probably want to save this file for real experiments, but not when you are just testing your program. So on the desktop computer set it to 0. This can be 1 to always save a data file, 0 not to save data file,n>1 saves a data file only if greater than n number of volumes have been collected) |
| calibType | Choose how you want to calibrate the monitor. This is for gamma correction of the monitor. Find latest calibration works with calibration files stored by moncalib, and will look for the latest calibration file in the directory task/displays that matches this computer and display name. If you want to specify a particular file then select that option and specify the calibration file in the field below. If you don''t have a calibration file created by moncalib then you might try to correct for a standard gamma value like 1.8 (mac computers) or 2.2 (PC computers). Or a gamma value of your choosing |
| calibFilename | Specify the calibration filename. This field is only used if you use Specify particular calibration from above |
| diginPortNum | Click this if you want to use the National Instruments cards digital io for detecting volume acquisitions and subject responses. If you use this, the keyboard will still work (i.e. you can still press backtick and response numbers. This uses the function mglDigIO – and you will need to make sure that you have compiled mgl/util/mglPrivateDigIO.c |
| diginAcqLine | This is the line from which to read the acquisition pulse (i.e. the one that occurs every volume) |
| diginAcqType | This is how to interpert the digial signals for the acquisition. If you want to trigger when the signal goes low then set this to 0. If you want trigger when the signal goes high, set this to 1. If you want to trigger when the signal changes state (i.e. either low or high), set to [0 1] |
| diginResponseLine | This is the lines from which to read the subject responses. If you want to specify line 1 and line 3 for subject response 1 and 2, you would enter [1 3], for instance. You can have up to 7 different lines for subject responses. |
| diginResponseType | This is how to interpert the digial signals for the responses. If you want to trigger when the signal goes low then set this to 0. If you want trigger when the signal goes high, set this to 1. If you want to trigger when the signal changes state (i.e. either low or high), set to [0 1] |
| testDigin | Click to test the digin settings |
| testSettings | Click to test the monitor settings |
initScreen
purpose: initializes the screen
usage: myscreen = initScreen(<myscreen>,<randstate>)
| argument | value |
|---|---|
| myscreen | Contains any desired initial parameters, can be left off if you are just using all defaults |
| randstate | Sets the initial status of the random number generator. This can either be an integer value, or it can be the field myscreen.randstate to set the state back to what it was on a particular experiment. |
This function initializes the screen by calling mglOpen, and also handles a number of different default initialization procedures such as setting up the gamma table with the correct linearization table. You should call this once at the beginning of the experiment. The variable myscreen will contain many fields associated with the status of the screen and records events like volume acquisitions and trial/segment times etc.
There are a number of parameters that will be specific to your computer/display that you can set by setting the screenParams field of myscreen to save information about your setup:
myscreen.screenParams{1} = {'yoyodyne.cns.nyu.edu',[],2,1280,1024,57,[31 23],60,1,1,[],[],[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. You select which display you are working on by setting myscreen.displayname to the string stored here)
- 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)
MGL 2.0: The initScreen program looks for a file called by default mgl/task/mglScreenParams.mat which contains a cell array of computer display information parameters like the above (as long as you don't set myscreen.screenParams before calling initScreen). This way you can save a file which contains information about all the computers in your lab and always load that up rather than having each stimulus program contain all the information. You can do this by changing the name of the file that initScreen loads by setting:
screenParams{1} = {'yoyodyne.cns.nyu.edu',[],2,1280,1024,57,[31 23],60,1,1,[],'yoyodyne',[0 0]};
screenParams{2} = {'terranova.bnf.brain.riken.jp',[],0,800,600,57,[31 23],60,1,0,[],[],[0 0]};
save ~/Desktop/myScreenParams.mat screenParams
mglSetParam('screenParamsFilename','~/Desktop/myScreenParams.mat');
initStimulus
purpose: initializes the global stimulus variable name
usage: myscreen = initScreen('stimulusName',myscreen);
| argument | value |
|---|---|
| stimulusName | string that contains the name of the global variable that is used for your stimulus (i.e. if you had global stimulus, then this should be 'stimulus') |
| myscreen | myscreen variable returned by initScreen |
Note that this function, only needs to be called if you want to save the stimulus in your stim file. Since stimulus is a global variable, if you call this function, at the end of the experiment it will get the global variable with the name you specified here and save it in your stim file. If you do not need to save your stimulus variable, you do not need to call this function.
initTask
purpose: initializes a task variable
usage: [task myscreen] = initTask(task,myscreen,startSegmentCallback,screenUpdateCallback, trialResponseCallback, <startTrialCallback>, <endTrialCallback>, <startBlockCallback>)
| argument | value |
|---|---|
| task | Parameters for the particular task (note this must be a struct not a cell array, for a cell array, call initTask fore each element of the cell array. |
| myscreen | Variable returned by initScreen |
| startSegmentCallback | Function pointer that will be called at start of a segment |
| screenUpdateCallback | Function pointer that will be called every screen update (i.e. for a 60Hz buffer once every 1/60 of a second) |
| trialResponseCallback | Function pointer that will be called when the subject responds and getResponse is set |
| startTrialCallback | Function pointer that will be called at start of a trial |
| endTrialCallback | Function pointer that will be called at end of a trial |
| startBlockCallback | Function pointer that will be called at start of a block |
The task variable gets set up as explained above. Here is a list of valid fields:
| field | value |
|---|---|
| verbose | display verbose message when running tasks (probably shouldn't be set for real experiment since print statements can be slow) |
| parameter | task parameters |
| seglen | array of length of segments (used when not using segmin and segmax) |
| segmin | array of minimum length of segment |
| segmax | array of maximum length of segment |
| segquant | array of quantization of segment lengths (used with segmin and segmax–i.e. if you want segments to be randomized in steps of 0.6 seconds, then set the sequant for that segment to be 0.6) |
| synchToVol | array where one means that the segment will synch to the next volume acquisiton once the segment is finished. |
| getResponse | array where one means to get subject responses during that segment, set to zero means that subject responses will be ignored and the responseCallback will not be called |
| numBlocks | number of blocks of trials to run before stopping |
| numTrials | number of trials to run before stopping |
| waitForBacktick | wait for a backtick before starting task phase |
| random | randomize the order of parameters for each trial when set to 1, otherwise have the parameters go in order |
| timeInTicks | when set to 1, segment legnths are in screen updates (not in seconds) |
| timeInVols | when set to 1, segment lengths are in volumes (not in seconds) |
| segmentTrace | internal variable that controls what trace this task will use to save out segment times (usually you will not set this) |
| responseTrace | internal variable that controls what trace this task will use to save out subject responses (usually you will not set this) |
| phaseTrace | internal variable that controls what trace this task will use to save out the phase number (usually you will not set this) |
| parameterCode | For parameters that have groups |
| private | A parameter that you can do whatever you want with |
| randVars | random variables |
| fudgeLastVolume | When you synchToVol or keep time in volumes, and want to have the experiment run for a set number of trials, the experiment won't usually end because in the last segment it is waiting for a volume to come in that never will. If you set this to 1, it will fudge that last one so that the experiment ends one TR after the last volume is aquired. |
updateTask
purpose: updates the task
usage: [task myscreen phaseNum] = updateTask(task,myscreen,phaseNum)
| argument | value |
|---|---|
| task | task variable, note task must be a cell array. If you only have one task phase, make phaseNum=1 and task a cell array of length one. |
| myscreen | myscreen variable returned by initScreen |
| phaseNum | The task phase you are currently updating. If you only have one phase, set to 1, for multiple phases, update Task will take care of switching from one phase to the next. |
tickScreen
purpose: updates the screen
usage: [myscreen task] = tickScreen(myscreen,task);
| argument | value |
|---|---|
| myscreen | myscreen variable returned by initScreen |
| task | task variable |
This function calls mglFlush to update the screen when it is needed and also checks for volumes and keys etc. Called with in main loop.
upDownStaircase
Implements a staircase for control of stimulus variable values. Type 'help upDownStaircase' for details. Also see taskTemplateFlashingStaircase.m and taskTemplateStaticStaircase.m for examples of using this function.
jumpSegment
Allows you to force a move to the next segment or the next trial:
task = jumpSegment(task) % this will end the segment and move to the next one
task = jumpSegment(task,inf) % this will end the trial and start a new trial
Eyelink
Overview
You can use the Eyelink eye tracker with mgl and the task code. First, make sure that the mgl eyelink is compiled by following instructions here.
- Using mglEditScreenParams select use of the Eyelink by setting eyeTrackerType to Eyelink.
- Set parameters such as the number of calibration points you want to use, sample rate and what data you want to save, by using mglEyelinkParams.
- In your experiment make sure to call eyeCalibDisp (see the task program taskTemplateSaccade for an example)
myscreen = eyeCalibDisp(myscreen);
- This will run the calibration routine, see under mglEyelinkSetup for a full reference on text commands that can be used to calibrate the scanner. As a quick guide, the basic procedure goes something like this.
- Hit ENTER to bring up an image of the eye
- Make sure that the eye is centered appropriately.
- Hit ESC to close this image of the eye.
- HIT C to run the calibration routine. You will need to hit enter after fixating the first fixation point, after that you should just be able to fixate each fixation point in turn and it will go through the calibration.
- Hit Enter to accept the calibration.
- Hit ESC to end the calibration routine and start your stimulus program.
- Upon ending your stimulus program, mgl will save a file with an edf extension that stores the eye position information.
- You can now read the saved data using getTaskEyeTraces.
To test this whole procedure, you may wish to use the stimulus program taskTemplateSaccade. This program has the subject saccade to a number of eccentric positions and back to the fixation. After you are done, you should have saved eye traces that look like ones below.
mglEyelinkParams
A simple GUI that allows you to set parameters for the EyeLink eye tracker. You can set the whether you want 5 or 9 calibration points (set calibrationType to HV5 or HV9, respectively). You can set the sample rate and which data you want to save.
getTaskEyeTraces
getTaskEyeTraces can be used to load the eye tracker data. It works like getTaskParameters, but also returns the eye traces sorted by trial. You can load up the data for an experiment. For example, from a run of taskTemplateSaccade
>> getTaskEyeTraces('100811_stim01')
(getTaskEyeTraces) Opening edf file /Users/justin/data/eyetracker/10081101.edf took 3 secs 374 ms
(getTaskEyeTraces) Extracting trial by trial data for 63 trials took 0 secs 842 ms
(getStimvolFromVarname) taskNum=[1], phaseNum=[1], segmentNum=[1]
(getstimvol) Same trial in multiple conditions.
(getStimvolFromVarname) targetAngle=0.0000000 targetRadius=8.0000000: 3 trials
(getStimvolFromVarname) targetAngle=45.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=90.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=135.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=180.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=225.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=270.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=315.0000000 targetRadius=8.0000000: 4 trials
(getStimvolFromVarname) targetAngle=0.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=45.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=90.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=135.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=180.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=225.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=270.0000000 targetRadius=12.0000000: 4 trials
(getStimvolFromVarname) targetAngle=315.0000000 targetRadius=12.0000000: 4 trials
This will return a structure similar to the one returned by getTaskParameters except there will be an “eye” field:
ans =
nTrials: 63
trialVolume: [1x63 double]
trialTime: [1x63 double]
trialTicknum: [1x63 double]
trials: [1x63 struct]
blockNum: [1x63 double]
blockTrialNum: [1x63 double]
response: [1x63 double]
reactionTime: [1x63 double]
originalTaskParameter: [1x1 struct]
responseVolume: [1x63 double]
parameter: [1x1 struct]
stimfile: [1x1 struct]
eye: [1x1 struct]
This will have the eye position data sorted by trial:
ans =
hPos: [63x2002 double]
vPos: [63x2002 double]
pupil: [63x2002 double]
time: [1x2002 double]
It will also display a figure (this can be turned off by setting the input parameter dispFig=0) of the eye position data sorted by trial type
Sample programs can be found in the /Task subfolder, and include:- taskTemplate (Stripped down version that contains only the functions you need to write a task. This is a good starting place for any new experiment you want to program).
- taskTemplateContrast10bit An example of how to implement a grating contrast display that has 10 bits of luminance by using the gamma tables. The limitation is that you can only show 8bits of luminance at any given time on the screen.
- taskTemplateDualMain and taskTemplateDualSubsidiary These provide an example of how to program a dual task.
- taskTemplateStaircase An example of implementing a staircase.
- testExperiment An example program with moving dots.
- taskTemplateSaccade A simple example program useful for testing eye tracking. It puts up saccade targets and saves eye tracking data.
- taskTemplateGazeContingent An example of how to make a gaze contingent display.
When things don’t work, there are some very simple bugs you should check for before losing hope.
Return variables
Many of the routines return multiple values–it is really important that you receive these properly. For example, initTask returns both the task and myscreen. Make sure that you call it as follows:
[task{1} myscreen] = initTask(task{1},myscreen,@startSegmentCallback,@screenUpdateCallback,@responseCallback);
If you did not have the myscreen in the left hand side, then initTask will not set fields (primarly myscreen.stimtrace) correctly in your myscreen–which can make saving out traces incorrect. Look out for this not only in initTask but in any function like updateTask to make sure you are always setting the appropriate return variables correctly (check the help on the function–or model your calls after the ones in the templates).
Screen flashes
Sometimes the screen might appear to flash momentarily with your stimulus in a way you don't expect. Often this is due to not calling mglClearScreen at the appropriate place (like at the beginning of a segment or at the beginning of the screenUpdateCallback). The reason for this occurring is often because you are drawing to the back buffer of the double buffered screen and it already has something from before when it was being displayed. Clearing the screen as you start drawing will insure that you don't have any junk from the last screen draw that will show up.
You may also notice screen flashes if you use mglSetGammaTable to reset the gamma table at certain points in your experiment. mglSetGammaTable relies on Mac OS X functions that are not part of the Open GL library, and therefore will not necessarily have the correct timing. Sometimes the gamma table will get set too soon and sometimes too late. If you notice this problem you should add a brief dummy segment in your trial to set the gamma table before the segment that contains your stimuli. Minimizing the command window during the experiment has also been found to substantially reduce these timing issues.
Matlab control
Don't let the window from which you are calling your code be on the screen that is taken over by MGL, or you will lose the ability to stop the code from running or exit if your code is interupted by an error. If you do this, you can always type option-command-ESC (brings up Force-quit applications) to close the window. This will also quit your matlab session.
Clear all
Mgl keeps the status of the display in the global variable called MGL. You can look at the values set in MGL by doing:
global MGL MGL
Be aware that if you clear the MGL variable then mgl will no longer know the status of the screen. The current version handles this by closing the display if you clear the MGL variable. If for some reason, you get stuck with an open display that you cannot close, you can try to call mglOpen again and then close. If you have reset the gamma table, you can go into your System Preferences/Displays/Color and reset the gamma table back to normal there. On Linux, you can use xgamma to set the gamma table, or easier, use NVIDIA's settings manager or the ATI settings manager to restore gamma tables. If you still cannot close the display, you can always do option-open apple-escape and quit out of matlab (on Mac). On Linux, Ctrl-C may work, or you may have to kill the matlab process from a terminal. If you only have one screen and it is locked, you can type Ctrl+Alt+F5 to drop out of the X server temporarily; this will allow you to login and kill the Matlab process without restarting X (Ctrl+Alt+F7 to return to X).
Spelling, capitalization, and periods
If an important variable (e.g. stimulus) is misspelled (e.g. sitmulus), or a variable or function name mis-capitalized (e.g. inittask instead of initTask), or you forget to put the period between a variable name and a field name (e.g. stimuluscontrast instead of stimulus.contrast) things won’t work and it’ll be very confusing! MGL will check for some things that it knows about, but it can’t know how you’ve named your variables…
Your task doesn't exit when it's over
First, make sure you've specified a certain number of trials, otherwise it'll go on forever.
Then, make sure you have a while loop around the updateTask call checking the phase number. Even if you only have one phase, you still need to give updateTask a phaseNum variable as input, because the code will only end when the phaseNum gets too big for the while loop (and updateTask increases the phaseNum after all the trials have been run)
Set the data directory
So as to always know where your data will be saved, it's good to set the data directory by doing:
myscreen.datadir = datadirname;
where datadirname is the full path of the directory to which you'd like the data to be saved.
Make sure your displays aren’t mirrored.
Sometimes nothing at all will happen. This can be because your screens are mirrored.
Some common error messages that have to do with cell arrays and how to fix them
Error message saying you haven't specified task.segmin and task.segmax
Make sure you've called initTask with the exact task and phase. For example:
task{1}.seglen = 1;
task{1} = initTask(task{1},myscreen,@startSegmentCallback,@screenUpdateCallback,@responseCallback);
or
task{1}{1}.seglen = 1;
task{1}{1} = initTask(task{1}{1},myscreen,@startSegmentCallback,@screenUpdateCallback,@responseCallback);
Error message saying 'Cell contents reference from a non-cell array object,'
Make sure you are calling updateTask with a cell array. This can be confusing, because even if you only have one task and only one phase, you still need to define task{1} (rather than just 'task') and then call updateTask with 'task' - e.g.:
task{1}.seglen = 1;
task{1} = initTask(task{1},myscreen,@startSegmentCallback,@screenUpdateCallback,@responseCallback);
phaseNum = 1; while (phaseNum <= length(task)) && ~myscreen.userHitEsc [task myscreen phaseNum] = updateTask(task,myscreen,phaseNum); end
If you're running two tasks, and need to differentiate them, then even if they each only have one phase, you must define task{1}{1} and task{2}{1} and then call updateTask with task{1} and task{2}. For example:
task{1}{1}.seglen = 1;
task{1}{1} = initTask(task{1}{1},myscreen,@startSegmentCallback,@screenUpdateCallback,@responseCallback);
task{2} = setSecondTask(myscreen); % as long as setSecondTask returns a cell array
phaseNum = 1;
while (phaseNum <= length(task{1})) && ~myscreen.userHitEsc
[task{1} myscreen phaseNum] = updateTask(task{1},myscreen,phaseNum);
[task{2} myscreen] = updateTask(task{2},myscreen,1);
end
Error message saying 'Attempt to reference field of non-structure array,'
Make sure that you call updateTask and also return from updateTask with the same cell array (see above).
Error message 'Input argument "tnum" is undefined,'
Make sure you are passing in a phaseNum argument in your updateTask function call.
Problem starting/stopping the eye tracker
To control the ASL eye tracker through digital I/O you must have the file
mgl/task/utils/readDigPort/writeDigPort.c
mex'd. You can do
cd mgl/task/utils/readDigPort mex writeDigPort.c
This compiles the program to send the digital pulse to the eye tracker. Also, if your task crashes at the beginning after it prints out
Dev1/port2
or something similar to that, you probably just need to recompile writeDigPort.c. If you continue to have problems and want to give up, you can delete the mexfile writeDigPort.mexmac and then mgl won't try and set the digital port (This is the default condition that the mgl library is in, because most computers don't have the NI card installed).
Contributors
Design and Implementation
- Justin Gardner (Mac OS X, OpenGL, sample programs, documentation, mgl/utils and mgl/task)
- Jonas Larsson (Linux, OpenGL, Mac OS X and mgl/utils)
Other contributors
- Christopher Broussard (Mac OS X, Windows)
- Denis Schluppeck (OpenGL and documentation)
- Eli Merriam (OpenGL, EyeLink tracker interface)
- Eric DeWitt (mgl/task, EyeLink tracker interface)
- Shani Offen (sample programs and documentation)
- Franco Pestilli (sample programs and documentation)




