Package psychopy :: Module visual
[frames] | no frames]

Source Code for Module psychopy.visual

   1  """To control the screen and visual stimuli for experiments 
   2  """ 
   3  # Part of the PsychoPy library 
   4  # Copyright (C) 2010 Jonathan Peirce 
   5  # Distributed under the terms of the GNU General Public License (GPL). 
   6   
   7  import psychopy #so we can get the __path__ 
   8  from psychopy import core, ext, log, preferences, monitors, event 
   9  import colors 
  10  import psychopy.event 
  11  #misc must only be imported *after* event or MovieStim breaks on win32 (JWP has no idea why!) 
  12  import psychopy.misc 
  13  import Image 
  14  import sys, os, platform, time, glob, copy 
  15  import makeMovies 
  16   
  17  import numpy 
  18  from numpy import sin, cos, pi 
  19   
  20  from psychopy.ext import rush as rush 
  21   
  22  prefs = preferences.Preferences()#load the site/user config files 
  23   
  24  #shaders will work but require OpenGL2.0 drivers AND PyOpenGL3.0+ 
  25  try: 
  26      import ctypes 
  27      import pyglet 
  28      pyglet.options['debug_gl'] = False#must be done before importing pyglet.gl or pyglet.window 
  29      import pyglet.gl, pyglet.window, pyglet.image, pyglet.font, pyglet.media, pyglet.event 
  30      import _shadersPyglet 
  31      import gamma 
  32      havePyglet=True 
  33  except: 
  34      havePyglet=False 
  35   
  36  #import _shadersPygame 
  37  try: 
  38      import OpenGL.GL, OpenGL.GL.ARB.multitexture, OpenGL.GLU 
  39      import pygame 
  40      havePygame=True 
  41      if OpenGL.__version__ > '3': 
  42          cTypesOpenGL = True 
  43      else: 
  44          cTypesOpenGL = False 
  45  except: 
  46      havePygame=False 
  47   
  48  global GL, GLU, GL_multitexture, _shaders#will use these later to assign the pyglet or pyopengl equivs 
  49   
  50  #check for advanced drawing abilities 
  51  #actually FBO isn't working yet so disable 
  52  try: 
  53      import OpenGL.GL.EXT.framebuffer_object as FB 
  54      haveFB=False 
  55  except: 
  56      haveFB=False 
  57   
  58   
  59  #try to get GLUT 
  60  try: 
  61      from OpenGL import GLUT 
  62      haveGLUT=True 
  63  except: 
  64      log.warning('GLUT not available - is the GLUT library installed on the path?') 
  65      haveGLUT=False 
  66   
  67  global DEBUG; DEBUG=False 
  68   
  69  _depthIncrements = {'pyglet':+0.001, 'pygame':-0.001, 'glut':-0.001} 
  70   
  71  #symbols for MovieStim 
  72  PLAYING=1 
  73  PAUSED=2 
  74  NOT_STARTED=0 
  75  FINISHED=-1 
  76   
77 -class Window:
78 """Used to set up a context in which to draw objects, 79 using either PyGame (python's SDL binding) or pyglet. 80 81 The pyglet backend allows multiple windows to be created, allows the user to specify 82 which screen to use (if more than one is available, duh!) and allows movies to be 83 rendered. 84 85 Pygame has fewer bells and whistles, but does seem a little faster in text rendering. 86 Pygame is used for all sound production and for monitoring the joystick. 87 88 """
89 - def __init__(self, 90 size = (800,600), 91 pos = None, 92 color=(0,0,0), 93 colorSpace='rgb', 94 rgb = None, 95 dkl=None, 96 lms=None, 97 fullscr=None, 98 allowGUI=None, 99 monitor=dict([]), 100 bitsMode=None, 101 winType=None, 102 units=None, 103 gamma = None, 104 blendMode='avg', 105 screen=0, 106 viewScale = None, 107 viewPos = None, 108 viewOri = 0.0, 109 waitBlanking=True):
110 """ 111 :Parameters: 112 113 size : (800,600) 114 Size of the window in pixels (X,Y) 115 pos : *None* or (x,y) 116 Location of the window on the screen 117 rgb : [0,0,0] 118 Color of background as [r,g,b] list or single value. Each gun can take values betweeen -1 and 1 119 fullscr : *None*, True or False 120 Better timing can be achieved in full-screen mode 121 allowGUI : *None*, True or False (if None prefs are used) 122 If set to False, window will be drawn with no frame and no buttons to close etc... 123 winType : *None*, 'pyglet', 'pygame' 124 If None then PsychoPy will revert to user/site preferences 125 monitor : *None*, string or a `~psychopy.monitors.Monitor` object 126 The monitor to be used during the experiment 127 units : *None*, 'norm' (normalised),'deg','cm','pix' 128 Defines the default units of stimuli drawn in the window (can be overridden by each stimulus) 129 See :ref:`units` for explanation of options. 130 screen : *0*, 1 (or higher if you have many screens) 131 Specifies the physical screen that stimuli will appear on (pyglet winType only) 132 viewScale : *None* or [x,y] 133 Can be used to apply a custom scaling to the current units of the :class:`~psychopy.visual.Window`. 134 viewPos : *None*, or [x,y] 135 If not None, redefines the origin for the window 136 viewOri : *0* or any numeric value 137 A single value determining the orientation of the view in degs 138 waitBlanking : *None*, True or False. 139 After a call to flip() should we wait for the blank before the script continues 140 gamma : 1.0, 141 Monitor gamma for linearisation (will use Bits++ if possible). Overrides monitor settings 142 bitsMode : None, 'fast', ('slow' mode is deprecated). 143 Defines how (and if) the Bits++ box will be used. 'fast' updates every frame by drawing a hidden line on the top of the screen. 144 145 :note: Preferences. Some parameters (e.g. units) can now be given default values in the user/site preferences and these will be used if None is given here. If you do specify a value here it will take precedence over preferences. 146 147 """ 148 149 self.size = numpy.array(size, numpy.int) 150 self.pos = pos 151 self.winHandle=None#this will get overridden once the window is created 152 153 self._defDepth=0.0 154 155 #settings for the monitor: local settings (if available) override monitor 156 #if we have a monitors.Monitor object (psychopy 0.54 onwards) 157 #convert to a Monitor object 158 if monitor==None: 159 self.monitor = monitors.Monitor('__blank__') 160 if type(monitor) in [str, unicode]: 161 self.monitor = monitors.Monitor(monitor) 162 elif type(monitor)==dict: 163 #convert into a monitor object 164 self.monitor = monitors.Monitor('temp',currentCalib=monitor,verbose=False) 165 else: 166 self.monitor = monitor 167 168 #otherwise monitor will just be a dict 169 self.scrWidthCM=self.monitor.getWidth() 170 self.scrDistCM=self.monitor.getDistance() 171 172 scrSize = self.monitor.getSizePix() 173 if scrSize==None: 174 self.scrWidthPIX=None 175 else:self.scrWidthPIX=scrSize[0] 176 177 if fullscr==None: self._isFullScr = prefs.general['fullscr'] 178 else: self._isFullScr = fullscr 179 if units==None: self.units = prefs.general['units'] 180 else: self.units = units 181 if allowGUI==None: self.allowGUI = prefs.general['allowGUI'] 182 else: self.allowGUI = allowGUI 183 self.screen = screen 184 185 #parameters for transforming the overall view 186 #scale 187 if type(viewScale) in [list, tuple]: 188 self.viewScale = numpy.array(viewScale, numpy.float64) 189 elif type(viewScale) in [int, float]: 190 self.viewScale = numpy.array([viewScale,viewScale], numpy.float64) 191 else: self.viewScale = viewScale 192 #pos 193 if type(viewPos) in [list, tuple]: 194 self.viewPos = numpy.array(viewPos, numpy.float64) 195 else: self.viewPos = viewPos 196 self.viewOri = float(viewOri) 197 198 #setup bits++ if possible 199 self.bitsMode = bitsMode #could be [None, 'fast', 'slow'] 200 if self.bitsMode!=None: 201 from psychopy.hardware.crs import bits 202 self.bits = bits.BitsBox(self) 203 self.haveBits = True 204 205 #gamma 206 if self.bitsMode!=None and hasattr(self.monitor, 'lineariseLums'): 207 #rather than a gamma value we could use bits++ and provide a complete linearised lookup table 208 #using monitor.lineariseLums(lumLevels) 209 self.gamma=None 210 if gamma != None and (type(gamma) in [float, int]): 211 #an integer that needs to be an array 212 self.gamma=[gamma,gamma,gamma] 213 self.useNativeGamma=False 214 elif gamma != None:# and (type(gamma) not in [float, int]): 215 #an array (hopefully!) 216 self.gamma=gamma 217 self.useNativeGamma=False 218 elif type(self.monitor.getGammaGrid())==numpy.ndarray: 219 self.gamma = self.monitor.getGammaGrid()[1:,2] 220 if self.monitor.gammaIsDefault(): #are we using the default gamma for all monitors? 221 self.useNativeGamma=True 222 else:self.useNativeGamma=False 223 elif self.monitor.getGamma()!=None: 224 self.gamma = self.monitor.getGamma() 225 self.useNativeGamma=False 226 else: 227 self.gamma = None #gamma wasn't set anywhere 228 self.useNativeGamma=True 229 230 #load color conversion matrices 231 dkl_rgb = self.monitor.getDKL_RGB() 232 if dkl_rgb!=None: 233 self.dkl_rgb=dkl_rgb 234 else: self.dkl_rgb = None 235 lms_rgb = self.monitor.getLMS_RGB() 236 if lms_rgb!=None: 237 self.lms_rgb=lms_rgb 238 else: self.lms_rgb = None 239 240 #set screen color 241 self.colorSpace=colorSpace 242 if rgb!=None: 243 log.warning("Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead") 244 self.setColor(rgb, colorSpace='rgb') 245 elif dkl!=None: 246 log.warning("Use of dkl arguments to stimuli are deprecated. Please use color and colorSpace args instead") 247 self.setColor(dkl, colorSpace='dkl') 248 elif lms!=None: 249 log.warning("Use of lms arguments to stimuli are deprecated. Please use color and colorSpace args instead") 250 self.setColor(lms, colorSpace='lms') 251 else: 252 self.setColor(color, colorSpace=colorSpace) 253 254 #check whether FBOs are supported 255 if blendMode=='add' and not haveFB: 256 log.warning("""User requested a blendmode of "add" but framebuffer objects not available. You need PyOpenGL3.0+ to use this blend mode""") 257 self.blendMode='average' #resort to the simpler blending without float rendering 258 else: self.blendMode=blendMode 259 260 #setup context and openGL() 261 if winType==None:#choose the default windowing 262 self.winType=prefs.general['winType'] 263 else: 264 self.winType = winType 265 if self.winType=='pyglet' and not havePyglet: 266 log.warning("Requested pyglet backend but pyglet is not installed or not fully working") 267 self.winType='pygame' 268 if self.winType=='pygame' and not havePygame: 269 log.warning("Requested pygame backend but pygame (or PyOpenGL) is not installed or not fully working") 270 self.winType='pyglet' 271 #setup the context 272 if self.winType == "glut": self._setupGlut() 273 elif self.winType == "pygame": self._setupPygame() 274 elif self.winType == "pyglet": 275 self._setupPyglet() 276 277 #check whether shaders are supported 278 if self.winType=='pyglet':#we can check using gl_info 279 if pyglet.gl.gl_info.get_version()>='2.0': 280 self._haveShaders=True #also will need to check for ARB_float extension, but that should be done after context is created 281 else: 282 self._haveShaders=False 283 else: 284 self._haveShaders=False 285 self._setupGL() 286 self.frameClock = core.Clock()#from psycho/core 287 self.frames = 0 #frames since last fps calc 288 self.movieFrames=[] #list of captured frames (Image objects) 289 290 self.recordFrameIntervals=False 291 self.frameIntervals=[] 292 293 294 if self.useNativeGamma: 295 log.info('Using gamma table of operating system') 296 else: 297 log.info('Using gamma: self.gamma' + str(self.gamma)) 298 self.setGamma(self.gamma)#using either pygame or bits++ 299 self.lastFrameT = core.getTime() 300 301 if self.units=='norm': self.setScale('norm') 302 else: self.setScale('pix') 303 304 self.waitBlanking = waitBlanking 305 306 self._refreshThreshold=1/1.0#initial val needed by flip() 307 self._monitorFrameRate = self._getActualFrameRate()#over several frames with no drawing 308 if self._monitorFrameRate != None: 309 self._refreshThreshold = (1.0/self._monitorFrameRate)*1.2 310 else: 311 self._refreshThreshold = (1.0/60)*1.2#guess its a flat panel
312
313 - def setRecordFrameIntervals(self, value=True):
314 """To provide accurate measures of frame intervals, to determine whether frames 315 are being dropped. Set this to False while the screen is not being updated 316 e.g. during event.waitkeys() and set to True during critical parts of the script 317 318 see also: 319 Window.saveFrameIntervals() 320 """ 321 self.recordFrameIntervals=value 322 self.frameClock.reset()
323 - def saveFrameIntervals(self, fileName=None, clear=True):
324 """Save recorded screen frame intervals to disk, as comma-separated values. 325 326 :Parameters: 327 328 fileName : *None* or the filename (including path if necessary) in which to store the data. 329 If None then 'lastFrameIntervals.log' will be used. 330 331 """ 332 if fileName==None: 333 fileName = 'lastFrameIntervals.log' 334 if len(self.frameIntervals): 335 intervalStr = str(self.frameIntervals)[1:-1] 336 f = open(fileName, 'w') 337 f.write(intervalStr) 338 f.close() 339 if clear: 340 self.frameIntervals=[] 341 self.frameClock.reset()
342 - def whenIdle(self,func):
343 """Defines the function to use during idling (GLUT only) 344 """ 345 GLUT.glutIdleFunc(func)
346 - def onResize(self, width, height):
347 '''A default resize event handler. 348 349 This default handler updates the GL viewport to cover the entire 350 window and sets the ``GL_PROJECTION`` matrix to be orthagonal in 351 window space. The bottom-left corner is (0, 0) and the top-right 352 corner is the width and height of the :class:`~psychopy.visual.Window` in pixels. 353 354 Override this event handler with your own to create another 355 projection, for example in perspective. 356 ''' 357 if height==0: 358 height=1 359 GL.glViewport(0, 0, width, height) 360 GL.glMatrixMode(GL.GL_PROJECTION) 361 GL.glLoadIdentity() 362 GL.glOrtho(-1,1,-1,1, -1, 1) 363 #GL.gluPerspective(90, 1.0*width/height, 0.1, 100.0) 364 GL.glMatrixMode(GL.GL_MODELVIEW) 365 GL.glLoadIdentity()
366
367 - def flip(self, clearBuffer=True):
368 """Flip the front and back buffers after drawing everything for your frame. 369 (This replaces the win.update() method, better reflecting what is happening underneath). 370 371 win.flip(clearBuffer=True)#results in a clear screen after flipping 372 win.flip(clearBuffer=False)#the screen is not cleared (so represent the previous screen) 373 """ 374 if haveFB: 375 #need blit the frambuffer object to the actual back buffer 376 377 FB.glBindFramebufferEXT(FB.GL_FRAMEBUFFER_EXT, 0)#unbind the framebuffer as the render target 378 379 #before flipping need to copy the renderBuffer to the frameBuffer 380 GL.glActiveTexture(GL.GL_TEXTURE0) 381 GL.glEnable(GL.GL_TEXTURE_2D) 382 GL.glBindTexture(GL.GL_TEXTURE_2D, self.frameTexture) 383 GL.glBegin( GL.GL_QUADS ) 384 GL.glTexCoord2f( 0.0, 0.0 ) ; GL.glVertex2f( -1.0,-1.0 ) 385 GL.glTexCoord2f( 0.0, 1.0 ) ; GL.glVertex2f( -1.0, 1.0 ) 386 GL.glTexCoord2f( 1.0, 1.0 ) ; GL.glVertex2f( 1.0, 1.0 ) 387 GL.glTexCoord2f( 1.0, 0.0 ) ; GL.glVertex2f( 1.0, -1.0 ) 388 GL.glEnd() 389 390 #update the bits++ LUT 391 if self.bitsMode == 'fast': 392 self.bits._drawLUTtoScreen() 393 394 if self.winType == "glut": GLUT.glutSwapBuffers() 395 elif self.winType =="pyglet": 396 #print "updating pyglet" 397 #make sure this is current context 398 self.winHandle.switch_to() 399 400 GL.glTranslatef(0.0,0.0,-5.0) 401 402 self.winHandle.dispatch_events()#this might need to be done even more often than once per frame? 403 pyglet.media.dispatch_events()#for sounds to be processed 404 self.winHandle.flip() 405 #self.winHandle.clear() 406 GL.glLoadIdentity() 407 else: 408 if pygame.display.get_init(): 409 pygame.display.flip() 410 pygame.event.pump()#keeps us in synch with system event queue 411 else: 412 core.quit()#we've unitialised pygame so quit 413 414 if self.recordFrameIntervals: 415 self.frames +=1 416 now = core.getTime() 417 deltaT = now - self.lastFrameT; self.lastFrameT=now 418 self.frameIntervals.append(deltaT) 419 420 if deltaT>self._refreshThreshold: 421 log.warning('t of last frame was %.2fms (=1/%i)' %(deltaT*1000, 1/deltaT)) 422 423 #rescale/reposition view of the window 424 if self.viewScale != None: 425 GL.glMatrixMode(GL.GL_PROJECTION) 426 GL.glLoadIdentity() 427 GL.glOrtho(-1,1,-1,1,-1,1) 428 GL.glScalef(self.viewScale[0], self.viewScale[1], 1) 429 if self.viewPos != None: 430 GL.glMatrixMode(GL.GL_MODELVIEW) 431 # GL.glLoadIdentity() 432 if self.viewScale==None: scale=[1,1] 433 else: scale=self.viewScale 434 norm_rf_pos_x = self.viewPos[0]/scale[0] 435 norm_rf_pos_y = self.viewPos[1]/scale[1] 436 GL.glTranslatef( norm_rf_pos_x, norm_rf_pos_y, 0.0) 437 if self.viewOri != None: 438 GL.glRotatef( self.viewOri, 0.0, 0.0, -1.0) 439 440 if haveFB: 441 #set rendering back to the framebuffer object 442 FB.glBindFramebufferEXT(FB.GL_FRAMEBUFFER_EXT, self.frameBuffer) 443 444 #reset returned buffer for next frame 445 if clearBuffer: GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) 446 else: GL.glClear(GL.GL_DEPTH_BUFFER_BIT)#always clear the depth bit 447 self._defDepth=0.0#gets gradually updated through frame 448 449 if self.waitBlanking: 450 GL.glBegin(GL.GL_POINTS) 451 GL.glColor4f(0,0,0,0) 452 GL.glVertex2i(10,10) 453 GL.glEnd() 454 GL.glFinish()
455
456 - def update(self):
457 """Deprecated: use Window.flip() instead 458 """ 459 self.flip(clearBuffer=True)#clearBuffer was the original behaviour for win.update()
460
461 - def clearBuffer(self):
462 """Clear the back buffer (to which you are currently drawing) without flipping the window. 463 Useful if you want to generate movie sequences from the back buffer without actually 464 taking the time to flip the window. 465 """ 466 #reset returned buffer for next frame 467 GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) 468 self._defDepth=0.0#gets gradually updated through frame
469
470 - def getMovieFrame(self, buffer='front'):
471 """ 472 Capture the current Window as an image. 473 This can be done at any time (usually after a .update() command). 474 475 Frames are stored in memory until a .saveMovieFrames(filename) command 476 is issued. You can issue getMovieFrame() as often 477 as you like and then save them all in one go when finished. 478 """ 479 im = self._getFrame(buffer=buffer) 480 self.movieFrames.append(im)
481
482 - def _getFrame(self, buffer='front'):
483 """ 484 Return the current Window as an image. 485 """ 486 #GL.glLoadIdentity() 487 #do the reading of the pixels 488 if buffer=='back': 489 GL.glReadBuffer(GL.GL_BACK) 490 else: 491 GL.glReadBuffer(GL.GL_FRONT) 492 493 #fetch the data with glReadPixels 494 if self.winType=='pyglet': 495 #pyglet.gl stores the data in a ctypes buffer 496 bufferDat = (GL.GLubyte * (4 * self.size[0] * self.size[1]))() 497 GL.glReadPixels(0,0,self.size[0],self.size[1], GL.GL_RGBA,GL.GL_UNSIGNED_BYTE,bufferDat) 498 im = Image.fromstring(mode='RGBA',size=self.size, data=bufferDat) 499 else: 500 #pyopengl returns the data 501 im = Image.fromstring(mode='RGBA',size=self.size, 502 data=GL.glReadPixels(0,0,self.size[0],self.size[1], GL.GL_RGBA,GL.GL_UNSIGNED_BYTE), 503 ) 504 505 im=im.transpose(Image.FLIP_TOP_BOTTOM) 506 im=im.convert('RGB') 507 508 return im
509
510 - def saveMovieFrames(self, fileName, mpgCodec='mpeg1video', 511 fps=30):
512 """ 513 Writes any captured frames to disk. Will write any format 514 that is understood by PIL (tif, jpg, bmp, png...) 515 516 :parameters: 517 518 filename: name of file, including path (required) 519 The extension at the end of the file determines the type of file(s) 520 created. If an image type is given the multiple static frames are created. 521 If it is .gif then an animated GIF image is created (although you will get higher 522 quality GIF by saving PNG files and then combining them in dedicated 523 image manipulation software (e.g. GIMP). On windows and linux `.mpeg` files 524 can be created if `pymedia` is installed. On OS X `.mov` files can be created 525 if the pyobjc-frameworks-QTKit is installed. 526 527 mpgCodec: the code to be used **by pymedia** if the filename ends in .mpg 528 529 fps: the frame rate to be used throughout the movie **only for quicktime (.mov) movies** 530 531 Examples:: 532 myWin.saveMovieFrames('frame.tif')#writes a series of static frames as frame001.tif, frame002.tif etc... 533 myWin.saveMovieFrames('stimuli.mov', fps=25)#on OS X only 534 myWin.saveMovieFrames('stimuli.gif')#but not great quality 535 myWin.saveMovieFrames('stimuli.mpg')#not on OS X 536 537 """ 538 fileRoot, fileExt = os.path.splitext(fileName) 539 if len(self.movieFrames)==0: 540 log.error('no frames to write - did you forget to update your window?') 541 return 542 else: 543 log.info('writing %i frames' %len(self.movieFrames)) 544 if fileExt=='.gif': makeMovies.makeAnimatedGIF(fileName, self.movieFrames) 545 elif fileExt in ['.mpg', '.mpeg']: 546 if sys.platform=='darwin': 547 raise IOError('Mpeg movies are not currently available under OSX.'+\ 548 ' You can use quicktime movies (.mov) instead though.') 549 makeMovies.makeMPEG(fileName, self.movieFrames, codec=mpgCodec) 550 elif fileExt in ['.mov', '.MOV']: 551 if sys.platform!='darwin': 552 raise IOError('Quicktime movies are only currently available under OSX.'+\ 553 ' Try using mpeg compression instead (.mpg).') 554 mov = makeMovies.QuicktimeMovie(fileName, fps=fps) 555 for frame in self.movieFrames: 556 mov.addFrame(frame) 557 mov.save() 558 elif len(self.movieFrames)==1: 559 self.movieFrames[0].save(fileName) 560 else: 561 frame_name_format = "%s%%0%dd%s" % (fileRoot, numpy.ceil(numpy.log10(len(self.movieFrames)+1)), fileExt) 562 for frameN, thisFrame in enumerate(self.movieFrames): 563 thisFileName = frame_name_format % (frameN+1,) 564 thisFrame.save(thisFileName)
565
566 - def _getRegionOfFrame(self, rect=[-1,1,1,-1], buffer='front', power2=False, squarePower2=False):
567 """ 568 Capture a rectangle (Left Top Right Bottom, norm units) of the window as an RBGA image. 569 570 power2 can be useful with older OpenGL versions to avoid interpolation in PatchStim. 571 If power2 or squarePower2, it will expand rect dimensions up to next power of two. 572 squarePower2 uses the max dimenions. You need to check what your hardware & 573 opengl supports, and call _getRegionOfFrame as appropriate. 574 """ 575 # Ideally: rewrite using GL frame buffer object; glReadPixels == slow 576 577 x, y = self.size # of window, not image 578 imType = 'RGBA' # not tested with anything else 579 580 box = [(rect[0]/2. + 0.5)*x, (rect[1]/-2. + 0.5)*y, # Left Top in pix 581 (rect[2]/2. + 0.5)*x, (rect[3]/-2. + 0.5)*y] # Right Bottom in pix 582 box = map(int, box) 583 if buffer=='back': 584 GL.glReadBuffer(GL.GL_BACK) 585 else: 586 GL.glReadBuffer(GL.GL_FRONT) 587 588 if self.winType == 'pyglet': #pyglet.gl stores the data in a ctypes buffer 589 bufferDat = (GL.GLubyte * (4 * (box[2]-box[0]) * (box[3]-box[1])))() 590 GL.glReadPixels(box[0], box[1], box[2]-box[0], box[3]-box[1], GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, bufferDat) 591 #http://www.opengl.org/sdk/docs/man/xhtml/glGetTexImage.xml 592 #GL.glGetTexImage(GL.GL_TEXTURE_1D, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, bufferDat) # not right 593 im = Image.fromstring(mode='RGBA', size=(box[2]-box[0], box[3]-box[1]), data=bufferDat) 594 else: # works but much slower #pyopengl returns the data 595 im = Image.fromstring(mode='RGBA', size=(box[2]-box[0], box[3]-box[1]), 596 data=GL.glReadPixels(box[0], box[1], (box[2]-box[0]), 597 (box[3]-box[1]), GL.GL_RGBA,GL.GL_UNSIGNED_BYTE),) 598 region = im.transpose(Image.FLIP_TOP_BOTTOM) 599 600 if power2 or squarePower2: # use to avoid interpolation in PatchStim 601 if squarePower2: 602 xPowerOf2 = yPowerOf2 = int(2**numpy.ceil(numpy.log2(max(region.size)))) 603 else: 604 xPowerOf2 = int(2**numpy.ceil(numpy.log2(region.size[0]))) 605 yPowerOf2 = int(2**numpy.ceil(numpy.log2(region.size[1]))) 606 imP2 = Image.new(imType, (xPowerOf2, yPowerOf2)) 607 imP2.paste(region, (int(xPowerOf2/2. - region.size[0]/2.), 608 int(yPowerOf2/2. - region.size[1]/2))) # paste centered 609 region = imP2 610 611 return region
612
613 - def fullScr(self):
614 """Toggles fullscreen mode (GLUT only). 615 616 Fullscreen mode for PyGame contexts must be set during initialisation 617 of the :class:`~psychopy.visual.Window` 618 """ 619 if self.winType=='glut': 620 if self._isFullScr: 621 GLUT.glutReshapeWindow(int(self.size[0]), int(self.size[1])) 622 self._isFullScr=0 623 else: 624 GLUT.glutFullScreen() 625 self._isFullScr=1 626 else: 627 log.warning('fullscreen toggling is only available to glut contexts')
628
629 - def close(self):
630 """Close the window (and reset the Bits++ if necess).""" 631 self.setMouseVisible(True) 632 if self.winType=='GLUT': 633 GLUT.glutDestroyWindow(self.handle) 634 elif self.winType=='pyglet': 635 self.winHandle.close() 636 else: 637 #pygame.quit() 638 pygame.display.quit() 639 if self.bitsMode!=None: 640 self.bits.reset()
641
642 - def go(self):
643 """start the display loop (GLUT only)""" 644 self.frameClock.reset() 645 GLUT.glutMainLoop()
646
647 - def fps(self):
648 """Report the frames per second since the last call to this function 649 (or since the window was created if this is first call)""" 650 fps = self.frames/(self.frameClock.getTime()) 651 self.frameClock.reset() 652 self.frames = 0 653 return fps
654
655 - def setColor(self, color, colorSpace=None, operation=''):
656 """Set the color of the window. 657 658 NB This command sets the color that the blank screen will have on the next 659 clear operation. As a result it effectively takes TWO `flip()` operations to become 660 visible (the first uses the color to create the new screen the second presents 661 that screen to the viewer). 662 663 See :ref:`colorspaces` for further information about the ways to specify colors and their various implications. 664 665 :Parameters: 666 667 color : 668 Can be specified in one of many ways. If a string is given then it 669 is interpreted as the name of the color. Any of the standard html/X11 670 `color names <http://www.w3schools.com/html/html_colornames.asp>` 671 can be used. e.g.:: 672 673 myStim.setColor('white') 674 myStim.setColor('RoyalBlue')#(the case is actually ignored) 675 676 A hex value can be provided, also formatted as with web colors. This can be 677 provided as a string that begins with # (not using python's usual 0x000000 format):: 678 679 myStim.setColor('#DDA0DD')#DDA0DD is hexadecimal for plum 680 681 You can also provide a triplet of values, which refer to the coordinates 682 in one of the :ref:`colorspaces`. If no color space is specified then the color 683 space most recently used for this stimulus is used again. 684 685 myStim.setColor([1.0,-1.0,-1.0], 'rgb')#a red color in rgb space 686 myStim.setColor([0.0,45.0,1.0], 'dkl') #DKL space with elev=0, azimuth=45 687 myStim.setColor([0,0,255], 'rgb255') #a blue stimulus using rgb255 space 688 689 Lastly, a single number can be provided, x, which is equivalent to providing 690 [x,x,x]. 691 692 myStim.setColor(255, 'rgb255') #all guns o max 693 694 colorSpace : string or None 695 696 defining which of the :ref:`colorspaces` to use. For strings and hex 697 values this is not needed. If None the default colorSpace for the stimulus is 698 used (defined during initialisation). 699 700 operation : one of '+','-','*','/', or '' for no operation (simply replace value) 701 702 for colors specified as a triplet of values (or single intensity value) 703 the new value will perform this operation on the previous color 704 705 thisStim.setColor([1,1,1],'rgb255','+')#increment all guns by 1 value 706 thisStim.setColor(-1, 'rgb', '*') #multiply the color by -1 (which in this space inverts the contrast) 707 thisStim.setColor([10,0,0], 'dkl', '+')#raise the elevation from the isoluminant plane by 10 deg 708 """ 709 _setColor(self, color, colorSpace=colorSpace, operation=operation, 710 rgbAttrib='rgb', #or 'fillRGB' etc 711 colorAttrib='color') 712 713 if self.winHandle!=None:#if it is None then this will be done during window setup 714 if self.winType=='pyglet': self.winHandle.switch_to() 715 GL.glClearColor((self.rgb[0]+1.0)/2.0, (self.rgb[1]+1.0)/2.0, (self.rgb[2]+1.0)/2.0, 1.0)
716
717 - def setRGB(self, newRGB):
718 """Deprecated: As of v1.61.00 please use `setColor()` instead 719 """ 720 global GL 721 if type(newRGB) in [int, float]: 722 self.rgb=[newRGB, newRGB, newRGB] 723 else: 724 self.rgb=newRGB 725 if self.winType=='pyglet': self.winHandle.switch_to() 726 GL.glClearColor((self.rgb[0]+1.0)/2.0, (self.rgb[1]+1.0)/2.0, (self.rgb[2]+1.0)/2.0, 1.0)
727
728 - def setScale(self, units, font='dummyFont', prevScale=(1.0,1.0)):
729 """This method is called from within the draw routine and sets the 730 scale of the OpenGL context to map between units. Could potentially be 731 called by the user in order to draw OpenGl objects manually 732 in each frame. 733 734 The `units` can be 'norm'(normalised),'pix'(pixels),'cm' or 735 'stroke_font'. The `font` parameter is only used if units='stroke_font' 736 """ 737 if units=="norm": 738 thisScale = numpy.array([1.0,1.0]) 739 elif units in ["pix", "pixels"]: 740 thisScale = 2.0/numpy.array(self.size) 741 elif units=="cm": 742 #windowPerCM = windowPerPIX / CMperPIX 743 # = (window /winPIX) / (scrCm /scrPIX) 744 if (self.scrWidthCM in [0,None]) or (self.scrWidthPIX in [0, None]): 745 log.error('you didnt give me the width of the screen (pixels and cm). Check settings in MonitorCentre.') 746 core.wait(1.0); core.quit() 747 thisScale = (numpy.array([2.0,2.0])/self.size)/(float(self.scrWidthCM)/float(self.scrWidthPIX)) 748 elif units in ["deg", "degs"]: 749 #windowPerDeg = winPerCM*CMperDEG 750 # = winPerCM * tan(pi/180) * distance 751 if (self.scrWidthCM in [0,None]) or (self.scrWidthPIX in [0, None]): 752 log.error('you didnt give me the width of the screen (pixels and cm). Check settings in MonitorCentre.') 753 core.wait(1.0); core.quit() 754 cmScale = (numpy.array([2.0,2.0])/self.size)/(float(self.scrWidthCM)/float(self.scrWidthPIX)) 755 thisScale = cmScale * 0.017455 * self.scrDistCM 756 elif units=="stroke_font": 757 thisScale = numpy.array([2*font.letterWidth,2*font.letterWidth]/self.size/38.0) 758 #actually set the scale as appropriate 759 thisScale = thisScale/numpy.asarray(prevScale)#allows undoing of a previous scaling procedure 760 GL.glScalef(thisScale[0], thisScale[1], 1.0) 761 return thisScale #just in case the user wants to know?!
762
763 - def setGamma(self,gamma):
764 """Set the monitor gamma, using Bits++ if possible""" 765 if type(gamma) in [float, int]: 766 self.gamma=[gamma,gamma,gamma] 767 else: 768 self.gamma=gamma 769 770 if self.bitsMode != None: 771 #first ensure that window gamma is 1.0 772 if self.winType=='pygame': 773 pygame.display.set_gamma(1.0,1.0,1.0) 774 elif self.winType=='pyglet': 775 self.winHandle.setGamma(self.winHandle, 1.0) 776 #then set bits++ to desired gamma 777 self.bits.setGamma(self.gamma) 778 elif self.winType=='pygame': 779 pygame.display.set_gamma(self.gamma[0], self.gamma[1], self.gamma[2]) 780 elif self.winType=='pyglet': 781 self.winHandle.setGamma(self.winHandle, self.gamma)
782
783 - def _setupGlut(self):
784 self.winType="glut" 785 #initialise a window 786 GLUT.glutInit(sys.argv) 787 iconFile = os.path.join(psychopy.__path__[0], 'psychopy.gif') 788 GLUT.glutSetIconTitle(iconFile) 789 GLUT.glutInitDisplayMode(GLUT.GLUT_RGBA | GLUT.GLUT_DOUBLE | GLUT.GLUT_ALPHA | GLUT.GLUT_DEPTH) 790 self.handle = GLUT.glutCreateWindow('PsychoPy') 791 792 if self._isFullScr: GLUT.glutFullScreen() 793 else: GLUT.glutReshapeWindow(int(self.size[0]), int(self.size[1])) 794 #set the redisplay callback 795 GLUT.glutDisplayFunc(self.update)
796 - def _setupPyglet(self):
797 global GL, GLU, GL_multitexture, _shaders#will use these later to assign the pyglet or pyopengl equivs 798 self.winType = "pyglet" 799 #setup the global use of pyglet.gl 800 GL = pyglet.gl 801 GLU = pyglet.gl 802 GL_multitexture = pyglet.gl 803 804 config = GL.Config(depth_size=8, double_buffer=True) 805 allScrs = pyglet.window.get_platform().get_default_display().get_screens() 806 if len(allScrs)>self.screen: 807 thisScreen = allScrs[self.screen] 808 log.info('configured pyglet screen %i' %self.screen) 809 else: 810 log.error("Requested an unavailable screen number") 811 #if fullscreen check screen size 812 if self._isFullScr: 813 self._checkMatchingSizes(self.size,[thisScreen.width, thisScreen.height]) 814 w=h=None 815 else:w,h=self.size 816 if self.allowGUI: style=None 817 else: style='borderless' 818 self.winHandle = pyglet.window.Window(width=w,height=h, 819 caption="PsychoPy", 820 fullscreen=self._isFullScr, 821 config=config, 822 screen=thisScreen, 823 style=style 824 ) 825 #add these methods to the pyglet window 826 self.winHandle.setGamma = gamma.setGamma 827 self.winHandle.setGammaRamp = gamma.setGammaRamp 828 self.winHandle.getGammaRamp = gamma.getGammaRamp 829 self.winHandle.set_vsync(True) 830 self.winHandle.on_key_press = psychopy.event._onPygletKey 831 self.winHandle.on_mouse_press = psychopy.event._onPygletMousePress 832 self.winHandle.on_mouse_release = psychopy.event._onPygletMouseRelease 833 self.winHandle.on_mouse_scroll = psychopy.event._onPygletMouseWheel 834 if not self.allowGUI: 835 #make mouse invisible. Could go further and make it 'exclusive' (but need to alter x,y handling then) 836 self.winHandle.set_mouse_visible(False) 837 self.winHandle.on_resize = self.onResize 838 if self.pos==None: 839 #work out where the centre should be 840 self.pos = [ (thisScreen.width-self.size[0])/2 , (thisScreen.height-self.size[1])/2 ] 841 self.winHandle.set_location(self.pos[0]+thisScreen.x, self.pos[1]+thisScreen.y)#add the necessary amount for second screen 842 843 try: #to load an icon for the window 844 iconFile = os.path.join(psychopy.__path__[0], 'psychopy.png') 845 icon = pyglet.image.load(filename=iconFile) 846 self.winHandle.set_icon(icon) 847 except: pass#doesn't matter
848 - def _checkMatchingSizes(self, requested,actual):
849 """Checks whether the requested and actual screen sizes differ. If not 850 then a warning is output and the window size is set to actual 851 """ 852 if list(requested)!=list(actual): 853 log.warning("User requested fullscreen with size %s, but screen is actually %s. Using actual size" \ 854 %(requested, actual)) 855 self.size=numpy.array(actual)
856 - def _setupPygame(self):
857 self.winType = "pygame" 858 global GL, GLU, GL_multitexture, _shaders#will use these later to assign the pyglet or pyopengl equivs 859 860 #setup the global use of PyOpenGL (rather than pyglet.gl) 861 GL = OpenGL.GL 862 GL_multitexture = OpenGL.GL.ARB.multitexture 863 GLU = OpenGL.GLU 864 #pygame.mixer.pre_init(22050,16,2)#set the values to initialise sound system if it gets used 865 pygame.init() 866 867 try: #to load an icon for the window 868 iconFile = os.path.join(psychopy.__path__[0], 'psychopy.png') 869 icon = pygame.image.load(iconFile) 870 pygame.display.set_icon(icon) 871 except: pass#doesn't matter 872 873 winSettings = pygame.OPENGL|pygame.DOUBLEBUF#|pygame.OPENGLBLIT #these are ints stored in pygame.locals 874 if self._isFullScr: 875 winSettings = winSettings | pygame.FULLSCREEN 876 #check screen size if full screen 877 scrInfo=pygame.display.Info() 878 self._checkMatchingSizes(self.size,[scrInfo.current_w,scrInfo.current_h]) 879 elif self.pos==None: 880 #centre video 881 os.environ['SDL_VIDEO_CENTERED']="1" 882 else: os.environ['SDL_VIDEO_WINDOW_POS']= '%i,%i' %(self.pos[0], self.pos[1]) 883 if sys.platform=='win32': 884 os.environ['SDL_VIDEODRIVER'] = 'windib' 885 if not self.allowGUI: 886 winSettings = winSettings |pygame.NOFRAME 887 self.setMouseVisible(False) 888 pygame.display.set_caption('PsychoPy (NB use with allowGUI=False when running properly)') 889 else: 890 self.setMouseVisible(True) 891 pygame.display.set_caption('PsychoPy') 892 self.winHandle = pygame.display.set_mode(self.size.astype('i'),winSettings) 893 pygame.display.set_gamma(1.0) #this will be set appropriately later
894 - def _setupGL(self):
895 if self.winType=='pyglet': _shaders=_shadersPyglet 896 # else: _shaders=_shadersPygame 897 898 #setup screen color 899 if self.colorSpace in ['rgb','dkl','lms']: #these spaces are 0-centred 900 desiredRGB = (self.rgb+1)/2.0#RGB in range 0:1 and scaled for contrast 901 else: 902 desiredRGB = self.rgb/255.0 903 GL.glClearColor(desiredRGB[0],desiredRGB[1],desiredRGB[2], 1.0) 904 GL.glClearDepth(1.0) 905 906 GL.glViewport(0, 0, int(self.size[0]), int(self.size[1])); 907 908 GL.glMatrixMode(GL.GL_PROJECTION) # Reset The Projection Matrix 909 GL.glLoadIdentity() 910 if self.winType=='pyglet': GL.gluOrtho2D(-1,1,-1,1) 911 912 GL.glMatrixMode(GL.GL_MODELVIEW)# Reset The Projection Matrix 913 GL.glLoadIdentity() 914 915 GL.glEnable(GL.GL_DEPTH_TEST) # Enables Depth Testing 916 GL.glDepthFunc(GL.GL_LESS) # The Type Of Depth Test To Do 917 GL.glEnable(GL.GL_BLEND) 918 GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) 919 920 GL.glShadeModel(GL.GL_SMOOTH) # Color Shading (FLAT or SMOOTH) 921 GL.glEnable(GL.GL_POINT_SMOOTH) 922 923 if self.winType!='pyglet': 924 GL_multitexture.glInitMultitextureARB() 925 else: 926 #check for GL_ARB_texture_float (which is needed for shaders to be useful) 927 #this needs to be done AFTER the context has been created 928 if not pyglet.gl.gl_info.have_extension('GL_ARB_texture_float'): 929 self._haveShaders=False 930 931 if self.winType=='pyglet' and self._haveShaders: 932 #we should be able to compile shaders (don't just 'try') 933 self._progSignedTexMask = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTexMask)#fragSignedColorTexMask 934 self._progSignedTex = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTex) 935 self._progSignedTexMask1D = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTexMask1D) 936 self._progSignedTexFont = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTexFont) 937 # elif self.winType=='pygame':#on PyOpenGL we should try to get an init value 938 # from OpenGL.GL.ARB import shader_objects 939 # if shader_objects.glInitShaderObjectsARB(): 940 # self._haveShaders=True 941 # self._progSignedTexMask = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTexMask)#fragSignedColorTexMask 942 # self._progSignedTex = _shaders.compileProgram(_shaders.vertSimple, _shaders.fragSignedColorTex) 943 # else: 944 # self._haveShaders=False 945 946 GL.glClear(GL.GL_COLOR_BUFFER_BIT|GL.GL_DEPTH_BUFFER_BIT) 947 948 if sys.platform=='darwin': 949 ext.darwin.syncSwapBuffers(1) 950 951 if haveFB: 952 self._setupFrameBuffer()
953
954 - def _setupFrameBuffer(self):
955 # Setup framebuffer 956 self.frameBuffer = FB.glGenFramebuffersEXT(1) 957 958 FB.glBindFramebufferEXT(FB.GL_FRAMEBUFFER_EXT, self.frameBuffer) 959 # Setup depthbuffer 960 self.depthBuffer = FB.glGenRenderbuffersEXT(1) 961 FB.glBindRenderbufferEXT (FB.GL_RENDERBUFFER_EXT,self.depthBuffer) 962 FB.glRenderbufferStorageEXT (FB.GL_RENDERBUFFER_EXT, GL.GL_DEPTH_COMPONENT, int(self.size[0]), int(self.size[1])) 963 964 # Create texture to render to 965 self.frameTexture = GL.glGenTextures (1) 966 GL.glBindTexture (GL.GL_TEXTURE_2D, self.frameTexture) 967 GL.glTexParameteri (GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) 968 GL.glTexParameteri (GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) 969 GL.glTexImage2D (GL.GL_TEXTURE_2D, 0, GL.GL_RGBA32F_ARB, int(self.size[0]), int(self.size[1]), 0, 970 GL.GL_RGBA, GL.GL_FLOAT, None) 971 972 #attach texture to the frame buffer 973 FB.glFramebufferTexture2DEXT (FB.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, 974 GL.GL_TEXTURE_2D, self.frameTexture, 0); 975 FB.glFramebufferRenderbufferEXT(FB.GL_FRAMEBUFFER_EXT, GL.GL_DEPTH_ATTACHMENT_EXT, 976 FB.GL_RENDERBUFFER_EXT, self.depthBuffer); 977 978 status = FB.glCheckFramebufferStatusEXT (FB.GL_FRAMEBUFFER_EXT); 979 if status != FB.GL_FRAMEBUFFER_COMPLETE_EXT: 980 print "Error in framebuffer activation" 981 return 982 GL.glDisable(GL.GL_TEXTURE_2D)
983
984 - def setMouseVisible(self,visibility):
985 """Sets the visibility of the mouse cursor. 986 987 If Window was initilised with noGUI=True then the mouse is initially 988 set to invisible, otherwise it will initially be visible. 989 990 Usage: 991 ``setMouseVisible(False)`` 992 ``setMouseVisible(True)`` 993 """ 994 if self.winType=='pygame':wasVisible = pygame.mouse.set_visible(visibility) 995 elif self.winType=='pyglet':self.winHandle.set_mouse_visible(visibility) 996 self.mouseVisible = visibility
997 - def _getActualFrameRate(self,nMaxFrames=100,nWarmUpFrames=10, threshold=1):
998 """Measures the actual fps for the screen. 999 1000 This is done by waiting (for a max of nMaxFrames) until 10 frames in a 1001 row have identical frame times (std dev below 1ms). 1002 1003 If there are no 10 consecutive identical frames a warning is logged and 1004 `None` will be returned. 1005 1006 :parameters: 1007 1008 nMaxFrames: 1009 the maxmimum number of frames to wait for a matching set of 10 1010 1011 nWarmUpFrames: 1012 the number of frames to display before starting the test (this is 1013 in place to allow the system to settle after opening the 1014 `Window` for the first time. 1015 1016 threshold: 1017 the threshold for the std deviation (in ms) before the set are considered 1018 a match 1019 1020 """ 1021 recordFrmIntsOrig = self.recordFrameIntervals 1022 #run warm-ups 1023 self.setRecordFrameIntervals(False) 1024 for frameN in range(nWarmUpFrames): 1025 self.flip() 1026 #run test frames 1027 self.setRecordFrameIntervals(True) 1028 for frameN in range(nMaxFrames): 1029 self.flip() 1030 if len(self.frameIntervals)>=10 and numpy.std(self.frameIntervals[-10:])<(threshold/1000.0): 1031 rate = 1.0/numpy.mean(self.frameIntervals[-10:]) 1032 if self.screen==None:scrStr="" 1033 else: scrStr = " (%i)" %self.screen 1034 log.debug('Screen%s actual frame rate measured at %.2f' %(scrStr,rate)) 1035 self.setRecordFrameIntervals(recordFrmIntsOrig) 1036 self.frameIntervals=[] 1037 return rate 1038 #if we got here we reached end of maxFrames with no consistent value 1039 log.warning("Couldn't measure a consistent frame rate.\n" + \ 1040 " - Is your graphics card set to sync to vertical blank?\n" + \ 1041 " - Are you running other processes on your computer?\n") 1042 return None
1043
1044 -class _BaseVisualStim:
1045 """A template for a stimulus class, on which PatchStim, TextStim etc... are based. 1046 Not finished...? 1047 """
1048 - def __init__(self):
1049 raise NotImplementedError('Stimulus classes must overide _BaseVisualStim.__init__')
1050 - def draw(self):
1051 raise NotImplementedError('Stimulus classes must overide _BaseVisualStim.draw')
1052 - def setPos(self, newPos, operation='', units=None):
1053 """Set the stimulus position in the specified (or inheritted) `units` 1054 """ 1055 self._set('pos', val=newPos, op=operation) 1056 self._calcPosRendered()
1057 - def setDepth(self,newDepth, operation=''):
1058 self._set('depth', newDepth, operation)
1059 - def setSize(self, newSize, operation='', units=None):
1060 """Set the stimulus size [X,Y] in the specified (or inheritted) `units` 1061 """ 1062 if units==None: units=self.units#need to change this to create several units from one 1063 self._set('size', newSize, op=operation) 1064 self._calcSizeRendered() 1065 self.needUpdate=True
1066 - def setOri(self, newOri, operation=''):
1067 """Set the stimulus orientation in degrees 1068 """ 1069 self._set('ori',val=newOri, op=operation)
1070 - def setOpacity(self,newOpacity,operation=''):
1071 self._set('opacity', newOpacity, operation) 1072 #opacity is coded by the texture, if not using shaders 1073 if not self._useShaders: 1074 self.setMask(self._maskName)
1075 - def setDKL(self, newDKL, operation=''):
1076 """DEPRECATED since v1.60.05: Please use setColor 1077 """ 1078 self._set('dkl', val=newDKL, op=operation) 1079 self.setRGB(psychopy.misc.dkl2rgb(self.dkl, self.win.dkl_rgb))
1080 - def setLMS(self, newLMS, operation=''):
1081 """DEPRECATED since v1.60.05: Please use setColor 1082 """ 1083 self._set('lms', value=newLMS, op=operation) 1084 self.setRGB(psychopy.misc.lms2rgb(self.lms, self.win.lms_rgb))
1085 - def setRGB(self, newRGB, operation=''):
1086 """DEPRECATED since v1.60.05: Please use setColor 1087 """ 1088 self._set('rgb', newRGB, operation) 1089 _setTexIfNoShaders(self)
1090
1091 - def setColor(self, color, colorSpace=None, operation=''):
1092 """Set the color of the stimulus. See :ref:`colorspaces` for further information 1093 about the various ways to specify colors and their various implications. 1094 1095 :Parameters: 1096 1097 color : 1098 Can be specified in one of many ways. If a string is given then it 1099 is interpreted as the name of the color. Any of the standard html/X11 1100 `color names <http://www.w3schools.com/html/html_colornames.asp>` 1101 can be used. e.g.:: 1102 1103 myStim.setColor('white') 1104 myStim.setColor('RoyalBlue')#(the case is actually ignored) 1105 1106 A hex value can be provided, also formatted as with web colors. This can be 1107 provided as a string that begins with # (not using python's usual 0x000000 format):: 1108 1109 myStim.setColor('#DDA0DD')#DDA0DD is hexadecimal for plum 1110 1111 You can also provide a triplet of values, which refer to the coordinates 1112 in one of the :ref:`colorspaces`. If no color space is specified then the color 1113 space most recently used for this stimulus is used again. 1114 1115 myStim.setColor([1.0,-1.0,-1.0], 'rgb')#a red color in rgb space 1116 myStim.setColor([0.0,45.0,1.0], 'dkl') #DKL space with elev=0, azimuth=45 1117 myStim.setColor([0,0,255], 'rgb255') #a blue stimulus using rgb255 space 1118 1119 Lastly, a single number can be provided, x, which is equivalent to providing 1120 [x,x,x]. 1121 1122 myStim.setColor(255, 'rgb255') #all guns o max 1123 1124 colorSpace : string or None 1125 1126 defining which of the :ref:`colorspaces` to use. For strings and hex 1127 values this is not needed. If None the default colorSpace for the stimulus is 1128 used (defined during initialisation). 1129 1130 operation : one of '+','-','*','/', or '' for no operation (simply replace value) 1131 1132 for colors specified as a triplet of values (or single intensity value) 1133 the new value will perform this operation on the previous color 1134 1135 thisStim.setColor([1,1,1],'rgb255','+')#increment all guns by 1 value 1136 thisStim.setColor(-1, 'rgb', '*') #multiply the color by -1 (which in this space inverts the contrast) 1137 thisStim.setColor([10,0,0], 'dkl', '+')#raise the elevation from the isoluminant plane by 10 deg 1138 """ 1139 _setColor(self,color, colorSpace=colorSpace, operation=operation, 1140 rgbAttrib='rgb', #or 'fillRGB' etc 1141 colorAttrib='color')
1142 - def setContr(self, newContr, operation=''):
1143 """Set the contrast of the stimulus 1144 """ 1145 self._set('contr', newContr, operation) 1146 #if we don't have shaders we need to rebuild the texture 1147 if not self._useShaders: 1148 self.setTex(self._texName)
1149 - def _set(self, attrib, val, op=''):
1150 """ 1151 Deprecated. Use methods specific to the parameter you want to set 1152 1153 e.g. :: 1154 1155 stim.setPos([3,2.5]) 1156 stim.setOri(45) 1157 stim.setPhase(0.5, "+") 1158 1159 NB this method does not flag the need for updates any more - that is 1160 done by specific methods as described above. 1161 """ 1162 if op==None: op='' 1163 #format the input value as float vectors 1164 if type(val) in [tuple,list]: 1165 val=numpy.asarray(val,float) 1166 1167 if op=='':#this routine can handle single value inputs (e.g. size) for multi out (e.g. h,w) 1168 exec('self.'+attrib+'*=0') #set all values in array to 0 1169 exec('self.'+attrib+'+=val') #then add the value to array 1170 else: 1171 exec('self.'+attrib+op+'=val')
1172 - def setUseShaders(self, val=True):
1173 """Set this stimulus to use shaders if possible. 1174 """ 1175 #NB TextStim overrides this function, so changes here may need changing there too 1176 if val==True and self.win._haveShaders==False: 1177 log.error("Shaders were requested for PatchStim but aren't available. Shaders need OpenGL 2.0+ drivers") 1178 if val!=self._useShaders: 1179 self._useShaders=val 1180 self.setTex(self._texName) 1181 self.setMask(self._maskName) 1182 self.needUpdate=True
1183
1184 - def _updateList(self):
1185 """ 1186 The user shouldn't need this method since it gets called 1187 after every call to .set() 1188 Chooses between using and not using shaders each call. 1189 """ 1190 if self._useShaders: 1191 self._updateListShaders() 1192 else: self._updateListNoShaders()
1193 - def _calcSizeRendered(self):
1194 """Calculate the size of the stimulus in coords of the :class:`~psychopy.visual.Window` (normalised or pixels)""" 1195 if self.units in ['norm','pix']: self._sizeRendered=self.size 1196 elif self.units in ['deg', 'degs']: self._sizeRendered=psychopy.misc.deg2pix(self.size, self.win.monitor) 1197 elif self.units=='cm': self._sizeRendered=psychopy.misc.cm2pix(self.size, self.win.monitor) 1198 else: 1199 log.ERROR("Stimulus units should be 'norm', 'deg', 'cm' or 'pix', not '%s'" %self.units)
1200 - def _calcPosRendered(self):
1201 """Calculate the pos of the stimulus in coords of the :class:`~psychopy.visual.Window` (normalised or pixels)""" 1202 if self.units in ['norm','pix']: self._posRendered=self.pos 1203 elif self.units in ['deg', 'degs']: self._posRendered=psychopy.misc.deg2pix(self.pos, self.win.monitor) 1204 elif self.units=='cm': self._posRendered=psychopy.misc.cm2pix(self.pos, self.win.monitor)
1205 1206
1207 -class DotStim(_BaseVisualStim):
1208 """ 1209 This stimulus class defines a field of dots with an update rule that determines how they change 1210 on every call to the .draw() method. 1211 1212 This standard class can be used to generate a wide variety of dot motion types. For a review of 1213 possible types and their pros and cons see Scase, Braddick & Raymond (1996). All six possible 1214 motions they describe can be generated with appropriate choices of the signalDots (which 1215 determines whether signal dots are the 'same' or 'different' from frame to frame), noiseDots 1216 (which determines the locations of the noise dots on each frame) and the dotLife (which 1217 determines for how many frames the dot will continue before being regenerated). 1218 1219 'Movshon'-type noise uses a random position, rather than random direction, for the noise dots 1220 and the signal dots are distinct (noiseDots='different'). This has the disadvantage that the 1221 noise dots not only have a random direction but also a random speed (so differ in two ways 1222 from the signal dots). The default option for DotStim is that the dots follow a random walk, 1223 with the dot and noise elements being randomised each frame. This provides greater certainty 1224 that individual dots cannot be used to determine the motion direction. 1225 1226 When dots go out of bounds or reach the end of their life they are given a new random position. 1227 As a result, to prevent inhomogeneities arising in the dots distribution across the field, a 1228 limitted lifetime dot is strongly recommended. 1229 1230 If further customisation is required, then the DotStim should be subclassed and its 1231 _update_dotsXY and _newDotsXY methods overridden. 1232 """
1233 - def __init__(self, 1234 win, 1235 units ='', 1236 nDots =1, 1237 coherence =0.5, 1238 fieldPos =(0.0,0.0), 1239 fieldSize = (1.0,1.0), 1240 fieldShape = 'sqr', 1241 dotSize =2.0, 1242 dotLife = 3, 1243 dir =0.0, 1244 speed =0.5, 1245 rgb =None, 1246 color=(1.0,1.0,1.0), 1247 colorSpace='rgb', 1248 opacity =1.0, 1249 depth =0, 1250 element=None, 1251 signalDots='different', 1252 noiseDots='position'):
1253 """ 1254 :Parameters: 1255 1256 win : 1257 a :class:`~psychopy.visual.Window` object (required) 1258 units : **None**, 'norm', 'cm', 'deg' or 'pix' 1259 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 1260 See :ref:`units` for explanation of other options. 1261 nDots : int 1262 number of dots to be generated 1263 fieldPos : (x,y) or [x,y] 1264 specifying the location of the centre of the stimulus. 1265 fieldSize : a single value, specifying the diameter of the field 1266 Sizes can be negative and can extend beyond the window. 1267 fieldShape : *'sqr'* or 'circle' 1268 Defines the envelope used to present the dots 1269 dotSize 1270 specified in pixels (overridden if `element` is specified) 1271 dotLife : int 1272 Number of frames each dot lives for (default=3, -1=infinite) 1273 dir : float (degrees) 1274 direction of the coherent dots 1275 speed : float 1276 speed of the dots (in *units*/frame) 1277 signalDots : 'same' or 'different' 1278 If 'same' then the chosen signal dots remain the same on each frame. 1279 If 'different' they are randomly chosen each frame. This paramater 1280 corresponds to Scase et al's (1996) categories of RDK. 1281 noiseDots : 'position','direction' or 'walk' 1282 Determines the behaviour of the noise dots, taken directly from 1283 Scase et al's (1996) categories. For 'position', noise dots take a 1284 random position every frame. For 'direction' noise dots follow a 1285 random, but constant direction. For 'walk' noise dots vary their 1286 direction every frame, but keep a constant speed. 1287 rgb : (r,g,b) or [r,g,b] or a single intensity value 1288 or a single value (which will be applied to all guns). 1289 RGB vals are applied to simple textures and to greyscale 1290 image files but not to RGB images. 1291 **NB** units range -1:1 (so 0.0 is GREY). See :ref:`rgb` for further info. 1292 opacity : float 1293 1.0 is opaque, 0.0 is transparent 1294 depth : 0, 1295 This can be used to choose which 1296 stimulus overlays which. (more negative values are nearer). 1297 At present the window does not do perspective rendering 1298 but could do if that's really useful(?!) 1299 element : *None* or a visual stimulus object 1300 This can be any object that has a ``.draw()`` method and a 1301 ``.setPos([x,y])`` method (e.g. a PatchStim, TextStim...)!! 1302 See `ElementArrayStim` for a faster implementation of this idea. 1303 """ 1304 self.win = win 1305 1306 self.nDots = nDots 1307 #size 1308 if type(fieldPos) in [tuple,list]: 1309 self.fieldPos = numpy.array(fieldPos,float) 1310 else: self.fieldPos=fieldPos 1311 if type(fieldSize) in [tuple,list]: 1312 self.fieldSize = numpy.array(fieldSize) 1313 else:self.fieldSize=fieldSize 1314 if type(dotSize) in [tuple,list]: 1315 self.dotSize = numpy.array(dotSize) 1316 else:self.dotSize=dotSize 1317 self.fieldShape = fieldShape 1318 self.dir = dir 1319 self.speed = speed 1320 self.opacity = opacity 1321 self.element = element 1322 self.dotLife = dotLife 1323 self.signalDots = signalDots 1324 self.noiseDots = noiseDots 1325 1326 #unit conversions 1327 if len(units): self.units = units 1328 else: self.units = win.units 1329 if self.units=='norm': self._winScale='norm' 1330 else: self._winScale='pix' #set the window to have pixels coords 1331 #'rendered' coordinates represent the stimuli in the scaled coords of the window 1332 #(i.e. norm for units==norm, but pix for all other units) 1333 self._dotSizeRendered=None 1334 self._speedRendered=None 1335 self._fieldSizeRendered=None 1336 self._fieldPosRendered=None 1337 1338 self._useShaders=False#not needed for dots? 1339 self.colorSpace=colorSpace 1340 if rgb!=None: 1341 log.warning("Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead") 1342 self.setColor(rgb, colorSpace='rgb') 1343 else: 1344 self.setColor(color) 1345 1346 self.depth=depth 1347 """initialise the dots themselves - give them all random dir and then 1348 fix the first n in the array to have the direction specified""" 1349 1350 self.coherence=round(coherence*self.nDots)/self.nDots#store actual coherence 1351 1352 self._dotsXY = self._newDotsXY(self.nDots) #initialise a random array of X,Y 1353 self._dotsSpeed = numpy.ones(self.nDots, 'f')*self.speed#all dots have the same speed 1354 self._dotsLife = abs(dotLife)*numpy.random.rand(self.nDots)#abs() means we can ignore the -1 case (no life) 1355 #determine which dots are signal 1356 self._signalDots = numpy.zeros(self.nDots, dtype=bool) 1357 self._signalDots[0:int(self.coherence*self.nDots)]=True 1358 #numpy.random.shuffle(self._signalDots)#not really necessary 1359 #set directions (only used when self.noiseDots='direction') 1360 self._dotsDir = numpy.random.rand(self.nDots)*2*pi 1361 self._dotsDir[self._signalDots] = self.dir*pi/180 1362 1363 self._calcFieldCoordsRendered() 1364 self._update_dotsXY()
1365
1366 - def _set(self, attrib, val, op=''):
1367 """Use this to set attributes of your stimulus after initialising it. 1368 1369 :Parameters: 1370 1371 attrib : a string naming any of the attributes of the stimulus (set during init) 1372 val : the value to be used in the operation on the attrib 1373 op : a string representing the operation to be performed (optional) most maths operators apply ('+','-','*'...) 1374 1375 examples:: 1376 1377 myStim.set('rgb',0) #will simply set all guns to zero (black) 1378 myStim.set('rgb',0.5,'+') #will increment all 3 guns by 0.5 1379 myStim.set('rgb',(1.0,0.5,0.5),'*') # will keep the red gun the same and halve the others 1380 1381 """ 1382 #format the input value as float vectors 1383 if type(val) in [tuple,list]: 1384 val=numpy.array(val,float) 1385 1386 #change the attribute as requested 1387 if op=='': 1388 #note: this routine can handle single value inputs (e.g. size) for multi out (e.g. h,w) 1389 exec('self.'+attrib+'*=0') #set all values in array to 0 1390 exec('self.'+attrib+'+=val') #then add the value to array 1391 else: 1392 exec('self.'+attrib+op+'=val') 1393 1394 #update the actual coherence for the requested coherence and nDots 1395 if attrib in ['nDots','coherence']: 1396 self.coherence=round(self.coherence*self.nDots)/self.nDots
1397 1398
1399 - def set(self, attrib, val, op=''):
1400 """DotStim.set() is obselete and may not be supported in future 1401 versions of PsychoPy. Use the specific method for each parameter instead 1402 (e.g. setFieldPos(), setCoherence()...) 1403 """ 1404 self._set(attrib, val, op)
1405 - def setPos(self, newPos=None, operation='', units=None):
1406 """Obselete - users should use setFieldPos or instead of setPos 1407 """ 1408 log.error("User called DotStim.setPos(pos). Use DotStim.SetFieldPos(pos) instead.")
1409 - def setFieldPos(self,val, op=''):
1410 self._set('fieldPos', val, op) 1411 self._calcFieldCoordsRendered()
1412 - def setFieldCoherence(self,val, op=''):
1413 """Change the coherence (%) of the DotStim. This will be rounded according 1414 to the number of dots in the stimulus. 1415 """ 1416 self._set('coherence', val, op) 1417 self.coherence=round(self.coherence*self.nDots)/self.nDots#store actual coherence rounded by nDots 1418 self._signalDots = numpy.zeros(self.nDots, dtype=bool) 1419 self._signalDots[0:int(self.coherence*self.nDots)]=True 1420 #for 'direction' method we need to update the direction of the number 1421 #of signal dots immediately, but for other methods it will be done during updateXY 1422 if self.noiseDots == 'direction': 1423 self._dotsDir=numpy.random.rand(self.nDots)*2*pi 1424 self._dotsDir[self._signalDots]=self.dir*pi/180
1425 - def setDir(self,val, op=''):
1426 """Change the direction of the signal dots (units in degrees) 1427 """ 1428 #check which dots are signal 1429 signalDots = self._dotsDir==(self.dir*pi/180) 1430 self._set('dir', val, op) 1431 #dots currently moving in the signal direction also need to update their direction 1432 self._dotsDir[signalDots] = self.dir*pi/180
1433 - def setSpeed(self,val, op=''):
1434 """Change the speed of the dots (in stimulus `units` per second) 1435 """ 1436 self._set('speed', val, op)
1437 - def draw(self, win=None):
1438 """Draw the stimulus in its relevant window. You must call 1439 this method after every MyWin.flip() if you want the 1440 stimulus to appear on that frame and then update the screen 1441 again. 1442 """ 1443 if win==None: win=self.win 1444 if win.winType=='pyglet': win.winHandle.switch_to() 1445 1446 self._update_dotsXY() 1447 1448 GL.glPushMatrix()#push before drawing, pop after 1449 if self.depth==0: 1450 thisDepth = self.win._defDepth 1451 win._defDepth += _depthIncrements[win.winType] 1452 else: 1453 thisDepth=self.depth 1454 1455 #draw the dots 1456 if self.element==None: 1457 win.setScale(self._winScale) 1458 #scale the drawing frame etc... 1459 GL.glTranslatef(self._fieldPosRendered[0],self._fieldPosRendered[1],thisDepth) 1460 GL.glPointSize(self.dotSize) 1461 1462 #load Null textures into multitexteureARB - they modulate with glColor 1463 GL.glActiveTexture(GL.GL_TEXTURE0) 1464 GL.glEnable(GL.GL_TEXTURE_2D) 1465 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 1466 GL.glActiveTexture(GL.GL_TEXTURE1) 1467 GL.glEnable(GL.GL_TEXTURE_2D) 1468 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 1469 1470 if self.win.winType == 'pyglet': 1471 GL.glVertexPointer(2, GL.GL_DOUBLE, 0, self._dotsXYRendered.ctypes.data_as(ctypes.POINTER(ctypes.c_double))) 1472 else: 1473 GL.glVertexPointerd(self._dotsXYRendered) 1474 if self.colorSpace in ['rgb','dkl','lms']: 1475 GL.glColor4f(self.rgb[0]/2.0+0.5, self.rgb[1]/2.0+0.5, self.rgb[2]/2.0+0.5, 1.0) 1476 else: 1477 GL.glColor4f(self.rgb[0]/255.0, self.rgb[1]/255.0, self.rgb[2]/255.0, 1.0) 1478 GL.glEnableClientState(GL.GL_VERTEX_ARRAY) 1479 GL.glDrawArrays(GL.GL_POINTS, 0, self.nDots) 1480 GL.glDisableClientState(GL.GL_VERTEX_ARRAY) 1481 else: 1482 #we don't want to do the screen scaling twice so for each dot subtract the screen centre 1483 initialDepth=self.element.depth 1484 for pointN in range(0,self.nDots): 1485 self.element.setPos(self._dotsXY[pointN,:]+self.fieldPos) 1486 self.element.draw() 1487 self.element.setDepth(initialDepth)#reset depth before going to next frame 1488 GL.glPopMatrix()
1489
1490 - def _newDotsXY(self, nDots):
1491 """Returns a uniform spread of dots, according to the fieldShape and fieldSize 1492 1493 usage:: 1494 1495 dots = self._newDots(nDots) 1496 1497 """ 1498 if self.fieldShape=='circle':#make more dots than we need and only use those that are within circle 1499 while True:#repeat until we have enough 1500 new=numpy.random.uniform(-1, 1, [nDots*2,2])#fetch twice as many as needed 1501 inCircle= (numpy.hypot(new[:,0],new[:,1])<1) 1502 if sum(inCircle)>=nDots: 1503 return new[inCircle,:][:nDots,:]*self.fieldSize/2.0 1504 else: 1505 return numpy.random.uniform(-self.fieldSize/2.0, self.fieldSize/2.0, [nDots,2])
1506
1507 - def _update_dotsXY(self):
1508 """ 1509 The user shouldn't call this - its gets done within draw() 1510 """ 1511 1512 """Find dead dots, update positions, get new positions for dead and out-of-bounds 1513 """ 1514 #renew dead dots 1515 if self.dotLife>0:#if less than zero ignore it 1516 self._dotsLife -= 1 #decrement. Then dots to be reborn will be negative 1517 dead = (self._dotsLife<=0.0) 1518 self._dotsLife[dead]=self.dotLife 1519 else: 1520 dead=numpy.zeros(self.nDots, dtype=bool) 1521 1522 ##update XY based on speed and dir 1523 #NB self._dotsDir is in radians, but self.dir is in degs 1524 #update which are the noise/signal dots 1525 if self.signalDots =='same': 1526 #noise and signal dots change identity constantly 1527 #easiest way to keep _signalDots and _dotsDir in sync is to shuffle _dotsDir 1528 numpy.random.shuffle(self._dotsDir) 1529 self._signalDots = (self._dotsDir==(self.dir*pi/180))#and then update _signalDots from that 1530 1531 #update the locations of signal and noise 1532 if self.noiseDots=='walk': 1533 # noise dots are ~self._signalDots 1534 self._dotsDir[~self._signalDots] = numpy.random.rand((~self._signalDots).sum())*pi*2 1535 #then update all positions from dir*speed 1536 self._dotsXY[:,0] += self.speed*numpy.reshape(numpy.cos(self._dotsDir),(self.nDots,)) 1537 self._dotsXY[:,1] += self.speed*numpy.reshape(numpy.sin(self._dotsDir),(self.nDots,))# 0 radians=East! 1538 elif self.noiseDots == 'direction': 1539 #simply use the stored directions to update position 1540 self._dotsXY[:,0] += self.speed*numpy.reshape(numpy.cos(self._dotsDir),(self.nDots,)) 1541 self._dotsXY[:,1] += self.speed*numpy.reshape(numpy.sin(self._dotsDir),(self.nDots,))# 0 radians=East! 1542 elif self.noiseDots=='position': 1543 #update signal dots 1544 self._dotsXY[self._signalDots,0] += \ 1545 self.speed*numpy.reshape(numpy.cos(self._dotsDir[self._signalDots]),(self._signalDots.sum(),)) 1546 self._dotsXY[self._signalDots,1] += \ 1547 self.speed*numpy.reshape(numpy.sin(self._dotsDir[self._signalDots]),(self._signalDots.sum(),))# 0 radians=East! 1548 #update noise dots 1549 dead = dead+(~self._signalDots)#just create new ones 1550 1551 #handle boundaries of the field 1552 if self.fieldShape in [None, 'square', 'sqr']: 1553 dead = dead+(numpy.abs(self._dotsXY[:,0])>(self.fieldSize/2.0))+(numpy.abs(self._dotsXY[:,1])>(self.fieldSize/2.0)) 1554 elif self.fieldShape == 'circle': 1555 #transform to a normalised circle (radius = 1 all around) then to polar coords to check 1556 normXY = self._dotsXY/(self.fieldSize/2.0)#the normalised XY position (where radius should be <1) 1557 dead = dead + (numpy.hypot(normXY[:,0],normXY[:,1])>1) #add out-of-bounds to those that need replacing 1558 1559 #update any dead dots 1560 if sum(dead): 1561 self._dotsXY[dead,:] = self._newDotsXY(sum(dead)) 1562 1563 #update the pixel XY coordinates 1564 self._calcDotsXYRendered()
1565
1566 - def _calcDotsXYRendered(self):
1567 if self.units in ['norm','pix']: self._dotsXYRendered=self._dotsXY 1568 elif self.units in ['deg','degs']: self._dotsXYRendered=psychopy.misc.deg2pix(self._dotsXY, self.win.monitor) 1569 elif self.units=='cm': self._dotsXYRendered=psychopy.misc.cm2pix(self._dotsXY, self.win.monitor)
1570 - def _calcFieldCoordsRendered(self):
1571 if self.units in ['norm', 'pix']: 1572 self._fieldSizeRendered=self.fieldSize 1573 self._fieldPosRendered=self.fieldPos 1574 elif self.units in ['deg', 'degs']: 1575 self._fieldSizeRendered=psychopy.misc.deg2pix(self.fieldSize, self.win.monitor) 1576 self._fieldPosRendered=psychopy.misc.deg2pix(self.fieldPos, self.win.monitor) 1577 elif self.units=='cm': 1578 self._fieldSizeRendered=psychopy.misc.cm2pix(self.fieldSize, self.win.monitor) 1579 self._fieldPosRendered=psychopy.misc.cm2pix(self.fieldPos, self.win.monitor)
1580
1581 -class SimpleImageStim:
1582 """A simple stimulus for loading images from a file and presenting at exactly 1583 the resolution and color in the file (subject to gamma correction if set). 1584 1585 Unlike the PatchStim, this type of stimulus cannot be rescaled, rotated or 1586 masked (although flipping horizontally or vertically is possible). Drawing will 1587 also tend to be marginally slower, because the image isn't preloaded to the 1588 gfx card. The advantage, however is that the stimulus will always be in its 1589 original aspect ratio, with no interplotation or other transformation. It is always 1590 1591 SimpleImageStim does not support a depth parameter (the OpenGL method 1592 that draws the pixels does not support it). Simple images will obscure any other 1593 stimulus type. 1594 1595 Also, unlike the PatchStim (whose textures should be square and power-of-two 1596 in size, there is no restriction on the size of images for the SimpleImageStim 1597 1598 1599 """
1600 - def __init__(self, 1601 win, 1602 image ="", 1603 units ="", 1604 pos =(0.0,0.0), 1605 contrast=1.0, 1606 opacity=1.0, 1607 flipHoriz=False, 1608 flipVert=False):
1609 """ 1610 :Parameters: 1611 1612 1613 win : 1614 a :class:`~psychopy.visual.Window` object (required) 1615 image : 1616 The filename, including relative or absolute path. The image 1617 can be any format that the Python Imagin Library can import 1618 (which is almost all). 1619 units : **None**, 'norm', 'cm', 'deg' or 'pix' 1620 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 1621 See :ref:`units` for explanation of other options. 1622 pos : 1623 a tuple (0.0,0.0) or a list [0.0,0.0] for the x and y of the centre of the stimulus. 1624 The origin is the screen centre, the units are determined 1625 by units (see above). Stimuli can be position beyond the 1626 window! 1627 contrast : 1628 How far the stimulus deviates from the middle grey. 1629 Contrast can vary -1:1 (this is a multiplier for the 1630 values given in the color description of the stimulus) 1631 opacity : 1632 1.0 is opaque, 0.0 is transparent 1633 1634 """ 1635 1636 self.win = win 1637 if win._haveShaders: self._useShaders=True#by default, this is a good thing 1638 else: self._useShaders=False 1639 1640 if units in [None, "", []]: 1641 self.units = win.units 1642 else:self.units = units 1643 1644 self.contrast = float(contrast) 1645 self.opacity = opacity 1646 self.pos = numpy.array(pos, float) 1647 self.setImage(image) 1648 1649 #flip if necess 1650 self.flipHoriz=False#initially it is false, then so the flip according to arg above 1651 self.setFlipHoriz(flipHoriz) 1652 self.flipVert=False#initially it is false, then so the flip according to arg above 1653 self.setFlipVert(flipVert) 1654 #fix scaling to window coords 1655 if self.units=='norm': self._winScale='norm' 1656 else: self._winScale='pix' #set the window to have pixels coords 1657 self._calcPosRendered()
1658 - def setFlipHoriz(self,newVal=True):
1659 """If set to True then the image will be flipped horiztonally (left-to-right). 1660 Note that this is relative to the original image, not relative to the current state. 1661 """ 1662 if newVal!=self.flipHoriz: #we need to make the flip 1663 self.imArray = numpy.flipud(self.imArray)#numpy and pyglet disagree about ori so ud<=>lr 1664 self.flipHoriz=newVal 1665 self._needStrUpdate=True
1666 - def setFlipVert(self,newVal=True):
1667 """If set to True then the image will be flipped vertically (top-to-bottom). 1668 Note that this is relative to the original image, not relative to the current state. 1669 """ 1670 if newVal!=self.flipVert: #we need to make the flip 1671 self.imArray = numpy.fliplr(self.imArray)#numpy and pyglet disagree about ori so ud<=>lr 1672 self.flipVert=newVal 1673 self._needStrUpdate=True
1674 - def setUseShaders(self, val=True):
1675 """Set this stimulus to use shaders if possible. 1676 """ 1677 #NB TextStim overrides this function, so changes here may need changing there too 1678 if val==True and self.win._haveShaders==False: 1679 log.error("Shaders were requested for PatchStim but aren't available. Shaders need OpenGL 2.0+ drivers") 1680 if val!=self._useShaders: 1681 self._useShaders=val 1682 self.setImage()
1683 - def _updateImageStr(self):
1684 self._imStr=self.imArray.tostring() 1685 self._needStrUpdate=False
1686 - def draw(self, win=None):
1687 """ 1688 Draw the stimulus in its relevant window. You must call 1689 this method after every MyWin.flip() if you want the 1690 stimulus to appear on that frame and then update the screen 1691 again. 1692 """ 1693 #set the window to draw to 1694 if win==None: win=self.win 1695 if win.winType=='pyglet': win.winHandle.switch_to() 1696 #push the projection matrix and set to orthorgaphic 1697 GL.glMatrixMode(GL.GL_PROJECTION) 1698 GL.glPushMatrix() 1699 GL.glLoadIdentity() 1700 GL.glOrtho( 0, self.win.size[0],0, self.win.size[1], 0, 1 ) #this also sets the 0,0 to be top-left 1701 #but return to modelview for rendering 1702 GL.glMatrixMode(GL.GL_MODELVIEW) 1703 GL.glLoadIdentity() 1704 1705 if self._needStrUpdate: self._updateImageStr() 1706 #unbind any textures 1707 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE0_ARB) 1708 GL.glEnable(GL.GL_TEXTURE_2D) 1709 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 1710 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE1_ARB) 1711 GL.glEnable(GL.GL_TEXTURE_2D) 1712 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 1713 1714 #move to centre of stimulus and rotate 1715 GL.glRasterPos2f(self.win.size[0]/2.0 - self.size[0]/2.0 + self._posRendered[0], 1716 self.win.size[1]/2.0 - self.size[1]/2.0 + self._posRendered[1]) 1717 1718 #GL.glDrawPixelsub(GL.GL_RGB, self.imArr) 1719 GL.glDrawPixels(self.size[0],self.size[1], 1720 self.internalFormat,self.dataType, 1721 self._imStr) 1722 #return to 3D mode (go and pop the projection matrix) 1723 GL.glMatrixMode( GL.GL_PROJECTION ) 1724 GL.glPopMatrix() 1725 GL.glMatrixMode( GL.GL_MODELVIEW )
1726 - def setPos(self, newPos, operation='', units=None):
1727 self._set('pos', val=newPos, op=operation) 1728 self._calcPosRendered()
1729 - def setDepth(self,newDepth, operation=''):
1730 self._set('depth', newDepth, operation)
1731 - def _calcPosRendered(self):
1732 """Calculate the pos of the stimulus in coords of the :class:`~psychopy.visual.Window` (normalised or pixels)""" 1733 if self.units in ['pix', 'pixels']: self._posRendered=self.pos 1734 elif self.units=='norm': self._posRendered=self.pos 1735 elif self.units in ['deg', 'degs']: self._posRendered=psychopy.misc.deg2pix(self.pos, self.win.monitor) 1736 elif self.units=='cm': self._posRendered=psychopy.misc.cm2pix(self.pos, self.win.monitor)
1737 - def setImage(self,filename=None):
1738 """Set the image to be drawn. 1739 1740 :Parameters: 1741 - filename: 1742 The filename, including relative or absolute path if necessary. 1743 Can actually also be an image loaded by PIL. 1744 1745 """ 1746 if type(filename) in [str, unicode]: 1747 #is a string - see if it points to a file 1748 if os.path.isfile(filename): 1749 self.filename=filename 1750 im = Image.open(self.filename) 1751 im = im.transpose(Image.FLIP_TOP_BOTTOM) 1752 else: 1753 log.error("couldn't find image...%s" %(filename)) 1754 core.quit() 1755 raise #so thatensure we quit 1756 else: 1757 #not a string - have we been passed an image? 1758 try: 1759 im = filename.copy().transpose(Image.FLIP_TOP_BOTTOM) 1760 except AttributeError: # ...but apparently not 1761 log.error("couldn't find image...%s" %(filename)) 1762 core.quit() 1763 raise #ensure we quit 1764 self.filename = repr(filename) #'<Image.Image image ...>' 1765 1766 self.size = im.size 1767 #set correct formats for bytes/floats 1768 self.imArray = numpy.array(im.convert("RGB")).astype(numpy.float32)/255 1769 self.internalFormat = GL.GL_RGB 1770 if self._useShaders: 1771 self.dataType = GL.GL_FLOAT 1772 else: 1773 self.dataType = GL.GL_UNSIGNED_BYTE 1774 self.imArray = psychopy.misc.float_uint8(self.imArray*2-1) 1775 self._needStrUpdate = True
1776
1777 -class PatchStim(_BaseVisualStim):
1778 """Stimulus object for drawing arbitrary bitmaps, textures and shapes. 1779 One of the main stimuli for PsychoPy. 1780 1781 Formally PatchStim is just a texture behind an optional 1782 transparency mask (an 'alpha mask'). Both the texture and mask can be 1783 arbitrary bitmaps and their combination allows an enormous variety of 1784 stimuli to be drawn in realtime. 1785 1786 **Examples**:: 1787 1788 myGrat = PatchStim(tex='sin',mask='circle') #gives a circular patch of grating 1789 myGabor = PatchStim(tex='sin',mask='gauss') #gives a 'Gabor' patchgrating 1790 myImage = PatchStim(tex='face.jpg',mask=None) #simply draws the image face.jpg 1791 1792 An PatchStim can be rotated scaled and shifted in position, its texture can 1793 be drifted in X and/or Y and it can have a spatial frequency in X and/or Y 1794 (for an image file that simply draws multiple copies in the patch). 1795 1796 Also since transparency can be controlled two PatchStims can combine e.g. 1797 to form a plaid. 1798 1799 **Using Patchstim with images from disk (jpg, tif, pgn...)** 1800 1801 Ideally images to be rendered should be square with 'power-of-2' dimensions 1802 e.g. 16x16, 128x128. Any image that is not will be upscaled (with linear interp) 1803 to the nearest such texture by PsychoPy. The size of the stimulus should be 1804 specified in the normal way using the appropriate units (deg, pix, cm...). Be 1805 sure to get the aspect ratio the same as the image (if you don't want it 1806 stretched!). 1807 1808 **Why can't I have a normal image, drawn pixel-by-pixel?** PatchStims are 1809 rendered using OpenGL textures. This is more powerful than using simple screen 1810 blitting - it allows the rotation, masking, transparency to work. It is still 1811 necessary to have power-of-2 textures on most graphics cards. 1812 """
1813 - def __init__(self, 1814 win, 1815 tex ="sin", 1816 mask ="none", 1817 units ="", 1818 pos =(0.0,0.0), 1819 size =None, 1820 sf =None, 1821 ori =0.0, 1822 phase =(0.0,0.0), 1823 texRes =128, 1824 rgb =None, 1825 dkl=None, 1826 lms=None, 1827 color=(1.0,1.0,1.0), 1828 colorSpace='rgb', 1829 contrast=1.0, 1830 opacity=1.0, 1831 depth=0, 1832 rgbPedestal = (0.0,0.0,0.0), 1833 interpolate=False):
1834 """ 1835 :Parameters: 1836 1837 win : 1838 a :class:`~psychopy.visual.Window` object (required) 1839 tex : 1840 The texture forming the image 1841 1842 + **'sin'**,'sqr', 'saw', 'tri', None 1843 + or the name of an image file (most formats supported) 1844 + or a numpy array (1xN or NxN) ranging -1:1 1845 1846 mask : 1847 The alpha mask (forming the shape of the image) 1848 1849 + **None**, 'circle', 'gauss' 1850 + or the name of an image file (most formats supported) 1851 + or a numpy array (1xN or NxN) ranging -1:1 1852 1853 units : **None**, 'norm', 'cm', 'deg' or 'pix' 1854 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 1855 See :ref:`units` for explanation of other options. 1856 pos : 1857 a tuple (0.0,0.0) or a list [0.0,0.0] for the x and y of the centre of the stimulus. 1858 The origin is the screen centre, the units are determined 1859 by units (see above). Stimuli can be position beyond the 1860 window! 1861 size : 1862 a tuple (0.5,0.5) or a list [0.5,0.5] for the x and y 1863 OR a single value (which will be applied to x and y). 1864 Units are specified by 'units' (see above). 1865 Sizes can be negative and can extend beyond the window. 1866 sf: 1867 a tuple (1.0,1.0) or a list [1.0,1.0] for the x and y 1868 OR a single value (which will be applied to x and y). 1869 Where `units` == 'deg' or 'cm' units are in cycles per deg/cm. 1870 If `units` == 'norm' then sf units are in cycles per stimulus (so scale with stimulus size). 1871 If texture is an image loaded from a file then sf defaults to 1/stim size to give one cycle of the image. 1872 ori: 1873 orientation of stimulus in degrees 1874 phase: 1875 a tuple (0.0,0.0) or a list [0.0,0.0] for the x and y 1876 OR a single value (which will be applied to x and y). 1877 Phase of the stimulus in each direction. 1878 **NB** phase has modulus 1 (rather than 360 or 2*pi) 1879 This is a little unconventional but has the nice effect 1880 that setting phase=t*n drifts a stimulus at n Hz 1881 texRes: 1882 resolution of the texture (if not loading from an image file) 1883 color: 1884 Could be a the web name for a color (e.g. 'FireBrick'); 1885 a hex value (e.g. '#FF0047'); 1886 a tuple (1.0,1.0,1.0); a list [1.0,1.0, 1.0]; or numpy array. 1887 If the last three are used then the color space should also be given 1888 See :ref:`colorspaces` 1889 colorSpace: 1890 the color space controlling the interpretation of the `color` 1891 See :ref:`colorspaces` 1892 contrast: 1893 How far the stimulus deviates from the middle grey. 1894 Contrast can vary -1:1 (this is a multiplier for the 1895 values given in the color description of the stimulus). 1896 opacity: 1897 1.0 is opaque, 0.0 is transparent 1898 depth: 1899 This can potentially be used (not tested!) to choose which 1900 stimulus overlays which. (more negative values are nearer). 1901 At present the window does not do perspective rendering 1902 but could do if that's really useful(?!) 1903 1904 """ 1905 1906 self.win = win 1907 if win._haveShaders: self._useShaders=True#by default, this is a good thing 1908 else: self._useShaders=False 1909 1910 if units in [None, "", []]: 1911 self.units = win.units 1912 else:self.units = units 1913 1914 self.ori = float(ori) 1915 self.texRes = texRes #must be power of 2 1916 self.contrast = float(contrast) 1917 self.opacity = opacity 1918 self.interpolate=interpolate 1919 self.origSize=None#if an image texture is loaded this will be updated 1920 1921 self.colorSpace=colorSpace 1922 if rgb!=None: 1923 log.warning("Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead") 1924 self.setColor(rgb, colorSpace='rgb') 1925 elif dkl!=None: 1926 log.warning("Use of dkl arguments to stimuli are deprecated. Please use color and colorSpace args instead") 1927 self.setColor(dkl, colorSpace='dkl') 1928 elif lms!=None: 1929 log.warning("Use of lms arguments to stimuli are deprecated. Please use color and colorSpace args instead") 1930 self.setColor(lms, colorSpace='lms') 1931 else: 1932 self.setColor(color, colorSpace=colorSpace) 1933 1934 #NB Pedestal isn't currently being used during rendering - this is a place-holder 1935 if type(rgbPedestal)==float or type(rgbPedestal)==int: #user may give a luminance val 1936 self.rgbPedestal=numpy.array((rgbPedestal,rgbPedestal,rgbPedestal), float) 1937 else: 1938 self.rgbPedestal = numpy.asarray(rgbPedestal, float) 1939 1940 #phase (ranging 0:1) 1941 if type(phase) in [tuple,list]: 1942 self.phase = numpy.array(phase, float) 1943 else: 1944 self.phase = numpy.array((phase,0),float) 1945 1946 #initialise textures for stimulus 1947 if self.win.winType=="pyglet": 1948 self.texID=GL.GLuint() 1949 GL.glGenTextures(1, ctypes.byref(self.texID)) 1950 self.maskID=GL.GLuint() 1951 GL.glGenTextures(1, ctypes.byref(self.maskID)) 1952 elif cTypesOpenGL: 1953 (tID, mID) = GL.glGenTextures(2) 1954 self.texID = GL.GLuint(int(tID))#need to convert to GLUint (via ints!!) 1955 self.maskID = GL.GLuint(int(mID)) 1956 else: 1957 (self.texID, self.maskID) = GL.glGenTextures(2) 1958 self.setTex(tex) 1959 self.setMask(mask) 1960 1961 #size 1962 if size==None and self.origSize is None: 1963 self.size=numpy.array([0.5,0.5])#this was PsychoPy's original default 1964 elif size==None and self.origSize is not None: 1965 if self.units=='pix': self.size=numpy.array(self.origSize) 1966 elif self.units=='deg': self.size= psychopy.misc.pix2deg(numpy.array(self.origSize, float), self.win.monitor) 1967 elif self.units=='cm': self.size= psychopy.misc.pix2cm(numpy.array(self.origSize, float), self.win.monitor) 1968 elif self.units=='norm': self.size= 2*numpy.array(self.origSize, float)/self.win.size 1969 elif type(size) in [tuple,list]: 1970 self.size = numpy.array(size,float) 1971 else: 1972 self.size = numpy.array((size,size),float)#make a square if only given one dimension 1973 1974 #sf 1975 if sf is None: 1976 if units=='norm': 1977 self.sf=numpy.array([1.0,1.0]) 1978 elif self.origSize is not None or units in ['pix', 'pixels']: 1979 self.sf=1.0/self.size#default to one cycle 1980 else: self.sf=numpy.array([1.0,1.0]) 1981 elif type(sf) in [float, int] or len(sf)==1: 1982 self.sf = numpy.array((sf,sf),float) 1983 else: 1984 self.sf = numpy.array(sf,float) 1985 1986 self.pos = numpy.array(pos, float) 1987 1988 self.depth=depth 1989 1990 #fix scaling to window coords 1991 if self.units=='norm': self._winScale='norm' 1992 else: self._winScale='pix' #set the window to have pixels coords 1993 self._calcCyclesPerStim() 1994 self._calcPosRendered() 1995 self._calcSizeRendered() 1996 1997 #generate a displaylist ID 1998 self._listID = GL.glGenLists(1) 1999 self._updateList()#ie refresh display list
2000
2001 - def setSF(self,value,operation=''):
2002 self._set('sf', value, operation) 2003 self.needUpdate = 1 2004 self._calcCyclesPerStim()
2005 - def setPhase(self,value, operation=''):
2006 self._set('phase', value, operation) 2007 self.needUpdate = 1
2008 - def setContrast(self,value,operation=''):
2009 self._set('contrast', value, operation) 2010 #if we don't have shaders we need to rebuild the texture 2011 if not self._useShaders: 2012 self.setTex(self._texName)
2013 - def setTex(self,value):
2014 self._texName = value 2015 createTexture(value, id=self.texID, pixFormat=GL.GL_RGB, stim=self, res=self.texRes)
2016 - def setMask(self,value):
2017 self._maskName = value 2018 createTexture(value, id=self.maskID, pixFormat=GL.GL_ALPHA, stim=self, res=self.texRes)
2019 - def draw(self, win=None):
2020 """ 2021 Draw the stimulus in its relevant window. You must call 2022 this method after every MyWin.flip() if you want the 2023 stimulus to appear on that frame and then update the screen 2024 again. 2025 """ 2026 #set the window to draw to 2027 if win==None: win=self.win 2028 if win.winType=='pyglet': win.winHandle.switch_to() 2029 2030 #work out next default depth 2031 if self.depth==0: 2032 thisDepth = self.win._defDepth 2033 win._defDepth += _depthIncrements[win.winType] 2034 else: 2035 thisDepth=self.depth 2036 2037 #do scaling 2038 GL.glPushMatrix()#push before the list, pop after 2039 win.setScale(self._winScale) 2040 #move to centre of stimulus and rotate 2041 GL.glTranslatef(self._posRendered[0],self._posRendered[1],thisDepth) 2042 GL.glRotatef(-self.ori,0.0,0.0,1.0) 2043 #the list just does the texture mapping 2044 2045 if self.colorSpace in ['rgb','dkl','lms']: #these spaces are 0-centred 2046 desiredRGB = (self.rgb*self.contrast+1)/2.0#RGB in range 0:1 and scaled for contrast 2047 if numpy.any(desiredRGB**2.0>1.0): 2048 desiredRGB=[0.6,0.6,0.4] 2049 else: 2050 desiredRGB = (self.rgb*self.contrast)/255.0 2051 GL.glColor4f(desiredRGB[0],desiredRGB[1],desiredRGB[2], self.opacity) 2052 2053 if self.needUpdate: self._updateList() 2054 GL.glCallList(self._listID) 2055 2056 #return the view to previous state 2057 GL.glPopMatrix()
2058
2059 - def _updateListShaders(self):
2060 """ 2061 The user shouldn't need this method since it gets called 2062 after every call to .set() Basically it updates the OpenGL 2063 representation of your stimulus if some parameter of the 2064 stimulus changes. Call it if you change a property manually 2065 rather than using the .set() command 2066 """ 2067 #print 'updating Shaders list' 2068 self.needUpdate=0 2069 GL.glNewList(self._listID,GL.GL_COMPILE) 2070 #setup the shaderprogram 2071 GL.glUseProgram(self.win._progSignedTexMask) 2072 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask, "texture"), 0) #set the texture to be texture unit 0 2073 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask, "mask"), 1) # mask is texture unit 1 2074 #mask 2075 GL.glActiveTexture(GL.GL_TEXTURE1) 2076 GL.glBindTexture(GL.GL_TEXTURE_2D, self.maskID) 2077 GL.glEnable(GL.GL_TEXTURE_2D)#implicitly disables 1D 2078 2079 #main texture 2080 GL.glActiveTexture(GL.GL_TEXTURE0) 2081 GL.glBindTexture(GL.GL_TEXTURE_2D, self.texID) 2082 GL.glEnable(GL.GL_TEXTURE_2D) 2083 #calculate coords in advance: 2084 L = -self._sizeRendered[0]/2#vertices 2085 R = self._sizeRendered[0]/2 2086 T = self._sizeRendered[1]/2 2087 B = -self._sizeRendered[1]/2 2088 #depth = self.depth 2089 Ltex = -self._cycles[0]/2 - self.phase[0]+0.5 2090 Rtex = +self._cycles[0]/2 - self.phase[0]+0.5 2091 Ttex = +self._cycles[1]/2 - self.phase[1]+0.5 2092 Btex = -self._cycles[1]/2 - self.phase[1]+0.5 2093 Lmask=Bmask= 0.0; Tmask=Rmask=1.0#mask 2094 2095 GL.glBegin(GL.GL_QUADS) # draw a 4 sided polygon 2096 # right bottom 2097 GL.glMultiTexCoord2f(GL.GL_TEXTURE0,Rtex, Btex) 2098 GL.glMultiTexCoord2f(GL.GL_TEXTURE1,Rmask,Bmask) 2099 GL.glVertex2f(R,B) 2100 # left bottom 2101 GL.glMultiTexCoord2f(GL.GL_TEXTURE0,Ltex,Btex) 2102 GL.glMultiTexCoord2f(GL.GL_TEXTURE1,Lmask,Bmask) 2103 GL.glVertex2f(L,B) 2104 # left top 2105 GL.glMultiTexCoord2f(GL.GL_TEXTURE0,Ltex,Ttex) 2106 GL.glMultiTexCoord2f(GL.GL_TEXTURE1,Lmask,Tmask) 2107 GL.glVertex2f(L,T) 2108 # right top 2109 GL.glMultiTexCoord2f(GL.GL_TEXTURE0,Rtex,Ttex) 2110 GL.glMultiTexCoord2f(GL.GL_TEXTURE1,Rmask,Tmask) 2111 GL.glVertex2f(R,T) 2112 GL.glEnd() 2113 2114 #unbind the textures 2115 GL.glActiveTexture(GL.GL_TEXTURE1) 2116 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 2117 GL.glDisable(GL.GL_TEXTURE_2D)#implicitly disables 1D 2118 #main texture 2119 GL.glActiveTexture(GL.GL_TEXTURE0) 2120 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 2121 GL.glDisable(GL.GL_TEXTURE_2D) 2122 2123 GL.glUseProgram(0) 2124 2125 GL.glEndList()
2126 2127 #for the sake of older graphics cards------------------------------------
2128 - def _updateListNoShaders(self):
2129 """ 2130 The user shouldn't need this method since it gets called 2131 after every call to .set() Basically it updates the OpenGL 2132 representation of your stimulus if some parameter of the 2133 stimulus changes. Call it if you change a property manually 2134 rather than using the .set() command 2135 """ 2136 #print 'updating No Shaders list' 2137 self.needUpdate=0 2138 2139 GL.glNewList(self._listID,GL.GL_COMPILE) 2140 GL.glColor4f(1.0,1.0,1.0,1.0)#glColor can interfere with multitextures 2141 #mask 2142 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE1_ARB) 2143 GL.glEnable(GL.GL_TEXTURE_2D)#implicitly disables 1D 2144 GL.glBindTexture(GL.GL_TEXTURE_2D, self.maskID) 2145 2146 #main texture 2147 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE0_ARB) 2148 GL.glEnable(GL.GL_TEXTURE_2D) 2149 GL.glBindTexture(GL.GL_TEXTURE_2D, self.texID) 2150 #calculate coords in advance: 2151 L = -self._sizeRendered[0]/2#vertices 2152 R = self._sizeRendered[0]/2 2153 T = self._sizeRendered[1]/2 2154 B = -self._sizeRendered[1]/2 2155 #depth = self.depth 2156 Ltex = -self._cycles[0]/2 - self.phase[0]+0.5 2157 Rtex = +self._cycles[0]/2 - self.phase[0]+0.5 2158 Ttex = +self._cycles[1]/2 - self.phase[1]+0.5 2159 Btex = -self._cycles[1]/2 - self.phase[1]+0.5 2160 Lmask=Bmask= 0.0; Tmask=Rmask=1.0#mask 2161 2162 GL.glBegin(GL.GL_QUADS) # draw a 4 sided polygon 2163 # right bottom 2164 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE0_ARB,Rtex, Btex) 2165 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE1_ARB,Rmask,Bmask) 2166 GL.glVertex2f(R,B) 2167 # left bottom 2168 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE0_ARB,Ltex,Btex) 2169 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE1_ARB,Lmask,Bmask) 2170 GL.glVertex2f(L,B) 2171 # left top 2172 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE0_ARB,Ltex,Ttex) 2173 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE1_ARB,Lmask,Tmask) 2174 GL.glVertex2f(L,T) 2175 # right top 2176 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE0_ARB,Rtex,Ttex) 2177 GL_multitexture.glMultiTexCoord2fARB(GL_multitexture.GL_TEXTURE1_ARB,Rmask,Tmask) 2178 GL.glVertex2f(R,T) 2179 GL.glEnd() 2180 2181 GL.glDisable(GL.GL_TEXTURE_2D) 2182 GL.glEndList()
2183 2184
2185 - def __del__(self):
2186 self.clearTextures()#remove textures from graphics card to prevent crash
2187
2188 - def clearTextures(self):
2189 """ 2190 Clear the textures associated with the given stimulus. 2191 As of v1.61.00 this is called automatically during garbage collection of 2192 your stimulus, so doesn't need calling explicitly by the user. 2193 """ 2194 #only needed for pyglet 2195 if self.win.winType=='pyglet': 2196 GL.glDeleteTextures(1, self.texID) 2197 GL.glDeleteTextures(1, self.maskID)
2198
2199 - def _calcCyclesPerStim(self):
2200 if self.units=='norm': self._cycles=self.sf#this is the only form of sf that is not size dependent 2201 else: self._cycles=self.sf*self.size
2202
2203 -class RadialStim(PatchStim):
2204 """Stimulus object for drawing radial stimuli, like an annulus, a rotating wedge, 2205 a checkerboard etc... 2206 2207 Ideal for fMRI retinotopy stimuli! 2208 2209 Many of the capabilities are built on top of the PatchStim. 2210 2211 This stimulus is still relatively new and I'm finding occasional gliches. it also takes longer to draw 2212 than a typical PatchStim, so not recommended for tasks where high frame rates are needed. 2213 """
2214 - def __init__(self, 2215 win, 2216 tex ="sqrXsqr", 2217 mask ="none", 2218 units ="", 2219 pos =(0.0,0.0), 2220 size =(1.0,1.0), 2221 radialCycles=3, 2222 angularCycles=4, 2223 radialPhase=0, 2224 angularPhase=0, 2225 ori =0.0, 2226 texRes =64, 2227 angularRes=100, 2228 visibleWedge=(0, 360), 2229 rgb =None, 2230 color=(1.0,1.0,1.0), 2231 colorSpace='rgb', 2232 dkl=None, 2233 lms=None, 2234 contrast=1.0, 2235 opacity=1.0, 2236 depth=0, 2237 rgbPedestal = (0.0,0.0,0.0), 2238 interpolate=False):
2239 """ 2240 :Parameters: 2241 2242 win : 2243 a :class:`~psychopy.visual.Window` object (required) 2244 tex : 2245 The texture forming the image 2246 2247 - 'sqrXsqr', 'sinXsin', 'sin','sqr',None 2248 - or the name of an image file (most formats supported) 2249 - or a numpy array (1xN or NxN) ranging -1:1 2250 2251 mask : 2252 Unlike the mask in the PatchStim, this is a 1-D mask dictating the behaviour 2253 from the centre of the stimulus to the surround. 2254 units : **None**, 'norm', 'cm', 'deg' or 'pix' 2255 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 2256 See :ref:`units` for explanation of other options. 2257 pos : 2258 a tuple (0.0,0.0) or a list [0.0,0.0] for the x and y of the centre of the stimulus. 2259 Stimuli can be position beyond the window! 2260 size : 2261 a tuple (0.5,0.5) or a list [0.5,0.5] for the x and y 2262 OR a single value (which will be applied to x and y). 2263 Sizes can be negative and stimuli can extend beyond the window. 2264 ori : 2265 orientation of stimulus in degrees. 2266 texRes : (default= *128* ) 2267 resolution of the texture (if not loading from an image file) 2268 angularRes : (default= *100* ) 2269 100, the number of triangles used to make the sti 2270 radialPhase : 2271 the phase of the texture from the centre to the perimeter 2272 of the stimulus 2273 angularPhase : 2274 the phase of the texture around the stimulus 2275 rgb : 2276 a tuple (1.0,1.0, 1.0) or a list [1.0,1.0, 1.0] 2277 or a single value (which will be applied to all guns). 2278 RGB vals are applied to simple textures and to greyscale 2279 image files but not to RGB images. 2280 2281 **NB** units range -1:1 (so 0.0 is GREY). See :ref:`rgb` for further info. 2282 2283 dkl : a tuple (45.0,90.0, 1.0) or a list [45.0,90.0, 1.0] 2284 specifying the coordinates of the stimuli in cone-opponent 2285 space (Derrington, Krauskopf, Lennie 1984). See :ref:`dkl` for further info. 2286 Triplets represent [elevation, azimuth, magnitude]. 2287 Note that the monitor must be calibrated for this to be 2288 accurate (if not, example phosphors from a Sony Trinitron 2289 CRT will be used). 2290 lms : a tuple (0.5, 1.0, 1.0) or a list [0.5, 1.0, 1.0] 2291 specifying the coordinates of the stimuli in cone space 2292 Triplets represent relative modulation of each cone [L, M, S]. 2293 See :ref:`lms` for further info. 2294 Note that the monitor must be calibrated for this to be 2295 accurate (if not, example phosphors from a Sony Trinitron 2296 CRT will be used). 2297 contrast : (default= *1.0* ) 2298 How far the stimulus deviates from the middle grey. 2299 Contrast can vary -1:1 (this is a multiplier for the 2300 values given in the color description of the stimulus) 2301 opacity : 2302 1.0 is opaque, 0.0 is transparent 2303 depth : 2304 This can potentially be used (not tested!) to choose which 2305 stimulus overlays which. (more negative values are nearer). 2306 At present the window does not do perspective rendering 2307 but could do if that's really useful(?!) 2308 2309 """ 2310 self.win = win 2311 if win._haveShaders: self._useShaders=True#by default, this is a good thing 2312 else: self._useShaders=False 2313 if len(units): self.units = units 2314 else: self.units = win.units 2315 2316 self.ori = float(ori) 2317 self.texRes = texRes #must be power of 2 2318 self.angularRes = angularRes 2319 self.radialPhase = radialPhase 2320 self.radialCycles = radialCycles 2321 self.maskRadialPhase = 0 2322 self.visibleWedge = visibleWedge 2323 self.angularCycles = angularCycles 2324 self.angularPhase = angularPhase 2325 self.contrast = float(contrast) 2326 self.opacity = opacity 2327 self.pos = numpy.array(pos, float) 2328 self.interpolate=interpolate 2329 2330 #these are defined by the PatchStim but will just cause confusion here! 2331 self.setSF = None 2332 self.setPhase = None 2333 self.setSF = None 2334 2335 self.colorSpace=colorSpace 2336 if rgb!=None: 2337 log.warning("Use of rgb arguments to stimuli are deprecated. Please use color and colorSpace args instead") 2338 self.setColor(rgb, colorSpace='rgb') 2339 elif dkl!=None: 2340 log.warning("Use of dkl arguments to stimuli are deprecated. Please use color and colorSpace args instead") 2341 self.setColor(dkl, colorSpace='dkl') 2342 elif lms!=None: 2343 log.warning("Use of lms arguments to stimuli are deprecated. Please use color and colorSpace args instead") 2344 self.setColor(lms, colorSpace='lms') 2345 else: 2346 self.setColor(color) 2347 2348 if type(rgbPedestal)==float or type(rgbPedestal)==int: #user may give a luminance val 2349 self.rgbPedestal=numpy.array((rgbPedestal,rgbPedestal,rgbPedestal), float) 2350 else: 2351 self.rgbPedestal = numpy.asarray(rgbPedestal, float) 2352 2353 self.depth=depth 2354 2355 #size 2356 if type(size) in [tuple,list]: 2357 self.size = numpy.array(size,float) 2358 else: 2359 self.size = numpy.array((size,size),float)#make a square if only given one dimension 2360 #initialise textures for stimulus 2361 if self.win.winType=="pyglet": 2362 self.texID=GL.GLuint() 2363 GL.glGenTextures(1, ctypes.byref(self.texID)) 2364 self.maskID=GL.GLuint() 2365 GL.glGenTextures(1, ctypes.byref(self.maskID)) 2366 else: 2367 (self.texID, self.maskID) = GL.glGenTextures(2) 2368 self.setTex(tex) 2369 self.setMask(mask) 2370 2371 # 2372 self._triangleWidth = pi*2/self.angularRes 2373 self._angles = numpy.arange(0,pi*2, self._triangleWidth, dtype='float64') 2374 #which vertices are visible? 2375 self._visible = (self._angles>=(self.visibleWedge[0]*pi/180))#first edge of wedge 2376 self._visible[(self._angles+self._triangleWidth)*180/pi>(self.visibleWedge[1])] = False#second edge of wedge 2377 self._nVisible = numpy.sum(self._visible)*3 2378 2379 #do the scaling to the window coordinate system (norm or pix coords) 2380 if self.units=='norm': self._winScale='norm' 2381 else: self._winScale='pix' #set the window to have pixels coords 2382 self._calcPosRendered() 2383 self._calcSizeRendered()#must be done BEFORE _updateXY 2384 2385 self._updateTextureCoords() 2386 self._updateMaskCoords() 2387 self._updateXY() 2388 if not self._useShaders: 2389 #generate a displaylist ID 2390 self._listID = GL.glGenLists(1) 2391 self._updateList()#ie refresh display list
2392
2393 - def setSize(self, value, operation=''):
2394 self._set('size', value, operation) 2395 self._calcSizeRendered() 2396 self._updateXY() 2397 self.needUpdate=True
2398 - def setAngularCycles(self,value,operation=''):
2399 """set the number of cycles going around the stimulus""" 2400 self._set('angularCycles', value, operation) 2401 self._updateTextureCoords() 2402 self.needUpdate=True
2403 - def setRadialCycles(self,value,operation=''):
2404 """set the number of texture cycles from centre to periphery""" 2405 self._set('radialCycles', value, operation) 2406 self._updateTextureCoords() 2407 self.needUpdate=True
2408 - def setAngularPhase(self,value, operation=''):
2409 """set the angular phase of the texture""" 2410 self._set('angularPhase', value, operation) 2411 self._updateTextureCoords() 2412 self.needUpdate=True
2413 - def setRadialPhase(self,value, operation=''):
2414 """set the radial phase of the texture""" 2415 self._set('radialPhase', value, operation) 2416 self._updateTextureCoords() 2417 self.needUpdate=True
2418
2419 - def draw(self, win=None):
2420 """ 2421 Draw the stimulus in its relevant window. You must call 2422 this method after every MyWin.flip() if you want the 2423 stimulus to appear on that frame and then update the screen 2424 again. 2425 2426 If win is specified then override the normal window of this stimulus. 2427 """ 2428 #set the window to draw to 2429 if win==None: win=self.win 2430 if win.winType=='pyglet': win.winHandle.switch_to() 2431 2432 #work out next default depth 2433 if self.depth==0: 2434 thisDepth = self.win._defDepth 2435 self.win._defDepth += _depthIncrements[self.win.winType] 2436 else: 2437 thisDepth=self.depth 2438 2439 #do scaling 2440 GL.glPushMatrix()#push before the list, pop after 2441 #scale the viewport to the appropriate size 2442 self.win.setScale(self._winScale) 2443 #move to centre of stimulus and rotate 2444 GL.glTranslatef(self._posRendered[0],self._posRendered[1],thisDepth) 2445 GL.glRotatef(-self.ori,0.0,0.0,1.0) 2446 2447 if self._useShaders: 2448 #setup color 2449 desiredRGB = (self.rgb*self.contrast+1)/2.0#RGB in range 0:1 and scaled for contrast 2450 if numpy.any(desiredRGB**2.0>1.0): 2451 desiredRGB=[0.6,0.6,0.4] 2452 GL.glColor4f(desiredRGB[0],desiredRGB[1],desiredRGB[2], self.opacity) 2453 2454 #assign vertex array 2455 if self.win.winType=='pyglet': 2456 GL.glVertexPointer(2, GL.GL_DOUBLE, 0, self._visibleXY.ctypes) 2457 else: 2458 GL.glVertexPointerd(self._visibleXY)#must be reshaped in to Nx2 coordinates 2459 2460 #then bind main texture 2461 GL.glActiveTexture(GL.GL_TEXTURE0) 2462 GL.glBindTexture(GL.GL_TEXTURE_2D, self.texID) 2463 GL.glEnable(GL.GL_TEXTURE_2D) 2464 #and mask 2465 GL.glActiveTexture(GL.GL_TEXTURE1) 2466 GL.glBindTexture(GL.GL_TEXTURE_1D, self.maskID) 2467 GL.glDisable(GL.GL_TEXTURE_2D) 2468 GL.glEnable(GL.GL_TEXTURE_1D) 2469 2470 #setup the shaderprogram 2471 GL.glUseProgram(self.win._progSignedTexMask1D) 2472 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask1D, "texture"), 0) #set the texture to be texture unit 0 2473 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask1D, "mask"), 1) # mask is texture unit 1 2474 2475 #set pointers to visible textures 2476 GL.glClientActiveTexture(GL.GL_TEXTURE0) 2477 if self.win.winType=='pyglet': 2478 GL.glTexCoordPointer(2, GL.GL_DOUBLE, 0, self._visibleTexture.ctypes) 2479 else: 2480 GL.glTexCoordPointerd(self._visibleTexture) 2481 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2482 2483 #mask 2484 GL.glClientActiveTexture(GL.GL_TEXTURE1) 2485 if self.win.winType=='pyglet': 2486 GL.glTexCoordPointer(1, GL.GL_DOUBLE, 0, self._visibleMask.ctypes) 2487 else: 2488 GL.glTexCoordPointerd(self._visibleMask) 2489 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2490 2491 #do the drawing 2492 GL.glEnableClientState(GL.GL_VERTEX_ARRAY) 2493 GL.glDrawArrays(GL.GL_TRIANGLES, 0, self._nVisible) 2494 2495 #unbind the textures 2496 GL.glClientActiveTexture(GL.GL_TEXTURE1) 2497 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 2498 #main texture 2499 GL.glClientActiveTexture(GL.GL_TEXTURE0) 2500 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 2501 GL.glDisable(GL.GL_TEXTURE_2D) 2502 #disable set states 2503 GL.glDisableClientState(GL.GL_VERTEX_ARRAY) 2504 GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2505 2506 GL.glUseProgram(0) 2507 else: 2508 #the list does the texture mapping 2509 if self.needUpdate: self._updateList() 2510 GL.glCallList(self._listID) 2511 2512 #return the view to previous state 2513 GL.glPopMatrix()
2514
2515 - def _updateXY(self):
2516 """Update if the SIZE changes 2517 Update AFTER _calcSizeRendered""" 2518 #triangles = [trisX100, verticesX3, xyX2] 2519 self._XY = numpy.zeros([self.angularRes, 3, 2]) 2520 self._XY[:,1,0] = numpy.sin(self._angles)*self._sizeRendered[0]/2 #x position of 1st outer vertex 2521 self._XY[:,1,1] = numpy.cos(self._angles)*self._sizeRendered[1]/2#y position of 1st outer vertex 2522 self._XY[:,2,0] = numpy.sin(self._angles+self._triangleWidth)*self._sizeRendered[0]/2#x position of 2nd outer vertex 2523 self._XY[:,2,1] = numpy.cos(self._angles+self._triangleWidth)*self._sizeRendered[1]/2#y position of 2nd outer vertex 2524 2525 self._visibleXY = self._XY[self._visible,:,:] 2526 self._visibleXY = self._visibleXY.reshape(self._nVisible,2)
2527
2528 - def _updateTextureCoords(self):
2529 #calculate texture coordinates if angularCycles or Phase change 2530 self._textureCoords = numpy.zeros([self.angularRes, 3, 2]) 2531 self._textureCoords[:,0,0] = (self._angles+self._triangleWidth/2)*self.angularCycles/(2*pi)+self.angularPhase #x position of inner vertex 2532 self._textureCoords[:,0,1] = -self.radialPhase #y position of inner vertex 2533 self._textureCoords[:,1,0] = (self._angles)*self.angularCycles/(2*pi)+self.angularPhase #x position of 1st outer vertex 2534 self._textureCoords[:,1,1] = self.radialCycles-self.radialPhase#y position of 1st outer vertex 2535 self._textureCoords[:,2,0] = (self._angles+self._triangleWidth)*self.angularCycles/(2*pi)+self.angularPhase#x position of 2nd outer vertex 2536 self._textureCoords[:,2,1] = self.radialCycles-self.radialPhase#y position of 2nd outer vertex 2537 self._visibleTexture = self._textureCoords[self._visible,:,:].reshape(self._nVisible,2)
2538
2539 - def _updateMaskCoords(self):
2540 #calculate mask coords 2541 self._maskCoords = numpy.zeros([self.angularRes,3]) + self.maskRadialPhase 2542 self._maskCoords[:,1:] = 1 + self.maskRadialPhase#all outer points have mask value of 1 2543 self._visibleMask = self._maskCoords[self._visible,:]
2544 2545
2546 - def _updateListShaders(self):
2547 """ 2548 The user shouldn't need this method since it gets called 2549 after every call to .set() Basically it updates the OpenGL 2550 representation of your stimulus if some parameter of the 2551 stimulus changes. Call it if you change a property manually 2552 rather than using the .set() command 2553 """ 2554 self.needUpdate=0 2555 GL.glNewList(self._listID,GL.GL_COMPILE) 2556 2557 #assign vertex array 2558 if self.win.winType=='pyglet': 2559 arrPointer = self._visibleXY.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) 2560 GL.glVertexPointer(2, GL.GL_FLOAT, 0, arrPointer) 2561 else: 2562 GL.glVertexPointerd(self._visibleXY)#must be reshaped in to Nx2 coordinates 2563 2564 #setup the shaderprogram 2565 GL.glUseProgram(self.win._progSignedTexMask1D) 2566 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask1D, "texture"), 0) #set the texture to be texture unit 0 2567 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask1D, "mask"), 1) # mask is texture unit 1 2568 2569 #set pointers to visible textures 2570 GL.glClientActiveTexture(GL.GL_TEXTURE0) 2571 if self.win.winType=='pyglet': 2572 arrPointer = self._visibleTexture.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) 2573 GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, arrPointer) 2574 else: 2575 GL.glTexCoordPointerd(self._visibleTexture) 2576 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2577 #then bind main texture 2578 GL.glActiveTexture(GL.GL_TEXTURE0) 2579 GL.glBindTexture(GL.GL_TEXTURE_2D, self.texID) 2580 GL.glEnable(GL.GL_TEXTURE_2D) 2581 2582 #mask 2583 GL.glClientActiveTexture(GL.GL_TEXTURE1) 2584 if self.win.winType=='pyglet': 2585 arrPointer = self._visibleMask.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) 2586 GL.glTexCoordPointer(1, GL.GL_FLOAT, 0, arrPointer) 2587 else: 2588 GL.glTexCoordPointerd(self._visibleMask) 2589 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2590 #and mask 2591 GL.glActiveTexture(GL.GL_TEXTURE1) 2592 GL.glBindTexture(GL.GL_TEXTURE_1D, self.maskID) 2593 GL.glDisable(GL.GL_TEXTURE_2D) 2594 GL.glEnable(GL.GL_TEXTURE_1D) 2595 2596 #do the drawing 2597 GL.glEnableClientState(GL.GL_VERTEX_ARRAY) 2598 GL.glDrawArrays(GL.GL_TRIANGLES, 0, self._nVisible*3) 2599 #disable set states 2600 GL.glDisableClientState(GL.GL_VERTEX_ARRAY) 2601 GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2602 GL.glDisable(GL.GL_TEXTURE_2D) 2603 2604 GL.glUseProgram(0) 2605 #setup the shaderprogram 2606 GL.glEndList()
2607
2608 - def _updateListNoShaders(self):
2609 """ 2610 The user shouldn't need this method since it gets called 2611 after every call to .set() Basically it updates the OpenGL 2612 representation of your stimulus if some parameter of the 2613 stimulus changes. Call it if you change a property manually 2614 rather than using the .set() command 2615 """ 2616 self.needUpdate=0 2617 GL.glNewList(self._listID,GL.GL_COMPILE) 2618 GL.glColor4f(1.0,1.0,1.0,1.0)#glColor can interfere with multitextures 2619 2620 #assign vertex array 2621 if self.win.winType=='pyglet': 2622 GL.glVertexPointer(2, GL.GL_DOUBLE, 0, self._visibleXY.ctypes) 2623 else: 2624 GL.glVertexPointerd(self._visibleXY)#must be reshaped in to Nx2 coordinates 2625 GL.glEnableClientState(GL.GL_VERTEX_ARRAY) 2626 2627 #bind and enable textures 2628 #main texture 2629 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE0_ARB) 2630 GL.glBindTexture(GL.GL_TEXTURE_2D, self.texID) 2631 GL.glEnable(GL.GL_TEXTURE_2D) 2632 #mask 2633 GL_multitexture.glActiveTextureARB(GL_multitexture.GL_TEXTURE1_ARB) 2634 GL.glBindTexture(GL.GL_TEXTURE_1D, self.maskID) 2635 GL.glDisable(GL.GL_TEXTURE_2D) 2636 GL.glEnable(GL.GL_TEXTURE_1D) 2637 2638 #set pointers to visible textures 2639 GL_multitexture.glClientActiveTextureARB(GL_multitexture.GL_TEXTURE0_ARB) 2640 if self.win.winType=='pyglet': 2641 GL.glTexCoordPointer(2, GL.GL_DOUBLE, 0,self._visibleTexture.ctypes) 2642 else: 2643 GL.glTexCoordPointerd(self._visibleTexture) 2644 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2645 #mask 2646 GL_multitexture.glClientActiveTextureARB(GL_multitexture.GL_TEXTURE1_ARB) 2647 if self.win.winType=='pyglet': 2648 GL.glTexCoordPointer(2, GL.GL_DOUBLE, 0, self._visibleMask.ctypes) 2649 else: 2650 GL.glTexCoordPointerd(self._visibleMask) 2651 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2652 2653 #do the drawing 2654 GL.glDrawArrays(GL.GL_TRIANGLES, 0, self._nVisible) 2655 2656 #disable set states 2657 GL.glDisableClientState(GL.GL_VERTEX_ARRAY) 2658 GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) 2659 GL.glDisable(GL.GL_TEXTURE_2D) 2660 2661 GL.glEndList()
2662
2663 - def setTex(self,value):
2664 self._texName = value 2665 createTexture(value, id=self.texID, pixFormat=GL.GL_RGB, stim=self, res=self.texRes)
2666 - def setMask(self,value):
2667 """ 2668 """ 2669 self._maskName = value 2670 res = self.texRes#resolution of texture - 128 is bearable 2671 step = 1.0/res 2672 rad = numpy.arange(0,1+step,step) 2673 if type(self._maskName) == numpy.ndarray: 2674 #handle a numpy array 2675 intensity = 255*self._maskName.astype(float) 2676 res = len(intensity) 2677 fromFile=0 2678 elif type(self._maskName) == list: 2679 #handle a numpy array 2680 intensity = 255*numpy.array(self._maskName, float) 2681 res = len(intensity) 2682 fromFile=0 2683 elif self._maskName == "circle": 2684 intensity = 255.0*(rad<=1) 2685 fromFile=0 2686 elif self._maskName == "gauss": 2687 sigma = 1/3.0; 2688 intensity = 255.0*numpy.exp( -rad**2.0 / (2.0*sigma**2.0) )#3sd.s by the edge of the stimulus 2689 fromFile=0 2690 elif self._maskName == "radRamp":#a radial ramp 2691 intensity = 255.0-255.0*rad 2692 intensity = numpy.where(rad<1, intensity, 0)#half wave rectify 2693 fromFile=0 2694 elif self._maskName in [None,"none","None"]: 2695 res=4 2696 intensity = 255.0*numpy.ones(res,float) 2697 fromFile=0 2698 else:#might be a filename of a tiff 2699 try: 2700 im = Image.open(self._maskName) 2701 im = im.transpose(Image.FLIP_TOP_BOTTOM) 2702 im = im.resize([max(im.size), max(im.size)],Image.BILINEAR)#make it square 2703 except IOError, (details): 2704 log.error("couldn't load mask...%s: %s" %(value,details)) 2705 return 2706 res = im.size[0] 2707 im = im.convert("L")#force to intensity (in case it was rgb) 2708 intensity = numpy.asarray(im) 2709 2710 data = intensity.astype(numpy.uint8) 2711 mask = data.tostring()#serialise 2712 2713 #do the openGL binding 2714 if self.interpolate: smoothing=GL.GL_LINEAR 2715 else: smoothing=GL.GL_NEAREST 2716 GL.glBindTexture(GL.GL_TEXTURE_1D, self.maskID) 2717 GL.glTexImage1D(GL.GL_TEXTURE_1D, 0, GL.GL_ALPHA, 2718 res, 0, 2719 GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, mask) 2720 GL.glTexParameteri(GL.GL_TEXTURE_1D,GL.GL_TEXTURE_WRAP_S,GL.GL_REPEAT) #makes the texture map wrap (this is actually default anyway) 2721 GL.glTexParameteri(GL.GL_TEXTURE_1D,GL.GL_TEXTURE_MAG_FILTER,smoothing) #linear smoothing if texture is stretched 2722 GL.glTexParameteri(GL.GL_TEXTURE_1D,GL.GL_TEXTURE_MIN_FILTER,smoothing) 2723 GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE) 2724 GL.glEnable(GL.GL_TEXTURE_1D) 2725 2726 self.needUpdate=True
2727
2728 - def __del__(self):
2729 self.clearTextures()#remove textures from graphics card to prevent crash
2730
2731 - def clearTextures(self):
2732 """ 2733 Clear the textures associated with the given stimulus. 2734 As of v1.61.00 this is called automatically during garbage collection of 2735 your stimulus, so doesn't need calling explicitly by the user. 2736 """ 2737 #only needed for pyglet 2738 if self.win.winType=='pyglet': 2739 GL.glDeleteTextures(1, self.texID) 2740 GL.glDeleteTextures(1, self.maskID)
2741 2742 2743
2744 -class ElementArrayStim:
2745 """ 2746 This stimulus class defines a field of elements whose behaviour can be independently 2747 controlled. Suitable for creating 'global form' stimuli or more detailed random dot 2748 stimuli. 2749 This stimulus can draw thousands of elements without dropping a frame, but in order 2750 to achieve this performance, uses several OpenGL extensions only available on modern 2751 graphics cards (supporting OpenGL2.0). See the ElementArray demo. 2752 """
2753 - def __init__(self, 2754 win, 2755 units = None, 2756 fieldPos = (0.0,0.0), 2757 fieldSize = (1.0,1.0), 2758 fieldShape = 'circle', 2759 nElements = 100, 2760 sizes = 2.0, 2761 xys = None, 2762 rgbs = (1.0,1.0,1.0), 2763 opacities = 1.0, 2764 depths = 0, 2765 fieldDepth = 0, 2766 oris = 0, 2767 sfs=1.0, 2768 contrs = 1, 2769 phases=0, 2770 elementTex='sin', 2771 elementMask='gauss', 2772 texRes=48):
2773 2774 """ 2775 :Parameters: 2776 2777 win : 2778 a :class:`~psychopy.visual.Window` object (required) 2779 2780 units : **None**, 'norm', 'cm', 'deg' or 'pix' 2781 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 2782 See :ref:`units` for explanation of other options. 2783 2784 fieldPos : 2785 The centre of the array of elements 2786 2787 fieldSize : 2788 The size of the array of elements (this will be overridden by setting explicit xy positions for the elements) 2789 2790 fieldShape : 2791 The shape of the array ('circle' or 'sqr') 2792 2793 nElements : 2794 number of elements in the array 2795 2796 sizes : 2797 an array of sizes Nx1, Nx2 or a single value 2798 2799 xys : 2800 the xy positions of the elements, relative to the field centre (fieldPos) 2801 2802 rgbs : 2803 specifying the color(s) of the elements. 2804 Should be Nx1 (different greys), Nx3 (different colors) or 1x3 (for a single color) 2805 2806 opacities : 2807 the opacity of each element (Nx1 or a single value) 2808 2809 depths : 2810 the depths of the elements (Nx1), relative the overall depth of the field (fieldDepth) 2811 2812 fieldDepth : 2813 the depth of the field (will be added to the depths of the elements) 2814 2815 oris : 2816 the orientations of the elements (Nx1 or a single value) 2817 2818 sfs : 2819 the spatial frequencies of the elements (Nx1, Nx2 or a single value) 2820 2821 contrs : 2822 the contrasts of the elements, ranging -1 to +1 (Nx1 or a single value) 2823 2824 phases : 2825 the spatial phase of the texture on the stimulus (Nx1 or a single value) 2826 2827 elementTex : 2828 the texture, to be used by all elements (e.g. 'sin', 'sqr',.. , 'myTexture.tif', numpy.ones([48,48])) 2829 2830 elementMask : 2831 the mask, to be used by all elements (e.g. 'circle', 'gauss',.. , 'myTexture.tif', numpy.ones([48,48])) 2832 2833 texRes : 2834 the number of pixels in the textures (overridden if an array or image is provided) 2835 2836 """ 2837 self.win = win 2838 if units in [None, "", []]: 2839 self.units = win.units 2840 else: self.units = units 2841 2842 self.fieldPos = fieldPos 2843 self.fieldSize = fieldSize 2844 self.fieldShape = fieldShape 2845 self.nElements = nElements 2846 #info for each element 2847 self.sizes = sizes 2848 self.rgbs=rgbs 2849 self.xys= xys 2850 self.opacities = opacities 2851 self.oris = oris 2852 self.contrs = contrs 2853 self.phases = phases 2854 self.needVertexUpdate=True 2855 self.needColorUpdate=True 2856 self._useShaders=True 2857 self.interpolate=True 2858 self.fieldDepth=fieldDepth 2859 if depths==0: 2860 self.depths=numpy.arange(0,_depthIncrements[self.win.winType]*self.nElements,_depthIncrements[self.win.winType]).repeat(4).reshape(self.nElements, 4) 2861 else: 2862 self.depths=depths 2863 if self.win.winType != 'pyglet': 2864 raise TypeError('ElementArray requires a pyglet context') 2865 2866 #Deal with input for fieldpos 2867 if type(fieldPos) in [tuple,list]: 2868 self.fieldPos = numpy.array(fieldPos,float) 2869 else: 2870 self.fieldPos = numpy.array((fieldPos,fieldPos),float) 2871 2872 #Deal with input for fieldsize 2873 if type(fieldSize) in [tuple,list]: 2874 self.fieldSize = numpy.array(fieldSize,float) 2875 else: 2876 self.fieldSize = numpy.array((fieldSize,fieldSize),float)#make a square if only given one dimension 2877 2878 #create textures 2879 self.texRes=texRes 2880 self.texID=GL.GLuint() 2881 GL.glGenTextures(1, ctypes.byref(self.texID)) 2882 self.maskID=GL.GLuint() 2883 GL.glGenTextures(1, ctypes.byref(self.maskID)) 2884 self.setMask(elementMask) 2885 self.setTex(elementTex) 2886 2887 #set units for rendering (pix or norm) 2888 if self.units=='norm': self._winScale='norm' 2889 else: self._winScale='pix' #set the window to have pixels coords 2890 2891 self.setContrs(contrs) 2892 self.setRgbs(rgbs) 2893 self.setOpacities(opacities)#opacities is used by setRgbs, so this needs to be early 2894 self.setXYs(xys) 2895 self.setOris(oris) 2896 self.setSizes(sizes) #set sizes before sfs (sfs may need it formatted) 2897 self.setSfs(sfs) 2898 self.setPhases(phases) 2899 2900 self._calcFieldCoordsRendered() 2901 self._calcSizesRendered() 2902 self._calcXYsRendered()
2903 2904
2905 - def setXYs(self,value=None, operation=''):
2906 """Set the xy values of the element centres (relative to the centre of the field). 2907 Values should be: 2908 2909 - None 2910 - an array/list of Nx2 coordinates. 2911 2912 If value is None then the xy positions will be generated automatically, based 2913 on the fieldSize and fieldPos. In this case opacity will also be overridden 2914 by this function (it is used to make elements outside the field invisible. 2915 """ 2916 if value==None: 2917 if self.fieldShape is 'sqr': 2918 self.xys = numpy.random.rand(self.nElements,2)*self.fieldSize - self.fieldSize/2 #initialise a random array of X,Y 2919 #gone outside the square 2920 self.xys[:,0] = ((self.xys[:,0]+self.fieldSize[0]/2) % self.fieldSize[0])-self.fieldSize[0]/2 2921 self.xys[:,1] = ((self.xys[:,1]+self.fieldSize[1]/2) % self.fieldSize[1])-self.fieldSize[1]/2 2922 elif self.fieldShape is 'circle': 2923 #take twice as many elements as we need (and cull the ones outside the circle) 2924 xys = numpy.random.rand(self.nElements*2,2)*self.fieldSize - self.fieldSize/2 #initialise a random array of X,Y 2925 #gone outside the square 2926 xys[:,0] = ((xys[:,0]+self.fieldSize[0]/2) % self.fieldSize[0])-self.fieldSize[0]/2 2927 xys[:,1] = ((xys[:,1]+self.fieldSize[1]/2) % self.fieldSize[1])-self.fieldSize[1]/2 2928 #use a circular envelope and flips dot to opposite edge if they fall 2929 #beyond radius. 2930 #NB always circular - uses fieldSize in X only 2931 normxy = xys/(self.fieldSize/2.0) 2932 dotDist = numpy.sqrt((normxy[:,0]**2.0 + normxy[:,1]**2.0)) 2933 self.xys = xys[dotDist<1.0,:][0:self.nElements] 2934 else: 2935 #make into an array 2936 if type(value) in [int, float, list, tuple]: 2937 value = numpy.array(value, dtype=float) 2938 #check shape 2939 if not (value.shape in [(),(2,),(self.nElements,2)]): 2940 raise ValueError("New value for setXYs should be either None or Nx2") 2941 if operation=='': 2942 self.xys=value 2943 else: exec('self.xys'+operation+'=value') 2944 self.needVertexUpdate=True
2945
2946 - def setOris(self,value,operation=''):
2947 """Set the orientation for each element. 2948 Should either be a single value or an Nx1 array/list 2949 """ 2950 #make into an array 2951 if type(value) in [int, float, list, tuple]: 2952 value = numpy.array(value, dtype=float) 2953 2954 #check shape 2955 if value.shape in [(),(1,)]: 2956 value = value.repeat(self.nElements) 2957 elif value.shape in [(self.nElements,), (self.nElements,1)]: 2958 pass #is already Nx1 2959 else: 2960 raise ValueError("New value for setOris should be either Nx1 or a single value") 2961 if operation=='': 2962 self.oris=value 2963 else: exec('self.oris'+operation+'=value') 2964 self.needVertexUpdate=True
2965 #----------------------------------------------------------------------
2966 - def setSfs(self, value,operation=''):
2967 """Set the spatial frequency for each element. 2968 Should either be: 2969 2970 - a single value 2971 - an Nx1 array/list 2972 - an Nx2 array/list (spatial frequency of the element in X and Y). 2973 2974 If the units for the stimulus are 'pix' or 'norm' then the units of sf 2975 are cycles per stimulus width. For units of 'deg' or 'cm' the units 2976 are c/cm or c/deg respectively. 2977 2978 """ 2979 2980 #make into an array 2981 if type(value) in [int, float, list, tuple]: 2982 value = numpy.array(value, dtype=float) 2983 2984 #check shape 2985 if value.shape in [(),(1,)]: 2986 value = numpy.resize(value, [self.nElements,2]) 2987 elif value.shape in [(self.nElements,), (self.nElements,1)]: 2988 value.shape=(self.nElements,1)#set to be 2D 2989 value = value.repeat(2,1) #repeat once on dim 1 2990 elif value.shape == (self.nElements,2): 2991 pass#all is good 2992 else: 2993 raise ValueError("New value for setSfs should be either Nx1, Nx2 or a single value") 2994 2995 if operation=='': 2996 self.sfs=value 2997 else: exec('self.sfs'+operation+'=value')
2998
2999 - def setOpacities(self,value,operation=''):
3000 """Set the opacity for each element. 3001 Should either be a single value or an Nx1 array/list 3002 """ 3003 #make into an array 3004 if type(value) in [int, float, list, tuple]: 3005 value = numpy.array(value, dtype=float) 3006 3007 #check shape 3008 if value.shape in [(),(1,)]: 3009 value = value.repeat(self.nElements) 3010 elif value.shape in [(self.nElements,), (self.nElements,1)]: 3011 pass #is already Nx1 3012 else: 3013 raise ValueError("New value for setOpacities should be either Nx1 or a single value") 3014 3015 if operation=='': 3016 self.opacities=value 3017 else: exec('self.opacities'+operation+'=value') 3018 self.needColorUpdate =True
3019
3020 - def setSizes(self,value,operation=''):
3021 """Set the size for each element. 3022 Should either be: 3023 3024 - a single value 3025 - an Nx1 array/list 3026 - an Nx2 array/list 3027 """ 3028 #make into an array 3029 if type(value) in [int, float, list, tuple]: 3030 value = numpy.array(value, dtype=float) 3031 #check shape 3032 if value.shape in [(),(1,)]: 3033 value = numpy.resize(value, [self.nElements,2]) 3034 elif value.shape in [(self.nElements,), (self.nElements,1)]: 3035 value.shape=(self.nElements,1)#set to be 2D 3036 value = value.repeat(2,1) #repeat once on dim 1 3037 elif value.shape == (self.nElements,2): 3038 pass#all is good 3039 else: 3040 raise ValueError("New value for setSizes should be either Nx1, Nx2 or a single value") 3041 3042 if operation=='': 3043 self.sizes=value 3044 else: exec('self.sizes'+operation+'=value') 3045 self._calcSizesRendered() 3046 self.needVertexUpdate=True
3047
3048 - def setPhases(self,value,operation=''):
3049 """Set the phase for each element. 3050 Should either be: 3051 3052 - a single value 3053 - an Nx1 array/list 3054 - an Nx2 array/list (for separate X and Y phase) 3055 """ 3056 #make into an array 3057 if type(value) in [int, float, list, tuple]: 3058 value = numpy.array(value, dtype=float) 3059 3060 #check shape 3061 if value.shape in [(),(1,)]: 3062 value = numpy.resize(value, [self.nElements,2]) 3063 elif value.shape in [(self.nElements,), (self.nElements,1)]: 3064 value.shape=(self.nElements,1)#set to be 2D 3065 value = value.repeat(2,1) #repeat once on dim 1 3066 elif value.shape == (self.nElements,2): 3067 pass#all is good 3068 else: 3069 raise ValueError("New value for setPhases should be either Nx1, Nx2 or a single value") 3070 3071 if operation=='': 3072 self.phases=value 3073 else: exec('self.phases'+operation+'=value') 3074 self.needTexCoordUpdate=True
3075
3076 - def setRgbs(self,value,operation=''):
3077 """Set the rgb for each element. 3078 Should either be: 3079 3080 - a single value 3081 - an Nx1 array/list 3082 - an Nx3 array/list 3083 """ 3084 #make into an array 3085 if type(value) in [int, float, list, tuple]: 3086 value = numpy.array(value, dtype=float) 3087 #check shape 3088 if value.shape in [(), (1,),(3,)]: 3089 value = numpy.resize(value, [self.nElements,3]) 3090 elif value.shape in [(self.nElements,), (self.nElements,1)]: 3091 value.shape=(self.nElements,1)#set to be 2D 3092 value = value.repeat(3,1) #repeat once on dim 1 3093 elif value.shape == (self.nElements,3): 3094 pass#all is good 3095 else: 3096 raise ValueError("New value for setRgbs should be either Nx1, Nx3 or a single value") 3097 if operation=='': 3098 self.rgbs=value 3099 else: exec('self.rgbs'+operation+'=value') 3100 3101 self.needColorUpdate=True
3102 - def setContrs(self,value,operation=''):
3103 """Set the contrast for each element. 3104 Should either be: 3105 3106 - a single value 3107 - an Nx1 array/list 3108 """ 3109 #make into an array 3110 if type(value) in [int, float, list, tuple]: 3111 value = numpy.array(value, dtype=float) 3112 #check shape 3113 if value.shape in [(),(1,)]: 3114 value = value.repeat(self.nElements) 3115 elif value.shape in [(self.nElements,), (self.nElements,1)]: 3116 pass #is already Nx1 3117 else: 3118 raise ValueError("New value for setContrs should be either Nx1 or a single value") 3119 3120 if operation=='': 3121 self.contrs=value 3122 else: exec('self.contrs'+operation+'=value') 3123 self.needColorUpdate=True
3124 - def setFieldPos(self,value,operation=''):
3125 """Set the centre of the array (X,Y) 3126 """ 3127 #make into an array 3128 if type(value) in [int, float, list, tuple]: 3129 value = numpy.array(value, dtype=float) 3130 #check shape 3131 if value.shape != (2,): 3132 raise ValueError("New value for setFieldPos should be [x,y]") 3133 3134 if operation=='': 3135 self.fieldPos=value 3136 else: 3137 exec('self.fieldPos'+operation+'=value')
3138 - def setPos(self, newPos=None, operation='', units=None):
3139 """Obselete - users should use setFieldPos or instead of setPos 3140 """ 3141 log.error("User called ElementArrayStim.setPos(pos). Use ElementArrayStim.SetFieldPos(pos) instead.")
3142
3143 - def setFieldSize(self,value,operation=''):
3144 """Set the size of the array on the screen (will override 3145 current XY positions of the elements) 3146 """ 3147 #make into an array 3148 if type(value) in [int, float, list, tuple]: 3149 value = numpy.array(value, dtype=float) 3150 #check shape 3151 if value.shape not in [(2,),(1,)]: 3152 raise ValueError("New value for setFieldSize should be [x,y] or a single value") 3153 3154 if operation=='': 3155 self.fieldSize=value 3156 else: 3157 exec('self.fieldSize'+operation+'=value') 3158 self.setXYs()#to reflect new settings, overriding individual xys
3159
3160 - def draw(self):
3161 """ 3162 Draw the stimulus in its relevant window. You must call 3163 this method after every MyWin.update() if you want the 3164 stimulus to appear on that frame and then update the screen 3165 again. 3166 """ 3167 import time 3168 t0=time.clock() 3169 if self.needVertexUpdate: 3170 self.updateElementVertices() 3171 if self.needColorUpdate: 3172 self.updataElementColors() 3173 if self.needTexCoordUpdate: 3174 self.updataTextureCoords() 3175 3176 #scale the drawing frame and get to centre of field 3177 GL.glPushMatrix()#push before drawing, pop after 3178 GL.glPushClientAttrib(GL.GL_CLIENT_ALL_ATTRIB_BITS)#push the data for client attributes 3179 3180 #GL.glLoadIdentity() 3181 self.win.setScale(self._winScale) 3182 if self.fieldDepth==0: 3183 thisDepth=self.win._defDepth 3184 self.win._defDepth += _depthIncrements[self.win.winType] 3185 GL.glTranslatef(self._fieldPosRendered[0],self._fieldPosRendered[1],0.0) 3186 3187 GL.glColorPointer(4, GL.GL_DOUBLE, 0, self._RGBAs.ctypes.data_as(ctypes.POINTER(ctypes.c_double))) 3188 GL.glVertexPointer(3, GL.GL_DOUBLE, 0, self._visXYZvertices.ctypes.data_as(ctypes.POINTER(ctypes.c_double))) 3189 3190 #setup the shaderprogram 3191 GL.glUseProgram(self.win._progSignedTexMask) 3192 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask, "texture"), 0) #set the texture to be texture unit 0 3193 GL.glUniform1i(GL.glGetUniformLocation(self.win._progSignedTexMask, "mask"), 1) # mask is texture unit 1 3194 3195 #bind textures 3196 GL.glActiveTexture (GL.GL_TEXTURE1) 3197 GL.glBindTexture (GL.GL_TEXTURE_2D, self.maskID) 3198 GL.glEnable(GL.GL_TEXTURE_2D) 3199 GL.glActiveTexture (GL.GL_TEXTURE0) 3200 GL.glBindTexture (GL.GL_TEXTURE_2D, self.texID) 3201 GL.glEnable(GL.GL_TEXTURE_2D) 3202 3203 #setup client texture coordinates first 3204 GL.glClientActiveTexture (GL.GL_TEXTURE0) 3205 GL.glTexCoordPointer (2, GL.GL_DOUBLE, 0, self._texCoords.ctypes) 3206 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 3207 GL.glClientActiveTexture (GL.GL_TEXTURE1) 3208 GL.glTexCoordPointer (2, GL.GL_DOUBLE, 0, self._maskCoords.ctypes) 3209 GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) 3210 3211 GL.glEnableClientState(GL.GL_COLOR_ARRAY) 3212 GL.glEnableClientState(GL.GL_VERTEX_ARRAY) 3213 GL.glDrawArrays(GL.GL_QUADS, 0, self._visXYZvertices.shape[0]*4) 3214 3215 #unbind the textures 3216 GL.glActiveTexture(GL.GL_TEXTURE1) 3217 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 3218 GL.glDisable(GL.GL_TEXTURE_2D) 3219 #main texture 3220 GL.glActiveTexture(GL.GL_TEXTURE0) 3221 GL.glBindTexture(GL.GL_TEXTURE_2D, 0) 3222 GL.glDisable(GL.GL_TEXTURE_2D) 3223 #disable states 3224 GL.glDisableClientState(GL.GL_COLOR_ARRAY) 3225 GL.glDisableClientState(GL.GL_VERTEX_ARRAY) 3226 GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) 3227 3228 GL.glPopClientAttrib() 3229 GL.glPopMatrix()
3230
3231 - def _calcSizesRendered(self):
3232 if self.units in ['norm','pix']: self._sizesRendered=self.sizes 3233 elif self.units in ['deg', 'degs']: self._sizesRendered=psychopy.misc.deg2pix(self.sizes, self.win.monitor) 3234 elif self.units=='cm': self._sizesRendered=psychopy.misc.cm2pix(self.sizes, self.win.monitor)
3235 - def _calcXYsRendered(self):
3236 if self.units in ['norm','pix']: self._XYsRendered=self.xys 3237 elif self.units in ['deg', 'degs']: self._XYsRendered=psychopy.misc.deg2pix(self.xys, self.win.monitor) 3238 elif self.units=='cm': self._XYsRendered=psychopy.misc.cm2pix(self.xys, self.win.monitor)
3239 - def _calcFieldCoordsRendered(self):
3240 if self.units in ['norm', 'pix']: 3241 self._fieldSizeRendered=self.fieldSize 3242 self._fieldPosRendered=self.fieldPos 3243 elif self.units in ['deg', 'degs']: 3244 self._fieldSizeRendered=psychopy.misc.deg2pix(self.fieldSize, self.win.monitor) 3245 self._fieldPosRendered=psychopy.misc.deg2pix(self.fieldPos, self.win.monitor) 3246 elif self.units=='cm': 3247 self._fieldSizeRendered=psychopy.misc.cm2pix(self.fieldSize, self.win.monitor) 3248 self._fieldPosRendered=psychopy.misc.cm2pix(self.fieldPos, self.win.monitor)
3249
3250 - def updateElementVertices(self):
3251 self._calcXYsRendered() 3252 3253 self._visXYZvertices=numpy.zeros([self.nElements , 4, 3],'d') 3254 wx = self._sizesRendered[:,0]*numpy.cos(self.oris[:]*numpy.pi/180)/2 3255 wy = self._sizesRendered[:,0]*numpy.sin(self.oris[:]*numpy.pi/180)/2 3256 hx = self._sizesRendered[:,1]*numpy.sin(self.oris[:]*numpy.pi/180)/2 3257 hy = -self._sizesRendered[:,1]*numpy.cos(self.oris[:]*numpy.pi/180)/2 3258 3259 #X 3260 self._visXYZvertices[:,0,0] = self._XYsRendered[:,0] -wx + hx#TopL 3261 self._visXYZvertices[:,1,0] = self._XYsRendered[:,0] +wx + hx#TopR 3262 self._visXYZvertices[:,2,0] = self._XYsRendered[:,0] +wx - hx#BotR 3263 self._visXYZvertices[:,3,0] = self._XYsRendered[:,0] -wx - hx#BotL 3264 3265 #Y 3266 self._visXYZvertices[:,0,1] = self._XYsRendered[:,1] -wy + hy 3267 self._visXYZvertices[:,1,1] = self._XYsRendered[:,1] +wy + hy 3268 self._visXYZvertices[:,2,1] = self._XYsRendered[:,1] +wy - hy 3269 self._visXYZvertices[:,3,1] = self._XYsRendered[:,1] -wy - hy 3270 3271 #depth 3272 self._visXYZvertices[:,:,2] = self.depths 3273 3274 self.needVertexUpdate=False
3275 3276 #----------------------------------------------------------------------
3277 - def updataElementColors(self):
3278 """Create a new array of self._RGBAs""" 3279 3280 N=self.nElements 3281 self._RGBAs=numpy.zeros([N,4],'d') 3282 self._RGBAs[:,0:3] = self.rgbs[:,:] * self.contrs[:].reshape([N,1]).repeat(3,1)/2+0.5 3283 self._RGBAs[:,-1] = self.opacities.reshape([N,]) 3284 self._RGBAs=self._RGBAs.reshape([N,1,4]).repeat(4,1)#repeat for the 4 vertices in the grid 3285 self.needColorUpdate=False
3286 - def updataTextureCoords(self):
3287 """Create a new array of self._maskCoords""" 3288 3289 N=self.nElements 3290 self._maskCoords=numpy.array([[0,1],[1,1],[1,0],[0,0]],'d').reshape([1,4,2]) 3291 self._maskCoords = self._maskCoords.repeat(N,0) 3292 3293 #for the main texture 3294 if self.units in ['norm', 'pix']:#sf is dependent on size (openGL default) 3295 L = -self.sfs[:,0]/2 - self.phases[:,0]+0.5 3296 R = +self.sfs[:,0]/2 - self.phases[:,0]+0.5 3297 T = +self.sfs[:,1]/2 - self.phases[:,1]+0.5 3298 B = -self.sfs[:,1]/2 - self.phases[:,1]+0.5 3299 else: #we should scale to become independent of size 3300 L = -self.sfs[:,0]*self.sizes[:,0]/2 - self.phases[:,0]+0.5 3301 R = +self.sfs[:,0]*self.sizes[:,0]/2 - self.phases[:,0]+0.5 3302 T = +self.sfs[:,1]*self.sizes[:,1]/2 - self.phases[:,1]+0.5 3303 B = -self.sfs[:,1]*self.sizes[:,1]/2 - self.phases[:,1]+0.5 3304 3305 #self._texCoords=numpy.array([[1,1],[1,0],[0,0],[0,1]],'d').reshape([1,4,2]) 3306 self._texCoords=numpy.concatenate([[L,T],[R,T],[R,B],[L,B]]) \ 3307 .transpose().reshape([N,4,2]).astype('d') 3308 self.needTexCoordUpdate=False
3309
3310 - def setTex(self,value):
3311 """Change the texture (all elements have the same base texture). Avoid this 3312 during time-critical points in your script. Uploading new textures to the 3313 graphics card can be time-consuming. 3314 """ 3315 self._texName = value 3316 createTexture(value, id=self.texID, pixFormat=GL.GL_RGB, stim=self, res=self.texRes)
3317 - def setMask(self,value):
3318 """Change the mask (all elements have the same mask). Avoid doing this 3319 during time-critical points in your script. Uploading new textures to the 3320 graphics card can be time-consuming.""" 3321 self._maskName = value 3322 createTexture(value, id=self.maskID, pixFormat=GL.GL_ALPHA, stim=self, res=self.texRes)
3323 - def __del__(self):
3324 self.clearTextures()#remove textures from graphics card to prevent crash
3325 - def clearTextures(self):
3326 """ 3327 Clear the textures associated with the given stimulus. 3328 As of v1.61.00 this is called automatically during garbage collection of 3329 your stimulus, so doesn't need calling explicitly by the user. 3330 """ 3331 #only needed for pyglet 3332 if self.win.winType=='pyglet': 3333 GL.glDeleteTextures(1, self.texID) 3334 GL.glDeleteTextures(1, self.maskID)
3335
3336 -class MovieStim(_BaseVisualStim):
3337 """A stimulus class for playing movies (mpeg, avi, etc...) in 3338 PsychoPy. 3339 3340 **examples**:: 3341 3342 mov = visual.MovieStim(myWin, 'testMovie.mp4', fliVert=False) 3343 print mov.duration 3344 print mov.format.width, mov.format.height #give the original size of the movie in pixels 3345 3346 mov.draw() #draw the current frame (automagically determined) 3347 3348 See MovieStim.py for demo. 3349 """
3350 - def __init__(self, win, 3351 filename = "", 3352 units = 'pix', 3353 size = None, 3354 pos =(0.0,0.0), 3355 ori =0.0, 3356 flipVert = False, 3357 flipHoriz = False, 3358 opacity=1.0):
3359 """ 3360 :Parameters: 3361 3362 win : 3363 a :class:`~psychopy.visual.Window` object (required) 3364 filename : 3365 a string giving the relative or absolute path to the movie. Can be any movie that 3366 AVbin can read (e.g. mpeg, DivX) 3367 units : **None**, 'norm', 'cm', 'deg' or 'pix' 3368 If None then the current units of the :class:`~psychopy.visual.Window` will be used. 3369 See :ref:`units` for explanation of other options. 3370 pos : 3371 position of the centre of the movie, given in the units specified 3372 flipVert : True or *False* 3373 If True then the movie will be top-bottom flipped 3374 flipHoriz : True or *False* 3375 If True then the movie will be right-left flipped 3376 ori : 3377 Orientation of the stimulus in degrees 3378 size : 3379 Size of the stimulus in units given. If not specified then the movie will take its 3380 original dimensions. 3381 opacity : 3382 the movie can be made transparent by reducing this 3383 """ 3384 self.win = win 3385 self._movie=None # the actual pyglet media object 3386 self._player=pyglet.media.ManagedSoundPlayer() 3387 self._player._on_eos=self._onEos 3388 self.filename=filename 3389 self.duration=None 3390 self.loadMovie( self.filename ) 3391 self.format=self._movie.video_format 3392 self.pos=pos 3393 self.depth=0 3394 self.pos = numpy.asarray(pos, float) 3395 self.flipVert = flipVert 3396 self.flipHoriz = flipHoriz 3397 self.opacity = opacity 3398 self.playing=NOT_STARTED 3399 #size 3400 if size == None: self.size= numpy.array([self.format.width, 3401 self.format.height] , float) 3402 3403 elif type(size) in [tuple,list]: self.size = numpy.array(size,float) 3404 else: self.size = numpy.array((size,size),float) 3405 3406 self.ori = ori 3407 if units in [None, "", []]: self.units = win.units 3408 else: self.units = units 3409 #fix scaling to window coords </