VTK/Tutorials/Composite Datasets
VTK 5.0 introduced composite datasets. Composite datasets are nothing but datasets comprising of other datasets. This notion is useful in defining complex structures comprising of other smaller components e.g. an unstructured grid for a car made of grids for the tires, chassis, seats etc. It is also used for representing datasets with adaptive mesh refinement (AMR). AMR refers to the technique of automatically refining certain regions of the physical domain during a numerical simulation.
The October 2006 Kitware Source included an article describing the composite datasets in VTK and how to use them. Since then, the implementation of composite datasets in VTK has undergone some major rework. The main goal was to make the use simple and intuitive. This article describes these changes. These changes should make it into VTK 5.2.
A rough summary of the design changes can be found here
Composite Datasets
The new class hierarchy for composite datasets is as follows:
As is obvious from the above diagram, we have 3 concrete subclasses of vtkCompositeDataSet. vtkMultiBlockDataSet is a dataset comprising of blocks. Each block can be a non-composite vtkDataObject subclass (or a leaf) or an instance of vtkMultiBlockDataSet itself. This makes is possible to build full trees. vtkHierarchicalBoxDataSet is used for AMR datasets which comprises of refinement levels and uniform grid datasets at each refinement level. vtkMultiPieceDataSet can be thought of as a specialization of vtkMultiBlockDataSet where none of the blocks can be a composite dataset. vtkMultiPieceDataSet is used to group multiple pieces of a dataset together.
vtkCompositeDataSet is the abstract base class for all composite datasets. It provides an implementation for a tree data structure. All subclasses of composite datasets are basically trees of vtkDataSet instances with certain restrictions. Hence, vtkCompositeDataSet provides the internal tree implementation with protected API for the subclasses to access this internal tree, while leaving it to the subclasses to provide public API to populate the dataset. The only public API that this class provides relates to iterators.
Iterators are used to access nodes in the composite dataset. Here’s an example showing the use of an iterator to iterate over non-empty, non-composite dataset nodes.
<source lang="cpp">
vtkCompositeDataIterator* iter = compositeData->NewIterator();
for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
{ vtkDataObject* dObj = iter->GetCurrentDataObject(); cout << dObj->GetClassName() <<endl; }
</source>
As we see, accessing nodes within a composite dataset hasn’t really changed. However, the generic API provided by vtkCompositeDataSet for setting datasets using an iterator makes it possible to create composite dataset trees with identical structures without having to downcast to a concrete type. Following is an example of an outline filter that applies the standard outline filter to each leaf dataset with the composite tree. The output is also a composite tree with each node replaced by the output of the outline filter.
<source lang="cpp">
vtkCompositeDataSet* input = …
vtkCompositeDataSet* output = input->NewInstance();
output->CopyStructure(input);
vtkCompositeDataIterator* iter = input->NewIterator();
for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
{ vtkDataSet* inputDS = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject()); vtkPolyData* outputDS = this->NewOutline(inputDS); output->SetDataSet(iter, outputDS); outputDS->Delete(); }
iter->Delete(); </source>
By default, the iterator only visits leaf nodes i.e. non-composite datasets within the composite tree. This can be changed by toggling the VisitOnlyLeaves flag. Default behavior to skip empty nodes can be avoided by setting the SkipEmptyNodes flag to false. Similarly, to avoid traversing the entire sub-tree, instead of just visiting the first level children, set the TraverseSubTree flag to false.
To make it possible to address a particular node within a composite tree, the iterator also provides a flat index for each node. Flat index for a node is the index of the node in a preorder traversal of the tree. In the following diagram the preorder traversal of the tree yields: A, B, D, E, C. Hence the flat index for A is 0, while that for C is 4. Filters such as vtkExtractBlockFilter use the flat index to identify nodes.
MultiPiece Dataset
vtkMultiPieceDataSet is the simplest of all composite datasets. It is used to combine a bunch of non-composite datasets together. It is useful to hold pieces of a dataset partitioned among processes and hence the name. To reiterate, a piece in a multi-piece dataset cannot be a composite dataset.
Multi-Block Dataset
A vtkMultiBlockDataSet is a composite dataset comprising of blocks. It provides API to set/access blocks such as SetBlock, GetBlock, GetNumberOfBlocks etc. A block can be an instance of vtkMultiBlockDataSet or any other subclass of vtkDataObject which is not a vtkCompositeDataSet. Multiblock datasets no longer support the notion of subdatasets with a block. To achieve the same effect, one can add a vtkMultiPieceDataSet as the block and then put the subdatasets as pieces in the vtkMultiPieceDataSet.
Hierarchical-Box Dataset
vtkHierarchicalBoxDataSet is used to represent AMR datasets. It comprises of levels of refinements and datasets associated with each level. The datasets at each level are restricted to vtkUniformGrid. vtkUniformGrid is vtkImageData with blanking support for cells and points. Internally, vtkHierarchicalBoxDataSet creates a vtkMultiPieceDataSet instance for each level. All datasets at a level are added as pieces to the multipiece dataset. vtkHierarchicalDataSet has been deprecated and no longer supported. It is not much different from a vtkMultiBlockDataSet and hence was deprecated.
Pipeline Execution
It is possible to create mixed pipelines of filters which can or cannot handle composite datasets. For filters that are not composite data aware, vtkCompositeDataPipeline executes the filter for each leaf node in the composite dataset to produce an output similar in structure to the input composite dataset. In the previous implementation of this executive, the output would always be a generic superclass of the concrete composite datasets. In other words, if vtkCellDataToPointData filter was inserted into a composite data pipeline, if the input was a vtkHierarchicalBoxDataSet, the output would still be vtkMultiGroupDataSet. This has been changed to try to preserve the input data type. Since vtkCellDataToPointData does not change the data type of the input datasets, if the input is vtkHierarchicalBoxDataSet, then now, the output will be vtkHierarchicalBoxDataSet. However, for filters such as vtkContourFilter where the output type is not a vtkUniformGrid, the output will be vtkMultiBlockDataSet with structure similar to the input vtkHierarchicalBoxDataSet.
Extraction Filters
A few new extraction filters have been added with enable extracting component dataset from a composite dataset.
Extract Block
vtkExtractBlock filter is used to extract a set of blocks from a vtkMultiBlockDataSet. The blocks to extract are identified by their flat indices. If PruneOutput is true, then the output will be pruned to remove empty branches and redundant vtkMultiBlockDataSet nodes i.e. vtkMultiBlockDataSet node with a single child which is also a vtkMultiBlockDataSet. The output of this filter is always a vtkMultiBlockDataSet, even if a single leaf node is selected to be extracted.
Extract Level
vtkExtractLevel is used to extract a set of levels from a vtkHierarhicalBoxDataSet. It simply removes the datasets from all levels except the ones chosen to be extracted. It always produces a vtkHierarchicalBoxDataSet as the output.
Extract Datasets
vtkExtractDataSets is used to extract datasets from a vtkHierarchicalBoxDataSet. The user identifies the datasets to extract using their level number and the dataset index within that level. Output is a vtkHierarchicalBoxDataSet with same structure as the input, with only the selected datasets.
Conclusion
With the redesign, composite datasets now use a full tree data structure to store the datasets rather than the table-of-tables approached used earlier. This makes it easier to build/parse the structure. Iterators have been empowered and can now be used to getting as well as setting datasets in the composite tree, thus minimizing the need to downcast to concrete subclasses for simple filters.