/*=========================================================================
 *
 *  Copyright UMC Utrecht and contributors
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef _itkUpsampleBSplineParametersFilter_hxx
#define _itkUpsampleBSplineParametersFilter_hxx

#include "itkUpsampleBSplineParametersFilter.h"

#include "itkBSplineResampleImageFunction.h"
#include "itkBSplineDecompositionImageFilter.h"
#include "itkResampleImageFilter.h"


namespace itk
{

/**
 * ******************* Constructor *******************
 */

template< class TArray, class TImage >
UpsampleBSplineParametersFilter< TArray, TImage >
::UpsampleBSplineParametersFilter()
{
  this->m_BSplineOrder = 3;

  // Initialize grid settings.
  this->m_CurrentGridOrigin.Fill( 0.0 );
  this->m_CurrentGridSpacing.Fill( 0.0 );
  this->m_CurrentGridDirection.Fill( 0.0 );
  this->m_RequiredGridOrigin.Fill( 0.0 );
  this->m_RequiredGridSpacing.Fill( 0.0 );
  this->m_RequiredGridDirection.Fill( 0.0 );
} // end Constructor()


/**
 * ******************* UpsampleParameters *******************
 */

template< class TArray, class TImage >
void
UpsampleBSplineParametersFilter< TArray, TImage >
::UpsampleParameters( const ArrayType & parameters_in,
  ArrayType & parameters_out )
{
  /** Determine if upsampling is required. */
  if( !this->DoUpsampling() )
  {
    parameters_out = parameters_in; // \todo: hard copy, can be avoided
    return;
  }

  /** Typedefs. */
  typedef itk::ResampleImageFilter<
    ImageType, ImageType >                        UpsampleFilterType;
  typedef itk::BSplineResampleImageFunction<
    ImageType, ValueType >                        CoefficientUpsampleFunctionType;
  typedef itk::BSplineDecompositionImageFilter<
    ImageType, ImageType >                        DecompositionFilterType;

  /** Get the number of parameters. */
  const unsigned int currentNumberOfPixels
    = this->m_CurrentGridRegion.GetNumberOfPixels();
  const unsigned int requiredNumberOfPixels
    = this->m_RequiredGridRegion.GetNumberOfPixels();

  /** Create the new vector of output parameters, with the correct size. */
  parameters_out.SetSize( requiredNumberOfPixels * Dimension );

  /** Get the pointer to the data of the input parameters. */
  PixelType * inputDataPointer
    = const_cast< PixelType * >( parameters_in.data_block() );
  PixelType * outputDataPointer
    = const_cast< PixelType * >( parameters_out.data_block() );

  /** The input parameters are represented as a coefficient image. */
  ImagePointer coeffs_in = ImageType::New();
  coeffs_in->SetOrigin(  this->m_CurrentGridOrigin );
  coeffs_in->SetSpacing( this->m_CurrentGridSpacing );
  coeffs_in->SetDirection( this->m_CurrentGridDirection );
  coeffs_in->SetRegions( this->m_CurrentGridRegion );

  /** Loop over dimension: each direction is upsampled separately. */
  for( unsigned int j = 0; j < Dimension; j++ )
  {
    /** Fill the coefficient image with parameter data. */
    coeffs_in->GetPixelContainer()->SetImportPointer(
      inputDataPointer, currentNumberOfPixels );
    inputDataPointer += currentNumberOfPixels;

    /** Set the coefficient image as the input of the upsampler filter.
     * The upsampler samples the deformation field at the locations
     * of the new control points, given the current coefficients
     * (note: it does not just interpolate the coefficient image,
     * which would be wrong). The B-spline coefficients that
     * describe the resulting image are computed by the
     * decomposition filter.
     *
     * This code is derived from the itk-example DeformableRegistration6.cxx.
     */
    typename UpsampleFilterType::Pointer upsampler
      = UpsampleFilterType::New();
    typename CoefficientUpsampleFunctionType::Pointer coeffUpsampleFunction
      = CoefficientUpsampleFunctionType::New();
    typename DecompositionFilterType::Pointer decompositionFilter
      = DecompositionFilterType::New();

    /** Setup the upsampler. */
    upsampler->SetInterpolator( coeffUpsampleFunction );
    upsampler->SetSize( this->m_RequiredGridRegion.GetSize() );
    upsampler->SetOutputStartIndex( this->m_RequiredGridRegion.GetIndex() );
    upsampler->SetOutputSpacing( this->m_RequiredGridSpacing );
    upsampler->SetOutputOrigin( this->m_RequiredGridOrigin );
    upsampler->SetOutputDirection( this->m_RequiredGridDirection );
    upsampler->SetInput( coeffs_in );

    /** Setup the decomposition filter. */
    decompositionFilter->SetSplineOrder( this->m_BSplineOrder );
    decompositionFilter->SetInput( upsampler->GetOutput() );

    /** Do the upsampling. */
    try
    {
      decompositionFilter->UpdateLargestPossibleRegion();
      // \todo: the decomposition filter could be multi-threaded
      // by deriving it from the RecursiveSeparableImageFilter,
      // similar to the SmoothingRecursiveGaussianImageFilter.
    }
    catch( itk::ExceptionObject & excp )
    {
      /** Add information to the exception. */
      excp.SetLocation( "UpsampleBSplineParametersFilter - UpsampleParameters()" );
      std::string err_str = excp.GetDescription();
      err_str += "\nError occurred while using decompositionFilter.\n";
      excp.SetDescription( err_str );

      /** Pass the exception to an higher level. */
      throw excp;
    }

    /** Get a pointer to the upsampled coefficient image. */
    const PixelType * coeffs_out = decompositionFilter->GetOutput()->GetBufferPointer();

    /** Copy the contents of coeffs_out in a ParametersType array. */
    std::copy( coeffs_out, coeffs_out + requiredNumberOfPixels,
      outputDataPointer + requiredNumberOfPixels * j );

  } // end for dimension loop

} // end UpsampleParameters()


/**
 * ******************* DoUpsampling *******************
 */

template< class TArray, class TImage >
bool
UpsampleBSplineParametersFilter< TArray, TImage >
::DoUpsampling( void )
{
  bool ret = ( this->m_CurrentGridOrigin != this->m_RequiredGridOrigin );
  ret |= ( this->m_CurrentGridSpacing != this->m_RequiredGridSpacing );
  ret |= ( this->m_CurrentGridDirection != this->m_RequiredGridDirection );
  ret |= ( this->m_CurrentGridRegion != this->m_RequiredGridRegion );

  return ret;

} // end DoUpsampling()


/**
 * ******************* PrintSelf *******************
 */

template< class TArray, class TImage >
void
UpsampleBSplineParametersFilter< TArray, TImage >
::PrintSelf( std::ostream & os, Indent indent ) const
{
  this->Superclass::PrintSelf( os, indent );

  os << indent << "CurrentGridOrigin: "  << this->m_CurrentGridOrigin << std::endl;
  os << indent << "CurrentGridSpacing: " << this->m_CurrentGridSpacing << std::endl;
  os << indent << "CurrentGridDirection: " << this->m_CurrentGridDirection << std::endl;
  os << indent << "CurrentGridRegion: "  << this->m_CurrentGridRegion << std::endl;

  os << indent << "RequiredGridOrigin: "  << this->m_RequiredGridOrigin << std::endl;
  os << indent << "RequiredGridSpacing: " << this->m_RequiredGridSpacing << std::endl;
  os << indent << "RequiredGridDirection: " << this->m_RequiredGridDirection << std::endl;
  os << indent << "RequiredGridRegion: "  << this->m_RequiredGridRegion << std::endl;

  os << indent << "BSplineOrder: " << this->m_BSplineOrder << std::endl;

} // end PrintSelf()


} // end namespace itk

#endif
