Matt Townsend

CS 519 Project Report

12 / 5 / 05

 

AMR Data Visualization in VTK

 

 

The goal of this project was to implement a new set of VTK classes for working with AMR Data in VTK.  AMR (Adaptive Mesh Refinement) Data simply consists of a list or hierarchy of grids at different resolutions.  This allows a data set to refine certain areas of a volume without the need to maintain the same level of refinement throughout.  The data I used for this program was assembled from x-ray cluster data of a galaxy formation.  The file “cluster.adset” was provided by Dave Bock at the NCSA, and contains a collection of 124 rectilinear grids with a total of 7 different levels of resolution.

 

There is currently no easy way to handle AMR data in VTK.  3D data sets can be stored in a variety of ways, including vtkImageData, vtkRectilinearGrids, and vtkUnstructuredGrids, all of which might be successful for storing a single grid within an AMR dataset.  An unstructured grid could also be used to just append all AMR grids into a single set of volume data, but refinement level information would be lost, and so levels could not be operated on individually.  VTK classes for storing collections of data sets also exist, such as vtkDataSetCollection.  This may be useful in creating a simple list of AMR grids to pass through a VTK pipeline, but again, it would be difficult to manage level information and run AMR-specific algorithms using these classes.  An easy way to create and manage AMR data in VTK would be useful, and was the focus of this project.

 

Once AMR Data is stored in VTK, there is a number of ways to prepare this data for visualization.  The first and most basic is to simply treat each block within the data as an individual volume data source and render them all together.  This is useful for comparisons between refinement levels.  For example, viewing all grids simultaneously will provide information on which features are captured by higher-level refinement regions, and where large errors in coarse levels exist.  For general purposes, however, visualizing all surfaces simultaneously can make the visualization appear cluttered and confusing in areas containing many different levels of refinement.  Also, if the data is rendered as opaque isosurfaces, for example, higher refinement levels may not be visible if they sit underneath a coarser level. 

 

A better way to prepare AMR data would be to filter out these overlapping regions.  This will ensure that the highest level of refinement is visible wherever possible and will make the visualization much cleaner.  Overlapping regions can be removed from a coarse grid by simply looping through all finer level grids and removing data in the coarse grid that sit inside the boundaries of a finer grid.  There are a number of VTK filters are useful for performing these operations, depending on what type of grid the operation is being performed on. 

 

Simply removing overlapping regions will result in seams between different levels of refinement.  Depending on the type of visualization that is being done, these seams may be distracting, especially on edges with a high refinement ratio.  There is a number of ways to remove these seams.  One is to simply insert triangles to patch up holes between levels, but this results in hard edges and is not effective in removing the distraction.  Another is to modify the edges of fine level blocks to mach up with coarser levels.  This causes high-resolution data to be ignored in certain areas, and detail is lost.  For my implementation, I used a method that creates transition regions around finer level grids to smoothly interpolate the edges to match up with their coarser neighbors.  Since these transition regions sit outside the high-resolution area, no data is lost when interpolating between levels.  My reference for this algorithm was the paper Extracting Geometrically Continuous Isosurfaces from Adaptive Mesh Refinement Data, published at the University of California, Davis.

 

This algorithm works by subdividing faces of a boundary cell to match the finer resolution grid, and then creating pyramids from the newly subdivided faces.  Each new face serves as the base for a pyramid, and the bases are connected to an apex located at the center of the cell, as shown in the diagram below.  The value of this apex is calculated by first finding the averages of each pyramid base, and then calculating the weighted average of all bases in the cell where the weight is the area of the base.  This is then divided by the total surface area of the cell.  This is a simple and elegant way of patching together seams of an AMR data set that can then be uses easily with any visualization algorithm.

            

 

I designed a number of classes to help manage AMR data in VTK with the above methods in mind.  These classes provide ways to read data from a file and set up a VTK pipeline that can easily apply typical VTK visualization algorithms to the data.  The classes are listed below with a short description of each.

 

vtkAMRData:

This is the main data storage class, derived from vtkObject.  It stores an two arrays of linked lists of vtkDataSets, one holding the grids in each level, and one that can be used to store transition regions generated from the vtkSeamFilter.  It keeps track of the number of grids, the number of levels, the upper and lower level currently stored in the array, and the scalar range of the data as a whole.  Easy access to this information makes setting up a VTK pipeline rather simple for the user.  For example, it is important to know the scalar range of the entire data, not just of each individual grid, so the same range can be used when visualizing all levels of the data.  Similarly, easy constant access to lists of grids within the same level makes it easy to isolate grids in a given level or a range of levels to use in a visualization.

 

vtkAMRDataReader:

This class is derived from vtkProcessObject.  It reads data from the *.adset file format and outputs a vtkAMRData object.  The grids in the data are generated as rectilinear grids, as are specified in the file.  This class allows a range of levels to be specified and read from the file, since AMR data is often too large to read in all at once and certain levels may be of more interest in a visualization.  This also gives the option of reading the data in as an image data.  Image data is better for applications with volume visualizations, since algorithms typically run faster on this type of data set.  This can be used if the rectilinear information in the file represents even-spaced grids, which it does for the first two levels of cluster.adset.  Storing these levels as Image Data speeds up the visualization.  If file data is represented as a rectilinear grid and is not evenly spaced, this can be read in as a rectilinear grid and then converted to image data using one of the available filters in VTK such as the vtkShepardMethod.

 

vtkAMRDataSource:

This is an abstract class derived vtkSource that is a parent class for any VTK filter that outputs data of type AMRData.  It allows specification of upper and lower levels to use when running a filter and basic data storage for inputs and outputs.

 

vtkAMRDataOverlapFilter:

This is derived from vtkAMRDataSource and is designed to take as an input a vtkAMRData object and remove all overlapping regions from its data.  It does this by cycling through each grid and removing the regions of the grid in which finer data is stored using the filter vtkExtractGeometry.  Since this generates grids with holes in them, the output AMRData for this stores grids of type vtkUnstructuredGrid.

 

 

 

VtkAMRDataSeamFilter:

This is another derived class of vtkAMRDataSource.  The OverlapFilter described above leaves seams in the data on edges where different refinement levels intersect.  The Seam filter removes these seams using the algorithm described earlier and stores resulting transition regions in a the AMRData to output.  These transition regions can then be observed independently of the data itself, or can be rendered with it to create a single coherent mesh.

 

vtkAMRDataToUnstructuredGrid:

This filter, derived from vtkUnstructuredGridSource, takes as an input an AMRData object and appends all grids in the data to a single unstructured grid.  This allows the data to be easily connected to a single filter, such as a vtkContourFilter or a vtkCutter.  The filter will also store the level of each grid in the cell data so it can later be mapped to color to visualize the different levels of the original data.  Transition regions store values between the two levels they are interpolating between (e.g. 1.5 for a region between level 1 and level 2).  This also makes it possible to extract certain levels from the AMR data even after it has combined into a single grid.  This filter will, however, lose information on the boundary regions of each grid, so to visualize these an outline filter must be generated for each of the grids individually.

Visualization Methods

 

With these basic classes added to VTK, writing a short program to visualize AMR data becomes extremely easy.  Once the data is read in and stored as vtkAMRData, there many possible ways to visualize it.  Perhaps the simplest, quickest, and even one of the most useful ways is to cycle through the levels of the AMR data and display the outline of each grid.  While this gives no concrete visualization of the data itself, it puts the data in context and identifies the regions of interest.  Bounding boxes are important to provide a frame of reference so the viewer doesn’t get lost or disoriented during the visualization.  When the data itself is visualized, bounding boxes may get in the way and clutter the visualization, so I’ve found it often works best to view these separately.  It is always a good idea to at least display the outline of the entire volume, no matter what visualization method is being used.

 

Another popular method for visualizing AMR Data is using an isosurface.  There are two ways to visualize the isosurface of AMR Data.  The first is to simply contour the entire data set, which will result in overlapping regions.  This may be useful in a way because it shows the disparity between surfaces generated at different refinement levels.  It will, however, appear cluttered in regions where many different refinement levels store information for the same area.  Also, finer levels may be inside coarser levels and therefore not visible.  As in the case with bounding boxes, this is better used to simply get a reference for what the data looks like and how different refinement levels capture different features of the data.  To extract the best and the cleanest isosurface, overlapping regions must first be removed from the data.  This is the purpose of the overlap filter I created in VTK.  Using this filter will ensure that the finest level of data is used wherever possible and is always visible. 

 

Slice planes can also be used to view a slice of the data.  This can easily be done with the new AMR tools by using the vtkAMRDataToUnstructuredGrid filter to create a single grid from all the data, and then using a filter such as vtkCutter to slice the data and color the plane based on scalar values.  If slice planes are used without removing overlapping regions, flickering will result, so it is a good idea to run the overlap filter prior to slicing the data.  The other option is to slice each level individually and offset each resulting plane so they don’t sit on top of each other.  As in the case of contouring overlapping regions of the data, this may appear more cluttered but can be useful for comparing data collected at different refinement levels. 

 

These are most of the methods I used to visualize the data, mostly because they were quicker and easier to use for debugging my code, but other methods provided by VTK can also be used, depending on the focus of the visualization.  One problem I had even in dealing with this relatively small dataset was keeping the visualization interactive.  Reading the entire file took about 20 seconds on my laptop, and depending on the visualization algorithm it used it took anywhere from 30 seconds to several minutes to display.  This is why it is important to include ways to specify which levels should be read from the file and which should be visualized. 

 

Results

 

Displaying Outlines:

 

 

 

These images show the outlines of all grids from the cluster dataset.  This program cycles through all grids in the vtkAMRData and generates outline filters for each grid.  I displayed different levels with different colors to easily show the position of the level grids in space and which levels show the most data.  Here, levels 0-6 are displayed with red, green, blue, yellow, magenta, cyan, and white respectively.  It can be seen that all higher resolution grids are focused at the center of the data, and that level 3 (shown in yellow) includes the most grids (in this case, 60).  Levels 0 and 1 only include one grid.

 

 

 

 

 

 

 

Displaying Overlapping Isosurfaces:

 

 

 

 

These images show the result of passing the vtkAMRDataReader output through the vtkAMRDataToUnstructuredGridFilter and then contouring the resulting unstructured grid.  Since the level information is stored in the cell data of the grid, it is easy to use the vtkPolyDataMapper class to color the surface based on cell data, resulting in a color gradient as the level ranges from 0 to 6.  Coarser levels are displayed in red and finer levels in green and blue.  Since overlapping regions were not removed from the data, the center of the data gets rather messy where there are many overlapping grids.

 

 

 

 

Displaying Non-Overlapping Isosurfaces:

 

 

 

These images show the top few levels of the data set when passed through a vtkAMROverlapFilter to remove overlapping regions.  Surfaces in the above images are much cleaner than those generated without the overlap filter.  Seams are visible between refinement levels.  One of the future goals of this project is to develop a filter to stitch together seams between refinement levels by modifying the underlying grid to create transition regions between levels, but I ran out of time to implement this filter before the project deadline.

 

 

Creating transition regions to patch seams

 

 

 

The image on the left shows the use of a vtkOverlapFilter.  The image on the right shows the same region passed through a vtkSeamFilter.  This filter creates transition regions to smoothly interpolate between different levels.  VtkAMRDataToUnstructuredGridFilter stores intermediate values for transition regions in the cell data, so they will be rendered using a separate color that lies between the colors of the two levels it is blending.  With the data set I used, the seam filter worked most effectively on the top two levels of refinement.  Other levels in this dataset did not always have an integer refinement ratio, and edges between levels did not always align, making it difficult to use the subdivision algorithm described earlier.

 

Displaying Slice Planes for Non-Overlapping Data:

 

 

 

These images show a slice plane through the non-overlapping data created using the vtkAMROverlapFilter.  Here I color the isosurface based on the threshold

 value.  Drawing it based on the level would clash with the colors on the slice plane that correspond to the scalar values and would make for a confusing visualization.  Not too apparent here but still visible if you look closely are the seams on the slice plane resulting from the overlap filter.

 

 

 

 

 

 

 

 

 

Using Slice Planes for Volume Visualization

 

 

 

These images show some results from using the vtkCutter class to generate a bunch of slice planes with low alpha values.  I played with the parameters to get a couple different looks.  The resulting visualization looks a bit messy but allows better depth perception of the data than a single slice plane.

 

AMR Data in Maya

 

 

I worked with by brother to integrate my new AMR functionality with software he is developing to link VTK with Maya.  What this meant on my end was to ensure that my new classes conformed to typical VTK standards and had all the necessary member functions to run successfully in Maya.  This included functions like DeepCopy in the data storage class and Execute in the filter classes.  Maya provides a user interface that allows parameters be changed in the pipeline with sliders and color ramps.

Contour and Slice Plane in Maya

 

 

 

Volume Rendering using Slice Planes – Fly Through (Maya)