Volume API

This Tutorial deals with the handling of volumes used by Voreen. For that you have to look at several classes.
First of all it is important to know that every volume is handled by Voreen as a single object. These objects of volumes can be created by the classes VolumeAtomic and VolumeFusion which are derived from the class Volume. Volume is just a class of some basic methods and virtual functions. In contrast to that VolumeAtomic and VolumeFusion define how to carry the datasets.
Furthermore VolumeAtomic and VolumeFusion are derived from the class VolumeElement. This class is important later, when we want to access voxels. Now it is time to take a closer look on the functionality of the classes. Due to the point of view that Volume is a base class to this matter (in the global scheme it is derived from other classes), we will start with it.

Table of contents

The structure of volumes

Volume

To create an object of a volume you need three parameters: the dimensions (x-,y-,z-expansion), the spacing (to scale the dimensions) and the stored bits of the new dataset. The class provides several getter-methods e.g. returning the lower left front (getLLF()) and the upper right back (getURB()) of the cube, the cube vertices getCubeVertices(), the cube size (getCubeSize()) (all mapped to the interval [-1,1]), the spacing (getSpacing()), the dimensions (getDimensions()), the number of voxels (getNumVoxels()) in the dataset, some information about the storage (stored bits (getBitsStored()), allocated bits (getBitsAllocated()), bytes per voxel (getBytesPerVoxel())and number of bytes (getNumBytes()) of the dataset), the number of channels (getNumChannels()) and setter- and getter-methods to set or get the values of channels of voxels via getVoxelFloat().
Finally there are some methods to influence the volume data, more precisely clear() which sets all the volume data to zero, getData() which returns the data used in the volume, scale() which creates the volume in a new scaled way (changed dimensions, another filter), mirrorZ() which mirrors the volume data at the x-y-plane and and createSubset() which creates a subset of the original volume.

VolumeSeries

 A VolumeSeries object holds a number of VolumeHandles which all share a common modality. This modality might be a "real" medical modality like a CT, MRI or PET-Scan or an "artificial modality" like a gradient, the normals or a segmentation. A VolumeSeries is identified by its name and its parent VolumeSet. It is possible to add a new VolumeHandle (addVolumeHandle()), find a VolumeHandle determined by a sample or a timestep (findVolumeHandle()), remove a VolumeHandle without and with deleting it (also determined by a sample or a timestep) (removeVolumeHandle()and deleteVolumeHandle()), get a VolumeHandle via an index or other getter and setter (s. doxygen for further information)

VolumeSet

A VolumeSet object provides methods for the storage, retrieval and modification of enclosed VolumeSeries. A particular object is identified by its name and by its parent VolumeSetContainer.

It's possible to add an existing VolumeSeries to a VolumeSet via addSeries(). Any retrieval and deleting method can either be called with a sample VolumeSeries argument, with a name or with a modality. The first method uses the name of the sample to find the appropriate -stored- VolumeSeries in the set. The second method does the same thing with a string and the last one operates (deletes, find or retrieves) on multiple VolumeSeries which have the wanted modality.

The available methods are as follows:  findSeries(), which finds an already existing VolumeSeries in the set and returns it to the caller. removeSeries() removes a series from the set without deleting it and deleteSeries() also deletes the series completely. getSeries() returns all the VolumeSeries and getSeriesNames() gets the names of all the series. Another important method is forceModality() which converts all series to the wanted modality. Essetially, they will be re-created with the new modality and the old ones will be deleted.

VolumeSetContainer

A VolumeSetContainer contains multiple VolumeSets and organizes them. If you destroy the VolumeSetContainer, it will destroy all the enclosed VolumeSets aswell. Apart from adding a new VolumeSet (addVolumeSet()), all the other operation can either take a name or a different VolumeSet as an argument. Those operations include: searching for a VolumeSet in the container (findVolumeSet()), just see if a VolumeSet is in the container (containsVolumeSet()), removing a VolumeSet from the container without deleting it (removeVolumeSet()), removing a VolumeSet and delete it right away (deleteVolumeSet()), delete all the VolumeSets in the container (clear()), list all the enclosed VolumeSets as a vector of names (getVolumeSetNames()) or get all the VolumeSets (getVolumeSets()).

VolumeAtomic<T>

This class expects a voxel type T (the values in the channels of the voxel has to be of this type) and it contains a destructor and two different constructors. The first one is like the constructor in the class Volume. It expects three parameters. Because there is no explicit data which could be used, a new empty dataset will be constructed. The second constructor has one further parameter which expects a input of a dataset (VolumeAtomic(data, dimension, spacing, bitsStored)).
In addition to that there are two methods which clone the volume including the dataset. One of these two methods (clone()) does not expect a parameter and creates a VolumeAtomic object equal to the original one, but with empty dataset. The second clone-method (clone(data)) expects a dataset as a parameter and creates a clone of the original VolumeAtomic object in the case the given dataset is the same. The class VolumeAtomic deals also with the definition of the types which can be passed as T to VolumeAtomic to create an instance of it. The type-definition is based on standard types like „float“, „double“ or „uint32_t“ and on types derived from tgt like „vec4“, „dvec4“ or „col4“. Of course these tgt-types are also based on standard types, but they are vectors.
Depending on the type of T the values of the enum-variables BYTES_PER_VOXEL and BITS_PER_VOXEL are varying. The values are important for the variable „bitsStored“ which is used as a parameter to create a new VolumeAtomic object. By the way the method getBitsAllocated() returns BITS_PER_VOXEL. In VolumeAtomic voxels are stored in the dataset in an array (data[numVoxels]). The index of a voxel in this array is calculated by a special method (calcPos(dim, pos) or calcPos(dim,x,y,z)).
To access the voxels in the dataset (which is integrated in the volume) there are several possibilities to reference it. The first reference expects an x-, a y,- and a zcoordinate (voxel(x,y,z)), the second one expects a three-dimensional vector (voxel(pos)) and the third one expects a scalar(voxel(i)). All three different references exist as getter and setter and also just as getter (constant reference). Voxels can possess one, three ore four channels. That depends on the colour space which is used in the volume by Voreen (gray scale, RGB-space or RGBA-space). To get access to the channels you can use special getter- and settermethods (getVoxelFloat(x,y,z,channel), setVoxelFloat(value,x,y,z,channel); also with „pos“ instead of x,y,z possible). These methods are derived from Volume (where they were defined as virtual) and different from the methods in VolumeFusion (the difference is explained there)!
Another possibility is the access to the channels via reference: For example „v->voxel(32,110,58) = 6“ means: the voxel with the coordinates (32,110,58) gets the value [6,6,6,6] (would be [6,6,6], then 3-dimensional, if the precondition was a RGB-colour space). This is equal to a write-access.
A read-access would look like that: „tgt::col4 value = v->voxel(32,110,58)“ means: the voxel with the coordinates (32,110,58) passes its values to the 4-dimensional vector value (col4 is a 4-dimensional type).
The type T of the voxels (so the type of the values of the channels) is flexible, but all voxels must have the same type. Beyond that there are some special methods to calculate the voxel wise difference between two volumes (calcDifferences(v1, v2)), the voxel wise first derivative (calc1stDerivative(v1, v2)) and the second derivative (calc2ndDerivative(v1, v2)). Furthermore there are also the other methods derived from Volume. In VolumeAtomic all the channels of one voxel are stored at the same point and it looks like this in the memory:
rgba rgba rgba rgba ...
This is a 4-channel VolumeAtomic object. On the other hand VolumeFusion stores several (one, three or four) VolumeAtomic objects in the following way: every VolumeAtomic is a one-dimensional channel each including so many elements as voxels are existing. In the memory it looks like that:
r r r r r ... 
g g g g g ... 
b b b b b ...
a a a a a ... 

So this is a VolumeFusion object evolved from four 1-channel VolumeAtomic objects. Each row represents one VolumeAtomic and all rows connected are one VolumeFusion object.

VolumeFusion<T, N>

This class VolumeFusion expects a voxel type T, a number of channels N, it contains a destructor and two constructors. N is the dimensionality of the elements of the VolumeAtomic object which is passed to a VolumeFusion-constructor. The first constructor does not expect a parameter and creates an empty VolumeFusion internally made of N empty VolumeAtomic objects. The second one expects a VolumeAtomic object (with one-, three- or four-dimensional elements, belonging to the colour space) and again the result is a VolumeFusion object with N channels (each presented by a one-dimensional VolumeAtomic construct). For all that it is important to mention that VolumeFusion is derived from VolumeAtomic.
Again there are two methods which are able to clone the VolumeFusion object, similar to the methods in VolumeAtomic. The getter- and setter-methods (getVoxelFloat(x,y,z,channel), setVoxelFloat(value,x,y,z,channel); also with „pos“ instead of x,y,z possible) for the voxels are also similar, but now the values are calculated in another way: the method looks for the position inside the channel in contrast to the methods in VolumeAtomic, where the method looks for the channel at the position of a voxel.
In addition to that there are also the dataset-independent methods derived from Volume. To complete the explanation according the basic classes dealing with volumes it is necessary to take a short look at one more class.

VolumeElement<scalar/vector<T>>

In general VolumeElement is a class which deals with one skalar or vector. In Voreen it deals with one element of a VolumeAtomic object and it possesses some methods returning or changing a property of the element. At first it should be mentioned that there is neither a constructor nor a destructor in this class. However it is possible to deal with the class in three different ways. There are three alternatives of this class and each of it is specialized in dealing with either a scalar, a 3- or a 4-dimensional vector. The functionality of the methods is nevertheless equivalent. The method getZero() returns a scalar/vector of zeros and getNumChannels() returns the dimensionality of the scalar/vector. Furthermore there is getChannel(t, channel) (t is a appropriate scalar/vector) which returns the value of channel of the scalar/vector and there is the method setChannel(value, t, channel), which changes the value of the channel of the scalar/vector. The methods getChannel(...) and setChannel(...) are for example used by getVoxelFloat(pos, channel) in the class VolumeAtomic to detect the value. At the end you still have to note that VolumeAtomic and VolumeFusion are derived from VolumeElement.

Volumes and its textures

VolumeGL

This class is the OpenGL interface for volume objects. It will create one (or if necessary multiple) 3D-textures which store the complete dataset and will apply a relevant transfer function (if the renderer doesn't use a fragment shader) to the volume.
Depending on your graphics card, there a three possibilities for the transfer function. These possibilities are encapsulated in the enum TFSupport. If your hardware supports fragment shaders SHADER will be set internally and the transfer function will be computed via texture lookup in the fragment shader. If your hardware doesn't support fragment shaders but supports paletted textures, PALETTED_TEXTURES will be set for VolumeGL. With paletted textures there might be a problem, if the textures are of a size different from 2^n. If that's the case the texture size will be increased and the newly won space will be filled with 0's. If both is not possible SOFTWARE is set and a software implementation will be used.
Another enum used in this class is LargeVolumeSupport which indicates how large datasets are handled. Three values are possible: RESIZE_NEAREST,RESIZE_LINEAR,BRICK. The latter is the default value in which the dataset will be splitted into several textures of the maximum size. The other two will resize the dataset with a nearest filter or a linear filter respectively.
The class has one constructor which takes at least a volume, you want to render, as an argument. Optional arguments are a transfer function, a scale value for the alpha channel and a tgt texture filter. If you are going to use a fragment shader, you needn't set a transfer function here at all. Even if you want to use the transfer function you can specify it later via the applyTransFunc()-method. The destructor of this class will leave the rendered volume intact.
In addition there are the following methods:

  • getVolume() which returns the volume used for the creation of the VolumeGL object
  • getFilter() which returns the filter used for the creation
  • setFilter() which sets a different filter
  • applyTransFunc() which sets the TransFunc (which is not necessary for fragment shader)
  • setLargeVolumeSupport() which sets another option of LargeVolumeSupport
  • getLargeVolumeSupport() which returns the active option of LargeVolumeSupport
Methods dealing with textures are:
  • getNumTextures() which returns the number of textures used by the object
  • getTexture(i) which returns one of the textures used by the object (dependent on i; i < number of used textures)
  • setMax3DTexSize(max3DTexSize) sets the maximum size for 3D-textures (useful when dealing with huge datasets)
  • getMax3DTexSize() which returns the maximum size for 3D-textures
  • getSortedTextures() which returns a vector of sorted VolumeTexture objects, sorted by distance (beginning with the farest)

VolumeTexture

Every object of this class is basically a tgt::texture. In addition it has some Voreen specific methods used for splitting a volume into several textures. To deal with the instances of the VolumeTexture objects it is necessary to have several values, because the class VolumeTexture expects nine parameters. The first parameter is data (the dataset), the second one is a matrix and the third and fourth are the lower left front (getLLF()) and the upper right back coordinates (getURB()) which are mapped to the interval [-1,1] (just one dimension has to get to these limits, the smaller ones are adapted to a smaller interval; by the way: these methods are available in VolumeTexture and also in VolumeAtomic). The fifth and the following parameters are „dimensions“, „format“, „internal format“, „datatype“ and „filter“. Further interesting values situated in the interval [-1,1] are the center coordinates (getCenter()), the vertices of the edges (getCubeVertices()) and the size of the cube (getCubeSize()). Important to mention is that VolumeGL is derived from VolumeTexture. When the textures are bigger than necessary, it is possible to fit its coordinates to render it correctly. This could look like that:
glMatrixMode(GL_TEXTURE_MATRIX);
tgt::loadMatrix(myVolumeTexture->getMatrix());

This code makes sure that values between 0 and 1 are mapped to the right area.

Loading volumes

Several classes derive from VolumeReader. Each of it is able to read another format, so VolumeReader is a very small abstract class. VolumeSerializer is a class which registers all these reader objects to use automatically the correct one for a given filename.
The process of registration is carried by another class called VolumeSerializerPopulator. An instance of VolumeSerializerPopulator creates all known reader and registers it at a VolumeSerializer object, also created by the VolumeSerializerPopulator instance. Destroying the VolumeSerializerPopulator object clears the memory previously blocked by the readers and the VolumeSerializer object.
The VolumeReader-method read(fileName, generateVolumeGL = true) returns a VolumeSet object. This is an object which is able to carry all volume data. However, it is possible to merge a new or loaded set and another one.
Furthermore VolumeGL objects will be created automatically (in the OpenGL version; that behavior is also suppressible). You have to notice that the option to store data persistently is not yet implemented completely!

 

VolumeReader

VolumeReader contains a constructor (with the parameter progress =0) and a virtual destructor. The intention of this class is to read volume datasets. For this reason it possesses a method read(fileName, generateVolumeGL = true) which returns a VolumeContainer object.

VolumeSerializer

In a VolumeSerializer object several VolumeReader are registered. A request to load a special dataset will induce a automatic select of a appropriate VolumeReader. VolumeSerializer contains a constructor and a destructor both without a parameter. There is one method load(), which returns a VolumeSet object.
One further method is save() which saves a volume to the given file.
At last there are two methods registerReader() which registers a VolumeReader object and registerWriter() which registers a VolumeWriter object.

VolumeSerializerPopulator

This class organizes the registration of several VolumeReader (and VolumeWriter) objects. An instance of VolumeSerializerPopulator creates a VolumeSerializer object and registers all known VolumeReader and VolumeWriter objects. The VolumeSerializer object will be destroyed when the VolumeSerializerPopulator instance is deleted.
There is a constructor with one parameter (progress = 0) and a destructor.
Furthermore there is a method getVolumeSerializer() which returns a VolumeSerializer object.