/*****************************************************************************
 * $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 incldues
#include "ManualThreshold.h"
#include "ActionWidget.h"
#include <ItkProgressObserver.h>
#include <Property.h>

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

using namespace camitk;


// --------------- constructor -------------------
ManualThreshold::ManualThreshold(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Manual Threshold Filter");
    setDescription("<br/>Creates a new binary image depending on the initial voxel value:\
                   <ul> \
                   <li>If the initial value is between low and high thresholds (included), the new voxel value will be set to 255 (usually displayed as white)</li> \
                   <li>Otherwise the new voxel value is set to 0 (usually displayed as black)</li> \
                   </ul>\
                   <p>If you want to select all the voxels that have a specific value <i>x</i>, set both thresholds to the value <i>x</i>.</p>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily("ITK Segmentation");
    addTag("Threshold");
    addTag("Manual");
    addTag("Classification");

    // Setting parameters default values
    Property* lowThresholdProperty = new Property(tr("Low Threshold"), 128.0, tr("Voxels which have a value strictly below <i>Low Threshold</i> will be set to 0 (usually displayed as black)."), "");
    lowThresholdProperty->setAttribute("minimum", 0);
    lowThresholdProperty->setAttribute("maximum", 255);
    lowThresholdProperty->setAttribute("singleStep", 1);
    lowThresholdProperty->setAttribute("decimals", 0);
    addParameter(lowThresholdProperty);

    Property* highThresholdProperty = new Property(tr("High Threshold"), 255.0, tr("Voxels which have a value strictly greater than <i>High Threshold</i> will be set to 0 (usually displayed as black)."), "");
    highThresholdProperty->setAttribute("minimum", 0);
    highThresholdProperty->setAttribute("maximum", 255);
    highThresholdProperty->setAttribute("singleStep", 1);
    highThresholdProperty->setAttribute("decimals", 0);
    addParameter(highThresholdProperty);
}

// --------------- destructor -------------------
ManualThreshold::~ManualThreshold() {
    // 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* ManualThreshold::getWidget() {
    //-- Compute the maximum and the minimum possible value for the selected images
    double min = VTK_DOUBLE_MAX;
    double max = VTK_DOUBLE_MIN;

    for (Component* comp : getTargets()) {
        vtkSmartPointer<vtkImageData> inputImage = dynamic_cast<ImageComponent*>(comp)->getImageData();
        double* imgRange = inputImage->GetScalarRange();

        if (min > imgRange[0]) {
            min = imgRange[0];
        }

        if (max < imgRange[1]) {
            max = imgRange[1];
        }
    }

    //-- Apply min/max values to the threshold properties
    camitk::Property* lowThresholdProperty = getProperty("Low Threshold");
    setParameterValue("Low Threshold", (max - min) / 2.0);
    lowThresholdProperty->setAttribute("minimum", min);
    lowThresholdProperty->setAttribute("maximum", max);
    double step = (max - min) / 255.0;
    lowThresholdProperty->setAttribute("singleStep", (step < 1.0) ? 1 : step);

    camitk::Property* highThresholdProperty = getProperty("High Threshold");
    setParameterValue("High Threshold", max);
    highThresholdProperty->setAttribute("minimum", min);
    highThresholdProperty->setAttribute("maximum", max);
    highThresholdProperty->setAttribute("singleStep", (step < 1.0) ? 1 : step);

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

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

    return SUCCESS;
}

// --------------- process -------------------
void ManualThreshold::process(ImageComponent* comp) {
    // Get the parameters
    lowThreshold = getParameterValue("Low Threshold").toInt();
    highThreshold = getParameterValue("High Threshold").toInt();

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

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

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

    refreshApplication();

}

#include "ManualThreshold.impl"

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

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

    typedef itk::BinaryThresholdImageFilter <InputImageType, OutputImageType> FilterType;
    typename FilterType::Pointer 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->SetLowerThreshold(lowThreshold);
    filter->SetUpperThreshold(highThreshold);

    // From ITK to VTK
    // Change the following line to put your filter instead of vtkToItkFilter
    // For example: itkToVtkFilter->SetInput(filter->GetOutput());
    itkToVtkFilter->SetInput(filter->GetOutput());

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

    // --------------------- Create and return a copy (the filters will be deleted)--
    vtkSmartPointer<vtkImageData> resultImage = itkToVtkFilter->GetOutput();
    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;
}

