The SoTransferFunction( C++ | Java | .NET ) node defines a mapping from scalar data set values to color and opacity (alpha) values. If no transfer function is specified, VolumeViz rendering nodes automatically use the predefined GRAY color map. Note this default color map contains a constant color (white) with an alpha ramp from 0 to 1, so it can be useful for volume rendering, but is not very useful for slice rendering. To use one of the predefined color maps, set the predefColorMap field to a valid value other than NONE, for example INTENSITY. The INTENSITY color map is the converse of GRAY. It contains a constant alpha value (1.0) with gray scale color ramp from 0 to 1, so it is useful for slice rendering, but not for volume rendering. Similarly the BLUE_WHITE_RED color map is approximately the opaque analog of the SEISMIC color map.
All the predefined color maps are opaque except GRAY and SEISMIC. Note that the “rainbow” color maps are generally not a good choice for volume visualization because they are not perceived by the human eye as a linear scale. The BLUE_WHITE_RED color map can be a useful starting point for “signed” data like seismic amplitudes where negative and positive peak values are significant and mid-range values are less important. The INTENSITY color map can be a useful starting point for data like medical/NDT scans because it maps intensity to luminance. The GLOW color map (sometimes called the heated body color scale) can also be useful for scanned data because the color changes are perceived as roughly linear.
The predefined color maps are shown here (note the checkerboard pattern is not part of the color map, it is just used to show where the color map contains alpha values less than 1).
To define a custom color map, set the predefColorMap field to NONE, set the colorMapType field to the appropriate type (RGBA is the default) and load the color map values into the colorMap field. The colorMap field contains an array of float values in the range [0,1]. The number of float values needed depends on the colorMapType. It is equal to the number of colors in the color map multiplied by the number of components in each color. An ALPHA color map has one component per color, a LUM_ALPHA (Luminance plus Alpha) color map has two components per color and an RGBA color map has four components per color. For example, for an RGBA color map of length N, there must be 4*N float values in the field. The following example effectively creates a “reversed” INTENSITY color map.
const int numColors = 256; SbColorRGBA* colors = new SbColorRGBA[numColors]; for (int i = 0; i < numColors; ++i) { float intensity = (numColors - i)/(float)numColors; colors[i].setValue( intensity, intensity, intensity, 1 ); } pTF->predefColorMap = SoTransferFunction::NONE; pTF->colorMap.setValues( 0, 4*numColors, (float*)colors ); delete [] colors;
const int numColors = 256; float[] colors = new float[4 * numColors]; for (int i = 0; i < numColors; ++i) { float intensity = (numColors - i) / (float)numColors; int index = i * 4; colors[index ] = intensity; colors[index+1] = intensity; colors[index+2] = intensity; colors[index+3] = 1; } TF.predefColorMap.Value = SoTransferFunction.PredefColorMaps.NONE; TF.colorMap.SetValues( 0, colors );
int numColors = 256; float[] colors = new float[4 * numColors]; for (int i = 0; i < numColors; ++i) { float intensity = (numColors - i) / (float)numColors; int index = i * 4; colors[index ] = intensity; colors[index+1] = intensity; colors[index+2] = intensity; colors[index+3] = 1; } TF.predefColorMap.setValue( SoTransferFunction.PredefColorMaps.NONE ); TF.colorMap.setValues( 0, colors );
The resulting image looks like this:
The colorMap field is an SoMFFloat( C++ | Java | .NET ) field and the usual guidelines for efficient use of MF fields apply. MF fields will automatically re-allocate memory as additional items are added to the field, but this is very inefficient. To avoid performance problems from repeatedly re-allocating memory:
|
When rendering a single volume, there is only one transfer function active at a time and each rendering node uses the current (i.e. last traversed) transfer function in the traversal state, similar to the way materials apply to polygonal geometry. This allows, for example, the use of one transfer function for volume rendering (SoVolumeRender( C++ | Java | .NET )) and a different transfer function for slices (e.g. SoOrthoSlice( C++ | Java | .NET )). Your application can switch from one transfer function to a different one. For example, by putting multiple transfer function nodes under an SoSwitch( C++ | Java | .NET ) node.
An application can also dynamically modify the contents of a transfer function, even if using a predefined color map. This is possible because the color map actually used for rendering is always the contents of the actualColorMap field and SoTransferFunction( C++ | Java | .NET ) automatically updates this field when the other fields are changed. When a predefined color map is selected, the corresponding values are automatically loaded in the actualColorMap field. An application can access this field and replace or modify its contents to dynamically change the color map. In the example below we invert the color (RGB) values currently in the color map.
const int numColors = pTF->actualColorMap.getNum() / 4; SbColorRGBA* colors = (SbColorRGBA*)pTF->actualColorMap.startEditing(); for (int i = 0; i < numColors; ++i) { colors[i].setValue( 1 – colors[i][0], 1 – colors[i][1], 1 – colors[i][2], 1 ); } pTF->actualColorMap.finishEditing();
int numColors = TF.actualColorMap.GetNum() / 4; SbNativeArray<float> colors = TF.actualColorMap.StartEditing(); for (int i = 0; i < numColors; ++i) { int index = i * 4; colors[index ] = 1 – colors[index ]; colors[index + 1] = 1 – colors[index + 1]; colors[index + 2] = 1 – colors[index + 2]; colors[index + 3] = 1; } TF.actualColorMap.FinishEditing();
FloatBuffer colors = TF.actualColorMap.startEditing(); for (int i = 0; i < numColors; ++i) { int index = i * 4; colors.put( 1 - colors.get(index )); colors.put( 1 - colors.get(index + 1)); colors.put( 1 - colors.get(index + 2)); colors.put( 1 ); } TF.actualColorMap.finishEditing();
The actualColorMap field always contains RGBA color values regardless of how the color map was originally created. So the number of color values is the number of float values in the field divided by 4. |
The color map may also be "remapped", compressing the original list of color values into the range of entries specified by the minValue and maxValue fields. Entries outside this range are set to full transparent black (all components set to zero). This may be convenient, for example, to quickly remove low value voxels that represent "noise" in the data. But note that this reduces the resolution of the color map. In general it's better to change the range of data values mapped into the color map (see SoDataRange( C++ | Java | .NET )). Changes to the minValue and maxValue fields also update the values in the actualColorMap field.
When combining multiple volumes, or using multiple volume masks, it is possible to have multiple transfer functions active at the same time. We will discuss this in a later section.
VolumeViz does not define any specific file format for saving and loading color maps. Because SoTransferFunction( C++ | Java | .NET ) is an Open Inventor node it is always possible to read and write the node using the Open Inventor ".iv" file format (see SoDB( C++ | Java | .NET )::readAll() and SoWriteAction( C++ | Java | .NET )). For convenience VolumeViz is able to load a color map from a file in the Avizo format (".am" or ".col"). Use the SoTransferFunction( C++ | Java | .NET ) method loadColormap(). Note that this method loads values into the colorMap field, but the application must still explicitly set the predefColorMap field to NONE for the custom color map values to be used.