vtk是进行3维图像处理的开源库。
核心源代码: vtk源代码: 包含源代码和相关实例程序。
相关数据: vtkdata:包含VTK自带的例子或测试程序运行时需要用到的数据。
文档: vtkDocHtml: 由Doxygen工具根据各个类的头文件自动生成的。
可执行程序: vtkexe: 安装后可以运行后缀为.tcl
的tcl脚本程序。vtk和很多例子都是以tck脚本写成的。
在vtk官网上下载相应的source, vtkdata, vtklargedata, Documentation。
下载cmake, windows下需要下载visual studio.
打开vtk之后,在where is the source code选择下载source的文件夹, where to build the binaries为利用cmake生成的工程所存放的文件夹。
vtk中还有一项cmake_install_prefix指定vtk安装路径,即最后vtk安装在哪儿。其他的选择默认即可。
完成后会生成visual studio工程。打开vtk.sln文件,然后选择build all右击并选择build。最后如果要安装vtk,选择install工程右击并选择Build。最后将安装后的vtk的bin目录添加到系统环境变量path中。
至此vtk安装完毕。
新建一个TestVTKInstall文件夹,在此目录下新建一个CMakeLists.txt文件,内容如下:
cmake_minimum_required(VERSION 3.17)
project(TestVTKInstall)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
add_executable(${PROJECT_NAME} TestVTKInstall.cpp)
target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
然后在TestVTKInstall
目录下新建一个TestVTKInstall.cpp
文件,内容为:
#include "vtkRenderWindow.h"
#include "vtkSmartPointer.h"
int main()
{
vtkSmartPointer<vtkRenderWindow> renWin= vtkSmartPointer<vtkRenderWindow>::New();
renWin->Render();
std::cin.get();
return 0;
}
完成后打开cmake, where is the source code选择TestVTKInstall目录, where to build the binaries选择TestVTKInstall\build
目录,然后选择点击configure,再点击generate。
上面的步骤会在TestVTKInstall\build目录下生成vs工程。打开上面的步骤会在TestVTKInstall.sln,然后将下面的上面的步骤会在TestVTKInstall设为启动项目,编译运行即可。
注意vtk中每个类的构造函数都定义为保护成员,因此只能用以下方法定义对象:
vtkRenderWindow *renWin = vtkRenderWindow::New();
如果采用:
vtkRenderWindow *renWin = new vtkRenderWindow()
则会提示如下的错误:
error C2248: vtkClassExample:: vtkClassExample: cannot access protected memberdeclared in class vtkClassExample.
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
指定cmake版本,version为必须的关键字,且全部大写。
project(project_name[cxx][c][java])
指定工程名称,后续可使用${PROJECT_NAME}
来获取该名称。
find_package( [version] [EXACT] [QUIET] [[REQUIRED|COMPONENTS][components...]][NO_POLICY_SCOPE])
寻找所需的外部工程,即VTKConfig.cmake
所在文件夹,一般为C:\Program Files (x86)\VTK\lib\cmake\vtk-8.2
.
include( [OPTIONAL][RESULT_VARIABLE][NO_POLICY_SCOPE])
指定载入一个文件或者模块,例如${VTK_USE_FILE}
在vtk安装目录(vtk/lib/cmake/vtk-8.2/VTKConfig.cmake)下的定义为:
set(VTK_USE_FILE "${VTK_CMAKE_DIR}/UseVTK.cmake")
include (${VTK_USE_FILE})
命令就是包含UseVTK.cmake
文件。
add_executable( [WIN32][MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 source2 sourceN)
定义该工程会生成一个名为name
的可执行文件,相关的源文件可用source1 source
,中间用空格隔开。如果文件太多,可用set先定义:
set(src source1 source2 source)
add_executable(project_name ${src})
target_link_libraries( [item1[item2 [...]]] [[debug|optimized|general]- ]
指定生成可执行文件时需要链接哪些文件。
vtk由两部分组成:可视化管线;渲染引擎
可视化管线用于获取或者创建数据,处理数据以及把数据写入文件或者把数据传递给渲染引擎进行渲染。数据对象(Data Object)、处理对象(Process Object)和数据流方向(Direction of Data Flow)是可视化管线的三个基本要素。每个VTK程序都会有可视化管线存在:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gw2V0WA1-1609141449576)(pics/9.png)]
其中上面的两部分就是可视化管线,下面的三部分属于渲染引擎的部分。该管线首先生成一个椎体数据,然后通过mapper将生成的多边形数据送入渲染引擎。
通常数据生成后一般先经过多个filter经过处理之后再通过mapper送入渲染引擎。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxsGRpKk-1609141449581)(pics/10.png)]
Source没有输入,但至少有一个输出;Filter可以有一个或多个输入,产生一个或多个输出;Mapper接受一个或多个的输出,但没有输出,写文件的Writer(如vtkBMPWriter)可以看作是Mapper,负责把数据写入文件或者流(Stream)中,因此,Mapper是可视化管线的终点,同时也是可视化管线和渲染引擎(有时也称之为图形管线)的桥梁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkZJrFOG-1609141449583)(pics/11.png)]
可视化管线里各个模块的连接是通过接口SetInputConnection()和GetOutputPort()来完成的。Filter概括起来有以下三种类型(图4.12):单个输入,产生单个输出;多个输入,产生单个输出,但输出的数据可有多种用途,比如,我们读入数据以后,可以对其作等值面提取,另外还可以针对读入的数据生成轮廓线(Outline);第三种Filter是单个输入,产生多个输出。
使用SetInputConnection()和GetOutputPort()连接可视化管线时,还要求连接的两部分之间的数据类型必须一样。由于管线是运行时才执行的,如果连接的两部分类型不匹配,程序运行时就会报错。
如果在连接VTK的可视化管线时,不是一个Filter或者source输出连接到另一个Filter的输入,而是某个数据作为另外一个Filter的输出,这种情况下,可以写成如下形式:
filter->SetInputConnection(dataset->GetProducerPort());
VTK采用一种叫做“惰性赋值”(LazyEvaluation)的方案来控制管线的执行,惰性赋值是指根据每个对象的内部修改时间来决定什么时候执行管线,只有当你或者程序发出“请求数据”时,管线才会被执行(前面提到vtkObject里有一个重要的成员变量MTime,管线里的每个从vtkObject派生的类的对象都会跟踪自己的内部修改时间,当遇到“请求数据”时,该对象会比较这个修改时间,如果发现修改时间发生了改变,对象就会执行。)。换言之,VTK是采用命令驱动(Demand Driven)的方法来控制管线的执行,这种方法的好处是,当对数据对象作了更改时,不必立即作计算,只有当发出请求时才开始处理,这样能最小化计算所需的时间,以便更流畅地与数据进行交互。
通常,我们不用显性地去调用Update()函数,因为在渲染引擎的最后,当我们调用Render()函数的时候,Actor就会收到渲染请求,接着Actor会请求Mapper给它发送数据,而Mapper又会请求上一层的Filter的数据,Filter最后去请求Source给它数据,于是,整条管线就被执行。除非像上面的代码段里列出的,读入数据以后,中间想要输出某些信息,在得到这些信息之前,你就应该显性地调用Update()函数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R1iRqNZU-1609141449586)(pics/12.png)]
##5. vtk各个部件之间的关系
下面通过一个例子来解释:
#include "vtkSmartPointer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkCylinderSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkActor.h"
#include "vtkPNGReader.h"
#include "vtkTexture.h"
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
int main()
{
vtkSmartPointer<vtkCylinderSource> cylinder = vtkSmartPointer<vtkCylinderSource>::New();
cylinder->SetHeight(3.0);
cylinder->SetRadius( 1.0 );
cylinder->SetResolution(6);
vtkSmartPointer<vtkPolyDataMapper> cylinderMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
vtkSmartPointer<vtkActor> cylinderActor = vtkSmartPointer<vtkActor>::New();
cylinderActor->SetMapper(cylinderMapper);
vtkSmartPointer<vtkPNGReader> pngReader = vtkSmartPointer<vtkPNGReader>::New();
pngReader->SetFileName("5.png");
vtkSmartPointer<vtkTexture> texture = vtkSmartPointer<vtkTexture>::New();
texture->SetInputConnection(pngReader->GetOutputPort());
texture->InterpolateOn();
cylinderActor->SetTexture(texture);
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(cylinderActor);
renderer->SetBackground(0.1, 0.2, 0.4);
vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
renWin->AddRenderer(renderer);
renWin->SetSize(300, 300);
vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New();
iren->SetRenderWindow(renWin);
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
iren->SetInteractorStyle(style);
iren->Initialize();
iren->Start();
return 0;
}
vtkCylinderSource:派生自vtkPolyDataAlgorithm。顾名思义,vtkCylinderSource生成的数据类型就是PolyData(vtkPolyData)的,它主要是生成一个中心在渲染场景原点的柱体,柱体的长轴沿着Y轴,柱体的高度、截面半径等都可以任意指定。
与source有关的类的继承图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZO6D7Xt-1609141449589)(pics/17.png)]
vtkPolyDataMapper:渲染多边形几何数据(vtkPolyData),派生自类vtkMapper,将输入的数据转换为几何图元(点、线、多边形)进行渲染。mapper是可视化管线的终点,也是连接可视化管线和渲染引擎的桥梁。
与mapper有关的类的继承图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Ofx0xlR-1609141449591)(pics/18.png)]
vtkActor:派生自vtkProp类,渲染场景中数据的可视化表达是通过vtkProp的子类负责的。比如,本例要渲染一个柱体,柱体的数据类型是vtkPolyData,数据要在场景中渲染时,不是直接把数据加入渲染场景就可以,待渲染的数据是以vtkProp的形式存在于渲染场景中。三维空间中渲染对象最常用的vtkProp子类是vtkActor(表达场景中的几何数据)和vtkVolume(表达场景中的体数据);二维空间中的数据则是用vtkActor2D表达。vtkProp子类负责确定渲染场景中对象的位置、大小和方向信息。Prop依赖于两个对象(Prop一词来源于戏剧里的“道具”,在VTK里表示的是渲染场景中可以看得到的对象。),一个是Mapper(vtkMapper)对象,负责存放数据和渲染信息,另一个是属性(vtkProperty)对象,负责控制颜色、不透明度等参数。
vtk中actor的继承关系如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nQN5ZCQV-1609141449593)(pics/16.png)]
VTK中定义了大量(超过50个)的Prop类,如vtkImageActor(负责图像显示)和vtkPieChartActor(用于创建数组数据的饼图可视化表达)。其中的一些Prop内部直接包括了控制显示的参数和待渲染数据的索引,因此并不需要额外的Property和Mapper对象。vtkActor的子类vtkFollower可以自动的更新方向信息以保持始终面向一个特定的相机。这样无论怎样旋转,三维场景中的广告板(Billboards)或者文本都是可见的。vtkActor的子类vtkLODActor可以自动改变自身的几何表达来实现需要达到的交互帧率。vtkProp3D的子类vtkLODProp3D则是通过从许多Mapper(可以是体数据的Mapper和几何数据的Mapper集合)中进行选择来实现交互。vtkAssembly建立Actor的等级结构以便在整个结构平移、旋转或者缩放时能够更合理的控制变换。
vtkActor::SetMapper()——设置生成几何图元的Mapper。即连接一个Actor到可视化管线的末端(可视化管线的末端就是Mapper)。
vtkRenderer:负责管理场景的渲染过程。组成场景的所有对象包括Prop,照相机(Camera)和光照(Light)都被集中在一个vtkRenderer对象中。一个vtkRenderWindow中可以有多个vtkRenderer对象,而这些vtkRenderer可以渲染在窗口中不同的矩形区域中(即视口),或者覆盖整个窗口区域。
vtkRenderer::AddActor() ——添加vtkProp类型的对象到渲染场景中。
vtkRenderer::SetBackground() ——该方法是从vtkRenderer的父类vtkViewport继承的,用于设置渲染场景的背景颜色,用R、G、B的格式设置,三个分量的取值为0.0~ 1.0。(0.0,0.0, 0.0)为黑色,(1.0,1.0, 1.0)为白色。除了可以设置单一的背景颜色之外,还可以设置渐变的背景颜色,vtkViewport::SetBackground2()用于设置渐变的另外一种颜色,但是要使背景颜色渐变生效或者关闭,必须调用以下的方法:
与renderer有关的类继承图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRqCHUzD-1609141449594)(pics/19.png)]
vtkRenderWindow:将操作系统与VTK渲染引擎连接到一起。不同平台下的vtkRenderWindow子类负责本地计算机系统中窗口创建和渲染过程管理。当使用VTK开发应用程序时,你只需要使用平台无关的vtkRendererWindow类,运行时,系统会自动替换为平台相关的vtkRendererWindow子类。比如,Windows下运行上述的VTK程序,实际创建的是vtkWin32OpenGLRenderWindow(vtkRenderWindow的子类)对象。vtkRenderWindow中包含了vtkRenderer集合、渲染参数,如立体显示(Stereo),反走样,运动模糊(Motion Blur)和焦点深度(Focal Depth)等。
vtkRenderWindow::AddRenderer() ——加入vtkRenderer对象。
vtkRenderWindow::SetSize() ——该方法是从vtkRenderWindow的父类vtkWindow继承过来的,用于设置窗口的大小,以像素为单位。
与renderwindow有关的类继承图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M2dVh5Hm-1609141449595)(pics/20.png)]
vtkRenderWindowInteractor:提供平台独立的响应鼠标、键盘和时钟事件的交互机制,通过VTK的Command/Observer设计模式将监听到的特定平台的鼠标、键盘和时钟事件交由vtkInteractorObserver或其子类,如vtkInteractorStyle进行处理。vtkInteractorStyle等监听这些消息并进行处理以完成旋转、拉伸和放缩等运动控制。vtkRenderWindowInteractor自动建立一个默认的3D场景交互器样式(Interactor Style):vtkInteractorStyleSwitch,当然你也可以选择其他的交互器样式,或者是创建自己的交互器样式。在本例中,我们就是选择了其他的交互器样式来替代默认的:vtkInteractorStyleTrackballCamera。
vtkRenderWindowInteractor::SetRenderWindow()——设置渲染窗口,消息是通过渲染窗口捕获到的,所以必须要给交互器对象设置渲染窗口。
vtkRenderWindowInteractor::SetInteractorStyle()——定义交互器样式,默认的交互样式为vtkInteractorStyleSwitch。
vtkRenderWindowInteractor::Initialize() ——为处理窗口事件做准备,交互器工作之前必须先调用这个方法进行初始化。
vtkRenderWindowInteractor::Start() ——开始进入事件响应循环,交互器处于等待状态,等待用户交互事件的发生。进入事件响应循环之前必须先调用Initialize()方法。
与renderwindowinteractor有关的类继承图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gk8IErSW-1609141449597)(pics/21.png)]
下面通过一个类比进行解释:
当我们去看舞台剧的时候,我们坐在台下,展现在我们面前的是一个舞台,舞台上有各式的灯光,各样的演员。演员出场的时候肯定是会先化妆,有些演员可能会打扮成高富帅,有些演员可能会化妆成白富美。观众有时还会与台上的演员有一定的互动。
整个剧院就好比VTK程序的渲染窗口(vtkRenderWindow);舞台就相当于渲染场景(vtkRenderer);而那些高富帅、白富美就是我们程序中的Actor(有些文献翻译成“演员”,有些翻译成“角色”,这里我们不作翻译);台上的演员与台下观众的互动可以看成是程序的交互(vtkRenderWindowInteractor);演员与观众的互动方式有很多种,现场的观众可以直接上台跟演员们握手拥抱,电视机前的可以发短信,电脑、移动终端用户等可以微博关注、加粉等等,这就好比我们程序里的交互器样式(vtkInteractorStyle);舞台上的演员我们都能一一分辨出来,不会把高富帅弄混淆,是因为他们化的妆、穿的服饰都不一样,这就相当于我们程序里vtkActor的不同属性(vtkProperty);台下观众的眼睛可以看作是vtkCamera,前排的观众因为离得近,看台上演员会显得比较高大,而后排的观众看到的会显得小点,每个观众看到的东西在他的世界里都是唯一的,所以渲染场景Renderer里的vtkCamera对象也是只有一个;舞台上的灯光可以有多个,所以渲染场景里的vtkLight也存在多个,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7qcxgno-1609141449599)(pics/13.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vkuoGfOC-1609141449600)(pics/14.png)]
vtk渲染引擎各个部分的关系如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbO6HGWm-1609141449602)(pics/15.png)]
##6. vtk的数据结构
可视化数据具有零维、一维、二维、三维等任意维度。如,零维的数据表现为点,一维数据表现为曲线,二维数据表现为曲面,三维数据表现为体等。数据的维度决定了数据可视化的方法,如,对于二维的数据,可以将数据存储到一个矩阵,然后再采用针对二维数据的可视化方法进行可视化(如等高图)。
在VTK中,数据一般以数据对象(Data Object,对应VTK里的类vtkDataObject)的形式表现,是VTK里可视化数据最一般的表达形式。数据对象是数据的集合,数据对象表现的数据是可以被可视化管线处理的数据,只有数据对象被组织成一种结构(Structure)后,才能被VTK提供的可视化算法处理。
vtk数据相关的类的继承关系如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8SfchpX-1609141449604)(pics/22.png)]
数据对象被组织成一种结构并且被赋予相应的属性值时就形成数据集(Dataset)。VTK里与数据集对应的类是vtkDataSet,该类从vtkDataObject直接派生。vtkDataSet由两个部分组成,即组织结构(Organizing Structure)以及与组织结构相关联的属性数据(Attribute Data)。vtkDataSet是一个抽象基类,结构的实现及表达由其具体的子类来完成。
vtkDataSet的组织结构由拓扑结构(Topology)和几何结构(Geometry)两部分组成。拓扑结构描述了物体的构成形式,几何结构描述了物体的空间位置关系。换言之,点数据(Point Data)所定义的一系列坐标点构成了vtkDataSet(数据集)的几何结构;点数据的连接(点的连接先形成单元数据(Cell Data),由单元数据再形成拓扑)就形成了数据集的拓扑结构。比如,我们想要在屏幕上显示一个三角形,首先我们必须定义三角形三个点的坐标(即Point Data,记三个点为P1, P2和P3),然后将这三个点按照一定的顺序连接起来(P1-P2-P3,或者是P3-P2-P1的顺序),这三个点定义了数据集的几何结构,它们的连接就构成了数据集的拓扑结构。亦即,点数据(Point Data)定义数据集的几何结构,单元数据(Cell Data)定义数据集的拓扑结构,要形成完整的数据集,必须有几何和拓扑两种结构。
关于拓扑、几何结构以及属性数据的更多解释:拓扑结构具有几何变换不变性。例如,说一个多边形是三角形,即指其拓扑结构,而给定的每个点的坐标,则为其几何结构。几何结构是一种空间描述,与空间变换有紧密联系,常见的变换有旋转、平移和缩放。属性数据是对拓扑结构和几何结构信息的补充,属性数据可以是某个空间点的温度值,也可以是某个单元的质量之类的。
接下来创建一个三角形,并定义几何结构和拓补结构:
#include
#include
#include
#include
#include
int main(int argc, char*argv[])
{
//创建点的坐标
double X[3] = { 1.0, 0.0, 0.0 };
double Y[3] = { 0.0, 0.0, 1.0 };
double Z[3] = { 0.0, 0.0, 0.0 };
//创建点数据以及在每个点坐标上加入顶点(Vertex)类型的单元l
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> vertices = vtkSmartPointer<vtkCellArray>::New();
for (unsigned int i = 0; i < 3; ++i)
{
//定义用于存储点索引的中间变量,vtkIdType就相当于int、long等类型
vtkIdType pid[1];
//把每个点坐标加入到vtkPoints中,InsertNextPoint()返回加入的点的索引号,
pid[0] = points->InsertNextPoint(X[i], Y[i], Z[i]);
//在每个坐标点上分别创建一个顶点,顶点是单元(Cell)里的一种类型
vertices->InsertNextCell(1, pid);
}
//创建vtkPolyData对象
vtkSmartPointer<vtkPolyData> polydata = vtkSmartPointer<vtkPolyData>::New();
//指定数据集的几何结构(由points指定),以及数据集的拓扑结构(由vertices指定)
polydata->SetPoints(points);
polydata->SetVerts(vertices);
//将生成的数据集写到TriangleVerts.vtk文件里,保存在工程当前目录下
vtkSmartPointer<vtkPolyDataWriter> writer = vtkSmartPointer<vtkPolyDataWriter>::New();
writer->SetFileName("TriangleVerts.vtk");
writer->SetInputData(polydata);
writer->Write();
return 0;
}
该程序实例化了一个vtkCellArray的对象,点数据(Point Data)定义数据集的几何结构,单元数据(Cell Data)定义数据集的拓扑结构。vtkCellArray类型的对象vertices就是用来指定数据集polydata的拓扑结构,而polydata的几何结构则是由points来定义的。该程序中定义的数据集的拓扑结构是零维的点,即单元类型是Vertex。
下面来定义一个有边的三角形:
#include
#include
#include
#include
#include
#include
int main(int argc, char*argv[])
{
//创建三个坐标点
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
points->InsertNextPoint(1.0, 0.0, 0.0); //返回第一个点的ID:0
points->InsertNextPoint(0.0, 0.0, 1.0); //返回第二个点的ID:1
points->InsertNextPoint(0.0, 0.0, 0.0); //返回第三个点的ID:2
//每两个坐标点之间分别创建一条线
//SetId()的第一个参数是线段的端点ID,第二个参数是连接的点的ID
vtkSmartPointer<vtkLine> line0 = vtkSmartPointer<vtkLine>::New();
line0->GetPointIds()->SetId(0, 0);
line0->GetPointIds()->SetId(1, 1);
//The first 0 is the index of the triangle vertex which is ALWAYS 0-2.
//The second 0 is the index into the point (geometry) array, so this can range from 0-(NumPoints-1)
//i.e. a more general statement is triangle->GetPointIds()->SetId(0, PointId);
vtkSmartPointer<vtkLine>line1 = vtkSmartPointer<vtkLine>::New();
line1->GetPointIds()->SetId(0, 1);
line1->GetPointIds()->SetId(1, 2);
vtkSmartPointer<vtkLine> line2 = vtkSmartPointer<vtkLine>::New();
line2->GetPointIds()->SetId(0, 2);
line2->GetPointIds()->SetId(1, 0);
//创建单元数组,用于存储以上创建的线段
vtkSmartPointer<vtkCellArray> lines = vtkSmartPointer<vtkCellArray>::New();
lines->InsertNextCell(line0);
lines->InsertNextCell(line1);
lines->InsertNextCell(line2);
vtkSmartPointer<vtkPolyData> polydata = vtkSmartPointer<vtkPolyData>::New();
//将点和线加入到数据集中,前者定义数据集的几何结构,后者定义拓扑结构
polydata->SetPoints(points);
polydata->SetLines(lines);
vtkSmartPointer<vtkPolyDataWriter> writer = vtkSmartPointer<vtkPolyDataWriter>::New();
writer->SetFileName("TriangleLines.vtk");
writer->SetInputData(polydata);
writer->Write();
return 0;
}
该程序除了定义数据集的几何结构(由points定义),还定义了相应的拓扑结构,该拓扑结构是一维的线拓扑结构,它们都是保存在由类vtkCellArray所实例化的对象里,除了零维的点、一维的线等类型的单元以外,VTK还定义了其他类型的单元。
数据集由一个或多个单元组成,VTK支持线性和非线性类型的单元。一系列有序的点按指定类型连接所定义的结构就是单元(Cell),单元是可视化系统的基础。这些点的连接顺序通常也称为顶点列表(Connectivity List);所指定的类型定义了单元的拓扑结构,而点的坐标定义了单元的几何结构。单元是由单元的类型(如六面体)和构成单元的顶点列表两部分构成。
通常我们用数学符号 C i C_i Ci来表示单元,换言之,单元就是一个有顺序的点集: C i = { p 1 , p 2 , … , p n } C_i = \{p_1, p_2,…, p_n\} Ci={p1,p2,…,pn},其中 p i ∈ P p_i∈P pi∈P, P P P就是该有序的点集。单元的类型决定了点集里点的顺序,或者说单元的拓扑;而定义单元的点的个数就是该单元的大小(Size)。
单元的拓扑维度除了三维,还可以是零维、一维、二维等,如零维的顶点(Vertex)、一维的线(Line)以及二维的三角形(Triangle)。单元可以是基本类型或者基本类型的组合,基本类型是指不可再分的单元,组合类型是由基本类型组合而成。
单元类型的线性与非线性的划分主要是以插值函数为依据的,VTK里单元的类型定义在vtkCellType.h文件里。
线性单元,采用的是线性或者常量插值函数。线性的单元类型有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySFThcvV-1609141449606)(pics/23.png)]
非线性单元有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3g6fZa7T-1609141449607)(pics/24.png)]
vtkDataSet类的继承图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AmNifUFt-1609141449608)(pics/25.png)]
常见的数据集类型有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XDsksmUU-1609141449610)(pics/26.png)]
####6.5.1 vtkImageData
vtkImageData类型的数据是按规则排列在矩形方格中的点和单元的集合。如果数据集的点和单元排列在平面(二维)上,称此数据集为像素映射(Pixmap)、位图或图像,由vtkPixel单元组成;如果排列在层叠面(三维)上,则称为体(Volume),由vtkVoxel单元组成。vtkImageData是由一维的线、二维的像素或三维的体素组成,vtkImageData在几何结构及拓扑结构都是规则的,因此每个点的位置可隐式地表达,只需要知道vtkImageData数据的维数、起始点的位置和相邻点之间的间隔,就可以计算出每个点的空间位置。数据维数用一个三元组 ( n x , n y , n z ) (n_x, n_y, n_z) (nx,ny,nz)来表示,分别表示在X、Y和Z方向上点的个数。vtkImageData数据集的点的个数一共是 n x × n y × n z n_x×n_y×n_z nx×ny×nz,单元的个数一共是 ( n x − 1 ) × ( n y − 1 ) × ( n z − 1 ) (n_x-1)×(n_y-1)×(n_z-1) (nx−1)×(ny−1)×(nz−1)。
vtkImageData类型的数据集在图像处理和计算机图形学领域应用都非常广泛,而医学图像则会频繁产生体素数据,如CT(ComputedTomography)和MRI(Magnetic ResonanceImaging)。
####6.5.2 vtkPolyData
多边形数据集vtkPolyData由顶点(Vertex)、多顶点(Polyvertex)、线(Line)、折线(Polyline)和三角条带(Triangle Strip)等单元构成,多边形数据是不规则结构的,并且多边形数据集的单元在拓扑维度上有多种类型,如图6.11-e所示。多边形数据是数据、算法和高速计算机图像学的桥梁。
顶点、线和多边形构成了用来表达0、1和2维几何图形的基本要素的最小集合,同时用多顶点、折线和三角形条带单元来提高效率和性能,特别是三角形条带,用一个三角形条带表达N个三角形只需要用N+2个点,但是用传统的表达方法需要用3N个点,而且大多数图形库渲染三角形条带的速度比直接渲染三角形要快很多。
###6.6 数据集的存储和表达
VTK中对绝大多数的数据对象的内存分配采用连续内存,连续内存的结构可被快速地创建、删除和遍历,称为Data Array (数据数组),用类vtkDataArray
实现。
数据数组的访问是基于索引的,从零开始计数。我们以vtkFloatArray类来说明如何在VTK中实现连续内存的数据数组。如图6.13所示,变量Array是一个指向浮点型数组的指针,数组的长度由变量Size指定,由于数组的长度是动态地增加的,所以当存储数据的数组长度超出指定的长度时,会自动触发Resize()操作来调整数组的长度,使数组的长度变成原来的两倍,MaxId是一个整型的偏移量,用来定义最后一个插入的数据的索引。如果没有数据插入,MaxId等于-1,否则,MaxId的值介于0到Size之间,即0≤MaxId
可视化数据有各种各样的类型,如简单的浮点型、整型、字节型和双精度型等,再复杂一点的类型,如特征字符串和多维标识符等。VTK通过对数据对象的抽象(AbstractData Object)提供运行时解决方案以及使用C++编译时动态绑定的方法(模板类)来解决这个问题的。如下图所示,vtkDataArray是一个抽象基类,其子类实现特定类型的数据数组及相关操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuUGwXKK-1609141449613)(pics/28.png)]
vtkDataArray及其子类是建立VTK数据对象(DataObject)的基础。以vtkPolyData为例,该类含有存储几何结构的数据数组(在vtkPoints类内),拓扑结构(存储在vtkCellArray内)和属性数据(vtkField,vtkPointData和vtkCellData类内)等同样有数据数组。vtkDataObject是一种通用的可视化数据的表达形式,内部封装了与可视化管线的执行相关的变量和方法,在vtkDataObject内部有一个vtkFieldData(场数据)的实例,负责对数据的表达。如图6.16所示,场数据(FieldData)可以看作是数组的数组,数组里的每一个元素都一个数组,数组的类型、长度、元组的大小和名称等都可以各不相同。VTK里的可视化算法很少有直接对vtkDataObject作处理,大多数的算法更关心的是待处理数据的组织结构(OrganizingStructure)等信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeCtjZLt-1609141449615)(pics/29.png)]
vtkPointData和vtkCellData类内)等同样有数据数组。vtkDataObject是一种通用的可视化数据的表达形式,内部封装了与可视化管线的执行相关的变量和方法,在vtkDataObject内部有一个vtkFieldData(场数据)的实例,负责对数据的表达。如图6.16所示,场数据(FieldData)可以看作是数组的数组,数组里的每一个元素都一个数组,数组的类型、长度、元组的大小和名称等都可以各不相同。VTK里的可视化算法很少有直接对vtkDataObject作处理,大多数的算法更关心的是待处理数据的组织结构(OrganizingStructure)等信息:
[外链图片转存中…(img-IeCtjZLt-1609141449615)]