Using Numpy with OpenGLContext

In this document you will learn to

Numeric Python is used throughout the OpenGLContext project, and you will likely want to make use of the package in your own code.  This document focuses primarily on Numeric Python itself, rather than on PyOpenGL.  Mastering the features shown here will be useful when doing non-trivial programming in PyOpenGL, but it's not necessary to know this material before you start playing with OpenGLContext.

Numeric Python makes it easy to work with (potentially large) homogenous arrays of a given data type.  It provides extended slicing semantics for dealing with multidimensional arrays, utility functions for processing all elements of an array, and mechanisms for storing and converting to/from arrays and native Python data structures.

Creating Numeric Python Arrays

Before it can be used, Numeric Python needs to be imported into your module.  Depending on how many of the functions in the module you'll be wanting to use, you may use either from Numeric import * or import Numeric to gain access.

Creating a Numeric array is done with one of a set of functions, each of which has a particular purpose (note, these are just the commonly used functions):

zeros, ones Generate an array filled with either 0 or 1 values of a given size and data type.
zeros( (2,3), 'd')
ones( (2,3), 'f')
array, asarray Convert a sequence (often a nested sequence) into an equivalently structured array of a given data type (or of an automatically determined type if none is specified).

asarray skips copying if the passed argument is already an array of the appropriate type.

array( [[2,3,4],[5,6,7]],'i')
asarray( might_be_array, 'f')
identity Generate an x by x integer identity matrix for the given x.
identity ( 4 )
arange An advanced form of the standard Python range, allowing for floating point ranges as well as integer ranges, generating an array as the result.
arange( 3.0, 0.0, -.1, 'd')

The Numeric type codes mentioned in the above table include a considerable number of exotic data types.  For OpenGL work, here are the significant type codes and their equivalent OpenGL types:

double Python float d GL_DOUBLE
float No native Python equivalent f GL_FLOAT
32-bit integer Python integer i GL_INT
16-bit integer No native Python equivalent s GL_SHORT
unsigned byte Python string b (c) GL_UNSIGNED_BYTE
signed byte No native Python equivalent 1 GL_BYTE

Notably absent from the list above are the unsigned integer and unsigned short types.  As of yet, there is no satisfactory representation of these types in the Numeric module.

Array dimensions are specified as tuples, in the order of indexing.  The current shape of an array can be retrieved using the shape( some_array ) function.  You can use the reshape,resize,transpose, and ravel functions to get a copy of an array with different dimensions. Note: most Numeric functions allow for in-place copying through an optional extra argument specifying the destination for the operation.  See the Numeric Python manual for details. 

Basic Mathematics

Mathematical operators tend to work element-wise for arrays, and allow for a fairly wide variety of second arguments.

For instance, to multiply an entire array by 3.0, you would write result = myarray * 3.0 with similar approaches for addition, subtraction, and division.

If, instead, you wanted to multiply each element in one array by the corresponding element in another array, you would write result = firstarray * secondarray again, with the same approach working for most basic math operations.

Slicing

Numeric Python uses an extended slicing notation, which allows for flexible and powerful manipulation of multidimensional arrays (though at the price of making the slice specifications somewhat less intuitive looking).

The syntax for the extended slicing notation goes like this:

[ start : stop : step, start : stop : step, ... ]

with one start, stop, step set allowed (but not required beyond the first) for each dimension of the array. The step argument and the preceding colon can be left off unless actually needed.  As with standard slicing notation, if you leave off a start value, zero is assumed, and if you leave off a stop value, the end of the array is assumed.

Generally it is easiest to understand the slicing notation through examples. Consider this interactive session:

>>> m = array( [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], 'd')
>>> m
array([[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[ 13., 14., 15., 16.]])
# item 0 of the array in first dimension
>>> m[0]
array([ 1., 2., 3., 4.])
# item 1 of the array in first dimension
>>> m[1]
array([ 5., 6., 7., 8.])
# item 0 of the array in second dimension
>>> m[:,0]
array([ 1., 5., 9., 13.])
# item 1 of the array in second dimension
>>> m[:,1]
array([ 2., 6., 10., 14.])
# all items in first dimension taking every 
# second item
>>> m[::2]
array([[ 1., 2., 3., 4.],
[ 9., 10., 11., 12.]])
# as previous, but now take item 1 in the 
# second dimension for each row
>>> m[::2,1]
array([ 2., 10.])
# as previous, but starting the slice of first dimension 
# at the second item (i.e. take 1 and 3 instead of 0 and 2)
>>> m[1::2,1]
array([ 6., 14.])
>>>

Common slices you'll see include:

# x-coordinate of an i*3 array
xes = points[:,0]
yes = points[:,1]
# first points of i*3 set of points describing triangles
firstPoints = trianglePoints[::3]
secondPoints = trianglePoints[1::3]
# r-values of an x*y*3 r,g,b image
reds = image[:,:,0]
greens = image[:,:,1]

Multiplication

There are three different types of multiplication commonly used in OpenGLContext. Element-wise multiplication was covered in Basic Mathematics above.  The other two common multiplication types are dot product and cross product.  Numeric Python provides a dot product function:

result = dot( first, second )

Unfortunately, it doesn't seem to provide any cross product function, so the OpenGLContext utility module provides its own cross product function (currently implemented in Python, potentially implemented in C in the future):

from OpenGLContext.utilities import crossprod
result = crossprod( (ux, uy, uz, uw), (vx, vy, vz, vw) )

When working with arrays of vectors, the triangleutilities function crossProduct should be used, as it will process the entire array of vectors using Numeric Python mechanisms (instead of calling a Python function for every vector).

Array Processing Functions

The basic manipulations above are primarily useful for selecting a particular subset of an array for processing, while the array processing functions do the bulk of the calculations in most algorithms.  Because of the number of functions, you should use the Numeric Python Handbook for descriptions of the functions.

Universal (Math) Functions

These function-like objects provide for extremely flexible application of common mathematical operations on arguments of varying type.

absolute, add, arccos, arcsin, arctan, arctan2, bitwise_and, bitwise_or, bitwise_xor, ceil, conjugate, cos, cosh, divide, divide_safe, equal, exp, fabs, floor, fmod, greater, greater_equal, hypot, invert, left_shift, less, less_equal, log, log10, logical_and, logical_not, logical_or, logical_xor, maximum, minimum, multiply, negative, not_equal, power, remainder, right_shift, sin, sinh, sqrt, subtract, tan, tanh

Index(ed)-Array Functions

These functions deal with index arrays or indexed arrays, that is, arrays which refer to other arrays, either by having equivalent structure, or including actual indices into the other array. These functions can provide efficient operation by not requiring you to copy the arrays being processed.  For an example, see OpenGLContext.scenegraph.polygonsort's use of argsort to generate the indices required for rendering sorted transparent geometry without needing to copying/rearranging the point array.

argmax, argmin, argsort, choose, take, where, put, putmask, compress

Binary Operator Functions

These functions deal with two "data arrays", combining their values in some way to create a new array.  The most commonly used function of this group is dot.

convolve, cross_correlate, dot, outerproduct, innerproduct, sum

Array Value

These functions produce single value outputs from an entire array, checking, for instance, that the entire array is true, or calculating the sum of the entire array.

alltrue, cumproduct, cumsum, product, sometrue, trace

Altered Array Values

These functions produce a copy of an array with each value modified by the given function, or the array's ordering/contents modified as dictated by the function's rules of operation.

around, clip, sign, sort, compress

The OpenGLContext Utility Modules

There are a number of functions not provided by Numeric Python which are common and useful enough that they are included in the OpenGLContext utility module.  The cross product function was mentioned above, also available are:
rotMatrix( (x,y,z,a) )
Given rotation as x,y,z,a (a in radians), return rotation matrix
normalise( vector)
Given a 3 or 4-item vector, return a 3-item unit vector
crossprod( (ux, uy, uz, uw), (vx, vy, vz, vw) )
Given 2 4-item vectors, return the cross product as a 4-item vector
mag( vector )
Given a 3 or 4-item vector, return the vector's magnitude

The vectorutilities module provides similar functions for operating on entire arrays of vectors (they can also work with individual vectors, but are not generally convenient for such operation).

Note, the current set of utility modules is not ideal, it is likely that the "utility" module will largely go away with the explicit utility modules (vector, triangle) replacing its functionality.

Array-based Geometry

OpenGL 1.1 introduced a number of geometry-specification mechanisms based on specifying pointers to arrays of data values.  The OpenGL implementation was passed the entire array of data, then would loop through the array (normally at hardware speed) dispatching a command for each of those values in the array which were indicated.  The user-side code need only make a single call to have potentially thousands of data values passed rendering pipeline.

With C code, the difference between the array-based code and individual calls to transfer data values was noticeable, but it is still quite practical to use the individual calls in many instances.  With Python code, however, it is generally impractical to use individual data transfer calls, as the overhead for the API call is enormous compared to the actual work being done with such low level functions.

Approach

The basic approach of the array-based geometry is to specify a pointer for a number of different array types: vertex, color, normal and textureCoordinate (potentially multiple coordinates if using multi-texturing).

glVertexPointerd( self.verticies )
glColorPointerd ( self.colours )
glNormalPointerd ( self.normals )
glTexCoordPointerd( self.textures )

This tells the OpenGL engine that these are the active arrays for each of these types.  For all of the arrays save texture coordinates, only one active array is possible.  Where multi-texturing is enabled (multi-texturing is an OpenGL extension not available on all platforms), you can specify one active texture coordinate array for each texture engine.

Once you have specified the active arrays, you enable or disable each array type you wish to use for a given call.

glEnable( GL_VERTEX_ARRAY )
glEnable( GL_COLOR_ARRAY )
glEnable( GL_NORMAL_ARRAY )
glEnable( GL_TEXTURE_COORD_ARRAY )

The array drawing calls such as glDrawArrays and glDrawElements are called in place of a glBegin, glEnd construct (including the glVertex, glColor, glNormal, glTexCoord, etc. calls that normally go between them).  The array drawing call will specify the primitive type to draw (the same values as for glBegin), and potentially a subset of the arrays which is to be drawn (for instance, a start position, a count of items to draw, or a list of indices into the arrays which are to be drawn).

glDrawArrays( GL_TRIANGLES, 0, 32 )
glDrawElementsui( GL_TRIANGLES, indices )

Further Reading

For information on using Numeric for image manipulation, see Images and Textures

For further information on Numeric, see the Numeric Python Handbook and/or the Numeric Python Homepage

OpenGLContext.scenegraph.arraygeometry module for a simple implementation of array-based geometry rendering.

OpenGLContext.vectorutilities, OpenGLContext.triangleutilities for examples of array manipulation.

The following programs in the tests directory test array geometry functionality (and therefore include example drawing code): gldrawarrays, gldrawarrays_string, gldrawelements, glarrayelement, glinterleavedarrays.