Hi Andrew thanks for your reply. Actually you were right and I was OK with the solution of changing phase instead of tf. But I had another issue: I also want to use square functions to modulate temporal frequency (e.g. use tf=0.8 for 3 seconds, then -0.8 for the next 3 seconds and so on). There I had the problem of phase-shifting, whenever the phase was not 360 or 180 at the time of change. Therefore I tried to implement the solution you suggested, which would solve both problems. I did so for the SinGrating2D class (Gratings_cycle) and also for the SphereGrating class, which I mostly use. In this class, I also included color1, color2 and max_alpha parameters, therefore called it SphereMap_color. I added two parameters: "cycle_length" (default = None) which, when not set to None controls storing of time and phase every iteration, and calculation of dt. In addition, I added the parameter "max_tf", which allows me to vary maximal temporal frequency when using a sin-function to modulate temporal frequency. The variables "phase_before", "time_before" and "dt" I added to _slots_, setting them all to 0.0 in _init_. In the SphereGrating class, I was not sure what to do under _rebuild_texture_object, using the variables there resulted in attribute errors, so I left phase calculation unchanged there and only changed it under "draw". I'm sure there would be much more elegant ways to implement this (I have very little experience with python programming) , but it seems to work. If you have time to have a look at the code, I would of course be happy if you can tell me if I did any mistake. I attached the modified classes mentioned above, as well as two example scripts making use of each of them ("linear_grating_sinfunc_vs_squarefunc" for testing Gratings_cycle, and "windmill_squarefunc" for testing SphereMap_color.) Btw: My first manuscript making use of the VisionEgg was just accepted at J Neurosci Meth (http://dx.doi.org/10.1016/j.jneumeth.2009.10.020), just in case you'd like to add it to the list on the visionegg website. Best, Kaspar |
# The Vision Egg: Gratings # # Copyright (C) 2001-2003 Andrew Straw. # Copyright (C) 2005,2008 California Institute of Technology # # URL: <http://www.visionegg.org/> # # Distributed under the terms of the GNU Lesser General Public License # (LGPL). See LICENSE.TXT that came with this file. """ Grating stimuli. """ #################################################################### # # Import all the necessary packages # #################################################################### import logging # available in Python 2.3 import VisionEgg import VisionEgg.Core import VisionEgg.Textures import VisionEgg.ParameterTypes as ve_types import numpy import math, types, string import VisionEgg.GL as gl # get all OpenGL stuff in one namespace import _vegl def _get_type_info( bitdepth ): """Private helper function to calculate type info based on bit depth""" if bitdepth == 8: gl_type = gl.GL_UNSIGNED_BYTE numpy_dtype = numpy.uint8 max_int_val = float((2**8)-1) elif bitdepth == 12: gl_type = gl.GL_SHORT numpy_dtype = numpy.int16 max_int_val = float((2**15)-1) elif bitdepth == 16: gl_type = gl.GL_INT numpy_dtype = numpy.int32 max_int_val = float((2.**31.)-1) # do as float to avoid overflow else: raise ValueError("supported bitdepths are 8, 12, and 16.") return gl_type, numpy_dtype, max_int_val class LuminanceGratingCommon(VisionEgg.Core.Stimulus): """Base class with common code to all ways of drawing luminance gratings. Parameters ========== bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Default: 8 """ parameters_and_defaults = VisionEgg.ParameterDefinition({ 'bit_depth':(8, ve_types.UnsignedInteger, 'precision with which grating is calculated and sent to OpenGL'), }) __slots__ = ( 'gl_internal_format', 'format', 'gl_type', 'numpy_dtype', 'max_int_val', 'cached_bit_depth', ) def calculate_bit_depth_dependencies(self): """Calculate a number of parameters dependent on bit depth.""" bit_depth_warning = False p = self.parameters # shorthand red_bits = gl.glGetIntegerv( gl.GL_RED_BITS ) green_bits = gl.glGetIntegerv( gl.GL_GREEN_BITS ) blue_bits = gl.glGetIntegerv( gl.GL_BLUE_BITS ) min_bits = min( (red_bits,green_bits,blue_bits) ) if min_bits < p.bit_depth: logger = logging.getLogger('VisionEgg.Gratings') logger.warning("Requested bit depth of %d in " "LuminanceGratingCommon, which is " "greater than your current OpenGL context " "supports (%d)."% (p.bit_depth,min_bits)) self.gl_internal_format = gl.GL_LUMINANCE self.format = gl.GL_LUMINANCE self.gl_type, self.numpy_dtype, self.max_int_val = _get_type_info( p.bit_depth ) self.cached_bit_depth = p.bit_depth class AlphaGratingCommon(VisionEgg.Core.Stimulus): """Base class with common code to all ways of drawing gratings in alpha. This class is currently not used by any other classes. Parameters ========== bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Default: 8 """ parameters_and_defaults = VisionEgg.ParameterDefinition({ 'bit_depth':(8, ve_types.UnsignedInteger, 'precision with which grating is calculated and sent to OpenGL'), }) __slots__ = ( 'gl_internal_format', 'format', 'gl_type', 'numpy_dtype', 'max_int_val', 'cached_bit_depth', ) def calculate_bit_depth_dependencies(self): """Calculate a number of parameters dependent on bit depth.""" p = self.parameters # shorthand alpha_bit_depth = gl.glGetIntegerv( gl.GL_ALPHA_BITS ) if alpha_bit_depth < p.bit_depth: logger = logging.getLogger('VisionEgg.Gratings') logger.warning("Requested bit depth of %d, which is " "greater than your current OpenGL context " "supports (%d)."% (p.bit_depth,min_bits)) self.gl_internal_format = gl.GL_ALPHA self.format = gl.GL_ALPHA self.gl_type, self.numpy_dtype, self.max_int_val = _get_type_info( p.bit_depth ) self.cached_bit_depth = p.bit_depth class SinGrating2D(LuminanceGratingCommon): """Sine wave grating stimulus This is a general-purpose, realtime sine-wave luminace grating generator. To acheive an arbitrary orientation, this class rotates a textured quad. To draw a grating with sides that always remain horizontal and vertical, draw a large grating in a small viewport. (The viewport will clip anything beyond its edges.) Parameters ========== anchor -- specifies how position parameter is interpreted (String) Default: center bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Inherited from LuminanceGratingCommon Default: 8 color1 -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (1.0, 1.0, 1.0) color2 -- optional color with which to perform interpolation with color1 in RGB space (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (determined at runtime) contrast -- (Real) Default: 1.0 depth -- (Real) Default: (determined at runtime) ignore_time -- (Boolean) Default: False mask -- optional masking function (Instance of <class 'VisionEgg.Textures.Mask2D'>) Default: (determined at runtime) max_alpha -- (Real) Default: 1.0 num_samples -- (UnsignedInteger) Default: 512 on -- draw stimulus? (Boolean) Default: True orientation -- (Real) Default: 0.0 pedestal -- (Real) Default: 0.5 phase_at_t0 -- (Real) Default: 0.0 position -- (units: eye coordinates) (Sequence2 of Real) Default: (320.0, 240.0) recalculate_phase_tolerance -- (Real) Default: (determined at runtime) size -- defines coordinate size of grating (in eye coordinates) (Sequence2 of Real) Default: (640.0, 480.0) spatial_freq -- frequency defined relative to coordinates defined in size parameter (units: cycles/eye_coord_unit) (Real) Default: 0.0078125 t0_time_sec_absolute -- (Real) Default: (determined at runtime) temporal_freq_hz -- (Real) Default: 5.0 max_tf -- (Real) Default: 5.0 cycle_length -- (Real) Default: None """ parameters_and_defaults = VisionEgg.ParameterDefinition({ 'on':(True, ve_types.Boolean, "draw stimulus?"), 'mask':(None, # allows window onto otherwise (tilted) rectangular grating ve_types.Instance(VisionEgg.Textures.Mask2D), "optional masking function"), 'contrast':(1.0, ve_types.Real), 'pedestal':(0.5, ve_types.Real), 'position':((320.0,240.0), # in eye coordinates ve_types.Sequence2(ve_types.Real), "(units: eye coordinates)"), 'anchor':('center', ve_types.String, "specifies how position parameter is interpreted"), 'depth':(None, # if not None, turns on depth testing and allows for occlusion ve_types.Real), 'size':((640.0,480.0), ve_types.Sequence2(ve_types.Real), "defines coordinate size of grating (in eye coordinates)", ), 'spatial_freq':(1.0/128.0, # cycles/eye coord units ve_types.Real, "frequency defined relative to coordinates defined in size parameter (units: cycles/eye_coord_unit)", ), 'temporal_freq_hz':(5.0, # hz ve_types.Real), 'max_tf':(5.0, ve_types.Real), 'cycle_length':(None, ve_types.Real), 't0_time_sec_absolute':(None, # Will be assigned during first call to draw() ve_types.Real), 'ignore_time':(False, # ignore temporal frequency variable - allow control purely with phase_at_t0 ve_types.Boolean), 'phase_at_t0':(0.0, # degrees [0.0-360.0] ve_types.Real), 'orientation':(0.0, # 0=right, 90=up ve_types.Real), 'num_samples':(512, # number of spatial samples, should be a power of 2 ve_types.UnsignedInteger), 'max_alpha':(1.0, # controls "opacity": 1.0 = completely opaque, 0.0 = completely transparent ve_types.Real), 'color1':((1.0, 1.0, 1.0), # alpha is ignored (if given) -- use max_alpha parameter ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'color2':(None, # perform interpolation with color1 in RGB space. ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "optional color with which to perform interpolation with color1 in RGB space"), 'recalculate_phase_tolerance':(None, # only recalculate texture when phase is changed by more than this amount, None for always recalculate. (Saves time.) ve_types.Real), }) __slots__ = ( '_texture_object_id', '_last_phase', '_phase_before', '_time_before', '_dt', ) def __init__(self,**kw): LuminanceGratingCommon.__init__(self,**kw) p = self.parameters # shorthand self._texture_object_id = gl.glGenTextures(1) if p.mask: gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) gl.glBindTexture(gl.GL_TEXTURE_1D,self._texture_object_id) # Do error-checking on texture to make sure it will load max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) if p.num_samples > max_dim: raise NumSamplesTooLargeError("Grating num_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,)) self.calculate_bit_depth_dependencies() w = p.size[0] inc = w/float(p.num_samples) phase = 0.0 # this data won't get used - don't care about phase self._last_phase = phase floating_point_sin = numpy.sin(2.0*math.pi*p.spatial_freq*numpy.arange(0.0,w,inc,dtype=numpy.float)+(phase/180.0*math.pi))*0.5*p.contrast+p.pedestal floating_point_sin = numpy.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring() self._phase_before = 0.0 self._time_before = 0.0 self._dt = 0.0 # Because the MAX_TEXTURE_SIZE method is insensitive to the current # state of the video system, another check must be done using # "proxy textures". gl.glTexImage1D(gl.GL_PROXY_TEXTURE_1D, # target 0, # level self.gl_internal_format, # video RAM internal format p.num_samples, # width 0, # border self.format, # format of texel data self.gl_type, # type of texel data texel_data) # texel data (irrelevant for proxy) if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_1D, # Need PyOpenGL >= 2.0 0, gl.GL_TEXTURE_WIDTH) == 0: raise NumSamplesTooLargeError("Grating num_samples is too wide for your video system!") # If we got here, it worked and we can load the texture for real. gl.glTexImage1D(gl.GL_TEXTURE_1D, # target 0, # level self.gl_internal_format, # video RAM internal format p.num_samples, # width 0, # border self.format, # format of texel data self.gl_type, # type of texel data texel_data) # texel data # Set texture object defaults gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_S,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_T,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,gl.GL_LINEAR) if p.color2 is not None: if VisionEgg.Core.gl_renderer == 'ATi Rage 128 Pro OpenGL Engine' and VisionEgg.Core.gl_version == '1.1 ATI-1.2.22': logger = logging.getLogger('VisionEgg.Gratings') logger.warning("Your video card and driver have known " "bugs which prevent them from rendering " "color gratings properly.") def __del__(self): gl.glDeleteTextures( [self._texture_object_id] ) def draw(self): p = self.parameters # shorthand if p.on: # calculate center center = VisionEgg._get_center(p.position,p.anchor,p.size) if p.mask: gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) gl.glBindTexture(gl.GL_TEXTURE_1D,self._texture_object_id) gl.glEnable(gl.GL_TEXTURE_1D) gl.glDisable(gl.GL_TEXTURE_2D) if p.bit_depth != self.cached_bit_depth: self.calculate_bit_depth_dependencies() # Clear the modeview matrix gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() # Rotate about the center of the texture gl.glTranslate(center[0], center[1], 0) gl.glRotate(p.orientation,0,0,1) if p.depth is None: gl.glDisable(gl.GL_DEPTH_TEST) depth = 0.0 else: gl.glEnable(gl.GL_DEPTH_TEST) depth = p.depth # allow max_alpha value to control blending gl.glEnable( gl.GL_BLEND ) gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) if p.color2: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_BLEND) gl.glTexEnvfv(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_COLOR, p.color2) ## alpha is ignored because the texture base internal format is luminance else: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) if p.t0_time_sec_absolute is None and not p.ignore_time: p.t0_time_sec_absolute = VisionEgg.time_func() w = p.size[0] inc = w/float(p.num_samples) if p.ignore_time: phase = p.phase_at_t0 else: if p.cycle_length is not None: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute self._dt = t_var - self._time_before phase = self._dt*p.temporal_freq_hz*-360.0 + self._phase_before + p.phase_at_t0 self._time_before = t_var self._phase_before = phase else: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute phase = t_var*p.temporal_freq_hz*-360.0 + p.phase_at_t0 if p.recalculate_phase_tolerance is None or abs(self._last_phase - phase) > p.recalculate_phase_tolerance: self._last_phase = phase # we're re-drawing the phase at this angle floating_point_sin = numpy.sin(2.0*math.pi*p.spatial_freq*numpy.arange(0.0,w,inc,dtype=numpy.float)+(phase/180.0*math.pi))*0.5*p.contrast+p.pedestal floating_point_sin = numpy.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype) # PyOpenGL 2.0.1.09 has a bug, so use our own wrapper _vegl.veglTexSubImage1D(gl.GL_TEXTURE_1D, # target 0, # level 0, # x offset p.num_samples, # width self.format, # format of new texel data self.gl_type, # type of new texel data texel_data) # new texel data if 0: compare_array = numpy.empty(texel_data.shape,dtype=texel_data.dtype) pixels = _vegl.veglGetTexImage(gl.GL_TEXTURE_1D, # target 0, # level self.format, # format self.gl_type, # type compare_array) assert numpy.allclose( compare_array, texel_data ) h_w = p.size[0]/2.0 h_h = p.size[1]/2.0 l = -h_w r = h_w b = -h_h t = h_h # in the case of only color1, # the texel data multiplies color1 to produce a color # with color2, # the texel data linearly interpolates between color1 and color2 gl.glColor4f(p.color1[0],p.color1[1],p.color1[2],p.max_alpha) if p.mask: p.mask.draw_masked_quad(0.0,1.0,0.0,1.0, # l,r,b,t for texture coordinates l,r,b,t, # l,r,b,t in eye coordinates depth ) # also in eye coordinates else: # draw unmasked quad gl.glBegin(gl.GL_QUADS) gl.glTexCoord2f(0.0,0.0) gl.glVertex3f(l,b,depth) gl.glTexCoord2f(1.0,0.0) gl.glVertex3f(r,b,depth) gl.glTexCoord2f(1.0,1.0) gl.glVertex3f(r,t,depth) gl.glTexCoord2f(0.0,1.0) gl.glVertex3f(l,t,depth) gl.glEnd() # GL_QUADS gl.glDisable(gl.GL_TEXTURE_1D) gl.glPopMatrix() class SinGrating3D(LuminanceGratingCommon): """Sine wave grating stimulus texture mapped onto quad in 3D This is a general-purpose, realtime sine-wave luminace grating generator. This 3D version doesn't support an orientation parameter. This could be implemented, but for now should be done by orienting the quad in 3D. Parameters ========== bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Inherited from LuminanceGratingCommon Default: 8 color1 -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (1.0, 1.0, 1.0) color2 -- optional color with which to perform interpolation with color1 in RGB space (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (determined at runtime) contrast -- (Real) Default: 1.0 depth -- (Real) Default: (determined at runtime) depth_test -- perform depth test? (Boolean) Default: True ignore_time -- (Boolean) Default: False lowerleft -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (0.0, 0.0, -1.0) lowerright -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (1.0, 0.0, -1.0) mask -- optional masking function (Instance of <class 'VisionEgg.Textures.Mask2D'>) Default: (determined at runtime) max_alpha -- (Real) Default: 1.0 num_samples -- (UnsignedInteger) Default: 512 on -- draw stimulus? (Boolean) Default: True pedestal -- (Real) Default: 0.5 phase_at_t0 -- (Real) Default: 0.0 recalculate_phase_tolerance -- (Real) Default: (determined at runtime) size -- defines coordinate size of grating (in eye coordinates) (Sequence2 of Real) Default: (1.0, 1.0) spatial_freq -- frequency defined relative to coordinates defined in size parameter (units; cycles/eye_coord_unit) (Real) Default: 4.0 t0_time_sec_absolute -- (Real) Default: (determined at runtime) temporal_freq_hz -- (Real) Default: 5.0 upperleft -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (0.0, 1.0, -1.0) upperright -- vertex position (units: eye coordinates) (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (1.0, 1.0, -1.0) """ parameters_and_defaults = VisionEgg.ParameterDefinition({ 'on':(True, ve_types.Boolean, "draw stimulus?"), 'mask':(None, # allows window onto otherwise (tilted) rectangular grating ve_types.Instance(VisionEgg.Textures.Mask2D), "optional masking function"), 'contrast':(1.0, ve_types.Real), 'pedestal':(0.5, ve_types.Real), 'depth':(None, # if not None, turns on depth testing and allows for occlusion ve_types.Real), 'size':((1.0,1.0), # in eye coordinates ve_types.Sequence2(ve_types.Real), "defines coordinate size of grating (in eye coordinates)"), 'spatial_freq':(4.0, # cycles/eye coord units ve_types.Real, "frequency defined relative to coordinates defined in size parameter (units; cycles/eye_coord_unit)"), 'temporal_freq_hz':(5.0, # hz ve_types.Real), 't0_time_sec_absolute':(None, # Will be assigned during first call to draw() ve_types.Real), 'ignore_time':(False, # ignore temporal frequency variable - allow control purely with phase_at_t0 ve_types.Boolean), 'phase_at_t0':(0.0, # degrees [0.0-360.0] ve_types.Real), 'num_samples':(512, # number of spatial samples, should be a power of 2 ve_types.UnsignedInteger), 'max_alpha':(1.0, # controls "opacity": 1.0 = completely opaque, 0.0 = completely transparent ve_types.Real), 'color1':((1.0, 1.0, 1.0), # alpha is ignored (if given) -- use max_alpha parameter ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'color2':(None, # perform interpolation with color1 in RGB space. ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "optional color with which to perform interpolation with color1 in RGB space"), 'recalculate_phase_tolerance':(None, # only recalculate texture when phase is changed by more than this amount, None for always recalculate. (Saves time.) ve_types.Real), 'depth_test':(True, ve_types.Boolean, "perform depth test?"), 'lowerleft':((0.0,0.0,-1.0), # in eye coordinates ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "vertex position (units: eye coordinates)"), 'lowerright':((1.0,0.0,-1.0), # in eye coordinates ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "vertex position (units: eye coordinates)"), 'upperleft':((0.0,1.0,-1.0), # in eye coordinates ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "vertex position (units: eye coordinates)"), 'upperright':((1.0,1.0,-1.0), # in eye coordinates ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "vertex position (units: eye coordinates)"), 'polygon_offset_enabled':(False, ve_types.Boolean, "perform polygon offset?"), 'polygon_offset_factor':(1.0, ve_types.Real, "polygon factor"), 'polygon_offset_units':(1.0, ve_types.Real, "polygon units"), }) __slots__ = ( '_texture_object_id', '_last_phase', ) def __init__(self,**kw): LuminanceGratingCommon.__init__(self,**kw) p = self.parameters # shorthand self._texture_object_id = gl.glGenTextures(1) if p.mask: gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) gl.glBindTexture(gl.GL_TEXTURE_1D,self._texture_object_id) # Do error-checking on texture to make sure it will load max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) if p.num_samples > max_dim: raise NumSamplesTooLargeError("Grating num_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,)) self.calculate_bit_depth_dependencies() w = p.size[0] inc = w/float(p.num_samples) phase = 0.0 # this data won't get used - don't care about phase self._last_phase = phase floating_point_sin = numpy.sin(2.0*math.pi*p.spatial_freq*numpy.arange(0.0,w,inc,dtype=numpy.float)+(phase/180.0*math.pi))*0.5*p.contrast+p.pedestal floating_point_sin = numpy.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring() # Because the MAX_TEXTURE_SIZE method is insensitive to the current # state of the video system, another check must be done using # "proxy textures". gl.glTexImage1D(gl.GL_PROXY_TEXTURE_1D, # target 0, # level self.gl_internal_format, # video RAM internal format p.num_samples, # width 0, # border self.format, # format of texel data self.gl_type, # type of texel data texel_data) # texel data (irrelevant for proxy) if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_1D, # Need PyOpenGL >= 2.0 0, gl.GL_TEXTURE_WIDTH) == 0: raise NumSamplesTooLargeError("Grating num_samples is too wide for your video system!") # If we got here, it worked and we can load the texture for real. gl.glTexImage1D(gl.GL_TEXTURE_1D, # target 0, # level self.gl_internal_format, # video RAM internal format p.num_samples, # width 0, # border self.format, # format of texel data self.gl_type, # type of texel data texel_data) # texel data # Set texture object defaults gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_S,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_T,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,gl.GL_LINEAR) if p.color2 is not None: if VisionEgg.Core.gl_renderer == 'ATi Rage 128 Pro OpenGL Engine' and VisionEgg.Core.gl_version == '1.1 ATI-1.2.22': logger = logging.getLogger('VisionEgg.Gratings') logger.warning("Your video card and driver have known " "bugs which prevent them from rendering " "color gratings properly.") def __del__(self): gl.glDeleteTextures( [self._texture_object_id] ) def draw(self): p = self.parameters # shorthand if p.on: if p.mask: gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) if p.depth_test: gl.glEnable(gl.GL_DEPTH_TEST) else: gl.glDisable(gl.GL_DEPTH_TEST) if p.polygon_offset_enabled: gl.glEnable(gl.GL_POLYGON_OFFSET_EXT) gl.glPolygonOffset(p.polygon_offset_factor, p.polygon_offset_units) gl.glBindTexture(gl.GL_TEXTURE_1D,self._texture_object_id) gl.glEnable(gl.GL_TEXTURE_1D) gl.glDisable(gl.GL_TEXTURE_2D) if p.bit_depth != self.cached_bit_depth: self.calculate_bit_depth_dependencies() # allow max_alpha value to control blending gl.glEnable( gl.GL_BLEND ) gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) if p.color2: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_BLEND) gl.glTexEnvfv(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_COLOR, p.color2) ## alpha is ignored because the texture base internal format is luminance else: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) if p.t0_time_sec_absolute is None and not p.ignore_time: p.t0_time_sec_absolute = VisionEgg.time_func() w = p.size[0] inc = w/float(p.num_samples) if p.ignore_time: phase = p.phase_at_t0 else: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute phase = t_var*p.temporal_freq_hz*-360.0 + p.phase_at_t0 if p.recalculate_phase_tolerance is None or abs(self._last_phase - phase) > p.recalculate_phase_tolerance: self._last_phase = phase # we're re-drawing the phase at this angle floating_point_sin = numpy.sin(2.0*math.pi*p.spatial_freq*numpy.arange(0.0,w,inc,dtype=numpy.float)+(phase/180.0*math.pi))*0.5*p.contrast+p.pedestal floating_point_sin = numpy.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring() gl.glTexSubImage1D(gl.GL_TEXTURE_1D, # target 0, # level 0, # x offset p.num_samples, # width self.format, # format of new texel data self.gl_type, # type of new texel data texel_data) # new texel data # in the case of only color1, # the texel data multiplies color1 to produce a color # with color2, # the texel data linearly interpolates between color1 and color2 gl.glColor4f(p.color1[0],p.color1[1],p.color1[2],p.max_alpha) if p.mask: p.mask.draw_masked_quad_3d(0.0,1.0,0.0,1.0, # for texture coordinates p.lowerleft,p.lowerright,p.upperright,p.upperleft) else: # draw unmasked quad gl.glBegin(gl.GL_QUADS) gl.glTexCoord2f(0.0,0.0) gl.glVertex(*p.lowerleft) gl.glTexCoord2f(1.0,0.0) gl.glVertex(*p.lowerright) gl.glTexCoord2f(1.0,1.0) gl.glVertex(*p.upperright) gl.glTexCoord2f(0.0,1.0) gl.glVertex(*p.upperleft) gl.glEnd() # GL_QUADS gl.glDisable(gl.GL_TEXTURE_1D) if p.polygon_offset_enabled: gl.glDisable(gl.GL_POLYGON_OFFSET_EXT) class NumSamplesTooLargeError( RuntimeError ): pass
#!/usr/bin/env python """Sinusoidal grating calculated in realtime.""" ############################ # Import various modules # ############################ import VisionEgg VisionEgg.start_default_logging(); VisionEgg.watch_exceptions() from VisionEgg.Core import * from VisionEgg.FlowControl import Presentation from VisionEgg.Gratings_cycle import * from math import * VisionEgg.config.VISIONEGG_GUI_INIT = 0 #(to get the gratings directly) ##################################### # Initialize OpenGL window/screen # ##################################### screen = get_default_screen() ###################################### # Create sinusoidal grating object # ###################################### stimulus = SinGrating2D(position = ( screen.size[0]/2.0, screen.size[1]/2.0+100 ), anchor = 'center', size = ( 300.0 , 150.0 ), spatial_freq = 10.0 / screen.size[0], # units of cycles/pixel max_tf = 0.8, orientation = 0.0, cycle_length = 3.0, contrast = 1.0) stimulus2 = SinGrating2D(position = ( screen.size[0]/2.0, screen.size[1]/2.0-100 ), anchor = 'center', size = ( 300.0 , 150.0 ), spatial_freq = 10.0 / screen.size[0], # units of cycles/pixel max_tf = 0.8, orientation = 0.0, cycle_length = 3.0, contrast = 1.0) ############################################################### # Create viewport - intermediary between stimuli and screen # ############################################################### viewport = Viewport( screen=screen, stimuli=[stimulus,stimulus2] ) ######################################## # Create presentation object and go! # ######################################## def square_func(t): max_temporal_freq_hz=stimulus.parameters.max_tf if max_temporal_freq_hz*sin(pi*t/stimulus.parameters.cycle_length)>0: return max_temporal_freq_hz else: return -max_temporal_freq_hz def sin_func(t): max_temporal_freq_hz=stimulus2.parameters.max_tf return max_temporal_freq_hz*sin(pi*t/stimulus2.parameters.cycle_length) p = Presentation(go_duration=(9.0,'seconds'),viewports=[viewport]) p.add_controller(stimulus,'temporal_freq_hz',FunctionController(during_go_func=square_func)) p.add_controller(stimulus2,'temporal_freq_hz',FunctionController(during_go_func=sin_func)) p.go()
# The Vision Egg: SphereMap # # Copyright (C) 2001-2004 Andrew Straw. # Copyright (C) 2005-2008 California Institute of Technology # # Author: Andrew Straw <astraw@xxxxxxxxxxxxxxxxxxxxx> # URL: <http://www.visionegg.org/> # # Distributed under the terms of the GNU Lesser General Public License # (LGPL). See LICENSE.TXT that came with this file. """ Stimuli on spheres, including texture maps. """ import math, types import logging import VisionEgg.Core import VisionEgg.Textures import VisionEgg.Text import VisionEgg.Gratings import VisionEgg.ThreeDeeMath import VisionEgg.ParameterTypes as ve_types import numpy import numpy.oldnumeric as Numeric import Image import VisionEgg.GL as gl # get all OpenGL stuff in one namespace __version__ = VisionEgg.release_name class AzElGrid(VisionEgg.Core.Stimulus): """Spherical grid of iso-azimuth and iso-elevation lines. Parameters ========== anti_aliasing -- (Boolean) Default: True center_azimuth -- (Real) Default: 0.0 center_elevation -- (Real) Default: 0.0 major_line_color -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (0.0, 0.0, 0.0) major_line_width -- (Real) Default: 2.0 minor_line_color -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (0.0, 0.0, 1.0) minor_line_width -- (Real) Default: 1.0 my_viewport -- (Instance of <class 'VisionEgg.Core.Viewport'>) Default: (determined at runtime) on -- (Boolean) Default: True text_offset -- (Sequence2 of Real) Default: (3, -2) Constant Parameters =================== az_major_spacing -- (Real) Default: 30.0 az_minor_spacing -- (Real) Default: 10.0 el_major_spacing -- (Real) Default: 30.0 el_minor_spacing -- (Real) Default: 10.0 font_size -- (UnsignedInteger) Default: 24 num_samples_per_circle -- (UnsignedInteger) Default: 100 radius -- (Real) Default: 1.0 text_anchor -- (String) Default: lowerleft text_color -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (0.0, 0.0, 0.0) use_text -- (Boolean) Default: True """ parameters_and_defaults = { 'on':(True, ve_types.Boolean), 'center_azimuth':(0.0, # 0=right, 90=right ve_types.Real), 'center_elevation':(0.0, # 0=right, 90=up ve_types.Real), 'minor_line_width':(1.0, ve_types.Real), 'major_line_width':(2.0, ve_types.Real), 'minor_line_color':((0.0,0.0,1.0), ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'major_line_color':((0.0,0.0,0.0), ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'my_viewport':(None, # viewport I'm in ve_types.Instance(VisionEgg.Core.Viewport)), 'text_offset':((3,-2), # offset (x,y) to nudge text labels ve_types.Sequence2(ve_types.Real)), 'anti_aliasing' : ( True, ve_types.Boolean ), } constant_parameters_and_defaults = { 'use_text':(True, ve_types.Boolean), 'radius':(1.0, ve_types.Real), 'az_minor_spacing':(10.0, ve_types.Real), 'az_major_spacing':(30.0, ve_types.Real), 'el_minor_spacing':(10.0, ve_types.Real), 'el_major_spacing':(30.0, ve_types.Real), 'num_samples_per_circle':(100, ve_types.UnsignedInteger), 'font_size':(24, ve_types.UnsignedInteger), 'text_color':((0.0,0.0,0.0), ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'text_anchor':('lowerleft', ve_types.String), } __slots__ = ( 'cached_minor_lines_display_list', 'cached_major_lines_display_list', 'text_viewport', 'text_viewport_orig', '_gave_alpha_warning', 'labels', 'labels_xyz', ) def __init__(self,**kw): VisionEgg.Core.Stimulus.__init__(self,**kw) self.cached_minor_lines_display_list = gl.glGenLists(1) # Allocate a new display list self.cached_major_lines_display_list = gl.glGenLists(1) # Allocate a new display list self.__rebuild_display_lists() self.text_viewport = None # not set yet self._gave_alpha_warning = False def __rebuild_display_lists(self): def get_xyz(theta,phi,radius): # theta normally between 0 and pi (north pole to south pole) # phi between -pi and pi y = radius * math.cos( theta ) w = radius * math.sin( theta ) x = w * math.cos( phi ) z = w * math.sin( phi ) return x,y,z def draw_half_great_circle(az): for i in range(cp.num_samples_per_circle/2): # let theta exceed 1 pi to draw 2nd half of circle theta_start = i/float(cp.num_samples_per_circle)*2*math.pi theta_stop = (i+1)/float(cp.num_samples_per_circle)*2*math.pi phi_start = phi_stop = (az-90.0)/180.0*math.pi x_start,y_start,z_start = get_xyz(theta_start,phi_start,cp.radius) x_stop,y_stop,z_stop = get_xyz(theta_stop,phi_stop,cp.radius) gl.glVertex3f(x_start, y_start, z_start) gl.glVertex3f(x_stop, y_stop, z_stop) def draw_iso_elevation_circle(el): # el from -90 = pi to el 90 = 0 theta_start = theta_stop = -(el-90) / 180.0 * math.pi for i in range(cp.num_samples_per_circle): phi_start = i/float(cp.num_samples_per_circle)*2*math.pi phi_stop = (i+1)/float(cp.num_samples_per_circle)*2*math.pi x_start,y_start,z_start = get_xyz(theta_start,phi_start,cp.radius) x_stop,y_stop,z_stop = get_xyz(theta_stop,phi_stop,cp.radius) gl.glVertex3f(x_start, y_start, z_start) gl.glVertex3f(x_stop, y_stop, z_stop) cp = self.constant_parameters # Weird range construction to be sure to include zero. azs_major = numpy.concatenate(( numpy.arange(0.0,180.0,cp.az_major_spacing), -numpy.arange(0.0,180.0,cp.az_major_spacing)[1:])) azs_minor = numpy.concatenate(( numpy.arange(0.0,180.0,cp.az_minor_spacing), -numpy.arange(0.0,180.0,cp.az_minor_spacing)[1:])) els_major = numpy.concatenate(( numpy.arange(0.0,90.0,cp.el_major_spacing), -numpy.arange(0.0,90.0,cp.el_major_spacing)[1:])) els_minor = numpy.concatenate(( numpy.arange(0.0,90.0,cp.el_minor_spacing), -numpy.arange(0.0,90.0,cp.el_minor_spacing)[1:])) gl.glNewList(self.cached_minor_lines_display_list,gl.GL_COMPILE) gl.glBegin(gl.GL_LINES) # az minor for az in azs_minor: if az in azs_major: continue # draw only once as major draw_half_great_circle(az) for el in els_minor: if el in els_major: continue # draw only once as major draw_iso_elevation_circle(el) gl.glEnd() gl.glEndList() gl.glNewList(self.cached_major_lines_display_list,gl.GL_COMPILE) gl.glBegin(gl.GL_LINES) for az in azs_major: draw_half_great_circle(az) for el in els_major: draw_iso_elevation_circle(el) gl.glEnd() gl.glEndList() if cp.use_text: self.labels = [] self.labels_xyz = [] els_major = list(els_major)+[90.0] # make sure we have north pole for el in els_major: for az in azs_major: theta = -(el-90) / 180.0 * math.pi phi = (az-90.0)/180.0*math.pi x,y,z = get_xyz(theta,phi,cp.radius) self.labels_xyz.append((x,y,z)) self.labels.append( VisionEgg.Text.Text( text = '%.0f, %.0f'%(az,el), font_size = cp.font_size, color = cp.text_color, anchor = cp.text_anchor, ) ) if (el == -90) or (el == 90): self.labels[-1].parameters.text = 'x, %.0f'%(el,) break # only one label at the poles self.labels_xyz = Numeric.array(self.labels_xyz) def draw(self): p = self.parameters cp = self.constant_parameters if p.on: # Set OpenGL state variables gl.glDisable( gl.GL_DEPTH_TEST ) gl.glDisable( gl.GL_TEXTURE_2D ) # Make sure textures are not drawn gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() gl.glRotatef(p.center_azimuth,0.0,-1.0,0.0) gl.glRotatef(p.center_elevation,1.0,0.0,0.0) if p.anti_aliasing: if len(p.minor_line_color) == 4 and not self._gave_alpha_warning: if p.minor_line_color[3] != 1.0: logger = logging.getLogger('VisionEgg.SphereMap') logger.warning("The parameter anti_aliasing is " "set to true in the AzElGrid " "stimulus class, but the color " "parameter specifies an alpha " "value other than 1.0. To " "acheive the best anti-aliasing, " "ensure that the alpha value for " "the color parameter is 1.0.") self._gave_alpha_warning = 1 if len(p.major_line_color) == 4 and not self._gave_alpha_warning: if p.major_line_color[3] != 1.0: logger = logging.getLogger('VisionEgg.SphereMap') logger.warning("The parameter anti_aliasing is " "set to true in the AzElGrid " "stimulus class, but the color " "parameter specifies an alpha " "value other than 1.0. To " "acheive the best anti-aliasing, " "ensure that the alpha value for " "the color parameter is 1.0.") self._gave_alpha_warning = 1 gl.glEnable( gl.GL_LINE_SMOOTH ) # allow max_alpha value to control blending gl.glEnable( gl.GL_BLEND ) gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) else: gl.glDisable( gl.GL_BLEND ) if len(p.minor_line_color)==3: gl.glColor3f(*p.minor_line_color) elif len(p.minor_line_color)==4: gl.glColor4f(*p.minor_line_color) gl.glLineWidth(p.minor_line_width) gl.glCallList(self.cached_minor_lines_display_list) if len(p.major_line_color)==3: gl.glColor3f(*p.major_line_color) elif len(p.major_line_color)==4: gl.glColor4f(*p.major_line_color) gl.glLineWidth(p.major_line_width) gl.glCallList(self.cached_major_lines_display_list) if p.anti_aliasing: gl.glDisable( gl.GL_LINE_SMOOTH ) # turn off if cp.use_text: my_view = p.my_viewport if (my_view is None) or (not my_view._is_drawing): raise ValueError('use_text is True, but my_viewport not (properly) assigned') if self.text_viewport is None or self.text_viewport_orig != my_view: # make viewport for text (uses default orthographic projection) vp = my_view.parameters self.text_viewport = VisionEgg.Core.Viewport(screen=vp.screen, position=vp.position, size=vp.size, anchor=vp.anchor, ) lowerleft = VisionEgg._get_lowerleft(vp.position,vp.anchor,vp.size) self.text_viewport.parameters.projection.stateless_translate(-lowerleft[0],-lowerleft[1],0) self.text_viewport_orig = p.my_viewport # in case my_viewport changes, change text_viewport # draw text labels my_proj = my_view.parameters.projection xyz = self.labels_xyz t = VisionEgg.ThreeDeeMath.TransformMatrix() t.rotate( p.center_azimuth,0.0,-1.0,0.0 ) # acheive same transforms as the lines t.rotate( p.center_elevation,1.0,0.0,0.0 ) xyz = t.transform_vertices(self.labels_xyz) clip = my_proj.eye_2_clip(xyz) try: # this is much faster when no OverflowError... window_coords = my_view.clip_2_window(clip) all_at_once = True except OverflowError: all_at_once = False draw_labels = [] for i in range(len(self.labels)): if clip[i,3] < 0: continue # this vertex is not on screen label = self.labels[i] if all_at_once: this_pos = window_coords[i,:2] else: try: window_coords = my_view.clip_2_window(clip[i,:]) except OverflowError: continue # not much we can do with this vertex, either this_pos = window_coords[:2] label.parameters.position = (this_pos[0] + p.text_offset[0], this_pos[1] + p.text_offset[1]) draw_labels.append(label) self.text_viewport.parameters.stimuli = draw_labels self.text_viewport.draw() my_view.make_current() # restore viewport gl.glPopMatrix() class SphereMap(VisionEgg.Textures.TextureStimulusBaseClass): """Mercator mapping of rectangular texture onto sphere. Parameters ========== center_azimuth -- (Real) Default: 0.0 center_elevation -- (Real) Default: 0.0 contrast -- (Real) Default: 1.0 on -- (Boolean) Default: True radius -- (Real) Default: 1.0 slices -- (UnsignedInteger) Default: 30 stacks -- (UnsignedInteger) Default: 30 texture -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>) Inherited from VisionEgg.Textures.TextureStimulusBaseClass Default: (determined at runtime) texture_mag_filter -- OpenGL filter enum (Integer) Inherited from VisionEgg.Textures.TextureStimulusBaseClass Default: GL_LINEAR (9729) texture_min_filter -- OpenGL filter enum (Integer) Inherited from VisionEgg.Textures.TextureStimulusBaseClass Default: (GL enum determined at runtime) texture_wrap_s -- OpenGL texture wrap enum (Integer) Inherited from VisionEgg.Textures.TextureStimulusBaseClass Default: (GL enum determined at runtime) texture_wrap_t -- OpenGL texture wrap enum (Integer) Inherited from VisionEgg.Textures.TextureStimulusBaseClass Default: (GL enum determined at runtime) Constant Parameters =================== internal_format -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer) Default: GL_RGB (6407) mipmaps_enabled -- Are mipmaps enabled? (Boolean) Default: True shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean) Default: False """ parameters_and_defaults = { 'on':(True, ve_types.Boolean), 'contrast':(1.0, ve_types.Real), 'center_azimuth':(0.0, # 0=right, 90=right ve_types.Real), 'center_elevation':(0.0, # 0=right, 90=up ve_types.Real), # Changing these parameters will cause re-computation of display list (may cause frame skip) 'radius':(1.0, ve_types.Real), 'slices':(30, ve_types.UnsignedInteger), 'stacks':(30, ve_types.UnsignedInteger)} __slots__ = ( 'cached_display_list', '_cached_radius', '_cached_slices', '_cached_stacks', ) def __init__(self,**kw): VisionEgg.Textures.TextureStimulusBaseClass.__init__(self,**kw) self.cached_display_list = gl.glGenLists(1) # Allocate a new display list self.__rebuild_display_list() def __rebuild_display_list(self): p = self.parameters s_gain = p.texture.buf_rf - p.texture.buf_lf t_gain = p.texture.buf_bf - p.texture.buf_tf s_offs = p.texture.buf_lf t_offs = p.texture.buf_tf gl.glNewList(self.cached_display_list,gl.GL_COMPILE) gl.glBegin(gl.GL_QUADS) for stack in range(p.stacks): stack_upper_frac = float(stack+1)/p.stacks stack_lower_frac = float(stack)/p.stacks theta_upper = stack_upper_frac * math.pi theta_lower = stack_lower_frac * math.pi y_upper = p.radius * math.cos( theta_upper ) w_upper = p.radius * math.sin( theta_upper ) y_lower = p.radius * math.cos( theta_lower ) w_lower = p.radius * math.sin( theta_lower ) for slice in range(p.slices): slice_start_frac = float(slice)/p.slices slice_stop_frac = float(slice+1)/p.slices phi_start = slice_start_frac * 2 * math.pi phi_stop = slice_stop_frac * 2 * math.pi x_start_upper = w_upper * math.cos(phi_start) x_start_lower = w_lower * math.cos(phi_start) x_stop_upper = w_upper * math.cos(phi_stop) x_stop_lower = w_lower * math.cos(phi_stop) z_start_upper = w_upper * math.sin(phi_start) z_start_lower = w_lower * math.sin(phi_start) z_stop_upper = w_upper * math.sin(phi_stop) z_stop_lower = w_lower * math.sin(phi_stop) tex_l = slice_start_frac*s_gain+s_offs tex_r = slice_stop_frac*s_gain+s_offs tex_b = stack_lower_frac*t_gain+t_offs tex_t = stack_upper_frac*t_gain+t_offs gl.glTexCoord2f(tex_l,tex_t) gl.glVertex3f(x_start_upper, y_upper, z_start_upper) gl.glTexCoord2f(tex_r,tex_t) gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper) gl.glTexCoord2f(tex_r,tex_b) gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower) gl.glTexCoord2f(tex_l,tex_b) gl.glVertex3f(x_start_lower, y_lower, z_start_lower) gl.glEnd() gl.glEndList() self._cached_radius = p.radius self._cached_slices = p.slices self._cached_stacks = p.stacks def draw(self): """Redraw the scene on every frame. """ p = self.parameters if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks: self.__rebuild_display_list() if p.on: # Set OpenGL state variables gl.glEnable( gl.GL_DEPTH_TEST ) gl.glEnable( gl.GL_TEXTURE_2D ) # Make sure textures are drawn gl.glEnable( gl.GL_BLEND ) # Contrast control implemented through blending # All of the contrast control stuff is somewhat arcane and # not very clear from reading the code, so here is how it # works in English. (Not that it makes it any more clear!) # # In the final "textured fragment" (before being blended # to the framebuffer), the color values are equal to those # of the texture (with the exception of pixels around the # edges which have their amplitudes reduced due to # anti-aliasing and are intermediate between the color of # the texture and mid-gray), and the alpha value is set to # the contrast. Blending occurs, and by choosing the # appropriate values for glBlendFunc, adds the product of # fragment alpha (contrast) and fragment color to the # product of one minus fragment alpha (contrast) and what # was already in the framebuffer. gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL) # clear modelview matrix gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() gl.glColor4f(0.5,0.5,0.5,p.contrast) # Set the polygons' fragment color (implements contrast) if not self.constant_parameters.mipmaps_enabled: if p.texture_min_filter in VisionEgg.Textures.TextureStimulusBaseClass._mipmap_modes: raise RuntimeError("Specified a mipmap mode in texture_min_filter, but mipmaps not enabled.") self.texture_object.set_min_filter( p.texture_min_filter ) self.texture_object.set_mag_filter( p.texture_mag_filter ) self.texture_object.set_wrap_mode_s( p.texture_wrap_s ) self.texture_object.set_wrap_mode_t( p.texture_wrap_t ) # center the texture map gl.glRotatef(p.center_azimuth,0.0,-1.0,0.0) gl.glRotatef(p.center_elevation,1.0,0.0,0.0) gl.glCallList(self.cached_display_list) gl.glPopMatrix() class SphereGrating(VisionEgg.Gratings.LuminanceGratingCommon): """Map 2D sinusoidal grating onto sphere. Parameters ========== bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Inherited from VisionEgg.Gratings.LuminanceGratingCommon Default: 8 check_texture_size -- (Boolean) Default: True contrast -- (Real) Default: 1.0 color1 -- (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (1.0, 1.0, 1.0) color2 -- optional color with which to perform interpolation with color1 in RGB space (AnyOf(Sequence3 of Real or Sequence4 of Real)) Default: (determined at runtime) max_alpha -- (Real) Default: 1.0 grating_center_azimuth -- (Real) Default: 0.0 grating_center_elevation -- (Real) Default: 0.0 ignore_time -- (Boolean) Default: False lowpass_cutoff_cycles_per_texel -- helps prevent spatial aliasing (Real) Default: 0.5 min_filter -- OpenGL filter enum (Integer) Default: GL_LINEAR (9729) num_samples -- (UnsignedInteger) Default: 1024 on -- (Boolean) Default: True orientation -- (Real) Default: 0.0 phase_at_t0 -- (Real) Default: 0.0 radius -- (Real) Default: 1.0 slices -- (UnsignedInteger) Default: 30 spatial_freq_cpd -- (Real) Default: 0.0277777777778 stacks -- (UnsignedInteger) Default: 30 t0_time_sec_absolute -- (Real) Default: (determined at runtime) temporal_freq_hz -- (Real) Default: 5.0 max_tf -- (Real) Default: 5.0 cycle_length -- (Real) Default: None """ parameters_and_defaults = { 'on':(True, ve_types.Boolean), 'contrast':(1.0, ve_types.Real), 'color1':((1.0, 1.0, 1.0), # alpha is ignored (if given) -- use max_alpha parameter ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real))), 'color2':(None, # perform interpolation with color1 in RGB space. ve_types.AnyOf(ve_types.Sequence3(ve_types.Real), ve_types.Sequence4(ve_types.Real)), "optional color with which to perform interpolation with color1 in RGB space"), 'max_alpha':(1.0, # controls "opacity": 1.0 = completely opaque, 0.0 = completely transparent ve_types.Real), 'spatial_freq_cpd':(1.0/36.0, # cycles/degree ve_types.Real), 'temporal_freq_hz':(5.0, # hz ve_types.Real), 'max_tf':(5.0, # hz ve_types.Real), 't0_time_sec_absolute':(None, ve_types.Real), 'ignore_time':(False, # ignore temporal frequency variable - allow control purely with phase_at_t0 ve_types.Boolean), 'phase_at_t0':(0.0, # degrees ve_types.Real), 'cycle_length':(None, ve_types.Real), 'orientation':(0.0, # 0=right, 90=up ve_types.Real), 'grating_center_azimuth':(0.0, # 0=right, 90=down ve_types.Real), 'grating_center_elevation':(0.0, # 0=right, 90=down ve_types.Real), 'check_texture_size':(True, # slows down drawing but catches errors ve_types.Boolean), 'lowpass_cutoff_cycles_per_texel':(0.5, ve_types.Real, 'helps prevent spatial aliasing'), 'min_filter':(gl.GL_LINEAR, ve_types.Integer, "OpenGL filter enum", VisionEgg.ParameterDefinition.OPENGL_ENUM), # changing this parameters causes re-drawing of the texture object and may cause frame skipping 'num_samples':(1024, # number of spatial samples, should be a power of 2 ve_types.UnsignedInteger), # Changing these parameters will cause re-computation of display list (may cause frame skip) 'radius':(1.0, ve_types.Real), 'slices':(30, ve_types.UnsignedInteger), 'stacks':(30, ve_types.UnsignedInteger), } __slots__ = ( 'texture_object_id', 'cached_display_list_id', '_cached_num_samples', '_cached_radius', '_cached_slices', '_cached_stacks', '_phase_before', '_time_before', '_dt', ) def __init__(self,**kw): VisionEgg.Gratings.LuminanceGratingCommon.__init__(self,**kw) if self.parameters.t0_time_sec_absolute is None: self.parameters.t0_time_sec_absolute = VisionEgg.time_func() self.texture_object_id = gl.glGenTextures(1) # Allocate a new texture object self.__rebuild_texture_object() self.cached_display_list_id = gl.glGenLists(1) # Allocate a new display list self.__rebuild_display_list() self._phase_before = 0.0 self._time_before = 0.0 self._dt = 0.0 def __rebuild_texture_object(self): gl.glBindTexture(gl.GL_TEXTURE_1D,self.texture_object_id) p = self.parameters # shorthand # Do error-checking on texture to make sure it will load max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) if p.num_samples > max_dim: raise VisionEgg.Gratings.NumSamplesTooLargeError("Grating num_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,)) self.calculate_bit_depth_dependencies() l = 0.0 r = 360.0 mipmap_level = 0 this_mipmap_level_num_samples = p.num_samples while this_mipmap_level_num_samples >= 1: inc = 360.0/float(this_mipmap_level_num_samples) # degrees per pixel cycles_per_texel = p.spatial_freq_cpd * inc if cycles_per_texel < p.lowpass_cutoff_cycles_per_texel: # sharp cutoff lowpass filter # below cutoff frequency - draw sine wave if p.ignore_time: phase = p.phase_at_t0 else: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute phase = t_var*p.temporal_freq_hz*360.0 + p.phase_at_t0 floating_point_sin = Numeric.sin(2.0*math.pi*p.spatial_freq_cpd*Numeric.arange(l,r,inc,'d')-(phase/180.0*math.pi))*0.5*p.contrast+0.5 floating_point_sin = Numeric.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring() else: # above cutoff frequency - blank texel_data = (self.max_int_val*0.5)*Numeric.ones((this_mipmap_level_num_samples,),self.numpy_dtype) if p.check_texture_size: # Because the MAX_TEXTURE_SIZE method is insensitive to the current # state of the video system, another check must be done using # "proxy textures". gl.glTexImage1D(gl.GL_PROXY_TEXTURE_1D, # target mipmap_level, # level self.gl_internal_format, # video RAM internal format: RGB this_mipmap_level_num_samples, # width 0, # border self.format, # format of image data self.gl_type, # type of image data texel_data) # texel data if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_1D,0,gl.GL_TEXTURE_WIDTH) == 0: raise NumSamplesTooLargeError("Grating num_samples is too wide for your video system!") # If we got here, it worked and we can load the texture for real. gl.glTexImage1D(gl.GL_TEXTURE_1D, # target mipmap_level, # level self.gl_internal_format, # video RAM internal format: RGB this_mipmap_level_num_samples, # width 0, # border self.format, # format of image data self.gl_type, # type of image data texel_data) # texel data # prepare for next mipmap level this_mipmap_level_num_samples = this_mipmap_level_num_samples/2 # integer division mipmap_level += 1 # Set some texture object defaults gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_S,gl.GL_REPEAT) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_T,gl.GL_REPEAT) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,p.min_filter) self._cached_num_samples = p.num_samples def __rebuild_display_list(self): gl.glNewList(self.cached_display_list_id,gl.GL_COMPILE) p = self.parameters gl.glBegin(gl.GL_QUADS) for stack in range(p.stacks): stack_upper_frac = float(stack+1)/p.stacks stack_lower_frac = float(stack)/p.stacks theta_upper = stack_upper_frac * math.pi theta_lower = stack_lower_frac * math.pi y_upper = p.radius * math.cos( theta_upper ) w_upper = p.radius * math.sin( theta_upper ) y_lower = p.radius * math.cos( theta_lower ) w_lower = p.radius * math.sin( theta_lower ) for slice in range(p.slices): slice_start_frac = float(slice)/p.slices slice_stop_frac = float(slice+1)/p.slices phi_start = slice_start_frac * 2 * math.pi phi_stop = slice_stop_frac * 2 * math.pi x_start_upper = w_upper * math.cos(phi_start) x_start_lower = w_lower * math.cos(phi_start) x_stop_upper = w_upper * math.cos(phi_stop) x_stop_lower = w_lower * math.cos(phi_stop) z_start_upper = w_upper * math.sin(phi_start) z_start_lower = w_lower * math.sin(phi_start) z_stop_upper = w_upper * math.sin(phi_stop) z_stop_lower = w_lower * math.sin(phi_stop) tex_l = slice_start_frac tex_r = slice_stop_frac tex_b = 0.0#stack_lower_frac tex_t = 1.0#stack_upper_frac gl.glTexCoord2f(tex_l,tex_t) gl.glVertex3f(x_start_upper, y_upper, z_start_upper) gl.glTexCoord2f(tex_r,tex_t) gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper) gl.glTexCoord2f(tex_r,tex_b) gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower) gl.glTexCoord2f(tex_l,tex_b) gl.glVertex3f(x_start_lower, y_lower, z_start_lower) gl.glEnd() gl.glEndList() self._cached_radius = p.radius self._cached_slices = p.slices self._cached_stacks = p.stacks def draw(self): """Redraw the scene on every frame. """ p = self.parameters if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks: self.__rebuild_display_list() if self._cached_num_samples != p.num_samples: self.__rebuild_texture_object() if p.on: if p.bit_depth != self.cached_bit_depth: self.calculate_bit_depth_dependencies() # Set OpenGL state variables gl.glEnable( gl.GL_DEPTH_TEST ) gl.glEnable( gl.GL_TEXTURE_1D ) # Make sure textures are drawn gl.glDisable( gl.GL_TEXTURE_2D ) gl.glDisable( gl.GL_BLEND ) gl.glBindTexture(gl.GL_TEXTURE_1D,self.texture_object_id) gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,p.min_filter) l = 0.0 r = 360.0 mipmap_level = 0 this_mipmap_level_num_samples = p.num_samples while this_mipmap_level_num_samples >= 1: inc = 360.0/float(this_mipmap_level_num_samples)# degrees per pixel cycles_per_texel = p.spatial_freq_cpd * inc if cycles_per_texel < p.lowpass_cutoff_cycles_per_texel: # sharp cutoff lowpass filter if p.ignore_time: phase = p.phase_at_t0 else: if p.cycle_length is not None: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute self._dt = t_var - self._time_before phase = self._dt*p.temporal_freq_hz*-360.0 + p.phase_at_t0 + self._phase_before self._time_before = t_var self._phase_before = phase else: t_var = VisionEgg.time_func() - p.t0_time_sec_absolute phase = t_var*p.temporal_freq_hz*360.0 + p.phase_at_t0 floating_point_sin = Numeric.sin(2.0*math.pi*p.spatial_freq_cpd*Numeric.arange(l,r,inc,'d')-(phase/180.0*math.pi))*0.5*p.contrast+0.5 floating_point_sin = Numeric.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1 texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring() else: blank = 0.5*Numeric.ones((this_mipmap_level_num_samples,),'d') texel_data = (blank*self.max_int_val).astype(self.numpy_dtype).tostring() gl.glTexSubImage1D(gl.GL_TEXTURE_1D, # target mipmap_level, # level 0, # x offset this_mipmap_level_num_samples, # width self.format, # data format self.gl_type, # data type texel_data) # prepare for next mipmap level this_mipmap_level_num_samples = this_mipmap_level_num_samples/2 # integer division mipmap_level += 1 # allow max_alpha value to control blending gl.glEnable( gl.GL_BLEND ) gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) if p.color2: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_BLEND) gl.glTexEnvfv(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_COLOR, p.color2) ## alpha is ignored because the texture base internal format is luminance else: gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_MODULATE) gl.glColor4f(p.color1[0],p.color1[1],p.color1[2],p.max_alpha) # clear modelview matrix gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() # center the grating gl.glRotatef(p.grating_center_azimuth,0.0,-1.0,0.0) gl.glRotatef(p.grating_center_elevation,1.0,0.0,0.0) # do the orientation gl.glRotatef(p.orientation,0.0,0.0,1.0) gl.glCallList(self.cached_display_list_id) gl.glDisable( gl.GL_TEXTURE_1D ) gl.glPopMatrix() class SphereWindow(VisionEgg.Gratings.LuminanceGratingCommon): """This draws an opaque sphere with a single window in it. This is useful when you need to have a viewport on a 3D scene. Parameters ========== bit_depth -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger) Inherited from VisionEgg.Gratings.LuminanceGratingCommon Default: 8 num_s_samples -- (UnsignedInteger) Default: 512 num_t_samples -- (UnsignedInteger) Default: 512 on -- (Boolean) Default: True opaque_color -- (Sequence4 of Real) Default: (0.5, 0.5, 0.5, 0.0) radius -- (Real) Default: 1.0 slices -- (UnsignedInteger) Default: 30 stacks -- (UnsignedInteger) Default: 30 window_center_azimuth -- (Real) Default: 0.0 window_center_elevation -- (Real) Default: 0.0 window_shape -- can be 'circle', 'gaussian', or 'lat-long rectangle' (String) Default: gaussian window_shape_parameter2 -- (currently only used for) height of lat-long rectangle (in degrees) (Real) Default: 30.0 window_shape_radius_parameter -- radius of circle, sigma of gaussian, width of lat-long rectangle (in degrees) (Real) Default: 36.0 """ parameters_and_defaults = { 'on':(True, ve_types.Boolean), 'window_center_elevation':(0.0, ve_types.Real), 'window_center_azimuth':(0.0, ve_types.Real), 'opaque_color':((0.5,0.5,0.5,0.0), ve_types.Sequence4(ve_types.Real)), # changing these parameters causes re-drawing of the texture object and may cause frame skipping 'window_shape':('gaussian', # can be 'circle' or 'gaussian' ve_types.String, "can be 'circle', 'gaussian', or 'lat-long rectangle'", ), 'window_shape_radius_parameter':(36.0, ve_types.Real, 'radius of circle, sigma of gaussian, width of lat-long rectangle (in degrees)', ), 'window_shape_parameter2':(30.0, ve_types.Real, '(currently only used for) height of lat-long rectangle (in degrees)', ), 'num_s_samples':(512, # number of horizontal spatial samples, should be a power of 2 ve_types.UnsignedInteger), 'num_t_samples':(512, # number of vertical spatial samples, should be a power of 2 ve_types.UnsignedInteger), # Changing these parameters will cause re-computation of display list (may cause frame skip) 'radius':(1.0, # XXX could modify code below to use scaling, thus avoiding need for recomputation ve_types.Real), 'slices':(30, ve_types.UnsignedInteger), 'stacks':(30, ve_types.UnsignedInteger), } __slots__ = ( 'texture_object_id', 'windowed_display_list_id', 'opaque_display_list_id', '_cached_window_shape', '_cached_shape_radius_parameter', '_cached_shape_parameter2', '_cached_num_s_samples', '_cached_num_t_samples', '_cached_radius', '_cached_slices', '_cached_stacks', '_texture_s_is_azimuth', ) def __init__(self, **kw): VisionEgg.Gratings.LuminanceGratingCommon.__init__(self, **kw ) p = self.parameters # set self._texture_s_is_azimuth in advance if p.window_shape == 'lat-long rectangle': self._texture_s_is_azimuth = True else: self._texture_s_is_azimuth = False self.texture_object_id = gl.glGenTextures(1) self.__rebuild_texture_object() self.windowed_display_list_id = gl.glGenLists(1) # Allocate a new display list self.opaque_display_list_id = gl.glGenLists(1) # Allocate a new display list self.__rebuild_display_lists() def __rebuild_texture_object(self): gl.glBindTexture(gl.GL_TEXTURE_2D,self.texture_object_id) p = self.parameters # Do error-checking on texture to make sure it will load max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE) if p.num_s_samples > max_dim: raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_s_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,)) if p.num_t_samples > max_dim: raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_t_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,)) self.calculate_bit_depth_dependencies() self.gl_internal_format = gl.GL_ALPHA # change from luminance to alpha self.format = gl.GL_ALPHA # texture coordinates are Mercator: (determined when building display list) # s: x within sphere # t: z within sphere if p.window_shape == 'circle': if self._texture_s_is_azimuth: self.__rebuild_display_lists() # XXX this is aliased s_axis = (Numeric.arange(p.num_s_samples)/float(p.num_s_samples)-0.5)**2 t_axis = (Numeric.arange(p.num_t_samples)/float(p.num_t_samples)-0.5)**2 mask = s_axis[Numeric.NewAxis,:] + t_axis[:,Numeric.NewAxis] angle_deg = min(180,p.window_shape_radius_parameter) # clip angle cartesian_radius = 0.5*math.sin(p.window_shape_radius_parameter/180.0*math.pi) floating_point_window = Numeric.less(mask,cartesian_radius**2) elif p.window_shape == 'gaussian': if self._texture_s_is_azimuth: self.__rebuild_display_lists() MIN_EXP = -745.0 MAX_EXP = 709.0 s = Numeric.arange(0.0,p.num_s_samples,1.0,'f')/p.num_s_samples t = Numeric.arange(0.0,p.num_t_samples,1.0,'f')/p.num_t_samples sigma_normalized = p.window_shape_radius_parameter / 90.0 * 0.5 check_s = -((s-0.5)**2/(2.0*sigma_normalized**2)) try: # some platforms raise OverflowError when doing this on small numbers val_s = Numeric.exp( check_s ) except OverflowError: check_s = Numeric.clip(check_s,MIN_EXP,MAX_EXP) val_s = Numeric.exp( check_s ) check_t = -((t-0.5)**2/(2.0*sigma_normalized**2)) try: val_t = Numeric.exp( check_t ) except OverflowError: check_t = Numeric.clip(check_t,MIN_EXP,MAX_EXP) val_t = Numeric.exp( check_t ) floating_point_window = Numeric.outerproduct(val_t,val_s) elif p.window_shape == 'lat-long rectangle': if not self._texture_s_is_azimuth: self.__rebuild_display_lists() # s coordinate represents -90 to +90 degrees (azimuth). s_axis = (Numeric.arange(p.num_s_samples)/float(p.num_s_samples)-0.5)*180 s_axis = Numeric.less( abs(s_axis), p.window_shape_radius_parameter*0.5 ) # t coordinate represents height. # Convert angle to height. angle_deg = min(90,p.window_shape_parameter2*0.5) # clip angle desired_height = math.sin(angle_deg/180.0*math.pi)*0.5 t_axis = Numeric.arange(p.num_t_samples)/float(p.num_t_samples)-0.5 t_axis = Numeric.less(abs(t_axis),desired_height) floating_point_window = Numeric.outerproduct(t_axis,s_axis) else: raise RuntimeError('Unknown window_shape "%s"'%(p.window_shape,)) texel_data = (floating_point_window * self.max_int_val).astype(self.numpy_dtype).tostring() # Because the MAX_TEXTURE_SIZE method is insensitive to the current # state of the video system, another check must be done using # "proxy textures". gl.glTexImage2D(gl.GL_PROXY_TEXTURE_2D, # target 0, # mipmap_level self.gl_internal_format, # video RAM internal format p.num_s_samples, # width p.num_t_samples, # height 0, # border self.format, # format of image data self.gl_type, # type of image data texel_data) # texel data if (gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_2D, # Need PyOpenGL >= 2.0 0, gl.GL_TEXTURE_WIDTH) == 0) or ( gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_2D, 0, gl.GL_TEXTURE_HEIGHT) == 0): raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_s_samples or num_t_samples is too large for your video system!") gl.glTexImage2D(gl.GL_TEXTURE_2D, # target 0, # mipmap_level self.gl_internal_format, # video RAM internal format p.num_s_samples, # width p.num_t_samples, # height 0, # border self.format, # format of image data self.gl_type, # type of image data texel_data) # texel data # Set some texture object defaults gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_WRAP_S,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_WRAP_T,gl.GL_CLAMP_TO_EDGE) gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR) gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_MIN_FILTER,gl.GL_LINEAR) self._cached_window_shape = p.window_shape self._cached_shape_radius_parameter = p.window_shape_radius_parameter self._cached_shape_parameter2 = p.window_shape_parameter2 self._cached_num_s_samples = p.num_s_samples self._cached_num_t_samples = p.num_t_samples def __rebuild_display_lists(self): gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() p = self.parameters if p.window_shape == 'lat-long rectangle': self._texture_s_is_azimuth = True else: self._texture_s_is_azimuth = False gl.glNewList(self.windowed_display_list_id,gl.GL_COMPILE) gl.glBegin(gl.GL_QUADS) for stack in range(p.stacks): stack_upper_frac = float(stack+1)/p.stacks stack_lower_frac = float(stack)/p.stacks theta_upper = stack_upper_frac * math.pi theta_lower = stack_lower_frac * math.pi y_upper = p.radius * math.cos( theta_upper ) w_upper = p.radius * math.sin( theta_upper ) y_lower = p.radius * math.cos( theta_lower ) w_lower = p.radius * math.sin( theta_lower ) for slice in range(p.slices/2,p.slices): # only do half of sphere (other half has no window) slice_start_frac = float(slice)/p.slices slice_stop_frac = float(slice+1)/p.slices phi_start = slice_start_frac * 2 * math.pi phi_stop = slice_stop_frac * 2 * math.pi x_start_upper = w_upper * math.cos(phi_start) x_start_lower = w_lower * math.cos(phi_start) x_stop_upper = w_upper * math.cos(phi_stop) x_stop_lower = w_lower * math.cos(phi_stop) z_start_upper = w_upper * math.sin(phi_start) z_start_lower = w_lower * math.sin(phi_start) z_stop_upper = w_upper * math.sin(phi_stop) z_stop_lower = w_lower * math.sin(phi_stop) o = 0.5 g = 0.5 / p.radius if self._texture_s_is_azimuth: tex_s_start = slice_start_frac*2-1 tex_s_stop = slice_stop_frac*2-1 else: tex_s_start = x_start_upper*g+o tex_s_stop = x_stop_upper*g+o gl.glTexCoord2f(tex_s_start,y_upper*g+o) gl.glVertex3f(x_start_upper, y_upper, z_start_upper) gl.glTexCoord2f(tex_s_stop,y_upper*g+o) gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper) gl.glTexCoord2f(tex_s_stop,y_lower*g+o) gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower) gl.glTexCoord2f(tex_s_start,y_lower*g+o) gl.glVertex3f(x_start_lower, y_lower, z_start_lower) gl.glEnd() gl.glEndList() gl.glNewList(self.opaque_display_list_id,gl.GL_COMPILE) gl.glBegin(gl.GL_QUADS) for stack in range(p.stacks): stack_upper_frac = float(stack+1)/p.stacks stack_lower_frac = float(stack)/p.stacks theta_upper = stack_upper_frac * math.pi theta_lower = stack_lower_frac * math.pi y_upper = p.radius * math.cos( theta_upper ) w_upper = p.radius * math.sin( theta_upper ) y_lower = p.radius * math.cos( theta_lower ) w_lower = p.radius * math.sin( theta_lower ) for slice in range(p.slices/2): # half of sphere with no window slice_start_frac = float(slice)/p.slices slice_stop_frac = float(slice+1)/p.slices phi_start = slice_start_frac * 2 * math.pi phi_stop = slice_stop_frac * 2 * math.pi x_start_upper = w_upper * math.cos(phi_start) x_start_lower = w_lower * math.cos(phi_start) x_stop_upper = w_upper * math.cos(phi_stop) x_stop_lower = w_lower * math.cos(phi_stop) z_start_upper = w_upper * math.sin(phi_start) z_start_lower = w_lower * math.sin(phi_start) z_stop_upper = w_upper * math.sin(phi_stop) z_stop_lower = w_lower * math.sin(phi_stop) gl.glVertex3f(x_start_upper, y_upper, z_start_upper) gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper) gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower) gl.glVertex3f(x_start_lower, y_lower, z_start_lower) gl.glEnd() gl.glEndList() self._cached_radius = p.radius self._cached_slices = p.slices self._cached_stacks = p.stacks gl.glPopMatrix() def draw(self): """Redraw the scene on every frame. """ p = self.parameters if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks: self.__rebuild_display_lists() if self._cached_window_shape != p.window_shape or self._cached_shape_radius_parameter != p.window_shape_radius_parameter: self.__rebuild_texture_object() if p.window_shape == 'lat-long rectangle' and self._cached_shape_parameter2 != p.window_shape_parameter2: self.__rebuild_texture_object() if self._cached_num_s_samples != p.num_s_samples or self._cached_num_t_samples != p.num_t_samples: self.__rebuild_texture_object() if p.on: #gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_LINE ) if p.bit_depth != self.cached_bit_depth: self.calculate_bit_depth_dependencies() self.gl_internal_format = gl.GL_ALPHA # change from luminance to alpha self.format = gl.GL_ALPHA # Set OpenGL state variables gl.glEnable( gl.GL_DEPTH_TEST ) gl.glEnable( gl.GL_TEXTURE_2D ) gl.glEnable( gl.GL_BLEND ) gl.glBlendFunc( gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_SRC_ALPHA ) # alpha 1.0 = transparent gl.glBindTexture(gl.GL_TEXTURE_2D,self.texture_object_id) gl.glColor4f( *p.opaque_color ) gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_REPLACE) # clear modelview matrix gl.glMatrixMode(gl.GL_MODELVIEW) gl.glPushMatrix() # do the window position gl.glRotatef(p.window_center_azimuth,0.0,-1.0,0.0) gl.glRotatef(p.window_center_elevation,1.0,0.0,0.0) gl.glCallList(self.windowed_display_list_id) gl.glCallList(self.opaque_display_list_id) gl.glPopMatrix()
#!/usr/bin/env python """Sinusoidal grating calculated in realtime.""" ############################ # Import various modules # ############################ import VisionEgg VisionEgg.start_default_logging(); VisionEgg.watch_exceptions() from VisionEgg.Core import * from VisionEgg.FlowControl import Presentation from VisionEgg.SphereMap_color import * from math import * VisionEgg.config.VISIONEGG_GUI_INIT = 0 #(to get the gratings directly) ##################################### # Initialize OpenGL window/screen # ##################################### screen = get_default_screen() ###################################### # Create sinusoidal grating object # ###################################### fov_x = 90.0 projection_3d = SimplePerspectiveProjection(fov_x=fov_x) stimulus = SphereGrating(num_samples=2048, radius = 1.0, slices = 50, stacks = 50, grating_center_elevation = 90, t0_time_sec_absolute=0, cycle_length = 3.0, max_tf = 0.8) ############################################################### # Create viewport - intermediary between stimuli and screen # ############################################################### viewport = Viewport( screen=screen, projection=projection_3d, stimuli=[stimulus] ) ######################################## # Create presentation object and go! # ######################################## def square_func(t): max_temporal_freq_hz=stimulus.parameters.max_tf if max_temporal_freq_hz*sin(pi*t/stimulus.parameters.cycle_length)>=0: return max_temporal_freq_hz else: return -max_temporal_freq_hz p = Presentation(go_duration=(9.0,'seconds'),viewports=[viewport]) p.add_controller(stimulus,'temporal_freq_hz',FunctionController(during_go_func=square_func)) p.go()
************************************************ Kaspar Müller University of Zurich Institute of Zoology, Neurobiology Winterthurerstrasse 190 CH - 8057 Zurich, Switzerland Y-13-K-76 +41 (0)44 635 48 33 +41 (0)78 722 15 02 On 24.11.2009, at 01:51, Andrew Straw wrote:
|