VTK开发精要 by济南友泉软件有限公司
一、数据存储
图形图像处理与科学计算数据可视化的数据主要由几何信息、拓扑信息、属性信息等三类数据组成。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 data-object creation pass before the information pass. if(!this->UpdateDataObject()) { return 0; } // Setup the request for information. if (!this->InfoRequest) { this->InfoRequest = vtkInformation::New(); this->InfoRequest->Set(REQUEST_INFORMATION()); // The request is forwarded upstream through the pipeline. this->InfoRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream); // Algorithms process this request after it is forwarded. this->InfoRequest->Set(vtkExecutive::ALGORITHM_AFTER_FORWARD(), 1); } // Send the request. return this->ProcessRequest(this->InfoRequest, this->GetInputInformation(), this->GetOutputInformation()); }
REQUEST_DATA
处理完毕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)
附录A: vtkDataObject子类
附录B:vtkExecutive子类
参考资料
张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.
————————————————