/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * 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
 *
 * 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.
 */

#include "otbWrapperApplication.h"
#include "otbWrapperApplicationFactory.h"

#include "otbMeanShiftSmoothingImageFilter.h"

namespace otb
{
namespace Wrapper
{


class MeanShiftSmoothing : public Application
{
public:
  /** Standard class typedefs. */
  typedef MeanShiftSmoothing         Self;
  typedef Application                   Superclass;
  typedef itk::SmartPointer<Self>       Pointer;
  typedef itk::SmartPointer<const Self> ConstPointer;

  typedef otb::MeanShiftSmoothingImageFilter<FloatVectorImageType, FloatVectorImageType> MSFilterType;

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

  itkTypeMacro(MeanShiftSmoothing, otb::Application);

private:
  void DoInit() override
  {
    SetName("MeanShiftSmoothing");
    SetDescription("This application smooths an image using the MeanShift algorithm.");

    // Documentation
    SetDocName("MeanShift Smoothing");
    SetDocLongDescription("MeanShift [1,2,3] is an iterative edge-preserving image smoothing"
                          " algorithm often used in image processing and as a first step for"
                          " image segmentation. The MeanShift algorithm can be applied to"
                          " multispectral images.\n\n"
                          "At first iteration, for any given pixel of the input image, the"
                          " filtered value correspond to the average spectral signature of"
                          " neighborhood pixels that are both spatially closer than the spatial"
                          " radius parameter (spatialr) and with spectral signature that have an euclidean"
                          " distance to the input pixel lower than the range radius (ranger), that is,"
                          " pixels that are both close in space and in spectral signatures."
                          " Subsequent iterations will repeat this process by considering that"
                          " the pixel signature corresponds to the average spectral signature"
                          " computed during previous iteration, and that the pixel position"
                          " corresponds to the average position of pixels used to compute the"
                          " average signature."
                          "The algorithm stops when the maximum number of iterations (maxiter) is reached,"
                          " or when the position and spectral signature does not change much"
                          " between iterations, according to the convergence threshold (thres). If the"
                          " modesearch option is used then convergence will also stops if the"
                          " spatial position reaches a pixel that has already converged. This"
                          " will speed-up convergence, at the expense of stability of the result.\n\n"

                          "The application outputs the image of the final averaged spectral"
                          " signatures (fout), and can also optionally output the 2D"
                          " displacement field between input pixel position and final pixel"
                          " position after convergence (foutpos).\n\n"

                          "Note that computing an euclidean distance between spectral signatures"
                          " may be inaccurate and that techniques such as color space transform or image"
                          " normalisation could be applied before using this application. Also"
                          " note that most satellite images noise model is not gaussian, since"
                          " noise variance linearly depends on radiance (the higher the"
                          " radiance, the higher the noise variance). To account for such noise"
                          " model, the application provides the range radius ramp option"
                          " (rangeramp), which will vary the range radius linearly with the"
                          " central pixel intensity. Default value is 1. (no ramp).\n\n"

                          "This application is the first step of the large scale MeanShift method"
                          " depicted in [4]. Both outputs (fout and foutpos) can be passed to the"
                          " large scale MeanShift segmentation application [5]. If the"
                          " application is used for large scale MeanShift, modesearch option should be off.");

    SetDocLimitations("When modesearch is on, the application will yield slightly different"
                      " results between executions, due to multi-threading. Results will also"
                      " not be stable [4].");
    
    SetDocAuthors("OTB-Team");
    SetDocSeeAlso("[1] Comaniciu, D., & Meer, P. (2002). Mean shift: A robust approach"
                  " toward feature space analysis. IEEE Transactions on pattern analysis"
                  " and machine intelligence, 24(5), 603-619.\n"
                  "[2] Comaniciu, D., & Meer, P. (1997, June). Robust analysis of feature"
                  " spaces: color image segmentation. In Computer Vision and Pattern"
                  " Recognition, 1997. Proceedings., 1997 IEEE Computer Society Conference"
                  " on (pp. 750-755). IEEE.\n"
                  "[3] Comaniciu, D., & Meer, P. (1999). Mean shift analysis and"
                  " applications. In Computer Vision, 1999. The Proceedings of the Seventh"
                  " IEEE International Conference on (Vol. 2, pp. 1197-1203). IEEE.\n"
                  "[4] Michel, J., Youssefi, D., & Grizonnet, M. (2015). Stable"
                  " mean-shift algorithm and its application to the segmentation of"
                  " arbitrarily large remote sensing images. IEEE Transactions on"
                  " Geoscience and Remote Sensing, 53(2), 952-964.\n"
                  "[5] LSMSSegmentation application");

    AddDocTag(Tags::Filter);
    AddDocTag("LSMS");

    AddParameter(ParameterType_InputImage,   "in",     "Input Image");
    SetParameterDescription( "in", "The input image can be any single or multiband image. Beware of pontential imbalance between bands ranges as it may alter euclidean distance." );

    AddParameter(ParameterType_OutputImage,  "fout",    "Spectral filtered output");
    SetParameterDescription( "fout", "This output image contains the final average spectral signatures of each pixel. The output type should be at least as wide as the input image type. Floating point encoding is advised. This output can be used as input image (in) of the LSMSSegmentation application [4,5]."  );

    AddParameter(ParameterType_OutputImage,  "foutpos",    "Spatial filtered displacement output");
    SetParameterDescription( "foutpos", " This output image contains the 2D displacement between the input pixel spatial position and the final position after convergence. Floating point encoding is mandatory. This output can be used as input image (in) of the LSMSSegmentation application [4,5].");
    MandatoryOff("foutpos");

    AddRAMParameter();

    AddParameter(ParameterType_Int, "spatialr", "Spatial radius");
    SetParameterDescription("spatialr", "Radius of the spatial neighborhood for averaging. Higher values will result in more smoothing and higher processing time.");
    SetDefaultParameterInt("spatialr", 5);
    MandatoryOff("spatialr");

    AddParameter(ParameterType_Float, "ranger", "Range radius");
    SetParameterDescription("ranger", "Threshold on spectral signature euclidean distance (expressed in radiometry unit) to consider neighborhood pixel for averaging. Higher values will be less edge-preserving (more similar to simple average in neighborhood), whereas lower values will result in less noise smoothing. Note that this parameter has no effect on processing time.");
    SetDefaultParameterFloat("ranger", 15.0);
    MandatoryOff("ranger");

    AddParameter(ParameterType_Float, "thres", "Mode convergence threshold");
    SetParameterDescription("thres", "Algorithm will stop if update of average spectral signature and spatial position is below this threshold.");
    SetMinimumParameterFloatValue("thres", 0.0);
    SetDefaultParameterFloat("thres", 0.1);
    MandatoryOff("thres");

    AddParameter(ParameterType_Int, "maxiter", "Maximum number of iterations");
    SetParameterDescription("maxiter", "Algorithm will stop if convergence threshold is not met after the maximum number of iterations.");
    SetDefaultParameterInt("maxiter", 100);
    SetMinimumParameterIntValue("maxiter", 1);
    MandatoryOff("maxiter");

    AddParameter(ParameterType_Float, "rangeramp", "Range radius ramp coefficient");
    SetParameterDescription("rangeramp", "Vary the range radius linearly with the central pixel intensity (experimental).");
    SetDefaultParameterFloat("rangeramp", 0.);
    SetMinimumParameterFloatValue("rangeramp", 0);
    MandatoryOff("rangeramp");

    AddParameter(ParameterType_Bool, "modesearch", "Mode search.");
    SetParameterDescription("modesearch", "If activated pixel iterative convergence is stopped if the path crosses an already converged pixel. Be careful, with this option, the result will slightly depend on thread number and the results will not be stable (see [4] for more details).");

    // Doc example parameter settings
    SetDocExampleParameterValue("in", "maur_rgb.png");
    SetDocExampleParameterValue("fout", "smooth.tif");
    SetDocExampleParameterValue("foutpos", "position.tif");
    SetDocExampleParameterValue("spatialr", "16");
    SetDocExampleParameterValue("ranger", "16");
    SetDocExampleParameterValue("thres", "0.1");
    SetDocExampleParameterValue("maxiter", "100");

    SetOfficialDocLink();
  }

  void DoUpdateParameters() override
  {}

  void DoExecute() override
  {
    FloatVectorImageType* input = GetParameterImage("in");

    MSFilterType::Pointer filter = MSFilterType::New();

    filter->SetInput(input);

    filter->SetSpatialBandwidth(GetParameterInt("spatialr"));
    filter->SetRangeBandwidth(GetParameterFloat("ranger"));
    filter->SetThreshold(GetParameterFloat("thres"));
    filter->SetMaxIterationNumber(GetParameterInt("maxiter"));
    filter->SetRangeBandwidthRamp(GetParameterFloat("rangeramp"));
    filter->SetModeSearch(GetParameterInt("modesearch"));

    //Compute the margin used to ensure exact results (tile wise smoothing)
    //This margin is valid for the default uniform kernel used by the
    //MeanShiftSmoothing filter (bandwidth equal to radius in this case)
    const unsigned int margin = (filter->GetMaxIterationNumber() * filter->GetSpatialBandwidth()) + 1;
    
    otbAppLogINFO(<<"Margin of " << margin << " pixels applied to each tile to stabilized mean shift filtering." << std::endl);

    if ( margin > std::min(input->GetLargestPossibleRegion().GetSize()[0],input->GetLargestPossibleRegion().GetSize()[1]) )
      {
      otbAppLogWARNING(<<"Margin value exceed the input image size." << std::endl);
      }

    SetParameterOutputImage("fout", filter->GetOutput());
    if (IsParameterEnabled("foutpos") && HasValue("foutpos"))
      {
      SetParameterOutputImage("foutpos", filter->GetSpatialOutput());
      }
    if(!GetParameterInt("modesearch"))
      {
      otbAppLogINFO(<<"Mode Search is disabled." << std::endl);
      }
    RegisterPipeline();
   }

};


}
}

OTB_APPLICATION_EXPORT(otb::Wrapper::MeanShiftSmoothing)
