/*
 * 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.sequence;

import java.awt.Color;
import java.util.HashSet;
import java.util.Set;

import icy.image.IcyBufferedImage;
import icy.type.DataType;
import icy.util.OMEUtil;
import icy.util.StringUtil;
import icy.util.XMLUtil;
import loci.common.services.ServiceException;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.ome.OMEXMLMetadataImpl;
import ome.units.quantity.Time;
import ome.xml.meta.OMEXMLMetadata;
import ome.xml.model.Annotation;
import ome.xml.model.Channel;
import ome.xml.model.Dataset;
import ome.xml.model.Experiment;
import ome.xml.model.Experimenter;
import ome.xml.model.ExperimenterGroup;
import ome.xml.model.Image;
import ome.xml.model.Instrument;
import ome.xml.model.OME;
import ome.xml.model.Pixels;
import ome.xml.model.Plane;
import ome.xml.model.ROI;
import ome.xml.model.StructuredAnnotations;
import ome.xml.model.XMLAnnotation;
import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.primitives.PositiveInteger;

/**
 * Meta data utilities class.<br>
 * Basically provide safe access to metadata.
 * 
 * @author Stephane
 */
public class MetaDataUtil
{
    public static final String DEFAULT_CHANNEL_NAME = "ch ";

    /**
     * Returns OME root element (create it if needed).
     */
    public static OME getOME(OMEXMLMetadata metaData)
    {
        OME result = (OME) metaData.getRoot();

        if (result == null)
        {
            metaData.createRoot();
            result = (OME) metaData.getRoot();
        }

        return result;
    }

    /**
     * @deprecated Use {@link #getOME(OMEXMLMetadata)} instead
     */
    @Deprecated
    public static OME getOME(OMEXMLMetadataImpl metaData)
    {
        return getOME((OMEXMLMetadata) metaData);
    }

    /**
     * Returns the number of image serie of the specified metaData description.
     */
    public static int getNumSerie(OMEXMLMetadata metaData)
    {
        return metaData.getImageCount();
    }

    /**
     * @deprecated Use {@link #getNumSerie(OMEXMLMetadata)} instead
     */
    @Deprecated
    public static int getNumSerie(OMEXMLMetadataImpl metaData)
    {
        return getNumSerie((OMEXMLMetadata) metaData);
    }

    /**
     * Return image serie object at specified index for the specified metaData description.
     */
    public static Image getSerie(OMEXMLMetadata metaData, int index)
    {
        final OME ome = getOME(metaData);

        if (index < ome.sizeOfImageList())
            return ome.getImage(index);

        return null;
    }

    /**
     * @deprecated Use {@link #getSerie(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static Image getSerie(OMEXMLMetadataImpl metaData, int index)
    {
        return getSerie((OMEXMLMetadata) metaData, index);
    }

    /**
     * Ensure the image serie at specified index exist for the specified metaData description.
     */
    public static Image ensureSerie(OME ome, int index)
    {
        // create missing image
        while (ome.sizeOfImageList() <= index)
        {
            final Image img = new Image();
            ome.addImage(img);
        }

        final Image result = ome.getImage(index);

        if (result.getPixels() == null)
        {
            final Pixels pix = new Pixels();
            // wanted default dimension order
            pix.setDimensionOrder(DimensionOrder.XYCZT);
            // create default pixels object
            result.setPixels(pix);
        }

        return result;
    }

    /**
     * Set the number of image serie for the specified metaData description.
     */
    public static void setNumSerie(OMEXMLMetadata metaData, int num)
    {
        final OME ome = getOME(metaData);

        // keep only desired number of image
        while (ome.sizeOfImageList() > num)
            ome.removeImage(ome.getImage(ome.sizeOfImageList() - 1));

        // create missing image
        ensureSerie(ome, num - 1);
    }

    /**
     * @deprecated Use {@link #setNumSerie(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static void setNumSerie(OMEXMLMetadataImpl metaData, int num)
    {
        setNumSerie((OMEXMLMetadata) metaData, num);
    }

    /**
     * Return pixels object at specified index for the specified metaData description.
     */
    public static Pixels getPixels(OME ome, int index)
    {
        if (ome != null)
        {
            if (index < ome.sizeOfImageList())
                return ome.getImage(index).getPixels();
        }

        return null;
    }

    /**
     * Return pixels object at specified index for the specified metaData description.
     */
    public static Pixels getPixels(OMEXMLMetadata metaData, int index)
    {
        return getPixels(getOME(metaData), index);
    }

    /**
     * @deprecated Use {@link #getPixels(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static Pixels getPixels(OMEXMLMetadataImpl metaData, int index)
    {
        return getPixels((OMEXMLMetadata) metaData, index);
    }

    /**
     * Return plane index for the specified T, Z, C position.
     */
    public static int getPlaneIndex(Pixels pix, int t, int z, int c)
    {
        // can't compute plane index --> return 0 by default
        if ((t < 0) || (z < 0) || (c < 0))
            return 0;
        // trivial opti...
        if ((t == 0) && (z == 0) && (c == 0))
            return 0;

        final int sizeT = OMEUtil.getValue(pix.getSizeT(), 0);
        final int sizeZ = OMEUtil.getValue(pix.getSizeZ(), 0);
        int sizeC = OMEUtil.getValue(pix.getSizeC(), 0);

        // can't compute plane index --> return 0 by default
        if ((sizeT == 0) || (sizeZ == 0) || (sizeC == 0))
            return 0;

        int adjC = c;

        if (pix.sizeOfChannelList() > 0)
        {
            final Channel channel = pix.getChannel(0);
            if (channel != null)
            {
                final int spp = OMEUtil.getValue(channel.getSamplesPerPixel(), 0);
                // channel are packed in pixel so consider sizeC = 1
                if ((spp != 0) && (spp == sizeC))
                {
                    sizeC = 1;
                    adjC = 0;
                }
            }
        }

        // first try to get index from real plan position
        final int len = pix.sizeOfPlaneList();
        for (int i = 0; i < len; i++)
        {
            final Plane plane = pix.getPlane(i);

            // plane found --> return index
            if ((OMEUtil.getValue(plane.getTheT(), -1) == t) && (OMEUtil.getValue(plane.getTheZ(), -1) == z)
                    && (OMEUtil.getValue(plane.getTheC(), -1) == c))
                return i;
        }

        DimensionOrder dimOrder = pix.getDimensionOrder();
        // use default dimension order
        if (dimOrder == null)
            dimOrder = DimensionOrder.XYCZT;

        // use computed method
        return FormatTools.getIndex(dimOrder.getValue(), sizeZ, sizeC, sizeT, sizeZ * sizeC * sizeT, z, adjC, t);
    }

    public static Plane getPlane(Pixels pix, int index)
    {
        if (pix != null)
        {
            if (index < pix.sizeOfPlaneList())
                return pix.getPlane(index);
        }

        return null;
    }

    /**
     * Return plane object for the specified T, Z, C position.
     */
    public static Plane getPlane(Pixels pix, int t, int z, int c)
    {
        return getPlane(pix, getPlaneIndex(pix, t, z, c));
    }

    /**
     * Ensure the plane at specified index exist for the specified Pixels object.
     */
    public static Plane ensurePlane(Pixels pix, int index)
    {
        // create missing plane
        while (pix.sizeOfPlaneList() <= index)
            pix.addPlane(new Plane());

        return pix.getPlane(index);
    }

    /**
     * Ensure the plane at specified T, Z, C position exist for the specified Pixels object.
     */
    public static Plane ensurePlane(Pixels pix, int t, int z, int c)
    {
        return ensurePlane(pix, getPlaneIndex(pix, t, z, c));
    }

    /**
     * Remove the plane at specified position.
     * 
     * @return <code>true</code> if the operation succeed, <code>false</code> otherwise
     */
    public static boolean removePlane(Image img, int index)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return false;

        final int numPlane = pix.sizeOfPlaneList();

        // no plane here --> return false
        if (index >= numPlane)
            return false;

        final Plane plane = getPlane(pix, index);

        // remove plane
        pix.removePlane(plane);

        // remove associated annotation
        for (int i = 0; i < plane.sizeOfLinkedAnnotationList(); i++)
            img.unlinkAnnotation(plane.getLinkedAnnotation(i));

        // clean some data
        if (pix.sizeOfBinDataList() == numPlane)
            pix.removeBinData(pix.getBinData(index));
        if (pix.sizeOfTiffDataList() == numPlane)
            pix.removeTiffData(pix.getTiffData(index));

        return true;
    }

    /**
     * Remove the plane at specified position.
     * 
     * @return <code>true</code> if the operation succeed, <code>false</code> otherwise
     */
    public static boolean removePlane(Image img, int t, int z, int c)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return false;

        return removePlane(img, getPlaneIndex(pix, t, z, c));
    }

    /**
     * Remove the plane at specified position.
     * 
     * @return <code>true</code> if the operation succeed, <code>false</code> otherwise
     */
    public static boolean removePlane(OMEXMLMetadata metadata, int serie, int t, int z, int c)
    {
        final Image img = getSerie(metadata, serie);
        if (img == null)
            return false;

        return removePlane(img, t, z, c);
    }

    /**
     * @deprecated Use {@link #removePlane(OMEXMLMetadata, int, int, int, int)} instead
     */
    @Deprecated
    public static boolean removePlane(OMEXMLMetadataImpl metadata, int serie, int t, int z, int c)
    {
        return removePlane((OMEXMLMetadata) metadata, serie, t, z, c);
    }

    /**
     * Remove planes at given position
     * 
     * @param posT
     *        T position where we want to remove metadata (-1 for all)
     * @param posZ
     *        Z position where we want to remove metadata (-1 for all)
     * @param posC
     *        C position where we want to remove metadata (-1 for all)
     */
    public static void removePlanes(OMEXMLMetadata metadata, int serie, int posT, int posZ, int posC)
    {
        final int minT, maxT;
        final int minZ, maxZ;
        final int minC, maxC;

        if (posT < 0)
        {
            minT = 0;
            maxT = getSizeT(metadata, serie) - 1;
        }
        else
        {
            minT = posT;
            maxT = posT;
        }
        if (posZ < 0)
        {
            minZ = 0;
            maxZ = getSizeZ(metadata, serie) - 1;
        }
        else
        {
            minZ = posZ;
            maxZ = posZ;
        }
        if (posC < 0)
        {
            minC = 0;
            maxC = getSizeC(metadata, serie) - 1;
        }
        else
        {
            minC = posC;
            maxC = posC;
        }

        for (int t = minT; t <= maxT; t++)
            for (int z = minZ; z <= maxZ; z++)
                for (int c = minC; c <= maxC; c++)
                    MetaDataUtil.removePlane(metadata, 0, t, z, c);
    }

    /**
     * @deprecated Use {@link #removePlanes(OMEXMLMetadata, int, int, int, int)} instead
     */
    @Deprecated
    public static void removePlanes(OMEXMLMetadataImpl metadata, int serie, int posT, int posZ, int posC)
    {
        removePlanes((OMEXMLMetadata) metadata, serie, posT, posZ, posC);
    }

    /**
     * Returns the data type of the specified image serie.
     */
    public static DataType getDataType(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return DataType.getDataTypeFromPixelType(pix.getType());

        // assume byte by default
        return DataType.UBYTE;
    }

    /**
     * @deprecated Use {@link #getDataType(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static DataType getDataType(OMEXMLMetadataImpl metaData, int serie)
    {
        return getDataType((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the width (sizeX) of the specified image serie.
     */
    public static int getSizeX(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getSizeX(), 0);

        return 0;
    }

    /**
     * @deprecated Use {@link #getSizeX(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getSizeX(OMEXMLMetadataImpl metaData, int serie)
    {
        return getSizeX((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the height (sizeY) of the specified image serie.
     */
    public static int getSizeY(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getSizeY(), 0);

        return 0;
    }

    /**
     * @deprecated Use {@link #getSizeY(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getSizeY(OMEXMLMetadataImpl metaData, int serie)
    {
        return getSizeY((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the number of channel (sizeC) of the specified image serie.
     */
    public static int getSizeC(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getSizeC(), 0);

        return 0;
    }

    /**
     * @deprecated Use {@link #getSizeC(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getSizeC(OMEXMLMetadataImpl metaData, int serie)
    {
        return getSizeC((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the depth (sizeZ) of the specified image serie.
     */
    public static int getSizeZ(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getSizeZ(), 0);

        return 0;
    }

    /**
     * @deprecated Use {@link #getSizeZ(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getSizeZ(OMEXMLMetadataImpl metaData, int serie)
    {
        return getSizeZ((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the number of frame (sizeT) of the specified Pixels object.
     */
    private static int getSizeT(Pixels pix)
    {
        return OMEUtil.getValue(pix.getSizeT(), 0);
    }

    /**
     * Returns the number of frame (sizeT) of the specified image serie.
     */
    public static int getSizeT(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return getSizeT(pix);

        return 0;
    }

    /**
     * @deprecated Use {@link #getSizeT(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getSizeT(OMEXMLMetadataImpl metaData, int serie)
    {
        return getSizeT((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns the total data size (in bytes) of the specified image serie.
     */
    public static long getDataSize(OMEXMLMetadata metaData, int serie)
    {
        return getDataSize(metaData, serie, 0);
    }

    /**
     * Returns the total data size (in bytes) of the specified image serie
     * for the given resolution (0 = full, 1 = 1/2, ...)
     */
    public static long getDataSize(OMEXMLMetadata metaData, int serie, int resolution)
    {
        return getDataSize(metaData, serie, resolution, getSizeZ(metaData, serie), getSizeT(metaData, serie));
    }

    /**
     * Returns the total data size (in bytes) of the specified image serie
     * for the given resolution (0 = full, 1 = 1/2, ...) and size informations
     */
    public static long getDataSize(OMEXMLMetadata metaData, int serie, int resolution, int sizeZ, int sizeT)
    {
        return getDataSize(metaData, serie, resolution, sizeZ, sizeT, getSizeC(metaData, serie));
    }

    /**
     * Returns the total data size (in bytes) of the specified image serie
     * for the given resolution (0 = full, 1 = 1/2, ...) and size informations
     */
    public static long getDataSize(OMEXMLMetadata metaData, int serie, int resolution, int sizeZ, int sizeT, int sizeC)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            long sizeXY = (long) OMEUtil.getValue(pix.getSizeX(), 0) * (long) OMEUtil.getValue(pix.getSizeY(), 0);

            if (resolution > 0)
                sizeXY /= Math.pow(4d, resolution);

            return sizeXY * sizeC * sizeZ * sizeT * DataType.getDataTypeFromPixelType(pix.getType()).getSize();
        }

        return 0L;
    }

    /**
     * Sets the data type of the specified image serie.
     */
    public static void setDataType(OMEXMLMetadata metaData, int serie, DataType dataType)
    {
        metaData.setPixelsType(dataType.toPixelType(), serie);
    }

    /**
     * @deprecated Use {@link #setDataType(OMEXMLMetadata, int, DataType)} instead
     */
    @Deprecated
    public static void setDataType(OMEXMLMetadataImpl metaData, int serie, DataType dataType)
    {
        setDataType((OMEXMLMetadata) metaData, serie, dataType);
    }

    /**
     * Sets the width (sizeX) of the specified image serie (need to be >= 1).
     */
    public static void setSizeX(OMEXMLMetadata metaData, int serie, int sizeX)
    {
        metaData.setPixelsSizeX(OMEUtil.getPositiveInteger(sizeX), serie);
    }

    /**
     * @deprecated Use {@link #setSizeX(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setSizeX(OMEXMLMetadataImpl metaData, int serie, int sizeX)
    {
        setSizeX((OMEXMLMetadata) metaData, serie, sizeX);
    }

    /**
     * Sets the height (sizeY) of the specified image serie (need to be >= 1).
     */
    public static void setSizeY(OMEXMLMetadata metaData, int serie, int sizeY)
    {
        metaData.setPixelsSizeY(OMEUtil.getPositiveInteger(sizeY), serie);
    }

    /**
     * @deprecated Use {@link #setSizeY(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setSizeY(OMEXMLMetadataImpl metaData, int serie, int sizeY)
    {
        setSizeY((OMEXMLMetadata) metaData, serie, sizeY);
    }

    /**
     * Sets the number of channel (sizeC) of the specified image serie (need to be >= 1).
     */
    public static void setSizeC(OMEXMLMetadata metaData, int serie, int sizeC)
    {
        metaData.setPixelsSizeC(OMEUtil.getPositiveInteger(sizeC), serie);
    }

    /**
     * @deprecated Use {@link #setSizeC(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setSizeC(OMEXMLMetadataImpl metaData, int serie, int sizeC)
    {
        setSizeC((OMEXMLMetadata) metaData, serie, sizeC);
    }

    /**
     * Sets the depth (sizeZ) of the specified image serie (need to be >= 1).
     */
    public static void setSizeZ(OMEXMLMetadata metaData, int serie, int sizeZ)
    {
        metaData.setPixelsSizeZ(OMEUtil.getPositiveInteger(sizeZ), serie);
    }

    /**
     * @deprecated Use {@link #setSizeZ(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setSizeZ(OMEXMLMetadataImpl metaData, int serie, int sizeZ)
    {
        setSizeZ((OMEXMLMetadata) metaData, serie, sizeZ);
    }

    /**
     * Sets the number of frame (sizeT) of the specified image serie (need to be >= 1).
     */
    public static void setSizeT(OMEXMLMetadata metaData, int serie, int sizeT)
    {
        metaData.setPixelsSizeT(OMEUtil.getPositiveInteger(sizeT), serie);
    }

    /**
     * @deprecated Use {@link #setSizeT(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setSizeT(OMEXMLMetadataImpl metaData, int serie, int sizeT)
    {
        setSizeT((OMEXMLMetadata) metaData, serie, sizeT);
    }

    /**
     * Returns the id of the specified image serie.
     */
    public static String getImageID(OMEXMLMetadata metaData, int serie)
    {
        final Image img = getSerie(metaData, serie);

        if (img != null)
            return StringUtil.getValue(img.getID(), "");

        return "";
    }

    /**
     * @deprecated Use {@link #getImageID(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static String getImageID(OMEXMLMetadataImpl metaData, int serie)
    {
        return getImageID((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Set the id of the specified image serie.
     */
    public static void setImageID(OMEXMLMetadata metaData, int serie, String value)
    {
        metaData.setImageID(value, serie);
    }

    /**
     * @deprecated Use {@link #setImageID(OMEXMLMetadata, int, String)} instead
     */
    @Deprecated
    public static void setImageID(OMEXMLMetadataImpl metaData, int serie, String value)
    {
        setImageID((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Returns the name of the specified image serie.
     */
    public static String getName(OMEXMLMetadata metaData, int serie)
    {
        final Image img = getSerie(metaData, serie);

        if (img != null)
            return StringUtil.getValue(img.getName(), "");

        return "";
    }

    /**
     * @deprecated Use {@link #getName(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static String getName(OMEXMLMetadataImpl metaData, int serie)
    {
        return getName((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Set the name of the specified image serie.
     */
    public static void setName(OMEXMLMetadata metaData, int serie, String value)
    {
        metaData.setImageName(value, serie);
    }

    /**
     * @deprecated Use {@link #setName(OMEXMLMetadata, int, String)} instead
     */
    @Deprecated
    public static void setName(OMEXMLMetadataImpl metaData, int serie, String value)
    {
        setName((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Returns X pixel size (in µm) of the specified image serie.
     */
    public static double getPixelSizeX(OMEXMLMetadata metaData, int serie, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getPhysicalSizeX(), defaultValue);

        return defaultValue;
    }

    /**
     * @deprecated Use {@link #getPixelSizeX(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static double getPixelSizeX(OMEXMLMetadataImpl metaData, int serie, double defaultValue)
    {
        return getPixelSizeX((OMEXMLMetadata) metaData, serie, defaultValue);
    }

    /**
     * Returns Y pixel size (in µm) of the specified image serie.
     */
    public static double getPixelSizeY(OMEXMLMetadata metaData, int serie, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getPhysicalSizeY(), defaultValue);

        return defaultValue;
    }

    /**
     * @deprecated Use {@link #getPixelSizeY(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static double getPixelSizeY(OMEXMLMetadataImpl metaData, int serie, double defaultValue)
    {
        return getPixelSizeY((OMEXMLMetadata) metaData, serie, defaultValue);
    }

    /**
     * Returns Z pixel size (in µm) of the specified image serie.
     */
    public static double getPixelSizeZ(OMEXMLMetadata metaData, int serie, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return OMEUtil.getValue(pix.getPhysicalSizeZ(), defaultValue);

        return defaultValue;
    }

    /**
     * @deprecated Use {@link #getPixelSizeZ(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static double getPixelSizeZ(OMEXMLMetadataImpl metaData, int serie, double defaultValue)
    {
        return getPixelSizeZ((OMEXMLMetadata) metaData, serie, defaultValue);
    }

    /**
     * Computes and returns the T time interval (in second) from internal time positions.<br>
     * If there is no internal time positions <code>0d</code> is returned.
     */
    public static double getTimeIntervalFromTimePositions(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        // try to compute time interval from time position
        if (pix != null)
            return computeTimeIntervalFromTimePosition(pix);

        return 0d;
    }

    /**
     * @deprecated Use {@link #getTimeIntervalFromTimePositions(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static double getTimeIntervalFromTimePositions(OMEXMLMetadataImpl metaData, int serie)
    {
        return getTimeIntervalFromTimePositions((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Returns T time interval (in second) for the specified image serie.
     */
    public static double getTimeInterval(OMEXMLMetadata metaData, int serie, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Time timeInc = pix.getTimeIncrement();
            if (timeInc != null)
                return OMEUtil.getValue(timeInc, defaultValue);
        }

        return defaultValue;
    }

    /**
     * @deprecated Use {@link #getTimeInterval(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static double getTimeInterval(OMEXMLMetadataImpl metaData, int serie, double defaultValue)
    {
        return getTimeInterval((OMEXMLMetadata) metaData, serie, defaultValue);
    }

    /**
     * Set X pixel size (in µm) of the specified image serie.
     */
    public static void setPixelSizeX(OMEXMLMetadata metaData, int serie, double value)
    {
        metaData.setPixelsPhysicalSizeX(OMEUtil.getLength(value), serie);
    }

    /**
     * @deprecated Use {@link #setPixelSizeX(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static void setPixelSizeX(OMEXMLMetadataImpl metaData, int serie, double value)
    {
        setPixelSizeX((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Set Y pixel size (in µm) of the specified image serie.
     */
    public static void setPixelSizeY(OMEXMLMetadata metaData, int serie, double value)
    {
        metaData.setPixelsPhysicalSizeY(OMEUtil.getLength(value), serie);
    }

    /**
     * @deprecated Use {@link #setPixelSizeY(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static void setPixelSizeY(OMEXMLMetadataImpl metaData, int serie, double value)
    {
        setPixelSizeY((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Set Z pixel size (in µm) of the specified image serie.
     */
    public static void setPixelSizeZ(OMEXMLMetadata metaData, int serie, double value)
    {
        metaData.setPixelsPhysicalSizeZ(OMEUtil.getLength(value), serie);
    }

    /**
     * @deprecated Use {@link #setPixelSizeZ(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static void setPixelSizeZ(OMEXMLMetadataImpl metaData, int serie, double value)
    {
        setPixelSizeZ((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Set T time resolution (in second) of the specified image serie.
     */
    public static void setTimeInterval(OMEXMLMetadata metaData, int serie, double value)
    {
        metaData.setPixelsTimeIncrement(OMEUtil.getTime(value), serie);
    }

    /**
     * @deprecated Use {@link #setTimeInterval(OMEXMLMetadata, int, double)} instead
     */
    @Deprecated
    public static void setTimeInterval(OMEXMLMetadataImpl metaData, int serie, double value)
    {
        setTimeInterval((OMEXMLMetadata) metaData, serie, value);
    }

    /**
     * Returns the X field position (in µm) for the image at the specified Z, T, C position.
     */
    public static double getPositionX(OMEXMLMetadata metaData, int serie, int t, int z, int c, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = getPlane(pix, t, z, c);

            if (plane != null)
                return OMEUtil.getValue(plane.getPositionX(), defaultValue);
        }

        return defaultValue;
    }

    /**
     * Returns the Y field position (in µm) for the image at the specified Z, T, C position.
     */
    public static double getPositionY(OMEXMLMetadata metaData, int serie, int t, int z, int c, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = getPlane(pix, t, z, c);

            if (plane != null)
                return OMEUtil.getValue(plane.getPositionY(), defaultValue);
        }

        return defaultValue;
    }

    /**
     * Returns the Z field position (in µm) for the image at the specified Z, T, C position.
     */
    public static double getPositionZ(OMEXMLMetadata metaData, int serie, int t, int z, int c, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = getPlane(pix, t, z, c);

            if (plane != null)
                return OMEUtil.getValue(plane.getPositionZ(), defaultValue);
        }

        return defaultValue;
    }

    /**
     * Returns the time position (in second) for the Pixels object at the specified Z, T, C
     * position.
     */
    private static double getTimePosition(Pixels pix, int t, int z, int c, double defaultValue)
    {
        final Plane plane = getPlane(pix, t, z, c);

        if (plane != null)
            return OMEUtil.getValue(plane.getDeltaT(), defaultValue);

        return defaultValue;
    }

    /**
     * Returns the time position (in second) for the image at the specified Z, T, C position.
     */
    public static double getTimePosition(OMEXMLMetadata metaData, int serie, int t, int z, int c, double defaultValue)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return getTimePosition(pix, t, z, c, defaultValue);

        return defaultValue;
    }

    /**
     * Computes time interval from the time position informations.<br>
     * Returns <code>0d</code> if time position informations are missing ot if we have only 1 frame in the image.
     */
    private static double computeTimeIntervalFromTimePosition(Pixels pix)
    {
        final int sizeT = getSizeT(pix);

        if (sizeT <= 1)
            return 0d;

        double result = 0d;
        double last = -1d;
        int lastT = 0;
        int num = 0;

        for (int t = 0; t < sizeT; t++)
        {
            final Plane plane = getPlane(pix, t, 0, 0);

            if (plane != null)
            {
                final double timePos = OMEUtil.getValue(plane.getDeltaT(), Double.NaN);

                if (!Double.isNaN(timePos))
                {
                    if (last != -1d)
                    {
                        // get delta
                        result += (timePos - last) / (t - lastT);
                        num++;
                    }

                    last = timePos;
                    lastT = t;
                }
            }
        }

        // we need at least 1 delta
        if (num == 0)
            return 0d;

        return result / num;
    }

    /**
     * Sets the X field position (in µm) for the image at the specified Z, T, C position.
     */
    public static void setPositionX(OMEXMLMetadata metaData, int serie, int t, int z, int c, double value)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = ensurePlane(pix, t, z, c);

            if (plane != null)
                plane.setPositionX(OMEUtil.getLength(value));
        }
    }

    /**
     * Sets the Y field position (in µm) for the image at the specified Z, T, C position.
     */
    public static void setPositionY(OMEXMLMetadata metaData, int serie, int t, int z, int c, double value)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = ensurePlane(pix, t, z, c);

            if (plane != null)
                plane.setPositionY(OMEUtil.getLength(value));
        }
    }

    /**
     * Sets the Z field position (in µm) for the image at the specified Z, T, C position.
     */
    public static void setPositionZ(OMEXMLMetadata metaData, int serie, int t, int z, int c, double value)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
        {
            final Plane plane = ensurePlane(pix, t, z, c);

            if (plane != null)
                plane.setPositionZ(OMEUtil.getLength(value));
        }
    }

    /**
     * Get default name for specified channel.
     */
    public static String getDefaultChannelName(int channel)
    {
        return DEFAULT_CHANNEL_NAME + channel;
    }

    /**
     * Returns the number of channel for the specified image serie in metaData description.
     */
    public static int getNumChannel(OMEXMLMetadata metaData, int serie)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return pix.sizeOfChannelList();

        return 0;
    }

    /**
     * @deprecated Use {@link #getNumChannel(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static int getNumChannel(OMEXMLMetadataImpl metaData, int serie)
    {
        return getNumChannel((OMEXMLMetadata) metaData, serie);
    }

    /**
     * Return channel object at specified index for the specified image serie.
     */
    public static Channel getChannel(OMEXMLMetadata metaData, int serie, int index)
    {
        final Pixels pix = getPixels(metaData, serie);

        if ((pix != null) && (index < pix.sizeOfChannelList()))
            return pix.getChannel(index);

        return null;
    }

    /**
     * @deprecated Use {@link #getChannel(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static Channel getChannel(OMEXMLMetadataImpl metaData, int serie, int index)
    {
        return getChannel((OMEXMLMetadata) metaData, serie, index);
    }

    /**
     * Ensure the channel at specified index exist for the specified image serie.
     */
    public static Channel ensureChannel(OMEXMLMetadata metaData, int serie, int index)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            return ensureChannel(pix, index);

        return null;
    }

    /**
     * @deprecated Use {@link #ensureChannel(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static Channel ensureChannel(OMEXMLMetadataImpl metaData, int serie, int index)
    {
        return ensureChannel((OMEXMLMetadata) metaData, serie, index);
    }

    /**
     * Ensure the channel at specified index exist for the specified image serie.
     */
    public static Channel ensureChannel(Pixels pix, int index)
    {
        // create missing channel
        while (pix.sizeOfChannelList() <= index)
            pix.addChannel(new Channel());

        return pix.getChannel(index);
    }

    /**
     * Remove a channel for the specified image serie.
     */
    public static void removeChannel(OMEXMLMetadata metaData, int serie, int index)
    {
        final Pixels pix = getPixels(metaData, serie);

        if (pix != null)
            removeChannel(pix, index);
    }

    /**
     * @deprecated Use {@link #removeChannel(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void removeChannel(OMEXMLMetadataImpl metaData, int serie, int index)
    {
        removeChannel((OMEXMLMetadata) metaData, serie, index);
    }

    /**
     * Remove a channel from the specified Pixels object.
     */
    public static void removeChannel(Pixels pix, int index)
    {
        if (pix.sizeOfChannelList() > index)
            pix.removeChannel(pix.getChannel(index));
    }

    /**
     * Set the number of channel for the specified image serie in metaData description.<br>
     * This is different from {@link #getSizeC(OMEXMLMetadata, int)}.
     */
    public static void setNumChannel(OMEXMLMetadata metaData, int serie, int num)
    {
        final OME ome = getOME(metaData);

        ensureSerie(ome, serie);

        final Image img = ome.getImage(serie);
        Pixels pix = img.getPixels();

        if (pix == null)
        {
            // create pixels object
            pix = new Pixels();
            img.setPixels(pix);
        }

        // keep only desired number of image
        while (pix.sizeOfChannelList() > num)
            removeChannel(pix, pix.sizeOfChannelList() - 1);

        // create missing image
        ensureChannel(pix, num - 1);
    }

    /**
     * @deprecated Use {@link #setNumChannel(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static void setNumChannel(OMEXMLMetadataImpl metaData, int serie, int num)
    {
        setNumChannel((OMEXMLMetadata) metaData, serie, num);
    }

    /**
     * Initialize default channel name until specified index if they are missing from the meta data
     * description.
     */
    private static void prepareMetaChannelName(OMEXMLMetadata metaData, int serie, int channel)
    {
        int c = getNumChannel(metaData, serie);

        while (channel >= c)
        {
            // set default channel name
            metaData.setChannelName(getDefaultChannelName(c), serie, c);
            c++;
        }
    }

    /**
     * Returns name of specified channel image serie.
     */
    public static String getChannelName(OMEXMLMetadata metaData, int serie, int channel)
    {
        // needed as LOCI does not initialize them on read
        prepareMetaChannelName(metaData, serie, channel);

        final String result = StringUtil.getValue(metaData.getChannelName(serie, channel),
                getDefaultChannelName(channel));
        final String cleaned = XMLUtil.filterString(result);

        // cleaned string != original value --> set it
        if (!cleaned.equals(result))
            setChannelName(metaData, serie, channel, cleaned);

        return cleaned;
    }

    /**
     * @deprecated Use {@link #getChannelName(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static String getChannelName(OMEXMLMetadataImpl metaData, int serie, int channel)
    {
        return getChannelName((OMEXMLMetadata) metaData, serie, channel);
    }

    /**
     * Set name of specified channel image serie.
     */
    public static void setChannelName(OMEXMLMetadata metaData, int serie, int channel, String value)
    {
        // needed as LOCI only add current channel if it's missing
        prepareMetaChannelName(metaData, serie, channel - 1);

        metaData.setChannelName(value, serie, channel);
    }

    /**
     * @deprecated Use {@link #setChannelName(OMEXMLMetadata, int, int, String)} instead
     */
    @Deprecated
    public static void setChannelName(OMEXMLMetadataImpl metaData, int serie, int channel, String value)
    {
        setChannelName((OMEXMLMetadata) metaData, serie, channel, value);
    }

    /**
     * Returns Color of specified channel image serie.
     */
    public static Color getChannelColor(OMEXMLMetadata metaData, int serie, int channel)
    {
        // needed as LOCI does not initialize them on read
        prepareMetaChannelName(metaData, serie, channel);

        return OMEUtil.getJavaColor(metaData.getChannelColor(serie, channel));
    }

    /**
     * @deprecated Use {@link #getChannelColor(OMEXMLMetadata, int, int)} instead
     */
    @Deprecated
    public static Color getChannelColor(OMEXMLMetadataImpl metaData, int serie, int channel)
    {
        return getChannelColor((OMEXMLMetadata) metaData, serie, channel);
    }

    /**
     * Create and return a default (OME XML) Metadata object with default image name.
     */
    public static OMEXMLMetadata createMetadata(String name)
    {
        final OMEXMLMetadata result = OMEUtil.createOMEXMLMetadata();
        final OME ome = getOME(result);

        ensureSerie(ome, 0);

        result.setImageID(MetadataTools.createLSID("Image", 0), 0);
        result.setImageName(name, 0);

        return result;
    }

    /**
     * @deprecated Use {@link #createMetadata(String)} instead.
     */
    @Deprecated
    public static OMEXMLMetadataImpl createDefaultMetadata(String name)
    {
        return (OMEXMLMetadataImpl) createMetadata(name);
    }

    /**
     * @deprecated Use {@link OMEUtil#createOMEXMLMetadata(MetadataRetrieve, int)}
     */
    @Deprecated
    public static OMEXMLMetadata createOMEMetadata(MetadataRetrieve metadata, int serie)
    {
        return OMEUtil.createOMEXMLMetadata(metadata, serie);
    }

    /**
     * Set metadata object with the given image properties.
     * 
     * @param metadata
     *        metadata object to fill.
     * @param sizeX
     *        width in pixels (need to be >= 1)
     * @param sizeY
     *        height in pixels (need to be >= 1)
     * @param sizeC
     *        number of channel (need to be >= 1)
     * @param sizeZ
     *        number of Z slices (need to be >= 1)
     * @param sizeT
     *        number of T frames (need to be >= 1)
     * @param dataType
     *        data type.
     * @param separateChannel
     *        true if we want channel data to be separated.
     * @throws ServiceException
     */
    public static void setMetaData(OMEXMLMetadata metadata, int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT,
            DataType dataType, boolean separateChannel) throws ServiceException
    {
        OME ome = (OME) metadata.getRoot();

        if (ome == null)
        {
            metadata.createRoot();
            ome = (OME) metadata.getRoot();
        }

        // keep only one image
        setNumSerie(metadata, 1);
        // clean TiffData metadata (can produce error on reloading)
        cleanTiffData(ome.getImage(0));
        // clean binData metadata (can produce error on reloading)
        cleanBinData(ome.getImage(0));

        if (StringUtil.isEmpty(metadata.getImageID(0)))
            metadata.setImageID(MetadataTools.createLSID("Image", 0), 0);
        if (StringUtil.isEmpty(metadata.getImageName(0)))
            metadata.setImageName("Sample", 0);

        if (StringUtil.isEmpty(metadata.getPixelsID(0)))
            metadata.setPixelsID(MetadataTools.createLSID("Pixels", 0), 0);

        // prefer big endian as JVM is big endian
        metadata.setPixelsBigEndian(Boolean.TRUE, 0);
        metadata.setPixelsBinDataBigEndian(Boolean.TRUE, 0, 0);
        // force XYCZT dimension order
        metadata.setPixelsDimensionOrder(DimensionOrder.XYCZT, 0);

        // adjust pixel type and dimension size
        metadata.setPixelsType(dataType.toPixelType(), 0);
        metadata.setPixelsSizeX(OMEUtil.getPositiveInteger(sizeX), 0);
        metadata.setPixelsSizeY(OMEUtil.getPositiveInteger(sizeY), 0);
        metadata.setPixelsSizeC(OMEUtil.getPositiveInteger(sizeC), 0);
        metadata.setPixelsSizeZ(OMEUtil.getPositiveInteger(sizeZ), 0);
        metadata.setPixelsSizeT(OMEUtil.getPositiveInteger(sizeT), 0);

        // clean plane metadata outside allowed range
        cleanPlanes(ome.getImage(0));

        // get time interval information
        double timeInterval = MetaDataUtil.getTimeInterval(metadata, 0, 0d);
        // not defined ?
        if (timeInterval == 0d)
        {
            // try to compute it from time positions
            timeInterval = getTimeIntervalFromTimePositions(metadata, 0);
            // we got something --> set it as the time interval
            if (timeInterval != 0d)
                MetaDataUtil.setTimeInterval(metadata, 0, timeInterval);
        }

        // fix channel number depending separate channel flag
        if (separateChannel)
        {
            // set channel number
            setNumChannel(metadata, 0, sizeC);

            for (int c = 0; c < sizeC; c++)
            {
                if (StringUtil.isEmpty(metadata.getChannelID(0, c)))
                    metadata.setChannelID(MetadataTools.createLSID("Channel", 0, c), 0, c);
                metadata.setChannelSamplesPerPixel(new PositiveInteger(Integer.valueOf(1)), 0, c);
                // metadata.getChannelName(0, c);
            }
        }
        else
        {
            // set channel number
            setNumChannel(metadata, 0, 1);

            if (StringUtil.isEmpty(metadata.getChannelID(0, 0)))
                metadata.setChannelID(MetadataTools.createLSID("Channel", 0, 0), 0, 0);
            metadata.setChannelSamplesPerPixel(new PositiveInteger(Integer.valueOf(sizeC)), 0, 0);
        }
    }

    /**
     * @deprecated Use {@link #setMetaData(OMEXMLMetadata, int, int, int, int, int, DataType, boolean)} instead
     */
    @Deprecated
    public static void setMetaData(OMEXMLMetadataImpl metadata, int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT,
            DataType dataType, boolean separateChannel) throws ServiceException
    {
        setMetaData((OMEXMLMetadata) metadata, sizeX, sizeY, sizeC, sizeZ, sizeT, dataType, separateChannel);
    }

    /**
     * Generates meta data for the given image properties.
     * 
     * @param sizeX
     *        width in pixels.
     * @param sizeY
     *        height in pixels.
     * @param sizeC
     *        number of channel.
     * @param sizeZ
     *        number of Z slices.
     * @param sizeT
     *        number of T frames.
     * @param dataType
     *        data type.
     * @param separateChannel
     *        true if we want channel data to be separated.
     * @return OMEXMLMetadata
     * @throws ServiceException
     */
    public static OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT,
            DataType dataType, boolean separateChannel) throws ServiceException
    {
        final OMEXMLMetadata result = createDefaultMetadata("Sample");

        setMetaData(result, sizeX, sizeY, sizeC, sizeZ, sizeT, dataType, separateChannel);

        return result;
    }

    /**
     * Generates Meta Data for the given arguments.
     * 
     * @see #setMetaData(OMEXMLMetadata, int, int, int, int, int, DataType, boolean)
     */
    public static OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, DataType dataType,
            boolean separateChannel) throws ServiceException
    {
        return generateMetaData(sizeX, sizeY, sizeC, 1, 1, dataType, separateChannel);
    }

    /**
     * Generates Meta Data for the given BufferedImage.
     * 
     * @see #setMetaData(OMEXMLMetadata, int, int, int, int, int, DataType, boolean)
     */
    public static OMEXMLMetadata generateMetaData(IcyBufferedImage image, boolean separateChannel)
            throws ServiceException
    {
        return generateMetaData(image.getSizeX(), image.getSizeY(), image.getSizeC(), image.getDataType_(),
                separateChannel);
    }

    /**
     * @deprecated Use {@link #generateMetaData(Sequence, boolean)} instead.
     */
    @Deprecated
    public static OMEXMLMetadata generateMetaData(Sequence sequence, boolean useZ, boolean useT,
            boolean separateChannel) throws ServiceException
    {
        return generateMetaData(sequence, separateChannel);
    }

    /**
     * @deprecated Use {@link #generateMetaData(Sequence, boolean)} instead.
     */
    @Deprecated
    public static OMEXMLMetadata generateMetaData(Sequence sequence, int sizeZ, int sizeT, boolean separateChannel)
            throws ServiceException
    {
        return generateMetaData(sequence, separateChannel);
    }

    /**
     * Generates Meta Data for the given Sequence.
     * 
     * @see #setMetaData(OMEXMLMetadata, int, int, int, int, int, DataType, boolean)
     */
    public static OMEXMLMetadata generateMetaData(Sequence sequence, boolean separateChannel) throws ServiceException
    {
        // do a copy as we mean use several time the same source sequence metadata
        final OMEXMLMetadata result = OMEUtil.createOMEXMLMetadata(sequence.getMetadata());

        setMetaData(result, sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(), sequence.getSizeZ(),
                sequence.getSizeT(), sequence.getDataType_(), separateChannel);

        return result;
    }

    /**
     * Keep only the specified image serie.
     */
    public static void keepSingleSerie(OMEXMLMetadata metaData, int num)
    {
        final OME ome = getOME(metaData);
        final int numSeries = ome.sizeOfImageList();
        final Image img = getSerie(metaData, num);

        // keep only the desired image
        for (int i = numSeries - 1; i >= 0; i--)
            if (i != num)
                ome.removeImage(ome.getImage(i));

        final Set<Object> toKeep = new HashSet<Object>();

        // try to keep associated dataset only
        toKeep.clear();
        for (int i = 0; i < img.sizeOfLinkedDatasetList(); i++)
            toKeep.add(img.getLinkedDataset(i));
        if (!toKeep.isEmpty())
        {
            for (int i = ome.sizeOfDatasetList() - 1; i >= 0; i--)
            {
                final Dataset obj = ome.getDataset(i);
                if (!toKeep.contains(obj))
                    ome.removeDataset(obj);
            }
        }
        // just assume they are indirectly linked
        else if (ome.sizeOfDatasetList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeDataset(ome.getDataset(i));
        }

        // try to keep associated ROI only
        toKeep.clear();
        for (int i = 0; i < img.sizeOfLinkedROIList(); i++)
            toKeep.add(img.getLinkedROI(i));
        if (!toKeep.isEmpty())
        {
            for (int i = ome.sizeOfROIList() - 1; i >= 0; i--)
            {
                final ROI obj = ome.getROI(i);
                if (!toKeep.contains(obj))
                    ome.removeROI(obj);
            }
        }
        // just assume they are indirectly linked
        else if (ome.sizeOfROIList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeROI(ome.getROI(i));
        }

        // try to keep associated experiment only
        final Experiment exp = img.getLinkedExperiment();
        if (exp != null)
        {
            for (int i = ome.sizeOfExperimentList() - 1; i >= 0; i--)
            {
                final Experiment obj = ome.getExperiment(i);
                if (obj != exp)
                    ome.removeExperiment(obj);
            }
        }
        else if (ome.sizeOfExperimentList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeExperiment(ome.getExperiment(i));
        }

        // try to keep associated experimenter only
        final Experimenter expr = img.getLinkedExperimenter();
        if (expr != null)
        {
            for (int i = ome.sizeOfExperimenterList() - 1; i >= 0; i--)
            {
                final Experimenter obj = ome.getExperimenter(i);
                if (obj != expr)
                    ome.removeExperimenter(obj);
            }
        }
        else if (ome.sizeOfExperimenterList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeExperimenter(ome.getExperimenter(i));
        }

        // try to keep associated experimenter group only
        final ExperimenterGroup exprGroup = img.getLinkedExperimenterGroup();
        if (exprGroup != null)
        {
            for (int i = ome.sizeOfExperimenterGroupList() - 1; i >= 0; i--)
            {
                final ExperimenterGroup obj = ome.getExperimenterGroup(i);
                if (obj != exprGroup)
                    ome.removeExperimenterGroup(obj);
            }
        }
        else if (ome.sizeOfExperimenterGroupList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeExperimenterGroup(ome.getExperimenterGroup(i));
        }

        // try to keep associated instrument only
        final Instrument instr = img.getLinkedInstrument();
        if (instr != null)
        {
            for (int i = ome.sizeOfInstrumentList() - 1; i >= 0; i--)
            {
                final Instrument obj = ome.getInstrument(i);
                if (obj != instr)
                    ome.removeInstrument(obj);
            }
        }
        else if (ome.sizeOfInstrumentList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeInstrument(ome.getInstrument(i));
        }

        // others misc data to clean
        if (ome.sizeOfPlateList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removePlate(ome.getPlate(i));
        }
        if (ome.sizeOfProjectList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeProject(ome.getProject(i));
        }
        if (ome.sizeOfScreenList() == numSeries)
        {
            for (int i = numSeries - 1; i >= 0; i--)
                if (i != num)
                    ome.removeScreen(ome.getScreen(i));
        }
    }

    /**
     * Keep only the specified plane metadata.
     */
    public static void keepSinglePlane(Image img, int index)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return;

        final int numPlane = pix.sizeOfPlaneList();
        final Plane plane = getPlane(pix, index);

        // keep only the desired plane
        for (int i = numPlane - 1; i >= 0; i--)
        {
            if (i != index)
                pix.removePlane(pix.getPlane(i));
        }

        final Set<Object> toKeep = new HashSet<Object>();

        // try to keep associated annotation only
        toKeep.clear();
        for (int i = 0; i < plane.sizeOfLinkedAnnotationList(); i++)
            toKeep.add(plane.getLinkedAnnotation(i));
        if (!toKeep.isEmpty())
        {
            for (int i = img.sizeOfLinkedAnnotationList() - 1; i >= 0; i--)
            {
                final Annotation obj = img.getLinkedAnnotation(i);
                if (!toKeep.contains(obj))
                    img.unlinkAnnotation(obj);
            }
        }
        // just assume they are indirectly linked
        else if (img.sizeOfLinkedAnnotationList() == numPlane)
        {
            for (int i = numPlane - 1; i >= 0; i--)
                if (i != index)
                    img.unlinkAnnotation(img.getLinkedAnnotation(i));
        }

        // clean some data
        if (pix.sizeOfBinDataList() == numPlane)
        {
            for (int i = numPlane - 1; i >= 0; i--)
                if (i != index)
                    pix.removeBinData(pix.getBinData(i));
        }
        if (pix.sizeOfTiffDataList() == numPlane)
        {
            for (int i = numPlane - 1; i >= 0; i--)
                if (i != index)
                    pix.removeTiffData(pix.getTiffData(i));
        }
    }

    /**
     * @deprecated Use {@link #keepSingleSerie(OMEXMLMetadata, int)} instead
     */
    @Deprecated
    public static void keepSingleSerie(OMEXMLMetadataImpl metaData, int num)
    {
        keepSingleSerie((OMEXMLMetadata) metaData, num);
    }

    /**
     * Keep only plane(s) at specified C, Z, T position from the given metadata.
     * 
     * @param img
     *        image metadata to clean plane from
     * @param posT
     *        keep Plane at given T position (-1 to keep all)
     * @param posZ
     *        keep Plane at given Z position (-1 to keep all)
     * @param posC
     *        keep Plane at given C position (-1 to keep all)
     */
    public static void keepPlanes(Image img, int posT, int posZ, int posC)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return;

        final int sizeT = OMEUtil.getValue(pix.getSizeT(), 0);
        final int sizeZ = OMEUtil.getValue(pix.getSizeZ(), 0);
        final int sizeC = OMEUtil.getValue(pix.getSizeC(), 0);

        for (int t = 0; t < sizeT; t++)
        {
            boolean remove = (posT != -1) && (posT != t);

            for (int z = 0; z < sizeZ; z++)
            {
                remove |= (posZ != -1) && (posZ != z);

                for (int c = 0; c < sizeC; c++)
                {
                    remove |= (posC != -1) && (posC != c);

                    if (remove)
                        removePlane(img, t, z, c);
                }
            }
        }
    }

    /**
     * Keep only plane(s) at specified C, Z, T position from the given metadata.
     * 
     * @param posT
     *        keep Plane at given T position (-1 to keep all)
     * @param posZ
     *        keep Plane at given Z position (-1 to keep all)
     * @param posC
     *        keep Plane at given C position (-1 to keep all)
     */
    public static void keepPlanes(OMEXMLMetadata metadata, int serie, int posT, int posZ, int posC)
    {
        final Image img = getSerie(metadata, serie);

        if (img != null)
            keepPlanes(img, posT, posZ, posC);
    }

    /**
     * Clean plane(s) which are outside the pixel sizeC / sizeZ and sizeT.
     * 
     * @param img
     *        image metadata to clean plane from
     */
    public static void cleanPlanes(Image img)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return;

        final int sizeT = OMEUtil.getValue(pix.getSizeT(), 0);
        final int sizeZ = OMEUtil.getValue(pix.getSizeZ(), 0);
        final int sizeC = OMEUtil.getValue(pix.getSizeC(), 0);
        if ((sizeT < 1) || (sizeZ < 1) || (sizeC < 1))
            return;

        // get allowed maximum plane
        final int allowedMaxPlaneIndex = getPlaneIndex(pix, sizeT - 1, sizeZ - 1, sizeC - 1);
        // current number of plane
        int maxPlaneIndex = pix.sizeOfPlaneList() - 1;

        // remove plan outside allowed region
        while (maxPlaneIndex > allowedMaxPlaneIndex)
            removePlane(img, maxPlaneIndex--);
    }

    /**
     * Clean TiffData packet
     * 
     * @param img
     *        image metadata to clean TiffData from
     */
    public static void cleanTiffData(Image img)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return;

        while (pix.sizeOfTiffDataList() > 0)
            pix.removeTiffData(pix.getTiffData(pix.sizeOfTiffDataList() - 1));
    }

    /**
     * Clean BinData packet
     * 
     * @param img
     *        image metadata to clean BinData from
     */
    public static void cleanBinData(Image img)
    {
        final Pixels pix = img.getPixels();
        if (pix == null)
            return;

        while (pix.sizeOfBinDataList() > 0)
            pix.removeBinData(pix.getBinData(pix.sizeOfBinDataList() - 1));
    }

    /**
     * Cleanup the meta data (sometime we have empty data structure sitting there)
     */
    public static void clean(OMEXMLMetadata metaData)
    {
        final OME ome = getOME(metaData);
        final StructuredAnnotations annotations = ome.getStructuredAnnotations();

        if (annotations != null)
        {
            for (int i = annotations.sizeOfXMLAnnotationList() - 1; i >= 0; i--)
            {
                final XMLAnnotation xmlAnnotation = annotations.getXMLAnnotation(i);

                if (isEmpty(xmlAnnotation))
                    annotations.removeXMLAnnotation(xmlAnnotation);
            }
        }
    }

    /**
     * @deprecated Use {@link #clean(OMEXMLMetadata)} instead.
     */
    @Deprecated
    public static void clean(OMEXMLMetadataImpl metaData)
    {
        clean((OMEXMLMetadata) metaData);
    }

    /**
     * Returns <code>true</code> if the specified XML annotation are empty.
     */
    public static boolean isEmpty(XMLAnnotation xmlAnnotation)
    {
        return StringUtil.isEmpty(xmlAnnotation.getDescription()) && StringUtil.isEmpty(xmlAnnotation.getValue());
    }
}