CS6630 - Scientific Visualization - Project 3

Carlos Eduardo Scheidegger
cscheid@cs.utah.edu
cs6630

Project 3: Visualizing (scalar) data with isosurfaces

Part 1: Extracting Isosurfaces

In this part of the project, we want to extract meaningful isosurfaces from a portion of the Visible Human dataset. To do that, we'll use the vtkContourFilter class from VTK. The first part of the project is to actually determine the interesting isosurfaces. I found two of them: one roughly related to the skin of the model, and one roughly related to the bones. One way of determining these automatically is using techniques like the Contour Spectrum, in which differential quantities of isovalues are computed and local maxima tend to indicate more interesting isovalues. Since VTK does not offer any similar technique, I resorted to experimentation. For that, I used a recently published system called VisTrails [1], which offers a visual dataflow programming paradigm similar to SCIRun [2], and has VTK as the underlying computational infrastructure. VisTrails was built to support comparative, multiview visualization, and explorations like these are particularly well-suited. Here's a figure of the dataflow used to explore the isosurface values:


Figure 1: Dataflow to visualize isosurface in VisTrails

To find the best isosurface corresponding to skin, I experimented around the isovalue 25-50:


Figure 2: Different choices for skin isovalue in VisTrails

I found the best-looking iso-value for skin to be the value 30, in which details of the ear are still present. I could not remove the strange lines going through the head by changing the isovalue without seriously deteriorating the overall result (as can be seen on the lower row).

The second interesting isosurface is the one that shows the bone structure of the Visible Human. By looking around somewhat randomly, I found that the bone was somewhere between the values 60-120. The dataflow used was the same, with the exception that I now changed the diffuse coilor to off-white:


Figure 3: Different choices for bone isovalue in VisTrails

Each different view has a difference of 10 in the isovalue. The best isovalue seems to be between 70 and 80, so I decided to refine the search, to resolve the forehead bone structure (as in the second view from the lower row), but without the artifacts (as in the first view from the lower row). Another series of views shows the differences:


Figure 4: Refining the choice for bone isovalue in VisTrails

Again, somewhat arbitrarily I decided that the best-looking isosurface is the top-most in the right, since it exhibits no "floating" parts of bone, and still shows a resemblance of the forehead bone structure. Since I used the low-res version of the dataset to explore, I decided to compare it against the high-quality version. Again I set up a VisTrails multi-view window to see the results:


Figure 5: Low-res vs. High-res

The high-res versions are shown in the right column, and it's clear that there's much more detail. At the same time, the choice of isosurface still seems to be appropriate.


Part 2: Transparent isosurfaces

if { [catch {set VTK_TCL $env(VTK_TCL)}] != 0} { set VTK_TCL ".." }
if { [catch {set VTK_DATA $env(VTK_DATA)}] != 0} { set VTK_DATA "../vtkdata" }

source "common.tcl"

# boilerplate stuff
proc connect { source oport dest iport } {
    $dest SetInputConnection $iport [$source GetOutputPort $oport]
}

set datasetresolution "60"
set bivariatecolormapresolution "8"
set datasetsuffix ".$datasetresolution.vtk"
set bivariatecolormapsuffix ".$datasetsuffix.$bivariatecolormapresolution.vtk"

# objects
vtkDataSetReader vh
vtkContourFilter bones
vtkContourFilter skin
vtkPolyDataMapper bonesMapper
vtkPolyDataMapper skinMapper
vtkProperty skinProperty
vtkProperty bonesProperty
vtkActor skinActor
vtkActor bonesActor
vtkCamera camera
vtkRenderer ren
vtkRenderWindow renwin
vtkRenderWindowInteractor iren
vtkInteractorStyleTrackballCamera trackball

# parameters
vh SetFileName "$VTK_DATA/head$datasetsuffix"
skin SetValue 0 30
bones SetValue 0 73
skinProperty SetDiffuseColor 1 0.8 0.2
skinProperty SetOpacity 0.5
bonesProperty SetDiffuseColor 0.8 0.8 0.8
bonesMapper ScalarVisibilityOff
skinMapper ScalarVisibilityOff
ren SetBackground 1 1 1
renwin SetSize 512 512

# dataflow topology
connect vh 0 skin 0
connect vh 0 bones 0
connect skin 0 skinMapper 0
connect bones 0 bonesMapper 0		

# object topology
skinActor SetMapper skinMapper
skinActor SetProperty skinProperty
bonesActor SetMapper bonesMapper
bonesActor SetProperty bonesProperty
ren SetActiveCamera camera
ren AddActor skinActor
ren AddActor bonesActor
iren SetRenderWindow renwin
iren SetInteractorStyle trackball
renwin AddRenderer ren

# More boilerplate
iren Initialize
ren ResetCamera
skin_and_bones.tcl: Visualize two different isosurfaces simultaneously

In the second part of the project, we want to display both isosurfaces together. So that the user can see both of them, the skin will not be completely opaque. The dataflow used for this visualization is shown on the next figure:


Figure 6: Two isosurfaces in VisTrails

To investigate the best-looking opacity, I set up four different views whose only changed parameter was the opacity:


Figure 7: Investigating different opacities in VisTrails

The best-looking visualization seems to be the top-right one, where both isosurfaces can be seen clearly. It is the one where the opacity is set at 0.5. With that in mind, I implemented the code in TCL as shown in the right column.


Part 3: Visualizing curvature colormaps

if { [catch {set VTK_TCL $env(VTK_TCL)}] != 0} { set VTK_TCL ".." }
if { [catch {set VTK_DATA $env(VTK_DATA)}] != 0} { set VTK_DATA "../vtkdata" }

source "common.tcl"


# boilerplate stuff
proc connect { source oport dest iport } {
    $dest SetInputConnection $iport [$source GetOutputPort $oport]
}

set curvaturename "norm"
set datasetresolution "120"
set bivariatecolormapresolution "8"
set datasetsuffix ".$datasetresolution.vtk"
set bivariatecolormapsuffix ".$datasetsuffix.$bivariatecolormapresolution.vtk"


# objects
vtkDataSetReader vh
vtkContourFilter skin
vtkSmoothPolyDataFilter smooth
vtkDataSetReader curvature
vtkProbeFilter probe
vtkPolyDataMapper skinMapper
vtkProperty skinProperty
vtkActor skinActor
vtkCamera camera
vtkRenderer ren
vtkRenderWindow renwin
vtkRenderWindowInteractor iren
vtkLookupTable table
vtkInteractorStyleTrackballCamera trackball


# parameters
vh SetFileName "$VTK_DATA/head$datasetsuffix"
smooth SetNumberOfIterations 2
smooth SetRelaxationFactor 0.5
skinMapper SetColorModeToMapScalars
skinMapper SetScalarRange 0 128
curvature SetFileName "$VTK_DATA/$curvaturename$datasetsuffix"
skin SetValue 0 30
ren SetBackground 1 1 1
renwin SetSize 512 512
table SetHueRange 0.0 0.0
table SetSaturationRange 0.0 1.0
table SetValueRange 0.3 1.0

# dataflow topology
connect vh 0 skin 0
connect skin 0 smooth 0
connect smooth 0 probe 0
connect curvature 0 probe 1
connect probe 0 skinMapper 0

# object topology
skinActor SetMapper skinMapper
skinMapper SetLookupTable table
ren SetActiveCamera camera
ren AddActor skinActor
iren SetRenderWindow renwin
iren SetInteractorStyle trackball
renwin AddRenderer ren

# More boilerplate
iren Initialize
ren ResetCamera
curvature_norm.tcl: Visualizing total curvature
if { [catch {set VTK_TCL $env(VTK_TCL)}] != 0} { set VTK_TCL ".." }
if { [catch {set VTK_DATA $env(VTK_DATA)}] != 0} { set VTK_DATA "../vtkdata" }

source "common.tcl"


# boilerplate stuff
proc connect { source oport dest iport } {
    $dest SetInputConnection $iport [$source GetOutputPort $oport]
}

set curvaturename "angle"
set datasetresolution "120"
set bivariatecolormapresolution "8"
set datasetsuffix ".$datasetresolution.vtk"
set bivariatecolormapsuffix ".$datasetsuffix.$bivariatecolormapresolution.vtk"


# objects
vtkDataSetReader vh
vtkContourFilter skin
vtkSmoothPolyDataFilter smooth
vtkDataSetReader curvature
vtkProbeFilter probe
vtkPolyDataMapper skinMapper
vtkProperty skinProperty
vtkActor skinActor
vtkCamera camera
vtkRenderer ren
vtkRenderWindow renwin
vtkRenderWindowInteractor iren
vtkLookupTable table
vtkInteractorStyleTrackballCamera trackball


# parameters
vh SetFileName "$VTK_DATA/head$datasetsuffix"
smooth SetNumberOfIterations 2
smooth SetRelaxationFactor 0.5
skinMapper SetColorModeToMapScalars
skinMapper SetScalarRange 0 255
curvature SetFileName "$VTK_DATA/$curvaturename$datasetsuffix"
skin SetValue 0 30
ren SetBackground 1 1 1
renwin SetSize 512 512

# dataflow topology
connect vh 0 skin 0
connect skin 0 smooth 0
connect smooth 0 probe 0
connect curvature 0 probe 1
connect probe 0 skinMapper 0

# object topology
skinActor SetMapper skinMapper
skinMapper SetLookupTable table
ren SetActiveCamera camera
ren AddActor skinActor
iren SetRenderWindow renwin
iren SetInteractorStyle trackball
renwin AddRenderer ren

# More boilerplate
iren Initialize
ren ResetCamera
curvature_angle.tcl: Visualizing curvature shape

The third part of the assignment required us to visualize the curvature volumes provided by using the vtkProbeFilter class along with an appropriate colormap. I again used VisTrails to explore the parameter space. The dataflow is as follows:


Figure 8: Colormapping curvature in VisTrails

Notice that I added an additional smoothing step after the isocontour extraction. The reason I did this was so that the resulting triangles were nicer. Particularly, the shading becomes much smoother and it's then easier to determine the different curvature values. In these figures you can see the difference:



Figure 9: Different smoothing parameters on curvature trace

To decide on the best colormap to visualize the norm of the curvature, I used VisTrails, where I could directly compare a series of different colormaps side by side. This is a screenshot of the session:


Figure 10: Exploring different colormaps for visualizing total curvature

The top row shows how bad it is to use different hues for the total curvature. This is because different hues are best used to visualize scalars in which there's no relation between different scalars - the colormap is simply used for differentiation. Since we want to be able to notice higher total curvature, hue is not really appropriate. I decided to use a combination of value and saturation on a red hue:


Figure 11: Final colormap for total curvature

We can see clearly with this colormap that the norm of the second fundamental form gives a clear indication of the total amount of curvature (how much the surface fails to be a plane). If we had used a hue-based colormap, it would have been harder to tell which parts of the surface are more bent:


Figure 12: Wrong colormap (left) vs. right colormap (right) for total curvature

The next curvature invariant we want to visualize is the trace. The trace gives an overall idea of the shape of the surface. The default colormap for VTK uses hue, and since we don't want to perceive one shape as "more" or "less" than the other, hue variation is an adequate choice for this colormap:


Figure 13: Final colormap for curvature trace

Hue is indeed a good indicator: notice how peaks are blue, ridges are cyan, saddle regions are green, valleys are yellow, and bowls are red. If we had used value or saturation, it would have been hard to discriminate the different regions:


Figure 14: Wrong colormap (left) vs right colormap (right) for curvature trace

Part 4: Bivariate colormaps

if { [catch {set VTK_TCL $env(VTK_TCL)}] != 0} { set VTK_TCL ".." }
if { [catch {set VTK_DATA $env(VTK_DATA)}] != 0} { set VTK_DATA "../vtkdata" }

source "common.tcl"

proc ValueHueSaturation { lut pos x y res } {
    global mnh
    global mns
    global mnv
    global mxv
    global mxh
    global mxs
    set h [clamp $y $mnh $mxh]
    set s [clamp $x $mns $mxs]
    set v [clamp $x $mnv $mxv]

    set i [expr int($h * 6.0)]
    set f [expr ($h * 6.0) - $i]

    set p [expr $v * (1 - $s)]
    set q [expr $v * (1 - ($s * $f))]
    set t [expr $v * (1 - ($s * (1 - $f)))]

    switch $i {
	0 { $lut SetTableValue $pos $v $t $p 1 }
	1 { $lut SetTableValue $pos $q $v $p 1 }
	2 { $lut SetTableValue $pos $p $v $t 1 }
	3 { $lut SetTableValue $pos $p $q $v 1 }
	4 { $lut SetTableValue $pos $t $p $v 1 }
	5 { $lut SetTableValue $pos $v $p $q 1 }
	default { $lut SetTableValue $pos $v $t $p 1 }
    }
}

proc makeBivariateLUT { lut res theproc } {
    $lut SetNumberOfTableValues [expr $res * $res]
    for {set x 0} {$x<$res} {incr x} {
	for {set y 0} {$y<$res} {incr y} {
	    $theproc $lut [expr $y * $res + $x] $x $y $res
	}
    }
    $lut SetTableRange 0 [expr $res * $res]
    $lut Build
}

set datasetresolution "120"
set bivariatecolormapresolution 8
set bivariatecolormapdim [expr int(pow(2, $bivariatecolormapresolution))]
set datasetsuffix ".$datasetresolution.vtk"
set bivariatecolormapsuffix ".$datasetresolution.$bivariatecolormapresolution.vtk"

set mns [expr  0.0 * $bivariatecolormapdim]
set mxs [expr  0.5 * $bivariatecolormapdim]
set mnh [expr  0.0 * $bivariatecolormapdim]
set mxh [expr  1.2 * $bivariatecolormapdim]
set mnv [expr -1.0 * $bivariatecolormapdim]
set mxv [expr  0.5 * $bivariatecolormapdim]

# objects
vtkDataSetReader vh
vtkContourFilter skin
vtkSmoothPolyDataFilter smooth
vtkImageMapToColors imageMapToColors
vtkDataSetReader curvature
vtkProbeFilter probe
vtkPolyDataMapper skinMapper
vtkProperty skinProperty
vtkActor skinActor
vtkCamera camera
vtkRenderer ren
vtkRenderWindow renwin
vtkRenderWindowInteractor iren
vtkLookupTable table
vtkInteractorStyleTrackballCamera trackball


# parameters
vh SetFileName "$VTK_DATA/head$datasetsuffix"
curvature SetFileName "$VTK_DATA/anglenorm$bivariatecolormapsuffix"
skin SetValue 0 30
ren SetBackground 1 1 1
renwin SetSize 512 512
makeBivariateLUT table $bivariatecolormapdim {ValueHueSaturation}

# dataflow topology
connect vh 0 skin 0
connect skin 0 smooth 0
connect smooth 0 probe 0
connect curvature 0 imageMapToColors 0
connect imageMapToColors 0 probe 1
connect probe 0 skinMapper 0


# object topology
imageMapToColors SetLookupTable table
skinActor SetMapper skinMapper
ren SetActiveCamera camera
ren AddActor skinActor
iren SetRenderWindow renwin
iren SetInteractorStyle trackball
renwin AddRenderer ren


# More boilerplate
iren Initialize
ren ResetCamera
curvature_bivariate.tcl: Visualizing both trace and norm in a single colormap
For the final part of the project, I used a bivariate colormap to visualize both aspects of the isosurface curvature at the same time. The colormap I used was intended to show no difference in the trace of the curvature if the norm tends to zero (since a flat region is nothing like a bowl, a peak, a ridge, a valley or a saddle at all). So the norm component of the curvature will be displayed using a combination of value and saturation. The trace of the colormap will be displayed using hue. The specific mapping will be different from the previously used hue colormap. Here's the test square colormapped with my bivariate colormap:

Figure 15: Bivariate colormap

One thing I noticed in this part of the assignment is that 6 bits for the entire range of the norm for the entire volume leads to severe quantization. Here's my colormap working on the 6-bit version:


Figure 16: Bivariate colormap on 6-bit precision

Here's the colormap working on the 8-bit version:


Figure 17: Bivariate colormap on 6-bit precision

The above figures show a preliminary colormap. Specifically, they use the entire range of the norm for the volume to colormap the data, which leads to the quantization artifact and to very washed-out colors because some small regions have very high curvature:


Figure 17: Small features with high curvature

These features will push the highest curvature norm to a value that is usually not present in the isosurfaces, effectively reducing the dynamic range of the colormap. Since we're first colormapping and then probing the surface, it becomes impossible to figure out the minimum and maximum norms for a given surface. The solution is to manually clamp the ranges to an acceptable limit. The result is the following:


Figure 18: Final colormap showing interesting regions

I also changed the hue slightly from the previous maps. Notice that in this figure we can clearly see the bivariate colormap working: In relatively flat regions, the colors are very washed out, making (as it should be) a very flat but valley-like region almost identical to a very flat but ridge-like region. In regions of high curvature, the saturation and value are higher, so the features are quite visible, like the red bowl in the inside of the eye, the yellow values tracing the face feature lines, the green saddles in the bridge of the nose, the purple peaks in the tip of the nose, and the blue ridges of whatever was surrounding the head at scan time. Notice that the higher curvatures stand out naturally.


Notes

I just noticed the scripts were supposed to be runnable in VTK 4.4, and VTK 4.4 does not use the new vtkAlgorithm based pipeline. I fixed my scripts so that they'd run on shell.cs.utah.edu, but the actual .tcl files are now different from the ones I show in this document.


References

[1] Bavoil, L. et al. VisTrails: Enabling Interactive Multiple-View Visualizations. In Proceedings of IEEE Visualization 2005.

[2] Parker, S. The SCIRun Problem Solving Environment and Computational Steering Software System, PhD Thesis, August 1999.