1.VTK管线机制
VTK中通过管线机制来实现组合各种算法处理数据。每一种算法是一个人Filter,多个Filter连接在一起形成VTK管线。每一个Filter可以分为两个部分:一个是算法对象,继承自vtkAlgrithm,主要负责处理输入的数据和信息;另一个是执行对象,继承自vtkExecute(),负责通知算法对象何时运行以及需要处理的数据和信息。Filter类继承自vtkAlgrithm及其子类,实例化时,内部会生成一个默认的Executive()对象,用于管理执行管线。数据和信息通过端口在Filter中传递,根据数据流的方向,分为输入端口和输出端口,如下图所示:
Filter的输入数据与信息存储在输入端中。一个Filter可能有0个输入端口(例如Reader对象);也可能有一个或多个输入端口(例如,vtkGlyph3D类需要两个输入端口,每一个输入端口可以建立多个连接)。
一个Filter可能有一个或多个输出端口,每个输出端口对应一个逻辑输出。例如,vtkExtractvectorComponents类,从一个三维向量数据中提取每个分量数据,该Filter需要一个输入端口接受向量数据,三个输出端口用于输出三个分量数据,端口号分别为0,1,2.
Filter之间通过(Port)建立连接(Connection)。例如一个标准的联机代码如下:Filter2->SetInputConnection(Filter1->GetOutputPort());
该句代码将Filter1的输出端口与Filter2的输入端口建立连接,连接中只涉及一个输入端口和一个输出端口。而VTK中还有许多Filter可能需要多个输入,例如vtkGlyph3D,该类需要两个输入数据并生成一盒输出数据。因此这里需要建立两个连接,相应的函数分别为SetInputConnection()和SetSourceConnection(),其中,SetInputConnection()输出的是几何点集数据,对应输入端口为0,SetSourceConnection()输入的Glyph图形数据,对应输入端1.vtkGlyph3D中输入的两个数据具有不同的意义,因此建立两个不同的输入端口。另外对一个Filter的多个输入数据具有相同意义时,则只需要建立一个输入端口,并使用AddInputConnection()来添加新的连接。例如vtkAppendFilter类实现数据的合并,其多个输入数据具有相同的意义,而不行vtkGlyph3D的两个输入表示不同的对象,因此其连接建立如下:
apeend = vtkAppendFilter::New(); append->AddInputConnection( foo->GetOutputPort ); append->AddInputConnection( bar->GetOutputPort );
下图显示了Filter之间建立连接的示意图:
2.完整的VTK管线
一个完整的VTK管线通常包含Source对象,Filter对象和Mapper对象。
1.Source对象时一个管线的起点,主要负责读取文件或者根据参数生成管线处理的数据,例如,vtkBMPReader、vtkSphereSource等。读取文件的source通常称为Reader。
2.Filter对象是处理数据的算法类,需要一个或者多个输入类生成一个或者多个输出,例如,vtkImageCast、vtkCurvarures等。
3.Mapper对象负责将数据转换为图元,但也可能将数据写入文件或者其他软件系统,写文件的Mapper通常称为Write。
执行管线连接和控制的实现
一般情况下,Source对象、Filter对象、Mapper对象统称为Filter。VTK中存在多种数据结构,如果每种数据类型都要定义一种Filter,那么将会产生非常大的Filter群,不利于定义一种通用的VTK执行管线。
管线的接口是通过逻辑端口(Logical Port)而不是数据流实现的,因此在形成连接的过程中不需要知道实际的数据类型,而是在执行时进行数据类型检查,以决定管线是否执行。
vtk定义了一个vtkInfomation类,用于存储和传递管线执行过程中的信息、请求和数据,实现执行管线的连接和控制。
3.信息对象类vtkInformation
vtkInformation是实现VTK实现VTK执行管线的一个非常重要的类。此类实际上是一个Mapper容器,采用Key-Value的映射方式,通过索引(Key)的类型来决定其对应的数据,用于存储管线中各种信息和数据。
使用vtkInformation类较好地增加了VTK执行管线二等灵活性,对于管线而言,不需要知道数据和信息的实际类型,从而不改变VTK执行管线类的接口的情况下,方便地为Filter的端口添加新的数据。
vtkInformation类中索引Key的类型为vtkInformationKey的子类,VTK定义了大量的索引类型,这些类都继承自vtkinformationKey。比如:vtkObjectData类中国蒂尼的静态函数;static vtkInformationDataObjectKey* DDATA_OBJECT();用于获取一个映射vtkDataObject类型的数据的vtkInformationDataObjectKey类型索引对于获取一个映射vtkDataObject类型数据的vtkInformationDataObjectKey类型索引对象,可以方便的保存和获取一个vtkDataObject数据,该函数在CPP文件中实现如下:vtkInformationKeyMacro( vtkDataObject, DATA_OBJECT, DataObject);vtkInformationKeyMacro是一个宏,其代码如下:
#define vtkInformationKeyMacro(CLASS, NAME, Type);
{
static vtkInformation##type##Key* CLASS##_##NAME =
new vtkInformation##type##Key(#NAME, #CLASS);
return CLASS##_##NAME;
}
从该宏的定义可知,需要三个参数:一个是调用宏的类名CLASS,一个是函数名字NAME,另外一个是索引类型Type。CLASS与NAME仅在内部使用,而type决定了返回的Key的类型,如以上的vtkInformationDataObjectKey传入的type为DataObject。所以一个Key类型结构为vtkInformation##type##Key,输入不同的type,即可得到不同的Key。
Key的约束
当一个Key类型的构造函数需要额外的信息来限制Key时,需要使用vtkInformationKeyRestrictedMacro宏:
#define vtkInformationKeyRestrictedMacro( CLASS, NAME, type, required) \ { static vtkInformation##type##Key* CLASS##_##NAME = new vtkInformation##type##Key( #NAME, #CLASS, required); return CLASS##_##NAME; }
例如,vtkDataObject中静态函数ORIGIN()用于返回映射三维向量的索引Key:static vtkInformationDoubleVectorKey* ORIGIN();
其实现如下:
vtkInformationKeyRestrictedMacro( vtkDataObject, ORIGIN, Double Vector, 3);
以上返回的是一个vtkInformationDoublevectorVectorKey类型,由于使用需要限定维数大小,因此使用vtkInformationKeyRestrictedMacro,并将为数作为第四个参数传入来限定向量的大小,其类型参数为DoubleVector。使用上述方法可以方便地自定义Key类型和访问函数。
管线信息对象
管线信息对象用于存储执行管线的执行信息,存储在Filter的执行对象中。每个输出端口对应一个管线对象,通过vtkExecutive::GetOutputInformation()函数获取。输出端口的管线信息对象中包含了输出端口的vtkDataObject数据,可以通过相应的索引Key值来获取;与此同时,每一个人输出端口vtkDataObject::GetPipelineInformation()来访问。另外,每一个输出端口
连接也对应一个管线对象,通过vtkExecutive::GetInputInformation()函数获取。输入端口连接对应的管线对象实际上为该链接对应的上游Filter的输出端口管线对象。
SetInputConnection(int potr,vtkAlgrithmOutput* intput)用于建立连接,其中input是一个vtkAlgrithmObject类型参数,定义一个端口index和算法对象Producer,通过函数GetIndex()和GetProducer()可以直接获取。因此在SetInputConnection()函数中,可以通过如下代码获取上游Filter的端口信息对象来建立连接。
vtkExecutive* producer =
( input && input->GetProducer() ) ? input->GetProducer()->GetExecutive():0;
int producerPort = producer? intput->GetIndex():0;
vtkInformation* newInfo =
producer ? producer->GetOutputInformation(producerPort):0;
GetOutputPort()函数用于获取上游Filter的输入端口信息,即SetInputConnection()中的input对象。
GetOutputPort()函数代码如下:
vtkAlgrithmOutput* vtkAlgrithm::GetOutputPort(int port)
{
if( !this->OutputPortIndexInRange(port, "get") )
{
return 0;
}
//Create the vtkAlgrithmOutput proxy object if there is not one
if( !this->AlgrithmInternal->Outputs[port] )
{
this->AlgrithmInternal[port] =
vtkSmartPoint<vtkAlgorithmOutput>::New();
this->AlgorithmInternal[port]->SetProducer(this);
this->AlgorithmInternal[port]->SetIndex[port];
}
//return the proxy pbject instance
return this->AlgorithmInternal->Outputs[port];
}
一个Filter可能会有多个输出端口,这就对应着多个管线信息对象,VTK中使用VTKInformationVector类表示信息对象的集合。在vtkExecutive中即使用该类来定义管线信息对象集合。使用该类可以方便地对信息对象进行操作,例如,GetNumberOfInformationObjects()/SetNumberOfInformationObjects()用于获取或设置信息对象数目;SetInformationObject()/GetinformationObject()用于设置或获取某个信息对象;Append()/Remove()用于追加或删除信息对象。在vtkAlgrithm中,也是该类来定义端口信息对象的集合。
端口信息对象
端口信息对象存储在vtkAlgrithm类中,每个输入端口和每个输出端口都对应一个端口信息对象。端口信息对象的作用是值输入/输出的类型,管线执行时需要根据输入/输出数据类型来判断和生成相应的数据。vtkAlgrithm::GetInputPortInformation()用于获取输入端口信息对象。在vtkAlgrithm,使用PORT_REQUIREMENTS_FILLED索引来标识是否设置端口的需求信息。因此,在GetInputInformation()中需要判断是否设置输入短偶需求信息。如果还为设置,则调用vtkAlgrithm::fillInformation()函数来设置输入端口需求信息。vtkAlgrithm也是一个很基本的基类。所以vtkAlgrithm::FillInputPortInformation()仅实现了一个空虚函数。在vtkAlgrithm子类中需要对该虚函数进行覆盖。例如vtkPolyDataAlgrithm中需要输入端口的数据类型为vtkPolyData,那么相应的FillInputPortInformation()实现如下:
int vtkPolyDataAlgrithm::FillInputPortInformation( int vtkNotUsed(port), vtkInformation* info) { info->Set( vtkAlgrithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); return 1; }
上面的代码就是使用了INPUT_REQUIRED_DATA_TYPE索引来设置输入数据类型的值。
算法信息对象
在vtkAlgrithm算法对象中,定义了端口信息对象,还定义一个算法信息对象。算法信息对象存储了关于算法对象的相关信息。可以通过vtkAlgrithm::GetInformation()获取。
vtkShirinkFilter::vtkShrinkFilter() { this->ShrinkFactor = 0.5; this->GetInformation()->Set( vtkAlgorithm::PRESERVES_RANGES(), 1); this->GetInformation()->Set( vtkAlgorithm::PRESERVES_BOUNDS(), 1); }
请求信息对象
VTK管线是通过一系列的请求完成的。请求被封装为vtkInformation对象在管线中传递。例如在vtkDemandDriven::Update()中定义请求如下:
this->DataRequest = vtkInformation::New(); this->DataRequest->Set(REQUEST_DATA()); //the request is forwarded upstream through the pipeline this->DataRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream); //Algorithms process this request after it is forwarded. this->DataRequest->Set( vtkExecutive::ALGORITHM_AFTER_FORWARDED, 1);
DataRequest是一个vtkInformation对象,通过vtkInformation::New()定义。REQUEST_DATA是一个重要的请求类型。REQUEST_DATA()函数返回一个VTKInformationRequestKey索引对象,其中报损了请求的名字为REQUEST_DATA。vtkInformationRequestKey用于表示一个索引请求。
数据信息对象
每个vtkDataObject数据对象中都保持了一个vtkInformation信息对象,用于存储当前数据对象中的逐句类型,可以通过函数vtkDataObject::GetInformation()来获取。