[visionegg] numpy and VisionEgg.Textures
- From: Martin Spacek <visionegg@xxxxxxxxxxxxx>
- To: visionegg@xxxxxxxxxxxxx
- Date: Tue, 17 Apr 2007 14:59:11 -0700
Hello,
I'm in the midst of overhauling our stimulus system (based on
VisionEgg). I've dumped Numeric and numarray and I'm exclusively using
numpy now. Andrew, you added support for numarray to Textures.py a
couple of years ago. I've made a couple of changes that now add numpy
support as well. It's not thoroughly tested, but so far it seems to
work. Here's a patch against the latest svn (1385). It mostly involved
substituting Numeric/numarray's typecode() call with numpy's dtype attrib.
Out of curiosity, are there any plans to move VisionEgg wholesale away
from Numeric/numarray to numpy?
Cheers,
Martin
Index: VisionEgg/Textures.py
===================================================================
--- VisionEgg/Textures.py (revision 1385)
+++ VisionEgg/Textures.py (working copy)
@@ -63,13 +63,18 @@
else:
shrink_filter = Image.BICUBIC # Fallback filtering
-# Allow use of numarray Texture data without requiring numarray
+# Allow use of numarray and numpy Texture data without requiring either
array_types = [Numeric.ArrayType]
try:
import numarray
array_types.append( numarray.numarraycore.NumArray )
except ImportError:
pass
+try:
+ import numpy
+ array_types.append( numpy.ndarray )
+except ImportError:
+ pass
####################################################################
#
@@ -188,7 +193,7 @@
self.texels = texels
self.texture_object = None
-
+
if size is not None and size != self.size:
raise ValueError("size was specified, but data could not be
rescaled")
@@ -207,7 +212,7 @@
def make_half_size(self):
if self.texture_object is not None:
raise RuntimeError("make_half_size() only available BEFORE texture
loaded to OpenGL.")
-
+
if isinstance(self.texels,Image.Image):
w = self.size[0]/2
h = self.size[1]/2
@@ -224,7 +229,7 @@
are no other references to the TextureObject instance. To
ensure this, all references to the texture_object argument
passed to the load() method should be deleted."""
-
+
self.texture_object = None
def get_texels_as_image(self):
@@ -232,12 +237,20 @@
if type(self.texels) in array_types:
if len(self.texels.shape) == 2:
a = self.texels
- if a.typecode() == Numeric.UInt8:
- mode = "L"
- elif a.typecode() == Numeric.Float32:
- mode = "F"
- else:
- raise ValueError("unsupported image mode")
+ try: # Numeric/numarray array?
+ if a.typecode() == Numeric.UInt8:
+ mode = "L"
+ elif a.typecode() == Numeric.Float32:
+ mode = "F"
+ else:
+ raise ValueError("unsupported image mode")
+ except AttributeError: # no typecode attrib, numpy array ?
+ if a.dtype == numpy.uint8:
+ mode = "L"
+ elif a.dtype == numpy.float32:
+ mode = "F"
+ else:
+ raise ValueError("unsupported image mode")
return Image.fromstring(mode, (a.shape[1], a.shape[0]),
a.tostring())
else:
raise NotImplementedError("Currently only luminance data can
be converted to images")
@@ -295,14 +308,25 @@
if width != width_pow2 or height != height_pow2:
if type(self.texels) in array_types:
- if len(self.texels.shape) == 2:
- buffer = Numeric.zeros( (height_pow2,width_pow2),
self.texels.typecode() )
- buffer[0:height,0:width] = self.texels
- elif len(self.texels.shape) == 3:
- buffer = Numeric.zeros(
(height_pow2,width_pow2,self.texels.shape[2]), self.texels.typecode() )
- buffer[0:height,0:width,:] = self.texels
- else:
- raise RuntimeError("Unexpected shape for self.texels")
+ try: # Numeric/numarray array?
+ if len(self.texels.shape) == 2:
+ buffer = Numeric.zeros( (height_pow2,width_pow2),
self.texels.typecode() )
+ buffer[0:height,0:width] = self.texels
+ elif len(self.texels.shape) == 3:
+ buffer = Numeric.zeros(
(height_pow2,width_pow2,self.texels.shape[2]), self.texels.typecode() )
+ buffer[0:height,0:width,:] = self.texels
+ else:
+ raise RuntimeError("Unexpected shape for self.texels")
+ except AttributeError: # no typecode attrib, numpy array?
+ if len(self.texels.shape) == 2:
+ buffer = numpy.zeros( (height_pow2,width_pow2),
dtype=self.texels.dtype )
+ buffer[0:height,0:width] = self.texels
+ elif len(self.texels.shape) == 3:
+ buffer = numpy.zeros(
(height_pow2,width_pow2,self.texels.shape[2]), dtype=self.texels.dtype )
+ buffer[0:height,0:width,:] = self.texels
+ else:
+ raise RuntimeError("Unexpected shape for self.texels")
+
elif isinstance(self.texels, Image.Image): # PIL Image
if rescale_original_to_fill_texture_object:
# reset coverage values
@@ -315,7 +339,7 @@
self._buf_r = width_pow2
self._buf_t = 0
self._buf_b = height_pow2
-
+
buffer =
self.texels.resize((width_pow2,height_pow2),shrink_filter)
self.size = (width_pow2, height_pow2)
@@ -372,7 +396,7 @@
mipmap_level += 1
biggest_dim = max(this_width,this_height)
-
+
# Keep reference to texture_object
self.texture_object = texture_object
@@ -405,11 +429,11 @@
'gl_id',
'__gl_module__',
)
-
+
_cube_map_side_names = ['positive_x', 'negative_x',
'positive_y', 'negative_y',
'positive_z', 'negative_z']
-
+
def __init__(self,
dimensions = 2):
if dimensions not in [1,2,3,'cube']:
@@ -491,7 +515,7 @@
cube_side = None,
image_data = None, # DEPRECATED name (use texel_data)
):
-
+
"""Put Numeric array or PIL Image into OpenGL as texture data.
The texel_data parameter contains the texture data. If it is
@@ -513,7 +537,7 @@
The internal_format parameter specifies the format in which
the image data is stored on the video card. See the OpenGL
specification for all possible values.
-
+
If the data_format parameter is None (the default), an attempt
is made to guess data_format according to the following
description. For Numeric arrays: If texel_data.shape is equal
@@ -536,7 +560,7 @@
supported. For PIL images: texel_data is used as unsigned
bytes. This is the usual format for common computer graphics
files."""
-
+
if image_data is not None: # check for deprecated parameter name
if not hasattr(TextureObject,"_gave_put_new_image_data_warning"):
logger = logging.getLogger('VisionEgg.Textures')
@@ -600,8 +624,12 @@
if data_type is None: # guess the data type
data_type = gl.GL_UNSIGNED_BYTE
if type(texel_data) in array_types:
- if texel_data.typecode() == Numeric.Float:
- texel_data = texel_data*255.0
+ try: # Numeric/numarray array?
+ if texel_data.typecode() == Numeric.Float:
+ texel_data = texel_data*255.0
+ except AttributeError: # no typecode attrib, numpy array?
+ if texel_data.dtype == numpy.float:
+ texel_data = texel_data*255.0
if data_type == gl.GL_UNSIGNED_BYTE:
if type(texel_data) in array_types:
@@ -672,7 +700,7 @@
raw_data)
if gl.glGetTexLevelParameteriv(target, # Need PyOpenGL >= 2.0
mipmap_level,
- gl.GL_TEXTURE_WIDTH) == 0:
+ gl.GL_TEXTURE_WIDTH) == 0:
raise TextureTooLargeError("texel_data is too wide for
your video system.")
if
gl.glGetTexLevelParameteriv(target,mipmap_level,gl.GL_TEXTURE_HEIGHT) == 0:
raise TextureTooLargeError("texel_data is too tall for
your video system.")
@@ -693,7 +721,7 @@
raise TextureTooLargeError("texel_data is too deep for
your video system.")
else:
raise RuntimeError("Unknown number of dimensions.")
-
+
# No OpenGL error, put the texture in!
if self.dimensions == 1:
gl.glTexImage1Dub(gl.GL_TEXTURE_1D,
@@ -735,12 +763,12 @@
data_format = None, # automatic guess
unless set explicitly
data_type = None, # automatic guess unless
set explicitly
):
-
+
"""Similar to put_new_image(), but builds mipmaps."""
-
+
if self.dimensions != 2:
raise ValueError("can only handle 2D texel data for automatic
mipmap building")
-
+
if type(texel_data) in array_types:
assert(cube_side == None)
data_dimensions = len(texel_data.shape)
@@ -789,9 +817,14 @@
if data_type is None: # guess the data type
data_type = gl.GL_UNSIGNED_BYTE
if type(texel_data) in array_types:
- if texel_data.typecode() == Numeric.Float:
- texel_data = texel_data*255.0
+ try: # Numeric/numarray array?
+ if texel_data.typecode() == Numeric.Float:
+ texel_data = texel_data*255.0
+ except AttributeError: # no typecode attrib, numpy array?
+ if texel_data.dtype == numpy.float:
+ texel_data = texel_data*255.0
+
if data_type == gl.GL_UNSIGNED_BYTE:
if type(texel_data) in array_types:
texel_data = texel_data.astype(Numeric.UInt8) # (re)cast if
necessary
@@ -848,7 +881,7 @@
For an explanation of most parameters, see the
put_new_image() method."""
-
+
if image_data is not None: # check for deprecated parameter name
if not hasattr(TextureObject,"_gave_put_sub_image_data_warning"):
logger = logging.getLogger('VisionEgg.Textures')
@@ -860,7 +893,7 @@
raise ValueError("Cannot set both texel_data and image_data")
else:
texel_data = image_data
-
+
if type(texel_data) in array_types:
if self.dimensions != 'cube':
assert(cube_side == None)
@@ -880,7 +913,7 @@
# Determine the data_format, data_type and rescale the data if needed
data = texel_data
-
+
if data_format is None: # guess the format of the data
if type(data) in array_types:
if len(data.shape) == self.dimensions:
@@ -914,8 +947,12 @@
if data_type is None: # guess the data type
data_type = gl.GL_UNSIGNED_BYTE
if type(data) in array_types:
- if data.typecode() == Numeric.Float:
- data = data*255.0
+ try: # Numeric/numarray array?
+ if data.typecode() == Numeric.Float:
+ data = data*255.0
+ except AttributeError: # no typecode attrib, numpy array?
+ if data.dtype == numpy.float:
+ data = data*255.0
if data_type == gl.GL_UNSIGNED_BYTE:
if type(data) in array_types:
@@ -974,7 +1011,7 @@
raise RuntimeError("Cannot put_sub_image on 3D texture_object.")
else:
raise RuntimeError("Unknown number of dimensions.")
-
+
def put_new_framebuffer(self,
buffer='back',
mipmap_level = 0,
@@ -995,7 +1032,7 @@
For an explanation of most parameters, see the
put_new_image() method."""
-
+
if self.dimensions != 2:
raise RuntimeError("put_new_framebuffer only supported for 2D
textures.")
@@ -1004,8 +1041,8 @@
elif buffer == 'back':
gl.glReadBuffer( gl.GL_BACK )
else:
- raise ValueError('No support for "%s" framebuffer'%buffer)
-
+ raise ValueError('No support for "%s" framebuffer'%buffer)
+
# make myself the active texture
gl.glBindTexture(self.target, self.gl_id)
@@ -1086,7 +1123,7 @@
"OpenGL texture wrap enum",
VisionEgg.ParameterDefinition.OPENGL_ENUM),
}
-
+
constant_parameters_and_defaults = {
'internal_format':(gl.GL_RGB,#None,
ve_types.Integer,
@@ -1099,7 +1136,7 @@
ve_types.Boolean,
"Allow automatic shrinking of texture if too
big?"),
}
-
+
__slots__ = (
'texture_object',
'_using_texture',
@@ -1121,7 +1158,7 @@
self.parameters.texture_min_filter = gl.GL_LINEAR_MIPMAP_LINEAR
else:
self.parameters.texture_min_filter = gl.GL_LINEAR
-
+
if not self.constant_parameters.mipmaps_enabled:
if self.parameters.texture_min_filter in
TextureStimulusBaseClass._mipmap_modes:
raise ValueError("texture_min_filter cannot be a mipmap type
if mipmaps not enabled.")
@@ -1209,10 +1246,10 @@
raise RuntimeError("Mask must have width num_samples power of 2")
if height != next_power_of_2(height):
raise RuntimeError("Mask must have height num_samples power of 2")
-
+
gl.glActiveTextureARB(gl.GL_TEXTURE1_ARB) # Need PyOpenGL >= 2.0
self.texture_object = TextureObject(dimensions=2)
-
+
if cp.function == "gaussian":
xx = Numeric.outerproduct(Numeric.ones((1,cp.num_samples[1])),
Numeric.arange(0,cp.num_samples[0],1.0)-cp.num_samples[0]/2)
@@ -1251,40 +1288,40 @@
# reset active texture unit to 0
gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB)
-
+
def draw_masked_quad_3d(self,lt,rt,bt,tt,v1,v2,v3,v4):
# The *t parameters are the texture coordinates.
-
+
# By the time this method is called, GL_TEXTURE0_ARB should be
# loaded as the texture object to be masked.
gl.glActiveTextureARB(gl.GL_TEXTURE1_ARB) # bind 2nd texture unit to
mask texture
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_object.gl_id)
gl.glEnable(gl.GL_TEXTURE_2D)
-
+
# The normal TEXTURE2D object is the 1st (TEXTURE0) texture unit
gl.glBegin(gl.GL_QUADS)
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,lt,bt)
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,0.0,0.0)
gl.glVertex3f(*v1)
-
+
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,rt,bt)
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,1.0,0.0)
gl.glVertex3f(*v2)
-
+
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,rt,tt)
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,1.0,1.0)
gl.glVertex3f(*v3)
-
+
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE0_ARB,lt,tt)
gl.glMultiTexCoord2fARB(gl.GL_TEXTURE1_ARB,0.0,1.0)
gl.glVertex3f(*v4)
-
+
gl.glEnd() # GL_QUADS
gl.glDisable(gl.GL_TEXTURE_2D) # turn off texturing in this texture
unit
gl.glActiveTextureARB(gl.GL_TEXTURE0_ARB) # return to 1st texture unit
-
+
def draw_masked_quad(self,lt,rt,bt,tt,le,re,be,te,depth):
# The *t parameters are the texture coordinates. The *e
# parameters are the eye coordinates for the vertices of the
@@ -1294,7 +1331,7 @@
v3 = (re,te,depth)
v4 = (le,te,depth)
self.draw_masked_quad_3d(lt,rt,bt,tt,v1,v2,v3,v4)
-
+
class TextureStimulus(TextureStimulusBaseClass):
"""A textured rectangle.
@@ -1347,7 +1384,7 @@
shrink_texture_ok -- Allow automatic shrinking of texture if too big?
(Boolean)
Default: False
"""
-
+
parameters_and_defaults = {
'on':(True,
ve_types.Boolean,
@@ -1384,7 +1421,7 @@
ve_types.Boolean,
"perform depth test?"),
}
-
+
def draw(self):
p = self.parameters
if p.texture != self._using_texture: # self._using_texture is from
TextureStimulusBaseClass
@@ -1406,13 +1443,13 @@
if p.size is None:
# Note: 'size' attribute is not supposed to be part of the API,
# so this is naughty.
- size = tex.size
+ size = tex.size
else:
size = p.size
-
+
# calculate lowerleft corner
lowerleft = VisionEgg._get_lowerleft(p.position,p.anchor,size)
-
+
# Clear the modelview matrix
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPushMatrix()
@@ -1425,7 +1462,7 @@
# allow max_alpha value to control blending
gl.glEnable( gl.GL_BLEND )
- gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA )
+ gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA )
if not self.constant_parameters.mipmaps_enabled:
if p.texture_min_filter in
TextureStimulusBaseClass._mipmap_modes:
@@ -1515,7 +1552,7 @@
"""
parameters_and_defaults = {'on':(True,
- ve_types.Boolean),
+ ve_types.Boolean),
'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)),
@@ -1536,7 +1573,7 @@
ve_types.Boolean,
"perform depth test?"),
}
-
+
def draw(self):
p = self.parameters
if p.texture != self._using_texture: # self._using_texture is from
TextureStimulusBaseClass
@@ -1560,7 +1597,7 @@
# allow max_alpha value to control blending
gl.glEnable( gl.GL_BLEND )
- gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA )
+ 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_MODULATE)
tex = self.parameters.texture
@@ -1579,7 +1616,7 @@
gl.glTexCoord2f(tex.buf_lf,tex.buf_tf)
gl.glVertex(*p.upperleft)
gl.glEnd() # GL_QUADS
-
+
####################################################################
#
# Stimulus - Spinning Drum
@@ -1677,7 +1714,7 @@
'drum_center_azimuth':(0.0,
ve_types.Real,
'changes orientation of drum in space',
- ),
+ ),
'drum_center_elevation':(0.0,
ve_types.Real,
'changes orientation of drum in space'),
@@ -1685,14 +1722,14 @@
ve_types.Real,
'0=right, 90=up'),
}
-
+
__slots__ = (
'cached_display_list_normal',
'cached_display_list_mirror',
'cached_display_list_num_sides',
'texture_stimulus',
)
-
+
def __init__(self,**kw):
TextureStimulusBaseClass.__init__(self,**kw)
self.cached_display_list_normal = gl.glGenLists(1) # Allocate a new
display list
@@ -1726,12 +1763,12 @@
# 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.
+ # 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()
@@ -1854,7 +1891,7 @@
else:
gl.glCallList(self.cached_display_list_mirror)
finally:
- gl.glMatrixMode(gl.GL_MODELVIEW)
+ gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glPopMatrix()
def rebuild_display_list(self):
@@ -1874,7 +1911,7 @@
num_sides = self.parameters.num_sides
self.cached_display_list_num_sides = num_sides
-
+
deltaTheta = 2.0*math.pi / num_sides
for direction in ['normal','mirror']:
if direction == 'normal':
@@ -1899,16 +1936,16 @@
z1 = r*math.sin(theta1)
x2 = r*math.cos(theta2)
z2 = r*math.sin(theta2)
-
+
#Bottom left of quad
gl.glTexCoord2f(frac1, tex.buf_bf)
gl.glVertex4f( x1, -h, z1, 1.0 )
-
+
#Bottom right of quad
gl.glTexCoord2f(frac2, tex.buf_bf)
gl.glVertex4f( x2, -h, z2, 1.0 )
#Top right of quad
- gl.glTexCoord2f(frac2, tex.buf_tf);
+ gl.glTexCoord2f(frac2, tex.buf_tf);
gl.glVertex4f( x2, h, z2, 1.0 )
#Top left of quad
gl.glTexCoord2f(frac1, tex.buf_tf)
@@ -1933,7 +1970,7 @@
texture_size -- (Sequence2 of Real)
Default: (64, 64)
"""
-
+
parameters_and_defaults = {
'on':(True,
ve_types.Boolean),
@@ -1950,7 +1987,7 @@
__slots__ = (
'texture_stimulus',
)
-
+
def __init__(self,**kw):
VisionEgg.Core.Stimulus.__init__(self,**kw)
s = self.constant_parameters.texture_size
@@ -1978,6 +2015,6 @@
contained.size = my.size
contained.on = my.on
self.texture_stimulus.draw()
-
+
class TextureTooLargeError( RuntimeError ):
pass
- Follow-Ups:
- [visionegg] Re: numpy and VisionEgg.Textures
- From: Andrew Straw
Other related posts:
- » [visionegg] numpy and VisionEgg.Textures
- » [visionegg] Re: numpy and VisionEgg.Textures
- [visionegg] Re: numpy and VisionEgg.Textures
- From: Andrew Straw