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?

djl
  • 267
  • 1
  • 3
  • 13
  • Developing a minimal reproducible example would help you to narrow down the problem, as well as increase the odds of someone actually reading your code. – Michael MacAskill Apr 16 '16 at 05:03
  • i.e. it is hard to tell if you are actually following the established guidance to only update the text property of a text stimulus if that value has actually changed. Also, where ever possible, don't create text stimuli multiple times. Create them once, and then just update their properties as required. – Michael MacAskill Apr 16 '16 at 05:05
  • Hi Michael, could you clarify what the established guidance is for updating text and image stimuli properly, because I thought I was doing it correctly. For example, near the top of my script I set am image variable called image, and then throughout the rest of the script if I need to change one of its attributes (i.e position, the image itself) then I would change it by doing image.image = etc, image.pos = etc. I thought this method was a good way to save on memory. Is there a more efficient way of updating their properties? – djl Apr 17 '16 at 12:29

1 Answers1

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.

Jon
  • 1,198
  • 7
  • 8