Trouble with executing original function in Psychopy

I am currently designing a paradigm where participants receive thermal stimulation after visual presentation of a cue. I have written my own function in Python which allows me to trigger the thermal stimulation once executed (function inputs are temperature value and DeviceChannel (see below)).

I have used the Code component at the beginning of a Routine (termed HeatStim which is preceded by a ButtonPress Routine and followed by a rating screen) where a visual cue is shown for 3 seconds in conjunction with the thermal stimulation. The following code is as follows:

#calling thermode stimulation function

if (ActionStart.corr == 1):
    heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(temp_val,DeviceChannel)
elif (ActionStart.corr == 0):
    heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(0,DeviceChannel)

Where ActionStart.corr = whether a preceding button press was correct, nidaqmx_analog_out.analog_in_out = my function for thermode stimulation, relevant libraries imported at the beginning of the experiment, temp_val = the temperature value, inputted at the beginning of the experiment, and DeviceChannel = variable defined in my .csv file. It outputs heat_stim_end to indicate that the stimulation has finished, and a value telling me the temperature of my thermode which I save to my log file.

The script runs without error and the thermodes are triggered by the correct temperature value, but it skips my last routine (a rating screen) and I’m not sure why? I was wondering if the function triggers the end of my trial for some reason? I’m a beginner with Python and PsychoPy so any help would be much appreciated!

Thank you in advance!

I think we need to see some screenshots of your routine layout and flow panel, as this could be an issue of when the code component runs relative to other stimuli.

How long does your function take to return a value? i.e. does it take three seconds?

Hi Michael, thank you for your quick response! Please see a screenshot of my flow panel:

And my routine layout for the ‘HeatStim’ period:

Within the Code component termed “CallHeatStimFunc” I call the following in the ‘Begin Experiment’ tab:

#import the library needed for using the thermodes 
import nidaqmx
import nidaqmx_analog_out

And within the ‘Begin Routine’ section I call the function with the following syntax:

#calling thermode stimulation function
if (ActionStart.corr == 1):
    heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(temp_val,DeviceChannel)
elif (ActionStart.corr == 0):
    heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(0,DeviceChannel)

And finally at the ‘End Routine’ section I save my files to .csv:

RepeatTrial.addData('analog_in', input_val)

I was taking a look at a test .csv file yesterday (run on myself when trying to figure out the script) and noticed that in my preceding Routines I had no ‘.stopped’ values recorded for any of my previous picture cues (e.g. in the ‘ActionInitiation’ Routine I show a Go_cue to indicate the participant should press a button and the .csv had not stored a value for the Go_cue.stopped) - they were recorded as ‘None’. Do you think Psychopy is getting confused with the timing of my stimuli because of the addition of the extra function?

Again, thank you for your help!

As above:

Hi Michael,

I’ve checked the timing of my function and even though I’ve coded it to administer temperature for 3 seconds it actually takes approx. 8 seconds to finish executing. With this in mind I amended my routine as below, so the Picture Cue (‘Cue_Shown_4’) is only shown during the 3 seconds of heat, followed by my rating screen within one routine (‘Heat_Stim’):

However, when I run the trial the Picture (‘Cue_Shown_3’) and Go Cue (green rectangle) from the previous routine (‘ActionInitiaion’) is still shown during the ‘HeatStim’ routine and it still skips the rating screen at the end of the trial… I’m not quite sure why? Could it possibly be that PsychoPy cannot run whilst the function is being executed, and waits until it is finished but by that point it’s already at the end of the trial?

Again, thank you in advance for your help!

Thanks for the extra detail.

Yes, Builder is fundamentally structured on a drawing loop, where it expects to be updating all stimuli (even if they are unchanging) by drawing to the screen at the native refresh rate of your monitor (typically 60 Hz). This means that any custom code you call has to be able to be completed in well under 16 ms if it isn’t going to upset Builder’s normal mode of running code on every screen refresh cycle.

So, for example, at the beginning of your routine, you call this function that takes 8 s to complete. That call is issued before the first screen update of the new routine is carried out. This is why you still see the old stimuli from the previous routine being shown, because you have stopped Builder even getting to draw the first update of the next routine, because Python is now busy running your function instead.

So for a start, you need to shift that code out of the “begin routine” tab and into the “each frame” tab. That way you can ensure that the code isn’t called on the very first (i.e. zeroth) frame update, but instead happens on the second (number 1). e.g:


# only run this on the second frame, after the stimuli for this routine have been drawn:

if frameN == 1:
    # calling thermode stimulation function
    if (ActionStart.corr == 1):
        heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(temp_val,DeviceChannel)
    elif (ActionStart.corr == 0):
        heat_stim_end, input_val=nidaqmx_analog_out.analog_in_out(0,DeviceChannel)

So now the stimuli should at least be shown for this trial. But you are still breaking Builder’s drawing loop, by hogging Python for the next 8 s, so Builder’s code doesn’t get a chance to run after drawing the first frame of this routine. So it can’t keep continually checking what the current time is, and stop drawing your image at 3 s, for example.

This is hitting up against a much more fundamental problem: you need Builder’s code to keep running, while your code is also running. Normally, Python code only does one thing at a time. You’ll need to look into topics like threading to find out how you can get two things to effectively happen at once. That isn’t trivial.

But if the nidaqmx_analog_out.analog_in_out() function is one you wrote yourself, perhaps it is possible to re-write it so that it doesn’t hog Python for the full 8 s? e.g. could it be re-factored to occur within the “each frame” tab of a code component, so that, just like Builder code, it can run just once per screen refresh, rather than as a monolithic block of code? I guess we’d need to see the function code to assess whether that is viable.

Hi Michael,

Thank you for your detailed response - I’m new to Psychopy/Python so it’s really helpful to hear how the Builder functions in terms of running code, and how this relates in general to Python.

Following your advice, I’ve used threading in order to execute my function in line with running Psychopy. For anyone who is faced with the same problem in the future I’ve pasted the solution below.

In the code component of my ‘HeatStim’ routine (see above) in the ‘Begin Experiment’ tab I import the relevant libraries/modules for my function and for threading:

#import the library needed for using the thermodes 

import nidaqmx
import nidaqmx_analog_out

#import threading module and allocate one function to the pool

from multiprocessing.pool import ThreadPool
pool = ThreadPool(processes=1)

Now when I call my function in the ‘Each Frame’ tab I use the syntax:

#calling thermode stimulation function

if frameN == 1:
    Thermode_start = pool.apply_async(nidaqmx_analog_out.analog_in_out, (temp_val, DeviceChannel))

At the end of my last routine of my trial in the ‘End Routine’ tab of a newly inserted code component, I then save an output from my function which was executed in the thread with the syntax:

input_val= Thermode_start.get()
RepeatTrial.addData('analog_in', input_val)

I found the following documentation useful for figuring out how to do this: https://docs.python.org/2/library/threading.html#thread-objects and https://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python

Thank you again Michael for your help - without your guidance I wouldn’t have figured out the need to use threading!