Presenting random stimuli on a circle

spyder (4.0.1, python 3.6), psychopy (v3.0)

I’d like to present N = 2, 4, 6 (set size randomly varies from trial to trial) stimuli located on an imaginary circle. The idea (possibly not very good) was to use the current trial’s nStim as the number of randomly generated angles (thetas). Fixation screen and possibly fixation point from the encoding display are present. The gratings are not generated. Specifically, it stops at the fixation point and waits there (still running) until it self-terminates.

With focus on the relevant parts:

nStim = [2,4,6] # trial list 
stim_ecc = 7 # deg
stimN = visual.GratingStim(win, sf=1, size=4, tex='sin', mask='gauss', # create generic stim and only update attributes later
                            ori=0)

trials = data.TrialHandler(nStim, nReps = 5, # n repeats for all conditions
                           method='random',
                           seed=None, 
                           autoLog=True)
exp.addLoop(trials)
totalTrialCnt  = 0
thisTrial = trials.nStim[0] # to initialise stimuli with some values
for thisTrial in trials:
    if nStim == 2:
        current_N = 2
    elif nStim == 4:
        current_N = 4
    else:
        current_N = 6

    theta = numpy.linspace(0, 2*numpy.pi, **current_N**, retstep=True) # evenly spaced
    rho = stim_ecc

    stim_vec = {1:current_N}
    for i in stim_vec:
        def pol2cart(rho, theta):
            x = rho*np.cos(theta)
            y = rho*np.sin(theta)
            return(x, y)
            stimN(i).setPos([x, y]) 
            stimN(i).draw()
    win.flip()
    core.wait(0.7)
    
    trials.next()
    exp.nextEntry()

Error:

Traceback (most recent call last):

  File "C:\Users\username\Dropbox\trial_handler.py", line 131, in <module>
    trials.next() # in which order?

  File "C:\Users\username\Anaconda3\envs\psychopy\lib\site-packages\psychopy\data\trial.py", line 350, in __next__
    self._terminate()

  File "C:\Users\username\Anaconda3\envs\psychopy\lib\site-packages\psychopy\data\base.py", line 115, in _terminate
    raise StopIteration

StopIteration

Maybe the calculation time takes longer than the duration of the display (.7 sec)? Or there is an issue in the writing part i don’t see/don’t know.

After uncommenting thisTrial = trials.nStim[0] line there is error:
AttributeError: 'TrialHandler' object has no attribute 'nStim'

So possibly the problem is with the way the ‘conditions’ were added.
Thanks!

At the moment, you have not told the TrialHandler anything about a value called nStim that should be accessible on each trial. Check the documentation for the trialList parameter here:
https://www.psychopy.org/api/data.html#trialhandler

It is defined as

a simple list (or flat array) of dictionaries specifying conditions. This can be imported from an excel/csv file using importConditions()

You have just given a list of numbers, not of dictionaries (which would allow the linking of those numbers to a named parameter like 'nStim'). As suggested, either import the values from a .csv file, which has a column header of nStim, or for such a short list, manually define the dictionaries, e.g.

nStim = [{'nStim': 2}, {'nStim': 4}, {'nStim': 6}]

although to ease the confusion, you might want to use distinct names, like:

stim_list = [{'nStim': 2}, {'nStim': 4}, {'nStim': 6}]

Now each trial will have access to a named attribute called 'nStim', e.g.

for thisTrial in trials:
    print(thisTrial['nStim'])

This doesn’t work, because a TrialHandler object does not have an attribute called .nStim

As above, if you want to access this value, you need to get if from an individual trial’s key:value dictionary pairing.

This code is needlessly inefficient (as well as nStim being a list, so it would never be equal to a single value anyway):

for thisTrial in trials:
    if nStim == 2:
        current_N = 2
    elif nStim == 4:
        current_N = 4
    else:
        current_N = 6

If you do actually need to define a new variable name, simply do it like this:

for thisTrial in trials:
    current_N = thisTrial['nStim']

The result of this line is constant, so it shouldn’t be inside your loop (causing it to keep getting re-calculated needlessly):

theta = numpy.linspace(0, 2*numpy.pi, **current_N**, retstep=True) # evenly spaced

Not clear why you would give this variable a new name instead of using the original:

rho = stim_ecc

Again, the pol2cart() function doesn’t change, so define it just once, before any of your loops start. At the moment it will be needlessly re-defined for every iteration of both enclosing loops:

for i in stim_vec:
    def pol2cart(rho, theta):

Also note that code in a function that comes after the return statement is not going to be executed:

    return(x, y) # all done.
    stimN(i).setPos([x, y]) # So I'm doing nothing
    stimN(i).draw() # and neither am I

And I’m not sure this is necessary. Just let your for loop run:

trials.next()

Nearly all of the issues you are encountering here are basic ones of Python programming, rather than anything to do with PsychoPy (except how to construct TrialHandler objects). So I’d strongly recommend that you actually construct your experiment in Builder. It will handle all of the laborious stuff for you, including best-practice timing of stimulus display and response collection, automatic data saving, and getting direct access to variables like nStim rather than using the dictionary notation. i.e. you might be treating this as a good way to learn Python, but you also need to learn the even more complicated issues of synchronising with the display refresh rate and so on. Let Builder handle that for you instead, so you can have more confidence in the data that gets generated.

This isn’t intended as patronising advice: even most experienced PsychoPy users (including most of its developers) will tend to let Builder do most of the leg work these days. That way you can focus your Python skills solely on the additional snippets of code you need, in this case, to control the location of your stimuli.

i.e. Build the experiment graphically, and then insert a graphical code component from the “custom” component panel. Just put snippets of code in the relevant tabs there (e.g. define your custom functions in the “Begin experiment” tab, and calculate the stimulus locations in the “begin routine” tab). Builder will automatically insert those code snippets in the right place in the long and comprehensive .py file it generates for you.

2 Likes

Hi Michael,

Thanks a lot for the prompt and thorough response - many points are clearer now and i learned a lot! I suppose, i should first search ‘create lists of dictionaries python’ before any trial to create the nStim.

After discussing it with my supervisor, one solution would be that after randomly defining theta1 and stepsize as 2*pi/N, each subsequent theta would be equal to the previous one + stepsize.

About programming, they say, once you did something once, the next time would be easier and there is always something more to learn.