PsychoPy textStim Memory Leak Issue

0

I've been developing an experiment in the coder section of PsychoPy. It's a relatively simple task but requires a fair amount to text and image drawing. I tried running the entire experiment (consists of 123 trials) a few days ago, and around the 28th iteration I received the following error:

WindowsError: exception: access violation writing 0x00000004

I looked into this, and it appears that there is a memory leak issues caused in pyglet that occurs when drawing a large amount of text stim to the window. I refined my code to only change the text components when the experiment demands it. I'm listing my entire code below as a reference:

from __future__ import division
from psychopy import locale_setup, visual, core, event, data, gui
import numpy as np
import pandas as pd
import sys, os, csv, time, random
from win32api import GetSystemMetrics

#Directory:
cwd = os.path.dirname(os.path.abspath(__file__))

#GUI:
expName = "CMNT"
expInfo = {"participant": "", "session": "001", "condition": "F1"}       #Condition Files: F1, F2, M1, or M2
condition = expInfo["condition"]
condition = expInfo["condition"]
dlg = gui.DlgFromDict(dictionary = expInfo, title = expName, order =  ["participant","session","condition"])
if dlg.OK == False:
    core.quit()

#Window:
win = visual.Window(size = (GetSystemMetrics(0), GetSystemMetrics(1)),     fullscr = True, pos = (0,0), units = "norm", color = "Gray")

#Turn off Mouse
event.Mouse(visible = False)

#Timers:
timer = core.Clock()
breakTimer = core.Clock()

#Load Condition File:
stim_df = pd.read_csv("exp_stimuli.csv", nrows = 124)
run_length = 123

#Output Directory:
fileLocation = cwd + "\%s_data\%s" %(expName, expInfo["participant"])
if not os.path.exists(fileLocation):
    os.makedirs(fileLocation)
os.chdir(fileLocation)
if os.path.isfile("logFile.csv"):
    os.remove("logFile.csv")

#List and Panda File Header 
run_param_list = []
header = ["Block","MentalState", "Condition", "Speaker","Prompt",    "RespButton", "CorrAnswer","RT(sec)", "ACC"]

#Define text and image stimuli
text = visual.TextStim(win = win, text = '', height = 0.1, pos = (0,0),    color="White")
image = visual.ImageStim(win = win, pos = (0,0), size = (0.1,0.1), image = cwd + "\\" + "gray.png")

#Run Instruction Page:
text.text = '\t\tREMEMBER:\n\t\tAnswer each question,\n\t\t"Which should I     pick?"\n\t\tusing the LEFT and RIGHT\n\t\tarrow key.'
text.height = 0.1
text.draw()
win.flip()
while True:
    theseKeys = event.getKeys()
    if "escape" in theseKeys:
        core.quit()
    if len(theseKeys):
        break

#Begin Trials:
for i in xrange(run_length):
    if stim_df["MentalState"][i] == "0":
        breakTimer.reset()
        while breakTimer.getTime() < 15.0:
        text.text = "+"
        text.height = 0.25
        text.pos = (0,0)
        text.draw()
    win.flip()
    else:   
        #win.flip()
        timer.reset()
        exit_press = []
        event_press = []

        speaker = visual.TextStim(win, text = stim_df["speaker"][i], height = 0.1, pos = (0,0.6))
        speaker.setAutoDraw(True)

        #0.5 seconds
        while timer.getTime() < 0.5:
            win.flip()   

        #3.0 seconds
        if stim_df["speaker"][i] == "Computer":
            text.text = stim_df["prompt"][i]
            text.pos = (-0.1,0)
            text.height = 0.08
            text_box_length = text.boundingBox[0]/GetSystemMetrics(0)*2

            image.image = cwd + "\\" + "gray.png"
            image.pos = (-0.1,0)
            image.size = (text_box_length, 0.35)

            image.draw()
            text.draw()
        else:
            text.text = stim_df["prompt"][i]
            text.pos = (-0.1,0)
            text.height = 0.08
            text_box_length = text.boundingBox[0]/GetSystemMetrics(0)*2

            image.image = cwd + "\\" + "blue.png"
            image.pos = (-0.1,0)
            image.size = (text_box_length, 0.35)

            image.draw()
            text.draw()

        win.flip()
        while timer.getTime() < 3.5:
            exit_press += event.getKeys()
            if "escape" in exit_press:
                core.quit()            
        onset_Time = timer.getTime()
        speaker.setAutoDraw(False)

        #3.5 seconds
        text.text = stim_df["question"][i]
        text.pos = (0,0.6)
        text.height = 0.1
        text.draw()

        text.text = stim_df["answerA"][i]
        text.pos = (-0.3, -0.5)
        text.draw()

        text.text = stim_df["answerB"][i]
        text.pos = (0.3, -0.5)
        text.height = 0.1
        text.draw()
        win.flip()

        length = 0
        while timer.getTime() < 7.0:
            event_press += event.getKeys(keyList = ["left","right"])
            if len(event_press) > length:
                RT = timer.getTime() - onset_Time
                length = len(event_press)
            elif len(event_press) == 0:
                RT = "N/A"            
            exit_press += event.getKeys()
            if "escape" in exit_press:
                core.quit()

        #Jitter 1 time
        text.text = "+"
        text.pos = (0,0)
        text.height = 0.25
        text.draw()
        win.flip()        
        while timer.getTime() < 7.0 + (stim_df["jitter1"][i]/1000):
            exit_press += event.getKeys()
            if "escape" in exit_press:
                core.quit()
        #Check Conditional Input        
        try:
            response = event_press[-1]
        except:
            response = None

        if response == "left":
            reply = stim_df["answerA"][i]
            if reply == stim_df["corrAnswer"][i]:
                acc = 1
            else:
                acc = 0   
        elif response == "right":
            reply = stim_df["answerB"][i]
            if reply == stim_df["corrAnswer"][i]:
                acc = 1
            else:
                acc = 0
        elif response == None:
            response = "N/A"
            reply = stim_df["corrAnswer"][i]
            acc = 0

        text.text = reply
        text.height = 0.08
        text.pos = (0.1,0.3)
        #2 seconds   
        if stim_df["speaker"][i] == "Computer":     
            text.text = reply
            text.height = 0.08
            text.pos = (0.1,0.3)

            speaker.draw()
            image.image = cwd + "\\" + "gray.png"
            image.size = (text.boundingBox[0]/GetSystemMetrics(0)*2 + 0.2,  text.boundingBox[1]/GetSystemMetrics(1)*2 + 0.1)
            image.pos = (0.1, 0.3)

            image.draw()
            text.draw()

            text.pos = (-0.1,-0.1)
            text.text = reply + u" \u2713"
            text_box_length = text.boundingBox[0]/GetSystemMetrics(0)*2 + 0.1

            image.image = cwd + "\\" + "gray.png"
            image.size = (text.boundingBox[0]/GetSystemMetrics(0)*2 + 0.1, text.boundingBox[1]/GetSystemMetrics(1)*2 + 0.1)
            image.pos = (-0.1,-0.1)

            image.draw()
            text.draw()
        else:
            text.text = reply
            text.height = 0.08
            text.pos = (0.1,0.3)

            speaker.draw()
            image.image = cwd + "\\" + "green.png"
            image.size = (text.boundingBox[0]/GetSystemMetrics(0)*2 + 0.1, text.boundingBox[1]/GetSystemMetrics(1)*2 + 0.1)
            image.pos = (0.1,0.3)

            image.draw()
            text.draw()

            text.text = reply + " :)"
            text.pos = (-0.1,-0.1)

            image.image = cwd + "\\" + "blue.png"
            image.size = (text.boundingBox[0]/GetSystemMetrics(0)*2 + 0.1, text.boundingBox[1]/GetSystemMetrics(1)*2 + 0.1)
            image.pos = (-0.1, -0.1)

            image.draw()
            text.draw()
        win.flip()
        while timer.getTime() < 9.0 + (stim_df["jitter1"][i]/1000):
            exit_press += event.getKeys()
            if "escape" in exit_press:
                core.quit()

        #Jitter 2 time
        text.text = "+"
        text.height = 0.25
        text.pos = (0,0)
        text.draw()
        win.flip()
        while timer.getTime() < 11.0 + (stim_df["jitter1"][i]/1000) + (stim_df["jitter2"][i]/1000):
            exit_press += event.getKeys()
            if "escape" in exit_press:
                core.quit()

        #Panda Output File
        run_param_list.append([stim_df["block"][i], stim_df["MentalState"][i], condition, stim_df["speaker"][i], stim_df["prompt"][i], response, stim_df["corrAnswer"][i], RT, acc])
        fid = pd.DataFrame(run_param_list, columns = header)
        fid.to_csv("logFile.csv", header = True)

#Close up Shop:
win.close()
core.quit()

I was wondering if anyone has a suggestion as to how to refine the code better so that the memory leak error won't occur. If this error can't be fixed, would the best solution be to replicate the experiment through PsychoPy Builder?

python
psychopy
asked on Stack Overflow Apr 15, 2016 by djl

1 Answer

1

My guess is that this comes from a memory leak that seems to exist in pyglet (which is what we use for text rendering): https://bitbucket.org/pyglet/pyglet/issues/66/memory-leak-in-fonttext Sounds like one of the pyglet developers has found a fix for it but that isn't going to be included for a little while.

PsychoPy also has a stimulus class called TextBox. That's written entirely from the ground up (by Sol Simpson) and is more efficient in many ways but doesn't support fonts very well (specifically, only supports mono-spaced fonts at the moment). I'm sure that won't show the same memory problems if you're able to use that.

answered on Stack Overflow Apr 18, 2016 by Jon

User contributions licensed under CC BY-SA 3.0