Higher-Order Tensor Glyphs in Teem

A significant part of the C code written for our paper [SK10] is available in the open source library Teem. On this page, we will explain how you can integrate this portable and efficient code directly into your own favorite visualization framework.

The relevant modules are called limn (OpenGL-oriented geometry processing), tijk (higher-order tensor algebra), and elf (the actual glyph implementation). Tijk and elf are still flagged as “experimental”, so make sure to get the latest version from the SVN and enable experimental libraries when building Teem, as described in my quick introduction. In your application, you’ll need to

#include <teem/elf.h>

First, let’s tesselate a unit sphere by repeated subdivision of an icosahedron. Three levels of subdivision produce 642 vertices and are sufficient for nice-looking glyphs.

unsigned int level=3;
unsigned int infoBitFlag=(1<<limnPolyDataInfoNorm) |
                         (1<<limnPolyDataInfoRGBA);
limnPolyData *sphere = limnPolyDataNew();
limnPolyDataIcoSphere(sphere, infoBitFlag, level);

/* at the end of your program: */
sphere=limnPolyDataNix(sphere);

Our code takes care of two details that help speed up later computations. First, it produces limnPolyData in which vertices 2*i and 2*i+1 are antipodal (i.e., they denote position v and -v). Second, the vertices of the lower subdivision levels are continuous (i.e., the first 12 vertices are the ones of the original icosahedron), which makes it easy to render lower levels of detail when glyphs only cover a small area in screen space.

Glyph generation will be based on the coefficients of a symmetric higher-order tensor. For a 4th-order tensor, these are 15 floating point numbers giving the non-redundant tensor coefficients in lexicographic order (xxxx, xxxy, xxxz, xxyy, ...). If your input data are real-valued even-order spherical harmonics (in the order described in [DAFD07]), Tijk provides a way of converting them to a higher-order tensor. Tijk also allows you to approximate your input data with a positive definite tensor, which will be required for the HOME glyph. Corresponding user code could look like this:

const tijk_type *type=tijk_4o3d_sym;
float ten[type->num],res[type->num];

/* convert even-order spherical harmonics to higher-order tensor */
tijk_esh_to_3d_sym_f(ten, esh, 4);

/* create positive approximation of the tensor */
tijk_refine_rankk_parm *parm=tijk_refine_rankk_parm_new();
parm->pos=1;
tijk_approx_rankk_3d_f(NULL, NULL, res, ten, type, 6, parm);
parm=tijk_refine_rankk_parm_nix(parm);
tijk_sub_f(ten,ten,res,type);

Tijk currently implements symmetric 2nd-order, 4th-order, and 6th-order 3D tensors. Orders higher than six might be added in the future.

Glyph generation transforms (and thus overwrites) the unit sphere that is provided as an input. To avoid having to re-create the sphere from scratch for every glyph, we work with a copy:

const char normalize=0;
limnPolyData *glyph = limnPolyDataNew();
limnPolyDataCopy(glyph, sphere);
float radius=elfGlyphHOME(glyph, 1, ten, type, NULL, normalize);

/* at the end of your program: */
glyph=limnPolyDataNix(glyph);

The computed radius specifies a bounding sphere around the glyph and can be used to cull glyphs that fall outside the viewport. If you want your surface normals to be unit-length, simply set normalize=1. In my own code, normalization is done by the vertex shader (during rendering). To generate the traditional polar plot, simply use elfGlyphPolar() instead of elfGlyphHOME().

So far, your glyph will have the traditional per-vertex, rainbow-like XYZ-RGB coloring. If you prefer the per-peak coloring described in our paper, you will first need to extract the direct neighbors of each mesh vertex:

int *neighbors;
unsigned int nbstride;
limnPolyDataNeighborArray(&neighbors, &nbstride, glyph);

Once you have the neighbors (which will be the same for all glyphs, so they only need to be computed once), simply call:

elfColorGlyphMaxima(glyph, 1, neighbors, nbstride, ten, type, 1, 0.8);

That’s it. If you look at the definition of limnPolyData in teem/limn.h, it should be obvious how to render the result via standard OpenGL calls (I’m using glDrawElements, combined with vertex buffer objects). You may want to look at the documentation of the individual functions to tweak parameters or do proper error checking.

All above-mentioned functions are thread-safe, so you can have all your CPU cores work on creating and coloring glyph geometry in parallel.

References

[DAFD07]Maxime Descoteaux, Elaine Angelino, Shaun Fitzgibbons, Rachid Deriche. Regularized, Fast, and Robust Analytical Q-Ball Imaging. Magnetic Resonance in Medicine 28:497-510, 2007.
[SK10]Thomas Schultz and Gordon Kindlmann. A Maximum Enhancing Higher-Order Tensor Glyph. Computer Graphics Forum (Proc. EuroVis) 29(3):1143-1152, 2010.

Table Of Contents

Previous topic

Ball-and-Stick Model Fitting in Teem

Next topic

Superquadric Glyphs for Symmetric Second-Order Tensors

This Page