RDK density problem

271 views
Skip to first unread message

mat

unread,
Dec 12, 2011, 10:59:52 AM12/12/11
to psychopy-users
Dear psychopy users,
The random dot kinematogram is a wonderful tool for cognitive
research. In some psychophysical papers using RDK (e.g., Pilly &
Seitz, 2009):
-Dot density is kept constant often at 16.7 dots deg-2 s-1.
-To conserve dot density, signal dots moving out of the aperture are
wrapped around to appear from the opposite side.
In psychopy, the density is kept constant by replacing these dots
randomly in the stimulus field. I would like to know if it is possible
to make them appear from the opposite side, like in Pilly et Seitz's
paper...

Furthemore, I would like to report a stange bug:
Manipulating the percent coherence of the dots moving in a particular
direction perfectly works with all combinations of signal/noise dots
algorithms, excepted when using signalDots = 'different' combined with
noiseDots = 'walk'. At the moment, I don't know if it is a bug of
psychopy or a coding problem in my script.

Cheers,
Mat

Jonathan Peirce

unread,
Dec 12, 2011, 11:15:54 AM12/12/11
to psychop...@googlegroups.com
I've never added code to do that. I did try it a while ago but I always
found it looked weird, with some dots being noticeably cyclic in the
pattern of how they come back on and off the patch. I personally think
it's probably good to stick with short lifetimes regardless of how to
handle out-of-field dots.

I'll add it to the list of issues
(https://github.com/psychopy/psychopy/issues) but it isn't a priority
for me and you'll see there are lots of other things that need doing. So
you'll get it changed faster if you write the code and submit it as a
patch. ;-)

Have a look at the code in DostStim._update_dotsXY() to customise to
your heart's content.

Jon

--
Jonathan Peirce
Nottingham Visual Neuroscience

http://www.peirce.org.uk


This message and any attachment are intended solely for the addressee and may contain confidential information. If you have received this message in error, please send it back to me, and immediately delete it. Please do not use, copy or disclose the information contained in this message or in any attachment. Any views or opinions expressed by the author of this email do not necessarily reflect the views of the University of Nottingham.

This message has been checked for viruses but the contents of an attachment
may still contain software viruses which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.

Paolo G.

unread,
Dec 13, 2011, 12:49:55 PM12/13/11
to psychopy-users
Dear psychopy community,

I'm an Italian Phd student and I'm quiet new on the forums. I'm also
using random dot motion stimuli in my current research, but I'm only
interested in motion-induced attentional shifts. The symmetric
reploting of dead dots you're speaking about is interesting for me for
various theoretical reasons. I've just had a look on the source code
of the DotStim class and particularly on the _update_dotsXY function
which seems to control the destiny of out of bounds dots. At the
moment, all my attempts for implementing this symmetric reborn are not
working (the code looks difficult to me), and I'm searching a simple
way to make it work. Any bright idea would be appreciated!
Paolo.

On Dec 12, 5:15 pm, Jonathan Peirce <jonathan.pei...@nottingham.ac.uk>
wrote:

philippe lacherez

unread,
Feb 3, 2012, 7:33:46 AM2/3/12
to psychopy-users

Hi,

I solved this recently for an experiment I'm doing. Rather than edit
the psychopy source I created a new class which inherits from dotStim,
which I call wrappingDotStim, but which overrides the updateDotsXY and
newDotsXY methods:


from psychopy import core, visual, gui, data, misc, event
import time, numpy, math, os.path, pickle
from numpy import *
from psychopy import *

class wrappingDotStim(visual.DotStim):

def _newDotsXY(self, nDots):
"""Returns a uniform spread of dots, according to the
fieldShape and fieldSize

usage::

dots = self._newDots(nDots)

"""
if self.fieldShape=='circle':#make more dots than we need and
only use those that are within circle
while True:#repeat until we have enough
new=numpy.random.uniform(-1, 1, [nDots*2,2])#fetch
twice as many as needed
inCircle= (numpy.hypot(new[:,0],new[:,1])<1)
if sum(inCircle)>=nDots:
return numpy.array(new[inCircle,:]
[:nDots,:]*self.fieldSize/2.0)
else:
return numpy.array(numpy.random.uniform(-self.fieldSize/
2.0, self.fieldSize/2.0, [nDots,2]))


def _update_dotsXY(self):
"""
The user shouldn't call this - its gets done within draw()
"""

"""Find dead dots, update positions, get new positions for
dead and out-of-bounds
"""
#renew dead dots
if self.dotLife>0:#if less than zero ignore it
self._dotsLife -= 1 #decrement. Then dots to be reborn
will be negative
dead = (self._dotsLife<=0.0)
self._dotsLife[dead]=self.dotLife
else:
dead=numpy.zeros(self.nDots, dtype=bool)

##update XY based on speed and dir
#NB self._dotsDir is in radians, but self.dir is in degs
#update which are the noise/signal dots
if self.signalDots =='different':
# **up to version 1.70.00 this was the other way around,
not in keeping with Scase et al**
#noise and signal dots change identity constantly
numpy.random.shuffle(self._dotsDir)
self._signalDots = (self._dotsDir==(self.dir*pi/180))#and
then update _signalDots from that

#update the locations of signal and noise
if self.noiseDots=='walk':
# noise dots are ~self._signalDots
self._dotsDir[~self._signalDots] =
numpy.random.rand((~self._signalDots).sum())*pi*2
#then update all positions from dir*speed
self._dotsXY[:,0] +=
self.speed*numpy.reshape(numpy.cos(self._dotsDir),(self.nDots,))
self._dotsXY[:,1] +=
self.speed*numpy.reshape(numpy.sin(self._dotsDir),(self.nDots,))# 0
radians=East!
elif self.noiseDots == 'direction':
#simply use the stored directions to update position
self._dotsXY[:,0] +=
self.speed*numpy.reshape(numpy.cos(self._dotsDir),(self.nDots,))
self._dotsXY[:,1] +=
self.speed*numpy.reshape(numpy.sin(self._dotsDir),(self.nDots,))# 0
radians=East!
elif self.noiseDots=='position':
#update signal dots
self._dotsXY[self._signalDots,0] += \

self.speed*numpy.reshape(numpy.cos(self._dotsDir[self._signalDots]),
(self._signalDots.sum(),))
self._dotsXY[self._signalDots,1] += \

self.speed*numpy.reshape(numpy.sin(self._dotsDir[self._signalDots]),
(self._signalDots.sum(),))# 0 radians=East!
#update noise dots
dead = dead+(~self._signalDots)#just create new ones

#handle boundaries of the field
if self.fieldShape in [None, 'square', 'sqr']:
self._dotsXY[(self._dotsXY[:,0]>(self.fieldSize/2.0)),0] =
numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
0]>(self.fieldSize/2.0)),0]), (self.fieldSize/2.0)), (self.fieldSize/
2.0))
self._dotsXY[(self._dotsXY[:,1]>(self.fieldSize/2.0)),1] =
numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
1]>(self.fieldSize/2.0)),1]), (self.fieldSize/2.0)),(self.fieldSize/
2))
self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize/2.0)),0]
= numpy.mod(self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize/2.0)),0],
(self.fieldSize/2.0))
self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize/2.0)),1]
= numpy.mod(self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize/2.0)),1],
(self.fieldSize/2.0))
elif self.fieldShape == 'circle':
#transform to a normalised circle (radius = 1 all around)
then to polar coords to check
normXY = self._dotsXY/(self.fieldSize/2.0)#the normalised
XY position (where radius should be <1)
dead = dead + (numpy.hypot(normXY[:,0],normXY[:,1])>1)
#add out-of-bounds to those that need replacing

#update any dead dots
if sum(dead):
self._dotsXY[dead,:] = self._newDotsXY(sum(dead))

#update the pixel XY coordinates
self._calcDotsXYRendered()

I call this file wrappingDotStim.py, and the file exists in the same
directory as my psychopy source I am running, so include it in the
source using:

from wrappingDotStim import *

I only overrode the update rule for the default square mask, but
something similar would work for circle....

It seems to work....Please comment and let me know if you encounter
any probs.
Philippe



On Dec 14 2011, 3:49 am, "Paolo G." <nehr...@gmail.com> wrote:
> Dear psychopy community,
>
> I'm an Italian Phd student and I'm quiet new on the forums. I'm also
> using randomdotmotionstimuli in my current research, but I'm only
> interested inmotion-induced attentional shifts. The symmetric
> > > The randomdotkinematogram is a wonderful tool for cognitive
> > > research. In some psychophysical papers using RDK (e.g., Pilly&
> > > Seitz, 2009):
> > > -Dotdensity is kept constant often at 16.7 dots deg-2 s-1.
> > > -To conservedotdensity, signal dots moving out of the aperture are

Jonathan Peirce

unread,
Feb 3, 2012, 11:42:43 AM2/3/12
to psychop...@googlegroups.com
Thanks Philippe. That's the ideal way to customise bits of PsychoPy for
your own use.

For others, what Philippe has done here means that his new stimulus
class works just like the existing DotStim. All the methods that he
hasn't touched (like setCoherence()) will still exist and do the same
thing as before. But he's overwritten just the method about how the dots
are updated. And because he hasn't had to do this within the psychopy
files themselves, if he updates psychopy it won't affect his additional
piece.

It's a useful technique to learn for python programming in general.

Jon

On 3 February 2012 12:33, philippe lacherez

> --
> You received this message because you are subscribed to the Google Groups "psychopy-users" group.
> To post to this group, send email to psychop...@googlegroups.com.
> To unsubscribe from this group, send email to psychopy-user...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/psychopy-users?hl=en.
>

dani

unread,
Feb 24, 2012, 1:22:43 AM2/24/12
to psychop...@googlegroups.com
Thank you very much . I tried to use your class but I got this error:

  File "/Applications/PsychoPy2.app/Contents/Resources/lib/python2.6/psychopy/visual.py", line 1538, in __init__
    self._update_dotsXY()
  File "/Users/danilinares/Documents/code/python/wrappingDots/wrappingDotStim.py", line 60, in _update_dotsXY
    self._dotsXY[(self._dotsXY[:,0]>(self.fieldSize/2.0)),0] = numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:, 0]>(self.fieldSize/2.0)),0]), (self.fieldSize/2.0)), (self.fieldSize/ 2.0)) 
ValueError: shape mismatch: objects cannot be broadcast to a single shape

I attach the the stimulus file and the file containing your class. Any idea of what happened? Thanks again. 
dots.py
wrappingDotStim.py

philippe lacherez

unread,
Mar 23, 2012, 5:30:49 AM3/23/12
to psychopy-users

Hi dani,

Yes, looks like something changed in the last update -- the fieldSize
is now an array instead of a scalar as previously -- not sure why?

This works, I think:

from psychopy import core, gui, data, misc, event , visual
import time, numpy, math, os.path, pickle
from numpy import *
from psychopy import *

class wrappingDotStim(visual.DotStim):
def _newDotsXY(self, nDots):
if not(type(self.fieldSize) in [float,int, numpy.int32]):
self.fieldSize = float(self.fieldSize[0])
self._dotsXY[(self._dotsXY[:,0]>(self.fieldSize/2.0)),0] =
numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
0]>(self.fieldSize/2.0)),0]), (self.fieldSize/2.0)), (self.fieldSize/
2.0))
self._dotsXY[(self._dotsXY[:,1]>(self.fieldSize/2.0)),1] =
numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
1]>(self.fieldSize/2.0)),1]), (self.fieldSize/2.0)),(self.fieldSize/
2.0))
self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize/2.0)),0]
= numpy.mod(self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize/2.0)),0],
(self.fieldSize/2.0))
self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize/2.0)),1]
= numpy.mod(self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize/2.0)),1],
(self.fieldSize/2.0))

elif self.fieldShape == 'circle':
#transform to a normalised circle (radius = 1 all around)
then to polar coords to check
normXY = self._dotsXY/(self.fieldSize/2.0)#the normalised
XY position (where radius should be <1)
dead = dead + (numpy.hypot(normXY[:,0],normXY[:,1])>1)
#add out-of-bounds to those that need replacing
#update any dead dots
if sum(dead):
self._dotsXY[dead,:] = self._newDotsXY(sum(dead))
#update the pixel XY coordinates
self._calcDotsXYRendered()


I just changed the fieldSize back to a scalar. Let me know if there
are any probs....

Cheers
Philippe


On Feb 24, 4:22 pm, dani <danilina...@gmail.com> wrote:
> Thank you very much . I tried to use your class but I got this error:
>
>   File
> "/Applications/PsychoPy2.app/Contents/Resources/lib/python2.6/psychopy/visu al.py",
> line 1538, in __init__
>     self._update_dotsXY()
>   File
> "/Users/danilinares/Documents/code/python/wrappingDots/wrappingDotStim.py",
> line 60, in _update_dotsXY
>     self._dotsXY[(self._dotsXY[:,0]>(self.fieldSize/2.0)),0] =
> numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
> 0]>(self.fieldSize/2.0)),0]), (self.fieldSize/2.0)), (self.fieldSize/ 2.0))
> ValueError: shape mismatch: objects cannot be broadcast to a single shape
>
> I attach the the stimulus file and the file containing your class. Any idea
> of what happened? Thanks again.
>
>  dots.py
> 1KViewDownload
>
>  wrappingDotStim.py
> 4KViewDownload

dani

unread,
Mar 23, 2012, 5:39:34 AM3/23/12
to psychop...@googlegroups.com
Thank you very much. It works fine!

Jonathan Peirce

unread,
Mar 23, 2012, 7:44:11 AM3/23/12
to psychop...@googlegroups.com

On 23/03/2012 09:30, philippe lacherez wrote:
> Hi dani,
>
> Yes, looks like something changed in the last update -- the fieldSize
> is now an array instead of a scalar as previously -- not sure why?

By changing to an array for fieldSize, rather than a scalar, allows
fields with different aspect ratios (e.g. squares and ellipses). But it
seems like that's causing a fatal error under some conditions. Under
what circumstances (e.g. size settings) does this error occur? e.g. when
I run the dot stim demo I don't get the error coming up.

Jon

philippe lacherez

unread,
Mar 23, 2012, 7:41:02 PM3/23/12
to psychopy-users


Okay, got it. It's just that this inherited class uses the fieldSize
as the modulus for wrapping the dots, so was implemented based on the
previous version where fieldSize was scalar. So then this should work
even if you want a rectangular field....

from psychopy import core, gui, data, misc, event , visual
import time, numpy, math, os.path, pickle
from numpy import *
from psychopy import *

class wrappingDotStim(visual.DotStim):
def _newDotsXY(self, nDots):
self._dotsXY[(self._dotsXY[:,0]>(self.fieldSize[0]/2.0)),
0] = numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
0]>(self.fieldSize[0]/2.0)),0]), (self.fieldSize[0]/2.0)),
(self.fieldSize[0]/ 2.0))
self._dotsXY[(self._dotsXY[:,1]>(self.fieldSize[1]/2.0)),
1] = numpy.subtract(numpy.mod((self._dotsXY[(self._dotsXY[:,
1]>(self.fieldSize[1]/2.0)),1]), (self.fieldSize[1]/2.0)),
(self.fieldSize[1]/ 2.0))
self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize[0]/2.0)),
0] = numpy.mod(self._dotsXY[(self._dotsXY[:,0]<-(self.fieldSize[0]/
2.0)),0], (self.fieldSize[0]/2.0))
self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize[1]/2.0)),
1] = numpy.mod(self._dotsXY[(self._dotsXY[:,1]<-(self.fieldSize[1]/
2.0)),1], (self.fieldSize[1]/2.0))

elif self.fieldShape == 'circle':
#transform to a normalised circle (radius = 1 all around)
then to polar coords to check
normXY = self._dotsXY/(self.fieldSize/2.0)#the normalised
XY position (where radius should be <1)
dead = dead + (numpy.hypot(normXY[:,0],normXY[:,1])>1)
#add out-of-bounds to those that need replacing
#update any dead dots
if sum(dead):
self._dotsXY[dead,:] = self._newDotsXY(sum(dead))
#update the pixel XY coordinates
self._calcDotsXYRendered()



On Mar 23, 9:44 pm, Jonathan Peirce <jonathan.pei...@nottingham.ac.uk>
wrote:
Reply all
Reply to author
Forward
0 new messages