Difference between revisions of "Proposals:Calculators Architecture"

From KitwarePublic
Jump to navigationJump to search
(Created page with "= Proposal = A more flexible inverse transform API is needed in order to support numeric inverse transformation for transforms which do not have an analytical inverse. = Existin...")
 
 
(28 intermediate revisions by the same user not shown)
Line 1: Line 1:
= Proposal =
= Proposal =
A more flexible inverse transform API is needed in order to support numeric inverse transformation for transforms which do not have an analytical inverse.
There are currently two different types of Calculators implemented in ITK, one type that inherits from Object and one type that inherits from ProcessObject. We should choose one of these types: should Calculators inherit from Object and be separate from the pipeline, or should they inherit from ProcessObject and be part of the pipeline?


= Existing API =
= Current situation =  
* The inverse transformation functionality is available via the GetInverse method. The GetInverse method is not virtual, even though it is declared in the base class. The GetInverse method expects as its parameter a pointer to the forward transform object. The GetInverse method fills in the parameter list of the passed-in forward transform such that it would define the inverse transform.
 
* The inverse transformation functionality is also avaible via the deprecated BackTransform API (analogous to the TransformPoint, TransformVector and TransformCovariantVector forward transform API).
* Originally, all calculators had the following properties:
** Inherit from '''Object'''
** Are '''not''' part of the pipeline
** Were designed to be fast, separate from the pipeline structure, and easy to write avoiding the code overhead of filters.
** Must include the following methods: Compute
** Must include context specific Set/Get methods such as SetImage and GetThreshold
** Examples: All calculators except those that inherit from itkHistogramThresholdCalculator (which inherits from ProcessObject).  This includes, for example, OtsuMultipleThresholdsCalculator.
 
* A new group of calculators having the following properties:
** Inherit from '''ProcessObject'''
** Are part of the pipeline
** Suffer speed and complexity overhead from belonging to the pipeline
** Must include the following methods: Update, SetInput, GetOutput
** Provide a Compute() method that simply calls Update()
** Examples: All calculators that inherit from itkHistogramThresholdCalculator.  This includes, for example, OtsuThresholdCalculator.


= Existing limitations =
= Existing limitations =
* Since the GetInverse method requires that the type of the inverse transform must be the same as the type of the forward transform, the possibility of a numeric inverse transform is automatically precluded.
* The fact that there is no virtual method for inverse transformations is a big limitation and inconvenience.
* The identity transform does not provide any inverse transformation functionality at all.


= Proposed changes =
* How should a developer write a new calculator?  Should it inherit from Object or ProcessObject?
* The current GetInverse method may make perfect sense for transforms which are analytically invertible to a transform of the same type (Translation, Scale, Rotation, etc). It may make sense to keep this API intact.
* Having two separate implementations, both with the name "Calculator", that inherit from entirely different trees and with different methods is inconsistent and confusing.
* A virtual method declared at the base class should be provided that would return a pointer to a transform object. At the discretion of the forward transform, the inverse transform could be an analytic inverse or a numeric inverse.
* Another drawback of having two different inheritance trees is that the calculators can't be hot-swapped since they have different methods.
* A new iterative inverse transform class must be defined. This class should be parameterized by the forward transform type. The numeric inverse can be implemented using the Newton-Raphson method for nonlinear systems of equations.
* In order for the Newton-Raphson numeric inverse transform to be used, the forward transform API must define a method for evaluating the derivative of the transform with respect to the input image dimensions -- another Jacobian, different from the Jacobian currently provided by the forward transforms.


It makes some sense to implement the numeric inverse transform as a subclass of itk::Transform -- in order to provide a consistent API across forward and inverse transforms:
Here is a very troublesome example:
  template <typename TForwardTransform>
* The OtsuThresholCalculator inherits from HistogramThresholdCalculator, which in turn inherits from ProcessObject.
  class NumericInverseTransform :
* The OtsuMultipleThresholdsCalculator inherits from HistogramAlgorithmBase, which in turn inherits directly from Object. 
    public Transform<TForwardTransform::ScalarType,
* The implementations of the two algorithms can be nearly identical except that one can compute multiple thresholds.
                    TForwardTransform::OutputSpaceDimension,
* We cannot take advantage of inheritance even though both calculators are nearly identical!
                    TForwardTransform::InputSpaceDimension>
* Since the inheritance trees are so different, OtsuThresholdCalculator cannot be refactored to inherit from OtsuMultipleThresholdsCalculator even though that would be a clean implementation.
* In addition, the two filters have different methods: SetInput versus SetInputHistogram and Compile versus Update. 


However, not all of the expected functionality associated with the forward itk::Transform class makes sense for a numeric inverse transform. For example, there is only one parameter that defines a numeric inverse -- a reference to the forward transform. Therefore, the GetParameters/SetParameters methods are meaningless.
Solution (unfortunate):
* The only simple option is to instantiate one inside of the other.  OtsuMultipleThresholdsCalculator is instantiated in the GenerateData of itkOtsuThresholdCalculator to do all of the work.  See the following patch to implement this: http://review.source.kitware.com/#/c/11935/
* All methods need to be explicitly called on the one being instantiated. 
* If a new method is added to the one being called, the caller needs to explicitly call that method as well (wouldn't be needed using inheritance)


The following addition is proposed for the itk::Transform interface:
= Proposed changes =


  // Return an inverse transform -- the derived class must decide what to do here.
A decision should be made on which framework should be chosen going forward.  Then the current calculators should be refactored to all have the same inheritance tree and consistent methods.  Here are some suggestions, none of which is great.  
  // The default implementation throws an exception.
* Change the name of the calculators that inherit from itkHistogramThresholdCalculator to have "HistogramFilter" in the name instead of "Calculator".  Take guts out of the new calculators and put them in non-pipelined calculators that are called from the new calculators.
  virtual typename Transform<TScalarType,  
* (Dangerous) Change the inheritance tree of itkHistogramThresholdCalculator to derive from Object instead of ProcessObject.  Then clean up the resulting mess of changing GenerateData() calls to Compute().  To retain backward compatibility, add (deprecated) Update() methods to all of the derived classes that simply call Compute().
                            NOutputDimensions,
* Are there other better suggestions?
                            NInputDimensions>::Pointer GetInverse() const;


The following forward transform interface is required by the Newton-Raphson solver.
If the original framework (non-pipelined calculators) is chosen, methods like Compute() should be moved to a layer between Object and the calculator so that all calculators inherit them automatically to yield a consistent API.  Such a layer could consist of classes that have consistent methods for inputs and outputs. For example:
* HistogramThresholdCalculator class with methods: Compute, SetInputHistogram, GetOutputThreshold
* ImageCalculator class with methods: Compute, SetInputImage


  // evaluate F = T(x), J = dT/dx (another Jacobian):
= Current Status =  
  void Eval(const std::vector<ScalarType> & x,
            std::vector<ScalarType> & F,
            std::vector<std::vector<ScalarType> > & J) const;


= Current status =
Discussion phase...
I have a working implementation of the iterative numeric inverse using the numerical recipes code for the Newton-Raphson nonlinear solver. Currently, it is implemented within the deprecated BackTransform API (requiring minor modifications to the itk::Transform class). The inverse transform class does not derive from the itk::Transform class. This code is not up-to the ITK coding standards and will require some re-writing before being considered for inclusion into the ITK tree. Also, I am unclear whether the use of the numerical recipes code is allowed within ITK. If not, that will have to be rewritten as well.


{{ITK/Template/Footer}}
{{ITK/Template/Footer}}

Latest revision as of 17:33, 26 July 2013

Proposal

There are currently two different types of Calculators implemented in ITK, one type that inherits from Object and one type that inherits from ProcessObject. We should choose one of these types: should Calculators inherit from Object and be separate from the pipeline, or should they inherit from ProcessObject and be part of the pipeline?

Current situation

  • Originally, all calculators had the following properties:
    • Inherit from Object
    • Are not part of the pipeline
    • Were designed to be fast, separate from the pipeline structure, and easy to write avoiding the code overhead of filters.
    • Must include the following methods: Compute
    • Must include context specific Set/Get methods such as SetImage and GetThreshold
    • Examples: All calculators except those that inherit from itkHistogramThresholdCalculator (which inherits from ProcessObject). This includes, for example, OtsuMultipleThresholdsCalculator.
  • A new group of calculators having the following properties:
    • Inherit from ProcessObject
    • Are part of the pipeline
    • Suffer speed and complexity overhead from belonging to the pipeline
    • Must include the following methods: Update, SetInput, GetOutput
    • Provide a Compute() method that simply calls Update()
    • Examples: All calculators that inherit from itkHistogramThresholdCalculator. This includes, for example, OtsuThresholdCalculator.

Existing limitations

  • How should a developer write a new calculator? Should it inherit from Object or ProcessObject?
  • Having two separate implementations, both with the name "Calculator", that inherit from entirely different trees and with different methods is inconsistent and confusing.
  • Another drawback of having two different inheritance trees is that the calculators can't be hot-swapped since they have different methods.

Here is a very troublesome example:

  • The OtsuThresholCalculator inherits from HistogramThresholdCalculator, which in turn inherits from ProcessObject.
  • The OtsuMultipleThresholdsCalculator inherits from HistogramAlgorithmBase, which in turn inherits directly from Object.
  • The implementations of the two algorithms can be nearly identical except that one can compute multiple thresholds.
  • We cannot take advantage of inheritance even though both calculators are nearly identical!
  • Since the inheritance trees are so different, OtsuThresholdCalculator cannot be refactored to inherit from OtsuMultipleThresholdsCalculator even though that would be a clean implementation.
  • In addition, the two filters have different methods: SetInput versus SetInputHistogram and Compile versus Update.

Solution (unfortunate):

  • The only simple option is to instantiate one inside of the other. OtsuMultipleThresholdsCalculator is instantiated in the GenerateData of itkOtsuThresholdCalculator to do all of the work. See the following patch to implement this: http://review.source.kitware.com/#/c/11935/
  • All methods need to be explicitly called on the one being instantiated.
  • If a new method is added to the one being called, the caller needs to explicitly call that method as well (wouldn't be needed using inheritance)

Proposed changes

A decision should be made on which framework should be chosen going forward. Then the current calculators should be refactored to all have the same inheritance tree and consistent methods. Here are some suggestions, none of which is great.

  • Change the name of the calculators that inherit from itkHistogramThresholdCalculator to have "HistogramFilter" in the name instead of "Calculator". Take guts out of the new calculators and put them in non-pipelined calculators that are called from the new calculators.
  • (Dangerous) Change the inheritance tree of itkHistogramThresholdCalculator to derive from Object instead of ProcessObject. Then clean up the resulting mess of changing GenerateData() calls to Compute(). To retain backward compatibility, add (deprecated) Update() methods to all of the derived classes that simply call Compute().
  • Are there other better suggestions?

If the original framework (non-pipelined calculators) is chosen, methods like Compute() should be moved to a layer between Object and the calculator so that all calculators inherit them automatically to yield a consistent API. Such a layer could consist of classes that have consistent methods for inputs and outputs. For example:

  • HistogramThresholdCalculator class with methods: Compute, SetInputHistogram, GetOutputThreshold
  • ImageCalculator class with methods: Compute, SetInputImage

Current Status

Discussion phase...



ITK: [Welcome | Site Map]