psychopy.org | Reference | Downloads | Github

Script Keeps Freezing

Hi All,
I wrote a simple script for rating pictures, but the task freezes when I run it for about 20 minutes. I was wondering if anyone might have suggestions for fixing this issue:

# Last updated: Feb. 8, 2020
# Author: Arkadiy

#!/usr/bin/env python 
# -*- coding: utf-8 -*-

########################################################################################
# Conditions: PS*TS*P*S*NS*TS*P*S*NS*NS*TS*P*S*NS*NS*NS*TS*P*S*
# 30 Trials
# 4-6 squares in between each stimulus
# Each Each stimulus: 1 second
# ISI: 175
########################################################################################


# Important all the important libraries, without which the experiment would not work.
from __future__ import absolute_import, division
import os
import pandas as pd
import decimal
from psychopy import locale_setup, prefs, gui, visual, core, data, event, logging, clock
import numpy as np
import sys 
from psychopy.iohub import launchHubServer
from psychopy.core import getTime
from psychopy.hardware import keyboard
import time
from datetime import datetime
import random
from random import choices, shuffle


# Set the Path for stimuli
Stim_Path = '/Users/Ratings/'

# Store info about the experiment session
psychopyVersion = '3.2.4'
expName = 'testing'  # from the Builder filename that created this script
expInfo = {'Study Pool ID': '', 'session': '001'}
dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
if dlg.OK == False:
    core.quit()  # user pressed cancel
expInfo['date'] = data.getDateStr()  # add a simple timestamp
expInfo['expName'] = expName
expInfo['psychopyVersion'] = psychopyVersion

endExpNow = False  # flag for 'escape' or other condition => quit the exp
frameTolerance = 0.001  # how close to onset before 'same' frame

# Setup the Window

win = visual.Window(
    size=(1024, 768), fullscr=True, screen=0, 
    winType='pyglet', allowGUI=False, allowStencil=False,
    monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
    blendMode='avg', useFBO=True, 
    units='height')

# Make the cursor disappear
win.mouseVisible = False
    
# store frame rate of monitor if we can measure it
expInfo['frameRate'] = win.getActualFrameRate()
if expInfo['frameRate'] != None:
    frameDur = 1.0 / round(expInfo['frameRate'])
else:
    frameDur = 1.0 / 60.0  # could not measure, so guess

# Initialize components for Routine "trial"
clock = core.Clock()


# Show Text
def Text(x):
    c = None
    while c==None:   # loop until a key is pressed
        message = visual.TextStim(win, text=str(x), pos=(0.0, 0.0), height=0.05)
        message.draw()
        win.flip()
        core.wait(2)
        c = event.waitKeys(keyList=['space', 'Esc'])
        if 'Esc' in c:
            core.quit()


# fixation cross
fixation = visual.ShapeStim(win, 
    vertices=((0, -0.05), (0, 0.05), (0,0), (-0.05,0), (0.05, 0)),
    lineWidth=10,
    closeShape=False,
    lineColor="white")


d = {'ID': [], 'Date': [], 'Time': [], 'Session': [], 'Trial_Number': [],'Stimulus': [],'Response_Type': [],'Reaction_Time': [], 'choiceHistory':[]}



# Create a list from Pics
Pic_List = []
for root, dirs, files in os.walk(Stim_Path):
    for file in files:
        if file.endswith('.jpg') and not file.startswith('Pract') :
            Pic_List.append(Stim_Path + file)

# Shuffle the list
np.random.shuffle(Pic_List)


# Include Some Info about the experiment
d['ID'].append(('ID'))
d['Date'].append((time.strftime("%m/%d/%Y")))
d['Time'].append((time.strftime("%H:%M")))
d['Session'].append(('Encoding'))

OldNewScale = visual.RatingScale(win, choices=['Old High', 'Old Medium', 'Old Low', 'New Low', 'New Medium', 'New High'], markerStart=2.5, acceptKeys=['space'], leftKeys='left', rightKeys='right', pos= (0.0, -0.5), disappear=True, noMouse=True, skipKeys=None, size = 1, stretch= 2.5, acceptSize=1)

# Present the Pics and store responses
def Present_and_Record():
    trial_count = 0
    for stim in Pic_List:
        Response = []
        Pic_to_Draw = visual.ImageStim(win, image=str(stim), pos=(0.0, 0.1))
        clock.reset()
        while OldNewScale.noResponse:
            Pic_to_Draw.draw()
            OldNewScale.draw()
            line= visual.Line(win, start=(0.0, -0.32), end=(0.0, -0.15), lineWidth=6, ori=0.5)
            line.draw()
            message = visual.TextStim(win, text=str('Old'), pos=(-0.19, -0.18), height=0.05)
            message.draw()
            message = visual.TextStim(win, text=str('New'), pos=(0.19, -0.18), height=0.05)
            message.draw()
            win.flip()
            Response = OldNewScale.getRating()
        trial_count = trial_count + 1
        decisionTime = OldNewScale.getRT()
        d['Stimulus'].append((stim))
        d['Trial_Number'].append((trial_count))
        d['Response_Type'].append((Response))
        d['Reaction_Time'].append((decisionTime))
        OldNewScale.reset()  # reset between repeated uses of the same rating scale        
        Response = []
    Text('Thank you for your participation! You are all done. Please get the experimenter to let him know that you are finished.')


# Record Responses

    return(d)


#a = Practice()
d= Present_and_Record()

# Save Dict as a Df
df= pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in d.items() ]))
# Export df to CSV
df.to_csv('/Users/Ratings.csv', index = None, header=True)


# make sure everything is closed down
win.close()
core.quit()

I don’t see anything obviously wrong with the script itself. My first instinct is a memory load issue. Does it fail after 20 minutes if you don’t advance trials, or does it fail after a certain number of trials? Also, when you say it “freezes”, do you mean it crashes with an error or just locks up and refuses to take further input?

I’d start by testing with a reduced number of trials just to make sure that it’s not something stupid like the experiment failing when it tries to end, or trying it on a different computer with more memory and see if it gets to the end (or try it on one with less and see if it fails faster). If you can isolate why it’s failing, that’ll make it easier to figure out how to fix it.

Alternately, if it’s time-based rather than trial-based (i.e. 20 mins if it’s still on the first trial), it could also be something about the computer itself trying to go to sleep or something and you could check those settings.

The other thing I’d suggest is that your code is very heavy on re-creating stimuli from scratch within loops and functions. From a performance point of view, it is better where possible to create your stimuli just once and just update their properties as required.

e.g. every time you call the Text function, you recreate the message TextStim. This is quite a slow process. Instead, you could just create message once at the start of the script, and then just do this inside the function:

message.text = x 

Actually the Present_and_Record() function is even less efficient, as message is created twice, rather than just being updated. Since the message content in that function is relatively fixed, an even better approach would be to create just two constant text stimuli, message_old and message_new for use in the function, and just draw them as required, without needing to update their content at all (frequently updating text stimulus content has caused memory leaks in the third-party pyglet library that is used for drawing stimuli).

Similarly for your ImageStims, just do this:

Pic_to_Draw.image = stim

rather than recreate the entire stimulus from scratch each time.

Strictly, although slow, this shouldn’t cause memory issues, as each time you create a new stimulus, ideally the memory consumed by the previous one should be freed up, but this might not happen immediately. So you might get some relief by not re-creating stimulus objects all the time. It’s kind of an empirical question though: just try it and see if it makes a difference.

1 Like

@Michael thank you for these suggestions. I optimized the script in accordance to your suggestions, and it no longer crashes. It looks like the memory load by an inefficient loop was indeed the issue. Will be useful to keep this in mind going forward.

1 Like