1
2
3
4
5
6
7
8
9
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
39 import numpy
40 import numpy.oldnumeric as Numeric
41 import warnings
42 import traceback
43 import StringIO
44
45 import logging
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
54
55
56 if not hasattr(sys,'frozen'):
57
58 try:
59 __import__('VisionEgg.VisionEgg')
60 except ImportError:
61 pass
62 else:
63
64 raise RuntimeError('Outdated "VisionEgg.py" and/or "VisionEgg.pyc" found. Please delete from your VisionEgg package directory.')
65
66
67 config = VisionEgg.Configuration.Config()
68
69
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
77 """Create and add log handlers"""
78 global _default_logging_started
79 if _default_logging_started:
80 return
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
104
105 if not sys.argv[0]:
106 config.VISIONEGG_GUI_ON_ERROR = 0
107
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
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
131
132
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
142 if hasattr(config,"_pygame_started"):
143 if config._pygame_started:
144 pygame_bug_workaround = True
145 if sys.platform.startswith('linux'):
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
154 __keep_config__ = config
155 self.orig_hook(exc_type, exc_value, exc_traceback)
156 config = __keep_config__
157
159 self._sys = sys
160 self.orig_hook = self._sys.excepthook
161 sys.excepthook = self.handle_exception
162
164 self._sys.excepthook = self.orig_hook
165
167 """Catch exceptions, log them, and optionally open GUI."""
168 global _exception_hook_keeper
169 _exception_hook_keeper = _ExceptionHookKeeper()
170
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
185
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
193 result2 = []
194 for r in result:
195 if r not in result2:
196 result2.append(r)
197 return result2
198
199
200
201 if sys.platform == "win32":
202
203 true_time_func = time.clock
204 else:
205 true_time_func = time.time
206
207
208
209
210 config._FRAMECOUNT_ABSOLUTE = 0
213
214 time_func = true_time_func
215
218
221
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
233
234
235
237 """Parameter container.
238
239 Simple empty class to act something like a C struct."""
240 pass
241
247
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({})
268 constant_parameters_and_defaults = ParameterDefinition({})
269
270 __slots__ = ('parameters','constant_parameters')
271
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
284 """support for being unpickled"""
285 for attr in dict.keys():
286 setattr(self,attr,dict[attr])
287
288 __safe_for_unpickling__ = True
289
291 """Create self.parameters and set values."""
292 self.constant_parameters = Parameters()
293 self.parameters = Parameters()
294
295
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
303 for klass in classes:
304 if klass == object:
305 continue
306
307
308
309
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
313 if hasattr(self.parameters,parameter_name):
314 raise ValueError("More than one definition of parameter '%s'"%parameter_name)
315
316 value,tipe = klass.parameters_and_defaults[parameter_name][:2]
317
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
321 if kw.has_key(parameter_name):
322 value = kw[parameter_name]
323 done_kw.append(parameter_name)
324
325 if value is not None:
326
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)
339 else:
340 value_str = "(array data)"
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
346
347
348
349
350
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
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
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
364 if kw.has_key(parameter_name):
365 value = kw[parameter_name]
366 done_kw.append(parameter_name)
367
368 if type(value) != type(None):
369
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)
376 else:
377 value_str = "(array data)"
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
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
397
408
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
425
426
427
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
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
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
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
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