Save information from the stochastic motion displays (rdk stimuli) to compute psychophysical kernels

I plan to calculate psychophysical kernels using the information from the stochastic motion displays, using the parameterization of the random dot stimulus presented below (white noise). What is the most efficient way to do that? Should I simply set a specific seed for each subject? Any suggestion/code sharing would be very helpful!

#######################Parameters for RDK stimuli
framerate = 60.0
speed_deg_sec =8.
dot_density = 16.7#in deg-2 s-1
rayon_cercle = 9.0#in deg
number_of_dots = int(np.round(dot_density * np.pi * np.square(rayon_cercle) * (1/framerate)))

dots=visual.DotStim(win=win, name='random_dots',
    nDots= number_of_dots, 
    dotSize=4,#in pixels
    units = 'deg',
    speed= speed_deg_sec/framerate,#in degrees per frame
    dir= 0, #in degrees  --> manipulated
    coherence= 0.0,#--> manipulated
    fieldPos=[0.0, 0.0],
    signalDots='different', # on each frame the dots constituting the signal could be the same as on the previous frame or different. If 'same', participants may determine the direction of dots based on a single dot.
    noiseDots='position', #can be set to 'direction' or 'location'. 'location' has the disadvantage that the noise dots not only have a random direction but alos a random speed (whereas signal dots have a constant speed and constant direction)
    dotLife= -1,#number of frames each dot lives. 
    color=[255,255,255], colorSpace='rgb255')#color of the dots

@jon @schofiaj Is there a way to set the seed of the random dot stimulus without modifying the source code ?

Hi, uses the numpy random number generator so a call to numpy.random.seed() should do the trick. Note however that lots of other components in PsychoPy including the loop handlers use the same ransom number generator so other random sequences including the order of trials may also be fixed by this.

Yes, as @schofiaj says, dotstim uses numpy’s default RNG and that means it potentially interacts with other calls to the random lib. We should move it to use its own dedicated instance of the RNG, which we’ve done recently for the trial handlers etc.

One note though is that, if the exact position/path of every dot is important to you then this feels like a somewhat fragile approach. If a trial lasts one frame less than expected, for instance, then the next stimulus will be seeded from the wrong value and you will get something completely different from then onwards. If you really need specific dot locations/paths to be the same across trials I think I’d be tempted to generate movies of XY values and store the sequence in some sort of file for re-use

Hi @jon a little off topic, but if you are separating out the RNGs it would be a good idea to have one dedicated to the visual noise component - and a way to set its seed in builder.

Sure. It suffers the same slight problem that I would worry the seed could get off-course by some very small change in environment.

I suppose, for both these instances, you could set the stimulus seed on each trial though

While we’re on the topic, in the summer release (2021.2 planned for mid-June), I also plan to switch the generator itself. Numpy now recommends the PCG64 over the Mersenne Twister that we’ve been using to date. Faster, and with better statistical properties) supposedly, at least according to the creators of it. That also means, though, that seeds will not match before/after that release.

True, but unlike the RDKs which have to update from the RNG every frame, noise samples might only be updated once per trial. So there would be conditions where they are less prone to this problem. Maybe its something not to put in Builder and leave coders to work out a safe way to do what they want to do. Decoupling RNGs is still a good idea as prior to that change fixing the trial order might also fix the noise samples for some - but not necessarily all - types of noise. (Some noise types have a critical call to the RNG before the seed for loops is set).

1 Like

@Jon @schofiaj Instead of generating movies, could it be possible to store XY values of each dot at each frame (this is essentially what I need to compute a psychophysical kernel)? The resulting numpy array will be huge, but I could save it at the end of each trial (and reset it). Is there a simple way to do that without modifying the source code ?
While we’re on the topic, I am a bit confused about the dotLife parameter when signalDots = ‘different’ and noiseDots = ‘position’. This setting corresponds to a white noise algorithm : a new set of signal dots are randomly chosen to move in the signal direction from each frame to the next, and the remaining (noise) dots are randomly relocated. However, this white noise algorithm should require dotSize = -1 (infinite) to work properly right ? Otherwise coherence may not be accurate.