/*****************************************************************************
* $CAMITK_LICENCE_BEGIN$
*
* CamiTK - Computer Assisted Medical Intervention ToolKit
* (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
*
* 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$
****************************************************************************/

// -- Core image component stuff
#include "ImageComponent.h"
#include "ImageComponentExtension.h"
#include "SingleImageComponent.h"
#include "ImageOrientationHelper.h"

// -- Core stuff
#include "InteractiveViewer.h"
#include "PropertyExplorer.h"
#include "MeshComponent.h"
#include "Property.h"
#include "Frame.h"

// -- vtk stuff
#include <vtkUniformGrid.h>
#include <vtkTransformFilter.h>
#include <vtkImageReslice.h>
#include <vtkImageFlip.h>
#include <vtkUnstructuredGrid.h>
#include <vtkVertex.h>
#include <vtkActor.h>

#include <vtkPiecewiseFunction.h>
#include <vtkColorTransferFunction.h>
#include <vtkVolumeProperty.h>
#include <vtkVolumeRayCastMapper.h>
#include <vtkImageCast.h>
#include <vtkVolumeRayCastCompositeFunction.h>
#include <vtkVolume.h>

#include <vtkCellArray.h>
#include <vtkFloatArray.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>


// -- QT stuff
#include <QTableView>
#include <QStandardItemModel>

// -- stl stuff
#include <iostream>


namespace camitk {
// -------------------- constructors --------------------
ImageComponent::ImageComponent(const QString& file) : Component(file, "Image Volume") {
    init();
}

ImageComponent::ImageComponent(vtkSmartPointer<vtkImageData> anImageData, const QString& name, bool copy, ImageOrientationHelper::PossibleImageOrientations initialOrientation) : Component("", name) {
    init();
    setImageData(anImageData, copy, initialOrientation);
    setName(name);
    setModified();
}

// -------------------- destructor  --------------------
ImageComponent::~ImageComponent() {

    if (model != nullptr) {
        delete model;
    }
    if (selectionView != nullptr) {
        delete selectionView;
    }

    originalImageData = nullptr;
}

// -------------------- init --------------------
void ImageComponent::init() {

    originalImageData = nullptr;
    axialSlices     = nullptr;
    coronalSlices   = nullptr;
    sagittalSlices  = nullptr;
    arbitrarySlices = nullptr;
    volumeRenderingChild = nullptr;

    currentPixelPicked[0] = -1;
    currentPixelPicked[1] = -1;
    currentPixelPicked[2] = -1;

    lut = vtkSmartPointer<vtkWindowLevelLookupTable>::New();

    // Properties
    Property* nameProp  = new Property("Image Name", QVariant(getName()), tr("Name of the image displayed in the explorer"), "");
    addProperty(nameProp);

    Property* imageDims = new Property("Image Dimensions", QVariant("(0, 0, 0)"), tr("Dimensions of the image in each direction (x,y,z)"), "voxel number");
    imageDims->setReadOnly(true);
    addProperty(imageDims);

    Property* imageSize = new Property("Image Size", QVariant("(0.0, 0.0, 0.0)"), tr("Size of the image in each direction (x, y, z)"), "mm<sup>3</sup>");
    imageSize->setReadOnly(true);
    addProperty(imageSize);

    Property* voxelSize = new Property("Voxel Size", QVariant("(0.0, 0.0, 0.0)"), tr("Size of an image voxel in each direction (x, y, z)"), "mm <sup>3</sup>");
    voxelSize->setReadOnly(true);
    addProperty(voxelSize);

    Property* voxelDataType = new Property("Voxel Data Type", QVariant("None"), tr("Type of the data stored in each voxel"), "data type");
    voxelDataType->setReadOnly(true);
    addProperty(voxelDataType);

    addProperty(new Property("Display Image in 3D Viewer", QVariant(false), tr("Displays or not the image slices in the 3D viewer"), ""));

    // Picking properties
    selectionView = new QTableView();
    selectionView->setObjectName("Selection");
    selectionView->setSelectionBehavior(QAbstractItemView::SelectRows);
    model = new QStandardItemModel(10, 2, this);

    model->setData(model->index(0, 0), QVariant("Value"));
    model->setData(model->index(1, 0), QVariant("Voxel: x"));
    model->setData(model->index(2, 0), QVariant("Voxel: y"));
    model->setData(model->index(3, 0), QVariant("Voxel: z"));
    model->setData(model->index(4, 0), QVariant("Img coords: x"));
    model->setData(model->index(5, 0), QVariant("Img coords: y"));
    model->setData(model->index(6, 0), QVariant("Img coords: z"));
    model->setData(model->index(7, 0), QVariant("World coords: x"));
    model->setData(model->index(8, 0), QVariant("World coords: y"));
    model->setData(model->index(9, 0), QVariant("World coords: z"));

    selectionView->setModel(model);
    selectionView->setSelectionMode(QAbstractItemView::SingleSelection);
}

// -------------------- initImageProperties --------------------
void ImageComponent::initImageProperties() {
    // from QObject documentation, section "Detailed Description":
    // "To avoid never ending notification loops you can temporarily block signals with blockSignals()."
    setProperty("Image Name", QVariant(getName()));
    if (originalImageData) {

        int* imgDims = originalImageData->GetDimensions();
        QString imageDims = "(";
        imageDims += QString::number(imgDims[0]);
        imageDims +=  ", ";
        imageDims += QString::number(imgDims[1]);
        imageDims +=  ", ";
        imageDims += QString::number(imgDims[2]);
        imageDims +=  ")";
        setProperty("Image Dimensions", QVariant(imageDims));

        double* spacing = originalImageData->GetSpacing();
        QString imageSpacing = "(";
        imageSpacing += QString::number(spacing[0]);
        imageSpacing += ", ";
        imageSpacing += QString::number(spacing[1]);
        imageSpacing += ", ";
        imageSpacing += QString::number(spacing[2]);
        imageSpacing += ")";
        setProperty("Voxel Size", QVariant(imageSpacing));

        QString imageSize = "(";
        imageSize += QString::number(imgDims[0] * spacing[0], 'f', 2);
        imageSize += ", ";
        imageSize += QString::number(imgDims[1] * spacing[1], 'f', 2);
        imageSize += ", ";
        imageSize += QString::number(imgDims[2] * spacing[2], 'f', 2);
        imageSize += ")";
        setProperty("Image Size", QVariant(imageSize));

        setProperty("Voxel Data Type", QString(originalImageData->GetScalarTypeAsString()));

        setProperty("Display Image in 3D Viewer", QVariant(true));

    }
}

// -------------------- updateProperty --------------------
void ImageComponent::updateProperty(QString name, QVariant value) {
    // update the non-read only properties
    if (name == "Display Image in 3D Viewer") {
        update3DViewer();
    }
    else if (name == "Image Name") {
        setName(value.toString());
        refreshInterfaceNode();
    }
    // bypass read-only properties
    // (other specific properties are read only and should not be sent up to Component::updateProperty)
    else if (name != "Image Dimensions"
             && name != "Image Size"
             && name != "Voxel Size"
             && name != "Voxel Data Type"
             && name != "Initial Image Orientation") {
        Component::updateProperty(name, value);
    }
}

// -------------------- setImageData --------------------
void ImageComponent::setImageData(vtkSmartPointer<vtkImageData> anImageData,
                                  bool copy,
                                  ImageOrientationHelper::PossibleImageOrientations initialOrientation,
                                  vtkSmartPointer<vtkMatrix4x4> initialRotationMatrix) {

    this->initialOrientation = initialOrientation;

    originalImageData = nullptr;
    vtkSmartPointer<vtkImageData> inputImage = nullptr;

    if (copy) {
        // We need to use a shall because of some strange behaviour of mingw
        // (In mingw variable seems to become out of scope when going from one DLL to another one,
        // which generates a loss of smartness in the vtkSmartPointer)
        // Disconnect the previous Vtk Pipeline without memcopy
        // (equivalent of ITK DisconnectPipeline()...)
        // Note : this (i.e disconnect/deepcopy/shallowcopy) should not have to be done
        // in the components...
        inputImage = vtkSmartPointer<vtkImageData>::New();
        inputImage->ShallowCopy(anImageData);
    }
    else {
        inputImage = anImageData;
    }

    // 1. Get / compute the initial translation of the image
    double t_x, t_y, t_z;
    inputImage->GetOrigin(t_x, t_y, t_z);
    vtkSmartPointer<vtkTransform> initialTranslation = vtkSmartPointer<vtkTransform>::New();
    initialTranslation->Identity();
    initialTranslation->Translate(t_x, t_y, t_z);
    initialTranslation->Update();

    // 2. Get the orientation of the image
    double* imgSpacing = inputImage->GetSpacing();
    int* imgDims    = inputImage->GetDimensions();
    double dims[3];
    dims[0] = imgSpacing[0] * imgDims[0];
    dims[1] = imgSpacing[1] * imgDims[1];
    dims[2] = imgSpacing[2] * imgDims[2];
    vtkSmartPointer<vtkMatrix4x4> orientationToRAIMatrix = ImageOrientationHelper::getTransformToRAI(initialOrientation, dims[0], dims[1], dims[2]);

    // TODO: clean this lines by moving them to the constructor
    // 3. Store the orientation information as a property of the Image
    Property* orientationProperty = new Property(tr("Initial Image Orientation"), ImageOrientationHelper::getOrientationAsQString(initialOrientation), tr("The initial orientation of the image when it was acquired by the medical device. \nRemember, we display image in the RAI format."), "");
    orientationProperty->setReadOnly(true);
    addProperty(orientationProperty);


    // 4. Store the Translation * orientation -> RAI = initialImageDataTransform
    initialImageDataTransform = vtkSmartPointer<vtkTransform>::New();
    vtkSmartPointer<vtkMatrix4x4> initialImageDataMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(initialTranslation->GetMatrix(), orientationToRAIMatrix, initialImageDataMatrix);
    initialImageDataTransform->SetMatrix(initialImageDataMatrix);

    // 5. Store the rotation * translation * orientation -> RAI = initialFrameTransform
    initialFrameTransform = vtkSmartPointer<vtkTransform>::New();
    vtkSmartPointer<vtkMatrix4x4> initialFrameMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    rotationMatrix = initialRotationMatrix;
    if (!rotationMatrix) {
        rotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
        rotationMatrix->Identity();
    }
    vtkMatrix4x4::Multiply4x4(rotationMatrix, initialTranslation->GetMatrix(), initialFrameMatrix);
    vtkMatrix4x4::Multiply4x4(initialFrameMatrix, orientationToRAIMatrix, initialFrameMatrix);
    initialFrameTransform->SetMatrix(initialFrameMatrix);

    // 6. Apply initialImageDataTransform to the image data
    vtkSmartPointer<vtkImageReslice> imageResliceFilter = vtkSmartPointer<vtkImageReslice>::New();
    imageResliceFilter->SetInputData(inputImage);
    imageResliceFilter->SetOutputDimensionality(3);
    imageResliceFilter->SetResliceAxes(initialImageDataTransform->GetMatrix());
    imageResliceFilter->Update();
    originalImageData = imageResliceFilter->GetOutput();

    // 7. Store initialFrameTransform as the current frame
    // note: we need to get another matrix instance for the transformation (else it is deleted)
    vtkSmartPointer<vtkMatrix4x4> originalMatrix = this->getTransform()->GetMatrix();
    vtkSmartPointer<vtkMatrix4x4> updatedMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(originalMatrix, initialFrameTransform->GetMatrix(), updatedMatrix);
    vtkSmartPointer<vtkTransform> updatedTransform = vtkSmartPointer<vtkTransform>::New();
    updatedTransform->SetMatrix(updatedMatrix);
    setTransform(updatedTransform);

    // Clean smart pointers
    inputImage = nullptr;
    imageResliceFilter = NULL;

    // Build default lookup table
    initLookupTable();

    // Build the Axial, Sagittal and Coronal SingleImageComponents...
    buildImageComponents();

    // Init or update camitk:Properties
    initImageProperties();
}

// -------------------- getImageDataWithFrameTransform --------------------
vtkSmartPointer<vtkImageData> ImageComponent::getImageDataWithFrameTransform() {
    vtkSmartPointer<vtkImageData> imageDataFramed = vtkSmartPointer<vtkImageData>::New();
    imageDataFramed->DeepCopy(originalImageData);

    // 1. Put back image data to its original state
    vtkSmartPointer<vtkMatrix4x4> backToOriginalImageDataMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    backToOriginalImageDataMatrix->DeepCopy(initialImageDataTransform->GetMatrix());
    backToOriginalImageDataMatrix->Invert();
    vtkSmartPointer<vtkImageReslice> backToOrigingImageDataFilter = vtkSmartPointer<vtkImageReslice>::New();
    backToOrigingImageDataFilter->SetInputData(imageDataFramed);
    backToOrigingImageDataFilter->SetOutputDimensionality(3);
    backToOrigingImageDataFilter->SetResliceAxes(backToOriginalImageDataMatrix);
    imageDataFramed = backToOrigingImageDataFilter->GetOutput();
    backToOrigingImageDataFilter->Update();

    // 2. Retrieve the matrice of the user Muser which has been applied to the initial frame
    // Assuming Mframe = MinitFrame * Muser
    vtkSmartPointer<vtkMatrix4x4> initialFrameMatrixInverse = vtkSmartPointer<vtkMatrix4x4>::New();
    initialFrameMatrixInverse->DeepCopy(initialFrameTransform->GetMatrix());
    initialFrameMatrixInverse->Invert();
    const vtkSmartPointer<vtkTransform> frame = getTransform();
    vtkSmartPointer<vtkMatrix4x4> frameMatrix = frame->GetMatrix();
    vtkSmartPointer<vtkMatrix4x4> userMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(initialFrameMatrixInverse, frameMatrix, userMatrix);

    vtkSmartPointer<vtkTransform> userTransform = vtkSmartPointer<vtkTransform>::New();
    userTransform->SetMatrix(userMatrix);

    // 3. Retrieve the translation of the user matrix and apply it to the image data
    // This way, the translation will be saved under the Offset tag
    double pos[3];
    userTransform->GetPosition(pos);
    vtkSmartPointer<vtkTransform> translationTransform = vtkSmartPointer<vtkTransform>::New();
    translationTransform->Identity();
    translationTransform->Translate(pos[0], pos[1], pos[2]);
    translationTransform->Update();
    vtkSmartPointer<vtkImageReslice> translationFilter = vtkSmartPointer<vtkImageReslice>::New();
    translationFilter->SetInputData(imageDataFramed);
    translationFilter->SetOutputDimensionality(3);
    translationFilter->SetResliceAxes(translationTransform->GetMatrix());
    imageDataFramed = translationFilter->GetOutput();
    translationFilter->Update();

    // 4. Retrieve the rotation matrix from the user matrix to manually save it
    // only if user has modify it
    if ((userMatrix->GetElement(0, 0) != 1) ||
            (userMatrix->GetElement(1, 1) != 1) ||
            (userMatrix->GetElement(2, 2) != 1)) {

        rotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
        rotationMatrix->DeepCopy(userMatrix);
        rotationMatrix->SetElement(0, 3, 0.0);
        rotationMatrix->SetElement(1, 3, 0.0);
        rotationMatrix->SetElement(2, 3, 0.0);
    }

    return imageDataFramed;
}

// -------------------- initLookupTable --------------------
void ImageComponent::initLookupTable() {


    // TODO set in a betterway information about the lut thanks to image data
    if (this->originalImageData->GetNumberOfScalarComponents() == 1) {
        // Default values for grey level lut
        lut->SetTableRange(0, 255);
        lut->SetSaturationRange(1.0, 1.0);
        lut->SetHueRange(0.667, 0.667);
        lut->SetValueRange(1.0, 1.0);
        lut->SetAlphaRange(1.0, 1.0);
        lut->SetLevel((int)((getActualMaxColor() + getActualMinColor()) / 2));
        lut->SetWindow(getActualNumberOfColors());
        lut->SetRampToSCurve();
        lut->Build();  // effective build

    }
    else {
        // For colored image, we need to extract the information from the image
        // but a classic lut is useless (if there is one, it displays wrong information)
        // do not consider a lut for colored images
        lut = nullptr;
    }
}

// -------------------- replaceImageData --------------------
void ImageComponent::replaceImageData(vtkSmartPointer<vtkImageData> anImageData, bool copy, ImageOrientationHelper::PossibleImageOrientations initialOrientation) {
    // Delete ImageComponents
    if (axialSlices) {
        removeChild(axialSlices);
        delete axialSlices;
        axialSlices = nullptr;
    }

    if (coronalSlices) {
        removeChild(coronalSlices);
        delete coronalSlices;
        coronalSlices = nullptr;
    }

    if (sagittalSlices) {
        removeChild(sagittalSlices);
        delete sagittalSlices;
        sagittalSlices = nullptr;
    }

    if (arbitrarySlices) {
        removeChild(arbitrarySlices);
        delete arbitrarySlices;
        arbitrarySlices = nullptr;
    }

    if (volumeRenderingChild) {
        removeChild(volumeRenderingChild);
        delete volumeRenderingChild;
        volumeRenderingChild = nullptr;
    }

    this->refreshInterfaceNode();

    setImageData(anImageData, copy, initialOrientation);

    this->refreshInterfaceNode();
}

// -------------------- getAxialSlices --------------------
SingleImageComponent* ImageComponent::getAxialSlices() {
    return this->axialSlices;
}

// -------------------- getCoronalSlices --------------------
SingleImageComponent* ImageComponent::getCoronalSlices() {
    return this->coronalSlices;
}

// -------------------- getSagittalSlices --------------------
SingleImageComponent* ImageComponent::getSagittalSlices() {
    return this->sagittalSlices;
}

// -------------------- getArbitrarySlices --------------------
SingleImageComponent* ImageComponent::getArbitrarySlices() {
    return this->arbitrarySlices;
}

// -------------------- setSingleImageComponents --------------------
void ImageComponent::setSingleImageComponents(SingleImageComponent* axialSlices, SingleImageComponent* sagittalSlices, SingleImageComponent* coronalSlices, SingleImageComponent* arbitrarySlices) {
    if (this->axialSlices && axialSlices != this->axialSlices) {
        removeChild(this->axialSlices);
        delete this->axialSlices;
        this->axialSlices = axialSlices;
    }

    if (this->coronalSlices && coronalSlices != this->coronalSlices) {
        removeChild(this->coronalSlices);
        delete this->coronalSlices;
        this->coronalSlices = coronalSlices;
    }

    if (this->sagittalSlices && sagittalSlices != this->sagittalSlices) {
        removeChild(this->sagittalSlices);
        delete this->sagittalSlices;
        this->sagittalSlices = sagittalSlices;
    }

    if (this->arbitrarySlices && arbitrarySlices != this->arbitrarySlices) {
        removeChild(this->arbitrarySlices);
        delete this->arbitrarySlices;
        this->arbitrarySlices = arbitrarySlices;
    }
}

// ---------------- getVolumeRenderingChild -------------------
MeshComponent* ImageComponent::getVolumeRenderingChild() {
    return this->volumeRenderingChild;
}

// -------------------- setImageName --------------------
void ImageComponent::setImageName(const QString& imageName) {
    // Change name in the ImageFilterAddon
    Component::setName(imageName);
}

// -------------------- setImageName --------------------
QString ImageComponent::getImageName() const {
    return getName();
}

// -------------------- update3DViewer --------------------
void ImageComponent::update3DViewer() {
    bool viewIn3D = property("Display Image in 3D Viewer").toBool();

    if (viewIn3D) {
        if (axialSlices) {
            axialSlices->setViewSliceIn3D(true);
        }

        if (coronalSlices) {
            coronalSlices->setViewSliceIn3D(true);
        }

        if (sagittalSlices) {
            sagittalSlices->setViewSliceIn3D(true);
        }

        if (arbitrarySlices) {
            // by default not visible in 3D
            arbitrarySlices->setViewSliceIn3D(false);
        }

        if (volumeRenderingChild) {
            volumeRenderingChild->setVisibility(InteractiveViewer::get3DViewer(), true);
        }
    }
    else {
        if (axialSlices) {
            axialSlices->setViewSliceIn3D(false);
        }

        if (coronalSlices) {
            coronalSlices->setViewSliceIn3D(false);
        }

        if (sagittalSlices) {
            sagittalSlices->setViewSliceIn3D(false);
        }

        if (arbitrarySlices) {
            arbitrarySlices->setViewSliceIn3D(false);
        }

        if (volumeRenderingChild) {
            volumeRenderingChild->setVisibility(InteractiveViewer::get3DViewer(), false);
        }
    }

}

// -------------------- buildImageComponents --------------------
void ImageComponent::buildImageComponents() {
    if (!axialSlices) {
        axialSlices  = new SingleImageComponent(this, Slice::AXIAL, "Axial view", lut);
    }

    if (this->originalImageData->GetDataDimension() == 3) {
        if (!coronalSlices) {
            coronalSlices = new SingleImageComponent(this, Slice::CORONAL, "Coronal view", lut);
        }

        if (!sagittalSlices) {
            sagittalSlices = new SingleImageComponent(this, Slice::SAGITTAL, "Sagittal view", lut);
        }

        if (!arbitrarySlices) {
            // TODO implement arbitrary slice orientation
            arbitrarySlices = nullptr; // new SingleImageComponent(this, Slice::ARBITRARY, "Arbitrary view", lut);
        }

        if (volumeRenderingChild) {
            delete volumeRenderingChild;
        }

        // compute bounding box
        vtkSmartPointer<vtkPolyData> bbox = getBoundingBox();
        volumeRenderingChild = new MeshComponent(this, bbox, "Volume Rendering");
        volumeRenderingChild->setRenderingModes(InterfaceGeometry::Wireframe);
        volumeRenderingChild->setParentFrame(getFrame());
    }

    else {
        if (coronalSlices) {
            delete coronalSlices;
        }

        coronalSlices  = nullptr;

        if (sagittalSlices) {
            delete sagittalSlices;
        }

        sagittalSlices = nullptr;

        if (arbitrarySlices) {
            delete arbitrarySlices;
        }

        arbitrarySlices = nullptr;

        if (volumeRenderingChild) {
            delete volumeRenderingChild;
        }

        volumeRenderingChild = nullptr;
    }

    // Let there be slices...
    setProperty("Display Image in 3D Viewer", true);

}

// -------------------- updateImageComponents --------------------
void ImageComponent::updateImageComponents() {
    if (axialSlices) {
        axialSlices->setOriginalVolume(originalImageData);
    }

    if (coronalSlices) {
        coronalSlices->setOriginalVolume(originalImageData);
    }

    if (sagittalSlices) {
        sagittalSlices->setOriginalVolume(originalImageData);
    }

    if (arbitrarySlices) {
        arbitrarySlices->setOriginalVolume(originalImageData);
    }

}

// -------------------- pixelPicked --------------------
void ImageComponent::pixelPicked(double x, double y, double z, SingleImageComponent* whoIsAsking) {
    // x, y z are expressed in Real Image coordinates.
    currentPixelPicked[0] = x;
    currentPixelPicked[1] = y;
    currentPixelPicked[2] = z;

    // Get Voxel Index Corrdinates
    int i, j, k;
    getLastPixelPicked(&i, &j, &k);

    double wx, wy, wz;
    getLastPointPickedWorldCoords(&wx, &wy, &wz);

    // Update each child even the one who is asking i order to display correctly the pixel pixed.
    foreach (Component* dc, getChildren()) {
        SingleImageComponent* child = dynamic_cast<SingleImageComponent*>(dc);

        if (child) {
            child->setSlice(x, y, z);
        }
    }

    // Show picked pixel data in Selection property tab
    model->setData(model->index(0, 1), originalImageData->GetScalarComponentAsDouble(i, j, k, 0));
    model->setData(model->index(1, 1), i);
    model->setData(model->index(2, 1), j);
    model->setData(model->index(3, 1), k);
    model->setData(model->index(4, 1), x);
    model->setData(model->index(5, 1), y);
    model->setData(model->index(6, 1), z);
    model->setData(model->index(7, 1), wx);
    model->setData(model->index(8, 1), wy);
    model->setData(model->index(9, 1), wz);
    selectionView->setModel(model);

    // Select the PropertyExplorer's Selection tab
    setIndexOfPropertyExplorerTab(1);
}

// -------------------- getNumberOfColors --------------------
int ImageComponent::getNumberOfColors() const {
    double minColor = getMinColor();
    double maxColor = getMaxColor();
    int nbColors = (int)(maxColor - minColor + 1);
    return nbColors;
}

// -------------------- getMinColor --------------------
double ImageComponent::getMinColor() const {
    return (double) originalImageData->GetScalarTypeMin();
}

// -------------------- getMaxColor --------------------
double ImageComponent::getMaxColor() const {
    return (double)(originalImageData->GetScalarTypeMax());
}


// -------------------- getNumberOfColors --------------------
int ImageComponent::getActualNumberOfColors() const {
    int nbColors = (int)(originalImageData->GetScalarRange()[1] - originalImageData->GetScalarRange()[0] + 1);
    return nbColors;
}

// -------------------- getMinColor --------------------
double ImageComponent::getActualMinColor() const {
    return (double)(originalImageData->GetScalarRange()[0]);
}

// -------------------- getMaxColor --------------------
double ImageComponent::getActualMaxColor() const {
    return (double)(originalImageData->GetScalarRange()[1]);
}

// -------------------- getNumberOfSlices --------------------
int ImageComponent::getNumberOfSlices() const {
    return this->axialSlices->getNumberOfSlices();
}

// -------------------- setLut --------------------
void ImageComponent::setLut(vtkSmartPointer<vtkWindowLevelLookupTable> lookupTable) {
    lut = lookupTable;
}

// -------------------- getLut --------------------
vtkSmartPointer<vtkWindowLevelLookupTable>  ImageComponent::getLut() {
    return this->lut;
}

// -------------------- setSelected --------------------
void ImageComponent::setSelected(const bool b, const bool) {
    foreach (Component* dc, getChildren()) {
        SingleImageComponent* child = dynamic_cast<SingleImageComponent*>(dc);

        if (child) {
            child->singleImageSelected(b);
        }
    }

    // do that only in the end, so that last selected will be this manager Component
    Component::setSelected(b, false);
}

// -------------------- getLastPixelPicked --------------------
void ImageComponent::getLastPixelPicked(int* x, int* y, int* z) {
    // Get Voxel Index Corrdinates
    double* spacing = originalImageData->GetSpacing();

    *x = rint(currentPixelPicked[0] / spacing[0]);
    *y = rint(currentPixelPicked[1] / spacing[1]);
    *z = rint(currentPixelPicked[2] / spacing[2]);
}

// -------------------- getLastPointPickedImageCoords --------------------
void ImageComponent::getLastPointPickedImageCoords(double* x, double* y, double* z) {
    *x = currentPixelPicked[0];
    *y = currentPixelPicked[1];
    *z = currentPixelPicked[2];
}

// -------------------- getLastPointPickedWorldCoords --------------------
void ImageComponent::getLastPointPickedWorldCoords(double* x, double* y, double* z) {
    // TODO Celine: Use Frame Service
    auto* wxyz = new double[3];
    getTransformFromWorld()->TransformPoint(currentPixelPicked, wxyz);

    *x = wxyz[0];
    *y = wxyz[1];
    *z = wxyz[2];

}

// -------------------- refresh --------------------
void ImageComponent::refresh() const {
    foreach (const Component* dc, childrenComponent) {
        dc->refresh();
    }
}

// -------------------- getBoundingBox --------------------
vtkSmartPointer<vtkPolyData> ImageComponent::getBoundingBox() {
    if (! originalImageData) {
        return NULL;
    }

    double* bounds = originalImageData->GetBounds();
    double x[8][3] = {{bounds[0], bounds[2], bounds[4]}, {bounds[1], bounds[2], bounds[4]},
        {bounds[1], bounds[3], bounds[4]}, {bounds[0], bounds[3], bounds[4]},
        {bounds[0], bounds[2], bounds[5]}, {bounds[1], bounds[2], bounds[5]},
        {bounds[1], bounds[3], bounds[5]}, {bounds[0], bounds[3], bounds[5]}
    };
    vtkIdType pts[6][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {0, 1, 5, 4},
        {1, 2, 6, 5}, {2, 3, 7, 6}, {3, 0, 4, 7}
    };
    vtkSmartPointer<vtkPolyData>    bbox    = vtkSmartPointer<vtkPolyData>::New();
    vtkSmartPointer<vtkPoints>      points  = vtkSmartPointer<vtkPoints>::New();
    vtkSmartPointer<vtkCellArray>   polys   = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkFloatArray>  scalars  = vtkSmartPointer<vtkFloatArray>::New();

    for (int i = 0; i < 8; i++) {
        points->InsertPoint(i, x[i]);
    }

    for (int i = 0; i < 6; i++) {
        polys->InsertNextCell(4, pts[i]);
    }

    bbox->SetPoints(points);
    bbox->SetPolys(polys);

    return bbox;
}

// -------------------- getNumberOfPropertyWidget --------------------
unsigned int ImageComponent::getNumberOfPropertyWidget() {
    return 1;
}

// -------------------- getPropertyWidgetAt --------------------
QWidget* ImageComponent::getPropertyWidgetAt(unsigned int i) {
    switch (i) {
        case 0:
            return selectionView;
            break;
        default:
            return nullptr;
    }
}

}
