psychopy.org | Reference | Downloads | Github

Pseudorandom jittering ITI by code in the builder

Hi,

I’m using PsychoPy 3.0.5 on macOS 10.14.3 (18D109).

I’m trying to pseudorandomized my jittering ITI. I have 88 trials followed by a jittering fixation cross. I’d like these 88 jitter values to be picked pseudorandomly, i.e. 11 repetitions of each value, as shown here:

1.5 s X11
1.75 s X11
2.0 s X11
2.25 s X11
2.5 s X11
2.75 s X11
3 s X11
3.25 s X11

At the moment I added this command to the"begin experiment" and “begin routine” tabs of a code block.

jitter = np.arange(1.5, 3.5, .25)
shuffle(jitter)

How can I edit it to make it pick the values at a pseudorandom order?

Many thanks,
Geneviève

Hi @Genevieve_Allaire-Du, you could add this to the “Begin Experiment” tab. Note, I have added an extra shuffle at the bottom if you want full random (explained below) but you could just delete the final line if you do not want it:

iti = [1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25]
nBlocks = 11
jitter = []

# Create blocks of random jitter
for block in range(nBlocks):
    # Append a random ample from the iti list
    jitter.append(list(np.random.choice(iti, size=len(iti), replace=False)))
    
# Flatten nested list
jitter = [jit for sublist in jitter for jit in sublist]

# If you want full random, such that the same item to appear potentially many times in a row, and repeat without necessarily having done all of the other trials in a block
shuffle(jitter)

You can then iterate through the jitter list using your trial handler. Assuming your loop is called “trials”:

jitter[trials.thisN]

Thank you I’ll try that.

Then in the properties of my fixation cross, should I use “$jitter[0]” in the duration field?

If you want the fixation presentation duration to change on every trial, according to the list just created, use jitter[trials.thisN], where “trials” is the name of your loop in the flow. This trialhandler attribute thisN will return an index of the current trial, so if you have 88 trials, it will iterate from 0 to 87 (88 trials in total), and this number is used to index the corresponding iti in the jitter list.

Thanks a lot, it seems to be running smoothly. To be sure I’d like to run the task and check that the experiment time matches my estimation (no delays). However, I can’t find a way to log the onset time of the routines so that they would appear in the .csv file in a separate column. Is there a way to do this from the experiment settings?

Geneviève

Do you mean time relative to the start of the experiment? If so, just add the following to th"e begin routine" tabs of a code component in all of your relevant routines:

thisExp.addData("routineNameStartTime", globalClock.getTime())

This will create a column called “routineNameStartTime” and will save the time of the start of the routine relative to the onset of the experiment, for each routine that you paste that code into.

IF you want to log the iti, then in the “end routine” tab, add:

thisExp.addData("iti", jitter[trials.thisN])

I used ‘‘thisExp.addData(“routineNameStartTime”, globalClock.getTime())’’ and it works great, thanks!

Can I also log the iti or will it cause some bug?

Great. Ok, so is the code in the previous example not what you are after? It should log the iti from the jitter list for that trial. you can do this either at the beginning or end of the routine.

Yes, it is the code I was looking for. I was just asking naively if I can use both to log both informations.

1 Like

Hi,

To make sure the random sampling with no replacement was working I ran the task and looked at the ITI (computed manually, see column J).

I was expecting the ITI to vary between 5 s and 6.75 s since the trials have a duration of 3.5 and the fixation cross is a random value between sampled from [1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25]. However, the ITI varies from 5.25 to 7.00 (even once at 7.25).

Do you have any idea why the values are not what I expect to see?

I greatly appreciate your help,
Geneviève

Check delay.xlsx (18.1 KB)

Hi @Genevieve_Allaire-Du, I cannot really tell from your spreadsheet, but it does suggest some of the timings are not adding up correctly if you want a range of 5s to 6.75s. If you can pass me your conditions file and maybe a minimally working experiment file we can see what is happening.

Thanks that would be super helpful.

I can’t attach the stimuli because I’d need to zip the folder containing all the images.

conditionsrun1.xlsx (13.6 KB)
warn5run1.py (23.7 KB)
warn5run1.psyexp (76.2 KB)

So you have a routine called initialFixation, which has a fixed duration. In that routine, you are writing an “iti”, taken from the jitter list of times. This should not be there, because you are not using jitter in that routine. Instead, if you want to record the duration of the routine, just add thisExp.addData("initialFixationEndTime", globalClock.getTime()) in the End Routine tab. You need to do this with all of your routines that are not part of the loop, because currently you are writing the iti, and it is irrelevant to routines outside of the loop. But, also add these to your loop routines, so you can see how long they are too, but remember to change the name of the column.

Also, in all of your routines where you are writing the start time, you are using “routineNameStartTime” to name your columns. In the previous examples I used routineName as a placeholder, and the idea is you can manually add the actual routine name e.g., initialfixation in place, which is the name of one of your routines. This way, it is clear which routine you are looking at in your datafile. So, the actual format in a more abstract way is:

thisExp.addData("column name in your excel file", saved_variable)

Also, when you do want to record the iti, you need to refer to the name of your actual loop (like you are in your fixation component), not the the one I used in my example. Your loop is called trialsloop, so to record your iti in your data file, you would use

thisExp.addData("iti", jitter[trialsloop.thisN])

Regarding the timing, sometimes you get 7 seconds, because:

3.5s trial duration + 3.25 iti duration (max iti duration) + .25s iti onset delay == 7 seconds

You see the 7.25s trial time on your last trial, where you transition from the loop to the start of a new routine. Not sure why it is 7.25 seconds, but it is not an accurate measure the length of your trial. You should measure the time from the start of the routine, to the end of the routine, and you do this by getting the time in the “Start routine” amd “End routine” tabs in the code component. Best not to measure trial time by measuring the start of one routine to the start of the next routine, which is what you are doing in your datafile when you subtract two consecutive rows in the “routineNameStartTime” column. As you have two routines within your loop, you can add their durations together to get your trial length. Hope thats clear enough, have a go with those changes and let me know :slight_smile:

Hi,

Thank you for looking closely into this. I now understand the logic better.

I ran the task again to check for delays and now the numbers match! :grinning:

There is only one minor thing that I don’t understand. Why are the initialfixation start and end times logged in on the first trial row?

And finally a remaining question. If I want to also log the EndTime data for the two routines in the loop, do I just write for example "thisExp.addData(“fixationEndTime”, globalClock.getTime())’ in the “end routine” tab where the “thisExp.addData(“iti”, jitter[trialsloop.thisN])” is located, do I put it on a separate line?

Geneviève

warn5run1.py (23.7 KB)

1526_WarnRun1_2019_Mar_02_1527.csv (14.0 KB)

Hi @Genevieve_Allaire-Du, the initial fixation is only logged on the first row because it is only presented once. This is because you present the initial fixation, and then you enter your loop, which loops 88 times and then moves on to the end of the experiment. It may be better if you swapped the order of the fixation and trial in the loop, so that the fixation comes first. Then you could remove your “intialFixation” routine.

Yes, you just need to add that code to a code component “End Routine” tab in each routine, and you can put the iti entry on the next line down e.g.,

thisExp.addData('fixationEndTime', globalClock.getTime())
thisExp.addData('iti', jitter[trialsloop.thisN])

Note, record the iti in only one of your routines, either fixation or trial.

Hi!

Thanks, this helps a lot once more!

The issue is that the first fixation cross needs to be longer (4s) instead of jittering between 1.5-3.5 to let the signal of the fMRI scanner become stable. It’s strange that it would log the data of the initial fixation in the first row of the loop trials and not the data of the other routines that are located outside the loop. I can work around that it is a minor problem.

Thanks again :grinning:

1 Like

Hi,

I tried to adapt this code component to a new experiment where this time I’d like 12 repetitions of these iti [1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0] because I have 84 trials (12X7=84).

I then wrote down:

iti = [1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0] 
nBlocks = 12 
jitter = []  

# Create blocks of random jitter 
for block in range(nBlocks):     
    # Append a random ample from the iti list     
    jitter.append(list(np.random.choice(iti, size=len(iti), replace=False)))      
    
# Flatten nested list 
jitter = [jit for sublist in jitter for jit in sublist]

and i used jitter[run1trials.thisN] in the duration field of my image, assuming that my loop is called run1trials

I get this error message:

key_resp_run1trials = event.BuilderKeyResponse()

AttributeError: ‘str’ object has no attribute ‘BuilderKeyResponse’

Is the error related to the code component? Do you have any idea what I might have done wrong?

Thanks a lot,
Geneviève

run1area.psyexp (37.7 KB)

Hi @Genevieve_Allaire-Du, have you used event as a variable name in your experiment e.g., in your conditions file?

Hi @dvbridges,

Yes, see conditions file attached.

conditionsrun1area.xlsx (12.4 KB)

That’s the problem then. You need the event module for starting your keyboard component, but it has been overwritten by your event variable in your conditions file. All you need to do, is rename the event column in your conditions file to something that is not used by PsychoPy e.g., events.