Preloading all stimuli

OS: Windows 10
Version: 2020.1.1
Standard Standalone (y)

My trials contain 4 .jpg images, each listed in their own column of the conditions file, which need preloading. I previously had them preloading for 0.5 seconds at the start of each trial, but this was causing timing issues and I can’t really afford to extend my trial time any more (I’d much prefer to not have to use any of my trial time for preloading).

There was one previous post I could find (here) that discussed how to do this, but somewhere in my trying it out the entire experiment got corrupted and gave an error on compiling (oops). Luckily that was a copy.

Is there a reasonable way for me to load all of the stimuli from my file at once? To make things a bit more annoying, I have an excel file corresponding to my experiment blocks, controlling which (of 4) excel condition files should be used for a block.

Hope I’ve given enough information.

You’ve already found a way to do it, from the linked post. We need to figure out why that didn’t work. To give useful suggestions, we need more information than “gave an error on compiling (oops)”.

Ok I gave it another shot, but ran into more errors. For reference, here is the flow:

This differs a bit from the other thread, but here is what I’ve done. At the start of my blocks loop I have a routine (preload) with a code component. In the begin experiment tab I set the stimulus path and import pandas (because I need to access this block’s conditions file and extract the stimulus names from 4 columns):

import pandas as pd
stimulus_path = 'C://Users//L//experiment//stimulus_files'

Then in Begin Routine of that component, using a similar approach to in the other thread:

df = pd.read_excel(block_file) # block file is random choice of 4
print(df) # it works

# I need 4 lists to choose from because I'm presenting 4 stimuli at a time on each trial
targets = []
dists1 = []
dists2 = []
dists3 = []

print(f"{stimulus_path}//{df['target_name'].iloc[0]}.jpg") # this definitely prints the correct path

for row in range(df.shape[0]):#['target_name'].values.tolist():
    targets.append(visual.ImageStim(win=win, image=f"{stimulus_path}//{df['target_name'].iloc[row]}.jpg"))
    dists1.append(visual.ImageStim(win=win, image=f"{stimulus_path}//{df['dist1_name'].iloc[row]}.jpg"))
    dists2.append(visual.ImageStim(win=win, image=f"{stimulus_path}//{df['dist2_name'].iloc[row]}.jpg"))
    dists3.append(visual.ImageStim(win=win, image=f"{stimulus_path}//{df['dist3_name'].iloc[row]}.jpg"))

So I printed out the path to the stimuli and it is definitely correct. I then printed out an element of the list, e.g. targets[-1] and this is what it looks like:

ImageStim(__class__=<class 'psychopy.visual.image.ImageStim'>, autoLog=True, color=array([1., 1., 1.]), colorSpace='rgb', contrast=1.0, depth=0, flipHoriz=False, flipVert=False, image=str(...), interpolate=False, mask=None, maskParams=None, name='unnamed ImageStim', opacity=1.0, ori=0.0, pos=array([0., 0.]), size=array([2.60416667, 2.60416667]), texRes=128, units='height', win=Window(...))

I guess the image=str(...) means it isn’t being set properly?

Finally, in the actual trial routine, and in the Properties box for each of the stimulus components, I used trials.thisN to index into the respective lists (for example $targets[trials.thisN]), and set every repeat.

Here’s the error output:

123.4102     DEPRECATION     Couldn't make sense of requested image.
ioHub Server Process Completed With Code:  0
Traceback (most recent call last):
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\basevisual.py", line 831, in _createTexture
    im = tex.copy().transpose(Image.FLIP_TOP_BOTTOM)
AttributeError: 'ImageStim' object has no attribute 'copy'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\L\Google Drive\PhD\experiments\ATTMEM\Attmem_PsychoPy\phase_1 - Copy_lastrun.py", line 553, in <module>
    target_stim.setImage(targets[trials.thisN])
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 302, in setImage
    setAttribute(self, 'image', value, log)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\tools\attributetools.py", line 141, in setAttribute
    setattr(self, attrib, value)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\tools\attributetools.py", line 32, in __set__
    newValue = self.func(obj, value)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 289, in image
    wrapping=False)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\basevisual.py", line 836, in _createTexture
    raise AttributeError(msg)
AttributeError: Couldn't make sense of requested image.
C:\Program Files\PsychoPy3\lib\site-packages\numpy\core\records.py:854: FutureWarning: fromrecords expected a list of tuples, may have received a list of lists instead. In the future that will raise an error
  return fromrecords(obj, dtype=dtype, shape=shape, **kwds)
##### Experiment ended. #####

Thanks

Can you try to directly access that attribute, like this:

print(targets[-1].image)

and

print(type(targets[-1].image))

Note that you don’t need to escape the forward slashes in paths (it’s only the Windows-esque backslash that causes problems). But also for better reliability and portability, just use relative paths (i.e. starting in your experiment directory, rather than absolute paths starting from C:/. e.g. if your .psyexp file is in the experiment/ folder, then stimulus_path is simply 'stimulus_files/'

The result of print(targets[-1].image is:
C://Users//L//experiment//stimulus_files//platypus.jpg,

and print(type(targets[-1].image)) returns:
<class 'str'>

So it all seems ok… yet the error persists, even after changing to single forward slashes (not sure why I’m doing double slashes in the first place now…).

(The reason for the dodgey paths is that I have the stimulus files in a totally different folder for a different experiment setup and I didn’t want to duplicate that many files.)

I should have looked more closely at the error message. The problem is that line above. This indicates that you are setting the image attribute of an image stimulus with another image stimulus, rather than just a filename.

The issue is that you are creating your image stimuli in advance in code (which is good, and what you want to do), but are then trying to shoehorn them into a Builder stimulus component. These are both the same sorts of objects. An analogy is that you are trying to squeeze a shoe into a shoe, rather than a foot into a shoe. It just isn’t going to work.

Instead, since you’ve created the image stimuli in code, you can draw them in code too, and not use a Builder image component at all. e.g. in the “each frame” tab of a code component on the relevant routine, simply put something like this:

targets[trials.thisN].draw()
1 Like

Oh I see! It seems to run now. Thanks for the help!

So it’s working quite nicely this way, although even after defining a name for each stimulus I present, it does not show up in the outputted data file. I tried setting autoLog=True but still nothing. How can I do this?

The log file and the data file are two different things, but either way, for things you do manually in code, you need to record info explicitly yourself, as Builder doesn’t know anything about these custom-made objects or what you want done with them.

e.g. to save something in the data file once per trial, put something like this in the “begin routine” tab:

thisExp.addData('image_file', targets[trials.thisN].image) # or .name or whatever
1 Like

I have a couple more questions regarding this actually. I basically just want to record the onset/offset times of these stimuli in the data and log files. I see for a component I called fix that the following code executes in the coder view:

            if fix.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
                # keep track of start time/frame for later
                fix.frameNStart = frameN  # exact frame index
                fix.tStart = t  # local t and not account for scr refresh
                fix.tStartRefresh = tThisFlipGlobal  # on global time
                win.timeOnFlip(fix, 'tStartRefresh')  # time at next scr refresh
                fix.setAutoDraw(True)
            if fix.status == STARTED:
                # is it time to stop? (based on global clock, using actual start)
                if tThisFlipGlobal > fix.tStartRefresh + 3 + ITI_duration-frameTolerance:
                    # keep track of stop time/frame for later
                    fix.tStop = t  # not accounting for scr refresh
                    fix.frameNStop = frameN  # exact frame index
                    win.timeOnFlip(fix, 'tStopRefresh')  # time at next scr refresh
                    fix.setAutoDraw(False)

Do I need to basically paste this into the code component on the Every Frame tab and just adapt it for the stimuli that I want to track the time for (and change the .setAutoDraw(True) to .draw()?

At the moment all I did was
in the Begin Routine tab:

thisExp.addData('stimuli_started', tThisFlipGlobal)

and then in the Each Frame tab:

if t == trial_duration:
    thisExp.addData('stimulus_offset', tThisFlipGlobal)

The timings seem a bit off though because I have a fix component and its start time does not agree with the stimuli I’m presenting in pure code.