VisionEgg.Core

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

# Source Code for Module VisionEgg.Core

       1  # The Vision Egg: Core
       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  Core Vision Egg functionality.
      13
      14  This module contains the architectural foundations of the Vision Egg.
      15
      16  """
      17
      18  ####################################################################
      19  #
      20  #        Import all the necessary packages
      21  #
      22  ####################################################################
      23
      24  import sys, types, math, time, os               # standard Python modules
      25  import StringIO
      26
      27  import logging                                  # available in Python 2.3
      28
      29  import VisionEgg                                # Vision Egg base module (__init__.py)
      30  import VisionEgg.PlatformDependent              # platform dependent Vision Egg C code
      31  import VisionEgg.ParameterTypes as ve_types     # Vision Egg type checking
      32  import VisionEgg.GLTrace                        # Allows tracing of all OpenGL calls
      33  import VisionEgg.ThreeDeeMath                   # OpenGL math simulation
      34
      35  import pygame                                   # pygame handles OpenGL window setup
      36  import pygame.locals
      37  import pygame.display
      38
      39  import VisionEgg.GL as gl # get all OpenGL stuff in one namespace
      40
      41  import numpy
      42  import numpy.oldnumeric as Numeric # emulate old Numeric Python package
      43
      44  # Define "sum" if it's not available as Python function
      45  try:
      46      sum
      47  except NameError:
      48      import operator
      49 -    def sum( values ):
      50          return reduce(operator.add, values )
      51
      52 -def swap_buffers():
      53      VisionEgg.config._FRAMECOUNT_ABSOLUTE += 1
      54      return pygame.display.flip()
      55
      56  ####################################################################
      57  #
      58  #        Screen
      59  #
      60  ####################################################################
      61
      62 -class Screen(VisionEgg.ClassWithParameters):
      63      """An OpenGL window, possibly displayed across multiple displays.
      64
      65      A Screen instance is an OpenGL window for the Vision Egg to draw
      66      in.  For an instance of Screen to do anything useful, it must
      67      contain one or more instances of the Viewport class and one or
      68      more instances of the Stimulus class.
      69
      70      Currently, only one OpenGL window is supported by the library with
      71      which the Vision Egg initializes graphics (pygame/SDL).  However,
      72      this need not limit display to a single physical display device.
      73      Many video drivers, for example, allow applications to treat two
      74      separate monitors as one large array of contiguous pixels.  By
      75      sizing a window such that it occupies both monitors and creating
      76      separate viewports for the portion of the window on each monitor,
      77      a multiple screen effect can be created.
      78
      79      Public read-only variables
      80      ==========================
      81      size -- Tuple of 2 integers specifying width and height
      82
      83      Parameters
      84      ==========
      85      bgcolor -- background color (AnyOf(Sequence3 of Real or Sequence4 of Real))
      86                 Default: (0.5, 0.5, 0.5, 0.0)
      87
      88      Constant Parameters
      89      ===================
      90      alpha_bits          -- number of bits per pixel for alpha channel. Can be set with VISIONEGG_REQUEST_ALPHA_BITS (UnsignedInteger)
      91                             Default: (determined at runtime)
      92      blue_bits           -- number of bits per pixel for blue channel. Can be set with VISIONEGG_REQUEST_BLUE_BITS (UnsignedInteger)
      93                             Default: (determined at runtime)
      94      double_buffer       -- use double buffering? Can be set with VISIONEGG_DOUBLE_BUFFER (Boolean)
      95                             Default: (determined at runtime)
      96      frameless           -- remove standard window frame? Can be set with VISIONEGG_FRAMELESS_WINDOW (Boolean)
      97                             Default: (determined at runtime)
      98      fullscreen          -- use full screen? Can be set with VISIONEGG_FULLSCREEN (Boolean)
      99                             Default: (determined at runtime)
     100      green_bits          -- number of bits per pixel for green channel. Can be set with VISIONEGG_REQUEST_GREEN_BITS (UnsignedInteger)
     101                             Default: (determined at runtime)
     102      hide_mouse          -- hide the mouse cursor? Can be set with VISIONEGG_HIDE_MOUSE (Boolean)
     103                             Default: (determined at runtime)
     104      is_stereo           -- allocate stereo framebuffers? Can be set with VISIONEGG_REQUEST_STEREO (Boolean)
     105                             Default: (determined at runtime)
     106      maxpriority         -- raise priority? (platform dependent) Can be set with VISIONEGG_MAXPRIORITY (Boolean)
     107                             Default: (determined at runtime)
     108      multisample_samples -- preferred number of multisamples for FSAA (UnsignedInteger)
     109                             Default: (determined at runtime)
     110      preferred_bpp       -- preferred bits per pixel (bit depth) Can be set with VISIONEGG_PREFERRED_BPP (UnsignedInteger)
     111                             Default: (determined at runtime)
     112      red_bits            -- number of bits per pixel for red channel. Can be set with VISIONEGG_REQUEST_RED_BITS (UnsignedInteger)
     113                             Default: (determined at runtime)
     114      size                -- size (units: pixels) Can be set with VISIONEGG_SCREEN_W and VISIONEGG_SCREEN_H (Sequence2 of Real)
     115                             Default: (determined at runtime)
     116      sync_swap           -- synchronize buffer swaps to vertical sync? Can be set with VISIONEGG_SYNC_SWAP (Boolean)
     117                             Default: (determined at runtime)
     118      """
     119
     120      parameters_and_defaults = VisionEgg.ParameterDefinition({
     121          'bgcolor':((0.5,0.5,0.5,0.0),
     122                     ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
     123                                    ve_types.Sequence4(ve_types.Real)),
     124                     'background color',),
     125          })
     126
     127      constant_parameters_and_defaults = VisionEgg.ParameterDefinition({
     128          'size':(None,
     129                  ve_types.Sequence2(ve_types.Real),
     130                  'size (units: pixels) Can be set with VISIONEGG_SCREEN_W and VISIONEGG_SCREEN_H'),
     131          'fullscreen':(None,
     132                        ve_types.Boolean,
     133                        'use full screen? Can be set with VISIONEGG_FULLSCREEN'),
     134          'double_buffer':(None,
     135                           ve_types.Boolean,
     136                           'use double buffering? Can be set with VISIONEGG_DOUBLE_BUFFER'),
     137          'preferred_bpp':(None,
     138                           ve_types.UnsignedInteger,
     139                           'preferred bits per pixel (bit depth) Can be set with VISIONEGG_PREFERRED_BPP'),
     140          'maxpriority':(None,
     141                         ve_types.Boolean,
     142                         'raise priority? (platform dependent) Can be set with VISIONEGG_MAXPRIORITY'),
     143          'hide_mouse':(None,
     144                        ve_types.Boolean,
     145                        'hide the mouse cursor? Can be set with VISIONEGG_HIDE_MOUSE'),
     146          'frameless':(None,
     147                       ve_types.Boolean,
     148                       'remove standard window frame? Can be set with VISIONEGG_FRAMELESS_WINDOW'),
     149          'sync_swap':(None,
     150                       ve_types.Boolean,
     151                       'synchronize buffer swaps to vertical sync? Can be set with VISIONEGG_SYNC_SWAP'),
     152          'red_bits':(None,
     153                      ve_types.UnsignedInteger,
     154                      'number of bits per pixel for red channel. Can be set with VISIONEGG_REQUEST_RED_BITS'),
     155          'green_bits':(None,
     156                      ve_types.UnsignedInteger,
     157                      'number of bits per pixel for green channel. Can be set with VISIONEGG_REQUEST_GREEN_BITS'),
     158          'blue_bits':(None,
     159                      ve_types.UnsignedInteger,
     160                      'number of bits per pixel for blue channel. Can be set with VISIONEGG_REQUEST_BLUE_BITS'),
     161          'alpha_bits':(None,
     162                      ve_types.UnsignedInteger,
     163                      'number of bits per pixel for alpha channel. Can be set with VISIONEGG_REQUEST_ALPHA_BITS'),
     164          'is_stereo':(None,
     165                       ve_types.Boolean,
     166                       'allocate stereo framebuffers? Can be set with VISIONEGG_REQUEST_STEREO'),
     167          'multisample_samples':(None, # support added by Mark Halko
     168                                 ve_types.UnsignedInteger,
     169                                 'preferred number of multisamples for FSAA'),
     170          })
     171
     172      __slots__ = (
     173          '__cursor_visible_func__',
     174          '__pygame_quit__',
     175          '_put_pixels_texture_stimulus',
     176          '_pixel_coord_projection',
     177          )
     178
     179 -    def __init__(self,**kw):
     180          logger = logging.getLogger('VisionEgg.Core')
     181
     182          VisionEgg.ClassWithParameters.__init__(self,**kw)
     183
     184          cp = self.constant_parameters # shorthand
     185          if cp.size is None:
     186              cp.size = (VisionEgg.config.VISIONEGG_SCREEN_W,
     187                         VisionEgg.config.VISIONEGG_SCREEN_H)
     188          if cp.double_buffer is None:
     189              cp.double_buffer = VisionEgg.config.VISIONEGG_DOUBLE_BUFFER
     190          if cp.fullscreen is None:
     191              cp.fullscreen = VisionEgg.config.VISIONEGG_FULLSCREEN
     192          if cp.preferred_bpp is None:
     193              cp.preferred_bpp = VisionEgg.config.VISIONEGG_PREFERRED_BPP
     194          if cp.maxpriority is None:
     195              cp.maxpriority = VisionEgg.config.VISIONEGG_MAXPRIORITY
     196          if cp.hide_mouse is None:
     197              cp.hide_mouse = VisionEgg.config.VISIONEGG_HIDE_MOUSE
     198          if cp.frameless is None:
     199              cp.frameless = VisionEgg.config.VISIONEGG_FRAMELESS_WINDOW
     200          if cp.sync_swap is None:
     201              cp.sync_swap = VisionEgg.config.VISIONEGG_SYNC_SWAP
     202          if cp.red_bits is None:
     203              cp.red_bits = VisionEgg.config.VISIONEGG_REQUEST_RED_BITS
     204          if cp.green_bits is None:
     205              cp.green_bits = VisionEgg.config.VISIONEGG_REQUEST_GREEN_BITS
     206          if cp.blue_bits is None:
     207              cp.blue_bits = VisionEgg.config.VISIONEGG_REQUEST_BLUE_BITS
     208          if cp.alpha_bits is None:
     209              cp.alpha_bits = VisionEgg.config.VISIONEGG_REQUEST_ALPHA_BITS
     210          if cp.is_stereo is None:
     211              cp.is_stereo = VisionEgg.config.VISIONEGG_REQUEST_STEREO
     212          if cp.multisample_samples is None:
     213               cp.multisample_samples = VisionEgg.config.VISIONEGG_MULTISAMPLE_SAMPLES
     214
     215          if VisionEgg.config.SYNCLYNC_PRESENT:
     216              global synclync # import into global namespace
     217              import synclync
     218              try:
     219                  VisionEgg.config._SYNCLYNC_CONNECTION = synclync.SyncLyncConnection()
     220              except synclync.SyncLyncError, x:
     221                  logger.warning( "Could not connect to SyncLync device (SyncLyncError: %s)."%str(x))
     222                  VisionEgg.config._SYNCLYNC_CONNECTION = None
     223              else:
     224                  logger.info( "Connected to SyncLync device" )
     225          else:
     226              VisionEgg.config._SYNCLYNC_CONNECTION = None
     227
     228          # Attempt to synchronize buffer swapping with vertical sync
     229          if cp.sync_swap:
     230              sync_success = VisionEgg.PlatformDependent.sync_swap_with_vbl_pre_gl_init()
     231
     232          # Initialize pygame stuff
     233  #        if sys.platform == "darwin": # bug in Mac OS X version of pygame
     234  #            pygame.init()
     235          pygame.display.init()
     236
     237          if hasattr(pygame.display,"gl_set_attribute"):
     238              pygame.display.gl_set_attribute(pygame.locals.GL_RED_SIZE,cp.red_bits)
     239              pygame.display.gl_set_attribute(pygame.locals.GL_GREEN_SIZE,cp.green_bits)
     240              pygame.display.gl_set_attribute(pygame.locals.GL_BLUE_SIZE,cp.blue_bits)
     241              pygame.display.gl_set_attribute(pygame.locals.GL_ALPHA_SIZE,cp.alpha_bits)
     242              pygame.display.gl_set_attribute(pygame.locals.GL_STEREO,cp.is_stereo)
     243              #Request FSAA
     244              if cp.multisample_samples > 0 :
     245                  pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLEBUFFERS,1)
     246                  pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLESAMPLES,cp.multisample_samples)
     247          else:
     248              logger.debug("Could not request or query exact bit depths, "
     249                           "alpha or stereo because you need "
     250                           "pygame release 1.4.9 or greater. This is "
     251                           "only of concern if you use a stimulus that "
     252                           "needs this. In that case, the stimulus "
     253                           "should check for the desired feature(s).")
     254
     255          if not hasattr(pygame.display,"set_gamma_ramp"):
     256              logger.debug("set_gamma_ramp function not available "
     257                           "because you need pygame release 1.5 or "
     258                           "greater. This is only of concern if you "
     259                           "need this feature.")
     260          pygame.display.set_caption("Vision Egg")
     261
     262          flags = pygame.locals.OPENGL
     263          if cp.double_buffer:
     264              flags = flags | pygame.locals.DOUBLEBUF
     265          if cp.fullscreen:
     266              flags = flags | pygame.locals.FULLSCREEN
     267          if cp.frameless:
     268              flags = flags | pygame.locals.NOFRAME
     269
     270          try_bpp = cp.preferred_bpp
     271
     272          append_str = ""
     273          if cp.fullscreen:
     274              screen_mode = "fullscreen"
     275          else:
     276              screen_mode = "window"
     277          if hasattr(pygame.display,"gl_set_attribute"):
     278              append_str = " (%d %d %d %d RGBA)."%(cp.red_bits,
     279                                                   cp.green_bits,
     280                                                   cp.blue_bits,
     281                                                   cp.alpha_bits)
     282
     283          logger.info("Requesting %s %d x %d %d bpp%s"%
     284                      (screen_mode,self.size[0],self.size[1],
     285                       try_bpp,append_str))
     286
     287          pygame.display.set_mode(self.size, flags, try_bpp )
     288          # set a global variable so we know workaround avoid pygame bug
     289          VisionEgg.config._pygame_started = 1
     290
     291          try:
     292              if sys.platform != 'darwin':
     293                  pygame.display.set_icon(pygame.transform.scale(pygame.image.load(
     294                      os.path.join(VisionEgg.config.VISIONEGG_SYSTEM_DIR,
     295                                   'data','visionegg.bmp')).convert(),(32,32)))
     296              else:
     297                  import AppKit # requires PyObjC, which is required by pygame osx
     298                  im = AppKit.NSImage.alloc()
     299                  im.initWithContentsOfFile_(
     300                      os.path.join(VisionEgg.config.VISIONEGG_SYSTEM_DIR,
     301                                   'data','visionegg.tif'))
     302                  AppKit.NSApplication.setApplicationIconImage_(AppKit.NSApp(),im)
     303
     304          except Exception,x:
     305              logger.info("Error while trying to set_icon: %s: %s"%
     306                          (str(x.__class__),str(x)))
     307
     308          global gl_vendor, gl_renderer, gl_version
     309          gl_vendor = gl.glGetString(gl.GL_VENDOR)
     310          gl_renderer = gl.glGetString(gl.GL_RENDERER)
     311          gl_version = gl.glGetString(gl.GL_VERSION)
     312
     313          logger.info("OpenGL %s, %s, %s (PyOpenGL %s)"%
     314                      (gl_version, gl_renderer, gl_vendor, gl.__version__))
     315
     316          if gl_renderer == "GDI Generic" and gl_vendor == "Microsoft Corporation":
     317              logger.warning("Using default Microsoft Windows OpenGL "
     318                             "drivers.  Please (re-)install the latest "
     319                             "video drivers from your video card "
     320                             "manufacturer to get hardware accelerated "
     321                             "performance.")
     322          if gl_renderer == "Mesa GLX Indirect" and gl_vendor == "VA Linux Systems, Inc.":
     323              logger.warning("Using default Mesa GLX drivers. Please "
     324                             "(re-)install the latest video drivers from "
     325                             "your video card manufacturer or DRI "
     326                             "project to get hardware accelarated "
     327                             "performance.")
     328          # Set values to unknown and fill based on OpenGL values
     329          cp.red_bits = None
     330          cp.green_bits = None
     331          cp.blue_bits = None
     332          cp.alpha_bits = None
     333          cp.is_stereo = None
     334          got_bpp = pygame.display.Info().bitsize
     335          append_str = ''
     336          if hasattr(pygame.display,"gl_get_attribute"):
     337              # Fill in values as known
     338              cp.red_bits = pygame.display.gl_get_attribute(pygame.locals.GL_RED_SIZE)
     339              cp.green_bits = pygame.display.gl_get_attribute(pygame.locals.GL_GREEN_SIZE)
     340              cp.blue_bits = pygame.display.gl_get_attribute(pygame.locals.GL_BLUE_SIZE)
     341              cp.alpha_bits = pygame.display.gl_get_attribute(pygame.locals.GL_ALPHA_SIZE)
     342              cp.is_stereo = pygame.display.gl_get_attribute(pygame.locals.GL_STEREO)
     343              if cp.is_stereo: stereo_string = ' stereo'
     344              else: stereo_string = ''
     345              append_str = " (%d %d %d %d RGBA%s)"%(
     346                  cp.red_bits,cp.green_bits,cp.blue_bits,cp.alpha_bits,
     347                  stereo_string)
     348          logger.info("Video system reports %d bpp%s."%(got_bpp,append_str))
     349          if got_bpp < try_bpp:
     350              logger.warning("Video system reports %d bits per pixel, "
     351                             "while your program requested %d. Can you "
     352                             "adjust your video drivers?"%(got_bpp,
     353                             try_bpp))
     354          # Save the address of these functions so they can be called
     355          # when closing the screen.
     356          self.__cursor_visible_func__ = pygame.mouse.set_visible
     357          self.__pygame_quit__ = pygame.quit
     358
     359          #Check FSAA requests
     360          if cp.multisample_samples>0 :
     361              if hasattr(pygame.display,"gl_set_attribute"):
     362                  got_ms_buf = pygame.display.gl_get_attribute(pygame.locals.GL_MULTISAMPLEBUFFERS)
     363                  got_ms_samp = pygame.display.gl_get_attribute(pygame.locals.GL_MULTISAMPLESAMPLES)
     364                  if got_ms_samp < cp.multisample_samples :
     365                      logger.warning("Video system reports %d multisample samples, "
     366                                     "while you requested %d.  FSAA requires "
     367                                     "SDL > 1.2.6, check that it is installed."%(got_ms_samp, cp.multisample_samples))
     368
     369          # Attempt to synchronize buffer swapping with vertical sync again
     370          if cp.sync_swap:
     371              if not sync_success:
     372                  if not VisionEgg.PlatformDependent.sync_swap_with_vbl_post_gl_init():
     373                      cp.sync_swap = False
     374                      logger.warning("Unable to detect or automatically "
     375                                     "synchronize buffer swapping with "
     376                                     "vertical retrace. May be possible "
     377                                     "by manually adjusting video "
     378                                     "drivers. (Look for 'Enable "
     379                                     "Vertical Sync' or similar.) If "
     380                                     "buffer swapping is not "
     381                                     "synchronized, frame by frame "
     382                                     "control will not be possible. "
     383                                     "Because of this, you will probably "
     384                                     "get a warning about calculated "
     385                                     "frames per second different than "
     386                                     "specified.")
     387          # Check previously made OpenGL assumptions now that we have OpenGL window
     388          post_gl_init()
     389
     390          if cp.hide_mouse:
     391              self.__cursor_visible_func__(0)
     392
     393          # Attempt to set maximum priority (This may not be the best
     394          # place in the code to do it because it's an application-level
     395          # thing, not a screen-level thing, but it fits reasonably well
     396          # here for now.)
     397          if cp.maxpriority:
     398              VisionEgg.PlatformDependent.set_priority() # defaults to max priority
     399
     400          if hasattr(VisionEgg.config,'_open_screens'):
     401              VisionEgg.config._open_screens.append(self)
     402          else:
     403              VisionEgg.config._open_screens = [self]
     404
     405      # Use Python descriptors (introduced in Python 2.2) to link size
     406      # attribute to constant_parameters.size.
     407 -    def get_size(self): return self.constant_parameters.size
     408 -    def set_size(self, value): raise RuntimeError("Attempting to set read-only value")
     409      size = property(get_size,set_size)
     410
     411 -    def get_framebuffer_as_image(self,
     412                                   buffer='back',
     413                                   format=gl.GL_RGB,
     414                                   position=(0,0),
     415                                   anchor='lowerleft',
     416                                   size=None, # if None, use full screen
     417                                   ):
     418          """get pixel values from framebuffer to PIL image"""
     419          import Image # Could import this at the beginning of the file, but it breaks sometimes.
     420
     421          fb_array = self.get_framebuffer_as_array(buffer=buffer,
     422                                                   format=format,
     423                                                   position=position,
     424                                                   anchor=anchor,
     425                                                   size=size,
     426                                                   )
     427          size = fb_array.shape[1], fb_array.shape[0]
     428          if format == gl.GL_RGB:
     429              pil_mode = 'RGB'
     430          elif format == gl.GL_RGBA:
     431              pil_mode = 'RGBA'
     432          fb_image = Image.fromstring(pil_mode,size,fb_array.tostring())
     433          fb_image = fb_image.transpose( Image.FLIP_TOP_BOTTOM )
     434          return fb_image
     435
     436 -    def get_framebuffer_as_array(self,
     437                                   buffer='back',
     438                                   format=gl.GL_RGB,
     439                                   position=(0,0),
     440                                   anchor='lowerleft',
     441                                   size=None, # if None, use full screen
     442                                   ):
     443          """get pixel values from framebuffer to Numeric array"""# (SLOW)"""
     444          if size is None:
     445              size = self.size
     446          lowerleft = VisionEgg._get_lowerleft(position,anchor,size)
     447          if buffer == 'front':
     448              gl.glReadBuffer( gl.GL_FRONT )
     449          elif buffer == 'back':
     450              gl.glReadBuffer( gl.GL_BACK )
     451          else:
     452              raise ValueError('No support for "%s" framebuffer'%buffer)
     453
     454          # according to Apple's glGrab demo, this should force DMA transfers:
     455          gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 4)
     456          gl.glPixelStorei(gl.GL_PACK_ROW_LENGTH, 0)
     457          gl.glPixelStorei(gl.GL_PACK_SKIP_ROWS, 0)
     458          gl.glPixelStorei(gl.GL_PACK_SKIP_PIXELS, 0)
     459          if gl_version >= '1.2' and hasattr(gl,'GL_BGRA'):
     460              framebuffer_pixels = gl.glReadPixels(lowerleft[0],lowerleft[1],
     461                                                   size[0],size[1],
     462                                                   gl.GL_BGRA,
     463                                                   gl.GL_UNSIGNED_INT_8_8_8_8_REV)
     464              raw_format = 'BGRA'
     465          else:
     466              framebuffer_pixels = gl.glReadPixels(lowerleft[0],lowerleft[1],
     467                                                   size[0],size[1],
     468                                                   gl.GL_RGBA,
     469                                                   gl.GL_UNSIGNED_BYTE)
     470              raw_format = 'RGBA'
     471          fb_array = Numeric.fromstring(framebuffer_pixels,Numeric.UInt8)
     472          fb_array = Numeric.reshape(fb_array,(size[1],size[0],4))
     473          # These work, but I don't know why.  There must be something I
     474          # don't understand about byte ordering!
     475          if format == gl.GL_RGB:
     476              if raw_format == 'BGRA':
     477                  fb_array = fb_array[:,:,1:]
     478              elif raw_format == 'RGBA':
     479                  fb_array = fb_array[:,:,:3]
     480          elif format == gl.GL_RGBA:
     481              if raw_format == 'BGRA':
     482                  alpha = fb_array[:,:,0,Numeric.NewAxis]
     483                  fb_array = fb_array[:,:,1:]
     484                  fb_array = Numeric.concatenate( (fb_array,alpha), axis=2)
     485              elif raw_format == 'RGBA':
     486                  pass
     487          else:
     488              raise NotImplementedError("Only RGB and RGBA formats currently supported")
     489          return fb_array
     490
     491 -    def put_pixels(self,
     492                     pixels=None,
     493                     position=(0,0),
     494                     anchor='lowerleft',
     495                     scale_x=1.0, # "zoom" the pixels
     496                     scale_y=1.0, # "zoom" the pixels
     497                     texture_min_filter=gl.GL_NEAREST, # only used if scale < 1.0
     498                     texture_mag_filter=gl.GL_NEAREST, # only used if scale > 1.0
     499                     internal_format=gl.GL_RGB, # pixel data converted to this format in texture (gl.GL_RGBA also useful)
     500                     ):
     501          """Put pixel values to screen.
     502
     503          Pixel values become texture data using the VisionEgg.Textures
     504          module.  Any source of texture data accepted by that module is
     505          accepted here.
     506
     507          This function could be sped up by allocating a fixed OpenGL texture object.
     508
     509          """
     510
     511          import VisionEgg.Textures # import here to avoid import loop
     512          make_new_texture_object = 0
     513          if not hasattr(self, "_put_pixels_texture_stimulus"):
     514              make_new_texture_object = 1
     515          else:
     516              if internal_format != self._put_pixels_texture_stimulus.constant_parameters.internal_format:
     517                  make_new_texture_object = 1
     518          if make_new_texture_object:
     519              # For speed, don't do this on anything other than 1st run
     520              texture = VisionEgg.Textures.Texture(pixels)
     521              on_screen_size = (texture.size[0]*scale_x, texture.size[1]*scale_y)
     522              t = VisionEgg.Textures.TextureStimulus(texture=texture,
     523                                                     position=position,
     524                                                     anchor=anchor,
     525                                                     size=on_screen_size,
     526                                                     mipmaps_enabled=0,
     527                                                     texture_min_filter=texture_min_filter,
     528                                                     texture_mag_filter=texture_mag_filter,
     529                                                     internal_format = internal_format,
     530                                                     )
     531              self._put_pixels_texture_stimulus = t # rename
     532              self._pixel_coord_projection = OrthographicProjection(left=0,
     533                                                                    right=self.size[0],
     534                                                                    bottom=0,
     535                                                                    top=self.size[1],
     536                                                                    z_clip_near=0.0,
     537                                                                    z_clip_far=1.0)
     538          else:
     539              # We've run once before and therefore already have a
     540              # texture stimulus. (XXX In the future, make use of
     541              # already assigned texture object and use put_sub_image
     542              # for speed.)
     543              self._put_pixels_texture_stimulus.parameters.texture = VisionEgg.Textures.Texture(pixels)
     544
     545          self._pixel_coord_projection.push_and_set_gl_projection() # Save projection
     546          self._put_pixels_texture_stimulus.draw() # Draw pixels as texture
     547
     548          gl.glMatrixMode(gl.GL_PROJECTION) # Restore projection
     549          gl.glPopMatrix()
     550
     551 -    def query_refresh_rate(self):
     552          return VisionEgg.PlatformDependent.query_refresh_rate(self)
     553
     554 -    def measure_refresh_rate(self,average_over_seconds=0.1):
     555          """Measure the refresh rate. Assumes swap buffers synced."""
     556          start_time = VisionEgg.time_func()
     557          duration_sec = 0.0
     558          num_frames = 0
     559          while duration_sec < average_over_seconds:
     560              swap_buffers()
     561              now = VisionEgg.time_func()
     562              num_frames += 1
     563              duration_sec = now - start_time
     564          if duration_sec > 0.0:
     565              fps = num_frames / duration_sec
     566          else:
     567              fps = 0.0
     568          return fps
     569
     570 -    def clear(self):
     571          """Called by Presentation instance. Clear the screen."""
     572
     573          c = self.parameters.bgcolor # Shorthand
     574          if len(c) == 4:
     575              gl.glClearColor(*c)
     576          else:
     577              gl.glClearColor(c[0],c[1],c[2],0.0) # set alpha to 0.0 unless specified
     578          gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
     579
     580 -    def make_current(self):
     581          """Called by Viewport instance. Makes screen active for drawing.
     582
     583          Can not be implemented until multiple screens are possible."""
     584          pass
     585
     586 -    def set_gamma_ramp(self,*args,**kw):
     587          """Set the gamma_ramp, if supported.
     588
     589          Call pygame.display.set_gamma_ramp, if available.
     590
     591          Returns True on success, False otherwise."""
     592          if not hasattr(pygame.display,"set_gamma_ramp"):
     593              logger = logging.getLogger('VisionEgg.Core')
     594              logger.error("Need pygame 1.5 or greater for set_gamma_ramp function")
     595              return False
     596          if pygame.display.set_gamma_ramp(*args,**kw):
     597              return True
     598          else:
     599              return False
     600
     601 -    def close(self):
     602          """Close the screen.
     603
     604          You can call this to close the screen.  Not necessary during
     605          normal operation because it gets automatically deleted."""
     606          # Close pygame if possible
     607          if hasattr(VisionEgg.config,'_open_screens'):
     608              if self in VisionEgg.config._open_screens:
     609                  VisionEgg.config._open_screens.remove(self)
     610              if len(VisionEgg.config._open_screens) == 0:
     611                  # no more open screens
     612                  if hasattr(self,"__cursor_visible_func__"):
     613                      self.__cursor_visible_func__(1)
     614                  pygame.quit()
     615          # No access to the cursor visible function anymore
     616          if hasattr(self,"__cursor_visible_func__"):
     617              del self.__cursor_visible_func__
     618
     619 -    def __del__(self):
     620          # Make sure mouse is visible after screen closed.
     621          if hasattr(self,"__cursor_visible_func__"):
     622              try:
     623                  self.__cursor_visible_func__(1)
     624                  self.__pygame_quit__()
     625              except pygame.error, x:
     626                  if str(x) != 'video system not initialized':
     627                      raise
     628
     629 -    def create_default():
     630          """Alternative constructor using configuration variables.
     631
     632          Most of the time you can create and instance of Screen using
     633          this method.  If your script needs explicit control of the
     634          Screen parameters, initialize with the normal constructor.
     635
     636          Uses VisionEgg.config.VISIONEGG_GUI_INIT to determine how the
     637          default screen parameters should are determined.  If this
     638          value is 0, the values from VisionEgg.cfg are used.  If this
     639          value is 1, a GUI panel is opened and allows manual settings
     640          of the screen parameters.  """
     641
     642          global VisionEgg # Allow "VisionEgg.config" instead of just "config"
     643          if VisionEgg.config.VISIONEGG_GUI_INIT:
     644              import VisionEgg.GUI # Could import in beginning, but no need if not using GUI
     645              window = VisionEgg.GUI.GraphicsConfigurationWindow()
     646              window.mainloop() # All this does is adjust VisionEgg.config
     647              if not window.clicked_ok:
     648                  sys.exit() # User wants to quit
     649          screen = None
     650          try:
     651              screen = Screen(size=(VisionEgg.config.VISIONEGG_SCREEN_W,
     652                                    VisionEgg.config.VISIONEGG_SCREEN_H),
     653                              fullscreen=VisionEgg.config.VISIONEGG_FULLSCREEN,
     654                              preferred_bpp=VisionEgg.config.VISIONEGG_PREFERRED_BPP,
     655                              bgcolor=(0.5,0.5,0.5,0.0),
     656                              maxpriority=VisionEgg.config.VISIONEGG_MAXPRIORITY,
     657                              frameless=VisionEgg.config.VISIONEGG_FRAMELESS_WINDOW,
     658                              hide_mouse=VisionEgg.config.VISIONEGG_HIDE_MOUSE)
     659          finally:
     660              if screen is None:
     661                  # Opening screen failed.  Let's do any cleanup that Screen.__init__ missed.
     662                  try:
     663                      pygame.mouse.set_visible(1) # make sure mouse is visible
     664                      pygame.quit() # close screen
     665                  except pygame.error, x:
     666                      if str(x) != 'video system not initialized':
     667                          raise
     668
     669          if screen is None:
     670              raise RuntimeError("Screen open failed. Check your error log for a traceback.")
     671
     672          gamma_source = VisionEgg.config.VISIONEGG_GAMMA_SOURCE.lower()
     673          if gamma_source != 'none':
     674              if gamma_source == 'invert':
     675                  native_red = VisionEgg.config.VISIONEGG_GAMMA_INVERT_RED
     676                  native_green = VisionEgg.config.VISIONEGG_GAMMA_INVERT_GREEN
     677                  native_blue = VisionEgg.config.VISIONEGG_GAMMA_INVERT_BLUE
     678                  red = screen._create_inverted_gamma_ramp( native_red )
     679                  green = screen._create_inverted_gamma_ramp( native_green )
     680                  blue = screen._create_inverted_gamma_ramp( native_blue )
     681                  gamma_set_string = "linearized gamma lookup tables to correct "+\
     682                                     "monitor with native gammas (%f, %f, %f) RGB"%(
     683                      native_red,
     684                      native_green,
     685                      native_blue)
     686              elif gamma_source == 'file':
     687                  filename = VisionEgg.config.VISIONEGG_GAMMA_FILE
     688                  red, green, blue = screen._open_gamma_file(filename)
     689                  gamma_set_string = "set gamma lookup tables from data in file %s"%os.path.abspath(filename)
     690              else:
     691                  raise ValueError("Unknown gamma source: '%s'"%gamma_source)
     692              logger = logging.getLogger('VisionEgg.Core')
     693              if not screen.set_gamma_ramp(red,green,blue):
     694                  logger.warning( "Setting gamma ramps failed." )
     695              else:
     696                  logger.info( "Gamma set sucessfully: %s"%gamma_set_string )
     697          return screen
     698      create_default = staticmethod(create_default)
     699
     700 -    def _create_inverted_gamma_ramp(self, gamma):
     701          # c is a constant scale factor.  It is always 1.0 when
     702          # luminance is normalized to range [0.0,1.0] and input units
     703          # in range [0.0,1.0], as is OpenGL standard.
     704          c = 1.0
     705          inc = 1.0/255
     706          target_luminances = Numeric.arange(0.0,1.0+inc,inc)
     707          output_ramp = Numeric.zeros(target_luminances.shape,Numeric.Int)
     708          for i in range(len(target_luminances)):
     709              L = target_luminances[i]
     710              if L == 0.0:
     711                  v_88fp = 0
     712              else:
     713                  v = math.exp( (math.log(L) - math.log(c)) /gamma)
     714                  v_88fp = int(round((v*255) * 256)) # convert to from [0.0,1.0] floating point to [0.0,255.0] 8.8 fixed point
     715              output_ramp[i] = v_88fp # 8.8 fixed point format
     716          return list(output_ramp) # convert to Python list
     717
     718 -    def _open_gamma_file(self, filename):
     719          fd = open(filename,"r")
     720          gamma_values = []
     721          for line in fd.readlines():
     722              line = line.strip() # remove leading/trailing whitespace
     723              if line.startswith("#"): # comment, ignore
     724                  continue
     725              gamma_values.append( map(int, line.split() ) )
     726              if len(gamma_values[-1]) != 3:
     727                  raise ValueError("expected 3 values per gamma entry")
     728          if len(gamma_values) != 256:
     729              raise ValueError("expected 256 gamma entries")
     730          red, green, blue = zip(*gamma_values)
     731          return red,green,blue
     732
     733 -def get_default_screen():
     734      """Make an instance of Screen using a GUI window or from config file."""
     735      return Screen.create_default()
     736
     737  ####################################################################
     738  #
     739  #        Projection and derived classes
     740  #
     741  ####################################################################
     742
     743 -class ProjectionBaseClass(VisionEgg.ClassWithParameters):
     744      """Converts stimulus coordinates to viewport coordinates.
     745
     746      This is an abstract base class which should be subclassed for
     747      actual use.
     748
     749      Parameters
     750      ==========
     751      matrix -- matrix specifying projection (Sequence4x4 of Real)
     752                Default: [[1 0 0 0]
     753                          [0 1 0 0]
     754                          [0 0 1 0]
     755                          [0 0 0 1]]
     756      """
     757
     758      # WARNING: This implementation should really get cleaned up and
     759      # NOT use OpenGL except when purposefully setting matrices.
     760
     761      parameters_and_defaults = VisionEgg.ParameterDefinition({
     762          'matrix':( Numeric.identity(4), # 4x4 identity matrix
     763                     ve_types.Sequence4x4(ve_types.Real),
     764                     'matrix specifying projection'),
     765          })
     766
     767      __slots__ = (
     768          'projection_type',
     769          )
     770
     771 -    def __init__(self,**kw):
     772          VisionEgg.ClassWithParameters.__init__(self,**kw)
     773          self.projection_type = None # derived class must override
     774
     775 -    def _get_matrix_type(self):
     776          if self.projection_type == gl.GL_PROJECTION:
     777              return gl.GL_PROJECTION_MATRIX
     778          elif self.projection_type == gl.GL_MODELVIEW:
     779              return gl.GL_MODELVIEW_MATRIX
     780
     781 -    def apply_to_gl(self):
     782          """Set the OpenGL projection matrix."""
     783          gl.glMatrixMode(self.projection_type)
     784          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     785
     786 -    def set_gl_modelview(self):
     787          """Set the OpenGL modelview matrix."""
     788          gl.glMatrixMode(gl.GL_MODELVIEW)
     789          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     790
     791 -    def set_gl_projection(self):
     792          """Set the OpenGL projection matrix."""
     793          gl.glMatrixMode(gl.GL_PROJECTION)
     794          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     795
     796 -    def push_and_set_gl_projection(self):
     797          """Set the OpenGL projection matrix, pushing current projection matrix to stack."""
     798          gl.glMatrixMode(self.projection_type) # Set OpenGL matrix state to modify the projection matrix
     799          gl.glPushMatrix()
     800          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     801
     802 -    def translate(self,x,y,z):
     803          """Compose a translation and set the OpenGL projection matrix."""
     804          gl.glMatrixMode(self.projection_type) # Set OpenGL matrix state to modify the projection matrix
     805          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     806          gl.glTranslatef(x,y,z)
     807          self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
     808
     809 -    def stateless_translate(self,x,y,z):
     810          """Compose a translation without changing OpenGL state."""
     811          M = VisionEgg.ThreeDeeMath.TransformMatrix(self.parameters.matrix)
     812          M.translate(x,y,z)
     813          self.parameters.matrix = M.get_matrix()
     814
     815 -    def rotate(self,angle_degrees,x,y,z):
     816          """Compose a rotation and set the OpenGL projection matrix."""
     817          gl.glMatrixMode(self.projection_type) # Set OpenGL matrix state to modify the projection matrix
     818          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     819          gl.glRotatef(angle_degrees,x,y,z)
     820          self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
     821
     822 -    def stateless_rotate(self,angle_degrees,x,y,z):
     823          """Compose a rotation without changing OpenGL state."""
     824          M = VisionEgg.ThreeDeeMath.TransformMatrix(self.parameters.matrix)
     825          M.rotate(angle_degrees,x,y,z)
     826          self.parameters.matrix = M.get_matrix()
     827
     828 -    def scale(self,x,y,z):
     829          """Compose a rotation and set the OpenGL projection matrix."""
     830          gl.glMatrixMode(self.projection_type) # Set OpenGL matrix state to modify the projection matrix
     831          gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     832          gl.glScalef(x,y,z)
     833          self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
     834
     835 -    def stateless_scale(self,x,y,z):
     836          """Compose a rotation without changing OpenGL state."""
     837          M = VisionEgg.ThreeDeeMath.TransformMatrix(self.parameters.matrix)
     838          M.scale(x,y,z)
     839          self.parameters.matrix = M.get_matrix()
     840
     841 -    def get_matrix(self):
     842          return self.parameters.matrix
     843
     844 -    def look_at(self, eye, center, up ):
     845          # Basically the same as gluLookAt
     846          def normalize(vec):
     847              numpy_vec = numpy.asarray(vec)
     848              mag = math.sqrt(numpy.sum(numpy_vec**2))
     849              return numpy_vec / mag
     850          def cross(vec1,vec2):
     851              return ( vec1[1]*vec2[2] - vec1[2]*vec2[1],
     852                       vec1[2]*vec2[0] - vec1[0]*vec2[2],
     853                       vec1[0]*vec2[1] - vec1[1]*vec2[0] )
     854          forward = numpy.array(( center[0] - eye[0],
     855                                  center[1] - eye[1],
     856                                  center[2] - eye[2]),'f')
     857          forward = normalize(forward)
     858          side = cross(forward,up)
     859          side = normalize(side)
     860          new_up = cross(side,forward) # recompute up
     861          # XXX I might have to transpose this matrix
     862          m = Numeric.array([[side[0], new_up[0], -forward[0], 0.0],
     863                             [side[1], new_up[1], -forward[1], 0.0],
     864                             [side[2], new_up[2], -forward[2], 0.0],
     865                             [    0.0,       0.0,         0.0, 1.0]])
     866          # XXX This should get optimized -- don't do it in OpenGL
     867          gl.glMatrixMode(self.projection_type) # Set OpenGL matrix state to modify the projection matrix
     868          gl.glPushMatrix() # save current matrix
     869          try:
     870              gl.glLoadMatrixf(self.parameters.matrix) # Need PyOpenGL >= 2.0
     871              gl.glMultMatrixf(m)
     872              gl.glTranslatef(-eye[0],-eye[1],-eye[2])
     873              self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
     874          finally:
     875              gl.glPopMatrix() # save current matrix
     876
     877 -    def eye_2_clip(self,eye_coords_vertex):
     878          """Transform eye coordinates to clip coordinates"""
     879          m = Numeric.array(self.parameters.matrix)
     880          v = Numeric.array(eye_coords_vertex)
     881          homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
     882          r = numpy.dot(homog,m)
     883          if len(homog.shape) > len(v.shape):
     884              r = Numeric.reshape(r,(4,))
     885          return r
     886 -    def clip_2_norm_device(self,clip_coords_vertex):
     887          """Transform clip coordinates to normalized device coordinates"""
     888          v = numpy.array(clip_coords_vertex)
     889          homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
     890          err=numpy.seterr(all='ignore')
     891          r = (homog/homog[:,3,numpy.newaxis])[:,:3]
     892          numpy.seterr(**err)
     893          if len(homog.shape) > len(v.shape):
     894              r = Numeric.reshape(r,(3,))
     895          return r
     896 -    def eye_2_norm_device(self,eye_coords_vertex):
     897          """Transform eye coordinates to normalized device coordinates"""
     898          return self.clip_2_norm_device(self.eye_2_clip(eye_coords_vertex))
     899
     900 -    def apply_to_vertex(self,vertex):
     901          """Perform multiplication on vertex to get transformed result"""
     902          M = VisionEgg.ThreeDeeMath.TransformMatrix(matrix=self.parameters.matrix)
     903          r = M.transform_vertices([vertex])
     904          return r[0]
     905
     906 -    def apply_to_vertices(self,vertices):
     907          """Perform multiplication on vertex to get transformed result"""
     908          M = VisionEgg.ThreeDeeMath.TransformMatrix(matrix=self.parameters.matrix)
     909          r = M.transform_vertices(vertices)
     910          return r
     911
     912 -class Projection(ProjectionBaseClass):
     913      """for use of OpenGL PROJECTION_MATRIX
     914
     915      Parameters
     916      ==========
     917      matrix -- matrix specifying projection (Sequence4x4 of Real)
     918                Default: [[1 0 0 0]
     919                          [0 1 0 0]
     920                          [0 0 1 0]
     921                          [0 0 0 1]]
     922      """
     923
     924 -    def __init__(self,*args,**kw):
     925          ProjectionBaseClass.__init__(self,*args,**kw)
     926          self.projection_type = gl.GL_PROJECTION
     927
     928 -class ModelView(ProjectionBaseClass):
     929      """for use of OpenGL MODELVIEW_MATRIX
     930
     931      Parameters
     932      ==========
     933      matrix -- matrix specifying projection (Sequence4x4 of Real)
     934                Default: [[1 0 0 0]
     935                          [0 1 0 0]
     936                          [0 0 1 0]
     937                          [0 0 0 1]]
     938      """
     939
     940 -    def __init__(self,*args,**kw):
     941          ProjectionBaseClass.__init__(self,*args,**kw)
     942          self.projection_type = gl.GL_MODELVIEW
     943
     944 -class OrthographicProjection(Projection):
     945      """An orthographic projection.
     946
     947      Parameters
     948      ==========
     949      matrix -- matrix specifying projection (Sequence4x4 of Real)
     950                Default: [[1 0 0 0]
     951                          [0 1 0 0]
     952                          [0 0 1 0]
     953                          [0 0 0 1]]
     954      """
     955
     956 -    def __init__(self,left=0.0,right=640.0,bottom=0.0,top=480.0,z_clip_near=0.0,z_clip_far=1.0):
     957          """Create an orthographic projection.
     958
     959          Defaults to map x eye coordinates in the range [0,640], y eye
     960          coordinates [0,480] and clip coordinates [0,1] to [0,1].
     961          Therefore, if the viewport is 640 x 480, eye coordinates
     962          correspond 1:1 with window (pixel) coordinates.  Only points
     963          between these clipping planes will be displayed.
     964          """
     965
     966          # using Numeric (from the OpenGL spec):
     967          matrix = Numeric.array([[ 2./(right-left), 0.,              0.,                           -(right+left)/(right-left)],
     968                                  [ 0.,              2./(top-bottom), 0.,                           -(top+bottom)/(top-bottom)],
     969                                  [ 0.,              0.,              -2./(z_clip_far-z_clip_near), -(z_clip_far+z_clip_near)/(z_clip_far-z_clip_near)],
     970                                  [ 0.,              0.,              0.,                           1.0]])
     971          matrix = Numeric.transpose(matrix) # convert to OpenGL format
     972
     973          ## same as above, but use OpenGL
     974          #gl.glMatrixMode(gl.GL_PROJECTION)
     975          #gl.glPushMatrix() # save current matrix
     976          #gl.glLoadIdentity()
     977          #gl.glOrtho(left,right,bottom,top,z_clip_near,z_clip_far)
     978          #matrix = gl.glGetFloatv(gl.GL_PROJECTION_MATRIX)
     979          #gl.glPopMatrix() # restore original matrix
     980
     981          Projection.__init__(self,**{'matrix':matrix})
     982
     983 -class OrthographicProjectionNoZClip(Projection):
     984      """An orthographic projection without Z clipping.
     985
     986      Parameters
     987      ==========
     988      matrix -- matrix specifying projection (Sequence4x4 of Real)
     989                Default: [[1 0 0 0]
     990                          [0 1 0 0]
     991                          [0 0 1 0]
     992                          [0 0 0 1]]
     993      """
     994
     995 -    def __init__(self,left=0.0,right=640.0,bottom=0.0,top=480.0):
     996          """Create an orthographic projection without Z clipping.
     997
     998          Defaults to map x eye coordinates in the range [0,640] and y
     999          eye coordinates [0,480] -> [0,1].  Therefore, if the viewport
    1000          is 640 x 480, eye coordinates correspond 1:1 with window
    1001          (pixel) coordinates.
    1002          """
    1003
    1004          # using Numeric (from the OpenGL spec):
    1005          matrix = Numeric.array([[ 2./(right-left), 0,               0, -(right+left)/(right-left)],
    1006                                  [ 0,               2./(top-bottom), 0, -(top+bottom)/(top-bottom)],
    1007                                  [ 0,               0,              -1, -1.],
    1008                                  [ 0,               0,               0,  1]])
    1009          matrix = Numeric.transpose(matrix) # convert to OpenGL format
    1010
    1011          Projection.__init__(self,**{'matrix':matrix})
    1012
    1013 -class SimplePerspectiveProjection(Projection):
    1014      """A simplified perspective projection.
    1015
    1016      Parameters
    1017      ==========
    1018      matrix -- matrix specifying projection (Sequence4x4 of Real)
    1019                Default: [[1 0 0 0]
    1020                          [0 1 0 0]
    1021                          [0 0 1 0]
    1022                          [0 0 0 1]]
    1023      """
    1024
    1025 -    def __init__(self,fov_x=45.0,z_clip_near = 0.1,z_clip_far=10000.0,aspect_ratio=4.0/3.0):
    1026          matrix = self._compute_matrix(fov_x,z_clip_near,z_clip_far,aspect_ratio)
    1027          Projection.__init__(self,**{'matrix':matrix})
    1028
    1029 -    def _compute_matrix(self,fov_x=45.0,z_clip_near = 0.1,z_clip_far=10000.0,aspect_ratio=4.0/3.0):
    1030          """Compute a 4x4 projection matrix that performs a perspective distortion."""
    1031          fov_y = fov_x / aspect_ratio
    1032          # This is a translation of what gluPerspective does:
    1033          #glu.gluPerspective(fov_y,aspect_ratio,z_clip_near,z_clip_far)
    1034          radians = fov_y / 2.0 * math.pi / 180.0
    1035          delta_z = z_clip_far - z_clip_near
    1036          sine = math.sin(radians)
    1037          if (delta_z == 0.0) or (sine == 0.0) or (aspect_ratio == 0.0):
    1038              raise ValueError("Invalid parameters passed to SimpleProjection.__init__()")
    1039          cotangent = math.cos(radians) / sine
    1040          matrix = Numeric.zeros((4,4),'f')
    1041          matrix[0][0] = cotangent/aspect_ratio
    1042          matrix[1][1] = cotangent
    1043          matrix[2][2] = -(z_clip_far + z_clip_near) / delta_z
    1044          matrix[2][3] = -1.0 # XXX this
    1045          matrix[3][2] = -2.0 * z_clip_near * z_clip_far / delta_z # XXX and this might cause the matrix to need to be transposed
    1046          matrix[3][3] = 0.0
    1047          return matrix
    1048
    1049 -class PerspectiveProjection(Projection):
    1050      """A perspective projection.
    1051
    1052      Parameters
    1053      ==========
    1054      matrix -- matrix specifying projection (Sequence4x4 of Real)
    1055                Default: [[1 0 0 0]
    1056                          [0 1 0 0]
    1057                          [0 0 1 0]
    1058                          [0 0 0 1]]
    1059      """
    1060
    1061 -    def __init__(self,left,right,bottom,top,near,far):
    1062          # XXX right now this is done in OpenGL, we should do it ourselves
    1063          gl.glMatrixMode(gl.GL_PROJECTION) # Set OpenGL matrix state to modify the projection matrix
    1064          gl.glPushMatrix()
    1065          gl.glLoadIdentity() # Clear the projection matrix
    1066          gl.glFrustum(left,right,bottom,top,near,far) # Let GL create a matrix and compose it
    1067          matrix = gl.glGetFloatv(gl.GL_PROJECTION_MATRIX)
    1068          gl.glPopMatrix()
    1069          if matrix is None:
    1070              # OpenGL wasn't started
    1071              raise RuntimeError("OpenGL matrix operations can only take place once OpenGL context started.")
    1072          if type(matrix) != Numeric.ArrayType:
    1073              matrix = Numeric.array(matrix) # Convert to Numeric array
    1074          Projection.__init__(self,**{'matrix':matrix})
    1075
    1076  ####################################################################
    1077  #
    1078  #        Stimulus - Base class
    1079  #
    1080  ####################################################################
    1081
    1082 -class Stimulus(VisionEgg.ClassWithParameters):
    1083      """Base class for a stimulus.
    1084
    1085      Any stimulus element should be a subclass of this Stimulus class.
    1086      The draw() method contains the code executed before every buffer
    1087      swap in order to render the stimulus to the frame buffer.  It
    1088      should execute as quickly as possible.  The init_gl() method must
    1089      be called before the first call to draw() so that any internal
    1090      data, OpenGL display lists, and OpenGL:texture objects can be
    1091      established.
    1092
    1093      To illustrate the concept of the Stimulus class, here is a
    1094      description of several methods of drawing two spots.  If your
    1095      experiment displays two spots simultaneously, you could create two
    1096      instances of (a single subclass of) Stimulus, varying parameters
    1097      so each draws at a different location.  Another possibility is to
    1098      create one instance of a subclass that draws two spots.  Another,
    1099      somewhat obscure, possibility is to create a single instance and
    1100      add it to two different viewports.  (Something that will not work
    1101      would be adding the same instance two times to the same viewport.
    1102      It would also get drawn twice, although at exactly the same
    1103      location.)
    1104
    1105      OpenGL is a 'state machine', meaning that it has internal
    1106      parameters whose values vary and affect how it operates.  Because
    1107      of this inherent uncertainty, there are only limited assumptions
    1108      about the state of OpenGL that an instance of Stimulus should
    1109      expect when its draw() method is called.  Because the Vision Egg
    1110      loops through stimuli this also imposes some important behaviors:
    1111
    1112      First, the framebuffer will contain the results of any drawing
    1113      operations performed since the last buffer swap by other instances
    1114      of (subclasses of) Stimulus. Therefore, the order in which stimuli
    1115      are present in the stimuli list of an instance of Viewport may be
    1116      important.  Additionally, if there are overlapping viewports, the
    1117      order in which viewports are added to an instance of Screen is
    1118      important.
    1119
    1120      Second, previously established OpenGL display lists and OpenGL
    1121      texture objects will be available.  The __init__() method should
    1122      establish these things.
    1123
    1124      Third, there are several OpenGL state variables which are
    1125      commonly set by subclasses of Stimulus, and which cannot be
    1126      assumed to have any particular value at the time draw() is called.
    1127      These state variables are: blending mode and function, texture
    1128      state and environment, the matrix mode (modelview or projection),
    1129      the modelview matrix, depth mode and settings. Therefore, if the
    1130      draw() method depends on specific values for any of these states,
    1131      it must specify its own values to OpenGL.
    1132
    1133      Finally, a well-behaved Stimulus subclass resets any OpenGL state
    1134      values other than those listed above to their initial state before
    1135      draw() and init_gl() were called.  In other words, before your
    1136      stimulus changes the state of an OpenGL variable, use
    1137      glGetBoolean, glGetInteger, glGetFloat, or a similar function to
    1138      query its value and restore it later.  For example, upon calling
    1139      the draw() method, the projection matrix will be that which was
    1140      set by the viewport. If the draw() method alters the projection
    1141      matrix, it must be restored. The glPushMatrix() and glPopMatrix()
    1142      commands provide an easy way to do this.
    1143
    1144      The default projection of Viewport maps eye coordinates in a 1:1
    1145      fashion to window coordinates (in other words, it sets eye
    1146      coordinates to use pixel units from the lower left corner of the
    1147      viewport). Therefore the default parameters for a stimulus should
    1148      specify pixel coordinates if possible (such as for a 2D
    1149      stimulus). Assuming a window size of 640 by 480 for the default
    1150      parameters is a pretty safe way to do things.
    1151
    1152      Also, be sure to check for any assumptions made about the system
    1153      in the __init__ method.  For example, if your stimulus needs alpha
    1154      in the framebuffer, check the value of
    1155      glGetIntegerv(GL_ALPHA_BITS) and raise an exception if it is not
    1156      available.
    1157      """
    1158
    1159 -    def __init__(self,**kw):
    1160          """Instantiate and get ready to draw.
    1161
    1162          Set parameter values and create anything needed to draw the
    1163          stimulus including OpenGL state variables such display lists
    1164          and texture objects.
    1165
    1166          """
    1167          VisionEgg.ClassWithParameters.__init__(self,**kw)
    1168
    1169 -    def draw(self):
    1170          """Draw the stimulus. (Called by Viewport instance.)
    1171
    1172          This method is called every frame.  This method actually
    1173          performs the OpenGL calls to draw the stimulus.
    1174
    1175          """
    1176          pass
    1177
    1178  ####################################################################
    1179  #
    1180  #        Viewport
    1181  #
    1182  ####################################################################
    1183
    1184 -class Viewport(VisionEgg.ClassWithParameters):
    1185      """Connects stimuli to a screen.
    1186
    1187      A viewport defines a (possibly clipped region) of the screen on
    1188      which stimuli are drawn.
    1189
    1190      A screen may have multiple viewports.  The viewports may be
    1191      overlapping.
    1192
    1193      A viewport may have multiple stimuli.
    1194
    1195      A single stimulus may be drawn simultaneously by several
    1196      viewports, although this is typically useful only for 3D stimuli
    1197      to represent different views of the same object.
    1198
    1199      The coordinates of the stimulus are converted to screen
    1200      coordinates via several steps, the most important of which is the
    1201      projection, which is defined by an instance of the Projection
    1202      class.
    1203
    1204      By default, a viewport has a projection which maps eye coordinates
    1205      to viewport coordinates in 1:1 manner.  In other words, eye
    1206      coordinates specify pixel location in the viewport.
    1207
    1208      For cases where pixel units are not natural to describe
    1209      coordinates of a stimulus, the application should specify the a
    1210      projection other than the default.  This is usually the case for
    1211      3D stimuli.
    1212
    1213      For details of the projection and clipping process, see the
    1214      section 'Coordinate Transformations' in the book/online document
    1215      'The OpenGL Graphics System: A Specification'
    1216
    1217      Parameters
    1218      ==========
    1219      anchor        -- How position parameter is interpreted (String)
    1220                       Default: lowerleft
    1221      camera_matrix -- extrinsic camera parameter matrix (position and orientation) (Instance of <class 'VisionEgg.Core.ModelView'>)
    1222                       Default: (determined at runtime)
    1223      depth_range   -- depth range (in object units) for rendering (Sequence2 of Real)
    1224                       Default: (0, 1)
    1225      position      -- Position (in pixel units) within the screen (Sequence2 of Real)
    1226                       Default: (0, 0)
    1227      projection    -- intrinsic camera parameter matrix (field of view, focal length, aspect ratio) (Instance of <class 'VisionEgg.Core.Projection'>)
    1228                       Default: (determined at runtime)
    1229      screen        -- The screen in which this viewport is drawn (Instance of <class 'VisionEgg.Core.Screen'>)
    1230                       Default: (determined at runtime)
    1231      size          -- Size (in pixel units) (Sequence2 of Real)
    1232                       Default: (determined at runtime)
    1233      stimuli       -- sequence of stimuli to draw in screen (Sequence of Instance of <class 'VisionEgg.Core.Stimulus'>)
    1234                       Default: (determined at runtime)
    1235      """
    1236
    1237      parameters_and_defaults = VisionEgg.ParameterDefinition({
    1238          'screen':(None,
    1239                    ve_types.Instance(Screen),
    1240                    'The screen in which this viewport is drawn'),
    1241          'position':((0,0),
    1242                      ve_types.Sequence2(ve_types.Real),
    1243                      'Position (in pixel units) within the screen'),
    1244          'anchor':('lowerleft',
    1245                    ve_types.String,
    1246                    'How position parameter is interpreted'),
    1247          'depth_range':((0,1),
    1248                         ve_types.Sequence2(ve_types.Real),
    1249                         'depth range (in object units) for rendering'),
    1250          'size':(None, # will use screen.size if not specified
    1251                  ve_types.Sequence2(ve_types.Real),
    1252                  'Size (in pixel units)'),
    1253          'projection':(None,
    1254                        ve_types.Instance(Projection),
    1255                        'intrinsic camera parameter matrix (field of view, focal length, aspect ratio)'),
    1256          'camera_matrix':(None,
    1257                           ve_types.Instance(ModelView),
    1258                           'extrinsic camera parameter matrix (position and orientation)'),
    1259          'stimuli':(None,
    1260                     ve_types.Sequence(ve_types.Instance(Stimulus)),
    1261                     'sequence of stimuli to draw in screen'),
    1262          'lowerleft':(None,  # DEPRECATED -- don't use
    1263                       ve_types.Sequence2(ve_types.Real),
    1264                       'position (in pixel units) of lower-left viewport corner',
    1265                       VisionEgg.ParameterDefinition.DEPRECATED),
    1266          })
    1267
    1268      __slots__ = (
    1269          '_is_drawing',
    1270          )
    1271
    1272 -    def __init__(self,**kw):
    1273          """Create a new instance.
    1274
    1275          Required arguments:
    1276
    1277          screen
    1278
    1279          Optional arguments (specify parameter value other than default):
    1280
    1281          position -- defaults to (0,0), position relative to screen by anchor (see below)
    1282          anchor -- defaults to 'lowerleft'
    1283          size -- defaults to screen.size
    1284          projection -- defaults to self.make_new_pixel_coord_projection()
    1285          stimuli -- defaults to empty list
    1286          """
    1287          VisionEgg.ClassWithParameters.__init__(self,**kw)
    1288
    1289          if self.parameters.screen is None:
    1290              raise ValueError("Must specify screen when creating an instance of Viewport.")
    1291
    1292          p = self.parameters # shorthand
    1293          if p.size is None:
    1294              p.size = p.screen.constant_parameters.size
    1295          if p.projection is None:
    1296              # Default projection maps eye coordinates 1:1 on window (pixel) coordinates
    1297              p.projection = self.make_new_pixel_coord_projection()
    1298          if p.camera_matrix is None:
    1299              p.camera_matrix = ModelView()
    1300          if p.stimuli is None:
    1301              p.stimuli = []
    1302          self._is_drawing = False
    1303
    1304 -    def make_new_pixel_coord_projection(self):
    1305          """Create instance of Projection mapping eye coordinates 1:1 with pixel coordinates."""
    1306          return OrthographicProjectionNoZClip(left=0,right=self.parameters.size[0],
    1307                                               bottom=0,top=self.parameters.size[1])
    1308
    1309 -    def make_current(self):
    1310          p = self.parameters # shorthand
    1311          p.screen.make_current()
    1312
    1313          if p.lowerleft != None:
    1314              if not hasattr(Viewport,"_gave_lowerleft_warning"):
    1315                  logger = logging.getLogger('VisionEgg.Core')
    1316                  logger.warning("lowerleft parameter of Viewport class "
    1317                                 "will stop being supported. Use "
    1318                                 "'position' instead with anchor set to "
    1319                                 "'lowerleft'.")
    1320                  Viewport._gave_lowerleft_warning = True
    1321              p.anchor = 'lowerleft'
    1322              p.position = p.lowerleft[0], p.lowerleft[1] # copy values (don't copy ref to tuple)
    1323
    1324          lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,p.size)
    1325
    1326          gl.glViewport(lowerleft[0],
    1327                        lowerleft[1],
    1328                        p.size[0],
    1329                        p.size[1])
    1330          gl.glDepthRange(p.depth_range[0],p.depth_range[1])
    1331
    1332          p.projection.apply_to_gl()
    1333          p.camera_matrix.apply_to_gl()
    1334
    1335 -    def draw(self):
    1336          """Set the viewport and draw stimuli."""
    1337          self.make_current()
    1338          self._is_drawing = True
    1339          for stimulus in self.parameters.stimuli:
    1340              stimulus.draw()
    1341          self._is_drawing = False
    1342
    1343 -    def norm_device_2_window(self,norm_device_vertex):
    1344          """Transform normalized device coordinates to window coordinates"""
    1345          v = Numeric.asarray(norm_device_vertex)
    1346          homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
    1347          xd = homog[:,0,Numeric.NewAxis]
    1348          yd = homog[:,1,Numeric.NewAxis]
    1349          zd = homog[:,2,Numeric.NewAxis]
    1350
    1351          p = self.parameters # shorthand
    1352          lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,p.size)
    1353          x,y = lowerleft
    1354          w,h = p.size
    1355          n,f = p.depth_range
    1356
    1357          # clamp n and f
    1358          n = min(1.0,max(0.0,n))
    1359          f = min(1.0,max(0.0,f))
    1360
    1361          ox = x + w/2.0
    1362          oy = y + h/2.0
    1363          px = w
    1364          py = h
    1365          xw = (px/2.0)*xd + ox
    1366          yw = (py/2.0)*yd + oy
    1367          zw = ((f-n)/2.0)*zd + (n+f)/2.0
    1368          # XXX I think zw (or zd) is clamped in OpenGL, but I can't
    1369          # find it in any spec!
    1370          #zw = Numeric.clip(zw,0.0,1.0) # clamp
    1371          r = Numeric.concatenate((xw,yw,zw),axis=1)
    1372          if len(homog.shape) > len(v.shape):
    1373              r = Numeric.reshape(r,(3,))
    1374          return r
    1375 -    def clip_2_window(self,eye_coords_vertex):
    1376          """Transform clip coordinates to window coordinates"""
    1377          my_proj = self.parameters.projection
    1378          return self.norm_device_2_window( my_proj.clip_2_norm_device( eye_coords_vertex ) )
    1379 -    def eye_2_window(self,eye_coords_vertex):
    1380          """Transform eye coordinates to window coordinates"""
    1381          my_proj = self.parameters.projection
    1382          return self.norm_device_2_window( my_proj.eye_2_norm_device( eye_coords_vertex ) )
    1383
    1384  ####################################################################
    1385  #
    1386  #        FixationSpot
    1387  #
    1388  ####################################################################
    1389
    1390 -class FixationSpot(Stimulus):
    1391      """A rectangle stimulus, typically used as a fixation spot.
    1392
    1393      Parameters
    1394      ==========
    1395      anchor   -- how position parameter is used (String)
    1396                  Default: center
    1397      color    -- color (AnyOf(Sequence3 of Real or Sequence4 of Real))
    1398                  Default: (1.0, 1.0, 1.0)
    1399      on       -- draw? (Boolean)
    1400                  Default: True
    1401      position -- position in eye coordinates (AnyOf(Sequence2 of Real or Sequence3 of Real or Sequence4 of Real))
    1402                  Default: (320.0, 240.0)
    1403      size     -- size in eye coordinates (Sequence2 of Real)
    1404                  Default: (4.0, 4.0)
    1405      """
    1406
    1407      parameters_and_defaults = VisionEgg.ParameterDefinition({
    1408          'on':(True,
    1409                ve_types.Boolean,
    1410                'draw?'),
    1411          'color':((1.0,1.0,1.0),
    1412                   ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
    1413                                  ve_types.Sequence4(ve_types.Real)),
    1414                   'color'),
    1415          'position' : ( ( 320.0, 240.0 ), # in eye coordinates
    1416                         ve_types.AnyOf(ve_types.Sequence2(ve_types.Real),
    1417                                        ve_types.Sequence3(ve_types.Real),
    1418                                        ve_types.Sequence4(ve_types.Real)),
    1419                         'position in eye coordinates'),
    1420          'anchor' : ('center',
    1421                      ve_types.String,
    1422                      'how position parameter is used'),
    1423          'size':((4.0,4.0), # horiz and vertical size
    1424                  ve_types.Sequence2(ve_types.Real),
    1425                  'size in eye coordinates'),
    1426          'center' : (None,  # DEPRECATED -- don't use
    1427                      ve_types.Sequence2(ve_types.Real),
    1428                      'position in eye coordinates',
    1429                      VisionEgg.ParameterDefinition.DEPRECATED),
    1430          })
    1431
    1432 -    def __init__(self,**kw):
    1433          Stimulus.__init__(self,**kw)
    1434
    1435 -    def draw(self):
    1436          p = self.parameters # shorthand
    1437          if p.center is not None:
    1438              if not hasattr(VisionEgg.config,"_GAVE_CENTER_DEPRECATION"):
    1439                  logger = logging.getLogger('VisionEgg.Core')
    1440                  logger.warning("Specifying FixationSpot by deprecated "
    1441                                 "'center' parameter deprecated.  Use "
    1442                                 "'position' parameter instead.  (Allows "
    1443                                 "use of 'anchor' parameter to set to "
    1444                                 "other values.)")
    1445                  VisionEgg.config._GAVE_CENTER_DEPRECATION = 1
    1446              p.anchor = 'center'
    1447              p.position = p.center[0], p.center[1] # copy values (don't copy ref to tuple)
    1448          if p.on:
    1449              # calculate center
    1450              center = VisionEgg._get_center(p.position,p.anchor,p.size)
    1451              gl.glDisable(gl.GL_DEPTH_TEST)
    1452              gl.glDisable(gl.GL_TEXTURE_2D)
    1453              gl.glDisable(gl.GL_BLEND)
    1454
    1455              if len(p.color)==3:
    1456                  gl.glColor3f(*p.color)
    1457              elif len(p.color)==4:
    1458                  gl.glColor4f(*p.color)
    1459
    1460              # This could go in a display list to speed it up, but then
    1461              # size wouldn't be dynamically adjustable this way.  Could
    1462              # still use one of the matrices to make it change size.
    1463              x_size = self.parameters.size[0]/2.0
    1464              y_size = self.parameters.size[1]/2.0
    1465              x,y = center[0],center[1]
    1466              x1 = x-x_size; x2 = x+x_size
    1467              y1 = y-y_size; y2 = y+y_size
    1468              gl.glBegin(gl.GL_QUADS)
    1469              gl.glVertex2f(x1,y1)
    1470              gl.glVertex2f(x2,y1)
    1471              gl.glVertex2f(x2,y2)
    1472              gl.glVertex2f(x1,y2)
    1473              gl.glEnd() # GL_QUADS
    1474
    1475  ####################################################################
    1476  #
    1477  #        Frame timing information
    1478  #
    1479  ####################################################################
    1480
    1481 -class FrameTimer:
    1482      """Time inter frame intervals and compute frames per second."""
    1483 -    def __init__(self, bin_start_msec=2, bin_stop_msec=28, bin_width_msec=2, running_average_num_frames=0,save_all_frametimes=False):
    1484          """Create instance of FrameTimer."""
    1485          self.bins = Numeric.arange( bin_start_msec, bin_stop_msec, bin_width_msec )
    1486          self.bin_width_msec = float(bin_width_msec)
    1487          self.timing_histogram = Numeric.zeros( self.bins.shape, Numeric.Float ) # make float to avoid (early) overflow errors
    1488          self._true_time_last_frame = None # no frames yet
    1489          self.longest_frame_draw_time_sec = None
    1490          self.first_tick_sec = None
    1491          self.total_frames = 0
    1492          self.running_average_num_frames = running_average_num_frames
    1493          if self.running_average_num_frames:
    1494              self.last_n_frame_times_sec = [None]*self.running_average_num_frames
    1495          self.save_all_frametimes = save_all_frametimes
    1496          if self.save_all_frametimes:
    1497              self.all_frametimes = []
    1498
    1499 -    def tick(self):
    1500          """Declare a frame has just been drawn."""
    1501          true_time_now = VisionEgg.true_time_func()
    1502          if self._true_time_last_frame != None:
    1503              this_frame_draw_time_sec = true_time_now - self._true_time_last_frame
    1504              index = int(math.ceil(this_frame_draw_time_sec*1000.0/self.bin_width_msec))-1
    1505              if index > (len(self.timing_histogram)-1):
    1506                  index = -1
    1507              self.timing_histogram[index] += 1
    1508              self.longest_frame_draw_time_sec = max(self.longest_frame_draw_time_sec,this_frame_draw_time_sec)
    1509              if self.running_average_num_frames:
    1510                  self.last_n_frame_times_sec.append(true_time_now)
    1511                  self.last_n_frame_times_sec.pop(0)
    1512          else:
    1513              self.first_tick_sec = true_time_now
    1514          self._true_time_last_frame = true_time_now # set for next frame
    1515
    1516          if self.save_all_frametimes:
    1517              self.all_frametimes.append( true_time_now )
    1518
    1519 -    def get_all_frametimes(self):
    1520          if self.save_all_frametimes:
    1521              return self.all_frametimes
    1522          else:
    1523              raise ValueError("must set save_all_frametimes")
    1524
    1525 -    def get_longest_frame_duration_sec(self):
    1526          return self.longest_frame_draw_time_sec
    1527
    1528 -    def get_running_average_ifi_sec(self):
    1529          if self.running_average_num_frames:
    1530              frame_times = []
    1531              for frame_time in self.last_n_frame_times_sec:
    1532                  if frame_time is not None:
    1533                      frame_times.append( frame_time )
    1534              if len(frame_times) >= 2:
    1535                  return (frame_times[-1] - frame_times[0]) / len(frame_times)
    1536          else:
    1537              raise RuntimeError("running_average_num_frames not set when creating FrameTimer instance")
    1538
    1539 -    def get_average_ifi_sec(self):
    1540          if self._true_time_last_frame is None:
    1541              raise RuntimeError("No frames were drawn, can't calculate average IFI")
    1542          return (self._true_time_last_frame - self.first_tick_sec) / sum( self.timing_histogram )
    1543
    1544 -    def print_histogram(self):
    1545          logger = logging.getLogger('VisionEgg.Core')
    1546          logger.warning("print_histogram() method of FrameTimer is "
    1547                         "deprecated will stop being supported. Use "
    1548                         "log_histogram() instead.")
    1549          self.log_histogram()
    1550
    1551 -    def log_histogram(self):
    1552          """Send histogram to logger."""
    1553          buffer = StringIO.StringIO()
    1554
    1555          n_frames = sum( self.timing_histogram )+1
    1556          if n_frames < 2:
    1557              print >> buffer, '%d frames were drawn.'%n_frames
    1558              return
    1559          average_ifi_sec = self.get_average_ifi_sec()
    1560          print >> buffer, '%d frames were drawn.'%int(n_frames)
    1561          print >> buffer, 'Mean IFI was %.2f msec (%.2f fps), longest IFI was %.2f msec.'%(
    1562              average_ifi_sec*1000.0,1.0/average_ifi_sec,self.longest_frame_draw_time_sec*1000.0)
    1563
    1564          h = hist = self.timing_histogram # shorthand
    1565          maxhist = float(max(h))
    1566          if maxhist == 0:
    1567              print >> buffer, "No frames were drawn."
    1568              return
    1569          lines = min(10,int(math.ceil(maxhist)))
    1570          hist = hist/maxhist*float(lines) # normalize to number of lines
    1571          print >> buffer, "histogram:"
    1572          for line in range(lines):
    1573              val = float(lines)-1.0-float(line)
    1574              timing_string = "%6d   "%(round(maxhist*val/lines),)
    1575              q = Numeric.greater(hist,val)
    1576              for qi in q:
    1577                  s = ' '
    1578                  if qi:
    1579                      s = '*'
    1580                  timing_string += "%4s "%(s,)
    1581              print >> buffer, timing_string
    1582          timing_string = " Time: "
    1583          timing_string += "%4d "%(0,)
    1584          for bin in self.bins[:-1]:
    1585              timing_string += "%4d "%(bin,)
    1586          timing_string += "+(msec)\n"
    1587          timing_string += "Total:    "
    1588          for hi in h:
    1589              if hi <= 999:
    1590                  num_str = str(int(hi)).center(5)
    1591              else:
    1592                  num_str = " +++ "
    1593              timing_string += num_str
    1594          print >> buffer, timing_string
    1595
    1596          buffer.seek(0)
    1597          logger = logging.getLogger('VisionEgg.Core')
    1598          logger.info(buffer.read())
    1599
    1600  ####################################################################
    1601  #
    1602  #        Error handling and assumption checking
    1603  #
    1604  ####################################################################
    1605
    1606  import VisionEgg.Deprecated
    1607  Message = VisionEgg.Deprecated.Message
    1608
    1609  message = VisionEgg.Deprecated.Message() # create instance of Message class for everything to use
    1610
    1611  gl_assumptions = []
    1612
    1613 -def add_gl_assumption(gl_variable,required_value,failure_callback):
    1614      """Save assumptions for later checking once OpenGL context created."""
    1615      if type(failure_callback) != types.FunctionType:
    1616          raise ValueError("failure_callback must be a function!")
    1617      gl_assumptions.append((gl_variable,required_value,failure_callback))
    1618
    1619 -def init_gl_extension(prefix,name):
    1620      global gl # interpreter knows when we're up to something funny with GLTrace
    1621      logger = logging.getLogger('VisionEgg.Core')
    1622
    1623      if gl is VisionEgg.GLTrace:
    1624          watched = True
    1625          gl = VisionEgg.GLTrace.gl # manipulate original module for now
    1626      else:
    1627          watched = False
    1628
    1629      module_name = "OpenGL.GL.%(prefix)s.%(name)s"%locals()
    1630      try:
    1631          exec "import "+module_name
    1632      except ImportError:
    1633          logger.warning("Could not import %s -- some features will be "
    1634                         "missing."%(module_name,))
    1635          return False
    1636      module = eval(module_name)
    1637      init_function_name = "glInit"+name.title().replace('_','')+prefix
    1638      init_function = getattr(module,init_function_name)
    1639      if not init_function():
    1640          logger.warning("Could not initialize %s -- some features will "
    1641                         "be missing."%(module_name,))
    1642          return False
    1643      for attr_name in dir(module):
    1644          # put attributes from module into "gl" module dictionary
    1645          # (Namespace overlap as you'd get OpenGL apps written in C)
    1646          attr = getattr(module,attr_name)
    1647          # reject unwanted attributes
    1648          if attr_name.startswith('__'):
    1649              continue
    1650          elif attr_name == init_function_name:
    1651              continue
    1652          elif attr_name == 'gl':
    1653              continue
    1654          elif type(attr) == type(VisionEgg): # module type
    1655              continue
    1656
    1657          gl_attr_name = attr_name
    1658          setattr(gl,gl_attr_name,attr)
    1659
    1660      if watched:
    1661          VisionEgg.GLTrace.gl_trace_attach() # (re)scan namespace
    1662          gl = VisionEgg.GLTrace # reinstall GLTrace
    1663      return True # success!
    1664
    1665 -def post_gl_init():
    1666      """Called by Screen instance. Requires OpenGL context to be created."""
    1667      global gl_vendor, gl_renderer, gl_version # set above
    1668      logger = logging.getLogger('VisionEgg.Core')
    1669
    1670      if gl_version < '1.3':
    1671          if not init_gl_extension('ARB','multitexture'):
    1672              logger.warning("multitexturing not available.  Some features "
    1673                             "will not be available")
    1674      else:
    1675          if not hasattr(gl,'glActiveTexture'):
    1676              logger.debug("PyOpenGL bug: OpenGL multitexturing not available "
    1677                           "even though OpenGL is 1.3 or greater. "
    1678                           "Attempting ctypes-based workaround.")
    1679              VisionEgg.PlatformDependent.attempt_to_load_multitexturing()
    1680          if hasattr(gl,'glActiveTexture'): # the above worked or PyOpenGL fixed
    1681              # OpenGL 1.3 has this extension built-in,
    1682              # but doing this allows use of ARB names.
    1683              gl.glActiveTextureARB = gl.glActiveTexture
    1684              gl.glMultiTexCoord2fARB = gl.glMultiTexCoord2f
    1685              gl.GL_TEXTURE0_ARB = gl.GL_TEXTURE0
    1686              gl.GL_TEXTURE1_ARB = gl.GL_TEXTURE1
    1687
    1688      if gl_version < '1.2':
    1689          if init_gl_extension('EXT','bgra'):
    1690              # make sure gl.GL_BRGA is defined
    1691              gl.GL_BGRA = gl.GL_BGRA_EXT
    1692
    1693      for gl_variable,required_value,failure_callback in gl_assumptions:
    1694          # Code required for each variable to be checked
    1695          if gl_variable == "__SPECIAL__":
    1696              if required_value == "linux_nvidia_or_new_ATI":
    1697                  ok = 0
    1698                  # Test for nVidia
    1699                  if "nvidia" == gl_vendor.split()[0].lower():
    1700                      ok = 1 # yes it is
    1701                  if gl_renderer.startswith('Mesa DRI Radeon'):
    1702                      date = gl_renderer.split()[3]
    1703                      if date > "20021216": # not sure about exact date
    1704                          ok=1
    1705                  if not ok:
    1706                      failure_callback()
    1707              else:
    1708                  raise RuntimeError, "Unknown gl_assumption: %s == %s"%(gl_variable,required_value)
    1709
    1710          elif gl_variable.upper() == "GL_VERSION":
    1711              value_str = gl_version.split()[0]
    1712              value_ints = map(int,value_str.split('.'))
    1713              value = float( str(value_ints[0]) + "." + ''.join(map(str,value_ints[1:])))
    1714              if value < required_value:
    1715                  failure_callback()
    1716          else:
    1717              raise RuntimeError, "Unknown gl_assumption"
    1718
    1719      # Do we have gl.GL_CLAMP_TO_EDGE ?
    1720      try:
    1721          gl.GL_CLAMP_TO_EDGE
    1722      except AttributeError:
    1723          if gl_version >= '1.2':
    1724              # If OpenGL version >= 1.2, this should be defined
    1725              # It seems to be a PyOpenGL bug that it's not.
    1726              logger.debug("GL_CLAMP_TO_EDGE is not defined. "
    1727                           "Because you have OpenGL version 1.2 or "
    1728                           "greater, this is probably a bug in "
    1729                           "PyOpenGL.  Assigning GL_CLAMP_TO_EDGE to "
    1730                           "the value that is usually used.")
    1731              gl.GL_CLAMP_TO_EDGE = 0x812F
    1732          else:
    1733              try:
    1734                  init_gl_extension('SGIS','texture_edge_clamp')
    1735                  gl.GL_CLAMP_TO_EDGE = gl.GL_CLAMP_TO_EDGE_SGIS
    1736              except:
    1737
    1738                  logger.warning("GL_CLAMP_TO_EDGE is not "
    1739                                 "available.  OpenGL version is "
    1740                                 "less than 1.2, and the "
    1741                                 "texture_edge_clamp_SGIS extension "
    1742                                 "failed to load. It may be impossible to "
    1743                                 "get exact 1:1 reproduction of "
    1744                                 "textures.  Using GL_CLAMP instead of "
    1745                                 "GL_CLAMP_TO_EDGE.")
    1746                  gl.GL_CLAMP_TO_EDGE = gl.GL_CLAMP
    1747
    1748  #########################################################################
    1749  #
    1750  #       Moved to FlowControl.py -- here only for backwards compatibility
    1751  #
    1752  #########################################################################
    1753
    1754  import VisionEgg.FlowControl
    1755  Presentation = VisionEgg.FlowControl.Presentation
    1756  Controller = VisionEgg.FlowControl.Controller
    1757  ConstantController = VisionEgg.FlowControl.ConstantController
    1758  EvalStringController = VisionEgg.FlowControl.EvalStringController
    1759  ExecStringController = VisionEgg.FlowControl.ExecStringController
    1760  FunctionController = VisionEgg.FlowControl.FunctionController
    1761  EncapsulatedController = VisionEgg.FlowControl.EncapsulatedController
    1762