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