/*****************************************************************************
 * $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$
 ****************************************************************************/
// CamiTK includes
#include "OtsuFilter.h"
#include <ActionWidget.h>
#include <ItkProgressObserver.h>
#include <Property.h>
#include <Log.h>

// Itk includes
#include <itkImageToVTKImageFilter.h>
#include <itkVTKImageToImageFilter.h>
#include <itkOtsuThresholdImageFilter.h>

// Tmp Include
#include <vtkSmartPointer.h>
#include <vtkImageThreshold.h>

using namespace camitk;


// --------------- constructor -------------------
OtsuFilter::OtsuFilter(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Otsu Threshold Filter");
    setDescription("<p> This filter creates a binary thresholded image that separates an image into foreground and background components. <br/>The filter calculates the optimum threshold separating those two classes so that their combined spread (intra-class variance) is minimal (see http://en.wikipedia.org/wiki/Otsu%27s_method).  Then the filter applies that threshold to the input image using an Itk binary fileter. The numberOfHistogram bins can be set for the Otsu Calculator. The insideValue and outsideValue can be set for the Itk binary filter.  The filter produces a labeled volume. <br/><br/>The original reference is: <br/>. <i>Otsu, ''A threshold selection method from gray level histograms,'' IEEE Trans.Syst.ManCybern.SMC-9,62-66 1979.</i></p>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    this->setFamily("ITK Segmentation");
    this->addTag("Threshold");
    this->addTag("Automatic");
    this->addTag("Otsu");

    // Setting parameters default values
    Property* insideValueProperty = new Property(tr("Inside Value"), 0, tr("The scalar type value assigned to voxels which have their scalar type BELOW the threshold value."), "");
    insideValueProperty->setAttribute("minimum", 0);
    insideValueProperty->setAttribute("maximum", 255);
    insideValueProperty->setAttribute("singleStep", 1);
    addParameter(insideValueProperty);

    Property* outsideValueProperty = new Property(tr("Outside Value"), 255, tr("The scalar type value assigned to voxels which have their scalar type ABOVE the threshold value."), "");
    outsideValueProperty->setAttribute("minimum", 0);
    outsideValueProperty->setAttribute("maximum", 255);
    outsideValueProperty->setAttribute("singleStep", 1);
    addParameter(outsideValueProperty);

    Property* computedThresholdProperty = new Property(tr("Computed Threshold"), "?", tr("The computed threshold found by the Otsu filter. \nThis property is read only and only updated after applying the action on a component."), "");
    computedThresholdProperty->setReadOnly(true);
    addParameter(computedThresholdProperty);
}

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

// --------------- getWidget -------------------
QWidget* OtsuFilter::getWidget() {
    setParameterValue("Computed Threshold", "?");

    //-- return the default widget
    return Action::getWidget();
}

// --------------- apply -------------------
Action::ApplyStatus OtsuFilter::apply() {
    for (Component* comp : getTargets()) {
        ImageComponent* input = dynamic_cast<ImageComponent*>(comp);
        process(input);
        CAMITK_INFO(tr("Otsu Computed Threshold for %1: %2").arg(input->getName()).arg(getParameterValue("Computed Threshold").toDouble()))
        // update the value of Computed Threshold in the GUI (no update if the widget was not shown, e.g. in non GUI pipelines)
        ActionWidget* myActionWidget = dynamic_cast<ActionWidget*>(actionWidget);
        if (myActionWidget != nullptr) {
            myActionWidget->update();
        }
        refreshApplication();
    }
    return SUCCESS;
}

// --------------- process -------------------
void OtsuFilter::process(ImageComponent* comp) {
    /*
        // V01: Only VTK, no pipeline cutting
        // 1- Closing threshold image first
        //    a)If you close first threshold image -> memory of the threshold image is freed
        //    b)If then, you close the original image -> memory of the original image is freed
        // 2- Closing original image first
        //    a) If you first close original image -> no memory is freed
        //      (the threshold image is still linked to the original image via the pipeline)
        //    b) If you then close the threshold image -> memory of both images is freed

        vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
        vtkSmartPointer<vtkImageData> outputImage = nullptr;
        vtkSmartPointer<vtkImageThreshold> imageThreshold = vtkSmartPointer<vtkImageThreshold>::New();
        imageThreshold->SetInput(inputImage);
        unsigned char lower = 100;
        unsigned char upper = 200;
        imageThreshold->ThresholdBetween(lower, upper);
        imageThreshold->ReplaceInOn();
        imageThreshold->SetInValue(insideValue);
        imageThreshold->ReplaceOutOn();
        imageThreshold->SetOutValue(outsideValue);
        imageThreshold->Update();
        outputImage = imageThreshold->GetOutput();
        new ImageComponent(outputImage, comp->getName() + "_otsu");
        refreshApplication();
    */

    /*
        // V02: Only VTK, pipeline cutting by deep copy
        // 1- Closing threshold image first
        //    a)If you close first threshold image -> memory of the threshold image is freed
        //    b)If then, you close the original image -> memory of the original image is freed
        // 2- Closing original image first
        //    a) If you first close original image -> memory of the original image is freed
        //      (the threshold image is no more linked to the original image via the pipeline)
        //    b) If you then close the threshold image -> memory of the threshold image is freed
        vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
        vtkSmartPointer<vtkImageData> outputImage = nullptr;
        vtkSmartPointer<vtkImageThreshold> imageThreshold = vtkSmartPointer<vtkImageThreshold>::New();
        imageThreshold->SetInput(inputImage);
        unsigned char lower = 100;
        unsigned char upper = 200;
        imageThreshold->ThresholdBetween(lower, upper);
        imageThreshold->ReplaceInOn();
        imageThreshold->SetInValue(insideValue);
        imageThreshold->ReplaceOutOn();
        imageThreshold->SetOutValue(outsideValue);
        imageThreshold->Update();
        outputImage = imageThreshold->GetOutput();
        vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
        int extent[6];
        outputImage->GetExtent(extent);
        result->SetExtent(extent);
        result->DeepCopy(outputImage);
        result->Update();
        new ImageComponent(result, comp->getName() + "_otsu");
        refreshApplication();
    */

    /*
        // V03: VTK and Itk Pipeline, trying to cut the pipeline by deep copy
        // 1- Closing threshold image first
        //    a)If you close first threshold image -> memory of the threshold image is freed
        //    b)If then, you close the original image -> memory of the original image is NOT freed
        //   --> A counter from the pipeline remains on the original image
        // 2- Closing original image first
        //    a) If you first close original image -> memory of the original image is NOT freed
        //   --> A counter from the pipeline remains on the original image
        //    b) If you then close the threshod image -> memory of the threshold image is freed
        vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
        vtkSmartPointer<vtkImageData> outputImage = implementProcess(inputImage);
        vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
        int extent[6];
        outputImage->GetExtent(extent);
        result->SetExtent(extent);
        result->DeepCopy(outputImage);
        result->Update();
        new ImageComponent(result, comp->getName() + "_otsu");
        refreshApplication();
    */

    /*
        // V04: VTK and Itk Pipeline, trying to cut the pipeline by deep copy and pointers
        // ==> The worse: the original image is never freed...
        // 1- Closing threshold image first
        //    a)If you close first threshold image -> memory of the threshold image is freed
        //    b)If then, you close the original image -> memory of the original image is NOT freed
        //   --> A counter from the pipeline remains on the original image
        // 2- Closing original image first
        //    a) If you first close original image -> memory of the original image is NOT freed
        //   --> A counter from the pipeline remains on the original image
        //    b) If you then close the threshod image -> memory of the threshold image is freed
        vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
        vtkSmartPointer<vtkImageData> outputImage = implementProcess(inputImage);
        vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
        int extent[6];
        outputImage->GetExtent(extent);
        result->SetExtent(extent);
        result->DeepCopy(outputImage);
        result->Update();
        new ImageComponent(result, comp->getName() + "_otsu");
        refreshApplication();
    */

    /*
        // V05: VTK and Itk Pipeline, trying to cut the pipeline by 2 deep copies (argh !)
        // ==> One of the created copies is never deleted !
        // 1- Closing threshold image first
        //    a)If you close first threshold image -> memory of the threshold image is freed
        //       But NOT the memory of its copy !
        //    b)If then, you close the original image -> memory of the original image is freed
        // 2- Closing original image first
        //    a) If you first close original image -> memory of the original image is freed
        //    b) If you then close the threshod image -> memory of the threshold image is freed
        //       But NOT the memory of its copy !
        vtkSmartPointer<vtkImageData> inputImage = vtkSmartPointer<vtkImageData>::New();
        int extentInput[6];
        comp->getImageData()->GetExtent(extentInput);
        inputImage->SetExtent(extentInput);
        inputImage->DeepCopy(comp->getImageData());
        inputImage->Update();
        vtkSmartPointer<vtkImageData> outputImage = implementProcess(inputImage);
        inputImage = nullptr;
        vtkSmartPointer<vtkImageData> result = vtkSmartPointer<vtkImageData>::New();
        int extent[6];
        outputImage->GetExtent(extent);
        result->SetExtent(extent);
        result->DeepCopy(outputImage);
        result->Update();
        outputImage = nullptr;

        new ImageComponent(result, comp->getName() + "_otsu");
        result = nullptr;
        refreshApplication();
    */


    // V06: VTK and Itk Pipeline, trying to cut the pipeline by 2 shallow copies (bof...)
    // ==> One of the created copies is never deleted !
    // 1- Closing threshold image first
    //    a)If you close first threshold image -> memory of the threshold image is freed
    //       But NOT the memory of its copy !
    //    b)If then, you close the original image -> memory of the original image is freed
    // 2- Closing original image first
    //    a) If you first close original image -> memory of the original image is freed
    //    b) If you then close the threshod image -> memory of the threshold image is freed
    //       But NOT the memory of its copy !
    vtkSmartPointer<vtkImageData> inputImage = comp->getImageData();
    vtkSmartPointer<vtkImageData> outputImage = implementProcess(inputImage);

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

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

#include "OtsuFilter.impl"

// ITK filter implementation
template <class InputPixelType, class OutputPixelType, const int dim>
vtkSmartPointer<vtkImageData> OtsuFilter::itkProcess(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;

    // 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::OtsuThresholdImageFilter<InputImageType, OutputImageType> FilterType;
    typename itk::SmartPointer<FilterType> filter = FilterType::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();


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

    // For the filter itself
    filter->SetInput(vtkToItkFilter->GetOutput());
    filter->AddObserver(itk::ProgressEvent(), observer);
    filter->SetInsideValue(getParameterValue("Inside Value").toInt());
    filter->SetOutsideValue(getParameterValue("Outside Value").toInt());

    // From ITK to VTK
    // Change the following line to put your filter instead of vtkToItkFilter
    filter->Update();
    itkToVtkFilter->SetInput(filter->GetOutput());
    // --------------------- Actually execute all filters parts --------------------
    itkToVtkFilter->Update();

    //this->usedThreshold = (double) filter->GetThreshold();
    setParameterValue("Computed Threshold", (double) filter->GetThreshold());

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

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

    return result;
}

