/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/
#include "GaussianFilter.h"

// CamiTK includes
#include <ItkProgressObserver.h>
#include <Property.h>

// ITK includes
#include <itkImageToVTKImageFilter.h>
#include <itkVTKImageToImageFilter.h>
#include <itkCastImageFilter.h>
#include <itkRescaleIntensityImageFilter.h>
#include <itkDiscreteGaussianImageFilter.h>
#include <itkRecursiveGaussianImageFilter.h>

using namespace camitk;


// --------------- constructor -------------------
GaussianFilter::GaussianFilter(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Gaussian Filter");
    setDescription("<p>Blurring is the traditional apporach for removing noise from images. It is usually implemented in the form of a convolution with a kernel. One of the most commonly used kernels is the Gaussian.</p><p>The classical methode of smoothing an image by convolution with a Gaussian kernel has the drawback that it is slow when the standard deviation sigma of the Gaussian is large.</p><p>The Recursive IIR (Infinite Impulse Rewponse) implements an approximation of convolution with the Gaussian. In practice, this filter requires a constant number of operations for approximating the convolution, regardless of the sigma value.</p>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    this->setFamily("ITK Filter");
    this->addTag("Blur");
    this->addTag("Smoothing");
    this->addTag("Neighborhood Filter");
    this->addTag("Convolution");
    this->addTag("Gaussian");

    // Setting parameters default values
    Property* varianceProperty = new Property(tr("Variance"), 1.0, tr("The variance is an input parameter of the gaussian kernel. \nThe higher the variance is, the blurer the resulting image will be."), "Depending on the \"Use Image Spacing\", this value is in image spacing unit (usually mm) or number of pixels.");
    varianceProperty->setAttribute("minimum", 0);
    varianceProperty->setAttribute("maximum", 100);
    varianceProperty->setAttribute("singleStep", 0.001);
    varianceProperty->setAttribute("decimals", 3);
    addParameter(varianceProperty);

    Property* gaussianTypeProperty = new Property(tr("Gaussian Type"), STANDARD, tr("Choose the type of kernel for the gaussian filtering"), "");
    gaussianTypeProperty->setEnumTypeName("GaussianType");
    QStringList gaussianTypeValue;
    gaussianTypeValue << tr("Standard") << tr("Recursive IIR");
    gaussianTypeProperty->setAttribute("enumNames", gaussianTypeValue);
    addParameter(gaussianTypeProperty);

    Property* useImageSpacingProperty = new Property(tr("Use Image Spacing"), true, tr("The variance or standard deviation (sigma) will be evaluated as pixel units if this is off or as physical units if this is on."), "");
    addParameter(useImageSpacingProperty);

}

// --------------- destructor -------------------
GaussianFilter::~GaussianFilter() {
    // do not delete the widget has it might have been used in the ActionViewer (i.e. the ownership might have been taken by the stacked widget)
}

GaussianFilter::GaussianType GaussianFilter::getType() {
    return (GaussianFilter::GaussianType) getParameterValue("Gaussian Type").toInt();
}


// --------------- apply -------------------
Action::ApplyStatus GaussianFilter::apply() {
    for (Component* comp : getTargets()) {
        ImageComponent* input = dynamic_cast<ImageComponent*>(comp);
        process(input);
    }
    return SUCCESS;
}

void GaussianFilter::process(ImageComponent* comp) {
    // Get the parameters
    this->variance = getParameterValue("Variance").toDouble();

    // ITK filter implementation using templates
    vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
    vtkSmartPointer<vtkImageData> outputImage = implementProcess(inputImage);

    ImageComponent* outputComp = new ImageComponent(outputImage, comp->getName() + "_gaussian");

    // same frame as input
    outputComp->setFrameFrom(comp);

    refreshApplication();

}

#include "GaussianFilter.impl"

// ITK filter implementation
template <class InputPixelType, class OutputPixelType, const int dim>
vtkSmartPointer<vtkImageData> GaussianFilter::itkProcess(vtkSmartPointer<vtkImageData> img) {
    vtkSmartPointer<vtkImageData> outputImage = nullptr;

    switch (this->getType()) {
        case GaussianFilter::STANDARD:
            outputImage = itkProcessStandardGaussian<InputPixelType, OutputPixelType, dim>(img);
            break;
        case GaussianFilter::RECURSIVE_IIR:
            outputImage = itkProcessIIRGaussian<InputPixelType, OutputPixelType, dim>(img);
            break;
        default:
            break;
    }

    return outputImage;

}

template <class InputPixelType, class OutputPixelType, const int dim>
vtkSmartPointer<vtkImageData> GaussianFilter::itkProcessStandardGaussian(vtkSmartPointer<vtkImageData> img) {

    // --------------------- Filters declaration and creation ----------------------
    // Define ITK input and output image types with respect to the instantiation
    //    types of the tamplate.
    typedef itk::Image< InputPixelType,  dim > InputImageType;
    typedef itk::Image< OutputPixelType, dim > OutputImageType;
    typedef itk::Image< double, dim > ComputeImageType;

    // Convert the image from CamiTK in VTK format to ITK format to use ITK filters.
    typedef itk::VTKImageToImageFilter<InputImageType> vtkToItkFilterType;
    typename vtkToItkFilterType::Pointer vtkToItkFilter = vtkToItkFilterType::New();

    // In the same way, once the image is filtered, we need to convert it again to
    //    VTK format to give it to CamiTK.
    typedef itk::ImageToVTKImageFilter<OutputImageType> itkToVtkFilterType;
    typename itkToVtkFilterType::Pointer itkToVtkFilter = itkToVtkFilterType::New();

    // Declare and create your own private ITK filter here...
    // Cast image
    typedef itk::CastImageFilter<InputImageType, ComputeImageType> CastFilterType;
    typename CastFilterType::Pointer castFilter = CastFilterType::New();
    // Perform actual filtering (here should be your piece of code)
    typedef itk::DiscreteGaussianImageFilter<ComputeImageType, ComputeImageType> FilterType;
    typename FilterType::Pointer filter = FilterType::New();
    // Re-cast to original pixel type
    typedef itk::RescaleIntensityImageFilter<ComputeImageType, OutputImageType> RescaleFilterType;
    typename RescaleFilterType::Pointer rescaleFilter = RescaleFilterType::New();

    // To update CamiTK progress bar while filtering, add an ITK observer to the filters.
    // Create a timer for the progress bar
    ItkProgressObserver::Pointer observerCast = ItkProgressObserver::New();
    ItkProgressObserver::Pointer observerBlur = ItkProgressObserver::New();
    ItkProgressObserver::Pointer observerRescale = ItkProgressObserver::New();
    observerCast->SetCoef(33.3);
    observerCast->SetStartValue(0.0);
    observerBlur->SetCoef(333.3);
    observerBlur->SetStartValue(33.3);
    observerRescale->SetCoef(33.3);
    observerRescale->SetStartValue(66.6);

    // --------------------- Plug filters and parameters ---------------------------
    // From VTK to ITK
    vtkToItkFilter->SetInput(img);
    // For the filter itself
    castFilter->SetInput(vtkToItkFilter->GetOutput());
    castFilter->AddObserver(itk::ProgressEvent(), observerCast);

    filter->SetInput(castFilter->GetOutput());
    filter->AddObserver(itk::ProgressEvent(), observerBlur);
    filter->SetVariance(this->variance);
    filter->SetUseImageSpacing(getParameterValue("Use Image Spacing").toBool());

    rescaleFilter->SetInput(filter->GetOutput());
    rescaleFilter->AddObserver(itk::ProgressEvent(), observerRescale);


    // From ITK to VTK
    itkToVtkFilter->SetInput(rescaleFilter->GetOutput());

    // --------------------- Actually execute all filters parts --------------------
    itkToVtkFilter->Update();

    // --------------------- Create and return a copy (the filters will be deleted)--
    vtkSmartPointer<vtkImageData> resultImage = itkToVtkFilter->GetOutput();
    vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
    int extent[6];
    resultImage->GetExtent(extent);
    result->SetExtent(extent);
    result->DeepCopy(resultImage);

    observerCast->Reset();
    observerBlur->Reset();
    observerRescale->Reset();

    observerBlur = nullptr;
    observerCast = nullptr;
    observerRescale = nullptr;

    castFilter = nullptr;
    filter = nullptr;
    rescaleFilter = nullptr;

    return result;
}

template <class InputPixelType, class OutputPixelType, const int dim>
vtkSmartPointer<vtkImageData> GaussianFilter::itkProcessIIRGaussian(vtkSmartPointer<vtkImageData> img) {

    // Definition of image types
    typedef itk::Image< InputPixelType,  dim > InputImageType;
    typedef itk::Image< OutputPixelType, dim > OutputImageType;

    // Convert the VTK image given by IMP to an ITK image
    typedef itk::VTKImageToImageFilter<InputImageType> vtkToItkFilterType;
    typename vtkToItkFilterType::Pointer vtkToItkFilter = vtkToItkFilterType::New();

    vtkToItkFilter->SetInput(img);

    // Create a timer for the progress bar
    ItkProgressObserver::Pointer observerX;
    ItkProgressObserver::Pointer observerY;
    ItkProgressObserver::Pointer observerZ;

    observerX = ItkProgressObserver::New();
    observerY = ItkProgressObserver::New();
    if (dim == 2) {
        observerX->SetCoef(50.0);
        observerY->SetCoef(50.0);

        observerX->SetStartValue(0.0);
        observerY->SetStartValue(50.0);
    }
    else {   // dim == 3
        observerZ = ItkProgressObserver::New();

        observerX->SetCoef(33.3);
        observerY->SetCoef(33.3);
        observerZ->SetCoef(33.3);

        observerX->SetStartValue(0.0);
        observerY->SetStartValue(33.3);
        observerZ->SetStartValue(66.6);
    }

    // Convert to OutputPixelType


    // Perform actual filtering (here should be your piece of code)
    typedef itk::RecursiveGaussianImageFilter<InputImageType, OutputImageType> FilterType;
    typename FilterType::Pointer filterX;
    typename FilterType::Pointer filterY;
    typename FilterType::Pointer filterZ;


    filterX  = FilterType::New();
    filterX->SetDirection(0);
    filterX->SetSigma(this->variance);
    filterX->SetOrder(FilterType::ZeroOrder);
    filterX->AddObserver(itk::ProgressEvent(), observerX);

    filterY  = FilterType::New();
    filterY->SetDirection(1);
    filterY->SetSigma(this->variance);
    filterY->SetOrder(FilterType::ZeroOrder);
    filterY->AddObserver(itk::ProgressEvent(), observerY);

    if (dim == 3) {
        filterZ = FilterType::New();
        filterZ->SetDirection(2);
        filterZ->SetOrder(FilterType::ZeroOrder);
        filterZ->SetSigma(this->variance);
        filterZ->AddObserver(itk::ProgressEvent(), observerZ);
    }


    filterX->SetInput(vtkToItkFilter->GetOutput());
    filterY->SetInput(filterX->GetOutput());
    if (dim == 3) {
        filterZ->SetInput(filterY->GetOutput());
    }

    // Re-connect the obtained ITK imge to CamiTK via VTK image
    typedef itk::ImageToVTKImageFilter<OutputImageType> itkToVtkFilterType;
    typename itkToVtkFilterType::Pointer itkToVtkFilter = itkToVtkFilterType::New();

    if (dim == 2) {
        itkToVtkFilter->SetInput(filterY->GetOutput());
    }
    else {
        itkToVtkFilter->SetInput(filterZ->GetOutput());
    }

    itkToVtkFilter->Update();

    // Return a copy (at the end, the filters won't exist..
    vtkSmartPointer<vtkImageData> resultImage = itkToVtkFilter->GetOutput();
    vtkSmartPointer<vtkImageData> imageCopy = vtkSmartPointer<vtkImageData>::New();
    int extent[6];
    resultImage->GetExtent(extent);
    imageCopy->SetExtent(extent);
    imageCopy->DeepCopy(resultImage);

    observerX->Reset();
    observerY->Reset();
    observerZ->Reset();

    // Set CamiTK progress bar back to zero (the processing filter is over)
    observerX = nullptr;
    observerY = nullptr;
    observerZ = nullptr;


    return imageCopy;
}
