VTK开发精要:数据与管线机制
VTK开发精要
目录
一、 数据存储
2.1 vtkDataArray
2.2 vtkFieldData
2.3 vtkDataObject
2.4 vtkDataSet
2.5 vtkMultiBlockDataSet
二、 管线机制
2.1 管线
2.2 Filter
三、 vtkExcutive
3.1 关联算法
3.2 端口信息
3.3 数据流的传递
3.4 算法执行
四、 vtkDemandDrivenPipeline
4.1 请求触发
4.2 REQUEST_DATA_OBJECT
4.3 REQUEST_INFROMATION
4.4 REQUEST_DATA
五、 vtkStreamingDemandDrivenPipeline
六、 vktCompositeDataPipeline
附录A: vtkDataObject子类
附录B:vtkExecutive子类
参考资料
- 一、数据存储
图形图像处理与科学计算数据可视化的数据主要由几何信息、拓扑信息、属性信息等三类数据组成。VTK定义一套数据结构用于存储这三类信息。
- vtkDataArray
vtkDataArray派生于vtkAbstractArray,实际上一个Tuple元素的动态数组,每个Tuple包含vtkAbstractArray::NumberOfComponents个分量,总共包含vtkAbstractArray::MaxId个Tuples。
- vtkFieldData
vtkFiledData实际上是一组vtkDataArray (A group of vtkDataArray objects)。不同vtkDataArray可以有不同的Tuple数目,而且不同vtkDataArray中Tuple的分量大小也可以不同。可以看到,可以将速度、压力、温度等场变量存储到vtkFiledData内部的vtkDataArray中。
另外,从vtkFieldData派生的vtkPointData与vtkCellData分别用于存储定义在节点、单元中心的多个场数据(速度、压力、温度等)。
- vtkDataObject
vtkDataObject包含一个vtkFieldData类型的成员变量FieldData,同时vtkDataObject定义了数据可视化过程中通过vtkInformation、vtkInformationVector等信息对象来控制成员变量vtkDataObject::FieldData的接口。
VTK中定义了大量的派生于vtkDataObject的类,参见附录A。
- vtkDataSet
vtkDataSet直接派生于vtkDataObject,在vtkDataObject基础之上,其内部增加了vtkPointData、vtkCellData用于存储定义在节点、单元中心的场数据。vtkDataSet关联了几何、拓扑与属性信息,可以方便地进行可视化。
- vtkMultiBlockDataSet
vtkMultiBlockDataSet间接派生于vtkDataObject,提供类树状结构,每个树节点包含一个vtkDataObject指针。在CFD中,可以将计算结果按照空间或者时间存储到vtkMultiBlockDataSet的不同blocks内。
处理vtkMultiBlockDataSet这种混合数据的基本流程就是将树状结构中的每一个vtkDataObject进行处理,然后将所有的处理结果进行合并。
- 二、管线机制
VTK使用管线机制来完成数据与信息的处理,管线是由多个Filter顺序组合而成的逻辑上的管线状的结构。目前,VTK提供了vtkDemandDrivenPipeline、vtkStreamingDemandPipeline、vtkCompositeDataPipeline等三种管线实现(附录B)。
- 管线
VTK管线用于将多个Filter组合在一起以处理数据与信息,构成了逻辑上“线状”的结构。
- Filter
Filter指的是派生于vtkAlgorithm的类,用于处理数据与算法。Filter由输入端口、算法、执行对象(vtkExcutive及其子类)、输出端口、信息对象等组成。
每个输入端口可以包含多个连接,而每个输出端口只有一个连接。当Filter输入端口个数为0,这样的Filter也被成为Reader或者Source;当Filter的输出端口个数为0,这样的Filter被称作Writer或者Sink。
Filter内部的算法通常是在重写vtkAlgorithm::ProcessRequest()函数,或者派生于vtkAlgorithm子类,然后重写对应的请求处理函数。例如可以派生vtkMultiBlockAlgorithm,然后通过重写RequestData()函数响应REQUEST_DATA_OBJECT请求。
每个Filter内部都有一个执行对象(vtkExcutive及其子类),用于控制管线的运行。当每次调用Filter的Update()函数更新数据时,实际上是通过执行对象生成多个请求,按照指定流向与顺序,在上游或者下游的Filter及其对应的执行对象内依次执行。
信息对象vtkInformation实际上VTK实现的一个Map数据结构,而vtkInfromationVector是一个vtkInformation类型数组。vtkInformation /vtkInfromationVector用于存储管线相关的信息,包括输入输出端口参数、算法数据等。
- 三、vtkExcutive
VTK中的管线机制的实现比较复杂,大体上主要涉及vtkExcutive及其子类、vtkAlgorithm、vtkInformation等相关类型。本节对vtkExcutive的实现做分析。
- 关联算法
vtkExecutive内定义了vtkAlgorithm指针,用于关联对应的vtkAlgorithm对象,
// The algorithm managed by this executive. vtkAlgorithm* Algorithm;在vtkAlgorithm::SetExecutive函数中,完成vtkAlgorithm与vtkExecutive的关联,
void vtkAlgorithm::SetExecutive(vtkExecutive* newExecutive) { vtkExecutive* oldExecutive = this->Executive; if(newExecutive != oldExecutive) { if(newExecutive) { newExecutive->Register(this); vtkAlgorithmToExecutiveFriendship::SetAlgorithm(newExecutive, this); } this->Executive = newExecutive; if(oldExecutive) { vtkAlgorithmToExecutiveFriendship::SetAlgorithm(oldExecutive, nullptr); oldExecutive->UnRegister(this); } } }可以看到,vtkAlgorithm是通过vtkAlgorithmToExecutiveFriendship::SetAlgortihtm来调用vtkExecutive::SetAlgorithm设置。
class vtkAlgorithmToExecutiveFriendship { public: static void SetAlgorithm(vtkExecutive* executive, vtkAlgorithm* algorithm) { executive->SetAlgorithm(algorithm); } };- 端口信息
vtkExecutive内定义了类型为vtkInformationVector的OutputInformation来存储每个输出端口的信息,
// Store an information object for each output port of the algorithm. vtkInformationVector* OutputInformation;vtkInformationVector是vtkInformation的数组,OutputInformation的每个vtkInformation元素对应输出端口的信息。
由于每个输入端口可以有多个连接,vtkExcutive定义了vtkExecutiveInternals类型的ExecutiveInternal来存储输入端口的信息,
// Internal implementation details. vtkExecutiveInternals* ExecutiveInternal;vtkExecutiveInternals实际上一个vtkInfromationVector的列表,可以看成一个vtkInformation的不等列表格,每一行表示一个输入端口,对应的列数等于端口上连接数目。
class vtkExecutiveInternals { public: std::vector<vtkInformationVector*> InputInformation; vtkExecutiveInternals(); ~vtkExecutiveInternals(); vtkInformationVector** GetInputInformation(int newNumberOfPorts); };- 数据流的传递
请求会通过ProcessRequest()下发,在这里,ProcessRequest()实际上起到分发请求的作用,即将请求交给不同的响应代码进行处理。
在vtkExcutive::ProcessRequest函数中,会根据FORWARD_DIRECTION设置的选项决定请求是向上(vtkExcutive::RequestUpstream)还是向下(vtkExcutive::RequestDownstream)传递,可以看到如果指定向上传递请求,则会调用ForwardUpstream这个虚函数将请求向上游传递。
vtkExecutive::ProcessRequest函数会根据ALGOTIYHM_BEFOR_FORWARD与ALGORITHM_AFTER_FORWARD来决定本Filter内部算法执行数据处理是发生在传递请求之前还是传递请求之后。
int vtkExecutive::ProcessRequest(vtkInformation* request, vtkInformationVector** inInfo, vtkInformationVector* outInfo) { if(request->Has(FORWARD_DIRECTION())) { // Request will be forwarded. if(request->Get(FORWARD_DIRECTION()) == vtkExecutive::RequestUpstream) { if(this->Algorithm && request->Get(ALGORITHM_BEFORE_FORWARD())) { if(!this->CallAlgorithm(request, vtkExecutive::RequestUpstream, inInfo, outInfo)) { return 0; } } if(!this->ForwardUpstream(request)) { return 0; } if(this->Algorithm && request->Get(ALGORITHM_AFTER_FORWARD())) { if(!this->CallAlgorithm(request, vtkExecutive::RequestDownstream, inInfo, outInfo)) { return 0; } } } if(request->Get(FORWARD_DIRECTION()) == vtkExecutive::RequestDownstream) { vtkErrorMacro("Downstream forwarding not yet implemented."); return 0; } } else { // Request will not be forwarded. vtkErrorMacro("Non-forwarded requests are not yet implemented."); return 0; } return 1; }- 算法执行
在ProcessRequest中,会调用vtkExcutive::CallAlgorithm来执行本Filter的数据处理。
int vtkExecutive::CallAlgorithm(vtkInformation* request, int direction, vtkInformationVector** inInfo, vtkInformationVector* outInfo) { // Copy default information in the direction of information flow. this->CopyDefaultInformation(request, direction, inInfo, outInfo); // Invoke the request on the algorithm. this->InAlgorithm = 1; int result = this->Algorithm->ProcessRequest(request, inInfo, outInfo); this->InAlgorithm = 0; // If the algorithm failed report it now. if(!result) { vtkErrorMacro("Algorithm " << this->Algorithm->GetClassName() << "(" << this->Algorithm << ") returned failure for request: " << *request); } return result; }可以看到vtkExecutive会根据数据流的方向,调用CopyDefaultInformation将数据流从输出端口(输入连接)复制到输入连接(输出端口),然后调用虚函数vtkAlgorithm::ProcessRequest完成本Filter内实际的数据处理。
- 四、vtkDemandDrivenPipeline
vtkDemandDrivenPipeline在vtkExcutive基础之上,增加请求触发功能,同时增加对REUQEST_DATA_OBJECT、REQUEST_INFROMATION、REUQEST_DATA等请求的支持。
- 请求触发
如果管线采用vtkCompositeDataPipeline,则当调用vtkAlgorithm::Update函数之后,会调用vtkDemandDrivenPipeline::Update函数,其内部则是调用了两个虚函数UpdateInformation与UpdateData来完成主要业务。
int vtkDemandDrivenPipeline::Update(int port) { if(!this->UpdateInformation()) { return 0; } if(port >= -1 && port < this->Algorithm->GetNumberOfOutputPorts()) { return this->UpdateData(port); } else { return 1; } }- REQUEST_DATA_OBJECT
在vktCompositeDataPipeline::UpdateInformation中,首先调用UpdateDataObject虚函数生成REQUEST_DATA_OBJECT请求,并下发到ProcessRequest中
int vtkDemandDrivenPipeline::UpdateDataObject() { // The algorithm should not invoke anything on the executive. if(!this->CheckAlgorithm("UpdateDataObject", nullptr)) { return 0; } // Update the pipeline mtime first. if(!this->UpdatePipelineMTime()) { return 0; } // Setup the request for data object creation. if (!this->DataObjectRequest) { this->DataObjectRequest = vtkInformation::New(); this->DataObjectRequest->Set(REQUEST_DATA_OBJECT()); // The request is forwarded upstream through the pipeline. this->DataObjectRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream); // Algorithms process this request after it is forwarded. this->DataObjectRequest->Set(vtkExecutive::ALGORITHM_AFTER_FORWARD(), 1); } // Send the request. return this->ProcessRequest(this->DataObjectRequest, this->GetInputInformation(), this->GetOutputInformation()); }- REQUEST_INFROMATION
在vktCompositeDataPipeline::UpdateInformation中,处理完REQUEST_DATA_OBJECT请求之后,会进一步创建REQUEST_INFORMATION请求,并将其下发到ProcessRequest处理。
int vtkDemandDrivenPipeline::UpdateInformation() { // The algorithm should not invoke anything on the executive. if(!this->CheckAlgorithm("UpdateInformation", nullptr)) { return 0; } // Do the>处理完毕REQUEST_INFROMATION请求之后,vtkDemandDrivenPipeline::Update函数调用UpdateData虚函数,在vtkDemandDrivenPipeline::UpdateData中会创建REQUEST_DATA请求,并下发到ProcessRequest去处理。int vtkDemandDrivenPipeline::UpdateData(int outputPort) { // The algorithm should not invoke anything on the executive. if(!this->CheckAlgorithm("UpdateData", nullptr)) { return 0; } // Range check. if(outputPort < -1 || outputPort >= this->Algorithm->GetNumberOfOutputPorts()) { vtkErrorMacro("UpdateData given output port index " << outputPort << " on an algorithm with " << this->Algorithm->GetNumberOfOutputPorts() << " output ports."); return 0; } // Setup the request for data. if (!this->DataRequest) { 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_FORWARD(), 1); } // Send the request. this->DataRequest->Set(FROM_OUTPUT_PORT(), outputPort); return this->ProcessRequest(this->DataRequest, this->GetInputInformation(), this->GetOutputInformation()); }
- 五、vtkStreamingDemandDrivenPipeline
vtkStreamingDemandDrivenPipeline在vtkDemandDrivenPipeline提供的流水线功能基础之上,主要是增加对局部数据(分片数据)更新的功能。这一部分比较复杂,暂时不予以研究。
- 六、vktCompositeDataPipeline
vktCompositeDataPipeline不仅可以支持非混合数据,同时提供了对等混合数据处理算法的支持,混合数据算法指的是能够处理vtkMultiBlockDataSet、vtkUniformGridAMR等数据的vtkAlgorithm派生类。
vktCompositeDataPipeline是最新VTK默认使用的管线。(笔者机器上使用的VTK版本是VTK-8.2.0)
参考资料
- 张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.
- VTK官网
