首页 > 代码库 > 第05章-可视化技术(2)

第05章-可视化技术(2)

【译者:这个系列教程是以Kitware公司出版的《VTK User’s Guide -11th edition》一书作的中文翻译(出版时间2010年,ISBN: 978-1-930934-23-8),由于时间关系,我们不能保证每周都能更新本书内容,但尽量做到一周更新一篇到两篇内容。敬请期待^_^。欢迎转载,另请转载时注明本文出处,谢谢合作!同时,由于译者水平有限,出错之处在所难免,欢迎指出订正!】

【本小节内容对应原书的第95页至第105页】

流线(Streamlines)

流线可以看做无重量粒子在向量场(如速度场)中的移动路径。流线可以表达向量场的结构。通常可以创建多个流线来探索向量场中的感兴趣特征。如图5-4。流线可以通过数值积分来计算,因此只能近似的模拟真实的流线。


图5-4 被管道所包围的流线

创建流线需要指定起始点,方向(沿着或者反着流向),以及其他的控制前进的参数。下面代码说明了如何创建一条流线。该流线由一个管道表示,管道半径正比于速度模值的倒数。当流比较小时,管道会比较粗;反之亦然。Tcl代码摘自VTK/Exmaples/VisualizationAlgorithms/Tcl/OfficeTube.tcl。

vtkStructuredGridReader reader
    reader SetFileName"$VTK_DATA_ROOT/Data/office.binary.vtk"
    reader Update;#force a read to occur
 
vtkRungeKutta4 integ
vtkStreamTracer streamer
    streamer SetInputConnection [reader GetOutputPort]
    streamer SetStartPosition 0.1 2.1 0.5
    streamer SetMaximumPropagation 500
    streamerSetMaximumPropagationUnitToTimeUnit
    streamer SetInitialIntegrationStep 0.05
    streamer SetInitialIntegrationStepUnitToCellLengthUnit
    streamer SetIntegrationDirectionToBoth
streamer SetIntegrator integ
 
vtkTubeFilter streamTube
    streamTube SetInputConnection [streamer GetOutputPort]
    streamTube SetInputArrayToProcess 1 0 0vtkDataObject::FIELD_ASSOCIATION_POINTS vectors
    streamTube SetRadius 0.02
    streamTube SetNumberOfSides 12
    streamTube SetVaryRadiusToVaryRadiusByVector
vtkPolyDataMapper mapStreamTube
    mapStreamTube SetInputConnection[streamTube GetOutputPort]
    eval mapStreamTube SetScalarRange        [[[[reader GetOutput] GetPointData]GetScalars] GetRange]
vtkActor streamTubeActor
    streamTubeActor SetMapper mapStreamTube
[streamTubeActor GetProperty] BackfaceCullingOn

在该例中我们设置起始点为(0.1,2.1,  0.5)。也可以通过指定单元id,单元子id和参数坐标来指定起始坐标。MaximumPropagation变量用来控制流线的最大长度(度量单位由MaximumPropagationUnit变量指定)。如果需要更高的精度(代价是更多的计算时间),设置InitialIntegrationsetp变量为一个更小的值。(这里该参数指定为单元长度,也可以选择时间或者距离)另外,精度也可以通过选择一个vtkInitialValueProblemSolver的子类如vtkRungeKutta2或者vtkRungeKutta45(可以设置自适应的步长)来控制。默认情况下,流线追踪类利用vtkRungeKutta2来执行数值积分运算。积分方向可以由以下三个函数控制。

  • SetIntegrationDirectionToForward()
  • SetIntegrationDirectionToBackward()
  • SetIntergrationDirectionToBoth()

由于线通常难以观察,因为我们采用一个管道Filter来表示。管道Filter设置为半径正比于速度模值的倒数,通过函数SetVaryRadiusToVaryRadiusByVector()开启该功能。也可以通过SetVaryRadiusToVaryRadiusByScalar()设置根据标量控制半径,或者取消半径可变(SetVaryRadiusToVaryRadiusOff())。注意需要通知管道filter利用哪个数组来控制半径,这里“vectors”数组通过SetInputArrayToProcess()函数通知管道filter。

有时候我们需要产生同步地产生许多流线。其中一个方法是利用SetSourceConnection()方法来指定一个vtkDataSet的实例,利用这个实例的点来追踪流线。下面是该应用的一个例子,代码摘自VTK/Examples/VisualizationAlgorithms/Tcl/OffcicesTubes.tcl。

vtkPointSource seeds
    seeds SetRadius 0.15
    eval seeds SetCenter 0.1 2.1 0.5
    seeds SetNumberOfPoints 6
vtkRungeKutta4 integ
vtkStreamTracer streamer
    streamer SetInputConnection  [reader GetOutputPort]
    streamer SetSourceConnection [seeds GetOutputPort]
    streamer SetMaximumPropagation 500
    streamer SetMaximumPropagationUnitToTimeUnit
    streamer SetInitialIntegrationStep 0.05
    streamer SetInitialIntegrationStepUnitToCellLengthUnit
    streamer SetIntegrationDirectionToBoth
    streamer SetIntegrator integ

注意该例中采用vtkPointSource对象来创建球状点云作为streamer的输入。对于输入的每一个点都会计算一条流线。

流面(StreamSurfaces)

高级用户可能想利用VTK的流面计算功能。流面计算分为两步,首先用一个有序点集生成一个流线序列,然后再由vtkRuledSurfaceFilter来创建流面。vtkRuledSurfaceFilter假设每条流线是有序排列的,而且每条流线与左右相邻流线在指定距离内,因此输入点集保持有序十分重要,否则计算结果将会非常糟糕。下面代码演示了如何创建一个流面(代码取自VTK/Examples/VisualizationAlgorithms/Tch/streamSurface.tcl,结果如图5-5)。


图5-5 流面

vtkLineSource rake
  rake SetPoint1 15 -5 32
  rake SetPoint2 15 5 32
  rake SetResolution 21
vtkPolyDataMapper rakeMapper
  rakeMapper SetInputConnection [rake GetOutputPort]
vtkActor rakeActor
  rakeActor SetMapper rakeMapper
 
vtkRungeKutta4 integ
vtkStreamTracer sl
  sl SetInputConnection [pl3d GetOutputPort]
  sl SetSourceConnection [rake GetOutputPort]
  sl SetIntegrator integ
  sl SetMaximumPropagation 0.1
  sl SetMaximumPropagationUnitToTimeUnit
  sl SetInitialIntegrationStep 0.1
  sl SetInitialIntegrationStepUnitToCellLengthUnit
  sl SetIntegrationDirectionToBackward
 
vtkRuledSurfaceFilter scalarSurface
  scalarSurface SetInputConnection [slGetOutputPort]
  scalarSurface SetOffset 0
  scalarSurface SetOnRatio 2
  scalarSurface PassLinesOn
  scalarSurface SetRuledModeToPointWalk
  scalarSurface SetDistanceFactor 30
vtkPolyDataMapper mapper
  mapper SetInputConnection [scalarSurface GetOutputPort]
  eval mapper SetScalarRange [[pl3d GetOutput] GetScalarRange]
vtkActoractor
  actor SetMapper mapper

vtkRuledSurfaceFilter一个优点是当输入多个流线时可以关闭带功能,这样有助于理解曲面的结构。

切割(Cutting)

VTK中切割或者切面化数据集意味着在数据集中利用任意类型的隐函数来创建交叉区域。例如我们可以采用一个平面来创建一个平面切面对数据集进行切面显示。切面进行切割时对数据进行插值,然后采用任何一个标准可视化技术来显示。切割结果通常是vtkPolyData类型(n维对象的切割结果是一个n-1维几何体。例如,切割一个四面体将产生一个三角形或者四边形)。

下面Tcl实例中演示了一个燃烧室被平面切割的结果,如图5-6。代码摘自VTK/Graphics/Tesing/Tcl/pro 。

vtkPlane plane
    eval plane SetOrigin [[pl3d GetOutput] GetCenter]
    plane SetNormal -0.287 0 0.9579
vtkCutter planeCut
    planeCut SetInputConnection [pl3d GetOutputPort]
    planeCut SetCutFunction plane
vtkProbeFilter probe
    probe SetInputConnection [planeCut GetOutputPort]
    probe SetSourceConnection [pl3d GetOutputPort]
vtkDataSetMappercutMapper
    cutMapper SetInputConnection [probe GetOutputPort]
    eval cutMapper SetScalarRange       [[[[pl3d GetOutput] GetPointData] GetScalars] GetRange]
vtkActor cutActor
cutActor SetMapper cutMapper


图5-6 燃烧室切割效果图

vtkCutter需要指定完成切割的隐函数。另外,你可能希望指定一个或者多个切割值,可以通过SetValue()或者GenerateValues()函数实现。这些数值指定了执行切割的隐式函数值。(默认情况下切割值为0 ,即切面精确地位于隐函数曲面,小于或者大于0值的曲面则位于该隐曲面的下面和上面。切割值可以看做是到隐曲面的“距离”。)

合并数据(MergingData)

到目前为止我们已经了解了简单的线性可视化管线。然后,管线还可以存在分支,合并甚至是循环情况。接下来我们介绍两个Filter,它们可以利用其他的数据集来构建新的数据集。现在从vtkMergeFilter开始。

vtkMergeFilter将多个数据集中的数据片段合并为一个新的数据集。例如,可以将一个数据集的结构(拓扑和几何),第二个数据集的标量数据,第三个数据集的向量数据合并为一个数据集。这里是该应用的一个实例。(代码摘自VTK/Examples/VisualizationAlgorithms/Tcl/imageWarp.tcl)。(主要关注vtkMergeFilter,其他的不熟悉的可以先忽略。在第106页我们将更加详细的进行描述。)

vtkBMPReader reader
  reader SetFileName $VTK_DATA_ROOT/Data/masonry.bmp
vtkImageLuminance luminance
  luminance SetInputConnection [reader GetOutputPort]
vtkImageDataGeometryFilter geometry
  geometry SetInputConnection [luminance GetOutputPort]
vtkWarpScalarwarp
  warp SetInputConnection [geometry GetOutputPort]
  warp SetScaleFactor -0.1
 
vtkMergeFilter merge
  merge SetGeometryConnection [warp GetOutputPort]
  merge SetScalarsConnection  [reader GetOutputPort]
vtkDataSetMapper mapper
  mapper SetInputConnection [merge GetOutputPort]
  mapper SetScalarRange 0 255
  mapper ImmediateModeRenderingOff
vtkActor actor
  actor SetMapper mapper<span style="font-size: 14px; line-height: 1.846153846; font-family: Arial; background-color: rgb(255, 255, 255);"> </span>

这里所做的是将vtkWarpScalar(输出是vtkPolyData类型)的输出与vtkBMPReader标量数据合并到一起。管线先分后合,因为几何结构需要利用标量数据来单独处理。

当合并数据时,数组中的元组数必须与点的个数一致,单元数据也是一样。

追加数据(AppendingData)

类似vtkMergeFilter,vtkAppendFilter以及vtkAppendPolyData通过追加数据集来产生新数据集。追加Filter接收一系列输入,这些输入的类型必须一致。在追加操作中,只有那些共同的数据数据才会合并在一块。下一节中演示了一个比较好的实例。

探测(Probing)

探测是利用其它数据集对数据集进行采样的过程。在VTK中,可以利用任意的数据集来作为探测几何,其点属性则由其它数据集映射而来。例如下例中创建了三个平面作为探测几何来对一个结构网格数据集进行采样。然后这些平面通过vtkContourFilter来计算等值线。代码摘自VTK/Examples/VisualizationAlgorithms/Tcl/probeComb.tcl。

vtkPLOT3DReader pl3d
    pl3d SetXYZFileName "$VTK_DATA_ROOT/Data/combxyz.bin"
    pl3d SetQFileName "$VTK_DATA_ROOT/Data/combq.bin"
    pl3d SetScalarFunctionNumber 100
    pl3d SetVectorFunctionNumber 202
pl3d Update
 
vtkPlaneSource plane
    plane SetResolution 50 50
vtkTransform transP1
    transP1 Translate 3.7 0.0 28.37
    transP1 Scale 5 5 5
    transP1 RotateY 90
vtkTransformPolyDataFilter tpd1
    tpd1 SetInputConnection [plane GetOutputPort]
    tpd1 SetTransform transP1
vtkOutlineFilter outTpd1
    outTpd1 SetInputConnection [tpd1 GetOutputPort]
vtkPolyDataMapper mapTpd1
    mapTpd1 SetInputConnection [outTpd1 GetOutputPort]
vtkActort pd1Actor
    tpd1Actor SetMapper mapTpd1
    [tpd1Actor GetProperty] SetColor 0 0 0
 
vtkTransform transP2
    transP2 Translate 9.2 0.0 31.20
    transP2 Scale 5 5 5
    transP2 RotateY 90
vtkTransformPolyDataFilter tpd2
    tpd2 SetInputConnection [plane GetOutputPort]
    tpd2 SetTransform transP2
vtkOutlineFilter outTpd2
    outTpd2 SetInputConnection [tpd2 GetOutputPort]
vtkPolyDataMapper mapTpd2
    mapTpd2 SetInputConnection [outTpd2 GetOutputPort]
vtkActor tpd2Actor
    tpd2Actor SetMapper mapTpd2
    [tpd2Actor GetProperty] SetColor 0 0 0
 
vtkTransform transP3
    transP3 Translate 13.27 0.0 33.30
    transP3 Scale 5 5 5
    transP3 RotateY 90
vtkTransformPolyDataFilter tpd3
    tpd3 SetInputConnection [plane GetOutputPort]
    tpd3 SetTransform transP3
vtkOutlineFilter outTpd3
    outTpd3 SetInputConnection [tpd3 GetOutputPort]
vtkPolyDataMapper mapTpd3
    mapTpd3 SetInputConnection [outTpd3 GetOutputPort]
vtkActort pd3Actor
    tpd3Actor SetMapper mapTpd3
    [tpd3Actor GetProperty] SetColor 0 0 0
 
vtkAppendPolyData appendF
    appendF AddInputConnection [tpd1 GetOutputPort]
    appendF AddInputConnection [tpd2 GetOutputPort]
appendF AddInputConnection [tpd3 GetOutputPort]
vtkProbeFilter probe
    probe SetInputConnection  [appendF GetOutputPort]
    probe SetSourceConnection [pl3d GetOutputPort]
vtkContourFilter contour
    contour SetInputConnection [probeGetOutputPort]
evalcontour GenerateValues 50 [[pl3d GetOutput] GetScalarRange]
 
vtkPolyDataMapper contourMapper
    contourMapper SetInputConnection [contour GetOutputPort]
evalcontourMapper SetScalarRange [[pl3d GetOutput] GetScalarRange]
 
vtkActor planeActor
planeActor SetMapper contourMapper


图5-7 数据探测

注意通过SetInputConnection()方法来设置探测器,而待探测数据集是通过SetSourceConnection()函数设置。

探测的另外一个应用是重采样数据。例如,如果你有一个无结构网格数据,而你想通过专门可视化vtkImageData的工具来显示(例如体绘制,139页)。那么可以采用vtkProbeFilter对无结构网格数据采用为一个体数据,然后再可视化。同样也可以采用直线来探测数据,并将结果绘制成X-Y曲线。

最后值得注意的是,切割和探测都能得到类似的结果,除了在分辨率上有所差别外。类似于98页的切割实例,vtkProbeFilter可以利用vtkPlaneSource来产生一个平面,而该平面的属性数据来自于结构网格数据。然而,切割产生的曲面分辨率要依赖于输入数据。而探测产生的曲面分辨率则独立于输入数据。因此在探测数据时,需要特别注意低采用或者过采样。低采样会导致显示错误,过采样则会占用过多的计算时间。

用其他标量着色等值面

计算等值面并用另一个标量进行着色是一个常见的可视化操作。可能你会想到用探测器,但是如果你等值面如果包含你想用来着色的数据时,会有一个更加有效的方法。因为vtkContourFilter(实际用来产生等值面)在计算过程中会对所有数据进行插值。插值后的数据在映射过程中用来进行着色。下面例子取自VTK/Examples/VisualizationAlgorithms/Tcl/ColorIsosurface.tcl。

vtkPLOT3DReader pl3d
    pl3d SetXYZFileName "$VTK_DATA_ROOT/Data/combxyz.bin"
    pl3d SetQFileName "$VTK_DATA_ROOT/Data/combq.bin"
    pl3d SetScalarFunctionNumber 100
    pl3d SetVectorFunctionNumber 202
    pl3d AddFunction 153
    pl3d Update
 vtkContourFilter iso
    iso SetInputConnection [pl3d GetOutputPort]
    iso SetValue 0.24
 
vtkPolyDataNormals normals
    normals SetInputConnection [iso GetOutputPort]
normals SetFeatureAngle 45
 
vtkPolyDataMapper isoMapper
    isoMapper SetInputConnection [normals GetOutputPort]
    isoMapper ScalarVisibilityOn
    isoMapper SetScalarRange 0 1500
    isoMapper SetScalarModeToUsePointFieldData
    isoMapper ColorByArrayComponent "VelocityMagnitude" 0
 
vtkLODActor isoActor
    isoActor SetMapper isoMapper
    isoActor SetNumberOfCloudPoints 1000


图5-8 用其他标题着色等值面

首先通过vtkPLOT3DReader读取数据集。这里我们添加一个要读的函数(函数号153),函数名字是“Velocity Magnitude”。计算等值面时,也会对所有输入数据包括速度场数据进行插值。然后我们调用SetScalarModeToUsePointFieldData()函数来利用速度模值来对等值面着色,ColorByArrayComponent()方法用来指定着色所用的数据数组。

提取单元子集

可视化数据的数据量一般都非常大,处理这样的数据往往要耗费大量的时间和内存。因此,提取部分数据功能显示非常重要。多数情况下,只有部分数据包含有意义的信息,或者在不影响精度的条件下对数据进行消减。

VTK中提供了许多工具来提取部分数据或者对数据降采样。我们已经了解到vtkProbeFilter可以用来进行降采样(见100页“Probing”)。其他工具包含降采样类,还有一些工具能够实现从空间区域中提取单元(降采样工具针对于特定数据,详见105页“降采样图像数据”,和113页“降采样规则网格数据”)。本节中我们主要讲述怎样在空间区域中提取数据片段。

vtkExtractGeometryl类提取数据集中位于隐函数曲面vtkImplicitFucntion内部或者外部的所有单元(注意,隐函数可以是多个隐式函数的二值组合)。下面代码创建了两个椭球的二值组合用来提取区域。vtkShrinkFilter用来对单元进行收缩以便能观察被提取的数据。(代码取自VTK/Examples/VisualizationAlgorithms/Tcl/ExtractionGeometry.tcl.)

vtkQuadric quadric
    quadric SetCoefficients .5 1 .2 0 .1 0 0 .20 0
vtkSampleFunction sample
    sample SetSampleDimensions 50 50 50
    sample SetImplicitFunction quadric
    sample ComputeNormalsOff
vtkTransform trans
    trans Scale 1 .5 .333
vtkSphere sphere
    sphere SetRadius 0.25
    sphere SetTransform trans
vtkTransform trans2
    trans2 Scale .25 .5 1.0
vtkSphere sphere2
    sphere2 SetRadius 0.25
    sphere2 SetTransform trans2
vtkImplicitBoolean union
    union AddFunction sphere
    union AddFunction sphere2
    union SetOperationType 0;#union
 
vtkExtractGeometryextract
    extract SetInputConnection [sampleGetOutputPort]
    extract SetImplicitFunction union
vtkShrinkFilter shrink
    shrink SetInputConnection [extract GetOutputPort]
    shrink SetShrinkFactor 0.5
vtkDataSetMapper dataMapper
    dataMapper SetInputConnection [shrink GetOutputPort]
vtkActor dataActor
dataActor SetMapper dataMapper


图5-9 提取单元的子集

vtkExtractGeometry的输出通常是vtkUnstructuredGrid类型。因为在提取过程中,数据集的拓扑结构往往被破坏,因此必须采用最普遍的数据格式来表示输出。

注意,隐式函数可以通过分配一个vtkTransform来进行变换。如果指定了变换,vtkTransform就被用来改变隐式函数的取值。你可能希望对该功能进行测试。

以多边形数据类型为输出提取单元

大部分数据类型不能直接被图形硬件或者图形库渲染。渲染系统中只有多边形数据(vtkPolyData)被广泛的支持。规则数据集,特别是图像以及体数据也可以被图形系统支持。而其他的数据则需要特别的处理才能被渲染。在VTK中,渲染非多边形数据的一个方法是将其转换为多边形数据。vtkGeometryFilter可实现该功能。

vtkGeometryFilter接收任意vtkDataSet类型数据并输出vtkPolyData数据。其执行转换时,遵循如下规则。所有二维及以下拓扑单元(如多边形,直线,顶点)直接传递至输出。位于数据集边界的三维单元面会被传递至输出结果中。(如果一个面只属于一个单元,那么这个面位于边界。)

vtkGeometryFilter常被用来进行数据格式转换。下面代码中利用vtkGeometryFilter来将二维不规则网格转换为多边形数据,这些多边形数据接下来会作为其他接收vtkPolyData的Filter所接收。代码取自VTK/Examples/DataManipulation/Tcl/pointToCellData.tcl。这里vtkConnectivityFilter提取vtkUnstructedGrid数据然后通过vtkGeometryFilter将其转换为多边形数据。

vtkConnectivityFilter connect2
    connect2 SetInputConnection [thresh GetOutputPort]
vtkGeometryFilter parison
    parison SetInputConnection [connect2 GetOutputPort]
vtkPolyDataNormals normals2
    normals2 SetInputConnection [parison GetOutputPort]
    normals2 SetFeatureAngle 60
vtkLookupTable lut
    lut SetHueRange 0.0 0.66667
vtkPolyDataMapper parisonMapper
    parisonMapper SetInputConnection [normals2 GetOutputPort]
    parisonMapper SetLookupTable lut
    parisonMapper SetScalarRange 0.12 1.0
vtkActor parisonActor
parisonActor SetMapper parisonMapper

实际上vtkDataSetMapper内部使用vtkGeometryFilter来将任意数据类型转换为多边形数据。(该Filter可以直接将输入的vtkPolyData传递到输出。)

另外vtkGeometryFilter提供了一系列函数来根据点id集合,单元ids集合或者判断是否位于一个特定的矩形空间区域中来提取数据单元。利用点或者面id集合提取数据片段时,使用的函数有PointClippingOn(),SetPointMinimum(),SetPointMaximum()和CellClippingOn(),SetCellMinimum(),SetCellMaximum()。最小值和最大值指定了提取的id 的范围。同样还可以指定一个空间矩形区域来限制提取的范围。用ExtentClippingOn()和SetExtent()来开始空间切割和指定范围。Extent包含了六个参数来定义一个包围盒-( , )。可以使用这三种方式的任意组合来提取数据。在调试数据时会非常有用,或者当你只想观察部分数据时。