VisionEgg.Textures

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