卡核-致力于提供工业软件卡脖子内核的综合解决方案

Visualization Toolkit官方开发文档翻译(5)–第四章可视化管道

在前一章中,我们使用简单的照明、观察和几何模型数学模型创建了图形图像光照模型包括环境、漫反射和镜面反射效果。观看包括透视和投影的效果。几何被定义为图形基元的静态集合,例如点和多边形。为了描述可视化的过程,我们需要扩展我们对几何的理解以包括更复杂的形式。我们将看到可视化过程将数据转换为图形基元。本章研究了数据转换的过程,并为可视化系统开发了一个数据流模型。

4.1 概述

可视化将数据转换为图像,从而有效且准确地传达有关数据的信息。因此,可视化解决了转换表示的问题。

转换是将数据从其原始形式转换为图形基元并最终转换为计算机图像的过程。这是我们对可视化过程的工作定义。这种转换的一个示例是提取股票价格并创建xy图将股票价格描述为时间函数的过程。

表示既包括用于描述数据的内部数据结构,也包括用于显示数据的图形基元。例如,股票价格数组和时间数组是数据的计算表示,而xy图是图形表示。可视化将计算形式转换为图形形式。

从面向对象的角度来看,转换是功能模型中的过程,而表示是对象模型中的对象。因此,我们用功能模型和对象模型来描述可视化模型。

数据可视化示例

一个简单的二次曲线数学函数将阐明这些概念。功能

是二次曲线的数学表示。图 4-1a显示了该区域中公式 4-1的可视化−1≤X,是,和≤1. 可视化过程如下。我们在规则网格上以分辨率对数据进行采样50×50×50. 然后使用三种不同的可视化技术。在左侧,我们生成对应于函数的 3D 表面F(x,y,z)=c其中 c 是任意常数(即等值面值)。在中心,我们展示了三个不同的平面,它们穿过数据并按函数值着色。在右侧,我们显示了相同的三个平面,这些平面已用等值线勾勒出轮廓。在每个周围我们放置一个线框轮廓。

功能模型

中的功能模型图 4-1b说明创建可视化的步骤。椭圆形块表示我们对数据执行的操作(过程),矩形块表示数据存储(对象),它们表示并提供对数据的访问。箭头表示数据移动的方向。指向块的箭头是输入;流出块的数据表示输出。这些块还可以具有用作附加输入的本地参数。在没有输入的情况下创建数据的过程称为数据源对象,或简称为源。使用没有输出的数据的进程称为接收器(也称为映射器,因为这些进程将数据映射到最终图像或输出)。具有输入和输出的进程称为过滤器。功能模型显示数据如何流经系统。它还描述了各个部分之间的依赖关系。要正确执行任何给定的流程,所有输入都必须是最新的。这表明功能模型需要同步机制来确保生成正确的输出。

功能模型显示数据如何流经系统。它还描述了各个部分之间的依赖关系。要正确执行任何给定的流程,所有输入都必须是最新的。这表明功能模型需要同步机制来确保生成正确的输出。

可视化模型

在接下来的示例中,我们将经常使用功能模型的简化表示来描述可视化过程(图 4-1c)。我们不会明确区分源、汇、数据存储和进程对象。源和汇是根据输入或输出的数量隐含的。源将是没有输入的过程对象。接收器将是没有输出的过程对象。过滤器将是具有至少一个输入和一个输出的过程对象。不会表示中间数据存储。相反,我们将假设它们存在以支持数据流。因此,作为图 4-1c显示,Outline对象生成的Lines数据存储(图 4-1b被组合成单个对象Outline。我们使用椭圆形来表示可视化模型中的对象。(b) 函数模型样本 F(x,y,z)

图 4-1a请参阅 QuadricVisualization.cxxQuadricVisualization.py

图4-1b(b) 功能模型

图4-1c(c) 可视化网络

图 4-1。可视化二次函数 F(x,y,z) = c..

对象模型

功能模型描述了我们可视化中的数据流,对象模型描述了哪些模块对其进行操作。但是系统的对象是什么?乍一看,我们有两个选择(图 4-2).

图4-2图 4-2。对象模型设计选择。一种基本选择是将流程和数据存储组合到一个对象中。这是通常的面向对象的选择。另一种选择创建单独的数据对象和过程对象。

第一种选择将数据存储(对象属性)与进程(对象方法)组合成一个对象。在第二种选择中,我们将单独的对象用于数据存储和处理。实际上还有第三种选择:这两种选择的混合组合。

传统的面向对象方法(我们上面的首选)将数据存储和处理组合到一个对象中。该视图遵循标准定义,即对象包含数据表示以及对数据进行操作的过程。这种方法的一个优点是作为数据可视化算法的过程可以完全访问数据结构,从而产生良好的计算性能。但是这种选择有几个缺点。

  • 从用户的角度来看,流程通常被视为独立于数据表示。换句话说,进程自然地被视为系统中的对象。例如,我们经常说我们要对数据进行“轮廓化”,即创建与恒定数据值相对应的线或曲面。对于用户来说,使用单个轮廓对象来对不同的数据表示进行操作是很方便的。
  • 我们必须重复算法实现。与前面的轮廓示例一样,如果我们将数据存储和处理绑定到一个对象中,则必须为每种数据类型重新创建轮廓操作。即使算法的实现在功能和结构上可能相似,这也会导致重复代码。修改此类算法也意味着修改大量代码,因为它们是跨许多对象实现的。
  • 将数据存储和算法绑定在一起会产生复杂的数据相关代码。一些算法可能比它们操作的数据复杂得多,具有大量实例变量和复杂的数据结构。通过将许多这样的算法与数据存储相结合,对象的复杂性大大增加,对象的简单含义变得丢失。

第二种选择将数据存储和进程分开。也就是说,一组对象表示并提供对数据的访问,而另一组对象实现对数据的所有操作。我们的经验表明,这对用户来说是很自然的,尽管对于面向对象的纯粹主义者来说,这可能被认为是非常规的。我们还发现生成的代码简单、模块化,并且易于开发人员理解、维护和扩展。

第二种选择的一个缺点是数据表示和过程之间的接口更正式。因此,必须仔细设计接口以确保良好的性能和灵活性。另一个缺点是数据和流程的强分离会导致重复代码。也就是说,我们可能会实现与算法重复的操作,并且这些操作不能被视为严格的数据访问方法。这种情况的一个例子是计算数据导数。这个操作不仅仅是简单的数据访问,所以严格来说它不属于数据对象的方法。因此,为了计算导数,我们必须在每次需要计算导数时复制代码。(或创建函数或宏的程序库!)

由于这些问题,我们在Visualization Toolkit中使用了混合方法。我们的方法最接近于上述第二种选择,但我们选择了在数据对象中实现的一小组关键操作。这些操作是根据我们实施可视化算法的经验确定的。这有效地结合了前两种选择,以获得最大的好处和最少的缺点。

4.2 可视化管道

在数据可视化的背景下,图 4-1c被称为可视化管道可视化网络。管道由表示数据的对象(数据对象)、对数据进行操作的对象(过程对象)和指示的数据流方向(对象之间的箭头连接)组成。在接下来的文本中,我们将经常使用可视化网络来描述特定可视化技术的实现。

数据对象

数据对象代表信息。数据对象还提供创建、访问和删除此信息的方法。除非通过正式的对象方法,否则不允许直接修改由数据对象表示的数据。此功能是为流程对象保留的。还可以使用其他方法来获取数据的特征。这包括确定最小和最大数据值,或确定对象中数据值的大小或数量。

数据对象根据其内部表示而有所不同。内部表示对数据的访问方法以及与数据对象交互的过程对象的存储效率或计算性能有重大影响。因此,根据对效率和过程通用性的要求,可以使用不同的数据对象来表示相同的数据。

处理对象

过程对象对输入数据进行操作以生成输出数据。流程对象要么从其输入中派生新数据,要么将输入数据转换为新形式。例如,一个过程对象可能会从压力场导出压力梯度数据或将压力场转换为恒定值压力等值线。流程对象的输入包括一个或多个数据对象以及控制其操作的本地参数。局部参数包括实例变量或关联以及对其他对象的引用。例如,中心和半径是控制球体图元生成的局部参数。

过程对象进一步表征为源对象过滤器对象映射器对象。此分类基于对象是启动、维护还是终止可视化数据流。

源对象与外部数据源接口或从本地参数生成数据。从局部参数生成数据的源对象称为过程对象。前面的例子图 4-1使用程序对象为Equation4-1的二次函数生成函数值。与外部数据接口的源对象称为读取器对象,因为必须读取外部文件并将其转换为内部形式。源对象也可以连接到外部数据通信端口和设备。可能的示例包括模拟或建模程序,或用于测量温度、压力或其他类似物理属性的数据采集系统。

过滤器对象需要一个或多个输入数据对象并生成一个或多个输出数据对象。本地参数控制过程对象的操作。计算每周股票市场平均值,将数据值表示为缩放图标,或对两个输入数据源执行联合集操作是过滤器对象的典型示例过程。

映射器对象对应于功能模型中的接收器。映射器对象需要一个或多个输入数据对象并终止可视化管道数据流。通常映射器对象用于将数据转换为图形基元,但它们可以将数据写入文件或与另一个软件系统或设备接口。将数据写入计算机文件的映射器对象称为写入器对象。

4.3 流水线拓扑

在本节中,我们将描述如何连接数据和处理对象以形成可视化网络。

管道连接

管道的元素(源、过滤器和映射器)可以以多种方式连接以创建可视化网络。但是,当我们尝试组装这些网络时,会出现两个重要问题:类型多重性。

类型是指过程对象作为输入或作为输出生成的数据的形式或类型。例如,球体源对象可以生成多边形或多面表示、隐式表示(例如,圆锥方程的参数)或3D空间的离散表示中的一组占用值作为输出。映射器对象可以将多边形、三角形带、线或点几何表示作为输入。必须正确指定过程对象的输入才能成功运行。

图4-3图 4-3。维护兼容的数据类型。(a) 单一类型系统不需要类型检查。在多类型系统中,只有兼容的类型才能连接在一起。

有两种通用方法可以保持正确的输入类型。一种方法是使用无类型或单一类型系统进行设计。也就是说,创建单一类型的数据对象并创建仅对这一类型进行操作的过滤器(图 4-3)。例如,我们可以设计一个通用的DataSet来表示我们感兴趣的任何形式的数据,并且流程对象只会输入DataSets并生成DataSets。这种方法简单而优雅,但不够灵活。通常,特别有用的算法(即过程对象)将只对特定类型的数据进行操作,并且将它们泛化会导致表示或数据访问的效率大大降低。一个典型的例子是表示结构化数据的数据对象,例如像素图或 3D 体积。因为数据是结构化的,所以可以很容易地以平面或线的形式访问。但是,一般表示将不包括此功能,因为通常数据不是结构化的。

维护正确输入类型的另一种方法是设计类型化系统。在类型化系统中,只允许将兼容类型的对象连接在一起。也就是说,设计了不止一种类型,但对输入执行类型检查以确保正确连接。根据特定的计算机语言,可以在编译、链接或运行时执行类型检查。尽管类型检查确实可以确保正确的输入类型,但这种方法经常会遇到类型爆炸的问题。如果不小心,可视化系统的设计者可能会创建太多类型,从而导致系统碎片化、难以使用和理解。此外,系统可能需要大量的类型转换器过滤器。(类型转换器过滤器仅用于将数据从一种形式转换为另一种形式。)极端情况下,过度的类型转换会导致计算和内存浪费系统。

多重性问题涉及允许的输入数据对象的数量,以及在流程对象操作期间创建的输出数据对象的数量(图 4-4)。我们知道所有过滤器和映射器对象至少需要一个输入数据对象,但通常这些过滤器可以跨输入列表顺序操作。一些过滤器可能自然需要特定数量的输入。实现布尔运算的过滤器就是一个例子。一次对两个数据值实施布尔运算,例如联合或交集。然而,即使在这里多于两个输入也可以定义为对每个输入的操作的递归应用。

图4-4图 4-4。输入和输出的多样性。(a) 源、过滤器和映射器对象的定义。(b) 各种类型的输入和输出。

我们需要区分什么是输出的多样性。大多数源和过滤器生成单个输出。当一个对象生成的输出用于多个对象的输入时,就会发生多个扇出。例如,当源对象用于读取数据文件,而结果数据用于生成数据的线框轮廓以及数据的轮廓(例如,图 4-1a)。当一个对象生成两个或多个输出数据对象时,就会发生多个输出。多输出的一个示例是将梯度函数的 x、y 和 z 分量生成为不同的数据对象。多扇出和多输出的组合是可能的。

循环

在到目前为止描述的示例中,可视化网络没有循环。在图论中,这些被称为有向无环图。然而,在某些情况下,我们希望将反馈回路引入我们的可视化网络。可视化网络中的反馈循环允许我们将流程对象的输出引导到上游以影响其输入。

图 4-5显示了可视化网络中反馈循环的示例。我们用一组初始随机点播种速度场。探针过滤器用于确定每个点的速度(可能还有其他数据)。然后每个点在其相关矢量值的方向上重新定位,可能使用比例因子来控制运动的幅度。该过程一直持续到点退出数据集或超过最大迭代计数。

图4-5图 4-5。在可视化网络中循环。这个例子实现了线性积分。创建样本点以初始化循环过程。一旦过程开始,积分滤波器的输出将用于代替采样点。

我们将在下一节讨论可视化网络的控制和执行。但是,可以说循环可能会在可视化网络中造成特殊问题,具体取决于执行模型的设计。设计必须确保循环不会进入无限循环或非终止递归状态。通常,为了查看中间结果,循环的执行次数是有限的。但是,可以根据需要重复执行循环以处理数据。

4.4 执行管道

到目前为止,我们已经了解了可视化网络的基本元素以及将这些元素连接在一起的方法。在本节中,我们将讨论如何控制网络的执行。

为了有用,可视化网络必须处理数据以生成所需的结果。使每个进程对象运行的完整过程称为网络的执行。

大多数情况下,可视化网络不止一次执行。例如,我们可能会更改流程对象的参数或输入。这通常是由于用户交互:用户可能正在探索或有条不紊地改变输入以观察结果。在对流程对象或其输入进行一项或多项更改后,我们必须执行网络以生成最新结果。

为了获得最高性能,可视化网络中的过程对象必须仅在其输入发生更改时才执行。在某些网络中,如图所示图 4-6,如果对象在特定分支的本地修改,我们可能会有不需要执行的并行分支。在该图中,我们看到对象 D 以及下游对象 E 和 F 必须执行,因为 D 的输入参数发生了变化,并且对象 E 和 F 的输入依赖于 D。其他对象不需要执行,因为它们的输入没有变化。

我们可以使用需求驱动或事件驱动的方法来控制网络的执行。在需求驱动的方法中,我们仅在请求输出时执行网络,并且仅影响结果的那部分网络。在事件驱动的方法中,对流程对象或其输入的每次更改都会导致网络重新执行。事件驱动方法的优点是输出始终是最新的(除了在短期计算期间)。需求驱动方法的优点是无需中间计算即可处理大量更改(即,仅在收到数据请求后才处理数据)。需求驱动的方法最大限度地减少了计算并产生了更具交互性的可视化网络。

图 4-6图 4-6。网络执行。并行分支不需要执行。

网络的执行需要进程对象之间的同步。我们希望仅当所有输入对象都是最新的时才执行流程对象。同步网络执行一般有两种方式:显式或隐式控制(图 4-7).

显式执行

显式控制意味着直接跟踪网络的变化,然后基于显式依赖分析直接控制流程对象的执行。这种方法的主要特点是使用集中执行器来协调网络执行。该执行程序必须跟踪每个对象的参数和输入的变化,包括网络拓扑的后续变化(图 4-7a)。

这种方法的优点是同步分析和更新方法对于单个执行对象是本地的。此外,我们可以创建依赖图并在每次请求输出时执行数据流分析。如果我们希望分解网络以进行并行计算或跨计算机网络分配执行,则此功能尤其重要。

显式方法的缺点是每个过程对象都依赖于执行程序,因为必须通知执行程序任何更改。此外,如果网络执行是有条件的,则执行程序无法轻松控制执行,因为是否执行取决于一个或多个进程对象的本地结果。最后,集中式执行器可能会在并行计算环境中产生不可扩展的瓶颈。

显式方法可以是需求驱动的,也可以是事件驱动的。在事件驱动的方法中,只要对象发生更改(通常是响应用户界面事件),就会通知执行者,并立即执行网络。在需求驱动的方法中,执行者累积对对象输入的更改,并根据明确的用户需求执行网络。

图4-7图 4-7。显式和隐式网络执行。

中央执行的显式方法是许多商业可视化系统的典型,例如 AVS、Irix Explorer 和 IBM Data Explorer。通常,这些系统使用可视化编程接口来构建可视化网络。这些系统通常是在并行计算机上实现的,分布式计算的能力是必不可少的。

隐式执行

隐式控制意味着流程对象仅在其本地输入或参数发生变化时执行(图 4-7)。隐式控制是使用两遍过程实现的。首先,当从特定对象请求输出时,该对象从其输入对象请求输入。这个过程递归地重复,直到遇到源对象。如果源对象已更改或其外部输入已更改,则源对象将执行。然后,当每个流程对象检查其输入并确定是否执行时,递归就会展开。此过程重复,直到初始请求对象执行并终止该过程。这两个步骤称为更新执行过程。

隐式网络执行自然是使用需求驱动的控制来实现的。此处网络执行仅在请求输出数据时发生。如果我们在每次遇到适当的事件(例如更改对象参数)时简单地请求输出,隐式网络执行也可能是事件驱动的。

图 4-8图 4-8。条件执行的例子。根据范围,数据通过不同的颜色查找表进行映射。

隐式控制方案的主要优点是其简单性。每个对象只需要跟踪其内部修改时间。当请求输出时,对象将其修改时间与其输入的修改时间进行比较,如果过时则执行。此外,过程对象只需要知道它们的直接输入,因此不需要其他对象(例如网络执行程序)的全局知识。

隐式控制的缺点是难以在计算机之间分配网络执行或实施复杂的执行策略。一种简单的方法是创建一个队列,该队列按照网络执行的顺序(可能以分布式方式)执行进程对象。当然,一旦将中心对象引入系统,隐式控制和显式控制之间的界限就会变得模糊。

条件执行

可视化网络的另一个重要功能是条件执行。例如,我们可能希望根据数据范围的变化通过不同的颜色查找表映射数据。通过在数据范围内分配更多颜色可以放大小的变化,而我们可以通过为数据范围分配少量颜色来压缩我们的颜色显示(图 4-8 ).

可视化模型的条件执行(如图所示图 4-1c) 原则上可以实现。然而,在实践中,我们必须用条件语言来补充可视化网络来表达网络执行的规则。因此,可视化网络的条件执行是实现语言的功能。许多可视化系统是使用可视化编程风格进行编程的。这种方法基本上是一个可视化编辑器,可以直接构建数据流图。使用这种方法很难表达网络的条件执行。或者,在过程编程语言中,网络的条件执行很简单。我们将这个话题的讨论推迟到“把它放在一起”

4.5 内存和计算的权衡

就计算机内存和计算要求而言,可视化是一项要求很高的应用。大约 1 兆字节到 1 千兆字节的数据流并不少见。许多可视化算法的计算成本很高,部分原因是输入大小,但也归因于固有的算法复杂性。为了创建具有合理性能的应用程序,大多数可视化系统都有各种机制来权衡内存和计算成本。

图 4-9图 4-9。典型网络的静态与动态内存模型的比较。从对象 *C* 和 *D* 请求输出时开始执行。在更复杂的动态模型中,我们可以通过执行更彻底的依赖分析图像来防止 *B* 执行两次。

静态和动态内存模型

在执行可视化网络时,内存和计算的权衡是重要的性能问题。在迄今为止提出的网络中,假定流程对象的输出始终可供下游流程对象使用。因此,网络计算被最小化。然而,保存滤波器输出的计算机内存需求可能很大。只有少数对象的网络会占用大量的计算机内存资源。

另一种方法是仅在其他对象需要时才保存中间结果。一旦这些对象完成处理,就可以丢弃中间结果。每次请求输出时,这种方法都会导致额外的计算。以增加计算为代价,大大减少了所需的内存资源。像所有权衡一样,适当的解决方案取决于特定应用程序和执行可视化网络的计算机系统的性质。

我们将这两种方法称为静态动态记忆模型。在静态模型中保存中间数据以减少整体计算。在动态模型中,中间数据在不再需要时被丢弃。当网络的小部分可变部分重新执行时,以及当数据大小可由计算机系统管理时,静态模型的效果最好。当数据流很大或每次执行网络的同一部分时,动态模型效果最好。通常,希望将静态和动态模型结合到同一个网络中。如果网络的整个分支每次都必须执行,那么存储中间结果是没有意义的,因为它们永远不会被重用。另一方面,我们可能希望在网络中的一个分支点保存一个中间结果,因为数据更有可能被重用。图 4-9.

图 4-10图 4-10。引用计数以节省内存资源。每个过滤器 A、B 和 C 共享一个公共点表示。其他数据是每个对象的本地数据。

如图所示,静态模型只执行每个流程对象一次,存储中间结果。在动态模型中,每个流程对象在下游对象完成执行后释放内存。根据动态模型的实现,过程对象 B 可能会执行一次或两次。如果进行了彻底的依赖分析,进程 B 只有在对象 C 和 D 都执行后才会释放内存。在更简单的实现中,对象 B 将在 C 之后释放内存,随后 D 执行。

引用计数和垃圾收集

另一个使内存成本最小化的有用工具是使用引用计数来共享存储。为了使用引用计数,我们允许多个进程对象引用同一个数据对象并跟踪引用的数量。例如,假设我们有三个对象ABC,它们构成了可视化网络的一部分,如图所示图 4-10. 还假设这些对象仅修改其输入数据的一部分,而使指定xyz坐标位置的数据对象保持不变。然后为了节省内存资源,我们可以允许每个进程对象的输出引用代表这些点的单个数据对象。更改的数据保留在每个过滤器的本地,并且不共享。只有当引用计数变为零时,对象才会被删除。

垃圾收集是一种替代的内存管理策略,它不太适合可视化应用程序。垃圾收集过程是自动的;它试图回收正在运行的应用程序永远不会再次访问的对象使用的内存。虽然由于其自动化特性而方便,但通常垃圾收集会引入开销,可能会在不合时宜的时间(在交互过程中)无意中将暂停引入软件执行。然而,更令人担忧的是,系统可能不会回收已释放的、未使用的内存,直到对内存的最后一次引用被删除后的一段时间,并且在可视化管道中,该内存可能太大而无法在任何时间长度内留下. 也就是说,在某些应用程序中,如果过滤器中的内存使用没有立即释放,

4.6 高级可视化管道模型

前面的部分为实现有用的可视化管道模型提供了一个通用框架。但是,复杂的应用程序通常需要一些高级功能。这些功能是由前面描述的更简单设计中的缺陷驱动的。开发高级模型的主要驱动力包括:处理未知数据集类型、管理复杂的执行策略(包括处理数据片段)以及扩展可视化管道以传播新信息。这些问题将在以下三个部分中讨论。

处理未知数据集类型

存在数据文件和数据源,其中由文件或源表示的数据集类型在运行时之前是未知的。例如,考虑可以读取任何类型的 VTK 数据文件的通用 VTK 阅读器。这样的类很方便,因为用户不需要关心数据集的类型,相反,用户可能希望建立一个单一的管道来处理找到的任何类型。如所示图 4-3,如果系统是单一数据集类型,这种方法效果很好,但是在实践中,由于性能/效率问题,可视化系统中通常存在许多不同类型的数据。另一种选择显示在图 4-3是强制类型检查。但是,在上述阅读器示例这样的情况下,不可能在编译时强制执行类型检查,因为类型是由数据决定的。因此,必须在运行时执行类型检查。

多数据集类型可视化系统的运行时类型检查要求在过滤器之间传递的数据是通用数据集容器(即,它看起来像单一类型但包含实际数据和确定它是什么类型的数据的方法)。运行时类型检查具有灵活性的优势,但代价是在程序执行之前管道可能无法正确执行。例如,可以设计一个通用管道来处理结构化数据(参见第5 章中的“数据集类型”),但数据文件可能包含非结构化数据。在这种情况下,管道将无法在运行时执行,从而产生空输出。因此,设计用于处理任何类型数据的管道必须仔细组装以创建强大的应用程序。

扩展数据对象表示

如本章前面所述,管道由流程对象操作的数据对象组成。此外,由于过程对象与它们所操作的数据对象是分开的,因此必然存在这些对象通过其交换信息的预期接口。定义这个接口具有巩固数据表示的副作用,这意味着如果不修改相应的接口就很难扩展它,因此所有依赖于接口的类(其中有很多)。幸运的是,倾向于改变的不是基本数据表示(这些通常是很好建立的),而是与数据集本身相关的元数据发生了变化。(在可视化的上下文中,元数据是描述数据集的数据。) 虽然通过创建新类来表示新数据集是可行的(因为新数据集类型的添加很少发生);元数据的多样性排除了创建新类的可能性,因为由此产生的数据类型激增以及编程接口的潜在变化将对可视化系统的稳定性产生不利影响。因此,需要一种支持元数据的通用机制。将元数据打包到一个包含数据集和元数据的通用容器中是一种显而易见的设计,并且与上一节中描述的设计兼容。会对可视化系统的稳定性产生不利影响。因此,需要一种支持元数据的通用机制。将元数据打包到一个包含数据集和元数据的通用容器中是一种显而易见的设计,并且与上一节中描述的设计兼容。会对可视化系统的稳定性产生不利影响。因此,需要一种支持元数据的通用机制。将元数据打包到一个包含数据集和元数据的通用容器中是一种显而易见的设计,并且与上一节中描述的设计兼容。

元数据的示例包括时间步长信息、数据范围或其他数据特征、采集协议、患者姓名和注释。在可扩展的可视化管道中,特定的数据读取器(或其他数据源)可以读取此类信息并将其与它产生的输出数据相关联。虽然许多过滤器可能会忽略元数据,但可以将它们配置为沿管道传递信息。或者,管道接收器(或映射器)可能会请求通过管道传递特定的元数据,以便对其进行适当处理。例如,映射器可能会请求注释,如果可用,将它们放置在最终图像上。

管理复杂的执行策略

在实际应用中,迄今为止描述的管道设计可能无法充分支持复杂的执行策略,或者当数据量变大时可能无法成功执行。在接下来的部分中,我们通过考虑替代设计的可能性来解决这些问题。

大数据。先前关于可视化管道的讨论假设特定数据集的大小不超过计算机系统的总内存资源。然而,随着现代数据集大小进入 TB 甚至 PB 范围,典型的台式计算机系统无法处理此类数据集。因此,在处理大数据时必须采用替代策略。一种这样的方法是基于将数据分解成片段,然后通过可视化管道[Martin2001]流式传输这些片段。图 4-11说明如何将数据集划分为多个部分。

图4-11图 4-11。将球体划分为具有幽灵级别单元格和点(蓝色和绿色)的一块(红色)。

通过可视化管道流式传输数据有两个主要好处。首先是可以处理通常不适合内存的可视化数据。第二个是可视化可以在更小的内存占用下运行,从而导致更高的缓存命中率,并且很少或没有交换到磁盘。为了实现这些好处,可视化软件必须支持将数据集分解成片段并正确处理这些片段。这要求数据集和对其进行操作的算法是可分离的、可映射的和结果不变的,如以下[Law99]中所述。

  1. 可分离。数据必须是可分离的。也就是说,数据可以被分解成碎片。理想情况下,每个部分都应该在几何、拓扑和/或数据结构上保持一致。数据的分离应该简单有效。此外,该架构中的算法必须能够正确处理数据片段。
  2. 可映射。为了控制通过管道的数据流,我们必须能够确定需要输入数据的哪一部分来生成给定部分的输出。这允许我们通过管道控制数据的大小,并配置算法。
  3. 结果不变。结果应该独立于片段的数量,并且独立于执行模式(即,单线程或多线程)。这意味着正确处理边界并开发跨可能在其边界上重叠的部分的多线程安全算法。

如果数据是结构化的,即拓扑规则的(参见第 5 章中的“数据集类型”),则将数据分成几块相对简单。此类数据集可以通过在规则xyz细分立方域中的矩形范围进行拓扑描述(参见图5 -7 (a)-(c))。但是,如果数据是非结构化的(例如,三角形或多边形的网格),则很难指定片段。通常,通过将相邻数据(例如,单元格)分组来定义非结构化范围件,然后使用N of M表示法对每个件进行寻址,其中N是总共M个中的第n 个^th^件件。一块的确切组织结构未指定,取决于用于对数据进行分组的特定应用程序和算法。

为了满足结果不变性的第三个要求,处理片段还需要能够生成边界数据或重影级别。当需要来自一块的邻居的信息来执行计算时,边界信息是必要的。例如,梯度计算或边界分析(例如,我有一个单元面邻居吗?)需要一层边界信息。在极少数情况下,需要两个或更多级别。图 4-11说明了与球体中央红色部分相对应的边界单元和点。

最后,应该注意的是,将数据划分为流式传输的能力与数据并行处理所需的能力完全相同。在这种方法中,数据被细分并发送到不同的处理器以进行并行操作。执行某些计算也可能需要边界信息。并行处理具有额外的复杂性,即必须将数据传送到处理器(在分布式计算的情况下)或必须采用互斥(即互斥)以避免同时写入操作。因此,流式处理和并行处理是大数据计算中使用的互补技术。

复杂的执行策略。在许多情况下,简单的执行模型图 4-7不适合复杂的数据处理任务。例如,如上一节所述,当数据集变得太大而无法放入内存或使用并行计算时,流数据是一种复杂的执行策略。在某些情况下,事件驱动(请参阅“执行管道”)或“推送”管道(即接收数据并通过管道推送数据以进行处理的管道)可能是首选。最后,存在分层数据结构,例如多块或自适应网格细化 (AMR) [Berger84] 网格。在管道中处理此类数据集需要分层遍历,因为过滤器会处理网格中的每个块(可视化领域的高级研究主题,本版本未涉及)。

满足这些要求意味着必须扩展执行模型。因此,我们在下一节中重新审视面向对象的设计。

重新审视面向对象的设计。 图 4-2说明了与可视化对象模型的设计相关的两种选择。第一个被抛弃的选择是将数据和对数据的操作组合成一个对象,典型的面向对象的设计模式。提倡的第二种选择是创建一个由两个类组成的设计——数据对象和过程对象——然后将它们组合成可视化管道。虽然第二种策略适用于简单的管道,但当引入复杂的执行策略时,这种设计开始崩溃。这是因为执行策略必然且隐含地分布在数据对象和过程对象中;没有明确的机制来实施特定的战略。因此设计是有问题的,因为如果不修改数据和过程对象的接口就不能引入新策略。好的设计要求执行策略与数据对象和流程对象分开。这种设计的好处包括降低数据和流程对象的复杂性、封装执行策略、执行运行时类型检查(参见“处理未知数据集类型”)甚至管理元数据(请参阅“扩展数据对象表示”)。

随着执行模型变得越来越复杂,执行策略与数据和流程对象分离为单独的类。

高级设计重新引入了执行者的概念(请参阅“执行管道”)。但是,设计与之前的不同图 4-7. 如该图所示,单个集中式执行程序将依赖项引入管道,这些依赖项不会随着管道复杂性的增加或在并行处理应用程序中扩展。在高级设计中,我们假设有多个主管,通常每个过滤器一个。在某些情况下,执行者可以控制多个过滤器。如果过滤器相互依赖或需要复杂的执行策略,这将特别有用。不同级别的执行者可以实施不同的执行策略,例如需求驱动的流式管道就是这样一种策略。其他重要的类别包括协调对复合数据集执行过滤器的管理人员。

图4-12图 4-12。随着执行模型变得越来越复杂,执行策略与数据和流程对象分离为单独的类。

图 4-12是执行程序及其与数据和流程对象的关系的高级视图。在“管道设计与实现”中,对设计进行了更详细的探讨。

4.7 编程模型

可视化系统本质上是为人类交互而设计的。因此,它们必须易于使用。另一方面,可视化系统必须容易适应新数据,并且必须足够灵活以允许快速数据探索。为了满足这些需求,已经开发了多种编程模型。

可视化模型

最高级别是应用程序。可视化应用程序具有特定于应用程序领域的精细定制的用户界面,例如流体流动可视化。应用程序最容易使用,但最不灵活。由于固有的后勤问题,用户很难或不可能将应用程序扩展到新领域。商业交钥匙可视化软件通常被认为是应用软件。

在频谱的另一端是编程库。传统的编程库是对特定于库的数据结构进行操作的过程的集合。这些库通常是用 C 或 FORTRAN 等传统编程语言编写的。这些提供了极大的灵活性,并且可以轻松地与其他编程工具和技术相结合。可以通过添加用户编写的代码来扩展或修改编程库。不幸的是,编程库的有效使用需要熟练的程序员。此外,非图形/可视化专家无法轻松使用编程库,因为不知道如何正确地将程序组合(或排序)在一起。随着输入参数的变化,这些库还需要广泛的同步方案来控制执行。

许多可视化系统介于这两个极端之间。这些通常使用可视化编程方法来构建可视化网络。基本思想是提供图形工具和模块或过程对象的库。使用简单的图形布局工具,可以根据输入/输出类型约束连接模块。此外,用户界面工具允许将界面小部件与对象输入参数相关联。系统执行通常通过内部执行执行程序对用户透明。

替代可视化编程模型

还有另外两个图形和可视化编程模型值得一提。这些是场景图电子表格模型。

场景图通常出现在 3D 图形系统中,例如 Open Inventor [Wernecke94]. 场景图是非循环树结构,以树布局定义的顺序表示对象或节点。节点可以是定义完整场景的几何体(称为形状节点)、图形属性、变换、操纵器、灯光、相机等。父/子关系控制如何在渲染节点时将属性和变换应用于节点,或者对象如何与场景中的其他对象相关(例如,灯光照射在哪些对象上)。场景图不用于控制可视化管道的执行,而是用于控制渲染过程。场景图和可视化管道可以在同一个应用程序中一起使用。在这种情况下,可视化管道是形状节点的生成器,

场景图在图形社区中得到了广泛的应用,因为它们能够紧凑地以图形方式表示场景。此外,由于最近在 VRML 和 Java3D 等 Web 工具中的使用,场景图已得到普及。有关详细信息,请参阅第 11 章 – Web 上的可视化

最近引入的另一种可视化编程技术是 Levoy [Levoy94]的电子表格技术。在电子表格模型中,我们将操作安排在类似于常见电子会计电子表格的规则网格上。网格由单元格的行和列组成,其中每个单元格都表示为其他单元格的计算组合。通过使用简单的编程语言来表示每个单元格的组合,以进行加、减或执行其他更复杂的操作。计算结果(即视觉输出)显示在单元格中。VisTrails [Bavoil2005]举例说明了电子表格方法的最新扩展,一个通过简化可视化管道的创建和维护以及优化管道的执行来实现交互式多视图可视化的系统。VisTrails 的另一个好处是它可以跟踪可视化管道的更改,因此可以直接创建广泛的设计研究。

尽管可视化编程系统取得了广泛的成功,但它们存在两个缺点。首先,它们不像应用程序那样量身定制,需要大量编程,尽管是可视化的。其次,可视化编程对于详细控制来说太有限了,因此构建复杂的低级算法和用户界面是不可行的。所需要的是提供可视化系统的“模块化”和自动执行控制,以及编程库的低级编程能力的可视化系统。面向对象的系统有可能提供这些能力。精心设计的对象库通过对编程库的控制提供了视觉系统的易用性。这是本文所描述的主要目标。

4.8 数据接口问题

在本文的这一点上,您可能想知道如何将可视化管道应用于您自己的数据。答案取决于您拥有的数据类型、编程风格的偏好以及所需的复杂性。尽管我们还没有描述特定类型的数据(我们将在下一章中介绍),但是在将数据连接到可视化系统时,您可能希望考虑两种通用方法:编程接口和应用程序接口。

编程接口

最强大和最灵活的方法是直接对您的应用程序进行编程以读取、写入和处理数据。使用这种方法可以实现的目标几乎没有限制。不幸的是,在像 VTK 这样的复杂系统中,这需要一定程度的专业知识,这可能超出您的时间预算。(如果您对使用 VTK 的这种方法感兴趣,您必须熟悉系统中的对象。您还需要参考 Doxygen 生成的手册页 — 在线http://www .vtk.org或 CD-ROM。配套文本VTK 用户指南也很有帮助。)

需要编程接口的典型应用程序是连接到系统当前不支持的数据文件或在没有数据文件可用的情况下生成合成数据(例如,从数学关系)。此外,有时直接以程序的形式对数据进行编码,然后执行程序以将结果可视化是很有用的。(这正是许多 VTK 示例所做的。)

通常,由于初始学习曲线,对诸如 VTK 之类的复杂系统进行编程是一项艰巨的任务。然而,有更简单的方法来连接数据。虽然可能需要熟练的开发人员来创建复杂的应用程序,但像 VTK 这样的面向对象工具包的要点在于它提供了与通用数据表单接口所需的许多部分。因此,关注那些导入和导出数据的对象是与数据交互的良好开端。在 VTK 中,这些对象被称为读取器、写入器、导入器和导出器。

文件接口(读取器/写入器)。在本章中,我们看到读取器是源对象,写入器是映射器。从实际的角度来看,这意味着读者将从文件中提取数据,创建数据对象,然后将对象传递到管道中进行处理。类似地,写入者摄取数据对象,然后将数据对象写入文件。因此,如果 VTK 支持您的格式,读取器和写入器将很好地与您的数据交互,并且您只需要读取或写入单个数据对象。如果系统不支持您的数据文件格式,您将需要通过上述通用编程接口连接您的数据。或者,如果您希望与对象集合交互,您可能希望查看是否存在导出器或导入器对象(将在下一节中描述)以支持您的应用程序。

读取器的示例包括vtkSTLReader(读取立体光刻文件)和vtkBYUReader(读取 MOVIE.BYU 格式数据文件)。类似地,对象vtkSTLWritervtkBYUWriter可用于写入数据文件。要查看 VTK 支持哪些阅读器和编写器,请参阅VTK 用户指南或参阅位于http://www.vtk.org的网页以获取当前的 Doxygen 手册页。

文件接口(进口商/出口商)。 导入器导出器是系统中读取或写入由多个对象组成的数据文件的对象。通常,导入器和导出器用于保存或恢复整个场景(即灯光、相机、演员、数据、变换等)。执行导入程序时,它会读取一个或多个文件,并可能创建多个对象。例如,在 VTK 中,vtk3DSImporter导入3D Studio文件并创建渲染窗口、渲染器、灯光、摄像机和演员。同样,vtkVRMLExporter在给定 VTK 渲染窗口的情况下创建 VRML 文件。VRML 文件包含摄像机、灯光、演员、几何体、变换等,由提供的渲染窗口间接引用。

Visualization Toolkit中,有几个导入器和导出器。要查看 VTK 支持哪些导入器和导出器,请参阅VTK 用户指南。您可能还想查看位于http://www.vtk.org的网页以获取当前的 Doxygen 手册页。如果您要查找的导出器不存在,则必须使用编程接口开发自己的导出器。

图4-13a
图 4-13

图 4-13。在 VTK 中导入和导出文件。导入器创建一个描述场景的 vtkRenderWindow。导出器使用 vtkRenderWindow 的实例来获取场景的描述。请参阅 3DSImporter.cxx3DSImporter.py

图 4-13显示从3D Studio模型创建并保存为Renderman RIB 文件的图像。

应用程序接口

大多数用户通过使用现有应用程序来访问他们的数据。用户无需编写管道或编写自己的阅读器和编写器,而是获得适合其特定可视化需求的应用程序。然后,为了与他们的数据交互,用户只需识别可以成功处理数据的读取器、写入器、导入器和/或导出器。在某些情况下,用户可能必须修改用于生成数据的程序,以便将其导出为标准数据格式。使用现有应用程序的优点是用户界面和管道是预先编程的,确保用户可以专注于他们的数据,而不是花费大量资源来编写可视化程序。使用现有应用程序的缺点是经常缺少必要的功能,

选择正确的应用程序并不总是那么简单。应用程序必须支持正确的数据集类型,并支持合适的渲染设备,例如在大型显示器[Humphreys99]或洞穴[CruzNeira93]环境中生成图像。在某些情况下,需要用户交互,并且对并行处理或数据处理能力的要求进一步使选择复杂化。例如,像 ParaView 这样的通用工具(图 4-14a) 可用于可视化大多数类型的数据,包括为大数据和并行计算提供支持,专门的工具如 VolView (图 4-14b) 可能更适合特定类型的任务,例如查看图中所示的医疗数据。如果用户要成功地为他们的数据选择正确的应用程序,那么他们必须熟悉可视化过程。

4.9 放在一起

在前面的部分中,我们已经处理了与可视化模型相关的各种主题。在本节中,我们将描述我们在可视化工具包中采用的特定实现细节。

#   import from 3d Studio vtk3DSImporter importer
importer ComputeNormalsOn
importer SetFileName "$VTK_DATA_ROOT/Data/iflamigm.3ds"
importer Read
#   export to rib format

vtkRIBExporter exporter
exporter SetFilePrefix importExport
exporter SetRenderWindow [importer GetRenderWindow]
exporter BackgroundOn
exporter Write

程序语言实现

可视化工具包以过程语言 C++ 实现。自动包装技术创建与 Python、Tcl 和 Java 解释性编程语言[King03]的语言绑定。类库包含数据对象、过滤器(即过程对象)和执行程序,以方便可视化应用程序的构建。各种支持抽象超类可用于派生新对象,包括数据对象和过滤器。可视化管道旨在直接连接到前一章中描述的图形子系统。这种连接是通过VTK 的映射器实现的,它是管道的接收器和VTK 参与者的接口。

可以(并且已经)使用提供的类库来实现可视化编程接口。然而,对于现实世界的应用程序,过程语言实现提供了几个优点。这包括条件网络执行和循环的直接实现、与其他系统的轻松接口以及创建具有复杂图形用户界面的自定义应用程序的能力。VTK 社区已经从工具包中创建了几个可视化编程和可视化应用程序。其中许多可作为开源软件(例如 paraview.org 上的 ParaView)或商业应用程序(例如www.volview.com上的 VolView )获得。

图4-14a

图 4-14a。ParaView 并行可视化应用程序。

图4-14b

图 4-14b。VolView 体积渲染应用程序。
图 4-14适当的可视化应用程序的选择取决于它必须支持的数据集类型、所需的交互技术、渲染能力以及对大数据的支持,包括并行处理。虽然上述两个应用程序都是使用 VTK 可视化工具包构建的,但它们提供了非常不同的用户体验。ParaView (paraview.org) 是一个通用的可视化系统,可以在分布式并行环境(以及单处理器系统)中处理大数据,并能够在洞穴或平铺显示器上显示。VolView (volview.com) 专注于体积和图像数据,并使用多线程和复杂的细节层次方法来实现交互性能。

管道设计与实现

可视化工具包实现了一个通用的执行机制。过滤器分为两个基本部分:算法和执行对象。一个算法对象,其类派生自vtkAlgorithm,负责处理信息和数据。一个执行对象,其类派生自vtkExecutive,负责告诉算法何时执行以及处理什么信息和数据。过滤器的执行组件可以独立于算法组件创建,允许自定义管道执行机制,而无需修改核心 VTK 类。

过滤器产生的信息和数据存储在一个或多个输出端口中。一个输出端口对应于滤波器的一个逻辑输出。例如,产生彩色图像和相应二进制掩码图像的过滤器将定义两个输出端口,每个端口都保存一个图像。管道相关信息存储在每个输出端口上的vtkInformation实例中。输出端口的数据存储在从vtkDataObject派生的类的实例中。

过滤器使用的信息和数据通过一个或多个输入端口检索。一个输入端口对应于滤波器的一个逻辑输入。例如,字形过滤器将为字形本身定义一个输入端口,而另一个输入端口定义字形位置。输入端口存储引用其他过滤器的输出端口的输入连接;这些输出端口最终为过滤器提供信息和数据。每个输入连接提供一个数据对象及其从建立连接的输出端口获得的相应信息。由于连接是通过逻辑端口而不是在流经这些端口的数据中存储的,因此在建立连接时不需要知道数据类型。“管道连接”“处理未知数据集类型”)。

图4-15
图 4-15。在 VTK 中实现的隐式执行过程的描述。Update() 方法是通过参与者的 Render() 方法启动的。数据通过 RequestData() 方法流回映射器。连接过滤器和数据对象的箭头指示 Update() 过程的方向。

要了解 VTK 管道的执行,从几个不同的有利位置查看流程很有用。请注意,以下每张图并不完全准确,而是以描述的形式呈现,其目的是描述该过程的重要特征。

图 4-15显示了 VTK 执行过程的简化描述。通常管道的执行是由映射器的 Render() 方法调用触发的,通常是响应关联的vtkActor上的 Render() 方法调用(它反过来从渲染窗口接收它)。接下来,在映射器的输入上调用 Update() 方法(导致一连串方法调用请求信息和数据)。最终,必须计算数据并将其返回给发起请求的对象,在本例中为映射器。RequestData() 方法实际上执行管道中的过滤器并生成输出数据。注意流向—这里我们定义数据流向为下游方向,Update()调用的方向为上游方向。

图4-16
图 4-16。构成过滤器的算法、执行器和端口的逻辑关系。执行者负责管理算法的执行,并与通过管道传输的信息请求进行协调。端口对应于逻辑的、不同的输入和输出。

下图,图 4-16,显示了执行器和算法之间的关系,它们配对形成一个过滤器。过滤器的这个视图独立于管道,包含有关算法接口的所有信息,即输入和输出的数量和可用性。最后图 4-17显示过滤器之间的连接。请注意,输出数据对象并未直接连接到输入连接。相反,下游过滤器的输入连接与上游过滤器的输出端口相关联。数据对象与输入端口的这种分离意味着数据类型检查可以推迟到运行时,当消费过滤器从数据的生产者请求数据时。因此生产者可以生成不同类型的数据(例如,它是一个读取器,产生不同的数据类型),只要消费者支持这些不同的数据类型,管道就会执行而不会出错。

连接管道对象

这将我们引向在过滤器和数据对象之间建立连接以形成可视化管道的方法。从前面的图中可以明显看出,Visualization Toolkit管道架构旨在支持多个输入和输出。在实践中,您会发现大多数过滤器和源实际上生成单个输出,而过滤器接受单个输入。这是因为大多数算法本质上往往是单一的输入/输出。也有例外,我们将简要介绍其中的一些。然而,首先我们想提供一个关于 VTK 流水线架构演变的简短历史课程。这一课很有启发意义,因为它阐明了管道设计的演变,以响应新的要求。

在 VTK 5.0 之前。在 VTK 的早期版本(即 5.0 之前的版本)中,可视化管道架构如图4-15所示。在此图中,显示了如何连接过滤器和数据对象以形成可视化网络,输入数据由 Input 实例变量表示,并使用 SetInput() 方法设置。输出数据由 Output 实例变量表示,并使用 GetOutput() 方法进行访问。要将过滤器连接在一起,C++ 语句

filter2->SetInput(filter1->GetOutput()); //Prior to VTK5.0

通常与兼容类型的 filter1 和 filter2 过滤器对象一起使用。在这种设计中,执行了编译时类型检查(即,C++ 编译器将强制执行正确的类型。)显然,这意味着同时更正过滤器产生未知类型的输出是有问题的。这种设计的其他几个问题仍然存在,其中许多问题在前面已经提到过,但在这里进行总结是为了鼓励使用更新的管道架构。

图 4-17
图 4-17。端口和连接的逻辑关系 一个输入端口可能有多个与之关联的连接。在某些过滤器(例如附加过滤器)中可能存在多个连接,其中单个逻辑输入端口表示要“附加”在一起的所有数据,并且每个输入由不同的连接表示。
  • 较旧的设计不支持延迟数据集类型检查。很难支持可以产生不同类型输出的任意阅读器类型或过滤器。
  • 更新和管理管道执行的策略隐含在流程对象和数据对象中。随着策略变得更加复杂或需要更改,这需要修改数据和/或流程对象。
  • 在较旧的设计中,很难在更新过程中中止管道执行。此外,无法集中错误检查;每个过滤器都必须进行一些检查,从而复制代码。
  • 将元数据引入管道需要将 API 更改为数据和流程对象。希望支持读取器将元数据添加到数据流并让管道中的过滤器检索它的能力,而无需修改 API。

为此,以及与并行处理相关的其他原因,原始的 VTK 流水线设计被重新设计。虽然过渡很困难,但如果软件系统要随着技术的进步而改变和发展,这种改变通常是必要的。

VTK 5.0 及更高版本。虽然 VTK 5.0 仍然支持使用 SetInput()/GetOutput(),但它在图 4-16图 4-17气馁。相反,应该使用更新的管道架构。参考图 4-17,我们使用连接和端口来配置VTK的可视化管道:

filter2->SetInputConnection(filter1->GetOutputPort()); //VTK 5.0

您可能已经猜到如何将这种方法扩展到多输入和多输出。让我们看一些具体的例子。vtkGlyph3D是一个接受多个输入并生成单个输出的过滤器示例。vtkGlyph3D的输入由 Input 和 Source 实例变量表示。vtkGlyph3D的目的是将Source中数据定义的几何图形复制到Input定义的每个点上。根据源数据值(例如,标量和矢量)修改几何。(有关字形的更多信息,请参阅“字形”。)要在 C++ 代码中使用vtkGlyph3D对象,您需要执行以下操作:

glyph = vtkGlyph3D::New();
 glyph->SetInputConnection(foo->GetOutputPort());
 glyph->SetSourceConnection(bar->GetOutputPort());...

其中 foo 和 bar 是返回适当类型输出的过滤器。vtkExtractVectorComponents类是具有单个输入和多个输出的过滤器的示例。此过滤器将 3D 矢量的三个分量提取为单独的标量分量。它的三个输出在输出端口 0、1 和 2 上可用。过滤器的使用示例如下:

vz = vtkExtractVectorComponents::New();
foo = vtkDataSetMapper::New();
foo->SetInputConnection(vz->GetOutputPort(2));

其他几个具有多个输入或输出的特殊对象也是可用的。一些更值得注意的类是vtkMergeFiltervtkAppendFiltervtkAppendPolyData。这些过滤器组合多个管道流并生成单个输出。但是请注意,虽然vtkMergeFilter有多个输入端口(即不同的逻辑输入),但vtkAppendFilter只有一个逻辑输入,但假定对那个输入进行了多个连接。这是因为在vtkMergeFilter的情况下,每个输入都有不同且独立的目的,而在vtkAppendFilter所有输入都具有相同的含义(即,列表中只有一个输入要附加在一起)。以下是一些代码片段:

merge = vtkMergeFilter::New();
merge->SetGeometryConnection(foo->GetOutputPort());
merge->SetScalarsConnection(bar->GetOutputPort());

append = vtkAppendFilter::New();
append->AddInputConnection(foo->GetOutputPort());
append->AddInputConnection(bar->GetOutputPort());

注意 AddInputConnection() 方法的使用。此方法添加到连接列表中,而 SetInputConnection() 清除列表并指定到端口的单个连接。

另一个重要的过滤类是vtkProbeFilter。这个过滤器有两个输入。第一个输入是我们希望探测的数据。第二个输入提供一组用作探测点的点。一些流程对象采用输入数据列表。另一个有趣的过滤器是 vtkBooleanStructuredPoints 类,它对体积数据集执行集合操作。列表中的第一个数据项用于初始化集合操作。列表中的每个后续项目都使用用户指定的布尔运算与先前操作的结果相结合。

有关过滤器和数据对象的对象设计的更多详细信息,请参阅第 5 章 – 数据表示第 6 章 – 基本算法

管道执行和信息对象

到目前为止,我们使用的术语元数据和信息对象相当非正式。如前所述,在 VTK 的上下文中,这些术语指的是描述数据集的数据。在本节中,我们将展示如何使用这些对象(它们是vtkInformation的子类)来促进 VTK 管道的执行。

信息对象。信息对象是整个 VTK 管道中用于保存各种元数据的基本容器。它们是异构的键值映射,其中键的类型决定了值的类型。以下是使用信息对象的地点的枚举。

  • 管道信息对象保存管道执行的信息。它们存储在vtkExecutive或子类的实例中,并且可以通过方法vtkExecutive ::GetOutputInformation() 访问。每个输出端口有一个管道信息对象。它包含一个指向相应端口上的输出vtkDataObject的条目(如果已创建)。vtkDataObject包含一个指向其相应管道信息对象的指针,可通过vtkDataObject访问::GetPipelineInformation()。管道信息对象还保存有关在过滤器执行并生成输出时将填充数据对象的信息。包含的实际信息由输出数据类型和使用的执行模型决定。输入连接的管道信息对象可通过vtkExecutive ::GetInputInformation() 方法访问,它们是输入端口所连接的输出端口上的管道信息对象。
  • 端口信息对象保存有关在输出端口上生成并由输入端口使用的数据类型的信息。它们由vtkAlgorithm的实例存储。每个输入端口有一个输入端口信息对象,每个输出端口有一个输出端口信息对象。它们可以通过vtkAlgorithm ::GetInputPortInformation() 和vtkAlgorithm ::GetOutputPortInformation() 方法访问。端口信息对象通常由vtkAlgorithm的子类创建和填充,以指定过滤器的接口。
  • 请求信息对象保存有关发送到执行程序或算法的特定请求的信息。有一个条目指示正在发送什么请求,并且可能还有其他条目提供有关特定请求的其他详细信息。这些信息对象不能通过任何公共方法访问,而是传递给实现请求的 ProcessRequest() 方法。
  • 数据信息对象保存有关当前存储在vtkDataObject中的信息。每个数据对象中都有一个数据信息对象,可通过vtkDataObject ::GetInformation() 访问。包含的实际信息由数据对象类型决定。
  • 算法信息对象保存有关vtkAlgorithm实例的信息。每个算法对象有一个算法信息对象,可通过vtkAlgorithm ::GetInformation() 访问。包含的实际信息由算法对象类型决定。

VTK 中信息对象的重要性在于它们是灵活的(例如,可以很容易地添加新的键值对)和可扩展的。也就是说,读取器、过滤器和映射器可以将新信息添加到容器中,而无需更改管道相关类的 API。

管道执行模型。在 VTK 中,基本的管道更新机制是基于请求的。请求是基本的管道操作(或“管道传递”),通常要求通过管道传播特定的信息。执行模型是由特定执行者定义的一组请求。参考图 4-18下面描述执行过程。

图4-18
图 4-18。通过管道发送的请求的路径。例如,假设消费者(最右边)只需要这个数据的一个片段(例如,4 个中的第 1 个);还假设生产者(最左边)是一个可以将其数据分割成片段的读取器。消费者将这个请求传递到上游,并继续上游(通过执行者),直到它到达可以满足请求的生产者。当读取器算法被要求提供一条数据时,它会提供它,并将新数据(以及它是 4 块中的 1 块的信息)传递回管道。当它到达发出请求的消费者时它会停止。

请求由过滤器的执行对象生成,由于某些用户调用,该过滤器已被其算法明确要求更新。例如,当调用 writer 的 Write() 方法时,算法对象通过调用 this->GetExecutive()->Update() 要求其执行程序更新管道,并执行 writer。可以通过管道发送多个请求以使其保持最新。

请求被实现为信息对象。有一个vtkInformationRequestKey类型的键指定请求本身。此密钥通常由执行人员的班级定义。关于请求的附加信息也可以存储在请求信息对象中。

请求由每个过滤器的执行者通过管道传播。在给定请求信息对象的执行程序上调用vtkExecutive ::ProcessRequest() 方法。这个方法由每个执行者实现,并负责在它认为合适的时候完成请求。只有在为提供其输入的过滤器完成了请求后,才能为过滤器完成许多请求。对于这些请求,执行者会将请求传递给这些上游过滤器的执行者,然后自行处理请求。

执行官经常向其算法对象寻求帮助以完成请求。它通过调用vtkAlgorithm ::ProcessRequest() 方法将请求发送到算法对象。该方法由所有算法实现,负责处理请求。输入和输出管道信息对象作为参数提供给该方法。该算法必须仅使用它自己的过滤器参数设置和给定的管道信息对象来处理请求。算法不允许向其执行人员询问任何附加信息。这确保了算法独立于高管。图 4-18显示了请求通过管道发送时采用的典型路径。通常,请求源自管道末端的消费者。它由高管通过管道送回。每个主管都要求其算法帮助处理请求。

灵活的计算/内存权衡

默认情况下,使用Visualization Toolkit构建的网络存储中间计算结果(即偏好计算)。但是,可以设置单个类变量以在不再需要中间数据时丢弃它们(即,有利于内存)。此外,可以在每个过程对象中设置一个局部参数,以在对象级别控制这种权衡。

该全局变量设置如下。给定数据对象 O,(或使用 O=filter->GetOutput() 获得的过滤器的输出),调用 O->SetGlobalReleaseDataFlagOn() 以启用数据释放。要为特定对象启用数据释放,请使用 O->SetReleaseDataFlagOn()。也存在适当的方法来禁用内存释放。

高级对象设计

在本文的这一点上,描述构成可视化管道的各种对象的设计细节还为时过早。但是,有两个重要的类会影响文本中的许多对象。这些是vtkObject[vtkObject](https://www.vtk.org/doc/nightly/html/classvtkObject.html#details)Base类。

vtkObjectBase是 VTK 中几乎所有继承层次结构的基础对象。vtkObjectBase实现数据对象引用计数(参见“引用计数和垃圾收集”)。vtkObjectBase的子类可以被其他对象共享,而不需要复制内存。它还为对象定义了一个 API 来打印关于它们自己的信息。

vtkObject[vtkObject](https://www.vtk.org/doc/nightly/html/classvtkObject.html#details)Base的子类。它提供方法和实例变量来控制运行时调试并维护内部对象修改时间。特别是,Modified() 方法用于更新修改时间,GetMTim​​e() 方法用于检索它。vtkObject还为我们在前一章中看到的事件回调提供了一个框架(参见第3 章中的“事件和观察者”)。

请注意,我们并不总是在对象图中包含vtkObject[vtkObject](https://www.vtk.org/doc/nightly/html/classvtkObject.html#details)Base以节省空间。请参阅源代码以获取明确的声明。

例子

我们现在将通过四个示例演示可视化管道的一些功能。这里使用的一些对象对您来说可能并不熟悉。请忽略缺失的细节,直到我们在本书后面介绍这些信息。这里的目标是提供对软件架构及其使用的风格和熟悉度。

简单的球体。第一个示例演示了一个简单的可视化管道。使用源对象 ( vtkSphereSource )创建球体的多边形表示。球体通过一个过滤器(vtkElevationFilter),该过滤器计算球体每个点在平面上方的高度。该平面垂直于 z 轴,并通过点 (0,0,-1)。数据最终通过查找表映射( vtkDataSetMapper )。映射过程将高度值转换为颜色,并将球体几何连接到渲染库。映射器被分配给一个actor,然后显示这个actor。可视化网络、部分代码和输出图像显示在图 4-19.

图 4-19a
图 4-19

图 4-19。一个简单的球体示例。请参阅 ColoredSphere.cxxColoredSphere.py

当我们渲染actor时,管道的执行会隐式发生。每个参与者都要求其映射器更新自己。映射器反过来要求其输入更新自己。这个过程一直持续到遇到源对象。如果自上次渲染后修改源,则源将执行。

然后系统遍历网络并在其输入或实例变量过期时执行每个对象。完成后,演员的映射器是最新的并生成图像。

现在让我们通过以下方法调用重新检查管道执行的相同过程。当参与者从渲染器接收到 Render() 消息时,该过程开始。Actor 依次向其映射器发送 Render() 消息。映射器通过 Update() 操作要求其输入更新自身来开始网络执行。这会导致一系列 Update() 方法,因为每个过滤器依次要求其输入更新自身。如果管道中存在分支,则更新方法也将分支。最后,级联在遇到源对象时终止。如果源对象已过期,它将向自身发送一个 RequestData() 命令。每个过滤器都会根据需要向自己发送一个 RequestData() 以使自己保持最新状态。最后,映射器将执行操作以将其输入转换为渲染图元。

Visualization Toolkit中,Update() 方法是公共的,而 RequestData() 方法是受保护的。因此,您可以通过调用 Update() 操作手动使网络执行发生。当您想根据上游执行的结果在网络中设置实例变量但不希望整个网络更新时,这可能很有用。RequestData() 方法受到保护,因为它需要某个对象状态才能存在。Update() 方法确保此状态存在。

最后一点。代码的缩进用于指示对象在何处被实例化和修改。第一行(即 New() 运算符)是创建对象的位置。后面的缩进线表示正在对对象执行各种操作。我们鼓励您在自己的工作中使用类似的缩进方案。

扭曲的球体。这个例子扩展了前面例子的管道,并展示了类型检查对流程对象连接性的影响。我们添加一个变换过滤器 ( vtkTransformFilter ) 以在 xyz 方向上非均匀地缩放球体。

变换过滤器仅对具有显式点坐标表示的对象(即vtkPointSet的子类)进行操作。但是,高程过滤器生成更通用的形式vtkDataSet作为输出。因此我们不能将变换过滤器连接到高程过滤器。但是我们可以将变换过滤器连接到球体源,然后将高程过滤器连接到变换过滤器。结果显示在图 4-20. (注意:另一种方法是使用vtkCastToConcrete来执行运行时转换。)

图4-20a
图 4-20

图 4-20。在前面的示例中添加了一个变换过滤器。请参阅 TransformSphere.cxxTransformSphere.py

C++ 编译器强制源、过滤器和映射器的正确连接。为了确定哪些对象是兼容的,我们检查 SetInput() 方法的类型规范。如果输入对象返回一个输出对象或该类型的子类,则这两个对象是兼容的并且可以连接。

生成定向字形。此示例演示了如何使用具有多个输入的对象。vtkGlyph3D在每个输入点放置 3D 图标或字形(即任何多边形几何)。图标几何由实例变量 Source 指定,输入点从 Input 实例变量中获得。每个字形可以以多种方式定向和缩放,具体取决于输入和实例变量。在我们的示例中,我们将锥体放置在点法线方向(图 4-21).

图4-21a
图 4-21

图 4-21。多个输入和输出的示例。请参阅 Mace.cxxMace.py。

可视化网络在vtkGlyph3D分支。如果任一分支被修改,则此过滤器将重新执行。网络更新必须双向分支,并且在vtkGlyph3D执行时两个分支都必须是最新的。这些要求由 Update() 方法强制执行,并且对隐式执行方法没有任何问题。

消失的球体。在我们的最后一个示例中,我们构建了一个带有反馈回路的可视化网络,并展示了我们如何使用过程编程来改变网络的拓扑结构。该网络由四个对象组成:vtkSphereSource用于创建初始多边形几何体,vtkShrinkFilter用于缩小多边形并在邻居之间创建间隙或空间,vtkElevationFilter用于根据 xy 平面上方的高度为几何体着色,以及vtkDataSetMapper通过查找表和渲染库的接口。网络拓扑、部分 C++ 代码和输出显示在图 4-22.

图4-22a
图 4-22

图 4-22。带有循环的网络 (LoopShrk.cxx)。VTK 5.0 不允许您执行循环的可视化网络;这在以前版本的 VTK 中是可能的。请参阅 LoopShrink.cxxLoopShrink.py

vtkSphereSource生成初始几何图形(响应渲染请求)后,vtkShrinkFilter的输入变为vtkElevationFilter的输出。由于反馈循环,vtkShrinkFilter将始终重新执行。因此,网络的行为是每次执行渲染时重新执行。因为收缩过滤器被重新应用到相同的数据,多边形变得越来越小,最终消失。

4.10 章节总结

可视化过程自然地使用功能模型和对象模型的组合进行建模。功能模型可以简化并用于描述可视化网络。对象模型指定可视化网络的组件。可视化网络由过程对象和数据对象组成。数据对象代表信息;过程对象将数据从一种形式转换为另一种形式。无输入且至少有一个输出的过程对象源共有三种类型;过滤器至少有一个输入和输出;接收器或映射器终止可视化网络。可以隐式或显式控制网络的执行。隐式控制意味着每个对象必须确保其输入是最新的,从而分配控制机制。显式控制意味着有一个集中的执行者来协调每个对象的执行。许多技术可用于对可视化网络进行编程。直接可视化编程在商业系统中最为常见。在更高的层次上,应用程序提供定制但更严格的界面来可视化信息。在最低级别,子程序或对象库提供了最大的灵活性。这Visualization Toolkit包含一个用 C++ 实现的对象库,用于构建可视化网络。

4.11 书目注释

了解可视化过程的实用方法是研究商用系统。这些系统可以归类为直接可视化编程环境或应用程序。常见的可视化编程系统包括 AVS [AVS89]、Iris Explorer [IrisExplorer]、IBM Data Explorer [DataExplorer]、aPE [aPE90]和 Khoros [Rasure91]。应用系统通常提供的灵活性低于可视化编程系统,但更适合特定问题域。PLOT3D [PLOT3D]是 CFD 可视化工具的早期示例。这已被 FAST [FAST90]取代. FieldView 是另一种流行的 CFD 可视化工具 [FieldView91]。VISUAL3 [VISUAL3]是用于非结构化或结构化网格可视化的通用工具。PV-WAVE [Charal90]可以被认为是一个混合系统,因为它既具有简单的可视化编程技术来连接数据文件,又具有比可视化编程环境更结构化的用户界面。Wavefront 的 DataVisualizer [DataVisualizer]是一个通用的可视化工具。它的独特之处在于它是强大的渲染和动画包的一部分。VIS5D 是一个很好的可视化 3D 网格数据(例如由数值天气模型产生的数据)的系统。访问VIS5D 网站了解更多信息。

尽管许多可视化系统都声称是面向对象的,但这通常更多的是外观而不是实现。关于可视化的面向对象设计问题的文章很少。VISAGE [VISAGE92]提供了一个类似于本章描述的架构。Favre [Favre94]描述了一种更传统的面向对象方法。他的数据集类是基于拓扑维度的,数据和方法都组合成类。

4.12 参考文献

[aPE90] DS 代尔。“用于可视化的数据流工具包。” IEEE 计算机图形学和应用程序。10(4):60-69,1990 年 7 月。

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

[Bavoil2005] L. Bavoil、SP Callahan、PJ Crossno、J. Freire、CE Scheidegger、CT Silva 和 HT Vo。“VisTrails:启用交互式多视图可视化。” 在2005 年IEEE 可视化会议记录中 。IEEE 计算机学会出版社,2005 年。

[Berger84] M. Berger 和 J. Oliger。双曲偏微分方程的自适应网格细化。计算物理学杂志,53:484-512,1984 年 3 月。

[Charal90] S. Charalamides。“欢迎使用新浪潮技术图形。” 十二月用户,1990 年 8 月。

[CruzNeira93] C. CruzNeira、DJ Sandin 和 T. DeFanti。“基于环绕屏幕投影的虚拟现实:CAVE 的设计与实现。” 在SIGGRAPH 93 会议记录中,第 135–142 页,1993 年 8 月。

[DataExplorer] 数据浏览器参考手册。IBM 公司,纽约州阿蒙克,1991 年。

[数据可视化器] 数据可视化器用户手册。Wavefront Technologies,加利福尼亚州圣巴巴拉,1990 年。

[FAST90] GV Bancroft、FJ Merritt、TC Plessell、PG Kelaita、RK McCabe 和 A.Globus。“FAST:可视化的多处理环境。” 在90 年可视化论文集。第 14-27 页,IEEE 计算机协会出版社,加利福尼亚州洛斯阿拉米托斯,1990 年。

[Favre94] JM Favre 和 J. Hahn。“面向对象的多变量数据对象可视化设计。” 在可视化 ’94 论文集。第 319-325 页,IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1994 年。

[FieldView91] SM 莱根斯基。“桌面工作站上的高级可视化。” 在可视化 ’91 论文集。第 372-378 页,IEEE 计算机协会出版社,加利福尼亚州洛斯阿拉米托斯,1991 年。

[Haeberli88] PE Haeberli。“ConMan:交互式图形的可视化编程语言。” 计算机 图形学(SIGGRAPH ’88)。22(4):103-11,1988。

[Humphreys99] G. Humphreys 和 P. Hanrahan。“用于大型平铺显示器的分布式图形系统。” 在过程中。IEEE Visualization ’99,第 215-224 页,IEEE 计算机协会出版社,1999 年 10 月。

[IrisExplorer] Iris Explorer 用户指南。Silicon Graphics Inc.,加利福尼亚州山景城,1991 年。

[King03] B. 金和 W. 施罗德。“复杂 C++ 代码的自动包装”。C/C++ 用户杂志, 2003 年 1 月。

[Law99] C. Charles Law、KM Martin、WJ Schroeder、JE Temkin。“用于大型结构化数据集的多线程流式管道架构。” 在过程中。可视化\’99。IEEE 计算机学会出版社,1999 年。

[Levoy94] M. Levoy。“图像电子表格”。在SIGGRAPH ’94 会议记录中。第 139–146 页,1994 年。

[Martin2001] KM Martin,B. Geveci,J. Ahrens,C. Law。“使用并行数据流的大规模数据可视化。” IEEE 计算机图形与应用,21(4):34-41,2001 年 7 月。

[PLOT3D] PP Walatka 和 PG Buning。PLOT3D 用户手册。美国宇航局流体动力学部,1988 年。

[Rasure91] J. Rasure、D. Argiro、T. Sauer 和 C. Williams。“用于图像处理的视觉语言和软件开发环境。” 国际成像系统与技术杂志。1991 年。

[VISAGE92] WJ Schroeder、WE Lorensen、GD Montanaro 和 CR Volpe。“VISAGE:面向对象的可视化系统。” 在可视化论文集 ’92中。第 219-226 页,IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1992 年。

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

[Wernecke94] J. Wernecke。发明家导师。Addison-Wesley 出版公司,ISBN 0-201-62495-8,1994。

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

昵称

取消
昵称表情代码图片

    暂无评论内容