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

#include "itkImageToImageFilter.h"
#include "itkSmoothingRecursiveGaussianImageFilter.h"

namespace itk
{
/**
 * \class UnsharpMaskImageFilter
 * \brief Edge enhancement filter.
 *
 * This filter subtracts a smoothed version of the image from the image
 * to achieve the edge enhancing effect.
 * https://en.wikipedia.org/w/index.php?title=Unsharp_masking&oldid=750486803#Photographic_unsharp_masking
 *
 * It has configurable amount, radius (sigma) and threshold,
 * and whether to clamp the resulting values to the range of output type.
 *
 * Formula: sharpened=original+[abs(original-blurred)-threshold]*amount
 *
 * If clamping is turned off (it is on by default),
 * casting to output pixel format is done using C++ defaults,
 * meaning that values are not clamped but rather wrap around
 * e.g. 260 -> 4 (unsigned char).
 *
 * \sa ImageToImageFilter
 * \sa SmoothingRecursiveGaussianImageFilter
 * \sa RescaleIntensityImageFilter
 *
 * \ingroup ImageFeatureExtraction
 *
 * \ingroup ITKImageFeature
 *
 */

template< typename TInputImage, typename TOutputImage = TInputImage, typename TInternalPrecision = float >
class ITK_TEMPLATE_EXPORT UnsharpMaskImageFilter:
  public ImageToImageFilter< TInputImage, TOutputImage >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(UnsharpMaskImageFilter);

  /**
   * Standard "Self" & Superclass type alias.
   */
  using Self = UnsharpMaskImageFilter;
  using Superclass = ImageToImageFilter< TInputImage, TOutputImage >;

  /**
   * Extract some information from the image types.
   */
  using OutputPixelType = typename TOutputImage::PixelType;
  using OutputInternalPixelType = typename TOutputImage::InternalPixelType;
  using InputPixelType = typename TInputImage::PixelType;
  using InputInternalPixelType = typename TInputImage::InternalPixelType;
  using OutputImageRegionType = typename TOutputImage::RegionType;
  using InputImageRegionType = typename TInputImage::RegionType;
  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 InputImagePointer = typename InputImageType::Pointer;

  using InternalPrecisionType = TInternalPrecision;

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

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

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

#ifdef ITK_USE_CONCEPT_CHECKING
  itkConceptMacro( SameDimensionCheck,
    ( Concept::SameDimension< InputImageDimension, ImageDimension > ) );
  itkConceptMacro( InputHasNumericTraitsCheck,
    ( Concept::HasNumericTraits< OutputPixelType > ) );
  itkConceptMacro( InternalTypeIsFloatingPoint,
    ( Concept::IsFloatingPoint< TInternalPrecision > ) );
#endif

  using GaussianType = SmoothingRecursiveGaussianImageFilter<
    TInputImage, Image<TInternalPrecision, TOutputImage::ImageDimension> >;

  using SigmaArrayType = typename GaussianType::SigmaArrayType;

  /** Set/Get Sigma values measured in the units of image spacing. Default: 1.0. */
  itkSetMacro(Sigmas, SigmaArrayType);
  itkGetConstMacro(Sigmas, SigmaArrayType);

  /** Convenience method for setting all dimensional parameters
  * to the same values. */
  void SetSigma(const typename SigmaArrayType::ValueType sigma)
  {
    SigmaArrayType sigmas;
    sigmas.Fill(sigma);
    this->SetSigmas(sigmas); //checks whether it is actually modified
  }

  /** Set/Get amount of enhancement. Usual range: 0.1 to 2.0. Default: 0.5. */
  itkSetMacro(Amount, TInternalPrecision);
  itkGetConstMacro(Amount, TInternalPrecision);


  /** Set/Get threshold for enhancement. Default: 0. */
  itkSetMacro(Threshold, TInternalPrecision);
  itkGetConstMacro(Threshold, TInternalPrecision);

  /** Set/Get whether to clamp values to supported
  * range of output type. Default: On. */
  itkSetMacro(Clamp, bool);
  itkGetConstMacro(Clamp, bool);
  itkBooleanMacro(Clamp);

protected:
  UnsharpMaskImageFilter();
  ~UnsharpMaskImageFilter() override = default;

  /**
  * UnsharpMaskImageFilter needs a larger input requested region than
  * the output requested region (larger by the size of the
  * Gaussian kernel).  As such, UnsharpMaskImageFilter needs to
  * provide an implementation for GenerateInputRequestedRegion() in
  * order to inform the pipeline execution model.
  * \sa ImageToImageFilter::GenerateInputRequestedRegion() */
  void GenerateInputRequestedRegion() override;

  void VerifyPreconditions() ITKv5_CONST override;
  void GenerateData() override;

  void PrintSelf(std::ostream & os, Indent indent) const override;

private:
  /** The edge amplification amount */
  TInternalPrecision m_Amount;
  TInternalPrecision m_Threshold;
  SigmaArrayType     m_Sigmas;
  bool               m_Clamp;

  template<typename InPixelType, typename FunctorRealType = TInternalPrecision, typename OutPixelType = InPixelType>
  class UnsharpMaskingFunctor
  {
  private:
    FunctorRealType m_Amount;
    FunctorRealType m_Threshold;
    bool m_Clamp{false};

  public:
    UnsharpMaskingFunctor()
      : m_Amount(0.5),
      m_Threshold(0.0)

    {
    }

    UnsharpMaskingFunctor(FunctorRealType amount, FunctorRealType threshold, bool clamp)
      : m_Amount(amount),
      m_Threshold(threshold),
      m_Clamp(clamp)
    {
      assert(m_Threshold >= 0.0);
    }

    bool operator==(const UnsharpMaskingFunctor & other)
    {
      return (m_Amount == other.m_Amount)
             && (m_Threshold == other.m_Threshold)
             && (m_Clamp == other.m_Clamp);
    }

    bool operator!= (const UnsharpMaskingFunctor & other)
    {
      return !(*this == other);
    }

    inline OutPixelType operator()(const InPixelType & v, const FunctorRealType & s) const
    {
      FunctorRealType diff = v - s;
      FunctorRealType result;
      if (diff > m_Threshold)
        {
        result = v + (diff - m_Threshold)*m_Amount;
        }
      else if (-diff > m_Threshold)
        {
        result = v + (diff + m_Threshold)*m_Amount;
        }
      else
        {
        result = v;
        }

      if (m_Clamp)
        {
        if (result < itk::NumericTraits< OutPixelType >::NonpositiveMin())
          {
          return itk::NumericTraits< OutPixelType >::NonpositiveMin();
          }
        else if (result > itk::NumericTraits< OutPixelType >::max())
          {
          return itk::NumericTraits< OutPixelType >::max();
          }
        }

      return static_cast<OutPixelType>(result);
    }
  }; //end UnsharpMaskingFunctor
}; //end UnsharpMaskImageFilter
} // end namespace itk

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

#endif
