/*****************************************************************************
 * $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 "Derivative.h"

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

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

using namespace camitk;

// --------------- constructor -------------------
Derivative::Derivative(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Partial Derivative");
    setDescription("<br><b>This filter is used for computing the <i>partial derivative</i> of an image.</b><br>\
                   <br><br> \
                   Choose the order of the derivative:\
                   <ul> \
                       <li> Zero order: no derivation \
                       <li> First order derivative \
                       <li> Second order derivative \
                   </ul> \
                   Choose the direction of the derivative:\
                   <ul> \
                       <li> Derivative along X axis \
                       <li> Derivative along Y axis \
                       <li> Derivative along Z axis \
                   </ul> \
				   ");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    this->setFamily("ITK Filter");
    this->addTag("Derivative");
    this->addTag("Edge Detection");
    this->addTag("Contours");

    // Setting parameters default values
    Property* keepOrgVoxelTypeProperty = new Property(tr("Keep original voxel type?"), true, tr("Does the output image have the same voxel type as the input one?"), "");
    addParameter(keepOrgVoxelTypeProperty);

    Property* derivativeOrderProperty = new Property(tr("Derivative order"), ORDER_0, tr("The derivative order of the partial derivative along the selected direction (x, y, z)."), "");
    derivativeOrderProperty->setEnumTypeName("derivativeOrder");
    QStringList derivativeOrderValues;
    derivativeOrderValues << "0th order: no derivation" << "1st order derivative" << "2nd order derivative";
    derivativeOrderProperty->setAttribute("enumNames", derivativeOrderValues);
    addParameter(derivativeOrderProperty);

    Property* directionProperty = new Property(tr("Direction"), DIRECTION_X, tr("The direction of the partial derivative."), "");
    directionProperty->setEnumTypeName("derivativeDirection");
    QStringList directionValues;
    directionValues << "Along X axis" << "Along Y axis" << "Along Z axis";
    directionProperty->setAttribute("enumNames", directionValues);
    addParameter(directionProperty);
}

// --------------- destructor -------------------
Derivative::~Derivative() {
    // 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)
}

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

void Derivative::process(ImageComponent* comp) {
    // Get the parameters
    this->keepOrgVoxelType = getParameterValue("Keep original voxel type?").toBool();
    this->order = getParameterValue("Derivative order").toInt();
    this->direction = getParameterValue("Direction").toInt();

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

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

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

    refreshApplication();
}

#include "Derivative.impl"

// ITK filter implementation
template <class InputPixelType, class OutputPixelType, const int dim>
vtkSmartPointer<vtkImageData> Derivative::itkProcess(vtkSmartPointer<vtkImageData> img) {
    vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
    vtkSmartPointer<vtkImageData> resultImage;

    // --------------------- 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;

    // 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();

    // Declare and create your own private ITK filter here...
    typedef itk::CastImageFilter<InputImageType, OutputImageType> CastFilterType;
    typename CastFilterType::Pointer toDoubleFilter = CastFilterType::New();

    typedef itk::DerivativeImageFilter<OutputImageType, OutputImageType> DerivativeFilterType;
    typename DerivativeFilterType::Pointer derivativeFilter = DerivativeFilterType::New();

    typedef itk::RescaleIntensityImageFilter<OutputImageType, InputImageType> ToOrgFilterType;
    typename ToOrgFilterType::Pointer toOrgFilter = ToOrgFilterType::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> ItkToVtkFloatFilterType;
    typename ItkToVtkFloatFilterType::Pointer itkToVtkFloatFilter = ItkToVtkFloatFilterType::New();

    typedef itk::ImageToVTKImageFilter<InputImageType> ItkToVtkOrgFilterType;
    typename ItkToVtkOrgFilterType::Pointer itkToVtkOrgFilter = ItkToVtkOrgFilterType::New();

    // To update CamiTK progress bar while filtering, add an ITK observer to the filters.
    ItkProgressObserver::Pointer observer = ItkProgressObserver::New();
    // ITK observers generally give values between 0 and 1, and CamiTK progress bar
    //    wants values between 0 and 100...
    observer->SetCoef(100.0);

    // --------------------- Plug filters and parameters ---------------------------
    // From VTK to ITK
    vtkToItkFilter->SetInput(img);
    vtkToItkFilter->AddObserver(itk::ProgressEvent(), observer);
    vtkToItkFilter->Update();
    observer->Reset();

    toDoubleFilter->SetInput(vtkToItkFilter->GetOutput());
    toDoubleFilter->AddObserver(itk::ProgressEvent(), observer);
    toDoubleFilter->Update();
    observer->Reset();

    derivativeFilter->SetInput(toDoubleFilter->GetOutput());
    derivativeFilter->SetOrder(this->order);
    derivativeFilter->SetDirection(this->direction);
    derivativeFilter->AddObserver(itk::ProgressEvent(), observer);
    derivativeFilter->Update();
    observer->Reset();

    if (keepOrgVoxelType) {
        toOrgFilter->SetInput(derivativeFilter->GetOutput());
        toOrgFilter->AddObserver(itk::ProgressEvent(), observer);
        toOrgFilter->Update();
        observer->Reset();

        itkToVtkOrgFilter->SetInput(toOrgFilter->GetOutput());
        itkToVtkOrgFilter->AddObserver(itk::ProgressEvent(), observer);
        itkToVtkOrgFilter->Update();
        observer->Reset();

        resultImage = itkToVtkOrgFilter->GetOutput();
    }
    else {
        itkToVtkFloatFilter->SetInput(derivativeFilter->GetOutput());
        itkToVtkFloatFilter->AddObserver(itk::ProgressEvent(), observer);
        itkToVtkFloatFilter->Update();
        observer->Reset();

        resultImage = itkToVtkFloatFilter->GetOutput();
    }

    // --------------------- Create and return a copy (the filters will be deleted)--
    int extent[6];
    resultImage->GetExtent(extent);
    result->SetExtent(extent);
    result->DeepCopy(resultImage);

    // Set CamiTK progress bar back to zero (the processing filter is over)
    observer->Reset();

    return result;
}

