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

Source Code for Module VisionEgg.FlowControl

   1  # The Vision Egg: FlowControl 
   2  # 
   3  # Copyright (C) 2001-2004 Andrew Straw. 
   4  # Copyright (C) 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  Flow control for the Vision Egg. 
  13   
  14  """ 
  15   
  16  import logging 
  17  import logging.handlers 
  18   
  19  import VisionEgg 
  20  import VisionEgg.GL as gl # get all OpenGL stuff in one namespace 
  21  import VisionEgg.ParameterTypes as ve_types 
  22  import numpy.oldnumeric as Numeric, math, types 
  23  import pygame 
  24   
  25  #################################################################### 
  26  # 
  27  #        Presentation 
  28  # 
  29  #################################################################### 
  30   
31 -class Presentation(VisionEgg.ClassWithParameters):
32 """Handles the timing and coordination of stimulus presentation. 33 34 This class is the key to the real-time operation of the Vision 35 Egg. It contains the main 'go' loop, and maintains the association 36 between 'controllers', instances of the Controller class, and the 37 parameters they control. 38 39 During the main 'go' loop and at other specific times, the 40 parameters are updated via function calls to the controllers. 41 42 Between entries into the 'go' loop, a Vision Egg application 43 should call the method between_presentations as often as possible 44 to ensure parameter values are kept up to date and any 45 housekeeping done by controllers is done. 46 47 No OpenGL environment I know of can guarantee that a new frame is 48 drawn and the double buffers swapped before the monitor's next 49 vertical retrace sync pulse. Still, although one can worry 50 endlessly about this problem, it works. In other words, on a fast 51 computer with a fast graphics card running even a pre-emptive 52 multi-tasking operating system (see below for specific 53 information), a new frame is drawn before every monitor update. If 54 this did become a problem, the go() method could be re-implemented 55 in C, along with the functions it calls. This would probably 56 result in speed gains, but without skipping frames at 200 Hz, why 57 bother? 58 59 Parameters 60 ========== 61 check_events -- allow input event checking during 'go' loop? (Boolean) 62 Default: True 63 collect_timing_info -- log timing statistics during go loop? (Boolean) 64 Default: True 65 enter_go_loop -- test used by run_forever() to enter go loop (Boolean) 66 Default: False 67 go_duration -- Tuple to specify 'go' loop duration. Either (value,units) or ('forever',) (Sequence of AnyOf(Real or String)) 68 Default: (5.0, 'seconds') 69 handle_event_callbacks -- List of tuples to handle events. (event_type,event_callback_func) (Sequence of Sequence2 of AnyOf(Integer or Callable)) 70 Default: (determined at runtime) 71 override_t_abs_sec -- Override t_abs. Set only when reconstructing experiments. (units: seconds) (Real) 72 Default: (determined at runtime) 73 quit -- quit the run_forever loop? (Boolean) 74 Default: False 75 trigger_armed -- test trigger on go loop? (Boolean) 76 Default: True 77 trigger_go_if_armed -- trigger go loop? (Boolean) 78 Default: True 79 viewports -- list of Viewport instances to draw. Order is important. (Sequence of Instance of <class 'VisionEgg.ClassWithParameters'>) 80 Default: (determined at runtime) 81 warn_longest_frame_threshold -- threshold to print frame skipped warning (units: factor of inter-frame-interval) (Real) 82 Default: 2.0 83 warn_mean_fps_threshold -- threshold to print observered vs. expected frame rate warning (fraction units) (Real) 84 Default: 0.01 85 """ 86 parameters_and_defaults = { 87 'viewports' : (None, 88 # XXX should really require VisionEgg.Core.Viewport 89 # but that would lead to circular import problem 90 ve_types.Sequence(ve_types.Instance(VisionEgg.ClassWithParameters)), 91 'list of Viewport instances to draw. Order is important.'), 92 'collect_timing_info' : (True, 93 ve_types.Boolean, 94 'log timing statistics during go loop?'), 95 'go_duration' : ((5.0,'seconds'), 96 ve_types.Sequence(ve_types.AnyOf(ve_types.Real, 97 ve_types.String)), 98 "Tuple to specify 'go' loop duration. Either (value,units) or ('forever',)"), 99 'check_events' : (True, # May cause slight performance hit, but probably negligible 100 ve_types.Boolean, 101 "allow input event checking during 'go' loop?"), 102 'handle_event_callbacks' : (None, 103 ve_types.Sequence(ve_types.Sequence2(ve_types.AnyOf(ve_types.Integer,ve_types.Callable))), 104 "List of tuples to handle events. (event_type,event_callback_func)"), 105 'trigger_armed':(True, 106 ve_types.Boolean, 107 "test trigger on go loop?"), 108 'trigger_go_if_armed':(True, 109 ve_types.Boolean, 110 "trigger go loop?"), 111 'enter_go_loop':(False, 112 ve_types.Boolean, 113 "test used by run_forever() to enter go loop"), 114 'quit':(False, 115 ve_types.Boolean, 116 "quit the run_forever loop?"), 117 'warn_mean_fps_threshold':(0.01, # fraction (0.1 = 10%) 118 ve_types.Real, 119 "threshold to print observered vs. expected frame rate warning (fraction units)"), 120 'warn_longest_frame_threshold': (2.0, # fraction (set to 2.0 for no false positives) 121 ve_types.Real, 122 "threshold to print frame skipped warning (units: factor of inter-frame-interval)"), 123 'override_t_abs_sec':(None, # override t_abs (in seconds) -- set only when reconstructing experiments 124 ve_types.Real, 125 "Override t_abs. Set only when reconstructing experiments. (units: seconds)"), 126 } 127 128 __slots__ = ( 129 'controllers', 130 'num_frame_controllers', 131 'frame_draw_times', 132 'time_sec_absolute', 133 'frames_absolute', 134 'in_go_loop', 135 'frames_dropped_in_last_go_loop', 136 'last_go_loop_start_time_absolute_sec', 137 'time_sec_since_go', 138 'frames_since_go', 139 ) 140
141 - def __init__(self,**kw):
142 VisionEgg.ClassWithParameters.__init__(self,**kw) 143 144 if self.parameters.viewports is None: 145 self.parameters.viewports = [] 146 147 if self.parameters.handle_event_callbacks is None: 148 self.parameters.handle_event_callbacks = [] 149 150 self.controllers = [] 151 self.num_frame_controllers = 0 # reference counter for controllers that are called on frame by frame basis 152 153 # A list that optionally records when frames were drawn by go() method. 154 self.frame_draw_times = [] 155 156 self.time_sec_absolute=VisionEgg.time_func() 157 self.frames_absolute=0 158 159 self.in_go_loop = False 160 self.frames_dropped_in_last_go_loop = False 161 self.last_go_loop_start_time_absolute_sec = None
162
163 - def add_controller( self, class_with_parameters, parameter_name, controller ):
164 """Add a controller""" 165 # Check if type checking needed 166 if type(class_with_parameters) != types.NoneType and type(parameter_name) != types.NoneType: 167 # Check if return type of controller eval is same as parameter type 168 if class_with_parameters.is_constant_parameter(parameter_name): 169 raise TypeError("Attempt to control constant parameter '%s' of class %s."%(parameter_name,class_with_parameters)) 170 require_type = class_with_parameters.get_specified_type(parameter_name) 171 try: 172 ve_types.assert_type(controller.returns_type(),require_type) 173 except TypeError: 174 raise TypeError("Attempting to control parameter '%s' of type %s with controller that returns type %s"%( 175 parameter_name, 176 require_type, 177 controller.returns_type())) 178 if not hasattr(class_with_parameters.parameters,parameter_name): 179 raise AttributeError("%s has no instance '%s'"%parameter_name) 180 self.controllers.append( (class_with_parameters.parameters,parameter_name, controller) ) 181 else: # At least one of class_with_parameters or parameter_name is None. 182 # Make sure they both are None. 183 if not (type(class_with_parameters) == types.NoneType and type(parameter_name) == types.NoneType): 184 raise ValueError("Neither or both of class_with_parameters and parameter_name must be None.") 185 self.controllers.append( (None,None,controller) ) 186 if controller.temporal_variables & (FRAMES_SINCE_GO|FRAMES_ABSOLUTE): 187 self.num_frame_controllers = self.num_frame_controllers + 1
188
189 - def remove_controller( self, class_with_parameters, parameter_name, controller=None ):
190 """Remove one (or more--see below) controller(s). 191 192 If controller is None, all controllers affecting the 193 specified parameter are removed. 194 195 If class_with_parameters and paramter_name are None, the 196 controller is removed completely 197 198 If class_with_parameters, paramter_name, and controller are 199 all None, all controllers are removed. 200 201 """ 202 203 if class_with_parameters is None and parameter_name is None: 204 if not isinstance(controller,Controller) and controller != None: 205 206 raise TypeError( "When deleting a controller, specify an " 207 "instance of VisionEgg.FlowControl.Controller class!") 208 209 if controller == None: #Added by Tony, May30/2005 210 self.controllers = [] 211 212 i = 0 213 while i < len(self.controllers): 214 orig_parameters,orig_parameter_name,orig_controller = self.controllers[i] 215 if controller == orig_controller: 216 del self.controllers[i] 217 else: 218 i = i + 1 219 return 220 if controller is None: 221 # The controller function is not specified: 222 # Delete all controllers that control the parameter specified. 223 if class_with_parameters is None or parameter_name is None: 224 raise ValueError("Must specify parameter from which controller should be removed.") 225 i = 0 226 while i < len(self.controllers): 227 orig_parameters,orig_parameter_name,orig_controller = self.controllers[i] 228 if (orig_parameters == class_with_parameters.parameters and 229 orig_parameter_name == parameter_name): 230 controller = self.controllers[i][2] 231 if controller.temporal_variables & (FRAMES_SINCE_GO|FRAMES_ABSOLUTE): 232 self.num_frame_controllers = self.num_frame_controllers - 1 233 del self.controllers[i] 234 else: 235 i = i + 1 236 else: # controller is specified 237 # Delete only that specific controller 238 i = 0 239 while i < len(self.controllers): 240 orig_parameters,orig_parameter_name,orig_controller = self.controllers[i] 241 if (orig_parameters == class_with_parameters.parameters and 242 orig_parameter_name == parameter_name and 243 orig_controller == controller): 244 if controller.temporal_variables & (FRAMES_SINCE_GO|FRAMES_ABSOLUTE): 245 self.num_frame_controllers = self.num_frame_controllers - 1 246 else: 247 i = i + 1
248
249 - def __call_controllers(self, 250 go_started=None, 251 doing_transition=None):
252 done_once = [] # list of ONCE contollers to switch status of 253 for (parameters_instance, parameter_name, controller) in self.controllers: 254 evaluate = 0 255 if controller.eval_frequency & ONCE: 256 evaluate = 1 257 done_once.append(controller) 258 elif doing_transition and (controller.eval_frequency & TRANSITIONS): 259 evaluate = 1 260 elif controller.eval_frequency & EVERY_FRAME: 261 evaluate = 1 262 263 if evaluate: 264 if controller.temporal_variables & TIME_SEC_ABSOLUTE: 265 controller.time_sec_absolute = self.time_sec_absolute 266 if controller.temporal_variables & FRAMES_ABSOLUTE: 267 controller.frames_absolute = self.frames_absolute 268 269 if go_started: 270 if not (controller.eval_frequency & NOT_DURING_GO): 271 if controller.temporal_variables & TIME_SEC_SINCE_GO: 272 controller.time_sec_since_go = self.time_sec_since_go 273 if controller.temporal_variables & FRAMES_SINCE_GO: 274 controller.frames_since_go = self.frames_since_go 275 result = controller.during_go_eval() 276 if parameter_name is not None: 277 setattr(parameters_instance, parameter_name, result) 278 else: 279 if not (controller.eval_frequency & NOT_BETWEEN_GO): 280 if controller.temporal_variables & TIME_SEC_SINCE_GO: 281 controller.time_sec_since_go = None 282 if controller.temporal_variables & FRAMES_SINCE_GO: 283 controller.frames_since_go = None 284 result = controller.between_go_eval() 285 if parameter_name is not None: 286 setattr(parameters_instance, parameter_name, result) 287 288 for controller in done_once: 289 #Unset ONCE flag 290 controller.eval_frequency = controller.eval_frequency & ~ONCE 291 if isinstance(controller,EncapsulatedController): 292 controller.contained_controller.eval_frequency = controller.contained_controller.eval_frequency & ~ONCE
293
294 - def is_in_go_loop(self):
295 """Queries if the presentation is in a go loop. 296 297 This is useful to check the state of the Vision Egg 298 application from a remote client over Pyro.""" 299 return self.in_go_loop
300
302 return self.frames_dropped_in_last_go_loop
303
305 return self.last_go_loop_start_time_absolute_sec
306
307 - def go(self):
308 """Main control loop during stimulus presentation. 309 310 This is the heart of realtime control in the Vision Egg, and 311 contains the main loop during a stimulus presentation. This 312 coordinates the timing of calling the controllers. 313 314 In the main loop, the current time (in absolute seconds, 315 go-loop-start-relative seconds, and go-loop-start-relative 316 frames) is computed, the appropriate controllers are called 317 with this information, the screen is cleared, each viewport is 318 drawn to the back buffer (while the video card continues 319 painting the front buffer on the display), and the buffers are 320 swapped. 321 322 """ 323 import VisionEgg.Core # here to prevent circular import 324 self.in_go_loop = 1 325 326 swap_buffers = VisionEgg.Core.swap_buffers # shorthand 327 328 # Clear boolean indicator 329 self.frames_dropped_in_last_go_loop = False 330 331 # Create shorthand notation, which speeds the main loop 332 # slightly by not performing name lookup each time. 333 p = self.parameters 334 335 if p.collect_timing_info: 336 frame_timer = VisionEgg.