/*****************************************************************************
* $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$
****************************************************************************/

// -- Core image component stuff
#include "ImageComponent.h"
#include "SingleImageComponent.h"
#include "ArbitrarySingleImageComponent.h"
#include "ImageOrientationHelper.h"
#include "Application.h"
#include "Transformation.h"
#include "TransformationManager.h"

// -- Core stuff
#include "MeshComponent.h"
#include "Property.h"
#include "Viewer.h"

#include "Log.h"

// -- vtk stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkColorTransferFunction.h>
#include <vtkVolumeProperty.h>
#include <vtkImageCast.h>
#include <vtkVolume.h>
#include "CamiTKReEnableWarnings"

#include <vtkUniformGrid.h>
#include <vtkTransformFilter.h>
#include <vtkImageReslice.h>
#include <vtkImageFlip.h>
#include <vtkUnstructuredGrid.h>
#include <vtkVoxel.h>
#include <vtkVertex.h>
#include <vtkActor.h>

#include <vtkPiecewiseFunction.h>

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

#include <vtkDataSetMapper.h>
#include <vtkProperty.h>


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

namespace camitk {
// -------------------- constructors --------------------
ImageComponent::ImageComponent(const QString& file, const QString& name) : Component(file, name) {
    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;
    cursorActor = nullptr;
    cursorActorPointSet = nullptr;

    // update main frame name
    frameOfReference->setName(getName() + " (main)");
    frameOfReference->setDescription("Main frame of image '" + getName() + "'");
    // ensure the ImageComponent has a path to world, this will also ensure that the data and arbitrary frames also have a path to world
    // but also that the default identity transformation is independent of the viewer refresh order
    TransformationManager::ensurePathToWorld(frameOfReference.get());
    // data frame and main transformation
    dataFrame = TransformationManager::addFrameOfReference(getName() + " (data)", "Data frame of image '" + getName() + "'");
    mainTransformation = TransformationManager::addTransformation(dataFrame, frameOfReference);
    additionalFrames.clear();
    additionalTransformations.clear();

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

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

    // Properties
    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(13, 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("Data coords: x"));
    model->setData(model->index(5, 0), QVariant("Data coords: y"));
    model->setData(model->index(6, 0), QVariant("Data coords: z"));
    model->setData(model->index(7, 0), QVariant("Main coords: x"));
    model->setData(model->index(8, 0), QVariant("Main coords: y"));
    model->setData(model->index(9, 0), QVariant("Main coords: z"));
    model->setData(model->index(10, 0), QVariant("World coords: x"));
    model->setData(model->index(11, 0), QVariant("World coords: y"));
    model->setData(model->index(12, 0), QVariant("World coords: z"));

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

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

    if (originalImageData != nullptr) {

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

    }
}

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

// -------------------- 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 shallow 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. \nCamiTK transforms the read image and display it in the RAI format. This initial orientation is therefore informative only."), "");
    orientationProperty->setReadOnly(true);
    addProperty(orientationProperty);
    // Save orientation information in the main FrameOfReference
    if (frameOfReference != nullptr) {
        frameOfReference->setAnatomicalOrientation(ImageOrientationHelper::getOrientationAsQString(initialOrientation));
    }

    // 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 translation * rotation * 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(initialTranslation->GetMatrix(), rotationMatrix, initialFrameMatrix);
    // TODO check that: we don't need to apply the transformation to RAI as the real orientation is stored in main frame
    //vtkMatrix4x4::Multiply4x4(initialFrameMatrix, orientationToRAIMatrix, initialFrameMatrix);
    initialFrameTransform->SetMatrix(initialFrameMatrix);

    originalImageData = inputImage;
    // Remove the translation from the image data, the Frames/Transformation manage this.
    originalImageData->SetOrigin(0.0, 0.0, 0.0);

    // 6. check if the transformation from data to main frame already exists
    std::shared_ptr<Transformation> mainTransfo = TransformationManager::getTransformationOwnership(getDataFrame(), getFrame());
    if (mainTransfo != nullptr) {
        TransformationManager::updateTransformation(getDataFrame(), getFrame(), initialFrameTransform);
        setMainTransformation(mainTransfo);
    }
    else {
        setMainTransformation(TransformationManager::addTransformation(getDataFrame(), getFrame(), initialFrameTransform));
    }

    // Clean smart pointers
    inputImage = nullptr;

    // Build default lookup table
    initLookupTable();

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

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

    // init the cursor
    initCursor();
}

// -------------------- initLookupTable --------------------
void ImageComponent::initLookupTable() {
    // TODO set in a better way information about the lut thanks to image data
    if (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;
    }
}

void ImageComponent::initCursor() {

    // create the cursor actor 3D geometry
    vtkSmartPointer<vtkPoints> cursorPoints = vtkSmartPointer<vtkPoints>::New();
    cursorPoints->SetNumberOfPoints(8 * 3);

    // create zero-positionned points
    for (int i = 0; i < cursorPoints->GetNumberOfPoints(); i++) {
        cursorPoints->InsertPoint(i, 0.0, 0.0, 0.0);
    }

    // Create a voxel cells (each branch)
    vtkSmartPointer<vtkVoxel> branchCell[3];
    cursorActorPointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
    cursorActorPointSet->Allocate(3);

    for (int cell = 0; cell < 3; cell++) {
        branchCell[cell] = vtkSmartPointer<vtkVoxel>::New();
        for (int i = 0; i < branchCell[cell]->GetNumberOfPoints(); i++) {
            branchCell[cell]->GetPointIds()->SetId(i, cell * branchCell[cell]->GetNumberOfPoints() + i);
        }
        cursorActorPointSet->InsertNextCell(branchCell[cell]->GetCellType(), branchCell[cell]->GetPointIds());
    }
    cursorActorPointSet->SetPoints(cursorPoints);

    // Create the corresponding mapper
    vtkSmartPointer<vtkDataSetMapper> cursorMapper = vtkSmartPointer<vtkDataSetMapper>::New();
    cursorMapper->SetInputData(cursorActorPointSet);
    // And the actor
    cursorActor = vtkSmartPointer<vtkActor>::New();
    cursorActor->SetMapper(cursorMapper);
    cursorActor->GetProperty()->SetAmbient(1.0);
    cursorActor->GetProperty()->SetDiffuse(1.0);
    cursorActor->GetProperty()->SetColor(1.0, 1.0, 0.0);
    cursorActor->GetProperty()->SetLineWidth(1.0);
    cursorActor->GetProperty()->SetRepresentationToWireframe();
    // The cursor cannot be picked
    cursorActor->PickableOff();
    // The cursor is not visible unless there is a picked pixel
    cursorActor->VisibilityOff();

    updateCursor();
}

void ImageComponent::updateCursor() {
    if (currentPixelPicked[0] < 0 || originalImageData == nullptr) { // No pixel was picked or there is no image
        cursorActor->VisibilityOff();
        return;
    }
    double* spacing = originalImageData->GetSpacing();
    double* bounds = originalImageData->GetBounds();
    double min[3 * 3], max[3 * 3];
    for (int cell = 0; cell < 3; cell++) {
        // 0->maxX, y-spacingY/2 -> y+spacingY/2, z-spacingZ/2 -> z+spacingZ/2 for axis 0...
        for (int axis = 0; axis < 3; axis++) {
            if (cell == axis) { // bounds is in data Frame
                min[cell * 3 + axis] = bounds[2 * axis];
                max[cell * 3 + axis] = bounds[2 * axis + 1];
            }
            else { // currentPixelPicked is in data Frame too
                min[cell * 3 + axis] = currentPixelPicked[axis] - 0.5 * spacing[axis];
                max[cell * 3 + axis] = currentPixelPicked[axis] + 0.5 * spacing[axis];
            }
        }
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 0, min[cell * 3], min[cell * 3 + 1], min[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 1, max[cell * 3], min[cell * 3 + 1], min[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 2, min[cell * 3], max[cell * 3 + 1], min[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 3, max[cell * 3], max[cell * 3 + 1], min[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 4, min[cell * 3], min[cell * 3 + 1], max[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 5, max[cell * 3], min[cell * 3 + 1], max[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 6, min[cell * 3], max[cell * 3 + 1], max[cell * 3 + 2]);
        cursorActorPointSet->GetPoints()->SetPoint(cell * 8 + 7, max[cell * 3], max[cell * 3 + 1], max[cell * 3 + 2]);
    }
    cursorActorPointSet->Modified();
    cursorActor->VisibilityOn();
}

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

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

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

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

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

    setImageData(anImageData, copy, initialOrientation);
}

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

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

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

// -------------------- getArbitrarySlices --------------------
ArbitrarySingleImageComponent* ImageComponent::getArbitrarySlices() {
    return arbitrarySlices;
}

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

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

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

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

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

// -------------------- setVisibility --------------------
void ImageComponent::setVisibility(QString viewerName, bool b) {
    if (viewerName == "3D Viewer") {
        // if setVisibility was called and the current state is different, update it
        if (property("Display Image in 3D Viewer").toBool() != b) {
            setProperty("Display Image in 3D Viewer", b);
        }
    }

    Component::setVisibility(viewerName, b);
}

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

    if (axialSlices != nullptr) {
        axialSlices->setVisibility("3D Viewer", viewIn3D);
    }

    if (coronalSlices != nullptr) {
        coronalSlices->setVisibility("3D Viewer", viewIn3D);
    }

    if (sagittalSlices != nullptr) {
        sagittalSlices->setVisibility("3D Viewer", viewIn3D);
    }

}

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

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

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

        if (arbitrarySlices == nullptr) {
            arbitrarySlices = new ArbitrarySingleImageComponent(this, "Arbitrary view", lut);
        }

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

        // compute bounding box
        vtkSmartPointer<vtkPolyData> bbox = getBoundingBox();
        volumeRenderingChild = new MeshComponent(this, bbox, "Volume Rendering");
        volumeRenderingChild->setRenderingModes(InterfaceGeometry::Wireframe);
        volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 0.0, 0.0, 0.0);
        // FIXME VolumeRendering bounding box should not be pickable - this does not work
        // volumeRenderingChild->getProp(0)->PickableOff();
        // Remove the subcomponent custom frame and use the data frame for the volume rendering
        volumeRenderingChild->setFrame(dataFrame);
    }
    else {
        if (coronalSlices != nullptr) {
            delete coronalSlices;
            coronalSlices = nullptr;
        }

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

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

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

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

}

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

    if (coronalSlices != nullptr) {
        coronalSlices->setOriginalVolume(originalImageData);
    }

    if (sagittalSlices != nullptr) {
        sagittalSlices->setOriginalVolume(originalImageData);
    }

    if (arbitrarySlices != nullptr) {
        arbitrarySlices->setOriginalVolume(originalImageData);
    }

}

// -------------------- pixelPicked --------------------
void ImageComponent::pixelPicked(double x, double y, double z) {
    // Do not allow picks outside the image bounding box
    double* bb = originalImageData->GetBounds();
    if (x < bb[0] || x > bb[1] || y < bb[2] || y > bb[3] || z < bb[4] || z > bb[5]) {
        CAMITK_TRACE(QString("pixel picked outside boundingbox (%1,%2,%3,%4,%5, %6) vs picked (%7,%8,%9)").arg(bb[0]).arg(bb[1]).arg(bb[2]).arg(bb[3]).arg(bb[4]).arg(bb[5]).arg(x).arg(y).arg(z))
        return;
    }

    // x, y z are expressed in data frame.
    currentPixelPicked[0] = x;
    currentPixelPicked[1] = y;
    currentPixelPicked[2] = z;

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

    // Set the point in data Frame to the middle of the selected voxel
    setLastPointPickedFromPixel(i, j, k);

    // Update the 3D cursor using mainFrame
    updateCursor();

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

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

        if (child != nullptr) {
            child->setSlice(currentPixelPicked[0], currentPixelPicked[1], currentPixelPicked[2]);
        }
    }

    // Compute coordinates in mainFrame
    double main_xyz[3] = {0, 0, 0};
    getMainTransformation()->getTransform()->TransformPoint(currentPixelPicked, main_xyz);

    // Show picked pixel data in Selection property tab
    model->setData(model->index(0, 1), originalImageData->GetScalarComponentAsDouble(i, j, k, 0)); // Pixel value
    model->setData(model->index(1, 1), i); // X pixel coordinate
    model->setData(model->index(2, 1), j); // Y pixel coordinate
    model->setData(model->index(3, 1), k); // Z pixel coordinate
    model->setData(model->index(4, 1), currentPixelPicked[0]); // X data Frame coordinate
    model->setData(model->index(5, 1), currentPixelPicked[1]); // Y data Frame coordinate
    model->setData(model->index(6, 1), currentPixelPicked[2]); // Z data Frame coordinate
    model->setData(model->index(7, 1), main_xyz[0]); // X mainFrame coordinate
    model->setData(model->index(8, 1), main_xyz[1]); // Y mainFrame coordinate
    model->setData(model->index(9, 1), main_xyz[2]); // Z mainFrame coordinate
    model->setData(model->index(10, 1), wx); // X VtkWorld coordinate
    model->setData(model->index(11, 1), wy); // Y VtkWorld coordinate
    model->setData(model->index(12, 1), wz); // Z VtkWorld coordinate
    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 axialSlices->getNumberOfSlices();
}

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

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

const vtkSmartPointer<vtkWindowLevelLookupTable> ImageComponent::getLut() const {
    return lut;
}

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

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

    // highlight bounding box
    if (volumeRenderingChild != nullptr) {
        // red
        if (b) {
            volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 1.0, 0.0, 0.0);
        }
        else {
            // back in black
            volumeRenderingChild->setActorColor(InterfaceGeometry::Wireframe, 0.0, 0.0, 0.0);
        }
    }

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

vtkSmartPointer<vtkActor> ImageComponent::get3DCursor() {
    return cursorActor;
}

// -------------------- getLastPixelPicked --------------------
void ImageComponent::getLastPixelPicked(int* x, int* y, int* z) {
    // Get Voxel Index Coordinates
    double* spacing = originalImageData->GetSpacing();
    *x = floor(0.5 + currentPixelPicked[0] / spacing[0]);
    *y = floor(0.5 + currentPixelPicked[1] / spacing[1]);
    *z = floor(0.5 + currentPixelPicked[2] / spacing[2]);
}

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

// -------------------- getLastPointPickedWorldCoords --------------------
void ImageComponent::getLastPointPickedWorldFrame(double* x, double* y, double* z) {
    double wxyz[3] = {0, 0, 0};

    auto transfo = TransformationManager::getTransformation(dataFrame.get(), TransformationManager::getWorldFrame());
    if (transfo != nullptr) {
        transfo->getTransform()->TransformPoint(currentPixelPicked, wxyz);
    }

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

// -------------------- setLastPointPickedFromPixel --------------------
void ImageComponent::setLastPointPickedFromPixel(int x, int y, int z) {
    // Get Voxel size
    double* spacing = originalImageData->GetSpacing();
    currentPixelPicked[0] = (double(x) + .05) * spacing[0]; // +0.5 to be centered in the voxel
    currentPixelPicked[1] = (double(y) + .05) * spacing[1];
    currentPixelPicked[2] = (double(z) + .05) * spacing[2];
}

// -------------------- refresh --------------------
void ImageComponent::refresh() {
    for (Component* child : childrenComponent) {
        child->refresh();
    }
}

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

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

// -------------------- setFramesAndTransformation --------------------
void ImageComponent::setFramesAndTransformation(const std::shared_ptr<FrameOfReference>& mainFrame, const std::shared_ptr<FrameOfReference>& dataFrame, const std::shared_ptr<Transformation>& mainTransformation) {
    Component::setFrame(mainFrame);
    this->dataFrame = dataFrame;
    this->mainTransformation = mainTransformation;
    // Set children's frames to our data frame
    for (Component* child : getChildren()) {
        child->setFrame(dataFrame);
    }
}

// -------------------- setFrame --------------------
void ImageComponent::setFrame(const std::shared_ptr<FrameOfReference>& frame) {
    // Must update the MainTransformation if the frame changes
    if (frame != frameOfReference) {
        // Check if there is already a transformation from data to the new main frame
        std::shared_ptr<Transformation> checkExistingTransformation = TransformationManager::getTransformationOwnership(getDataFrame(), frame.get());
        if (checkExistingTransformation != nullptr) {
            // The transformation from data to the new frame already exists, just use it as the new main transformation for the current image. This overwrites the previous main transformation matrix
            Component::setFrame(frame);
            setMainTransformation(checkExistingTransformation);
        }
        else {
            // Creates a new transformation, but keeps the original transformation matrix
            if (setMainTransformation(TransformationManager::addTransformation(getDataFrame(), frame.get(), getMainTransformation()->getMatrix()))) {
                Component::setFrame(frame);
            }
            else {
                CAMITK_ERROR(QString("Cannot modify the frame of image component '%1': a path between the given frame '%2' to the current data frame '%3' already exists and is a composition of two or more existing non-default transformation.").arg(getName()).arg(frame->getName()).arg(getDataFrame()->getName()))
            }
        }
    }
}

// -------------------- setFrameFrom --------------------
void ImageComponent::setFrameFrom(const InterfaceFrame* from) {
    const ImageComponent* fromImageComponent = dynamic_cast<const ImageComponent*>(from);
    if (fromImageComponent != nullptr) {
        setFramesAndTransformation(fromImageComponent->frameOfReference, fromImageComponent->dataFrame, fromImageComponent->mainTransformation);
    }
    else {
        Component::setFrameFrom(from);
    }
}

// -------------------- resetFrame --------------------
void ImageComponent::resetFrame() {
    std::shared_ptr<FrameOfReference> newFrameOfReference = TransformationManager::addFrameOfReference(getName() + " (main)");
    std::shared_ptr<FrameOfReference> newDataFrame = TransformationManager::addFrameOfReference(getName() + " (data)", "Data frame for component '" + getName() + "'");
    std::shared_ptr<Transformation> newTransformation = TransformationManager::addTransformation(newDataFrame, newFrameOfReference, getMainTransformation()->getMatrix());
    setFramesAndTransformation(newFrameOfReference, newDataFrame, newTransformation);
}

// -------------------- setMainTransformation --------------------
bool ImageComponent::setMainTransformation(const std::shared_ptr<Transformation>& tr) {
    if (tr == nullptr) {
        return false;
    }
    else {
        mainTransformation = tr;
        return true;
    }
}

// -------------------- getAllFrames --------------------
QMultiMap<const FrameOfReference*, Component*> ImageComponent::getAllFrames(bool includeChildrenFrames) {
    QMultiMap<const FrameOfReference*, Component*> allFrames = {{this->getFrame(), this}, {this->getDataFrame(), this}};
    for (auto& fr : getAdditionalFrames()) {
        allFrames.insert(fr.get(), this);
    }
    if (includeChildrenFrames) {
        for (Component* child : getChildren()) {
            allFrames = child->getAllFrames() + allFrames;
        }
    }
    return allFrames;
}

// -------------------- getAllTransformations --------------------
QMultiMap<const Transformation*, Component*> ImageComponent::getAllTransformations(bool includeChildrenTransformations) {
    QMultiMap<const Transformation*, Component*> allTransformations{{getMainTransformation(), this}};
    for (auto& tr : getAdditionalTransformations()) {
        allTransformations.insert(tr.get(), this);
    }
    if (includeChildrenTransformations) {
        for (Component* child : getChildren()) {
            allTransformations = child->getAllTransformations() + allTransformations;
        }
    }
    return allTransformations;
}

// -------------------- updateMainTransformation --------------------
void ImageComponent::updateMainTransformation(vtkSmartPointer<vtkMatrix4x4> newMatrix) {
    TransformationManager::updateTransformation(mainTransformation.get(), newMatrix.Get());
}

// -------------------- toVariant --------------------
QVariant ImageComponent::toVariant() const {
    // Call standard component method
    QVariant variant = Component::toVariant();
    QVariantMap variantMap = variant.toMap();
    // Add other transformations and frames
    QList<QVariant> additionalFramesID;
    QList<QVariant> transformationsID;
    std::transform(additionalFrames.begin(), additionalFrames.end(), std::back_inserter(additionalFramesID), [](auto & fr) {
        return fr->getUuid();
    });
    std::transform(additionalTransformations.begin(), additionalTransformations.end(), std::back_inserter(transformationsID), [](auto & tr) {
        return tr->getUuid();
    });

    variantMap.insert("dataFrame", dataFrame->getUuid());
    variantMap.insert("mainTransformation", mainTransformation == nullptr ? QUuid() : mainTransformation->getUuid());
    variantMap.insert("additionalFrames", additionalFramesID);
    variantMap.insert("additionalTransformations", transformationsID);

    // Add LUT data
    const auto lut = getLut();
    if (lut != nullptr) {
        double minColor[4], maxColor[4], firstColor[4], lastColor[4];
        lut->GetMinimumTableValue(minColor);
        lut->GetMaximumTableValue(maxColor);
        lut->GetTableValue(0, firstColor);
        lut->GetTableValue(lut->GetNumberOfTableValues() - 1, lastColor);

        // set window level and width according to user set up
        QVariantMap lutVariant {
            {"level", QVariant(lut->GetLevel())},
            {"window", QVariant(lut->GetWindow())},
            {"numberColorValues", QVariant(lut->GetNumberOfTableValues())},
            {"minimumTableValue", QVariant(QList<QVariant>({minColor[0], minColor[1], minColor[2], minColor[3]}))},
            {"maximumTableValue", QVariant(QList<QVariant>({maxColor[0], maxColor[1], maxColor[2], maxColor[3]}))},
            {"firstValueAlpha", QVariant(firstColor[3])},
            {"lastValueAlpha", QVariant(lastColor[3])}
        };

        variantMap.insert("LUT", lutVariant);
    }

    // Add frame data for subcomponent Arbitrary
    if (arbitrarySlices != nullptr) {
        // Arbitrary manages its own frame
        variantMap.insert("arbitraryFrame", arbitrarySlices->getArbitraryFrame()->getUuid());
    }
    return variantMap;
}

// -------------------- fromVariant --------------------
void ImageComponent::fromVariant(const QVariant& variant) {
    QVariantMap newValuesMap = variant.toMap();

    std::shared_ptr<FrameOfReference> mainFrame = TransformationManager::getFrameOfReferenceOwnership(newValuesMap.value("frame").toUuid());
    if (mainFrame == nullptr) {
        CAMITK_WARNING_ALT(QString("Could not load main frame for Image Component '%1'").arg(getName()))
    }

    std::shared_ptr<FrameOfReference> dataFrame = nullptr;
    // If there is data Frame in the variant, get it, otherwise just keep the default Frame
    if (newValuesMap.contains("dataFrame")) {
        dataFrame = TransformationManager::getFrameOfReferenceOwnership(newValuesMap.value("dataFrame").toUuid());
        if (dataFrame == nullptr) {
            CAMITK_WARNING_ALT(QString("Could not load data frame for Image Component '%1'").arg(getName()))
        }
    }

    std::shared_ptr<FrameOfReference> fr;
    if (newValuesMap.contains("additionalFrames")) {
        additionalFrames.clear();
        for (const auto& otherFr : newValuesMap.value("additionalFrames").toList()) {
            fr = TransformationManager::getFrameOfReferenceOwnership(otherFr.toUuid());
            if (fr != nullptr) {
                additionalFrames.push_back(fr);
            }
        }
    }

    std::shared_ptr<Transformation> mainTransformation = nullptr;
    if (newValuesMap.contains("mainTransformation")) {
        mainTransformation = TransformationManager::getTransformationOwnership(newValuesMap.value("mainTransformation").toUuid());
        if (mainTransformation == nullptr) {
            CAMITK_WARNING_ALT(QString("Could not load main transformation for Image Component '%1'").arg(getName()))
        }
    }

    std::shared_ptr<Transformation> tr;
    if (newValuesMap.contains("additionalTransformations")) {
        additionalTransformations.clear();
        for (const auto& trId : newValuesMap.value("additionalTransformations").toList()) {
            tr = TransformationManager::getTransformationOwnership(trId.toUuid());
            if (tr != nullptr) {
                additionalTransformations.push_back(tr);
            }
        }
    }

    // set up whole frame/transformation system
    if (dataFrame != nullptr && mainTransformation != nullptr) {
        setFramesAndTransformation(mainFrame, dataFrame, mainTransformation);
    }

    if (newValuesMap.contains("LUT")) {
        auto lut = getLut();
        if (lut != nullptr) {
            QVariantMap lutVariant = newValuesMap.value("LUT").toMap();
            lut->SetLevel(lutVariant.value("level").toDouble());
            lut->SetWindow(lutVariant.value("window").toDouble());
            lut->SetNumberOfTableValues(lutVariant.value("numberColorValues").toInt());
            QList<QVariant> minimumTableValue = lutVariant.value("minimumTableValue").toList();
            lut->SetMinimumTableValue(minimumTableValue[0].toDouble(), minimumTableValue[1].toDouble(), minimumTableValue[2].toDouble(), minimumTableValue[3].toDouble());
            QList<QVariant> maximumTableValue = lutVariant.value("maximumTableValue").toList();
            lut->SetMaximumTableValue(maximumTableValue[0].toDouble(), maximumTableValue[1].toDouble(), maximumTableValue[2].toDouble(), maximumTableValue[3].toDouble());
            lut->Build();
            // In ImageLutWidget::applyLUT there is first a SetMaximumTableValue with alpha = 1.0, then build, then setting r,g,b,a with SetTableValue
            lut->SetTableValue(0, minimumTableValue[0].toDouble(), minimumTableValue[1].toDouble(), minimumTableValue[2].toDouble(), lutVariant.value("firstValueAlpha").toDouble());
            lut->SetTableValue(lutVariant.value("numberColorValues").toInt() - 1, maximumTableValue[0].toDouble(), maximumTableValue[1].toDouble(), maximumTableValue[2].toDouble(), lutVariant.value("lastValueAlpha").toDouble());
        }
        else {
            CAMITK_ERROR(QString("Current Image Component '%1' does not have a LUT, cannot load its values").arg(getName()))
        }
    }

    // Read the frame and transformation from the arbitrary
    if (newValuesMap.contains("arbitraryFrame") && arbitrarySlices != nullptr) {
        auto arbitraryFrame = TransformationManager::getFrameOfReferenceOwnership(newValuesMap.value("arbitraryFrame").toUuid());
        arbitrarySlices->initArbitraryTransformation(arbitraryFrame,
                dataFrame,
                TransformationManager::getTransformationOwnership(arbitraryFrame.get(), dataFrame.get()));
        // update internal property values to reflect the loaded matrix
        arbitrarySlices->updatePropertyFromTransformation();
    }

    // load all the properties (the frame should not change)
    Component::fromVariant(variant);
}

} // namespace camitk