psychopy.org | Reference | Downloads | Github

Memory Leak Issue - troubleshooting - 14 questions


#1

So it seems I’ve managed to write code with a memory leak (the memory usage consistently ramps up and then eventually the entire computer crashes (thus not leaving any error messages to guide me on my way))… Annoyingly Python memory leaks are rare (http://tech.labs.oliverwyman.com/blog/2008/11/14/tracing-python-memory-leaks/ ) so I must have done something seriously wrong somewhere…

My first question (Q1) is: was this ever resolved - https://stackoverflow.com/questions/36651509/psychopy-textstim-memory-leak-issue ? I update textStim regularly throughout in the following fashion:

score_text = visual.TextStim(win=win, name='score_text', text= score_update , font='Arial', pos=(0.6, 0.3), height=0.1, wrapWidth=None, ori=0,color='white', colorSpace='rgb', opacity=1, depth=-2.0)

So basically the only thing that updates is the text in the variable ‘score_update’, but this happens on every single trial. Could the memory leak be something to do with that? Should I use TextBox instead?

As I’ve Frankenstein-ed a lot of the pre-existing PsychoPy code (bad practice I know, but it’s way too late for me to consider going back and doing it any other way – the code has taken me months and other than this memory leak it does exactly what I want it to now… Also I’m pretty sure that this training part of the experiment couldn’t have been made in builder view anyway) there are bits of it that I’ve never completely understood. Given that I’ve made a mistake somewhere, I thought it best to check these through. Apologies in advance for how much of a complete dunce I will seem in the following questions – I’ve decided it’s better to admit idiocy and ask some probably obvious questions. I’d really appreciate your help with any of them – they range from straightforward to complex.

Preamble – my code is a musical pitch trainer. Participants learn to associate pitches with the first notes of famous musical melodies. They are played a stimulus (either a pitch, or a random melody that ends with that pitch) and then required to guess which famous tune that stimulus is associated with. There are 12 musical notes that need training, but I build up the difficulty of the task by starting with 6 and working up one-by-one until all 12 are trained.

In terms of indents it has 8 layers. The overall thing is a FOR loop that adds a new musical note to the training on each iteration. It is only allowed to complete when a variable within the second layer called go_on is set from 1 to 0. Layer 3 sets up the TrialHandler and sets the trial-set specific on_screen text. Layer 4 for is for any given specific trial, and the remaining layers also apply to this trial. Layer 6 onwards is for data storage.

Questions:

Q1: (above)
Q2: Within the first layer if I don’t set:

audio_feedback = sound.Sound('A', secs=-1) 
audio_feedback.setVolume(1) 

…then the code won’t run. Why would this be the case? audio_feedback is already set with this exact same code in the component initialization stage. It is set to various audio files throughout the code that follows, so I don’t understand why it wouldn’t just continue to set to new audio files from here on in – why should it need to go back to the initialization-style default, and why should it need to do this when a new musical note is added into the overall FOR loop? I use another audio stimulus called audio_stimulus – why should it only need to reset audio_feedback and not also audio_stimulus…?

Q3 – Am I correct in thinking that a memory leak is usually due to a variable being allowed to progressively accumulate a large amount of data unnecessarily? To my knowledge as soon as you set a variable to something else, it erases what was previously in it? Every time the trials are set up (in the third layer (within the loop ‘while qualifier < 0.95’)) trials is reset as follows:

trials = data.TrialHandler(nReps= repetitions, method='sequential', extraInfo=expInfo, originPath=-1, trialList= condition_selector(excel_file2, no_of_octaves, the_keys) , seed=None, name='trials')

So am I correct in thinking that trials becomes a new TrialHandler on each iteration of the while loop, clearing the previous TrialHandler from the variable?

(n.b., condition selector is a function I’ve written that selects a trialList depending upon the musical notes that we want to train on this iteration of the overall FOR loop (layer 1))

Q4 –
This code:

thisTrial = trials.trialList[0]  

simply states that the variable thisTrial is the most recent item in the trial list, right?

Q5 – I also have this code in Loop layer 3 (where the trialHandler is set up):

if thisTrial != None:
                for paramName in thisTrial.keys():
                     exec('{} = thisTrial[paramName]'.format(paramName))

I don’t really know what it is achieving…?

It concerns me that the identical code immediately appears again within loop layer 4 (where the code is specific to the current trial). I imagine I just copied this from the original PsychoPy code – what is the logic of this?

Q6 – in loop layer 4 (within any given trial itself) I set t = 0, reset the trialClock and set frameN = -1. I don’t really understand frames, and how they accumulate – can someone explain frames to me in plain English (sorry)?

In loop layer 5 ‘t’ is set to:

t = trialClock.getTime() 

Does this set ‘t’ to a fixed time, or is it a constantly accumulating time? ‘t’ appears in a series of if loops that set components into motion, e.g.:

if t >= 0.0 and audio_stimulus.status == NOT_STARTED:
    audio_stimulus.tStart = t
    audio_stimulus.frameNStart = frameN  
    audio_stimulus.play()  

Why does the if statement need the t >= 0.0 section here? What is the logic. The code doesn’t work if it’s not included, but I don’t understand why? How do the frames accumulate? At the beginning of any given current trial (loop layer 4) I’ve set

frameN = -1

Then in layer 5 (while continueRoutine) it sets it to frameN + 1

frameN = frameN + 1

Surely this is just setting frameN to 0 for every single trial? Seems like I’m possibly doing something pretty wrong here?

Q7 – I’ve noticed I’ve set up a variable called currentLoop, containing trials, in layer 4. This variable never gets used. I’m guessing it may have come from the original PsychoPy code, and I’ve either intentionally or unintentionally abandoned it. Is there any standard PsychoPy reason for saving trials to another variable within each trial?

Q8 – When starting the keyboard response, I have the following code:

if t >= 0.0 and key_resp_2.status == NOT_STARTED:
    key_resp_2.tStart = t
    key_resp_2.frameNStart = frameN  # exact frame index
    key_resp_2.status = STARTED
    win.callOnFlip(key_resp_2.clock.reset)  
    event.clearEvents(eventType='keyboard')

In plain English, what does this bit achieve, and why does it appear in this chunk of code:

win.callOnFlip(key_resp_2.clock.reset)  
event.clearEvents(eventType='keyboard')

?

Q9 – In a moment of probably very bad coding I did the following:

frameRemains = 1000 + 1.0- win.monitorFramePeriod * 0.75

I have no idea what the implications of this are?

I did it because when frameRemains resulted in a much lower number, the audio_feedback audio would often be cut short. This, on the surface level, fixed the problem. But obviously it could have consequences that I haven’t considered given that I don’t really understand frames or what any of ‘1.0- win.monitorFramePeriod * 0.75’ refers to… can someone explain?

The code that decides when audio_feedback should stop is as follows:

if audio_feedback.status == STARTED and t >= frameRemains:
    audio_feedback.stop()  

Q10. Within layer 5 (within ‘while continueRoutine’) I have the following code:

if continueRoutine:  
         win.flip()

In plain English (sorry), what does win.flip() do…?

Q11. In Layer 5 I use:

score_text.setAutoDraw(False) 

to remove the score_text from the screen, before I update it (in layer 5) and then set score_text.setAutoDraw(True). Is this ok practice? It seems that if I don’t do that, the old score_text remains on the screen, with the new score_text being drawn over it.

Q12.
In layer 6 (within the loop ‘if key_resp_2.status == STARTED’) I use the code:

theseKeys = event.getKeys(keyList=key_strings)

Currently participants are able to respond more than once, and PsychoPy stores only their final response. How can I limit this so that participants can respond only once?

Also participants shouldn’t have the option of responding with no response, so can I just go ahead and delete the following bit:

if key_resp_2.keys in ['', [], None]:  
         key_resp_2.keys=None
         if str(corrAns).lower() == 'none':
                    key_resp_2.corr = 1  
         else:
                    key_resp_2.corr = 0  

?

Or do I need that bit for the later code:

if key_resp_2.keys != None:  
         trials.addData('key_resp_2.rt', key_resp_2.rt)

?

Q13. Within loop layer 7 (within ‘if len(theseKeys) > 0), I have the following code:

key_resp_2.rt = key_resp_2.clock.getTime()

How does PsychoPy calculate the response rate? I presume that getTime() uses t.Start and measures from there? So I guess t has been accumulating ever since loop layer 5 when it is set to trialClock.getTime() ? I’m right to think that the trialClock itself has been running ever since it was initialized as:

trialClock = core.Clock() 

?

I presume that running clocks isn’t too much of a strain on memory?

Q14.

Within the final layer (layer 8) I tried, in the instance that the participant made the correct response, to have some text come on the screen saying that they had got the answer correct:

if t >= 0.0 and correct_text.status == NOT_STARTED:
            correct_text.tStart = t
            correct_text.frameNStart = frameN  # exact frame index
            correct_text.setAutoDraw(True)
            incorrect_text.status = FINISHED

audio_feedback.status = FINISHED

However this never worked. I think this is because in the instance that they got the response correct I set the audio_feedback to be FINISHED? I ended up dealing with this by including whether or not the previous response was correct as a part of the score text, which is always on screen. This was a bit of a cheap solution, but does the trick…

My ‘incorrect_text’ component works just fine:

else:
    key_resp_2.corr = 0
#store that the response was incorrect within key_resp_2
    how_many_goes +=1
#Add one to the how_many_goes counter
    is_it_correct = ""
#Save “”  in the variable is_it_correct, so that nothing displays on screen
    AP_correctAns = songTitle
#Store songTitle, from the conditions file, in the variable AP_correctAns, for display on screen
    percent = int((how_many_correct/how_many_goes) * 100)
#Present the current percentage as a percentage (and an integer), for display on screen
    audio_stimulus.stop()
#Stop the audio_stimulus
    audio_feedback.tStart = t
#Store the current value of t as the audio_feedback tStart
    audio_feedback.frameNStart = frameN  
#Stores the frame?
    audio_feedback.play()
#The audio feedback plays

    if t >= 0.0 and incorrect_text.status == NOT_STARTED:
            incorrect_text.tStart = t
            incorrect_text.frameNStart = frameN  # exact frame index
            incorrect_text.setAutoDraw(True)
            correct_text.status = FINISHED

I’m presuming that’s because once the audio_feedback is playing, there is time for the incorrect_text to be presented, whereas when the audio_feedback is stopped there isn’t time for the correct_text to be presented?

Thanks for your help guys – apologies for my being so crap at this, I’m definitely not a natural coder…

Sam


#2

Not a lot of replies to this - I’m wondering if the way I’ve presented it has put people off?

• Would this be better as 14 separate questions? I just didn’t want to take over the forum with all of my probably quite basic questions, but could repost them separately?

• Wondering if people were bored by the pre-amble… I think a number of the questions can be answered without reading it…?

• Wondering if it’s how I’ve presented the code in it - I haven’t sussed out how to present it in a grey box like I see on other people’s posts… (how do I do that?)

If there’s a way I can make this more accessible to you then let me know - would be great to get some answers to some of these if possible - sorry for taking your time.

Thanks


#3

It would help to go back to your original post and edit it as follows to get the code more readable:

But yes, a less monolithic question might attract more attention: people here are keen to help but are all busy people, so make it as easy on them as possible to identify the issues at stake.


#4

Brilliant - thanks Michael!

I’ve edited it up for now - fingers crossed that draws a little more interest to it… Failing that I’ll break it down into a few separate posts (I’m just concerned about hogging space on here that might be taken by posts that are of more interest to other users)

Thank you :slight_smile:


#5

Reading through this long post, it really does need to be broken up into separate questions, as many are quite independent of each other (and then easily answerable).

But fundamentally, if you are working at adapting a verbose Builder-generated script but are asking a question as basic as “what does win.flip() mean?”, then you are currently a bit out of your depth. What you need to do before proceeding with this current experiment is to work through the demos available from the Coder view under the demo’s menu. You’ll see that they are much more concise than a Builder-generated script and so they allow you to focus on understanding key techniques without having to wade through lots of housekeeping code. Look at various demos on presenting and timing stimuli and getting responses. Adapt some of those scripts and adapt and extend them. Insert print() statements throughout so you can examine different variables and objects and how they change.

You need to get confident with understanding some of the basics if you are going to confidently break into a Builder script, which is relatively verbose and complicated. In particular, look at timing by frames and how to use clocks.

I do firmly believe that the best way for a researcher to learn programming is to start by trying to solve a real problem, rather than trying to learn a language in a context-free setting. But you do need to walk before you can run.


#6

Thanks Michael - I think I’ve been doing a mixture of running perfectly fine with some elements, and then attempting to run when I can’t walk with others. The annoying thing is that, other than this memory leak, the code I’ve written does actually do what i want it to do.

The win.flip() question was probably a bit dumb to include. I get the basic principle that it clears the screen. I guess I was asking because it appears at the end of loop layer 5 (the 5th nested loop) yet after it appears I’m currently having to actively use

score_text.setAutoDraw(False) 

in order to actively remove the text from the screen before the updated version of the text is drawn (otherwise it just draws on top of the original).

So if I were to rephrase that question it would be: in plain English does win.flip() just clear the screen, or am I misunderstanding this. Is there an implication of win.flip() that could result in text stimuli surviving it and thus being drawn over by future text stimuli?

Thanks for alerting me to the demos, they’ll definitely be useful to go through.

Sorry if the questions seem simple, I’ve literally gone through my code and found the 14 bits that are possible danger points for me. Some of them are just me being cautious on things i’m 95% sure I’ve got correct - e.g. ‘To my knowledge as soon as you set a variable to something else, it erases what was previously in it?’ - I’m pretty damn sure the answer is yes… I’m just scratching my head as to where a memory leak could be occurring and the trialHandler seems like a possible red flag). If any of them are answerable for you or anyone else then that would be awesome. If any seem too simple or like a waste of time then feel free to ignore them.

I am by no means anything even approaching a good programmer, but I get the principles of python and should be capable of understanding you. The code I’ve written is an entire musical pitch training scheme and is thus fairly complex, if the questions make me look like a dumbass then thats only a 67.9% fair representation of me :wink:

In general my biggest questions revolve around frames: what they are, how they work, what their implications are. Is there somewhere that you’d suggest I read about that. In the original code I just ignored all the frames stuff, so it seems that (if it isn’t the TextStim vs TextBox memory leak issue: Using TextBox instead of TextStim ) this particular ignorance of mine is a likely source of error for me.

Thanks for your help and the demos tip off


#7

No, win.flip() doesn’t clear the screen: it actually moves what you have drawn on to the screen. When you are drawing stimuli, you are doing it into an off-screen memory buffer. When you are ready to display that content, you call win.flip() to flip the off-screen buffer with the content currently on-screen. i.e. you flip the off-screen buffer to be the memory that is displayed on-screen. By default, what was in the on-screen buffer is erased, ready to be drawn into (although you can suppress that erasure).

The key to understanding frames and frame counting is that win.flip() is not executed immediately. Instead, your code effectively pauses at that point until the next screen refresh cycle occurs. e.g. an LCD screen typically updates at 60 Hz, or every 16.666 ms. If it takes you 4 ms to manipulate and draw your stimuli, and then you call win.flip(), then there will be a 12.666 ms pause until that content actually appears on screen. i.e. our code has to operate within the bounds of that inexorable hardware cycle. The problem here is when it takes longer than 16.666 ms to do everything needed: in that case, the current content stays on the screen unchanged until the next cycle, which is what is called “dropping a frame”.

I’m not too familiar with how autodraw works (I prefer to manually draw stimuli as required), but I guess it is performed for each stimulus by win.flip(), so yes, you need to turn it off if you don’t want a stimulus to be displayed on the next cycle.

Those questions are absolutely fine and would be easily answerable, but you should probably pose each of them separately, where they can attract a simple response, rather than people being put off by trying to wade through a huge series of questions and feeling inadequate about just addressing a single one. Plus it makes it of more value to future readers who may have the same questions but won’t be able to figure that out from the current long post.

Why now that you mention it :wink: :


#8

Legend :slight_smile:

And I’ll def get the book!


#9

As a heads up, I’ve resolved Q2 - was a stupid error. I thought I’d set up audio_feedback at the initialisation stage, but it turns out I’d used ‘audioFeedback’ as the variable name at that stage. As such it was necessary to include audio_feedback at the start of the loop as it still needed to be initialised.