/*****************************************************************************
 * $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 stuff
#include "Slice.h"

#include <Log.h>

// -- vtk stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkViewport.h>
#include <vtkProperty.h>
#include <vtkImageMapper3D.h>
#include <vtkDataSetMapper.h>
#include "CamiTKReEnableWarnings"

#include <vtkQuad.h>
#include <vtkImageMapToColors.h>
#include <vtkTrivialProducer.h>
#include <vtkImageChangeInformation.h>

namespace camitk {
// -------------------- constructor --------------------
Slice::Slice(vtkSmartPointer<vtkImageData> volume, SliceOrientation orientation, vtkSmartPointer<vtkWindowLevelLookupTable> lookupTable) : lut{lookupTable} {
    sliceOrientation = orientation;
    init();
    setOriginalVolume(volume);
    setSlice(getNumberOfSlices() / 2);
}


// -------------------- Destructor --------------------
Slice::~Slice() {
}

// -------------------- init --------------------
void Slice::init() {
    currentSliceIndex = 0;

    for (int i = 0; i < 3; i++) {
        originalSpacing[i] = 1.0;
    }

    originalVolume = nullptr;
    image3DActor = nullptr;
    image2DActor  = nullptr;
    pickPlaneActor = nullptr;
    pickPlaneActorPointSet = nullptr;
    pixelActor = nullptr;
    pixelActorPointSet = nullptr;
    image2DReslicer = nullptr;
}

// -------------------- getImageData --------------------
vtkSmartPointer<vtkImageData> Slice::getImageData() const {
    return originalVolume;
}

// -------------------- setOriginalVolume --------------------
void Slice::setOriginalVolume(vtkSmartPointer<vtkImageData> volume) {
    // If there were already a referenced volume,
    //  de-reference the smart pointer.
    originalVolume = volume;

    // Get the original volume information
    // Original volume dimensions in number of voxels (x, y and z)
    int originalDimensions[3];
    originalVolume->GetDimensions(originalDimensions);
    originalVolume->GetSpacing(originalSpacing);

    for (int i = 0; i < 3; i++) {
        // compute original size (nb of slice * spacing)
        originalSize[i] = originalDimensions[i] * originalSpacing[i];

        // As originalSpacing[i] will be used to get the slice number
        // for 2D images, lets replace 0.0 by 1.0 to avoid division by 0
        if (originalSpacing[i] == 0.0) {
            originalSpacing[i] = 1.0;
        }
    }

    // Prepare all the visualization pipeline
    initActors();
}

// -------------------- setArbitraryTransform --------------------
void Slice::setArbitraryTransform(vtkSmartPointer<vtkTransform> transform) {
    image2DReslicer->SetResliceTransform(transform);
    image2DReslicer->Update();
}

// -------------------- pixelPicked --------------------
void Slice::pixelPicked(double x, double y, double z) {
    return;
}

// -------------------- reslicedToVolumeCoords --------------------
void Slice::reslicedToVolumeCoords(const double* ijk, double* xyz) {
    xyz[0] = ijk[0] * originalSpacing[0];
    xyz[1] = ijk[1] * originalSpacing[1];
    xyz[2] = ijk[2] * originalSpacing[2];
}

// -------------------- volumeToReslicedCoords --------------------
void Slice::volumeToReslicedCoords(const double* xyz, double* ijk) {
    ijk[0] = xyz[0] / originalSpacing[0];
    ijk[1] = xyz[1] / originalSpacing[1];
    ijk[2] = xyz[2] / originalSpacing[2];
}

// -------------------- getNumberOfSlices --------------------
int Slice::getNumberOfSlices() const {
    // Array containing first and last indices of the image in each direction
    // 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z
    int extent[6];
    originalVolume->GetExtent(extent);

    int nbSlices;
    switch (sliceOrientation) {
        case AXIAL:
        case AXIAL_NEURO:
            nbSlices = extent[5] - extent[4] + 1;
            break;
        case CORONAL:
            nbSlices = extent[3] - extent[2] + 1;
            break;
        case SAGITTAL:
            nbSlices = extent[1] - extent[0] + 1;
            break;
        default:
            nbSlices = 0;
            break;
    }

    return nbSlices;
}

// -------------------- getSlice --------------------
int Slice::getSlice() const {
    return currentSliceIndex;
}

// -------------------- setSlice --------------------
void Slice::setSlice(int s) {
    // Check if s is inside the volume bounds
    if (s < 0) {
        currentSliceIndex = 0;
    }
    else {
        if (s >= getNumberOfSlices()) {
            currentSliceIndex = getNumberOfSlices() - 1;
        }
        else {
            currentSliceIndex = s;
        }
    }

    // Array containing first and last indices of the image in each direction
    // 0 & 1 -> x; 2 and 3 -> y; 4 & 5 -> z
    int extent[6];
    originalVolume->GetExtent(extent);

    switch (sliceOrientation) {
        case AXIAL:
        case AXIAL_NEURO:
            image3DActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], currentSliceIndex, currentSliceIndex);
            image2DActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], currentSliceIndex, currentSliceIndex);
            break;
        case CORONAL:
            image3DActor->SetDisplayExtent(extent[0], extent[1], currentSliceIndex, currentSliceIndex, extent[4], extent[5]);
            image2DActor->SetDisplayExtent(extent[0], extent[1], currentSliceIndex, currentSliceIndex, extent[4], extent[5]);
            break;
        case SAGITTAL:
            image3DActor->SetDisplayExtent(currentSliceIndex, currentSliceIndex, extent[2], extent[3], extent[4], extent[5]);
            image2DActor->SetDisplayExtent(currentSliceIndex, currentSliceIndex, extent[2], extent[3], extent[4], extent[5]);
            break;
        default:
            break;
    }

    updatePickPlane();

    // hide pixel actor for now (it should be visible only when user Ctrl-Click on the image
    pixelActor->VisibilityOff();
}


// -------------------- setSlice --------------------
void Slice::setSlice(double x, double y, double z) {
    // At this point, coordinates are expressed in the data Frame coordinate system
    double ijk[3] = {0.0, 0.0, 0.0};
    double xyz[3] = {x, y, z};

    // Tranform real coordinates to index coordinates
    volumeToReslicedCoords(xyz, ijk);

    switch (sliceOrientation) {
        case AXIAL:
        case AXIAL_NEURO:
            setSlice(rint(ijk[2]));
            break;
        case CORONAL:
            setSlice(rint(ijk[1]));
            break;
        case SAGITTAL:
            setSlice(rint(ijk[0]));
            break;
        default:
            break;
    }

    // Set pixel position in current slice.
    setPixelRealPosition(x, y, z);
}

// -------------------- getNumberOfColors --------------------
int Slice::getNumberOfColors() const {
    return originalVolume->GetScalarTypeMax() - originalVolume->GetScalarTypeMin();
}

// -------------------- setPixelRealPosition --------------------
void Slice::setPixelRealPosition(double x, double y, double z) {
    // update and force visibility of the pixel actor
    updatePixelActor(x, y, z);
    pixelActor->VisibilityOn();
}

// -------------------- get2DImageActor --------------------
vtkSmartPointer<vtkImageActor> Slice::get2DImageActor() const {
    return image2DActor;
}

// -------------------- get3DImageActor --------------------
vtkSmartPointer<vtkImageActor> Slice::get3DImageActor() const {
    return image3DActor;
}

// -------------------- getPixelActor --------------------
vtkSmartPointer<vtkActor> Slice::getPixelActor() {
    return pixelActor;
}

// -------------------- getPickPlaneActor --------------------
vtkSmartPointer<vtkActor> Slice::getPickPlaneActor() const {
    return pickPlaneActor;
}

// -------------------- initActors --------------------
void Slice::initActors() {
    // Add LUT color map filter if grayscale image or nothing if color image
    vtkAlgorithmOutput* outputPort = nullptr;
    vtkSmartPointer<vtkImageMapToColors> imgToMapFilter; // for grayscale
    vtkSmartPointer<vtkTrivialProducer> virtualFilter;   // for color image, not filter required

    // set the lookupTable for grayscale images
    if (originalVolume->GetNumberOfScalarComponents() == 1) {
        imgToMapFilter = vtkSmartPointer<vtkImageMapToColors>::New();
        imgToMapFilter->SetInputData(originalVolume);
        imgToMapFilter->SetLookupTable(lut);
        outputPort = imgToMapFilter->GetOutputPort();
    }
    else {
        // Do not use lut, create a virtual filter to obtain an output port with no modification
        virtualFilter = vtkSmartPointer<vtkTrivialProducer>::New();
        virtualFilter->SetOutput(originalVolume);
        virtualFilter->Update();
        outputPort = virtualFilter->GetOutputPort();  // Or wrap as algorithm if needed
    }

    // reslicer for the arbitrary slice
    image2DReslicer = vtkSmartPointer<vtkImageReslice>::New();
    image2DReslicer->SetInputConnection(outputPort);
    // 2D output
    image2DReslicer->SetOutputDimensionality(2);
    // generate image wherever there is a input voxel (and therefore modify the image dimensions if possible)
    image2DReslicer->AutoCropOutputOn();

    // for the 2D and 3D image actors it is either directly plugged to the output of imgToMapFilter
    // (if this is a perpendicular orientation) or to the reslicer (if this is an arbitrary orientation)
    image3DActor = vtkSmartPointer<vtkImageActor>::New();
    image3DActor->InterpolateOn();

    image2DActor = vtkSmartPointer<vtkImageActor>::New();
    image2DActor->InterpolateOn();

    if (sliceOrientation == ARBITRARY) {
        image3DActor->GetMapper()->SetInputConnection(image2DReslicer->GetOutputPort());
        image2DActor->GetMapper()->SetInputConnection(image2DReslicer->GetOutputPort());
    }
    else {
        image3DActor->GetMapper()->SetInputConnection(outputPort);
        image2DActor->GetMapper()->SetInputConnection(outputPort);
    }

    // Picked plane
    initPickPlaneActor();
    updatePickPlane();

    // Picked pixel
    initPixelActor();
    updatePixelActor();
}

// -------------------- initPickPlaneActor --------------------
void Slice::initPickPlaneActor() {
    // create the picked plane actor 3D geometry
    vtkSmartPointer<vtkPoints> planePoints = vtkSmartPointer<vtkPoints>::New();
    planePoints->SetNumberOfPoints(8);

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

    // vtkQuad is defined by the four points (0,1,2,3) in counterclockwise order.
    vtkSmartPointer<vtkQuad> leftQuad = vtkSmartPointer<vtkQuad>::New();
    leftQuad->GetPointIds()->SetId(0, 0);
    leftQuad->GetPointIds()->SetId(1, 1);
    leftQuad->GetPointIds()->SetId(2, 2);
    leftQuad->GetPointIds()->SetId(3, 3);

    vtkSmartPointer<vtkQuad> topQuad = vtkSmartPointer<vtkQuad>::New();
    topQuad->GetPointIds()->SetId(0, 4);
    topQuad->GetPointIds()->SetId(1, 5);
    topQuad->GetPointIds()->SetId(2, 1);
    topQuad->GetPointIds()->SetId(3, 0);

    vtkSmartPointer<vtkQuad> rightQuad = vtkSmartPointer<vtkQuad>::New();
    rightQuad->GetPointIds()->SetId(0, 5);
    rightQuad->GetPointIds()->SetId(1, 4);
    rightQuad->GetPointIds()->SetId(2, 7);
    rightQuad->GetPointIds()->SetId(3, 6);

    vtkSmartPointer<vtkQuad> bottomQuad = vtkSmartPointer<vtkQuad>::New();
    bottomQuad->GetPointIds()->SetId(0, 3);
    bottomQuad->GetPointIds()->SetId(1, 2);
    bottomQuad->GetPointIds()->SetId(2, 6);
    bottomQuad->GetPointIds()->SetId(3, 7);

    // Create the unstructured grid that includes the two quads
    pickPlaneActorPointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
    pickPlaneActorPointSet->Allocate(4);
    pickPlaneActorPointSet->InsertNextCell(leftQuad->GetCellType(), leftQuad->GetPointIds());
    pickPlaneActorPointSet->InsertNextCell(topQuad->GetCellType(), topQuad->GetPointIds());
    pickPlaneActorPointSet->InsertNextCell(rightQuad->GetCellType(), rightQuad->GetPointIds());
    pickPlaneActorPointSet->InsertNextCell(bottomQuad->GetCellType(), bottomQuad->GetPointIds());
    pickPlaneActorPointSet->SetPoints(planePoints);

    // Create the corresponding mapper
    vtkSmartPointer<vtkDataSetMapper> pickPlaneMapper = vtkSmartPointer<vtkDataSetMapper>::New();
    pickPlaneMapper->SetInputData(pickPlaneActorPointSet);

    // instantiate the actor
    pickPlaneActor = vtkSmartPointer<vtkActor>::New();
    pickPlaneActor->SetMapper(pickPlaneMapper);
    pickPlaneActor->GetProperty()->SetAmbient(1.0);
    pickPlaneActor->GetProperty()->SetDiffuse(1.0);

    // Update pixel actor properties
    switch (sliceOrientation) {
        case ARBITRARY:
            pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 0.0);
            break;
        case AXIAL_NEURO:
            pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
            break;
        case AXIAL:
            pickPlaneActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
            break;
        case CORONAL:
            pickPlaneActor->GetProperty()->SetColor(0.0, 1.0, 0.0);
            break;
        case SAGITTAL:
            pickPlaneActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
            break;
        default:
            pickPlaneActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
            break;
    }
    pickPlaneActor->GetProperty()->SetLineWidth(1.0);
    pickPlaneActor->GetProperty()->SetRepresentationToWireframe();

    //-- pickPlaneActor can not be picked
    pickPlaneActor->PickableOff();

    // by default, the plane actor is always visible
    pickPlaneActor->VisibilityOn();
}

// -------------------- initPixelActor --------------------
void Slice::initPixelActor() {
    // create the pixel actor 3D geometry
    vtkSmartPointer<vtkPoints> pixelPoints = vtkSmartPointer<vtkPoints>::New();
    pixelPoints->SetNumberOfPoints(8);

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

    // vtkQuad is defined by the four points (0,1,2,3) in counterclockwise order.
    vtkSmartPointer<vtkQuad> verticalQuad = vtkSmartPointer<vtkQuad>::New();
    verticalQuad->GetPointIds()->SetId(0, 0);
    verticalQuad->GetPointIds()->SetId(1, 1);
    verticalQuad->GetPointIds()->SetId(2, 2);
    verticalQuad->GetPointIds()->SetId(3, 3);

    vtkSmartPointer<vtkQuad> horizontalQuad = vtkSmartPointer<vtkQuad>::New();
    horizontalQuad->GetPointIds()->SetId(0, 4);
    horizontalQuad->GetPointIds()->SetId(1, 5);
    horizontalQuad->GetPointIds()->SetId(2, 6);
    horizontalQuad->GetPointIds()->SetId(3, 7);

    // Create the unstructured grid that includes the two quads
    pixelActorPointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
    pixelActorPointSet->Allocate(2);
    pixelActorPointSet->InsertNextCell(horizontalQuad->GetCellType(), horizontalQuad->GetPointIds());
    pixelActorPointSet->InsertNextCell(verticalQuad->GetCellType(), verticalQuad->GetPointIds());
    pixelActorPointSet->SetPoints(pixelPoints);

    // Create the corresponding mapper
    vtkSmartPointer<vtkDataSetMapper> pixelMapper = vtkSmartPointer<vtkDataSetMapper>::New();
    pixelMapper->SetInputData(pixelActorPointSet);

    // instantiate the actor
    pixelActor = vtkSmartPointer<vtkActor>::New();
    pixelActor->SetMapper(pixelMapper);
    pixelActor->GetProperty()->SetAmbient(1.0);
    pixelActor->GetProperty()->SetDiffuse(1.0);

    // Update pixel actor properties
    switch (sliceOrientation) {
        case ARBITRARY:
            pixelActor->GetProperty()->SetColor(1.0, 1.0, 0.0);
            break;
        case AXIAL_NEURO:
            pixelActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
            break;
        case AXIAL:
            pixelActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
            break;
        case CORONAL:
            pixelActor->GetProperty()->SetColor(0.0, 1.0, 0.0);
            break;
        case SAGITTAL:
            pixelActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
            break;
        default:
            pixelActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
            break;
    }
    pixelActor->GetProperty()->SetLineWidth(1.0);
    pixelActor->GetProperty()->SetRepresentationToWireframe();

    //-- pixelActor can not be picked
    pixelActor->PickableOff();

    // by default, the pixel actor is not visible (it should be only visible when user Ctrl-Click on the image)
    pixelActor->VisibilityOff();
}

// -------------------- updatePickPlane --------------------
void Slice::updatePickPlane() {
    // Be careful, the center of the first voxel (0,0,0) is displayed at coordinates (0.0, 0.0, 0.0).
    // Pixels within borders are represented (in 2D) only by their half, quarter or eighth depending on their coordinates.
    // see also bug #65 "Unexpected image behaviors in Viewers"

    // update the point positions - become the bounds come from the Actor, they are expressed in worldFrame
    double bounds[6];
    image2DActor->GetBounds(bounds);

    // If an axis has 0 thickness, set the bounds around it to slice thickness (-thickness/2, +thickness/2)
    for (int axis = 0; axis < 3; ++axis) {
        if (bounds[2 * axis] == bounds[2 * axis + 1]) {
            bounds[2 * axis] -= originalSpacing[axis] / 2.0;
            bounds[2 * axis + 1] += originalSpacing[axis] / 2.0;
        }
    }
    pickPlaneActorPointSet->GetPoints()->SetPoint(0, bounds[0], bounds[2], bounds[4]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(1, bounds[0], bounds[2], bounds[5]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(2, bounds[0], bounds[3], bounds[5]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(3, bounds[0], bounds[3], bounds[4]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(4, bounds[1], bounds[2], bounds[4]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(5, bounds[1], bounds[2], bounds[5]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(6, bounds[1], bounds[3], bounds[5]);
    pickPlaneActorPointSet->GetPoints()->SetPoint(7, bounds[1], bounds[3], bounds[4]);
    // Needed to notify the vtk pipeline of the change in the geometry (and therefore update the actor)
    pickPlaneActorPointSet->Modified();
}

// ----------------- updatePixelActorPosition -----------------
void Slice::updatePixelActor() {
    double xMin = 0.0;
    double xMax = originalSize[0];
    double yMin = 0.0;
    double yMax = originalSize[1];
    double zMin = 0.0;
    double zMax = originalSize[2];
    // Draw the bounding box around the center of the slice
    updatePixelActor(xMin + (xMax - xMin) / 2,  yMin + (yMax - yMin) / 2, zMin + (zMax - zMin) / 2);
}

void Slice::updatePixelActor(double x, double y, double z) {
    // x,y,z are in data Frame, bounds are in worldFrame
    double bounds[6];
    image2DActor->GetBounds(bounds);

    double sliceHalfThickness;
    int perpendicularAxis = 0;
    // If an axis has 0 thickness, set the bounds around it to slice thickness (-thickness/2, +thickness/2)
    for (int axis = 0; axis < 3; ++axis) {
        if (bounds[2 * axis] == bounds[2 * axis + 1]) {
            perpendicularAxis = axis;
            bounds[2 * axis] -= originalSpacing[axis] / 2.0;
            bounds[2 * axis + 1] += originalSpacing[axis] / 2.0;
        }
    }

    // update the point positions
    switch (sliceOrientation) {
        case AXIAL_NEURO:
        case AXIAL:
            sliceHalfThickness = originalSpacing[2] / 2.0;
            // vertical quad
            pixelActorPointSet->GetPoints()->SetPoint(0, x, 0.0, z - sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(1, x, 0.0, z + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(2, x, originalSize[1], z + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(3, x, originalSize[1], z - sliceHalfThickness);
            // horizontal quad
            pixelActorPointSet->GetPoints()->SetPoint(4, 0.0, y, z - sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(5, 0.0, y, z + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(6, originalSize[0], y, z + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(7, originalSize[0], y, z - sliceHalfThickness);
            break;

        case CORONAL:
            sliceHalfThickness = originalSpacing[1] / 2.0;
            // vertical quad
            pixelActorPointSet->GetPoints()->SetPoint(0, x, y - sliceHalfThickness, originalSize[2]);
            pixelActorPointSet->GetPoints()->SetPoint(1, x, y + sliceHalfThickness, originalSize[2]);
            pixelActorPointSet->GetPoints()->SetPoint(2, x, y + sliceHalfThickness, 0.0);
            pixelActorPointSet->GetPoints()->SetPoint(3, x, y - sliceHalfThickness, 0.0);
            // horizontal quad
            pixelActorPointSet->GetPoints()->SetPoint(4, 0.0, y - sliceHalfThickness, z);
            pixelActorPointSet->GetPoints()->SetPoint(5, 0.0, y + sliceHalfThickness, z);
            pixelActorPointSet->GetPoints()->SetPoint(6, originalSize[0], y + sliceHalfThickness, z);
            pixelActorPointSet->GetPoints()->SetPoint(7, originalSize[0], y - sliceHalfThickness, z);
            break;

        case SAGITTAL:
            sliceHalfThickness = originalSpacing[0] / 2.0;
            // vertical quad
            pixelActorPointSet->GetPoints()->SetPoint(0, x - sliceHalfThickness, y, originalSize[2]);
            pixelActorPointSet->GetPoints()->SetPoint(1, x + sliceHalfThickness, y, originalSize[2]);
            pixelActorPointSet->GetPoints()->SetPoint(2, x + sliceHalfThickness, y, 0.0);
            pixelActorPointSet->GetPoints()->SetPoint(3, x - sliceHalfThickness, y, 0.0);
            // horizontal quad
            pixelActorPointSet->GetPoints()->SetPoint(4, x - sliceHalfThickness, 0.0, z);
            pixelActorPointSet->GetPoints()->SetPoint(5, x + sliceHalfThickness, 0.0, z);
            pixelActorPointSet->GetPoints()->SetPoint(6, x + sliceHalfThickness, originalSize[1], z);
            pixelActorPointSet->GetPoints()->SetPoint(7, x - sliceHalfThickness, originalSize[1], z);
            break;

        case ARBITRARY: {
            vtkSmartPointer<vtkMatrix4x4> T_P2L = vtkSmartPointer<vtkMatrix4x4>::New();
            if (image2DReslicer->GetResliceAxes() != nullptr) {
                T_P2L->DeepCopy(image2DReslicer->GetResliceAxes());
            }
            T_P2L->Invert();
            double pixel_P[4] = {x, y, z, 1.0};
            double pixel_L[4];
            T_P2L->MultiplyPoint(pixel_P, pixel_L);

            sliceHalfThickness = originalSpacing[2] / 2.0;

            image2DActor->GetBounds(bounds);
            // vertical quad
            pixelActorPointSet->GetPoints()->SetPoint(0, pixel_L[0], bounds[2], pixel_L[2] - sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(1, pixel_L[0], bounds[2], pixel_L[2] + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(2, pixel_L[0], bounds[3], pixel_L[2] + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(3, pixel_L[0], bounds[3], pixel_L[2] - sliceHalfThickness);
            // horizontal quad
            pixelActorPointSet->GetPoints()->SetPoint(4, bounds[0], pixel_L[1], pixel_L[2] - sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(5, bounds[0], pixel_L[1], pixel_L[2] + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(6, bounds[1], pixel_L[1], pixel_L[2] + sliceHalfThickness);
            pixelActorPointSet->GetPoints()->SetPoint(7, bounds[1], pixel_L[1], pixel_L[2] - sliceHalfThickness);
            break;
        }
        break;
        default:
            break;
    }
    // Needed to notify the vtk pipeline of the change in the geometry (and therefore update the actor)
    pixelActorPointSet->Modified();
}





//------------------------------- addProp ----------------------------------------
bool Slice::addProp(const QString& name, vtkSmartPointer< vtkProp > prop) {
    if (!extraProp.contains(name)) {
        extraProp.insert(name, prop);
        return true;
    }
    else {
        return false;
    }
}

//------------------------------- getProp ----------------------------------------
vtkSmartPointer< vtkProp > Slice::getProp(const QString& name) {
    if (extraProp.contains(name)) {
        return extraProp.value(name);
    }
    else {
        return nullptr;
    }
}

//------------------------------- getNumberOfProp ----------------------------------------
unsigned int Slice::getNumberOfProp() const {
    return extraProp.size();
}

//------------------------------- getProp ----------------------------------------
vtkSmartPointer< vtkProp > Slice::getProp(unsigned int index) {
    return extraProp.values().at(index);
}

//------------------------------- removeProp ----------------------------------------
bool Slice::removeProp(const QString& name) {
    if (extraProp.contains(name)) {
        // remove the prop from any renderer/consummers
        vtkSmartPointer<vtkProp> prop = extraProp.value(name);
        prop->VisibilityOff();

        for (int i = 0; i < prop->GetNumberOfConsumers(); i++) {
            vtkViewport* viewer = vtkViewport::SafeDownCast(prop->GetConsumer(i));

            if (viewer) {
                viewer->RemoveViewProp(prop);
            }
        }

        // remove it from the maps
        extraProp.remove(name);
        return true;
    }
    else {
        return false;
    }
}




}

