首页/文章/ 详情

VTK开发精要:数据与管线机制

2年前浏览2985

VTK开发精要 by济南友泉软件有限公司

一、数据存储

图形图像处理与科学计算数据可视化的数据主要由几何信息、拓扑信息、属性信息等三类数据组成。VTK定义一套数据结构用于存储这三类信息。

  • vtkDataArray

vtkDataArray派生于vtkAbstractArray,实际上一个Tuple元素的动态数组,每个Tuple包含vtkAbstractArray::NumberOfComponents个分量,总共包含vtkAbstractArray::MaxId个Tuples。

image.png

  • vtkFieldData

vtkFiledData实际上是一组vtkDataArray (A group of vtkDataArray objects)。不同vtkDataArray可以有不同的Tuple数目,而且不同vtkDataArray中Tuple的分量大小也可以不同。可以看到,可以将速度、压力、温度等场变量存储到vtkFiledData内部的vtkDataArray中。

image.png

另外,从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组合在一起以处理数据与信息,构成了逻辑上“线状”的结构。

image.png

  • Filter

Filter指的是派生于vtkAlgorithm的类,用于处理数据与算法。Filter由输入端口、算法、执行对象(vtkExcutive及其子类)、输出端口、信息对象等组成。

image.png

每个输入端口可以包含多个连接,而每个输出端口只有一个连接。当Filter输入端口个数为0,这样的Filter也被成为Reader或者Source;当Filter的输出端口个数为0,这样的Filter被称作Writer或者Sink。

Filter内部的算法通常是在重写vtkAlgorithm::ProcessRequest()函数,或者派生于vtkAlgorithm子类,然后重写对应的请求处理函数。例如可以派生vtkMultiBlockAlgorithm,然后通过重写RequestData()函数响应REQUEST_DATA_OBJECT请求。

每个Filter内部都有一个执行对象(vtkExcutive及其子类),用于控制管线的运行。当每次调用Filter的Update()函数更新数据时,实际上是通过执行对象生成多个请求,按照指定流向与顺序,在上游或者下游的Filter及其对应的执行对象内依次执行。

image.png

信息对象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子类

image.png

附录B:vtkExecutive子类

image.png

参考资料

张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.

VTK官网

————————————————

理论科普单元技术网格处理几何处理通用流体基础结构基础ECADMCAD其他软件
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2021-12-20
最近编辑:2年前
YouQuan Soft
硕士 济南友泉软件有限公司
获赞 30粉丝 7文章 12课程 0
点赞
收藏
未登录
1条评论
YouQuan Soft
济南友泉软件有限公司
2年前
刚刚开始写,希望大家多多支持和转发
回复

课程
培训
服务
行家

VIP会员 学习 福利任务 兑换礼品
下载APP
联系我们
帮助与反馈