/*
 * Copyright 2010-2013 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.roi;

import icy.image.IntensityInfo;
import icy.math.DataIteratorMath;
import icy.math.MathUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginLauncher;
import icy.plugin.PluginLoader;
import icy.plugin.interface_.PluginROIDescriptor;
import icy.sequence.Sequence;
import icy.sequence.SequenceDataIterator;
import icy.system.IcyExceptionHandler;
import icy.type.DataIteratorUtil;
import icy.type.point.Point3D;
import icy.type.point.Point4D;
import icy.type.point.Point5D;
import icy.type.rectangle.Rectangle3D;
import icy.type.rectangle.Rectangle4D;
import icy.type.rectangle.Rectangle5D;
import icy.util.ShapeUtil.BooleanOperator;
import icy.util.StringUtil;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import plugins.kernel.roi.descriptor.basic.BasicROIDescriptorPlugin;

/**
 * ROI utilities class.
 * 
 * @author Stephane
 */
public class ROIUtil
{
    /**
     * Returns all available ROI descriptors (see {@link ROIDescriptor}) and their attached plugin
     * (see {@link PluginROIDescriptor}).<br/>
     * This list can be extended by installing new plugin(s) implementing the
     * {@link PluginROIDescriptor} interface.
     * 
     * @see ROIDescriptor#compute(ROI, Sequence, int, int, int)
     * @see PluginROIDescriptor#compute(ROI, Sequence, int, int, int)
     */
    public static Map<ROIDescriptor, PluginROIDescriptor> getROIDescriptors()
    {
        final Map<ROIDescriptor, PluginROIDescriptor> result = new HashMap<ROIDescriptor, PluginROIDescriptor>();
        final List<PluginDescriptor> pluginDescriptors = PluginLoader.getPlugins(PluginROIDescriptor.class, true,
                false, false);

        for (PluginDescriptor pluginDescriptor : pluginDescriptors)
        {
            try
            {
                final PluginROIDescriptor plugin = (PluginROIDescriptor) PluginLauncher.startSafe(pluginDescriptor);

                for (ROIDescriptor roiDescriptor : plugin.getDescriptors())
                    result.put(roiDescriptor, plugin);
            }
            catch (Exception e)
            {
                // show a message in the output console
                IcyExceptionHandler.showErrorMessage(e, false, true);
                // and send an error report (silent as we don't want a dialog appearing here)
                IcyExceptionHandler.report(pluginDescriptor, IcyExceptionHandler.getErrorMessage(e, true));
            }
        }

        return result;
    }

    /**
     * Computes the specified descriptor from the input {@link ROIDescriptor} set on given ROI
     * and returns the result (or <code>null</code> if the descriptor is not found).
     * 
     * @param roiDescriptors
     *        the input {@link ROIDescriptor} set (see {@link #getROIDescriptors()} method)
     * @param descriptorId
     *        the id of the descriptor we want to compute (
     *        {@link BasicROIDescriptorPlugin#ID_VOLUME} for
     *        instance)
     * @param roi
     *        the ROI on which the descriptor(s) should be computed
     * @param sequence
     *        an optional sequence where the pixel size can be retrieved
     * @param z
     *        the specific Z position (slice) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI Z dimension.
     * @param t
     *        the specific T position (frame) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI T dimension.
     * @param c
     *        the specific C position (channel) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI C dimension.
     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
     *         specified set
     * @throws UnsupportedOperationException
     *         if the type of the given ROI is not supported by this descriptor, or if
     *         <code>sequence</code> is <code>null</code> while the calculation requires it, or if
     *         the specified Z, T or C position are not supported by the descriptor
     */
    public static Object computeDescriptor(Set<ROIDescriptor> roiDescriptors, String descriptorId, ROI roi,
            Sequence sequence, int z, int t, int c)
    {
        for (ROIDescriptor roiDescriptor : roiDescriptors)
            if (StringUtil.equals(roiDescriptor.getId(), descriptorId))
                return roiDescriptor.compute(roi, sequence, z, t, c);

        return null;
    }

    /**
     * Computes the specified descriptor on given ROI and returns the result (or <code>null</code>
     * if the descriptor is not found).
     * 
     * @param descriptorId
     *        the id of the descriptor we want to compute (
     *        {@link BasicROIDescriptorPlugin#ID_VOLUME} for
     *        instance)
     * @param roi
     *        the ROI on which the descriptor(s) should be computed
     * @param sequence
     *        an optional sequence where the pixel size can be retrieved
     * @param z
     *        the specific Z position (slice) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI Z dimension.
     * @param t
     *        the specific T position (frame) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI T dimension.
     * @param c
     *        the specific C position (channel) where we want to compute the descriptor or
     *        <code>-1</code> to compute it over the whole ROI C dimension.
     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
     *         specified set
     * @throws UnsupportedOperationException
     *         if the type of the given ROI is not supported by this descriptor, or if
     *         <code>sequence</code> is <code>null</code> while the calculation requires it, or if
     *         the specified Z, T or C position are not supported by the descriptor
     */
    public static Object computeDescriptor(String descriptorId, ROI roi, Sequence sequence, int z, int t, int c)
    {
        return computeDescriptor(getROIDescriptors().keySet(), descriptorId, roi, sequence, z, t, c);
    }

    /**
     * Computes and returns the standard deviation for the specified sequence region.
     * 
     * @param sequence
     *        The sequence we want to get the intensity informations.
     * @param roi
     *        The ROI define the region where we want to compute the standard deviation.
     * @param z
     *        The specific Z position (slice) where we want to compute the standard deviation or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute the standard deviation or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute the standard deviation or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static double getStandardDeviation(Sequence sequence, ROI roi, int z, int t, int c)
    {
        try
        {
            final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c);

            long numPixels = 0;
            double sum = 0;
            double sum2 = 0;

            // faster to do all calculation in a single iteration run
            while (!it.done())
            {
                final double value = it.get();

                sum += value;
                sum2 += value * value;
                numPixels++;

                it.next();
            }

            if (numPixels > 0)
            {
                double x1 = (sum2 / numPixels);
                double x2 = sum / numPixels;
                x2 *= x2;

                return Math.sqrt(x1 - x2);
            }
        }
        catch (Exception e)
        {
            // we can have exception as the process can be really long
            // and size modified during this period
        }

        return 0d;
    }

    /**
     * Computes and returns the min, max, mean intensity for the specified sequence region.<br>
     * It can returns <code>null</code> if the sequence or the ROI has changed during the operation.
     * 
     * @param sequence
     *        The sequence we want to get the intensity informations.
     * @param roi
     *        The ROI define the region where we want to compute intensity information.
     * @param z
     *        The specific Z position (slice) where we want to compute intensity information or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute intensity information or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute intensity information or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi, int z, int t, int c)
    {
        try
        {
            final IntensityInfo result = new IntensityInfo();
            final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c);

            long numPixels = 0;
            double min = Double.MAX_VALUE;
            double max = -Double.MAX_VALUE;
            double sum = 0;

            // faster to do all calculation in a single iteration run
            while (!it.done())
            {
                final double value = it.get();

                if (value < min)
                    min = value;
                if (value > max)
                    max = value;
                sum += value;
                numPixels++;

                it.next();
            }

            if (numPixels > 0)
            {
                result.minIntensity = min;
                result.maxIntensity = max;
                result.meanIntensity = sum / numPixels;
            }
            else
            {
                result.minIntensity = 0d;
                result.maxIntensity = 0d;
                result.meanIntensity = 0d;
            }

            return result;
        }
        catch (Exception e)
        {
            // we can have exception as the process can be really long
            // and size modified during this period
            return null;
        }
    }

    /**
     * Returns the number of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the number of pixel.
     * @param roi
     *        The ROI define the region where we want to compute the number of pixel.
     * @param z
     *        The specific Z position (slice) where we want to compute the number of pixel or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute the number of pixel or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute the number of pixel or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static long getNumPixel(Sequence sequence, ROI roi, int z, int t, int c)
    {
        return DataIteratorUtil.count(new SequenceDataIterator(sequence, roi, false, z, t, c));
    }

    /**
     * Returns the minimum intensity of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the min intensity information.
     * @param roi
     *        The ROI define the region where we want to compute min intensity.
     * @param z
     *        The specific Z position (slice) where we want to compute min intensity or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute min intensity or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute min intensity or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static double getMinIntensity(Sequence sequence, ROI roi, int z, int t, int c)
    {
        return DataIteratorMath.min(new SequenceDataIterator(sequence, roi, false, z, t, c));
    }

    /**
     * Returns the maximum intensity of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the max intensity information.
     * @param roi
     *        The ROI define the region where we want to compute max intensity.
     * @param z
     *        The specific Z position (slice) where we want to compute max intensity or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute max intensity or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute max intensity or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static double getMaxIntensity(Sequence sequence, ROI roi, int z, int t, int c)
    {
        return DataIteratorMath.max(new SequenceDataIterator(sequence, roi, false, z, t, c));
    }

    /**
     * Returns the mean intensity of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the mean intensity.
     * @param roi
     *        The ROI define the region where we want to compute mean intensity.
     * @param z
     *        The specific Z position (slice) where we want to compute mean intensity or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute mean intensity or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute mean intensity or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static double getMeanIntensity(Sequence sequence, ROI roi, int z, int t, int c)
    {
        return DataIteratorMath.mean(new SequenceDataIterator(sequence, roi, false, z, t, c));
    }

    /**
     * Returns the sum of all intensity of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the intensity sum.
     * @param roi
     *        The ROI define the region where we want to compute intensity sum.
     * @param z
     *        The specific Z position (slice) where we want to compute intensity sum or
     *        <code>-1</code> to use the ROI Z dimension information.
     * @param t
     *        The specific T position (frame) where we want to compute intensity sum or
     *        <code>-1</code> to use the ROI T dimension information.
     * @param c
     *        The specific C position (channel) where we want to compute intensity sum or
     *        <code>-1</code> to use the ROI C dimension information.
     */
    public static double getSumIntensity(Sequence sequence, ROI roi, int z, int t, int c)
    {
        return DataIteratorMath.sum(new SequenceDataIterator(sequence, roi, false, z, t, c));
    }

    /**
     * Computes and returns the standard deviation for the specified sequence region.
     * 
     * @param sequence
     *        The sequence we want to get the intensity informations.
     * @param roi
     *        The ROI define the region where we want to compute the standard deviation.
     */
    public static double getStandardDeviation(Sequence sequence, ROI roi)
    {
        return getStandardDeviation(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the min, max, mean intensity of sequence pixels contained in the specified ROI.
     * 
     * @param sequence
     *        The sequence we want to get the intensity informations.
     * @param roi
     *        The ROI define the region where we want to compute intensity information.
     */
    public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi)
    {
        return getIntensityInfo(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the number of sequence pixels contained in the specified ROI.
     */
    public static long getNumPixel(Sequence sequence, ROI roi)
    {
        return getNumPixel(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the minimum intensity of sequence pixels contained in the specified ROI.
     */
    public static double getMinIntensity(Sequence sequence, ROI roi)
    {
        return getMinIntensity(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the maximum intensity of sequence pixels contained in the specified ROI.
     */
    public static double getMaxIntensity(Sequence sequence, ROI roi)
    {
        return getMaxIntensity(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the mean intensity of sequence pixels contained in the specified ROI.
     */
    public static double getMeanIntensity(Sequence sequence, ROI roi)
    {
        return getMeanIntensity(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the sum of all intensity of sequence pixels contained in the specified ROI.
     */
    public static double getSumIntensity(Sequence sequence, ROI roi)
    {
        return getSumIntensity(sequence, roi, -1, -1, -1);
    }

    /**
     * Returns the mass center of specified ROI.
     */
    public static Point5D getMassCenter(ROI roi)
    {
        switch (roi.getDimension())
        {
            case 2:
                final ROI2D roi2d = (ROI2D) roi;
                final Point2D pt2d = getMassCenter(roi2d);
                return new Point5D.Double(pt2d.getX(), pt2d.getY(), roi2d.getZ(), roi2d.getT(), roi2d.getC());

            case 3:
                final ROI3D roi3d = (ROI3D) roi;
                final Point3D pt3d = getMassCenter(roi3d);
                return new Point5D.Double(pt3d.getX(), pt3d.getY(), pt3d.getZ(), roi3d.getT(), roi3d.getC());

            case 4:
                final ROI4D roi4d = (ROI4D) roi;
                final Point4D pt4d = getMassCenter(roi4d);
                return new Point5D.Double(pt4d.getX(), pt4d.getY(), pt4d.getZ(), pt4d.getT(), roi4d.getC());

            case 5:
                return getMassCenter((ROI5D) roi);

            default:
                return null;
        }
    }

    /**
     * Returns the mass center of specified 2D ROI.
     */
    public static Point2D getMassCenter(ROI2D roi)
    {
        double x = 0, y = 0;
        long len = 0;

        final BooleanMask2D mask = roi.getBooleanMask(true);
        final boolean m[] = mask.mask;
        final int h = mask.bounds.height;
        final int w = mask.bounds.width;

        int off = 0;
        for (int j = 0; j < h; j++)
        {
            for (int i = 0; i < w; i++)
            {
                if (m[off++])
                {
                    x += i;
                    y += j;
                    len++;
                }
            }
        }

        // get bounds
        final Rectangle2D bounds2D = roi.getBounds2D();

        // empty roi --> use bounds center
        if (len == 0)
            return new Point2D.Double(bounds2D.getCenterX(), bounds2D.getCenterY());

        return new Point2D.Double(bounds2D.getX() + (x / len), bounds2D.getY() + (y / len));
    }

    /**
     * Returns the mass center of specified 3D ROI.
     */
    public static Point3D getMassCenter(ROI3D roi)
    {
        double x = 0, y = 0, z = 0;
        long len = 0;
        final BooleanMask3D mask3d = roi.getBooleanMask(true);

        for (Integer zSlice : mask3d.mask.keySet())
        {
            final int zi = zSlice.intValue();
            final double zd = zi;
            final BooleanMask2D mask = mask3d.getMask2D(zi);
            final boolean m[] = mask.mask;
            final double bx = mask.bounds.x;
            final double by = mask.bounds.y;
            final int h = mask.bounds.height;
            final int w = mask.bounds.width;

            int off = 0;
            for (int j = 0; j < h; j++)
            {
                for (int i = 0; i < w; i++)
                {
                    if (m[off++])
                    {
                        x += bx + i;
                        y += by + j;
                        z += zd;
                        len++;
                    }
                }
            }
        }

        // get bounds
        final Rectangle3D bounds3D = roi.getBounds3D();

        // empty roi --> use bounds center
        if (len == 0)
            return new Point3D.Double(bounds3D.getCenterX(), bounds3D.getCenterY(), bounds3D.getCenterZ());

        return new Point3D.Double((x / len), (y / len), (z / len));
    }

    /**
     * Returns the mass center of specified 4D ROI.
     */
    public static Point4D getMassCenter(ROI4D roi)
    {
        final BooleanMask4D mask4d = roi.getBooleanMask(true);
        double x = 0, y = 0, z = 0, t = 0;
        long len = 0;

        for (Integer tFrame : mask4d.mask.keySet())
        {
            final int ti = tFrame.intValue();
            final double td = ti;
            final BooleanMask3D mask3d = mask4d.getMask3D(ti);

            for (Integer zSlice : mask3d.mask.keySet())
            {
                final int zi = zSlice.intValue();
                final double zd = zi;
                final BooleanMask2D mask = mask3d.getMask2D(zi);
                final boolean m[] = mask.mask;
                final double bx = mask.bounds.x;
                final double by = mask.bounds.y;
                final int h = mask.bounds.height;
                final int w = mask.bounds.width;

                int off = 0;
                for (int j = 0; j < h; j++)
                {
                    for (int i = 0; i < w; i++)
                    {
                        if (m[off++])
                        {
                            x += bx + i;
                            y += by + j;
                            z += zd;
                            t += td;
                            len++;
                        }
                    }
                }
            }
        }

        // get bounds
        final Rectangle4D bounds4D = roi.getBounds4D();

        // empty roi --> use bounds center
        if (len == 0)
            return new Point4D.Double(bounds4D.getCenterX(), bounds4D.getCenterY(), bounds4D.getCenterZ(),
                    bounds4D.getCenterT());

        return new Point4D.Double((x / len), (y / len), (z / len), (t / len));

    }

    /**
     * Returns the mass center of specified 5D ROI.
     */
    public static Point5D getMassCenter(ROI5D roi)
    {
        final BooleanMask5D mask5d = roi.getBooleanMask(true);
        double x = 0, y = 0, z = 0, t = 0, c = 0;
        long len = 0;

        for (Integer cChannel : mask5d.mask.keySet())
        {
            final int ci = cChannel.intValue();
            final double cd = ci;
            final BooleanMask4D mask4d = mask5d.getMask4D(ci);

            for (Integer tFrame : mask4d.mask.keySet())
            {
                final int ti = tFrame.intValue();
                final double td = ti;
                final BooleanMask3D mask3d = mask4d.getMask3D(ti);

                for (Integer zSlice : mask3d.mask.keySet())
                {
                    final int zi = zSlice.intValue();
                    final double zd = zi;
                    final BooleanMask2D mask = mask3d.getMask2D(zi);
                    final boolean m[] = mask.mask;
                    final double bx = mask.bounds.x;
                    final double by = mask.bounds.y;
                    final int h = mask.bounds.height;
                    final int w = mask.bounds.width;

                    int off = 0;
                    for (int j = 0; j < h; j++)
                    {
                        for (int i = 0; i < w; i++)
                        {
                            if (m[off++])
                            {
                                x += bx + i;
                                y += by + j;
                                z += zd;
                                t += td;
                                c += cd;
                                len++;
                            }
                        }
                    }
                }
            }
        }

        // get bounds
        final Rectangle5D bounds5D = roi.getBounds5D();

        // empty roi --> use bounds center
        if (len == 0)
            return new Point5D.Double(bounds5D.getCenterX(), bounds5D.getCenterY(), bounds5D.getCenterZ(),
                    bounds5D.getCenterT(), bounds5D.getCenterC());

        return new Point5D.Double((x / len), (y / len), (z / len), (t / len), (c / len));
    }

    /**
     * Merge the specified array of {@link ROI} with the given {@link BooleanOperator}.<br>
     * 
     * @param rois
     *        ROIs we want to merge.
     * @param operator
     *        {@link BooleanOperator} to apply.
     * @return {@link ROI} representing the result of the merge operation.
     */
    public static ROI merge(List<? extends ROI> rois, BooleanOperator operator) throws UnsupportedOperationException
    {
        if (rois.size() == 0)
            return null;

        ROI result = rois.get(0);

        for (int i = 1; i < rois.size(); i++)
        {
            final ROI roi = rois.get(i);

            switch (operator)
            {
                case AND:
                    result = result.getIntersection(roi);
                    break;
                case OR:
                    result = result.getUnion(roi);
                    break;
                case XOR:
                    result = result.getExclusiveUnion(roi);
                    break;
            }
        }

        return result;
    }

    /**
     * Builds and returns a ROI corresponding to the union of the specified ROI list.
     */
    public static ROI getUnion(List<? extends ROI> rois) throws UnsupportedOperationException
    {
        return merge(rois, BooleanOperator.OR);
    }

    /**
     * Builds and returns a ROI corresponding to the exclusive union of the specified ROI list.
     */
    public static ROI getExclusiveUnion(List<? extends ROI> rois) throws UnsupportedOperationException
    {
        return merge(rois, BooleanOperator.XOR);
    }

    /**
     * Builds and returns a ROI corresponding to the intersection of the specified ROI list.
     */
    public static ROI getIntersection(List<? extends ROI> rois) throws UnsupportedOperationException
    {
        return merge(rois, BooleanOperator.AND);
    }

    /**
     * Subtract the content of the roi2 from the roi1 and return the result as a new {@link ROI}.<br>
     * This is equivalent to: <code>roi1.getSubtraction(roi2)</code>
     * 
     * @return {@link ROI} representing the result of subtraction.
     */
    public static ROI subtract(ROI roi1, ROI roi2) throws UnsupportedOperationException
    {
        return roi1.getSubtraction(roi2);
    }

    /**
     * Calculate the multiplier factor depending the wanted dimension information.
     */
    private static double getMultiplier(Sequence sequence, ROI roi, int dim)
    {
        final int dimRoi = roi.getDimension();

        // cannot give this information for this roi
        if (dimRoi > dim)
            return 0d;

        final Rectangle5D boundsRoi = roi.getBounds5D();
        double mul = 1d;

        switch (dim)
        {
            case 5:
                if (dimRoi == 4)
                {
                    final int sizeC = sequence.getSizeC();

                    if ((boundsRoi.getSizeC() == Double.POSITIVE_INFINITY) && (sizeC > 1))
                        mul *= sizeC;
                    // cannot give this information for this roi
                    else
                        mul = 0d;
                }
            case 4:
                if (dimRoi == 3)
                {
                    final int sizeT = sequence.getSizeT();

                    if ((boundsRoi.getSizeT() == Double.POSITIVE_INFINITY) && (sizeT > 1))
                        mul *= sizeT;
                    // cannot give this information for this roi
                    else
                        mul = 0d;
                }
            case 3:
                if (dimRoi == 2)
                {
                    final int sizeZ = sequence.getSizeZ();

                    if ((boundsRoi.getSizeZ() == Double.POSITIVE_INFINITY) && (sizeZ > 1))
                        mul *= sizeZ;
                    // cannot give this information for this roi
                    else
                        mul = 0d;
                }
            case 2:
                if (dimRoi == 1)
                {
                    final int sizeY = sequence.getSizeY();

                    if ((boundsRoi.getSizeY() == Double.POSITIVE_INFINITY) && (sizeY > 1))
                        mul *= sizeY;
                    // cannot give this information for this roi
                    else
                        mul = 0d;
                }
        }

        return mul;
    }

    /**
     * Returns the contour size for specified sequence and dimension from a given number of contour
     * points.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getContourSize(sequence, roi, 2, 5) --> "0.15 mm" (equivalent to perimeter)</li>
     * <li>getContourSize(sequence, roi, 3, 5) --> "0.028 µm2" (equivalent to surface area)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param contourPoints
     *        the number of contour points (override the ROI value)
     * @param roi
     *        the ROI we want to compute the contour size
     * @param dim
     *        the dimension for the contour size operation (2 = perimeter, 3 = surface area, ...)
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getContourSize(Sequence sequence, double contourPoints, ROI roi, int dim, int roundSignificant)
    {
        final double mul = getMultiplier(sequence, roi, dim);

        // 0 means the operation is not supported for this ROI
        if (mul != 0d)
            return sequence.calculateSize(MathUtil.roundSignificant(contourPoints, roundSignificant) * mul, dim - 1, 5);

        return "";
    }

    /**
     * Returns the ROI contour size for specified sequence and dimension.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getContourSize(sequence, roi, 2, 5) --> "0.15 mm" (equivalent to perimeter)</li>
     * <li>getContourSize(sequence, roi, 3, 5) --> "0.028 µm2" (equivalent to surface area)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the contour size
     * @param dim
     *        the dimension for the contour size operation (2 = perimeter, 3 = surface area, ...)
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getContourSize(Sequence sequence, ROI roi, int dim, int roundSignificant)
    {
        return getContourSize(sequence, roi.getNumberOfContourPoints(), roi, dim, roundSignificant);
    }

    /**
     * Returns the ROI contour size for specified sequence and dimension.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getContourSize(sequence, roi, 2) --> "0.15 mm" (equivalent to perimeter)</li>
     * <li>getContourSize(sequence, roi, 3) --> "0.028 µm2" (equivalent to surface area)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the contour size
     * @param dim
     *        the dimension for the contour size operation (2 = perimeter, 3 = surface area, ...)
     */
    public static String getContourSize(Sequence sequence, ROI roi, int dim)
    {
        return getContourSize(sequence, roi, dim, 0);
    }

    /**
     * Returns the ROI interior size for specified sequence and dimension from a given number of
     * interior points.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getInteriorSize(sequence, 1287.36, roi, 2, 5) --> "0.15 mm2" (equivalent to area)</li>
     * <li>getInteriorSize(sequence, 643852.125, roi, 3, 5) --> "0.028 µm3" (equivalent to volume)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param interiorPoints
     *        the number of interior points (override the ROI value)
     * @param roi
     *        the ROI we want to compute the interior size
     * @param dim
     *        the dimension for the interior size operation (2 = area, 3 = volume, ...)
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     * @see #getArea(Sequence, ROI)
     * @see #getVolume(Sequence, ROI)
     */
    public static String getInteriorSize(Sequence sequence, double interiorPoints, ROI roi, int dim,
            int roundSignificant)
    {
        final double mul = getMultiplier(sequence, roi, dim);

        // 0 means the operation is not supported for this ROI
        if (mul != 0d)
            return sequence.calculateSize(MathUtil.roundSignificant(interiorPoints, roundSignificant) * mul, dim, 5);

        return "";
    }

    /**
     * Returns the ROI interior size for specified sequence and dimension.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getInteriorSize(sequence, roi, 2, 5) --> "0.15 mm2" (equivalent to area)</li>
     * <li>getInteriorSize(sequence, roi, 3, 5) --> "0.028 µm3" (equivalent to volume)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the interior size
     * @param dim
     *        the dimension for the interior size operation (2 = area, 3 = volume, ...)
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     * @see #getArea(Sequence, ROI)
     * @see #getVolume(Sequence, ROI)
     */
    public static String getInteriorSize(Sequence sequence, ROI roi, int dim, int roundSignificant)
    {
        return getInteriorSize(sequence, roi.getNumberOfPoints(), roi, dim, roundSignificant);
    }

    /**
     * Returns the ROI interior size for specified sequence and dimension.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * <ul>
     * Ex:
     * <li>getInteriorSize(sequence, roi, 2) --> "0.15 mm2" (equivalent to area)</li>
     * <li>getInteriorSize(sequence, roi, 3) --> "0.028 µm3" (equivalent to volume)</li>
     * </ul>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the interior size
     * @param dim
     *        the dimension for the interior size operation (2 = area, 3 = volume, ...)
     * @see #getArea(Sequence, ROI)
     * @see #getVolume(Sequence, ROI)
     */
    public static String getInteriorSize(Sequence sequence, ROI roi, int dim)
    {
        return getInteriorSize(sequence, roi, dim, 0);
    }

    /**
     * Return perimeter of the specified ROI with the correct unit.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the perimeter
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getPerimeter(Sequence sequence, ROI roi, int roundSignificant)
    {
        return getContourSize(sequence, roi, 2, roundSignificant);
    }

    /**
     * Return perimeter of the specified ROI with the correct unit.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the perimeter
     */
    public static String getPerimeter(Sequence sequence, ROI roi)
    {
        return getPerimeter(sequence, roi, 0);
    }

    /**
     * Return area of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the area
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getArea(Sequence sequence, ROI roi, int roundSignificant)
    {
        return getInteriorSize(sequence, roi, 2, roundSignificant);
    }

    /**
     * Return area of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the area
     */
    public static String getArea(Sequence sequence, ROI roi)
    {
        return getArea(sequence, roi, 0);
    }

    /**
     * Return surface area of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the surface area
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getSurfaceArea(Sequence sequence, ROI roi, int roundSignificant)
    {
        return getContourSize(sequence, roi, 3, roundSignificant);
    }

    /**
     * Return surface area of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the surface area
     */
    public static String getSurfaceArea(Sequence sequence, ROI roi)
    {
        return getSurfaceArea(sequence, roi, 0);
    }

    /**
     * Return volume of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the volume
     * @param roundSignificant
     *        Round result value to specified number of significant digit (0 to keep all precision).
     */
    public static String getVolume(Sequence sequence, ROI roi, int roundSignificant)
    {
        return getInteriorSize(sequence, roi, 3, roundSignificant);
    }

    /**
     * Return volume of the specified ROI.<br>
     * The unit result is expressed depending the sequence pixel size information.<br>
     * It may returns an empty string if the operation is not supported for that ROI.
     * 
     * @param sequence
     *        the input sequence used to retrieve operation unit by using pixel size information.
     * @param roi
     *        the ROI we want to compute the volume
     */
    public static String getVolume(Sequence sequence, ROI roi)
    {
        return getVolume(sequence, roi, 0);
    }
}