Diff for "ScannerSyncDocumentation" - Methods
location: Diff for "ScannerSyncDocumentation"
Differences between revisions 1 and 2
Revision 1 as of 2007-07-26 12:46:46
Size: 16674
Editor: RhodriCusack
Comment:
Revision 2 as of 2007-07-26 12:53:28
Size: 17889
Editor: RhodriCusack
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
= Scanner synchronisation from Visual Basic/Eprime/Matlab =
Documentation for Version 1.3.7. Documentation last updated 23/11/04
'''Scanner synchronisation from Visual Basic/Eprime/Matlab [[BR]]
'''Documentation for Version 1.3.7. Documentation last updated 23/11/04 [[BR]]
Line 5: Line 5:
== Updates ==

' Changed 17/5/00 by Rhodri
' Added version command
' Added "Class_Terminate"
'''Updates
'''
' Changed 17/5/00 by Rhodri [[BR]]
' Added version command [[BR]]
' Added "Class_Terminate" [[BR]]
Line 12: Line 12:
' 1.0.1 ' 1.0.1 [[BR]]
Line 15: Line 15:
' 1.0.2 ' 1.0.2 [[BR]]
Line 18: Line 18:
' 1.0.3 ' 1.0.3 [[BR]]
Line 21: Line 21:
' 1.0.4 ' 1.0.4 [[BR]]
Line 24: Line 24:
' OX1.04 ' OX1.04 [[BR]]
Line 27: Line 27:
' 1.2.0
' For move back to Cambridge, added local mode
'

' 1.2.1
' Modified to look for low-going pulse
'

' 1.3.0
' New image - not much different
'

' 1.3.1
' 1.2.0 [[BR]]
' For move back to Cambridge, added local mode [[BR]]
' [[BR]]

' 1.2.1 [[BR]]
' Modified to look for low-going pulse [[BR]]
' [[BR]]

' 1.3.0 [[BR]]
' New image - not much different [[BR]]
' [[BR]]

' 1.3.1 [[BR]]
Line 39: Line 39:
' 1.3.2 ' 1.3.2 [[BR]]
Line 42: Line 42:
' 1.3.3
' 12/5/03 - Added SetPretendModeExtended
'

' 1.3.4
' 18/5/03 - Added GetResponseExtended
'

' 1.3.5
' 29/4/03 - Changed calculation of interval to cope with initial over estimates of TR
'

' 1.3.6
' 9/11/04 - Modify StartExperiment so that it returns error code properly
' Add error trapping for board problems
'

' 1.3.7
' 23/11/04 - Added SetMSPerSample option which allows reduction of sample rate
' in StartExperiment routine to tackle Matlab misbehaviour.

1.3.9
12/7/06 Added support for new button boxes with GetResponseLeft and GetResponseRight

1.3.10
24/7/06 Added SSGetTimer command as difficult to read from perfpres dll from matlab

Notes Apr 2003
Some notes on how to calculate your stimulus timings for SPM:
(1) Beware that the “pulse number” is a zero-based index: pulse 0 is the first one, collected by the objSS.StartExperiment command. Pulse “1” is the second, usually collected by your first dummy scan objSS.SynchroniseExperiment command. I set it up like this because this is how SPM likes its timings: time 0” is the first scan.
(2) At the current date with Christians EPI sequences the pulse starts 50ms before the start of the first scan. If you wish to be precise, subtract 50ms from your timings.
' 1.3.3 [[BR]]
' 12/5/03 - Added SetPretendModeExtended [[BR]]
' [[BR]]

' 1.3.4 [[BR]]
' 18/5/03 - Added GetResponseExtended [[BR]]
' [[BR]]

' 1.3.5 [[BR]]
' 29/4/03 - Changed calculation of interval to cope with initial over estimates of TR [[BR]]
' [[BR]]

' 1.3.6 [[BR]]
' 9/11/04 - Modify StartExperiment so that it returns error code properly [[BR]]
' Add error trapping for board problems [[BR]]
' [[BR]]

' 1.3.7 [[BR]]
' 23/11/04 - Added SetMSPerSample option which allows reduction of sample rate [[BR]]
' in StartExperiment routine to tackle Matlab misbehaviour. [[BR]]
' [[BR]]
'
1.3.9 [[BR]]
'
12/7/06 - Added support for new button boxes with GetResponseLeft and 'GetResponseRight [[BR]]
' [[BR]]
'
1.3.10 [[BR]]
'
24/7/06 - Added SSGetTimer command as difficult to read from perfpres dll from matlab

'''Notes Apr 2003 [[BR]]
'''
Some notes on how to calculate your stimulus timings for SPM: [[BR]]
(1) Beware that the "pulse number" is a zero-based index: pulse "0" is the first one, collected by the objSS.StartExperiment command. Pulse "1" is the second, usually collected by your first dummy scan objSS.SynchroniseExperiment command. I set it up like this because this is how SPM likes its timings: time "0" is the first scan. [[BR]]
(2) At the current date with "Christian's EPI sequences" the pulse starts 50ms before the start of the first scan. If you wish to be precise, subtract 50ms from your timings. [[BR]]
Line 71: Line 71:
General description
These routines should provide a flexible way of synchronising with the scanner. They allow for a range of designs, and are hopefully easy to integrate with many experimental designs.
'''General description [[BR]]
'''These routines should provide a flexible way of synchronising with the scanner. They allow for a range of designs, and are hopefully easy to integrate with many experimental designs.
Line 76: Line 76:
It doesnt matter when you call the pulse measurement routines - you can do this once every 30 seconds, or when the experiment determines (e.g., in a timing where the subjects responses determine progress, this might end up at 24, 75 and 82 seconds). The only restrictions are that you do it at least once a minute, and dont mind waiting for up to the TR time for a pulse to come in (although you can use the routines in a slightly different way to get around this last restriction). It doesn't matter when you call the pulse measurement routines - you can do this once every 30 seconds, or when the experiment determines (e.g., in a timing where the subject's responses determine progress, this might end up at 24, 75 and 82 seconds). The only restrictions are that you do it at least once a minute, and don't mind waiting for up to the TR time for a pulse to come in (although you can use the routines in a slightly different way to get around this last restriction).
Line 80: Line 80:
The reason it is not necessary to count every pulse is that although the scanner doesnt quite give us the time we ask for, it is pretty accurate - say within 1%. Hence, if weve asked for a TR of 3s, and we happen to listen out for a pulse after 32s, and find one at 33.33, then we know this is the 11th pulse, and that the actual TR is 3.03s. To do this, we didnt have to know a priori that we were measuring the 11th pulse- the routines can calculate this if they are told the rough TR. They then use the measurements to improve their estimate of the actual TR. For this to work, there is one restriction: that the drift time is smaller than half a TR. If p is the drift in percent, T is the TR time, and N the number of trials between pulse measurement, we have
  N*TR*p/100 < TR/2
Rearranging N < 50/p
 
The reason it is not necessary to count every pulse is that although the scanner doesn't quite give us the time we ask for, it is pretty accurate - say within 1%. Hence, if we've asked for a TR of 3s, and we happen to listen out for a pulse after 32s, and find one at 33.33, then we know this is the 11th pulse, and that the actual TR is 3.03s. To do this, we didn't have to know ''a priori'' that we were measuring the 11th pulse- the routines can calculate this if they are told the rough TR. They then use the measurements to improve their estimate of the actual TR. For this to work, there is one restriction: that the drift time is smaller than half a TR. If ''p ''is the drift in percent, ''T ''is the TR time, and ''N'' the number of trials between pulse measurement, we have [[BR]]
  N*TR*''p/100 ''< TR/2 [[BR]]
Rearranging N < 50/p [[BR]]
''  [[BR]]''
Line 86: Line 86:
In practice, even if you measure the first pulse (worst case) the routines will know the TR to within a a millisecond, and as soon as youve synchronised once the experimental timings will be accurate to within 1 ms. Matt Davis suggested that, as he does with DMDX, you could use the dummy scans at the start of each experiment to measure the TR.
 

Using the routines
(1) Download latest version from
http://www.mrc-cbu.cam.ac.uk/~rhodri.cusack/scannersync.zip
In practice, even if you measure the first pulse (worst case) the routines will know the TR to within a a millisecond, and as soon as you've synchronised once the experimental timings will be accurate to within 1 ms. Matt Davis suggested that, as he does with DMDX, you could use the dummy scans at the start of each experiment to measure the TR. [[BR]]
 [[BR]]
'''Using the routines [[BR]]
'''(1) Download latest version from  [[BR]]
http://www.mrc-cbu.cam.ac.uk/~rhodri.cusack/scannersync.zip [[BR]]
Line 96: Line 95:
(3) At the top of the code in which you want to use the synchronisation routine, put
 Dim objSS as New ScannerSync
This will make the ActiveX object through which youll access all the commands. At the end of your code, or in an escape routine, call Terminate (see notes under this below).

A note about timing: the scanner synchronisation routines use the high performance counter clock in the perfpres.dll, and will be very upset if you reset it. You can read the time with GetTimer, but dont reset the clock with StartTimer. If you wish to measure an interval, then use relative code:

Dim dblMyTempTime as Double
dblMyTempTime=GetTimer()
.... thing to be timed....

Debug.Print The interval was & GetTimer() - dblMyTempTime

Dont forget to wrap time critical bits of code (including commands such as StartExperiment) in the two instructions to increase and decrease the thread priority (PumpUpTheThreadPriority and RestoreThreadPriority).

 

Command reference
The commands are described by category below.

Initialising and terminating the communication with the board
Function Initialize(strConfig As String) as Integer
Gets the system ready.
Parameters:
strConfig You no longer need this configuration file just specify “”

Returns:
0 if no error
(3) At the top of the code in which you want to use the synchronisation routine, put [[BR]]
 Dim objSS as New ScannerSync [[BR]]
This will make the ActiveX object through which you'll access all the commands. At the end of your code, or in an escape routine, call Terminate (see notes under this below).
'''
'''
A note about timing: the scanner synchronisation routines use the high performance counter clock in the perfpres.dll, and will be very upset if you reset it. You can read the time with GetTimer, but don't reset the clock with StartTimer. If you wish to measure an interval, then use relative code:

Dim dblMyTempTime as Double [[BR]]
dblMyTempTime=GetTimer() [[BR]]
.... thing to be timed.... [[BR]]

Debug.Print "The interval was " & GetTimer() - dblMyTempTime

Don't forget to wrap time critical bits of code (including commands such as StartExperiment) in the two instructions to increase and decrease the thread priority (PumpUpTheThreadPriority and RestoreThreadPriority).
'''
Command reference [[BR]]
'''The commands are described by category below.

'''
Initialising and terminating the communication with the board [[BR]]
''''''''
Function Initialize(strConfig As String) as Integer [[BR]]
'''''
Gets the system ready. [[BR]]
Parameters: [[BR]]
strConfig You no longer need this configuration file - just specify ""

Returns: [[BR]]
0 if no error [[BR]]
Line 123: Line 121:
Possible errors and causes:
Board in use. If you havent called the Terminate command at the end of running a program, youll get an error when you start the program again. If this happens, just close the program entirely (e.g., quit the VB editor if youre using it) and start it again. This will release the drivers.

DASSHL32.DLL not found. Youre using a computer without the Keithly drivers installed.

Example
If (objSS.Initialize(“”)<>0) Then End


Sub Terminate
Calling this routine is no longer necessary - the board communication is terminated when the ScannerSync object is destroyed.

Generally useful

Sub SetPretendMode(booEmulateScannerPulse As Boolean)
Call this routine INSTEAD of calling Initialize command. The routines will give a warning, and wait for a keyboard press to start the experiment. Theyll then pretend they are hearing pulses from the scanner at a rate a little below the TR, and ask for keyboard responses instead of using the response box.
Example
Change...
If (objSS.Initialize(“”)<>0) Then End
to...
Possible errors and causes: [[BR]]
B
oard in use. If you haven't called the Terminate command at the end of running a program, you'll get an error when you start the program again. If this happens, just close the program entirely (e.g., quit the VB editor if you're using it) and start it again. This will release the drivers.

DASSHL32.DLL not found. You're using a computer without the Keithly drivers installed.

Example [[BR]]
If (objSS.Initialize("")<>0) Then End


'''''Sub Terminate [[BR]]
'''''
Calling this routine is no longer necessary - the board communication is terminated when the ScannerSync object is destroyed.

'''Generally useful
'''
'''''
Sub SetPretendMode(booEmulateScannerPulse As Boolean) [[BR]]
'''''
Call this routine INSTEAD of calling Initialize command. The routines will give a warning, and wait for a keyboard press to start the experiment. They'll then pretend they are hearing pulses from the scanner at a rate a little below the TR, and ask for keyboard responses instead of using the response box. [[BR]]
Example [[BR]]
Change... [[BR]]
If (objSS.Initialize("")<>0) Then End [[BR]]
to... [[BR]]
Line 145: Line 143:
Sub SetPretendModeExtended(booEmulateScannerPulse As Boolean, booEmulateResponses As Boolean)
Similar to SetPretendMode, but allows you to separately choose whether the routines emulate pulses from the scanner and whether they emulate the response box. If you have either as false (e.g., not pretend mode) you'll need to keep the objSS.Initialize command.
Example
 objSS.Initialize(“”)
'''''Sub SetPretendModeExtended(booEmulateScannerPulse As Boolean, booEmulateResponses As Boolean) [[BR]]
'''''Similar to ''SetPretendMode, ''but allows you to separately choose whether the routines emulate pulses from the scanner and whether they emulate the response box. If you have either as false (e.g., not pretend mode) you'll need to keep the objSS.Initialize command. [[BR]]
Example [[BR]]
 objSS.Initialize("")
Line 153: Line 151:
 Loop
Function GetVersion() As String
This routine returns the version of the ScannerSync installed, currently 1.0.2
Example
 MsgBox Current ScannerSync version is & ObjSS.GetVersion


  Synchronisation with board
Function CheckPulseSynchrony()
Waits for a pulse. It then measures its time precisely. From the previously supplied rough estimate of the TR, the routine works out the number of the pulse that has just arrived. It then uses the exact time to work out the true TR.
 Loop [[BR]]'''''
Function GetVersion() As String [[BR]]
'''''
This routine returns the version of the ScannerSync installed, currently 1.0.2 [[BR]]
Example [[BR]]
 MsgBox "Current ScannerSync version is " & ObjSS.GetVersion


'''Synchronisation with board [[BR]]
''''''''
Function CheckPulseSynchrony()  [[BR]]
'''''
Waits for a pulse. It then measures its time precisely. From the previously supplied rough estimate of the TR, the routine works out the number of the pulse that has just arrived. It then uses the exact time to work out the true TR.
Line 167: Line 164:
Function StartExperiment(TR As Double) As Integer
This starts the clock. You should do this once per block (run of acquisitions). If no pulse is received within the timeout period (default 20s; set using SetTimeout command) then an error is returned.
'''''Function StartExperiment(TR As Double) As Integer [[BR]]
'''''This starts the clock. You should do this once per block (run of acquisitions). If no pulse is received within the timeout period (default 20s; set using SetTimeout command) then an error is returned. [[BR]]
Line 171: Line 168:
Parameters
TR The time between pulses that youve asked for in milliseconds.

Returns
 0 if there has been no error
Parameters [[BR]]
TR The time between pulses that you've asked for in milliseconds.

Returns [[BR]]
 0 if there has been no error [[BR]]
Line 178: Line 175:
Example
PumpUpTheThreadPriority
objSS.StartExperiment 3000
RestoreThreadPriority
   
Function SynchroniseExperiment(booActuallyWaitForPulse As Boolean, dblDelay As Double)
Sychronises the execution of your program (trial presentation) with the scanner. You can choose whether you actually wish to wait for a pulse or not.
Parameters
dblDelay Time in ms after the pulse that you wish the routine to return.
booActuallyWaitForPulse If True, then actually wait for a scanner pulse to come in. If False, then allow use of calculated scanner pulse time. The advantage of the latter is that if a pulse has just happened (say 0.5 s ago) and you want a delay of 2s, then the routine will return in 1.5s. If you insist on waiting for a pulse, then at TR=3 it will be 2.5s before this comes in, and then youll have to wait for 2s giving a total delay of 4.5s.
Returns

0 if there has been no error
non zero otherwise.
  
Function SynchroniseExperimentToPulseNumber (booActuallyWaitForPulse As Boolean, intPulseNumber As Integer, dblDelay As Double) As Integer
As SynchroniseExperiment, but waits for a particular pulse number. If the pulse has already occurred then:
if booActuallyWaitForPulse=True the routine will return straight away (without delay);
Example [[BR]]
PumpUpTheThreadPriority [[BR]]
objSS.StartExperiment 3000 [[BR]]
RestoreThreadPriority [[BR]]
 [[BR]]
'''''
Function SynchroniseExperiment(booActuallyWaitForPulse As Boolean, dblDelay As Double) [[BR]]
'''''
Sychronises the execution of your program (trial presentation) with the scanner. You can choose whether you actually wish to wait for a pulse or not. [[BR]]
Parameters [[BR]]
dblDelay Time in ms after the pulse that you wish the routine to return. [[BR]]
booActuallyWaitForPulse If ''True'', then actually wait for a scanner pulse to come in. If False, then allow use of calculated scanner pulse time. The advantage of the latter is that if a pulse has just happened (say 0.5 s ago) and you want a delay of 2s, then the routine will return in 1.5s. If you insist on waiting for a pulse, then at TR=3 it will be 2.5s before this comes in, and then you'll have to wait for 2s giving a total delay of 4.5s. [[BR]]
Returns [[BR]]

0 if there has been no error [[BR]]
non zero otherwise. [[BR]]
'''''
Function SynchroniseExperimentToPulseNumber (booActuallyWaitForPulse As Boolean, intPulseNumber As Integer, dblDelay As Double) As Integer [[BR]]
'''''
As SynchroniseExperiment, but waits for a particular pulse number. If the pulse has already occurred then: [[BR]]
if booActuallyWaitForPulse=True the routine will return straight away (without delay); [[BR]]
Line 198: Line 193:

Function CheckPulseSynchronyForTime(dblTimeToWait As Double) As Integer
Waits for dblTimeToWait and records any scanner pulse(s). It will not return before this time has expired even if a pulse is found earlier. It will return at this time, even if a pulse has not been found.
If you wish to design an experiment, but can only spend short periods listening for a pulse, then just work out how many pulses youll catch on average by looking at the proportion of the TR that youre listening. So, for example, if you listen using this routine for 600ms using
 objSS.CheckPulseSynchronyForTime 600
then with a TR of 3000ms youll catch a pulse on average 600/3000=0.2=20% of looks.
 
Multi-tasking routines
Expert only!

Multi-tasking versions of all of the routines are implemented. These are identical to StartExperiment, SynchroniseExperiment etc., but can safely be interrupted. A quality control system ensures that only truely reliable measurements of scanner pulse are accounted for. The commands are the same as their non-interruptable counterparts described above, but have an additional sngRequiredConfidence parameter. This parameter describes the accuracy, in ms, that a measurement must have to be taken as a reliable timing measurement. If the routines are interrupted, and cannot guarantee their timing to this accuracy, then an error value (-1) is returned, and the measurement does not contribute to estimates of the TR etc.
Function WaitForPulse_Interruptable(ByRef dblPulseTime As Double, sngRequiredConfidence As Single) As Integer
Function StartExperiment_Interruptable(
dblTR As Double, sngRequiredConfidence As Single) As Integer
'''
''''''''Function CheckPulseSynchronyForTime(dblTimeToWait As Double) As Integer [[BR]]
'''''Waits for ''dblTimeToWait ''and records any scanner pulse(s). It will not return before this time has expired even if a pulse is found earlier. It will return at this time, even if a pulse has not been found. [[BR]]
If you wish to design an experiment, but can only spend short periods listening for a pulse, then just work out how many pulses you'll catch on average by looking at the proportion of the TR that you're listening. So, for example, if you listen using this routine for 600ms using [[BR]]
 objSS.CheckPulseSynchronyForTime 600 [[BR]]
then with a TR of 3000ms you'll catch a pulse on average 600/3000=0.2=20% of looks. [[BR]]
'''Multi-tasking routines [[BR]]
'''Expert only!

Multi-tasking versions of all of the routines are implemented. These are identical to StartExperiment, SynchroniseExperiment etc., but can safely be interrupted. A quality control system ensures that only truely reliable measurements of scanner pulse are accounted for. The commands are the same as their non-interruptable counterparts described above, but have an additional ''sngRequiredConfidence ''parameter. This parameter describes the accuracy, in ms, that a measurement must have to be taken as a reliable timing measurement. If the routines are interrupted, and cannot guarantee their timing to this accuracy, then an error value (-1) is returned, and the measurement does not contribute to estimates of the TR etc. [[BR]]
'''''Function WaitForPulse_Interruptable(ByRef dblPulseTime As Double, sngRequiredConfidence As Single) As Integer [[BR]]

Function StartExperiment_Interruptable(dblTR As Double, sngRequiredConfidence As Single) As Integer  [[BR]]
Line 212: Line 206:

  
Scanner Spy
This program runs in the background with a very low thread priority, listening to scanner pulses when it can and recording information about them. It uses the multi-tasking routines mentioned above, and can safely be interrupted, with poor timing measurements being discarded.

Getting information about what’s happening
Function GetMeasuredTR() As Double
Get the TR as estimated from all of the actual pulses measured in the experiment.

Function GetLastPulseTime(booLastActualMeasurement As Boolean) As Double
If booLastActualMeasurement=True then the routine returns the time in ms of the last pulse to be actually measured. However, this routine does not actually wait for a pulse whatever the flag settings. If it is False, then the routine returns the calculated last pulse time.

Function GetLastPulseNum(booLastActualMeasurement As Boolean) As Double
If booLastActualMeasurement=True then the routine returns the number of the last pulse to be actually measured. However, this routine does not actually wait for a pulse whatever the flag settings. If it is False, then the routine returns the calculated last pulse number.

Function GetResponse() As Integer
Find out what keys are pressed on the button box plugged into the Keithly Board. The value will be 2, 4, 8, or 16 depending on which button is pressed. If more than one button is pressed, then these values add together.
To find out if a particular key is pressed, use the keyword AND as in the example.
From what I understand, there are two button boxes that plug into the Keithly Board - a left handed and a right handed one. I dont know which way the buttons are wired up. You need to find someone who knows this or work it out. Please then email the vbsupport list with the answer!
Example
If (Not(GetResponse() And 4)) Then Debug.Print Button 2 was pressed

Function GetResponseExtended(intNumButtons as Integer) As Integer
As GetResponse except supports more than 4 buttons. Specify the number of buttons in the parameter intNumButtons and the return value will be masked appropriately.

Example
If (Not(GetResponseExtended(7) And 64)) Then Debug.Print Button 6 was pressed
 

Options
Function SetTimeout(dblTimeOutPeriod As Double)
Sets the time after which the routines will give up looking for a pulse - usually 20 seconds. dblTimeOutPeriod is in milliseconds.

Function ShowErrnums(booShowErrors As Boolean)
If you set this to true, then an error message will be display if there are unusual, large changes in the TR estimate. This might be useful while debugging but dont use it when you are scanning -just better to let the experiment take its course.
Function SetMSPerSample(dblMSPerSample As Double)
There is a bug in Matlab which means that while waiting in the StartExperiment routine a large amount of memory is consumed. After around 20 secs, this memory leak leads to the board drivers refusing to function. As a workaround, you may reduce the sample rate which slows the memory leak so that the routine doesnt crash out. We would recommend something like
 objSS.SetMSPerSample 2
'''''
'''
Scanner Spy [[BR]]
'''
This program runs in the background with a very low thread priority, listening to scanner pulses when it can and recording information about them. It uses the multi-tasking routines mentioned above, and can safely be interrupted, with poor timing measurements being discarded.

'''Getting information about what's happening [[BR]]
''''''''
Function GetMeasuredTR() As Double [[BR]]
'''''
Get the TR as estimated from all of the actual pulses measured in the experiment.

'''''Function GetLastPulseTime(booLastActualMeasurement As Boolean) As Double [[BR]]
'''''
If booLastActualMeasurement=True then the routine returns the time in ms of the last pulse to be actually measured. However, this routine ''does not actually wait for a pulse'' whatever the flag settings. If it is False, then the routine returns the calculated last pulse time.

'''''Function GetLastPulseNum(booLastActualMeasurement As Boolean) As Double [[BR]]
'''''
If booLastActualMeasurement=True then the routine returns the number of the last pulse to be actually measured. However, this routine ''does not actually wait for a pulse'' whatever the flag settings. If it is False, then the routine returns the calculated last pulse number.

'''''Function GetResponse() As Integer [[BR]]
'''''
Find out what keys are pressed on the button box plugged into the Keithly Board. The value will be 2, 4, 8, or 16 depending on which button is pressed. If more than one button is pressed, then these values add together. [[BR]]
To find out if a particular key is pressed, use the keyword AND as in the example. [[BR]]
From what I understand, there are two button boxes that plug into the Keithly Board - a left handed and a right handed one. I don't know which way the buttons are wired up. You need to find someone who knows this or work it out. Please then email the vbsupport list with the answer! [[BR]]
Example [[BR]]
If (Not(GetResponse() And 4)) Then Debug.Print "Button 2 was pressed"

'''''
Function GetResponseExtended(intNumButtons as Integer) As Integer [[BR]]
'''''
As GetResponse except supports more than 4 buttons. Specify the number of buttons in the parameter ''intNumButtons ''and the return value will be masked appropriately.

Example [[BR]]
If (Not(GetResponseExtended(7) And 64)) Then Debug.Print "Button 6 was pressed" [[BR]]
 [[BR]]
'''
Options [[BR]]
''''''''
Function SetTimeout(dblTimeOutPeriod As Double) [[BR]]
'''''
Sets the time after which the routines will give up looking for a pulse - usually 20 seconds. ''dblTimeOutPeriod ''is in milliseconds.

'''''Function ShowErrnums(booShowErrors As Boolean) [[BR]]
'''''
If you set this to true, then an error message will be display if there are unusual, large changes in the TR estimate. This might be useful while debugging but don't use it when you are scanning -just better to let the experiment take its course. [[BR]]
'''''
Function SetMSPerSample(dblMSPerSample As Double) [[BR]]
'''''
There is a bug in Matlab which means that while waiting in the StartExperiment routine a large amount of memory is consumed. After around 20 secs, this memory leak leads to the board drivers refusing to function. As a workaround, you may reduce the sample rate which slows the memory leak so that the routine doesn't crash out. We would recommend something like [[BR]]
 objSS.SetMSPerSample 2 [[BR]]
Line 252: Line 244:
Routines available that you shouldn’t normally need
Function WaitForPulse(ByRef dblPulseTime As Double) As Integer
Used internally by SynchroniseExperiment - use this instead.
Function GetTimeOfPulsePriorTo(dblMyTime As Double) As Double
Gives you the calculated time of the pulse prior to the specified time.
Function ReadPIOValue() As Integer
Reads from Keithly board- used internally.
'''Routines available that you shouldn't normally need [[BR]]
''''''''
Function WaitForPulse(ByRef dblPulseTime As Double) As Integer [[BR]]
'''''
Used internally by SynchroniseExperiment - use this instead. [[BR]]
'''''
Function GetTimeOfPulsePriorTo(dblMyTime As Double) As Double [[BR]]
'''''
Gives you the calculated time of the pulse prior to the specified time. [[BR]]
'''''
Function ReadPIOValue() As Integer [[BR]]
'''''
Reads from Keithly board- used internally.

Scanner synchronisation from Visual Basic/Eprime/Matlab BR Documentation for Version 1.3.7. Documentation last updated 23/11/04 BR Distributed under GNU lesser public license. Rhodri Cusack 2000-2004.

Updates ' Changed 17/5/00 by Rhodri BR ' Added version command BR ' Added "Class_Terminate" BR ' Added SetPretendMode and associated code

' 1.0.1 BR ' 25/5/00 - Added CheckPulseSynchronyUntilTimeout

' 1.0.2 BR ' 06/06/00 - Add pretend mode to response routines

' 1.0.3 BR ' 19/07/00 - Changed Checkpulsesynchrony so that it works in pretend mode

' 1.0.4 BR ' 5/09/00 Made it work with new DriverLinx drivers

' OX1.04 BR ' 1/12/00 Changed WaitForPulse_Interruptable so that it doesn't erroneously return 0 when time out occurs in pretend mode

' 1.2.0 BR ' For move back to Cambridge, added local mode BR ' BR ' 1.2.1 BR ' Modified to look for low-going pulse BR ' BR ' 1.3.0 BR ' New image - not much different BR ' BR ' 1.3.1 BR ' 11/4/02 - Added SetDevice command

' 1.3.2 BR ' 27/1/03 - Added option to scan for second driver when first not found, to make compatible with PCMCIA Keithly card

' 1.3.3 BR ' 12/5/03 - Added SetPretendModeExtended BR ' BR ' 1.3.4 BR ' 18/5/03 - Added GetResponseExtended BR ' BR ' 1.3.5 BR ' 29/4/03 - Changed calculation of interval to cope with initial over estimates of TR BR ' BR ' 1.3.6 BR ' 9/11/04 - Modify StartExperiment so that it returns error code properly BR ' Add error trapping for board problems BR ' BR ' 1.3.7 BR ' 23/11/04 - Added SetMSPerSample option which allows reduction of sample rate BR ' in StartExperiment routine to tackle Matlab misbehaviour. BR ' BR '1.3.9 BR ' 12/7/06 - Added support for new button boxes with GetResponseLeft and 'GetResponseRight BR ' BR '1.3.10 BR ' 24/7/06 - Added SSGetTimer command as difficult to read from perfpres dll from matlab

Notes Apr 2003 BR Some notes on how to calculate your stimulus timings for SPM: BR (1) Beware that the "pulse number" is a zero-based index: pulse "0" is the first one, collected by the objSS.StartExperiment command. Pulse "1" is the second, usually collected by your first dummy scan objSS.SynchroniseExperiment command. I set it up like this because this is how SPM likes its timings: time "0" is the first scan. BR (2) At the current date with "Christian's EPI sequences" the pulse starts 50ms before the start of the first scan. If you wish to be precise, subtract 50ms from your timings. BR (3) The old Bruker sequences had the pulse at the end, and so you had to subtract one TA from the timings. This is no longer the case.

General description BR These routines should provide a flexible way of synchronising with the scanner. They allow for a range of designs, and are hopefully easy to integrate with many experimental designs.

The main way this is achieved is by allowing you to measure the scanner pulse whenever you want. The only restriction is that you check for the pulse at least once every minute.

It doesn't matter when you call the pulse measurement routines - you can do this once every 30 seconds, or when the experiment determines (e.g., in a timing where the subject's responses determine progress, this might end up at 24, 75 and 82 seconds). The only restrictions are that you do it at least once a minute, and don't mind waiting for up to the TR time for a pulse to come in (although you can use the routines in a slightly different way to get around this last restriction).

You can choose to synchronise your experiment flow (trial presentation) to the scanner pulses at the same time as you actually measure a pulse, or at a different time, in which case the routines will calculate when the pulse occurred. This has an advantage if you wish the routines to synchronise to a certain delay after a pulse. For example, if you want you trial to start 2.5 seconds after a pulse, and there was a pulse 2 seconds ago, then you can start in 0.5 seconds. If you had actually waited for a pulse to arrive, then you would have had to wait a second for the pulse to come in, and then another 2.5 seconds, which would take 5.5 seconds. This might not always be acceptable.

The reason it is not necessary to count every pulse is that although the scanner doesn't quite give us the time we ask for, it is pretty accurate - say within 1%. Hence, if we've asked for a TR of 3s, and we happen to listen out for a pulse after 32s, and find one at 33.33, then we know this is the 11th pulse, and that the actual TR is 3.03s. To do this, we didn't have to know a priori that we were measuring the 11th pulse- the routines can calculate this if they are told the rough TR. They then use the measurements to improve their estimate of the actual TR. For this to work, there is one restriction: that the drift time is smaller than half a TR. If p is the drift in percent, T is the TR time, and N the number of trials between pulse measurement, we have BR

  • N*TR*p/100 < TR/2 BR

Rearranging N < 50/p BR BR Now, putting in p=1 this gives N<50. So the absolute limit is that N=50; for a TR of 3, this is once every 150s. If you want to be on the (very) safe side, then 30s would seem a good compromise.

In practice, even if you measure the first pulse (worst case) the routines will know the TR to within a a millisecond, and as soon as you've synchronised once the experimental timings will be accurate to within 1 ms. Matt Davis suggested that, as he does with DMDX, you could use the dummy scans at the start of each experiment to measure the TR. BR

Using the routines BR (1) Download latest version from BR http://www.mrc-cbu.cam.ac.uk/~rhodri.cusack/scannersync.zip BR Unzip and double click on Setup.exe

(2) In VB, go to Project > References. Find MRISync and tick the box.

(3) At the top of the code in which you want to use the synchronisation routine, put BR

This will make the ActiveX object through which you'll access all the commands. At the end of your code, or in an escape routine, call Terminate (see notes under this below). A note about timing: the scanner synchronisation routines use the high performance counter clock in the perfpres.dll, and will be very upset if you reset it. You can read the time with GetTimer, but don't reset the clock with StartTimer. If you wish to measure an interval, then use relative code:

Dim dblMyTempTime as Double BR dblMyTempTime=GetTimer() BR .... thing to be timed.... BR Debug.Print "The interval was " & GetTimer() - dblMyTempTime

Don't forget to wrap time critical bits of code (including commands such as StartExperiment) in the two instructions to increase and decrease the thread priority (PumpUpTheThreadPriority and RestoreThreadPriority). Command reference BR The commands are described by category below.

Initialising and terminating the communication with the board BR Function Initialize(strConfig As String) as Integer BR Gets the system ready. BR Parameters: BR strConfig You no longer need this configuration file - just specify ""

Returns: BR 0 if no error BR Keithly error code otherwise

Possible errors and causes: BR Board in use. If you haven't called the Terminate command at the end of running a program, you'll get an error when you start the program again. If this happens, just close the program entirely (e.g., quit the VB editor if you're using it) and start it again. This will release the drivers.

DASSHL32.DLL not found. You're using a computer without the Keithly drivers installed.

Example BR If (objSS.Initialize("")<>0) Then End

Sub Terminate BR Calling this routine is no longer necessary - the board communication is terminated when the ScannerSync object is destroyed.

Generally useful Sub SetPretendMode(booEmulateScannerPulse As Boolean) BR Call this routine INSTEAD of calling Initialize command. The routines will give a warning, and wait for a keyboard press to start the experiment. They'll then pretend they are hearing pulses from the scanner at a rate a little below the TR, and ask for keyboard responses instead of using the response box. BR Example BR Change... BR If (objSS.Initialize("")<>0) Then End BR to... BR

Sub SetPretendModeExtended(booEmulateScannerPulse As Boolean, booEmulateResponses As Boolean) BR Similar to SetPretendMode, but allows you to separately choose whether the routines emulate pulses from the scanner and whether they emulate the response box. If you have either as false (e.g., not pretend mode) you'll need to keep the objSS.Initialize command. BR Example BR

Function GetVersion() As String BR This routine returns the version of the ScannerSync installed, currently 1.0.2 BR Example BR

Synchronisation with board BR Function CheckPulseSynchrony() BR Waits for a pulse. It then measures its time precisely. From the previously supplied rough estimate of the TR, the routine works out the number of the pulse that has just arrived. It then uses the exact time to work out the true TR.

This routine can be called at any point in your code, with any degree of regularity/irregularity. You choose when! The only constraint is that it should be at least 30 secs - 1 minute or so as discussed in the introduction.

Function StartExperiment(TR As Double) As Integer BR This starts the clock. You should do this once per block (run of acquisitions). If no pulse is received within the timeout period (default 20s; set using SetTimeout command) then an error is returned. BR For accuracy, this command should be executed at a high thread priority (see example).

Parameters BR TR The time between pulses that you've asked for in milliseconds.

Returns BR

  • 0 if there has been no error BR

non zero otherwise

Example BR PumpUpTheThreadPriority BR objSS.StartExperiment 3000 BR RestoreThreadPriority BR

Function SynchroniseExperiment(booActuallyWaitForPulse As Boolean, dblDelay As Double) BR Sychronises the execution of your program (trial presentation) with the scanner. You can choose whether you actually wish to wait for a pulse or not. BR Parameters BR dblDelay Time in ms after the pulse that you wish the routine to return. BR booActuallyWaitForPulse If True, then actually wait for a scanner pulse to come in. If False, then allow use of calculated scanner pulse time. The advantage of the latter is that if a pulse has just happened (say 0.5 s ago) and you want a delay of 2s, then the routine will return in 1.5s. If you insist on waiting for a pulse, then at TR=3 it will be 2.5s before this comes in, and then you'll have to wait for 2s giving a total delay of 4.5s. BR Returns BR 0 if there has been no error BR non zero otherwise. BR Function SynchroniseExperimentToPulseNumber (booActuallyWaitForPulse As Boolean, intPulseNumber As Integer, dblDelay As Double) As Integer BR As SynchroniseExperiment, but waits for a particular pulse number. If the pulse has already occurred then: BR if booActuallyWaitForPulse=True the routine will return straight away (without delay); BR if booActuallyWaitForPulse=False then the routine will return straight away if the desired time has passed, or at the appropriate time if it has not.

Function CheckPulseSynchronyForTime(dblTimeToWait As Double) As Integer BR Waits for dblTimeToWait and records any scanner pulse(s). It will not return before this time has expired even if a pulse is found earlier. It will return at this time, even if a pulse has not been found. BR If you wish to design an experiment, but can only spend short periods listening for a pulse, then just work out how many pulses you'll catch on average by looking at the proportion of the TR that you're listening. So, for example, if you listen using this routine for 600ms using BR

then with a TR of 3000ms you'll catch a pulse on average 600/3000=0.2=20% of looks. BR Multi-tasking routines BR Expert only!

Multi-tasking versions of all of the routines are implemented. These are identical to StartExperiment, SynchroniseExperiment etc., but can safely be interrupted. A quality control system ensures that only truely reliable measurements of scanner pulse are accounted for. The commands are the same as their non-interruptable counterparts described above, but have an additional sngRequiredConfidence parameter. This parameter describes the accuracy, in ms, that a measurement must have to be taken as a reliable timing measurement. If the routines are interrupted, and cannot guarantee their timing to this accuracy, then an error value (-1) is returned, and the measurement does not contribute to estimates of the TR etc. BR Function WaitForPulse_Interruptable(ByRef dblPulseTime As Double, sngRequiredConfidence As Single) As Integer BR Function StartExperiment_Interruptable(dblTR As Double, sngRequiredConfidence As Single) As Integer BR Function CheckPulseSynchrony_Interruptable(sngRequiredConfidence As Single) As Integer Scanner Spy BR This program runs in the background with a very low thread priority, listening to scanner pulses when it can and recording information about them. It uses the multi-tasking routines mentioned above, and can safely be interrupted, with poor timing measurements being discarded.

Getting information about what's happening BR Function GetMeasuredTR() As Double BR Get the TR as estimated from all of the actual pulses measured in the experiment.

Function GetLastPulseTime(booLastActualMeasurement As Boolean) As Double BR If booLastActualMeasurement=True then the routine returns the time in ms of the last pulse to be actually measured. However, this routine does not actually wait for a pulse whatever the flag settings. If it is False, then the routine returns the calculated last pulse time.

Function GetLastPulseNum(booLastActualMeasurement As Boolean) As Double BR If booLastActualMeasurement=True then the routine returns the number of the last pulse to be actually measured. However, this routine does not actually wait for a pulse whatever the flag settings. If it is False, then the routine returns the calculated last pulse number.

Function GetResponse() As Integer BR Find out what keys are pressed on the button box plugged into the Keithly Board. The value will be 2, 4, 8, or 16 depending on which button is pressed. If more than one button is pressed, then these values add together. BR To find out if a particular key is pressed, use the keyword AND as in the example. BR From what I understand, there are two button boxes that plug into the Keithly Board - a left handed and a right handed one. I don't know which way the buttons are wired up. You need to find someone who knows this or work it out. Please then email the vbsupport list with the answer! BR Example BR If (Not(GetResponse() And 4)) Then Debug.Print "Button 2 was pressed"

Function GetResponseExtended(intNumButtons as Integer) As Integer BR As GetResponse except supports more than 4 buttons. Specify the number of buttons in the parameter intNumButtons and the return value will be masked appropriately.

Example BR If (Not(GetResponseExtended(7) And 64)) Then Debug.Print "Button 6 was pressed" BR

Options BR Function SetTimeout(dblTimeOutPeriod As Double) BR Sets the time after which the routines will give up looking for a pulse - usually 20 seconds. dblTimeOutPeriod is in milliseconds.

Function ShowErrnums(booShowErrors As Boolean) BR If you set this to true, then an error message will be display if there are unusual, large changes in the TR estimate. This might be useful while debugging but don't use it when you are scanning -just better to let the experiment take its course. BR Function SetMSPerSample(dblMSPerSample As Double) BR There is a bug in Matlab which means that while waiting in the StartExperiment routine a large amount of memory is consumed. After around 20 secs, this memory leak leads to the board drivers refusing to function. As a workaround, you may reduce the sample rate which slows the memory leak so that the routine doesn't crash out. We would recommend something like BR

  • objSS.SetMSPerSample 2 BR

Thanks to Tom Morey for helping with this one.

Routines available that you shouldn't normally need BR Function WaitForPulse(ByRef dblPulseTime As Double) As Integer BR Used internally by SynchroniseExperiment - use this instead. BR Function GetTimeOfPulsePriorTo(dblMyTime As Double) As Double BR Gives you the calculated time of the pulse prior to the specified time. BR Function ReadPIOValue() As Integer BR Reads from Keithly board- used internally.

None: ScannerSyncDocumentation (last edited 2013-03-08 10:28:25 by localhost)