[visionegg] Re: Grating with sinusoidal changing temporal frequency

  • From: Kaspar Müller <kaspar.mueller@xxxxxxxxxxx>
  • To: visionegg@xxxxxxxxxxxxx
  • Date: Tue, 1 Dec 2009 15:39:02 +0100

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:

Kaspar Müller wrote:
Hi all,
I tried to build a grating pattern which changes temporal frequency sinusoidal (i.e. the grating should wave back and forth, maximal velocity should stay constant).
I tried this by using:
def sin_func(t):
   max_temporal_freq_hz=1
   return max_temporal_freq_hz*sin(2.0*pi*t/3)
and
p.add_controller(stimulus,'temporal_freq_hz',FunctionController(during_go_func=sin_func)) (see attached file)
The grating now changes direction as it should, but continously increases the maximal velocity. Although, when I use manual flow control and print stimulus.parameters.temporal_freq_hz in every iteration of the loop, I see that temporal_freq_hz is correctly modulated between -1 and +1.
I could get it to work by using ignore_time=True and using the sin-function to modulate phase_at_t0, but I do not understand why modulating temporal_freq_hz does not work.
Any ideas?

Hi Kaspar,

The VE algorithm for calculating instantaneous phase of the grating "winds up" the effect of the current TF across time since t0. This is good for calculating the instantaneous phase of a grating without needing to calculate a dt and dphase from the previous frame, but this method is bad when you want to dynamically change TF because of the effect you observed. To avoid that, the VE would have to store the time and phase of the grating when rendered on each phase. This isn't what's implemented, but I think it would be a useful option. I'd be interested to consider an option for the VE if you implement it.

Anyhow, it seems like your current solution is OK for now. Is that true?

Thanks for the report,
Andrew

--
Andrew D. Straw, Ph.D.
California Institute of Technology
http://www.its.caltech.edu/~astraw/
======================================
The Vision Egg mailing list
Archives: //www.freelists.org/archives/visionegg
Website: http://www.visionegg.org/mailinglist.html

Other related posts: