Difference in autosaved and manually saved onset times of stimuli (letters)

Hi all,

I am presenting stimuli in my experiment where letter ‘X’ serve as target and rest of the letters as non targets. All the letters appear without categorisation into target and non target conditions coz of the randomisation. Then I define target and non target letters to be saved with their onset timings separately in the csv data file by following code in Each Frame and End Routine tabs. As you can see in the attached screenshot(#2) of csv data file that “xcpt.started” are the autosaved onset timings for each letter as they appear. But I would like to save the onset timings for letters as Targets (‘X’) and Nontargets, however there is always a difference in autosaved (“xcpt.started”) and manually saved (Onset-Target/Nontarget) onset timings. Screenshot (#builder 1) of the design is also attached.
Could anyone suggest what can be wrong or I’m using a wrong timer? Thanks!

Btw I also tried to use xcpt.tStart or text_X.tStart but then it gives error that there’s no attribute as xcpt or text_X.

#Each frame tab: saveas Target/Nontarget  
if text_X == 'X' :
   thisExp.addData('Target',text_X)
else:
    thisExp.addData('Non-Target',text_X)
#End routine tab: save Onset times of Targets/Nontargets 
if text_X == 'X' : 
   thisExp.addData("Onset-Target",core.getTime())
else:    
    thisExp.addData("Onset-Nontarget",core.getTime())

Hi There,

Is there a reason to be using a different clock? e.g. why not use:

if text_X == 'X' : 
    thisExp.addData("Onset-Target",xcpt.started)
else:
    thisExp.addData("Onset-Nontarget",xcpt.started)

I think the problem here will be that you’re getting the time at the end of the routine right? rather than when the target started?

Becca

Hi,

Thanks for your response.
1.

Is there a reason to be using a different clock?

I simply need to store the stimulus onset times xcpt.started as 'Onset-Target and Onset-Nontarget'. I assumed core.getTime will give the current time.

I think the problem here will be that you’re getting the time at the end of the routine right? rather than when the target started?

I previously tried in the Begin Routine as well, and your code putting either in Begin or End Routine but it gives the same following error msg.

thisExp.addData("Onset-Target",xcpt.started)

#Error msg
AttributeError: 'TextStim' object has no attribute 'started'

Any other insights?

  1. I also have issues with the timings of the experiment. Since, ISI (+) lasts 300 ms and each letter(stimulus) 300 ms in a single trial. Where onset of first + is at 0th second while the duration of a letter is 300 ms with first onset at 300 ms. Ideally the complete single trial should be 600 ms long. If I run it with nReps=10 for 30 letters it should make experiment to last total 180 seconds= 3 minutes. But when I run the experiment it takes approx 5 minutes in total. I guess there is some kind of time interval which comes from an unknown factor as a blink/blank screen between ‘+’ and stimulus, as can be seen in the attached gif image. How to address these incorrect timings?

xcpt

anyone?

Both problems have a similar root. Based on the screenshot, your key_resp object lasts until the 1 second mark, so the trial ends after 1 full second, even though the stimuli vanish after 600ms. This effectively produces a 400ms ISI. Each trial takes 1 full second, so for 300 repetitions, it lasts precisely 5 minutes. That’s the root of the timing issue.

This is also why you’re getting the wrong time using core.getTime(). It does indeed give you the time when the code is executed. If you put that code in the “end routine” tab, that’s going to be when the trial ends, i.e. 1 second after the start of the trial, or 700ms after the stimulus onset. At a quick glance the recorded times are always 700ms later than the start time recorded in the xcpt_started column.

If you put the code @Becca provided in your “end routine” tab, that should record what you want to record, it should just copy over the actual start time into the columns you created. You don’t need or want to use getTime() again.

As for the overall timing issue, that comes down to the “stop” time you put in the key_resp object. If you tell it to stop after .6 seconds, then the whole trial will end at 600ms and there will be no ISI.

Thanks for your answer. Indeed, key_resp was the delay factor which I corrected earlier and the exp ran in time but it is surprising that the time of response key affects stimulus timings. I tried the above code again in End Routine tab but it doesn’t work; same error ‘TextStim’ object has no attribute ‘started’. When I try tStart or t it works but it shows different timings. I am wondering how response key time would affect when one tries variable ISI times?

I see. tStart and t are probably tracking the start of the trial as a whole, or the latest-starting item within it. I’m not 100% sure how to get at the ‘started’ timestamp because it seems to be a property of the TrialHandler or ExperimentHandler rather than the TextStim itself. It should be possible, I’m just not entirely sure how to refer to it correctly.

I have a solution, it’s probably not the most elegant, but it does work with a consistent 16ms error (one frame), but it will record the time of the onset.

Get rid of your “end routine” frame code entirely. Instead put the following:

# In 'Begin Routine'
onsetRecorded = False
# In 'each frame'
if xcpt.status == STARTED and not onsetRecorded:
    if text_X == 'X' :
        thisExp.addData('Target',text_X)
        thisExp.addData('Onset-Target', core.getTime())
    else:
        thisExp.addData('Non-Target',text_X)
        thisExp.addData('Onset-Nontarget', core.getTime())
    onsetRecorded = True

This looks for the first frame that the text object gets the ‘STARTED’ status (indicating that it is being displayed), and records that time. For various obscure reasons, the time recorded by getTime() here is actually the timestamp of the frame before the text actually appears, so the ‘xcpt.started’ field will always be 16ms greater than this. However, you can either put a manual correction for that like so:

thisExp.addData('Onset-Nontarget', core.getTime()+0.0166666666667)

Or delay the recording by a frame by adding a second boolean

# begin routine
frameDisplayed =  False
onsetRecorded = False
# each frame
if xcpt.status == STARTED and not onsetRecorded:
    if text_X == 'X' :
        thisExp.addData('Target',text_X)
        thisExp.addData('Onset-Target', core.getTime())
    else:
        thisExp.addData('Non-Target',text_X)
        thisExp.addData('Onset-Nontarget', core.getTime())
   if frameDisplayed: 
       onsetRecorded = True
   else:
       frameDisplayed = True

The end result is basically the same.

As for the ISI/end of trial problem, more generally the way PsychoPy does things is that it just looks for the element in the trial that has the last end time. If the key response just waits until a response is made, then that’s just whenever the key response is made. All the ‘static’ or ISI object does is add one more thing to the trial that you can set the end time of, therefore moving when that end-of-trial flag occurs. RT will always be measured from the start of that particular trial, so if the ISI is put at the end of the trial, it won’t be affected at all.

Thanks once again for your input and time. I tried your suggested code but it saves different timings. I did a short trick though by putting the following piece of code in Each Frame tab which saves one of the two variables Target or Non-Target (whichever appears first in trials) next to the xcpt.started e.g. onset of stimulus in csv data file. This at least gives me correct onsets for first variable whereas rest of the onsets can be assumed for the second variable, for instance as in the attached snapshot. Verified it by running the exp again & again. I wanted to do this for handy analysis later on since plan is to record 50 or more subjects, but I guess if nothing is working the following trick would suffice.

if text_X == 'X':
            thisExp.timestampOnFlip(win, 'xcpt.started')
            thisExp.addData('Target', text_X)
else:
            thisExp.timestampOnFlip(win, 'xcpt.started')
            thisExp.addData('Non-Target', text_X)

@Becca and @jonathan.kominsky finally, following code copies the exact onsets in csv data file as desired:)

#Begin routine
#Copy xcpt.started onset as Onset Target/NonTarget
if text_X == 'X':
            thisExp.timestampOnFlip(win, 'OnTarget')
            thisExp.addData('Target', text_X)
            thisExp.addData('OnTarget', thisExp.timestampOnFlip(win, 'OnsetTarget'))
else:
            thisExp.timestampOnFlip(win, 'OnNonTarget')
            thisExp.addData('Non-Target', text_X)
            thisExp.addData('OnNonTarget', thisExp.timestampOnFlip(win, 'OnsetNonTarget'))

1 Like