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

#include "itkNormalVectorFunctionBase.h"
#include "itkNumericTraits.h"
#include <cmath>

namespace itk
{
/**
 * \class NormalVectorDiffusionFunction
 *
 * \brief This class defines all the necessary functionality for performing
 * isotropic and anisotropic diffusion operations on vector neighborhoods from
 * a sparse image.
 *
 * \par
 * This class implements the actual computations for performing isotropic and
 * anisotropic diffusion operations on a neighborhood of unit length
 * vectors. Moreover, this processing is intrinsic to a manifold as specified
 * by the ManifoldNormal member variables in the nodes of the sparse image.
 *
 * \par
 * Since the only difference between isotropic and anisotropic diffusion is the
 * exectution of 1 extra line of code, we have implemented both in this class
 * and made the choice between the two depend on a parameter (see below).

 * \par PARAMETERS
 * The choice between is isotropic/anisotropic diffusion is made by the
 * parameter NormalProcessType. A value of 0 corresponds to isotropic diffusion
 * whereas a value of 1 corresponds to anisotropic diffusion. If anisotropic
 * diffusion is chosen, the parameter ConductanceParameter should be set. This
 * conductance parameter determines the level of feature preservation.
 *
 * \par IMPORTANT
 * This class works on SparseImage neighborhoods. Before using this class
 * please read the documentation for SparseImage. Also the documentation for
 * ImplicitManifoldNormalVectorField class will be helpful in understanding how
 * to use this class as a function object.
 * \ingroup ITKLevelSets
 */
template< typename TSparseImageType >
class ITK_TEMPLATE_EXPORT NormalVectorDiffusionFunction:
  public NormalVectorFunctionBase< TSparseImageType >
{
public:
  ITK_DISALLOW_COPY_AND_ASSIGN(NormalVectorDiffusionFunction);

  /** Standard class type alias. */
  using Self = NormalVectorDiffusionFunction;
  using Superclass = NormalVectorFunctionBase< TSparseImageType >;
  using Pointer = SmartPointer< Self >;
  using ConstPointer = SmartPointer< const Self >;

  /** Run-time type information (and related methods) */
  itkTypeMacro(NormalVectorDiffusionFunction, NormalVectorFunctionBase);

  /** Image dimension derived from the superclass. */
  static constexpr unsigned int ImageDimension = Superclass::ImageDimension;

  /** Standard New macro. */
  itkNewMacro(Self);

  /** Typedefs from the superclass. */
  using TimeStepType = typename Superclass::TimeStepType;
  using RadiusType = typename Superclass::RadiusType;
  using NeighborhoodType = typename Superclass::NeighborhoodType;
  using NeighborhoodScalesType = typename Superclass::NeighborhoodScalesType;
  using FloatOffsetType = typename Superclass::FloatOffsetType;
  using IndexType = typename Superclass::IndexType;
  using SparseImageType = typename Superclass::SparseImageType;
  using NodeType = typename Superclass::NodeType;
  using NodeValueType = typename Superclass::NodeValueType;
  using NormalVectorType = typename Superclass::NormalVectorType;

  /** This method is used to choose between isotropic/anisotropic filtering. A
      parameter value of 0 indicates isotropic diffusion and is the
      default. Parameter value 1 is anisotropic diffusion. When using
      anisotropic diffusion the conductance parameter should also be set. */
  void SetNormalProcessType(int npt)
  { m_NormalProcessType = npt; }

  /** This method returns the isotropic/anisotropic filtering parameter. */
  int GetNormalProcessType() const
  { return m_NormalProcessType; }

  /** This method sets the conductance parameter used in anisotropic
   * filtering. Useful values for processing 2D and 3D shapes are between
   *  0.1 and 0.25. Lower values preserve more shape features, higher values
   *  smooth more. As the conductance parameter large, the processing becomes
   *  isotropic. Default is 0. */
  void SetConductanceParameter(NodeValueType cp)
  {
    m_ConductanceParameter = cp + static_cast< NodeValueType >( 0.001 );
    // we add a minimum conductance to avoid divide by zero
    // can make this a parameter.
    m_FluxStopConstant = static_cast< NodeValueType >
                         ( -1.0 / ( m_ConductanceParameter * m_ConductanceParameter ) );
  }

  /** This method returns the conductance parameter. */
  NodeValueType GetConductanceParameter() const
  { return m_ConductanceParameter; }

  /** This method returns the internal variable FluxStopConstant. */
  NodeValueType GetFluxStopConstant() const
  { return m_FluxStopConstant; }

  /** This function is called from LevelSetNormalImageFilter for all of the
   *  nodes to compute and store the flux vectors (first derivatives of the
   *  normal vectors. ComputeUpdateNormal then takes derivatives of the flux
   *  vectors. This way we avoid repeating the same flux computations. */
  void PrecomputeSparseUpdate(NeighborhoodType & it) const override;

  /** The actual update rule for the normal vectors. */
  NormalVectorType ComputeSparseUpdate(NeighborhoodType & neighborhood,
                                               void *globalData,
                                               const FloatOffsetType & offset) const override;

protected:
  NormalVectorDiffusionFunction();
  ~NormalVectorDiffusionFunction() override = default;
  void PrintSelf(std::ostream & os, Indent indent) const override;

  /** The method called in anisotropic diffusion to inhibit diffusion across
      areas with large curvature. */
  NodeValueType FluxStopFunction(const NodeValueType v) const
  {
    // the slow exp function could be replaced with a lookup table
    if ( v <= 0.0 ) { return NumericTraits< NodeValueType >::OneValue(); }
    else { return static_cast< NodeValueType >( std::exp(m_FluxStopConstant * v) ); }
  }

private:
  /** The conductance parameter used for anisotropic diffusion. */
  NodeValueType m_ConductanceParameter;

  /** The internal variable used in the FluxStopFunction. It is computed from
   * ConductanceParameter. */
  NodeValueType m_FluxStopConstant;

  /** The isotropic/anisotropic filtering choice parameter. */
  int m_NormalProcessType;

};
} // end namespace itk

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

#endif
