Working with Numeric Python
Using NumPy with PyOpenGL and 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
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):
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:
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
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
If, instead, you wanted to multiply each element in one array
by the corresponding element in another array, you would write
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')
# item 0 of the array in first dimension
# item 1 of the array in first dimension
# item 0 of the array in second dimension
# item 1 of the array in second dimension
# all items in first dimension taking every
# as previous, but now take item 1 in the
# as previous, but starting the slice of first dimension
Common slices you'll see include:
# x-coordinate of an i*3 array
# first points of i*3 set of points describing triangles
# r-values of an x*y*3 r,g,b image
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
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.
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.
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.
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.
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.
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:
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.
| 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
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.
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 )
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 )
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 )
| For information on using Numeric for image manipulation, see Images and Textures
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.