Numpy MemoryError when displaying successive PNGs

Hello PsychoPy users and gurus,

first of all, thanks for all of your help with my previous issue!

I’ve run into another problem with some custom code and I’m wondering if you can help me debug?

I’m having trouble with the display of a series of instruction screens that were converted from powerpoint to pngs. These images are 0.8 - 3 MB, but that shouldn’t be a problem for the system, given that it successfully can show videos in other tasks on the same computer. I had no problems when the images were cycled through based on a fixed presentation length. However, after adapting some code from elsewhere in the task to allow the user to advance through the instructions at their own pace, we started having some memory errors. These errors are somewhat idiosyncratic since they don’t always happen but, if they do, they always occur during the presentation of the instructions somewhere between the start of the 2nd and start of the 5th block (out of 5 blocks) of our task. The errors started happening around the start of 2nd or start of the 3rd block, and we tried to do whatever we could do reduce the memory load on the system (e.g. restarting the CPU right before running the task and having no other programs running). Now the crash is happening consistently during the display of instructions for the 5th block.

I’m attaching the error here, and can upload the whole script if you would like. Do you think that I may have inadvertently introduced a memory leak when I changed how to advance through the instructions? If so, do you have any suggestions on how to find it / test it? Also, is there some code I can add which will force PsychoPy to release any memory associated with that routine after each iteration?

I would really appreciate your help with this somewhat enigmatic error!

Regards,
Craig.

##### Running: C:\Users\Stimulus PC\Desktop\Tasks\Session_2\Pictures_1\Pictures_1_task.py #####
pyo version 0.8.7 (uses single precision)
Traceback (most recent call last):
  File "C:\Users\Stimulus PC\Desktop\Tasks\Session_2\Pictures_1\Pictures_1_task.py", line 554, in <module>
    ReminderOfInstructions2.setImage("Reminder_Slides_2.png")
  File "C:\Program Files (x86)\PsychoPy2\lib\site-packages\psychopy\visual\image.py", line 289, in setImage
    setAttribute(self, 'image', value, log)
  File "C:\Program Files (x86)\PsychoPy2\lib\site-packages\psychopy\tools\attributetools.py", line 137, in setAttribute
    setattr(self, attrib, value)
  File "C:\Program Files (x86)\PsychoPy2\lib\site-packages\psychopy\tools\attributetools.py", line 27, in __set__
    newValue = self.func(obj, value)
  File "C:\Program Files (x86)\PsychoPy2\lib\site-packages\psychopy\visual\image.py", line 276, in image
    forcePOW2=False)
  File "C:\Program Files (x86)\PsychoPy2\lib\site-packages\psychopy\visual\basevisual.py", line 855, in _createTexture
    intensity = numpy.array(im)
MemoryError

File size is only tangentially related to memory usage. e.g. Consider two 1920 × 1080 images, one a plain rectangle of a uniform colour, and the other having completely random pixel colour values. Using .png compression, the first will lead to a quite tiny file size, with the second being relatively large. But their memory usage when decompressed will be identical (approximately 1920 × 1080 × 3 bytes). So the relevant metric here for memory usage is pixel dimensions. Do all of your .pngs have pixel dimensions that are no more than the pixel dimensions of the image stimulus on your display? Any extra pixels are using memory but not being of benefit to the stimulus, which is scaled down as required.

I think video is streamed from the file, not loaded into memory in one hit, so again, file size here is not really a useful guide to memory usage.

Having said all the above, the memory used for each stimulus should be liberated when you apply a new image to it. But if there is a problem with memory leaking/not being realised, this may be delayed by ensuring that all of the source images are no larger than required.

To understand if your code is doing something which would lead to a memory leak, we probably need to see the relevant parts.

Please don’t: no-one will read it. Isolate the relevant parts. As part of that process, you might find it useful to strip down your experiment to the barebones, to the smallest state that creates this problem.

Was there anything else? “memory error” seems a bit terse.

Hi Michael,

thanks for your rapid and in-depth reply. Unfortunately, there was no other error output from PsychoPy, but I’m attaching the relevant bits of code below.

Here is where I set up the instruction screen components:

ReminderOfInstructions = visual.ImageStim(
    win=win, name='ReminderOfInstructions',units='norm', 
    image='sin', mask=None,
    ori=0, pos=(0, 0), size=(2,2),
    color=[1,1,1], colorSpace='rgb', opacity=1,
    flipHoriz=False, flipVert=False,
    texRes=128, interpolate=True, depth=-1.0)
    
ReminderOfInstructions2 = visual.ImageStim(
    win=win, name='ReminderOfInstructions2',units='norm', 
    image='sin', mask=None,
    ori=0, pos=(0, 0), size=(2,2),
    color=[1,1,1], colorSpace='rgb', opacity=1,
    flipHoriz=False, flipVert=False,
    texRes=128, interpolate=True, depth=-1.0)
    
ReminderOfInstructions3 = visual.ImageStim(
    win=win, name='ReminderOfInstructions3',units='norm', 
    image='sin', mask=None,
    ori=0, pos=(0, 0), size=(2,2),
    color=[1,1,1], colorSpace='rgb', opacity=1,
    flipHoriz=False, flipVert=False,
    texRes=128, interpolate=True, depth=-1.0)

Here is the code that executes the routine which includes the instructions:

    # ------Prepare to start Routine "Block_Intro"-------
    starttime = core.getAbsTime()

    t = 0
    Block_IntroClock.reset()  # clock
    frameN = -1
    continueRoutine = True
    # update component parameters for each repeat
    BlockNumber.setText('Welcome to ' + number + '.\n\n' + 'Please press space to begin the review of the instructions.')
    key_resp_3 = event.BuilderKeyResponse()
    key_resp_4 = event.BuilderKeyResponse()
    key_resp_5 = event.BuilderKeyResponse()
    key_resp_6 = event.BuilderKeyResponse()
    key_resp_7 = event.BuilderKeyResponse()
    # keep track of which components have finished
    Block_IntroComponents = [BlockNumber, key_resp_3, ReminderOfInstructions, key_resp_5, ReminderOfInstructions2, key_resp_6, ReminderOfInstructions3, key_resp_7, scan_start, key_resp_4, p_port_2]
    for thisComponent in Block_IntroComponents:
        if hasattr(thisComponent, 'status'):
            thisComponent.status = NOT_STARTED
    
    # -------Start Routine "Block_Intro"-------
    while continueRoutine:
        # get current time
        t = Block_IntroClock.getTime()
        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
        # update/draw components on each frame       
        
        # *p_port_2* updates
        if t >= 0.0 and p_port_2.status == NOT_STARTED:
        # keep track of start time/frame for later
            p_port_2.tStart = t
            p_port_2.frameNStart = frameN  # exact frame index
            p_port_2.status = STARTED
            win.callOnFlip(p_port_2.setData, int(189))
        frameRemains = 0.0 + 1.0- win.monitorFramePeriod * 0.75  # most of one frame period left
        if p_port_2.status == STARTED and t >= frameRemains:
            p_port_2.status = STOPPED
            win.callOnFlip(p_port_2.setData, int(0))        
        
        # *BlockNumber* updates
        if t >= 0.5 and BlockNumber.status == NOT_STARTED:
            # keep track of start time/frame for later
            BlockNumber.tStart = t
            BlockNumber.frameNStart = frameN  # exact frame index
            BlockNumber.setAutoDraw(True)
        if BlockNumber.status == STARTED and bool(key_resp_3.keys):
            BlockNumber.setAutoDraw(False)
            
        
        # *key_resp_3* updates
        if t >= 0.5 and key_resp_3.status == NOT_STARTED:
            # keep track of start time/frame for later
            key_resp_3.tStart = t
            key_resp_3.frameNStart = frameN  # exact frame index
            key_resp_3.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(key_resp_3.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        #if key_resp_3.status == STARTED and bool(ReminderOfInstructions.status==FINISHED):
            #key_resp_3.status = STOPPED
        if key_resp_3.status == STARTED:
            theseKeys = event.getKeys(keyList=['space'])
            
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                key_resp_3.keys = theseKeys[-1]  # just the last key pressed
                key_resp_3.rt = key_resp_3.clock.getTime()
                win.flip()
                event.clearEvents(eventType='keyboard')
                key_resp_3.status = STOPPED

        
        # *ReminderOfInstructions* updates
        if (key_resp_3.keys) and ReminderOfInstructions.status == NOT_STARTED:
            # keep track of start time/frame for later
            ReminderOfInstructions.setImage("Reminder_Slides_1.png")
            ReminderOfInstructions.tStart = t
            ReminderOfInstructions.frameNStart = frameN  # exact frame index
            ReminderOfInstructions.setAutoDraw(True)
        if ReminderOfInstructions.status == STARTED and bool(key_resp_5.keys):
            ReminderOfInstructions.setAutoDraw(False)
            
            
        # *key_resp_5* updates
        if ReminderOfInstructions.status == STARTED and key_resp_5.status == NOT_STARTED:
            # keep track of start time/frame for later
            key_resp_5.tStart = t
            key_resp_5.frameNStart = frameN  # exact frame index
            key_resp_5.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(key_resp_5.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        #if key_resp_5.status == STARTED and bool(ReminderOfInstructions.status==FINISHED):
            #key_resp_5.status = STOPPED
        if key_resp_5.status == STARTED:
            theseKeys = event.getKeys(keyList=['space'])   
            
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                key_resp_5.keys = theseKeys[-1]  # just the last key pressed
                key_resp_5.rt = key_resp_5.clock.getTime()
                win.flip()
                event.clearEvents(eventType='keyboard')
                key_resp_5.status = STOPPED
            
            
        # *ReminderOfInstructions2* updates
        if (key_resp_5.keys) and ReminderOfInstructions2.status == NOT_STARTED:
            # keep track of start time/frame for later
            ReminderOfInstructions2.setImage("Reminder_Slides_2.png")
            ReminderOfInstructions2.tStart = t
            ReminderOfInstructions2.frameNStart = frameN  # exact frame index
            ReminderOfInstructions2.setAutoDraw(True)
        if ReminderOfInstructions2.status == STARTED and bool(key_resp_6.keys):
            ReminderOfInstructions2.setAutoDraw(False)
            
        # *key_resp_6* updates
        if ReminderOfInstructions2.status == STARTED and key_resp_6.status == NOT_STARTED:
            # keep track of start time/frame for later
            key_resp_6.tStart = t
            key_resp_6.frameNStart = frameN  # exact frame index
            key_resp_6.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(key_resp_6.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        #if key_resp_6.status == STARTED and bool(ReminderOfInstructions2.status==FINISHED):
            #key_resp_6.status = STOPPED
        if key_resp_6.status == STARTED:
            theseKeys = event.getKeys(keyList=['space'])   
            
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                key_resp_6.keys = theseKeys[-1]  # just the last key pressed
                key_resp_6.rt = key_resp_6.clock.getTime()
                win.flip()
                event.clearEvents(eventType='keyboard')
                key_resp_6.status = STOPPED
             
            
        # *ReminderOfInstructions3* updates
        if (key_resp_6.keys) and ReminderOfInstructions3.status == NOT_STARTED:
            # keep track of start time/frame for later
            ReminderOfInstructions3.setImage("Reminder_Slides_3.png")
            ReminderOfInstructions3.tStart = t
            ReminderOfInstructions3.frameNStart = frameN  # exact frame index
            ReminderOfInstructions3.setAutoDraw(True)
        if ReminderOfInstructions3.status == STARTED and bool(key_resp_7.keys):
            ReminderOfInstructions3.setAutoDraw(False)
            
        # *key_resp_7* updates
        if ReminderOfInstructions3.status == STARTED and key_resp_7.status == NOT_STARTED:
            # keep track of start time/frame for later
            key_resp_7.tStart = t
            key_resp_7.frameNStart = frameN  # exact frame index
            key_resp_7.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(key_resp_7.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        #if key_resp_7.status == STARTED and bool(ReminderOfInstructions3.status==FINISHED):
            #key_resp_7.status = STOPPED
        if key_resp_7.status == STARTED:
            theseKeys = event.getKeys(keyList=['space'])    
            
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                key_resp_7.keys = theseKeys[-1]  # just the last key pressed
                key_resp_7.rt = key_resp_7.clock.getTime()
                win.flip()
                event.clearEvents(eventType='keyboard')
                key_resp_7.status = STOPPED
            
            
        # *scan_start* updates
        if (key_resp_7.keys) and scan_start.status == NOT_STARTED:
            # keep track of start time/frame for later
            scan_start.tStart = t
            scan_start.frameNStart = frameN  # exact frame index
            scan_start.setAutoDraw(True)
        if scan_start.status == STARTED and bool(key_resp_4.keys):
            scan_start.setAutoDraw(False)
            
        # *key_resp_4* updates
        if t >= scan_start.status == STARTED and key_resp_4.status == NOT_STARTED:
            # keep track of start time/frame for later
            key_resp_4.tStart = t
            key_resp_4.frameNStart = frameN  # exact frame index
            key_resp_4.status = STARTED
            # keyboard checking is just starting
            win.callOnFlip(key_resp_4.clock.reset)  # t=0 on next screen flip
            event.clearEvents(eventType='keyboard')
        if key_resp_4.status == STARTED and bool(scan_start.status==FINISHED):
            key_resp_4.status = STOPPED
        if key_resp_4.status == STARTED:
            theseKeys = event.getKeys(keyList=['space'])
            
            # check for quit:
            if "escape" in theseKeys:
                endExpNow = True
            if len(theseKeys) > 0:  # at least one key was pressed
                key_resp_4.keys = theseKeys[-1]  # just the last key pressed
                key_resp_4.rt = key_resp_4.clock.getTime()
            
        
        # check if all components have finished
        if not continueRoutine:  # a component has requested a forced-end of Routine
            break
        continueRoutine = False  # will revert to True if at least one component still running
        for thisComponent in Block_IntroComponents:
            if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
                continueRoutine = True
                break  # at least one component has not yet finished
        
        # check for quit (the Esc key)
        if endExpNow or event.getKeys(keyList=["escape"]):
            core.quit()
        
        # refresh the screen
        if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
            win.flip()
    
    # -------Ending Routine "Block_Intro"-------
    for thisComponent in Block_IntroComponents:
        if hasattr(thisComponent, "setAutoDraw"):
            thisComponent.setAutoDraw(False)
    # check responses
    if key_resp_3.keys in ['', [], None]:  # No response was made
        key_resp_3.keys=None
    trials_2.addData('key_resp_3.keys',key_resp_3.keys)
    if key_resp_3.keys != None:  # we had a response
        trials_2.addData('key_resp_3.rt', key_resp_3.rt)
    if key_resp_4.keys in ['', [], None]:  # No response was made
        key_resp_4.keys=None
    trials_2.addData('key_resp_4.keys',key_resp_4.keys)
    if key_resp_4.keys != None:  # we had a response
        trials_2.addData('key_resp_4.rt', key_resp_4.rt)
    trials_2.addData('Block_start_time',starttime)
   
    if p_port_2.status == STARTED:
        win.callOnFlip(p_port_2.setData, int(0))
        
    # the Routine "Block_Intro" was not non-slip safe, so reset the non-slip timer
    routineTimer.reset()
    
    # set up handler to look after randomisation of conditions etc
    trials = data.TrialHandler(nReps=1, method='random', 
        extraInfo=expInfo, originPath=-1,
        trialList=data.importConditions(csv),
        seed=None, name='trials')
    thisExp.addLoop(trials)  # add the loop to the experiment
    thisTrial = trials.trialList[0]  # so we can initialise stimuli with some values
    # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
    if thisTrial != None:
        for paramName in thisTrial.keys():
            exec(paramName + '= thisTrial.' + paramName)
    
    for thisTrial in trials:
        currentLoop = trials
        # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
        if thisTrial != None:
            for paramName in thisTrial.keys():
                exec(paramName + '= thisTrial.' + paramName)
        

Please let me know if you need more details or an explanation of what is else is going on in the routine, etc.

Thanks again for your help with this!

Best,
Craig.

Hello all,

just wanted to write again to see if anyone had any insight into the memory issue that we have been having? We’re trying to launch the study this week if possible. Thanks again for all the help so far!