1
2
3
4
5
6
7
8
9
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
21 import VisionEgg.ParameterTypes as ve_types
22 import numpy.oldnumeric as Numeric, math, types
23 import pygame
24
25
26
27
28
29
30
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
89
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,
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,
118 ve_types.Real,
119 "threshold to print observered vs. expected frame rate warning (fraction units)"),
120 'warn_longest_frame_threshold': (2.0,
121 ve_types.Real,
122 "threshold to print frame skipped warning (units: factor of inter-frame-interval)"),
123 'override_t_abs_sec':(None,
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
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
152
153
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
166 if type(class_with_parameters) != types.NoneType and type(parameter_name) != types.NoneType:
167
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:
182
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:
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
222
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:
237
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
252 done_once = []
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
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
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
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
324 self.in_go_loop = 1
325
326 swap_buffers = VisionEgg.Core.swap_buffers
327
328
329 self.frames_dropped_in_last_go_loop = False
330
331
332
333 p = self.parameters
334
335 if p.collect_timing_info:
336 frame_timer = VisionEgg.