/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  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 itkLabelGeometryImageFilter_hxx
#define itkLabelGeometryImageFilter_hxx

#include "itkLabelGeometryImageFilter.h"

#include "itkImageRegionConstIterator.h"
#include "itkImageRegionConstIteratorWithIndex.h"
#include "itkCastImageFilter.h"

#include "itkAffineTransform.h"
#include "itkResampleImageFilter.h"
#include "itkNearestNeighborInterpolateImageFunction.h"

namespace itk
{

namespace
{
//
// Private Helper functions
//
template< unsigned int NDimension >
vnl_matrix< double >
inline CalculateRotationMatrix(const vnl_symmetric_eigensystem< double > &eig)
{
  vnl_matrix<double> rotationMatrix(NDimension, NDimension, 0);
  for ( unsigned int i = 0; i < NDimension; i++ )
    {
    rotationMatrix.set_column( i, eig.get_eigenvector(i) );
    }
  // After vnl_symmetric_eigensystem, the columns of V are the eigenvectors,
  // sorted by increasing eigenvalue, from most negative to most positive.
  // First reorder the eigenvectors by decreasing eigenvalue.
  rotationMatrix.fliplr();

  // Next, check whether the determinant of the matrix is negative.
  // If it is, then the vectors do not follow the right-hand rule.  We
  // can fix this by making one of them negative.  Make the last
  // eigenvector (with smallest eigenvalue) negative.
  float matrixDet;
  if ( NDimension == 2 )
    {
    matrixDet = vnl_det(rotationMatrix[0], rotationMatrix[1]);
    }
  else if ( NDimension == 3 )
    {
    matrixDet = vnl_det(rotationMatrix[0], rotationMatrix[1], rotationMatrix[2]);
    }
  else
    {
    matrixDet = 0.0f;
    std::cerr << "ERROR: Determinant cannot be calculated for this dimension!" << std::endl;
    }

  if ( matrixDet < 0 )
    {
    rotationMatrix.set_column( NDimension - 1,
                               -rotationMatrix.get_column(NDimension - 1) );
    }

  // Transpose the matrix to yield the rotation matrix.
  rotationMatrix.inplace_transpose();

  return rotationMatrix;
}

template<typename TLabelImage, typename TIntensityImage, typename TInputImage >
inline bool
CalculateOrientedImage(
  const vnl_symmetric_eigensystem< double > &eig,
  typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelGeometry & labelGeometry,
  bool useLabelImage,
  const TInputImage *inputImage)
{
  static constexpr unsigned int Dimension = TInputImage::ImageDimension;

  // CalculateOrientedBoundingBoxVertices needs to have already been
  // called.  This is taken care of by the flags.

  // Rotate the original object to align with the coordinate
  // system defined by the eigenvectors.
  // Since the resampler moves from the output image to the input
  // image, we take the transpose of the rotation matrix.
  vnl_matrix<double> vnl_RotationMatrix = CalculateRotationMatrix< Dimension >(eig);
  vnl_RotationMatrix.inplace_transpose();

  // Set up the transform.  Here the center of rotation is the
  // centroid of the object, the rotation matrix is specified by the
  // eigenvectors, and there is no translation.
  using TransformType = itk::AffineTransform< double, Dimension >;
  typename TransformType::Pointer transform = TransformType::New();
  typename TransformType::MatrixType rotationMatrix(vnl_RotationMatrix);
  typename TransformType::CenterType center;
  typename TInputImage::PointType origin;
  for( unsigned int i = 0; i < Dimension; i++ )
  {
    center[i] = labelGeometry.m_Centroid[i] * inputImage->GetSpacing()[i];
    origin[i] = labelGeometry.m_OrientedBoundingBoxOrigin[i] * inputImage->GetSpacing()[i];
  }
  typename TransformType::OutputVectorType translation;
  translation.Fill(0);
  transform->SetCenter(center);
  transform->SetTranslation(translation);
  transform->SetMatrix(rotationMatrix);

  using ResampleFilterType = itk::ResampleImageFilter< TInputImage, TIntensityImage >;
  typename ResampleFilterType::Pointer resampler = ResampleFilterType::New();

  // The m_OrientedBoundingBoxSize is specified to float precision.
  // Here we need an integer size large enough to contain all of the points, so
  // we take the ceil of it.
  // We also need to ensure that that bounding box is not outside of
  // the image bounds.
  typename ResampleFilterType::SizeType boundingBoxSize;
  for ( unsigned int i = 0; i < Dimension; i++ )
    {
    boundingBoxSize[i] = ( typename ResampleFilterType::SizeType::SizeValueType )std::ceil(
      labelGeometry.m_OrientedBoundingBoxSize[i]);
    }

  resampler->SetTransform(transform);
  resampler->SetSize(boundingBoxSize);
  resampler->SetOutputSpacing( inputImage->GetSpacing() );
  resampler->SetOutputOrigin( origin );
  resampler->SetInput(inputImage);

  if ( useLabelImage )
    {
    // Set up the interpolator.
    // Use a nearest neighbor interpolator since these are labeled images.
    using InterpolatorType = itk::NearestNeighborInterpolateImageFunction< TInputImage, double >;
    typename InterpolatorType::Pointer interpolator  = InterpolatorType::New();
    resampler->SetInterpolator(interpolator);
    }
  else
    {
    // Set up the interpolator.
    // Use a linear interpolator since these are intensity images.
    using InterpolatorType = itk::LinearInterpolateImageFunction< TInputImage, double >;
    typename InterpolatorType::Pointer interpolator  = InterpolatorType::New();
    resampler->SetInterpolator(interpolator);
    }
  resampler->Update();
  labelGeometry.m_OrientedIntensityImage->Graft( resampler->GetOutput() );

  return true;
}

}

template< typename TLabelImage, typename TIntensityImage >
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::LabelGeometryImageFilter()
{
  this->SetNumberOfRequiredInputs(1);
  m_CalculatePixelIndices = false;
  m_CalculateOrientedBoundingBox = false;
  m_CalculateOrientedLabelRegions = false;
  m_CalculateOrientedIntensityRegions = false;
}

template< typename TLabelImage, typename TIntensityImage >
void
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GenerateData()
{
  LabelPixelType label;

  // Iterator over the label image.
  ImageRegionConstIterator< TLabelImage > labelIt ( this->GetInput(),
                                                    this->GetInput()->GetBufferedRegion() );

  using ImageIteratorWithIndexType = ImageRegionConstIteratorWithIndex< TLabelImage >;

  // Iterator over the mapping from labels to geometry values.
  MapIterator mapIt;

  // begin with empty m_LabelGeometryMapper and m_AllLabels
  m_LabelGeometryMapper.clear();
  m_AllLabels.clear();

  // Do the work
  while ( !labelIt.IsAtEnd() )
    {
    label = labelIt.Get();

    mapIt = m_LabelGeometryMapper.find(label);

    // Is the label already in the mapper?
    // If not, create a new geometry object.
    if ( mapIt == m_LabelGeometryMapper.end() )
      {
      using MapValueType = typename MapType::value_type;

      // map insert is defined as: pair<iterator, bool> insert(const value_type&
      // x)
      mapIt = m_LabelGeometryMapper.insert( MapValueType( label, LabelGeometry() ) ).first;
      }

    // Update the geometry values.

    // LABEL
    ( *mapIt ).second.m_Label = label;

    // BOUNDING BOX
    // The bounding box is defined in (min, max) pairs, such as
    // (xmin,xmax,ymin,ymax,zmin,zmax).
    typename ImageIteratorWithIndexType::IndexType index = labelIt.GetIndex();
    for ( unsigned int i = 0; i < ( 2 * ImageDimension ); i += 2 )
      {
      // Update min
      if ( ( *mapIt ).second.m_BoundingBox[i] > index[i / 2] )
        {
        ( *mapIt ).second.m_BoundingBox[i] = index[i / 2];
        }
      // Update max
      if ( ( *mapIt ).second.m_BoundingBox[i + 1] < index[i / 2] )
        {
        ( *mapIt ).second.m_BoundingBox[i + 1] = index[i / 2];
        }
      }

    // VOLUME
    ( *mapIt ).second.m_ZeroOrderMoment++;

    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      // FIRST ORDER RAW MOMENTS
      ( *mapIt ).second.m_FirstOrderRawMoments[i] += index[i];
      }

    // SECOND ORDER RAW MOMENTS
    // Even for ND, the second order moments can be found from just
    // two nested loops since second order moments consider only
    // interactions between pairs of indices.
    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      // It is only necessary to fill in half of the matrix since it is
      // symmetric.
      for ( unsigned int j = 0; j < ImageDimension; j++ )
        {
        ( *mapIt ).second.m_SecondOrderRawMoments(i, j) += index[i] * index[j];
        }
      }

    if ( m_CalculatePixelIndices == true )
      {
      // Pixel location list
      ( *mapIt ).second.m_PixelIndices.push_back(index);
      }

    ++labelIt;
    }

  const TIntensityImage *intensityImage = this->GetIntensityInput();

  // If an intensity image is defined, we can also calculate further
  // features.
  if ( intensityImage )
    {
    RealType value;

    // Iterator over the intensity image.
    using IntensityImageIteratorType = ImageRegionConstIteratorWithIndex< TIntensityImage >;

    IntensityImageIteratorType it( intensityImage, intensityImage->GetBufferedRegion() );

    typename IntensityImageIteratorType::IndexType index;

    labelIt.GoToBegin();

    while ( !it.IsAtEnd() )
      {
      label = labelIt.Get();
      mapIt = m_LabelGeometryMapper.find(label);

      value = static_cast< RealType >( it.Get() );
      index = it.GetIndex();

      // INTEGRATED PIXEL VALUE
      ( *mapIt ).second.m_Sum += value;

      for ( unsigned int i = 0; i < ImageDimension; i++ )
        {
        // FIRST ORDER WEIGHTED RAW MOMENTS
        ( *mapIt ).second.m_FirstOrderWeightedRawMoments[i] += index[i]
                                                               * ( typename LabelIndexType::IndexValueType )value;
        }

      ++it;
      ++labelIt;
      }
    }

  // If there is no intensity input defined, the oriented
  // intensity regions cannot be calculated.
  if ( !intensityImage )
    {
    if ( m_CalculateOrientedIntensityRegions )
      {
      std::cerr
      << "ERROR: An input intensity image must be used in order to calculate the oriented intensity image."
      << std::endl;
      }
    m_CalculateOrientedIntensityRegions = false;
    }

  // We need to add to the second order moment the second order
  // moment of a pixel.  This can be derived analytically.  The first
  // order moment of a pixel can be shown to be 0, and the first order
  // cross moment can also be shown to be 0.  The second order moment
  // can be shown to be 1/12.
  float pixelSecondOrderCentralMoment = 1.0f / 12.0f;

  // Now that the m_LabelGeometryMapper has been updated for all
  // pixels in the image, we can calculate other geometrical values.
  // Loop through all labels of the image.
  for ( mapIt = m_LabelGeometryMapper.begin(); mapIt != m_LabelGeometryMapper.end(); mapIt++ )
    {
    // Update the bounding box measurements.
    ( *mapIt ).second.m_BoundingBoxVolume = 1;
    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      ( *mapIt ).second.m_BoundingBoxSize[i] =
        ( *mapIt ).second.m_BoundingBox[2 * i + 1] - ( *mapIt ).second.m_BoundingBox[2 * i] + 1;
      ( *mapIt ).second.m_BoundingBoxVolume = ( *mapIt ).second.m_BoundingBoxVolume
                                              * ( *mapIt ).second.m_BoundingBoxSize[i];
      }

    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      // Normalize the centroid sum by the count to get the centroid.
      ( *mapIt ).second.m_Centroid[i] =
        static_cast< typename LabelPointType::ValueType >( ( *mapIt ).second.m_FirstOrderRawMoments[i] )
        / ( *mapIt ).second.m_ZeroOrderMoment;

      // This is the weighted sum.  It only calculates correctly if
      // the intensity image is defined.
      if ( !intensityImage )
        {
        ( *mapIt ).second.m_WeightedCentroid[i] = 0.0;
        }
      else
        {
        ( *mapIt ).second.m_WeightedCentroid[i] =
          static_cast< typename LabelPointType::ValueType >( ( *mapIt ).second.m_FirstOrderWeightedRawMoments[i] )
          / ( *mapIt ).second.m_Sum;
        }
      }

    // Using the raw moments, we can calculate the central moments.
    MatrixType normalizedSecondOrderCentralMoments(ImageDimension, ImageDimension, 0);
    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      for ( unsigned int j = 0; j < ImageDimension; j++ )
        {
        normalizedSecondOrderCentralMoments(i,
                                            j) =
          ( ( *mapIt ).second.m_SecondOrderRawMoments(i,
                                                      j) ) / ( ( *mapIt ).second.m_ZeroOrderMoment )
          - ( *mapIt ).second.m_Centroid[i]
          * ( *mapIt ).second.m_Centroid[j];
        // We need to add to the second order moment the second order
        // moment of a pixel.  This can be derived analytically.
        if ( i == j )
          {
          normalizedSecondOrderCentralMoments(i, j) += pixelSecondOrderCentralMoment;
          }
        }
      }

    // Compute the eigenvalues/eigenvectors of the covariance matrix.
    // The result is stored in increasing eigenvalues with
    // corresponding eigenvectors.
    vnl_symmetric_eigensystem< double > eig(normalizedSecondOrderCentralMoments);

    // Calculate the eigenvalues/eigenvectors
    VectorType eigenvalues(ImageDimension, 0);
    MatrixType eigenvectors(ImageDimension, ImageDimension, 0);
    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      eigenvectors.set_column( i, eig.get_eigenvector(i) );
      eigenvalues[i] = eig.get_eigenvalue(i);
      }
    ( *mapIt ).second.m_Eigenvalues = eigenvalues;
    ( *mapIt ).second.m_Eigenvectors = eigenvectors;

    itk::FixedArray< float, ImageDimension > axesLength;
    for ( unsigned int i = 0; i < ImageDimension; i++ )
      {
      axesLength[i] = 4 * std::sqrt(eigenvalues[i]);
      }
    ( *mapIt ).second.m_AxesLength = axesLength;

    // The following three features are currently only meaningful in 2D.
    ( *mapIt ).second.m_Eccentricity = std::sqrt( ( eigenvalues[ImageDimension-1] - eigenvalues[0] ) / eigenvalues[ImageDimension-1] );
    ( *mapIt ).second.m_Elongation = axesLength[ImageDimension-1] / axesLength[0];
    RealType orientation = std::atan2(eig.get_eigenvector(ImageDimension-1)[1], eig.get_eigenvector(ImageDimension-1)[0]);
    // Change the orientation from being between -pi to pi to being from 0 to pi.
    // We can add pi because the orientation of the major axis is symmetric about the origin.
    ( *mapIt ).second.m_Orientation = orientation < 0.0 ? orientation + itk::Math::pi : orientation;

    if ( m_CalculateOrientedBoundingBox == true )
      {
      // Calculate the oriented bounding box using the eigenvectors.
      CalculateOrientedBoundingBoxVertices(eig, ( *mapIt ).second);
      }
    if ( m_CalculateOrientedLabelRegions == true )
      {
      CalculateOrientedImage<TLabelImage, TIntensityImage>(
        eig, ( *mapIt ).second, true, this->GetInput() );
      }
    if ( m_CalculateOrientedIntensityRegions == true )
      {
      // If there is no intensity input defined, the oriented
      // intensity regions cannot be calculated.
      if ( this->GetIntensityInput() )
        {
        CalculateOrientedImage<TLabelImage, TIntensityImage>(
          eig, ( *mapIt ).second, false, this->GetIntensityInput() );
        }
      }

    m_AllLabels.push_back( ( *mapIt ).first );
    }
}

template< typename TLabelImage, typename TIntensityImage >
bool
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::CalculateOrientedBoundingBoxVertices(vnl_symmetric_eigensystem< double > eig, LabelGeometry & labelGeometry)
{
  // Calculate the oriented bounding box using the eigenvectors.
  // For each label, the pixels are rotated to the new coordinate
  // system defined by the eigenvectors.  The bounding boxes are
  // calculated in this space, and then they are rotated back to the
  // original coordinate system to define the oriented bounding boxes.
  // The reverse rotation is the transpose of the rotation matrix.

  // m_PixelIndices needs to have already been calculated.  This is
  // handled by the flags.

  MatrixType rotationMatrix = CalculateRotationMatrix< TLabelImage::ImageDimension >(eig);
  MatrixType inverseRotationMatrix = rotationMatrix.transpose();

  labelGeometry.m_RotationMatrix = rotationMatrix;

  // Convert the pixel locations to a vnl_matrix.
  // Subtract the centroid of the region so that the rotation will
  // be about the center of the region.
  MatrixType pixelLocations(ImageDimension, labelGeometry.m_PixelIndices.size(), 0);
  for ( unsigned int i = 0; i < labelGeometry.m_PixelIndices.size(); i++ )
    {
    for ( unsigned int j = 0; j < ImageDimension; j++ )
      {
      pixelLocations(j, i) = labelGeometry.m_PixelIndices[i][j] - labelGeometry.m_Centroid[j];
      }
    }

  // Rotate the points by the rotation matrix from the eigenvectors.
  MatrixType transformedPixelLocations = rotationMatrix * pixelLocations;

  // Find the min and max of the point locations in this new
  // coordinate system.  These values will be float, so we use a
  // BoundingBoxFloatType rather than BoundingBoxType.
  // The bounding box order is [minX,maxX,minY,maxY,...]
  BoundingBoxFloatType transformedBoundingBox;
  // Initialize the bounding box values.
  for ( unsigned int i = 0; i < ImageDimension * 2; i += 2 )
    {
    transformedBoundingBox[i] = NumericTraits< float >::max();
    transformedBoundingBox[i + 1] = NumericTraits< float >::NonpositiveMin();
    }

  for ( unsigned int column = 0; column < transformedPixelLocations.columns(); column++ )
    {
    for ( unsigned int i = 0; i < ( 2 * ImageDimension ); i += 2 )
      {
      // Update min
      if ( transformedBoundingBox[i] > transformedPixelLocations(i / 2, column) )
        {
        transformedBoundingBox[i] = transformedPixelLocations(i / 2, column);
        }
      // Update max
      if ( transformedBoundingBox[i + 1] < transformedPixelLocations(i / 2, column) )
        {
        transformedBoundingBox[i + 1] = transformedPixelLocations(i / 2, column);
        }
      }
    }

  // Add 0.5 pixel buffers on each side of the bounding box to be sure to
  // encompass the pixels and not cut through them.
  for ( unsigned int i = 0; i < ( 2 * ImageDimension ); i += 2 )
    {
    // If the index corresponds with a min, subtract 0.5.
    transformedBoundingBox[i] -= 0.5;
    // Otherwise, add 0.5.
    transformedBoundingBox[i + 1] += 0.5;
    }

  // The bounding box volume can be calculated directly from the
  // transformed bounding box.
  // Since 0.5 was already added to the border of the bounding box,
  // it is not necessary to add 1 to the range to get the correct range.
  labelGeometry.m_OrientedBoundingBoxVolume = 1;
  for ( unsigned int i = 0; i < ( 2 * ImageDimension ); i += 2 )
    {
    labelGeometry.m_OrientedBoundingBoxSize[i / 2] = transformedBoundingBox[i + 1] - transformedBoundingBox[i];
    labelGeometry.m_OrientedBoundingBoxVolume *= transformedBoundingBox[i + 1] - transformedBoundingBox[i];
    }

  // The bounding box cannot be transformed directly because an
  // oriented bounding box cannot be specified simply by the min and
  // max in each dimension.  Instead, the oriented bounding box is
  // specified by the points corresponding to the vertices of the
  // oriented bounding box.  We find these from the oriented
  // bounding box and transform them back to the original coordinate frame.
  // The order of the vertices corresponds with binary counting,
  // where min is zero and max is one.  For example, in 2D, binary
  // counting will give [0,0],[0,1],[1,0],[1,1], which corresponds
  // to [minX,minY],[minX,maxY],[maxX,minY],[maxX,maxY].
  // Loop through each dimension of the bounding box and find all of the
  // vertices.
  unsigned int numberOfVertices = 1 << ImageDimension;
  MatrixType     transformedBoundingBoxVertices(ImageDimension, numberOfVertices, 0);
  int            val;
  LabelIndexType binaryIndex;
  int            arrayIndex;
  for ( unsigned int i = 0; i < numberOfVertices; i++ )
    {
    val = i;
    for ( unsigned int j = 0; j < ImageDimension; j++ )
      {
      // This is the binary index as described above.
      binaryIndex[j] = val % 2;
      val = val / 2;
      // This is the index into the transformedBoundingBox array
      // corresponding to the binaryIndex.
      arrayIndex = binaryIndex[j] + 2 * j;
      transformedBoundingBoxVertices(j, i) = transformedBoundingBox[arrayIndex];
      }
    }

  // Transform the transformed bounding box vertices back to the
  // original coordinate system.
  MatrixType orientedBoundingBoxVertices = inverseRotationMatrix * transformedBoundingBoxVertices;

  // Add the centroid back to each of the vertices since it was
  // subtracted when the points were rotated.
  for ( unsigned int i = 0; i < orientedBoundingBoxVertices.columns(); i++ )
    {
    for ( unsigned int j = 0; j < ImageDimension; j++ )
      {
      orientedBoundingBoxVertices(j, i) += labelGeometry.m_Centroid[j];
      // Copy the oriented bounding box vertices back to a vector of
      // points for the mapper.
      labelGeometry.m_OrientedBoundingBoxVertices[i][j] = orientedBoundingBoxVertices(j, i);
      }
    }

  // Find the origin of the oriented bounding box.
  for ( unsigned int i = 0; i < ImageDimension; i++ )
    {
    labelGeometry.m_OrientedBoundingBoxOrigin[i] = transformedBoundingBox[2 * i] + labelGeometry.m_Centroid[i];
    }

  return true;
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelIndicesType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetPixelIndices(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelIndicesType emptyVector;
    emptyVector.clear();
    return emptyVector;
    }
  else
    {
    return ( *mapIt ).second.m_PixelIndices;
    }
}

template< typename TLabelImage, typename TIntensityImage >
SizeValueType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetVolume(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return 0;
    }
  else
    {
    return ( *mapIt ).second.m_ZeroOrderMoment;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetIntegratedIntensity(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_Sum;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelPointType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetCentroid(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelPointType emptyCentroid;
    emptyCentroid.Fill(NumericTraits< typename LabelPointType::ValueType >::ZeroValue());
    return emptyCentroid;
    }
  else
    {
    return ( *mapIt ).second.m_Centroid;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelPointType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetWeightedCentroid(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelPointType emptyCentroid;
    emptyCentroid.Fill(NumericTraits< typename LabelPointType::ValueType >::ZeroValue());
    return emptyCentroid;
    }
  else
    {
    return ( *mapIt ).second.m_WeightedCentroid;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::VectorType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetEigenvalues(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    VectorType emptyVector(ImageDimension, 0);
    return emptyVector;
    }
  else
    {
    return ( *mapIt ).second.m_Eigenvalues;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::MatrixType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetEigenvectors(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    MatrixType emptyMatrix(ImageDimension, ImageDimension, 0);
    return emptyMatrix;
    }
  else
    {
    return ( *mapIt ).second.m_Eigenvectors;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::AxesLengthType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetAxesLength(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelPointType emptyAxesLength;
    emptyAxesLength.Fill(NumericTraits< typename AxesLengthType::ValueType >::ZeroValue());
    return emptyAxesLength;
    }
  else
    {
    return ( *mapIt ).second.m_AxesLength;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetMinorAxisLength(LabelPixelType label) const
{
  AxesLengthType axisLength = GetAxesLength(label);

  return axisLength[0];
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetMajorAxisLength(LabelPixelType label) const
{
  AxesLengthType axisLength = GetAxesLength(label);

  return axisLength[ImageDimension - 1];
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetEccentricity(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_Eccentricity;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetElongation(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_Elongation;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientation(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_Orientation;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::BoundingBoxType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetBoundingBox(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    BoundingBoxType emptyBox;
    emptyBox.Fill(NumericTraits< typename BoundingBoxType::ValueType >::ZeroValue());
    // label does not exist, return a default value
    return emptyBox;
    }
  else
    {
    return ( *mapIt ).second.m_BoundingBox;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetBoundingBoxVolume(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_BoundingBoxVolume;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelSizeType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetBoundingBoxSize(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelSizeType emptySize;
    emptySize.Fill(NumericTraits< typename LabelSizeType::SizeValueType >::ZeroValue());
    return emptySize;
    }
  else
    {
    return ( *mapIt ).second.m_BoundingBoxSize;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::BoundingBoxVerticesType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedBoundingBoxVertices(LabelPixelType label) const
{
  unsigned int numberOfVertices = 1 << ImageDimension;
  MapConstIterator mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelPointType emptyPoint;
    emptyPoint.Fill(0);
    BoundingBoxVerticesType emptyVertices;
    emptyVertices.resize(numberOfVertices, emptyPoint);
    return emptyVertices;
    }
  else
    {
    return ( *mapIt ).second.m_OrientedBoundingBoxVertices;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RealType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedBoundingBoxVolume(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return NumericTraits< RealType >::ZeroValue();
    }
  else
    {
    return ( *mapIt ).second.m_OrientedBoundingBoxVolume;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelPointType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedBoundingBoxSize(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
//     LabelSizeType emptySize;
//     emptySize.Fill( NumericTraits<LabelSizeType::SizeValueType>::ZeroValue());
//     return emptySize;
    LabelPointType emptySize;
    emptySize.Fill(NumericTraits< typename LabelPointType::ValueType >::ZeroValue());
    return emptySize;
    }
  else
    {
    return ( *mapIt ).second.m_OrientedBoundingBoxSize;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::LabelPointType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedBoundingBoxOrigin(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    LabelPointType emptySize;
    emptySize.Fill(NumericTraits< typename LabelPointType::ValueType >::ZeroValue());
    return emptySize;
    }
  else
    {
    return ( *mapIt ).second.m_OrientedBoundingBoxOrigin;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::MatrixType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetRotationMatrix(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    MatrixType emptyMatrix(ImageDimension, ImageDimension, 0);
    return emptyMatrix;
    }
  else
    {
    return ( *mapIt ).second.m_RotationMatrix;
    }
}

template< typename TLabelImage, typename TIntensityImage >
typename LabelGeometryImageFilter< TLabelImage, TIntensityImage >::RegionType
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetRegion(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);

  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    RegionType emptyRegion;
    // label does not exist, return a default value
    return emptyRegion;
    }
  else
    {
    BoundingBoxType bbox = this->GetBoundingBox(label);
    IndexType       index;
    SizeType        size;

    unsigned int dimension = bbox.Size() / 2;

    for ( unsigned int i = 0; i < dimension; i++ )
      {
      index[i] = bbox[2 * i];
      size[i] = bbox[2 * i + 1] - bbox[2 * i] + 1;
      }
    RegionType region;
    region.SetSize(size);
    region.SetIndex(index);

    return region;
    }
}

template< typename TLabelImage, typename TIntensityImage >
TLabelImage *
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedLabelImage(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return nullptr;
    }
  else
    {
    return ( *mapIt ).second.m_OrientedLabelImage;
    }
}

template< typename TLabelImage, typename TIntensityImage >
TIntensityImage *
LabelGeometryImageFilter< TLabelImage, TIntensityImage >
::GetOrientedIntensityImage(LabelPixelType label) const
{
  MapConstIterator mapIt;

  mapIt = m_LabelGeometryMapper.find(label);
  if ( mapIt == m_LabelGeometryMapper.end() )
    {
    // label does not exist, return a default value
    return nullptr;
    }
  else
    {
    return ( *mapIt ).second.m_OrientedIntensityImage;
    }
}

template< typename TImage, typename TLabelImage >
void
LabelGeometryImageFilter< TImage, TLabelImage >
::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "Number of labels: " << m_LabelGeometryMapper.size()
     << std::endl;

  MapConstIterator mapIt;
  for ( mapIt = m_LabelGeometryMapper.begin(); mapIt != m_LabelGeometryMapper.end(); mapIt++ )
    {
    using LabelPrintType = typename NumericTraits< LabelPixelType >::PrintType;
    os << indent << "Label[" << (LabelPrintType)( ( *mapIt ).second.m_Label ) << "]: ";
    os << "\t Volume: " << ( *mapIt ).second.m_ZeroOrderMoment;
    os << "\t Integrated Intensity: " << ( *mapIt ).second.m_Sum;
    os << "\t Centroid: " << ( *mapIt ).second.m_Centroid;
    os << "\t Weighted Centroid: " << ( *mapIt ).second.m_WeightedCentroid;
    os << "\t Axes Length: " << ( *mapIt ).second.m_AxesLength;
    os << "\t Eccentricity: " << ( *mapIt ).second.m_Eccentricity;
    os << "\t Elongation: " << ( *mapIt ).second.m_Elongation;
    os << "\t Orientation: " << ( *mapIt ).second.m_Orientation;
    os << "\t Bounding box: " << ( *mapIt ).second.m_BoundingBox;
    os << "\t Bounding box volume: " << ( *mapIt ).second.m_BoundingBoxVolume;
    os << "\t Bounding box size: " << ( *mapIt ).second.m_BoundingBoxSize;
    // Oriented bounding box verticies
    os << "\t Oriented bounding box volume: " << ( *mapIt ).second.m_OrientedBoundingBoxVolume;
    os << "\t Oriented bounding box size: " << ( *mapIt ).second.m_OrientedBoundingBoxSize;
    // Rotation matrix
    os << std::endl;
    os << "\t Calculate oriented intensity regions: " << m_CalculateOrientedIntensityRegions;
    os << "\t Calculate pixel indices: " << m_CalculatePixelIndices;
    os << "\t Calculate oriented bounding box: " << m_CalculateOrientedBoundingBox;
    os << "\t Calculate oriented label regions: " << m_CalculateOrientedLabelRegions;
    os << "\n\n";
    }
}
} // end namespace itk
#endif
