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