1
2
3
4
5
6
7
8
9
10
11 """
12 Core Vision Egg functionality.
13
14 This module contains the architectural foundations of the Vision Egg.
15
16 """
17
18
19
20
21
22
23
24 import sys, types, math, time, os
25 import StringIO
26
27 import logging
28
29 import VisionEgg
30 import VisionEgg.PlatformDependent
31 import VisionEgg.ParameterTypes as ve_types
32 import VisionEgg.GLTrace
33 import VisionEgg.ThreeDeeMath
34
35 import pygame
36 import pygame.locals
37 import pygame.display
38
39 import VisionEgg.GL as gl
40
41 import numpy
42 import numpy.oldnumeric as Numeric
43
44
45 try:
46 sum
47 except NameError:
48 import operator
50 return reduce(operator.add, values )
51
53 VisionEgg.config._FRAMECOUNT_ABSOLUTE += 1
54 return pygame.display.flip()
55
56
57
58
59
60
61
62 -class Screen(VisionEgg.ClassWithParameters):
63 """An OpenGL window, possibly displayed across multiple displays.
64
65 A Screen instance is an OpenGL window for the Vision Egg to draw
66 in. For an instance of Screen to do anything useful, it must
67 contain one or more instances of the Viewport class and one or
68 more instances of the Stimulus class.
69
70 Currently, only one OpenGL window is supported by the library with
71 which the Vision Egg initializes graphics (pygame/SDL). However,
72 this need not limit display to a single physical display device.
73 Many video drivers, for example, allow applications to treat two
74 separate monitors as one large array of contiguous pixels. By
75 sizing a window such that it occupies both monitors and creating
76 separate viewports for the portion of the window on each monitor,
77 a multiple screen effect can be created.
78
79 Public read-only variables
80 ==========================
81 size -- Tuple of 2 integers specifying width and height
82
83 Parameters
84 ==========
85 bgcolor -- background color (AnyOf(Sequence3 of Real or Sequence4 of Real))
86 Default: (0.5, 0.5, 0.5, 0.0)
87
88 Constant Parameters
89 ===================
90 alpha_bits -- number of bits per pixel for alpha channel. Can be set with VISIONEGG_REQUEST_ALPHA_BITS (UnsignedInteger)
91 Default: (determined at runtime)
92 blue_bits -- number of bits per pixel for blue channel. Can be set with VISIONEGG_REQUEST_BLUE_BITS (UnsignedInteger)
93 Default: (determined at runtime)
94 double_buffer -- use double buffering? Can be set with VISIONEGG_DOUBLE_BUFFER (Boolean)
95 Default: (determined at runtime)
96 frameless -- remove standard window frame? Can be set with VISIONEGG_FRAMELESS_WINDOW (Boolean)
97 Default: (determined at runtime)
98 fullscreen -- use full screen? Can be set with VISIONEGG_FULLSCREEN (Boolean)
99 Default: (determined at runtime)
100 green_bits -- number of bits per pixel for green channel. Can be set with VISIONEGG_REQUEST_GREEN_BITS (UnsignedInteger)
101 Default: (determined at runtime)
102 hide_mouse -- hide the mouse cursor? Can be set with VISIONEGG_HIDE_MOUSE (Boolean)
103 Default: (determined at runtime)
104 is_stereo -- allocate stereo framebuffers? Can be set with VISIONEGG_REQUEST_STEREO (Boolean)
105 Default: (determined at runtime)
106 maxpriority -- raise priority? (platform dependent) Can be set with VISIONEGG_MAXPRIORITY (Boolean)
107 Default: (determined at runtime)
108 multisample_samples -- preferred number of multisamples for FSAA (UnsignedInteger)
109 Default: (determined at runtime)
110 preferred_bpp -- preferred bits per pixel (bit depth) Can be set with VISIONEGG_PREFERRED_BPP (UnsignedInteger)
111 Default: (determined at runtime)
112 red_bits -- number of bits per pixel for red channel. Can be set with VISIONEGG_REQUEST_RED_BITS (UnsignedInteger)
113 Default: (determined at runtime)
114 size -- size (units: pixels) Can be set with VISIONEGG_SCREEN_W and VISIONEGG_SCREEN_H (Sequence2 of Real)
115 Default: (determined at runtime)
116 sync_swap -- synchronize buffer swaps to vertical sync? Can be set with VISIONEGG_SYNC_SWAP (Boolean)
117 Default: (determined at runtime)
118 """
119
120 parameters_and_defaults = VisionEgg.ParameterDefinition({
121 'bgcolor':((0.5,0.5,0.5,0.0),
122 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
123 ve_types.Sequence4(ve_types.Real)),
124 'background color',),
125 })
126
127 constant_parameters_and_defaults = VisionEgg.ParameterDefinition({
128 'size':(None,
129 ve_types.Sequence2(ve_types.Real),
130 'size (units: pixels) Can be set with VISIONEGG_SCREEN_W and VISIONEGG_SCREEN_H'),
131 'fullscreen':(None,
132 ve_types.Boolean,
133 'use full screen? Can be set with VISIONEGG_FULLSCREEN'),
134 'double_buffer':(None,
135 ve_types.Boolean,
136 'use double buffering? Can be set with VISIONEGG_DOUBLE_BUFFER'),
137 'preferred_bpp':(None,
138 ve_types.UnsignedInteger,
139 'preferred bits per pixel (bit depth) Can be set with VISIONEGG_PREFERRED_BPP'),
140 'maxpriority':(None,
141 ve_types.Boolean,
142 'raise priority? (platform dependent) Can be set with VISIONEGG_MAXPRIORITY'),
143 'hide_mouse':(None,
144 ve_types.Boolean,
145 'hide the mouse cursor? Can be set with VISIONEGG_HIDE_MOUSE'),
146 'frameless':(None,
147 ve_types.Boolean,
148 'remove standard window frame? Can be set with VISIONEGG_FRAMELESS_WINDOW'),
149 'sync_swap':(None,
150 ve_types.Boolean,
151 'synchronize buffer swaps to vertical sync? Can be set with VISIONEGG_SYNC_SWAP'),
152 'red_bits':(None,
153 ve_types.UnsignedInteger,
154 'number of bits per pixel for red channel. Can be set with VISIONEGG_REQUEST_RED_BITS'),
155 'green_bits':(None,
156 ve_types.UnsignedInteger,
157 'number of bits per pixel for green channel. Can be set with VISIONEGG_REQUEST_GREEN_BITS'),
158 'blue_bits':(None,
159 ve_types.UnsignedInteger,
160 'number of bits per pixel for blue channel. Can be set with VISIONEGG_REQUEST_BLUE_BITS'),
161 'alpha_bits':(None,
162 ve_types.UnsignedInteger,
163 'number of bits per pixel for alpha channel. Can be set with VISIONEGG_REQUEST_ALPHA_BITS'),
164 'is_stereo':(None,
165 ve_types.Boolean,
166 'allocate stereo framebuffers? Can be set with VISIONEGG_REQUEST_STEREO'),
167 'multisample_samples':(None,
168 ve_types.UnsignedInteger,
169 'preferred number of multisamples for FSAA'),
170 })
171
172 __slots__ = (
173 '__cursor_visible_func__',
174 '__pygame_quit__',
175 '_put_pixels_texture_stimulus',
176 '_pixel_coord_projection',
177 )
178
180 logger = logging.getLogger('VisionEgg.Core')
181
182 VisionEgg.ClassWithParameters.__init__(self,**kw)
183
184 cp = self.constant_parameters
185 if cp.size is None:
186 cp.size = (VisionEgg.config.VISIONEGG_SCREEN_W,
187 VisionEgg.config.VISIONEGG_SCREEN_H)
188 if cp.double_buffer is None:
189 cp.double_buffer = VisionEgg.config.VISIONEGG_DOUBLE_BUFFER
190 if cp.fullscreen is None:
191 cp.fullscreen = VisionEgg.config.VISIONEGG_FULLSCREEN
192 if cp.preferred_bpp is None:
193 cp.preferred_bpp = VisionEgg.config.VISIONEGG_PREFERRED_BPP
194 if cp.maxpriority is None:
195 cp.maxpriority = VisionEgg.config.VISIONEGG_MAXPRIORITY
196 if cp.hide_mouse is None:
197 cp.hide_mouse = VisionEgg.config.VISIONEGG_HIDE_MOUSE
198 if cp.frameless is None:
199 cp.frameless = VisionEgg.config.VISIONEGG_FRAMELESS_WINDOW
200 if cp.sync_swap is None:
201 cp.sync_swap = VisionEgg.config.VISIONEGG_SYNC_SWAP
202 if cp.red_bits is None:
203 cp.red_bits = VisionEgg.config.VISIONEGG_REQUEST_RED_BITS
204 if cp.green_bits is None:
205 cp.green_bits = VisionEgg.config.VISIONEGG_REQUEST_GREEN_BITS
206 if cp.blue_bits is None:
207 cp.blue_bits = VisionEgg.config.VISIONEGG_REQUEST_BLUE_BITS
208 if cp.alpha_bits is None:
209 cp.alpha_bits = VisionEgg.config.VISIONEGG_REQUEST_ALPHA_BITS
210 if cp.is_stereo is None:
211 cp.is_stereo = VisionEgg.config.VISIONEGG_REQUEST_STEREO
212 if cp.multisample_samples is None:
213 cp.multisample_samples = VisionEgg.config.VISIONEGG_MULTISAMPLE_SAMPLES
214
215 if VisionEgg.config.SYNCLYNC_PRESENT:
216 global synclync
217 import synclync
218 try:
219 VisionEgg.config._SYNCLYNC_CONNECTION = synclync.SyncLyncConnection()
220 except synclync.SyncLyncError, x:
221 logger.warning( "Could not connect to SyncLync device (SyncLyncError: %s)."%str(x))
222 VisionEgg.config._SYNCLYNC_CONNECTION = None
223 else:
224 logger.info( "Connected to SyncLync device" )
225 else:
226 VisionEgg.config._SYNCLYNC_CONNECTION = None
227
228
229 if cp.sync_swap:
230 sync_success = VisionEgg.PlatformDependent.sync_swap_with_vbl_pre_gl_init()
231
232
233
234
235 pygame.display.init()
236
237 if hasattr(pygame.display,"gl_set_attribute"):
238 pygame.display.gl_set_attribute(pygame.locals.GL_RED_SIZE,cp.red_bits)
239 pygame.display.gl_set_attribute(pygame.locals.GL_GREEN_SIZE,cp.green_bits)
240 pygame.display.gl_set_attribute(pygame.locals.GL_BLUE_SIZE,cp.blue_bits)
241 pygame.display.gl_set_attribute(pygame.locals.GL_ALPHA_SIZE,cp.alpha_bits)
242 pygame.display.gl_set_attribute(pygame.locals.GL_STEREO,cp.is_stereo)
243
244 if cp.multisample_samples > 0 :
245 pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLEBUFFERS,1)
246 pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLESAMPLES,cp.multisample_samples)
247 else:
248 logger.debug("Could not request or query exact bit depths, "
249 "alpha or stereo because you need "
250 "pygame release 1.4.9 or greater. This is "
251 "only of concern if you use a stimulus that "
252 "needs this. In that case, the stimulus "
253 "should check for the desired feature(s).")
254
255 if not hasattr(pygame.display,"set_gamma_ramp"):
256 logger.debug("set_gamma_ramp function not available "
257 "because you need pygame release 1.5 or "
258 "greater. This is only of concern if you "
259 "need this feature.")
260 pygame.display.set_caption("Vision Egg")
261
262 flags = pygame.locals.OPENGL
263 if cp.double_buffer:
264 flags = flags | pygame.locals.DOUBLEBUF
265 if cp.fullscreen:
266 flags = flags | pygame.locals.FULLSCREEN
267 if cp.frameless:
268 flags = flags | pygame.locals.NOFRAME
269
270 try_bpp = cp.preferred_bpp
271
272 append_str = ""
273 if cp.fullscreen:
274 screen_mode = "fullscreen"
275 else:
276 screen_mode = "window"
277 if hasattr(pygame.display,"gl_set_attribute"):
278 append_str = " (%d %d %d %d RGBA)."%(cp.red_bits,
279 cp.green_bits,
280 cp.blue_bits,
281 cp.alpha_bits)
282
283 logger.info("Requesting %s %d x %d %d bpp%s"%
284 (screen_mode,self.size[0],self.size[1],
285 try_bpp,append_str))
286
287 pygame.display.set_mode(self.size, flags, try_bpp )
288
289 VisionEgg.config._pygame_started = 1
290
291 try:
292 if sys.platform != 'darwin':
293 pygame.display.set_icon(pygame.transform.scale(pygame.image.load(
294 os.path.join(VisionEgg.config.VISIONEGG_SYSTEM_DIR,
295 'data','visionegg.bmp')).convert(),(32,32)))
296 else:
297 import AppKit
298 im = AppKit.NSImage.alloc()
299 im.initWithContentsOfFile_(
300 os.path.join(VisionEgg.config.VISIONEGG_SYSTEM_DIR,
301 'data','visionegg.tif'))
302 AppKit.NSApplication.setApplicationIconImage_(AppKit.NSApp(),im)
303
304 except Exception,x:
305 logger.info("Error while trying to set_icon: %s: %s"%
306 (str(x.__class__),str(x)))
307
308 global gl_vendor, gl_renderer, gl_version
309 gl_vendor = gl.glGetString(gl.GL_VENDOR)
310 gl_renderer = gl.glGetString(gl.GL_RENDERER)
311 gl_version = gl.glGetString(gl.GL_VERSION)
312
313 logger.info("OpenGL %s, %s, %s (PyOpenGL %s)"%
314 (gl_version, gl_renderer, gl_vendor, gl.__version__))
315
316 if gl_renderer == "GDI Generic" and gl_vendor == "Microsoft Corporation":
317 logger.warning("Using default Microsoft Windows OpenGL "
318 "drivers. Please (re-)install the latest "
319 "video drivers from your video card "
320 "manufacturer to get hardware accelerated "
321 "performance.")
322 if gl_renderer == "Mesa GLX Indirect" and gl_vendor == "VA Linux Systems, Inc.":
323 logger.warning("Using default Mesa GLX drivers. Please "
324 "(re-)install the latest video drivers from "
325 "your video card manufacturer or DRI "
326 "project to get hardware accelarated "
327 "performance.")
328
329 cp.red_bits = None
330 cp.green_bits = None
331 cp.blue_bits = None
332 cp.alpha_bits = None
333 cp.is_stereo = None
334 got_bpp = pygame.display.Info().bitsize
335 append_str = ''
336 if hasattr(pygame.display,"gl_get_attribute"):
337
338 cp.red_bits = pygame.display.gl_get_attribute(pygame.locals.GL_RED_SIZE)
339 cp.green_bits = pygame.display.gl_get_attribute(pygame.locals.GL_GREEN_SIZE)
340 cp.blue_bits = pygame.display.gl_get_attribute(pygame.locals.GL_BLUE_SIZE)
341 cp.alpha_bits = pygame.display.gl_get_attribute(pygame.locals.GL_ALPHA_SIZE)
342 cp.is_stereo = pygame.display.gl_get_attribute(pygame.locals.GL_STEREO)
343 if cp.is_stereo: stereo_string = ' stereo'
344 else: stereo_string = ''
345 append_str = " (%d %d %d %d RGBA%s)"%(
346 cp.red_bits,cp.green_bits,cp.blue_bits,cp.alpha_bits,
347 stereo_string)
348 logger.info("Video system reports %d bpp%s."%(got_bpp,append_str))
349 if got_bpp < try_bpp:
350 logger.warning("Video system reports %d bits per pixel, "
351 "while your program requested %d. Can you "
352 "adjust your video drivers?"%(got_bpp,
353 try_bpp))
354
355
356 self.__cursor_visible_func__ = pygame.mouse.set_visible
357 self.__pygame_quit__ = pygame.quit
358
359
360 if cp.multisample_samples>0 :
361 if hasattr(pygame.display,"gl_set_attribute"):
362 got_ms_buf = pygame.display.gl_get_attribute(pygame.locals.GL_MULTISAMPLEBUFFERS)
363 got_ms_samp = pygame.display.gl_get_attribute(pygame.locals.GL_MULTISAMPLESAMPLES)
364 if got_ms_samp < cp.multisample_samples :
365 logger.warning("Video system reports %d multisample samples, "
366 "while you requested %d. FSAA requires "
367 "SDL > 1.2.6, check that it is installed."%(got_ms_samp, cp.multisample_samples))
368
369
370 if cp.sync_swap:
371 if not sync_success:
372 if not VisionEgg.PlatformDependent.sync_swap_with_vbl_post_gl_init():
373 cp.sync_swap = False
374 logger.warning("Unable to detect or automatically "
375 "synchronize buffer swapping with "
376 "vertical retrace. May be possible "
377 "by manually adjusting video "
378 "drivers. (Look for 'Enable "
379 "Vertical Sync' or similar.) If "
380 "buffer swapping is not "
381 "synchronized, frame by frame "
382 "control will not be possible. "
383 "Because of this, you will probably "
384 "get a warning about calculated "
385 "frames per second different than "
386 "specified.")
387
388 post_gl_init()
389
390 if cp.hide_mouse:
391 self.__cursor_visible_func__(0)
392
393
394
395
396
397 if cp.maxpriority:
398 VisionEgg.PlatformDependent.set_priority()
399
400 if hasattr(VisionEgg.config,'_open_screens'):
401 VisionEgg.config._open_screens.append(self)
402 else:
403 VisionEgg.config._open_screens = [self]
404
405
406
408 - def set_size(self, value): raise RuntimeError("Attempting to set read-only value")
409 size = property(get_size,set_size)
410
411 - def get_framebuffer_as_image(self,
412 buffer='back',
413 format=gl.GL_RGB,
414 position=(0,0),
415 anchor='lowerleft',
416 size=None,
417 ):
418 """get pixel values from framebuffer to PIL image"""
419 import Image
420
421 fb_array = self.get_framebuffer_as_array(buffer=buffer,
422 format=format,
423 position=position,
424 anchor=anchor,
425 size=size,
426 )
427 size = fb_array.shape[1], fb_array.shape[0]
428 if format == gl.GL_RGB:
429 pil_mode = 'RGB'
430 elif format == gl.GL_RGBA:
431 pil_mode = 'RGBA'
432 fb_image = Image.fromstring(pil_mode,size,fb_array.tostring())
433 fb_image = fb_image.transpose( Image.FLIP_TOP_BOTTOM )
434 return fb_image
435
436 - def get_framebuffer_as_array(self,
437 buffer='back',
438 format=gl.GL_RGB,
439 position=(0,0),
440 anchor='lowerleft',
441 size=None,
442 ):
443 """get pixel values from framebuffer to Numeric array"""
444 if size is None:
445 size = self.size
446 lowerleft = VisionEgg._get_lowerleft(position,anchor,size)
447 if buffer == 'front':
448 gl.glReadBuffer( gl.GL_FRONT )
449 elif buffer == 'back':
450 gl.glReadBuffer( gl.GL_BACK )
451 else:
452 raise ValueError('No support for "%s" framebuffer'%buffer)
453
454
455 gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 4)
456 gl.glPixelStorei(gl.GL_PACK_ROW_LENGTH, 0)
457 gl.glPixelStorei(gl.GL_PACK_SKIP_ROWS, 0)
458 gl.glPixelStorei(gl.GL_PACK_SKIP_PIXELS, 0)
459 if gl_version >= '1.2' and hasattr(gl,'GL_BGRA'):
460 framebuffer_pixels = gl.glReadPixels(lowerleft[0],lowerleft[1],
461 size[0],size[1],
462 gl.GL_BGRA,
463 gl.GL_UNSIGNED_INT_8_8_8_8_REV)
464 raw_format = 'BGRA'
465 else:
466 framebuffer_pixels = gl.glReadPixels(lowerleft[0],lowerleft[1],
467 size[0],size[1],
468 gl.GL_RGBA,
469 gl.GL_UNSIGNED_BYTE)
470 raw_format = 'RGBA'
471 fb_array = Numeric.fromstring(framebuffer_pixels,Numeric.UInt8)
472 fb_array = Numeric.reshape(fb_array,(size[1],size[0],4))
473
474
475 if format == gl.GL_RGB:
476 if raw_format == 'BGRA':
477 fb_array = fb_array[:,:,1:]
478 elif raw_format == 'RGBA':
479 fb_array = fb_array[:,:,:3]
480 elif format == gl.GL_RGBA:
481 if raw_format == 'BGRA':
482 alpha = fb_array[:,:,0,Numeric.NewAxis]
483 fb_array = fb_array[:,:,1:]
484 fb_array = Numeric.concatenate( (fb_array,alpha), axis=2)
485 elif raw_format == 'RGBA':
486 pass
487 else:
488 raise NotImplementedError("Only RGB and RGBA formats currently supported")
489 return fb_array
490
491 - def put_pixels(self,
492 pixels=None,
493 position=(0,0),
494 anchor='lowerleft',
495 scale_x=1.0,
496 scale_y=1.0,
497 texture_min_filter=gl.GL_NEAREST,
498 texture_mag_filter=gl.GL_NEAREST,
499 internal_format=gl.GL_RGB,
500 ):
501 """Put pixel values to screen.
502
503 Pixel values become texture data using the VisionEgg.Textures
504 module. Any source of texture data accepted by that module is
505 accepted here.
506
507 This function could be sped up by allocating a fixed OpenGL texture object.
508
509 """
510
511 import VisionEgg.Textures
512 make_new_texture_object = 0
513 if not hasattr(self, "_put_pixels_texture_stimulus"):
514 make_new_texture_object = 1
515 else:
516 if internal_format != self._put_pixels_texture_stimulus.constant_parameters.internal_format:
517 make_new_texture_object = 1
518 if make_new_texture_object:
519
520 texture = VisionEgg.Textures.Texture(pixels)
521 on_screen_size = (texture.size[0]*scale_x, texture.size[1]*scale_y)
522 t = VisionEgg.Textures.TextureStimulus(texture=texture,
523 position=position,
524 anchor=anchor,
525 size=on_screen_size,
526 mipmaps_enabled=0,
527 texture_min_filter=texture_min_filter,
528 texture_mag_filter=texture_mag_filter,
529 internal_format = internal_format,
530 )
531 self._put_pixels_texture_stimulus = t
532 self._pixel_coord_projection = OrthographicProjection(left=0,
533 right=self.size[0],
534 bottom=0,
535 top=self.size[1],
536 z_clip_near=0.0,
537 z_clip_far=1.0)
538 else:
539
540
541
542
543 self._put_pixels_texture_stimulus.parameters.texture = VisionEgg.Textures.Texture(pixels)
544
545 self._pixel_coord_projection.push_and_set_gl_projection()
546 self._put_pixels_texture_stimulus.draw()
547
548 gl.glMatrixMode(gl.GL_PROJECTION)
549 gl.glPopMatrix()
550
553
555 """Measure the refresh rate. Assumes swap buffers synced."""
556 start_time = VisionEgg.time_func()
557 duration_sec = 0.0
558 num_frames = 0
559 while duration_sec < average_over_seconds:
560 swap_buffers()
561 now = VisionEgg.time_func()
562 num_frames += 1
563 duration_sec = now - start_time
564 if duration_sec > 0.0:
565 fps = num_frames / duration_sec
566 else:
567 fps = 0.0
568 return fps
569
571 """Called by Presentation instance. Clear the screen."""
572
573 c = self.parameters.bgcolor
574 if len(c) == 4:
575 gl.glClearColor(*c)
576 else:
577 gl.glClearColor(c[0],c[1],c[2],0.0)
578 gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
579
581 """Called by Viewport instance. Makes screen active for drawing.
582
583 Can not be implemented until multiple screens are possible."""
584 pass
585
587 """Set the gamma_ramp, if supported.
588
589 Call pygame.display.set_gamma_ramp, if available.
590
591 Returns True on success, False otherwise."""
592 if not hasattr(pygame.display,"set_gamma_ramp"):
593 logger = logging.getLogger('VisionEgg.Core')
594 logger.error("Need pygame 1.5 or greater for set_gamma_ramp function")
595 return False
596 if pygame.display.set_gamma_ramp(*args,**kw):
597 return True
598 else:
599 return False
600
602 """Close the screen.
603
604 You can call this to close the screen. Not necessary during
605 normal operation because it gets automatically deleted."""
606
607 if hasattr(VisionEgg.config,'_open_screens'):
608 if self in VisionEgg.config._open_screens:
609 VisionEgg.config._open_screens.remove(self)
610 if len(VisionEgg.config._open_screens) == 0:
611
612 if hasattr(self,"__cursor_visible_func__"):
613 self.__cursor_visible_func__(1)
614 pygame.quit()
615
616 if hasattr(self,"__cursor_visible_func__"):
617 del self.__cursor_visible_func__
618
620
621 if hasattr(self,"__cursor_visible_func__"):
622 try:
623 self.__cursor_visible_func__(1)
624 self.__pygame_quit__()
625 except pygame.error, x:
626 if str(x) != 'video system not initialized':
627 raise
628
630 """Alternative constructor using configuration variables.
631
632 Most of the time you can create and instance of Screen using
633 this method. If your script needs explicit control of the
634 Screen parameters, initialize with the normal constructor.
635
636 Uses VisionEgg.config.VISIONEGG_GUI_INIT to determine how the
637 default screen parameters should are determined. If this
638 value is 0, the values from VisionEgg.cfg are used. If this
639 value is 1, a GUI panel is opened and allows manual settings
640 of the screen parameters. """
641
642 global VisionEgg
643 if VisionEgg.config.VISIONEGG_GUI_INIT:
644 import VisionEgg.GUI
645 window = VisionEgg.GUI.GraphicsConfigurationWindow()
646 window.mainloop()
647 if not window.clicked_ok:
648 sys.exit()
649 screen = None
650 try:
651 screen = Screen(size=(VisionEgg.config.VISIONEGG_SCREEN_W,
652 VisionEgg.config.VISIONEGG_SCREEN_H),
653 fullscreen=VisionEgg.config.VISIONEGG_FULLSCREEN,
654 preferred_bpp=VisionEgg.config.VISIONEGG_PREFERRED_BPP,
655 bgcolor=(0.5,0.5,0.5,0.0),
656 maxpriority=VisionEgg.config.VISIONEGG_MAXPRIORITY,
657 frameless=VisionEgg.config.VISIONEGG_FRAMELESS_WINDOW,
658 hide_mouse=VisionEgg.config.VISIONEGG_HIDE_MOUSE)
659 finally:
660 if screen is None:
661
662 try:
663 pygame.mouse.set_visible(1)
664 pygame.quit()
665 except pygame.error, x:
666 if str(x) != 'video system not initialized':
667 raise
668
669 if screen is None:
670 raise RuntimeError("Screen open failed. Check your error log for a traceback.")
671
672 gamma_source = VisionEgg.config.VISIONEGG_GAMMA_SOURCE.lower()
673 if gamma_source != 'none':
674 if gamma_source == 'invert':
675 native_red = VisionEgg.config.VISIONEGG_GAMMA_INVERT_RED
676 native_green = VisionEgg.config.VISIONEGG_GAMMA_INVERT_GREEN
677 native_blue = VisionEgg.config.VISIONEGG_GAMMA_INVERT_BLUE
678 red = screen._create_inverted_gamma_ramp( native_red )
679 green = screen._create_inverted_gamma_ramp( native_green )
680 blue = screen._create_inverted_gamma_ramp( native_blue )
681 gamma_set_string = "linearized gamma lookup tables to correct "+\
682 "monitor with native gammas (%f, %f, %f) RGB"%(
683 native_red,
684 native_green,
685 native_blue)
686 elif gamma_source == 'file':
687 filename = VisionEgg.config.VISIONEGG_GAMMA_FILE
688 red, green, blue = screen._open_gamma_file(filename)
689 gamma_set_string = "set gamma lookup tables from data in file %s"%os.path.abspath(filename)
690 else:
691 raise ValueError("Unknown gamma source: '%s'"%gamma_source)
692 logger = logging.getLogger('VisionEgg.Core')
693 if not screen.set_gamma_ramp(red,green,blue):
694 logger.warning( "Setting gamma ramps failed." )
695 else:
696 logger.info( "Gamma set sucessfully: %s"%gamma_set_string )
697 return screen
698 create_default = staticmethod(create_default)
699
701
702
703
704 c = 1.0
705 inc = 1.0/255
706 target_luminances = Numeric.arange(0.0,1.0+inc,inc)
707 output_ramp = Numeric.zeros(target_luminances.shape,Numeric.Int)
708 for i in range(len(target_luminances)):
709 L = target_luminances[i]
710 if L == 0.0:
711 v_88fp = 0
712 else:
713 v = math.exp( (math.log(L) - math.log(c)) /gamma)
714 v_88fp = int(round((v*255) * 256))
715 output_ramp[i] = v_88fp
716 return list(output_ramp)
717
719 fd = open(filename,"r")
720 gamma_values = []
721 for line in fd.readlines():
722 line = line.strip()
723 if line.startswith("#"):
724 continue
725 gamma_values.append( map(int, line.split() ) )
726 if len(gamma_values[-1]) != 3:
727 raise ValueError("expected 3 values per gamma entry")
728 if len(gamma_values) != 256:
729 raise ValueError("expected 256 gamma entries")
730 red, green, blue = zip(*gamma_values)
731 return red,green,blue
732
734 """Make an instance of Screen using a GUI window or from config file."""
735 return Screen.create_default()
736
737
738
739
740
741
742
744 """Converts stimulus coordinates to viewport coordinates.
745
746 This is an abstract base class which should be subclassed for
747 actual use.
748
749 Parameters
750 ==========
751 matrix -- matrix specifying projection (Sequence4x4 of Real)
752 Default: [[1 0 0 0]
753 [0 1 0 0]
754 [0 0 1 0]
755 [0 0 0 1]]
756 """
757
758
759
760
761 parameters_and_defaults = VisionEgg.ParameterDefinition({
762 'matrix':( Numeric.identity(4),
763 ve_types.Sequence4x4(ve_types.Real),
764 'matrix specifying projection'),
765 })
766
767 __slots__ = (
768 'projection_type',
769 )
770
774
776 if self.projection_type == gl.GL_PROJECTION:
777 return gl.GL_PROJECTION_MATRIX
778 elif self.projection_type == gl.GL_MODELVIEW:
779 return gl.GL_MODELVIEW_MATRIX
780
782 """Set the OpenGL projection matrix."""
783 gl.glMatrixMode(self.projection_type)
784 gl.glLoadMatrixf(self.parameters.matrix)
785
787 """Set the OpenGL modelview matrix."""
788 gl.glMatrixMode(gl.GL_MODELVIEW)
789 gl.glLoadMatrixf(self.parameters.matrix)
790
792 """Set the OpenGL projection matrix."""
793 gl.glMatrixMode(gl.GL_PROJECTION)
794 gl.glLoadMatrixf(self.parameters.matrix)
795
797 """Set the OpenGL projection matrix, pushing current projection matrix to stack."""
798 gl.glMatrixMode(self.projection_type)
799 gl.glPushMatrix()
800 gl.glLoadMatrixf(self.parameters.matrix)
801
803 """Compose a translation and set the OpenGL projection matrix."""
804 gl.glMatrixMode(self.projection_type)
805 gl.glLoadMatrixf(self.parameters.matrix)
806 gl.glTranslatef(x,y,z)
807 self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
808
814
815 - def rotate(self,angle_degrees,x,y,z):
816 """Compose a rotation and set the OpenGL projection matrix."""
817 gl.glMatrixMode(self.projection_type)
818 gl.glLoadMatrixf(self.parameters.matrix)
819 gl.glRotatef(angle_degrees,x,y,z)
820 self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
821
827
829 """Compose a rotation and set the OpenGL projection matrix."""
830 gl.glMatrixMode(self.projection_type)
831 gl.glLoadMatrixf(self.parameters.matrix)
832 gl.glScalef(x,y,z)
833 self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
834
840
842 return self.parameters.matrix
843
844 - def look_at(self, eye, center, up ):
845
846 def normalize(vec):
847 numpy_vec = numpy.asarray(vec)
848 mag = math.sqrt(numpy.sum(numpy_vec**2))
849 return numpy_vec / mag
850 def cross(vec1,vec2):
851 return ( vec1[1]*vec2[2] - vec1[2]*vec2[1],
852 vec1[2]*vec2[0] - vec1[0]*vec2[2],
853 vec1[0]*vec2[1] - vec1[1]*vec2[0] )
854 forward = numpy.array(( center[0] - eye[0],
855 center[1] - eye[1],
856 center[2] - eye[2]),'f')
857 forward = normalize(forward)
858 side = cross(forward,up)
859 side = normalize(side)
860 new_up = cross(side,forward)
861
862 m = Numeric.array([[side[0], new_up[0], -forward[0], 0.0],
863 [side[1], new_up[1], -forward[1], 0.0],
864 [side[2], new_up[2], -forward[2], 0.0],
865 [ 0.0, 0.0, 0.0, 1.0]])
866
867 gl.glMatrixMode(self.projection_type)
868 gl.glPushMatrix()
869 try:
870 gl.glLoadMatrixf(self.parameters.matrix)
871 gl.glMultMatrixf(m)
872 gl.glTranslatef(-eye[0],-eye[1],-eye[2])
873 self.parameters.matrix = gl.glGetFloatv(self._get_matrix_type())
874 finally:
875 gl.glPopMatrix()
876
878 """Transform eye coordinates to clip coordinates"""
879 m = Numeric.array(self.parameters.matrix)
880 v = Numeric.array(eye_coords_vertex)
881 homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
882 r = numpy.dot(homog,m)
883 if len(homog.shape) > len(v.shape):
884 r = Numeric.reshape(r,(4,))
885 return r
887 """Transform clip coordinates to normalized device coordinates"""
888 v = numpy.array(clip_coords_vertex)
889 homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
890 err=numpy.seterr(all='ignore')
891 r = (homog/homog[:,3,numpy.newaxis])[:,:3]
892 numpy.seterr(**err)
893 if len(homog.shape) > len(v.shape):
894 r = Numeric.reshape(r,(3,))
895 return r
899
905
911
913 """for use of OpenGL PROJECTION_MATRIX
914
915 Parameters
916 ==========
917 matrix -- matrix specifying projection (Sequence4x4 of Real)
918 Default: [[1 0 0 0]
919 [0 1 0 0]
920 [0 0 1 0]
921 [0 0 0 1]]
922 """
923
927
929 """for use of OpenGL MODELVIEW_MATRIX
930
931 Parameters
932 ==========
933 matrix -- matrix specifying projection (Sequence4x4 of Real)
934 Default: [[1 0 0 0]
935 [0 1 0 0]
936 [0 0 1 0]
937 [0 0 0 1]]
938 """
939
943
945 """An orthographic projection.
946
947 Parameters
948 ==========
949 matrix -- matrix specifying projection (Sequence4x4 of Real)
950 Default: [[1 0 0 0]
951 [0 1 0 0]
952 [0 0 1 0]
953 [0 0 0 1]]
954 """
955
956 - def __init__(self,left=0.0,right=640.0,bottom=0.0,top=480.0,z_clip_near=0.0,z_clip_far=1.0):
957 """Create an orthographic projection.
958
959 Defaults to map x eye coordinates in the range [0,640], y eye
960 coordinates [0,480] and clip coordinates [0,1] to [0,1].
961 Therefore, if the viewport is 640 x 480, eye coordinates
962 correspond 1:1 with window (pixel) coordinates. Only points
963 between these clipping planes will be displayed.
964 """
965
966
967 matrix = Numeric.array([[ 2./(right-left), 0., 0., -(right+left)/(right-left)],
968 [ 0., 2./(top-bottom), 0., -(top+bottom)/(top-bottom)],
969 [ 0., 0., -2./(z_clip_far-z_clip_near), -(z_clip_far+z_clip_near)/(z_clip_far-z_clip_near)],
970 [ 0., 0., 0., 1.0]])
971 matrix = Numeric.transpose(matrix)
972
973
974
975
976
977
978
979
980
981 Projection.__init__(self,**{'matrix':matrix})
982
984 """An orthographic projection without Z clipping.
985
986 Parameters
987 ==========
988 matrix -- matrix specifying projection (Sequence4x4 of Real)
989 Default: [[1 0 0 0]
990 [0 1 0 0]
991 [0 0 1 0]
992 [0 0 0 1]]
993 """
994
995 - def __init__(self,left=0.0,right=640.0,bottom=0.0,top=480.0):
996 """Create an orthographic projection without Z clipping.
997
998 Defaults to map x eye coordinates in the range [0,640] and y
999 eye coordinates [0,480] -> [0,1]. Therefore, if the viewport
1000 is 640 x 480, eye coordinates correspond 1:1 with window
1001 (pixel) coordinates.
1002 """
1003
1004
1005 matrix = Numeric.array([[ 2./(right-left), 0, 0, -(right+left)/(right-left)],
1006 [ 0, 2./(top-bottom), 0, -(top+bottom)/(top-bottom)],
1007 [ 0, 0, -1, -1.],
1008 [ 0, 0, 0, 1]])
1009 matrix = Numeric.transpose(matrix)
1010
1011 Projection.__init__(self,**{'matrix':matrix})
1012
1014 """A simplified perspective projection.
1015
1016 Parameters
1017 ==========
1018 matrix -- matrix specifying projection (Sequence4x4 of Real)
1019 Default: [[1 0 0 0]
1020 [0 1 0 0]
1021 [0 0 1 0]
1022 [0 0 0 1]]
1023 """
1024
1025 - def __init__(self,fov_x=45.0,z_clip_near = 0.1,z_clip_far=10000.0,aspect_ratio=4.0/3.0):
1026 matrix = self._compute_matrix(fov_x,z_clip_near,z_clip_far,aspect_ratio)
1027 Projection.__init__(self,**{'matrix':matrix})
1028
1029 - def _compute_matrix(self,fov_x=45.0,z_clip_near = 0.1,z_clip_far=10000.0,aspect_ratio=4.0/3.0):
1030 """Compute a 4x4 projection matrix that performs a perspective distortion."""
1031 fov_y = fov_x / aspect_ratio
1032
1033
1034 radians = fov_y / 2.0 * math.pi / 180.0
1035 delta_z = z_clip_far - z_clip_near
1036 sine = math.sin(radians)
1037 if (delta_z == 0.0) or (sine == 0.0) or (aspect_ratio == 0.0):
1038 raise ValueError("Invalid parameters passed to SimpleProjection.__init__()")
1039 cotangent = math.cos(radians) / sine
1040 matrix = Numeric.zeros((4,4),'f')
1041 matrix[0][0] = cotangent/aspect_ratio
1042 matrix[1][1] = cotangent
1043 matrix[2][2] = -(z_clip_far + z_clip_near) / delta_z
1044 matrix[2][3] = -1.0
1045 matrix[3][2] = -2.0 * z_clip_near * z_clip_far / delta_z
1046 matrix[3][3] = 0.0
1047 return matrix
1048
1050 """A perspective projection.
1051
1052 Parameters
1053 ==========
1054 matrix -- matrix specifying projection (Sequence4x4 of Real)
1055 Default: [[1 0 0 0]
1056 [0 1 0 0]
1057 [0 0 1 0]
1058 [0 0 0 1]]
1059 """
1060
1061 - def __init__(self,left,right,bottom,top,near,far):
1062
1063 gl.glMatrixMode(gl.GL_PROJECTION)
1064 gl.glPushMatrix()
1065 gl.glLoadIdentity()
1066 gl.glFrustum(left,right,bottom,top,near,far)
1067 matrix = gl.glGetFloatv(gl.GL_PROJECTION_MATRIX)
1068 gl.glPopMatrix()
1069 if matrix is None:
1070
1071 raise RuntimeError("OpenGL matrix operations can only take place once OpenGL context started.")
1072 if type(matrix) != Numeric.ArrayType:
1073 matrix = Numeric.array(matrix)
1074 Projection.__init__(self,**{'matrix':matrix})
1075
1076
1077
1078
1079
1080
1081
1082 -class Stimulus(VisionEgg.ClassWithParameters):
1083 """Base class for a stimulus.
1084
1085 Any stimulus element should be a subclass of this Stimulus class.
1086 The draw() method contains the code executed before every buffer
1087 swap in order to render the stimulus to the frame buffer. It
1088 should execute as quickly as possible. The init_gl() method must
1089 be called before the first call to draw() so that any internal
1090 data, OpenGL display lists, and OpenGL:texture objects can be
1091 established.
1092
1093 To illustrate the concept of the Stimulus class, here is a
1094 description of several methods of drawing two spots. If your
1095 experiment displays two spots simultaneously, you could create two
1096 instances of (a single subclass of) Stimulus, varying parameters
1097 so each draws at a different location. Another possibility is to
1098 create one instance of a subclass that draws two spots. Another,
1099 somewhat obscure, possibility is to create a single instance and
1100 add it to two different viewports. (Something that will not work
1101 would be adding the same instance two times to the same viewport.
1102 It would also get drawn twice, although at exactly the same
1103 location.)
1104
1105 OpenGL is a 'state machine', meaning that it has internal
1106 parameters whose values vary and affect how it operates. Because
1107 of this inherent uncertainty, there are only limited assumptions
1108 about the state of OpenGL that an instance of Stimulus should
1109 expect when its draw() method is called. Because the Vision Egg
1110 loops through stimuli this also imposes some important behaviors:
1111
1112 First, the framebuffer will contain the results of any drawing
1113 operations performed since the last buffer swap by other instances
1114 of (subclasses of) Stimulus. Therefore, the order in which stimuli
1115 are present in the stimuli list of an instance of Viewport may be
1116 important. Additionally, if there are overlapping viewports, the
1117 order in which viewports are added to an instance of Screen is
1118 important.
1119
1120 Second, previously established OpenGL display lists and OpenGL
1121 texture objects will be available. The __init__() method should
1122 establish these things.
1123
1124 Third, there are several OpenGL state variables which are
1125 commonly set by subclasses of Stimulus, and which cannot be
1126 assumed to have any particular value at the time draw() is called.
1127 These state variables are: blending mode and function, texture
1128 state and environment, the matrix mode (modelview or projection),
1129 the modelview matrix, depth mode and settings. Therefore, if the
1130 draw() method depends on specific values for any of these states,
1131 it must specify its own values to OpenGL.
1132
1133 Finally, a well-behaved Stimulus subclass resets any OpenGL state
1134 values other than those listed above to their initial state before
1135 draw() and init_gl() were called. In other words, before your
1136 stimulus changes the state of an OpenGL variable, use
1137 glGetBoolean, glGetInteger, glGetFloat, or a similar function to
1138 query its value and restore it later. For example, upon calling
1139 the draw() method, the projection matrix will be that which was
1140 set by the viewport. If the draw() method alters the projection
1141 matrix, it must be restored. The glPushMatrix() and glPopMatrix()
1142 commands provide an easy way to do this.
1143
1144 The default projection of Viewport maps eye coordinates in a 1:1
1145 fashion to window coordinates (in other words, it sets eye
1146 coordinates to use pixel units from the lower left corner of the
1147 viewport). Therefore the default parameters for a stimulus should
1148 specify pixel coordinates if possible (such as for a 2D
1149 stimulus). Assuming a window size of 640 by 480 for the default
1150 parameters is a pretty safe way to do things.
1151
1152 Also, be sure to check for any assumptions made about the system
1153 in the __init__ method. For example, if your stimulus needs alpha
1154 in the framebuffer, check the value of
1155 glGetIntegerv(GL_ALPHA_BITS) and raise an exception if it is not
1156 available.
1157 """
1158
1160 """Instantiate and get ready to draw.
1161
1162 Set parameter values and create anything needed to draw the
1163 stimulus including OpenGL state variables such display lists
1164 and texture objects.
1165
1166 """
1167 VisionEgg.ClassWithParameters.__init__(self,**kw)
1168
1170 """Draw the stimulus. (Called by Viewport instance.)
1171
1172 This method is called every frame. This method actually
1173 performs the OpenGL calls to draw the stimulus.
1174
1175 """
1176 pass
1177
1178
1179
1180
1181
1182
1183
1184 -class Viewport(VisionEgg.ClassWithParameters):
1185 """Connects stimuli to a screen.
1186
1187 A viewport defines a (possibly clipped region) of the screen on
1188 which stimuli are drawn.
1189
1190 A screen may have multiple viewports. The viewports may be
1191 overlapping.
1192
1193 A viewport may have multiple stimuli.
1194
1195 A single stimulus may be drawn simultaneously by several
1196 viewports, although this is typically useful only for 3D stimuli
1197 to represent different views of the same object.
1198
1199 The coordinates of the stimulus are converted to screen
1200 coordinates via several steps, the most important of which is the
1201 projection, which is defined by an instance of the Projection
1202 class.
1203
1204 By default, a viewport has a projection which maps eye coordinates
1205 to viewport coordinates in 1:1 manner. In other words, eye
1206 coordinates specify pixel location in the viewport.
1207
1208 For cases where pixel units are not natural to describe
1209 coordinates of a stimulus, the application should specify the a
1210 projection other than the default. This is usually the case for
1211 3D stimuli.
1212
1213 For details of the projection and clipping process, see the
1214 section 'Coordinate Transformations' in the book/online document
1215 'The OpenGL Graphics System: A Specification'
1216
1217 Parameters
1218 ==========
1219 anchor -- How position parameter is interpreted (String)
1220 Default: lowerleft
1221 camera_matrix -- extrinsic camera parameter matrix (position and orientation) (Instance of <class 'VisionEgg.Core.ModelView'>)
1222 Default: (determined at runtime)
1223 depth_range -- depth range (in object units) for rendering (Sequence2 of Real)
1224 Default: (0, 1)
1225 position -- Position (in pixel units) within the screen (Sequence2 of Real)
1226 Default: (0, 0)
1227 projection -- intrinsic camera parameter matrix (field of view, focal length, aspect ratio) (Instance of <class 'VisionEgg.Core.Projection'>)
1228 Default: (determined at runtime)
1229 screen -- The screen in which this viewport is drawn (Instance of <class 'VisionEgg.Core.Screen'>)
1230 Default: (determined at runtime)
1231 size -- Size (in pixel units) (Sequence2 of Real)
1232 Default: (determined at runtime)
1233 stimuli -- sequence of stimuli to draw in screen (Sequence of Instance of <class 'VisionEgg.Core.Stimulus'>)
1234 Default: (determined at runtime)
1235 """
1236
1237 parameters_and_defaults = VisionEgg.ParameterDefinition({
1238 'screen':(None,
1239 ve_types.Instance(Screen),
1240 'The screen in which this viewport is drawn'),
1241 'position':((0,0),
1242 ve_types.Sequence2(ve_types.Real),
1243 'Position (in pixel units) within the screen'),
1244 'anchor':('lowerleft',
1245 ve_types.String,
1246 'How position parameter is interpreted'),
1247 'depth_range':((0,1),
1248 ve_types.Sequence2(ve_types.Real),
1249 'depth range (in object units) for rendering'),
1250 'size':(None,
1251 ve_types.Sequence2(ve_types.Real),
1252 'Size (in pixel units)'),
1253 'projection':(None,
1254 ve_types.Instance(Projection),
1255 'intrinsic camera parameter matrix (field of view, focal length, aspect ratio)'),
1256 'camera_matrix':(None,
1257 ve_types.Instance(ModelView),
1258 'extrinsic camera parameter matrix (position and orientation)'),
1259 'stimuli':(None,
1260 ve_types.Sequence(ve_types.Instance(Stimulus)),
1261 'sequence of stimuli to draw in screen'),
1262 'lowerleft':(None,
1263 ve_types.Sequence2(ve_types.Real),
1264 'position (in pixel units) of lower-left viewport corner',
1265 VisionEgg.ParameterDefinition.DEPRECATED),
1266 })
1267
1268 __slots__ = (
1269 '_is_drawing',
1270 )
1271
1273 """Create a new instance.
1274
1275 Required arguments:
1276
1277 screen
1278
1279 Optional arguments (specify parameter value other than default):
1280
1281 position -- defaults to (0,0), position relative to screen by anchor (see below)
1282 anchor -- defaults to 'lowerleft'
1283 size -- defaults to screen.size
1284 projection -- defaults to self.make_new_pixel_coord_projection()
1285 stimuli -- defaults to empty list
1286 """
1287 VisionEgg.ClassWithParameters.__init__(self,**kw)
1288
1289 if self.parameters.screen is None:
1290 raise ValueError("Must specify screen when creating an instance of Viewport.")
1291
1292 p = self.parameters
1293 if p.size is None:
1294 p.size = p.screen.constant_parameters.size
1295 if p.projection is None:
1296
1297 p.projection = self.make_new_pixel_coord_projection()
1298 if p.camera_matrix is None:
1299 p.camera_matrix = ModelView()
1300 if p.stimuli is None:
1301 p.stimuli = []
1302 self._is_drawing = False
1303
1308
1310 p = self.parameters
1311 p.screen.make_current()
1312
1313 if p.lowerleft != None:
1314 if not hasattr(Viewport,"_gave_lowerleft_warning"):
1315 logger = logging.getLogger('VisionEgg.Core')
1316 logger.warning("lowerleft parameter of Viewport class "
1317 "will stop being supported. Use "
1318 "'position' instead with anchor set to "
1319 "'lowerleft'.")
1320 Viewport._gave_lowerleft_warning = True
1321 p.anchor = 'lowerleft'
1322 p.position = p.lowerleft[0], p.lowerleft[1]
1323
1324 lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,p.size)
1325
1326 gl.glViewport(lowerleft[0],
1327 lowerleft[1],
1328 p.size[0],
1329 p.size[1])
1330 gl.glDepthRange(p.depth_range[0],p.depth_range[1])
1331
1332 p.projection.apply_to_gl()
1333 p.camera_matrix.apply_to_gl()
1334
1336 """Set the viewport and draw stimuli."""
1337 self.make_current()
1338 self._is_drawing = True
1339 for stimulus in self.parameters.stimuli:
1340 stimulus.draw()
1341 self._is_drawing = False
1342
1344 """Transform normalized device coordinates to window coordinates"""
1345 v = Numeric.asarray(norm_device_vertex)
1346 homog = VisionEgg.ThreeDeeMath.make_homogeneous_coord_rows(v)
1347 xd = homog[:,0,Numeric.NewAxis]
1348 yd = homog[:,1,Numeric.NewAxis]
1349 zd = homog[:,2,Numeric.NewAxis]
1350
1351 p = self.parameters
1352 lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,p.size)
1353 x,y = lowerleft
1354 w,h = p.size
1355 n,f = p.depth_range
1356
1357
1358 n = min(1.0,max(0.0,n))
1359 f = min(1.0,max(0.0,f))
1360
1361 ox = x + w/2.0
1362 oy = y + h/2.0
1363 px = w
1364 py = h
1365 xw = (px/2.0)*xd + ox
1366 yw = (py/2.0)*yd + oy
1367 zw = ((f-n)/2.0)*zd + (n+f)/2.0
1368
1369
1370
1371 r = Numeric.concatenate((xw,yw,zw),axis=1)
1372 if len(homog.shape) > len(v.shape):
1373 r = Numeric.reshape(r,(3,))
1374 return r
1376 """Transform clip coordinates to window coordinates"""
1377 my_proj = self.parameters.projection
1378 return self.norm_device_2_window( my_proj.clip_2_norm_device( eye_coords_vertex ) )
1380 """Transform eye coordinates to window coordinates"""
1381 my_proj = self.parameters.projection
1382 return self.norm_device_2_window( my_proj.eye_2_norm_device( eye_coords_vertex ) )
1383
1384
1385
1386
1387
1388
1389
1391 """A rectangle stimulus, typically used as a fixation spot.
1392
1393 Parameters
1394 ==========
1395 anchor -- how position parameter is used (String)
1396 Default: center
1397 color -- color (AnyOf(Sequence3 of Real or Sequence4 of Real))
1398 Default: (1.0, 1.0, 1.0)
1399 on -- draw? (Boolean)
1400 Default: True
1401 position -- position in eye coordinates (AnyOf(Sequence2 of Real or Sequence3 of Real or Sequence4 of Real))
1402 Default: (320.0, 240.0)
1403 size -- size in eye coordinates (Sequence2 of Real)
1404 Default: (4.0, 4.0)
1405 """
1406
1407 parameters_and_defaults = VisionEgg.ParameterDefinition({
1408 'on':(True,
1409 ve_types.Boolean,
1410 'draw?'),
1411 'color':((1.0,1.0,1.0),
1412 ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
1413 ve_types.Sequence4(ve_types.Real)),
1414 'color'),
1415 'position' : ( ( 320.0, 240.0 ),
1416 ve_types.AnyOf(ve_types.Sequence2(ve_types.Real),
1417 ve_types.Sequence3(ve_types.Real),
1418 ve_types.Sequence4(ve_types.Real)),
1419 'position in eye coordinates'),
1420 'anchor' : ('center',
1421 ve_types.String,
1422 'how position parameter is used'),
1423 'size':((4.0,4.0),
1424 ve_types.Sequence2(ve_types.Real),
1425 'size in eye coordinates'),
1426 'center' : (None,
1427 ve_types.Sequence2(ve_types.Real),
1428 'position in eye coordinates',
1429 VisionEgg.ParameterDefinition.DEPRECATED),
1430 })
1431
1434
1436 p = self.parameters
1437 if p.center is not None:
1438 if not hasattr(VisionEgg.config,"_GAVE_CENTER_DEPRECATION"):
1439 logger = logging.getLogger('VisionEgg.Core')
1440 logger.warning("Specifying FixationSpot by deprecated "
1441 "'center' parameter deprecated. Use "
1442 "'position' parameter instead. (Allows "
1443 "use of 'anchor' parameter to set to "
1444 "other values.)")
1445 VisionEgg.config._GAVE_CENTER_DEPRECATION = 1
1446 p.anchor = 'center'
1447 p.position = p.center[0], p.center[1]
1448 if p.on:
1449
1450 center = VisionEgg._get_center(p.position,p.anchor,p.size)
1451 gl.glDisable(gl.GL_DEPTH_TEST)
1452 gl.glDisable(gl.GL_TEXTURE_2D)
1453 gl.glDisable(gl.GL_BLEND)
1454
1455 if len(p.color)==3:
1456 gl.glColor3f(*p.color)
1457 elif len(p.color)==4:
1458 gl.glColor4f(*p.color)
1459
1460
1461
1462
1463 x_size = self.parameters.size[0]/2.0
1464 y_size = self.parameters.size[1]/2.0
1465 x,y = center[0],center[1]
1466 x1 = x-x_size; x2 = x+x_size
1467 y1 = y-y_size; y2 = y+y_size
1468 gl.glBegin(gl.GL_QUADS)
1469 gl.glVertex2f(x1,y1)
1470 gl.glVertex2f(x2,y1)
1471 gl.glVertex2f(x2,y2)
1472 gl.glVertex2f(x1,y2)
1473 gl.glEnd()
1474
1475
1476
1477
1478
1479
1480
1482 """Time inter frame intervals and compute frames per second."""
1483 - def __init__(self, bin_start_msec=2, bin_stop_msec=28, bin_width_msec=2, running_average_num_frames=0,save_all_frametimes=False):
1484 """Create instance of FrameTimer."""
1485 self.bins = Numeric.arange( bin_start_msec, bin_stop_msec, bin_width_msec )
1486 self.bin_width_msec = float(bin_width_msec)
1487 self.timing_histogram = Numeric.zeros( self.bins.shape, Numeric.Float )
1488 self._true_time_last_frame = None
1489 self.longest_frame_draw_time_sec = None
1490 self.first_tick_sec = None
1491 self.total_frames = 0
1492 self.running_average_num_frames = running_average_num_frames
1493 if self.running_average_num_frames:
1494 self.last_n_frame_times_sec = [None]*self.running_average_num_frames
1495 self.save_all_frametimes = save_all_frametimes
1496 if self.save_all_frametimes:
1497 self.all_frametimes = []
1498
1500 """Declare a frame has just been drawn."""
1501 true_time_now = VisionEgg.true_time_func()
1502 if self._true_time_last_frame != None:
1503 this_frame_draw_time_sec = true_time_now - self._true_time_last_frame
1504 index = int(math.ceil(this_frame_draw_time_sec*1000.0/self.bin_width_msec))-1
1505 if index > (len(self.timing_histogram)-1):
1506 index = -1
1507 self.timing_histogram[index] += 1
1508 self.longest_frame_draw_time_sec = max(self.longest_frame_draw_time_sec,this_frame_draw_time_sec)
1509 if self.running_average_num_frames:
1510 self.last_n_frame_times_sec.append(true_time_now)
1511 self.last_n_frame_times_sec.pop(0)
1512 else:
1513 self.first_tick_sec = true_time_now
1514 self._true_time_last_frame = true_time_now
1515
1516 if self.save_all_frametimes:
1517 self.all_frametimes.append( true_time_now )
1518
1520 if self.save_all_frametimes:
1521 return self.all_frametimes
1522 else:
1523 raise ValueError("must set save_all_frametimes")
1524
1526 return self.longest_frame_draw_time_sec
1527
1529 if self.running_average_num_frames:
1530 frame_times = []
1531 for frame_time in self.last_n_frame_times_sec:
1532 if frame_time is not None:
1533 frame_times.append( frame_time )
1534 if len(frame_times) >= 2:
1535 return (frame_times[-1] - frame_times[0]) / len(frame_times)
1536 else:
1537 raise RuntimeError("running_average_num_frames not set when creating FrameTimer instance")
1538
1540 if self._true_time_last_frame is None:
1541 raise RuntimeError("No frames were drawn, can't calculate average IFI")
1542 return (self._true_time_last_frame - self.first_tick_sec) / sum( self.timing_histogram )
1543
1545 logger = logging.getLogger('VisionEgg.Core')
1546 logger.warning("print_histogram() method of FrameTimer is "
1547 "deprecated will stop being supported. Use "
1548 "log_histogram() instead.")
1549 self.log_histogram()
1550
1552 """Send histogram to logger."""
1553 buffer = StringIO.StringIO()
1554
1555 n_frames = sum( self.timing_histogram )+1
1556 if n_frames < 2:
1557 print >> buffer, '%d frames were drawn.'%n_frames
1558 return
1559 average_ifi_sec = self.get_average_ifi_sec()
1560 print >> buffer, '%d frames were drawn.'%int(n_frames)
1561 print >> buffer, 'Mean IFI was %.2f msec (%.2f fps), longest IFI was %.2f msec.'%(
1562 average_ifi_sec*1000.0,1.0/average_ifi_sec,self.longest_frame_draw_time_sec*1000.0)
1563
1564 h = hist = self.timing_histogram
1565 maxhist = float(max(h))
1566 if maxhist == 0:
1567 print >> buffer, "No frames were drawn."
1568 return
1569 lines = min(10,int(math.ceil(maxhist)))
1570 hist = hist/maxhist*float(lines)
1571 print >> buffer, "histogram:"
1572 for line in range(lines):
1573 val = float(lines)-1.0-float(line)
1574 timing_string = "%6d "%(round(maxhist*val/lines),)
1575 q = Numeric.greater(hist,val)
1576 for qi in q:
1577 s = ' '
1578 if qi:
1579 s = '*'
1580 timing_string += "%4s "%(s,)
1581 print >> buffer, timing_string
1582 timing_string = " Time: "
1583 timing_string += "%4d "%(0,)
1584 for bin in self.bins[:-1]:
1585 timing_string += "%4d "%(bin,)
1586 timing_string += "+(msec)\n"
1587 timing_string += "Total: "
1588 for hi in h:
1589 if hi <= 999:
1590 num_str = str(int(hi)).center(5)
1591 else:
1592 num_str = " +++ "
1593 timing_string += num_str
1594 print >> buffer, timing_string
1595
1596 buffer.seek(0)
1597 logger = logging.getLogger('VisionEgg.Core')
1598 logger.info(buffer.read())
1599
1600
1601
1602
1603
1604
1605
1606 import VisionEgg.Deprecated
1607 Message = VisionEgg.Deprecated.Message
1608
1609 message = VisionEgg.Deprecated.Message()
1610
1611 gl_assumptions = []
1612
1614 """Save assumptions for later checking once OpenGL context created."""
1615 if type(failure_callback) != types.FunctionType:
1616 raise ValueError("failure_callback must be a function!")
1617 gl_assumptions.append((gl_variable,required_value,failure_callback))
1618
1620 global gl
1621 logger = logging.getLogger('VisionEgg.Core')
1622
1623 if gl is VisionEgg.GLTrace:
1624 watched = True
1625 gl = VisionEgg.GLTrace.gl
1626 else:
1627 watched = False
1628
1629 module_name = "OpenGL.GL.%(prefix)s.%(name)s"%locals()
1630 try:
1631 exec "import "+module_name
1632 except ImportError:
1633 logger.warning("Could not import %s -- some features will be "
1634 "missing."%(module_name,))
1635 return False
1636 module = eval(module_name)
1637 init_function_name = "glInit"+name.title().replace('_','')+prefix
1638 init_function = getattr(module,init_function_name)
1639 if not init_function():
1640 logger.warning("Could not initialize %s -- some features will "
1641 "be missing."%(module_name,))
1642 return False
1643 for attr_name in dir(module):
1644
1645
1646 attr = getattr(module,attr_name)
1647
1648 if attr_name.startswith('__'):
1649 continue
1650 elif attr_name == init_function_name:
1651 continue
1652 elif attr_name == 'gl':
1653 continue
1654 elif type(attr) == type(VisionEgg):
1655 continue
1656
1657 gl_attr_name = attr_name
1658 setattr(gl,gl_attr_name,attr)
1659
1660 if watched:
1661 VisionEgg.GLTrace.gl_trace_attach()
1662 gl = VisionEgg.GLTrace
1663 return True
1664
1666 """Called by Screen instance. Requires OpenGL context to be created."""
1667 global gl_vendor, gl_renderer, gl_version
1668 logger = logging.getLogger('VisionEgg.Core')
1669
1670 if gl_version < '1.3':
1671 if not init_gl_extension('ARB','multitexture'):
1672 logger.warning("multitexturing not available. Some features "
1673 "will not be available")
1674 else:
1675 if not hasattr(gl,'glActiveTexture'):
1676 logger.debug("PyOpenGL bug: OpenGL multitexturing not available "
1677 "even though OpenGL is 1.3 or greater. "
1678 "Attempting ctypes-based workaround.")
1679 VisionEgg.PlatformDependent.attempt_to_load_multitexturing()
1680 if hasattr(gl,'glActiveTexture'):
1681
1682
1683 gl.glActiveTextureARB = gl.glActiveTexture
1684 gl.glMultiTexCoord2fARB = gl.glMultiTexCoord2f
1685 gl.GL_TEXTURE0_ARB = gl.GL_TEXTURE0
1686 gl.GL_TEXTURE1_ARB = gl.GL_TEXTURE1
1687
1688 if gl_version < '1.2':
1689 if init_gl_extension('EXT','bgra'):
1690
1691 gl.GL_BGRA = gl.GL_BGRA_EXT
1692
1693 for gl_variable,required_value,failure_callback in gl_assumptions:
1694
1695 if gl_variable == "__SPECIAL__":
1696 if required_value == "linux_nvidia_or_new_ATI":
1697 ok = 0
1698
1699 if "nvidia" == gl_vendor.split()[0].lower():
1700 ok = 1
1701 if gl_renderer.startswith('Mesa DRI Radeon'):
1702 date = gl_renderer.split()[3]
1703 if date > "20021216":
1704 ok=1
1705 if not ok:
1706 failure_callback()
1707 else:
1708 raise RuntimeError, "Unknown gl_assumption: %s == %s"%(gl_variable,required_value)
1709
1710 elif gl_variable.upper() == "GL_VERSION":
1711 value_str = gl_version.split()[0]
1712 value_ints = map(int,value_str.split('.'))
1713 value = float( str(value_ints[0]) + "." + ''.join(map(str,value_ints[1:])))
1714 if value < required_value:
1715 failure_callback()
1716 else:
1717 raise RuntimeError, "Unknown gl_assumption"
1718
1719
1720 try:
1721 gl.GL_CLAMP_TO_EDGE
1722 except AttributeError:
1723 if gl_version >= '1.2':
1724
1725
1726 logger.debug("GL_CLAMP_TO_EDGE is not defined. "
1727 "Because you have OpenGL version 1.2 or "
1728 "greater, this is probably a bug in "
1729 "PyOpenGL. Assigning GL_CLAMP_TO_EDGE to "
1730 "the value that is usually used.")
1731 gl.GL_CLAMP_TO_EDGE = 0x812F
1732 else:
1733 try:
1734 init_gl_extension('SGIS','texture_edge_clamp')
1735 gl.GL_CLAMP_TO_EDGE = gl.GL_CLAMP_TO_EDGE_SGIS
1736 except:
1737
1738 logger.warning("GL_CLAMP_TO_EDGE is not "
1739 "available. OpenGL version is "
1740 "less than 1.2, and the "
1741 "texture_edge_clamp_SGIS extension "
1742 "failed to load. It may be impossible to "
1743 "get exact 1:1 reproduction of "
1744 "textures. Using GL_CLAMP instead of "
1745 "GL_CLAMP_TO_EDGE.")
1746 gl.GL_CLAMP_TO_EDGE = gl.GL_CLAMP
1747
1748
1749
1750
1751
1752
1753
1754 import VisionEgg.FlowControl
1755 Presentation = VisionEgg.FlowControl.Presentation
1756 Controller = VisionEgg.FlowControl.Controller
1757 ConstantController = VisionEgg.FlowControl.ConstantController
1758 EvalStringController = VisionEgg.FlowControl.EvalStringController
1759 ExecStringController = VisionEgg.FlowControl.ExecStringController
1760 FunctionController = VisionEgg.FlowControl.FunctionController
1761 EncapsulatedController = VisionEgg.FlowControl.EncapsulatedController
1762