Best way to create and display many image stimuli simultaneously in Builder

If this template helps then use it. If not then just delete and start from scratch.

OS : Win10
PsychoPy version (e.g. 1.84.x): 2023.2.3
Standard Standalone? (y/n) yes
**What are you trying to achieve?:
Displaying 16 images on screen while controlling them from a condition list

**What did you try to make it work?:

OK, this is going to be a list of related questions.

  1. Initially I thought I could create one ImageStimuli component, and just use deepcopy() to duplicate it in a code component and flexibly set the file names. This didn’t work due to error:
File "C:\Users\AppData\Local\Programs\PsychoPy\lib\socket.py", line 272, in __getstate__
    raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
TypeError: cannot pickle 'socket' object

Seems that ImageStimuli is not copiable?

  1. Then I tried creating all stimuli inside a code component (image = visual.ImageStim()), this resulted in nothing, no error message, no image shown.
    Looking into the script it generated, I found that compared to the one generated by using the Image component, there are chunks of code missing, specifically:
    image_displayComponents = [image]
    for thisComponent in image_displayComponents:
        thisComponent.tStart = None
        thisComponent.tStop = None
        thisComponent.tStartRefresh = None
        thisComponent.tStopRefresh = None
        if hasattr(thisComponent, 'status'):
            thisComponent.status = NOT_STARTED
    # reset timers
    t = 0
    _timeToFirstFrame = win.getFutureFlipTime(clock="now")
    frameN = -1
    
    # --- Run Routine "image_display" ---
    routineForceEnded = not continueRoutine
    while continueRoutine and routineTimer.getTime() < 5.0:
        # get current time
        t = routineTimer.getTime()
        tThisFlip = win.getFutureFlipTime(clock=routineTimer)
        tThisFlipGlobal = win.getFutureFlipTime(clock=None)
        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
        # update/draw components on each frame
        # is it time to end the Routine? (based on local clock)
        if tThisFlip > 5-frameTolerance:
            continueRoutine = False
        
        # *image* updates
        
        # if image is starting this frame...
        if image.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
            # keep track of start time/frame for later
            image.frameNStart = frameN  # exact frame index
            image.tStart = t  # local t and not account for scr refresh
            image.tStartRefresh = tThisFlipGlobal  # on global time
            win.timeOnFlip(image, 'tStartRefresh')  # time at next scr refresh
            # add timestamp to datafile
            thisExp.timestampOnFlip(win, 'image.started')
            # update status
            image.status = STARTED
            image.setAutoDraw(True)
        
        # if image is active this frame...
        if image.status == STARTED:
            # update params
            pass
        
        # if image is stopping this frame...
        if image.status == STARTED:
            # is it time to stop? (based on global clock, using actual start)
            if tThisFlipGlobal > image.tStartRefresh + 1.0-frameTolerance:
                # keep track of stop time/frame for later
                image.tStop = t  # not accounting for scr refresh
                image.frameNStop = frameN  # exact frame index
                # add timestamp to datafile
                thisExp.timestampOnFlip(win, 'image.stopped')
                # update status
                image.status = FINISHED
                image.setAutoDraw(False)

I’m not sure why Builder doesn’t handle manually created ImageStimuli the same way as its prebuilt component equivalent? Can I even realisticly do that in custom code?

I know I can always click Image component 16 times but thought I would ask since it’s an intriguing question. Thank you if you have read through the question.

Mengya
.

How I do this is create a list of visual objects in the Begin Experiment tab of a code component.

images = []
for Idx in range(16):
     images.append( visual.ImageStim(
    win=win,
    name='imageName'+str(Idx), 
    image='imagePath'+str(Idx)+'.png', 
    ori=0, 
    pos=[0,0], 
    size=[.2,.2],
    color=white, 
    colorSpace='rgb', 
    opacity=1,
    flipHoriz=False, 
    flipVert=False,
    texRes=128, 
    interpolate=True))

Then in Begin Routine I can use:

for Idx in range(16):
     images[Idx].setPos(position[Idx]) # There are other ways of setting the positions but having a list is one possibility
     images[Idx].setAutoDraw(True)

and End Routine

for Idx in range(16):
     images[Idx].setAutoDraw(False)

Hi thank you! This works now. The key seems to be adding setAutoDraw(False) at the end of routine.