[Insight-developers] Progress reporting revisited

Paul Yushkevich pauly at cognitica . com
Sun, 01 Jun 2003 09:40:37 -0400


This is a multi-part message in MIME format.
--------------010209050204000204020801
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Hi,

I needed to report progress in a couple of mini-pipeline filters in the 
SNAP application.  I noticed the message from a week ago saying that 
some of the mini-pipeline filters in ITK, such as the 
DiscreteGaussianImageFilter, do not report progress properly.

I wrote a small class, which I called itk::ProgressAccumulator.  It 
makes it easy to 'collect' progress from a chain of filters and report 
it as a single weighted sum progress value.  Here is an example of how 
ProgressAccumulator can be used in a filter's GenerateData method.  In 
this example we have a mini-pipeline filter that chains together 
internal filters m_Filter1, m_Filter2, and m_Filter3.

void
SomeMiniPipelineFilter
::GenerateData()
{
  ProgressAccumulator::Pointer accumulator = ProgressAccumulator::New();
  accumulator->SetMiniPipelineFilter(this);

  // Assign different weights to different filters if desired.  In this
  // case, Filter1 will run from 0 to 0.2, Filter2 from 0.2 to 0.9 and
  // Filter3 from 0.9 to 1.0. 
  accumulator->RegisterInternalFilter(m_Filter1, 0.2f); 
  accumulator->RegisterInternalFilter(m_Filter2, 0.7f);
  accumulator->RegisterInternalFilter(m_Filter3, 0.1f);

  ...

  m_Filter3->Update();
}

The accumulator adds itself as an observer to each of the internal 
filters.  When progress events fire, it computes the weighted sum of the 
progress values and calls the mini-pipeline filter's UpdateProgress() 
method, passing the accumulated progress as the parameter.

I've attached the code for the ProgressAccumulator as well as code for 
DiscreteGaussianImageFilter that uses this reporting mechanism.  If 
ya'll approve of it, could we add ProgressAccumulator code to the 
Insight/Common directory?  I think it would make it easy to fix progress 
reporting in all the other mini-pipeline filters that need fixing.

Paul.

--------------010209050204000204020801
Content-Type: text/plain;
 name="itkDiscreteGaussianImageFilter.txx"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="itkDiscreteGaussianImageFilter.txx"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkDiscreteGaussianImageFilter.txx,v $
  Language:  C++
  Date:      $Date: 2003/05/07 21:58:18 $
  Version:   $Revision: 1.29 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#ifndef _itkDiscreteGaussianImageFilter_txx
#define _itkDiscreteGaussianImageFilter_txx

#include "itkNeighborhoodOperatorImageFilter.h"
#include "itkGaussianOperator.h"
#include "itkImageRegionIterator.h"
#include "itkProgressAccumulator.h"

namespace itk
{
template <class TInputImage, class TOutputImage>
void 
DiscreteGaussianImageFilter<TInputImage,TOutputImage>
::GenerateInputRequestedRegion() throw(InvalidRequestedRegionError)
{
  // call the superclass' implementation of this method. this should
  // copy the output requested region to the input requested region
  Superclass::GenerateInputRequestedRegion();
  
  // get pointers to the input and output
  typename Superclass::InputImagePointer  inputPtr = 
    const_cast< TInputImage *>( this->GetInput() );
  
  if ( !inputPtr )
    {
    return;
    }

  // Build an operator so that we can determine the kernel size
  GaussianOperator<OutputPixelType, ImageDimension> oper;
  typename TInputImage::SizeType radius;
  
  for (unsigned int i = 0; i < TInputImage::ImageDimension; i++)
    {
    // Determine the size of the operator in this dimension.  Note that the
    // Gaussian is built as a 1D operator in each of the specified directions.
    oper.SetDirection(i);
    if (m_UseImageSpacing == true)
      {
      if (this->GetInput()->GetSpacing()[i] == 0.0)
        {
        itkExceptionMacro(<< "Pixel spacing cannot be zero");
        }
      else
        {
        oper.SetVariance(m_Variance[i] / this->GetInput()->GetSpacing()[i]);
        }
      }
    else
      {
      oper.SetVariance(m_Variance[i]);
      }
    oper.SetMaximumError(m_MaximumError[i]);
    oper.SetMaximumKernelWidth(m_MaximumKernelWidth);
    oper.CreateDirectional();
    
    radius[i] = oper.GetRadius(i);
    }

  // get a copy of the input requested region (should equal the output
  // requested region)
  typename TInputImage::RegionType inputRequestedRegion;
  inputRequestedRegion = inputPtr->GetRequestedRegion();

  // pad the input requested region by the operator radius
  inputRequestedRegion.PadByRadius( radius );

  // crop the input requested region at the input's largest possible region
  if ( inputRequestedRegion.Crop(inputPtr->GetLargestPossibleRegion()) )
    {
    inputPtr->SetRequestedRegion( inputRequestedRegion );
    return;
    }
  else
    {
    // Couldn't crop the region (requested region is outside the largest
    // possible region).  Throw an exception.

    // store what we tried to request (prior to trying to crop)
    inputPtr->SetRequestedRegion( inputRequestedRegion );
    
    // build an exception
    InvalidRequestedRegionError e(__FILE__, __LINE__);
    OStringStream msg;
    msg << static_cast<const char *>(this->GetNameOfClass())
        << "::GenerateInputRequestedRegion()";
    e.SetLocation(msg.str().c_str());
    e.SetDescription("Requested region is (at least partially) outside the largest possible region.");
    e.SetDataObject(inputPtr);
    throw e;
    }
}


template< class TInputImage, class TOutputImage >
void
DiscreteGaussianImageFilter<TInputImage, TOutputImage>
::GenerateData()
{
  typename TOutputImage::Pointer output = this->GetOutput();
  
  output->SetBufferedRegion(output->GetRequestedRegion());
  output->Allocate();

  // Type definition for the internal neighborhood filter
  typedef NeighborhoodOperatorImageFilter<
    InputImageType, OutputImageType>              InternalFilterType;
  typedef typename InternalFilterType::Pointer InternalFilterPointer;

  // Create a chain of filters
  InternalFilterPointer *filter = 
    new InternalFilterPointer[m_FilterDimensionality];

  // Create a series of operators
  GaussianOperator<OutputPixelType, ImageDimension> *oper = 
    new GaussianOperator<OutputPixelType,ImageDimension>[m_FilterDimensionality];

  // Create a process accumulator for tracking the progress of this minipipeline
  ProgressAccumulator::Pointer progress = ProgressAccumulator::New();
  progress->SetMiniPipelineFilter(this);

  // Initialize and connect the filters
  for (unsigned int i = 0; i < m_FilterDimensionality; ++i)
    {
    // Set up the operator for this dimension
    oper[i].SetDirection(i);
    if (m_UseImageSpacing == true)
      {
      if (this->GetInput()->GetSpacing()[i] == 0.0)
        {
        itkExceptionMacro(<< "Pixel spacing cannot be zero");
        }
      else
        {
        oper[i].SetVariance(m_Variance[i] / this->GetInput()->GetSpacing()[i]);
        }
      }
    else
      {
      oper[i].SetVariance(m_Variance[i]);
      }

    oper[i].SetMaximumKernelWidth(m_MaximumKernelWidth);
    oper[i].SetMaximumError(m_MaximumError[i]);
    oper[i].CreateDirectional();

    // Set up the filter
    filter[i] = InternalFilterType::New();
    filter[i]->SetOperator(oper[i]);
    
    // Release data on all but the 1st filter
    if(i != 0)
      filter[i]->ReleaseDataFlagOn();

    // Register the filter with the with progress accumulator using
    // equal weight proportion
    progress->RegisterInternalFilter(filter[i],1.0f / m_FilterDimensionality);

    // Connect the filter to the input or to the previous filter
    filter[i]->SetInput(i == 0 ? this->GetInput() : filter[i-1]->GetOutput());
    }

  // Graft this filters output onto the mini-pipeline so that the mini-pipeline
  // has the correct region ivars and will write to this filters bulk data
  // output.
  filter[m_FilterDimensionality-1]->GraftOutput( output );

  // Update the last filter in the chain
  filter[m_FilterDimensionality-1]->Update();
  
  // Graft the last output of the mini-pipeline onto this filters output so
  // the final output has the correct region ivars and a handle to the final
  // bulk data
  this->GraftOutput(output);

  // Clean up
  delete[] oper;
  delete[] filter;
}

template< class TInputImage, class TOutputImage >
void
DiscreteGaussianImageFilter<TInputImage, TOutputImage>
::PrintSelf(std::ostream& os, Indent indent) const
  {
  Superclass::PrintSelf(os,indent);

  os << indent << "Variance: " << m_Variance << std::endl;
  os << indent << "MaximumError: " << m_MaximumError << std::endl;
  os << indent << "MaximumKernelWidth: " << m_MaximumKernelWidth << std::endl;
  os << indent << "FilterDimensionality: " << m_FilterDimensionality << std::endl;
  os << indent << "UseImageSpacing: " << m_UseImageSpacing << std::endl;
}

} // end namespace itk

#endif

--------------010209050204000204020801
Content-Type: text/plain;
 name="itkProgressAccumulator.cxx"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="itkProgressAccumulator.cxx"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkProgressAccumulator.cxx,v $
  Language:  C++
  Date:      $Date: 2002/01/15 19:36:20 $
  Version:   $Revision: 1.4 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "itkProgressAccumulator.h"

namespace itk {

ProgressAccumulator
::ProgressAccumulator()
{
  // Initialize the progress values
  this->ResetProgress();

  // Create a member command
  m_CallbackCommand = CommandType::New();
  m_CallbackCommand->SetCallbackFunction( this, & Self::ReportProgress );
}

ProgressAccumulator
::~ProgressAccumulator()
{
  UnregisterAllFilters();
}

void
ProgressAccumulator
::RegisterInternalFilter(GenericFilterType *filter,float weight)
{
  // Observe the filter
  unsigned long tag = 
    filter->AddObserver(ProgressEvent(), m_CallbackCommand);
  
  // Create a record for the filter
  struct FilterRecord record;
  record.Filter = filter;
  record.Weight = weight;
  record.ObserverTag = tag;
  record.Progress = 0.0f;

  // Add the record to the list
  m_FilterRecord.push_back(record);
}

void 
ProgressAccumulator
::UnregisterAllFilters()
{
  // The filters should no longer be observing us
  FilterRecordVector::iterator it;
  for(it = m_FilterRecord.begin();it!=m_FilterRecord.end();++it)
    it->Filter->RemoveObserver(it->ObserverTag);
 
  // Clear the filter array
  m_FilterRecord.clear();

  // Reset the progress meter
  ResetProgress();
}

void 
ProgressAccumulator
::ResetProgress()
{
  // Reset the accumulated progress
  m_AccumulatedProgress = 0.0f;
  
  // Reset each of the individial progress meters 
  FilterRecordVector::iterator it;
  for(it = m_FilterRecord.begin();it!=m_FilterRecord.end();++it)
    it->Progress = 0.0f;
 
}

void 
ProgressAccumulator
::ReportProgress(Object *object, const EventObject &event)
{
  const ProcessObject * internalFilter = 
    dynamic_cast<const ProcessObject *>( object );

  if( typeid( event ) == typeid( ProgressEvent() ) )
    {
    // Add up the progress from different filters
    m_AccumulatedProgress = 0.0f;

    FilterRecordVector::iterator it;
    for(it = m_FilterRecord.begin();it!=m_FilterRecord.end();++it)
      {
      // Record the progress if it's from one of the registered filters
      if(it->Filter == internalFilter)
        {
        it->Progress = internalFilter->GetProgress();
        }
      
      m_AccumulatedProgress += it->Progress * it->Weight;
      }

    // Update the progress of the client mini-pipeline filter
    m_MiniPipelineFilter->UpdateProgress(m_AccumulatedProgress);
    }
}

} // End namespace itk



--------------010209050204000204020801
Content-Type: text/plain;
 name="itkProgressAccumulator.h"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="itkProgressAccumulator.h"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkProgressAccumulator.h,v $
  Language:  C++
  Date:      $Date: 2003/05/06 15:27:24 $
  Version:   $Revision: 1.13 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#ifndef __itkProgressAccumulator_h_
#define __itkProgressAccumulator_h_

#include "itkCommand.h"
#include "itkObject.h"
#include "itkProcessObject.h"
#include <vector>

namespace itk {

/**
 * \class ProgressAccumulator
 * \brief Facilitates progress reporting for filters that wrap around
 *        multiple other filters.
 *        
 * This object allows a mini-pipeline filters to easily keep track of the 
 * progress performed by the internal filters.  
 * See DiscreteGaussianImageFilter.txx for an implementation example.
 *
 * \sa DiscreteGaussianImageFilter
 *
 * <pre>
 * 
 */ 
class ProgressAccumulator : public Object
{
public:
  /** Standard class typedefs. */
  typedef ProgressAccumulator        Self;
  typedef Object                     Superclass;
  typedef SmartPointer<Self>         Pointer;
  typedef SmartPointer<const Self>   ConstPointer;

  /** Typedef for inputting filters */
  typedef ProcessObject              GenericFilterType;
  typedef GenericFilterType::Pointer GenericFilterPointer;

  /** Standard New method. */
  itkNewMacro(Self);  

  /** Runtime information support. */
  itkTypeMacro(ProgressAccumulator,Object);
  
  /** Get the total progress accumulated by this object */
  itkGetMacro(AccumulatedProgress,float);

  /** Set the mini-pipeline filter */
  itkSetMacro(MiniPipelineFilter,GenericFilterPointer);

  /** Set the mini-pipeline filter */
  itkGetMacro(MiniPipelineFilter,GenericFilterPointer);

  /** 
   * Register a filter with the progress accumulator and specify the
   * fraction of the overall progress associated with this filter
   */
  void RegisterInternalFilter(GenericFilterType *filter, float weight);

  /**
   * Unregister all filters that have been registered with this object
   */
  void UnregisterAllFilters();

  /** 
   * Reset the progress accumulator.  This method should be called in
   * the beginning of the GenerateData() method in the mini-pipeline
   * filter.
   */
  void ResetProgress();

protected:
  ProgressAccumulator();
  virtual ~ProgressAccumulator();

private:
  /**  Command for observing progress of pipeline filters */
  typedef MemberCommand< Self >      CommandType;  
  typedef CommandType::Pointer       CommandPointer;  

  /** Structure associated with each filter in the pipeline */
  struct FilterRecord 
    {
    // Pointer to the filter
    GenericFilterPointer Filter;

    // The weight of the filter in total progress of the mini-pipeline
    float                Weight;

    // The tag for adding/removing observers to mini-pipeline filter
    unsigned long        ObserverTag;

    // The progress accumulated by the filter since last Reset()
    float                Progress;
    };

  /** A callback function that is called by the progressing filters */
  void ReportProgress(Object * object, const EventObject & event);
    
  /** The client mini-pipeline filter */
  GenericFilterPointer m_MiniPipelineFilter;
  
  /** An array of record structures */
  typedef std::vector<struct FilterRecord> FilterRecordVector;

  /** The total accumulated progress */
  float m_AccumulatedProgress;

  /** 
   * A list of progress proportions of the different filters in the 
   * pipeline 
   */
  FilterRecordVector m_FilterRecord;

  /** The callback command */
  CommandPointer m_CallbackCommand; 
};

} // End namespace itk

#endif // __itkProgressAccumulator_h_


--------------010209050204000204020801--