Trouble preloading and randomly selecting an image

Hi all! I’m creating a visual N-back working memory task. I’ve built a verbal version of this before, but I’m having a hard time converting what I learned during that process to a visual version of the task (using line drawings instead of letters). Specifically, I’m struggling to get the images to preload and then randomly select from the list of stimuli.

OS (e.g. Win10): macOS catalina 10.15.6
PsychoPy version (e.g. 1.84.x): v2020.1.2
Standard Standalone? (y/n) yes
What are you trying to achieve?: use code component to 1) preload visual stimuli and then 2) randomly select and display an image

What did you try to make it work?:

Code component for the N=0 target routine
Begin experiment

Target0=[]

# get the list of filenames with the relevant extension:
import glob
import os
filenames = glob.glob(os.path.join('/stim/','line*.png'))

# need to start with an empty list
images = []
# create an image stimulus from each file, and store in the list:
for file in filenames:
    images.append(visual.ImageStim(win=win, image=file))

Begin routine

import numpy as np
import random
Target0 = np.random.choice(images, size=1,replace=True)
thisExp.addData('Target0', Target0)

Each frame

Target0.draw()
win.flip()

Code component for the N=0 routine where subject indicate if the image presented matches the aforementioned target image
Begin experiment

image0=[]

Begin routine

#referring to the 0back conditions xlsx file specifies in the 0back loop properties
if trialType=='nonTarget':
    from random import choice
    image0=choice(images)
    if image0==Target0:
            from random import choice
            image0=choice(images)
elif trialType=='target':
    image0=Target0
thisExp.addData('image0', image0)

Each frame

image0.draw()
win.flip()

What specifically went wrong when you tried that?:

When I try to run the experiment, I get the following error:

Running: /Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py

2020-10-13 15:37:35.127 python[61595:8356841] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to (null)
Traceback (most recent call last):
File “/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py”, line 447, in
Target0 = np.random.choice(images,size=1,replace=True)
File “mtrand.pyx”, line 1125, in mtrand.RandomState.choice
ValueError: ‘a’ cannot be empty unless no samples are taken

Experiment ended.

So I think the issue may originate from the images.append step?? Does np.random.choice not work for lists of images? Any help/advice would be greatly appreciated.

Hi,

A few suggestions:

You don’t need to import numpy, Builder does this at the top of all scripts.

Don’t import random: that is Python’s own built-in random library, which you are not actually using (you’re using numpy.random, which is better).

We normally only want to save values like strings of characters or numbers in the data file. Here, Target0 is supposed to be an image object: that won’t really store useful info here in a text output format. I think what you want is the name of the associated file, which I think would be something like Target0.name

I’m guessing the cause of this rather cryptic error is that your list is actually empty, and so a sample can’t be taken from it.

So you should insert some (temporary) debugging code earlier on, like:

filenames = glob.glob(os.path.join('/stim/','line*.png'))

# DEBUG:
print(filenames)

# need to start with an empty list
images = []
# create an image stimulus from each file, and store in the list:
for file in filenames:
    images.append(visual.ImageStim(win=win, image=file))

# DEBUG:
print(images)

What output do you see from those print statements?

If you are using Builder then you shouldn’t use draw or flip

Thanks for your help! I removed the unnecessary import statements. When I add some debugging code, I get the following output code:

## Running: /Users/meganhuibregtse/OneDrive - Indiana University/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py ##
filenames output:
[]
images output:
[]
13.9937     WARNING     t of last frame was 27.34ms (=1/36)
14.0165     WARNING     t of last frame was 22.77ms (=1/43)
2020-10-13 19:02:58.863 python[73153:8479379] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to (null)
Traceback (most recent call last):
  File "/Users/meganhuibregtse/OneDrive - Indiana University/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py", line 452, in <module>
    Target0 = np.random.choice(a=images,size=1,replace=True)
  File "mtrand.pyx", line 1125, in mtrand.RandomState.choice
ValueError: 'a' cannot be empty unless no samples are taken
##### Experiment ended. #####

So you’re right, images is empty (because so is filenames!)

I switched back to an old way I had grabbed the image filenames from the stim folder

path = os.getcwd()
filenames = glob.glob(os.path.join(path, 'stim/*.png'))

Now the output indicates that filenames and images contain the correct contents; however, now the following error message appears in the output:

## Running: /Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py ##
filenames output:
['/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line1.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line2.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line3.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line7.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line6.png', '/Users/meganhuibregtse//Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line4.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line5.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line10.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line11.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line12.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line8.png', '/Users/meganhuibregtse/Dissertation/fMRI Tasks/TASKS/Visual_Mem/stim/line9.png']
images output:
[<psychopy.visual.image.ImageStim object at 0x1338ba390>, <psychopy.visual.image.ImageStim object at 0x1338ba8d0>, <psychopy.visual.image.ImageStim object at 0x1338c9860>, <psychopy.visual.image.ImageStim object at 0x1338c9a20>, <psychopy.visual.image.ImageStim object at 0x1338c9518>, <psychopy.visual.image.ImageStim object at 0x1338c9128>, <psychopy.visual.image.ImageStim object at 0x1338c9b70>, <psychopy.visual.image.ImageStim object at 0x1338c9c50>, <psychopy.visual.image.ImageStim object at 0x1338c9d30>, <psychopy.visual.image.ImageStim object at 0x1338c9e10>, <psychopy.visual.image.ImageStim object at 0x1338c9ef0>, <psychopy.visual.image.ImageStim object at 0x1338c9f98>]
2020-10-13 20:16:49.834 python[78053:8525732] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to (null)
Traceback (most recent call last):
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/data/experiment.py", line 216, in addData
    hash(value)
TypeError: unhashable type: 'numpy.ndarray'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 180, in deepcopy
  File "copy.pyc", line 280, in _reconstruct
  File "copy.pyc", line 150, in deepcopy
  File "copy.pyc", line 240, in _deepcopy_dict
  File "copy.pyc", line 169, in deepcopy
ValueError: ctypes objects containing pointers cannot be pickled

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/meganhuibregtse/OneDrive - Indiana University/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py", line 456, in <module>
    thisExp.addData('Target0', Target0)
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/data/experiment.py", line 219, in addData
    value = copy.deepcopy(value)
  File "copy.pyc", line 161, in deepcopy
SystemError: <built-in method __deepcopy__ of numpy.ndarray object at 0x137ec6760> returned a result with an error set
Exception ignored in: <bound method ImageStim.__del__ of <psychopy.visual.image.ImageStim object at 0x13dbea240>>
Traceback (most recent call last):
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/image.py", line 239, in __del__
    self.clearTextures()
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/basevisual.py", line 1015, in clearTextures
    GL.glDeleteTextures(1, self._texID)
AttributeError: 'ImageStim' object has no attribute '_texID'
Exception ignored in: <bound method Window.__del__ of <psychopy.visual.window.Window object at 0x13dbea0b8>>
Traceback (most recent call last):
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/psychopy/visual/window.py", line 576, in __del__
    if self._closed is False:
AttributeError: 'Window' object has no attribute '_closed'
Exception ignored in: <bound method CocoaScreenMode.__del__ of CocoaScreenMode(width=None, height=None, depth=None, rate=None)>
Traceback (most recent call last):
  File "/Applications/PsychoPy3.app/Contents/Resources/lib/python3.6/pyglet/canvas/cocoa.py", line 111, in __del__
    quartz.CGDisplayModeRelease(self.cgmode)
AttributeError: 'CocoaScreenMode' object has no attribute 'cgmode'
##### Experiment ended. #####

So from my rudimentary knowledge of interpreting the output code, it looks like images is a numpy.ndarray and not exactly an array of ImageStim to chose from in the begin routine step? This is the error I get now:

  File "/Users/meganhuibregtse/OneDrive - Indiana University/Dissertation/fMRI Tasks/TASKS/Visual_Mem/N-back_visual_lastrun.py", line 453, in <module>
    thisExp.addData('Target0',Target0.name)
AttributeError: 'numpy.ndarray' object has no attribute 'name'

How do I get PsychoPy to recognize Target0 as an image? And then display it on the screen?

The issue isn’t with PsychoPy here :wink:

Python is a very flexible language, but we as programmers need to be very aware of what types our objects are: Python will be very flexible until it suddenly gets very fussy.

I got lost a little bit lost with the errors, as clearly at one point you were still doing this:

thisExp.addData('Target0', Target0)

and as noted, we only want to try to save simple types (like numbers and strings) in the datafile, not complex objects like image stimuli (this is causing all sorts of complex memory copying and so on and leading to errors). Removing that alone should help things. But later you show an that you are using:

thisExp.addData('Target0', Target0.name)

but it isn’t clear where or when that occurs (in a different run with the problem above removed)?

Again, what you want is some debugging strategies. Along with simple print() statements, you can use the type() function to check what an object is. i.e. at various points in your code, is the object you are dealing with what you think it is? e.g.

print(type(Target0))

Use this after you create Target0 and potentially at points afterwards, to see why it isn’t the ImageStim that you think it should be. e.g. maybe somehow it comes out as an array of length 1, and you just need to extract it from that array? I can’t tell without seeing the code.

1 Like

Haha very true! Ok so I don’t think I need the “thisExp.addData” step.

The output for

print(type(Target0))

is
<class ‘numpy.ndarray’>

And when I try

print(type(Target0.name))

this throws an error (AttributeError: ‘numpy.ndarray’ object has no attribute ‘name’) which makes sense.

When I try

print(Target0)
print(len(Target0))

I get
[<psychopy.visual.image.ImageStim object at 0x1341adba8>]
1

So when I want to draw the Target0 image in the Each Frame step, I just have to use Target0[0].draw and it works!!!

I was able to apply this to the rest of my experiment, and it seems to have worked for the 1-back and 2-back conditions, too. Thanks for your help!

For ease, you might just want to do:

Target0 = Target0[0]

To get rid of (however it arose) the enclosing array.

Yes, as long as you do take care to record Target0.name (now that you can access that object and its attribute), so that you have a record of it on each trial.

thisExp.addData('Target0', Target0.name)

win.flip() is definitely out in a Builder script (in nearly all cases). But some_stimulus.draw() is fine for objects that have been created in code: Builder won’t know they exist and so can’t display them itself.

Are you sure? I use setAutoDraw(True). My understanding is that .draw() fails because it only draws something for one frame, though I guess you could repeat the command every frame.

It’s all about your choice of code logic. If the code is running every frame, then using .draw() is perfectly fine. And if you only want the stimulus to appear for one frame, its more efficient to just draw it once, rather than set and then unset autoDraw.

If the display of the stimulus is conditional, then it’s simply a choice between setting auto draw to True, but then also remembering to set it to False at some other point, or actively drawing it on every frame, but only when the condition is met. The two approaches are equivalent, because you still need conditional checks to run on every frame.

I prefer active drawing, because the code is more obvious when being read (i.e. its possible to set autoDraw to True in a previous code block, and be wondering why the stimulus is appearing when there is no reference to it in the current code). Others will prefer using autoDraw because it can be more concise - it’s just personal preference. But active drawing can certainly make it easier to control or change the layering of stimuli - that can be a challenge with autoDraw, as everything there is put in an ordered queue that you don’t have easy control over.

1 Like

Thanks for the explanation. I did wonder whether this might be the case, just as I posted my question. I’ll edit my crib sheet accordingly. Logically I think it makes most sense to use AutoDraw if you are activating an object for the whole routine and draw if you are only going to be drawing it for a subset of frames.

I’ve been trying to use .draw() but it’s not working for me online. If I set an object to draw it seems to autodraw and I end up with overlaid objects instead of (as I’m working on at the moment) RSVP. So instead I’m going to switch AutoDraw on and off.