持久性筛选器

引言

如第章所示  流和线程化 ,OTB有两种主要机制来处理大数据:流允许逐段处理图像,多线程允许同时处理一个流数据块的多个块。使用这些概念,人们可以很容易地编写像素或基于邻域的过滤器,并将它们插入到相对于输入图像大小可缩放的流水线中。

然而,有时我们需要计算整个图像的全局特征。一个例子是确定输入图像的图像均值和方差,以便产生居中和缩小的图像。居中和缩小每个像素的操作与流和线程完全兼容,但必须首先估计图像的均值和方差。这第一步需要遍历整个图像一次,传统的基于流和多线程的过滤器架构在这里没有帮助。

这是因为这两个操作之间有一个根本的区别:一个支持流,另一个需要执行流。事实上,我们希望通过一些过滤器逐段地传输整个图像,该过滤器将收集和保留均值和方差累积量,然后合成这些累积量,以计算最终的均值和方差,一旦整个图像被流传输。每个流也将受益于并行处理。这正是持久性过滤器的作用所在。

架构

持久性筛选器框架中有两个主要对象。第一个是 otb::PersistentImageFilter ,第二个是 otb::PersistentFilterStreamingDecorator

持久性筛选器类

这个 otb::PersistentImageFilter 班级是固定的 itk::ImageToImageFilter ,带有两个附加的纯虚拟方法: Synthetize() 以及 Reset() 方法:研究方法。

想象一下, GenerateData()ThreadedGenerateData() 渐进地计算整个图像的一些全局特征,使用类的某个成员来存储中间结果。这个 Synthetize() 是一种额外的方法,它被设计为一种被称为整个图像已经被处理的方法,以便从中间结果计算最终结果。这个 Reset() 方法旨在允许重置中间结果成员,以便开始新的处理。

类的任何子类 otb::PersistentImageFilter 可以作为常规的 itk::ImageToImageFilter (前提是两者都 Synthetize()Reset() 都已实现,但这些过滤器的真正用处是与下一节中介绍的流修饰符类一起使用。

流装饰器类

这个 otb::PersistentFilterStreamingDecorator 是一个被设计为使用 otb::PersistentImageFilter 。它提供了一种机制,使用第三个名为 otb::StreamingImageVirtualWriter 。当 Update() 方法,则在 otb::PersistentFilterStreamingDecorator 的模板化子类的管道。 otb::PersistentImageFilter 添加到一个实例 otb::StreamingImageVirtualWriter 被创造出来了。后者随后被更新,其行为类似于常规 otb::ImageFileWriter 但它实际上不会向磁盘写入任何内容 : streaming pieces are requested and immediately discarded. The otb::PersistentFilterStreamingDecorator 也可以调用 Reset() 开始时的方法和 Synthetize() 在流处理结束时调用。因此,它将整个机制打包,以便使用 otb::PersistentImageFilter

  1. 调用 Reset() 方法,以重置任何临时结果成员,
  2. 将图像分段地流过滤光器,
  3. 调用 Synthetize() 方法,以计算最终结果。

有一些方法允许调整 otb::StreamingImageVirtualWriter 允许相对于某个目标可用内存量改变图像分割方法(瓦片或条带)或流的大小。有关详细信息,请参阅类文档。的实例 otb::StreamingImageVirtualWriter 可以从 otb::PersistentFilterStreamingDecorator 通过 GetStreamer() 方法。

通过内部过滤器, otb::PersistentFilterStreamingDecorator 可以通过 GetFilter() 方法时,通常会派生该类以打包流修饰的筛选器并包装参数setter和getter。

端到端示例

这是一个端到端的示例,使用支持流和线程的过滤器计算完整图像的平均值。请注意,这里只解释了具体的细节。有关如何编写筛选器的更多一般信息,请参阅部分  滤器

第一步:编写持久化过滤器

第一步是编写一个持久的均值图像过滤器。我们需要包括适当的标题:

#include "otbPersistentImageFilter.h"

然后,我们按如下方式声明类原型:

template<class TInputImage>
class ITK_EXPORT PersistentMeanImageFilter :
  public PersistentImageFilter<TInputImage, TInputImage>

由于输出图像将仅用于流目的,因此我们不需要声明不同的输入和输出模板类型。

private 部分,我们将声明一个将用于存储临时结果的成员和一个将用于存储最终结果的成员。

private:
  // Temporary results container
  std::vector<PixelType> m_TemporarySums;

  // Final result member
  double m_Mean;

接下来,我们将编写 Reset() 中的方法实现 protected 班级的一部分。这里处理相对于线程数量的临时结果容器的适当分配。

protected:
  virtual void Reset()
  {
    // Retrieve the number of threads
    unsigned int numberOfThreads = this->GetNumberOfThreads();

    // Reset the temporary results container
    m_TemporarySums = std::vector<PixelType>(numberOfThreads, itk::NumericTraits<PixelType>::Zero);

    // Reset the final result
    m_Mean = 0.;
  }

现在,我们需要编写 ThreadedGenerateData() 方法(也可以在 protected 节),将为每一条流计算临时结果。

virtual void ThreadedGenerateData(const RegionType&
                                  outputRegionForThread,
                                  itk::ThreadIdType threadId)
{
  // Enable progress reporting
  itk::ProgressReporter(this,threadId,outputRegionForThread.GetNumberOfPixels());

  // Retrieve the input pointer
  InputImagePointer inputPtr = const_cast<TInputImage *>(this->GetInput());

  // Declare an iterator on the region
  itk::ImageRegionConstIteratorWithIndex<TInputImage> it(inputPtr,
  outputRegionForThread);

  // Walk the region of the image with the iterator
  for (it.GoToBegin(); !it.IsAtEnd(); ++it, progress.CompletedPixel())
  {
    // Retrieve pixel value
    const PixelType& value = it.Get();

    // Update temporary results for the current thread
    m_TemporarySums[threadId]+= value;
  }
}

最后,我们需要定义 Synthetize() 方法(仍在 protected 节),它将产生最终结果:

virtual void Synthetize()
{
  // For each thread
  for(unsigned int threadId = 0; threadId <this->GetNumberOfThreads();++threadId)
  {
    // Update final result
    m_Mean+=m_TemporarySums[threadId];
  }

  // Complete calculus by dividing by the total number of pixels
  unsigned int nbPixels = this->GetInput()->GetLargestPossibleRegion().GetNumberOfPixels();

  if(nbPixels != 0)
  {
    m_Mean /= nbPixels;
  }
}

第二步:装饰滤镜并使用它

现在,要使用滤镜,只需使用 otb::PersistentFilterStreamingDecorator 。第一步是包括适当的标题:

#include "otbPersistentMeanImageFilter.h"
#include "otbPersistentFilterStreamingDecorator.h"

然后,我们使用一些typedef来装饰滤镜:

typedef otb::PersistentMeanImageFilter<ImageType> PersitentMeanFilterType;
typedef otb::PersistentFilterStreamingDecorator <PersitentMeanFilterType> StreamingMeanFilterType;

现在,装饰后的滤镜可以像任何标准滤镜一样使用:

StreamingMeanFilterType::Pointer filter = StreamingMeanFilterType::New();

filter->SetInput(reader->GetOutput());
filter->Update();

第三步:一个阶级统治所有人

通过从修饰过的筛选器派生一个新类,通常可以方便地避免前面部分中的几个typedef:

template<class TInputImage>
class ITK_EXPORT StreamingMeanImageFilter :
  public PersistentFilterStreamingDecorator<PersistentImageFilter<TInputImage, TInputImage>>

这还允许重新定义参数的setter和getter,从而避免调用 GetFilter() 方法来设置它们。