vtk用户指南 第十七章 如何为VTK编写算法

本文介绍了在VTK中创建自定义算法的方法。实现VTK滤波器需设置管道接口、用户界面,编写完成管道请求的方法。还阐述了VTK算法原理,如不修改输入数据等。此外,给出了简单阅读器、流媒体过滤器等多种过滤器的实现示例,帮助开发者在VTK中编写自定义过滤器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        本节介绍如何在VTK中创建自己的算法。算法是产生数据(源)、对数据进行操作以生成新数据(过滤器)以及将数据连接到图形系统和/或其他系统(映射器)的对象。(人们也可以将算法一般称为过滤器。)您可能想要查看第25页的“可视化管道”以获取有关图形管道的信息。管道执行将在第1章中详细讨论
19.

17.1概述

实现算法或过程作为VTK滤波器需要三个基本步骤。
1. 设置管道接口。这指定过滤器使用和/或产生的输入和/或输出的数量和类型。
2. 设置用户界面。这为过滤器的用户提供了一种调整或控制算法参数的方法。
3. 编写方法来完成管道请求。它们提供了过滤器的核心功能,并包含了算法实现。

管道接口

过滤器的管道接口决定了它如何在VTK管道中连接和使用。使用数据的过滤器定义一个或多个输入端口,产生数据的过滤器定义一个或多个输出端口。每个端口对应于过滤器实现的算法的逻辑输入或输出。
定义管道接口的第一部分是选择合适的父类,从中派生实现新算法的类。所有过滤器都直接或间接地派生自vtkAlgorithm,但是直接推导只在少数情况下由高级过滤器使用。

VTK提供了vtkAlgorithm的几个子类,定义了大多数常见情况下的管道接口。过滤器派生自定义最适合其算法的管道接口的类。合适的超类取决于过滤器的用途以及它要处理的几何形状和数据的类型。超类名称旨在指示它生成的vtkDataObject的类型,尽管这通常也是它所使用的类型。

•图形过滤器几乎总是来自vtkPolyDataAlgorithm或vtkUnstructuredGridAlgorithm。

•成像过滤器几乎总是来自vtkImageAlgorithm或vtkthreaddimagealgorithm。

•根据支持的数据集类型,处理科学模拟结果的过滤器可能来自vtkreclineargridalgorithm, vtkStructuredGridAlgorithm或vtkUnstructuredGridAlgorithm。

•抽象过滤器可以派生自vtkdataobjectalgalgorithm、vtkdatasetalgalgorithm或vtkpointsetalgalgorithm,以便处理各种类型的数据对象。

•还有一些其他的超类意味着过滤器处理高级数据集类型超出了本书的范围。所有这些超类在默认情况下都定义了一个由一个输入端口和一个输出端口组成的管道接口。

在构建过滤器时,通过调用SetNumberOfInputPorts()和/或SetNumberOfOutputPorts()方法来设置端口的数量。例如,vtkPolyDataAlgorithm的构造函数包含以下代码。

vtkPolyDataAlgorithm::vtkPolyDataAlgorithm()
 {
 ...
 this->SetNumberOfInputPorts(1);
 this->SetNumberOfOutputPorts(1);
 }

过滤器可以在其构造函数中更改此数字。例如,vtkSphereSource不消耗任何数据(即,它是一个源对象),因此它的构造函数将输入端口的数量设置为零。

vtkSphereSource::vtkSphereSource()
 {
 ...
 this->SetNumberOfInputPorts(0);
 }

端口需求存储在端口信息对象中。这些信息对象由FillInputPortInformation()和FillOutputPortInformation()方法填充。上述超类提供了这些方法的默认实现,只需在每个输入端口上提供一个相应类型的数据对象,并在每个输出端口上提供一个这样的数据对象。例如,vtkPolyDataAlgorithm指定每个输入端口消耗一个vtkPolyData,每个输出端口产生一个vtkPolyData。

int
vtkPolyDataAlgorithm
 ::FillInputPortInformation(int, vtkInformation* info)
 {
 info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(),
 "vtkPolyData");
 return 1;
 }
 int
 vtkPolyDataAlgorithm
 ::FillOutputPortInformation(int, vtkInformation* info)
 {
 info->Set(vtkDataObject::DATA_TYPE_NAME(),
 "vtkPolyData");
 return 1;
 }

算法的一个逻辑输入实际上可能允许任意数量的数据对象,每个数据对象由不同的输入连接提供。如果一个输入端口允许零连接,则该端口是可选的。如果它允许多个连接,则称为可重复连接。例如,vtkGlyph3D消耗两个逻辑输入:一个提供描述字形位置的几何图形,另一个提供零个或多个要放置的字形。它的构造函数将输入端口的数量设置为2。

vtkGlyph3D::vtkGlyph3D()
 {
 ...
 this->SetNumberOfInputPorts(2);
 ...
 }

然后vtkGlyph3D实现FillInputPortInformation()来指定每个输入端口的需求。具体来说,输入端口0只需要一个与vtkDataSet派生的任何类型的数据集的连接,输入端口1支持零个或多个提供vtkPolyData对象的连接。

int vtkGlyph3D::FillInputPortInformation(int port,
 vtkInformation *info)
 {
 if (port == 0)
 {
 info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(),
 "vtkDataSet");
 return 1;
 }
 else if (port == 1)
 {
 info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 1);
 info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1);
info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
 return 1;
 }
 return 0;
 }
  • 用户界面 /户界面

许多算法定义了调整其行为的参数。VTK过滤器可以在其类中定义由实例变量存储的参数。设置和获取参数值的方法由“set / get”宏定义。这些宏实现了当参数改变时自动更新筛选器对象的修改时间的方法。

例如,vtkGlyph3D过滤器提供了一个“ScaleFactor”参数,用于控制在输出中放置输入字形时缩放的数量。该类定义了一个存储参数的成员变量,并使用vtkSetMacro和vtkGetMacro来定义用户界面方法SetScaleFactor()和GetScaleFactor()。

 class vtkGlyph3D
 {
 public:
 ...
 vtkSetMacro(ScaleFactor,double);
 vtkGetMacro(ScaleFactor,double);
 ...
 protected:
 double ScaleFactor;
 };

参数由过滤器的构造函数初始化为默认值。在vtkGlyph3D实现中,构造函数将ScaleFactor参数初始化为1.0。

 vtkGlyph3D::vtkGlyph3D()
 {
 ...
 this->ScaleFactor = 1.0;
 ...
 }

所有VTK类都定义了PrintSelf()方法,该方法打印有关类实例当前状态的信息。在算法实现的情况下,应该包括算法的参数。例如,vtkGlyph3D在其PrintSelf()方法中打印ScaleFactor的值。

void vtkGlyph3D::PrintSelf(ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 ...
 os << indent << "Scale Factor: "
 << this->ScaleFactor << "\n";
 ...
 }

完成管道请求

        一旦管道和用户界面已经建立,就可以在VTK管道中包含一个新的过滤器。最后一步是实现实际的算法。当管道更新时,过滤器可能会收到请求,要求它处理一些信息和/或数据。

        这些请求首先被发送到过滤器的执行对象,然后该对象可以通过调用虚拟方法vtkAlgorithm::ProcessRequest()将它们发送到算法实现。

        该方法被赋予请求信息对象和一组输入和输出管道信息对象,在这些对象上进行操作。它负责尝试完成请求并报告成功或失败。每个算法对象都必须提供ProcessRequest()的实现,因为它是算法执行的入口点。许多过滤器可以在不直接提供ProcessRequest()方法的情况下实现。标准筛选器超类提供了ProcessRequest()的默认实现,该实现通过将最常见的请求转换为对特定于请求的方法的调用来实现。例如,vtkPolyDataAlgorithm实现ProcessRequest()如下所示。

int
vtkPolyDataAlgorithm
::ProcessRequest(vtkInformation* request,
vtkInformationVector** inputVector,
vtkInformationVector* outputVector)
{
if(request->Has(vtkDemandDrivenPipeline::REQUEST_INFORMATION()))
{
return this->RequestInformation(request, inputVector,
outputVector);
}
if(request->Has(
vtkStreamingDemandDrivenPipeline::REQUEST_UPDATE_EXTENT()))
{
return this->RequestUpdateExtent(request, inputVector,
outputVector);
}
if(request->Has(vtkDemandDrivenPipeline::REQUEST_DATA()))
{
return this->RequestData(request, inputVector, outputVector);
}
return this->Superclass::ProcessRequest(request, inputVector,
outputVector);
}

当使用标准超类时,过滤器只需要实现RequestData()方法来包含其实际的算法实现。如果过滤器需要在输出和输入之间转换请求的范围,也可以实现RequestInformation()和RequestUpdateExtent()方法。传递给这些方法的管道信息包含它们应该操作的所有输入和输出数据对象。例如,vtkGlyph3D实现RequestData()如下所示。

int vtkGlyph3D::RequestData(vtkInformation* request,
vtkInformationVector** inputVector,
vtkInformationVector* outputVector)
{
vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
vtkInformation* outInfo = outputVector->GetInformationObject(0);
vtkDataSet* input = vtkDataSet::SafeDownCast(
inInfo->Get(vtkDataObject::DATA_OBJECT()));
vtkPolyData* output = vtkPolyData::SafeDownCast(
outInfo->Get(vtkDataObject::DATA_OBJECT()));
...
int numberOfSources =
inputVector[1]->GetNumberOfInformationObjects();
for(int i=0; i < numberOfSources; ++i)
{
vtkInformation* sourceInfo =
inputVector[1]->GetInformationObject(i);
vtkPolyData* source = vtkPolyData::SafeDownCast(
sourceInfo->Get(vtkDataObject::DATA_OBJECT()));
...
}
...
}

inputVector包含输入管道信息。数组的每个元素代表一个输入端口。它是一个vtkInformationVector,其条目是对应输入端口上连接的输入管道信息对象。outputVector是一个vtkInformationVector,它包含过滤器的每个输出端口的一个输出管道信息对象。用于输入和输出的每个管道信息对象都包含一个vtkDataObject,其存储键为vtkDataObject::DATA_OBJECT()。一旦RequestData()的实现从管道信息中检索了它的输入和输出数据对象,它就可以继续它的算法实现。

17.2 VTK算法的原理

不要修改输入数据

对于任何过滤器编写者来说,最重要的指导原则之一是永远不要修改过滤器的输入。这样做的原因很简单:管道的正确执行要求过滤器创建和修改它们自己的输出。请记住,其他过滤器也可能使用输入数据;如果修改筛选器的输入,则可能会损坏使用该数据的其他筛选器或创建该数据的筛选器的数据。

参考计数数据

如果过滤器的输入数据未加更改地发送到过滤器的输出,请确保通过使用引用计数共享该表示。这减少了管道的内存成本,对于大型可视化数据来说,管道的内存成本可能相当高。通常,如果使用Get__()和Set__()方法来获取和设置数据,则会自动处理引用计数。或者,可以直接调用对象的Register()和UnRegister()方法。vtkSmartPointer&lt;比;模板可以用来保存作为局部变量的对象引用,但不应该在类的公共接口中使用。看到有关引用计数和智能指针的更多信息,请参阅第20页的“低级对象模型”。筛选器作者可能希望使用专门的方法通过筛选器传递数据,请参阅
更多信息请参阅362页的“字段和属性数据”。

使用调试宏

当设置了对象的Debug标志时,过滤器应该提供调试信息。这是使用VTK/Common/vtkSetGet.h中定义的VTK调试宏方便地完成的。至少,过滤器应该报告类似于以下的启动执行(来自VTK/Graphics/vtkContourFilter.cxx)。

vtkDebugMacro(<< "Executing contour filter");

您可能还希望在过滤器执行时提供其他信息,例如,执行摘要(同样来自vtkContourFilter.cxx):

vtkDebugMacro(<<"Created: "
<< newPts->GetNumberOfPoints() << " points, "
<< newVerts->GetNumberOfCells() << " verts, "
<< newLines->GetNumberOfCells() << " lines, "
<< newPolys->GetNumberOfCells() << " triangles");

不要将调试宏放在循环的内部部分,因为宏调用if检查可能会影响性能。此外,如果在内部循环中打开调试,则会输出过多的信息,无法进行有意义的解释。

回收/删除已分配的内存

过滤器编写者常犯的一个错误是引入内存泄漏或使用过多的内存。通过配对所有VTK对象的New()和Delete()方法以及所有本机或非VTK对象的New和Delete方法,可以避免内存泄漏。(参见第300页的“标准方法:创建和删除对象”。)

另一种减少内存使用的方法是使用vtkDataArray和子类提供的Squeeze()方法。此方法回收对象可能正在使用的多余内存。当数据对象的大小只能在初始分配时估计时,请使用Squeeze()方法

计算修改时间

        编写过滤器最棘手的部分之一是确保正确管理其修改时间。您可能还记得,修改时间是每个对象为响应其内部状态的变化而维护的内部时间戳。通常,当调用Set__()方法时,修改时间会发生变化。例如,方法vtkTubeFilter::SetNumberOfSides(num)导致vtkTubeFilter的修改时间在调用该方法时发生变化,只要num与管道过滤器的当前实例变量值不同。通常情况下,滤波器的修改时间保持不变,不需要干预。例如,如果使用VTK/Common/vtkSetGet.h中定义的vtkSet/Get宏来获取和设置实例变量值,则修改后的时间被正确管理,并且继承方法vtkObject::GetMTime()返回正确的值。

        然而,如果你定义了自己的Set__()方法,或者包含修改对象内部状态的方法,则必须在过滤器上适当地调用Modified()(即更改内部修改时间)。而且,如果您的对象定义包含对其他对象的引用,则过滤器的正确修改时间既包括过滤器也包括它所依赖的对象。这需要重载GetMTime()方法。

        vtkCutter是展示这种行为的过滤器的一个例子。这个过滤器引用另一个对象(即CutFunction), vtkImplicitFunction的一个实例。当隐式函数定义改变时,我们期望刀具重新执行。因此,过滤器的GetMTime()方法必须通过考虑隐式函数的修改时间来反映这一点;从它的超类vtkObject继承的GetMTime()方法必须重载。vtkCutter::GetMTime()方法的实现如下:它实际上比这里建议的更复杂,因为vtkCutter依赖于另外两个对象(vtkLocator和vtkContourValues),如下所示。

unsigned long vtkCutter::GetMTime()
{
 unsigned long mTime=this->Superclass::GetMTime();
 unsigned long contourValuesMTime=this->ContourValues->GetMTime();
 unsigned long time;
 mTime = ( contourValuesMTime > mTime ?
contourValuesMTime : mTime );
 if ( this->CutFunction != NULL )
 {
 time = this->CutFunction->GetMTime();
 mTime = ( time > mTime ? time : mTime );
 }
 if ( this->Locator != NULL )
 {
 time = this->Locator->GetMTime();
 mTime = ( time > mTime ? time : mTime );
 }
 return mTime;
}

        类vtkLocator用于在过滤器执行时合并重合点;vtkContourValues是一个辅助类,用于为那些使用等值面的函数生成等高线值,作为其执行过程的一部分。GetMTime()方法必须检查vtkCutter所依赖的所有对象,返回它找到的最大修改时间。

        虽然使用修改时间和VTK可视化管道的隐式执行模型很简单,但有一个常见的混淆来源。也就是说,您必须区分过滤器的修改时间及其对数据流的依赖关系。请记住,过滤器将在它们被修改(修改的时间发生变化)或过滤器的输入发生变化(依赖于数据流)时执行。修改时间仅反映对过滤器的更改或对独立于数据流的其他对象的依赖关系。

使用ProgressEvent和AbortExecute

        在执行源、过滤器或映射器对象(即任何算法)期间,会定期调用ProgressEvent。进度用户方法通常执行诸如更新应用程序用户界面之类的功能(例如,想象一下在GUI中操作进度条)。(见“一般GUI交互指南”(第423页)和“用户方法、观察者和命令”(第29页)。

        通过使用事件类型vtkCommand::ProgressEvent调用AddObserver()来创建进度用户方法。当调用progress方法时,过滤器还设置当前进度值(使用GetProgress()方法检索的介于(0,1)之间的小数)。进度方法可以与vtkAlgorithm对StartEvent和EndEvent的调用结合使用。例如,Examples/Tutorial/Step2展示了如何使用这些方法。这里有一个代码片段来展示它是如何工作的。

vtkDecimatePro deci
deci AddObserver StartEvent {StartProgress deci “Decimating...”}
deci AddObserver ProgressEvent {ShowProgress deci “Decimating...”}
deci AddObserver EndEvent EndProgress

        并非所有过滤器都调用ProgressEvents或很少调用它们(取决于过滤器的实现)。进度值可能不是过滤器实际完成工作的真实度量,因为在一些过滤器中很难度量进度,并且/或者过滤器实现者可能选择在算法中的关键点更新过滤器(例如,在构建内部数据结构,读取数据块等之后)。与进度方法相关的是用于停止过滤器执行的标志的概念。这个标志在vtkAlgorithm中定义,称为AbortExecute标志。

        设置后,一些过滤器将终止它们的执行,将执行结果的一部分(或者可能什么都没有)发送到它们的输出。通常,该标志是在调用ProgressEvent期间设置的,用于在过滤器执行时间过长或应用程序试图跟上用户输入事件时过早终止过滤器的执行。并非所有过滤器都支持AbortExecute标志。检查源代码以确定哪些支持该标志。(大多数都有,而那些没有的在不久的将来也会有。)下面的代码片段显示了进度用户方法和中止标志的使用示例,取自VTK/ Graphics/vtkDecimatePro.cxx。

for ( ptId=0; ptId < npts && !abortExecute ; ptId++ )
 {
 if ( ! (ptId % 10000) )
 {
 vtkDebugMacro(<<"Inserting vertex #" << ptId);
 this->UpdateProgress (0.25*ptId/npts);//25% inserting
 if (this->GetAbortExecute())
 {
 abortExecute = 1;
 break;
 }
 }
 this->Insert(ptId);
 }

请注意,过滤器实现者做了一些任意的决定:每10,000个点调用进度方法;并且假定RequestData()方法所显示的部分大约占用总执行时间的25%。此外,一些调试输出与progress方法的执行以及中止标志上的状态检查相结合。这指出了一个重要的指南——

行:作为过滤器实现者,您不希望过于频繁地调用进度方法,因为它们会影响整体性能。

实现PrintSelf()方法

所有VTK类都实现一个PrintSelf()方法,该方法以人类可读的格式打印对象的状态。此方法的实现必须首先将调用传递给该方法的父类实现,然后打印类实例变量的状态。如果其中一个实例变量本身是一个VTK类,那么该对象的PrintSelf()方法应该以递增的缩进级别调用。例如,vtkCutter实现PrintSelf()如下所示。

 void vtkCutter::PrintSelf(ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 os << indent << "Cut Function: " << this->CutFunction << "\n";
 os << indent << "Sort By: " << this->GetSortByAsString() << "\n";
 if ( this->Locator )
 {
 os << indent << "Locator: " << this->Locator << "\n";
 }
 else
 {
 os << indent << "Locator: (none)\n";
 }
 this->ContourValues->PrintSelf(os, indent.GetNextIndent());
 os << indent << "Generate Cut Scalars: "
 << (this->GenerateCutScalars ? "On\n" : "Off\n");
 }

从管道信息中获取输入/输出数据

许多过滤器提供了GetInput()和GetOutput()方法,这些方法对于用户获取他们创建的过滤器的输入和输出很有用。在ProcessRequest()的实现或它调用的方法中使用这些方法是很有诱惑力的,但不应该这样做。传递给ProcessRequest()和相关方法的管道信息对象包含应该使用的数据对象。这些可能不同于GetInput()或GetOutput()返回的数据对象,特别是当一个过滤器在另一个过滤器的实现中内部使用时。

17.3算法示例

本节给出了几个VTK滤波器的例子。我们将按照上面概述的步骤演示如何实现过滤器。

图形过滤器

类vtkShrinkFilter定义了一个简单的图形过滤器。它的目的是缩小输入数据集的所有单元格,以便可视化每个单元格的结构。由于支持所有标准的VTK单元格类型,因此输出类型必须是vtkUnstructuredGrid。我们还需要一个输入端口用于输入数据集,一个输出端口用于输出数据集。这使得vtkUnstructuredGridAlgorithm是合适的选择超类。该算法定义了一个参数ShrinkFactor,它指示每个输出单元格应该使用的原始单元格大小的百分比。类声明出现在
VTK/Graphics/vtkShrinkFilter.h如下。

#ifndef __vtkShrinkFilter_h
 #define __vtkShrinkFilter_h
 #include "vtkUnstructuredGridAlgorithm.h"
 class VTK_GRAPHICS_EXPORT vtkShrinkFilter : public
 vtkUnstructuredGridAlgorithm
 {
 public:
 static vtkShrinkFilter* New();
 vtkTypeRevisionMacro(vtkShrinkFilter,
 vtkUnstructuredGridAlgorithm);
 void PrintSelf(ostream& os, vtkIndent indent);
 // Description:
 // Get/Set the fraction of shrink for each cell.
 vtkSetClampMacro(ShrinkFactor, double, 0.0, 1.0);
 vtkGetMacro(ShrinkFactor, double);
 protected:
 vtkShrinkFilter();
 ~vtkShrinkFilter() {}
 virtual int FillInputPortInformation(int port,
 vtkInformation* info);
 virtual int RequestData(vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*);
 double ShrinkFactor;
 private:
 vtkShrinkFilter(const vtkShrinkFilter&);
 void operator=(const vtkShrinkFilter&);
 };
 #endif

为了完成类的定义,我们需要实现构造函数和FillInputPortInformation()、PrintSelf()和RequestData()方法。这些实现,摘自VTK/Graphics/vtkShrinkFilter。CXX文件,如下所示。(注:VTK_GRAPHICS_EXPORT是一个#define宏,被一些编译器用来从共享库导出符号。)

构造函数将ShrinkFactor参数初始化为默认值。由于超类vtkUnstructuredGridAlgorithm将输入端口和输出端口的数量设置为一个,因此此构造函数不需要更改它。

vtkShrinkFilter::vtkShrinkFilter() { this->ShrinkFactor = 0.5; }

PrintSelf()方法打印ShrinkFactor参数的设置。

void vtkShrinkFilter::PrintSelf(ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 os << indent << "Shrink Factor: "
 << this->ShrinkFactor << "\n";
 }

FillInputPortInformation()方法覆盖了vtkUnstructuredGridAlgorithm的默认值,以指定支持接受任何vtkDataSet作为输入。

 int vtkShrinkFilter::FillInputPortInformation(
 int, vtkInformation* info)
 {
 info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(),
 "vtkDataSet");
 return 1;
 }

最后,RequestData()方法实现单元格收缩算法。

 int vtkShrinkFilter::RequestData(
 vtkInformation*,
 vtkInformationVector** inputVector,
 vtkInformationVector* outputVector)
 {
 // Get input and output data.
 vtkDataSet* input =
 vtkDataSet::GetData(inputVector[0]);
 vtkUnstructuredGrid* output =
 vtkUnstructuredGrid::GetData(outputVector);
 // We are now executing this filter.
 vtkDebugMacro("Shrinking cells");
 // Skip execution if there is no input geometry.
 vtkIdType numCells = input->GetNumberOfCells();
 vtkIdType numPts = input->GetNumberOfPoints();
 if(numCells < 1 || numPts < 1)
 {
 vtkDebugMacro("No data to shrink!");
 return 1;
 }
 // Allocate working space for new and old cell
 // point lists.
 vtkSmartPointer<vtkIdList> ptIds =
 vtkSmartPointer<vtkIdList>::New();
 vtkSmartPointer<vtkIdList> newPtIds =
 vtkSmartPointer<vtkIdList>::New();
 ptIds->Allocate(VTK_CELL_SIZE);
 newPtIds->Allocate(VTK_CELL_SIZE);
// Allocate approximately the space needed for the output cells.
 output->Allocate(numCells);
 // Allocate space for a new set of points.
 vtkSmartPointer<vtkPoints> newPts =
vtkSmartPointer<vtkPoints>::New();
 newPts->Allocate(numPts*8, numPts);
 // Allocate space for data associated with the
 // new set of points.
 vtkPointData* inPD = input->GetPointData();
 vtkPointData* outPD = output->GetPointData();
 outPD->CopyAllocate(inPD, numPts*8, numPts);
 // Support progress and abort.
 vtkIdType tenth =
 (numCells >= 10? numCells/10 : 1);
 double numCellsInv = 1.0/numCells;
 int abort = 0;
 // Traverse all cells, obtaining node
 // coordinates. Compute "center" of cell, then
 // create new vertices shrunk towards center.
 for(vtkIdType cellId = 0;
 cellId < numCells && !abort;
 ++cellId)
 {
 // Get the list of points for this cell.
 input->GetCellPoints(cellId, ptIds);
 vtkIdType numIds = ptIds->GetNumberOfIds();
 // Periodically update progress and check for
 // an abort request.
 if(cellId % tenth == 0)
 {
 this->UpdateProgress((cellId+1)*numCellsInv);
 abort = this->GetAbortExecute();
 }
 // Compute the center of mass of the cell
 // points.
 double center[3] = {0,0,0};
 for(vtkIdType i=0; i < numIds; ++i)
 {
 double p[3];
 input->GetPoint(ptIds->GetId(i), p);
 for(int j=0; j < 3; ++j)
 {
 center[j] += p[j];
 }
 }
for(int j=0; j < 3; ++j)
 {
 center[j] /= numIds;
 }
 // Create new points for this cell.
 newPtIds->Reset();
 for(vtkIdType i=0; i < numIds; ++i)
 {
 // Get the old point location.
 double p[3];
 input->GetPoint(ptIds->GetId(i), p);
 // Compute the new point location.
 double newPt[3];
 for(int j=0; j < 3; ++j)
 {
 newPt[j] =
 (center[j] +
 this->ShrinkFactor*(p[j] - center[j]));
 }
 // Create the new point for this cell.
 vtkIdType newId =
 newPts->InsertNextPoint(newPt);
 newPtIds->InsertId(i, newId);
 // Copy point data from the old point.
 vtkIdType oldId = ptIds->GetId(i);
 outPD->CopyData(inPD, oldId, newId);
 }
 // Store the new cell in the output.
 output->InsertNextCell(
 input->GetCellType(cellId), newPtIds);
 }
 // Store the new set of points in the output.
 output->SetPoints(newPts);
 // Just pass cell data through because we still
 // have the same number and type of cells.
 output->GetCellData()
 ->PassData(input->GetCellData());
 // Avoid keeping extra memory around.
 output->Squeeze();
 return 1;
 }

vtkShrinkFilter示例是典型的图形过滤器。更简单的过滤器只支持多边形单元格类型,可以使用超类vtkPolyDataAlgorithm代替。

一个简单的成像滤波器

类vtkSimpleImageFilterExample定义了一个非常简单的图像过滤器。它简单地将图像数据从输入复制到输出,并打算由希望在VTK滤波器中实现非常简单的图像处理任务的用户复制和修改。一个特殊的超类vtkSimpleImageToImageFilter提供了一个适合于这种简单成像过滤器的管道接口。它甚至实现了RequestData()方法,并将其转换为对非常简单的方法SimpleExecute()的调用。类声明出现在VTK/Imaging/vtkSimpleImageFilterExample.h中,如下所示。

#ifndef __vtkSimpleImageFilterExample_h
 #define __vtkSimpleImageFilterExample_h
 #include "vtkSimpleImageToImageFilter.h"
 class VTK_IMAGING_EXPORT vtkSimpleImageFilterExample
 : public vtkSimpleImageToImageFilter
 {
 public:
 static vtkSimpleImageFilterExample* New();
 vtkTypeRevisionMacro(vtkSimpleImageFilterExample,
 vtkSimpleImageToImageFilter);
 protected:
 vtkSimpleImageFilterExample() {}
 ~vtkSimpleImageFilterExample() {}
 virtual void SimpleExecute(vtkImageData* input,
 vtkImageData* output);
 private:
 vtkSimpleImageFilterExample(
 const vtkSimpleImageFilterExample&);
 void operator=(
 const vtkSimpleImageFilterExample&);
 };
 #endif

为了完成类的定义,我们需要实现SimpleExecute()方法。它的实现出现在VTK/Imaging/vtkSimpleImageFilterExample中。CXX如图所示。(注意:VTK_IMAGING_EXPORT是一个#define宏,被一些编译器用来从共享库导出符号。)

VTK图像数据可以使用几种标量数据类型中的任何一种来表示。图像处理过滤器通常使用函数模板实现。指向输入和输出图像数据数组的指针在最后两个参数中给出。在本例中,我们假设输出数据类型与输入数据类型相同,尽管在实践中可能并不总是如此。

template <class IT>
 void vtkSimpleImageFilterExampleExecute(
 vtkImageData* input, vtkImageData* output,
 IT* inPtr, IT* outPtr)
 {
 int dims[3];
input->GetDimensions(dims);
 if (input->GetScalarType() !=
 output->GetScalarType())
 {
 vtkGenericWarningMacro(
 << "Execute: input ScalarType, "
 << input->GetScalarType()
 << ", must match out ScalarType "
 << output->GetScalarType());
 return;
 }
 int size = dims[0]*dims[1]*dims[2];
 for(int i=0; i<size; i++)
 {
 outPtr[i] = inPtr[i];
 }
 }

SimpleExecute()方法由vtkSimpleImageToImageFilter定义的RequestData()方法调用。超类已经从给定的输入和输出管道信息中提取了输入和输出数据对象。这些数据对象作为参数传递给SimpleExecute()方法。为了支持所有可能的图像标量类型,在switch语句中使用vtkTemplateMacro来实例化对上述函数模板的调用。

 void vtkSimpleImageFilterExample::SimpleExecute(
 vtkImageData* input, vtkImageData* output)
 {
 void* inPtr = input->GetScalarPointer();
 void* outPtr = output->GetScalarPointer();
 switch(output->GetScalarType())
 {
 // This is simply a #define for a big case list.
 // It handles all data types VTK supports.
 vtkTemplateMacro(
 vtkSimpleImageFilterExampleExecute(
 input, output,
 (VTK_TT*)(inPtr), (VTK_TT*)(outPtr)));
 default:
 vtkGenericWarningMacro(
 "Execute: Unknown input ScalarType");
 return;
 }
 }

虽然vtkSimpleImageToImageFilter超类使编写成像过滤器非常简单,但它也不支持许多先进的VTK管道功能。使用这个超类编写的过滤器将在VTK管道中工作,但可能不是特别有效。严肃的图像过滤器实现应该直接从vtkImageAlgorithm子类化并支持流,甚至vtkThreadedImageAlgorithm来支持线程处理。

一种螺纹成像滤波器

类vtkImageShiftScale定义了一个线程化的图像过滤器。它的目的是缩放和转移像素值的范围从输入到输出。这种过滤器的一个示例应用程序是在写入文件之前将浮点图像转换为无符号8位整数表示。由于输入和输出类型是vtkImageData,我们可能希望选择vtkImageAlgorithm作为超类。但是,由于移动和缩放是逐像素操作,因此支持线程实现是微不足道的。这使得vtkThreadedImageAlgorithm成为这个过滤器超类的理想选择。该算法定义了四个参数,用于指定移位和缩放的数量、输出标量类型以及是否将值箝位到输出类型的范围内。类声明出现在VTK/ Imaging/vtkImageShiftScale.h中,如下所示。

 #ifndef __vtkImageShiftScale_h
 #define __vtkImageShiftScale_h
 #include "vtkThreadedImageAlgorithm.h"
 class VTK_IMAGING_EXPORT vtkImageShiftScale
 : public vtkThreadedImageAlgorithm
 {
 public:
 static vtkImageShiftScale* New();
 vtkTypeRevisionMacro(vtkImageShiftScale,
 vtkThreadedImageAlgorithm);
 void PrintSelf(ostream& os, vtkIndent indent);
 // Description:
 // Set/Get the shift value.
 vtkSetMacro(Shift,double);
 vtkGetMacro(Shift,double);
 // Description:
 // Set/Get the scale value.
 vtkSetMacro(Scale,double);
 vtkGetMacro(Scale,double);
 // Description:
 // Set the desired output scalar type. The result of the shift
 // and scale operations is cast to the type specified.
 vtkSetMacro(OutputScalarType, int);
 vtkGetMacro(OutputScalarType, int);
 void SetOutputScalarTypeToDouble()
 {this->SetOutputScalarType(VTK_DOUBLE);}
 void SetOutputScalarTypeToFloat()
 {this->SetOutputScalarType(VTK_FLOAT);}
 void SetOutputScalarTypeToLong()
 {this->SetOutputScalarType(VTK_LONG);}
 void SetOutputScalarTypeToUnsignedLong()
 {this->SetOutputScalarType(VTK_UNSIGNED_LONG);};
 void SetOutputScalarTypeToInt()
 {this->SetOutputScalarType(VTK_INT);}
 void SetOutputScalarTypeToUnsignedInt()
 {this->SetOutputScalarType(VTK_UNSIGNED_INT);}
void SetOutputScalarTypeToShort()
 {this->SetOutputScalarType(VTK_SHORT);}
 void SetOutputScalarTypeToUnsignedShort()
 {this->SetOutputScalarType(VTK_UNSIGNED_SHORT);}
 void SetOutputScalarTypeToChar()
 {this->SetOutputScalarType(VTK_CHAR);}
 void SetOutputScalarTypeToUnsignedChar()
 {this->SetOutputScalarType(VTK_UNSIGNED_CHAR);}
 // Description:
 // When the ClampOverflow flag is on, the data is thresholded so that
 // the output value does not exceed the max or min of the data type.
 // By default, ClampOverflow is off.
 vtkSetMacro(ClampOverflow, int);
 vtkGetMacro(ClampOverflow, int);
 vtkBooleanMacro(ClampOverflow, int);
 protected:
 vtkImageShiftScale();
 ~vtkImageShiftScale() {}
 double Shift;
 double Scale;
 int OutputScalarType;
 int ClampOverflow;
 virtual
 int RequestInformation(vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*);
 virtual
 void ThreadedRequestData(vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*,
 vtkImageData*** inData,
 vtkImageData** outData,
 int outExt[6],
 int threadId);
 private:
 vtkImageShiftScale(const vtkImageShiftScale&);
 void operator=(const vtkImageShiftScale&);
 };
 #endif

为了完成类的定义,我们需要实现构造函数和PrintSelf()、RequestInformation()和ThreadedRequestData()方法。这些实现,摘自VTK/Imaging/vtkImageShiftScale。CXX文件,如下所示。(注意:VTK_IMAGING_EXPORT是一个#define宏,被一些编译器用来从共享库导出符号。此外,SetOutputScalarType()的大量变体允许从Tcl、Python或Java包装器轻松设置参数,并且不重要。

构造函数将参数值初始化为合理的默认值。

 vtkImageShiftScale::vtkImageShiftScale()
 {
 this->Shift = 0.0;
 this->Scale = 1.0;
 this->OutputScalarType = -1;
 this->ClampOverflow = 0;
 }

PrintSelf()方法打印参数的设置

 void vtkImageShiftScale::PrintSelf(
 ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 os << indent << "Shift: " << this->Shift << "\n";
 os << indent << "Scale: " << this->Scale << "\n";
 os << indent << "Output Scalar Type: "
 << this->OutputScalarType << "\n";
 os << indent << "ClampOverflow: "
 << (this->ClampOverflow? "On" : "Off") << "\n";
 }

RequestInformation()方法将输出类型更改为OutputScalarType参数指定的类型(如果已设置)。

 int vtkImageShiftScale::RequestInformation(
 vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector* outputVector)
 {
 // Set the image scalar type for the output.
 if(this->OutputScalarType != -1)
 {
 vtkInformation* outInfo =
 outputVector->GetInformationObject(0);
 vtkDataObject::SetPointDataActiveScalarInfo(
 outInfo, this->OutputScalarType, -1);
 }
 return 1;
 }

        为了支持输入和输出标量类型的任意组合,算法的核心是在函数模板vtkImageShiftScaleExecute中实现的。该函数仅在类实现文件中使用,不需要在头文件中声明。它应该在需要之前定义和实现。函数定义之前的template关键字表明它是一个模板化的函数,并且它是在T类型上模板化的。如果你不熟悉c++模板,你可以把T看作一个字符串,它被int、short、float等代替如果是函数而不是方法,它们不能访问this指针及其关联的实例变量。相反,我们将this指针作为一个名为self的变量传入。然后可以使用self参数访问类的值,就像下面的示例所示的那样。例如,在下面的代码中,我们从实例中获取Scale的值,并将其放入一个名为Scale的局部变量中。模板将被实例化,并由threadadedrequestdata()方法对所有可能的组合进行调用。每个线程被赋予它所负责的输出图像的一个区域。该区域由outExt参数指定。

        这段代码还引入了一个新概念:图像迭代器。在VTK中,有几个方便的类可用于循环(或迭代)图像中的像素。类vtkImageIterator被模板化在图像的类型上,与VTK中的大多数其他类不同,它不是用New()工厂方法实例化的。相反,图像迭代器是用标准c++构造函数在堆栈上实例化的。构造函数接受两个参数:指向vtkImageData实例的指针和要迭代的相关范围。

        vtkImageProgressIterator是另一个类似于vtkImageIterator的方便类(它是vtkImageIterator的子类)。然而,vtkImageProgressIterator需要两个额外的参数来进行实例化:一个指向实例化进度迭代器的过滤器的指针(即本例中的self)和线程id。vtkImageProgressIterator所做的是首先确保线程id为零(以限制回调到单个线程的数量),并定期调用过滤器上的UpdateProgress()。它还在每次调用IsAtEnd()时检查过滤器上的AbortExecute标志,如果设置了该标志,则提前报告数据结束。

        请注意如何使用BeginSpan()和EndSpan()方法一次处理一行像素。图像迭代器方法IsAtEnd()用于在处理完所有像素后停止对图像的处理。

 template <class IT, class OT>
 void vtkImageShiftScaleExecute(vtkImageShiftScale* self,
 vtkImageData* inData,
 vtkImageData* outData,
 int outExt[6], int id,
 IT*, OT*)
 {
 // Create iterators for the input and output extents assigned to
 // this thread.
 vtkImageIterator<IT> inIt(inData, outExt);
 vtkImageProgressIterator<OT> outIt(outData, outExt, self, id);
 // Get the shift and scale parameters values.
 double shift = self->GetShift();
 double scale = self->GetScale();
 // Clamp pixel values within the range of the output type.
 double typeMin = outData->GetScalarTypeMin();
 double typeMax = outData->GetScalarTypeMax();
 int clamp = self->GetClampOverflow();
 // Loop through output pixels.
 while (!outIt.IsAtEnd())
 {
 IT* inSI = inIt.BeginSpan();
OT* outSI = outIt.BeginSpan();
 OT* outSIEnd = outIt.EndSpan();
 if (clamp)
 {
 while (outSI != outSIEnd)
 {
 // Pixel operation
 double val = ((double)(*inSI) + shift) * scale;
 if (val > typeMax)
 {
 val = typeMax;
 }
 if (val < typeMin)
 {
 val = typeMin;
 }
 *outSI = (OT)(val);
 ++outSI;
 ++inSI;
 }
 }
 else
 {
 while (outSI != outSIEnd)
 {
 // Pixel operation
 *outSI = (OT)(((double)(*inSI) + shift) * scale);
 ++outSI;
 ++inSI;
 }
 }
 inIt.NextSpan();
 outIt.NextSpan();
 }
 }

下面的threadadedrequestdata()使用vtkTemplateMacro为每个可能的输入标量数据类型调用函数模板vtkImageShiftScaleExecute1()。该函数使用vtkTemplateMacro为给定输入标量数据类型的每个可能的输出标量数据类型调用上述函数模板vtkImageShiftScaleExecute()。vtkTemplateMacro的两种用法为输入和输出标量类型的所有可能组合调度对vtkImageShiftScaleExecute()函数模板的调用。

template <class T>
 void vtkImageShiftScaleExecute1(
 vtkImageShiftScale* self,
 vtkImageData* inData,
 vtkImageData* outData,
 int outExt[6], int id, T*)
 {
 switch (outData->GetScalarType())
{
 vtkTemplateMacro(
 vtkImageShiftScaleExecute(self, inData,
 outData, outExt, id,
 static_cast<T*>(0),
 static_cast<VTK_TT*>(0)));
 default:
 vtkErrorWithObjectMacro(
 self, "ThreadedRequestData: Unknown output ScalarType");
 return;
 }
 }

最后,threadadedrequestdata()方法是每个线程中过滤器实现的入口点。超类vtkThreadedImageAlgorithm实现RequestData()方法并创建一些辅助线程。在每个线程中调用这个threadadedrequestdata()方法。

void vtkImageShiftScale::ThreadedRequestData(
 vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*,
 vtkImageData*** inData,
 vtkImageData** outData,
 int outExt[6],
 int threadId)
 {
 vtkImageData* input = inData[0][0];
 vtkImageData* output = outData[0];
 switch(input->GetScalarType())
 {
 vtkTemplateMacro(
 vtkImageShiftScaleExecute1(this, input, output, outExt, threadId,
 static_cast<VTK_TT*>(0)));
 default:
 vtkErrorMacro("ThreadedRequestData: Unknown input ScalarType");
 return;
 }
 }

为简单起见,每个输入连接和每个输出端口的输入和输出图像数据对象都从它们的管道信息对象中提取出来,并以数组的形式传递给这个方法。这个过滤器只使用一个输入连接和一个输出,因此只使用这些数组的第一个条目。
非线程过滤器的实现方式与本示例类似。他们应该使用vtkImageAlgorithm作为超类,并实现RequestData()而不是threadadedrequestdata()。这两个超类足以实现大多数图像处理算法。

一个简单的阅读器

类vtkSimplePointsReader提供了一个如何编写读取器的简单示例。它的目的是从一个ASCII文件中读取一个点列表。每个点在一行上由三个浮点数指定值。阅读器有零输入端口和一个输出端口产生vtkPolyData。这使得vtkPolyDataAlgorithm成为超类的合适选择。有一个名为FileName的参数指定要从中读取点的文件的名称。类声明出现在VTK/IO/ vtkSimplePointsReader.h中,如下所示。

 #ifndef __vtkSimplePointsReader_h
 #define __vtkSimplePointsReader_h
 #include "vtkPolyDataAlgorithm.h"
 class VTK_IO_EXPORT vtkSimplePointsReader
 : public vtkPolyDataAlgorithm
 {
 public:
 static vtkSimplePointsReader* New();
 vtkTypeRevisionMacro(vtkSimplePointsReader,
 vtkPolyDataAlgorithm);
 void PrintSelf(ostream& os, vtkIndent indent);
 // Description: Set/Get the name of the file from which to read points.
 vtkSetStringMacro(FileName);
 vtkGetStringMacro(FileName);
 protected:
 vtkSimplePointsReader();
 ~vtkSimplePointsReader();
 char* FileName;
 int RequestData(vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*);
 private:
 vtkSimplePointsReader(
 const vtkSimplePointsReader&);
 void operator=(const vtkSimplePointsReader&);
 };

        注意,vtkSetStringMacro()和vtkGetStringMacro()调用定义了SetFileName()和GetFileName()方法,它们自动管理文件名字符串的内存。下面显示的构造函数将FileName初始化为NULL指针,析构函数通过将FileName设置回NULL来释放任何分配的字符串。

        为了完成类的定义,我们需要实现构造函数和析构函数,以及PrintSelf()和RequestData()方法。这些实现,摘自VTK/IO/ vtkSimplePointsReader。CXX文件,如下所示。(注:VTK_IO_EXPORT是一个#define宏,被一些编译器用来从共享库中导出符号。)构造函数将参数值初始化为合理的默认值。它还将输入端口的数量更改为零,因为阅读器不消耗任何输入。

vtkSimplePointsReader::vtkSimplePointsReader()
 {
 this->FileName = 0;
 this->SetNumberOfInputPorts(0);
 }

析构函数释放FileName参数所消耗的内存。

 vtkSimplePointsReader::~vtkSimplePointsReader()
 {
 this->SetFileName(0);
 }

PrintSelf()方法打印FileName参数的值。

 void vtkSimplePointsReader::PrintSelf(ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 os << indent << "FileName: "
 << (this->FileName ? this->FileName : "(none)") << "\n";
 }

最后,RequestData()方法包含读取器实现。

int vtkSimplePointsReader::RequestData(
 vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector* outputVector)
 {
 // Make sure we have a file to read.
 if(!this->FileName)
 {
 vtkErrorMacro("A FileName must be specified.");
 return 0;
 }
 // Open the input file.
 ifstream fin(this->FileName);
 if(!fin)
 {
 vtkErrorMacro("Error opening file "
 << this->FileName);
 return 0;
 }
 // Allocate objects to hold points and
 // vertex cells.
 vtkSmartPointer<vtkPoints> points =
 vtkSmartPointer<vtkPoints>::New();
 vtkSmartPointer<vtkCellArray> verts =
 vtkSmartPointer<vtkCellArray>::New();
 // Read points from the file.
 vtkDebugMacro("Reading points from file "
 << this->FileName);
 double x[3];
 while(fin >> x[0] >> x[1] >> x[2])
{
 vtkIdType id = points->InsertNextPoint(x);
 verts->InsertNextCell(1, &id);
 }
 vtkDebugMacro("Read "
 << points->GetNumberOfPoints()
 << " points.");
 // Store the points and cells in the output
 // data object.
 vtkPolyData* output =
 vtkPolyData::GetData(outputVector);
 output->SetPoints(points);
 output->SetVerts(verts);
 return 1;
 }

阅读器实现必须处理可能不存在或格式错误的输入文件。这导致了许多潜在的失败情况。当RequestData()方法失败时,应该使用vtkErrorMacro报告原因,该方法应该返回0。当RequestData()方法成功时,它应该返回1。注意使用vtkSmartPointer&lt;&gt;模板来创建和保存点和顶点对象。这避免了在将这些对象传递给vtkPolyData对象后显式调用Delete()的需要。由于智能指针持有的引用由其析构函数释放,因此该方法的所有可能退出路径都将自动释放对象。

流媒体过滤器

        类vtkImageGradient提供了一个使用中心差分计算近似图像梯度的过滤器。它是过滤器从输入到输出改变图像大小的一个很好的例子。为了在输出的请求范围内的所有像素上提供梯度方向,过滤器必须从输入请求围绕该范围的一个额外的像素层。过滤器参数HandleBoundaries指定如何处理图像边界。如果启用,该算法就像边界像素是重复的一样工作,因此中心差分对边界像素起作用。如果禁用,则图像的输出整体范围沿每个轴减少一个像素边界。该类由VTK/Imaging/vtkImageGradient.h定义,并由VTK/Imaging/vtkImageGradient.cxx实现。

        下面展示了两个关键方法的实现。如果未启用边界处理,RequestInformation()方法将图像缩小一个像素。这相当于更改输入管道信息的整个范围,并将其存储在输出管道信息中。

 int vtkImageGradient::RequestInformation(
 vtkInformation*,
 vtkInformationVector** inputVector,
 vtkInformationVector* outputVector)
 {
 // Get input and output pipeline information.
 vtkInformation* outInfo =
 outputVector->GetInformationObject(0);
 vtkInformation* inInfo =
 inputVector[0]->GetInformationObject(0);
 // Get the input whole extent.
 int extent[6];
 inInfo->Get(
 vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),
 extent);
 // Shrink output image extent by one pixel if
 // not handling boundaries.
 if(!this->HandleBoundaries)
 {
 for(int idx = 0;
 idx < this->Dimensionality;
 ++idx)
 {
 extent[idx*2] += 1;
 extent[idx*2 + 1] -= 1;
 }
 }
 // Store the new whole extent for the output.
 outInfo->Set(
 vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),
 extent, 6);
 // Set the number of point data componets to the
 // number of components in the gradient vector.
 vtkDataObject::SetPointDataActiveScalarInfo(
 outInfo, VTK_DOUBLE, this->Dimensionality);
 return 1;
 }

RequestUpdateExtent()方法将输出的使用者请求的范围在边界周围增加一个像素,以从输入生成所需的范围。在启用边界处理的情况下,增长范围必须被输入图像的整个范围剪切,以避免访问不存在的像素。

 int vtkImageGradient::RequestUpdateExtent(
 vtkInformation*,
 vtkInformationVector** inputVector,
 vtkInformationVector* outputVector)
 {
 // Get input and output pipeline information.
 vtkInformation* outInfo =
 outputVector->GetInformationObject(0);
 vtkInformation* inInfo =
 inputVector[0]->GetInformationObject(0);
 // Get the input whole extent.
 int wholeExtent[6];
 inInfo->Get(
 vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),
 wholeExtent);
 // Get the requested update extent from the output.
 int inUExt[6];
 outInfo->Get(
 vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(),
 inUExt);
 // In order to do central differencing we need
 // one more layer of input pixels than we are
 // producing output pixels.
 for(int idx = 0;
 idx < this->Dimensionality;
 ++idx)
 {
 inUExt[idx*2] -= 1;
 inUExt[idx*2+1] += 1;
 // If handling boundaries instead of shrinking
 // the image then we must clip the needed
 // extent within the whole extent of the input.
 if (this->HandleBoundaries)
 {
 if(inUExt[idx*2] < wholeExtent[idx*2])
 {
 inUExt[idx*2] = wholeExtent[idx*2];
 }
 if(inUExt[idx*2+1] > wholeExtent[idx*2+1])
 {
 inUExt[idx*2+1] = wholeExtent[idx*2+1];
 }
 }
 }
 // Store the update extent needed from the intput.
 inInfo->Set(
 vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(),
 inUExt, 6);
 return 1;
 }

在管道执行期间,RequestInformation()方法将首先被调用。这将通知消费者输出图像的调整大小。稍后将调用RequestUpdateExtent()方法来询问过滤器需要多少输入图像才能生成所请求的输出范围。最后将调用RequestData()方法来实际计算梯度。我们在这里省略它简洁。(实际上,vtkImageGradient是线程的,所以它使用vtkthreaddimagealgorithm作为它的超类,并实现threaddrequestdata()。)

抽象过滤器

类vtkElevationFilter定义了一个抽象图形过滤器。它的目的是通过将数据集中的点的位置投影到一维参数空间来生成标量数据。引用计数用于避免分配所有输入几何形状和数据的重复副本。由于只修改属性数据(例如,标量),而不改变数据集的底层几何结构,因此不需要要求特定的输入或输出数据集类型。过滤器接受任何vtkDataSet,并生成数据集的副本作为输出,但添加了高程数据。这使得vtkdatasetalgalgorithm成为一个合适的超类选择。该算法定义了三个输入参数,定义了从3D到1D参数空间再到输出标量范围的映射。类声明出现在VTK/Graphics/vtkElevationFilter.h中,如下所示。

 #ifndef __vtkElevationFilter_h
 #define __vtkElevationFilter_h
 #include "vtkDataSetAlgorithm.h"
 class VTK_GRAPHICS_EXPORT vtkElevationFilter
 : public vtkDataSetAlgorithm
 {
 public:
 static vtkElevationFilter* New();
 vtkTypeRevisionMacro(vtkElevationFilter,
 vtkDataSetAlgorithm);
 void PrintSelf(ostream& os, vtkIndent indent);
 // Description:
 // Define one end of the line
 // (small scalar values). Default is (0,0,0).
 vtkSetVector3Macro(LowPoint,double);
 vtkGetVectorMacro(LowPoint,double,3);
 // Description:
 // Define other end of the line
 // (large scalar values). Default is (0,0,1).
 vtkSetVector3Macro(HighPoint,double);
 vtkGetVectorMacro(HighPoint,double,3);
 // Description:
 // Specify range to map scalars into.
 // Default is [0, 1].
 vtkSetVector2Macro(ScalarRange,double);
 vtkGetVectorMacro(ScalarRange,double,2);
 protected:
 vtkElevationFilter();
 ~vtkElevationFilter() {}
 int RequestData(vtkInformation*,
 vtkInformationVector**,
 vtkInformationVector*);
 double LowPoint[3];
 double HighPoint[3];
 double ScalarRange[2];
 private:
 vtkElevationFilter(const vtkElevationFilter&);
 void operator=(const vtkElevationFilter&);
 };
 #endif

为了完成类的定义,我们需要实现构造函数和PrintSelf()和
RequestData()方法。这些实现,摘录自VTK/Graphics/vtkElevationFilter。CXX文件,如下所示。(注:VTK_GRAPHICS_EXPORT是一个#define宏,被一些编译器用来从共享库导出符号。)
构造函数将参数初始化为默认值。由于父类vtkdatasetalgalgorithm将输入端口和输出端口的数量设置为一个,因此此构造函数不需要更改它。

vtkElevationFilter::vtkElevationFilter()
 {
 this->LowPoint[0] = 0.0;
 this->LowPoint[1] = 0.0;
 this->LowPoint[2] = 0.0;
 this->HighPoint[0] = 0.0;
 this->HighPoint[1] = 0.0;
 this->HighPoint[2] = 1.0;
 this->ScalarRange[0] = 0.0;
 this->ScalarRange[1] = 1.0;
 }

PrintSelf()方法打印参数值。

 void vtkElevationFilter::PrintSelf(
 ostream& os, vtkIndent indent)
 {
 this->Superclass::PrintSelf(os,indent);
 os << indent << "Low Point: ("
 << this->LowPoint[0] << ", "
 << this->LowPoint[1] << ", "
 << this->LowPoint[2] << ")\n";
 os << indent << "High Point: ("
 << this->HighPoint[0] << ", "
 << this->HighPoint[1] << ", "
 << this->HighPoint[2] << ")\n";
 os << indent << "Scalar Range: ("
 << this->ScalarRange[0] << ", "
 << this->ScalarRange[1] << ")\n";
 }

最后,RequestData()方法实现算法。

 int vtkElevationFilter::RequestData(
 vtkInformation*,
 vtkInformationVector* outputVector)
 {
 // Get the input and output data objects.
 vtkDataSet* input =
 vtkDataSet::GetData(inputVector[0]);
 vtkDataSet* output =
 vtkDataSet::GetData(outputVector);
 // Check the size of the input.
 vtkIdType numPts = input->GetNumberOfPoints();
 if(numPts < 1)
 {
 vtkDebugMacro("No input!");
 return 1;
 }
 // Allocate space for the elevation scalar data.
 vtkSmartPointer<vtkFloatArray> newScalars =
 vtkSmartPointer<vtkFloatArray>::New();
 newScalars->SetNumberOfTuples(numPts);
 // Set up 1D parametric system and make sure it
 // is valid.
 double diffVector[3] =
 { this->HighPoint[0] - this->LowPoint[0],
 this->HighPoint[1] - this->LowPoint[1],
 this->HighPoint[2] - this->LowPoint[2] };
 double length2 = vtkMath::Dot(diffVector,
 diffVector);
 if(length2 <= 0)
 {
 vtkErrorMacro("Bad vector, using (0,0,1).");
 diffVector[0] = 0;
 diffVector[1] = 0;
 diffVector[2] = 1;
 length2 = 1.0;
 }
 // Support progress and abort.
 vtkIdType tenth = (numPts >= 10? numPts/10 : 1);
 double numPtsInv = 1.0/numPts;
 int abort = 0;
 // Compute parametric coordinate and map into
 // scalar range.
 double diffScalar =
 this->ScalarRange[1] - this->ScalarRange[0];
vtkDebugMacro("Generating elevation scalars!");
 for(vtkIdType i=0; i < numPts && !abort; ++i)
 {
 // Periodically update progress and check for
 // an abort request.
 if(i % tenth == 0)
 {
 this->UpdateProgress((i+1)*numPtsInv);
 abort = this->GetAbortExecute();
 }
 // Project this input point into the 1D system.
 double x[3];
 input->GetPoint(i, x);
 double v[3] = { x[0] - this->LowPoint[0],
 x[1] - this->LowPoint[1],
 x[2] - this->LowPoint[2] };
 double s =
 vtkMath::Dot(v, diffVector) / length2;
 s = (s < 0.0 ? 0.0 : s > 1.0 ? 1.0 : s);
 // Store the resulting scalar value.
 newScalars->SetValue(
 i, this->ScalarRange[0] + s*diffScalar);
 }
 // Copy all the input geometry and data to
 // the output.
 output->CopyStructure(input);
 output->GetPointData()
 ->PassData(input->GetPointData());
 output->GetCellData()
 ->PassData(input->GetCellData());
 // Add the new scalars array to the output.
 newScalars->SetName("Elevation");
 output->GetPointData()->AddArray(newScalars);
 output->GetPointData()
 ->SetActiveScalars("Elevation");
 return 1;
 }

请注意,过滤器只计算标量数据,然后将其传递给输出。输出结构的实际生成是使用CopyStructure()方法完成的。该方法生成输入几何结构和原始属性数据的引用计数副本。

vtkpointsetalgalgorithm以类似的方式工作,除了点坐标被修改或生成并发送到输出。如果您想看到vtkpointsetalgalgorithm的具体示例,请参阅vtkTransformFilter。

当编写一个修改属性数据的过滤器,或者修改点的位置而不改变点或单元格的数量时,vtkdatasetalgalgorithm或vtkpointsetalgalgorithm都是合适的超类。

复合数据集感知过滤器

类vtkExtractBlock是从多块数据集中提取块的过滤器。过滤器接收一个多块数据集并生成一个多块数据集。因此,它是一个vtkmultiblockdatasetalgalgorithm子类。用户使用AddIndex()、RemoveIndex()、RemoveAllIndices() API选择要提取的块。vtkExtractBlock有一个属性PruneOutput,当设置结果时,输出多块被修剪为没有任何空分支。为了简单起见,我们将忽略该算法的树修剪组件,建议读者查看源代码以了解修剪算法的详细信息。在VTK/Graphics/vtkExtractBlock.h中的类声明,减去树修剪代码如下:

#ifndef __vtkExtractBlock_h
#define __vtkExtractBlock_h
#include "vtkMultiBlockDataSetAlgorithm.h"
class vtkCompositeDataIterator;
class vtkMultiPieceDataSet;
class VTK_GRAPHICS_EXPORT vtkExtractBlock : public
vtkMultiBlockDataSetAlgorithm
{
public:
static vtkExtractBlock* New();
vtkTypeRevisionMacro(vtkExtractBlock, vtkMultiBlockDataSetAlgorithm);
void PrintSelf(ostream& os, vtkIndent indent);
// Description: Select the block indices to extract.
// Each node in the multi-block tree is identified by an \c index.
// The index can be obtained by performing a preorder traversal of the
// tree (including empty nodes). eg. A(B (D, E), C(F, G)).
// Inorder traversal yields: A, B, D, E, C, F, G
// Index of A is 0, while index of C is 4.
void AddIndex(unsigned int index);
void RemoveIndex(unsigned int index);
void RemoveAllIndices();
//BTX
protected:
vtkExtractBlock();
~vtkExtractBlock();
// Implementation of the algorithm.
virtual int RequestData(vtkInformation *,
vtkInformationVector **, vtkInformationVector *);
// Extract subtree
void CopySubTree(vtkCompositeDataIterator* loc,
vtkMultiBlockDataSet* output, vtkMultiBlockDataSet* input);
private:
vtkExtractBlock(const vtkExtractBlock&); // Not implemented.
void operator=(const vtkExtractBlock&); // Not implemented.
class vtkSet;
vtkSet *Indices;
vtkSet *ActiveIndices;
//ETX
};
#endif

以下是VTK/Graphics/vtkExtractBlock的摘录。CXX文件(减去树修剪代码),其中显示了各种方法的实现。
构造函数初始化默认参数,其中包括用于提取索引的集合的分配。

{
this->Indices = new vtkExtractBlock::vtkSet();
this->ActiveIndices = new vtkExtractBlock::vtkSet();
}

这里vtkSet仅仅是STL set的子类,这样定义是为了避免在VTK头文件中包含STL头文件,如下所示:

#include <vtkstd/set>
class vtkExtractBlock::vtkSet : public vtkstd::set<unsigned int>
{
};
The destructor releases the memory allocated for sets.
vtkExtractBlock::~vtkExtractBlock()
{
delete this->Indices;
delete this->ActiveIndices;
}

RequestData()方法是实现过滤器的关键所在。以下为节选:

Author: utkarsh Subject: Inserted Text Date: 5/26/2009 12:02:34 PM
int vtkExtractBlock::RequestData(
vtkInformation *vtkNotUsed(request),
vtkInformationVector **inputVector,
vtkInformationVector *outputVector)
{
vtkMultiBlockDataSet *input =
vtkMultiBlockDataSet::GetData(inputVector[0], 0);
vtkMultiBlockDataSet *output = 
vtkMultiBlockDataSet::GetData(outputVector, 0);
if (this->Indices->find(0) != this->Indices->end())
{
// trivial case.
output->ShallowCopy(input);
return 1;
}
output->CopyStructure(input);
(*this->ActiveIndices) = (*this->Indices);
// Copy selected blocks over to the output.
vtkCompositeDataIterator* iter = input->NewIterator();
iter->VisitOnlyLeavesOff();
for (iter->InitTraversal();
!iter->IsDoneWithTraversal() && this->ActiveIndices->size()>0;
iter->GoToNextItem())
{
if (this->ActiveIndices->find(iter->GetCurrentFlatIndex()) !=
this->ActiveIndices->end())
{
this->ActiveIndices->erase(iter->GetCurrentFlatIndex());
// This removed the visited indices from this->ActiveIndices.
this->CopySubTree(iter, output, input);
}
}
iter->Delete();
this->ActiveIndices->clear();
...
return 1;
}

注意,与前面使用的vtkCompositeDataSet上的CopyStructure()不同,vtkCompositeDataSet上的CopyStructure只是复制复合树的结构,而不是复制输入的几何结构。一旦我们有了一个与输入具有相同结构的输出复合树,我们就可以使用vtkCompositeDataIterator对输入进行迭代,并仅将所选索引的块复制到输出。
CopySubTree()是一个复制整个子树的方法,如下所示:

void vtkExtractBlock::CopySubTree(vtkCompositeDataIterator* loc,
vtkMultiBlockDataSet* output, vtkMultiBlockDataSet* input)
{
vtkDataObject* inputNode = input->GetDataSet(loc);
if (!inputNode->IsA("vtkCompositeDataSet"))
{
vtkDataObject* clone = inputNode->NewInstance();
clone->ShallowCopy(inputNode);
output->SetDataSet(loc, clone);
clone->Delete();
}
else
{
vtkCompositeDataSet* cinput =
vtkCompositeDataSet::SafeDownCast(inputNode);
vtkCompositeDataSet* coutput = vtkCompositeDataSet::SafeDownCast(
outut->GetDataSet(loc));
vtkCompositeDataIterator* iter = cinput->NewIterator();
iter->VisitOnlyLeavesOff();
for (iter->InitTraversal(); !iter->IsDoneWithTraversal();
iter->GoToNextItem())
{
vtkDataObject* curNode = iter->GetCurrentDataObject();
vtkDataObject* clone = curNode->NewInstance();
clone->ShallowCopy(curNode);
coutput->SetDataSet(iter, clone);
clone->Delete();
this->ActiveIndices->erase(loc->GetCurrentFlatIndex() +
iter->GetCurrentFlatIndex());
}
iter->Delete();
}
}

通常,处理复合数据集的过滤器使用vtkCompositeDataIterator来遍历复合树中的节点。vtkCompositeDataSet的具体子类也提供了额外的API来访问树。vtkMultiBlockDataSet有GetBlock() API。当编写处理复合数据集的过滤器时,确保创建的执行器是vtkCompositeDataPipeline。这是通过重写vtkAlgorithm::CreateDefaultExecutive()来完成的,该()已经在vtkmultiblockdatasetalgalgorithm中实现,因此我们不在这个过滤器中这样做。

  • 可编程滤波器

在c++中开发过滤器的另一种选择是使用可编程算法。这些对象允许您创建在算法执行期间(即在RequestData()方法期间)调用的函数。可编程过滤器的优点是,您不必重新构建VTK库,甚至不必使用c++。

        实际上,您可以使用受支持的解释性语言Tcl、Java和Python来创建过滤器!可编程源和过滤器是允许您在运行时创建新过滤器的算法。不需要创建c++类或重新构建对象库。可编程对象负责连接到可视化管道的开销,只需要编写过滤器RequestData()方法的主体。可编程对象是vtkprogramablesource、vtkprogramablefilter、vtkprogramableleglyphfilter、vtkprogramableattributedatafilter和vtkprogramabledataobjectsource。

        vtkProgrammableSource是一个源对象,支持并可以生成任何VTK数据集类型的输出。vtkprogramablefilter允许您设置输入和检索任何数据集类型的输出(例如,GetPolyDataOutput())。过滤器vtkProgrammableAttributeDataFilter允许一个或多个相同或不同类型的输入,并且可以生成任何数据集类型的输出。

        一个示例将阐明这些过滤器的应用。这段代码摘自VTK/Examples/ modeling /Tcl/expCos.tcl。

vtkProgrammableFilter besselF
 besselF SetInputConnection [transF GetOutputPort]
besselF SetExecuteMethod bessel
 proc bessel {} {
 set input [besselF GetPolyDataInput]
 set numPts [$input GetNumberOfPoints]
vtkPoints newPts
 vtkFloatArray derivs
for {set i 0} {$i < $numPts} {incr i} {
set x [$input GetPoint $i]
set x0 [lindex $x 0]
set x1 [lindex $x 1]
set r [expr sqrt($x0*$x0 + $x1*$x1)]
set x2 [expr exp(-$r) * cos(10.0*$r)]
set deriv [expr -exp(-$r) * (cos(10.0*$r) + 10.0*sin(10.0*$r))]
newPts InsertPoint $i $x0 $x1 $x2
eval derivs InsertValue $i $deriv
}
set output [besselF GetPolyDataOutput]
$output CopyStructure $input
$output SetPoints newPts
 [$output GetPointData] SetScalars derivs
 newPts Delete; #reference counting - it's ok
derivs Delete
 }
 vtkWarpScalar warp
warp SetInput [besselF GetPolyDataOutput]
warp XYPlaneOn
warp SetScaleFactor 0.5

这个例子实例化了一个vtkProgrammableFilter,然后Tcl进程bessel()作为计算bessel函数和导数的函数。注意,bessel()直接使用GetPolyDataOutput()方法获得的过滤器输出。这是因为besselF的输出可以是任何VTK支持的数据集类型,并且我们必须指示使用输出的对象使用哪种类型。

        我们希望本章可以帮助您在VTK中编写自己的过滤器。您可能希望通过研究其他筛选器中的源代码来利用本文提供的信息。如果你能找到一个你理解的算法,然后看看VTK是如何实现它的,这将特别有帮助。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值