/*=========================================================================
 *
 *  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 itkRelabelComponentImageFilter_h
#define itkRelabelComponentImageFilter_h

#include "itkInPlaceImageFilter.h"
#include "itkImage.h"
#include <vector>

namespace itk
{
/**
 * \class RelabelComponentImageFilter
 * \brief Relabel the components in an image such that consecutive labels are used.
 *
 * RelabelComponentImageFilter remaps the labels associated with the
 * objects in an image (as from the output of
 * ConnectedComponentImageFilter) such that the label numbers are
 * consecutive with no gaps between the label numbers used. By
 * default, the relabeling will also sort the labels based on the size
 * of the object: the largest object will have label #1, the second
 * largest will have label #2, etc. If two labels have the same size
 * their initial order is kept. The sorting by size can be disabled using
 * SetSortByObjectSize.
 *
 * Label #0 is assumed to be the background and is left unaltered by the
 * relabeling.
 *
 * RelabelComponentImageFilter is typically used on the output of the
 * ConnectedComponentImageFilter for those applications that want to
 * extract the largest object or the "k" largest objects. Any
 * particular object can be extracted from the relabeled output using
 * a BinaryThresholdImageFilter. A group of objects can be extracted
 * from the relabled output using a ThresholdImageFilter.
 *
 * Once all the objects are relabeled, the application can query the
 * number of objects and the size of each object. Object sizes are
 * returned in a vector. The size of the background is not
 * calculated. So the size of object #1 is
 * GetSizeOfObjectsInPixels()[0], the size of object #2 is
 * GetSizeOfObjectsInPixels()[1], etc.
 *
 * If user sets a minimum object size, all objects with fewer pixels
 * than the minimum will be discarded, so that the number of objects
 * reported will be only those remaining. The
 * GetOriginalNumberOfObjects method can be called to find out how
 * many objects were present before the small ones were discarded.
 *
 * RelabelComponentImageFilter can be run as an "in place" filter,
 * where it will overwrite its output. The default is run out of
 * place (or generate a separate output). "In place" operation can be
 * controlled via methods in the superclass,
 * InPlaceImageFilter::InPlaceOn() and InPlaceImageFilter::InPlaceOff().
 *
 * \sa ConnectedComponentImageFilter, BinaryThresholdImageFilter, ThresholdImageFilter
 *
 * \ingroup SingleThreaded
 * \ingroup ITKConnectedComponents
 *
 * \wiki
 * \wikiexample{ImageProcessing/RelabelComponentImageFilter,Assign contiguous labels to connected regions of an image}
 * \endwiki
 */

template< typename TInputImage, typename TOutputImage >
class ITK_TEMPLATE_EXPORT RelabelComponentImageFilter:
  public InPlaceImageFilter< TInputImage, TOutputImage >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(RelabelComponentImageFilter);

  /**
   * Standard "Self" & Superclass typedef.
   */
  using Self = RelabelComponentImageFilter;
  using Superclass = InPlaceImageFilter< TInputImage, TOutputImage >;

  /**
   * Types from the Superclass
   */
  using InputImagePointer = typename Superclass::InputImagePointer;

  /**
   * Extract some information from the image types.  Dimensionality
   * of the two images is assumed to be the same.
   */
  using OutputPixelType = typename TOutputImage::PixelType;
  using OutputInternalPixelType = typename TOutputImage::InternalPixelType;
  using InputPixelType = typename TInputImage::PixelType;
  using InputInternalPixelType = typename TInputImage::InternalPixelType;
  static constexpr unsigned int ImageDimension = TOutputImage::ImageDimension;
  static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension;

  /**
   * Image type alias support
   */
  using InputImageType = TInputImage;
  using OutputImageType = TOutputImage;
  using IndexType = typename TInputImage::IndexType;
  using SizeType = typename TInputImage::SizeType;
  using RegionType = typename TOutputImage::RegionType;

  /**
   * Smart pointer type alias support
   */
  using Pointer = SmartPointer< Self >;
  using ConstPointer = SmartPointer< const Self >;

  /**
   * Run-time type information (and related methods)
   */
  itkTypeMacro(RelabelComponentImageFilter, ImageToImageFilter);

  /**
   * Method for creation through the object factory.
   */
  itkNewMacro(Self);

  /** Type used as identifier for the different component lables. */
  using LabelType = IdentifierType;

  /** Type used to count number of pixels in objects. */
  using ObjectSizeType = SizeValueType;

  /** Get the number of objects in the image. This information is only
   * valid after the filter has executed. */
  itkGetConstMacro(NumberOfObjects, LabelType);

  using ObjectSizeInPixelsContainerType = std::vector< ObjectSizeType >;
  using ObjectSizeInPhysicalUnitsContainerType = std::vector< float >;

  /** Get the original number of objects in the image before small
   * objects were discarded. This information is only valid after
   * the filter has executed. If the caller has not specified a
   * minimum object size, OriginalNumberOfObjects is the same as
   * NumberOfObjects. */
  itkGetConstMacro(OriginalNumberOfObjects, LabelType);

  /** Get/Set the number of objects enumerated and described when the
   * filter is printed. */
  itkSetMacro(NumberOfObjectsToPrint, LabelType);
  itkGetConstReferenceMacro(NumberOfObjectsToPrint, LabelType);

  /** Set the minimum size in pixels for an object. All objects
   * smaller than this size will be discarded and will not appear
   * in the output label map. NumberOfObjects will count only the
   * objects whose pixel counts are greater than or equal to the
   * minimum size. Call GetOriginalNumberOfObjects to find out how
   * many objects were present in the original label map. */
  itkSetMacro(MinimumObjectSize, ObjectSizeType);

  /** Get the caller-defined minimum size of an object in pixels.
   * If the caller has not set the minimum, 0 will be returned,
   * which is to be interpreted as meaning that no minimum exists,
   * and all objects in the original label map will be passed
   * through to the output. */
  itkGetConstMacro(MinimumObjectSize, ObjectSizeType);

  /** Controls whether the object labels are sorted by size.
  * If false, initial order of labels is kept. */
  itkSetMacro(SortByObjectSize, bool);
  itkGetConstMacro(SortByObjectSize, bool);
  itkBooleanMacro(SortByObjectSize);

  /** Get the size of each object in pixels. This information is only
   * valid after the filter has executed.  Size of the background is
   * not calculated.  Size of object #1 is
   * GetSizeOfObjectsInPixels()[0]. Size of object #2 is
   * GetSizeOfObjectsInPixels()[1]. Etc. */
  const ObjectSizeInPixelsContainerType & GetSizeOfObjectsInPixels() const
    {
    // The GetConstReferenceMacro can't be used here becase this container
    // doesn't have an ostream<< operator overloaded.
    return this->m_SizeOfObjectsInPixels;
    }

  /** Get the size of each object in physical space (in units of pixel
   * size). This information is only valid after the filter has
   * executed. Size of the background is not calculated.  Size of
   * object #1 is GetSizeOfObjectsInPhysicalUnits()[0]. Size of object
   * #2 is GetSizeOfObjectsInPhysicalUnits()[1]. Etc. */
  const ObjectSizeInPhysicalUnitsContainerType & GetSizeOfObjectsInPhysicalUnits() const
    {
    // The GetConstReferenceMacro can't be used here becase this container
    // doesn't have an ostream<< operator overloaded.
    return this->m_SizeOfObjectsInPhysicalUnits;
    }

  /** Get the size of a particular object in pixels. This information is only
   * valid after the filter has executed.  Size of the background
   * (object #0) is not calculated.  */
  ObjectSizeType GetSizeOfObjectInPixels(LabelType obj) const
  {
    if ( obj > 0 && obj <= m_NumberOfObjects )
      {
      return m_SizeOfObjectsInPixels[obj - 1];
      }
    else
      {
      return 0;
      }
  }

  /** Get the size of a particular object in physical space (in units of pixel
   * size). This information is only valid after the filter has
   * executed. Size of the background (object #0) is not calculated.  */
  float GetSizeOfObjectInPhysicalUnits(LabelType obj) const
  {
    if ( obj > 0 && obj <= m_NumberOfObjects )
      {
      return m_SizeOfObjectsInPhysicalUnits[obj - 1];
      }
    else
      {
      return 0;
      }
  }

#ifdef ITK_USE_CONCEPT_CHECKING
  // Begin concept checking
  itkConceptMacro( InputEqualityComparableCheck,
                   ( Concept::EqualityComparable< InputPixelType > ) );
  itkConceptMacro( UnsignedLongConvertibleToInputCheck,
                   ( Concept::Convertible< LabelType, InputPixelType > ) );
  itkConceptMacro( OutputLongConvertibleToUnsignedLongCheck,
                   ( Concept::Convertible< OutputPixelType, LabelType > ) );
  itkConceptMacro( InputConvertibleToOutputCheck,
                   ( Concept::Convertible< InputPixelType, OutputPixelType > ) );
  itkConceptMacro( SameDimensionCheck,
                   ( Concept::SameDimension< InputImageDimension, ImageDimension > ) );
  // End concept checking
#endif

protected:

  RelabelComponentImageFilter()

  { this->InPlaceOff(); }
  ~RelabelComponentImageFilter() override = default;

  /**
   * Standard pipeline method.
   */
  void GenerateData() override;

  /** RelabelComponentImageFilter needs the entire input. Therefore
   * it must provide an implementation GenerateInputRequestedRegion().
   * \sa ProcessObject::GenerateInputRequestedRegion(). */
  void GenerateInputRequestedRegion() override;

  /** Standard printself method */
  void PrintSelf(std::ostream & os, Indent indent) const override;

  struct RelabelComponentObjectType {
    LabelType m_ObjectNumber;
    ObjectSizeType m_SizeInPixels;
    float m_SizeInPhysicalUnits;
  };

  // put the function objects here for sorting in descending order
  class RelabelComponentSizeInPixelsComparator
  {
  public:
    bool operator()(const RelabelComponentObjectType & a,
                    const RelabelComponentObjectType & b)
    {
      if ( a.m_SizeInPixels > b.m_SizeInPixels )
        {
        return true;
        }
      else if ( a.m_SizeInPixels < b.m_SizeInPixels )
        {
        return false;
        }
      // size in pixels and physical units are the same, sort based on
      // original object number
      else if ( a.m_ObjectNumber < b.m_ObjectNumber )
        {
        return true;
        }
      else
        {
        return false;
        }
    }
  };

private:

  LabelType      m_NumberOfObjects{0};
  LabelType      m_NumberOfObjectsToPrint{10};
  LabelType      m_OriginalNumberOfObjects{0};
  ObjectSizeType m_MinimumObjectSize{0};
  bool           m_SortByObjectSize{true};

  ObjectSizeInPixelsContainerType         m_SizeOfObjectsInPixels;
  ObjectSizeInPhysicalUnitsContainerType  m_SizeOfObjectsInPhysicalUnits;
};
} // end namespace itk

#ifndef ITK_MANUAL_INSTANTIATION
#include "itkRelabelComponentImageFilter.hxx"
#endif

#endif
