/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.vtk;

import icy.canvas.Layer;
import icy.image.colormap.IcyColorMap;
import icy.image.lut.LUT.LUTChannel;
import icy.math.Scaler;
import icy.painter.Overlay;
import icy.painter.VtkPainter;
import icy.roi.ROI;
import icy.type.DataType;
import icy.type.collection.array.Array2DUtil;
import icy.type.collection.array.ArrayUtil;
import icy.type.rectangle.Rectangle5D;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import plugins.kernel.canvas.VtkCanvas;
import vtk.vtkActor;
import vtk.vtkActor2D;
import vtk.vtkActor2DCollection;
import vtk.vtkActorCollection;
import vtk.vtkCellArray;
import vtk.vtkCollection;
import vtk.vtkColorTransferFunction;
import vtk.vtkContourFilter;
import vtk.vtkDataArray;
import vtk.vtkDoubleArray;
import vtk.vtkFloatArray;
import vtk.vtkIdTypeArray;
import vtk.vtkImageConstantPad;
import vtk.vtkImageData;
import vtk.vtkIntArray;
import vtk.vtkLongArray;
import vtk.vtkObject;
import vtk.vtkPiecewiseFunction;
import vtk.vtkPoints;
import vtk.vtkPolyData;
import vtk.vtkProp;
import vtk.vtkPropCollection;
import vtk.vtkRenderer;
import vtk.vtkShortArray;
import vtk.vtkUnsignedCharArray;
import vtk.vtkUnsignedIntArray;
import vtk.vtkUnsignedLongArray;
import vtk.vtkUnsignedShortArray;

/**
 * @author Stephane
 */
public class VtkUtil
{
    // VTK type
    public final static int VTK_VOID = 0;
    public final static int VTK_BIT = 1;
    public final static int VTK_CHAR = 2;
    public final static int VTK_SIGNED_CHAR = 15;
    public final static int VTK_UNSIGNED_CHAR = 3;
    public final static int VTK_SHORT = 4;
    public final static int VTK_UNSIGNED_SHORT = 5;
    public final static int VTK_INT = 6;
    public final static int VTK_UNSIGNED_INT = 7;
    public final static int VTK_LONG = 8;
    public final static int VTK_UNSIGNED_LONG = 9;
    public final static int VTK_FLOAT = 10;
    public final static int VTK_DOUBLE = 11;
    public final static int VTK_ID = 12;

    // VTK interpolation
    public final static int VTK_NEAREST_INTERPOLATION = 0;
    public final static int VTK_LINEAR_INTERPOLATION = 1;
    public final static int VTK_CUBIC_INTERPOLATION = 2;

    // VTK bounding box
    public final static int VTK_FLY_OUTER_EDGES = 0;
    public final static int VTK_FLY_CLOSEST_TRIAD = 1;
    public final static int VTK_FLY_FURTHEST_TRIAD = 2;
    public final static int VTK_FLY_STATIC_TRIAD = 3;
    public final static int VTK_FLY_STATIC_EDGES = 4;

    public final static int VTK_TICKS_INSIDE = 0;
    public final static int VTK_TICKS_OUTSIDE = 1;
    public final static int VTK_TICKS_BOTH = 2;

    public final static int VTK_GRID_LINES_ALL = 0;
    public final static int VTK_GRID_LINES_CLOSEST = 1;
    public final static int VTK_GRID_LINES_FURTHEST = 2;

    /**
     * Returns the VTK type corresponding to the specified DataType
     */
    public static int getVtkType(DataType type)
    {
        switch (type)
        {
            default:
            case UBYTE:
            case BYTE:
                return VTK_UNSIGNED_CHAR;

                // FIXME: signed char not supported by VTK java wrapper ??
                // case BYTE:
                // return VTK_CHAR;
                // return VTK_SIGNED_CHAR;

            case USHORT:
                return VTK_UNSIGNED_SHORT;
            case SHORT:
                return VTK_SHORT;
            case UINT:
                return VTK_UNSIGNED_INT;
            case INT:
                return VTK_INT;
            case ULONG:
                return VTK_UNSIGNED_LONG;
            case LONG:
                return VTK_LONG;
            case FLOAT:
                return VTK_FLOAT;
            case DOUBLE:
                return VTK_DOUBLE;
        }
    }

    public static vtkDataArray getVtkArray(Object array, boolean signed)
    {
        switch (ArrayUtil.getDataType(array))
        {
            case BYTE:
                return getUCharArray((byte[]) array);
            case SHORT:
                if (signed)
                    return getUShortArray((short[]) array);
                return getShortArray((short[]) array);
            case INT:
                if (signed)
                    return getUIntArray((int[]) array);
                return getIntArray((int[]) array);
            case LONG:
                if (signed)
                    return getULongArray((long[]) array);
                return getLongArray((long[]) array);
            case FLOAT:
                return getFloatArray((float[]) array);
            case DOUBLE:
                return getDoubleArray((double[]) array);
            default:
                return null;
        }
    }

    public static vtkUnsignedCharArray getUCharArray(byte[] array)
    {
        final vtkUnsignedCharArray result = new vtkUnsignedCharArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedShortArray getUShortArray(short[] array)
    {
        final vtkUnsignedShortArray result = new vtkUnsignedShortArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedIntArray getUIntArray(int[] array)
    {
        final vtkUnsignedIntArray result = new vtkUnsignedIntArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkUnsignedLongArray getULongArray(long[] array)
    {
        final vtkUnsignedLongArray result = new vtkUnsignedLongArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkShortArray getShortArray(short[] array)
    {
        final vtkShortArray result = new vtkShortArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkIntArray getIntArray(int[] array)
    {
        final vtkIntArray result = new vtkIntArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkLongArray getLongArray(long[] array)
    {
        final vtkLongArray result = new vtkLongArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkFloatArray getFloatArray(float[] array)
    {
        final vtkFloatArray result = new vtkFloatArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkDoubleArray getDoubleArray(double[] array)
    {
        final vtkDoubleArray result = new vtkDoubleArray();

        result.SetJavaArray(array);

        return result;
    }

    public static vtkIdTypeArray getIdTypeArray(int[] array)
    {
        final vtkIdTypeArray result = new vtkIdTypeArray();
        final vtkIntArray iarray = getIntArray(array);

        result.DeepCopy(iarray);

        return result;
    }

    public static int[] getArray(vtkIdTypeArray array)
    {
        final vtkIntArray iarray = new vtkIntArray();

        iarray.DeepCopy(array);

        return iarray.GetJavaArray();
    }

    /**
     * Transforms a vtkCollection to an array
     */
    public static vtkObject[] vtkCollectionToArray(vtkCollection collection)
    {
        final vtkObject[] result = new vtkObject[collection.GetNumberOfItems()];

        collection.InitTraversal();
        for (int i = 0; i < result.length; i++)
            result[i] = collection.GetNextItemAsObject();

        return result;
    }

    /**
     * Get vtkPoints from double[]
     */
    public static vtkPoints getPoints(double[] points)
    {
        final vtkPoints result = new vtkPoints();
        final vtkDoubleArray array = getDoubleArray(points);

        array.SetNumberOfComponents(3);
        result.SetData(array);

        return result;
    }

    /**
     * Get vtkPoints from double[][3]
     */
    public static vtkPoints getPoints(double[][] points)
    {
        return getPoints(Array2DUtil.toDoubleArray1D(points));
    }

    /**
     * Get vtkPoints from float[]
     */
    public static vtkPoints getPoints(float[] points)
    {
        final vtkPoints result = new vtkPoints();
        final vtkFloatArray array = getFloatArray(points);

        array.SetNumberOfComponents(3);
        result.SetData(array);

        return result;
    }

    /**
     * Get vtkPoints from float[][3]
     */
    public static vtkPoints getPoints(float[][] points)
    {
        return getPoints(Array2DUtil.toFloatArray1D(points));
    }

    /**
     * Get vtkPoints from int[]
     */
    public static vtkPoints getPoints(int[] points)
    {
        final vtkPoints result = new vtkPoints();
        final vtkIntArray array = getIntArray(points);

        array.SetNumberOfComponents(3);
        result.SetData(array);

        return result;
    }

    /**
     * Get vtkPoints from int[][3]
     */
    public static vtkPoints getPoints(int[][] points)
    {
        return getPoints(Array2DUtil.toIntArray1D(points));
    }

    /**
     * Get vtkCellArray from a 1D prepared cells array ( {n, i1, i2, ..., n, i1, i2,...} )
     */
    public static vtkCellArray getCells(int numCell, int[] cells)
    {
        final vtkCellArray result = new vtkCellArray();

        result.SetCells(numCell, getIdTypeArray(cells));

        return result;
    }

    /**
     * Returns the <i>vtkProp</i> from the specified <i>Layer</i> object.<br>
     * Returns a 0 sized array if the specified layer is <code>null</code> or does not contains any vtkProp.
     */
    public static vtkProp[] getLayerProps(Layer layer)
    {
        if (layer != null)
        {
            // add painter actor from the vtk render
            final Overlay overlay = layer.getOverlay();

            if (overlay instanceof VtkPainter)
                return ((VtkPainter) overlay).getProps();
        }

        return new vtkProp[0];
    }

    /**
     * Returns all <i>vtkProp</i> from the specified list of <i>Layer</i> object.<br>
     * Returns a 0 sized array if specified layers does not contains any vtkProp.
     */
    public static vtkProp[] getLayersProps(List<Layer> layers)
    {
        final List<vtkProp[]> layersProps = new ArrayList<vtkProp[]>();
        int totalSize = 0;

        for (Layer layer : layers)
        {
            if (layer != null)
            {
                // add painter actor from the vtk render
                final Overlay overlay = layer.getOverlay();

                if (overlay instanceof VtkPainter)
                {
                    final vtkProp[] props = ((VtkPainter) overlay).getProps();

                    if (props.length > 0)
                    {
                        layersProps.add(props);
                        totalSize += props.length;
                    }
                }
            }
        }

        final vtkProp[] result = new vtkProp[totalSize];
        int ind = 0;

        for (vtkProp[] props : layersProps)
        {
            final int size = props.length;

            System.arraycopy(props, 0, result, ind, size);
            ind += size;
        }

        return result;
    }

    /**
     * Return all actor / view prop from the specified renderer
     */
    public static vtkProp[] getProps(vtkRenderer renderer)
    {
        if (renderer == null)
            return new vtkProp[0];

        final vtkPropCollection collection = renderer.GetViewProps();
        final vtkProp[] result = new vtkProp[collection.GetNumberOfItems()];

        collection.InitTraversal();
        for (int i = 0; i < result.length; i++)
            result[i] = collection.GetNextProp();

        return result;
    }

    /**
     * Return true if the renderer contains the specified actor / view prop
     */
    public static boolean hasProp(vtkRenderer renderer, vtkProp actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        return renderer.HasViewProp(actor) != 0;
    }

    /**
     * @deprecated Use {@link #hasProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static boolean findProp(vtkRenderer renderer, vtkProp actor)
    {
        return hasProp(renderer, actor);
    }

    /**
     * @deprecated Use {@link #hasProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static boolean findActor(vtkRenderer renderer, vtkActor actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        final vtkActorCollection actors = renderer.GetActors();

        actors.InitTraversal();
        for (int i = 0; i < actors.GetNumberOfItems(); i++)
        {
            final vtkActor curActor = actors.GetNextActor();

            // already present --> exit
            if (curActor == actor)
                return true;

            // // search in sub actor
            // if (findActor(curActor, actor))
            // return true;
        }

        return false;
    }

    /**
     * @deprecated Use {@link #hasProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static boolean findActor2D(vtkRenderer renderer, vtkActor2D actor)
    {
        if ((renderer == null) || (actor == null))
            return false;

        final vtkActor2DCollection actors = renderer.GetActors2D();

        actors.InitTraversal();
        for (int i = 0; i < actors.GetNumberOfItems(); i++)
        {
            final vtkActor2D curActor = actors.GetNextActor2D();

            // already present --> exit
            if (curActor == actor)
                return true;

            // // search in sub actor
            // if (findActor2D(curActor, actor))
            // return true;
        }

        return false;
    }

    /**
     * Add an actor (vtkProp) to the specified renderer.<br>
     * If the actor is already existing in the renderer then no operation is done.
     */
    public static void addProp(vtkRenderer renderer, vtkProp prop)
    {
        if ((renderer == null) || (prop == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (renderer.HasViewProp(prop) == 0)
            renderer.AddViewProp(prop);
    }

    /**
     * @deprecated Use {@link #addProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static void addActor(vtkRenderer renderer, vtkActor actor)
    {
        if ((renderer == null) || (actor == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (!VtkUtil.findActor(renderer, actor))
            renderer.AddActor(actor);
    }

    /**
     * @deprecated Use {@link #addProp(vtkRenderer, vtkProp)} instead.
     */
    @Deprecated
    public static void addActor2D(vtkRenderer renderer, vtkActor2D actor)
    {
        if ((renderer == null) || (actor == null))
            return;

        // actor not yet present in renderer ? --> add it
        if (!VtkUtil.findActor2D(renderer, actor))
            renderer.AddActor2D(actor);
    }

    /**
     * Add an array of actor (vtkProp) to the specified renderer.<br>
     * If an actor is already existing in the renderer then nothing is done for this actor.
     */
    public static void addProps(vtkRenderer renderer, vtkProp[] props)
    {
        if ((renderer == null) || (props == null))
            return;

        for (vtkProp prop : props)
        {
            // actor not yet present in renderer ? --> add it
            if (renderer.HasViewProp(prop) == 0)
                renderer.AddViewProp(prop);
        }
    }

    /**
     * Remove an actor from the specified renderer.
     */
    public static void removeProp(vtkRenderer renderer, vtkProp actor)
    {
        renderer.RemoveViewProp(actor);
    }

    /**
     * Return a 1D cells array from a 2D indexes array
     */
    public static int[] prepareCells(int[][] indexes)
    {
        final int len = indexes.length;

        int total_len = 0;
        for (int i = 0; i < len; i++)
            total_len += indexes[i].length + 1;

        final int[] result = new int[total_len];

        int offset = 0;
        for (int i = 0; i < len; i++)
        {
            final int[] s_cells = indexes[i];
            final int s_len = s_cells.length;

            result[offset++] = s_len;
            for (int j = 0; j < s_len; j++)
                result[offset++] = s_cells[j];
        }

        return result;
    }

    /**
     * Return a 1D cells array from a 1D indexes array and num vertex per cell (polygon)
     */
    public static int[] prepareCells(int numVertexPerCell, int[] indexes)
    {
        final int num_cells = indexes.length / numVertexPerCell;
        final int[] result = new int[num_cells * (numVertexPerCell + 1)];

        int off_dst = 0;
        int off_src = 0;
        for (int i = 0; i < num_cells; i++)
        {
            result[off_dst++] = numVertexPerCell;

            for (int j = 0; j < numVertexPerCell; j++)
                result[off_dst++] = indexes[off_src + j];

            off_src += numVertexPerCell;
        }

        return result;
    }

    /**
     * Creates and returns a 3D binary (0/1 values) {@link vtkImageData} object corresponding to the ROI 3D boolean mask
     * (C dimension is not considered) at specified T position.
     * 
     * @param roi
     *        the roi we want to retrieve the vtkImageData mask
     * @param sz
     *        the Z size to use for ROI with infinite Z dimension (if ROI has a finite Z dimension then ROI Z size is
     *        used).
     * @param t
     *        the T position we want to retrieve the 3D mask data
     */
    public static vtkImageData getBinaryImageData(ROI roi, int sz, int t)
    {
        final vtkImageData result;

        final Rectangle5D bounds5d = roi.getBounds5D();
        final int sizeX;
        final int sizeY;
        final int sizeZ;
        final int x;
        final int y;
        final int z;
        final int c;

        x = (int) bounds5d.getX();
        y = (int) bounds5d.getY();
        sizeX = (int) (bounds5d.getMaxX() - x);
        sizeY = (int) (bounds5d.getMaxY() - y);
        if (bounds5d.isInfiniteZ())
        {
            z = 0;
            sizeZ = sz;
        }
        else
        {
            z = (int) bounds5d.getZ();
            sizeZ = (int) (bounds5d.getMaxZ() - z);
        }
        if (bounds5d.isInfiniteC())
            c = 0;
        else
            c = (int) bounds5d.getC();

        long totalSize = sizeX;
        totalSize *= sizeY;
        totalSize *= sizeZ;

        if (totalSize > Integer.MAX_VALUE)
            throw new RuntimeException("Can't allocate array (size > 2^31)");

        // build java array
        final int sizeXY = sizeX * sizeY;
        final byte[] array = new byte[(int) totalSize];
        int offset = 0;

        if (bounds5d.isInfiniteZ())
        {
            final boolean[] mask = roi.getBooleanMask2D(x, y, sizeX, sizeY, 0, t, c, true);

            for (int curZ = z; curZ < (z + sizeZ); curZ++)
            {
                for (int i = 0; i < sizeXY; i++)
                    array[offset++] = mask[i] ? (byte) 1 : (byte) 0;
            }
        }
        else
        {
            for (int curZ = z; curZ < (z + sizeZ); curZ++)
            {
                final boolean[] mask = roi.getBooleanMask2D(x, y, sizeX, sizeY, curZ, t, c, true);

                for (int i = 0; i < sizeXY; i++)
                    array[offset++] = mask[i] ? (byte) 1 : (byte) 0;
            }
        }

        // create a new image data structure
        result = new vtkImageData();
        result.SetDimensions(sizeX, sizeY, sizeZ);
        result.SetExtent(0, sizeX - 1, 0, sizeY - 1, 0, sizeZ - 1);
        // pre-allocate data
        result.AllocateScalars(VTK_UNSIGNED_CHAR, 1);
        // set data
        ((vtkUnsignedCharArray) result.GetPointData().GetScalars()).SetJavaArray(array);

        return result;
    }

    /**
     * Creates and returns a {@link vtkImageData} object from the specified 1D array data.
     */
    public static vtkImageData getImageData(Object data, DataType dataType, int sizeX, int sizeY, int sizeZ, int sizeC)
    {
        final vtkImageData result;
        final vtkDataArray array;

        // create a new image data structure
        result = new vtkImageData();
        result.SetDimensions(sizeX, sizeY, sizeZ);
        result.SetExtent(0, sizeX - 1, 0, sizeY - 1, 0, sizeZ - 1);
        // pre-allocate data
        result.AllocateScalars(getVtkType(dataType), sizeC);
        // get array structure
        array = result.GetPointData().GetScalars();

        switch (dataType)
        {
            case UBYTE:
            case BYTE:
                ((vtkUnsignedCharArray) array).SetJavaArray((byte[]) data);
                break;
            case USHORT:
                ((vtkUnsignedShortArray) array).SetJavaArray((short[]) data);
                break;
            case SHORT:
                ((vtkShortArray) array).SetJavaArray((short[]) data);
                break;
            case UINT:
                ((vtkUnsignedIntArray) array).SetJavaArray((int[]) data);
                break;
            case INT:
                ((vtkIntArray) array).SetJavaArray((int[]) data);
                break;
            case FLOAT:
                ((vtkFloatArray) array).SetJavaArray((float[]) data);
                break;
            case DOUBLE:
                ((vtkDoubleArray) array).SetJavaArray((double[]) data);
                break;
        }

        return result;
    }

    /**
     * Create a 3D surface in VTK polygon format from the input VTK image.
     * 
     * @param imageData
     *        the input image to construct surface from
     * @param threshold
     *        the threshold intensity value used to build the surface
     */
    public static vtkPolyData getSurfaceFromImage(vtkImageData imageData, double threshold)
    {
        vtkImageData out;
        vtkPolyData result;

        final int[] extent = imageData.GetExtent();
        extent[0]--; // min X
        extent[1]++; // max X
        extent[2]--; // min Y
        extent[3]++; // max Y
        extent[4]--; // min Z
        extent[5]++; // max Z

        // pad on all sides to guarantee closed meshes
        final vtkImageConstantPad pad = new vtkImageConstantPad();

        pad.SetOutputWholeExtent(extent);
        pad.SetInputData(imageData);
        pad.Update();

        out = pad.GetOutput();
        // do not delete input image
        pad.Delete();

        final vtkContourFilter contourFilter = new vtkContourFilter();
        contourFilter.SetInputData(out);
        contourFilter.SetValue(0, threshold);
        contourFilter.Update();
        result = contourFilter.GetOutput();
        contourFilter.GetInput().Delete();
        contourFilter.Delete();

        // final vtkMarchingCubes marchingCubes = new vtkMarchingCubes();
        //
        // marchingCubes.SetInputData(out);
        // marchingCubes.SetValue(0, threshold);
        // marchingCubes.Update();
        //
        // // get the poly data result
        // result = marchingCubes.GetOutput();
        // marchingCubes.GetInput().Delete();
        // marchingCubes.Delete();

        // if (keepLargest)
        // {
        // final vtkPolyDataConnectivityFilter cc = new vtkPolyDataConnectivityFilter();
        //
        // cc.SetInputData(result);
        // cc.SetExtractionModeToLargestRegion();
        // cc.Update();
        //
        // result = cc.GetOutput();
        // cc.GetInput().Delete();
        // cc.Delete();
        // }
        //
        // if (simplifyMesh)
        // {
        // final vtkDecimatePro dec = new vtkDecimatePro();
        //
        // dec.SetInputData(result);
        // dec.PreserveTopologyOn();
        // dec.SetTargetReduction(0.9);
        // dec.Update();
        //
        // result = dec.GetOutput();
        // dec.GetInput().Delete();
        // dec.Delete();
        // }
        //
        // if (smoothness > 0)
        // {
        // final vtkSmoothPolyDataFilter smoother = new vtkSmoothPolyDataFilter();
        //
        // smoother.SetInputData(result);
        // smoother.SetRelaxationFactor(0.3);
        // smoother.FeatureEdgeSmoothingOff();
        // smoother.BoundarySmoothingOn();
        // smoother.SetNumberOfIterations(smoothness);
        // smoother.Update();
        //
        // result = smoother.GetOutput();
        // smoother.GetInput().Delete();
        // smoother.Delete();
        // }

        return result;
    }

    /**
     * Creates and returns the color map in {@link vtkColorTransferFunction} format from the
     * specified {@link LUTChannel}.
     */
    public static vtkColorTransferFunction getColorMap(LUTChannel lutChannel)
    {
        final IcyColorMap colorMap = lutChannel.getColorMap();
        final Scaler scaler = lutChannel.getScaler();

        // SCALAR COLOR FUNCTION
        final vtkColorTransferFunction result = new vtkColorTransferFunction();

        result.SetRange(scaler.getLeftIn(), scaler.getRightIn());
        for (int i = 0; i < IcyColorMap.SIZE; i++)
        {
            result.AddRGBPoint(scaler.unscale(i), colorMap.getNormalizedRed(i), colorMap.getNormalizedGreen(i),
                    colorMap.getNormalizedBlue(i));
        }

        return result;
    }

    /**
     * Creates and returns the opacity map in {@link vtkPiecewiseFunction} format from the specified {@link LUTChannel}.
     */
    public static vtkPiecewiseFunction getOpacityMap(LUTChannel lutChannel)
    {
        final IcyColorMap colorMap = lutChannel.getColorMap();
        final Scaler scaler = lutChannel.getScaler();

        // SCALAR OPACITY FUNCTION
        final vtkPiecewiseFunction result = new vtkPiecewiseFunction();

        if (colorMap.isEnabled())
        {
            for (int i = 0; i < IcyColorMap.SIZE; i++)
                result.AddPoint(scaler.unscale(i), colorMap.getNormalizedAlpha(i));
        }
        else
        {
            for (int i = 0; i < IcyColorMap.SIZE; i++)
                result.AddPoint(scaler.unscale(i), 0d);
        }

        return result;
    }

    /**
     * Creates and returns a binary color map in {@link vtkColorTransferFunction} format where 0 value is black and 1 is
     * set to specified color.
     */
    public static vtkColorTransferFunction getBinaryColorMap(Color color)
    {
        // SCALAR COLOR FUNCTION
        final vtkColorTransferFunction result = new vtkColorTransferFunction();

        result.SetRange(0, 1);
        result.AddRGBPoint(0d, 0d, 0d, 0d);
        result.AddRGBPoint(1d, color.getRed() / 255d, color.getGreen() / 255d, color.getBlue() / 255d);

        return result;
    }

    /**
     * Creates and returns a binary opacity map in {@link vtkPiecewiseFunction} format where 0 is 100% transparent and 1
     * to the specified opacity value.
     */
    public static vtkPiecewiseFunction getBinaryOpacityMap(double opacity)
    {
        // SCALAR OPACITY FUNCTION
        final vtkPiecewiseFunction result = new vtkPiecewiseFunction();

        result.AddPoint(0d, 1d);
        result.AddPoint(1d, opacity);

        return result;
    }

    /**
     * Set the Color of the specified {@link vtkPolyData} object.
     * 
     * @param polyData
     *        the vtkPolyData we want to change color
     * @param color
     *        the color to set
     * @param canvas
     *        the VtkCanvas object to lock during the color change operation for safety (can be <code>null</code> if we
     *        don't need to lock the VtkCanvas here)
     */
    public static void setPolyDataColor(vtkPolyData polyData, Color color, VtkCanvas canvas)
    {
        final int numPts = polyData.GetNumberOfPoints();
        vtkUnsignedCharArray colors = null;

        // try to recover colors object
        if (polyData.GetPointData() != null)
        {
            final vtkDataArray dataArray = polyData.GetPointData().GetScalars();

            if (dataArray instanceof vtkUnsignedCharArray)
                colors = (vtkUnsignedCharArray) dataArray;
            // delete it
            else if (dataArray != null)
                dataArray.Delete();
        }

        // colors is not correctly defined ? --> reallocate
        if ((colors == null) || (colors.GetNumberOfTuples() != numPts) || (colors.GetNumberOfComponents() != 3))
        {
            // delete first
            if (colors != null)
                colors.Delete();

            // and reallocate
            colors = new vtkUnsignedCharArray();
            colors.SetNumberOfComponents(3);
            colors.SetNumberOfTuples(numPts);
            // set colors array
            polyData.GetPointData().SetScalars(colors);
        }

        final int len = numPts * 3;

        final byte r = (byte) color.getRed();
        final byte g = (byte) color.getGreen();
        final byte b = (byte) color.getBlue();
        final byte[] data = new byte[len];

        for (int i = 0; i < len; i += 3)
        {
            data[i + 0] = r;
            data[i + 1] = g;
            data[i + 2] = b;
        }

        final IcyVtkPanel vtkPanel = (canvas != null) ? canvas.getVtkPanel() : null;

        if (vtkPanel != null)
        {
            vtkPanel.lock();
            try
            {
                colors.SetJavaArray(data);
                colors.Modified();
            }
            finally
            {
                vtkPanel.unlock();
            }
        }
        else
        {
            colors.SetJavaArray(data);
            colors.Modified();
        }
    }

    /**
     * Returns a cube polydata object representing the specified bounding box coordinate
     * 
     * @see #setOutlineBounds(vtkPolyData, double, double, double, double, double, double, VtkCanvas)
     */
    public static vtkPolyData getOutline(double xMin, double xMax, double yMin, double yMax, double zMin, double zMax)
    {
        final double points[][] = new double[8][3];
        final int indexes[][] = { {0, 2, 3, 1}, {4, 5, 7, 6}, {0, 1, 5, 4}, {1, 3, 7, 5}, {0, 4, 6, 2}, {3, 2, 6, 7}};

        for (int i = 0; i < 8; i++)
        {
            points[i][0] = ((i & 1) == 0) ? xMin : xMax;
            points[i][1] = ((i & 2) == 0) ? yMin : yMax;
            points[i][2] = ((i & 4) == 0) ? zMin : zMax;
        }

        final vtkCellArray vCells = VtkUtil.getCells(6, prepareCells(indexes));
        final vtkPoints vPoints = VtkUtil.getPoints(points);
        final vtkPolyData result = new vtkPolyData();

        result.SetPolys(vCells);
        result.SetPoints(vPoints);

        return result;
    }

    /**
     * Set the bounds of specified outline polydata object (previously created with <i>VtkUtil.getOutline(..)</i>)
     * 
     * @param canvas
     *        the VtkCanvas object to lock during the color change operation for safety (can be <code>null</code> if we
     *        don't need to lock the VtkCanvas here)
     * @return <code>false</code> if the specified polydata object is not a valid outline object
     * @see #getOutline(double, double, double, double, double, double)
     */
    public static boolean setOutlineBounds(vtkPolyData outline, double xMin, double xMax, double yMin, double yMax,
            double zMin, double zMax, VtkCanvas canvas)
    {
        final vtkPoints previousPoints = outline.GetPoints();

        // not valid
        if ((previousPoints != null) && (previousPoints.GetNumberOfPoints() != 8))
            return false;

        final double newPoints[][] = new double[8][3];
        for (int i = 0; i < 8; i++)
        {
            newPoints[i][0] = ((i & 1) == 0) ? xMin : xMax;
            newPoints[i][1] = ((i & 2) == 0) ? yMin : yMax;
            newPoints[i][2] = ((i & 4) == 0) ? zMin : zMax;
        }

        final vtkPoints points = getPoints(newPoints);
        final IcyVtkPanel vtkPanel = (canvas != null) ? canvas.getVtkPanel() : null;

        if (vtkPanel != null)
        {
            vtkPanel.lock();
            try
            {
                // rebuild points
                outline.SetPoints(points);
                // changed
                outline.Modified();
                // delete previous points
                if (previousPoints != null)
                    previousPoints.Delete();
            }
            finally
            {
                vtkPanel.unlock();
            }
        }
        else
        {
            // rebuild points
            outline.SetPoints(points);
            // changed
            outline.Modified();
            // delete previous points
            if (previousPoints != null)
                previousPoints.Delete();
        }

        return true;
    }
}