基于 VTK 的数据显示工具 miniWiki

VTK

在不同的语境下,VTK (Visualization ToolKit) 可能表示:

面向对象设计

VTK 类名

VTK 中的所有类名都是以 vtk 起始的。为节省空间以突出继承关系,下图中省去了该字段(即 DataSet 应补全为 vtkDataSet)。

对象模型

在 VTK 中,几乎所有的类都是 vtkObject 的派生类。

所有对象 (object) 都是动态的,在 C++ 中,它们都必须由所属类的 New() 方法创建,并由所属类的 ` Delete()` 方法销毁:

vtkObjectBase* obj = vtkExampleClass::New();  // 创建
otherObject->SetExample(obj);                 // 使用
obj->Delete();                                // 销毁

New() 方法返回指向动态数据的原始指针 (raw pointer)。 如果(在离开创建它的作用域前)忘记调用 Delete(),则会造成内存泄漏 (memory leak)。 VTK 提供了一种基于引用计数 (reference count)智能指针 (smart pointer) 来管理动态对象:

auto obj = vtkSmartPointer<vtkExampleClass>::New();  // 创建
otherObject->SetExample(obj);                        // 使用
// obj->Delete();                                    // 销毁(自动完成)

API

在线文档只给出了 C++ 版的 API。 解释型 (interpreted) 语言(如 Python)的语法机制没有 C++ 丰 (fan) 富 (suo)获 (cai) 得 (ce) 相应的 API 需要对 C++ 版作适当的压缩

计算机图形学基础

计算机图形学借用了一些电影行业的术语。场景 (scene) 由以下要素构成:

  • 表示被显示的数据或对象的演员 (actor)
  • 发出光线照亮演员的光源 (light source)
  • 摄取演员在底片上投影的镜头 (camera)

在 VTK 中,上述概念由以下类实现:

  • vtkRenderWindow 对象负责管理render window (渲染窗口)渲染器 (renderer)
    • 不同操作系统有不同的视窗实现方式。
    • VTK 隐藏了这些与具体系统相关的细节。
  • vtkRender 对象负责协调演员、光源、镜头以生成图像。
    • 每个 vtkRender 对象都必须关联到一个 vtkRenderWindow 对象,且对应于其中的一个由归一化坐标定义的长方形视窗 (viewport)
    • 若某个 vtkRenderWindow 对象只关联了一个 vtkRender 对象,则相应视窗的归一化坐标为 (0,0,1,1),即占据整个渲染窗口。
  • vtkLight 对象用于照亮其所处的场景。
    • 可调参数包括位置、方向、颜色、亮度、开关状态。
  • vtkCamera 对象用于摄取场景。
    • 可调参数包括位置、方向、焦点、前(后)剪切平面、视场。
  • vtkActor 对象表示场景中的演员,即被显示的数据或对象。
  • vtkMapper 对象是数据的内部表示与显示模块之间的接口。

示例:

可视化管道

数据可视化 (data visualization) 包括以下两部分:

  • 数据变换 (data transformation) 是指由原始数据 (original data) 获得图形基元 (graphics primitives) 乃至计算机图像 (computer images) 的过程。描述数据变换过程的模型称为功能模型 (functional models)
  • 数据表示 (data representation) 包括用于存储数据的数据结构 (data structures) 及用于显示数据的图形基元 (graphics primitives)。描述数据表示的模型称为对象模型 (object models)

可视化管道 (visualization pipeline)可视化网络 (visualization network) 由以下对象构成:

  • 数据对象 (data objects) 用于表示信息。
  • 操作对象 (operation objects)处理对象 (process objects) 用于操作数据,可分为
    • 源 (source):无输入、有输出。
    • 汇 (sink)映射器 (mapper):有输入、无输出。
    • 滤镜 (filter):有输入、有输出,以端口 (port) 与其他操作对象交互。

数据表示(文件格式)

详见《VTK User’s Guide》的《VTK File Formats》一节,及《The Visualization Toolkit》的《Basic Data Representation》一章。

传统 VTK 格式

这种格式的定义较为简单,对于简单的应用,可以独立于 VTK 程序库实现一套 IO 模块。

现代 XML 格式

这是一种支持随机访问 (random access)并行读写 (parallel IO) 的文件格式,以 .vt[irsupm] 为扩展名:

扩展名 数据集类型
vti vtkImageData
vtr vtkRectlinearGrid
vts vtkStructuredGrid
vtu vtkUnstructuredGrid
vtp vtkPolyData
vtm vtkMultiBlockDataSet

这种格式的定义比传统 VTK 格式复杂,建议直接调用 VTK 程序库提供的 API

如果在本地部署 VTK 程序库有困难(无网络、无权限),可以考虑使用 PyEVTK。 它完全用 Python & Cython 实现,因此不依赖于 VTK 程序库。 安装后即可在本地 Python 程序中 import 该模块,具体用法可以参照 src/examples 目录下的示例。

Python 示例

read.pywrite.py 演示了如何用 vtk 模块提供的 API 读写 vtkUnstructuredGrid。 ⚠️ 要运行该示例,必须先在本地部署 VTK 模块(可直接运行 pip install vtk 安装)。

运行以下命令

mkdir build  # 在 write.py 所属目录下,创建 build 目录
cd build
python3 ../write.py 

会在 build 目录下生成四个文件:

ugrid_demo_ascii.vtk
ugrid_demo_ascii.vtu
ugrid_demo_binary.vtk
ugrid_demo_binary.vtu

读取其中的两个文件:

python3 ../read.py 

C++ 示例

源代码仓库的 Examples 目录下有各个 VTK 模块的示例。

这里将 Examples/IO/Cxx⁩/DumpXMLFile.cxx 简化为 read.cpp

  • 手动构建较为繁琐,且依赖于本地 VTK 的安装位置(假设:头文件位于 /usr/local/include/vtk-8.2,库文件位于 /usr/local/lib)及版本号:
    mkdir build  # 在 read.cpp 所属目录下,创建 build 目录
    cd build
    c++ -c ../read.cpp -I/usr/local/include/vtk-8.2 -std=c++17
    c++ -o read read.o -L/usr/local/lib -lvtkCommonDataModel-8.2 \
    -lvtkCommonCore-8.2 \
    -lvtkIOLegacy-8.2 \
    -lvtkIOXML-8.2 \
    -lvtkIOGeometry-8.2 \
    -lvtkIOImport-8.2 \
    -lvtksys-8.2
    
  • 推荐使用 CMake 构建,构建选项以跨平台的方式写在 CMakeLists.txt 中,CMake 会自动查找头文件及库文件位置:
    mkdir build  # 在 read.cpp 所属目录下,创建 build 目录
    cd build
    cmake -S .. -B .
    cmake --build .
    ./read *.vtk *.vtu  # 读取在《Python 示例》中生成的文件
    

可视化算法

VTK 中的算法 (algorithm) 是指对数据作变换(以改变数据的表示形式或生成其他数据)的对象。

算法可以按被处理的数据类型来分类:

  • 标量算法,如:用等值线图显式标量场。
  • 向量算法,如:用有向箭头显式向量场。
  • 张量算法,如:提取二阶张量的主分量。
  • 建模算法,其他无法归入以上各类的算法。

标量算法

颜色映射 (color mapping) 是一种常用的标量算法。它将标量值(如:温度值、压力值)映射为颜色值(如:RGB 数组)。该映射通常由以下方式实现:

  • 查询表 (lookup table):选定被显示标量值的有效区间 [s_min, s_max] 及 RGB 数组的长度 n,则标量值 s 映射到 (r[i], g[i], b[i]),其中 i 由以下线性分布关系确定
    i = 0
    if s_min < s:
        if s_max < s:
            i = n - 1
        else:
            i = n * (s - s_min) // (s - s_max)
    
  • 变换函数 (transfer function):只需保证任意 s 都可以被唯一地映射到某个 i,可视为查询表的推广。
from vtk import *
# 创建默认查询表(由红到蓝)
lookup_table = vtkLookupTable()
lookup_table.SetHueRange(0.6667, 0.0)
lookup_table.Build()

图像处理

ParaView

ParaView 是基于 VTK 的 GUI 前端。

启动后,在 Help 列表中有各种本地或在线文档的链接,其中《Getting Started》可用于快速入门,《ParaView Guide》用于系统学习。

可执行程序

paraview

paraview 是基于 Qt 的 GUI 程序。

pvpython

pvpython 是封装了 ParaView 程序库的 Python shell —— 在其中可以使用 Python 自带的功能,也可以像调用其他 Python 模块(包)一样加载 ParaView 模块:

from paraview.simple import *

paraview (GUI) 中的所有操作,几乎都可以在 pvpython (CLI) 中以 Python 指令的形式来完成。这些指令可以被同步记录到 .py 文件中,只需在 paraview (GUI) 中以 Tools → Start Trace 开启记录、以 Tools → Stop Trace 停止记录。

完整 API 列表参见《ParaView’s Python documentation》。

pvbatch

pvbatch 是由 .py 文件驱动的 CLI 程序。

pvserver

pvserver 是运行在远程主机上的 CLI 程序。

简单数据显示

使用 ParaView 主要分三个基本步骤:

  1. 从数据文件中读取 (read) 原始数据。
  2. 对原始数据进行过滤 (filter),提取出感兴趣的信息。
  3. 将提取得到的信息在图形界面上进行渲染 (render)

对于同一个数据文件,第 1 步只需执行 1 次,而第 2、3 步可以执行多次。

动态数据显示

最简单(最通用)的动态数据显示方式,是将每一时间步的数据写入一个对应于该时刻的文件,这样的一个文件对应于动画中的一帧。ParaView 在加载按以下任意一种风格命名的一组 (group) 文件时,会自动将它们识别为一个文件序列 (file series)

fooN.vtk
Nfoo.vtk
foo.vtk.N
foo_N.vtk
foo.N.vtk
N.foo.vtk
foo.vtksN

其中 foo 可以是任意非空字符串,N 为整数编号,扩展名 .vtk 可以替换为任意 VTK 文件格式所对应的扩展名。

远程数据显式

  • 在远程及本地主机上:
  • 在远程主机上:
    • 生成 VTK 数据文件。
    • 启动 pvserver
  • 在本地主机上:
    • 启动 paraview
    • 连接到远程主机(首次连接需要 Add Server),如果 Pipeline Browser 中的 builtin 被替换为远程主机的标识符,则表明连接成功。
    • 像操作本地数据一样,加载并显示远程数据。

Filters

Calculator

# 构造矢量
Velocity = (MomentumX*iHat + MomentumY*jHat + MomentumZ*kHat) / Density
# 构造标量(三维空间)
Pressure = 0.4 * (EnergyStagnationDensity - dot(Velocity, Velocity) * Density / 2)
# 构造标量(一维空间)
Pressure = 0.4 * (EnergyStagnationDensity - MomentumX * MomentumX / Density / 2)