Hi Jason,
I've been developing a handful of fMRI experiments over the past few months and would be happy to share my experience (and experiments). Like Jeremy said, the biggest concerns with fMRI involve syncing the timing between the stimulus presentation and the scanner's acquisitions. Scanners are set up to send a sync pulse for each TR, which is recorded at different sites and scanners as either a keyboard stroke coming in over an emulated USB keyboard, or a voltage change on a parallel port pin. When I was using presentation we had a parallel port, but I believe Presentation can monitor pulses from a keyboard as well. Figuring out how your scanner sends its sync pulses will determine how you setup the experiment to listen to pulses. The MR Techs or Physicists running your scanner should be able to tell you how the pulses are sent.
Since PsychoPy has its roots in visual presentation, its emphasis was on displaying stimuli for very precise durations, but not necessarily at very precise times. Therefore, with the small amount of overhead that occurs checking routine/flow conditions and entering/exiting flow loops, I've found that I'll get somewhere around 8-10 seconds worth of "wall clock" time drift in a 5 minute experiment with what should be a "10 second" trial length. For example, if I have an experiment with 30 10s trials, it should in theory last 300 seconds, but actually lasts 310 or so. The drift hasn't seemed to vary too much between runs, so if you collect 312 seconds of data (156 2s TRs) from the scanner, and set your experiment to close at exactly 312 seconds, the experiment and scanner should finish within milliseconds of each other. That way you'll know that the onset times you collected were accurate and can be matched up to your BOLD images in the model.
I'm actually in the middle of checking this with some of our other users, but I believe that for standard (block and fast event-related) experiments this is fine, since a small amount of drift will shift the real acquisition time across a TR, essentially jittering the experiment and increasing the acquisition time resolution. The critical thing is that the onset times are correct, or the model will be off. This means the second part of Jeremy's comment, getting accurate onset times, is critical.
If you're concerned about maintaining the trial onset lock-step with the scanner acquisition, you should include some space at the end of the trial loop (typically a trial feedback routine or fixation) and write a check to count received reps, and start the next trial at precisely the timing of the next pulse. This is a similar, but not identical strategy to what Presentation does, where you specify the exact pulse that a picture should be presented on:
picture $p1; mri_pulse=4; time=0; target_button = 2; code=101;
picture $p2; mri_pulse=6; time = 500; target_button = 1; code=102; #4.5
picture $null; mri_pulse=7; time = 1500; code=100; #7.5
I haven't written any pulse-waiting code for PsychoPy yet, but may do so in the near future. Currently PsychoPy's includes a way to sync the start of the experiment using the fMRI launchScan that you can see in the fMRI coder demo, but does not include support for counting sync pulses, although it shouldn't be too hard to find a way to do that. I typically do this by starting an experiment with an instruction routine, then following that with a waitForScanner routine. In the waitForScanner, there's one code component called waitForMriCode with the following:
waitForMriCode Begin Experiment:
from psychopy.hardware.emulator import launchScan
#
# settings for launchScan:
MR_settings = {
'TR': 2.000, # duration (sec) per volume
'volumes': 210, # number of whole-brain 3D volumes / frames
'sync': 'equal', # character to use as the sync timing event; assumed to come at start of a volume
'skip': 4, # number of volumes lacking a sync pulse at start of scan (for T1 stabilization)
}
waitForMriCode Begin Routine:
vol = launchScan(win, MR_settings, globalClock=logging.defaultClock, mode=expInfo['mriMode'])
I then have a setting in the builder's Experiment Settings called 'mriMode' which is set to either 'scan' or 'test', following the launchScan demo. That will hold the experiment until the scanner sends a pulse that indicates it is ready to begin (in this case, after 4 skipped discarded acquisitions (discdacqs) ).
Someone please correct me if I'm wrong, but I think all that you'll primarily need for most experiments is to 1) sync the starting time and 2) to record the onsets carefully so that you can later enter them into the model. I've been parsing my logfiles to get outsets in a pretty hackneyed way, but will also be writing an easy onset extractor in the next month or two.
Jason, we may be able to help you more if you send more detail about your task, or have some more specific questions after looking at the fMRI_launchScan demo. Feel free to send to me or the list, and we'll try to help if we can.