VisionEgg

Package VisionEgg |
[frames] | no frames]

# Source Code for Package VisionEgg

      1  # The Vision Egg
      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  """
     13  The Vision Egg package.
     14
     15  The Vision Egg is a programming library (with demo applications) that
     16  uses standard, inexpensive computer graphics cards to produce visual
     17  stimuli for vision research experiments.
     18
     19  Today's consumer computer graphics cards, thanks to the demands of
     20  computer gamers, are capable of drawing and updating computer graphics
     21  suitable for producing research-quality visual stimuli. The Vision Egg
     22  allows the vision scientist (or anyone else) to program these cards
     23  using OpenGL, the standard in computer graphics
     24  programming. Potentially difficult tasks, such as initializing
     25  graphics, getting precise timing information, controlling stimulus
     26  parameters in real-time, and synchronizing with data acquisition are
     27  greatly eased by routines within the Vision Egg.
     28
     29  See the 'Core' module for the fundamental Vision Egg classes.
     30
     31  """
     32  release_name = '1.1.dev'
     33
     34  __version__ = release_name
     35
     36  import VisionEgg.Configuration
     37  import VisionEgg.ParameterTypes as ve_types
     38  import os, sys, time, types # standard python modules
     39  import numpy
     40  import numpy.oldnumeric as Numeric
     41  import warnings
     42  import traceback
     43  import StringIO
     44
     45  import logging  # available in Python 2.3
     46  import logging.handlers
     47
     48  if not hasattr(Numeric,'UInt8'):
     49      Numeric.UInt8 = 'b'
     50  if not hasattr(Numeric,'Float32'):
     51      Numeric.UInt8 = 'f'
     52
     53  # Make sure we don't have an old version of the VisionEgg installed.
     54  # (There used to be a module named VisionEgg.VisionEgg.  If it still
     55  # exists, it will randomly crash things.)
     56  if not hasattr(sys,'frozen'):
     57      # don't do this if frozen:
     58      try:
     59          __import__('VisionEgg.VisionEgg')
     60      except ImportError:
     61          pass # It's OK, the old version isn't there
     62      else:
     63          # If we can import it, report error
     64          raise RuntimeError('Outdated "VisionEgg.py" and/or "VisionEgg.pyc" found.  Please delete from your VisionEgg package directory.')
     65
     66  ############# Get config defaults #############
     67  config = VisionEgg.Configuration.Config()
     68
     69  ############# Logging #############
     70
     71  logger = logging.getLogger('VisionEgg')
     72  logger.setLevel( logging.INFO )
     73  log_formatter = logging.Formatter('%(asctime)s (%(process)d) %(levelname)s: %(message)s')
     74  _default_logging_started = False
     75
     76 -def start_default_logging(maxBytes=100000):
     77      """Create and add log handlers"""
     78      global _default_logging_started
     79      if _default_logging_started:
     80          return # default logging already started
     81
     82      if config.VISIONEGG_LOG_TO_STDERR:
     83          log_handler_stderr = logging.StreamHandler()
     84          log_handler_stderr.setFormatter( log_formatter )
     85          logger.addHandler( log_handler_stderr )
     86
     87      if config.VISIONEGG_LOG_FILE:
     88          if hasattr(logging, 'handlers'):
     89              log_handler_logfile = logging.handlers.RotatingFileHandler( config.VISIONEGG_LOG_FILE,
     90                                                                          maxBytes=maxBytes )
     91          else:
     92              log_handler_logfile = logging.FileHandler( config.VISIONEGG_LOG_FILE )
     93          log_handler_logfile.setFormatter( log_formatter )
     94          logger.addHandler( log_handler_logfile )
     95
     96      script_name = sys.argv[0]
     97      if not script_name:
     98          script_name = "(interactive shell)"
     99      logger.info("Script "+script_name+" started Vision Egg %s with process id %d."%(VisionEgg.release_name,os.getpid()))
    100      _default_logging_started = True
    101
    102
    103  ############# Default exception handler #############
    104
    105  if not sys.argv[0]: # Interactive mode
    106      config.VISIONEGG_GUI_ON_ERROR = 0
    107
    108 -class _ExceptionHookKeeper:
    109 -    def handle_exception(self, exc_type, exc_value, exc_traceback):
    110          global config
    111
    112          traceback_stream = StringIO.StringIO()
    113          traceback.print_exception(exc_type,exc_value,exc_traceback,None,traceback_stream)
    114          traceback_stream.seek(0)
    115
    116          try:
    117              # don't send to stderr here (original exception handler does it)
    118              logger.removeHandler( log_handler_stderr )
    119              removed_stderr = True
    120          except:
    121              removed_stderr = False
    122
    123          logger.critical(traceback_stream.read())
    124
    125          if removed_stderr:
    126              logger.addHandler( log_handler_stderr )
    127
    128          if config is not None:
    129              if config.VISIONEGG_GUI_ON_ERROR and config.VISIONEGG_TKINTER_OK:
    130                  # Should really check if any GUI windows are open and only do this then
    131
    132                  # close any open screens
    133                  if hasattr(config,'_open_screens'):
    134                      for screen in config._open_screens:
    135                          screen.close()
    136
    137                  traceback_stream = StringIO.StringIO()
    138                  traceback.print_tb(exc_traceback,None,traceback_stream)
    139                  traceback_stream.seek(0)
    140
    141                  pygame_bug_workaround = False # do we need to workaround pygame bug?
    142                  if hasattr(config,"_pygame_started"):
    143                      if config._pygame_started:
    144                          pygame_bug_workaround = True
    145                  if sys.platform.startswith('linux'): # doesn't affect linux for some reason
    146                      pygame_bug_workaround = False
    147                  if not pygame_bug_workaround:
    148                      if hasattr(config,'_Tkinter_used'):
    149                          if config._Tkinter_used:
    150                              import GUI
    151                              GUI.showexception(exc_type, exc_value, traceback_stream.getvalue())
    152
    153          # continue on with normal exception processing:
    154          __keep_config__ = config # bizarre that the exception handler changes our values...
    155          self.orig_hook(exc_type, exc_value, exc_traceback)
    156          config = __keep_config__ # but we work around it!
    157
    158 -    def __init__(self):
    159          self._sys = sys # preserve ref to sys module
    160          self.orig_hook = self._sys.excepthook # keep copy
    161          sys.excepthook = self.handle_exception
    162
    163 -    def __del__(self):
    164          self._sys.excepthook = self.orig_hook # restore original
    165
    166 -def watch_exceptions():
    167      """Catch exceptions, log them, and optionally open GUI."""
    168      global _exception_hook_keeper
    169      _exception_hook_keeper = _ExceptionHookKeeper()
    170
    171 -def stop_watching_exceptions():
    172      """Stop catching exceptions, returning to previous state."""
    173      global _exception_hook_keeper
    174      del _exception_hook_keeper
    175
    176  if config.VISIONEGG_ALWAYS_START_LOGGING:
    177      start_default_logging()
    178      watch_exceptions()
    179      if len(config._delayed_configuration_log_warnings) != 0:
    180          logger = logging.getLogger('VisionEgg.Configuration')
    181          for msg in config._delayed_configuration_log_warnings:
    182              logger.warning( msg )
    183
    184  ############ A base class finder utility function ###########
    185
    186 -def recursive_base_class_finder(klass):
    187      """A function to find all base classes."""
    188      result = [klass]
    189      for base_class in klass.__bases__:
    190          for base_base_class in recursive_base_class_finder(base_class):
    191              result.append(base_base_class)
    192      # Make only a single copy of each class found
    193      result2 = []
    194      for r in result:
    195          if r not in result2:
    196              result2.append(r)
    197      return result2
    198
    199  ############# Setup timing functions #############
    200
    201  if sys.platform == "win32":
    202      # on win32, time.clock() theoretically has better resolution than time.time()
    203      true_time_func = time.clock
    204  else:
    205      true_time_func = time.time
    206      # XXX Possible To-Do:
    207      #  On MacOSX use AudioGetCurrentHostTime() and
    208      #  AudioConvertHostTimeToNanos() #
    209
    210  config._FRAMECOUNT_ABSOLUTE = 0 # initialize global variable
    211 -def time_func_locked_to_frames():
    212      return config._FRAMECOUNT_ABSOLUTE / float(config.VISIONEGG_MONITOR_REFRESH_HZ)
    213
    214  time_func = true_time_func # name of time function Vision Egg programs should use
    215
    216 -def set_time_func_to_true_time():
    217      time_func = true_time_func
    218
    219 -def set_time_func_to_frame_locked():
    220      time_func = time_func_locked_to_frames
    221
    222 -def timing_func():
    223      """DEPRECATED.  Use time_func instead"""
    224      warnings.warn("timing_func() has been changed to time_func(). "
    225                    "This warning will only be issued once, but each call to "
    226                    "timing_func() will be slower than if you called time_func() "
    227                    "directly", DeprecationWarning, stacklevel=2)
    228      return time_func()
    229
    230  ####################################################################
    231  #
    232  #        Parameters
    233  #
    234  ####################################################################
    235
    236 -class Parameters:
    237      """Parameter container.
    238
    239      Simple empty class to act something like a C struct."""
    240      pass
    241
    242 -class ParameterDefinition( dict ):
    243      """Define parameters used in ClassWithParameters
    244      """
    245      DEPRECATED = 1
    246      OPENGL_ENUM = 2
    247
    248 -class ClassWithParameters( object ):
    249      """Base class for any class that uses parameters.
    250
    251      Any class that uses parameters potentially modifiable in realtime
    252      should be a subclass of ClassWithParameters.  This class enforces
    253      type checking and sets default values.
    254
    255      Any subclass of ClassWithParameters can define two class (not
    256      instance) attributes, "parameters_and_defaults" and
    257      "constant_parameters_and_defaults". These are dictionaries where
    258      the key is a string containing the name of the parameter and the
    259      the value is a tuple of length 2 containing the default value and
    260      the type.  For example, an acceptable dictionary would be
    261      {"parameter1" : (1.0, ve_types.Real)}
    262
    263      See the ParameterTypes module for more information about types.
    264
    265      """
    266
    267      parameters_and_defaults = ParameterDefinition({}) # empty for base class
    268      constant_parameters_and_defaults = ParameterDefinition({}) # empty for base class
    269
    270      __slots__ = ('parameters','constant_parameters') # limit access only to specified attributes
    271
    272 -    def __getstate__(self):
    273          """support for being pickled"""
    274          result = {}
    275          classes = recursive_base_class_finder(self.__class__)
    276          for klass in classes:
    277              if hasattr(klass,'__slots__'):
    278                  for attr in klass.__slots__:
    279                      if hasattr(self,attr):
    280                          result[attr] = getattr(self,attr)
    281          return result
    282
    283 -    def __setstate__(self,dict):
    284          """support for being unpickled"""
    285          for attr in dict.keys():
    286              setattr(self,attr,dict[attr])
    287
    288      __safe_for_unpickling__ = True # tell python 2.2 we know what we're doing
    289
    290 -    def __init__(self,**kw):
    291          """Create self.parameters and set values."""
    292          self.constant_parameters = Parameters() # create self.constant_parameters
    293          self.parameters = Parameters() # create self.parameters
    294
    295          # Get a list of all classes this instance is derived from
    296          classes = recursive_base_class_finder(self.__class__)
    297
    298          done_constant_parameters_and_defaults = []
    299          done_parameters_and_defaults = []
    300          done_kw = []
    301
    302          # Fill self.parameters with parameter names and set to default values
    303          for klass in classes:
    304              if klass == object:
    305                  continue # base class of new style classes - ignore
    306              # Create self.parameters and set values to keyword argument if found,
    307              # otherwise to default value.
    308              #
    309              # If a class didn't override base class's parameters_and_defaults dictionary, don't deal with it twice
    310              if hasattr(klass, 'parameters_and_defaults') and klass.parameters_and_defaults not in done_parameters_and_defaults:
    311                  for parameter_name in klass.parameters_and_defaults.keys():
    312                      # Make sure this parameter key/value pair doesn't exist already
    313                      if hasattr(self.parameters,parameter_name):
    314                          raise ValueError("More than one definition of parameter '%s'"%parameter_name)
    315                      # Get default value and the type
    316                      value,tipe = klass.parameters_and_defaults[parameter_name][:2]
    317                      # Check tipe is valid
    318                      if not ve_types.is_parameter_type_def(tipe):
    319                          raise ValueError("In definition of parameter '%s', %s is not a valid type declaration."%(parameter_name,tipe))
    320                      # Was a non-default value passed for this parameter?
    321                      if kw.has_key(parameter_name):
    322                          value = kw[parameter_name]
    323                          done_kw.append(parameter_name)
    324                      # Allow None to pass as acceptable value -- lets __init__ set own defaults
    325                      if value is not None:
    326                          # Check anything other than None
    327                          if not tipe.verify(value):
    328                              print 'parameter_name',parameter_name
    329                              print 'value',value
    330                              print 'type value',type(value)
    331                              print 'isinstance(value, numpy.ndarray)',isinstance(value, numpy.ndarray)
    332                              print 'tipe',tipe
    333
    334                              if not isinstance(value, numpy.ndarray):
    335                                  value_str = str(value)
    336                              else:
    337                                  if Numeric.multiply.reduce(value.shape) < 10:
    338                                      value_str = str(value) # print array if it's smallish
    339                                  else:
    340                                      value_str = "(array data)" # don't pring if it's big
    341                              raise TypeError("Parameter '%s' value %s is type %s (not type %s) in %s"%(parameter_name,value_str,type(value),tipe,self))
    342                      setattr(self.parameters,parameter_name,value)
    343                  done_parameters_and_defaults.append(klass.parameters_and_defaults)
    344
    345              # Same thing as above for self.constant_parameters:
    346              #
    347              # Create self.constant_parameters and set values to keyword argument if found,
    348              # otherwise to default value.
    349              #
    350              # If a class didn't override base class's parameters_and_defaults dictionary, don't deal with it twice
    351              if hasattr(klass, 'constant_parameters_and_defaults') and klass.constant_parameters_and_defaults not in done_constant_parameters_and_defaults:
    352                  for parameter_name in klass.constant_parameters_and_defaults.keys():
    353                      # Make sure this parameter key/value pair doesn't exist already
    354                      if hasattr(self.parameters,parameter_name):
    355                          raise ValueError("Definition of '%s' as variable parameter and constant parameter."%parameter_name)
    356                      if hasattr(self.constant_parameters,parameter_name):
    357                          raise ValueError("More than one definition of constant parameter '%s'"%parameter_name)
    358                      # Get default value and the type
    359                      value,tipe = klass.constant_parameters_and_defaults[parameter_name][:2]
    360
    361                      if not ve_types.is_parameter_type_def(tipe):
    362                          raise ValueError("In definition of constant parameter '%s', %s is not a valid type declaration."%(parameter_name,tipe))
    363                      # Was a non-default value passed for this parameter?
    364                      if kw.has_key(parameter_name):
    365                          value = kw[parameter_name]
    366                          done_kw.append(parameter_name)
    367                      # Allow None to pass as acceptable value -- lets __init__ set own default
    368                      if type(value) != type(None):
    369                          # Check anything other than None
    370                          if not tipe.verify(value):
    371                              if type(value) != Numeric.ArrayType:
    372                                  value_str = str(value)
    373                              else:
    374                                  if Numeric.multiply.reduce(value.shape) < 10:
    375                                      value_str = str(value) # print array if it's smallish
    376                                  else:
    377                                      value_str = "(array data)" # don't pring if it's big
    378                              raise TypeError("Constant parameter '%s' value %s is type %s (not type %s) in %s"%(parameter_name,value_str,type(value),tipe,self))
    379                      setattr(self.constant_parameters,parameter_name,value)
    380                  done_constant_parameters_and_defaults.append(klass.constant_parameters_and_defaults)
    381
    382          # Set self.parameters to the value in "kw"
    383          for kw_parameter_name in kw.keys():
    384              if kw_parameter_name not in done_kw:
    385                  raise ValueError("parameter '%s' passed as keyword argument, but not specified by %s (or subclasses) as potential parameter"%(kw_parameter_name,self.__class__))
    386
    387 -    def is_constant_parameter(self,parameter_name):
    388          # Get a list of all classes this instance is derived from
    389          classes = recursive_base_class_finder(self.__class__)
    390          for klass in classes:
    391              if klass == object:
    392                  continue # base class of new style classes - ignore
    393              if klass.constant_parameters_and_defaults.has_key(parameter_name):
    394                  return True
    395          # The for loop only completes if parameter_name is not in any subclass
    396          return False
    397
    398 -    def get_specified_type(self,parameter_name):
    399          # Get a list of all classes this instance is derived from
    400          classes = recursive_base_class_finder(self.__class__)
    401          for klass in classes:
    402              if klass == object:
    403                  continue # base class of new style classes - ignore
    404              if klass.parameters_and_defaults.has_key(parameter_name):
    405                  return klass.parameters_and_defaults[parameter_name][1]
    406          # The for loop only completes if parameter_name is not in any subclass
    407          raise AttributeError("%s has no parameter named '%s'"%(self.__class__,parameter_name))
    408
    409 -    def verify_parameters(self):
    410          """Perform type check on all parameters"""
    411          for parameter_name in dir(self.parameters):
    412              if parameter_name.startswith('__'):
    413                  continue
    414              require_type = self.get_specified_type(parameter_name)
    415              this_type = ve_types.get_type(getattr(self.parameters,parameter_name))
    416              ve_types.assert_type(this_type,require_type)
    417
    418 -    def set(self,**kw):
    419          """Set a parameter with type-checked value
    420
    421          This is the slow but safe way to set parameters.  It is recommended to
    422          use this method in all but speed-critical portions of code.
    423          """
    424          # Note that we don't overload __setattr__ because that would always slow
    425          # down assignment, not just when it was convenient.
    426          #
    427          # (We could make a checked_parameters attribute though.)
    428          for parameter_name in kw.keys():
    429              setattr(self.parameters,parameter_name,kw[parameter_name])
    430              require_type = self.get_specified_type(parameter_name)
    431              value = kw[parameter_name]
    432              this_type = ve_types.get_type(value)
    433              ve_types.assert_type(this_type,require_type)
    434              setattr(self.parameters,parameter_name,value)
    435
    436 -def get_type(value):
    437      warnings.warn("VisionEgg.get_type() has been moved to "+\
    438                    "VisionEgg.ParameterTypes.get_type()",
    439                    DeprecationWarning, stacklevel=2)
    440      return  ve_types.get_type(value)
    441
    442 -def assert_type(*args):
    443      warnings.warn("VisionEgg.assert_type() has been moved to "+\
    444                    "VisionEgg.ParameterTypes.assert_type()",
    445                    DeprecationWarning, stacklevel=2)
    446      return ve_types.assert_type(*args)
    447
    448 -def _get_lowerleft(position,anchor,size):
    449      """Private helper function
    450
    451      size is (width, height)
    452      """
    453      if anchor == 'lowerleft':
    454          lowerleft = position
    455      else:
    456          if len(position) > 2: z = position[2]
    457          else: z = 0.0
    458          if len(position) > 3: w = position[3]
    459          else: w = 1.0
    460          if z != 0.0: warnings.warn( "z coordinate (other than 0.0) specificed where anchor not 'lowerleft' -- cannot compute")
    461          if w != 1.0: warnings.warn("w coordinate (other than 1.0) specificed where anchor not 'lowerleft' -- cannot compute")
    462          if anchor == 'center':
    463              lowerleft = (position[0] - size[0]/2.0, position[1] - size[1]/2.0)
    464          elif anchor == 'lowerright':
    465              lowerleft = (position[0] - size[0],position[1])
    466          elif anchor == 'upperright':
    467              lowerleft = (position[0] - size[0],position[1] - size[1])
    468          elif anchor == 'upperleft':
    469              lowerleft = (position[0],position[1] - size[1])
    470          elif anchor == 'left':
    471              lowerleft = (position[0],position[1] - size[1]/2.0)
    472          elif anchor == 'right':
    473              lowerleft = (position[0] - size[0],position[1] - size[1]/2.0)
    474          elif anchor == 'bottom':
    475              lowerleft = (position[0] - size[0]/2.0,position[1])
    476          elif anchor == 'top':
    477              lowerleft = (position[0] - size[0]/2.0,position[1] - size[1])
    478          else:
    479              raise ValueError("No anchor position %s"%anchor)
    480      return lowerleft
    481
    482 -def _get_center(position,anchor,size):
    483      """Private helper function"""
    484      if anchor == 'center':
    485          center = position
    486      else:
    487          if len(position) > 2: z = position[2]
    488          else: z = 0.0
    489          if len(position) > 3: w = position[3]
    490          else: w = 1.0
    491          if z != 0.0: raise ValueError("z coordinate (other than 0.0) specificed where anchor not 'center' -- cannot compute")
    492          if w != 1.0: raise ValueError("w coordinate (other than 1.0) specificed where anchor not 'center' -- cannot compute")
    493          if anchor == 'lowerleft':
    494              center = (position[0] + size[0]/2.0, position[1] + size[1]/2.0)
    495          elif anchor == 'lowerright':
    496              center = (position[0] - size[0]/2.0, position[1] + size[1]/2.0)
    497          elif anchor == 'upperright':
    498              center = (position[0] - size[0]/2.0, position[1] - size[1]/2.0)
    499          elif anchor == 'upperleft':
    500              center = (position[0] + size[0]/2.0, position[1] - size[1]/2.0)
    501          elif anchor == 'left':
    502              center = (position[0] + size[0]/2.0, position[1])
    503          elif anchor == 'right':
    504              center = (position[0] - size[0]/2.0, position[1])
    505          elif anchor == 'bottom':
    506              center = (position[0],position[1] + size[1]/2.0)
    507          elif anchor == 'top':
    508              center = (position[0],position[1] - size[1]/2.0)
    509          else:
    510              raise ValueError("No anchor position %s"%anchor)
    511      return center
    512