Visualization Toolkit官方开发文档翻译(6)–第五章数据表示

在第4– 可视化管道中,我们开发了可视化过程的实用定义:将信息映射到图形基元。我们看到了这种映射是如何通过一个或多个步骤进行的,每一步都将数据从一种形式或数据表示形式转换为另一种形式。在本章中,我们将研究用于可视化的常见数据形式。目的是让您熟悉这些表格,以便您可以使用本文中提供的工具和技术来可视化您自己的数据。

5.1 简介

为了设计数据的表示方案,我们需要了解我们可能遇到的数据。我们还需要牢记设计目标,这样才能设计出高效的数据结构和访问方法。接下来的两节将解决这些问题。

表征可视化数据

由于我们的目标是可视化数据,显然我们需要了解数据的特征。这些知识将帮助我们创建有用的数据模型和强大的可视化系统。如果没有清楚地了解数据,我们就有可能设计不灵活且有限的可视化系统。下面我们将描述数据的重要特征。这些特征是数据的离散性,无论是规则的还是不规则的,以及它的拓扑维度。

首先,可视化数据是离散的。这是因为我们使用数字计算机来获取、分析和表示我们的数据,并且通常在有限数量的点上测量或采样信息。因此,所有信息都必须以离散形式表示。

考虑可视化简单的连续函数. 如果我们使用传统的数字计算机,我们必须离散化这个方程来操作它所代表的数据(我们忽略了符号/模拟计算机和方法)。例如,要绘制这个方程,我们会在某个区间(例如 (-1,1))中对函数进行采样,然后在一系列离散点处计算函数的值 在这个区间。结果点((x0,y0),(x1,y1),(x2,y2),…(xn,yn))用直线段连接点。因此,我们的(连续)数据由离散采样表示。

由于数据的离散特性,我们对数据值之间的区域一无所知。在我们之前的示例中,我们知道数据是从函数生成的,但是,一般来说,当我们测量甚至计算数据时,我们无法推断点之间的数据值。这带来了一个严重的问题,因为一个重要的可视化活动是确定任意位置的数据值。例如,我们可能会探测我们的数据并期望数据值,即使探测位置没有落在已知点上。

这个问题有一个明显的解决方案:插值。我们假设相邻数据值之间的关系。通常这是一个线性函数,但我们可以使用二次、三次、样条或其他插值函数。第 8 章 – 高级数据表示更详细地讨论了插值函数,但现在只要说插值函数在已知点之间生成数据值就足够了。

可视化数据的第二个重要特征是其结构可以是规则的或不规则的(或者,结构化非结构化)。常规数据在数据点之间具有固有的关系。例如,如果我们在一组均匀分布的点上进行采样,我们不需要存储所有的点坐标,只需要存储区间的开始位置、点之间的间距和点的总数。然后隐含地知道点位置,可以利用它来节省计算机内存。

不规则的数据是不规则的数据。不规则数据的优点是,我们可以在变化快的地方更密集地表示信息,而在变化不太大的地方,我们可以不那么密集地表示信息。因此,不规则数据允许我们创建自适应表示形式,这在计算资源有限的情况下可能是有益的。

将数据表征为规则或不规则允许我们对数据做出有用的假设。正如我们刚才看到的,我们可以更紧凑地存储常规数据。通常,相对于不规则数据,我们还可以更有效地使用规则数据进行计算。另一方面,不规则数据让我们在表示数据时有了更大的自由度,可以表示没有规则模式的数据。

最后,数据具有拓扑维度。在我们的例子中,数据的维度是一,因为我们有单个自变量 x。数据可能具有从 0D 点到 1D 曲线、2D 曲面、3D 体积甚至更高维区域的任何维度。

数据的维度很重要,因为它意味着可视化和数据表示的适当方法。例如,在一维中,我们自然会使用xy图、条形图或饼图,并将数据存储为一维值列表。对于 2D 数据,我们可以将数据存储在矩阵中,并使用变形的曲面图(即高度场 – 参见练习 4.2)将其可视化。

在本章和第 8 章 – 高级数据表示中,我们展示了这些特征:离散、规则/不规则和数据维度如何塑造我们的可视化数据模型。阅读这些章节时,请牢记这些功能。

图5-1

图 5-1。数据集的架构。数据集由具有拓扑和几何属性的组织结构以及与该结构关联的属性数据组成。

 

设计标准

数据可视化涉及与外部数据的接口、映射到内部形式、处理数据以及在计算机显示设备上生成图像。我们提出一个问题:我们应该使用什么形式来表示数据?当然,我们有很多选择。表示的选择很重要,因为它影响与外部数据交互的能力和整个可视化系统的性能。为了决定这个问题,我们使用以下设计标准:

袖珍的。可视化数据往往很大,因此我们需要紧凑的存储方案来最小化计算机内存需求。

高效的。数据必须在计算上是可访问的。我们希望在恒定时间内检索和存储数据(即,与数据大小无关)。这一要求为我们提供了开发时间复杂度为线性或O(n)的算法的机会。

可映射。有两种类型的映射。首先,数据表示需要有效地映射到图形基元。这确保了我们数据的快速、交互式显示。其次,我们必须能够轻松地将外部数据转换为内部可视化数据结构。否则,我们将承受复杂的转换过程或不灵活的软件的负担

最小覆盖。单一的数据表示不能有效地描述所有可能的数据类型。我们也不希望遇到的每种数据类型都有不同的数据表示。因此,我们需要一组最小的数据表示来平衡效率与数据类型的数量。

简单的。应用计算的一个主要教训是简单设计优于复杂设计。简单的设计更容易理解,因此更容易优化。简单的价值怎么强调都不为过。本文中的许多算法和数据表示都高度​​重视这一设计标准。

本章的其余部分描述了基于这些设计标准的常见可视化数据形式。我们的基本抽象是数据对象,是各种具体可视化数据类型的总称,是数据对象的子类。

5.2 数据对象

在 VTK 中发现的最通用的数据形式是数据对象。数据对象可以被认为是没有任何形式的数据集合。数据对象表示由可视化管道处理的数据(参见上一章和图 4-2)。就其本身而言,数据对象几乎没有携带有用的信息。只有当它们被组织成某种结构时,它们才能提供一种我们可以使用可视化算法进行操作的形式。

5.3 数据集

具有组织结构和相关数据属性的数据对象(图 5-11) 形成数据集。数据集是一种抽象形式;我们将结构的表示和实现留给它的具体子类。VTK 中的大多数算法(或过程对象)都在数据集上运行。

该结构有两部分:拓扑几何。拓扑是在某些几何变换下不变的属性集[Weiler86]。这里我们考虑变换:旋转、平移和非均匀缩放。几何是拓扑的实例化,是 3D 空间中位置的规范。例如,说多边形是“三角形”,指定拓扑。通过提供点坐标,我们指定几何。

数据集属性是与几何和/或拓扑相关的补充信息。该信息可能是某个点的温度值或电池的惯性质量。

我们的数据集模型假设结构由单元格组成。单元指定拓扑,而点指定几何。典型的属性包括标量、向量、法线、纹理坐标和张量。

将数据集的结构定义为单元格和点的集合是我们数据离散性的直接结果。点位于数据已知的位置,并且单元允许我们在点之间进行插值。我们将在以下部分中详细描述数据集结构和属性。

5.4 细胞类型

一个数据集由一个或多个单元组成(图 5-2图 5-4)。细胞是可视化系统的基本组成部分。单元格是通过指定类型和点的有序列表来定义的。有序列表,通常称为连通性列表,结合类型规范,隐式定义了单元的拓扑。xyz点坐标定义单元几何。

图 5-3显示一种细胞类型,六面体。有序列表是索引到点坐标列表中的点 ID 序列。这个单元的拓扑结构是隐含的:我们知道 (8,10) 是六面体的 12 条边之一,而 (8,10,22,21) 是它的六个面之一。

在数学上,我们用符号表示一个单元格 那么单元格是一组有序的点C1=p1,p2,…,pnpiP是一组n维点(这里)。使用单元格的点数n就是单元格的大小是使用的所有单元格的集合pi:

U(pi)=Ci:piCi

图5-2

图 5-2。在 VTK 中发现的线性细胞类型。数字定义定义点的顺序。

 

图5-3图 5-3。六面体单元的示例。拓扑由点列表的排序隐式定义。图像的物理生成。

当我们探索数据集的拓扑结构时,“使用”和“使用集”的重要性将在第 8 章 – 高级数据表示中变得显而易见。

尽管我们在三个维度上定义了点,但单元格的拓扑维度可能会有所不同。顶点、线、三角形和四面体分别是嵌入在三维几何空间中的拓扑 0、1、2 和 3-D 单元的示例。细胞也可以是初级的或复合的。复合细胞由一个或多个原代细胞组成,而原代细胞不能分解成其他原代细胞类型的组合。例如,三角形条带由一个或多个紧凑排列的三角形组成。三角形带是一个复合单元,因为它可以分解成三角形,这些三角形是初级单元。

当然有无数种可能的细胞类型。在可视化工具包中,每种细胞类型都是根据应用需要选择的。我们已经看到了一些单元类型:顶点、线、多边形和三角形带(图 3-19)如何用于向图形子系统或库表示几何图形。其他细胞类型如四面体和六面体在数值模拟中很常见。通过本书中的可视化实践,每种细胞类型的实用性将变得显而易见。可视化工具包中发现的细胞类型的描述——包括它们的线性、非线性或其他分类——在以下部分中给出。

线性单元

线性单元的特征是线性或常数插值函数(有关更多信息,请参见第 8 章中的“插值函数”)。结果,一维或更大维的单元以直边为特征。因此,任何边都可以由两个顶点 id 来表征(1,2). 以下是目前在VTK中发现的线性单元。

顶点。顶点是主要的零维单元。它由一个点定义。

多顶点。多顶点是复合零维单元。多顶点由任意排序的点列表定义。

线。该线是主要的一维单元。它由两点定义。沿线的方向是从第一个点到第二个点。

折线。折线是由一条或多条连接线组成的复合一维单元。折线由n+1个点的有序列表定义,其中 n 是折线中的线数。每对点(i, i+1)定义一条线。

三角形。三角形是一个主要的二维单元。三角形由三个点的逆时针排序列表定义。点的顺序使用右手定则指定表面法线的方向。

三角地带三角形带是由一个或多个三角形组成的复合二维单元。定义三角形带的点不必位于平面内。三角形带由n+2个点的有序列表定义,其中n是三角形的数量。点的顺序是这样的,每组三个点(i,i+1,i+2 ) 与0一世n定义一个三角形。

四边形。四边形是主要的二维单元。它由平面内四个点的有序列表定义。四边形是凸的,它的边不能相交。这些点围绕四边形逆时针排列,使用右手定则定义表面法线。

像素。像素是由四个点的有序列表定义的主要二维单元。该单元在拓扑上等效于添加了几何约束的四边形。像素的每个边缘都垂直于其相邻边缘,并且平行于坐标轴xyz之一。因此,像素的法线也平行于坐标轴之一。

定义像素的点的顺序与四边形单元不同。这些点按轴坐标递增的方向排列,从x开始,然后是 y,然后是z。像素是四边形的一种特殊情况,用于提高计算性能。

一个重要的注意事项是这里给出的像素单元的定义与通常的像素定义不同。通常,像素被认为是图像中的常数值“图片元素”(参见第 3 章中的 [“Graphics Hardware”]/VTKBook/03Chapter3/#39-graphics-hardware )。这里给出的定义意味着四个图像元素形成像素单元的四个角点。我们通常使用术语像素来描述像素单元,但该术语的含义会因上下文而异。

多边形。多边形是一个主要的二维单元。多边形由位于平面中的三个或更多点的有序列表定义。多边形法线是通过使用右手法则对其点进行逆时针排序来隐式定义的。

多边形可能是非凸的,但可能没有内部环,并且它不能自相交。多边形有n条边,其中n是多边形中的点数。

四面体。四面体是一个主要的三维单元。四面体由四个非平面点的列表定义。四面体有六个边和四个三角形面,如图所示图 5-2.

六面体。六面体是一个主要的三维单元,由六个四边形面、十二个边和八个顶点组成。六面体由八个点的有序列表定义,如图所示图 5-2. 面和边不得与任何其他面和边相交,并且六面体必须是凸面。

体素。体素是一个主要的三维单元。体素在拓扑上等效于具有附加几何约束的六面体。体素的每个面都垂直于坐标xyz轴之一。定义点列表按坐标值递增的方向排序,如图图 5-2. 体素是六面体的一种特殊情况,用于提高计算性能。

与像素类似,我们对体素单元的定义不同于术语体素的传统定义。通常,体素被称为恒定值的“体积元素”。使用我们的定义,八个体积元素形成体素单元的八个角点。我们通常使用术语体素来描述体素单元,但该术语的含义会因上下文而异。

楔。楔是一个主要的三维单元,由三个四边形面、两个三角形面、九条边和六个顶点组成。楔形由六个点的有序列表定义,如图所示图 5-2. 面和边不得与任何其他面和边相交,并且楔形必须是凸的。

金字塔。金字塔是一个主要的三维单元,由一个四边形面、四个三角形面、八条边和五个顶点组成。金字塔由五个点的有序列表定义,如图所示图 5-2. 定义四边形基平面的四个点必须是凸的;第五个顶点不得与基点共面。

五角棱镜。五角棱镜是一个主要的三维单元,由五个四边形面、两个五边形面、十五条边和十个顶点组成。五棱柱由十个点的有序列表定义,如图所示图 5-2. 面和边不得与任何其他面和边相交,并且五边形必须是凸的。

六角棱镜。六角棱柱是一个主要的三维单元,由六个四边形面、两个六边形面、十八条边和十二个顶点组成。六角棱柱由十二个点的有序列表定义,如图所示图 5-2. 面和边不得与任何其他面和边相交,并且六边形必须是凸的。

非线性类型

在数值分析中通常使用非线性单元,即使用非线性基函数的单元公式。这些基函数通常由多项式的组合形成。非线性单元提供更精确的插值函数(有关更多信息,请参阅第 8 章中的“插值函数”)和更好的近似曲线几何形状。然而,可能的非线性基函数的数量是无限的,这对任何可视化系统都构成了组合问题(即,不可能实现所有非线性单元类型)。为了解决这个问题,VTK 采取了双重方法。首先,VTK 直接支持具有二次插值函数的非线性单元类型(参见图 5-4)。这样的单元是通过添加中间边缘节点构建的,偶尔还有中间面和内部节点,需要扩展连接列表以反映这些额外条目的添加。其次,VTK 有一个复杂的单元适配器框架,使用户能够将任何基函数连接到 VTK,只要基函数可以在第一个参数坐标系中唯一地表征。(注意:我们将在第 8 章 – 高级数据表示中更详细地描述单元适配器框架。)

图5-4

图 5-4。VTK 中发现的非线性细胞类型。

 

线性单元和非线性单元之间的一个显着区别是它们通过各种可视化算法进行渲染和操作的方式。线性单元很容易转换为线性图形基元,然后由图形库处理。另一方面,非线性单元通常在图形库中没有直接支持。(一个例外是非均匀有理 B 样条或 NURBS 系列。甚至这些通常由图形库细分为线性基元。)因此,可视化系统必须对非线性单元进行特殊处理。一些可能性包括:

  1. 将非线性单元格细分为线性单元格,然后对线性单元格进行操作。

  2. 开发自定义渲染和可视化算法以直接在非线性单元上进行操作。

  3. 在图形库中编写自定义渲染操作。这些问题是可视化研究中的活跃话题[Schroeder05]. 在 VTK 中,目前采用曲面细分方法,因为一旦曲面细分,单元格就可以通过现有的线性算法进行处理。上述解决方案 2) 和 3) 的困难在于,创建新的渲染和可视化算法的工作量很大,可能需要为每种类型的非线性单元提供不同的解决方案。此外,在专用渲染硬件(例如,处理线性单元)中发现的性能很可能远远超过任何用于更高阶单元的软件渲染解决方案。上述 1) 的困难在于必须仔细执行曲面细分,否则可能会在可视化中引入不可接受的错误。或者,如果单元被过度细分,则会导致过多的线性图元。第 8 章 – 高级数据表示了解更多信息)。

VTK 使用固定的细分细分非线性二次单元,如图所示图 5-5. 由于插值的阶数较低且定义单元的点数很少,这通常适用于二次单元。

二次边。二次边是主要的一维单元。它由三点定义。前两点定义边缘的端点;第三个点位于边缘的中心,如图所示图 5-4. 沿线的方向是从第一个点到第二个点。

二次三角形。二次三角形是主要的二维单元。它由六个点定义。前三个点位于三角形的顶点;接下来的三个位于三个边缘的中间,如图所示图 5-4.

二次线性四边形。二次线性四边形是主要的二维单元。它由六个点定义。前四个点位于四边形的顶点;接下来的两个位于第一条和第三条边的中间,如图所示图 5-4.

图5-5
图 5-5。将二次非线性单元分解为线性单元。二次四面体被细分为六个线性四面体;二次六面体被细分为八个线性六面体。请注意,某些细分需要添加新点。在 VTK 中,单元适配器框架可用于对具有任意复杂度的基函数的单元进行细分,有关更多信息,请参阅 [第 8 章 – 高级数据表示](../08Chapter8)。

二次四边形。二次四边形是主要的二维单元。它由八个点定义。前四个点位于四边形的顶点;接下来的四个位于四个边缘的中间,如图所示图 5-4.

双二次四边形。双二次四边形是主要的二维单元。它由九个点定义。前四个点位于四边形的顶点;接下来的四个位于四个边缘的中间;最后一个位于四边形的中心,如图所示图 5-4.

二次四面体。二次四面体是一个主要的三维单元。它由十个点定义。前四个点位于四面体的顶点;接下来的六个位于六个边缘的中间,如图所示图 5-4.

二次金字塔。二次金字塔是一个主要的三维单元。它由十三点定义。前五个点位于金字塔的顶点;接下来的八个位于八个边缘的中间,如图所示图 5-4.

二次线性楔形。二次线性楔是一个主要的三维单元。它由十二点定义。前六个点位于楔形的顶点;接下来的六个位于属于三角形面的六个边的​​中间,如图所示图 5-4.

二次楔形。二次楔形是一个主要的三维单元。它由十五个点定义。前六个点位于楔形的顶点;接下来的九个位于九个边缘的中间,如图所示图 5-4.

双二次楔形。双二次楔形是一个主要的三维单元。它由十八点定义。前六个点位于楔形的顶点;接下来的九个位于九个边缘的中间;接下来的三个位于每个四边形面的中心,如图所示图 5-4.

二次六面体。二次六面体是一个主要的三维单元。它由二十个点定义。前八个点位于六面体的顶点;接下来的十二个位于十二个边缘的中间,如图所示图 5-4.

双二次六面体。双二次六面体是一个主要的三维单元。它由二十四个点定义。前八个点位于六面体的顶点;接下来的十二个位于十二个边缘的中间;接下来的四个点位于前四个面的中心,如图所示图 5-4.

三二次六面体。三二次六面体是一个主要的三维单元。它由二十七点定义。前八个点位于六面体的顶点;接下来的十二个位于十二个边缘的中间;接下来的六个位于每个面的中心;最后一个位于六面体的中心,如图所示图 5-4.

5.5 属性数据

属性数据是与数据集结构相关的信息。此结构包括数据集几何和拓扑。大多数情况下,属性数据与数据集点或单元格相关联,但有时属性数据可能会分配给单元格组件,例如边或面。还可以跨整个数据集或跨一组单元格或点分配属性数据。我们将此信息称为属性数据,因为它是数据集结构的属性。典型示例包括某一点的温度或速度、细胞的质量或进出细胞面的热通量。

图5-6

图 5-6。属性数据。

 

属性数据通常分为特定类型的数据。这些类别是根据常见的数据形式创建的。可视化算法也根据它们操作的数据类型进行分类。

单值函数,例如温度或压力,是标量数据的示例,它是一种属性类型。更一般地,属性数据可以被视为 n 维数据数组。例如,单值函数温度可以被视为 1 x 1 数组,而速度可以被视为 x、y 和 z 方向的 3 x 1 分量数组。这种数据属性的抽象模型可以扩展到整个可视化系统。一些系统扩展了这个模型以包含数据的结构。例如,3D 图像数据集(即,体积)可以表示为 1xmxn 数据值的 3D 数组。非结构化数据可以表示为位置的 3D 向量以及连接数组。我们将这种通用方法称为可视化数据的超数据模型(参见 [“Other Data Abstractions”

在以下部分中,我们使用更简单的特定于类型的模型来描述数据属性(图 5-6)。我们也将自己限制在三维结构,因为假设数据集结构和图形是三维的。

标量

标量数据是数据集中每个位置的单值数据。标量数据的示例是温度、压力、密度、海拔和股票价格。标量数据是最简单、最常见的可视化数据形式。

矢量图

矢量数据是具有大小和方向的数据。在三个维度中,这表示为值的三元组(u、v、w)。矢量数据的示例包括流速、粒子轨迹、风运动和梯度函数。

法线

法线是方向向量:也就是说,它们是幅度|n|=1 的向量。图形系统经常使用法线来控制对象的着色。一些算法也可以使用法线来控制单元基元的方向或生成,例如从定向线创建色带。

纹理坐标

纹理坐标用于将笛卡尔空间中的点映射到 1 维、2 维或 3 维纹理空间。纹理空间通常称为纹理贴图。纹理贴图是颜色、强度和/或透明度值的常规数组,可为渲染对象提供额外的细节。

二维纹理的一种应用是将照片“粘贴”到一个或多个多边形上,从而产生没有大量图形基元的详细图像。(纹理映射在第 7 章 – 高级计算机图形学中有更详细的介绍。)

张量

张量是向量和矩阵的复杂数学概括。秩为 k 的张量可以被认为是一个 k 维表。秩为 0 的张量是标量,秩 1 是向量,秩 2 是矩阵,秩为 3 的张量是三维矩形数组。更高等级的张量是 k 维矩形阵列。

一般张量可视化是当前研究的一个领域。迄今为止的努力一直集中在二维、2 阶张量上,它们是 3 x 3 矩阵。这种张量最常见的形式是应力和应变张量,它们表示负载下物体中某一点的应力和应变。VTK 仅处理实值对称 3 x 3 张量。

5.6 数据集的类型

数据集由组织结构和相关属性数据组成。该结构具有拓扑和几何特性,由一个或多个点和单元组成。数据集的类型源自组织结构,并指定单元格和点之间的关系。常见的数据集类型显示在图 5-7. 数据集的特征在于其结构是规则的还是不规则的。如果组成点和单元格中存在单一的数学关系,则数据集是规则的。如果点是规则的,则数据集的几何形状是规则的。如果单元的拓扑关系是规则的,那么数据集的拓扑是规则的。可以隐式表示常规(或结构化)数据,从而大大节省内存和计算量。必须明确表示不规则(或非结构化)数据,因为没有可以紧凑描述的固有模式。非结构化数据往往更通用,但需要更大的内存和计算资源。

多边形数据

我们已经看到了如何设计图形库来渲染诸如线条和多边形之类的几何图元。这些原语也经常由计算几何和可视化算法生成或使用。在可视化工具包中,我们将此图形基元集合称为多边形数据。多边形数据集由顶点、多边形、线、折线、多边形和三角形带组成。多边形数据的拓扑和几何形状是非结构化的,构成该数据集的单元格的拓扑维度各不相同。多边形数据集在数据、算法和高速计算机图形之间架起了一座桥梁。

顶点、线和多边形形成一组最小的图元来表示 0 维、1 维和 2 维几何。为了方便、紧凑和性能,我们包含了 polyvertex、polyline 和三角形条形单元。尤其是三角形条带是高性能的基元。用三角形带表示n 个三角形只需要n+2个点,而传统表示需要3n个点。此外,许多图形库可以以比三角形多边形更高的速度渲染三角形条带。

图5-7

图 5-7。数据集类型。非结构化网格由所有单元格类型组成。

 

我们最小的单元选择基于常见的应用程序和性能,代表某些图形库中可用的单元子集。其他类型包括四边形网格、贝塞尔曲线和曲面,以及其他样条类型,例如 NURBS(非均匀有理 B 样条)[Mortenson85]。样条曲面通常用于精确建模和可视化几何。很少有可视化算法(几何可视化除外)需要样条曲面。

图像数据

图像数据集是排列在规则矩形格上的点和单元的集合。晶格的行、列和平面平行于全局xyz坐标系。如果点和单元格排列在平面上(即二维),则数据集称为像素图、位图或图像。如果点和单元被排列为堆叠平面(即三维),则数据集被称为体积。请记住,术语图像数据是指图像、体积或一维点数组的总称。请注意,一些作者将图像数据称为统一网格和结构化点。(结构化点是早期版本的 VTK 中使用的术语。)

图像数据由线元素 (1D)、像素 (2D) 或体素 (3D) 组成。图像数据在几何和拓扑中都是规则的,并且可以隐式表示。表示方案仅需要数据维度、原点和数据间距。数据的维度是一个 3 向量 (,指定点的数量x、y、z方向。原点是最小xyz点在三维空间中的位置。图像数据集中的每个像素 (2D) 或体素 (3D) 形状相同,间距指定xyz方向的长度。

图像数据集的拓扑结构和几何形状的规律性表明自然ijk坐标系。数据集中的点数为而细胞的数量是(nx1)×(ny1)×(nz1). 可以通过指定三个索引来选择特定的点或单元 . 类似地,通过指定三个索引中的两个来定义线,通过指定单个索引来定义平面。

表示的简单性和紧凑性是图像数据的理想特征。它是一种有效的遍历和计算结构。因此,作为最常见的可视化数据集形式,图像数据只能与多边形数据相媲美。图像数据的主要缺点是所谓的“维度灾难”。为了获得更高的数据分辨率,我们必须增加数据集的维度。增加图像的尺寸会导致内存需求增加 O(n2),而卷需要 O(n3) 增加。因此,使用图像数据解析小特征可能需要比可用空间更多的磁盘空间或计算机内存。

图像数据集通常用于成像和计算机图形学。体积通常由医学成像技术生成,例如计算机断层扫描 (CT) 和磁共振成像 (MRI)。有时,体积用于对数学函数或数值解进行采样。

直线网格

直线网格数据集是排列在规则格上的点和单元的集合。晶格的行、列和平面平行于全局xyz坐标系。虽然数据集的拓扑是规则的,但几何只是部分规则。也就是说,这些点沿坐标轴对齐,但点之间的间距可能会有所不同。

与图像数据集一样,直线网格由像素 (2D) 或体素 (3D) 组成。拓扑通过指定网格尺寸隐式表示。几何图形通过维护一个单独的 x、y 和 z 坐标列表来表示。要获得特定点的坐标,必须适当组合三个列表中的每一个的值。

结构化网格

结构化网格是具有规则拓扑和不规则几何形状的数据集。网格可以变形为任何单元格不重叠或自相交的配置。

结构化网格的拓扑结构通过指定一个 3 维向量来隐式表示(nx,ny,nz). 几何图形通过维护一个点坐标数组来明确表示。结构化网格的组成单元是四边形 (2D) 或六面体 (3D)。与图像数据一样,结构化网格具有自然坐标系,允许我们使用拓扑来引用特定点或单元格 坐标。

结构化网格在有限差分分析中很常见。有限差分是一种数值分析技术,用于逼近偏微分方程的解。典型应用包括流体流动、传热和燃烧。

非结构化点

非结构化点是空间中不规则分布的点。非结构化点数据集中没有拓扑,几何完全是非结构化的。顶点和多顶点单元用于表示非结构化点。

非结构化点是一种简单但重要的数据集类型。通常数据没有内在结构,可视化任务的一部分是发现或创建它。例如,考虑装有温度计的汽车中的活塞。在一组有限的点上选择仪表的数量及其位置,从而在活塞表面上的“不相关”(至少在可视化拓扑方面)位置处产生温度值。为了可视化表面温度,我们必须创建一个插值表面和方案来填充中间值。

非结构化点用于表示此类非结构化数据。通常,出于可视化目的,此数据形式会转换为另一种更结构化的形式。将非结构化点转换为其他形式的算法在第 9 章的“可视化非结构化点”中进行了描述。

非结构化网格

最通用的数据集形式是非结构化网格。拓扑和几何都是完全非结构化的。任何细胞类型都可以在非结构化网格中以任意组合进行组合。因此,单元的拓扑范围从 0D(顶点、多顶点)到 3D(四面体、六面体、体素)。在可视化工具包中,任何数据集类型都可以表示为非结构化网格。我们通常仅在绝对必要时才使用非结构化网格来表示数据,因为这种数据集类型需要最多的内存和计算资源来表示和操作。

5.7 其他数据抽象

除了此处介绍的数据集模型外,还提出了其他数据模型。我们简要检查了另外两个已成功应用的模型。这些是 AVS 字段模型和 Haber、Lucas 和 Collins 的模型,由商业 IBM Data Explorer 系统以修改形式改编。本节最后简要比较了这两个模型和 VTK 的数据模型。

应用可视化系统

AVS(应用程序可视化系统)是第一个大规模的商业可视化系统[AVS89]。由于 AVS 的直接应用或 AVS 对其他研究人员的影响,可视化技术的早期发展、可见性和成功应用的实现。AVS 是一个数据流可视化系统,具有用于创建、编辑和操作可视化网络的清晰用户界面。使用显式执行程序来控制网络的执行,AVS 可以运行分布式和并行可视化应用程序。由于 AVS 架构是开放的,研究人员和开发人员可以并且已经捐赠了过滤器供其他人使用。

AVS 数据模型由原始数据和聚合数据组成。原始数据是数据的基本表示,例如字节、整数、实数和字符串。聚合类型是原始类型的复杂组织,包括字段、颜色图、几何图形和像素图。字段可以认为是 AVS 的基本数据类型,稍后将详细介绍。颜色图用于将函数值(即标量值)映射为颜色和透明度值。几何图形由点、线和多边形等图形基元组成,并由几何渲染器用于显示对象。像素图是可视化的渲染图像或输出。

该字段是 AVS 数据模型中最有趣的部分。一般来说,它是一个 n 维数组,每个点都有标量或向量数据。标量是单个值,而向量是两个或多个值(不一定是三个)。字段数组可以有任意数量的维度,并且维度可以是任意大小。该字段没有隐式结构,而是定义了一个映射函数。也就是说,指定了从数据元素到坐标点的隐式或显式关系。因此,字段是两种空间之间的映射:字段数据的计算空间和坐标空间,坐标空间通常是全局坐标系。AVS 支持三种类型的映射:统一(即结构化)、直线和不规则(即非结构化)映射。

数据浏览器

Haber、Lucas 和 Collins [Haber91]的数据模型基于纤维束的数学。他们的工作目标是为规则和不规则网格上的字段分段表示创建一个通用模型。他们将他们的模型称为字段数据模型,但他们对字段一词的定义与 AVS 模型不同。字段是由基础数据和相关数据组成的对象。非正式地,基础是一个流形,其坐标是场的自变量,因数据将因变量的值与基础的自变量相关联。可视化数据由描述局部区域的基变量和因变量的字段元素组成。

可视化工具包

这些数据模型与VTK的数据集模型有异同。最大的不同是这些其他模型更抽象。它们能够表示更广泛的数据并且更灵活。特别是,AVS 字段模型能够以简单而优雅的方式表示任意数字流。Haber 等人的现场数据模型。也很强大:作者展示了如何使用这种数据表示来利用数据中的规律性来获得紧凑的表示。另一方面,所有这些模型(包括 VTK 的)都共享结构与数据的概念。AVS 字段模型通过使用映射函数来引入结构。Haber 等人的现场数据。模型类似于 VTK 的数据集模型,因为基础相当于 VTK 的单元,

抽象级别的差异在可视化系统的设计中提出了重要问题。在以下讨论中,我们将数据模型称为抽象模型或具体模型,其中具体模型的相对抽象级别较低。抽象类和具体类比较如下:

  • 抽象模型比具体模型更灵活,能够表示更广泛的数据形式。

  • 抽象模型适用于紧凑的计算机代码。

  • 具体模型比抽象模型更容易描述、接口和实现。

  • 抽象级别影响到数据模型的计算机代码和/或数据库接口。抽象模型产生抽象代码和数据表示;具体模型产生具体的代码和数据表示。

  • 抽象模型的复杂性可以通过创建更简单的、特定于应用程序的接口来隐藏。然而,这需要额外的努力。另一方面,具体模型不能通过修改接口变得更加抽象。

计算机系统的设计需要仔细注意抽象系统和具体系统之间的平衡。尤其是可视化系统,必须仔细设计,因为它们与其他系统和数据模型交互。过于抽象的模型可能会导致计算机代码和接口混乱,并且可能由于用户误解而被滥用。另一方面,具体模型的灵活性和能力有限,但往往更容易学习和应用。

Visualization Toolkit的设计中,我们选择使用相对于 AVS 和字段数据模型更具体的数据模型。我们的决定是基于系统既要提供信息又要实用的前提,并且我们希望清楚地展示基本概念。另一方面,VTK 的数据模型足够通用,足以支持我们的可视化实践。我们与用户的经验也告诉我们,VTK 的数据模型比更抽象的模型更容易被临时可视化用户理解。如果您决定设计自己的系统,我们建议您检查其他数据模型。但是,我们认为Visualization Toolkit中显示的代码的清晰性是设计抽象和简单性之间平衡权衡的一个例子。

图5-8

图 5-8。连续数组的实现。这个例子是类定义 vtkFloatArray 的一个片段。

 

5.8 放在一起

在本节中,我们将描述前面介绍的数据集类型的实现细节。我们还将通过各种 C++ 示例向您展示如何创建这些数据集。

内存分配和数据数组

由于数据的大小和范围,必须仔细管理内存以创建高效的可视化系统。在Visualization Toolkit中,我们使用连续数据数组作为大多数数据结构的基础。与其他数据结构(例如链表或结构指针数组)相比,可以更快地创建、删除和遍历连续数组。在 VTK 中,我们将这些称为数据数组,并用类vtkDataArray表示它们。

连续数组也可以很容易地通过网络传输,特别是如果数组中的信息与计算机内存地址无关。内存独立性避免了将信息从一个内存位置映射到另一个内存位置的开销。因此,在 VTK 中,我们基于“id”访问信息,它是类数组对象的整数索引。数据数组是 0offset 就像 C++ 数组一样。也就是说,给定 n 个数据值,我们使用 ids (0, 1, 2, …, n – 1) 依次访问这些值。

一个重要的设计决策是不使用对象数组来表示数据(例如,用于单元格和/或点的单独类)。我们的经验表明,由于构建和删除的成本,此类设计会严重影响性能。相反,我们专注于设计更高抽象级别的类。从性能的角度来看,面向对象的方法在应用程序级别上服务最好,而不是在实现级别上。

vtkFloatArray类是连续数组的一个示例。我们将使用这个类来描述如何在 VTK 中实现连续数组。如图所示图 5-8,实例变量Array是一个指向float类型内存的指针。数组的分配长度由 Size 给出。该数组是动态的,因此尝试插入超出分配大小的数据会自动生成 Resize() 操作。调整大小后,数组的大小每次大约翻倍。MaxId 字段是定义插入数据结尾的整数偏移量。如果没有插入数据,则 MaxId 等于 -1。否则,MaxId 是一个整数值,其中 0 \leq MaxId < Size。

元组抽象

许多可视化数据由多个组件值定义。xyz坐标三元组或 RGBA 颜色像素值就是两个这样的示例。为了在一个连续的数据数组中表示这些数据,引入了元组数据抽象。作为图 5-8如图所示,连续数组被分组为具有 NumberOfComponents 分量的较小子数组。这些子数组称为元组,对于给定的数组,元组大小或 NumberOfComponents 对于所有元组都是恒定的,如图 5-9.

用数据数组表示数据

属性数据和点,以及其他几个数据对象,在 VTK 中用数据数组表示。某些属性数据,例如点、向量、法线和张量,需要具有与其定义一致的元组大小。例如,点、向量和法线需要一个元组大小为 3 的数据数组;张量大小为 9 的元组(即 3 x 3 矩阵)。标量对元组大小没有任何要求。处理此类标量数据的算法通常在每个元组的第一个组件上运行。(VTK 中存在过滤器,用于将多分量数据数组拆分为单独的数组,并将单独的数据数组合并为一个数组。请参阅vtkSplitFieldvtkMergeFields。)

图 5-9

图 5-9。数据数组结构。在这个例子中,每个元组有 3 个组件。

 

抽象/具体数据数组对象

可视化数据有多种形式——浮点、整数、字节和双精度——仅举几个简单的类型。更复杂的类型,如字符串或多维标识符也是可能的。鉴于这种类型的多样性,我们如何使用数据数组来表示和操作这些数据?答案是通过抽象数据对象提供运行时解决方案,并使用模板化 C++ 代码提供编译时解决方案。

抽象数据对象是提供统一方法来使用动态绑定创建、操作和删除数据的对象。在 C++ 中,我们使用 virtual 关键字将方法声明为动态绑定。动态绑定允许我们通过操作对象的抽象超类来执行属于具体对象的方法(参见图 5-10).

考虑抽象类vtkDataArray。我们可以通过执行方法 double s = GetTuple1(129) 访问关联点 id 129 处的数据值。由于虚拟 GetTuple1() 方法返回一个浮点数据值,因此 vtkDataArray的每个子类也必须返回一个浮点值。尽管子类可以自由地以任何可能的形式表示数据,但它必须将其数据表示转换为浮点值。这个过程可能就像从内置类型到浮点值的强制转换一样简单,也可能是数据的复杂映射。例如,如果我们的数据由字符串组成,我们可以想象创建一个按字母顺序排列的列表并将字符串映射到列表中的某个位置,然后将该位置转换为双精度值。

图 5-10

图 5-10。数据数组对象图。vtkDataArray 是一个抽象基类。vtkDataArray 的子类实现类型特定的表示和操作。注意:此图中并未显示所有具体的数据数组子类。

 

虽然这种面向运行时的接口便于编写不依赖于特定数据类型的通用算法,但将本机表示转换为双精度类型是有问题的。首先,转换操作会对性能产生不利影响,因为数据访问方法被频繁调用,虚函数比内联或非虚调用慢,并且在许多情况下强制转换操作符很慢。其次,像 double 这样的复杂类型在转换为 double 时会丢失精度。为了解决这些问题,可以访问原始形式的数据并进行相应的处理。在这种方法中,使用了 C++ 模板。

要使用模板,必须获取指向数据的原始类型化指针,并了解数据的类型。vtkDataArray及其相关子类提供此功能。有了这些信息,就可以将数据类型切换到基于该类型模板化的函数中。在大多数成像过滤器中都可以找到使用此功能的典型代码片段,几乎所有这些都被模板化如下:

switch (outData->GetScalarType())
  {
  case VTK_CHAR:
  { typedef char VTK_TT;
    func(arg1, arg2, arg3, VTK_TT* arg4, VTK_TT* arg5); }
    break;
  case VTK_UNSIGNED_CHAR:
  { typedef unsigned char VTK_TT;
    func(arg1, arg2, arg3, VTK_TT* arg4, VTK_TT* arg5); }
    break;

    ...for all types.....
图5-11
图 5-11。数据对象表示为字段数据。一个字段可以表示为一个数组数组。每个数组都有一个指定的类型、长度、元组大小和名称。数据数组与点或单元格的关联,以及作为特定属性类型的标记,形成点和单元格属性数据。

在实践中,此代码使用宏进行了简化,并且使用 static_cast<> C++ 运算符来执行强制转换。请注意,函数 func 是一个模板函数。编译器将实例化适当类型的函数。在大多数情况下,所有本机类型都在 switch 语句中表示,因此 func 会相应地扩展。

使用面向编译时的方法(例如模板)避免了将每个数据访问转换为特定类型(例如,双精度)的需要。虽然它确实使代码有些复杂并导致更大的目标代码,但它通常比运行时虚拟方法更快。随着类型数量的增加,这种方法变得有问题。例如,一些过滤器如vtkImageShiftScale使用双重嵌套模板来解决输入和输出类型的潜在差异。该代码比通用的运行时方法更复杂、更大。

数据对象表示

数据对象在 VTK 中实现为 vtkDataArrays 数组,如图所示图 5-11vtkDataObject是可视化数据的一般表示。它用于封装用于可视化网络执行(参见前一章)的实例变量和方法,以及表示数据。在内部,数据由vtkFieldData类的实例表示。很少有算法直接对数据对象进行操作;相反,大多数算法都需要指定组织结构才能处理数据。数据集指定了下一节中描述的组织结构。

数据集表示

VTK 中实现了五个数据集:vtkPolyDatavtkImageDatavtkStructuredGridvtkRectilinearGridvtkUnstructuredGrid。未实现非结构化点数据集,但可以使用vtkPolyDatavtkUnstructuredGrid表示。

我们为每种数据集类型使用不同的内部数据表示。通过使用不同的表示,我们最小化了数据结构的内存需求并实现了高效的访问方法。本来可以使用vtkUnstructuredGrid来表示所有数据集类型,但是对于大数据来说内存和计算开销是不可接受的。以下部分描述了我们如何表示数据集。

vtk 图像数据。最简单和最紧凑的表示是vtkImageData。通过指定维度、数据间距和原点来隐式表示数据集点和单元格。尺寸定义了数据集的拓扑,而原点和间距指定了几何形状。vtkImageData数据集类型可以表示 1D 线样本、2D 图像和 3D 体积。(注意:在 VTK 的早期版本中,vtkImageData被称为vtkStructuredPoints。在代码库中仍然有这个术语的残余。)

组成vtkImageData的点和单元都有隐式排序。单元格和点都按 x、y、z 递增的方向编号。总点数为nx×ny×nz, 和nz 是vtkImageData的尺寸。细胞总数为(nx1)×(ny1)×(nz1).

vtkRectilinearGrid。虽然vtkRectilinearGrid的拓扑是规则的,但几何可以描述为“半规则”。拓扑通过指定沿 x、y 和 z 坐标轴的数据维度来隐式表示。几何是使用沿这些轴的三个坐标值数组定义的。这三个坐标数组可以组合起来确定数据集中任意点的坐标。在 VTK 中,我们使用vtkDataArray的三个实例来表示数组。点和单元的编号方式与vtkImageData中描述的方式完全相同。

vtkStructuredGrid。vtkImageData一样, vtkStructuredGrid的拓扑是规则的,并且通过在拓扑 ijk 坐标系中指定维度来定义。但是vtkStructuredGrid的几何是通过在全局xyz坐标系中指定点坐标来实现的。

抽象数据类vtkPoints用于表示点坐标。vtkPoints指的是vtkDataArray的底层实例,它实际上将点的表示保存为三分量元组的连续数组。通过指定特定的点 id,可以检索或插入特定的点坐标。点和单元的编号方式与vtkImageData相同。必须注意确保数据数组中的点数与网格尺寸所暗示的点数相同。

vtkPolyData。vtkImageDatavtkStructuredGrid不同, vtkPolyData的拓扑不是规则的,因此数据集的拓扑和几何都必须明确表示。vtkPolyData中的点数据使用类似于vtkStructuredGrid的vtkPoints类表示。

Visualization Toolkit使用类vtkCellArray来明确表示单元拓扑。此类是每个单元的连接列表。列表的结构是一个整数序列 (图 5-12)。列表中的第一个数字是计数(细胞连通性中的点数),接下来的一系列数字是细胞连通性。(连接列表中的每个数字都是点坐标列表实例的索引。)连接列表后面的计数序列会重复,直到枚举每个单元格。附加信息,例如列表中的单元格数量和列表中的当前位置(用于遍历目的)也由vtkCellArray维护。

图5-12

图 5-12。vtkCellArray 结构来表示单元拓扑。

 

请注意,此结构中不直接表示类型信息。相反,vtkPolyData为顶点、线、多边形和三角形带维护四个单独的列表。顶点列表表示vtkVertexvtkPolyVertex类型的单元格。行列表表示类型为vtkLinevtkPolyLine的单元格。多边形列表表示类型为vtkTrianglevtkQuadvtkPolygon的单元格。三角带列表表示单一类型的单元格[vtkTriangle](https://www.vtk.org/doc/nightly/html/classvtkTriangle.html#details)Strip. 结果,从定义单元的特定列表中可以知道单元类型,再加上定义单元的点数。

我们对vtkPolyData类的设计基于两个重要要求。首先,我们需要一个高效的外部图形库接口。其次,我们希望根据拓扑聚合单元。这四个单独的列表提供了高效的界面,因为图形库具有单独的顶点、线、多边形和三角形条带图元。因此,在 VTK 中,不需要运行时检查来将不同的单元类型与适当的“加载原语”功能相匹配,因为类型是从原语所在的列表中知道的。这四个列表还将单元格分为 0 维、1 维和 2 维类型。这很有用,因为可视化算法经常以不同的方式处理不同拓扑顺序的数据。

vtkUnstructuredGrid。就其表示拓扑和几何结构的能力而言,数据集类型vtkUnstructuredGrid是最通用的。点和单元都使用vtkPointsvtkCellArray的派生类显式表示。vtkUnstructuredGrid类类似于vtkPolyData,除了vtkUnstructuredGrid必须能够表示所有单元类型,而不仅仅是 vtkPolyData 的有限图形类型(即顶点、线、多边形和三角形条带

vtkUnstructuredGrid的另一个显着特征是我们以不同的方式表示类型信息。在vtkPolyData中,我们将单元格分类为四个单独的列表,从而间接表示单元格类型。在vtkUnstructuredGrid 中,我们添加了附加类vtkCellTypes来明确表示单元类型。

vtkCellTypes是一个补充信息数组对于每个单元格,一个整数标志定义单元格类型。另一个变量用于记录单元格定义在对应的vtkCellArray中的位置(图 5-13).

除了表示单元类型之外,这种设计还可以实现对单元的随机访问。由于单元连接列表的长度不同,vtkCellArray类无法定位特定单元,除非从原点遍历其数据结构。但是,通过添加的类vtkCellTypes,可以使用单个取消引用(即,使用偏移值)直接访问单元格。

vtkCellTypes也可以添加到vtkPolyData数据表示中——确实如此然而,我们添加这个的原因不是为了显式地表示类型,而是为了提供对单元的随机访问并启用许多拓扑操作。我们将在第 8 章 – 高级数据表示中扩展这个想法。

对象模型。五个数据集的实现如图所示图 5-14. 如该对象图所示,这些具体数据集是抽象类vtkDataSet的子类。还引入了两个额外的类。vtkStructuredData类为结构化数据提供实例变量和方法。vtkStructuredData与数据集没有继承关系;而是显示的结构化数据集委托给它以实现它们的一些方法。(这样做是为了避免多重继承。)vtkPointSet类的子类明确表示它们的点,即通过vtkPoints或其子类的实例。vtkPointSet提供方法和实例变量来操作点数据,以及查找点和单元格的通用搜索功能。(有关详细信息,请参阅第 8 章中的“搜索”。)

图5-13

图 5-13。vtkUnstructuredGrid 类的数据结构。(这是完整结构的子集。有关完整详细信息,请参阅 [第 8 章 – 高级数据表示](../08Chapter8)。)

 

图5-14

图 5-14。数据集对象图。五个数据集(阴影)在 VTK 中实现。

 

图5-15

图 5-15。VTK 中二十种具体单元类型的对象图。vtkEmptyCell 表示 NULL 单元格。vtkGenericCell 可以表示任何类型的单元格。三维单元是 vtkCell3D 的子类。高阶单元是 vtkNonLinearCell 的子类。

 

单元格表示

Visualization Toolkit中,每种单元格类型都是通过创建特定的类来实现的。每个单元格都是抽象类型vtkCell的子类。单元拓扑由有序点 id 列表表示,单元几何由点坐标列表表示。vtkCell及其子类的对象图显示在图 5-15.

抽象类vtkCell指定每个单元必须实现的方法。这些方法为单元的几何和拓扑提供了定义的接口。其他方法在单元上执行计算。这些方法将在第 8 章 – 高级数据表示中详细讨论。

数据属性

数据属性与数据集的结构相关联。数据集模型是建立在点和单元上的,因此将数据属性也与点和单元相关联是很自然的。中间结构特征,例如单元格边缘或面,没有明确表示,因此我们无法轻松地将数据属性与它们相关联。

在 VTK 中,数据属性与数据集的点和单元相关联。数据属性与三角形边或六面体面等中间拓扑特征没有关联。(这里我们将与点关联的数据属性称为点属性,将与单元关联的数据属性称为单元属性。)我们的设计选择基于以下基本原理。

  • 数据采集​​和数值模拟系统通常在点位置或单元中心测量和/或计算结果。

  • 边界属性信息(例如,在面和边上)可以作为根据单元拓扑排序的单元数据来维护。

  • 出于紧凑性和效率的原因,VTK 数据模型基于点和单元。表示单元边界上的属性数据将需要扩展此表示以支持少数需要直接支持单元边界上的属性数据的情况。如果将来需要更复杂的数据结构来表示边界属性数据,最好将其封装到单个类中,而不是在整个系统中强制抽象。

维护单元数据和点数据表示的一个困难是数据中可能出现不一致。例如,如果一个单元格的标量值为 0.5,并且其点的标量值不是 0.5,那么哪个值是正确的?尽管用户必须认识到可能存在这种不一致,但可以设计优先方案来解决这种情况。

图5-16

图 5-16表示数据集属性的继承层次结构。

 

为了表示数据集属性,我们使用组织类vtkPointDatavtkCellData,它们都是类vtkFieldData的子类,如图 5-16vtkDataSetAttributes类用于协调数据从一个进程对象到下一个进程对象的移动。它提供了在输入和输出之间复制、插值和移动数据的方法。

vtkDataSetAttributes的另一个重要特性是它提供了分配数据数组来表示特定数据属性的能力。例如,方法 SetScalars() 用于指定将哪个数据数组视为字段中的标量。

每个数据集点与其属性数据之间存在一一对应关系。点属性通过点 id 访问。例如,要访问数据集实例 aDataSet 中点 id 129 的标量值,我们使用 aDataSet->GetPointData()->GetScalars()->GetTuple(129);

此语句假定已为此数据集定义了标量数据并且为非 NULL。

例子

在下面的示例中,我们展示了数据集的手动创建和操作。通常,这些操作不是由 VTK 的用户直接执行的。相反,源对象用于读取数据文件或生成数据。这比此处显示的手动技术更方便,应尽可能使用。

数据集的创建是一个两步过程。首先必须定义数据集的几何和拓扑。根据数据集的类型,几何和拓扑定义将以不同的方式进行。然后创建点和/或单元属性数据并将其与数据集关联。请记住,属性数据与数据集中的点和单元之间存在一对一的关系。

 

图5-17
图 5-17

图 5-17。创建多边形立方体。请参阅 Cube.cxxCube.py

 

创建一个多边形数据集。在我们的第一个示例中,我们创建了一个立方体的多边形表示。立方体由八个点和六个四边形面定义。我们还创建了与立方体的八个顶点相关联的八个标量值。图 5-17显示用于创建数据的关键 C++ 代码片段以及生成的图像。

立方体的几何形状是使用vtkPoints类的实例定义的。默认情况下,vtkPoints 的底层类型vtkFloatArray。立方体的拓扑(即多边形)是用vtkCellArray类的实例定义的。它们分别定义了立方体的点和多边形。标量数据由vtkIntArray类的实例表示。

如本例所示,多边形数据是通过构建片段(例如,点、单元格和点属性数据)创建的,然后将这些片段组合起来形成完整的数据集。如果vtkPolyData的实例名称是 cube,我们可以将这三个步骤总结如下:

  1. 创建vtkPoints子类的实例以定义几何(即点坐标)。使用操作符 cube->SetPoints() 将点与数据集相关联。

  2. 创建vtkCellArray实例以定义顶点、线、多边形和三角形带的拓扑。使用操作符 cube->SetVerts()、cube->SetLines()、cube->SetPolys() 和 cube->SetStrips() 将单元格与数据集相关联。

  3. 创建点和/或属性数据。每个数据集都有两个字段代表 vtkPointData 和vtkCellData。使用运算符 pd=cube->GetPointData() 检索指向点属性数据的指针。使用运算符 pd=cube->GetCellData() 检索指向单元属性数据的指针。使用运算符 pd->SetScalars()、pd->SetVectors()、pd->SetNormals()、pd->SetTensors() 和 pd->SetTCoords() 将属性数据与数据集相关联(对于 cell数据)。

多边形数据支持以下单元类型:顶点、多边形、直线、折线、三角形、四边形、多边形和三角形条带。点和单元属性数据不需要定义 – 您可以任意组合创建一个、部分或全部点和单元属性。

这个例子最令人困惑的地方是 Delete() 方法。为了防止内存泄漏,我们必须在每个 New() 方法之后使用 Delete() 方法(VTK 的析构函数)。从示例中可以明显看出,实例的点、多边形和标量由另一个对象(例如,立方体)引用。那么调用 Delete() 方法不会造成问题吗?

答案是不。VTK 中的某些数据对象被引用计数以节省内存资源(即vtkObjectBase的子类)。这意味着它们可以在对象之间共享。对于大多数对象,Delete() 将调用析构函数。引用计数对象的行为略有不同。Delete() 方法只是减少引用计数。这可能会或可能不会破坏该对象,具体取决于它是否被另一个对象使用。在此示例中,多边形数据集立方体使用点、多边形和标量,因此在调用 Delete() 时不会删除它们。一旦我们释放数据集立方体,即当它们的引用计数降至零时,它们就会被释放。(有关内存管理的更多信息,请参阅VTK 用户指南。)

创建一个图像数据数据集。在这个例子中,我们创建了一个图像数据集(即vtkImageData的一个实例)。通过指定数据维度来定义数据集的拓扑。几何由数据间距和原点定义。间距指定每个体素的长度、宽度和高度。原点指定数据“左下”角在 3D 空间中的位置。在我们的示例中,我们设置数据集的原点和间距,使其中心位于原点,数据集的边界为 (-0.5,0.5, -0.5,0.5, -0.5,0.5)。

 

图5-18
图 5-18

图 5-18。创建图像数据集。标量数据是从球体的方程生成的。体积尺寸为 26^3。请参阅 Vol.cxxVol.py

 

在此示例中,我们创建标量数据以及图像数据数据集。标量值是根据球体的隐式函数计算得出的

屏幕截图 2022-11-06 161450半径 R = 0.4。标量数据存储在vtkFloatArray的实例中,并分配给数据集的点属性数据。

为了完成这个例子,一个轮廓滤波器被用来生成一个标量值的表面F(x,y,z)=0. 请注意,此功能(以更通用的形式)可从源对象vtkSampleFunctionvtkSphere结合使用。图 5-18显示了用于创建数据和绘制标量场轮廓的关键 C++ 代码片段,以及生成的图像。

图像数据数据集易于构建,因为几何和拓扑都是隐式定义的。如果vtkImageData的实例名称是 vol,我们可以总结创建数据集的步骤如下:

  1. 使用运算符 vol->SetDimensions() 定义数据集的拓扑。

  2. 使用运算符 vol->SetOrigin() 和 vol->SetSpacing() 定义数据集的几何形状。

  3. 创建点和/或属性数据并将其与数据集关联。

您不需要指定原点和数据间距。默认情况下,xyz方向的数据间距为 (1,1,1),原点为 (0,0,0)。因此,如果数据集的维度是(nx×ny×nz),数据集的默认长度、宽度和高度将为(nx1,ny1,nz1).

数据集的拓扑维度从其实例变量中隐含地知道。例如,如果任何维度等于一(其他两个大于一),数据集的拓扑维度为二。

创建结构化网格数据集。在下一个示例中,我们创建一个vtkStructuredGrid数据集。拓扑是从数据集的维度隐式定义的。几何是通过提供一个对象来表示点坐标来明确定义的。在这个例子中,我们使用vtkPoints的一个实例,并假设结构化网格根据圆柱体的方程进行扭曲

屏幕截图 2022-11-06 161303图5-19

 

图 5-19

图 5-19。创建半圆柱的结构化网格数据集。创建的矢量的大小与半径成正比,并以切线方向定向。请参阅 SGrid.cxxSGrid.py

 

我们任意选择切线方向的点数为13,径向的点数为11,轴向的点数为11(即维度为13×11×11).

矢量生成与圆柱相切,其大小与半径成正比。为了显示数据,我们在每个点绘制小的定向线,如图所示图 5-19. (这种技术称为刺猬。有关更多信息,请参阅第 6 章中的“刺猬和定向字形”。)

结构化网格数据集的创建是部分显式和部分隐式的。通过创建vtkPoints实例显式创建几何,而通过指定数据集维度隐式创建拓扑。如果 vtkStructuredGrid 的实例名称为sgrid,则使用以下三个步骤来创建它。

  1. 通过创建vtkPoints的实例来指定数据集的几何形状。使用运算符 grid->SetPoints() 将点与数据集相关联。

  2. 数据集拓扑使用运算符 grid->SetDimensions() 指定。确保在上面第 1 项中创建的点数等于隐含的点数nxnynz.

  3. 创建点和/或单元属性数据并将其与数据集关联。数据集的拓扑维度由指定维度隐含。例如,如果任何维度(nx,ny,nz)等于一,数据集的拓扑维度为二。如果三个维度(nx、ny、nz)中有两个等于一,则数据集的拓扑维度为一。

创建一个直线网格数据集。直线网格在拓扑上是规则的,在几何上是半规则的。与结构化网格或图像数据数据集类似,拓扑通过指定网格尺寸隐式表示。因为网格是轴对齐的,但沿每个轴的点坐标可能会有所不同,所以我们需要三个数据数组来表示数据集的几何形状,每个xyz轴一个数组。请注意,直线数据集的单元类型是像素和体素。

 

图 5-20
图 5-20

图 5-20。创建一个直线网格数据集。使用 vtkDataArray 的实例定义沿每个轴的坐标。请参阅 RGrid.cxxRGrid.py

 

为了在创建直线网格时获得最大的灵活性,在 VTK 中,我们使用三个vtkDataArray对象来定义轴数组。这意味着不同的本地数据类型(例如,unsigned char、int、float 等)可以用于每个轴。

总结创建vtkRectilinearGrid实例的过程,我们遵循四个步骤。在这个例子中(显示在图 5-20),我们假设vtkRectilinearGrid实例的名称是 rgrid。

  1. 通过创建vtkDataArray的三个实例来创建数据集几何,每个实例用于xyz坐标轴。我们将假设每个标量中的值的数量是nz.

  2. 三个实例中的每一个都分别使用 grid->SetXCoordinates()、rgrid->SetYCoordinates() 和 rgrid->SetZCoordinates() 方法分配给 x、y 和 z 轴。

  3. 数据集拓扑使用运算符 grid->SetDimensions() 指定。确保在上面第 1 项中创建的点数等于隐含的点数nxnynz.

  4. 创建点和/或单元属性数据并将其与数据集关联。

数据集的拓扑维度由指定维度隐含。例如,如果任何维度(nx,ny,nz)等于一,数据集的拓扑维度为二。如果三个维度中的两个(nx,ny,nz)等于一,数据集的拓扑维度为一。

创建一个非结构化网格数据集。非结构化网格数据集是拓扑和几何中最通用的数据集类型。在这个例子中,我们使用vtkUnstructuredGrid (图 5-21)。网格包含除像素和体素之外的每种细胞类型的示例。(像素和体素通常在内部用于处理图像数据数据集。只要观察到所需的点几何关系,就可以显式创建和操作它们。)创建数据集结构需要创建点来定义几何体和各种单元格来定义拓扑。(请注意,在有限元世界中,我们将这些称为节点和元素。)

 

图5-21
图 5-21

图 5-21。创建半圆柱的结构化网格数据集。创建的矢量的大小与半径成正比,并以切线方向定向。请参阅 UGrid.cxxUGrid.py

 

总结创建vtkUnstructuredGrid实例的过程,我们遵循五个步骤。我们假设vtkUnstructuredGrid实例的名称是 ugrid。

  1. 为数据集分配内存。使用运算符 ugrid->Allocate()。该运算符采用两个与数据大小相关的可选参数。第一个是连接列表的大小,第二个是扩展存储量(如果需要)。根据经验,使用单元数乘以定义两个参数的每个单元的平均点数。这些参数的确切值并不重要,尽管选择可能会影响性能。如果在插入数据之前未能执行此操作,软件将中断。

  2. 创建vtkPoints子类的实例以定义数据集几何。使用运算符 ugrid->SetPoints() 将点与数据集相关联。

  3. 使用单元插入运算符 ugrid->InsertNextCell() 逐个单元创建数据集拓扑。此运算符有多种风格,请使用适当的一种。

  4. 创建点和/或单元属性数据并将其与数据集关联。

  5. 通过执行 ugrid->Squeeze() 运算符完成创建过程。该运算符回收数据结构消耗的任何额外内存。尽管此步骤不是必需的,但它会将内存资源返回给计算机系统。

非结构化网格数据集的创建与其他数据集类型的创建有些不同。这是因为数据的非结构化性质以及内部数据结构的复杂性质。

5.9 章节总结

数据集表示可视化数据。数据集具有组织结构,具有拓扑和几何组件以及相关的属性数据。数据集的结构由单元(拓扑)和点(几何)组成。结构的一个重要特征是它的几何形状和拓扑结构是规则的还是不规则的(或等效的,结构化的或非结构化的)。常规数据比不规则数据更紧凑,通常计算效率更高。但是,不规则数据在表示能力上比规则数据更灵活。

重要的数据集类型包括多边形数据、直线网格、图像数据、结构化网格和非结构化网格。多边形数据集类型用于表示图形数据,以及多种可视化数据。非结构化网格是最通用的类​​型,由所有可能的单元格类型的任意组合组成。

属性数据由标量、向量、张量、纹理坐标和法线组成。其他数组也可以作为属性数据的一部分包含在内,因为它是一种字段数据。在Visualization Toolkit中,属性数据与数据集点和单元格相关联。

5.10 书目注释

已经为这里描述的每种数据集类型提出了各种表示方案。这些方案因设计目标而异。例如,即使是简单的体积表示也已经用其他更复杂的方案实现,例如游程编码和八叉树[Bloomenthal88][Haber91]、AVS 场模型[AVS89]和紧凑单元结构[Schroeder94]中提供了更一般的表示方案的描述。可以在[Gelberg90]中找到数据集类型的概述。一些面向数学的结构可以在[Brisson90][Poluzzi93]中找到。海姆斯[VISUAL3]描述了用于非结构化网格可视化的有效数据结构。

如果您对有限元方法的更多细节感兴趣,请参阅经典的 Zienkiewicz [Zienkiewicz87][Gallagher75][Lapidus82]中提供了有关有限差分法和有限元法的信息。

5.11 参考文献

[AVS89] C. Upson、T. Faulhaber, Jr.、D. Kamins 等。“应用可视化系统:科学可视化的计算环境。” IEEE 计算机图形学和应用程序。9(4):30-42,1989 年 7 月。

[Bloomenthal88] J. 布鲁门塔尔。“隐式曲面的多边形化”。计算机辅助几何设计。5(4):341-355,1988 年 11 月。

[布里森90] E.布里森。“在 d 维中表示几何结构:拓扑和顺序。” ACM 计算几何研讨会。ACM 出版社,纽约,1989 年。

[加拉格尔75] RH 加拉格尔。有限元分析:基础。Prentice Hall,上马鞍河,新泽西州,1975 年。

[Gelberg90] L. Gelberg、D. Kamins、D. Parker 和 J. Stacks。“结构化和非结构化科学数据的可视化技术”。SIGGRAPH ’90 最先进的数据可视化课程笔记。1990 年 8 月。

[Haber91] RB Haber,B.卢卡斯,N.柯林斯。“具有规则和不规则网格规定的科学可视化数据模型。” 在可视化论文集 ’91。第 298-395 页,IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1991 年。

[Lapidus82] L. Lapidus 和 GF Pinder。科学与工程中偏微分方程的数值解。约翰威利父子,纽约,1987 年。

[摩顿森85]我摩顿森。几何建模。约翰威利父子,纽约,1985 年。

[Poluzzi93] A. Paoluzzi、F. Bernardini、C. Cattani 和 V. Ferrucci。“具有单纯复形的与维度无关的建模。” ACM 图形事务。12(1):56-102,1993。

[Schroeder94] WJ Schroeder 和 B. Yamrom。“用于科学可视化的紧凑细胞结构。” SIGGRAPH ’93 和 ’94 科学可视化高级技术课程笔记。

[Schroeder05] WJ Schroeder、F. Bertel、M. Malaterre、D. Thompson、PP Pébay、R. O’Bara 和 S. Tendulkar。“可视化高阶基函数的框架。” In Proceedings of IEEE Visualization 2005,第 43-50 页,明尼阿波利斯,明尼苏达州,IEEE 计算机协会出版社,2005 年 10 月。

[图像 3] R. Haimes 和 M. Giles。“VISUAL3:交互式非稳定非结构化 3D 可视化。”AIAA 报告编号 AIAA-91-0794。1991 年 1 月。

[韦勒86] KJ韦勒。几何建模的拓扑结构。博士论文,伦斯勒理工学院,纽约州特洛伊,1986 年 5 月。

[Zienkiewicz87] OC Zienkiewicz 和 RL 泰勒。有限元方法,第一卷。1. McGraw-Hill Book Co.,纽约,第 4 版。1987 年。

© 版权声明
THE END
喜欢就支持一下吧
点赞904 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容