Package VisionEgg :: Module Textures
[frames] | no frames]

Source Code for Module VisionEgg.Textures

   1  # The Vision Egg: Textures 
   2  # 
   3  # Copyright (C) 2001-2004 Andrew Straw 
   4  # Copyright (C) 2004-2008 California Institute of Technology 
   5  # 
   6  # URL: <http://www.visionegg.org/> 
   7  # 
   8  # Distributed under the terms of the GNU Lesser General Public License 
   9  # (LGPL). See LICENSE.TXT that came with this file. 
  10   
  11  """ 
  12  Texture (images mapped onto polygons) stimuli. 
  13   
  14  """ 
  15   
  16  #################################################################### 
  17  # 
  18  #        Import all the necessary packages 
  19  # 
  20  #################################################################### 
  21   
  22  import logging                              # available in Python 2.3 
  23   
  24  import VisionEgg 
  25  import VisionEgg.Core 
  26  import VisionEgg.ParameterTypes as ve_types 
  27   
  28  import Image, ImageDraw                         # Python Imaging Library packages 
  29  import pygame.surface, pygame.image             # pygame 
  30  import math, types, os 
  31  import numpy 
  32  import numpy.oldnumeric as numpyNumeric, numpy.oldnumeric.mlab as MLab 
  33   
  34  import VisionEgg.GL as gl # get all OpenGL stuff in one namespace 
  35  import OpenGL.GLU as glu 
  36   
  37  # These modules are part of PIL and get loaded as needed by Image. 
  38  # They are listed here so that Gordon McMillan's Installer properly 
  39  # locates them.  You will not hurt anything other than your ability to 
  40  # make executables using Intaller if you remove these lines. 
  41  import _imaging 
  42  import ImageFile, ImageFileIO, BmpImagePlugin, JpegImagePlugin, PngImagePlugin 
  43   
  44  if Image.VERSION >= '1.1.3': 
  45      shrink_filter = Image.ANTIALIAS # Added in PIL 1.1.3 
  46  else: 
  47      shrink_filter = Image.BICUBIC # Fallback filtering 
  48   
  49  array_types = [numpy.ndarray] 
  50  # Allow use of numarray and original Numeric texels without requiring either 
  51  try: 
  52      import numarray 
  53      array_types.append( numarray.numarraycore.NumArray ) 
  54  except ImportError: 
  55      pass 
  56  try: 
  57      import Numeric as orig_Numeric 
  58      array_types.append( orig_Numeric.ArrayType ) 
  59  except ImportError: 
  60      pass 
  61   
62 -def convert_to_numpy_if_array(A):
63 if type(A) in array_types: 64 # with late release Numeric and numarray this is a view of the data 65 return numpy.asarray(A) 66 else: 67 return A
68 69 #################################################################### 70 # 71 # XXX ToDo: 72 73 # The main remaining feature to add to this module is automatic 74 # management of texture objects. This would allow many small images 75 # (e.g. a bit of text) to live in one large texture object. This 76 # would be much faster when many small textures are drawn in rapid 77 # succession. (Apparently this might not be such a big improvement on 78 # OS X. (See http://crystal.sourceforge.net/phpwiki/index.php?MacXGL) 79 80 # Here's a sample from Apple's TextureRange demo which is supposed 81 # to speed up texture transfers. 82 # glBindTextures( target, &texID); 83 # glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, 1); 84 # glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,image_ptr); 85 # Update the texture with: 86 # glTexSubImage2D(target, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,image_ptr); 87 88 #################################################################### 89 90 #################################################################### 91 # 92 # Textures 93 # 94 #################################################################### 95
96 -def next_power_of_2(f):
97 return int(math.pow(2.0,math.ceil(math.log(f)/math.log(2.0))))
98
99 -def is_power_of_2(f):
100 return f == next_power_of_2(f)
101
102 -class Texture(object):
103 """A 2 dimensional texture. 104 105 The pixel data can come from an image file, an image file stream, 106 an instance of Image from the Python Imaging Library, a numpy 107 array, or None. 108 109 If the data is a numpy array, floating point numbers are assumed 110 to be in the range 0.0 to 1.0, and integers are assumed to be in 111 the range 0 to 255. The first index is the row (y position), the 112 second index is the column (x position), and if it's RGB or RGBA 113 data, the third index specifies the color band. Thus, if the 114 texel data was 640 pixels wide by 480 pixels tall, the array would 115 have shape (480,640) for luminance information, (480,640,3) for 116 RGB information, and (480,640,4) for RGBA information. 117 118 The 2D texture data is not sent to OpenGL (video texture memory) 119 until the load() method is called. The unload() method may be 120 used to remove the data from OpenGL. 121 122 A reference to the original image data is maintained.""" 123 124 __slots__ = ('texels', 125 'texture_object', 126 'size', 127 '_filename', 128 '_file_stream', 129 'buf_lf', 130 'buf_rf', 131 'buf_bf', 132 'buf_tf', 133 '_buf_l', 134 '_buf_r', 135 '_buf_b', 136 '_buf_t', 137 ) 138
139 - def __init__(self,texels=None,size=None):
140 """Creates instance of Texture object. 141 142 texels -- Texture data. If not specified, a blank white 143 texture is created. 144 size -- If a tuple, force size of texture data if possible, 145 raising an exception if not. If None, has no effect. 146 """ 147 148 if texels is None: # no texel data: make default 149 if size is None: 150 size = (256,256) # an arbitrary default size 151 texels = Image.new("RGB",size,(255,255,255)) # white 152 153 if type(texels) == types.FileType: 154 texels = Image.open(texels) # Attempt to open as an image file 155 elif type(texels) in (types.StringType,types.UnicodeType): 156 # is this string a filename or raw image data? 157 if os.path.isfile(texels): 158 # cache filename and file stream for later use (if possible) 159 self._filename = texels 160 self._file_stream = open(texels,"rb") 161 texels = Image.open(texels) # Attempt to open as an image stream 162 163 texels = convert_to_numpy_if_array(texels) 164 165 if isinstance(texels, Image.Image): # PIL Image 166 if texels.mode == 'P': # convert from paletted 167 texels = texels.convert('RGBX') 168 self.size = texels.size 169 elif isinstance(texels, pygame.surface.Surface): # pygame surface 170 self.size = texels.get_size() 171 elif isinstance(texels,numpy.ndarray): # numpy array 172 if len(texels.shape) == 3: 173 if texels.shape[2] not in [3,4]: 174 raise ValueError("Only luminance (rank 2), and RGB, RGBA (rank 3) arrays allowed") 175 elif len(texels.shape) != 2: 176 raise ValueError("Only luminance (rank 2), and RGB, RGBA (rank 3) arrays allowed") 177 self.size = ( texels.shape[1], texels.shape[0] ) 178 else: 179 raise TypeError("texel data could not be recognized. (Use a PIL Image, numpy array, or pygame surface.)") 180 181 self.texels = texels 182 self.texture_object = None 183 184 if size is not None and size != self.size: 185 raise ValueError("size was specified, but data could not be rescaled")
186
187 - def update(self):
188 """Update texture data 189 190 This method does nothing, but may be overriden in classes that 191 need to update their texture data whenever drawn. 192 193 It it called by the draw() method of any stimuli using 194 textures when its texture object is active, so it can safely 195 use put_sub_image() to manipulate its own texture data. 196 """ 197 pass
198
199 - def make_half_size(self):
200 if self.texture_object is not None: 201 raise RuntimeError("make_half_size() only available BEFORE texture loaded to OpenGL.") 202 203 if isinstance(self.texels,Image.Image): 204 w = self.size[0]/2 205 h = self.size[1]/2 206 small_texels = self.texels.resize((w,h),shrink_filter) 207 self.texels = small_texels 208 self.size = (w,h) 209 else: 210 raise NotImplementedError("Texture too large, but rescaling only implemented for PIL images.")
211
212 - def unload(self):
213 """Unload texture data from video texture memory. 214 215 This only removes data from the video texture memory if there 216 are no other references to the TextureObject instance. To 217 ensure this, all references to the texture_object argument 218 passed to the load() method should be deleted.""" 219 220 self.texture_object = None
221
222 - def get_texels_as_image(self):
223 """Return texel data as PIL image""" 224 if isinstance(self.texels,numpy.ndarray): 225 if len(self.texels.shape) == 2: 226 a = self.texels 227 if a.dtype == numpy.uint8: 228 mode = "L" 229 elif a.dtype == numpy.float32: 230 mode = "F" 231 else: 232 raise ValueError("unsupported image mode") 233 return Image.fromstring(mode, (a.shape[1], a.shape[0]), a.tostring()) 234 else: 235 raise NotImplementedError("Currently only luminance data can be converted to images") 236 elif isinstance(self.texels, Image.Image): 237 return self.texels 238 else: 239 raise NotImplementedError("Don't know how to convert texel data to PIL image")
240
241 - def get_pixels_as_image(self):
242 logger = logging.getLogger('VisionEgg.Textures') 243 logger.warning("Using deprecated method get_pixels_as_image(). " 244 "Use get_texels_as_image() instead.") 245 return self.get_texels_as_image()
246
247 - def load(self, 248 texture_object, 249 build_mipmaps = True, 250 rescale_original_to_fill_texture_object = False, 251 internal_format=gl.GL_RGB):
252 """Load texture data to video texture memory. 253 254 This will cause the texture data to become resident in OpenGL 255 video texture memory, enabling fast drawing. 256 257 The texture_object argument is used to specify an instance of 258 the TextureObject class, which is a wrapper for the OpenGL 259 texture object holding the resident texture. 260 261 To remove a texture from OpenGL's resident textures: TextureObject passed as the texture_object argument and 2) 262 call the unload() method""" 263 264 assert( isinstance( texture_object, TextureObject )) 265 assert( texture_object.dimensions == 2 ) 266 267 width, height = self.size 268 269 width_pow2 = next_power_of_2(width) 270 height_pow2 = next_power_of_2(height) 271 272 if rescale_original_to_fill_texture_object: 273 if not isinstance(self.texels,Image.Image): 274 raise NotImplementedError("Automatic rescaling not implemented for this texel data type.") 275 276 # fractional coverage 277 self.buf_lf = 0.0 278 self.buf_rf = float(width)/width_pow2 279 self.buf_bf = 0.0 280 self.buf_tf = float(height)/height_pow2 281 282 # absolute (texel units) coverage 283 self._buf_l = 0 284 self._buf_r = width 285 self._buf_b = 0 286 self._buf_t = height 287 288 if width != width_pow2 or height != height_pow2: 289 if isinstance(self.texels,numpy.ndarray): 290 if len(self.texels.shape) == 2: 291 buffer = numpy.zeros( (height_pow2,width_pow2), dtype=self.texels.dtype ) 292 buffer[0:height,0:width] = self.texels 293 elif len(self.texels.shape) == 3: 294 buffer = numpy.zeros( (height_pow2,width_pow2,self.texels.shape[2]), dtype=self.texels.dtype ) 295 buffer[0:height,0:width,:] = self.texels 296 else: 297 raise RuntimeError("Unexpected shape for self.texels") 298 299 elif isinstance(self.texels, Image.Image): # PIL Image 300 if rescale_original_to_fill_texture_object: 301 # reset coverage values 302 self.buf_lf = 0.0 303 self.buf_rf = 1.0 304 self.buf_bf = 0.0 305 self.buf_tf = 1.0 306 307 self._buf_l = 0 308 self._buf_r = width_pow2 309 self._buf_t = 0 310 self._buf_b = height_pow2 311 312 buffer = self.texels.resize((width_pow2,height_pow2),shrink_filter) 313 314 self.size = (width_pow2, height_pow2) 315 else: 316 buffer = Image.new(self.texels.mode,(width_pow2, height_pow2)) 317 buffer.paste( self.texels, (0,height_pow2-height,width,height_pow2)) 318 elif isinstance(self.texels, pygame.surface.Surface): # pygame surface 319 buffer = pygame.surface.Surface( (width_pow2, height_pow2), 320 self.texels.get_flags(), 321 self.texels.get_bitsize() ) 322 buffer.blit( self.texels, (0,height_pow2-height) ) 323 else: 324 raise RuntimeError("texel data not recognized - changed?") 325 else: 326 buffer = self.texels 327 328 # Put data in texture object 329 if not build_mipmaps: 330 texture_object.put_new_image( buffer, internal_format=internal_format, mipmap_level=0 ) 331 else: 332 if 0: 333 # Build mipmaps with GLU (faster, but currently broken) 334 texture_object.put_new_image_build_mipmaps( buffer, internal_format=internal_format ) 335 else: 336 # Build mipmaps in PIL 337 texture_object.put_new_image( buffer, internal_format=internal_format, mipmap_level=0 ) 338 if not isinstance(self.texels, Image.Image): # PIL Image 339 raise NotImplementedError( 340 "Building of mipmaps not implemented for this texel "+\ 341 "data type. (Use PIL Images or set parameter "+\ 342 "mipmaps_enabled = False.)") 343 this_width, this_height = self.size 344 biggest_dim = max(this_width,this_height) 345 mipmap_level = 1 346 while biggest_dim > 1: 347 this_width = this_width/2.0 348 this_height = this_height/2.0 349 350 width_pix = int(math.ceil(this_width)) 351 height_pix = int(math.ceil(this_height)) 352 shrunk = self.texels.resize((width_pix,height_pix),shrink_filter) 353 354 width_pow2 = next_power_of_2(width_pix) 355 height_pow2 = next_power_of_2(height_pix) 356 357 im = Image.new(shrunk.mode,(width_pow2,height_pow2)) 358 im.paste(shrunk,(0,height_pow2-height_pix,width_pix,height_pow2)) 359 360 texture_object.put_new_image( im, 361 mipmap_level=mipmap_level, 362 internal_format = internal_format, 363 check_opengl_errors = False, # no point -- we've already seen biggest texture work, we're just making mipmap 364 ) 365 366 mipmap_level += 1 367 biggest_dim = max(this_width,this_height) 368 369 # Keep reference to texture_object 370 self.texture_object = texture_object
371
372 - def get_texture_object(self):
373 return self.texture_object
374
375 -class TextureFromFile( Texture ):
376 """DEPRECATED."""
377 - def __init__(self, filename ):
378 logger = logging.getLogger('VisionEgg.Textures') 379 logger.warning("class TextureFromFile deprecated, use class " 380 "Texture instead.") 381 Texture.__init__(self, filename)
382
383 -class TextureObject(object):
384 """Texture data in OpenGL. Potentially resident in video texture memory. 385 386 This class encapsulates the state variables in OpenGL texture objects. Do not 387 change attribute values directly. Use the methods provided instead.""" 388 389 __slots__ = ( 390 'min_filter', 391 'mag_filter', 392 'wrap_mode_r', # if dimensions > 2 393 'wrap_mode_s', 394 'wrap_mode_t', # if dimensions > 1 395 'border_color', 396 'target', 397 'dimensions', 398 'gl_id', 399 '__gl_module__', 400 ) 401 402 _cube_map_side_names = ['positive_x', 'negative_x', 403 'positive_y', 'negative_y', 404 'positive_z', 'negative_z'] 405
406 - def __init__(self, 407 dimensions = 2):
408 if dimensions not in [1,2,3,'cube']: 409 raise ValueError("TextureObject dimensions must be 1,2,3, or 'cube'") 410 # default OpenGL values for these values 411 self.min_filter = gl.GL_NEAREST_MIPMAP_LINEAR 412 self.mag_filter = gl.GL_LINEAR 413 self.wrap_mode_s = gl.GL_REPEAT 414 if dimensions != 1: 415 self.wrap_mode_t = gl.GL_REPEAT 416 if dimensions == 3: 417 self.wrap_mode_r = gl.GL_REPEAT 418 self.border_color = (0, 0, 0, 0) 419 420 if dimensions == 1: 421 self.target = gl.GL_TEXTURE_1D 422 elif dimensions == 2: 423 self.target = gl.GL_TEXTURE_2D 424 elif dimensions == 3: 425 self.target = gl.GL_TEXTURE_3D 426 elif dimensions == 'cube': 427 self.target = gl.GL_TEXTURE_CUBE 428 429 self.dimensions = dimensions 430 self.gl_id = gl.glGenTextures(1) 431 self.__gl_module__ = gl # keep so we there's no error in __del__
432
433 - def __del__(self):
434 self.__gl_module__.glDeleteTextures(self.gl_id)
435
436 - def is_resident(self):
437 return gl.glAreTexturesResident( self.gl_id )
438
439 - def set_priority(self, priority):
440 gl.glPrioritizeTextures( [self.gl_id], [priority] )
441
442 - def set_min_filter(self, filter):
443 gl.glBindTexture(self.target, self.gl_id) 444 gl.glTexParameteri( self.target, gl.GL_TEXTURE_MIN_FILTER,filter) 445 self.min_filter = filter
446
447 - def set_mag_filter(self, filter):
448 gl.glBindTexture( self.target, self.gl_id) 449 gl.glTexParameteri( self.target, gl.GL_TEXTURE_MAG_FILTER, filter) 450 self.mag_filter = filter
451
452 - def set_wrap_mode_s(self, wrap_mode):
453 """Set to GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT, or GL_CLAMP_TO_BORDER""" 454 gl.glBindTexture( self.target, self.gl_id) 455 gl.glTexParameteri( self.target, gl.GL_TEXTURE_WRAP_S, wrap_mode) 456 self.wrap_mode_s = wrap_mode
457
458 - def set_wrap_mode_t(self, wrap_mode):
459 """Set to GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT, or GL_CLAMP_TO_BORDER""" 460 gl.glBindTexture( self.target, self.gl_id) 461 gl.glTexParameteri( self.target, gl.GL_TEXTURE_WRAP_T, wrap_mode) 462 self.wrap_mode_t = wrap_mode
463
464 - def set_wrap_mode_r(self, wrap_mode):
465 """Set to GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT, or GL_CLAMP_TO_BORDER""" 466 gl.glBindTexture( self.target, self.gl_id) 467 gl.glTexParameteri( self.target, gl.GL_TEXTURE_WRAP_R, wrap_mode) 468 self.wrap_mode_r = wrap_mode
469
470 - def set_border_color(self, border_color):
471 """Set to a sequence of 4 floats in the range 0.0 to 1.0""" 472 gl.glBindTexture( self.target, self.gl_id) 473 gl.glTexParameteriv( self.target, gl.GL_TEXTURE_BORDER_COLOR, border_color) 474 self.border_color = border_color
475
476 - def put_new_image(self, 477 texel_data, 478 mipmap_level = 0, 479 border = 0, 480 check_opengl_errors = True, 481 internal_format = gl.GL_RGB, 482 data_format = None, # automatic guess unless set explicitly 483 data_type = None, # automatic guess unless set explicitly 484 cube_side = None, 485 ):
486 487 """Put numpy array or PIL Image into OpenGL as texture data. 488 489 The texel_data parameter contains the texture data. If it is 490 a numpy array, it must be 1D, 2D, or 3D data in grayscale or 491 color (RGB or RGBA). Remember that OpenGL begins its textures 492 from the lower left corner, so texel_data[0,:] = 1.0 would set 493 the bottom line of the texture to white, while texel_data[:,0] 494 = 1.0 would set the left line of the texture to white. 495 496 The mipmap_level parameter specifies which of the texture 497 object's mipmap arrays you are filling. 498 499 The border parameter specifies the width of the border. 500 501 The check_opengl_errors parameter turns on (more 502 comprehensible) error messages when something goes wrong. It 503 also slows down performance a little bit. 504 505 The internal_format parameter specifies the format in which 506 the image data is stored on the video card. See the OpenGL 507 specification for all possible values. 508 509 If the data_format parameter is None (the default), an attempt 510 is made to guess data_format according to the following 511 description. For numpy arrays: If texel_data.shape is equal 512 to the dimensions of the texture object, texel_data is assumed 513 to contain luminance (grayscale) information and data_format 514 is set to GL_LUMINANCE. If texel_data.shape is equal to one 515 plus the dimensions of the texture object, texel_data is 516 assumed to contain color information. If texel_data.shape[-1] 517 is 3, this is assumed to be RGB data and data_format is set to 518 GL_RGB. If, texel_data.shape[-1] is 4, this is assumed to be 519 RGBA data and data_format is set to GL_RGBA. For PIL images: 520 the "mode" attribute is queried. 521 522 If the data_type parameter is None (the default), it is set to 523 GL_UNSIGNED_BYTE. For numpy arrays: texel_data is (re)cast 524 to UInt8 and, if it is a floating point type, values are 525 assumed to be in the range 0.0-1.0 and are scaled to the range 526 0-255. If the data_type parameter is not None, the texel_data 527 is not rescaled or recast. Currently only GL_UNSIGNED_BYTE is 528 supported. For PIL images: texel_data is used as unsigned 529 bytes. This is the usual format for common computer graphics 530 files.""" 531 532 texel_data = convert_to_numpy_if_array(texel_data) 533 if isinstance(texel_data,numpy.ndarray): 534 if self.dimensions != 'cube': 535 assert(cube_side == None) 536 data_dimensions = len(texel_data.shape) 537 assert((data_dimensions == self.dimensions) or (data_dimensions == self.dimensions+1)) 538 else: 539 assert(cube_side in TextureObject._cube_map_side_names) 540 elif isinstance(texel_data,Image.Image): 541 assert( self.dimensions == 2 ) 542 elif isinstance(texel_data,pygame.surface.Surface): 543 assert( self.dimensions == 2 ) 544 else: 545 raise TypeError("Expecting numpy array, PIL image, or pygame surface") 546 547 # make myself the active texture 548 gl.glBindTexture(self.target, self.gl_id) 549 550 # Determine the data_format, data_type and rescale the data if needed 551 if data_format is None: # guess the format of the data 552 if isinstance(texel_data,numpy.ndarray): 553 if len(texel_data.shape) == self.dimensions: 554 data_format = gl.GL_LUMINANCE 555 elif len(texel_data.shape) == (self.dimensions+1): 556 if texel_data.shape[-1] == 3: 557 data_format = gl.GL_RGB 558 elif texel_data.shape[-1] == 4: 559 data_format = gl.GL_RGBA 560 else: 561 raise RuntimeError("Couldn't determine a format for your texel_data.") 562 else: 563 raise RuntimeError("Couldn't determine a format for your texel_data.") 564 elif isinstance(texel_data,Image.Image): 565 if texel_data.mode == 'L': 566 data_format = gl.GL_LUMINANCE 567 elif texel_data.mode == 'RGB': 568 data_format = gl.GL_RGB 569 elif texel_data.mode in ('RGBA','RGBX'): 570 data_format = gl.GL_RGBA 571 elif texel_data.mode == 'P': 572 raise NotImplementedError("Paletted images are not supported.") 573 else: 574 raise RuntimeError("Couldn't determine format for your texel_data. (PIL mode = '%s')"%texel_data.mode) 575 elif isinstance(texel_data,pygame.surface.Surface): 576 if texel_data.get_alpha(): 577 data_format = gl.GL_RGBA 578 else: 579 data_format = gl.GL_RGB 580 581 if data_type is None: # guess the data type 582 data_type = gl.GL_UNSIGNED_BYTE 583 if isinstance(texel_data,numpy.ndarray): 584 if texel_data.dtype == numpy.float: 585 texel_data = texel_data*255.0 586 587 if data_type == gl.GL_UNSIGNED_BYTE: 588 if isinstance(texel_data,numpy.ndarray): 589 texel_data = texel_data.astype(numpy.uint8) # (re)cast if necessary 590 else: 591 raise NotImplementedError("Only data_type GL_UNSIGNED_BYTE currently supported") 592 593 # determine size and make sure its power of 2 594 if self.dimensions == 1: 595 # must be numpy array 596 width = texel_data.shape[0] 597 if not is_power_of_2(width): raise ValueError("texel_data does not have all dimensions == n^2") 598 else: 599 if isinstance(texel_data,numpy.ndarray): 600 width = texel_data.shape[1] 601 height = texel_data.shape[0] 602 elif isinstance(texel_data,Image.Image): 603 width, height = texel_data.size 604 elif isinstance(texel_data,pygame.surface.Surface): 605 width, height = texel_data.get_size() 606 if not is_power_of_2(width): raise ValueError("texel_data does not have all dimensions == n^2") 607 if not is_power_of_2(height): raise ValueError("texel_data does not have all dimensions == n^2") 608 if self.dimensions == 3: 609 # must be numpy array 610 depth = texel_data.shape[2] 611 if not is_power_of_2(depth): raise ValueError("texel_data does not have all dimensions == n^2") 612 613 if self.dimensions in [2,'cube']: 614 if isinstance(texel_data,numpy.ndarray): 615 raw_data = texel_data.tostring() 616 elif isinstance(texel_data,Image.Image): 617 raw_data = texel_data.tostring('raw',texel_data.mode,0,-1) 618 elif isinstance(texel_data,pygame.surface.Surface): 619 if texel_data.get_alpha(): 620 raw_data = pygame.image.tostring(texel_data,'RGBA',1) 621 else: 622 raw_data = pygame.image.tostring(texel_data,'RGB',1) 623 624 # check for OpenGL errors 625 if check_opengl_errors: 626 max_dim = gl.glGetIntegerv( gl.GL_MAX_TEXTURE_SIZE ) 627 if width > max_dim: 628 raise TextureTooLargeError("texel_data is too wide for your video system.") 629 if self.dimensions == 1: 630 gl.glTexImage1Dub(gl.GL_PROXY_TEXTURE_1D, 631 mipmap_level, 632 internal_format, 633 border, 634 data_format, 635 texel_data) 636 if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_1D,mipmap_level,gl.GL_TEXTURE_WIDTH) == 0: 637 raise TextureTooLargeError("texel_data is too wide for your video system.") 638 elif self.dimensions in [2,'cube']: 639 if height > max_dim: 640 raise TextureTooLargeError("texel_data is too tall for your video system.") 641 if self.dimensions == 2: 642 target = gl.GL_PROXY_TEXTURE_2D 643 else: 644 target = gl.GL_PROXY_CUBE_MAP 645 gl.glTexImage2D(target, 646 mipmap_level, 647 internal_format, 648 width, 649 height, 650 border, 651 data_format, 652 data_type, 653 raw_data) 654 if gl.glGetTexLevelParameteriv(target, # Need PyOpenGL >= 2.0 655 mipmap_level, 656 gl.GL_TEXTURE_WIDTH) == 0: 657 raise TextureTooLargeError("texel_data is too wide for your video system.") 658 if gl.glGetTexLevelParameteriv(target,mipmap_level,gl.GL_TEXTURE_HEIGHT) == 0: 659 raise TextureTooLargeError("texel_data is too tall for your video system.") 660 elif self.dimensions == 3: 661 if max(height,depth) > max_dim: 662 raise TextureTooLargeError("texel_data is too large for your video system.") 663 gl.glTexImage3Dub(gl.GL_PROXY_TEXTURE_3D, 664 mipmap_level, 665 internal_format, 666 border, 667 data_format, 668 texel_data) 669 if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_3D,mipmap_level,gl.GL_TEXTURE_WIDTH) == 0: 670 raise TextureTooLargeError("texel_data is too wide for your video system.") 671 if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_3D,mipmap_level,gl.GL_TEXTURE_HEIGHT) == 0: 672 raise TextureTooLargeError("texel_data is too tall for your video system.") 673 if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_3D,mipmap_level,gl.GL_TEXTURE_DEPTH) == 0: 674 raise TextureTooLargeError("texel_data is too deep for your video system.") 675 else: 676 raise RuntimeError("Unknown number of dimensions.") 677 678 # No OpenGL error, put the texture in! 679 if self.dimensions == 1: 680 gl.glTexImage1Dub(gl.GL_TEXTURE_1D, 681 mipmap_level, 682 internal_format, 683 border, 684 data_format, 685 texel_data) 686 elif self.dimensions in [2,'cube']: 687 if self.dimensions == 2: 688 target = gl.GL_TEXTURE_2D 689 else: 690 target_name = 'GL_CUBE_MAP_'+cube_side.upper() 691 target = getattr(gl,target_name) 692 gl.glTexImage2D(target, 693 mipmap_level, 694 internal_format, 695 width, 696 height, 697 border, 698 data_format, 699 data_type, 700 raw_data) 701 elif self.dimensions == 3: 702 gl.glTexImage3Dub(gl.GL_TEXTURE_3D, 703 mipmap_level, 704 internal_format, 705 border, 706 data_format, 707 texel_data) 708 else: 709 raise RuntimeError("Unknown number of dimensions.")
710
711 - def put_new_image_build_mipmaps(self, 712 texel_data, 713 internal_format = gl.GL_RGB, 714 data_format = None, # automatic guess unless set explicitly 715 data_type = None, # automatic guess unless set explicitly 716 cube_side = None, 717 ):
718 719 """Similar to put_new_image(), but builds mipmaps.""" 720 721 if self.dimensions != 2: 722 raise ValueError("can only handle 2D texel data for automatic mipmap building") 723 texel_data = convert_to_numpy_if_array(texel_data) 724 if isinstance(texel_data,numpy.ndarray): 725 assert(cube_side == None) 726 data_dimensions = len(texel_data.shape) 727 assert((data_dimensions == self.dimensions) or (data_dimensions == self.dimensions+1)) 728 elif isinstance(texel_data,Image.Image): 729 assert( self.dimensions == 2 ) 730 elif isinstance(texel_data,pygame.surface.Surface): 731 assert( self.dimensions == 2 ) 732 else: 733 raise TypeError("Expecting numpy array, PIL image, or pygame surface") 734 735 # make myself the active texture 736 gl.glBindTexture(self.target, self.gl_id) 737 738 # Determine the data_format, data_type and rescale the data if needed 739 if data_format is None: # guess the format of the data 740 if isinstance(texel_data,numpy.ndarray): 741 if len(texel_data.shape) == self.dimensions: 742 data_format = gl.GL_LUMINANCE 743 elif len(texel_data.shape) == (self.dimensions+1): 744 if texel_data.shape[-1] == 3: 745 data_format = gl.GL_RGB 746 elif texel_data.shape[-1] == 4: 747 data_format = gl.GL_RGBA 748 else: 749 raise RuntimeError("Couldn't determine a format for your texel_data.") 750 else: 751 raise RuntimeError("Couldn't determine a format for your texel_data.") 752 elif isinstance(texel_data,Image.Image): 753 if texel_data.mode == 'L': 754 data_format = gl.GL_LUMINANCE 755 elif texel_data.mode == 'RGB': 756 data_format = gl.GL_RGB 757 elif texel_data.mode in ['RGBA','RGBX']: 758 data_format = gl.GL_RGBA 759 elif texel_data.mode == 'P': 760 raise NotImplementedError("Paletted images are not supported.") 761 else: 762 raise RuntimeError("Couldn't determine format for your texel_data. (PIL mode = '%s')"%texel_data.mode) 763 elif isinstance(texel_data,pygame.surface.Surface): 764 if texel_data.get_alpha(): 765 data_format = gl.GL_RGBA 766 else: 767 data_format = gl.GL_RGB 768 769 if data_type is None: # guess the data type 770 data_type = gl.GL_UNSIGNED_BYTE 771 if isinstance(texel_data,numpy.ndarray): 772 if texel_data.dtype == numpy.float: 773 texel_data = texel_data*255.0 774 775 if data_type == gl.GL_UNSIGNED_BYTE: 776 if isinstance(texel_data,numpy.ndarray): 777 texel_data = texel_data.astype(numpy.uint8) # (re)cast if necessary 778 else: 779 raise NotImplementedError("Only data_type GL_UNSIGNED_BYTE currently supported") 780 781 if isinstance(texel_data,numpy.ndarray): 782 width = texel_data.shape[1] 783 height = texel_data.shape[0] 784 elif isinstance(texel_data,Image.Image): 785 width, height = texel_data.size 786 elif isinstance(texel_data,pygame.surface.Surface): 787 width, height = texel_data.get_size() 788 if not is_power_of_2(width): raise ValueError("texel_data does not have all dimensions == n^2") 789 if not is_power_of_2(height): raise ValueError("texel_data does not have all dimensions == n^2") 790 791 if isinstance(texel_data,numpy.ndarray): 792 raw_data = texel_data.tostring() 793 elif isinstance(texel_data,Image.Image): 794 raw_data = texel_data.tostring('raw',texel_data.mode,0,-1) 795 elif isinstance(texel_data,pygame.surface.Surface): 796 if texel_data.get_alpha(): 797 raw_data = pygame.image.tostring(texel_data,'RGBA',1) 798 else: 799 raw_data = pygame.image.tostring(texel_data,'RGB',1) 800 801 args = (self.target, 802 internal_format, 803 width, # XXX should be width_pow2? 804 height,# XXX should be height_pow2? 805 data_format, 806 data_type) 807 #raw_data) 808 print 'args',args 809 810 glu.gluBuild2DMipmaps(self.target, 811 internal_format, 812 width, # XXX should be width_pow2? 813 height,# XXX should be height_pow2? 814 data_format, 815 data_type, 816 raw_data)
817
818 - def put_sub_image(self, 819 texel_data, 820 mipmap_level = 0, 821 offset_tuple = None, 822 data_format = None, # automatic guess unless set explicitly 823 data_type = None, # automatic guess unless set explicitly 824 cube_side = None, 825 ):
826 827 """Replace all or part of a texture object. 828 829 This is faster that put_new_image(), and can be used to 830 rapidly update textures. 831 832 The offset_tuple parameter determines the lower left corner 833 (for 2D textures) of your data in pixel units. For example, 834 (0,0) would be no offset and thus the new data would be placed 835 in the lower left of the texture. 836 837 For an explanation of most parameters, see the 838 put_new_image() method.""" 839 840 texel_data = convert_to_numpy_if_array(texel_data) 841 if isinstance(texel_data,numpy.ndarray): 842 if self.dimensions != 'cube': 843 assert(cube_side == None) 844 data_dimensions = len(texel_data.shape) 845 assert((data_dimensions == self.dimensions) or (data_dimensions == self.dimensions+1)) 846 else: 847 assert(cube_side in TextureObject._cube_map_side_names) 848 elif isinstance(texel_data,Image.Image): 849 assert( self.dimensions == 2 ) 850 elif isinstance(texel_data,pygame.surface.Surface): 851 assert( self.dimensions == 2 ) 852 else: 853 raise TypeError("Expecting numpy array, PIL image, or pygame surface") 854 855 # make myself the active texture 856 gl.glBindTexture(self.target, self.gl_id) 857 858 # Determine the data_format, data_type and rescale the data if needed 859 data = texel_data 860 861 if data_format is None: # guess the format of the data 862 if isinstance(texel_data,numpy.ndarray): 863 if len(data.shape) == self.dimensions: 864 data_format = gl.GL_LUMINANCE 865 elif len(data.shape) == (self.dimensions+1): 866 if data.shape[-1] == 3: 867 data_format = gl.GL_RGB 868 elif data.shape[-1] == 4: 869 data_format = gl.GL_RGBA 870 else: 871 raise RuntimeError("Couldn't determine a format for your texel_data.") 872 else: 873 raise RuntimeError("Couldn't determine a format for your texel_data.") 874 elif isinstance(texel_data,Image.Image): 875 if data.mode == 'L': 876 data_format = gl.GL_LUMINANCE 877 elif data.mode == 'RGB': 878 data_format = gl.GL_RGB 879 elif data.mode in ['RGBA','RGBX']: 880 data_format = gl.GL_RGBA 881 elif data.mode == 'P': 882 raise NotImplementedError("Paletted images are not supported.") 883 else: 884 raise RuntimeError("Couldn't determine format for your texel_data. (PIL mode = '%s')"%data.mode) 885 elif isinstance(texel_data,pygame.surface.Surface): 886 if data.get_alpha(): 887 data_format = gl.GL_RGBA 888 else: 889 data_format = gl.GL_RGB 890 891 if data_type is None: # guess the data type 892 data_type = gl.GL_UNSIGNED_BYTE 893 if isinstance(data,numpy.ndarray): 894 if data.dtype == numpy.float: 895 data = data*255.0 896 897 if data_type == gl.GL_UNSIGNED_BYTE: 898 if isinstance(data,numpy.ndarray): 899 data = data.astype(numpy.uint8) # (re)cast if necessary 900 else: 901 raise NotImplementedError("Only data_type GL_UNSIGNED_BYTE currently supported") 902 903 if self.dimensions == 1: 904 if offset_tuple is None: 905 x_offset = 0 906 else: 907 x_offset = offset_tuple[0] 908 width = data.shape[0] 909 raw_data = data.astype(numpy.uint8).tostring() 910 gl.glTexSubImage1D(gl.GL_TEXTURE_1D, 911 mipmap_level, 912 x_offset, 913 width, 914 data_format, 915 data_type, 916 raw_data) 917 elif self.dimensions in [2,'cube']: 918 if self.dimensions == 2: 919 target = gl.GL_TEXTURE_2D 920 else: 921 target_name = 'GL_CUBE_MAP_'+cube_side.upper() 922 target = getattr(gl,target_name) 923 if offset_tuple is None: 924 x_offset = y_offset = 0 925 else: 926 x_offset, y_offset = offset_tuple 927 if isinstance(data,numpy.ndarray): 928 width = data.shape[1] 929 height = data.shape[0] 930 raw_data = data.astype(numpy.uint8).tostring() 931 elif isinstance(texel_data,Image.Image): 932 width = data.size[0] 933 height = data.size[1] 934 raw_data = data.tostring('raw',data.mode,0,-1) 935 elif isinstance(texel_data,pygame.surface.Surface): 936 width, height = texel_data.get_size() 937 if data.get_alpha(): 938 raw_data = pygame.image.tostring(texel_data,'RGBA',1) 939 else: 940 raw_data = pygame.image.tostring(texel_data,'RGB',1) 941 gl.glTexSubImage2D(target, 942 mipmap_level, 943 x_offset, 944 y_offset, 945 width, 946 height, 947 data_format, 948 data_type, 949 raw_data) 950 elif self.dimensions == 3: 951 raise RuntimeError("Cannot put_sub_image on 3D texture_object.") 952 else: 953 raise RuntimeError("Unknown number of dimensions.")
954
955 - def put_new_framebuffer(self, 956 buffer='back', 957 mipmap_level = 0, 958 border = 0, 959 framebuffer_lowerleft = None, 960 size = None, 961 internal_format = gl.GL_RGB, 962 cube_side = None, 963 ):
964 965 """Replace texture object with the framebuffer contents. 966 967 The framebuffer_lowerleft parameter determines the lower left 968 corner of the framebuffer region from which to copy texel data 969 in pixel units. For example, (0,0) would be no offset and 970 thus the new data would be placed from the lower left of the 971 framebuffer. 972 973 For an explanation of most parameters, see the 974 put_new_image() method.""" 975 976 if self.dimensions != 2: 977 raise RuntimeError("put_new_framebuffer only supported for 2D textures.") 978 979 if buffer == 'front': 980 gl.glReadBuffer( gl.GL_FRONT ) 981 elif buffer == 'back': 982 gl.glReadBuffer( gl.GL_BACK ) 983 else: 984 raise ValueError('No support for "%s" framebuffer'%buffer) 985 986 # make myself the active texture 987 gl.glBindTexture(self.target, self.gl_id) 988 989 if framebuffer_lowerleft is None: 990 framebuffer_lowerleft = (0,0) 991 x,y = framebuffer_lowerleft 992 993 if size is None: 994 raise ValueError("Must specify size for put_new_framebuffer(): cannot guess") 995 996 # determine size and make sure its power of 2 997 width, height = size 998 if not is_power_of_2(width): raise ValueError("texel_data does not have all dimensions == n^2") 999 if not is_power_of_2(height): raise ValueError("texel_data does not have all dimensions == n^2") 1000 1001 target = gl.GL_TEXTURE_2D 1002 gl.glCopyTexImage2D(target, 1003 mipmap_level, 1004 internal_format, 1005 x, 1006 y, 1007 width, 1008 height, 1009 border)
1010 1011 #################################################################### 1012 # 1013 # Stimulus - TextureStimulus 1014 # 1015 #################################################################### 1016
1017 -class TextureStimulusBaseClass(VisionEgg.Core.Stimulus):
1018 """Parameters common to all stimuli that use textures. 1019 1020 Don't instantiate this class directly. 1021 1022 Parameters 1023 ========== 1024 texture -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>) 1025 Default: (determined at runtime) 1026 texture_mag_filter -- OpenGL filter enum (Integer) 1027 Default: GL_LINEAR (9729) 1028 texture_min_filter -- OpenGL filter enum (Integer) 1029 Default: (GL enum determined at runtime) 1030 texture_wrap_s -- OpenGL texture wrap enum (Integer) 1031 Default: (GL enum determined at runtime) 1032 texture_wrap_t -- OpenGL texture wrap enum (Integer) 1033 Default: (GL enum determined at runtime) 1034 1035 Constant Parameters 1036 =================== 1037 internal_format -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer) 1038 Default: GL_RGB (6407) 1039 mipmaps_enabled -- Are mipmaps enabled? (Boolean) 1040 Default: True 1041 shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean) 1042 Default: False 1043 """ 1044 1045 parameters_and_defaults = { 1046 'texture':(None, 1047 ve_types.Instance(Texture), 1048 "source of texture data"), 1049 'texture_mag_filter':(gl.GL_LINEAR, 1050 ve_types.Integer, 1051 "OpenGL filter enum", 1052 VisionEgg.ParameterDefinition.OPENGL_ENUM), 1053 'texture_min_filter':(None, # defaults to gl.GL_LINEAR_MIPMAP_LINEAR (unless mipmaps_enabled False, then gl.GL_LINEAR) 1054 ve_types.Integer, 1055 "OpenGL filter enum", 1056 VisionEgg.ParameterDefinition.OPENGL_ENUM), 1057 'texture_wrap_s':(None, # set to gl.GL_CLAMP_TO_EDGE below 1058 ve_types.Integer, 1059 "OpenGL texture wrap enum", 1060 VisionEgg.ParameterDefinition.OPENGL_ENUM), 1061 'texture_wrap_t':(None, # set to gl.GL_CLAMP_TO_EDGE below 1062 ve_types.Integer, 1063 "OpenGL texture wrap enum", 1064 VisionEgg.ParameterDefinition.OPENGL_ENUM), 1065 } 1066 1067 constant_parameters_and_defaults = { 1068 'internal_format':(gl.GL_RGB,#None, 1069 ve_types.Integer, 1070 "format with which OpenGL uses texture data (OpenGL data type enum)", 1071 VisionEgg.ParameterDefinition.OPENGL_ENUM), 1072 'mipmaps_enabled':(True, 1073 ve_types.Boolean, 1074 "Are mipmaps enabled?"), 1075 'shrink_texture_ok':(False, 1076 ve_types.Boolean, 1077 "Allow automatic shrinking of texture if too big?"), 1078 } 1079 1080 __slots__ = ( 1081 'texture_object', 1082 '_using_texture', 1083 ) 1084 1085 _mipmap_modes = [gl.GL_LINEAR_MIPMAP_LINEAR,gl.GL_LINEAR_MIPMAP_NEAREST, 1086 gl.GL_NEAREST_MIPMAP_LINEAR,gl.GL_NEAREST_MIPMAP_NEAREST] 1087
1088 - def __init__(self,**kw):
1089 VisionEgg.Core.Stimulus.__init__(self,**kw) 1090 1091 if self.parameters.texture is None: 1092 # generate default texture 1093 self.parameters.texture = Texture() 1094 1095 if self.parameters.texture_min_filter is None: 1096 # generate default texture minimization filter 1097 if self.constant_parameters.mipmaps_enabled: 1098 self.parameters.texture_min_filter = gl.GL_LINEAR_MIPMAP_LINEAR 1099 else: 1100 self.parameters.texture_min_filter = gl.GL_LINEAR 1101 1102 if not self.constant_parameters.mipmaps_enabled: 1103 if self.parameters.texture_min_filter in TextureStimulusBaseClass._mipmap_modes: 1104 raise ValueError("texture_min_filter cannot be a mipmap type if mipmaps not enabled.") 1105 # We have to set these parameters here because we may have 1106 # re-assigned gl.GL_CLAMP_TO_EDGE. This allows us to use 1107 # symbol gl.GL_CLAMP_TO_EDGE even if our version of OpenGL 1108 # doesn't support it. 1109 if self.parameters.texture_wrap_s is None: 1110 self.parameters.texture_wrap_s = gl.GL_CLAMP_TO_EDGE 1111 if self.parameters.texture_wrap_t is None: 1112 self.parameters.texture_wrap_t = gl.GL_CLAMP_TO_EDGE 1113 1114 # Create an OpenGL texture object this instance "owns" 1115 self.texture_object = TextureObject(dimensions=2) 1116 1117 self._reload_texture()
1118
1119 - def _reload_texture(self):
1120 """(Re)load texture to OpenGL""" 1121 p = self.parameters 1122 self._using_texture = p.texture 1123 1124 if not self.constant_parameters.shrink_texture_ok: 1125 # send texture to OpenGL 1126 p.texture.load( self.texture_object, 1127 internal_format = self.constant_parameters.internal_format, 1128 build_mipmaps = self.constant_parameters.mipmaps_enabled ) 1129 else: 1130 max_dim = gl.glGetIntegerv( gl.GL_MAX_TEXTURE_SIZE ) 1131 resized = 0 1132 while max(p.texture.size) > max_dim: 1133 p.texture.make_half_size() 1134 resized = 1 1135 loaded_ok = 0 1136 while not loaded_ok: 1137 try: 1138 # send texture to OpenGL 1139 p.texture.load( self.texture_object, 1140 internal_format = self.constant_parameters.internal_format, 1141 build_mipmaps = self.constant_parameters.mipmaps_enabled ) 1142 except TextureTooLargeError: 1143 p.texture.make_half_size() 1144 resized = 1 1145 else: 1146 loaded_ok = 1 1147 if resized: 1148 logger = logging.getLogger('VisionEgg.Textures') 1149 logger.warning("Resized texture in %s to %d x %d"%( 1150 str(self),p.texture.size[0],p.texture.size[1]))
1151
1152 -class Mask2D(VisionEgg.ClassWithParameters):
1153 """A mask for windowing a portion of a texture. 1154 1155 Thanks to the author, Jon Peirce, of the AlphaStim class from the 1156 PsychoPy package from which the idea to do this came. 1157 1158 Constant Parameters 1159 =================== 1160 function -- 'gaussian' or 'circle' (String) 1161 Default: gaussian 1162 num_samples -- size of mask texture data (units: number of texels) (Sequence2 of Real) 1163 Default: (256, 256) 1164 radius_parameter -- radius for circle, sigma for gaussian (Real) 1165 Default: 25.0 1166 """ 1167 1168 # All of these parameters are constant -- if you need a new mask, create a new instance 1169 constant_parameters_and_defaults = { 1170 'function':('gaussian', # can be 'gaussian' or 'circle' 1171 ve_types.String, 1172 "'gaussian' or 'circle'"), 1173 'radius_parameter':(25.0, # radius for circle, sigma for gaussian, same units as num_samples 1174 ve_types.Real, 1175 "radius for circle, sigma for gaussian"), 1176 'num_samples':((256,256), # size of mask data in texels 1177 ve_types.Sequence2(ve_types.Real), 1178 "size of mask texture data (units: number of texels)"), 1179 }
1180 - def __init__(self,**kw):
1181 VisionEgg.ClassWithParameters.__init__(self,**kw) 1182 1183 cp = self.constant_parameters # shorthand 1184 width,height = cp.num_samples 1185 if width != next_power_of_2(width): 1186 raise RuntimeError("Mask must have width num_samples power of 2") 1187 if height != next_power_of_2(height): 1188 raise RuntimeError("Mask must have height num_samples power of 2") 1189 1190 gl.glActiveTextureARB(gl.GL_TEXTURE1_ARB) # Need PyOpenGL >= 2.0 1191 self.texture_object = TextureObject(dimensions=2) 1192 1193 if cp.function == "gaussian": 1194 xx = numpyNumeric.outerproduct(numpyNumeric.ones((1,cp.num_samples[1])), 1195 numpyNumeric.arange(0,cp.num_samples[0],1.0)-cp.num_samples[0]/2) 1196 yy = numpyNumeric.outerproduct(numpyNumeric.arange(0,cp.num_samples[1],1.0)-cp.num_samples[1]/2, 1197 numpyNumeric.ones((1,cp.num_samples[0]))) 1198 dist_from_center = numpyNumeric.sqrt(xx**2 + yy**2) 1199 sigma = cp.radius_parameter 1200 data = numpyNumeric.exp( -dist_from_center**2.0 / (2.0*sigma**2.0) ) 1201 elif cp.function == "circle": 1202 data = numpyNumeric.zeros(cp.num_samples,numpyNumeric.Float) 1203 # perform anti-aliasing in circle computation by computing 1204 # at several slightly different locations and averaging 1205 oversamples = 4 1206 x_offsets = numpyNumeric.arange(0.0,1.0,1.0/oversamples) 1207 x_offsets = x_offsets - MLab.mean(x_offsets) 1208 y_offsets = x_offsets 1209 for x_offset in x_offsets: 1210 for y_offset in y_offsets: 1211 xx = numpyNumeric.outerproduct(numpyNumeric.ones((1,cp.num_samples[1])), 1212 numpyNumeric.arange(0,cp.num_samples[0],1.0)-cp.num_samples[0]/2+0.5)+x_offset 1213 yy = numpyNumeric.outerproduct(numpyNumeric.arange(0,cp.num_samples[1],1.0)-cp.num_samples[1]/2+0.5, 1214 numpyNumeric.ones((1,cp.num_samples[0])))+y_offset 1215 dist_from_center = numpyNumeric.sqrt(xx**2 + yy**2) 1216 data += dist_from_center <= cp.radius_parameter 1217 data = data / float( len(x_offsets)*len(y_offsets) ) 1218 else: 1219 raise RuntimeError("Don't know about window function %s"%self.constant_parameters.function) 1220 1221 self.texture_object.put_new_image(data, 1222 data_format=gl.GL_ALPHA, 1223 internal_format=gl.GL_ALPHA) 1224 self.texture_object.set_min_filter(gl.GL_LINEAR) # turn off mipmaps for mask 1225 self.texture_object.set_wrap_mode_s(gl.GL_CLAMP_TO_EDGE) 1226 self.texture_object.set_wrap_mode_t(gl.GL_CLAMP_TO_EDGE) 1227 gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) 1228 1229 # reset active texture unit to 0 1230 gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB)
1231
1232 - def draw_masked_quad_3d(self,lt,rt,bt,tt,v1,v2,v3,v4):
1233 # The *t parameters are the texture coordinates. 1234 1235 # By the time this method is called, GL_TEXTURE0_ARB should be 1236 # loaded as the texture object to be masked. 1237 1238 gl.glActiveTextureARB(gl.GL_TEXTURE1_ARB) # bind 2nd texture unit to mask texture 1239 gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_object.gl_id) 1240 gl.glEnable(gl.GL_TEXTURE_2D) 1241 1242 # The normal TEXTURE2D object is the 1st (TEXTURE0) texture unit 1243 gl.glBegin(gl.GL_QUADS) 1244 1245 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,lt,bt) 1246 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,0.0,0.0) 1247 gl.glVertex3f(*v1) 1248 1249 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,rt,bt) 1250 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,1.0,0.0) 1251 gl.glVertex3f(*v2) 1252 1253 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,rt,tt) 1254 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,1.0,1.0) 1255 gl.glVertex3f(*v3) 1256 1257 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,lt,tt) 1258 gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,0.0,1.0) 1259 gl.glVertex3f(*v4) 1260 1261 gl.glEnd() # GL_QUADS 1262 gl.glDisable(gl.GL_TEXTURE_2D) # turn off texturing in this texture unit 1263 gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) # return to 1st texture unit
1264
1265 - def draw_masked_quad(self,lt,rt,bt,tt,le,re,be,te,depth):
1266 # The *t parameters are the texture coordinates. The *e 1267 # parameters are the eye coordinates for the vertices of the 1268 # quad. 1269 v1 = (le,be,depth) 1270 v2 = (re,be,depth) 1271 v3 = (re,te,depth) 1272 v4 = (le,te,depth) 1273 self.draw_masked_quad_3d(lt,rt,bt,tt,v1,v2,v3,v4)
1274
1275 -class TextureStimulus(TextureStimulusBaseClass):
1276 """A textured rectangle. 1277 1278 This is mainly for 2D use (z coordinate fixed to 0.0 and w 1279 coordinated fixed to 1.0 if not given). 1280 1281 1282 Parameters 1283 ========== 1284 anchor -- specifies how position parameter is interpreted (String) 1285 Default: lowerleft 1286 angle -- units: degrees, 0=right, 90=up (Real) 1287 Default: 0.0 1288 color -- texture environment color. alpha ignored (if given) for max_alpha parameter (AnyOf(Sequence3 of Real or Sequence4 of Real)) 1289 Default: (1.0, 1.0, 1.0) 1290 depth_test -- perform depth test? (Boolean) 1291 Default: False 1292 mask -- optional masking function (Instance of <class 'VisionEgg.Textures.Mask2D'>) 1293 Default: (determined at runtime) 1294 max_alpha -- controls opacity. 1.0=copletely opaque, 0.0=completely transparent (Real) 1295 Default: 1.0 1296 on -- draw stimulus? (Boolean) 1297 Default: True 1298 position -- units: eye coordinates (AnyOf(Sequence2 of Real or Sequence3 of Real or Sequence4 of Real)) 1299 Default: (0.0, 0.0) 1300 size -- defaults to texture data size (units: eye coordinates) (Sequence2 of Real) 1301 Default: (determined at runtime) 1302 texture -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>) 1303 Inherited from TextureStimulusBaseClass 1304 Default: (determined at runtime) 1305 texture_mag_filter -- OpenGL filter enum (Integer) 1306 Inherited from TextureStimulusBaseClass 1307 Default: GL_LINEAR (9729) 1308 texture_min_filter -- OpenGL filter enum (Integer) 1309 Inherited from TextureStimulusBaseClass 1310 Default: (GL enum determined at runtime) 1311 texture_wrap_s -- OpenGL texture wrap enum (Integer) 1312 Inherited from TextureStimulusBaseClass 1313 Default: (GL enum determined at runtime) 1314 texture_wrap_t -- OpenGL texture wrap enum (Integer) 1315 Inherited from TextureStimulusBaseClass 1316 Default: (GL enum determined at runtime) 1317 1318 Constant Parameters 1319 =================== 1320 internal_format -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer) 1321 Default: GL_RGB (6407) 1322 mipmaps_enabled -- Are mipmaps enabled? (Boolean) 1323 Default: True 1324 shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean) 1325 Default: False 1326 """ 1327 1328 parameters_and_defaults = { 1329 'on':(True, 1330 ve_types.Boolean, 1331 "draw stimulus?"), 1332 'mask':(None, # texture mask 1333 ve_types.Instance(Mask2D), 1334 "optional masking function"), 1335 'position':((0.0,0.0), # in eye coordinates 1336 ve_types.AnyOf(ve_types.Sequence2(ve_types.Real), 1337 ve_types.Sequence3(ve_types.Real), 1338 ve_types.Sequence4(ve_types.Real)), 1339 "units: eye coordinates"), 1340 'anchor':('lowerleft', 1341 ve_types.String, 1342 "specifies how position parameter is interpreted"), 1343 'lowerleft':(None, # DEPRECATED -- don't use 1344 ve_types.Sequence2(ve_types.Real), 1345 "", 1346 VisionEgg.ParameterDefinition.DEPRECATED), 1347 'angle':(0.0, # in degrees 1348 ve_types.Real, 1349 "units: degrees, 0=right, 90=up"), 1350 'size':(None, 1351 ve_types.Sequence2(ve_types.Real), 1352 "defaults to texture data size (units: eye coordinates)"), 1353 'max_alpha':(1.0, # controls "opacity": 1.0 = completely opaque, 0.0 = completely transparent 1354 ve_types.Real, 1355 "controls opacity. 1.0=copletely opaque, 0.0=completely transparent"), 1356 'color':((1.0,1.0,1.0), # texture environment color. alpha is ignored (if given) -- use max_alpha parameter 1357 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), 1358 ve_types.Sequence4(ve_types.Real)), 1359 "texture environment color. alpha ignored (if given) for max_alpha parameter"), 1360 'depth_test':(False, 1361 ve_types.Boolean, 1362 "perform depth test?"), 1363 } 1364
1365 - def draw(self):
1366 p = self.parameters 1367 if p.texture != self._using_texture: # self._using_texture is from TextureStimulusBaseClass 1368 self._reload_texture() 1369 if p.lowerleft != None: 1370 if not hasattr(VisionEgg.config,"_GAVE_LOWERLEFT_DEPRECATION"): 1371 logger = logging.getLogger('VisionEgg.Textures') 1372 logger.warning("Specifying texture by 'lowerleft' " 1373 "deprecated parameter deprecated. Use " 1374 "'position' parameter instead. (Allows " 1375 "use of 'anchor' parameter to set to " 1376 "other values.)") 1377 VisionEgg.config._GAVE_LOWERLEFT_DEPRECATION = 1 1378 p.anchor = 'lowerleft' 1379 p.position = p.lowerleft[0], p.lowerleft[1] # copy values (don't copy ref to tuple) 1380 if p.on: 1381 tex = p.texture 1382 1383 if p.size is None: 1384 # Note: 'size' attribute is not supposed to be part of the API, 1385 # so this is naughty. 1386 size = tex.size 1387 else: 1388 size = p.size 1389 1390 # calculate lowerleft corner 1391 lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,size) 1392 1393 # Clear the modelview matrix 1394 gl.glMatrixMode(gl.GL_MODELVIEW) 1395 gl.glPushMatrix() 1396 try: 1397 if p.depth_test: 1398 gl.glEnable(gl.GL_DEPTH_TEST) 1399 else: 1400 gl.glDisable(gl.GL_DEPTH_TEST) 1401 gl.glEnable( gl.GL_TEXTURE_2D ) 1402 1403 # allow max_alpha value to control blending 1404 gl.glEnable( gl.GL_BLEND ) 1405 gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) 1406 1407 if not self.constant_parameters.mipmaps_enabled: 1408 if p.texture_min_filter in TextureStimulusBaseClass._mipmap_modes: 1409 raise RuntimeError("Specified a mipmap mode in texture_min_filter, but mipmaps not enabled.") 1410 self.texture_object.set_min_filter( p.texture_min_filter ) 1411 self.texture_object.set_mag_filter( p.texture_mag_filter ) 1412 self.texture_object.set_wrap_mode_s( p.texture_wrap_s ) 1413 self.texture_object.set_wrap_mode_t( p.texture_wrap_t ) 1414 gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) 1415 1416 translate_vector = p.position 1417 if len(translate_vector) == 2: 1418 translate_vector = translate_vector[0], translate_vector[1], 0 1419 gl.glTranslate(*translate_vector) 1420 gl.glRotate(p.angle,0,0,1) 1421 1422 gl.glColor4f(p.color[0],p.color[1],p.color[2],p.max_alpha) 1423 1424 # draw only if all values are finite 1425 if numpy.alltrue( numpy.isfinite( numpy.concatenate((lowerleft,p.position)) ) ): 1426 l = lowerleft[0] - p.position[0] 1427 r = l + size[0] 1428 b = lowerleft[1] - p.position[1] 1429 t = b + size[1] 1430 1431 tex.update() 1432 1433 if p.mask: 1434 p.mask.draw_masked_quad(tex.buf_lf,tex.buf_rf,tex.buf_bf,tex.buf_tf, # l,r,b,t for texture coordinates 1435 l,r,b,t,0.0) # l,r,b,t in eye coordinates 1436 else: 1437 gl.glBegin(gl.GL_QUADS) 1438 gl.glTexCoord2f(tex.buf_lf,tex.buf_bf) 1439 gl.glVertex2f(l,b) 1440 1441 gl.glTexCoord2f(tex.buf_rf,tex.buf_bf) 1442 gl.glVertex2f(r,b) 1443 1444 gl.glTexCoord2f(tex.buf_rf,tex.buf_tf) 1445 gl.glVertex2f(r,t) 1446 1447 gl.glTexCoord2f(tex.buf_lf,tex.buf_tf) 1448 gl.glVertex2f(l,t) 1449 gl.glEnd() # GL_QUADS 1450 finally: 1451 gl.glPopMatrix()
1452
1453 -class TextureStimulus3D(TextureStimulusBaseClass):
1454 """A textured rectangle placed arbitrarily in 3 space. 1455 1456 Parameters 1457 ========== 1458 depth_test -- perform depth test? (Boolean) 1459 Default: True 1460 lowerleft -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) 1461 Default: (0.0, 0.0, -1.0) 1462 lowerright -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) 1463 Default: (1.0, 0.0, -1.0) 1464 on -- (Boolean) 1465 Default: True 1466 texture -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>) 1467 Inherited from TextureStimulusBaseClass 1468 Default: (determined at runtime) 1469 texture_mag_filter -- OpenGL filter enum (Integer) 1470 Inherited from TextureStimulusBaseClass 1471 Default: GL_LINEAR (9729) 1472 texture_min_filter -- OpenGL filter enum (Integer) 1473 Inherited from TextureStimulusBaseClass 1474 Default: (GL enum determined at runtime) 1475 texture_wrap_s -- OpenGL texture wrap enum (Integer) 1476 Inherited from TextureStimulusBaseClass 1477 Default: (GL enum determined at runtime) 1478 texture_wrap_t -- OpenGL texture wrap enum (Integer) 1479 Inherited from TextureStimulusBaseClass 1480 Default: (GL enum determined at runtime) 1481 upperleft -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) 1482 Default: (0.0, 1.0, -1.0) 1483 upperright -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) 1484 Default: (1.0, 1.0, -1.0) 1485 1486 Constant Parameters 1487 =================== 1488 internal_format -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer) 1489 Default: GL_RGB (6407) 1490 mipmaps_enabled -- Are mipmaps enabled? (Boolean) 1491 Default: True 1492 shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean) 1493 Default: False 1494 """ 1495 1496 parameters_and_defaults = {'on':(True, 1497 ve_types.Boolean), 1498 'lowerleft':((0.0,0.0,-1.0), # in eye coordinates 1499 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), 1500 ve_types.Sequence4(ve_types.Real)), 1501 "vertex position (units: eye coordinates)"), 1502 'lowerright':((1.0,0.0,-1.0), # in eye coordinates 1503 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), 1504 ve_types.Sequence4(ve_types.Real)), 1505 "vertex position (units: eye coordinates)"), 1506 'upperleft':((0.0,1.0,-1.0), # in eye coordinates 1507 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), 1508 ve_types.Sequence4(ve_types.Real)), 1509 "vertex position (units: eye coordinates)"), 1510 'upperright':((1.0,1.0,-1.0), # in eye coordinates 1511 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), 1512 ve_types.Sequence4(ve_types.Real)), 1513 "vertex position (units: eye coordinates)"), 1514 'depth_test':(True, 1515 ve_types.Boolean, 1516 "perform depth test?"), 1517 } 1518
1519 - def draw(self):
1520 p = self.parameters 1521 if p.texture != self._using_texture: # self._using_texture is from TextureStimulusBaseClass 1522 self._reload_texture() 1523 if p.on: 1524 if p.depth_test: 1525 gl.glEnable(gl.GL_DEPTH_TEST) 1526 else: 1527 gl.glDisable(gl.GL_DEPTH_TEST) 1528 1529 gl.glEnable(gl.GL_TEXTURE_2D) 1530 gl.glBindTexture(gl.GL_TEXTURE_2D,self.texture_object.gl_id) 1531 1532 if not self.constant_parameters.mipmaps_enabled: 1533 if p.texture_min_filter in TextureStimulusBaseClass._mipmap_modes: 1534 raise RuntimeError("Specified a mipmap mode in texture_min_filter, but mipmaps not enabled.") 1535 self.texture_object.set_min_filter( p.texture_min_filter ) 1536 self.texture_object.set_mag_filter( p.texture_mag_filter ) 1537 self.texture_object.set_wrap_mode_s( p.texture_wrap_s ) 1538 self.texture_object.set_wrap_mode_t( p.texture_wrap_t ) 1539 1540 # allow max_alpha value to control blending 1541 gl.glEnable( gl.GL_BLEND ) 1542 gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) 1543 gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) 1544 1545 tex = self.parameters.texture 1546 tex.update() 1547 1548 gl.glBegin(gl.GL_QUADS) 1549 gl.glTexCoord2f(tex.buf_lf,tex.buf_bf) 1550 gl.glVertex(*p.lowerleft) 1551 1552 gl.glTexCoord2f(tex.buf_rf,tex.buf_bf) 1553 gl.glVertex(*p.lowerright) 1554 1555 gl.glTexCoord2f(tex.buf_rf,tex.buf_tf) 1556 gl.glVertex(*p.upperright) 1557 1558 gl.glTexCoord2f(tex.buf_lf,tex.buf_tf) 1559 gl.glVertex(*p.upperleft) 1560 gl.glEnd() # GL_QUADS
1561 1562 #################################################################### 1563 # 1564 # Stimulus - Spinning Drum 1565 # 1566 #################################################################### 1567
1568 -class SpinningDrum(TextureStimulusBaseClass):
1569 """Panoramic image texture mapped onto flat rectangle or 3D cylinder. 1570 1571 1572 Parameters 1573 ========== 1574 anchor -- only used when flat: same as anchor parameter of TextureStimulus (String) 1575 Default: center 1576 angular_position -- may be best to clamp in range [0.0,360.0] (Real) 1577 Default: 0.0 1578 contrast -- (Real) 1579 Default: 1.0 1580 drum_center_azimuth -- changes orientation of drum in space (Real) 1581 Default: 0.0 1582 drum_center_elevation -- changes orientation of drum in space (Real) 1583 Default: 0.0 1584 flat -- toggles flat vs. cylinder (Boolean) 1585 Default: False 1586 flat_size -- defaults to texture data size (units: eye coordinates) (Sequence2 of Real) 1587 Default: (determined at runtime) 1588 flip_image -- toggles normal vs. horizonally flipped image (Boolean) 1589 Default: False 1590 height -- height of cyliner, automatically set by texel aspect ratio if < 0. (Real) 1591 Default: -1 1592 num_sides -- (UnsignedInteger) 1593 Default: 50 1594 on -- (Boolean) 1595 Default: True 1596 orientation -- 0=right, 90=up (Real) 1597 Default: 0.0 1598 position -- 3D: position of drum center, 2D (flat): same as position parameter for TextureStimulus (AnyOf(Sequence2 of Real or Sequence3 of Real)) 1599 Default: (0.0, 0.0, 0.0) 1600 radius -- radius if cylinder (not used if flat) (Real) 1601 Default: 1.0 1602 texture -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>) 1603 Inherited from TextureStimulusBaseClass 1604 Default: (determined at runtime) 1605 texture_mag_filter -- OpenGL filter enum (Integer) 1606 Inherited from TextureStimulusBaseClass 1607 Default: GL_LINEAR (9729) 1608 texture_min_filter -- OpenGL filter enum (Integer) 1609 Inherited from TextureStimulusBaseClass 1610 Default: (GL enum determined at runtime) 1611 texture_wrap_s -- OpenGL texture wrap enum (Integer) 1612 Inherited from TextureStimulusBaseClass 1613 Default: (GL enum determined at runtime) 1614 texture_wrap_t -- OpenGL texture wrap enum (Integer) 1615 Inherited from TextureStimulusBaseClass 1616 Default: (GL enum determined at runtime) 1617 1618 Constant Parameters 1619 =================== 1620 internal_format -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer) 1621 Default: GL_RGB (6407) 1622 mipmaps_enabled -- Are mipmaps enabled? (Boolean) 1623 Default: True 1624 shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean) 1625 Default: False 1626 """ 1627 1628 parameters_and_defaults = { 1629 'on':(True, 1630 ve_types.Boolean), 1631 'height':(-1, 1632 ve_types.Real, 1633 'height of cyliner, automatically set by texel aspect ratio if < 0.', 1634 ), 1635 'num_sides':(50, 1636 ve_types.UnsignedInteger), 1637 'angular_position':(0.0, # may be best to clamp [0.0,360.0] 1638 ve_types.Real, 1639 'may be best to clamp in range [0.0,360.0]'), 1640 'contrast':(1.0, 1641 ve_types.Real), 1642 'flat':(False, 1643 ve_types.Boolean, 1644 'toggles flat vs. cylinder'), 1645 'flat_size':(None, 1646 ve_types.Sequence2(ve_types.Real), 1647 "defaults to texture data size (units: eye coordinates)"), 1648 'flip_image':(False, 1649 ve_types.Boolean, 1650 'toggles normal vs. horizonally flipped image'), 1651 'radius':(1.0, 1652 ve_types.Real, 1653 'radius if cylinder (not used if flat)'), 1654 'position':( (0.0,0.0,0.0), 1655 ve_types.AnyOf(ve_types.Sequence2(ve_types.Real), 1656 ve_types.Sequence3(ve_types.Real)), 1657 '3D: position of drum center, 2D (flat): same as position parameter for TextureStimulus'), 1658 'anchor':( 'center', 1659 ve_types.String, 1660 'only used when flat: same as anchor parameter of TextureStimulus', 1661 ), 1662 'drum_center_azimuth':(0.0, 1663 ve_types.Real, 1664 'changes orientation of drum in space', 1665 ), 1666 'drum_center_elevation':(0.0, 1667 ve_types.Real, 1668 'changes orientation of drum in space'), 1669 'orientation':(0.0, 1670 ve_types.Real, 1671 '0=right, 90=up'), 1672 } 1673 1674 __slots__ = ( 1675 'cached_display_list_normal', 1676 'cached_display_list_mirror', 1677 'cached_display_list_num_sides', 1678 'cached_display_list_radius', 1679 'cached_display_list_height', 1680 'texture_stimulus', 1681 ) 1682
1683 - def __init__(self,**kw):
1684 TextureStimulusBaseClass.__init__(self,**kw) 1685 self.cached_display_list_normal = gl.glGenLists(1) # Allocate a new display list 1686 self.cached_display_list_mirror = gl.glGenLists(1) # Allocate a new display list 1687 self.rebuild_display_list()
1688
1689 - def draw(self):
1690 """Redraw the stimulus on every frame. 1691 """ 1692 p = self.parameters 1693 if p.texture != self._using_texture: # self._using_texture is from TextureStimulusBaseClass 1694 self._reload_texture() 1695 self.rebuild_display_list() 1696 if p.on: 1697 # Set OpenGL state variables 1698 gl.glEnable( gl.GL_DEPTH_TEST ) 1699 gl.glEnable( gl.GL_TEXTURE_2D ) # Make sure textures are drawn 1700 gl.glEnable( gl.GL_BLEND ) # Contrast control implemented through blending 1701 1702 # All of the contrast control stuff is somewhat arcane and 1703 # not very clear from reading the code, so here is how it 1704 # works in English. (Not that it makes it any more clear!) 1705 # 1706 # In the final "textured fragment" (before being blended 1707 # to the framebuffer), the color values are equal to those 1708 # of the texture (with the exception of texels around the 1709 # edges which have their amplitudes reduced due to 1710 # anti-aliasing and are intermediate between the color of 1711 # the texture and mid-gray), and the alpha value is set to 1712 # the contrast. Blending occurs, and by choosing the 1713 # appropriate values for glBlendFunc, adds the product of 1714 # fragment alpha (contrast) and fragment color to the 1715 # product of one minus fragment alpha (contrast) and what 1716 # was already in the framebuffer. 1717 1718 gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) 1719 1720 gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL) 1721 1722 # clear modelview matrix 1723 gl.glMatrixMode(gl.GL_MODELVIEW) 1724 gl.glPushMatrix() 1725 try: 1726 gl.glColor4f(0.5,0.5,0.5,p.contrast) # Set the polygons' fragment color (implements contrast) 1727 1728 if not self.constant_parameters.mipmaps_enabled: 1729 if p.texture_min_filter in TextureStimulusBaseClass._mipmap_modes: 1730 raise RuntimeError("Specified a mipmap mode in texture_min_filter, but mipmaps not enabled.") 1731 self.texture_object.set_min_filter( p.texture_min_filter ) 1732 self.texture_object.set_mag_filter( p.texture_mag_filter ) 1733 self.texture_object.set_wrap_mode_s( p.texture_wrap_s ) 1734 self.texture_object.set_wrap_mode_t( p.texture_wrap_t ) 1735 1736 if p.flat: # draw as flat texture on a rectange 1737 lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,p.texture.size) 1738 1739 translate_vector = p.position 1740 if len(translate_vector) == 2: 1741 translate_vector = translate_vector[0], translate_vector[1], 0 1742 gl.glTranslate(*translate_vector) 1743 gl.glRotatef(p.orientation,0,0,1) 1744 1745 if p.flip_image: 1746 raise NotImplementedError("flip_image not yet supported for flat spinning drums.") 1747 w,h = p.texture.size 1748 1749 # calculate texture coordinates based on current angle 1750 tex_phase = p.angular_position/360.0 + 0.5 # offset to match non-flat 1751 tex_phase = tex_phase % 1.0 # make 0 <= tex_phase < 1.0 1752 1753 TINY = 1.0e-10 1754 tex = p.texture 1755 tex.update() 1756 1757 if p.flat_size is None: 1758 size = tex.size 1759 else: 1760 size = p.flat_size 1761 1762 l = lowerleft[0] - p.position[0] 1763 r = l + size[0] 1764 b = lowerleft[1] - p.position[1] 1765 t = b + size[1] 1766 1767 #tex_phase = 0.0 1768 if tex_phase < TINY: # it's effectively zero 1769 1770 gl.glBegin(gl.GL_QUADS) 1771 gl.glTexCoord2f(tex.buf_lf,tex.buf_bf) 1772 gl.glVertex2f(l,b) 1773 1774 gl.glTexCoord2f(tex.buf_rf,tex.buf_bf) 1775 gl.glVertex2f(r,b) 1776 1777 gl.glTexCoord2f(tex.buf_rf,tex.buf_tf) 1778 gl.glVertex2f(r,t) 1779 1780 gl.glTexCoord2f(tex.buf_lf,tex.buf_tf) 1781 gl.glVertex2f(l,t) 1782 gl.glEnd() # GL_QUADS 1783 1784 else: 1785 # Convert tex_phase into texture buffer fraction 1786 buf_break_f = ( (tex.buf_rf - tex.buf_lf) * (1.0-tex_phase) ) + tex.buf_lf 1787 1788 # Convert tex_phase into object coords value 1789 quad_x_break = (r-l) * tex_phase + l 1790 ## quad_x_break = w * tex_phase 1791 1792 gl.glBegin(gl.GL_QUADS) 1793 1794 # First quad 1795 1796 gl.glTexCoord2f(buf_break_f,tex.buf_bf) 1797 gl.glVertex2f(l,b) 1798 1799 gl.glTexCoord2f(tex.buf_rf,tex.buf_bf) 1800 gl.glVertex2f(quad_x_break,b) 1801 1802 gl.glTexCoord2f(tex.buf_rf,tex.buf_tf) 1803 gl.glVertex2f(quad_x_break,t) 1804 1805 gl.glTexCoord2f(buf_break_f,tex.buf_tf) 1806 gl.glVertex2f(l,t) 1807 1808 # Second quad 1809 1810 gl.glTexCoord2f(tex.buf_lf,tex.buf_bf) 1811 gl.glVertex2f(quad_x_break,b) 1812 1813 gl.glTexCoord2f(buf_break_f,tex.buf_bf) 1814 gl.glVertex2f(r,b) 1815 1816 gl.glTexCoord2f(buf_break_f,tex.buf_tf) 1817 gl.glVertex2f(r,t) 1818 1819 gl.glTexCoord2f(tex.buf_lf,tex.buf_tf) 1820 gl.glVertex2f(quad_x_break,t) 1821 gl.glEnd() # GL_QUADS 1822 1823 else: # draw as cylinder 1824 gl.glTranslatef(p.position[0],p.position[1],p.position[2]) 1825 1826 # center the drum on new coordinates 1827 gl.glRotatef(p.drum_center_azimuth,0,-1,0) 1828 gl.glRotatef(p.drum_center_elevation,1,0,0) 1829 1830 # do the orientation 1831 gl.glRotatef(p.orientation,0,0,1) 1832 1833 # turn the coordinate system so we don't have to deal with 1834 # figuring out where to draw the texture relative to drum 1835 gl.glRotatef(p.angular_position,0,-1,0) 1836 1837 if ((p.num_sides != self.cached_display_list_num_sides) or 1838 (p.radius != self.cached_display_list_radius) or 1839 (p.height != self.cached_display_list_height)): 1840 self.rebuild_display_list() 1841 1842 if not p.flip_image: 1843 gl.glCallList(self.cached_display_list_normal) 1844 else: 1845 gl.glCallList(self.cached_display_list_mirror) 1846 finally: 1847 gl.glMatrixMode(gl.GL_MODELVIEW) 1848 gl.glPopMatrix()
1849
1850 - def rebuild_display_list(self):
1851 # (Re)build the display list 1852 # 1853 # A "display list" is a series of OpenGL commands that is 1854 # cached in a list for rapid re-drawing of the same object. 1855 # 1856 # This draws a display list for an approximation of a cylinder. 1857 # The cylinder has "num_sides" sides. The following code 1858 # generates a list of vertices and the texture coordinates 1859 # to be used by those vertices. 1860 r = self.parameters.radius # in OpenGL (arbitrary) units 1861 circum = 2.0*math.pi*r 1862 tex = self.parameters.texture 1863 if self.parameters.height < 0: 1864 h = circum/float(tex.size[0])*float(tex.size[1])/2.0 1865 else: 1866 h = self.parameters.height 1867 1868 num_sides = self.parameters.num_sides 1869 self.cached_display_list_num_sides = num_sides 1870 self.cached_display_list_radius = r 1871 self.cached_display_list_height = self.parameters.height 1872 1873 deltaTheta = 2.0*math.pi / num_sides 1874 for direction in ['normal','mirror']: 1875 if direction == 'normal': 1876 gl.glNewList(self.cached_display_list_normal,gl.GL_COMPILE) 1877 else: 1878 gl.glNewList(self.cached_display_list_mirror,gl.GL_COMPILE) 1879 gl.glBegin(gl.GL_QUADS) 1880 for i in range(num_sides): 1881 # angle of sides 1882 theta1 = i*deltaTheta 1883 theta2 = (i+1)*deltaTheta 1884 # fraction of texture 1885 if direction == 'normal': 1886 frac1 = (tex.buf_lf + (float(i)/num_sides*tex.size[0]))/float(tex.size[0]) 1887 frac2 = (tex.buf_lf + (float(i+1)/num_sides*tex.size[0]))/float(tex.size[0]) 1888 else: 1889 j = num_sides-i-1 1890 frac1 = (tex.buf_lf + (float(j+1)/num_sides*tex.size[0]))/float(tex.size[0]) 1891 frac2 = (tex.buf_lf + (float(j)/num_sides*tex.size[0]))/float(tex.size[0]) 1892 # location of sides 1893 x1 = r*math.cos(theta1) 1894 z1 = r*math.sin(theta1) 1895 x2 = r*math.cos(theta2) 1896 z2 = r*math.sin(theta2) 1897 1898 #Bottom left of quad 1899 gl.glTexCoord2f(frac1, tex.buf_bf) 1900 gl.glVertex4f( x1, -h, z1, 1.0 ) 1901 1902 #Bottom right of quad 1903 gl.glTexCoord2f(frac2, tex.buf_bf) 1904 gl.glVertex4f( x2, -h, z2, 1.0 ) 1905 #Top right of quad 1906 gl.glTexCoord2f(frac2, tex.buf_tf); 1907 gl.glVertex4f( x2, h, z2, 1.0 ) 1908 #Top left of quad 1909 gl.glTexCoord2f(frac1, tex.buf_tf) 1910 gl.glVertex4f( x1, h, z1, 1.0 ) 1911 gl.glEnd() 1912 gl.glEndList()
1913
1914 -class FixationCross(VisionEgg.Core.Stimulus):
1915 """Cross useful for fixation point. 1916 1917 Parameters 1918 ========== 1919 on -- (Boolean) 1920 Default: True 1921 position -- (Sequence2 of Real) 1922 Default: (320, 240) 1923 size -- (Sequence2 of Real) 1924 Default: (64, 64) 1925 1926 Constant Parameters 1927 =================== 1928 texture_size -- (Sequence2 of Real) 1929 Default: (64, 64) 1930 """ 1931 1932 parameters_and_defaults = { 1933 'on':(True, 1934 ve_types.Boolean), 1935 'position':((320,240), 1936 ve_types.Sequence2(ve_types.Real)), 1937 'size':((64,64), 1938 ve_types.Sequence2(ve_types.Real)), 1939 } 1940 constant_parameters_and_defaults = { 1941 'texture_size':((64,64), 1942 ve_types.Sequence2(ve_types.Real)), 1943 } 1944 1945 __slots__ = ( 1946 'texture_stimulus', 1947 ) 1948
1949 - def __init__(self,**kw):
1950 VisionEgg.Core.Stimulus.__init__(self,**kw) 1951 s = self.constant_parameters.texture_size 1952 mid_x = s[0]/2.0 1953 mid_y = s[1]/2.0 1954 texels = Image.new("RGBX",s,(0,0,0,0)) 1955 texels_draw = ImageDraw.Draw(texels) 1956 texels_draw.rectangle( (mid_x-1, 0, mid_x+1, s[1]), fill=(0,0,0,255) ) 1957 texels_draw.rectangle( (0, mid_y-1, s[0], mid_y+1), fill=(0,0,0,255) ) 1958 texels_draw.line( (mid_x, 0, mid_x, s[1]), fill=(255,255,255,255) ) 1959 texels_draw.line( (0, mid_y, s[0], mid_y), fill=(255,255,255,255) ) 1960 self.texture_stimulus = TextureStimulus( texture = Texture(texels), 1961 position = self.parameters.position, 1962 anchor = 'center', 1963 size = self.parameters.size, 1964 internal_format = gl.GL_RGBA, 1965 mipmaps_enabled = False, 1966 texture_min_filter = gl.GL_NEAREST, 1967 texture_mag_filter = gl.GL_NEAREST, 1968 )
1969 - def draw(self):
1970 contained = self.texture_stimulus.parameters #shorthand 1971 my = self.parameters #shorthand 1972 contained.position = my.position 1973 contained.size = my.size 1974 contained.on = my.on 1975 self.texture_stimulus.draw()
1976
1977 -class TextureTooLargeError( RuntimeError ):
1978 pass
1979