vtk教程第四章 可视化管道

46 篇文章 18 订阅
12 篇文章 14 订阅

VisTrails多视图可视化系统。

VisTrials支持可视化管道的交互式创建,维护它们的发展历史,优化它们的执行,并允许多个管道在电子表格风格的布局中进行比较。图片来源:SCI

犹他大学研究所。

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

4.1概述

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

转换是将数据从原始形式转换为图形原语,并最终转换为计算机图像的过程。这是我们可视化过程的工作定义。这种转换的一个例子是提取股票价格并创建将股票价格描述为时间函数的x-y图的过程。表示既包括用于描述数据的内部数据结构,也包括用于显示数据的图形原语。例如,股票价格数组和时间数组是数据的计算表示,而x-y图是图形表示。

可视化将计算形式转换为图形形式。VisTrails多视图可视化系统。VisTrials支持可视化管道的交互式创建,维护它们的发展历史,优化它们的执行,并允许多个管道在电子表格风格的布局中进行比较。

可视化管道从面向对象的观点来看,转换是功能模型中的过程,而表示是对象模型中的对象。因此,我们用功能模型和对象模型来描述可视化模型。一个简单的二次曲线数学函数将阐明这些概念。这个函数

是二次曲线的数学表示。图4- 1 (a)显示了方程4- 1在该区域的可视化结果。可视化过程如下。我们以分辨率对规则网格上的数据进行采样。然后使用三种不同的可视化技术。在左边,我们生成对应于函数的3D曲面,其中c是任意常数(即等值面值)。在中心,我们展示了三个不同的平面,它们穿过数据,并根据函数值进行着色。在右边,我们展示了同样的三个平面,这些平面都是用常值线绘制的。围绕每个我们放置线框轮廓。

  • 函数模型

图4 - 1 (b)中的功能模型说明了创建可视化的步骤。椭圆块表示我们对数据执行的操作(流程),矩形块表示表示并提供对数据访问的数据存储(对象)。箭头表示数据移动的方向。指向一个块的箭头是输入;流出块的数据表示输出。这些块还可以具有作为额外输入的本地参数。无需输入就创建数据的进程称为数据源对象,或简称为源。使用没有输出的数据的进程称为接收器(也称为映射器,因为这些进程将数据映射到最终图像或输出)。同时具有输入和输出的流程称为过滤器。

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

  • 可视化模型

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

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

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

第一种选择是将数据存储(对象属性)和进程(对象方法)组合成一个对象。在第二种选择中,我们为数据存储和进程使用单独的对象。实际上还有第三种选择:这两种选择的混合组合。传统的面向对象方法(我们上面的第一个选择)将数据存储和进程组合到一个对象中。

该视图遵循标准定义,即对象包含数据表示,并结合对数据进行操作的过程。这种方法的一个优点是过程(即数据可视化算法)可以完全访问数据结构,从而获得良好的计算性能。但这种选择有几个缺点。

•从用户的角度来看,流程通常被视为独立于数据表示的。换句话说,流程自然地被视为系统中的对象。例如,我们经常说我们想要“等高线”数据,这意味着创建与常数数据值对应的线或面。对于用户来说,使用一个轮廓对象对不同的数据表示进行操作是很方便的。

•我们必须复制算法实现。与前面的轮廓化示例一样,如果将数据存储和进程绑定到单个对象中,则必须为每种数据类型重新创建轮廓化操作。即使算法的实现在功能和结构上相似,这也会导致代码重复。修改这样的算法也意味着修改大量的代码,因为它们是跨许多对象实现的。

•将数据存储和算法绑定在一起会导致复杂的、依赖数据的代码。有些算法可能比它们操作的数据复杂得多,有大量的图4-2对象模型设计选择。一个基本的选择是将进程和数据存储组合到一个对象中。这是通常的面向对象的选择。另一种选择是创建单独的数据对象和流程对象。流程数据存储数据代表数据对象方法流程对象组合流程和数据对象分离流程和数据对象4.2可视化管道85个实例变量和精细的数据结构。通过将许多这样的算法与数据存储相结合,对象的复杂性大大增加,对象的简单意义也失去了。

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

第二种选择的一个缺点是数据表示和流程之间的接口更加正式。因此,必须仔细设计接口,以确保良好的性能和灵活性。另一个缺点是数据和流程的强烈分离会导致重复的代码。也就是说,我们可以实现重复算法的操作,并且不能严格地将其视为数据访问方法。这种情况的一个例子是计算数据导数。这个操作不仅仅是简单的数据访问,所以严格来说它不属于数据对象方法。

因此,为了计算导数,每次需要计算导数时,我们都必须复制代码。(或者创建一个函数或宏的过程库!)考虑到这些问题,我们在可视化工具包中使用了混合方法。我们的方法与上面描述的第二种方法最接近,但是我们选择了在数据对象中实现的一小部分关键操作。这些操作是根据我们实现可视化算法的经验确定的。这有效地结合了前两个选择,以获得最大的好处和最小的缺点。

4.2可视化管道

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

数据对象

数据对象表示信息。数据对象还提供了创建、访问和删除这些信息的方法。除了通过形式对象方法外,不允许直接修改数据对象表示的数据。此功能是为流程对象保留的。还可以使用其他方法获取数据的特征特征。这包括确定最小和最大数据值,或确定对象中数据值的大小或数量。数据对象因其内部表示不同而不同。内部表示对数据的访问方法,以及与数据对象交互的流程对象的存储效率或计算性能有重大影响。

因此,不同的数据对象可以用来表示相同的数据,这取决于对效率和流程通用性的需求。可视化管道流程对象流程对象对输入数据进行操作以生成输出数据。process对象可以从其输入中派生新数据,也可以将输入数据转换为新形式。例如,流程对象可以从压力场中获得压力梯度数据,或者将压力场转换为定值压力轮廓。流程对象的输入包括一个或多个数据对象以及控制其操作的局部参数。局部参数既包括实例变量,也包括对其他对象的关联和引用。例如,中心和半径是控制球体原语生成的局部参数。

流程对象

进一步被描述为源对象、筛选器对象或映射器对象。这种分类是基于对象是初始化、维护还是终止可视化数据流。源对象与外部数据源接口或从本地参数生成数据。从局部参数生成数据的源对象称为过程对象。图4-1的前一个例子使用一个过程对象为公式4-1的二次函数生成函数值。与外部数据接口的源对象称为读取器对象,因为必须读取外部文件并将其转换为内部格式。

源对象还可以连接到外部数据通信端口和设备。可能的例子包括模拟或建模程序,或测量温度、压力或其他类似物理属性的数据采集系统。过滤器对象需要一个或多个输入数据对象,并生成一个或多个输出数据对象。局部参数控制流程对象的操作。计算每周股票市场平均值,将数据值表示为缩放图标,或在两个输入数据源上执行并集操作是过滤器对象的典型示例过程。Mapper对象对应于功能模型中的接收器。

Mapper对象需要一个或多个输入数据对象,并终止可视化管道数据流。通常,mapper对象用于将数据转换为图形原语,但它们也可以将数据写入文件或与另一个软件系统或设备的接口。将数据写入计算机文件的Mapper对象称为写入器对象。

4.3管路拓扑

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

管道连接

管道的元素(源、过滤器和映射器)可以以多种方式连接以创建可视化网络。然而,当我们试图组装这些网络时,会出现两个重要的问题:类型和多样性。类型是指处理对象作为输入或作为输出生成的数据的形式或类型。

例如,球体源对象可以生成一个多边形或面表示法,一个隐式表示法(例如,圆锥方程的参数),或一组三维空间离散化表示法中的占用值。Mapper对象可能以多边形、三角形条、线或点几何表示形式作为输入。流程对象的输入必须正确指定才能成功操作。

图4-3维护兼容数据类型(a)单一类型系统不需要类型检查。(b)

在多类型系统中,只有兼容的类型才能连接在一起。

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

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

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

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

各种类型的输入和输出。

我们需要区分输出的多样性是什么意思。大多数源和过滤器只生成一个输出。当一个对象生成一个用于多个对象输入的输出时,就会发生多个扇出。这将发生,例如,当一个源对象被用来读取一个数据文件,结果数据被用来生成数据的线框轮廓,加上数据的轮廓(例如,图4 - 1 (a))。当一个对象生成两个或多个输出数据对象时,就会发生多重输出。多输出的一个例子是将梯度函数的x、y和z分量作为不同的数据对象生成。多个扇出和多个输出的组合是可能的。在到目前为止所描述的例子中,可视化网络是没有循环的。

在图论中,这些被称为有向无环图。然而,在某些情况下,在我们的可视化网络中引入反馈循环是可取的。可视化网络中的反馈循环允许我们将流程对象的输出导向上游以影响其输入。

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

图4-5可视化网络循环。这个例子实现了线性积分。创建样本点是为了初始化循环过程。一旦流程开始,就使用集成过滤器的输出来代替样本点。

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

4.4执行管路

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

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

大多数情况下,可视化网络会被执行不止一次。例如,我们可以更改流程对象的参数或其输入。这通常是由于用户交互:用户可能正在探索或系统地改变输入以观察结果。在对流程对象或其输入进行一次或多次更改之后,我们必须执行网络以生成最新的结果。为了获得最高的性能,可视化网络中的流程对象只有在其输入发生更改时才必须执行。

在一些网络中,如图4 - 6所示,我们可能有并行分支,如果对象在特定分支的本地被修改,这些分支就不需要执行。在这个图中,我们看到对象D和下游对象E和F必须执行,因为D的输入参数被改变了,而对象E和F依赖于D的输入。其他对象不需要执行,因为它们的输入没有改变。

我们可以使用需求驱动或事件驱动的方法来控制网络的执行。在需求驱动方法中,我们只在请求输出时执行网络,并且只有网络的那一部分影响结果。在事件驱动方法中,对流程对象或其输入的每次更改都会导致网络重新执行。事件驱动方法的优点是输出总是最新的(除了短时间的计算)。需求驱动方法的优点是可以处理大量的更改

图4-6网络执行。如果更改是特定分支的本地更改,则不需要执行并行分支。

无需中间计算(即仅在接收到数据请求后才处理数据)。需求驱动的方法最大限度地减少了计算量,并产生了更具交互性的可视化网络。

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

显式执行

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

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

显式方法的缺点是每个流程对象都依赖于执行者,因为任何更改都必须通知执行者。同样,如果网络执行是有条件的,执行者不能轻易控制执行,因为是否执行取决于一个或多个进程对象的本地结果。最后,集中执行会在并行计算环境中产生不可伸缩的瓶颈。显式方法可以是需求驱动的,也可以是事件驱动的。

在事件驱动方法中,每当对象发生更改时(通常是响应用户界面事件),执行人员就会收到通知,并且网络立即执行。在需求驱动方法中,执行者累积对对象输入的更改,并基于显式用户执行网络

需求。

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

隐式执行

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

这个过程一直重复,直到初始请求对象执行并终止该过程。这两个步骤称为更新和执行传递。隐式网络执行自然是使用需求驱动控制实现的。在这里,网络执行只在请求输出数据时发生。隐式网络执行也可能是事件驱动的,如果我们每次遇到适当的事件(例如

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

更改为对象参数)。

隐式控制方案的主要优点是简单。每个对象只需要跟踪其内部修改时间。当请求输出时,对象将其修改时间与输入的修改时间进行比较,如果过期则执行。此外,流程对象只需要知道它们的直接输入,因此不需要了解其他对象(例如网络执行人员)的全局知识。隐式控制的缺点是很难在计算机间分布网络执行或实现复杂的执行策略。一种简单的方法是创建一个按照网络执行顺序执行流程对象的队列(可能采用分布式方式)。

当然,一旦一个中心对象被引入系统,隐式控制和显式控制之间的界限就模糊了。可视化网络的另一个重要功能是条件执行。例如,我们可能希望根据数据范围的变化,通过不同的颜色查找表来映射数据。在数据范围内分配更多的颜色可以放大小的变化,而我们可以通过在数据范围内分配少量的颜色来压缩我们的颜色显示(图4 - 8)。

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

4.5内存和计算的权衡

在计算机内存和计算方面,可视化是一个要求很高的应用程序

图4-9典型网络的静态与动态内存模型比较在更复杂的动态模型中,我们可以通过执行更彻底的依赖分析来防止B执行两次。

要求。1mb到1gb量级的数据流并不少见。许多可视化算法在计算上是昂贵的,部分原因是输入的大小,但也由于固有的算法复杂性。为了创建具有合理性能的应用程序,大多数可视化系统都有各种机制来权衡内存和计算成本。

静态和动态内存模型

在执行可视化网络时,内存和计算的权衡是重要的性能问题。在迄今为止介绍的网络中,流程对象的输出被假定为下游流程对象在任何时候都可用。因此,网络计算量最小化。然而,保存过滤器输出的计算机内存需求可能是巨大的。只有几个对象的网络可以占用大量的计算机内存资源。

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

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

图4-10引用计数节省内存资源。每个过滤器A, B,和

C具有公共点表示法。其他数据是每个对象的本地数据。

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

引用计数和垃圾收

集最小化内存成本的另一个有价值的工具是使用引用计数共享存储。为了使用引用计数,我们允许多个流程对象引用同一个数据对象,并跟踪引用的数量。例如,假设我们有三个对象A、B和C,它们组成了如图4 - 10所示的可视化网络的一部分。还要假设这些对象只修改其输入数据的一部分,而指定x-y-z坐标位置的数据对象保持不变。然后,为了节省内存资源,我们可以允许每个流程对象的输出引用表示这些点的单个数据对象。

更改的数据对每个筛选器来说都是本地的,不共享。只有当引用计数归零时,对象才会被删除。垃圾收集是另一种内存管理策略,不太适合可视化应用程序。垃圾收集过程是自动的;它尝试回收运行中的应用程序永远不会再次访问的对象所使用的内存。虽然由于它的自动化性质而方便,但通常垃圾收集会在不适当的时间(在交互过程中)不经意地在软件执行中引入暂停。然而,更值得关注的是,释放的、未使用的内存可能直到最后一个内存引用被删除后的一段时间才会被系统回收,并且在可视化管道中,这些内存可能太大了,无法在任何时间内保留。也就是说,在某些应用程序中,如果过滤器中的内存使用量没有立即释放,则下游过滤器可能没有足够的内存资源来成功执行。

4.6高级可视化管道模型

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

处理未知数据集类型

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

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

因此,设计用于处理任何类型数据的管道必须仔细组装,以创建健壮的应用程序。如本章前面所述,管道由流程对象操作的数据对象组成。此外,由于流程对象与它们所操作的数据对象是分开的,因此必须有一个预期的接口,这些对象通过该接口交换信息。定义这个接口有巩固数据表示的副作用,这意味着很难在不修改相应接口的情况下扩展它,因此依赖于该接口的所有类(有很多)。幸运的是,容易改变的不是基本数据表示(这些通常是很好的),而是与数据集本身相关的元数据发生了变化。(在可视化的上下文中,元数据是描述数据集的数据。)虽然通过创建新类来表示新数据集是可行的(因为添加新数据集类型并不经常发生);元数据的多样性阻止了创建新类,因为由此产生的数据类型爆炸和编程接口的潜在更改将对可视化系统的稳定性产生不利影响。因此,需要一种支持元数据的通用机制。将元数据打包到一个包含数据集和96可视化管道元数据的通用容器中是一种明显的设计,并且与前一节中描述的设计兼容。

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

管理复杂的执行策略

在真实的应用程序中,到目前为止所描述的管道设计可能不能充分支持复杂的执行策略,或者当数据量变大时可能无法成功执行。在下一节中,我们将通过考虑其他设计可能性来解决这些问题。

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

图4-11用幽灵级别的单元格和点(蓝色和绿色)将球体分成一片(红色)。

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

1. 可分。数据必须是可分离的。也就是说,数据可以被分解成小块。理想情况下,每个部分应该在几何、拓扑和/或数据结构上一致。数据的分离应该简单有效。此外,该体系结构中的算法必须能够正确地处理数据。

2. 可映射。为了控制通过管道的数据流,我们必须能够确定需要输入数据的哪一部分来生成给定的输出部分。这允许我们通过管道控制数据的大小,并配置算法。

3.结果不变。图4-11用鬼影级别的单元格和点(蓝色和绿色)将一个球体划分为一块(红色)。这意味着适当地处理边界,并开发跨边界可能重叠的部分的多线程安全算法。

如果数据是结构化的,即拓扑规则的(参见134页的“数据集类型”),那么将数据分离成碎片是相对简单的。这样的数据集可以用正则x-y-z细分立方域中的矩形范围进行拓扑描述(见图5 - 7 (a)-(c))。然而,如果数据是非结构化的(例如三角形或多边形的网格),那么指定块是困难的。一般来说,非结构化范围是通过将相邻数据(例如,单元格)分组成块,然后使用N (M)表示法对每个块进行寻址,其中N是M个块中的第N个块。数据块的确切组织结构未指定,取决于用于分组数据的特定应用程序和算法。为了满足结果不变性的第三个要求,处理片段还需要能够生成边界数据,或幽灵级别。

边界信息是必要的,当一个块的邻居信息需要执行计算。例如,梯度计算或边界分析(例如,我是否有一个单元格面邻居?)需要一个级别的边界信息。在极少数情况下,需要两个或更多级别。图4 - 11显示了与球体中心红色部分对应的边界单元格和点。

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

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

解决这些需求意味着必须扩展执行模型。

因此,我们将在下一节中重新讨论面向对象的设计。

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

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

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

4.7编程模型

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

可视化模型

在最高层次上是应用程序。可视化应用程序具有针对特定应用领域的精细定制的用户界面,例如流体流动可视化。应用程序最容易使用,但最不灵活。由于固有的后勤问题,用户很难或不可能将应用程序扩展到一个新的领域。商业交钥匙可视化软件通常被认为是应用软件。与之相对的是编程库。传统的编程库是对特定于库的数据结构进行操作的过程的集合。这些库通常是用C或FORTRAN等传统编程语言编写的。

它们提供了很大的灵活性,并且可以很容易地与其他编程工具和技术结合使用。编程库可以通过添加用户编写的代码来扩展或修改。不幸的是,有效地使用编程库需要熟练的程序员。此外,随着执行模型变得越来越复杂,执行策略从数据和流程对象中分离出来,作为单独的类。过程对象数据对象过程对象数据对象执行4.7编程模型99图形/可视化专家不能很容易地使用编程库,因为没有如何正确地将过程组合在一起的概念。随着输入参数的变化,这些库还需要大量的同步方案来控制执行。许多可视化系统介于这两个极端之间。它们通常使用可视化编程方法来构建可视化网络。其基本思想是提供图形化工具和模块或流程对象库。使用简单的图形布局工具,可以根据输入/输出类型约束连接模块。

此外,用户界面工具允许将界面小部件与对象输入参数相关联。系统执行通常通过内部执行执行对用户透明。还有另外两个图形和可视化编程模型值得一提。这些是场景图和电子表格模型。

场景图通常可以在3D图形系统中找到,例如OpenInventor [Wernecke94]。场景图是一种非循环树形结构,它按照树布局定义的顺序表示对象或节点。节点可以是定义完整场景的几何(称为形状节点)、图形属性、转换、操纵器、灯光、摄像机等等。父/子关系控制在节点被渲染时如何将属性和转换应用到节点上,或者对象如何与场景中的其他对象相关(例如,灯光照射在哪些对象上)。场景图不是用来控制可视化管道的执行,而是用来控制渲染过程。场景图和可视化管道可以在同一个应用程序中一起使用。在这种情况下,可视化管道是形状节点的生成器,场景图控制包括形状在内的场景的渲染。场景图在图形社区中得到了广泛的应用,因为它们能够简洁而图形化地表示一个场景。此外,场景图最近在VRML和Java3D等Web工具中得到了广泛应用。详见第11章。

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

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

4.8数据接口问题

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

编程接口

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

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

一般来说,编程一个像VTK这样复杂的系统是一项困难的工作,因为初始的学习曲线。但是,有一些更简单的方法来连接数据。虽然可能需要熟练的开发人员来创建复杂的应用程序,但像VTK这样的面向对象工具包的优点在于,它提供了与公共数据表单接口所需的许多部分。因此,将重点放在导入和导出数据的对象上是与数据交互的良好开端。在VTK中,这些对象被称为读取器、写入器、导入器和导出器。文件接口(读取器/写入器)。

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

阅读器的例子包括vtkSTLReader(读取立体光刻文件)和vtkBYUReader(读取电影。杨百翰格式化数据文件)。类似地,对象vtkSTLWriter和vtkBYUWriter可用于写入数据文件。要了解4.9把所有东西放在一起101 VTK支持哪些阅读器和写入器,请参阅VTK用户指南或参阅http://www.vtk.org的Web页面以获取当前的Doxygen手册页面。

文件接口(进口商/出口商)。导入器和导出器是系统中的对象,用于读写由多个对象组成的数据文件。通常,导入器和导出器用于保存或恢复整个场景(例如,灯光、摄像机、演员、数据、转换等)。在执行导入器时,它读取一个或多个文件,并可能创建多个对象。

例如,在VTK中,vtk3DSImporter导入3D Studio文件并创建渲染窗口、渲染器、灯光、摄像机和演员。类似地,给定一个VTK呈现窗口,vtkVRMLExporter创建一个VRML文件。VRML文件包含摄像机、灯光、演员、几何图形、转换等等,这些都是由提供的呈现窗口间接引用的。在可视化工具包中,有几个导入器和导出器。要了解VTK支持哪些进口商和出口商,请参阅VTK用户指南。您还可以在http://www.vtk.org上查看当前的Doxygen手册页面。如果您正在寻找的导出器不存在,则必须使用编程接口开发自己的导出器。图4 - 13显示了从3D Studio模型创建的图像,并保存为Renderman RIB文件。

应用接口

大多数用户通过使用现有的应用程序来访问他们的数据。用户无需编程管道或编写自己的读取器和写入器,而是获得适合其特定可视化需求的应用程序。然后,用户只需识别能够成功处理数据的读取者、写入者、导入者和/或导出者,就可以与他们的数据进行交互。在某些情况下,用户可能必须修改用于生成数据的程序,以便以标准数据格式导出数据。使用现有应用程序的优点是,用户界面和管道是预先编程的,确保用户可以专注于他们的数据,而不是花费大量的资源来编写可视化程序。使用现有应用程序的缺点是通常缺少必要的特性,应用程序通常缺乏通用工具所能提供的灵活性。选择正确的应用程序并不总是那么简单。

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

4.9把一切放在一起

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

图4-13 VTK导入导出文件。导入器创建了一个描述场景的vtkRenderWindow。导出器使用vtkRenderWindow的一个实例来获得场景的描述(3dsToRIB。TCL和火烈鸟。tcl)。

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

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

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

管道设计与实现

可视化工具包实现了一个通用的执行机制。过滤器分为两个基本部分:算法和执行对象。算法对象的类派生自vtkAlgorithm,它负责处理信息和数据。执行对象的类派生自vtkExecutive,它负责告诉算法何时执行以及要处理哪些信息和数据。过滤器的执行组件可以独立于算法组件创建,允许自定义管道执行机制,而无需修改核心VTK类。过滤器产生的信息和数据存储在一个或多个输出端口中。输出端口对应于过滤器的一个逻辑输出。例如,产生彩色图像和相应的二进制掩码图像的过滤器将定义两个输出端口,每个端口保存其中一个图像。与管道相关的信息存储在每个输出端口上的vtkInformation实例中。输出端口的数据存储在派生自vtkDataObject的类的实例中。过滤器使用的信息和数据通过一个或多个输入端口检索。一个输入端口对应于过滤器的一个逻辑输入。例如,一个字形过滤器将为字形本身定义一个输入端口,而另一个输入端口定义字形位置。输入端口存储引用其他滤波器输出端口的输入连接;这些输出端口最终向过滤器提供信息和数据。每个输入连接提供一个数据对象及其从连接到的输出端口获得的相应信息。由于连接是通过逻辑端口存储的,而不是在流经这些端口的数据中存储的,因此在建立连接时不需要知道数据类型。这在创建时特别有用

图4-15 VTK隐式执行流程描述Update()方法是通过参与者的Render()方法初始化的。数据通过RequestData()方法流回映射器。连接过滤器和数据对象的箭头表示

Update()的过程。

管道,它的源是一个读取器,直到文件被读取才知道它的输出数据类型(参见第86页的“管道连接”和第95页的“处理未知数据集类型”)。为了理解VTK管道的执行,从几个不同的角度来观察这个过程是很有用的。请注意,下面的每个图都不是完全准确的,而是作为描述来呈现的,其目的是描述过程的重要特性。

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

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

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

连接管道对象这将我们引向在过滤器和数据对象之间建立连接以形成可视化管道的方法。从前面的图中可以明显看出,Visualization Toolkit管道体系结构被设计为支持多个输入和输出。在实践中,您会发现大多数过滤器和源实际上只生成一个输出,并且过滤器接受一个输入。这是因为大多数算法在本质上倾向于单输入/输出。也有例外,我们将很快描述其中的一些例外。然而,首先,我们想提供一个与VTK管道架构的演变相关的简要历史教训。这一课是有指导意义的,因为它阐明了响应新需求的管道设计的演变。

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

filter2->SetInput(filter1->GetOutput());//在VTK5.0之前,

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

图4-17端口与连接的逻辑关系一个输入端口可能有多个关联的连接。在某些过滤器(如追加过滤器)中可能存在多个连接,其中单个逻辑输入端口表示要“追加”在一起的所有数据,每个输入由不同的连接表示。

旧的设计不支持延迟数据集类型检查。它很难支持产生不同类型输出的任意读取器类型或过滤器。

•更新和管理管道执行的策略隐式嵌入到流程对象和数据对象中。当策略变得更加复杂或需要更改时,这就需要修改数据和/或流程对象。

•在旧的设计中,在更新过程中很难中止管道执行。此外,不可能将错误检查集中起来;每个过滤器都必须做一些检查,从而重复代码。

•将元数据引入管道需要更改数据和流程对象的API。我们希望支持阅读器向数据流添加元数据的能力,并让管道中的过滤器检索元数据,而无需修改API。由于这个原因,以及与并行处理相关的其他原因,最初的VTK管道设计被重新设计。

虽然过渡是困难的,但是如果软件系统要随着技术的进步而变化和发展,这样的变化通常是必要的。

VTK 5.0及以上版本。虽然VTK 5.0仍然支持使用SetInput()/GetOutput(),但不鼓励使用它。相反,应该使用更新的管道体系结构。参考图4 - 16和图4 - 17,我们使用连接和端口配置VTK的可视化管道:

您可能已经猜到了如何将这种方法扩展到多个输入和多个输出。让我们看一些具体的例子。vtkGlyph3D是一个接受多个输入并生成单个输出的过滤器示例。vtkGlyph3D的输入由Input和Source实例变量表示。

vtkGlyph3D的目的是将Source中数据定义的几何图形复制到Input定义的每个点。几何图形根据源数据值(例如,标量和向量)进行修改。(有关字形的更多信息,请参阅190页的“字形”。)要在c++代码中使用vtkGlyph3D对象,您将执行以下操作:

其中foo和bar是返回适当输出类型的过滤器。

类vtkextractectorcomponents是一个具有单个输入和多个输出的过滤器的示例。该过滤器将3D矢量的三个分量提取为单独的标量分量。它的三个输出在输出端口0、1和2上可用。下面是过滤器的使用示例:

还有其他一些具有多个输入或输出的特殊对象。一些比较值得注意的类是vtkMergeFilter、vtkAppendFilter和vtkAppendPolyData。这些过滤器组合多个管道流并生成单个输出。但是请注意,虽然vtkMergeFilter有多个输入端口(即不同的逻辑输入),但vtkAppendFilter只有一个逻辑输入,但假设有多个连接连接到该输入。这是因为在vtkMergeFilter中,每个输入都有一个不同的、单独的目的,而在vtkAppendFilter中,所有输入都有相同的含义(即,只是在一个列表中增加了一个要附加在一起的输入)。下面是一些代码片段:

注意使用了AddInputConnection()方法。该方法添加到连接列表中,而SetInputConnection()则清除该列表并指定到端口的单个连接。另一个重要的过滤类是vtkProbeFilter。这个过滤器需要两个输入。第一个输入是我们希望探测的数据。

第二个输入提供了一组用作探测点的点。一些流程对象接受输入数据列表。另一个有趣的过滤器是vtkBooleanStructuredPoints类,它对卷数据集执行集合操作。列表中的第一个数据项用于初始化set操作。列表中的每个后续项都使用用户指定的布尔运算与前面操作的结果相结合。有关过滤器和数据对象的对象设计的更多细节,请参见第5章和第6章。

管道执行和信息对象

到目前为止,我们一直在非正式地使用术语元数据和信息对象。如前所述,在VTK上下文中,这些术语指的是描述数据集的数据。在本节中,我们将展示如何使用这些对象(它们是vtkInformation的子类)来促进VTK管道的执行。信息对象。信息对象是VTK管道中用于保存各种元数据的基本容器。它们是异构的键到值映射,其中键的类型决定值的类型。下面是使用信息对象的位置的枚举。•管道信息对象保存管道执行的信息。它们存储在vtkExecutive或子类的实例中,可以通过方法vtkExecutive::GetOutputInformation()访问。每个输出端口有一个管道信息对象。它包含一个条目,指向相应端口上的输出vtkDataObject(如果它已经创建)。

vtkDataObject包含一个指向其相应管道信息对象的指针,可以通过vtkDataObject::GetPipelineInformation()访问。

管道信息对象还保存有关在过滤器执行和生成输出时填充数据对象的内容的信息。所包含的实际信息由输出数据类型和所使用的执行模型决定。输入连接的管道信息对象可以通过方法vtkExecutive::GetInputInformation()访问,它们是输入端口连接到的输出端口上的管道信息对象。

•端口信息对象保存有关输出端口产生和输入端口使用的数据类型的信息。它们由vtkAlgorithm的实例存储。每个输入端口有一个输入端口信息对象,每个输出端口有一个输出端口信息对象。它们可以通过方法vtkAlgorithm::GetInputPortInformation()和vtkAlgorithm::GetOutputPortInformation()访问。端口信息对象通常由vtkAlgorithm的子类创建和填充,以便指定筛选器的接口。

•请求信息对象保存有关发送给高管或算法的特定请求的信息。其中有一个条目表明正在发送什么请求,其他条目可能提供关于特定请求的其他详细信息。这些信息对象不能通过任何公共方法访问,而是传递给实现请求的ProcessRequest()方法。

•数据信息对象保存有关当前存储在vtkDataObject中的信息。每个数据对象中都有一个数据信息对象,可通过

图4-18管道请求路径例如,假设消费者(在最右边)只需要这个数据中的一个片段(例如,4个片段中的第1段);还假设生产者(在最左边)是一个读取器,可以将其数据划分为多个部分。消费者向上游传递这个请求,然后它继续向上游(通过执行者),直到到达能够满足请求的生产者。当读取器算法被要求提供一段数据时,它会提供该数据,并沿着管道将新数据传递回来(带有4段中的第1段的信息)。当它到达发出请求的消费者时停止。

vtkDataObject: GetInformation()。实际包含的信息由数据对象类型决定。

•算法信息对象保存关于vtkAlgorithm实例的信息。每个算法对象都有一个算法信息对象,可以通过vtkAlgorithm::GetInformation()访问。实际包含的信息由算法对象类型决定。VTK中信息对象的重要性在于它们是灵活的(例如,可以轻松添加新的键-值对)和可扩展的。也就是说,读取器、过滤器和映射器可以向容器添加新信息,而不需要管道相关类的API更改。

管道执行模型。在VTK中,基本的管道更新机制是基于请求的。请求是基本的管道操作(或“管道通道”),通常要求通过管道传播特定的信息。执行模型是由特定执行者定义的一组请求。以下执行流程描述请参见图4 - 18。请求是由过滤器的执行对象生成的,由于某些用户调用,该过滤器的算法显式要求更新该过滤器。

例如,当写入器的Write()方法被调用时,算法对象通过调用->GetExecutive()-> update()要求它的执行者更新管道并执行写入器。为了使管道更新,可以通过管道发送几个请求。请求作为信息对象实现。有一个类型为vtkInformationRequestKey的键,用于指定请求本身。这个键通常由执行者的类定义。关于请求的其他信息也可以存储在请求信息对象中。执行ProcessRequest() ProcessRequest()算法图4-18管道请求路径例如,假设消费者(在最右边)只需要这个数据中的一个片段(例如,4个片段中的第1段);还假设生产者(在最左边)是一个读取器,可以将其数据划分为多个部分。

消费者向上游传递这个请求,然后它继续向上游(通过执行者),直到到达能够满足请求的生产者。当读取器算法被要求提供一段数据时,它会提供该数据,并沿着管道将新数据传递回来(带有4段中的第1段的信息)。当它到达发出请求的消费者时停止。执行执行ProcessRequest() ProcessRequest()算法算法ProcessRequest()开始请求结束请求110可视化管道请求由每个过滤器的执行人员通过管道传播。给定请求信息对象,在执行器上调用vtkExecutive::ProcessRequest()方法。此方法由每个执行人员实现,并负责在其认为合适的情况下实现请求。

一个过滤器的许多请求只有在提供其输入的过滤器完成之后才能被满足。对于这些请求,执行人员将把请求传递给这些上游过滤器的执行人员,然后自己处理请求。执行程序经常向其算法对象请求帮助以实现请求。它通过调用vtkAlgorithm::ProcessRequest()方法将请求发送给算法对象。此方法由所有算法实现,并负责处理请求。输入和输出管道信息对象作为方法的参数提供。算法必须只使用它自己的过滤器参数设置和给定的管道信息对象来处理请求。算法不允许向它的执行者询问任何额外的信息。这确保了算法独立于执行器。

图4 - 18显示了请求通过管道发送时的典型路径。通常,请求来自管道末端的使用者。它由高管们通过管道发回。每位高管都要求自己的算法帮助处理请求。

灵活的计算/内存权衡

默认情况下,使用可视化工具包构建的网络存储中间计算结果(即有利计算)。然而,当不再需要中间数据时,可以设置单个类变量来丢弃它们(例如,有利内存)。此外,可以在每个流程对象中设置一个局部参数,以在对象级控制这种权衡。

该全局变量的设置如下。给定数据对象O(或使用O=filter->GetOutput()获得的过滤器的输出),调用O->SetGlobalReleaseDataFlagOn()来启用数据释放。要启用特定对象的数据发布,请使用O->SetReleaseDataFlagOn()。也有适当的方法来禁用内存释放。在本文的这一点上,描述组成可视化管道的各种对象的设计细节还为时过早。然而,有两个重要的类会影响文本中的许多对象。这些是类vtkObject和vtkObjectBase。vtkObjectBase是VTK中几乎所有继承层次结构的基对象。vtkObjectBase实现了数据对象引用计数(参见第94页的“引用计数和垃圾收集”)。vtkObjectBase的子类可以由其他对象共享,而不需要复制内存。它还为对象定义了一个API,用于打印关于对象自身的信息。vtkObject是vtkObjectBase的子类。它提供方法和实例变量来控制运行时调试并维护内部对象修改时间。具体来说,方法Modified()用于更新修改时间,方法GetMTime()用于检索修改时间。vtkObject还为我们在前一章中看到的事件回调提供了一个框架(参见第63页的“事件和观察者”)。注意,为了节省空间,我们并不总是在对象图中包含vtkObject和vtkObjectBase。请参阅源代码以获得明确的语句。

现在,我们将通过四个示例演示可视化管道的一些特性。这里使用的一些对象您可能不熟悉。请忽略遗漏的细节,直到我们在本书后面介绍这些信息。这里的目标是让您熟悉软件体系结构及其用法。

简单的球体。第一个示例演示了一个简单的可视化管道。球体的多边形表示是用源对象(vtkSphereSource)创建的。球体通过一个过滤器(vtkElevationFilter),该过滤器计算球体在平面上的每个点的高度。平面垂直于z轴,并经过点(0,0,-1)。最后通过查找表映射数据(vtkDataSetMapper)。映射过程将高度值转换为颜色,并将球体几何图形接口到渲染库。将映射器分配给一个参与者,然后显示该参与者。可视化网络、部分代码和输出图像如图4 - 19所示。当我们呈现参与者时,管道的执行隐式地发生。每个参与者要求它的映射器更新自己。mapper反过来要求它的输入更新自己。这个过程一直持续到遇到源对象为止。如果自上次呈现后修改了源代码,则将执行。图4-19

一个简单的球的例子(ColorSph。cxx)。

vtkSphereSource vtkElevationFilter vtkDataSetMapper vtkSphereSource *sphere = vtkSphereSource::New();

球→SetPhiResolution (12);

球→SetThetaResolution (12);

vtkElevationFilter *colorIt = vtkElevationFilter::New();

colorIt→SetInputConnection(球体→GetOutputPort ());

colorIt→SetLowPoint (0, 0, 1);

colorIt→SetHighPoint (0, 0, 1);

vtkDataSetMapper *mapper = vtkDataSetMapper::New();

映射器→SetInputConnection (colorIt→GetOutputPort ());

vtkActor *actor = vtkActor::New();

演员→SetMapper(映射);

然后,系统遍历网络并执行每个对象,如果它的输入或实例变量过期了。完成后,参与者的映射器是最新的,并生成图像。现在让我们通过跟踪方法调用来重新检查相同的管道执行过程。当参与者从呈现器接收到Render()消息时,该过程开始。参与者依次向它的映射器发送Render()消息。映射器通过update()操作要求其输入更新自己,从而开始网络执行。这将导致Update()方法的级联,因为每个过滤器依次要求其输入更新自己。如果管道中存在分支,则更新方法也将进行分支。最后,级联在遇到源对象时终止。如果源对象已经过期,它将自己发送一个RequestData()命令。每个过滤器都会根据需要向自己发送一个RequestData()来更新自己。

最后,映射器将执行操作将其输入转换为呈现原语。在可视化工具包中,Update()方法是公共的,而RequestData()方法是受保护的。因此,您可以通过调用Update()操作手动导致网络执行。当您希望根据上游执行的结果在网络中设置实例变量,但不希望整个网络更新时,这可能很有用。RequestData()方法受到保护,因为它需要特定的对象状态才能存在。Update()方法确保此状态存在。

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

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

转换过滤器只对具有显式点坐标表示的对象(即vtkPointSet的子类)起作用。但是,提升过滤器会生成更通用的形式vtkDataSet作为输出。因此我们不能将变换过滤器连接到提升过滤器。但我们可以将变换过滤器连接到球面源,然后将仰角过滤器连接到变换过滤器。结果如图4 - 20所示。(注意:另一种方法是使用vtkCastToConcrete执行运行时强制转换。)

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

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

可视化网络分支位于vtkGlyph3D。如果任意一个分支被修改,那么这个筛选器将重新执行。网络更新必须在两个方向上进行分支,并且两个分支都必须是向上的

vtkGlyph3D执行时的日期。这些需求由Update()方法强制执行,并且不会对隐式执行方法造成问题。消失的球体。在我们的最后一个例子中,我们构造了一个带有反馈循环的可视化网络,并展示了如何使用过程性编程来改变网络的拓扑结构。该网络由四个对象组成:创建初始多边形几何的vtkSphereSource;

vtkShrinkFilter用于收缩多边形并在邻居之间创建间隙或空间,vtkElevationFilter用于根据x-y平面以上的高度为几何图形着色,vtkDataSetMapper用于通过查找表和接口将数据映射到呈现库。网络拓扑结构、部分c++代码和输出如图4 - 22所示。在vtkSphereSource生成初始几何图形(响应渲染请求)后,vtkShrinkFilter的输入被更改为vtkElevationFilter的输出。由于反馈循环,vtkShrinkFilter总是会重新执行。因此,网络的行为是在每次执行呈现时重新执行。由于收缩过滤器被重新应用于相同的数据,多边形变得越来越小,最终消失。

4.10章节小结

可视化过程自然地使用功能模型和对象模型的组合进行建模。功能模型可以简化并用于描述可视化网络。对象模型指定可视化网络的组件。可视化网络由过程对象和数据对象组成。数据对象表示信息;流程对象将数据从一种形式转换为另一种形式。有

vtkSphereSource *sphere = vtkSphereSource::New();

球→SetThetaResolution (12);

球→SetPhiResolution (12);

vtkShrinkFilter *收缩= vtkShrinkFilter::New();

缩小→SetInputConnection(球体→GetOutputPort ());

缩小→SetShrinkFactor (0.9);

vtkElevationFilter *colorIt = vtkElevationFilter::New();

colorIt→SetInputConnection(缩小→GetOutputPort ());

colorIt→SetLowPoint(0, 0,闲置);

colorIt→SetHighPoint(0, 0, 0。5);

vtkDataSetMapper *mapper = vtkDataSetMapper::New();

映射器→SetInputConnection (colorIt→GetOutputPort ());

vtkActor *actor = vtkActor::New();

演员→SetMapper(映射);renWin→渲染();

SetInputConnection(colorIt->GetOutputPort());

renWin→渲染();

图4-22环路组网(LoopShrk. loop)cxx)。

VTK 5.0不允许你执行一个循环可视化网络;这在以前版本的VTK中是可能的。vtkSphereSource vtkElevationFilter vtkDataSetMapper vtkShrinkFilter 116可视化管道处理对象-源没有输入,至少有一个输出;过滤器至少有一个输入和输出;接收器或映射器终止可视化网络。网络的执行可以隐式控制,也可以显式控制。隐式控制意味着每个对象必须确保其输入是最新的,从而分散了控制机制。显式控制意味着有一个集中的执行者来协调每个对象的执行。有许多技术可用于程序可视化网络。直接可视化编程在商业系统中最为常见。在更高的层次上,应用程序提供定制的但更严格的接口来可视化信息。在最底层,子例程或对象库提供了最大的灵活性。可视化工具包包含一个用c++实现的对象库,用于构建可视化网络。

4.11书目注释

了解可视化过程的实际方法是研究商业上可用的系统。这些系统可以分为直接可视化编程环境或应用程序。常见的可视化编程系统包括AVS [AVS89]、IrisExplorer [IrisExplorer]、IBM DataExplorer [DataExplorer]、aPE [aPE90]和Khoros [Rasure91]。应用程序系统通常比可视化编程系统提供更少的灵活性,但更适合特定的问题领域。PLOT3D [PLOT3D]是CFD可视化工具的早期例子。这已经被FAST [FAST90]所取代。FieldView是另一个流行的CFD可视化工具[FieldView91]。VISUAL3 [VISUAL3]是非结构化或结构化网格可视化的通用工具。PV-WAVE [Charal90]可以被认为是一个混合系统,因为它既有简单的可视化编程技术来连接数据文件,也有比可视化编程环境更结构化的用户界面。Wavefront的DataVisualizer [DataVisualizer]是一个通用可视化工具。它的独特之处在于它是一个强大的渲染和动画包的一部分。VIS5D是一个很好的三维网格数据可视化系统(例如由数值天气模型生成的数据)。更多信息请访问VIS5D网站http://www.ssec.wisc.edu/~billh/ VIS5D .html。尽管许多可视化系统都声称是面向对象的,但这通常在外观上多于实现上。关于可视化的面向对象设计问题的文章很少。VISAGE [VISAGE92]提供了一个类似于本章描述的体系结构。Favre [Favre94]描述了一种更为传统的面向对象方法。他的数据集类基于拓扑维度,数据和方法都被组合成类。

4.12参考资料

[aPE90]李志强。“一个用于可视化的数据流工具包。”计算机图形学与应用,10(4):60-69,1990。[AVS89]艾普生,小福哈伯,卡明斯等。应用可视化系统:科学可视化的计算环境计算机图形学与应用,9(4):30-42,1989。4.12文献117

[Bavoil2005] L. Bavoil, S.P. Callahan, P.J. Crossno, J. Freire, C.E. Scheidegger, C.T. Silva, H.T. Vo。VisTrails:启用交互式多视图可视化。2005年IEEE可视化会议论文集。IEEE计算机学会出版社,2005年。

[张志刚,张志刚。双曲偏微分方程的自适应网格细化。计算物理学报,43(3):484 - 512,1984年3月。[Charal90]张晓明。“欢迎新浪潮技术图形。”DEC用户,1990年8月。

[CruzNeira93]李国强,李国强。“环绕屏幕投影虚拟现实:CAVE的设计和实现。”《中国机械工程学报》1993年8月,第135-142页。

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

[DataVisualizer]数据可视化工具用户手册。波前技术,圣巴巴拉,加州,1990年。

[FAST90]李志强,李志强,李志强,等。FAST:用于可视化的多处理环境。在《可视化学报》90年。第14-27页,IEEE计算机协会出版社,Los Alamitos, CA, 1990。

[Favre94]李国强。多变量数据对象可视化的面向对象设计在94年的《可视化学报》上。第319-325页,IEEE计算机协会出版社,洛斯阿拉米托斯,加州,1994年。

[FieldView91]李志刚。桌面工作站的高级可视化。在《可视化学报》91年。第372-378页,IEEE计算机协会出版社,Los Alamitos, CA, 1991。

[Haeberli88]李国强。《ConMan:交互式图形的可视化编程语言》计算机图形学(SIGGRAPH ' 88)。22(4): 103 - 1988。

[汉弗瑞斯99]李国强。大型平铺显示器的分布式图形系统。见IEEE可视化99版,第215-224页,IEEE计算机协会出版社,1999年10月。

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

[footnoef: 5]刘国强,李国强。大型结构化数据集的多线程流管道体系结构在1999年的可视化课程中。IEEE计算机协会出版社,1999年。

[levoe94]李志强。“图像电子表格。”1994年SIGGRAPH学报。第139-146页,1994年。118 .可视化管道

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

[PLOT3D] P. P. Walatka和P. G. Buning。PLOT3D用户手册。美国宇航局流体动力学部,1988年。[asuu91]李志强,李志强。图像处理的视觉语言和软件开发环境。国际成像系统与技术杂志。1991. [VISAGE92]李志强,李志强。面向对象的可视化系统。在《可视化学报》92年。第219-226页,IEEE计算机协会出版社,Los Alamitos, CA, 1992。

[视觉3]海姆斯,贾尔斯。交互式非定常非结构化3D可视化AIAA报告编号张仁- 91 - 0794。1991年1月。

[刘志强,李志强,等。发明家导师。Addison-Wesley出版公司,ISBN 0-201-62495- 8,1994。

4.13运动

4.1考虑以下二维可视化技术:x-y绘图、条形图和饼图。对于每种技术:

a)构造功能模型。

b)构造对象模型。高度场是一个二维点的正则数组,其中h是点(x,y)以上的高度。高度字段通常用于表示地形数据。

4.2.设计一个面向对象的系统来可视化高度字段。

a)高度场如何表示?

b)你将使用什么方法来访问这些数据?

c)开发一个过程对象(即可视化技术)来可视化高度场。

4.3描述对象用于访问和操作高度字段的方法。描述你将如何实现网络执行的显式控制机制。

a)过程对象如何向执行者登记其输入数据?

b)如何通知执行人员修改对象?

c)通过什么方法通知执行人员网络执行是必要的?

d)描述网络依赖分析的方法。执行者如何调用流程对象的执行?

4.4可视化编程环境使用户能够通过图形化地连接过程对象来构建可视化应用程序。

a)设计一个图形符号来表示过程对象、它们的输入和输出以及数据流方向。

b)你将如何修改过程对象的实例变量(使用图形化技术hfxy=(), 4.13练习119 nique)?

c)通过什么机制启动网络执行?

d)你如何控制网络中的条件执行和循环?

e)你将如何利用并行计算?f)如何在共享网络连接的两台或多台计算机之间分配网络执行?

4.5在图4 - 20的杀手锏上放置定向圆柱(而不是锥)。

4.6 VTK所使用的可视化网络的隐式更新方法易于实现和理解。但是,它很容易出现常见的编程错误。这个错误是什么?

4.7对图4 - 20中的变换对象进行实验。

a)使用vtkTransform的Translate()方法翻译actor。

b)使用RotateX()、rotateey()和RotateZ()方法旋转actor。

c)使用Scale()方法缩放参与者。

d)尝试这些方法的组合。演员是否按照你所期望的方式转变?

4.8可视化下列函数。(提示:使用vtkSampleFunction,参考图4 - 1)a) b) c) Fxyz (),, x2 = Fxyz (),, = x2y3z1 ++ + Fxyz (),, x2y 2 = + - ()cos1 +

本书为英文翻译而来,供学习vtk.js的人参考。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值