首页 > 代码库 > 第05章-可视化技术(1)
第05章-可视化技术(1)
【译者:这个系列教程是以Kitware公司出版的《VTK User’s Guide -11th edition》一书作的中文翻译(出版时间2010年,ISBN: 978-1-930934-23-8),由于时间关系,我们不能保证每周都能更新本书内容,但尽量做到一周更新一篇到两篇内容。敬请期待^_^。欢迎转载,另请转载时注明本文出处,谢谢合作!同时,由于译者水平有限,出错之处在所难免,欢迎指出订正!】
【本小节内容对应原书的第89页至第95页】
前面的章节中我们已经介绍了一些数据渲染和交互的基本工具。本章中我们主要介绍一下可视化技术。这些技术(实现为Filter)根据其操作数据类型来组织。其中部分Filter是能够处理任何数据类型的一般性Filter,他们能够接收vtkDataSet(或者任意的子类)。而更多的Filter则是依赖于其处理的数据类型(如vtkPolyData)的专门性Filter。其中处理vtkImageData(或者其子类vtkStructuredData)数据类型的一类Filter本章中暂不涉及,将在下一章中具体讨论(第103页“图像处理和可视化”)。
阅读本章时,有两点需要铭记在心。一是Filter会产生多种输出数据类型,而这些类型并不一定与输入数据类型一致。第二,Filter会通过组合来创建复杂的数据处理管线。从本章中的例子中可以看到一些常见的Filter组合。
5.1 vtkDataSet(及其子类)的可视化
本节主要讲解vtkDataSet数据对象的一些常用可视化操作。注意vtkDataSet是VTK中所有数据类型的超类类型(见图3-2)。因此,这里讲到的操作都适用于所有的数据类型。换句话说,所有接收vtkDataSet类型的Filter都可以接收vtkPolyData,vtkImageData,vtkStructured,vtkRectilinearGrid和vtkUnstructuredGrid类型。
数据属性操作(WorkingWith Data Attributes)
数据属性是与数据结构相关的信息(如25页“可视化管线”所述)。VTK中,属性数据分为点属性数据和单元属性数据。属性数据与结构数据一起被许多的Filter处理产生新的结构和属性。关于属性数据的内容不是本章的重点,这里给出了一个简单的例子来讲述基本的思想。(更多信息请参阅362页“场和属性数据”和图16-1。)
数据属性是简单的vtkDataArray类型,它可以是标量scalar,向量vector,张量tensor,法向normal,纹理坐标texture coordinate,全局id (global id,如标识大量元素),或者pedigreeids(用来追踪管线中的元素历史)。vtkDataSet中的一个点或者单元都有独立的属性数据。数据属性可以关联vtkDataSet中的点或者单元。与vtkDataSet关联的vtkDataArray都是vtkDataArray的一个具体子类,例如vtkFloatArray或者vtkIntArray。这些数据数组可以看做连续的、线性的内存块。在内存块中,数据数组可以看作由子数组或者“元组”(Tuple)组成。创建属性数据即是根据类型实例化数组内存,制定元组大小,插入数据并与数据集关联,如下面Tcl脚本所示。属性数据类型可以指定为标量,向量,张量,纹理坐标或者法向。例如:
vtkFloatArray scalars scalarsInsertTuple1 0 1.0 scalarsInsertTuple1 1 1.2 ...etc... vtkDoubleArray vectors vectorsSetNumberOfComponents 3 vectorsInsertTuple3 0 0.0 0.0 1.0 vectorsInsertTuple3 1 1.2 0.3 1.1 ...ect... vtkIntArray justAnArray justAnArray SetNumberOfComponents 3 justAnArray SetNumberOfTuples $numberOfPoints justAnArray SetName "Solution Attributes" justAnArray SetTuple2 0 1 2 justAnArray SetTuple2 1 3 4 ...etc... vtkPolyData polyData; #A concrete type of vtkDataSet [polyDataGetPointData] SetScalars scalars [polyDataGetCellData] SetVectors vectors [polyDataGetPointData] AddArray justAnArray
这里创建了三种类型的属性数据,float,double和int。第一个数组scalars实例化后,默认的元组大小为1。InsertTuple1()方法用来向数组中插入数据(所有Insert___()方法负责分配足够的内存来保存数据)。下一个数组vectors的元组数为3,因此vectors定义为含有三个分量,InsertTuple3用来向数组中添加数据。最后创建的是元组数为2的数组,通过SetNumberOfTuples()分配内存。接着通过SetTuple2()添加数据;该方法使用的前提是内存已经分配,因此速度要明显快于Insert__()方法。当将属性数据关联到点数据或者单元数据时,注意区别设置类型的方法(SetScalars()和SetVectors())。注意点属性个数必须与数据结构中的点个数一致,单元属性与数据结构的单元个数一致。
类似的,采用如下方法访问属性数据
set scalars [[polyData GetPointData] GetScalars] set vectors [[polyData GetCellData] GetVectors]
许多的Filter需要专门的属性数据进行工作。例如,vtkElevationFilter依赖于相应的高度数据产生标量值。其他的Filter只依赖于结构数据,并忽略传来的属性数据。还有一些Filter需要结构数据和属性数据来工作,如vtkMarchingCubes。它利用输入的标量属性数据和结构数据来产生轮廓结构。其他类型的属性数据,例如向量,在计算轮廓时进行差值计算并输出。
另一个与属性数据相关的重要问题是,有些Filter只输出一种类型的属性,忽略其他的类型。例如,当你想采用一个不能处理输入数据的属性数据类型的Filter来处理数据时,或者你想直接将其从一种类型转换至另一个类型。有2个Filter可以帮你实现:vtkPointDataToCellData和vtkCellDataToPointData。下面例子演示来怎样使用他们(摘自VTK/Examples/DataManipulation/Tcl/pointToCellData.tcl)。
vtkUnstructuredGridReader reader readerSetFileName "$VTK_DATA_ROOT/Data/blow.vtk" readerSetScalarsName "thickness9" readerSetVectorsName "displacement9" vtkPointDataToCellData p2c p2cSetInputConnection [reader GetOutputPort] p2cPassPointDataOn vtkWarpVector warp warpSetInputConnection [p2c GetOutputPort] vtkThreshold thresh threshSetInputConnection [warp GetOutputPort] threshThresholdBetween 0.25 0.75 threshSetAttributeModeToUseCellData<span style="font-size: 14px; line-height: 1.846153846; font-family: Arial; background-color: rgb(255, 255, 255);"> </span>
该例子演示了怎样转换属性数据类型,以及一个可以任意处理单元数据或者点数据的Filter(vtkThreshold)。PassPointDataOn()方法设置vtkPointDataToCellData来创建单元数据,并将点数据输出。SetAttributeModeToUseCellData()设置vtkThreshold使用单元数据来进行处理。
点数据和单元数据之间的转换采用的是一个平均算法。将一个给定单元的所有点的数据求平均值即可将点的属性数据转换为单元数据;而单元数据转换为点数据则是通过计算所有用到该点的单元的数据的平均值。
颜色映射(ColorMapping)
最常用的可视化技术可能是通过标量值或者颜色映射来对物体着色。着色技术的思想比较简单,将标量值映射到一个颜色查找表来获取颜色,然后在渲染时使用颜色来改变点或者单元的外观。在阅读本节前,请先理解怎样控制Actor的颜色(详见54页“Actor颜色”一节)。
VTK中颜色映射主要由用户生成或者数据文件中的标量数据和vtkMapper实例执行颜色映射使用的颜色查询表来控制。也可以使用任意的数据数组通过ColorByArrayComponent()方法来控制。如果没有指明,Mapper会生成一个默认的颜色查询表,你也可以自己创建(下例摘自VTK/Examples/Rendering/Tcl/Rainbow.tcl,运行结果如图5-1所示)。
vtkLookupTable lut lut SetNumberOfColors 64 lut SetHueRange 0.0 0.667 lut Build for {set i 0} {$i<16} {incr i 1} { eval lutSetTableValue [expr $i*16] $red 1 eval lutSetTableValue [expr $i*16+1] $green 1 eval lutSetTableValue [expr $i*16+2] $blue 1 eval lutSetTableValue [expr $i*16+3] $black 1 } vtkPolyDataMapper planeMapper planeMapper SetLookupTable lut planeMapper SetInputConnection [planeGetOutputPort] planeMapper SetScalarRange 0.197813 0.710419 vtkActor planeActor planeActor SetMapper planeMapper
图5-1 颜色映射
如该例所示,操作查询表有两种方式。一是指定一个HSVA范围然后在HSVA空间中插值计算颜色表中颜色(实际由build()函数计算颜色)。第二种方法是在根据颜色表的位置人为指定颜色。注意颜色表中的颜色数目可以设置。本例中利用HSVA范围生成颜色表,然后利用SetTableValue()函数替换掉相应的颜色。
Mapper的SetScalarRange()函数控制标量值与颜色表的映射方式。大于最大值的所有标量值都被映射为最大值,小于最小值的所有标量值都映射为最小值。标量范围设置,可以通过在某一数据范围内映射更多的颜色来达到“扩展”的目的。
很多情况下标量数据就是颜色值,这样就不需要通过颜色查找表进行映射。映射器Mapper提供了多种方法来控制颜色映射。
- SetColorModeToDefault()设置使用默认Mapper映射方法。默认方法直接将三个unsignedchar类型标量数据作为颜色值而不需要进行映射;而其他的类型数据则是通过颜色查找表映射。
- SetColorModeToMapScalars()通过颜色查找表来映射所有类型的标量数据。如果每个元组由多于1个分量组成,那么通过第0个分量来执行颜色映射。
- vtkMapper另一个重要特性就是控制执行颜色渲染使用的数据,例如点或者单元数据,或者是数据数组。该功能可以由如下方法完成。需要注意的是,这些方法会产生非常不同的渲染结果:在渲染时点标量数据模式下,几何体表面所有的点数据通过插值后映射颜色,而单元标量数据模式下则将每个单元渲染为一个颜色。
- SetScalarModeToDefault()触发默认Mapper行为。Mapper默认采用点标量数据渲染;如果点标量数据不可用,则采用可用的单元数据进行渲染。
- SetScalarModeToUsePointData()利用点标量数据进行颜色映射完成渲染。如果没有点标量数据可用,那么标量数据将对物体颜色没有任何影响。
- SetScalarModeToUseCellData()利用单元标量数据进行颜色映射渲染物体。如果没有单元标量数据可用,那么标量数据将对物体颜色没有任何影响。
- SetScalarModeToUsePointFieldData()利用点属性数据中的数据数组,而不是点标量数据和单元标量数据。该函数需要结合ColorByArrayComponent()函数来指定数据数组和作为标量数据的分量。
- SetScalarModeToUseCellFieldData()利用单元属性数据中得场数据,而不是点或者单元标量数据。该方法也需要结合ColorByArrayComponent()函数来指定数据数组和作为标量数据的分量。
正常情况下Mapper默认行为都会正常工作,除非点和单元标量数据都可用。这时,你需要显式指明用点或者面标量数据来对物体着色。
轮廓(Contouring)
另一个常见的可视化技术是轮廓生成。轮廓是指具有相同标量数据的线或者面。VTK中vtkContourFilter执行轮廓生成,如下面的Tcl例子所示。示例代码摘自VTK/Examples/VisualizationAlgorithms/Tcl/VisQuad.tcl,运行结果如图5-2所示。
#Create 5 surfaces in range specified vtkContourFilter contours contours SetInputConnection [ Sample GetOutputPort] Contours GenerateValues 5 0.0 1.2 vtkPolyDataMapper contMapper contMapper SetInputConnection [contours GetOutputPort] contMapper SetScalarRange 0.0 1.2 vtkActor contActor contActor SetMapper contMapper
图5-2 生成轮廓
有两种方式来指定轮廓值。最简单的方式是通过SetValue()方法来指定轮廓数和对应的轮廓值(可以指定多个值)。
ContoursSetValue 0 0.5
示例代码中采用的是第二种方法,即GenerateValues()方法。通过该函数,你可以指定数据范围和该范围内要生成的轮廓个数(包含结束数据)。
注意在VTK中有多个对象来针对特定数据类型执行轮廓生成。如vtkSynchronizedTemplates2D和vtkSynchronizedTemplates3D。如果利用vtkContourFilter的话,不需要直接实例化这些类型的对象,该Filter会根据数据类型自动生成相应的轮廓生成函数。
符号化(Glyphing)
符号化是一种利用字符或字形来表示数据的可视化技术(图5-3)。这些符号可以简单,也可以复杂。简单的如利用有向锥体来表示向量数据,复杂的有多元字形,如Chernoff faces(由数据来控制表情的人脸字形表示)。VTK中利用vtkGlyph3D类来产生可以放缩、着色和具有方向的字形。输入数据的每个点都会拷贝一个字形。字形本身则是通过该Filter的第二个输入函数接收(接收vtkPolyData类型数据)。下面代码说明了vtkGlyph3D的使用。(代码摘自VTK/Examples/VisualizationAlgorithms/Tcl/spikeF.tcl)
vtkPolyDataReader fran franSetFileName "$VTK_DATA_ROOT/Data/fran_cut.vtk" vtkPolyDataNormals normals normalsSetInputConnection [fran GetOutputPort] normalsFlipNormalsOn vtkPolyDataMapper franMapper franMapper SetInputConnection [normals GetOutputPort] vtkActor franActor franActor SetMapper franMapper eval[franActor GetProperty] SetColor 1.0 0.49 0.25 vtkMaskPoints ptMask ptMask SetInputConnection [normals GetOutputPort] ptMask SetOnRatio 10 ptMask RandomModeOn # In this case we are using a cone as a glyph. Wetransform the cone so # its base is at 0,0,0. This is the point whereglyph rotation occurs. vtkConeSource cone cone SetResolution 6 vtkTransform transform transformTranslate 0.5 0.0 0.0 vtkTransformPolyDataFilter transformF transformF SetInputConnection [cone GetOutputPort] transformF SetTransform transform vtkGlyph3D glyph glyph SetInputConnection [ptMask GetOutputPort] glyph SetSourceConnection [transformF GetOutputPort] glyph SetVectorModeToUseNormal glyph SetScaleModeToScaleByVector glyph SetScaleFactor 0.004 vtkPolyDataMapper spikeMapper spikeMapper SetInputConnection [glyph GetOutputPort] vtkActor spikeActor spikeActor SetMapper spikeMapper eval[spikeActor GetProperty] SetColor 0.0 0.79 0.34
图5-3 在表面法向量位置上显示符号
这段代码演示了怎样用有向小锥体来表示曲面法向量。首先读入并显示一个数据集(由Cyberware激光数字化系统采集),然后vtkMastPoints用来对输入数据点进行降采样,采样结果(附带属性数据)作为vtkGlyph3D的输入。vtkConeSource用来生成字形实例。注意锥体采用vtkTransformPolyDataFilter平移来使原点位于(0, 0, 0) (因为vtkGlyph3D绕原点旋转)。
vtkGlyph3D对象glyph设置用点属性法向量作为方法向量。(SetVcetorModeToUseVector()设置使用向量数据来替代法向量)它利用向量模值来对锥体进行放缩。(当然也可以利用SetScaleModeToScaleByScalar()函数设置通过标量数据对字形进行放缩或者利用SetScaleModeToDataScalingOff()函数关闭放缩功能。)
当然也可以利用标量数据、向量数据或者放缩因子对字形进行着色。还可以创建一个字形表,采用标量或者向量数据来尽力索引。参考在线文档来获取更多信息。