6、VTK基本数据结构
我们已经学习了VTK的一个重要概念——可视化管线,了解了VTK数据的流动过程。好比我们做一道菜,在做每一道菜之前,首先要掌握这道菜的做法,什么时候放盐什么时候放酱油等调料,除了需要弄清楚做每一道菜的流程,还需要了解所做的每一道菜的原料,比如有些原料是要蒸出来才比较好吃,有些则可能会用炖的方法才比较可口,只有掌握做菜的流程以及了解菜的原料的特点,最后做出来的菜才可口美味。如果说VTK可视化管线是完成VTK应用程序这道菜的基本步骤,那么VTK的数据结构就好比我们做每一道菜的基本原料。针对可视化领域的特点,VTK定义了种类丰富的数据结构,这一章我们重点学习VTK的基本数据结构,了解这些数据结构,有助于我们写出更有针对性的、更高效的可视化应用程序。
6.1 可视化数据的基本特点
我们的目的是要对数据进行可视化,因此有必要对可视化的数据特点作一了解。归纳起来,可视化数据具有如下一些特点:
l 离散性
为了让计算机能够获取、处理和分析数据,必须对无限、连续的空间体进行采样,生成有限的采样数据点,这些数据以离散点的形式存储,采样的过程是一个离散化的过程。
由于可视化数据的离散性特点,在某些离散点上有精确的值存在,但点与点之间的值则是不可知的,要得到采样点之外的其他点的值,只有通过插值(Interpolation)的方法获取。常用的插值方法是线性插值,要得到更精确的数值可以采用非线性插值,如B样条插值方法。
l 数据具有规则或不规则的结构(或者说结构化与非结构化)
可视化数据可以分为规则(Regular)和不规则(Irregular)或者说结构化(Structured)和非结构化(Unstructured)。规则结构数据点之间有固定的关联关系,可以通过这些关联确定每个点的坐标,不规则结构数据之间没有固定的关联关系。
对于规则结构的数据,存储时不必存储所有的数据点,只需存储起始点、相邻两点之间的间隔以及点的总数就可以保存完整的数据信息。对于不规则结构的数据,虽然不可以像规则结构的数据那样存储,但它也有自身的优势,即在数据变化频繁的区域可以密集表示,而数据变化不频繁的区域则稀疏表示。规则结构的数据可以在存储及计算时占优势,不规则结构的数据虽然存储和计算时不能像规则结构的那样高效,但它在数据表达方面相对而言则更加*,更加细致、灵活的表现数据。
l 数据具有维度
可视化数据的第三个特点是拓扑维度(Topological Dimension)。可视化数据具有零维、一维、二维、三维等任意维度。如,零维的数据表现为点,一维数据表现为曲线,二维数据表现为曲面,三维数据表现为体等。数据的维度决定了数据可视化的方法,如,对于二维的数据,可以将数据存储到一个矩阵,然后再采用针对二维数据的可视化方法进行可视化(如等高图)。
6.2vtkDataObject和vtkDataSet
6.2.1 vtkDataObject
在VTK中,数据一般以数据对象(Data Object,对应VTK里的类vtkDataObject)的形式表现,是VTK里可视化数据最一般的表达形式。数据对象是数据的集合,数据对象表现的数据是可以被可视化管线处理的数据,只有数据对象被组织成一种结构(Structure)后,才能被VTK提供的可视化算法处理。
图6.1是vtkDataObject类的继承图,VTK里所有的数据结构形式都是从这个类派生出来的,实际的VTK应用程序中,没有直接使用vtkDataObject来实例化数据对象,而是根据具体的可视化数据选用其具体的子类实现可视化的。
图6.1vtkDataObject类的继承图
6.2.2 vtkDataSet
数据对象被组织成一种结构并且被赋予相应的属性值时就形成数据集(Dataset)。VTK里与数据集对应的类是vtkDataSet,该类从vtkDataObject直接派生。vtkDataSet由两个部分组成,即组织结构(Organizing Structure)以及与组织结构相关联的属性数据(Attribute Data),图6.2描述了vtkDataSet各结构的详细构成。vtkDataSet是一个抽象基类,结构的实现及表达由其具体的子类来完成。
vtkDataSet的组织结构由拓扑结构(Topology)和几何结构(Geometry)两部分组成。拓扑结构描述了物体的构成形式,几何结构描述了物体的空间位置关系。换言之,点数据(Point Data)所定义的一系列坐标点构成了vtkDataSet(数据集)的几何结构;点数据的连接(点的连接先形成单元数据(Cell Data),由单元数据再形成拓扑)就形成了数据集的拓扑结构。比如,我们想要在屏幕上显示一个三角形,首先我们必须定义三角形三个点的坐标(即Point Data,记三个点为P1, P2和P3),然后将这三个点按照一定的顺序连接起来(P1-P2-P3,或者是P3-P2-P1的顺序),这三个点定义了数据集的几何结构,它们的连接就构成了数据集的拓扑结构。亦即,点数据(Point Data)定义数据集的几何结构,单元数据(Cell Data)定义数据集的拓扑结构,要形成完整的数据集,必须有几何和拓扑两种结构。
关于拓扑、几何结构以及属性数据的更多解释:拓扑结构具有几何变换不变性。例如,说一个多边形是三角形,即指其拓扑结构,而给定的每个点的坐标,则为其几何结构。几何结构是一种空间描述,与空间变换有紧密联系,常见的变换有旋转、平移和缩放。属性数据是对拓扑结构和几何结构信息的补充,属性数据可以是某个空间点的温度值,也可以是某个单元的质量之类的。
图6.2vtkDataSet的结构组成
接下来我们通过例子说明怎么把几何结构和拓扑结构加入到数据集(vtkDataSet)中去。先看一下只有几何结构,没有拓扑结构的vtkDataSet。
示例TrianglePoints |
1: #include <vtkSmartPointer.h>
2: #include <vtkPoints.h>
3: #include <vtkPolyData.h>
4: #include <vtkPolyDataWriter.h>
5:
6: int main(int argc, char *argv[])
7: {
8: //创建点数据
9: vtkSmartPointer<vtkPoints> points =vtkSmartPointer<vtkPoints>::New();
10: points->InsertNextPoint ( 1.0, 0.0, 0.0 );
11: points->InsertNextPoint ( 0.0, 0.0, 0.0 );
12: points->InsertNextPoint ( 0.0, 1.0, 0.0 );
13:
14: //创建vtkPolyData类型的数据,vtkPolyData派生自vtkPointSet,
15: //vtkPointSet是vtkDataSet的子类。
16: vtkSmartPointer<vtkPolyData> polydata =vtkSmartPointer<vtkPolyData>::New();
17:
18: //将创建的点数据加入到vtkPolyData数据里
19: polydata->SetPoints ( points );
20:
21: //将vtkPolyData类型的数据写入到一个vtk文件,保存位置是工程当前目录
22: vtkSmartPointer< vtkPolyDataWriter > writer = vtkSmartPointer<vtkPolyDataWriter >::New();
23: writer->SetFileName("triangle.vtk");
24: writer->SetInput(polydata);
25: writer->Write();
26:
27: return 0;
28: }
TrianglePoints示例中,首先创建了一个点数据(vtkPoints),里面含有三个点;紧接着创建了一个类型为vtkPolyData的数据,vtkPolyData派生自类vtkPointSet,而vtkPointSet又派生自vtkDataSet,所以说vtkPolyData是一种具体的数据集;然后将创建的点数据加入到数据集,于是点数据就定义了该数据集的几何;最后把vtkPolyData的数据用类vtkPolyDataWriter写入到triangle.vtk文件。
可以利用ParaView软件(http://www.paraview.org,ParaView是使用VTK和Qt编写的开源的软件)打开示例中保存的triangle.vtk文件。
图6.3TrianglePoints生成的数据在ParaView软件经Glyph(符号化)处理后的结果图
使用ParaView软件打开该文件以后,在渲染窗口中我们看不到任何东西。这是因为我们只是在数据集vtkPolyData的实例里,只定义了数据的几何结构,没有定义拓扑结构。如果你想看一下我们生成的triangle.vtk数据是怎样一种形式,可以调用ParaView的菜单Filters->Common->Glyph(在点数据的空间位置生成符号),可以看到在三角形的三个顶点位置生成了三个同向的箭头(图6.3所示),也就说明了示例中生成的文件triangle.vtk里面确实存在数据,只不过它少了某种结构,导致它无法正常显示而已。
接下来我们再看一个例子,在TrianglePoints示例的基础上给数据集定义拓扑结构。
示例TriangleVertices |
1: #include<vtkCellArray.h>
2: #include<vtkSmartPointer.h>
3: #include <vtkPoints.h>
4: #include<vtkPolyDataWriter.h>
5: #include <vtkPolyData.h>
6:
7: int main(int argc, char*argv[])
8: {
9: //创建点的坐标
10: double X[3] = {1.0, 0.0,0.0};
11: double Y[3] = {0.0, 0.0,1.0};
12: double Z[3] = {0.0, 0.0,0.0};
13:
14: //创建点数据以及在每个点坐标上加入顶点(Vertex)类型的单元l
15: vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
16: vtkSmartPointer<vtkCellArray> vertices =vtkSmartPointer<vtkCellArray>::New();
17:
18: for ( unsigned int i = 0;i < 3; ++i )
19: {
20: //定义用于存储点索引的中间变量,vtkIdType就相当于int、long等类型
21: vtkIdType pid[1];
22:
23: //把每个点坐标加入到vtkPoints中,InsertNextPoint()返回加入的点的索引号,
24: //下面我们需要使用这个索引号来创建顶类型的单元
25: pid[0] =points->InsertNextPoint ( X[i], Y[i], Z[i] );
26:
27: //在每个坐标点上分别创建一个顶点,顶点是单元(Cell)里的一种类型
28: vertices->InsertNextCell ( 1,pid );
29: }
30:
31: //创建vtkPolyData对象
32: vtkSmartPointer<vtkPolyData> polydata =vtkSmartPointer<vtkPolyData>::New();
33:
34: //指定数据集的几何结构(由points指定),以及数据集的拓扑结构(由vertices指定)
35: polydata->SetPoints (points );
36: polydata->SetVerts (vertices );
37:
38: //将生成的数据集写到TriangleVerts.vtk文件里,保存在工程当前目录下
39: vtkSmartPointer<vtkPolyDataWriter> writer =vtkSmartPointer<vtkPolyDataWriter>::New();
40: writer->SetFileName ("TriangleVerts.vtk" );
41: writer->SetInput (polydata );
42: writer->Write();
43:
44: return 0;
45: }
TriangleVertices示例中的第16行实例化了一个vtkCellArray的对象,我们已经知道“点数据(Point Data)定义数据集的几何结构,单元数据(Cell Data)定义数据集的拓扑结构。”由此我们可以知道,vtkCellArray类型的对象vertices就是用来指定数据集polydata的拓扑结构(第36行),而polydata的几何结构则是由points来定义的(第35行)。
TriangleVertices示例中定义的数据集的拓扑结构是零维的点,即单元类型是Vertex。保存的VTK文件TriangleVerts.vtk在ParaView里的显示结果如图6.4所示(为了便于观察三角形的三个顶点,图6.4显示的时候把点的大小设置成5个像素)。
图6.4TriangleVertices示例生成的VTK文件在ParaView的显示结果
接下来我们继续在TriangleVertices示例的基础上做一些更改,将零维的点拓扑结构改成一维的线拓扑结构,示例的完整代码如下:(TriangleGeometryLines.cpp)
示例TriangleGeometryLines |
1: #include <vtkPoints.h>
2: #include <vtkLine.h>
3: #include<vtkCellArray.h>
4: #include<vtkSmartPointer.h>
5: #include<vtkPolyDataWriter.h>
6: #include <vtkPolyData.h>
7:
8: int main(int argc, char*argv[])
9: {
10: //创建三个坐标点
11: vtkSmartPointer<vtkPoints> points =vtkSmartPointer<vtkPoints>::New();
12: points->InsertNextPoint( 1.0, 0.0, 0.0 ); //返回第一个点的ID:0
13: points->InsertNextPoint( 0.0, 0.0, 1.0 ); //返回第二个点的ID:1
14: points->InsertNextPoint( 0.0, 0.0, 0.0 ); //返回第三个点的ID:2
15:
16: //每两个坐标点之间分别创建一条线
17: //SetId()的第一个参数是线段的端点ID,第二个参数是连接的点的ID
18: vtkSmartPointer<vtkLine> line0 =vtkSmartPointer<vtkLine>::New();
19: line0->GetPointIds()->SetId ( 0,0 );
20: line0->GetPointIds()->SetId ( 1,1 );
21:
22: vtkSmartPointer<vtkLine>line1 = vtkSmartPointer<vtkLine>::New();
23: line1->GetPointIds()->SetId ( 0,1 );
24: line1->GetPointIds()->SetId ( 1,2 );
25:
26: vtkSmartPointer<vtkLine> line2 =vtkSmartPointer<vtkLine>::New();
27: line2->GetPointIds()->SetId( 0,2 );
28: line2->GetPointIds()->SetId ( 1,0 );
29:
30: //创建单元数组,用于存储以上创建的线段
31: vtkSmartPointer<vtkCellArray> lines =vtkSmartPointer<vtkCellArray>::New();
32: lines->InsertNextCell (line0 );
33: lines->InsertNextCell (line1 );
34: lines->InsertNextCell (line2 );
35:
36: vtkSmartPointer<vtkPolyData> polydata =vtkSmartPointer<vtkPolyData>::New();
37:
38: //将点和线加入到数据集中,前者定义数据集的几何结构,后者定义拓扑结构
39: polydata->SetPoints (points );
40: polydata->SetLines (lines );
41:
42: vtkSmartPointer<vtkPolyDataWriter> writer =vtkSmartPointer<vtkPolyDataWriter>::New();
43: writer->SetFileName ("TriangleLines.vtk" );
44: writer->SetInput (polydata );
45: writer->Write();
46:
47: return 0;
48: }
示例TriangleGeometryLines生成的VTK文件在ParaView的显示结果如图6.5所示。
图6.5TriangleGeometryLines示例生成的VTK文件在ParaView的显示结果
对于VTK的数据集而言,数据集的几何结构和拓扑结构是其必不可少的两个部分。TrianglePoints示例只定义了数据集的几何结构,没有定义该数据集的拓扑结构,所以该数据集不能直接显示;TriangleVertices和TriangleGeometryLines除了定义数据集的几何结构(由points定义),还定义了相应的拓扑结构。其中示例TriangleVertices定义的是零维的点拓扑结构;TriangleGeometryLines定义的是一维的线拓扑结构,它们都是保存在由类vtkCellArray所实例化的对象里,除了零维的点、一维的线等类型的单元以外,VTK还定义了其他类型的单元。