001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.sequence;
020
021import java.awt.Dimension;
022import java.awt.Rectangle;
023import java.awt.image.BufferedImage;
024import java.io.Closeable;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map.Entry;
033import java.util.Set;
034import java.util.TreeMap;
035
036import javax.swing.undo.UndoManager;
037
038import org.w3c.dom.Node;
039
040import icy.common.CollapsibleEvent;
041import icy.common.UpdateEventHandler;
042import icy.common.exception.TooLargeArrayException;
043import icy.common.listener.ChangeListener;
044import icy.file.FileUtil;
045import icy.gui.viewer.Viewer;
046import icy.image.IcyBufferedImage;
047import icy.image.IcyBufferedImageEvent;
048import icy.image.IcyBufferedImageListener;
049import icy.image.IcyBufferedImageUtil;
050import icy.image.ImageProvider;
051import icy.image.colormap.IcyColorMap;
052import icy.image.colormodel.IcyColorModel;
053import icy.image.colormodel.IcyColorModelEvent;
054import icy.image.colormodel.IcyColorModelListener;
055import icy.image.lut.LUT;
056import icy.main.Icy;
057import icy.math.MathUtil;
058import icy.math.Scaler;
059import icy.math.UnitUtil;
060import icy.math.UnitUtil.UnitPrefix;
061import icy.painter.Overlay;
062import icy.painter.OverlayEvent;
063import icy.painter.OverlayEvent.OverlayEventType;
064import icy.painter.OverlayListener;
065import icy.painter.OverlayWrapper;
066import icy.painter.Painter;
067import icy.preferences.GeneralPreferences;
068import icy.roi.ROI;
069import icy.roi.ROI2D;
070import icy.roi.ROI3D;
071import icy.roi.ROIEvent;
072import icy.roi.ROIListener;
073import icy.sequence.SequenceEvent.SequenceEventSourceType;
074import icy.sequence.SequenceEvent.SequenceEventType;
075import icy.sequence.edit.DataSequenceEdit;
076import icy.sequence.edit.DefaultSequenceEdit;
077import icy.sequence.edit.MetadataSequenceEdit;
078import icy.sequence.edit.ROIAddSequenceEdit;
079import icy.sequence.edit.ROIAddsSequenceEdit;
080import icy.sequence.edit.ROIRemoveSequenceEdit;
081import icy.sequence.edit.ROIRemovesSequenceEdit;
082import icy.system.IcyExceptionHandler;
083import icy.system.thread.ThreadUtil;
084import icy.type.DataType;
085import icy.type.TypeUtil;
086import icy.type.collection.CollectionUtil;
087import icy.type.collection.array.Array1DUtil;
088import icy.type.dimension.Dimension5D;
089import icy.type.rectangle.Rectangle5D;
090import icy.undo.IcyUndoManager;
091import icy.undo.IcyUndoableEdit;
092import icy.util.OMEUtil;
093import icy.util.StringUtil;
094import loci.formats.ome.OMEXMLMetadataImpl;
095import ome.xml.meta.OMEXMLMetadata;
096
097/**
098 * Image sequence object.<br>
099 * A <code>Sequence</code> is basically a 5 dimension (XYCZT) image where :<br>
100 * XY dimension = planar image<br>
101 * C dimension = channel<br>
102 * Z dimension = depth<br>
103 * T dimension = time<br>
104 * <br>
105 * The XYC dimensions are bounded into the {@link IcyBufferedImage} object so <code>Sequence</code> define a list of
106 * {@link IcyBufferedImage} where each image is associated to a Z and T
107 * information.
108 * 
109 * @author Fabrice de Chaumont & Stephane
110 */
111
112public class Sequence implements SequenceModel, IcyColorModelListener, IcyBufferedImageListener, ChangeListener,
113        ROIListener, OverlayListener
114{
115    public static final String DEFAULT_NAME = "no name";
116
117    /**
118     * @deprecated
119     */
120    @Deprecated
121    public static final int TYPE_BYTE = TypeUtil.TYPE_BYTE;
122    /**
123     * @deprecated
124     */
125    @Deprecated
126    public static final int TYPE_DOUBLE = TypeUtil.TYPE_DOUBLE;
127    /**
128     * @deprecated
129     */
130    @Deprecated
131    public static final int TYPE_FLOAT = TypeUtil.TYPE_FLOAT;
132    /**
133     * @deprecated
134     */
135    @Deprecated
136    public static final int TYPE_INT = TypeUtil.TYPE_INT;
137    /**
138     * @deprecated
139     */
140    @Deprecated
141    public static final int TYPE_SHORT = TypeUtil.TYPE_SHORT;
142    /**
143     * @deprecated
144     */
145    @Deprecated
146    public static final int TYPE_UNDEFINED = TypeUtil.TYPE_UNDEFINED;
147
148    public static final String ID_NAME = "name";
149
150    public static final String ID_POSITION_X = "positionX";
151    public static final String ID_POSITION_Y = "positionY";
152    public static final String ID_POSITION_Z = "positionZ";
153    public static final String ID_POSITION_T = "positionT";
154    public static final String ID_POSITION_T_OFFSET = "positionTOffset";
155
156    public static final String ID_PIXEL_SIZE_X = "pixelSizeX";
157    public static final String ID_PIXEL_SIZE_Y = "pixelSizeY";
158    public static final String ID_PIXEL_SIZE_Z = "pixelSizeZ";
159    public static final String ID_TIME_INTERVAL = "timeInterval";
160
161    public static final String ID_CHANNEL_NAME = "channelName";
162
163    public static final String ID_VIRTUAL = "virtual";
164
165    /**
166     * id generator
167     */
168    protected static int id_gen = 1;
169
170    /**
171     * volumetric images (4D [XYCZ])
172     */
173    protected final TreeMap<Integer, VolumetricImage> volumetricImages;
174    /**
175     * painters
176     */
177    protected final Set<Overlay> overlays;
178    /**
179     * ROIs
180     */
181    protected final Set<ROI> rois;
182
183    /**
184     * id of sequence (uniq during an Icy session)
185     */
186    protected final int id;
187    /**
188     * colorModel of sequence
189     */
190    protected IcyColorModel colorModel;
191    /**
192     * default lut for this sequence
193     */
194    protected LUT defaultLut;
195    /**
196     * user lut for this sequence (saved in metadata)
197     */
198    protected LUT userLut;
199    /**
200     * Origin filename (from/to which the sequence has been loaded/saved)<br>
201     * null --> no file attachment<br>
202     * directory or metadata file --> multiples files attachment<br>
203     * image file --> single file attachment
204     */
205    protected String filename;
206    /**
207     * Returns the {@link ImageProvider} used to load the sequence data.<br>
208     * It can return <code>null</code> if the Sequence was not loaded from a specific resource or if it was saved in between.
209     */
210    protected ImageProvider imageProvider;
211
212    /**
213     * Resolution level from the original image<br>
214     * 0 --> full image resolution<br>
215     * 1 --> resolution / 2<br>
216     * 2 --> resolution / 4<br>
217     * 3 --> ...<br>
218     * Default value is 0
219     */
220    protected int originResolution;
221    /**
222     * Region (X,Y) from original image if this image is a crop of the original image.<br>
223     * Default value is <code>null</code> (no crop)
224     */
225    protected Rectangle originXYRegion;
226    /**
227     * Z range from original image if this image is a crop in Z of the original image.<br>
228     * Default value is -1, -1 if we have the whole Z range.
229     */
230    protected int originZMin;
231    protected int originZMax;
232    /**
233     * T range from original image if this image is a crop in T of the original image.<br>
234     * Default value is -1, -1 if we have the whole T range.
235     */
236    protected int originTMin;
237    protected int originTMax;
238    /**
239     * Channel position from original image if this image is a single channel extraction of the original image.<br>
240     * Default value is -1 which mean that all channels were preserved.
241     */
242    protected int originChannel;
243
244    /**
245     * Metadata
246     */
247    protected OMEXMLMetadata metaData;
248    // /**
249    // * X, Y, Z resolution (in mm)
250    // */
251    // private double pixelSizeX;
252    // private double pixelSizeY;
253    // private double pixelSizeZ;
254    // /**
255    // * T resolution (in ms)
256    // */
257    // private double timeInterval;
258    // /**
259    // * channels name
260    // */
261    // private String channelsName[];
262
263    // /**
264    // * automatic update of component absolute bounds
265    // */
266    // private boolean componentAbsBoundsAutoUpdate;
267    /**
268     * automatic update of channel bounds
269     */
270    protected boolean autoUpdateChannelBounds;
271    /**
272     * persistent object to load/save data (XML format)
273     */
274    protected final SequencePersistent persistent;
275    /**
276     * undo manager
277     */
278    protected final IcyUndoManager undoManager;
279
280    /**
281     * internal updater
282     */
283    protected final UpdateEventHandler updater;
284    /**
285     * listeners
286     */
287    protected final List<SequenceListener> listeners;
288    protected final List<SequenceModelListener> modelListeners;
289
290    /**
291     * internals
292     */
293    protected boolean channelBoundsInvalid;
294
295    /**
296     * Creates a new empty sequence with specified meta data object and name.
297     */
298    public Sequence(OMEXMLMetadata meta, String name)
299    {
300        super();
301
302        // set id
303        synchronized (Sequence.class)
304        {
305            id = id_gen;
306            id_gen++;
307        }
308
309        // set metadata object
310        if (meta == null)
311            metaData = MetaDataUtil.createMetadata(name);
312        else
313            metaData = meta;
314
315        // set name
316        if (!StringUtil.isEmpty(name))
317            MetaDataUtil.setName(metaData, 0, name);
318        else
319        {
320            // default name
321            if (StringUtil.isEmpty(MetaDataUtil.getName(metaData, 0)))
322                MetaDataUtil.setName(metaData, 0, DEFAULT_NAME + StringUtil.toString(id, 3));
323        }
324        filename = null;
325        imageProvider = null;
326
327        originResolution = 0;
328        originXYRegion = null;
329        originZMin = -1;
330        originZMax = -1;
331        originTMin = -1;
332        originTMax = -1;
333        originChannel = -1;
334
335        // default pixel size and time interval
336        if (Double.isNaN(MetaDataUtil.getPixelSizeX(metaData, 0, Double.NaN)))
337            MetaDataUtil.setPixelSizeX(metaData, 0, 1d);
338        if (Double.isNaN(MetaDataUtil.getPixelSizeY(metaData, 0, Double.NaN)))
339            MetaDataUtil.setPixelSizeY(metaData, 0, 1d);
340        if (Double.isNaN(MetaDataUtil.getPixelSizeZ(metaData, 0, Double.NaN)))
341            MetaDataUtil.setPixelSizeZ(metaData, 0, 1d);
342        if (Double.isNaN(MetaDataUtil.getTimeInterval(metaData, 0, Double.NaN)))
343            MetaDataUtil.setTimeInterval(metaData, 0, 1d);
344
345        volumetricImages = new TreeMap<Integer, VolumetricImage>();
346        overlays = new HashSet<Overlay>();
347        rois = new HashSet<ROI>();
348        persistent = new SequencePersistent(this);
349        undoManager = new IcyUndoManager(this, GeneralPreferences.getHistorySize());
350
351        updater = new UpdateEventHandler(this, false);
352        listeners = new ArrayList<SequenceListener>();
353        modelListeners = new ArrayList<SequenceModelListener>();
354
355        // no colorModel yet
356        colorModel = null;
357        defaultLut = null;
358        userLut = null;
359        channelBoundsInvalid = false;
360        // automatic update of channel bounds
361        autoUpdateChannelBounds = true;
362    }
363
364    /**
365     * @deprecated Use {@link #Sequence(OMEXMLMetadata, String)} instead.
366     */
367    @Deprecated
368    public Sequence(OMEXMLMetadataImpl meta, String name)
369    {
370        this((OMEXMLMetadata) meta, name);
371    }
372
373    /**
374     * Creates a sequence with specified name and containing the specified image
375     */
376    public Sequence(String name, IcyBufferedImage image)
377    {
378        this(name, (BufferedImage) image);
379    }
380
381    /**
382     * Creates a sequence with specified name and containing the specified image
383     */
384    public Sequence(String name, BufferedImage image)
385    {
386        this((OMEXMLMetadata) null, name);
387
388        addImage(image);
389    }
390
391    /**
392     * @deprecated Use {@link #Sequence(OMEXMLMetadata)} instead.
393     */
394    @Deprecated
395    public Sequence(OMEXMLMetadataImpl meta)
396    {
397        this((OMEXMLMetadata) meta);
398    }
399
400    /**
401     * Creates a new empty sequence with specified metadata.
402     */
403    public Sequence(OMEXMLMetadata meta)
404    {
405        this(meta, null);
406    }
407
408    /**
409     * Creates a sequence containing the specified image.
410     */
411    public Sequence(IcyBufferedImage image)
412    {
413        this((BufferedImage) image);
414    }
415
416    /**
417     * Creates a sequence containing the specified image.
418     */
419    public Sequence(BufferedImage image)
420    {
421        this((OMEXMLMetadata) null, null);
422
423        addImage(image);
424    }
425
426    /**
427     * Creates an empty sequence with specified name.
428     */
429    public Sequence(String name)
430    {
431        this((OMEXMLMetadata) null, name);
432    }
433
434    /**
435     * Creates an empty sequence.
436     */
437    public Sequence()
438    {
439        this((OMEXMLMetadata) null, null);
440    }
441
442    @Override
443    protected void finalize() throws Throwable
444    {
445        try
446        {
447            // close image provider if needed
448            if ((imageProvider != null) && (imageProvider instanceof Closeable))
449                ((Closeable) imageProvider).close();
450        }
451        catch (IOException e)
452        {
453            // ignore
454        }
455
456        super.finalize();
457    }
458
459    /**
460     * This method close all attached viewers
461     */
462    public void close()
463    {
464        Icy.getMainInterface().closeSequence(this);
465    }
466
467    /**
468     * Called when sequence has been closed (all viewers displaying it closed).<br>
469     * <i>Used internally, you should not call it this method directly !</i>
470     */
471    public void closed()
472    {
473        // do this in background as it can take sometime
474        while (!ThreadUtil.bgRun(new Runnable()
475        {
476            @Override
477            public void run()
478            {
479                // Sequence persistence enabled --> save XML
480                if (GeneralPreferences.getSequencePersistence())
481                    saveXMLData();
482            }
483        }))
484        {
485            // wait until the process execute
486            ThreadUtil.sleep(10L);
487        }
488
489        // notify close
490        fireClosedEvent();
491    }
492
493    /**
494     * Copy data and metadata from the specified Sequence
495     * 
496     * @param source
497     *        the source sequence to copy data from
498     * @param copyName
499     *        if set to <code>true</code> it will also copy the name from the source sequence
500     */
501    public void copyFrom(Sequence source, boolean copyName)
502    {
503        copyDataFrom(source);
504        copyMetaDataFrom(source, copyName);
505    }
506
507    /**
508     * Copy data from the specified Sequence
509     */
510    public void copyDataFrom(Sequence source)
511    {
512        final int sizeT = source.getSizeT();
513        final int sizeZ = source.getSizeZ();
514
515        beginUpdate();
516        try
517        {
518            removeAllImages();
519            for (int t = 0; t < sizeT; t++)
520            {
521                for (int z = 0; z < sizeZ; z++)
522                {
523                    final IcyBufferedImage img = source.getImage(t, z);
524
525                    if (img != null)
526                        setImage(t, z, IcyBufferedImageUtil.getCopy(img));
527                    else
528                        source.setImage(t, z, null);
529                }
530            }
531        }
532        finally
533        {
534            endUpdate();
535        }
536    }
537
538    /**
539     * Copy metadata from the specified Sequence
540     * 
541     * @param source
542     *        the source sequence to copy metadata from
543     * @param copyName
544     *        if set to <code>true</code> it will also copy the name from the source sequence
545     */
546    public void copyMetaDataFrom(Sequence source, boolean copyName)
547    {
548        // copy all metadata from source
549        metaData = OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata());
550
551        // restore name if needed
552        if (copyName)
553            setName(source.getName());
554
555        // notify metadata changed
556        metaChanged(null);
557    }
558
559    /**
560     * Create a complete restore point for this sequence.
561     * 
562     * @param name
563     *        restore point name (visible in the History panel)
564     * @return false if for some reason the operation failed (out of memory for instance)
565     * @see #undo()
566     */
567    public boolean createUndoPoint(String name)
568    {
569        try
570        {
571            undoManager.addEdit(new DefaultSequenceEdit(SequenceUtil.getCopy(this, false, false, false), this));
572            return true;
573        }
574        catch (Throwable t)
575        {
576            return false;
577        }
578    }
579
580    /**
581     * Create a restore point for sequence data.
582     * 
583     * @param name
584     *        restore point name (visible in the History panel)
585     * @return false if for some reason the operation failed (out of memory for instance)
586     * @see #undo()
587     */
588    public boolean createUndoDataPoint(String name)
589    {
590        try
591        {
592            undoManager.addEdit(new DataSequenceEdit(SequenceUtil.getCopy(this, false, false, false), this, name));
593            return true;
594        }
595        catch (Throwable t)
596        {
597            return false;
598        }
599    }
600
601    /**
602     * Create a restore point for sequence metadata.
603     * 
604     * @param name
605     *        restore point name (visible in the History panel)
606     * @return false if for some reason the operation failed (out of memory for instance)
607     * @see #undo()
608     */
609    public boolean createUndoMetadataPoint(String name)
610    {
611        try
612        {
613            undoManager.addEdit(new MetadataSequenceEdit(OMEUtil.createOMEXMLMetadata(metaData), this, name));
614            return true;
615        }
616        catch (Throwable t)
617        {
618            return false;
619        }
620    }
621
622    /**
623     * Add an Undoable edit to the Sequence UndoManager
624     * 
625     * @param edit
626     *        the undoable edit to add
627     * @return <code>false</code> if the operation failed
628     */
629    public boolean addUndoableEdit(IcyUndoableEdit edit)
630    {
631        if (edit != null)
632            return undoManager.addEdit(edit);
633
634        return false;
635    }
636
637    /**
638     * Undo to the last <i>Undoable</i> change set in the Sequence {@link UndoManager}
639     * 
640     * @return <code>true</code> if the operation succeed
641     * @see #createUndoPoint(String)
642     * @see UndoManager#undo()
643     */
644    public boolean undo()
645    {
646        if (undoManager.canUndo())
647        {
648            undoManager.undo();
649            return true;
650        }
651
652        return false;
653    }
654
655    /**
656     * Redo the next <i>Undoable</i> change set in the Sequence {@link UndoManager}
657     * 
658     * @return <code>true</code> if the operation succeed
659     * @see #createUndoPoint(String)
660     * @see UndoManager#redo()
661     */
662    public boolean redo()
663    {
664        if (undoManager.canRedo())
665        {
666            undoManager.redo();
667            return true;
668        }
669
670        return false;
671    }
672
673    /**
674     * Clear all undo operations from the {@link UndoManager}.<br>
675     * You should use this method after you modified the sequence without providing any <i>undo</i>
676     * support.
677     */
678    public void clearUndoManager()
679    {
680        getUndoManager().discardAllEdits();
681    }
682
683    protected void setColorModel(IcyColorModel cm)
684    {
685        // remove listener
686        if (colorModel != null)
687            colorModel.removeListener(this);
688
689        colorModel = cm;
690
691        // add listener
692        if (cm != null)
693            cm.addListener(this);
694
695        // sequence type changed
696        typeChanged();
697        // sequence component bounds changed
698        componentBoundsChanged(cm, -1);
699        // sequence colormap changed
700        colormapChanged(cm, -1);
701    }
702
703    /**
704     * @deprecated Use {@link SequenceUtil#convertToType(Sequence, DataType, boolean)} instead.
705     */
706    @Deprecated
707    public Sequence convertToType(DataType dataType, boolean rescale)
708    {
709        return SequenceUtil.convertToType(this, dataType, rescale);
710    }
711
712    /**
713     * @deprecated Use {@link SequenceUtil#convertType(Sequence, DataType, Scaler[])} instead.
714     */
715    @Deprecated
716    public Sequence convertToType(DataType dataType, Scaler scaler)
717    {
718        return SequenceUtil.convertToType(this, dataType, scaler);
719    }
720
721    /**
722     * @deprecated Use {@link SequenceUtil#convertToType(Sequence, DataType, boolean)} instead
723     */
724    @Deprecated
725    public Sequence convertToType(int dataType, boolean signed, boolean rescale)
726    {
727        return convertToType(DataType.getDataType(dataType, signed), rescale);
728    }
729
730    /**
731     * @deprecated Use {@link SequenceUtil#extractChannel(Sequence, int)} instead.
732     */
733    @Deprecated
734    public Sequence extractChannel(int channelNumber)
735    {
736        return SequenceUtil.extractChannel(this, channelNumber);
737    }
738
739    /**
740     * @deprecated Use {@link SequenceUtil#extractChannels(Sequence, List)} instead.
741     */
742    @Deprecated
743    public Sequence extractChannels(List<Integer> channelNumbers)
744    {
745        return SequenceUtil.extractChannels(this, channelNumbers);
746    }
747
748    /**
749     * @deprecated Use {@link SequenceUtil#extractChannel(Sequence, int)} instead
750     */
751    @Deprecated
752    public Sequence extractBand(int bandNumber)
753    {
754        return extractChannel(bandNumber);
755    }
756
757    /**
758     * @deprecated Use {@link SequenceUtil#extractChannels(Sequence, List)} instead
759     */
760    @Deprecated
761    public Sequence extractBands(List<Integer> bandNumbers)
762    {
763        return extractChannels(bandNumbers);
764    }
765
766    /**
767     * Returns all VolumetricImage as TreeMap (contains t position)
768     */
769    public TreeMap<Integer, VolumetricImage> getVolumetricImages()
770    {
771        synchronized (volumetricImages)
772        {
773            return new TreeMap<Integer, VolumetricImage>(volumetricImages);
774        }
775    }
776
777    /**
778     * Returns all VolumetricImage
779     */
780    public ArrayList<VolumetricImage> getAllVolumetricImage()
781    {
782        synchronized (volumetricImages)
783        {
784            return new ArrayList<VolumetricImage>(volumetricImages.values());
785        }
786    }
787
788    /**
789     * Returns first viewer attached to this sequence
790     */
791    public Viewer getFirstViewer()
792    {
793        return Icy.getMainInterface().getFirstViewer(this);
794    }
795
796    /**
797     * Returns viewers attached to this sequence
798     */
799    public ArrayList<Viewer> getViewers()
800    {
801        return Icy.getMainInterface().getViewers(this);
802    }
803
804    /**
805     * Set the volatile state for this Sequence (see {@link IcyBufferedImage#setVolatile(boolean)}).<br>
806     * 
807     * @throws OutOfMemoryError
808     *         if there is not enough memory available to store image
809     *         data when setting back to <i>non volatile</i> state
810     * @throws UnsupportedOperationException
811     *         if cache engine is not initialized (error at initialization).
812     */
813    public void setVolatile(boolean value) throws OutOfMemoryError, UnsupportedOperationException
814    {
815        final boolean vol = isVolatile();
816
817        try
818        {
819            // change volatile state for all images
820            for (IcyBufferedImage image : getAllImage())
821                if (image != null)
822                    image.setVolatile(value);
823
824            if (vol != value)
825                metaChanged(ID_VIRTUAL);
826        }
827        catch (OutOfMemoryError e)
828        {
829            // not enough memory to complete the operation --> restore previous state
830            for (IcyBufferedImage image : getAllImage())
831                if (image != null)
832                    image.setVolatile(!value);
833
834            throw e;
835        }
836    }
837
838    /**
839     * Same as {@link #setVolatile(boolean)}
840     * 
841     * @throws OutOfMemoryError
842     *         if there is not enough memory available to store image
843     *         data when setting back to <i>non volatile</i> state
844     * @throws UnsupportedOperationException
845     *         if cache engine is not initialized (error at initialization).
846     */
847    public void setVirtual(boolean value) throws OutOfMemoryError, UnsupportedOperationException
848    {
849        setVolatile(value);
850    }
851
852    /**
853     * Returns true if this sequence contains volatile image (see {@link IcyBufferedImage#isVolatile()}).
854     */
855    public boolean isVolatile()
856    {
857        final IcyBufferedImage img = getFirstNonNullImage();
858        if (img != null)
859            return img.isVolatile();
860
861        return false;
862    }
863
864    /**
865     * Same as {@link #isVolatile()}
866     */
867    public boolean isVirtual()
868    {
869        return isVolatile();
870    }
871
872    /**
873     * get sequence id (this id is unique during an ICY session)
874     */
875    public int getId()
876    {
877        return id;
878    }
879
880    /**
881     * Sequence name
882     */
883    public void setName(String value)
884    {
885        if (getName() != value)
886        {
887            MetaDataUtil.setName(metaData, 0, value);
888            metaChanged(ID_NAME);
889        }
890    }
891
892    public String getName()
893    {
894        return MetaDataUtil.getName(metaData, 0);
895    }
896
897    /**
898     * Returns the origin filename (from/to which the sequence has been loaded/saved).<br>
899     * This filename information is also used to store the XML persistent data.<br/>
900     * null / empty --> no file attachment<br>
901     * image file --> single file attachment
902     * directory or metadata file --> multiples files attachment<br>
903     * 
904     * @return the origin filename
905     */
906    public String getFilename()
907    {
908        return filename;
909    }
910
911    /**
912     * Set the origin filename (from/to which the sequence has been loaded/saved).<br>
913     * When you set the filename you need to ensure that "sub part" information are correctly reset (setOriginXXX(...)
914     * methods) as this filename will be used to generate the XML persistent data file name.<br/>
915     * null / empty --> no file attachment<br>
916     * image file --> single file attachment
917     * directory or metadata file --> multiples files attachment<br>
918     * 
919     * @param filename
920     *        the filename to set
921     */
922    public void setFilename(String filename)
923    {
924        if (this.filename != filename)
925        {
926            this.filename = filename;
927        }
928    }
929
930    /**
931     * Returns the {@link ImageProvider} used to load the sequence data.<br>
932     * It can return <code>null</code> if the Sequence was not loaded from a specific resource or if it was saved in between.<br>
933     * 
934     * @return the {@link ImageProvider} used to load the Sequence
935     */
936    public ImageProvider getImageProvider()
937    {
938        return imageProvider;
939    }
940
941    /**
942     * Set the {@link ImageProvider} used to load the sequence data.<br>
943     * When you set the <i>ImageProvider</i> you need to ensure we can use it (should be opened for {@link SequenceIdImporter}).<br>
944     * Also "sub part" informations has to be correctly set (setOriginXXX(...) methods) as we may use it to retrieve sequence data from the
945     * {@link ImageProvider}.
946     */
947    public void setImageProvider(ImageProvider value)
948    {
949        try
950        {
951            // close previous
952            if ((imageProvider != null) && (imageProvider instanceof Closeable))
953                ((Closeable) imageProvider).close();
954        }
955        catch (IOException e)
956        {
957            // ignore
958        }
959
960        imageProvider = value;
961    }
962
963    /**
964     * Returns the output base filename.<br>
965     * This function is supposed to be used internally only.
966     * 
967     * @param folderExt
968     *        If the filename of this sequence refer a folder then we extend it with 'folderExt' to build the base name.
969     * @see #getOutputExtension()
970     */
971    public String getOutputBaseName(String folderExt)
972    {
973        String result = getFilename();
974
975        if (StringUtil.isEmpty(result))
976            return "";
977
978        // remove some problematic character for XML file
979        result = FileUtil.cleanPath(result);
980
981        // filename reference a directory --> use "<directory>/<folderExt>"
982        if (FileUtil.isDirectory(result))
983            result += "/" + folderExt;
984        // otherwise remove extension
985        else
986            result = FileUtil.setExtension(result, "");
987
988        return result;
989    }
990
991    /**
992     * Returns the output filename extension (not the file extension, just extension from base name).<br>
993     * The extension is based on some internals informations as serie index and resolution level.<br>
994     * This function is supposed to be used internally only.
995     * 
996     * @see #getOutputBaseName(String)
997     */
998    public String getOutputExtension()
999    {
1000        String result = "";
1001
1002        // retrieve the serie index
1003        final int serieNum = getSeries();
1004
1005        // multi serie image --> add a specific extension
1006        if (serieNum != 0)
1007            result += "_S" + serieNum;
1008
1009        // retrieve the resolution
1010        final int resolution = getOriginResolution();
1011
1012        // sub resolution --> add a specific extension
1013        if (resolution != 0)
1014            result += "_R" + resolution;
1015
1016        // retrieve the XY region offset
1017        final Rectangle xyRegion = getOriginXYRegion();
1018
1019        // not null --> add a specific extension
1020        if (xyRegion != null)
1021            result += "_XY(" + xyRegion.x + "," + xyRegion.y + "-" + xyRegion.width + "," + xyRegion.height + ")";
1022
1023        // retrieve the Z range
1024        final int zMin = getOriginZMin();
1025        final int zMax = getOriginZMax();
1026
1027        // sub Z range --> add a specific extension
1028        if ((zMin != -1) || (zMax != -1))
1029        {
1030            if (zMin == zMax)
1031                result += "_Z" + zMin;
1032            else
1033                result += "_Z(" + zMin + "-" + zMax + ")";
1034        }
1035
1036        // retrieve the T range
1037        final int tMin = getOriginTMin();
1038        final int tMax = getOriginTMax();
1039
1040        // sub T range --> add a specific extension
1041        if ((tMin != -1) || (tMax != -1))
1042        {
1043            if (tMin == tMax)
1044                result += "_T" + tMin;
1045            else
1046                result += "_T(" + tMin + "-" + tMax + ")";
1047        }
1048
1049        // retrieve the original channel
1050        final int channel = getOriginChannel();
1051
1052        // single channel extraction --> add a specific extension
1053        if (channel != -1)
1054            result += "_C" + channel;
1055
1056        return result;
1057    }
1058
1059    /**
1060     * Return the desired output filename for this Sequence.<br>
1061     * It uses the origin filename and add a specific extension depending some internals properties.
1062     * 
1063     * @param withExtension
1064     *        Add the original file extension is set to <code>true</code>
1065     * @see #getFilename()
1066     * @see #getOutputBaseName(String)
1067     * @see #getOutputExtension()
1068     */
1069    public String getOutputFilename(boolean withExtension)
1070    {
1071        String result = getFilename();
1072
1073        if (StringUtil.isEmpty(result))
1074            return "";
1075
1076        final String ext = FileUtil.getFileExtension(result, true);
1077
1078        result = getOutputBaseName(FileUtil.getFileName(result, false)) + getOutputExtension();
1079        if (withExtension)
1080            result += ext;
1081
1082        return result;
1083    }
1084
1085    /**
1086     * Returns the resolution level from the origin image (defined by {@link #getFilename()}).<br>
1087     * By default it returns 0 if this sequence corresponds to the full resolution of the original image.<br>
1088     * 1 --> original resolution / 2<br>
1089     * 2 --> original resolution / 4<br>
1090     * 3 --> original resolution / 8<br>
1091     * ...
1092     */
1093    public int getOriginResolution()
1094    {
1095        return originResolution;
1096    }
1097
1098    /**
1099     * Internal use only, you should not directly use this method.
1100     * 
1101     * @see #getOriginResolution()
1102     */
1103    public void setOriginResolution(int value)
1104    {
1105        originResolution = value;
1106    }
1107
1108    /**
1109     * Returns the region (X,Y) from original image if this image is a crop of the original image (in original image
1110     * resolution).<br>
1111     * Default value is <code>null</code> (full size).
1112     */
1113    public Rectangle getOriginXYRegion()
1114    {
1115        return originXYRegion;
1116    }
1117
1118    /**
1119     * Internal use only, you should not directly use this method.
1120     * 
1121     * @see #getOriginXYRegion()
1122     */
1123    public void setOriginXYRegion(Rectangle value)
1124    {
1125        // better to use a copy
1126        if (value != null)
1127            originXYRegion = new Rectangle(value);
1128        // clear it
1129        else
1130            originXYRegion = null;
1131    }
1132
1133    /**
1134     * Returns the Z range minimum from original image if this image is a crop in Z of the original image.<br>
1135     * Default value is -1 which mean we have the whole Z range.
1136     */
1137    public int getOriginZMin()
1138    {
1139        return originZMin;
1140    }
1141
1142    /**
1143     * Internal use only, you should not directly use this method.
1144     * 
1145     * @see #getOriginZMin()
1146     */
1147    public void setOriginZMin(int value)
1148    {
1149        originZMin = value;
1150    }
1151
1152    /**
1153     * Returns the Z range maximum from original image if this image is a crop in Z of the original image.<br>
1154     * Default value is -1 which mean we have the whole Z range.
1155     */
1156    public int getOriginZMax()
1157    {
1158        return originZMax;
1159    }
1160
1161    /**
1162     * Internal use only, you should not directly use this method.
1163     * 
1164     * @see #getOriginZMax()
1165     */
1166    public void setOriginZMax(int value)
1167    {
1168        originZMax = value;
1169    }
1170
1171    /**
1172     * Returns the T range minimum from original image if this image is a crop in T of the original image.<br>
1173     * Default value is -1 which mean we have the whole T range.
1174     */
1175    public int getOriginTMin()
1176    {
1177        return originTMin;
1178    }
1179
1180    /**
1181     * Internal use only, you should not directly use this method.
1182     * 
1183     * @see #getOriginTMin()
1184     */
1185    public void setOriginTMin(int value)
1186    {
1187        originTMin = value;
1188    }
1189
1190    /**
1191     * Returns the T range maximum from original image if this image is a crop in T of the original image.<br>
1192     * Default value is -1 which mean we have the whole T range.
1193     */
1194    public int getOriginTMax()
1195    {
1196        return originTMax;
1197    }
1198
1199    /**
1200     * Internal use only, you should not directly use this method.
1201     * 
1202     * @see #getOriginTMax()
1203     */
1204    public void setOriginTMax(int value)
1205    {
1206        originTMax = value;
1207    }
1208
1209    /**
1210     * Returns the channel position from original image if this image is a single channel extraction of the original
1211     * image.<br>
1212     * Default value is -1 which mean that all channels were preserved.
1213     */
1214    public int getOriginChannel()
1215    {
1216        return originChannel;
1217    }
1218
1219    /**
1220     * Internal use only, you should not directly use this method.
1221     * 
1222     * @see #getOriginChannel()
1223     */
1224    public void setOriginChannel(int value)
1225    {
1226        originChannel = value;
1227    }
1228
1229    /**
1230     * Reset origin information (used after saved operation normally, internal use only).
1231     */
1232    public void resetOriginInformation()
1233    {
1234        setSeries(0);
1235        setOriginChannel(-1);
1236        setOriginResolution(0);
1237        setOriginTMin(-1);
1238        setOriginTMax(-1);
1239        setOriginZMin(-1);
1240        setOriginZMax(-1);
1241        setOriginXYRegion(null);
1242    }
1243
1244    /**
1245     * Returns series index if the Sequence comes from a multi serie image.<br>
1246     * By default it returns 0 if the sequence comes from a single serie image or if this is the
1247     * first series image.
1248     */
1249    public int getSeries()
1250    {
1251        // retrieve the image ID (sequences are always single serie)
1252        final String id = MetaDataUtil.getImageID(getOMEXMLMetadata(), 0);
1253
1254        if (id.startsWith("Image:"))
1255        {
1256            final String[] serieNums = id.substring(6).split(":");
1257
1258            if (serieNums.length > 0)
1259                return StringUtil.parseInt(serieNums[0], 0);
1260        }
1261
1262        return 0;
1263    }
1264
1265    /**
1266     * Set series index if the Sequence comes from a multi serie image (internal use only).
1267     */
1268    public void setSeries(int value)
1269    {
1270        // retrieve the image ID (sequences are always single serie)
1271        final String id = MetaDataUtil.getImageID(getOMEXMLMetadata(), 0);
1272
1273        if (id.startsWith("Image:"))
1274            MetaDataUtil.setImageID(getOMEXMLMetadata(), 0, "Image:" + value);
1275    }
1276
1277    /**
1278     * @deprecated Use {@link #getSeries()} instead
1279     */
1280    @Deprecated
1281    public int getSerieIndex()
1282    {
1283        return getSeries();
1284    }
1285
1286    /**
1287     * Returns meta data object
1288     */
1289    public OMEXMLMetadata getOMEXMLMetadata()
1290    {
1291        return metaData;
1292    }
1293
1294    /**
1295     * Set the meta data object
1296     */
1297    public void setMetaData(OMEXMLMetadata metaData)
1298    {
1299        if (this.metaData != metaData)
1300        {
1301            this.metaData = metaData;
1302            // all meta data changed
1303            metaChanged(null);
1304        }
1305    }
1306
1307    /**
1308     * @deprecated Use {@link #getOMEXMLMetadata()} instead.
1309     */
1310    @Deprecated
1311    public OMEXMLMetadataImpl getMetadata()
1312    {
1313        return (OMEXMLMetadataImpl) getOMEXMLMetadata();
1314    }
1315
1316    /**
1317     * @deprecated Use {@link #setMetaData(OMEXMLMetadata)} instead.
1318     */
1319    @Deprecated
1320    public void setMetaData(OMEXMLMetadataImpl metaData)
1321    {
1322        setMetaData((OMEXMLMetadata) metaData);
1323    }
1324
1325    /**
1326     * Returns the physical position [X,Y,Z] (in µm) of the image represented by this Sequence.
1327     * This information can be used to represent the position of the image in the original sample (microscope
1328     * information) or the position of a sub image from the original image (crop operation).<br>
1329     * Note that OME store this information at Plane level (each Z,T,C), here we just use value from Plane(0,0,0) then we use the pixels size and time interval
1330     * information to compute other positions.
1331     */
1332    public double[] getPosition()
1333    {
1334        return new double[] {getPositionX(), getPositionY(), getPositionZ()};
1335    }
1336
1337    /**
1338     * Returns the X physical position / offset (in µm) of the image represented by this Sequence.<br>
1339     * This information can be used to represent the position of the image in the original sample (microscope
1340     * information) or the position of a sub image the original image (crop operation).<br>
1341     * Note that OME store this information at Plane level (each Z,T,C), here we just use value from Plane(0,0,0) then we use the pixels size and time interval
1342     * information to compute other positions.
1343     */
1344    public double getPositionX()
1345    {
1346        return MetaDataUtil.getPositionX(metaData, 0, 0, 0, 0, 0d);
1347    }
1348
1349    /**
1350     * Returns the Y physical position / offset (in µm) of the image represented by this Sequence.<br>
1351     * This information can be used to represent the position of the image in the original sample (microscope
1352     * information) or the position of a sub image the original image (crop operation).<br>
1353     * Note that OME store this information at Plane level (each Z,T,C), here we just use value from Plane(0,0,0) then we use the pixels size and time interval
1354     * information to compute other positions.
1355     */
1356    public double getPositionY()
1357    {
1358        return MetaDataUtil.getPositionY(metaData, 0, 0, 0, 0, 0d);
1359    }
1360
1361    /**
1362     * Returns the Z physical position / offset (in µm) of the image represented by this Sequence.<br>
1363     * This information can be used to represent the position of the image in the original sample (microscope
1364     * information) or the position of a sub image the original image (crop operation).<br>
1365     * Note that OME store this information at Plane level (each Z,T,C), here we just use value from Plane(0,0,0) then we use the pixels size and time interval
1366     * information to compute other positions.
1367     */
1368    public double getPositionZ()
1369    {
1370        return MetaDataUtil.getPositionZ(metaData, 0, 0, 0, 0, 0d);
1371    }
1372
1373    /**
1374     * Same as {@link #getTimeStamp()}
1375     */
1376    public long getPositionT()
1377    {
1378        return getTimeStamp();
1379    }
1380
1381    /**
1382     * Returns the timestamp (elapsed milliseconds from the Java epoch of 1970-01-01 T00:00:00Z) of the image represented by this Sequence.
1383     *
1384     * @see #getPositionTOffset(int, int, int)
1385     * @see #getTimeInterval()
1386     */
1387    public long getTimeStamp()
1388    {
1389        return MetaDataUtil.getTimeStamp(metaData, 0, 0L);
1390    }
1391
1392    /**
1393     * Returns the time position offset (in second for OME compatibility) relative to first image for the image at specified (T,Z,C) position.
1394     * 
1395     * @see #getTimeInterval()
1396     * @see #getTimeStamp()
1397     */
1398    public double getPositionTOffset(int t, int z, int c)
1399    {
1400        return MetaDataUtil.getPositionTOffset(metaData, 0, t, z, c, 0d);
1401    }
1402
1403    /**
1404     * Sets the X physical position / offset (in µm) of the image represented by this Sequence.<br>
1405     * This information can be used to represent the position of the image in the original sample (microscope
1406     * information) or the position of a sub image the original image (crop operation).<br>
1407     * Note that OME store this information at Plane level (each Z,T,C), here we always use value from Plane(0,0,0)
1408     */
1409    public void setPositionX(double value)
1410    {
1411        if (getPositionX() != value)
1412        {
1413            MetaDataUtil.setPositionX(metaData, 0, 0, 0, 0, value);
1414            metaChanged(ID_POSITION_X);
1415        }
1416    }
1417
1418    /**
1419     * Sets the X physical position / offset (in µm) of the image represented by this Sequence.<br>
1420     * This information can be used to represent the position of the image in the original sample (microscope
1421     * information) or the position of a sub image the original image (crop operation).<br>
1422     * Note that OME store this information at Plane level (each Z,T,C), here we always use value from Plane(0,0,0)
1423     */
1424    public void setPositionY(double value)
1425    {
1426        if (getPositionY() != value)
1427        {
1428            MetaDataUtil.setPositionY(metaData, 0, 0, 0, 0, value);
1429            metaChanged(ID_POSITION_Y);
1430        }
1431    }
1432
1433    /**
1434     * Sets the X physical position / offset (in µm) of the image represented by this Sequence.<br>
1435     * This information can be used to represent the position of the image in the original sample (microscope
1436     * information) or the position of a sub image the original image (crop operation).<br>
1437     * Note that OME store this information at Plane level (each Z,T,C), here we always use value from Plane(0,0,0)
1438     */
1439    public void setPositionZ(double value)
1440    {
1441        if (getPositionZ() != value)
1442        {
1443            MetaDataUtil.setPositionZ(metaData, 0, 0, 0, 0, value);
1444            metaChanged(ID_POSITION_Z);
1445        }
1446    }
1447
1448    /**
1449     * Same as {@link #setTimeStamp(long)}
1450     */
1451    public void setPositionT(long value)
1452    {
1453        setTimeStamp(value);
1454    }
1455
1456    /**
1457     * Sets the timestamp (elapsed milliseconds from the Java epoch of 1970-01-01 T00:00:00Z) for the image represented by this Sequence.
1458     * 
1459     * @see #setPositionTOffset(int, int, int, double)
1460     * @see #setTimeInterval(double)
1461     */
1462    public void setTimeStamp(long value)
1463    {
1464        if (getTimeStamp() != value)
1465        {
1466            MetaDataUtil.setTimeStamp(metaData, 0, value);
1467            metaChanged(ID_POSITION_T);
1468        }
1469    }
1470
1471    /**
1472     * Sets the time position / offset (in second for OME compatibility) relative to first image for the image at specified (T,Z,C) position.
1473     * 
1474     * @see #setTimeInterval(double)
1475     * @see #setTimeStamp(long)
1476     */
1477    public void setPositionTOffset(int t, int z, int c, double value)
1478    {
1479        if (getPositionTOffset(t, z, c) != value)
1480        {
1481            MetaDataUtil.setPositionTOffset(metaData, 0, t, z, c, value);
1482            metaChanged(ID_POSITION_T_OFFSET, t);
1483        }
1484    }
1485
1486    /**
1487     * Returns pixel size for [X,Y,Z] dimension (in µm to be OME compatible)
1488     */
1489    public double[] getPixelSize()
1490    {
1491        return new double[] {getPixelSizeX(), getPixelSizeY(), getPixelSizeZ()};
1492    }
1493
1494    /**
1495     * Returns X pixel size (in µm to be OME compatible)
1496     */
1497    public double getPixelSizeX()
1498    {
1499        return MetaDataUtil.getPixelSizeX(metaData, 0, 1d);
1500    }
1501
1502    /**
1503     * Returns Y pixel size (in µm to be OME compatible)
1504     */
1505    public double getPixelSizeY()
1506    {
1507        return MetaDataUtil.getPixelSizeY(metaData, 0, 1d);
1508    }
1509
1510    /**
1511     * Returns Z pixel size (in µm to be OME compatible)
1512     */
1513    public double getPixelSizeZ()
1514    {
1515        return MetaDataUtil.getPixelSizeZ(metaData, 0, 1d);
1516    }
1517
1518    /**
1519     * Returns T time interval (in second for OME compatibility)
1520     * 
1521     * @see #getPositionTOffset(int, int, int)
1522     */
1523    public double getTimeInterval()
1524    {
1525        double result = MetaDataUtil.getTimeInterval(metaData, 0, 0d);
1526
1527        // not yet defined ?
1528        if (result == 0d)
1529        {
1530            result = MetaDataUtil.getTimeIntervalFromTimePositions(metaData, 0);
1531            // we got something --> set it as the time interval
1532            if (result != 0d)
1533                MetaDataUtil.setTimeInterval(metaData, 0, result);
1534        }
1535
1536        return result;
1537    }
1538
1539    /**
1540     * Set X pixel size (in µm to be OME compatible)
1541     */
1542    public void setPixelSizeX(double value)
1543    {
1544        if (getPixelSizeX() != value)
1545        {
1546            MetaDataUtil.setPixelSizeX(metaData, 0, value);
1547            metaChanged(ID_PIXEL_SIZE_X);
1548        }
1549    }
1550
1551    /**
1552     * Set Y pixel size (in µm to be OME compatible)
1553     */
1554    public void setPixelSizeY(double value)
1555    {
1556        if (getPixelSizeY() != value)
1557        {
1558            MetaDataUtil.setPixelSizeY(metaData, 0, value);
1559            metaChanged(ID_PIXEL_SIZE_Y);
1560        }
1561    }
1562
1563    /**
1564     * Set Z pixel size (in µm to be OME compatible)
1565     */
1566    public void setPixelSizeZ(double value)
1567    {
1568        if (getPixelSizeZ() != value)
1569        {
1570            MetaDataUtil.setPixelSizeZ(metaData, 0, value);
1571            metaChanged(ID_PIXEL_SIZE_Z);
1572        }
1573    }
1574
1575    /**
1576     * Set T time resolution (in second to be OME compatible)
1577     * 
1578     * @see #setPositionTOffset(int, int, int, double)
1579     */
1580    public void setTimeInterval(double value)
1581    {
1582        if (MetaDataUtil.getTimeInterval(metaData, 0, 0d) != value)
1583        {
1584            MetaDataUtil.setTimeInterval(metaData, 0, value);
1585            metaChanged(ID_TIME_INTERVAL);
1586        }
1587    }
1588
1589    /**
1590     * Returns the pixel size scaling factor to convert a number of pixel/voxel unit into <code>µm</code><br/>
1591     * <br>
1592     * For instance to get the scale ration for 2D distance:<br>
1593     * <code>valueMicroMeter = pixelNum * getPixelSizeScaling(2, 1)</code><br>
1594     * For a 2D surface:<br>
1595     * <code>valueMicroMeter2 = pixelNum * getPixelSizeScaling(2, 2)</code><br>
1596     * For a 3D volume:<br>
1597     * <code>valueMicroMeter3 = pixelNum * getPixelSizeScaling(3, 3)</code><br>
1598     * 
1599     * @param dimCompute
1600     *        dimension order for size calculation<br>
1601     *        <li>1 --> pixel size X used for conversion</li><br>
1602     *        <li>2 --> pixel size X and Y used for conversion</li><br>
1603     *        <li>3 or above --> pixel size X, Y and Z used for conversion</li><br>
1604     * @param dimResult
1605     *        dimension order for the result (unit)<br>
1606     *        <li>1 --> distance</li><br>
1607     *        <li>2 --> area</li><br>
1608     *        <li>3 or above --> volume</li><br>
1609     */
1610    public double getPixelSizeScaling(int dimCompute, int dimResult)
1611    {
1612        double result;
1613
1614        switch (dimCompute)
1615        {
1616            case 0:
1617                // incorrect
1618                return 0d;
1619
1620            case 1:
1621                result = getPixelSizeX();
1622                break;
1623
1624            case 2:
1625                result = getPixelSizeX() * getPixelSizeY();
1626                break;
1627
1628            default:
1629                result = getPixelSizeX() * getPixelSizeY() * getPixelSizeZ();
1630                break;
1631        }
1632
1633        result = Math.pow(result, (double) dimResult / (double) dimCompute);
1634
1635        return result;
1636    }
1637
1638    /**
1639     * Returns the best pixel size unit for the specified dimension order given the sequence's pixel
1640     * size informations.<br/>
1641     * <li>Compute a 2D distance:</li>
1642     * 
1643     * <pre>
1644     * dimCompute = 2;
1645     * dimUnit = 1;
1646     * valueMicroMeter = pixelNum * getPixelSizeScaling(dimCompute);
1647     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1648     * finalValue = UnitUtil.getValueInUnit(valueMicroMeter, UnitPrefix.MICRO, bestUnit);
1649     * valueString = Double.toString(finalValue) + &quot; &quot; + bestUnit.toString() + &quot;m&quot;;
1650     * </pre>
1651     * 
1652     * <li>Compute a 2D surface:</li>
1653     * 
1654     * <pre>
1655     * dimCompute = 2;
1656     * dimUnit = 2;
1657     * valueMicroMeter = pixelNum * getPixelSizeScaling(dimCompute);
1658     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1659     * finalValue = UnitUtil.getValueInUnit(valueMicroMeter, UnitPrefix.MICRO, bestUnit);
1660     * valueString = Double.toString(finalValue) + &quot; &quot; + bestUnit.toString() + &quot;m2&quot;;
1661     * </pre>
1662     * 
1663     * <li>Compute a 3D volume:</li>
1664     * 
1665     * <pre>
1666     * dimCompute = 3;
1667     * dimUnit = 3;
1668     * valueMicroMeter = pixelNum * getPixelSizeScaling(dimCompute);
1669     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1670     * finalValue = UnitUtil.getValueInUnit(valueMicroMeter, UnitPrefix.MICRO, bestUnit);
1671     * valueString = Double.toString(finalValue) + &quot; &quot; + bestUnit.toString() + &quot;m3&quot;;
1672     * </pre>
1673     * 
1674     * @param dimCompute
1675     *        dimension order for size calculation<br>
1676     *        <li>1 --> pixel size X used for conversion</li><br>
1677     *        <li>2 --> pixel size X and Y used for conversion</li><br>
1678     *        <li>3 or above --> pixel size X, Y and Z used for conversion</li><br>
1679     * @param dimResult
1680     *        dimension order for the result (unit)<br>
1681     *        <li>1 --> distance</li><br>
1682     *        <li>2 --> area</li><br>
1683     *        <li>3 or above --> volume</li><br>
1684     * @see #calculateSizeBestUnit(double, int, int)
1685     */
1686    public UnitPrefix getBestPixelSizeUnit(int dimCompute, int dimResult)
1687    {
1688        switch (dimResult)
1689        {
1690            case 0:
1691                // keep original
1692                return UnitPrefix.MICRO;
1693
1694            case 1:
1695                return UnitUtil.getBestUnit((getPixelSizeScaling(dimCompute, dimResult) * 10), UnitPrefix.MICRO,
1696                        dimResult);
1697
1698            case 2:
1699                return UnitUtil.getBestUnit((getPixelSizeScaling(dimCompute, dimResult) * 100), UnitPrefix.MICRO,
1700                        dimResult);
1701
1702            default:
1703                return UnitUtil.getBestUnit((getPixelSizeScaling(dimCompute, dimResult) * 1000), UnitPrefix.MICRO,
1704                        dimResult);
1705        }
1706    }
1707
1708    /**
1709     * Returns the size in µm for the specified amount of sample/pixel value in the specified
1710     * dimension order.<br>
1711     * <br>
1712     * For the perimeter in µm:<br>
1713     * <code>perimeter = calculateSize(contourInPixel, 2, 1)</code><br>
1714     * For a 2D surface in µm2:<br>
1715     * <code>surface = calculateSize(interiorInPixel, 2, 2)</code><br>
1716     * For a 2D surface area in µm2:<br>
1717     * <code>volume = calculateSize(contourInPixel, 3, 2)</code><br>
1718     * For a 3D volume in µm3:<br>
1719     * <code>volume = calculateSize(interiorInPixel, 3, 3)</code><br>
1720     * 
1721     * @param pixelNumber
1722     *        number of pixel
1723     * @param dimCompute
1724     *        dimension order for size calculation<br>
1725     *        <li>1 --> pixel size X used for conversion</li><br>
1726     *        <li>2 --> pixel size X and Y used for conversion</li><br>
1727     *        <li>3 or above --> pixel size X, Y and Z used for conversion</li><br>
1728     * @param dimResult
1729     *        dimension order for the result (unit)<br>
1730     *        <li>1 --> distance</li><br>
1731     *        <li>2 --> area</li><br>
1732     *        <li>3 or above --> volume</li><br>
1733     * @see #calculateSizeBestUnit(double, int, int)
1734     */
1735    public double calculateSize(double pixelNumber, int dimCompute, int dimResult)
1736    {
1737        return pixelNumber * getPixelSizeScaling(dimCompute, dimResult);
1738    }
1739
1740    /**
1741     * Returns the size converted in the best unit (see {@link #getBestPixelSizeUnit(int, int)} for
1742     * the specified amount of sample/pixel value in the specified dimension order.<br/>
1743     * <li>Compute a 2D distance:</li>
1744     * 
1745     * <pre>
1746     * dimCompute = 2;
1747     * dimUnit = 1;
1748     * valueBestUnit = calculateSizeBestUnit(pixelNum, dimCompute, dimUnit);
1749     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1750     * valueString = Double.toString(valueBestUnit) + &quot; &quot; + bestUnit.toString() + &quot;m&quot;;
1751     * </pre>
1752     * 
1753     * <li>Compute a 2D surface:</li>
1754     * 
1755     * <pre>
1756     * dimCompute = 2;
1757     * dimUnit = 2;
1758     * valueBestUnit = calculateSizeBestUnit(pixelNum, dimCompute, dimUnit);
1759     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1760     * valueString = Double.toString(valueBestUnit) + &quot; &quot; + bestUnit.toString() + &quot;m2&quot;;
1761     * </pre>
1762     * 
1763     * <li>Compute a 3D volume:</li>
1764     * 
1765     * <pre>
1766     * dimCompute = 3;
1767     * dimUnit = 3;
1768     * valueBestUnit = calculateSizeBestUnit(pixelNum, dimCompute, dimUnit);
1769     * bestUnit = getBestPixelSizeUnit(dimCompute, dimUnit);
1770     * valueString = Double.toString(valueBestUnit) + &quot; &quot; + bestUnit.toString() + &quot;m3&quot;;
1771     * </pre>
1772     * 
1773     * @param pixelNumber
1774     *        number of pixel
1775     * @param dimCompute
1776     *        dimension order for size calculation<br>
1777     *        <li>1 --> pixel size X used for conversion</li><br>
1778     *        <li>2 --> pixel size X and Y used for conversion</li><br>
1779     *        <li>3 or above --> pixel size X, Y and Z used for conversion</li><br>
1780     * @param dimResult
1781     *        dimension order for the result (unit)<br>
1782     *        <li>1 --> distance</li><br>
1783     *        <li>2 --> area</li><br>
1784     *        <li>3 or above --> volume</li><br>
1785     * @see #calculateSize(double, int, int)
1786     * @see #getBestPixelSizeUnit(int, int)
1787     */
1788    public double calculateSizeBestUnit(double pixelNumber, int dimCompute, int dimResult)
1789    {
1790        final double value = calculateSize(pixelNumber, dimCompute, dimResult);
1791        final UnitPrefix unit = getBestPixelSizeUnit(dimCompute, dimResult);
1792        return UnitUtil.getValueInUnit(value, UnitPrefix.MICRO, unit, dimResult);
1793    }
1794
1795    /**
1796     * Returns the size and appropriate unit in form of String for specified amount of sample/pixel
1797     * value in the specified dimension order.<br>
1798     * <br>
1799     * For instance if you want to retrieve the 2D distance:<br>
1800     * <code>distanceStr = calculateSize(distanceInPixel, 2, 1, 5)</code><br>
1801     * For a 2D surface:<br>
1802     * <code>surfaceStr = calculateSize(surfaceInPixel, 2, 2, 5)</code><br>
1803     * For a 3D volume:<br>
1804     * <code>volumeStr = calculateSize(volumeInPixel, 3, 3, 5)</code><br>
1805     * 
1806     * @param pixelNumber
1807     *        number of pixel
1808     * @param dimCompute
1809     *        dimension order for the calculation
1810     * @param dimResult
1811     *        dimension order for the result (unit)
1812     * @param significantDigit
1813     *        wanted significant digit for the result (0 for all)
1814     * @see #calculateSize(double, int, int)
1815     */
1816    public String calculateSize(double pixelNumber, int dimCompute, int dimResult, int significantDigit)
1817    {
1818        double value = calculateSize(pixelNumber, dimCompute, dimResult);
1819        final String postFix = (dimResult > 1) ? StringUtil.toString(dimResult) : "";
1820        final UnitPrefix unit = UnitUtil.getBestUnit(value, UnitPrefix.MICRO, dimResult);
1821        // final UnitPrefix unit = getBestPixelSizeUnit(dimCompute, dimResult);
1822
1823        value = UnitUtil.getValueInUnit(value, UnitPrefix.MICRO, unit, dimResult);
1824        if (significantDigit != 0)
1825            value = MathUtil.roundSignificant(value, significantDigit);
1826
1827        return StringUtil.toString(value) + " " + unit.toString() + "m" + postFix;
1828    }
1829
1830    /**
1831     * Get default name for specified channel
1832     */
1833    public String getDefaultChannelName(int index)
1834    {
1835        return MetaDataUtil.getDefaultChannelName(index);
1836    }
1837
1838    /**
1839     * Get name for specified channel
1840     */
1841    public String getChannelName(int index)
1842    {
1843        return MetaDataUtil.getChannelName(metaData, 0, index);
1844    }
1845
1846    /**
1847     * Set name for specified channel
1848     */
1849    public void setChannelName(int index, String value)
1850    {
1851        if (!StringUtil.equals(getChannelName(index), value))
1852        {
1853            MetaDataUtil.setChannelName(metaData, 0, index, value);
1854            metaChanged(ID_CHANNEL_NAME, index);
1855        }
1856    }
1857
1858    /**
1859     * @deprecated Use {@link #getAutoUpdateChannelBounds()} instead.
1860     */
1861    @Deprecated
1862    public boolean isComponentAbsBoundsAutoUpdate()
1863    {
1864        return getAutoUpdateChannelBounds();
1865    }
1866
1867    /**
1868     * @deprecated Use {@link #setAutoUpdateChannelBounds(boolean)} instead.
1869     */
1870    @Deprecated
1871    public void setComponentAbsBoundsAutoUpdate(boolean value)
1872    {
1873        // nothing here
1874    }
1875
1876    /**
1877     * @return true is channel bounds are automatically updated when sequence data is modified.
1878     * @see #setAutoUpdateChannelBounds(boolean)
1879     */
1880    public boolean getAutoUpdateChannelBounds()
1881    {
1882        return autoUpdateChannelBounds;
1883    }
1884
1885    /**
1886     * If set to <code>true</code> (default) then channel bounds will be automatically recalculated
1887     * when sequence data is modified.<br>
1888     * This can consume a lot of time if you make many updates on large sequence.<br>
1889     * In this case you should do your updates in a {@link #beginUpdate()} ... {@link #endUpdate()} block to avoid
1890     * severals recalculation.
1891     */
1892    public void setAutoUpdateChannelBounds(boolean value)
1893    {
1894        if (autoUpdateChannelBounds != value)
1895        {
1896            if (value)
1897                updateChannelsBounds(false);
1898
1899            autoUpdateChannelBounds = value;
1900        }
1901    }
1902
1903    /**
1904     * @deprecated Use {@link #getAutoUpdateChannelBounds()} instead.
1905     */
1906    @Deprecated
1907    public boolean isComponentUserBoundsAutoUpdate()
1908    {
1909        return getAutoUpdateChannelBounds();
1910    }
1911
1912    /**
1913     * @deprecated Use {@link #setAutoUpdateChannelBounds(boolean)} instead.
1914     */
1915    @Deprecated
1916    public void setComponentUserBoundsAutoUpdate(boolean value)
1917    {
1918        setAutoUpdateChannelBounds(value);
1919    }
1920
1921    /**
1922     * @return the AWT dispatching property
1923     * @deprecated Don't use it, events should stay on current thread
1924     */
1925    @Deprecated
1926    public boolean isAWTDispatching()
1927    {
1928        return updater.isAwtDispatch();
1929    }
1930
1931    /**
1932     * All events are dispatched on AWT when true else they are dispatched on current thread
1933     * 
1934     * @deprecated Don't use it, events should stay on current thread
1935     */
1936    @Deprecated
1937    public void setAWTDispatching(boolean value)
1938    {
1939        updater.setAwtDispatch(value);
1940    }
1941
1942    /**
1943     * Add the specified listener to listeners list
1944     */
1945    public void addListener(SequenceListener listener)
1946    {
1947        listeners.add(listener);
1948    }
1949
1950    /**
1951     * Remove the specified listener from listeners list
1952     */
1953    public void removeListener(SequenceListener listener)
1954    {
1955        listeners.remove(listener);
1956    }
1957
1958    /**
1959     * Get listeners list
1960     */
1961    public SequenceListener[] getListeners()
1962    {
1963        return listeners.toArray(new SequenceListener[0]);
1964    }
1965
1966    /**
1967     * Add the specified {@link icy.sequence.SequenceModel.SequenceModelListener} to listeners list
1968     */
1969    @Override
1970    public void addSequenceModelListener(SequenceModelListener listener)
1971    {
1972        modelListeners.add(listener);
1973    }
1974
1975    /**
1976     * Remove the specified {@link icy.sequence.SequenceModel.SequenceModelListener} from listeners
1977     * list
1978     */
1979    @Override
1980    public void removeSequenceModelListener(SequenceModelListener listener)
1981    {
1982        modelListeners.remove(listener);
1983    }
1984
1985    /**
1986     * Get the Undo manager of this sequence
1987     */
1988    public IcyUndoManager getUndoManager()
1989    {
1990        return undoManager;
1991    }
1992
1993    /**
1994     * @deprecated Use {@link #contains(Overlay)} instead.
1995     */
1996    @Deprecated
1997    public boolean contains(Painter painter)
1998    {
1999        return getOverlay(painter) != null;
2000    }
2001
2002    /**
2003     * Returns true if the sequence contains the specified overlay
2004     */
2005    public boolean contains(Overlay overlay)
2006    {
2007        if (overlay == null)
2008            return false;
2009
2010        synchronized (overlays)
2011        {
2012            return overlays.contains(overlay);
2013        }
2014    }
2015
2016    /**
2017     * Returns true if the sequence contains the specified ROI
2018     */
2019    public boolean contains(ROI roi)
2020    {
2021        if (roi == null)
2022            return false;
2023
2024        synchronized (rois)
2025        {
2026            return rois.contains(roi);
2027        }
2028    }
2029
2030    /**
2031     * @deprecated Use {@link #hasOverlay()} instead.
2032     */
2033    @Deprecated
2034    public boolean hasPainter()
2035    {
2036        return hasOverlay();
2037    }
2038
2039    /**
2040     * @deprecated Use {@link #getOverlays()} instead.
2041     */
2042    @Deprecated
2043    public ArrayList<Painter> getPainters()
2044    {
2045        final ArrayList<Painter> result = new ArrayList<Painter>(overlays.size());
2046
2047        synchronized (overlays)
2048        {
2049            for (Overlay overlay : overlays)
2050            {
2051                if (overlay instanceof OverlayWrapper)
2052                    result.add(((OverlayWrapper) overlay).getPainter());
2053                else
2054                    result.add(overlay);
2055            }
2056        }
2057
2058        return result;
2059    }
2060
2061    /**
2062     * @deprecated Use {@link #getOverlaySet()} instead.
2063     */
2064    @Deprecated
2065    public HashSet<Painter> getPainterSet()
2066    {
2067        final HashSet<Painter> result = new HashSet<Painter>(overlays.size());
2068
2069        synchronized (overlays)
2070        {
2071            for (Overlay overlay : overlays)
2072            {
2073                if (overlay instanceof OverlayWrapper)
2074                    result.add(((OverlayWrapper) overlay).getPainter());
2075                else
2076                    result.add(overlay);
2077            }
2078        }
2079
2080        return result;
2081    }
2082
2083    /**
2084     * @deprecated Use {@link #getOverlays(Class)} instead.
2085     */
2086    @Deprecated
2087    public List<Painter> getPainters(Class<? extends Painter> painterClass)
2088    {
2089        final ArrayList<Painter> result = new ArrayList<Painter>(overlays.size());
2090
2091        synchronized (overlays)
2092        {
2093            for (Overlay overlay : overlays)
2094            {
2095                if (overlay instanceof OverlayWrapper)
2096                {
2097                    if (painterClass.isInstance(((OverlayWrapper) overlay).getPainter()))
2098                        result.add(overlay);
2099                }
2100                else
2101                {
2102                    if (painterClass.isInstance(overlay))
2103                        result.add(overlay);
2104                }
2105            }
2106        }
2107
2108        return result;
2109    }
2110
2111    /**
2112     * Returns true if the sequence contains at least one Overlay.
2113     */
2114    public boolean hasOverlay()
2115    {
2116        return overlays.size() > 0;
2117    }
2118
2119    /**
2120     * Returns all overlays attached to this sequence
2121     */
2122    public List<Overlay> getOverlays()
2123    {
2124        synchronized (overlays)
2125        {
2126            return new ArrayList<Overlay>(overlays);
2127        }
2128    }
2129
2130    /**
2131     * Returns all overlays attached to this sequence (HashSet form)
2132     */
2133    public Set<Overlay> getOverlaySet()
2134    {
2135        synchronized (overlays)
2136        {
2137            return new HashSet<Overlay>(overlays);
2138        }
2139    }
2140
2141    /**
2142     * Returns true if the sequence contains Overlay of specified Overlay class.
2143     */
2144    public boolean hasOverlay(Class<? extends Overlay> overlayClass)
2145    {
2146        synchronized (overlays)
2147        {
2148            for (Overlay overlay : overlays)
2149                if (overlayClass.isInstance(overlay))
2150                    return true;
2151        }
2152
2153        return false;
2154    }
2155
2156    /**
2157     * Returns overlays of specified class attached to this sequence
2158     */
2159    @SuppressWarnings("unchecked")
2160    public <T extends Overlay> List<T> getOverlays(Class<T> overlayClass)
2161    {
2162        final List<T> result = new ArrayList<T>(overlays.size());
2163
2164        synchronized (overlays)
2165        {
2166            for (Overlay overlay : overlays)
2167                if (overlayClass.isInstance(overlay))
2168                    result.add((T) overlay);
2169        }
2170
2171        return result;
2172    }
2173
2174    /**
2175     * Returns true if the sequence contains at least one ROI.
2176     */
2177    public boolean hasROI()
2178    {
2179        return rois.size() > 0;
2180    }
2181
2182    /**
2183     * Returns all ROIs attached to this sequence.
2184     * 
2185     * @param sorted
2186     *        If true the returned list is ordered by the ROI id (creation order).
2187     */
2188    public List<ROI> getROIs(boolean sorted)
2189    {
2190        final List<ROI> result;
2191
2192        synchronized (rois)
2193        {
2194            result = new ArrayList<ROI>(rois);
2195        }
2196
2197        // sort it if required
2198        if (sorted)
2199            Collections.sort(result, ROI.idComparator);
2200
2201        return result;
2202    }
2203
2204    /**
2205     * Returns all ROIs attached to this sequence.
2206     */
2207    public ArrayList<ROI> getROIs()
2208    {
2209        return (ArrayList<ROI>) getROIs(false);
2210    }
2211
2212    /**
2213     * Returns all ROIs attached to this sequence (HashSet form)
2214     */
2215    public HashSet<ROI> getROISet()
2216    {
2217        synchronized (rois)
2218        {
2219            return new HashSet<ROI>(rois);
2220        }
2221    }
2222
2223    /**
2224     * Returns all 2D ROIs attached to this sequence.
2225     * 
2226     * @param sorted
2227     *        If true the returned list is ordered by the ROI id (creation order).
2228     */
2229    public List<ROI2D> getROI2Ds(boolean sorted)
2230    {
2231        final List<ROI2D> result = new ArrayList<ROI2D>(rois.size());
2232
2233        synchronized (rois)
2234        {
2235            for (ROI roi : rois)
2236                if (roi instanceof ROI2D)
2237                    result.add((ROI2D) roi);
2238        }
2239
2240        // sort it if required
2241        if (sorted)
2242            Collections.sort(result, ROI.idComparator);
2243
2244        return result;
2245    }
2246
2247    /**
2248     * Returns all 2D ROIs attached to this sequence.
2249     */
2250    public ArrayList<ROI2D> getROI2Ds()
2251    {
2252        return (ArrayList<ROI2D>) getROI2Ds(false);
2253    }
2254
2255    /**
2256     * Returns all 3D ROIs attached to this sequence.
2257     * 
2258     * @param sorted
2259     *        If true the returned list is ordered by the ROI id (creation order).
2260     */
2261    public List<ROI3D> getROI3Ds(boolean sorted)
2262    {
2263        final List<ROI3D> result = new ArrayList<ROI3D>(rois.size());
2264
2265        synchronized (rois)
2266        {
2267            for (ROI roi : rois)
2268                if (roi instanceof ROI3D)
2269                    result.add((ROI3D) roi);
2270        }
2271
2272        // sort it if required
2273        if (sorted)
2274            Collections.sort(result, ROI.idComparator);
2275
2276        return result;
2277    }
2278
2279    /**
2280     * Returns all 3D ROIs attached to this sequence.
2281     */
2282    public ArrayList<ROI3D> getROI3Ds()
2283    {
2284        return (ArrayList<ROI3D>) getROI3Ds(false);
2285    }
2286
2287    /**
2288     * Returns true if the sequence contains ROI of specified ROI class.
2289     */
2290    public boolean hasROI(Class<? extends ROI> roiClass)
2291    {
2292        synchronized (rois)
2293        {
2294            for (ROI roi : rois)
2295                if (roiClass.isInstance(roi))
2296                    return true;
2297        }
2298
2299        return false;
2300    }
2301
2302    /**
2303     * Returns ROIs of specified class attached to this sequence
2304     */
2305    @SuppressWarnings("unchecked")
2306    public <T extends ROI> List<T> getROIs(Class<T> roiClass, boolean sorted)
2307    {
2308        final List<T> result = new ArrayList<T>(rois.size());
2309
2310        synchronized (rois)
2311        {
2312            for (ROI roi : rois)
2313                if (roiClass.isInstance(roi))
2314                    result.add((T) roi);
2315        }
2316
2317        // sort it if required
2318        if (sorted)
2319            Collections.sort(result, ROI.idComparator);
2320
2321        return result;
2322    }
2323
2324    /**
2325     * @deprecated Use {@link #getROIs(Class, boolean)} instead
2326     */
2327    @Deprecated
2328    public List<ROI> getROIs(Class<? extends ROI> roiClass)
2329    {
2330        final List<ROI> result = new ArrayList<ROI>(rois.size());
2331
2332        synchronized (rois)
2333        {
2334            for (ROI roi : rois)
2335                if (roiClass.isInstance(roi))
2336                    result.add(roi);
2337        }
2338
2339        return result;
2340    }
2341
2342    /**
2343     * Returns the number of ROI of specified ROI class attached to the sequence.
2344     */
2345    public int getROICount(Class<? extends ROI> roiClass)
2346    {
2347        int result = 0;
2348
2349        synchronized (rois)
2350        {
2351            for (ROI roi : rois)
2352                if (roiClass.isInstance(roi))
2353                    result++;
2354        }
2355
2356        return result;
2357    }
2358
2359    /**
2360     * Returns true if the sequence contains at least one selected ROI.
2361     */
2362    public boolean hasSelectedROI()
2363    {
2364        return getSelectedROI() != null;
2365    }
2366
2367    /**
2368     * Returns the first selected ROI found (null if no ROI selected)
2369     */
2370    public ROI getSelectedROI()
2371    {
2372        synchronized (rois)
2373        {
2374            for (ROI roi : rois)
2375                if (roi.isSelected())
2376                    return roi;
2377        }
2378
2379        return null;
2380    }
2381
2382    /**
2383     * Returns the first selected 2D ROI found (null if no 2D ROI selected)
2384     */
2385    public ROI2D getSelectedROI2D()
2386    {
2387        synchronized (rois)
2388        {
2389            for (ROI roi : rois)
2390                if ((roi instanceof ROI2D) && roi.isSelected())
2391                    return (ROI2D) roi;
2392        }
2393
2394        return null;
2395    }
2396
2397    /**
2398     * Returns the first selected 3D ROI found (null if no 3D ROI selected)
2399     */
2400    public ROI3D getSelectedROI3D()
2401    {
2402        synchronized (rois)
2403        {
2404            for (ROI roi : rois)
2405                if ((roi instanceof ROI3D) && roi.isSelected())
2406                    return (ROI3D) roi;
2407        }
2408
2409        return null;
2410    }
2411
2412    /**
2413     * Returns all selected ROI of given class (Set format).
2414     * 
2415     * @param roiClass
2416     *        ROI class restriction
2417     * @param wantReadOnly
2418     *        also return ROI with read only state
2419     */
2420    public Set<ROI> getSelectedROISet(Class<? extends ROI> roiClass, boolean wantReadOnly)
2421    {
2422        final Set<ROI> result = new HashSet<ROI>(rois.size());
2423
2424        synchronized (rois)
2425        {
2426            for (ROI roi : rois)
2427                if (roi.isSelected() && roiClass.isInstance(roi))
2428                    if (wantReadOnly || !roi.isReadOnly())
2429                        result.add(roi);
2430        }
2431
2432        return result;
2433    }
2434
2435    /**
2436     * Returns all selected ROI of given class (Set format).
2437     * 
2438     * @param roiClass
2439     *        ROI class restriction
2440     */
2441    @SuppressWarnings("unchecked")
2442    public <T extends ROI> Set<T> getSelectedROISet(Class<T> roiClass)
2443    {
2444        final Set<T> result = new HashSet<T>(rois.size());
2445
2446        synchronized (rois)
2447        {
2448            for (ROI roi : rois)
2449                if (roi.isSelected() && roiClass.isInstance(roi))
2450                    result.add((T) roi);
2451        }
2452
2453        return result;
2454    }
2455
2456    /**
2457     * Returns all selected ROI (Set format).
2458     */
2459    public Set<ROI> getSelectedROISet()
2460    {
2461        final Set<ROI> result = new HashSet<ROI>(rois.size());
2462
2463        synchronized (rois)
2464        {
2465            for (ROI roi : rois)
2466                if (roi.isSelected())
2467                    result.add(roi);
2468        }
2469
2470        return result;
2471    }
2472
2473    /**
2474     * Returns all selected ROI of given class.
2475     * 
2476     * @param roiClass
2477     *        ROI class restriction
2478     * @param sorted
2479     *        If true the returned list is ordered by the ROI id (creation order)
2480     * @param wantReadOnly
2481     *        also return ROI with read only state
2482     */
2483    @SuppressWarnings("unchecked")
2484    public <T extends ROI> List<T> getSelectedROIs(Class<T> roiClass, boolean sorted, boolean wantReadOnly)
2485    {
2486        final List<T> result = new ArrayList<T>(rois.size());
2487
2488        synchronized (rois)
2489        {
2490            for (ROI roi : rois)
2491                if (roi.isSelected() && roiClass.isInstance(roi))
2492                    result.add((T) roi);
2493        }
2494
2495        // sort it if required
2496        if (sorted)
2497            Collections.sort(result, ROI.idComparator);
2498
2499        return result;
2500    }
2501
2502    /**
2503     * Returns all selected ROI of given class.
2504     * 
2505     * @param roiClass
2506     *        ROI class restriction
2507     * @param wantReadOnly
2508     *        also return ROI with read only state
2509     */
2510    public List<ROI> getSelectedROIs(Class<? extends ROI> roiClass, boolean wantReadOnly)
2511    {
2512        final List<ROI> result = new ArrayList<ROI>(rois.size());
2513
2514        synchronized (rois)
2515        {
2516            for (ROI roi : rois)
2517                if (roi.isSelected() && roiClass.isInstance(roi))
2518                    if (wantReadOnly || !roi.isReadOnly())
2519                        result.add(roi);
2520        }
2521
2522        return result;
2523    }
2524
2525    /**
2526     * Returns all selected ROI
2527     */
2528    public ArrayList<ROI> getSelectedROIs()
2529    {
2530        final ArrayList<ROI> result = new ArrayList<ROI>(rois.size());
2531
2532        synchronized (rois)
2533        {
2534            for (ROI roi : rois)
2535                if (roi.isSelected())
2536                    result.add(roi);
2537        }
2538
2539        return result;
2540    }
2541
2542    /**
2543     * Returns all selected 2D ROI
2544     */
2545    public ArrayList<ROI2D> getSelectedROI2Ds()
2546    {
2547        final ArrayList<ROI2D> result = new ArrayList<ROI2D>(rois.size());
2548
2549        synchronized (rois)
2550        {
2551            for (ROI roi : rois)
2552                if ((roi instanceof ROI2D) && roi.isSelected())
2553                    result.add((ROI2D) roi);
2554        }
2555
2556        return result;
2557    }
2558
2559    /**
2560     * Returns all selected 3D ROI
2561     */
2562    public ArrayList<ROI3D> getSelectedROI3Ds()
2563    {
2564        final ArrayList<ROI3D> result = new ArrayList<ROI3D>(rois.size());
2565
2566        synchronized (rois)
2567        {
2568            for (ROI roi : rois)
2569                if ((roi instanceof ROI3D) && roi.isSelected())
2570                    result.add((ROI3D) roi);
2571        }
2572
2573        return result;
2574    }
2575
2576    /**
2577     * Returns the current focused ROI (null if no ROI focused)
2578     */
2579    public ROI getFocusedROI()
2580    {
2581        synchronized (rois)
2582        {
2583            for (ROI roi : rois)
2584                if (roi.isFocused())
2585                    return roi;
2586        }
2587
2588        return null;
2589    }
2590
2591    /**
2592     * Set the selected ROI (exclusive selection).<br>
2593     * Specifying a <code>null</code> ROI here will actually clear all ROI selection.<br>
2594     * Note that you can use {@link #setSelectedROIs(List)} or {@link ROI#setSelected(boolean)} for
2595     * multiple ROI selection.
2596     * 
2597     * @param roi
2598     *        the ROI to select.
2599     * @return <code>false</code> is the specified ROI is not attached to the sequence.
2600     */
2601    public boolean setSelectedROI(ROI roi)
2602    {
2603        beginUpdate();
2604        try
2605        {
2606            synchronized (rois)
2607            {
2608                for (ROI currentRoi : rois)
2609                    if (currentRoi != roi)
2610                        currentRoi.setSelected(false);
2611            }
2612
2613            if (contains(roi))
2614            {
2615                roi.setSelected(true);
2616                return true;
2617            }
2618        }
2619        finally
2620        {
2621            endUpdate();
2622        }
2623
2624        return false;
2625    }
2626
2627    /**
2628     * @deprecated Use {@link #setSelectedROI(ROI)} instead.
2629     */
2630    @Deprecated
2631    public boolean setSelectedROI(ROI roi, boolean exclusive)
2632    {
2633        if (exclusive)
2634            return setSelectedROI(roi);
2635
2636        if (contains(roi))
2637        {
2638            roi.setSelected(true);
2639            return true;
2640        }
2641
2642        return false;
2643    }
2644
2645    /**
2646     * @deprecated Use {@link #setSelectedROIs(List)} instead.
2647     */
2648    @Deprecated
2649    public void setSelectedROIs(ArrayList<ROI> selected)
2650    {
2651        setSelectedROIs((List<ROI>) selected);
2652    }
2653
2654    /**
2655     * Set selected ROI (unselected all others)
2656     */
2657    public void setSelectedROIs(List<? extends ROI> selected)
2658    {
2659        final List<ROI> oldSelected = getSelectedROIs();
2660
2661        final int newSelectedSize = (selected == null) ? 0 : selected.size();
2662        final int oldSelectedSize = oldSelected.size();
2663
2664        // easy optimization
2665        if ((newSelectedSize == 0) && (oldSelectedSize == 0))
2666            return;
2667
2668        final HashSet<ROI> newSelected;
2669
2670        // use HashSet for fast .contains() !
2671        if (selected != null)
2672            newSelected = new HashSet<ROI>(selected);
2673        else
2674            newSelected = new HashSet<ROI>();
2675
2676        // selection changed ?
2677        if (!CollectionUtil.equals(oldSelected, newSelected))
2678        {
2679            beginUpdate();
2680            try
2681            {
2682                if (newSelectedSize > 0)
2683                {
2684                    for (ROI roi : getROIs())
2685                        roi.setSelected(newSelected.contains(roi));
2686                }
2687                else
2688                {
2689                    // unselected all ROIs
2690                    for (ROI roi : getROIs())
2691                        roi.setSelected(false);
2692                }
2693            }
2694            finally
2695            {
2696                endUpdate();
2697            }
2698        }
2699    }
2700
2701    /**
2702     * Set the focused ROI
2703     */
2704    public boolean setFocusedROI(ROI roi)
2705    {
2706        // faster .contain()
2707        final Set<ROI> listRoi = getROISet();
2708
2709        beginUpdate();
2710        try
2711        {
2712            for (ROI currentRoi : listRoi)
2713                if (currentRoi != roi)
2714                    currentRoi.internalUnfocus();
2715
2716            if (listRoi.contains(roi))
2717            {
2718                roi.internalFocus();
2719                return true;
2720            }
2721        }
2722        finally
2723        {
2724            endUpdate();
2725        }
2726
2727        return false;
2728    }
2729
2730    /**
2731     * Add the specified collection of ROI to the sequence.
2732     * 
2733     * @param rois
2734     *        the collection of ROI to attach to the sequence
2735     * @param canUndo
2736     *        If true the action can be canceled by the undo manager.
2737     * @return <code>true</code> if the operation succeed or <code>false</code> if some ROIs could
2738     *         not be added (already present)
2739     */
2740    public boolean addROIs(Collection<? extends ROI> rois, boolean canUndo)
2741    {
2742        if (!rois.isEmpty())
2743        {
2744            final List<ROI> addedRois = new ArrayList<ROI>();
2745
2746            for (ROI roi : rois)
2747            {
2748                if (addROI(roi, false))
2749                    addedRois.add(roi);
2750            }
2751
2752            if (canUndo && !addedRois.isEmpty())
2753                addUndoableEdit(new ROIAddsSequenceEdit(this, addedRois));
2754
2755            return addedRois.size() == rois.size();
2756        }
2757
2758        return true;
2759    }
2760
2761    /**
2762     * Add the specified ROI to the sequence.
2763     * 
2764     * @param roi
2765     *        ROI to attach to the sequence
2766     */
2767    public boolean addROI(ROI roi)
2768    {
2769        return addROI(roi, false);
2770    }
2771
2772    /**
2773     * Add the specified ROI to the sequence.
2774     * 
2775     * @param roi
2776     *        ROI to attach to the sequence
2777     * @param canUndo
2778     *        If true the action can be canceled by the undo manager.
2779     * @return <code>true</code> if the operation succeed or <code>false</code> otherwise (already
2780     *         present)
2781     */
2782    public boolean addROI(ROI roi, boolean canUndo)
2783    {
2784        if ((roi == null) || contains(roi))
2785            return false;
2786
2787        synchronized (rois)
2788        {
2789            rois.add(roi);
2790        }
2791        // add listener to ROI
2792        roi.addListener(this);
2793        // notify roi added
2794        roiChanged(roi, SequenceEventType.ADDED);
2795        // then add ROI overlay to sequence
2796        addOverlay(roi.getOverlay());
2797
2798        if (canUndo)
2799            addUndoableEdit(new ROIAddSequenceEdit(this, roi));
2800
2801        return true;
2802
2803    }
2804
2805    /**
2806     * Remove the specified ROI from the sequence.
2807     * 
2808     * @param roi
2809     *        ROI to detach from the sequence
2810     */
2811    public boolean removeROI(ROI roi)
2812    {
2813        return removeROI(roi, false);
2814    }
2815
2816    /**
2817     * Remove the specified ROI from the sequence.
2818     * 
2819     * @param roi
2820     *        ROI to detach from the sequence
2821     * @param canUndo
2822     *        If true the action can be canceled by the undo manager.
2823     * @return <code>false</code> if the ROI was not found in the sequence.<br/>
2824     *         Returns <code>true</code> otherwise.
2825     */
2826    public boolean removeROI(ROI roi, boolean canUndo)
2827    {
2828        if (contains(roi))
2829        {
2830            // remove ROI overlay first
2831            removeOverlay(roi.getOverlay());
2832
2833            // remove ROI
2834            synchronized (rois)
2835            {
2836                rois.remove(roi);
2837            }
2838            // remove listener
2839            roi.removeListener(this);
2840            // notify roi removed
2841            roiChanged(roi, SequenceEventType.REMOVED);
2842
2843            if (canUndo)
2844                addUndoableEdit(new ROIRemoveSequenceEdit(this, roi));
2845
2846            return true;
2847        }
2848
2849        return false;
2850    }
2851
2852    /**
2853     * Remove the specified collection of ROI from the sequence.
2854     * 
2855     * @param rois
2856     *        the collection of ROI to remove from the sequence
2857     * @param canUndo
2858     *        If true the action can be canceled by the undo manager.
2859     * @return <code>true</code> if all ROI from the collection has been correctly removed.
2860     */
2861    public boolean removeROIs(Collection<? extends ROI> rois, boolean canUndo)
2862    {
2863        if (!rois.isEmpty())
2864        {
2865            final List<ROI> removedRois = new ArrayList<ROI>();
2866
2867            for (ROI roi : rois)
2868            {
2869                if (removeROI(roi, false))
2870                    removedRois.add(roi);
2871            }
2872
2873            if (canUndo && !removedRois.isEmpty())
2874                addUndoableEdit(new ROIRemovesSequenceEdit(this, removedRois));
2875
2876            return removedRois.size() == rois.size();
2877        }
2878
2879        return true;
2880    }
2881
2882    /**
2883     * Remove all selected ROI from the sequence.
2884     * 
2885     * @param removeReadOnly
2886     *        Specify if we should also remove <i>read only</i> ROI (see {@link ROI#isReadOnly()})
2887     * @return <code>true</code> if at least one ROI was removed.<br/>
2888     *         Returns <code>false</code> otherwise
2889     */
2890    public boolean removeSelectedROIs(boolean removeReadOnly)
2891    {
2892        return removeSelectedROIs(removeReadOnly, false);
2893    }
2894
2895    /**
2896     * Remove all selected ROI from the sequence.
2897     * 
2898     * @param removeReadOnly
2899     *        Specify if we should also remove <i>read only</i> ROI (see {@link ROI#isReadOnly()})
2900     * @param canUndo
2901     *        If true the action can be canceled by the undo manager.
2902     * @return <code>true</code> if at least one ROI was removed.<br/>
2903     *         Returns <code>false</code> otherwise
2904     */
2905    public boolean removeSelectedROIs(boolean removeReadOnly, boolean canUndo)
2906    {
2907        final List<ROI> undoList = new ArrayList<ROI>();
2908
2909        beginUpdate();
2910        try
2911        {
2912            synchronized (rois)
2913            {
2914                for (ROI roi : getROIs())
2915                {
2916                    if (roi.isSelected() && (removeReadOnly || !roi.isReadOnly()))
2917                    {
2918                        // remove ROI overlay first
2919                        removeOverlay(roi.getOverlay());
2920
2921                        rois.remove(roi);
2922                        // remove listener
2923                        roi.removeListener(this);
2924                        // notify roi removed
2925                        roiChanged(roi, SequenceEventType.REMOVED);
2926
2927                        // save deleted ROI
2928                        undoList.add(roi);
2929                    }
2930                }
2931            }
2932
2933            if (canUndo)
2934                undoManager.addEdit(new ROIRemovesSequenceEdit(this, undoList));
2935        }
2936        finally
2937        {
2938            endUpdate();
2939        }
2940
2941        return !undoList.isEmpty();
2942    }
2943
2944    /**
2945     * Remove all ROI from the sequence.
2946     */
2947    public void removeAllROI()
2948    {
2949        removeAllROI(false);
2950    }
2951
2952    /**
2953     * Remove all ROI from the sequence.
2954     * 
2955     * @param canUndo
2956     *        If true the action can be canceled by the undo manager.
2957     */
2958    public void removeAllROI(boolean canUndo)
2959    {
2960        if (!rois.isEmpty())
2961        {
2962            final List<ROI> allROIs = getROIs();
2963
2964            // remove all ROI
2965            for (ROI roi : allROIs)
2966                removeROI(roi, false);
2967
2968            if (canUndo)
2969                addUndoableEdit(new ROIRemovesSequenceEdit(this, allROIs));
2970        }
2971    }
2972
2973    /**
2974     * Return the overlay associated to the specified painter.<br>
2975     * Used only for backward compatibility with {@link Painter} interface.
2976     */
2977    @SuppressWarnings("deprecation")
2978    protected Overlay getOverlay(Painter painter)
2979    {
2980        if (painter instanceof Overlay)
2981            return (Overlay) painter;
2982
2983        synchronized (overlays)
2984        {
2985            for (Overlay overlay : overlays)
2986                if (overlay instanceof OverlayWrapper)
2987                    if (((OverlayWrapper) overlay).getPainter() == painter)
2988                        return overlay;
2989        }
2990
2991        return null;
2992    }
2993
2994    /**
2995     * @deprecated Use {@link #addOverlay(Overlay)} instead.
2996     */
2997    @Deprecated
2998    public boolean addPainter(Painter painter)
2999    {
3000        if (painter instanceof Overlay)
3001            return addOverlay((Overlay) painter);
3002
3003        if ((painter == null) || contains(painter))
3004            return false;
3005
3006        addOverlay(new OverlayWrapper(painter, "Overlay wrapper"));
3007
3008        return true;
3009    }
3010
3011    /**
3012     * @deprecated Use {@link #removeOverlay(Overlay)} instead.
3013     */
3014    @Deprecated
3015    public boolean removePainter(Painter painter)
3016    {
3017        if (painter instanceof Overlay)
3018            return removeOverlay((Overlay) painter);
3019
3020        return removeOverlay(getOverlay(painter));
3021    }
3022
3023    /**
3024     * Add an overlay to the sequence.
3025     */
3026    public boolean addOverlay(Overlay overlay)
3027    {
3028        if ((overlay == null) || contains(overlay))
3029            return false;
3030
3031        synchronized (overlays)
3032        {
3033            overlays.add(overlay);
3034        }
3035
3036        // add listener
3037        overlay.addOverlayListener(this);
3038        // notify overlay added
3039        overlayChanged(overlay, SequenceEventType.ADDED);
3040
3041        return true;
3042    }
3043
3044    /**
3045     * Remove an overlay from the sequence.
3046     */
3047    public boolean removeOverlay(Overlay overlay)
3048    {
3049        boolean result;
3050
3051        synchronized (overlays)
3052        {
3053            result = overlays.remove(overlay);
3054        }
3055
3056        if (result)
3057        {
3058            // remove listener
3059            overlay.removeOverlayListener(this);
3060            // notify overlay removed
3061            overlayChanged(overlay, SequenceEventType.REMOVED);
3062        }
3063
3064        return result;
3065    }
3066
3067    /**
3068     * Returns the VolumetricImage at position t
3069     */
3070    public VolumetricImage getVolumetricImage(int t)
3071    {
3072        synchronized (volumetricImages)
3073        {
3074            return volumetricImages.get(Integer.valueOf(t));
3075        }
3076    }
3077
3078    /**
3079     * Returns the first VolumetricImage
3080     */
3081    protected VolumetricImage getFirstVolumetricImage()
3082    {
3083        final Entry<Integer, VolumetricImage> entry;
3084
3085        synchronized (volumetricImages)
3086        {
3087            entry = volumetricImages.firstEntry();
3088        }
3089
3090        if (entry != null)
3091            return entry.getValue();
3092
3093        return null;
3094    }
3095
3096    /**
3097     * Returns the last VolumetricImage
3098     */
3099    protected VolumetricImage getLastVolumetricImage()
3100    {
3101        final Entry<Integer, VolumetricImage> entry;
3102
3103        synchronized (volumetricImages)
3104        {
3105            entry = volumetricImages.lastEntry();
3106        }
3107
3108        if (entry != null)
3109            return entry.getValue();
3110
3111        return null;
3112    }
3113
3114    /**
3115     * Add an empty volumetricImage at last index + 1
3116     */
3117    public VolumetricImage addVolumetricImage()
3118    {
3119        return setVolumetricImage(getSizeT());
3120    }
3121
3122    /**
3123     * Add an empty volumetricImage at t position
3124     */
3125    protected VolumetricImage setVolumetricImage(int t)
3126    {
3127        // remove old volumetric image if any
3128        removeAllImages(t);
3129
3130        final VolumetricImage volImg = new VolumetricImage(this);
3131
3132        synchronized (volumetricImages)
3133        {
3134            volumetricImages.put(Integer.valueOf(t), volImg);
3135        }
3136
3137        return volImg;
3138    }
3139
3140    /**
3141     * Add a volumetricImage at t position<br>
3142     * It actually create a new volumetricImage and add it to the sequence<br>
3143     * The new created volumetricImage is returned
3144     */
3145    public VolumetricImage addVolumetricImage(int t, VolumetricImage volImg)
3146    {
3147        if (volImg != null)
3148        {
3149            final VolumetricImage result;
3150
3151            beginUpdate();
3152            try
3153            {
3154                // get new volumetric image (remove old one if any)
3155                result = setVolumetricImage(t);
3156
3157                for (Entry<Integer, IcyBufferedImage> entry : volImg.getImages().entrySet())
3158                    setImage(t, entry.getKey().intValue(), entry.getValue());
3159            }
3160            finally
3161            {
3162                endUpdate();
3163            }
3164
3165            return result;
3166        }
3167
3168        return null;
3169    }
3170
3171    /**
3172     * @deprecated Use {@link #removeAllImages(int)} instead.
3173     */
3174    @Deprecated
3175    public boolean removeVolumetricImage(int t)
3176    {
3177        return removeAllImages(t);
3178    }
3179
3180    /**
3181     * Returns the last image of VolumetricImage[t]
3182     */
3183    public IcyBufferedImage getLastImage(int t)
3184    {
3185        final VolumetricImage volImg = getVolumetricImage(t);
3186
3187        if (volImg != null)
3188            return volImg.getLastImage();
3189
3190        return null;
3191    }
3192
3193    /**
3194     * Returns the first image of first VolumetricImage
3195     */
3196    public IcyBufferedImage getFirstImage()
3197    {
3198        final VolumetricImage volImg = getFirstVolumetricImage();
3199
3200        if (volImg != null)
3201            return volImg.getFirstImage();
3202
3203        return null;
3204    }
3205
3206    /**
3207     * Returns the first non null image if exist
3208     */
3209    public IcyBufferedImage getFirstNonNullImage()
3210    {
3211        synchronized (volumetricImages)
3212        {
3213            for (VolumetricImage volImg : volumetricImages.values())
3214            {
3215                final IcyBufferedImage img = volImg.getFirstNonNullImage();
3216                if (img != null)
3217                    return img;
3218            }
3219        }
3220
3221        return null;
3222    }
3223
3224    /**
3225     * Returns the last image of last VolumetricImage
3226     */
3227    public IcyBufferedImage getLastImage()
3228    {
3229        final VolumetricImage volImg = getLastVolumetricImage();
3230
3231        if (volImg != null)
3232            return volImg.getLastImage();
3233
3234        return null;
3235    }
3236
3237    /**
3238     * Returns a single component image corresponding to the component c of the image
3239     * at time t and depth z.<br>
3240     * This actually create a new image which share its data with internal image
3241     * so any modifications to one affect the other.<br>
3242     * if <code>(c == -1)</code> then this method is equivalent to {@link #getImage(int, int)}<br>
3243     * if <code>((c == 0) || (sizeC == 1))</code> then this method is equivalent to {@link #getImage(int, int)}<br>
3244     * if <code>((c < 0) || (c >= sizeC))</code> then it returns <code>null</code>
3245     * 
3246     * @see IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)
3247     * @since version 1.0.3.3b
3248     */
3249    @Override
3250    public IcyBufferedImage getImage(int t, int z, int c)
3251    {
3252        final IcyBufferedImage src = getImage(t, z);
3253
3254        if ((src == null) || (c == -1))
3255            return src;
3256
3257        return src.getImage(c);
3258    }
3259
3260    /**
3261     * Returns image at time t and depth z.
3262     * 
3263     * @param loadData
3264     *        if <code>true</code> then we ensure that image data is loaded (in case of lazy loading) before returning the image
3265     */
3266    public IcyBufferedImage getImage(int t, int z, boolean loadData)
3267    {
3268        final VolumetricImage volImg = getVolumetricImage(t);
3269
3270        if (volImg != null)
3271        {
3272            final IcyBufferedImage result = volImg.getImage(z);
3273
3274            if (loadData && (result != null))
3275                result.loadData();
3276
3277            return result;
3278        }
3279
3280        return null;
3281    }
3282
3283    /**
3284     * Returns image at time t and depth z
3285     */
3286    @Override
3287    public IcyBufferedImage getImage(int t, int z)
3288    {
3289        // FIXME: check if that is *really* not needed anymore (as the image contains the importer information)
3290
3291        // by default we prefer to load data on getImage(t,z) call as we probably need it
3292        // (and that is important if we want to set the image in another Sequence)
3293        // return getImage(t, z, true);
3294
3295        return getImage(t, z, false);
3296    }
3297
3298    /**
3299     * Returns all images at specified t position
3300     */
3301    public ArrayList<IcyBufferedImage> getImages(int t)
3302    {
3303        final VolumetricImage volImg = getVolumetricImage(t);
3304
3305        if (volImg != null)
3306            return volImg.getAllImage();
3307
3308        return new ArrayList<IcyBufferedImage>();
3309    }
3310
3311    /**
3312     * Returns all images of sequence in [ZT] order:<br>
3313     * 
3314     * <pre>
3315     * T=0 Z=0
3316     * T=0 Z=1
3317     * T=0 Z=2
3318     * ...
3319     * T=1 Z=0
3320     * ...
3321     * </pre>
3322     */
3323    public ArrayList<IcyBufferedImage> getAllImage()
3324    {
3325        final ArrayList<IcyBufferedImage> result = new ArrayList<IcyBufferedImage>();
3326
3327        synchronized (volumetricImages)
3328        {
3329            for (VolumetricImage volImg : volumetricImages.values())
3330                result.addAll(volImg.getAllImage());
3331        }
3332
3333        return result;
3334    }
3335
3336    /**
3337     * Put an image into the specified VolumetricImage at the given z location
3338     */
3339    protected void setImage(VolumetricImage volImg, int z, BufferedImage image) throws IllegalArgumentException
3340    {
3341        if (volImg != null)
3342        {
3343            // not the same image ?
3344            if (volImg.getImage(z) != image)
3345            {
3346                // this is different from removeImage as we don't remove empty VolumetricImage
3347                if (image == null)
3348                    volImg.removeImage(z);
3349                else
3350                {
3351                    IcyBufferedImage icyImg;
3352
3353                    // convert to icyImage if needed
3354                    if (image instanceof IcyBufferedImage)
3355                        icyImg = (IcyBufferedImage) image;
3356                    else
3357                        icyImg = IcyBufferedImage.createFrom(image);
3358
3359                    // possible type change ?
3360                    final boolean typeChange = (colorModel == null) || isEmpty()
3361                            || ((getNumImage() == 1) && (volImg.getImage(z) != null));
3362
3363                    // not changing type and not compatible
3364                    if (!typeChange && !isCompatible(icyImg))
3365                        throw new IllegalArgumentException("Sequence.setImage: image is not compatible !");
3366
3367                    // we want to share the same color space for all the sequence:
3368                    // colormap eats a lot of memory so it's better to keep one global and we never
3369                    // use colormap for single image anyway. But it's important to preserve the colormodel for each
3370                    // image though as it store the channel bounds informations.
3371                    if (colorModel != null)
3372                        icyImg.getIcyColorModel().setColorSpace(colorModel.getIcyColorSpace());
3373
3374                    // set automatic channel update from sequence
3375                    icyImg.setAutoUpdateChannelBounds(getAutoUpdateChannelBounds());
3376
3377                    // set image
3378                    volImg.setImage(z, icyImg);
3379
3380                    // possible type change --> virtual state may have changed
3381                    if (typeChange)
3382                        metaChanged(ID_VIRTUAL);
3383                }
3384            }
3385        }
3386    }
3387
3388    /**
3389     * Set an image at the specified position.<br/>
3390     * Note that the image will be transformed in IcyBufferedImage internally if needed
3391     * 
3392     * @param t
3393     *        T position
3394     * @param z
3395     *        Z position
3396     * @param image
3397     *        the image to set
3398     */
3399    public void setImage(int t, int z, BufferedImage image) throws IllegalArgumentException
3400    {
3401        final boolean volImgCreated;
3402
3403        if (image == null)
3404            return;
3405
3406        VolumetricImage volImg = getVolumetricImage(t);
3407
3408        if (volImg == null)
3409        {
3410            volImg = setVolumetricImage(t);
3411            volImgCreated = true;
3412        }
3413        else
3414            volImgCreated = false;
3415
3416        try
3417        {
3418            // set image
3419            setImage(volImg, z, image);
3420        }
3421        catch (IllegalArgumentException e)
3422        {
3423            // image set failed ? remove empty image list if needed
3424            if (volImgCreated)
3425                removeAllImages(t);
3426            // throw exception
3427            throw e;
3428        }
3429    }
3430
3431    /**
3432     * Add an image (image is added in Z dimension).<br>
3433     * This method is equivalent to <code>setImage(max(getSizeT() - 1, 0), getSizeZ(t), image)</code>
3434     */
3435    public void addImage(BufferedImage image) throws IllegalArgumentException
3436    {
3437        final int t = Math.max(getSizeT() - 1, 0);
3438
3439        setImage(t, getSizeZ(t), image);
3440    }
3441
3442    /**
3443     * Add an image at specified T position.<br>
3444     * This method is equivalent to <code>setImage(t, getSizeZ(t), image)</code>
3445     */
3446    public void addImage(int t, BufferedImage image) throws IllegalArgumentException
3447    {
3448        setImage(t, getSizeZ(t), image);
3449    }
3450
3451    /**
3452     * Remove the image at the specified position.
3453     */
3454    public boolean removeImage(int t, int z)
3455    {
3456        final VolumetricImage volImg = getVolumetricImage(t);
3457
3458        if (volImg != null)
3459        {
3460            final boolean result;
3461
3462            beginUpdate();
3463            try
3464            {
3465                result = volImg.removeImage(z);
3466
3467                // empty ?
3468                if (volImg.isEmpty())
3469                    // remove it
3470                    removeAllImages(t);
3471            }
3472            finally
3473            {
3474                endUpdate();
3475            }
3476
3477            return result;
3478        }
3479
3480        return false;
3481    }
3482
3483    /**
3484     * Remove all images at position <code>t</code>
3485     */
3486    public boolean removeAllImages(int t)
3487    {
3488        final VolumetricImage volImg;
3489
3490        synchronized (volumetricImages)
3491        {
3492            volImg = volumetricImages.remove(Integer.valueOf(t));
3493        }
3494
3495        // we do manual clear to dispatch events correctly
3496        if (volImg != null)
3497            volImg.clear();
3498
3499        return volImg != null;
3500    }
3501
3502    /**
3503     * Remove all images
3504     */
3505    public void removeAllImages()
3506    {
3507        beginUpdate();
3508        try
3509        {
3510            synchronized (volumetricImages)
3511            {
3512                while (!volumetricImages.isEmpty())
3513                {
3514                    final VolumetricImage volImg = volumetricImages.pollFirstEntry().getValue();
3515                    // we do manual clear to dispatch events correctly
3516                    if (volImg != null)
3517                        volImg.clear();
3518                }
3519            }
3520        }
3521        finally
3522        {
3523            endUpdate();
3524        }
3525    }
3526
3527    /**
3528     * @deprecated Use {@link #removeAllImages(int)} instead.
3529     */
3530    @Deprecated
3531    public boolean removeAllImage(int t)
3532    {
3533        return removeAllImages(t);
3534    }
3535
3536    /**
3537     * @deprecated Use {@link #removeAllImages()} instead.
3538     */
3539    @Deprecated
3540    public void removeAllImage()
3541    {
3542        removeAllImages();
3543    }
3544
3545    /**
3546     * Remove empty element of image list
3547     */
3548    public void packImageList()
3549    {
3550        beginUpdate();
3551        try
3552        {
3553            synchronized (volumetricImages)
3554            {
3555                for (Entry<Integer, VolumetricImage> entry : volumetricImages.entrySet())
3556                {
3557                    final VolumetricImage volImg = entry.getValue();
3558                    final int t = entry.getKey().intValue();
3559
3560                    if (volImg == null)
3561                    {
3562                        removeAllImages(t);
3563                    }
3564                    else
3565                    {
3566                        // pack the list
3567                        volImg.pack();
3568                        // empty ? --> remove it
3569                        if (volImg.isEmpty())
3570                            removeAllImages(t);
3571                    }
3572                }
3573            }
3574        }
3575        finally
3576        {
3577            endUpdate();
3578        }
3579    }
3580
3581    /**
3582     * return the number of loaded image
3583     */
3584    public int getNumImage()
3585    {
3586        int result = 0;
3587
3588        synchronized (volumetricImages)
3589        {
3590            for (VolumetricImage volImg : volumetricImages.values())
3591                if (volImg != null)
3592                    result += volImg.getNumImage();
3593        }
3594
3595        return result;
3596    }
3597
3598    /**
3599     * return true if no image in sequence
3600     */
3601    public boolean isEmpty()
3602    {
3603        synchronized (volumetricImages)
3604        {
3605            for (VolumetricImage volImg : volumetricImages.values())
3606                if ((volImg != null) && (!volImg.isEmpty()))
3607                    return false;
3608        }
3609
3610        return true;
3611    }
3612
3613    /**
3614     * Returns true if the sequence uses default attributed name
3615     */
3616    public boolean isDefaultName()
3617    {
3618        return getName().startsWith(DEFAULT_NAME);
3619    }
3620
3621    /**
3622     * Returns true is the specified channel uses default attributed name
3623     */
3624    public boolean isDefaultChannelName(int index)
3625    {
3626        return StringUtil.equals(getChannelName(index), getDefaultChannelName(index));
3627    }
3628
3629    /**
3630     * Returns the number of volumetricImage in the sequence<br>
3631     * Use getSizeT instead
3632     * 
3633     * @see #getSizeT
3634     * @deprecated
3635     */
3636    @Deprecated
3637    public int getLength()
3638    {
3639        return getSizeT();
3640    }
3641
3642    /**
3643     * return the number of volumetricImage in the sequence
3644     */
3645    @Override
3646    public int getSizeT()
3647    {
3648        synchronized (volumetricImages)
3649        {
3650            if (volumetricImages.isEmpty())
3651                return 0;
3652
3653            return volumetricImages.lastKey().intValue() + 1;
3654        }
3655    }
3656
3657    /**
3658     * Returns the global number of z stack in the sequence.
3659     * Use getSizeZ instead
3660     * 
3661     * @see #getSizeZ
3662     * @deprecated
3663     */
3664    @Deprecated
3665    public int getDepth()
3666    {
3667        return getSizeZ();
3668    }
3669
3670    /**
3671     * Returns the global number of z stack in the sequence.
3672     */
3673    @Override
3674    public int getSizeZ()
3675    {
3676        final int sizeT = getSizeT();
3677
3678        int result = 0;
3679        for (int i = 0; i < sizeT; i++)
3680            result = Math.max(result, getSizeZ(i));
3681
3682        return result;
3683    }
3684
3685    /**
3686     * Returns the number of z stack for the volumetricImage[t].
3687     */
3688    public int getSizeZ(int t)
3689    {
3690        // t = -1 means global Z size
3691        if (t == -1)
3692            return getSizeZ();
3693
3694        final VolumetricImage volImg = getVolumetricImage(t);
3695
3696        if (volImg != null)
3697            return volImg.getSize();
3698
3699        return 0;
3700    }
3701
3702    /**
3703     * Returns the number of component/channel/band per image.<br>
3704     * Use getSizeC instead
3705     * 
3706     * @see #getSizeC
3707     * @deprecated
3708     */
3709    @Deprecated
3710    public int getNumComponents()
3711    {
3712        return getSizeC();
3713    }
3714
3715    /**
3716     * Returns the number of component/channel/band per image
3717     */
3718    @Override
3719    public int getSizeC()
3720    {
3721        // color model defined ? --> get it from color model
3722        if (colorModel != null)
3723            return colorModel.getNumComponents();
3724
3725        // else try to get it from metadata
3726        return MetaDataUtil.getSizeC(metaData, 0);
3727    }
3728
3729    /**
3730     * Same as {@link #getSizeY()}
3731     */
3732    public int getHeight()
3733    {
3734        return getSizeY();
3735    }
3736
3737    /**
3738     * Returns the height of the sequence (0 if the sequence contains no image).
3739     */
3740    @Override
3741    public int getSizeY()
3742    {
3743        // try to get from image first
3744        final IcyBufferedImage img = getFirstNonNullImage();
3745
3746        if (img != null)
3747            return img.getHeight();
3748
3749        // else try to get from metadata
3750        return MetaDataUtil.getSizeY(metaData, 0);
3751    }
3752
3753    /**
3754     * Same as {@link #getSizeX()}
3755     */
3756    public int getWidth()
3757    {
3758        return getSizeX();
3759    }
3760
3761    /**
3762     * Returns the width of the sequence (0 if the sequence contains no image).
3763     */
3764    @Override
3765    public int getSizeX()
3766    {
3767        final IcyBufferedImage img = getFirstNonNullImage();
3768
3769        // try to get it from image first
3770        if (img != null)
3771            return img.getWidth();
3772
3773        // else try to get from metadata
3774        return MetaDataUtil.getSizeX(metaData, 0);
3775    }
3776
3777    /**
3778     * Returns the size of the specified dimension
3779     */
3780    public int getSize(DimensionId dim)
3781    {
3782        switch (dim)
3783        {
3784            case X:
3785                return getSizeX();
3786            case Y:
3787                return getSizeY();
3788            case C:
3789                return getSizeC();
3790            case Z:
3791                return getSizeZ();
3792            case T:
3793                return getSizeT();
3794            default:
3795            case NULL:
3796                return 0;
3797        }
3798    }
3799
3800    /**
3801     * Returns 2D dimension of sequence {sizeX, sizeY}
3802     */
3803    public Dimension getDimension2D()
3804    {
3805        return new Dimension(getSizeX(), getSizeY());
3806    }
3807
3808    /**
3809     * Returns 5D dimension of sequence {sizeX, sizeY, sizeZ, sizeT, sizeC}
3810     */
3811    public Dimension5D.Integer getDimension5D()
3812    {
3813        return new Dimension5D.Integer(getSizeX(), getSizeY(), getSizeZ(), getSizeT(), getSizeC());
3814    }
3815
3816    /**
3817     * @deprecated Use {@link #getDimension2D()} instead.
3818     */
3819    @Deprecated
3820    public Dimension getDimension()
3821    {
3822        return getDimension2D();
3823    }
3824
3825    /**
3826     * Returns 2D bounds of sequence {0, 0, sizeX, sizeY}
3827     * 
3828     * @see #getDimension2D()
3829     */
3830    public Rectangle getBounds2D()
3831    {
3832        return new Rectangle(getSizeX(), getSizeY());
3833    }
3834
3835    /**
3836     * Returns 5D bounds of sequence {0, 0, 0, 0, 0, sizeX, sizeY, sizeZ, sizeT, sizeC}
3837     * 
3838     * @see #getDimension5D()
3839     */
3840    public Rectangle5D.Integer getBounds5D()
3841    {
3842        return new Rectangle5D.Integer(0, 0, 0, 0, 0, getSizeX(), getSizeY(), getSizeZ(), getSizeT(), getSizeC());
3843    }
3844
3845    /**
3846     * @deprecated Use {@link #getBounds2D()} instead
3847     */
3848    @Deprecated
3849    public Rectangle getBounds()
3850    {
3851        return getBounds2D();
3852    }
3853
3854    /**
3855     * Returns the number of sample.<br>
3856     * This is equivalent to<br>
3857     * <code>getSizeX() * getSizeY() * getSizeC() * getSizeZ() * getSizeT()</code>
3858     */
3859    public int getNumSample()
3860    {
3861        return getSizeX() * getSizeY() * getSizeC() * getSizeZ() * getSizeT();
3862    }
3863
3864    /**
3865     * Test if the specified image is compatible with current loaded images in sequence
3866     */
3867    public boolean isCompatible(IcyBufferedImage image)
3868    {
3869        if ((colorModel == null) || isEmpty())
3870            return true;
3871
3872        return (image.getWidth() == getWidth()) && (image.getHeight() == getHeight())
3873                && isCompatible(image.getIcyColorModel());
3874    }
3875
3876    /**
3877     * Test if the specified colorModel is compatible with sequence colorModel
3878     */
3879    public boolean isCompatible(IcyColorModel cm)
3880    {
3881        // test that colorModel are compatible
3882        if (colorModel == null)
3883            return true;
3884
3885        return colorModel.isCompatible(cm);
3886    }
3887
3888    /**
3889     * Returns true if specified LUT is compatible with sequence LUT
3890     */
3891    public boolean isLutCompatible(LUT lut)
3892    {
3893        IcyColorModel cm = colorModel;
3894        // not yet defined ? use default one
3895        if (cm == null)
3896            cm = IcyColorModel.createInstance();
3897
3898        return lut.isCompatible(cm);
3899    }
3900
3901    /**
3902     * Returns the colorModel
3903     */
3904    public IcyColorModel getColorModel()
3905    {
3906        return colorModel;
3907    }
3908
3909    /**
3910     * Same as {@link #createCompatibleLUT()}
3911     */
3912    public LUT getDefaultLUT()
3913    {
3914        // color model not anymore compatible with user LUT --> reset it
3915        if ((defaultLut == null) || ((colorModel != null) && !defaultLut.isCompatible(colorModel)))
3916            defaultLut = createCompatibleLUT();
3917
3918        return defaultLut;
3919    }
3920
3921    /**
3922     * Returns <code>true</code> if a user LUT has be defined for this sequence.
3923     */
3924    public boolean hasUserLUT()
3925    {
3926        return (userLut != null);
3927    }
3928
3929    /**
3930     * Returns the users LUT.<br>
3931     * If user LUT is not defined then a new default LUT is returned.
3932     * 
3933     * @see #getDefaultLUT()
3934     */
3935    public LUT getUserLUT()
3936    {
3937        // color model not anymore compatible with user LUT --> reset it
3938        if ((userLut == null) || ((colorModel != null) && !userLut.isCompatible(colorModel)))
3939            userLut = getDefaultLUT();
3940
3941        return userLut;
3942    }
3943
3944    /**
3945     * Sets the user LUT (saved in XML persistent metadata).
3946     */
3947    public void setUserLUT(LUT lut)
3948    {
3949        if ((colorModel == null) || lut.isCompatible(colorModel))
3950            userLut = lut;
3951    }
3952
3953    /**
3954     * Creates and returns the default LUT for this sequence.<br>
3955     * If the sequence is empty it returns a default ARGB LUT.
3956     */
3957    public LUT createCompatibleLUT()
3958    {
3959        final IcyColorModel result;
3960
3961        // not yet defined ? use default one
3962        if (colorModel == null)
3963            result = IcyColorModel.createInstance();
3964        else
3965            result = IcyColorModel.createInstance(colorModel, true, true);
3966
3967        return new LUT(result);
3968    }
3969
3970    /**
3971     * Get the default colormap for the specified channel
3972     * 
3973     * @param channel
3974     *        channel we want to set the colormap
3975     * @see #getColorMap(int)
3976     */
3977    public IcyColorMap getDefaultColorMap(int channel)
3978    {
3979        if (colorModel != null)
3980            return colorModel.getColorMap(channel);
3981
3982        return getDefaultLUT().getLutChannel(channel).getColorMap();
3983    }
3984
3985    /**
3986     * Set the default colormap for the specified channel
3987     * 
3988     * @param channel
3989     *        channel we want to set the colormap
3990     * @param map
3991     *        source colormap to copy
3992     * @param setAlpha
3993     *        also copy the alpha information
3994     * @see #getDefaultColorMap(int)
3995     */
3996    public void setDefaultColormap(int channel, IcyColorMap map, boolean setAlpha)
3997    {
3998        if (colorModel != null)
3999            colorModel.setColorMap(channel, map, setAlpha);
4000    }
4001
4002    /**
4003     * Set the default colormap for the specified channel
4004     * 
4005     * @param channel
4006     *        channel we want to set the colormap
4007     * @param map
4008     *        source colormap to copy
4009     * @see #getDefaultColorMap(int)
4010     */
4011    public void setDefaultColormap(int channel, IcyColorMap map)
4012    {
4013        setDefaultColormap(channel, map, map.isAlpha());
4014    }
4015
4016    /**
4017     * Get the user colormap for the specified channel.<br>
4018     * User colormap is saved in the XML persistent data and reloaded when opening the Sequence.
4019     * 
4020     * @param channel
4021     *        channel we want to set the colormap
4022     * @see #getDefaultColorMap(int)
4023     */
4024    public IcyColorMap getColorMap(int channel)
4025    {
4026        final LUT lut = getUserLUT();
4027
4028        if (channel < lut.getNumChannel())
4029            return lut.getLutChannel(channel).getColorMap();
4030
4031        return null;
4032    }
4033
4034    /**
4035     * Set the user colormap for the specified channel.<br>
4036     * User colormap is saved in the XML persistent data and reloaded when opening the Sequence.
4037     * 
4038     * @param channel
4039     *        channel we want to set the colormap
4040     * @param map
4041     *        source colormap to copy
4042     * @param setAlpha
4043     *        also copy the alpha information
4044     * @see #getColorMap(int)
4045     */
4046    public void setColormap(int channel, IcyColorMap map, boolean setAlpha)
4047    {
4048        final LUT lut = getUserLUT();
4049
4050        if (channel < lut.getNumChannel())
4051            lut.getLutChannel(channel).setColorMap(map, setAlpha);
4052    }
4053
4054    /**
4055     * Set the user colormap for the specified channel.<br>
4056     * User colormap is saved in the XML persistent data and reloaded when opening the Sequence.
4057     * 
4058     * @param channel
4059     *        channel we want to set the colormap
4060     * @param map
4061     *        source colormap to copy
4062     * @see #getColorMap(int)
4063     */
4064    public void setColormap(int channel, IcyColorMap map)
4065    {
4066        setColormap(channel, map, map.isAlpha());
4067    }
4068
4069    /**
4070     * Returns the data type of sequence
4071     */
4072    public DataType getDataType_()
4073    {
4074        // assume unsigned byte by default
4075        if (colorModel == null)
4076            // preserve UNDEFINED here for backward compatibility (Math Operation for instance)
4077            return DataType.UNDEFINED;
4078
4079        return colorModel.getDataType_();
4080    }
4081
4082    /**
4083     * Returns the data type of sequence
4084     * 
4085     * @deprecated use {@link #getDataType_()} instead
4086     */
4087    @Deprecated
4088    public int getDataType()
4089    {
4090        if (colorModel == null)
4091            return TypeUtil.TYPE_UNDEFINED;
4092
4093        return colorModel.getDataType();
4094    }
4095
4096    /**
4097     * Returns true if this is a float data type sequence
4098     */
4099    public boolean isFloatDataType()
4100    {
4101        return getDataType_().isFloat();
4102    }
4103
4104    /**
4105     * Returns true if this is a signed data type sequence
4106     */
4107    public boolean isSignedDataType()
4108    {
4109        return getDataType_().isSigned();
4110    }
4111
4112    /**
4113     * Internal use only.
4114     */
4115    private static double[][] adjustBounds(double[][] curBounds, double[][] bounds)
4116    {
4117        if (bounds == null)
4118            return curBounds;
4119
4120        for (int comp = 0; comp < bounds.length; comp++)
4121        {
4122            final double[] compBounds = bounds[comp];
4123            final double[] curCompBounds = curBounds[comp];
4124
4125            if (curCompBounds[0] < compBounds[0])
4126                compBounds[0] = curCompBounds[0];
4127            if (curCompBounds[1] > compBounds[1])
4128                compBounds[1] = curCompBounds[1];
4129        }
4130
4131        return bounds;
4132    }
4133
4134    /**
4135     * Recalculate all image channels bounds (min and max values).<br>
4136     * Internal use only.
4137     */
4138    protected void recalculateAllImageChannelsBounds()
4139    {
4140        // nothing to do...
4141        if ((colorModel == null) || isEmpty())
4142            return;
4143
4144        final List<VolumetricImage> volumes = getAllVolumetricImage();
4145
4146        beginUpdate();
4147        try
4148        {
4149            // recalculate images bounds (automatically update sequence bounds with event)
4150            for (VolumetricImage volImg : volumes)
4151                for (IcyBufferedImage img : volImg.getAllImage())
4152                    img.updateChannelsBounds();
4153        }
4154        finally
4155        {
4156            endUpdate();
4157        }
4158    }
4159
4160    /**
4161     * Update channels bounds (min and max values)<br>
4162     * At this point we assume images has correct channels bounds information.<br>
4163     * Internal use only.
4164     */
4165    protected void internalUpdateChannelsBounds()
4166    {
4167        // nothing to do...
4168        if ((colorModel == null) || isEmpty())
4169            return;
4170
4171        double[][] bounds;
4172
4173        bounds = null;
4174        // recalculate bounds from all images
4175        synchronized (volumetricImages)
4176        {
4177            for (VolumetricImage volImg : volumetricImages.values())
4178            {
4179                for (IcyBufferedImage img : volImg.getAllImage())
4180                {
4181                    if (img != null)
4182                        bounds = adjustBounds(img.getChannelsTypeBounds(), bounds);
4183                }
4184            }
4185        }
4186
4187        // set new computed bounds
4188        colorModel.setComponentsAbsBounds(bounds);
4189
4190        bounds = null;
4191        // recalculate user bounds from all images
4192        synchronized (volumetricImages)
4193        {
4194            for (VolumetricImage volImg : volumetricImages.values())
4195            {
4196                for (IcyBufferedImage img : volImg.getAllImage())
4197                {
4198                    if (img != null)
4199                        bounds = adjustBounds(img.getChannelsBounds(), bounds);
4200                }
4201            }
4202        }
4203
4204        // set new computed bounds
4205        colorModel.setComponentsUserBounds(bounds);
4206    }
4207
4208    /**
4209     * Update channels bounds (min and max values).<br>
4210     * 
4211     * @param forceRecalculation
4212     *        If true we force all images channels bounds recalculation (this can take sometime). <br>
4213     *        You can left this flag to false if sequence images have their bounds updated (which
4214     *        should be the case by default).
4215     */
4216    public void updateChannelsBounds(boolean forceRecalculation)
4217    {
4218        // force calculation of all images bounds
4219        if (forceRecalculation)
4220            recalculateAllImageChannelsBounds();
4221        // then update sequence bounds
4222        internalUpdateChannelsBounds();
4223    }
4224
4225    /**
4226     * Update channels bounds (min and max values).<br>
4227     * All images channels bounds are recalculated (this can take sometime).
4228     */
4229    public void updateChannelsBounds()
4230    {
4231        // force recalculation
4232        updateChannelsBounds(true);
4233    }
4234
4235    /**
4236     * @deprecated Use {@link #updateChannelsBounds(boolean)} instead.
4237     */
4238    @Deprecated
4239    public void updateComponentsBounds(boolean forceRecalculation, boolean adjustByteToo)
4240    {
4241        updateChannelsBounds(forceRecalculation);
4242    }
4243
4244    /**
4245     * @deprecated Use {@link #updateChannelsBounds(boolean)} instead.
4246     */
4247    @Deprecated
4248    public void updateComponentsBounds(boolean forceRecalculation)
4249    {
4250        updateChannelsBounds(forceRecalculation);
4251    }
4252
4253    /**
4254     * @deprecated Use {@link #updateChannelsBounds(boolean)} instead.
4255     */
4256    @Deprecated
4257    public void updateComponentsBounds()
4258    {
4259        // force recalculation
4260        updateChannelsBounds(true);
4261    }
4262
4263    /**
4264     * Get the data type minimum value.
4265     */
4266    public double getDataTypeMin()
4267    {
4268        return getDataType_().getMinValue();
4269    }
4270
4271    /**
4272     * Get the data type maximum value.
4273     */
4274    public double getDataTypeMax()
4275    {
4276        return getDataType_().getMaxValue();
4277    }
4278
4279    /**
4280     * Get data type bounds (min and max values).
4281     */
4282    public double[] getDataTypeBounds()
4283    {
4284        return new double[] {getDataTypeMin(), getDataTypeMax()};
4285    }
4286
4287    /**
4288     * Get the preferred data type minimum value in the whole sequence for the specified channel.
4289     */
4290    public double getChannelTypeMin(int channel)
4291    {
4292        if (colorModel == null)
4293            return 0d;
4294
4295        return colorModel.getComponentAbsMinValue(channel);
4296    }
4297
4298    /**
4299     * Get the preferred data type maximum value in the whole sequence for the specified channel.
4300     */
4301    public double getChannelTypeMax(int channel)
4302    {
4303        if (colorModel == null)
4304            return 0d;
4305
4306        return colorModel.getComponentAbsMaxValue(channel);
4307    }
4308
4309    /**
4310     * Get the preferred data type bounds (min and max values) in the whole sequence for the
4311     * specified channel.
4312     */
4313    public double[] getChannelTypeBounds(int channel)
4314    {
4315        if (colorModel == null)
4316            return new double[] {0d, 0d};
4317
4318        return colorModel.getComponentAbsBounds(channel);
4319    }
4320
4321    /**
4322     * Get the preferred data type bounds (min and max values) in the whole sequence for all
4323     * channels.
4324     */
4325    public double[][] getChannelsTypeBounds()
4326    {
4327        final int sizeC = getSizeC();
4328        final double[][] result = new double[sizeC][];
4329
4330        for (int c = 0; c < sizeC; c++)
4331            result[c] = getChannelTypeBounds(c);
4332
4333        return result;
4334    }
4335
4336    /**
4337     * Get the global preferred data type bounds (min and max values) for all channels.
4338     */
4339    public double[] getChannelsGlobalTypeBounds()
4340    {
4341        final int sizeC = getSizeC();
4342        final double[] result = getChannelTypeBounds(0);
4343
4344        for (int c = 1; c < sizeC; c++)
4345        {
4346            final double[] bounds = getChannelTypeBounds(c);
4347            result[0] = Math.min(bounds[0], result[0]);
4348            result[1] = Math.max(bounds[1], result[1]);
4349        }
4350
4351        return result;
4352    }
4353
4354    /**
4355     * @deprecated Use {@link #getChannelsGlobalTypeBounds()} instead
4356     */
4357    @Deprecated
4358    public double[] getChannelTypeGlobalBounds()
4359    {
4360        return getChannelsGlobalTypeBounds();
4361    }
4362
4363    /**
4364     * @deprecated Use {@link #getChannelTypeGlobalBounds()} instead.
4365     */
4366    @Deprecated
4367    public double[] getGlobalChannelTypeBounds()
4368    {
4369        return getChannelTypeGlobalBounds();
4370    }
4371
4372    /**
4373     * @deprecated Use {@link #getChannelTypeMin(int)} instead.
4374     */
4375    @Deprecated
4376    public double getComponentAbsMinValue(int component)
4377    {
4378        return getChannelTypeMin(component);
4379    }
4380
4381    /**
4382     * @deprecated Use {@link #getChannelTypeMax(int)} instead.
4383     */
4384    @Deprecated
4385    public double getComponentAbsMaxValue(int component)
4386    {
4387        return getChannelTypeMax(component);
4388    }
4389
4390    /**
4391     * @deprecated Use {@link #getChannelTypeBounds(int)} instead.
4392     */
4393    @Deprecated
4394    public double[] getComponentAbsBounds(int component)
4395    {
4396        return getChannelTypeBounds(component);
4397    }
4398
4399    /**
4400     * @deprecated Use {@link #getChannelsTypeBounds()} instead.
4401     */
4402    @Deprecated
4403    public double[][] getComponentsAbsBounds()
4404    {
4405        return getChannelsTypeBounds();
4406    }
4407
4408    /**
4409     * @deprecated Use {@link #getChannelsGlobalTypeBounds()} instead.
4410     */
4411    @Deprecated
4412    public double[] getGlobalComponentAbsBounds()
4413    {
4414        return getChannelsGlobalTypeBounds();
4415    }
4416
4417    /**
4418     * Get the minimum value in the whole sequence for the specified channel.
4419     */
4420    public double getChannelMin(int channel)
4421    {
4422        if (colorModel == null)
4423            return 0d;
4424
4425        return colorModel.getComponentUserMinValue(channel);
4426    }
4427
4428    /**
4429     * Get maximum value in the whole sequence for the specified channel.
4430     */
4431    public double getChannelMax(int channel)
4432    {
4433        if (colorModel == null)
4434            return 0d;
4435
4436        return colorModel.getComponentUserMaxValue(channel);
4437    }
4438
4439    /**
4440     * Get bounds (min and max values) in the whole sequence for the specified channel.
4441     */
4442    public double[] getChannelBounds(int channel)
4443    {
4444        if (colorModel == null)
4445            return new double[] {0d, 0d};
4446
4447        // lazy channel bounds update
4448        if (channelBoundsInvalid)
4449        {
4450            channelBoundsInvalid = false;
4451            // images channels bounds are valid at this point
4452            internalUpdateChannelsBounds();
4453        }
4454
4455        return colorModel.getComponentUserBounds(channel);
4456    }
4457
4458    /**
4459     * Get bounds (min and max values) in the whole sequence for all channels.
4460     */
4461    public double[][] getChannelsBounds()
4462    {
4463        final int sizeC = getSizeC();
4464        final double[][] result = new double[sizeC][];
4465
4466        for (int c = 0; c < sizeC; c++)
4467            result[c] = getChannelBounds(c);
4468
4469        return result;
4470    }
4471
4472    /**
4473     * Get global bounds (min and max values) in the whole sequence for all channels.
4474     */
4475    public double[] getChannelsGlobalBounds()
4476    {
4477        final int sizeC = getSizeC();
4478        final double[] result = new double[2];
4479
4480        result[0] = Double.MAX_VALUE;
4481        result[1] = -Double.MAX_VALUE;
4482
4483        for (int c = 0; c < sizeC; c++)
4484        {
4485            final double[] bounds = getChannelBounds(c);
4486
4487            if (bounds[0] < result[0])
4488                result[0] = bounds[0];
4489            if (bounds[1] > result[1])
4490                result[1] = bounds[1];
4491        }
4492
4493        return result;
4494    }
4495
4496    /**
4497     * @deprecated Use {@link #getChannelMin(int)} instead.
4498     */
4499    @Deprecated
4500    public double getComponentUserMinValue(int component)
4501    {
4502        return getChannelMin(component);
4503    }
4504
4505    /**
4506     * @deprecated Use {@link #getChannelMax(int)} instead.
4507     */
4508    @Deprecated
4509    public double getComponentUserMaxValue(int component)
4510    {
4511        return getChannelMax(component);
4512    }
4513
4514    /**
4515     * @deprecated Use {@link #getChannelBounds(int)} instead.
4516     */
4517    @Deprecated
4518    public double[] getComponentUserBounds(int component)
4519    {
4520        return getChannelBounds(component);
4521    }
4522
4523    /**
4524     * @deprecated Use {@link #getChannelsBounds()} instead.
4525     */
4526    @Deprecated
4527    public double[][] getComponentsUserBounds()
4528    {
4529        return getChannelsBounds();
4530    }
4531
4532    /**
4533     * Force all image data to be loaded (so channels bounds can be correctly computed).<br>
4534     * Be careful, this function can take sometime.
4535     */
4536    public void loadAllData()
4537    {
4538        for (IcyBufferedImage image : getAllImage())
4539            if (image != null)
4540                image.loadData();
4541    }
4542
4543    /**
4544     * Returns the data value located at position (t, z, c, y, x) as double.<br>
4545     * It returns 0d if value is not found.
4546     */
4547    public double getData(int t, int z, int c, int y, int x)
4548    {
4549        final IcyBufferedImage img = getImage(t, z);
4550
4551        if (img != null)
4552            return img.getData(x, y, c);
4553
4554        return 0d;
4555    }
4556
4557    /**
4558     * Returns the data value located at position (t, z, c, y, x) as double.<br>
4559     * The value is interpolated depending the current double (x,y,z) coordinates.<br>
4560     * It returns 0d if value is out of range.
4561     */
4562    public double getDataInterpolated(int t, double z, int c, double y, double x)
4563    {
4564        final int zi = (int) z;
4565        final double ratioNextZ = z - (double) zi;
4566
4567        double result = 0d;
4568        IcyBufferedImage img;
4569
4570        img = getImage(t, zi);
4571        if (img != null)
4572        {
4573            final double ratioCurZ = 1d - ratioNextZ;
4574            if (ratioCurZ > 0d)
4575                result += img.getDataInterpolated(x, y, c) * ratioCurZ;
4576        }
4577        img = getImage(t, zi + 1);
4578        if (img != null)
4579        {
4580            if (ratioNextZ > 0d)
4581                result += img.getDataInterpolated(x, y, c) * ratioNextZ;
4582        }
4583
4584        return result;
4585    }
4586
4587    /**
4588     * Returns a direct reference to 4D array data [T][Z][C][XY]
4589     */
4590    public Object getDataXYCZT()
4591    {
4592        switch (getDataType_().getJavaType())
4593        {
4594            case BYTE:
4595                return getDataXYCZTAsByte();
4596            case SHORT:
4597                return getDataXYCZTAsShort();
4598            case INT:
4599                return getDataXYCZTAsInt();
4600            case FLOAT:
4601                return getDataXYCZTAsFloat();
4602            case DOUBLE:
4603                return getDataXYCZTAsDouble();
4604            default:
4605                return null;
4606        }
4607    }
4608
4609    /**
4610     * Returns a direct reference to 3D array data [Z][C][XY] for specified t
4611     */
4612    public Object getDataXYCZ(int t)
4613    {
4614        switch (getDataType_().getJavaType())
4615        {
4616            case BYTE:
4617                return getDataXYCZAsByte(t);
4618            case SHORT:
4619                return getDataXYCZAsShort(t);
4620            case INT:
4621                return getDataXYCZAsInt(t);
4622            case FLOAT:
4623                return getDataXYCZAsFloat(t);
4624            case DOUBLE:
4625                return getDataXYCZAsDouble(t);
4626            default:
4627                return null;
4628        }
4629    }
4630
4631    /**
4632     * Returns a direct reference to 2D array data [C][XY] for specified t, z
4633     */
4634    public Object getDataXYC(int t, int z)
4635    {
4636        final IcyBufferedImage img = getImage(t, z);
4637
4638        if (img != null)
4639            return img.getDataXYC();
4640
4641        return null;
4642    }
4643
4644    /**
4645     * Returns a direct reference to 1D array data [XY] for specified t, z, c
4646     */
4647    public Object getDataXY(int t, int z, int c)
4648    {
4649        final IcyBufferedImage img = getImage(t, z);
4650
4651        if (img != null)
4652            return img.getDataXY(c);
4653
4654        return null;
4655    }
4656
4657    /**
4658     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
4659     */
4660    public Object getDataXYZT(int c)
4661    {
4662        switch (getDataType_().getJavaType())
4663        {
4664            case BYTE:
4665                return getDataXYZTAsByte(c);
4666            case SHORT:
4667                return getDataXYZTAsShort(c);
4668            case INT:
4669                return getDataXYZTAsInt(c);
4670            case FLOAT:
4671                return getDataXYZTAsFloat(c);
4672            case DOUBLE:
4673                return getDataXYZTAsDouble(c);
4674            default:
4675                return null;
4676        }
4677    }
4678
4679    /**
4680     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
4681     */
4682    public Object getDataXYZ(int t, int c)
4683    {
4684        switch (getDataType_().getJavaType())
4685        {
4686            case BYTE:
4687                return getDataXYZAsByte(t, c);
4688            case SHORT:
4689                return getDataXYZAsShort(t, c);
4690            case INT:
4691                return getDataXYZAsInt(t, c);
4692            case FLOAT:
4693                return getDataXYZAsFloat(t, c);
4694            case DOUBLE:
4695                return getDataXYZAsDouble(t, c);
4696            default:
4697                return null;
4698        }
4699    }
4700
4701    /**
4702     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
4703     */
4704    public Object getDataCopyXYCZT()
4705    {
4706        return getDataCopyXYCZT(null, 0);
4707    }
4708
4709    /**
4710     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
4711     * If (out != null) then it's used to store result at the specified offset
4712     */
4713    public Object getDataCopyXYCZT(Object out, int off)
4714    {
4715        switch (getDataType_().getJavaType())
4716        {
4717            case BYTE:
4718                return getDataCopyXYCZTAsByte((byte[]) out, off);
4719            case SHORT:
4720                return getDataCopyXYCZTAsShort((short[]) out, off);
4721            case INT:
4722                return getDataCopyXYCZTAsInt((int[]) out, off);
4723            case FLOAT:
4724                return getDataCopyXYCZTAsFloat((float[]) out, off);
4725            case DOUBLE:
4726                return getDataCopyXYCZTAsDouble((double[]) out, off);
4727            default:
4728                return null;
4729        }
4730    }
4731
4732    /**
4733     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
4734     */
4735    public Object getDataCopyXYCZ(int t)
4736    {
4737        return getDataCopyXYCZ(t, null, 0);
4738    }
4739
4740    /**
4741     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
4742     * If (out != null) then it's used to store result at the specified offset
4743     */
4744    public Object getDataCopyXYCZ(int t, Object out, int off)
4745    {
4746        switch (getDataType_().getJavaType())
4747        {
4748            case BYTE:
4749                return getDataCopyXYCZAsByte(t, (byte[]) out, off);
4750            case SHORT:
4751                return getDataCopyXYCZAsShort(t, (short[]) out, off);
4752            case INT:
4753                return getDataCopyXYCZAsInt(t, (int[]) out, off);
4754            case FLOAT:
4755                return getDataCopyXYCZAsFloat(t, (float[]) out, off);
4756            case DOUBLE:
4757                return getDataCopyXYCZAsDouble(t, (double[]) out, off);
4758            default:
4759                return null;
4760        }
4761    }
4762
4763    /**
4764     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
4765     */
4766    public Object getDataCopyXYC(int t, int z)
4767    {
4768        return getDataCopyXYC(t, z, null, 0);
4769    }
4770
4771    /**
4772     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
4773     * If (out != null) then it's used to store result at the specified offset
4774     */
4775    public Object getDataCopyXYC(int t, int z, Object out, int off)
4776    {
4777        final IcyBufferedImage img = getImage(t, z);
4778
4779        if (img != null)
4780            return img.getDataCopyXYC(out, off);
4781
4782        return out;
4783    }
4784
4785    /**
4786     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
4787     */
4788    public Object getDataCopyXY(int t, int z, int c)
4789    {
4790        return getDataCopyXY(t, z, c, null, 0);
4791    }
4792
4793    /**
4794     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
4795     * If (out != null) then it's used to store result at the specified offset
4796     */
4797    public Object getDataCopyXY(int t, int z, int c, Object out, int off)
4798    {
4799        final IcyBufferedImage img = getImage(t, z);
4800
4801        if (img != null)
4802            return img.getDataCopyXY(c, out, off);
4803
4804        return out;
4805    }
4806
4807    /**
4808     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
4809     */
4810    public Object getDataCopyCXYZT()
4811    {
4812        return getDataCopyCXYZT(null, 0);
4813    }
4814
4815    /**
4816     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
4817     * If (out != null) then it's used to store result at the specified offset
4818     */
4819    public Object getDataCopyCXYZT(Object out, int off)
4820    {
4821        switch (getDataType_().getJavaType())
4822        {
4823            case BYTE:
4824                return getDataCopyCXYZTAsByte((byte[]) out, off);
4825            case SHORT:
4826                return getDataCopyCXYZTAsShort((short[]) out, off);
4827            case INT:
4828                return getDataCopyCXYZTAsInt((int[]) out, off);
4829            case FLOAT:
4830                return getDataCopyCXYZTAsFloat((float[]) out, off);
4831            case DOUBLE:
4832                return getDataCopyCXYZTAsDouble((double[]) out, off);
4833            default:
4834                return null;
4835        }
4836    }
4837
4838    /**
4839     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
4840     */
4841    public Object getDataCopyCXYZ(int t)
4842    {
4843        return getDataCopyCXYZ(t, null, 0);
4844    }
4845
4846    /**
4847     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
4848     * If (out != null) then it's used to store result at the specified offset
4849     */
4850    public Object getDataCopyCXYZ(int t, Object out, int off)
4851    {
4852        switch (getDataType_().getJavaType())
4853        {
4854            case BYTE:
4855                return getDataCopyCXYZAsByte(t, (byte[]) out, off);
4856            case SHORT:
4857                return getDataCopyCXYZAsShort(t, (short[]) out, off);
4858            case INT:
4859                return getDataCopyCXYZAsInt(t, (int[]) out, off);
4860            case FLOAT:
4861                return getDataCopyCXYZAsFloat(t, (float[]) out, off);
4862            case DOUBLE:
4863                return getDataCopyCXYZAsDouble(t, (double[]) out, off);
4864            default:
4865                return null;
4866        }
4867    }
4868
4869    /**
4870     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
4871     */
4872    public Object getDataCopyCXY(int t, int z)
4873    {
4874        return getDataCopyCXY(t, z, null, 0);
4875    }
4876
4877    /**
4878     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
4879     * If (out != null) then it's used to store result at the specified offset
4880     */
4881    public Object getDataCopyCXY(int t, int z, Object out, int off)
4882    {
4883        final IcyBufferedImage img = getImage(t, z);
4884
4885        if (img != null)
4886            return img.getDataCopyCXY(out, off);
4887
4888        return out;
4889    }
4890
4891    /**
4892     * Returns a 1D array data copy [C] of specified t, z, x, y
4893     */
4894    public Object getDataCopyC(int t, int z, int x, int y)
4895    {
4896        return getDataCopyC(t, z, x, y, null, 0);
4897    }
4898
4899    /**
4900     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
4901     * If (out != null) then it's used to store result at the specified offset
4902     */
4903    public Object getDataCopyC(int t, int z, int x, int y, Object out, int off)
4904    {
4905        final IcyBufferedImage img = getImage(t, z);
4906
4907        if (img != null)
4908            return img.getDataCopyC(x, y, out, off);
4909
4910        return out;
4911    }
4912
4913    /**
4914     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
4915     */
4916    public Object getDataCopyXYZT(int c)
4917    {
4918        return getDataCopyXYZT(c, null, 0);
4919    }
4920
4921    /**
4922     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
4923     * If (out != null) then it's used to store result at the specified offset
4924     */
4925    public Object getDataCopyXYZT(int c, Object out, int off)
4926    {
4927        switch (getDataType_().getJavaType())
4928        {
4929            case BYTE:
4930                return getDataCopyXYZTAsByte(c, (byte[]) out, off);
4931            case SHORT:
4932                return getDataCopyXYZTAsShort(c, (short[]) out, off);
4933            case INT:
4934                return getDataCopyXYZTAsInt(c, (int[]) out, off);
4935            case FLOAT:
4936                return getDataCopyXYZTAsFloat(c, (float[]) out, off);
4937            case DOUBLE:
4938                return getDataCopyXYZTAsDouble(c, (double[]) out, off);
4939            default:
4940                return null;
4941        }
4942    }
4943
4944    /**
4945     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
4946     */
4947    public Object getDataCopyXYZ(int t, int c)
4948    {
4949        return getDataCopyXYZ(t, c, null, 0);
4950    }
4951
4952    /**
4953     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
4954     * If (out != null) then it's used to store result at the specified offset
4955     */
4956    public Object getDataCopyXYZ(int t, int c, Object out, int off)
4957    {
4958        switch (getDataType_().getJavaType())
4959        {
4960            case BYTE:
4961                return getDataCopyXYZAsByte(t, c, (byte[]) out, off);
4962            case SHORT:
4963                return getDataCopyXYZAsShort(t, c, (short[]) out, off);
4964            case INT:
4965                return getDataCopyXYZAsInt(t, c, (int[]) out, off);
4966            case FLOAT:
4967                return getDataCopyXYZAsFloat(t, c, (float[]) out, off);
4968            case DOUBLE:
4969                return getDataCopyXYZAsDouble(t, c, (double[]) out, off);
4970            default:
4971                return null;
4972        }
4973    }
4974
4975    /**
4976     * Returns a direct reference to 4D byte array data [T][Z][C][XY]
4977     */
4978    public byte[][][][] getDataXYCZTAsByte()
4979    {
4980        final int sizeT = getSizeT();
4981        final byte[][][][] result = new byte[sizeT][][][];
4982
4983        for (int t = 0; t < sizeT; t++)
4984            result[t] = getDataXYCZAsByte(t);
4985
4986        return result;
4987
4988    }
4989
4990    /**
4991     * Returns a direct reference to 4D byte array data [T][Z][C][XY]
4992     */
4993    public short[][][][] getDataXYCZTAsShort()
4994    {
4995        final int sizeT = getSizeT();
4996        final short[][][][] result = new short[sizeT][][][];
4997
4998        for (int t = 0; t < sizeT; t++)
4999            result[t] = getDataXYCZAsShort(t);
5000
5001        return result;
5002    }
5003
5004    /**
5005     * Returns a direct reference to 4D byte array data [T][Z][C][XY]
5006     */
5007    public int[][][][] getDataXYCZTAsInt()
5008    {
5009        final int sizeT = getSizeT();
5010        final int[][][][] result = new int[sizeT][][][];
5011
5012        for (int t = 0; t < sizeT; t++)
5013            result[t] = getDataXYCZAsInt(t);
5014
5015        return result;
5016    }
5017
5018    /**
5019     * Returns a direct reference to 4D byte array data [T][Z][C][XY]
5020     */
5021    public float[][][][] getDataXYCZTAsFloat()
5022    {
5023        final int sizeT = getSizeT();
5024        final float[][][][] result = new float[sizeT][][][];
5025
5026        for (int t = 0; t < sizeT; t++)
5027            result[t] = getDataXYCZAsFloat(t);
5028
5029        return result;
5030    }
5031
5032    /**
5033     * Returns a direct reference to 4D byte array data [T][Z][C][XY]
5034     */
5035    public double[][][][] getDataXYCZTAsDouble()
5036    {
5037        final int sizeT = getSizeT();
5038        final double[][][][] result = new double[sizeT][][][];
5039
5040        for (int t = 0; t < sizeT; t++)
5041            result[t] = getDataXYCZAsDouble(t);
5042
5043        return result;
5044    }
5045
5046    /**
5047     * Returns a direct reference to 3D byte array data [Z][C][XY] for specified t
5048     */
5049    public byte[][][] getDataXYCZAsByte(int t)
5050    {
5051        final int sizeZ = getSizeZ(t);
5052        final byte[][][] result = new byte[sizeZ][][];
5053
5054        for (int z = 0; z < sizeZ; z++)
5055            result[z] = getDataXYCAsByte(t, z);
5056
5057        return result;
5058    }
5059
5060    /**
5061     * Returns a direct reference to 3D byte array data [Z][C][XY] for specified t
5062     */
5063    public short[][][] getDataXYCZAsShort(int t)
5064    {
5065        final int sizeZ = getSizeZ(t);
5066        final short[][][] result = new short[sizeZ][][];
5067
5068        for (int z = 0; z < sizeZ; z++)
5069            result[z] = getDataXYCAsShort(t, z);
5070
5071        return result;
5072    }
5073
5074    /**
5075     * Returns a direct reference to 3D byte array data [Z][C][XY] for specified t
5076     */
5077    public int[][][] getDataXYCZAsInt(int t)
5078    {
5079        final int sizeZ = getSizeZ(t);
5080        final int[][][] result = new int[sizeZ][][];
5081
5082        for (int z = 0; z < sizeZ; z++)
5083            result[z] = getDataXYCAsInt(t, z);
5084
5085        return result;
5086    }
5087
5088    /**
5089     * Returns a direct reference to 3D byte array data [Z][C][XY] for specified t
5090     */
5091    public float[][][] getDataXYCZAsFloat(int t)
5092    {
5093        final int sizeZ = getSizeZ(t);
5094        final float[][][] result = new float[sizeZ][][];
5095
5096        for (int z = 0; z < sizeZ; z++)
5097            result[z] = getDataXYCAsFloat(t, z);
5098
5099        return result;
5100    }
5101
5102    /**
5103     * Returns a direct reference to 3D byte array data [Z][C][XY] for specified t
5104     */
5105    public double[][][] getDataXYCZAsDouble(int t)
5106    {
5107        final int sizeZ = getSizeZ(t);
5108        final double[][][] result = new double[sizeZ][][];
5109
5110        for (int z = 0; z < sizeZ; z++)
5111            result[z] = getDataXYCAsDouble(t, z);
5112
5113        return result;
5114    }
5115
5116    /**
5117     * Returns a direct reference to 2D byte array data [C][XY] for specified t, z
5118     */
5119    public byte[][] getDataXYCAsByte(int t, int z)
5120    {
5121        final IcyBufferedImage img = getImage(t, z);
5122
5123        if (img != null)
5124            return img.getDataXYCAsByte();
5125
5126        return null;
5127    }
5128
5129    /**
5130     * Returns a direct reference to 2D byte array data [C][XY] for specified t, z
5131     */
5132    public short[][] getDataXYCAsShort(int t, int z)
5133    {
5134        final IcyBufferedImage img = getImage(t, z);
5135
5136        if (img != null)
5137            return img.getDataXYCAsShort();
5138
5139        return null;
5140    }
5141
5142    /**
5143     * Returns a direct reference to 2D byte array data [C][XY] for specified t, z
5144     */
5145    public int[][] getDataXYCAsInt(int t, int z)
5146    {
5147        final IcyBufferedImage img = getImage(t, z);
5148
5149        if (img != null)
5150            return img.getDataXYCAsInt();
5151
5152        return null;
5153    }
5154
5155    /**
5156     * Returns a direct reference to 2D byte array data [C][XY] for specified t, z
5157     */
5158    public float[][] getDataXYCAsFloat(int t, int z)
5159    {
5160        final IcyBufferedImage img = getImage(t, z);
5161
5162        if (img != null)
5163            return img.getDataXYCAsFloat();
5164
5165        return null;
5166    }
5167
5168    /**
5169     * Returns a direct reference to 2D byte array data [C][XY] for specified t, z
5170     */
5171    public double[][] getDataXYCAsDouble(int t, int z)
5172    {
5173        final IcyBufferedImage img = getImage(t, z);
5174
5175        if (img != null)
5176            return img.getDataXYCAsDouble();
5177
5178        return null;
5179    }
5180
5181    /**
5182     * Returns a direct reference to 1D byte array data [XY] for specified t, z, c
5183     */
5184    public byte[] getDataXYAsByte(int t, int z, int c)
5185    {
5186        final IcyBufferedImage img = getImage(t, z);
5187
5188        if (img != null)
5189            return img.getDataXYAsByte(c);
5190
5191        return null;
5192    }
5193
5194    /**
5195     * Returns a direct reference to 1D byte array data [XY] for specified t, z, c
5196     */
5197    public short[] getDataXYAsShort(int t, int z, int c)
5198    {
5199        final IcyBufferedImage img = getImage(t, z);
5200
5201        if (img != null)
5202            return img.getDataXYAsShort(c);
5203
5204        return null;
5205    }
5206
5207    /**
5208     * Returns a direct reference to 1D byte array data [XY] for specified t, z, c
5209     */
5210    public int[] getDataXYAsInt(int t, int z, int c)
5211    {
5212        final IcyBufferedImage img = getImage(t, z);
5213
5214        if (img != null)
5215            return img.getDataXYAsInt(c);
5216
5217        return null;
5218    }
5219
5220    /**
5221     * Returns a direct reference to 1D byte array data [XY] for specified t, z, c
5222     */
5223    public float[] getDataXYAsFloat(int t, int z, int c)
5224    {
5225        final IcyBufferedImage img = getImage(t, z);
5226
5227        if (img != null)
5228            return img.getDataXYAsFloat(c);
5229
5230        return null;
5231    }
5232
5233    /**
5234     * Returns a direct reference to 1D byte array data [XY] for specified t, z, c
5235     */
5236    public double[] getDataXYAsDouble(int t, int z, int c)
5237    {
5238        final IcyBufferedImage img = getImage(t, z);
5239
5240        if (img != null)
5241            return img.getDataXYAsDouble(c);
5242
5243        return null;
5244    }
5245
5246    /**
5247     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
5248     */
5249    public byte[][][] getDataXYZTAsByte(int c)
5250    {
5251        final int sizeT = getSizeT();
5252        final byte[][][] result = new byte[sizeT][][];
5253
5254        for (int t = 0; t < sizeT; t++)
5255            result[t] = getDataXYZAsByte(t, c);
5256
5257        return result;
5258    }
5259
5260    /**
5261     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
5262     */
5263    public short[][][] getDataXYZTAsShort(int c)
5264    {
5265        final int sizeT = getSizeT();
5266        final short[][][] result = new short[sizeT][][];
5267
5268        for (int t = 0; t < sizeT; t++)
5269            result[t] = getDataXYZAsShort(t, c);
5270
5271        return result;
5272    }
5273
5274    /**
5275     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
5276     */
5277    public int[][][] getDataXYZTAsInt(int c)
5278    {
5279        final int sizeT = getSizeT();
5280        final int[][][] result = new int[sizeT][][];
5281
5282        for (int t = 0; t < sizeT; t++)
5283            result[t] = getDataXYZAsInt(t, c);
5284
5285        return result;
5286    }
5287
5288    /**
5289     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
5290     */
5291    public float[][][] getDataXYZTAsFloat(int c)
5292    {
5293        final int sizeT = getSizeT();
5294        final float[][][] result = new float[sizeT][][];
5295
5296        for (int t = 0; t < sizeT; t++)
5297            result[t] = getDataXYZAsFloat(t, c);
5298
5299        return result;
5300    }
5301
5302    /**
5303     * Returns a direct reference to 3D byte array data [T][Z][XY] for specified c
5304     */
5305    public double[][][] getDataXYZTAsDouble(int c)
5306    {
5307        final int sizeT = getSizeT();
5308        final double[][][] result = new double[sizeT][][];
5309
5310        for (int t = 0; t < sizeT; t++)
5311            result[t] = getDataXYZAsDouble(t, c);
5312
5313        return result;
5314    }
5315
5316    /**
5317     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
5318     */
5319    public byte[][] getDataXYZAsByte(int t, int c)
5320    {
5321        final int sizeZ = getSizeZ(t);
5322        final byte[][] result = new byte[sizeZ][];
5323
5324        for (int z = 0; z < sizeZ; z++)
5325            result[z] = getDataXYAsByte(t, z, c);
5326
5327        return result;
5328    }
5329
5330    /**
5331     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
5332     */
5333    public short[][] getDataXYZAsShort(int t, int c)
5334    {
5335        final int sizeZ = getSizeZ(t);
5336        final short[][] result = new short[sizeZ][];
5337
5338        for (int z = 0; z < sizeZ; z++)
5339            result[z] = getDataXYAsShort(t, z, c);
5340
5341        return result;
5342    }
5343
5344    /**
5345     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
5346     */
5347    public int[][] getDataXYZAsInt(int t, int c)
5348    {
5349        final int sizeZ = getSizeZ(t);
5350        final int[][] result = new int[sizeZ][];
5351
5352        for (int z = 0; z < sizeZ; z++)
5353            result[z] = getDataXYAsInt(t, z, c);
5354
5355        return result;
5356    }
5357
5358    /**
5359     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
5360     */
5361    public float[][] getDataXYZAsFloat(int t, int c)
5362    {
5363        final int sizeZ = getSizeZ(t);
5364        final float[][] result = new float[sizeZ][];
5365
5366        for (int z = 0; z < sizeZ; z++)
5367            result[z] = getDataXYAsFloat(t, z, c);
5368
5369        return result;
5370    }
5371
5372    /**
5373     * Returns a direct reference to 2D byte array data [Z][XY] for specified t, c
5374     */
5375    public double[][] getDataXYZAsDouble(int t, int c)
5376    {
5377        final int sizeZ = getSizeZ(t);
5378        final double[][] result = new double[sizeZ][];
5379
5380        for (int z = 0; z < sizeZ; z++)
5381            result[z] = getDataXYAsDouble(t, z, c);
5382
5383        return result;
5384    }
5385
5386    /**
5387     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
5388     */
5389    public byte[] getDataCopyXYCZTAsByte()
5390    {
5391        return getDataCopyXYCZTAsByte(null, 0);
5392    }
5393
5394    /**
5395     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
5396     * If (out != null) then it's used to store result at the specified offset
5397     */
5398    public byte[] getDataCopyXYCZTAsByte(byte[] out, int off)
5399    {
5400        final long sizeT = getSizeT();
5401        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5402        if ((len * sizeT) >= Integer.MAX_VALUE)
5403            throw new TooLargeArrayException();
5404
5405        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5406        int offset = off;
5407
5408        for (int t = 0; t < sizeT; t++)
5409        {
5410            getDataCopyXYCZAsByte(t, result, offset);
5411            offset += len;
5412        }
5413
5414        return result;
5415    }
5416
5417    /**
5418     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
5419     */
5420    public short[] getDataCopyXYCZTAsShort()
5421    {
5422        return getDataCopyXYCZTAsShort(null, 0);
5423    }
5424
5425    /**
5426     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
5427     * If (out != null) then it's used to store result at the specified offset
5428     */
5429    public short[] getDataCopyXYCZTAsShort(short[] out, int off)
5430    {
5431        final long sizeT = getSizeT();
5432        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5433        if ((len * sizeT) >= Integer.MAX_VALUE)
5434            throw new TooLargeArrayException();
5435
5436        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5437        int offset = off;
5438
5439        for (int t = 0; t < sizeT; t++)
5440        {
5441            getDataCopyXYCZAsShort(t, result, offset);
5442            offset += len;
5443        }
5444
5445        return result;
5446    }
5447
5448    /**
5449     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
5450     */
5451    public int[] getDataCopyXYCZTAsInt()
5452    {
5453        return getDataCopyXYCZTAsInt(null, 0);
5454    }
5455
5456    /**
5457     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
5458     * If (out != null) then it's used to store result at the specified offset
5459     */
5460    public int[] getDataCopyXYCZTAsInt(int[] out, int off)
5461    {
5462        final long sizeT = getSizeT();
5463        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5464        if ((len * sizeT) >= Integer.MAX_VALUE)
5465            throw new TooLargeArrayException();
5466
5467        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5468        int offset = off;
5469
5470        for (int t = 0; t < sizeT; t++)
5471        {
5472            getDataCopyXYCZAsInt(t, result, offset);
5473            offset += len;
5474        }
5475
5476        return result;
5477    }
5478
5479    /**
5480     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
5481     */
5482    public float[] getDataCopyXYCZTAsFloat()
5483    {
5484        return getDataCopyXYCZTAsFloat(null, 0);
5485    }
5486
5487    /**
5488     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
5489     * If (out != null) then it's used to store result at the specified offset
5490     */
5491    public float[] getDataCopyXYCZTAsFloat(float[] out, int off)
5492    {
5493        final long sizeT = getSizeT();
5494        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5495        if ((len * sizeT) >= Integer.MAX_VALUE)
5496            throw new TooLargeArrayException();
5497
5498        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5499        int offset = off;
5500
5501        for (int t = 0; t < sizeT; t++)
5502        {
5503            getDataCopyXYCZAsFloat(t, result, offset);
5504            offset += len;
5505        }
5506
5507        return result;
5508    }
5509
5510    /**
5511     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]
5512     */
5513    public double[] getDataCopyXYCZTAsDouble()
5514    {
5515        return getDataCopyXYCZTAsDouble(null, 0);
5516    }
5517
5518    /**
5519     * Returns a 1D array data copy [XYCZT] of internal 4D array data [T][Z][C][XY]<br>
5520     * If (out != null) then it's used to store result at the specified offset
5521     */
5522    public double[] getDataCopyXYCZTAsDouble(double[] out, int off)
5523    {
5524        final long sizeT = getSizeT();
5525        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5526        if ((len * sizeT) >= Integer.MAX_VALUE)
5527            throw new TooLargeArrayException();
5528
5529        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5530        int offset = off;
5531
5532        for (int t = 0; t < sizeT; t++)
5533        {
5534            getDataCopyXYCZAsDouble(t, result, offset);
5535            offset += len;
5536        }
5537
5538        return result;
5539    }
5540
5541    /**
5542     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
5543     */
5544    public byte[] getDataCopyXYCZAsByte(int t)
5545    {
5546        return getDataCopyXYCZAsByte(t, null, 0);
5547    }
5548
5549    /**
5550     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
5551     * If (out != null) then it's used to store result at the specified offset
5552     */
5553    public byte[] getDataCopyXYCZAsByte(int t, byte[] out, int off)
5554    {
5555        final long sizeZ = getSizeZ();
5556        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
5557        if ((len * sizeZ) >= Integer.MAX_VALUE)
5558            throw new TooLargeArrayException();
5559
5560        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
5561        int offset = off;
5562
5563        for (int z = 0; z < sizeZ; z++)
5564        {
5565            getDataCopyXYCAsByte(t, z, result, offset);
5566            offset += len;
5567        }
5568
5569        return result;
5570    }
5571
5572    /**
5573     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
5574     */
5575    public short[] getDataCopyXYCZAsShort(int t)
5576    {
5577        return getDataCopyXYCZAsShort(t, null, 0);
5578    }
5579
5580    /**
5581     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
5582     * If (out != null) then it's used to store result at the specified offset
5583     */
5584    public short[] getDataCopyXYCZAsShort(int t, short[] out, int off)
5585    {
5586        final long sizeZ = getSizeZ();
5587        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
5588        if ((len * sizeZ) >= Integer.MAX_VALUE)
5589            throw new TooLargeArrayException();
5590
5591        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
5592        int offset = off;
5593
5594        for (int z = 0; z < sizeZ; z++)
5595        {
5596            getDataCopyXYCAsShort(t, z, result, offset);
5597            offset += len;
5598        }
5599
5600        return result;
5601    }
5602
5603    /**
5604     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
5605     */
5606    public int[] getDataCopyXYCZAsInt(int t)
5607    {
5608        return getDataCopyXYCZAsInt(t, null, 0);
5609    }
5610
5611    /**
5612     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
5613     * If (out != null) then it's used to store result at the specified offset
5614     */
5615    public int[] getDataCopyXYCZAsInt(int t, int[] out, int off)
5616    {
5617        final long sizeZ = getSizeZ();
5618        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
5619        if ((len * sizeZ) >= Integer.MAX_VALUE)
5620            throw new TooLargeArrayException();
5621
5622        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
5623        int offset = off;
5624
5625        for (int z = 0; z < sizeZ; z++)
5626        {
5627            getDataCopyXYCAsInt(t, z, result, offset);
5628            offset += len;
5629        }
5630
5631        return result;
5632    }
5633
5634    /**
5635     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
5636     */
5637    public float[] getDataCopyXYCZAsFloat(int t)
5638    {
5639        return getDataCopyXYCZAsFloat(t, null, 0);
5640    }
5641
5642    /**
5643     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
5644     * If (out != null) then it's used to store result at the specified offset
5645     */
5646    public float[] getDataCopyXYCZAsFloat(int t, float[] out, int off)
5647    {
5648        final long sizeZ = getSizeZ();
5649        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
5650        if ((len * sizeZ) >= Integer.MAX_VALUE)
5651            throw new TooLargeArrayException();
5652
5653        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
5654        int offset = off;
5655
5656        for (int z = 0; z < sizeZ; z++)
5657        {
5658            getDataCopyXYCAsFloat(t, z, result, offset);
5659            offset += len;
5660        }
5661
5662        return result;
5663    }
5664
5665    /**
5666     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t
5667     */
5668    public double[] getDataCopyXYCZAsDouble(int t)
5669    {
5670        return getDataCopyXYCZAsDouble(t, null, 0);
5671    }
5672
5673    /**
5674     * Returns a 1D array data copy [XYCZ] of internal 3D array data [Z][C][XY] for specified t<br>
5675     * If (out != null) then it's used to store result at the specified offset
5676     */
5677    public double[] getDataCopyXYCZAsDouble(int t, double[] out, int off)
5678    {
5679        final long sizeZ = getSizeZ();
5680        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
5681        if ((len * sizeZ) >= Integer.MAX_VALUE)
5682            throw new TooLargeArrayException();
5683
5684        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
5685        int offset = off;
5686
5687        for (int z = 0; z < sizeZ; z++)
5688        {
5689            getDataCopyXYCAsDouble(t, z, result, offset);
5690            offset += len;
5691        }
5692
5693        return result;
5694    }
5695
5696    /**
5697     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
5698     */
5699    public byte[] getDataCopyXYCAsByte(int t, int z)
5700    {
5701        return getDataCopyXYCAsByte(t, z, null, 0);
5702    }
5703
5704    /**
5705     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
5706     * If (out != null) then it's used to store result at the specified offset
5707     */
5708    public byte[] getDataCopyXYCAsByte(int t, int z, byte[] out, int off)
5709    {
5710        final IcyBufferedImage img = getImage(t, z);
5711
5712        if (img != null)
5713            return img.getDataCopyXYCAsByte(out, off);
5714
5715        return out;
5716    }
5717
5718    /**
5719     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
5720     */
5721    public short[] getDataCopyXYCAsShort(int t, int z)
5722    {
5723        return getDataCopyXYCAsShort(t, z, null, 0);
5724    }
5725
5726    /**
5727     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
5728     * If (out != null) then it's used to store result at the specified offset
5729     */
5730    public short[] getDataCopyXYCAsShort(int t, int z, short[] out, int off)
5731    {
5732        final IcyBufferedImage img = getImage(t, z);
5733
5734        if (img != null)
5735            return img.getDataCopyXYCAsShort(out, off);
5736
5737        return out;
5738    }
5739
5740    /**
5741     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
5742     */
5743    public int[] getDataCopyXYCAsInt(int t, int z)
5744    {
5745        return getDataCopyXYCAsInt(t, z, null, 0);
5746    }
5747
5748    /**
5749     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
5750     * If (out != null) then it's used to store result at the specified offset
5751     */
5752    public int[] getDataCopyXYCAsInt(int t, int z, int[] out, int off)
5753    {
5754        final IcyBufferedImage img = getImage(t, z);
5755
5756        if (img != null)
5757            return img.getDataCopyXYCAsInt(out, off);
5758
5759        return out;
5760    }
5761
5762    /**
5763     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
5764     */
5765    public float[] getDataCopyXYCAsFloat(int t, int z)
5766    {
5767        return getDataCopyXYCAsFloat(t, z, null, 0);
5768    }
5769
5770    /**
5771     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
5772     * If (out != null) then it's used to store result at the specified offset
5773     */
5774    public float[] getDataCopyXYCAsFloat(int t, int z, float[] out, int off)
5775    {
5776        final IcyBufferedImage img = getImage(t, z);
5777
5778        if (img != null)
5779            return img.getDataCopyXYCAsFloat(out, off);
5780
5781        return out;
5782    }
5783
5784    /**
5785     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z
5786     */
5787    public double[] getDataCopyXYCAsDouble(int t, int z)
5788    {
5789        return getDataCopyXYCAsDouble(t, z, null, 0);
5790    }
5791
5792    /**
5793     * Returns a 1D array data copy [XYC] of internal 2D array data [C][XY] for specified t, z<br>
5794     * If (out != null) then it's used to store result at the specified offset
5795     */
5796    public double[] getDataCopyXYCAsDouble(int t, int z, double[] out, int off)
5797    {
5798        final IcyBufferedImage img = getImage(t, z);
5799
5800        if (img != null)
5801            return img.getDataCopyXYCAsDouble(out, off);
5802
5803        return out;
5804    }
5805
5806    /**
5807     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
5808     */
5809    public byte[] getDataCopyXYAsByte(int t, int z, int c)
5810    {
5811        return getDataCopyXYAsByte(t, z, c, null, 0);
5812    }
5813
5814    /**
5815     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
5816     * If (out != null) then it's used to store result at the specified offset
5817     */
5818    public byte[] getDataCopyXYAsByte(int t, int z, int c, byte[] out, int off)
5819    {
5820        final IcyBufferedImage img = getImage(t, z);
5821
5822        if (img != null)
5823            return img.getDataCopyXYAsByte(c, out, off);
5824
5825        return out;
5826    }
5827
5828    /**
5829     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
5830     */
5831    public short[] getDataCopyXYAsShort(int t, int z, int c)
5832    {
5833        return getDataCopyXYAsShort(t, z, c, null, 0);
5834    }
5835
5836    /**
5837     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
5838     * If (out != null) then it's used to store result at the specified offset
5839     */
5840    public short[] getDataCopyXYAsShort(int t, int z, int c, short[] out, int off)
5841    {
5842        final IcyBufferedImage img = getImage(t, z);
5843
5844        if (img != null)
5845            return img.getDataCopyXYAsShort(c, out, off);
5846
5847        return out;
5848    }
5849
5850    /**
5851     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
5852     */
5853    public int[] getDataCopyXYAsInt(int t, int z, int c)
5854    {
5855        return getDataCopyXYAsInt(t, z, c, null, 0);
5856    }
5857
5858    /**
5859     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
5860     * If (out != null) then it's used to store result at the specified offset
5861     */
5862    public int[] getDataCopyXYAsInt(int t, int z, int c, int[] out, int off)
5863    {
5864        final IcyBufferedImage img = getImage(t, z);
5865
5866        if (img != null)
5867            return img.getDataCopyXYAsInt(c, out, off);
5868
5869        return out;
5870    }
5871
5872    /**
5873     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
5874     */
5875    public float[] getDataCopyXYAsFloat(int t, int z, int c)
5876    {
5877        return getDataCopyXYAsFloat(t, z, c, null, 0);
5878    }
5879
5880    /**
5881     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
5882     * If (out != null) then it's used to store result at the specified offset
5883     */
5884    public float[] getDataCopyXYAsFloat(int t, int z, int c, float[] out, int off)
5885    {
5886        final IcyBufferedImage img = getImage(t, z);
5887
5888        if (img != null)
5889            return img.getDataCopyXYAsFloat(c, out, off);
5890
5891        return out;
5892    }
5893
5894    /**
5895     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c
5896     */
5897    public double[] getDataCopyXYAsDouble(int t, int z, int c)
5898    {
5899        return getDataCopyXYAsDouble(t, z, c, null, 0);
5900    }
5901
5902    /**
5903     * Returns a 1D array data copy [XY] of internal 1D array data [XY] for specified t, z, c<br>
5904     * If (out != null) then it's used to store result at the specified offset
5905     */
5906    public double[] getDataCopyXYAsDouble(int t, int z, int c, double[] out, int off)
5907    {
5908        final IcyBufferedImage img = getImage(t, z);
5909
5910        if (img != null)
5911            return img.getDataCopyXYAsDouble(c, out, off);
5912
5913        return out;
5914    }
5915
5916    /**
5917     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
5918     */
5919    public byte[] getDataCopyCXYZTAsByte()
5920    {
5921        return getDataCopyCXYZTAsByte(null, 0);
5922    }
5923
5924    /**
5925     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
5926     * If (out != null) then it's used to store result at the specified offset
5927     */
5928    public byte[] getDataCopyCXYZTAsByte(byte[] out, int off)
5929    {
5930        final long sizeT = getSizeT();
5931        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5932        if ((len * sizeT) >= Integer.MAX_VALUE)
5933            throw new TooLargeArrayException();
5934
5935        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5936        int offset = off;
5937
5938        for (int t = 0; t < sizeT; t++)
5939        {
5940            getDataCopyCXYZAsByte(t, result, offset);
5941            offset += len;
5942        }
5943
5944        return result;
5945    }
5946
5947    /**
5948     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
5949     */
5950    public short[] getDataCopyCXYZTAsShort()
5951    {
5952        return getDataCopyCXYZTAsShort(null, 0);
5953    }
5954
5955    /**
5956     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
5957     * If (out != null) then it's used to store result at the specified offset
5958     */
5959    public short[] getDataCopyCXYZTAsShort(short[] out, int off)
5960    {
5961        final long sizeT = getSizeT();
5962        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5963        if ((len * sizeT) >= Integer.MAX_VALUE)
5964            throw new TooLargeArrayException();
5965
5966        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5967        int offset = off;
5968
5969        for (int t = 0; t < sizeT; t++)
5970        {
5971            getDataCopyCXYZAsShort(t, result, offset);
5972            offset += len;
5973        }
5974
5975        return result;
5976    }
5977
5978    /**
5979     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
5980     */
5981    public int[] getDataCopyCXYZTAsInt()
5982    {
5983        return getDataCopyCXYZTAsInt(null, 0);
5984    }
5985
5986    /**
5987     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
5988     * If (out != null) then it's used to store result at the specified offset
5989     */
5990    public int[] getDataCopyCXYZTAsInt(int[] out, int off)
5991    {
5992        final long sizeT = getSizeT();
5993        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
5994        if ((len * sizeT) >= Integer.MAX_VALUE)
5995            throw new TooLargeArrayException();
5996
5997        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
5998        int offset = off;
5999
6000        for (int t = 0; t < sizeT; t++)
6001        {
6002            getDataCopyCXYZAsInt(t, result, offset);
6003            offset += len;
6004        }
6005
6006        return result;
6007    }
6008
6009    /**
6010     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
6011     */
6012    public float[] getDataCopyCXYZTAsFloat()
6013    {
6014        return getDataCopyCXYZTAsFloat(null, 0);
6015    }
6016
6017    /**
6018     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
6019     * If (out != null) then it's used to store result at the specified offset
6020     */
6021    public float[] getDataCopyCXYZTAsFloat(float[] out, int off)
6022    {
6023        final long sizeT = getSizeT();
6024        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
6025        if ((len * sizeT) >= Integer.MAX_VALUE)
6026            throw new TooLargeArrayException();
6027
6028        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6029        int offset = off;
6030
6031        for (int t = 0; t < sizeT; t++)
6032        {
6033            getDataCopyCXYZAsFloat(t, result, offset);
6034            offset += len;
6035        }
6036
6037        return result;
6038    }
6039
6040    /**
6041     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]
6042     */
6043    public double[] getDataCopyCXYZTAsDouble()
6044    {
6045        return getDataCopyCXYZTAsDouble(null, 0);
6046    }
6047
6048    /**
6049     * Returns a 1D array data copy [CXYZT] of internal 4D array data [T][Z][C][XY]<br>
6050     * If (out != null) then it's used to store result at the specified offset
6051     */
6052    public double[] getDataCopyCXYZTAsDouble(double[] out, int off)
6053    {
6054        final long sizeT = getSizeT();
6055        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC() * (long) getSizeZ();
6056        if ((len * sizeT) >= Integer.MAX_VALUE)
6057            throw new TooLargeArrayException();
6058
6059        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6060        int offset = off;
6061
6062        for (int t = 0; t < sizeT; t++)
6063        {
6064            getDataCopyCXYZAsDouble(t, result, offset);
6065            offset += len;
6066        }
6067
6068        return result;
6069    }
6070
6071    /**
6072     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
6073     */
6074    public byte[] getDataCopyCXYZAsByte(int t)
6075    {
6076        return getDataCopyCXYZAsByte(t, null, 0);
6077    }
6078
6079    /**
6080     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
6081     * If (out != null) then it's used to store result at the specified offset
6082     */
6083    public byte[] getDataCopyCXYZAsByte(int t, byte[] out, int off)
6084    {
6085        final long sizeZ = getSizeZ();
6086        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
6087        if ((len * sizeZ) >= Integer.MAX_VALUE)
6088            throw new TooLargeArrayException();
6089
6090        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6091        int offset = off;
6092
6093        for (int z = 0; z < sizeZ; z++)
6094        {
6095            getDataCopyCXYAsByte(t, z, result, offset);
6096            offset += len;
6097        }
6098
6099        return result;
6100    }
6101
6102    /**
6103     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
6104     */
6105    public short[] getDataCopyCXYZAsShort(int t)
6106    {
6107        return getDataCopyCXYZAsShort(t, null, 0);
6108    }
6109
6110    /**
6111     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
6112     * If (out != null) then it's used to store result at the specified offset
6113     */
6114    public short[] getDataCopyCXYZAsShort(int t, short[] out, int off)
6115    {
6116        final long sizeZ = getSizeZ();
6117        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
6118        if ((len * sizeZ) >= Integer.MAX_VALUE)
6119            throw new TooLargeArrayException();
6120
6121        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6122        int offset = off;
6123
6124        for (int z = 0; z < sizeZ; z++)
6125        {
6126            getDataCopyCXYAsShort(t, z, result, offset);
6127            offset += len;
6128        }
6129
6130        return result;
6131    }
6132
6133    /**
6134     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
6135     */
6136    public int[] getDataCopyCXYZAsInt(int t)
6137    {
6138        return getDataCopyCXYZAsInt(t, null, 0);
6139    }
6140
6141    /**
6142     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
6143     * If (out != null) then it's used to store result at the specified offset
6144     */
6145    public int[] getDataCopyCXYZAsInt(int t, int[] out, int off)
6146    {
6147        final long sizeZ = getSizeZ();
6148        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
6149        if ((len * sizeZ) >= Integer.MAX_VALUE)
6150            throw new TooLargeArrayException();
6151
6152        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6153        int offset = off;
6154
6155        for (int z = 0; z < sizeZ; z++)
6156        {
6157            getDataCopyCXYAsInt(t, z, result, offset);
6158            offset += len;
6159        }
6160
6161        return result;
6162    }
6163
6164    /**
6165     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
6166     */
6167    public float[] getDataCopyCXYZAsFloat(int t)
6168    {
6169        return getDataCopyCXYZAsFloat(t, null, 0);
6170    }
6171
6172    /**
6173     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
6174     * If (out != null) then it's used to store result at the specified offset
6175     */
6176    public float[] getDataCopyCXYZAsFloat(int t, float[] out, int off)
6177    {
6178        final long sizeZ = getSizeZ();
6179        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
6180        if ((len * sizeZ) >= Integer.MAX_VALUE)
6181            throw new TooLargeArrayException();
6182
6183        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6184        int offset = off;
6185
6186        for (int z = 0; z < sizeZ; z++)
6187        {
6188            getDataCopyCXYAsFloat(t, z, result, offset);
6189            offset += len;
6190        }
6191
6192        return result;
6193    }
6194
6195    /**
6196     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t
6197     */
6198    public double[] getDataCopyCXYZAsDouble(int t)
6199    {
6200        return getDataCopyCXYZAsDouble(t, null, 0);
6201    }
6202
6203    /**
6204     * Returns a 1D array data copy [CXYZ] of internal 3D array data [Z][C][XY] for specified t<br>
6205     * If (out != null) then it's used to store result at the specified offset
6206     */
6207    public double[] getDataCopyCXYZAsDouble(int t, double[] out, int off)
6208    {
6209        final long sizeZ = getSizeZ();
6210        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeC();
6211        if ((len * sizeZ) >= Integer.MAX_VALUE)
6212            throw new TooLargeArrayException();
6213
6214        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6215        int offset = off;
6216
6217        for (int z = 0; z < sizeZ; z++)
6218        {
6219            getDataCopyCXYAsDouble(t, z, result, offset);
6220            offset += len;
6221        }
6222
6223        return result;
6224    }
6225
6226    /**
6227     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
6228     */
6229    public byte[] getDataCopyCXYAsByte(int t, int z)
6230    {
6231        return getDataCopyCXYAsByte(t, z, null, 0);
6232    }
6233
6234    /**
6235     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
6236     * If (out != null) then it's used to store result at the specified offset
6237     */
6238    public byte[] getDataCopyCXYAsByte(int t, int z, byte[] out, int off)
6239    {
6240        final IcyBufferedImage img = getImage(t, z);
6241
6242        if (img != null)
6243            return img.getDataCopyCXYAsByte(out, off);
6244
6245        return out;
6246    }
6247
6248    /**
6249     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
6250     */
6251    public short[] getDataCopyCXYAsShort(int t, int z)
6252    {
6253        return getDataCopyCXYAsShort(t, z, null, 0);
6254    }
6255
6256    /**
6257     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
6258     * If (out != null) then it's used to store result at the specified offset
6259     */
6260    public short[] getDataCopyCXYAsShort(int t, int z, short[] out, int off)
6261    {
6262        final IcyBufferedImage img = getImage(t, z);
6263
6264        if (img != null)
6265            return img.getDataCopyCXYAsShort(out, off);
6266
6267        return out;
6268    }
6269
6270    /**
6271     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
6272     */
6273    public int[] getDataCopyCXYAsInt(int t, int z)
6274    {
6275        return getDataCopyCXYAsInt(t, z, null, 0);
6276    }
6277
6278    /**
6279     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
6280     * If (out != null) then it's used to store result at the specified offset
6281     */
6282    public int[] getDataCopyCXYAsInt(int t, int z, int[] out, int off)
6283    {
6284        final IcyBufferedImage img = getImage(t, z);
6285
6286        if (img != null)
6287            return img.getDataCopyCXYAsInt(out, off);
6288
6289        return out;
6290    }
6291
6292    /**
6293     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
6294     */
6295    public float[] getDataCopyCXYAsFloat(int t, int z)
6296    {
6297        return getDataCopyCXYAsFloat(t, z, null, 0);
6298    }
6299
6300    /**
6301     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
6302     * If (out != null) then it's used to store result at the specified offset
6303     */
6304    public float[] getDataCopyCXYAsFloat(int t, int z, float[] out, int off)
6305    {
6306        final IcyBufferedImage img = getImage(t, z);
6307
6308        if (img != null)
6309            return img.getDataCopyCXYAsFloat(out, off);
6310
6311        return out;
6312    }
6313
6314    /**
6315     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z
6316     */
6317    public double[] getDataCopyCXYAsDouble(int t, int z)
6318    {
6319        return getDataCopyCXYAsDouble(t, z, null, 0);
6320    }
6321
6322    /**
6323     * Returns a 1D array data copy [CXY] of internal 2D array data [C][XY] for specified t, z<br>
6324     * If (out != null) then it's used to store result at the specified offset
6325     */
6326    public double[] getDataCopyCXYAsDouble(int t, int z, double[] out, int off)
6327    {
6328        final IcyBufferedImage img = getImage(t, z);
6329
6330        if (img != null)
6331            return img.getDataCopyCXYAsDouble(out, off);
6332
6333        return out;
6334    }
6335
6336    /**
6337     * Returns a 1D array data copy [C] of specified t, z, x, y
6338     */
6339    public byte[] getDataCopyCAsByte(int t, int z, int x, int y)
6340    {
6341        return getDataCopyCAsByte(t, z, x, y, null, 0);
6342    }
6343
6344    /**
6345     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
6346     * If (out != null) then it's used to store result at the specified offset
6347     */
6348    public byte[] getDataCopyCAsByte(int t, int z, int x, int y, byte[] out, int off)
6349    {
6350        final IcyBufferedImage img = getImage(t, z);
6351
6352        if (img != null)
6353            return img.getDataCopyCAsByte(x, y, out, off);
6354
6355        return out;
6356    }
6357
6358    /**
6359     * Returns a 1D array data copy [C] of specified t, z, x, y
6360     */
6361    public short[] getDataCopyCAsShort(int t, int z, int x, int y)
6362    {
6363        return getDataCopyCAsShort(t, z, x, y, null, 0);
6364    }
6365
6366    /**
6367     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
6368     * If (out != null) then it's used to store result at the specified offset
6369     */
6370    public short[] getDataCopyCAsShort(int t, int z, int x, int y, short[] out, int off)
6371    {
6372        final IcyBufferedImage img = getImage(t, z);
6373
6374        if (img != null)
6375            return img.getDataCopyCAsShort(x, y, out, off);
6376
6377        return out;
6378    }
6379
6380    /**
6381     * Returns a 1D array data copy [C] of specified t, z, x, y
6382     */
6383    public int[] getDataCopyCAsInt(int t, int z, int x, int y)
6384    {
6385        return getDataCopyCAsInt(t, z, x, y, null, 0);
6386    }
6387
6388    /**
6389     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
6390     * If (out != null) then it's used to store result at the specified offset
6391     */
6392    public int[] getDataCopyCAsInt(int t, int z, int x, int y, int[] out, int off)
6393    {
6394        final IcyBufferedImage img = getImage(t, z);
6395
6396        if (img != null)
6397            return img.getDataCopyCAsInt(x, y, out, off);
6398
6399        return out;
6400    }
6401
6402    /**
6403     * Returns a 1D array data copy [C] of specified t, z, x, y
6404     */
6405    public float[] getDataCopyCAsFloat(int t, int z, int x, int y)
6406    {
6407        return getDataCopyCAsFloat(t, z, x, y, null, 0);
6408    }
6409
6410    /**
6411     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
6412     * If (out != null) then it's used to store result at the specified offset
6413     */
6414    public float[] getDataCopyCAsFloat(int t, int z, int x, int y, float[] out, int off)
6415    {
6416        final IcyBufferedImage img = getImage(t, z);
6417
6418        if (img != null)
6419            return img.getDataCopyCAsFloat(x, y, out, off);
6420
6421        return out;
6422    }
6423
6424    /**
6425     * Returns a 1D array data copy [C] of specified t, z, x, y
6426     */
6427    public double[] getDataCopyCAsDouble(int t, int z, int x, int y)
6428    {
6429        return getDataCopyCAsDouble(t, z, x, y, null, 0);
6430    }
6431
6432    /**
6433     * Returns a 1D array data copy [C] of specified t, z, x, y<br>
6434     * If (out != null) then it's used to store result at the specified offset
6435     */
6436    public double[] getDataCopyCAsDouble(int t, int z, int x, int y, double[] out, int off)
6437    {
6438        final IcyBufferedImage img = getImage(t, z);
6439
6440        if (img != null)
6441            return img.getDataCopyCAsDouble(x, y, out, off);
6442
6443        return out;
6444    }
6445
6446    /**
6447     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
6448     */
6449    public byte[] getDataCopyXYZTAsByte(int c)
6450    {
6451        return getDataCopyXYZTAsByte(c, null, 0);
6452    }
6453
6454    /**
6455     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
6456     * If (out != null) then it's used to store result at the specified offset
6457     */
6458    public byte[] getDataCopyXYZTAsByte(int c, byte[] out, int off)
6459    {
6460        final long sizeT = getSizeT();
6461        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeZ();
6462        if ((len * sizeT) >= Integer.MAX_VALUE)
6463            throw new TooLargeArrayException();
6464
6465        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6466        int offset = off;
6467
6468        for (int t = 0; t < sizeT; t++)
6469        {
6470            getDataCopyXYZAsByte(t, c, result, offset);
6471            offset += len;
6472        }
6473
6474        return result;
6475    }
6476
6477    /**
6478     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
6479     */
6480    public short[] getDataCopyXYZTAsShort(int c)
6481    {
6482        return getDataCopyXYZTAsShort(c, null, 0);
6483    }
6484
6485    /**
6486     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
6487     * If (out != null) then it's used to store result at the specified offset
6488     */
6489    public short[] getDataCopyXYZTAsShort(int c, short[] out, int off)
6490    {
6491        final long sizeT = getSizeT();
6492        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeZ();
6493        if ((len * sizeT) >= Integer.MAX_VALUE)
6494            throw new TooLargeArrayException();
6495
6496        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6497        int offset = off;
6498
6499        for (int t = 0; t < sizeT; t++)
6500        {
6501            getDataCopyXYZAsShort(t, c, result, offset);
6502            offset += len;
6503        }
6504
6505        return result;
6506    }
6507
6508    /**
6509     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
6510     */
6511    public int[] getDataCopyXYZTAsInt(int c)
6512    {
6513        return getDataCopyXYZTAsInt(c, null, 0);
6514    }
6515
6516    /**
6517     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
6518     * If (out != null) then it's used to store result at the specified offset
6519     */
6520    public int[] getDataCopyXYZTAsInt(int c, int[] out, int off)
6521    {
6522        final long sizeT = getSizeT();
6523        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeZ();
6524        if ((len * sizeT) >= Integer.MAX_VALUE)
6525            throw new TooLargeArrayException();
6526
6527        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6528        int offset = off;
6529
6530        for (int t = 0; t < sizeT; t++)
6531        {
6532            getDataCopyXYZAsInt(t, c, result, offset);
6533            offset += len;
6534        }
6535
6536        return result;
6537    }
6538
6539    /**
6540     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
6541     */
6542    public float[] getDataCopyXYZTAsFloat(int c)
6543    {
6544        return getDataCopyXYZTAsFloat(c, null, 0);
6545    }
6546
6547    /**
6548     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
6549     * If (out != null) then it's used to store result at the specified offset
6550     */
6551    public float[] getDataCopyXYZTAsFloat(int c, float[] out, int off)
6552    {
6553        final long sizeT = getSizeT();
6554        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeZ();
6555        if ((len * sizeT) >= Integer.MAX_VALUE)
6556            throw new TooLargeArrayException();
6557
6558        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6559        int offset = off;
6560
6561        for (int t = 0; t < sizeT; t++)
6562        {
6563            getDataCopyXYZAsFloat(t, c, result, offset);
6564            offset += len;
6565        }
6566
6567        return result;
6568    }
6569
6570    /**
6571     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c
6572     */
6573    public double[] getDataCopyXYZTAsDouble(int c)
6574    {
6575        return getDataCopyXYZTAsDouble(c, null, 0);
6576    }
6577
6578    /**
6579     * Returns a 1D array data copy [XYZT] of internal 3D array data [T][Z][XY] for specified c<br>
6580     * If (out != null) then it's used to store result at the specified offset
6581     */
6582    public double[] getDataCopyXYZTAsDouble(int c, double[] out, int off)
6583    {
6584        final long sizeT = getSizeT();
6585        final long len = (long) getSizeX() * (long) getSizeY() * (long) getSizeZ();
6586        if ((len * sizeT) >= Integer.MAX_VALUE)
6587            throw new TooLargeArrayException();
6588
6589        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeT));
6590        int offset = off;
6591
6592        for (int t = 0; t < sizeT; t++)
6593        {
6594            getDataCopyXYZAsDouble(t, c, result, offset);
6595            offset += len;
6596        }
6597
6598        return result;
6599    }
6600
6601    /**
6602     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
6603     */
6604    public byte[] getDataCopyXYZAsByte(int t, int c)
6605    {
6606        return getDataCopyXYZAsByte(t, c, null, 0);
6607    }
6608
6609    /**
6610     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
6611     * If (out != null) then it's used to store result at the specified offset
6612     */
6613    public byte[] getDataCopyXYZAsByte(int t, int c, byte[] out, int off)
6614    {
6615        final long sizeZ = getSizeZ();
6616        final long len = (long) getSizeX() * (long) getSizeY();
6617        if ((len * sizeZ) >= Integer.MAX_VALUE)
6618            throw new TooLargeArrayException();
6619
6620        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6621        int offset = off;
6622
6623        for (int z = 0; z < sizeZ; z++)
6624        {
6625            getDataCopyXYAsByte(t, z, c, result, offset);
6626            offset += len;
6627        }
6628
6629        return result;
6630    }
6631
6632    /**
6633     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
6634     */
6635    public short[] getDataCopyXYZAsShort(int t, int c)
6636    {
6637        return getDataCopyXYZAsShort(t, c, null, 0);
6638    }
6639
6640    /**
6641     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
6642     * If (out != null) then it's used to store result at the specified offset
6643     */
6644    public short[] getDataCopyXYZAsShort(int t, int c, short[] out, int off)
6645    {
6646        final long sizeZ = getSizeZ();
6647        final long len = (long) getSizeX() * (long) getSizeY();
6648        if ((len * sizeZ) >= Integer.MAX_VALUE)
6649            throw new TooLargeArrayException();
6650
6651        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6652        int offset = off;
6653
6654        for (int z = 0; z < sizeZ; z++)
6655        {
6656            getDataCopyXYAsShort(t, z, c, result, offset);
6657            offset += len;
6658        }
6659
6660        return result;
6661    }
6662
6663    /**
6664     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
6665     */
6666    public int[] getDataCopyXYZAsInt(int t, int c)
6667    {
6668        return getDataCopyXYZAsInt(t, c, null, 0);
6669    }
6670
6671    /**
6672     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
6673     * If (out != null) then it's used to store result at the specified offset
6674     */
6675    public int[] getDataCopyXYZAsInt(int t, int c, int[] out, int off)
6676    {
6677        final long sizeZ = getSizeZ();
6678        final long len = (long) getSizeX() * (long) getSizeY();
6679        if ((len * sizeZ) >= Integer.MAX_VALUE)
6680            throw new TooLargeArrayException();
6681
6682        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6683        int offset = off;
6684
6685        for (int z = 0; z < sizeZ; z++)
6686        {
6687            getDataCopyXYAsInt(t, z, c, result, offset);
6688            offset += len;
6689        }
6690
6691        return result;
6692    }
6693
6694    /**
6695     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
6696     */
6697    public float[] getDataCopyXYZAsFloat(int t, int c)
6698    {
6699        return getDataCopyXYZAsFloat(t, c, null, 0);
6700    }
6701
6702    /**
6703     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
6704     * If (out != null) then it's used to store result at the specified offset
6705     */
6706    public float[] getDataCopyXYZAsFloat(int t, int c, float[] out, int off)
6707    {
6708        final long sizeZ = getSizeZ();
6709        final long len = (long) getSizeX() * (long) getSizeY();
6710        if ((len * sizeZ) >= Integer.MAX_VALUE)
6711            throw new TooLargeArrayException();
6712
6713        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6714        int offset = off;
6715
6716        for (int z = 0; z < sizeZ; z++)
6717        {
6718            getDataCopyXYAsFloat(t, z, c, result, offset);
6719            offset += len;
6720        }
6721
6722        return result;
6723    }
6724
6725    /**
6726     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c
6727     */
6728    public double[] getDataCopyXYZAsDouble(int t, int c)
6729    {
6730        return getDataCopyXYZAsDouble(t, c, null, 0);
6731    }
6732
6733    /**
6734     * Returns a 1D array data copy [XYZ] of internal 2D array data [Z][XY] for specified t, c<br>
6735     * If (out != null) then it's used to store result at the specified offset
6736     */
6737    public double[] getDataCopyXYZAsDouble(int t, int c, double[] out, int off)
6738    {
6739        final long sizeZ = getSizeZ();
6740        final long len = (long) getSizeX() * (long) getSizeY();
6741        if ((len * sizeZ) >= Integer.MAX_VALUE)
6742            throw new TooLargeArrayException();
6743
6744        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeZ));
6745        int offset = off;
6746
6747        for (int z = 0; z < sizeZ; z++)
6748        {
6749            getDataCopyXYAsDouble(t, z, c, result, offset);
6750            offset += len;
6751        }
6752
6753        return result;
6754    }
6755
6756    /**
6757     * Sets 1D array data [XY] for specified t, z, c
6758     */
6759    public void setDataXY(int t, int z, int c, Object value)
6760    {
6761        final IcyBufferedImage img = getImage(t, z);
6762
6763        if (img != null)
6764            img.setDataXY(c, value);
6765    }
6766
6767    /**
6768     * @deprecated Uses {@link SequenceUtil#getSubSequence(Sequence, int, int, int, int, int, int, int, int)} instead.
6769     */
6770    @Deprecated
6771    public Sequence getSubSequence(int startX, int startY, int startZ, int startT, int sizeX, int sizeY, int sizeZ,
6772            int sizeT)
6773    {
6774        return SequenceUtil.getSubSequence(this, startX, startY, startZ, startT, sizeX, sizeY, sizeZ, sizeT);
6775    }
6776
6777    /**
6778     * @deprecated Use {@link SequenceUtil#getCopy(Sequence)} instead.
6779     */
6780    @Deprecated
6781    public Sequence getCopy()
6782    {
6783        return SequenceUtil.getCopy(this);
6784    }
6785
6786    /**
6787     * Set all viewer containing this sequence to time t.
6788     * 
6789     * @deprecated Use this piece of code instead :<br>
6790     *             <code>for(Viewer v: Icy.getMainInterface().getViewers(sequence))</code></br>
6791     *             <code>   v.setT(...)</code>
6792     */
6793    @Deprecated
6794    public void setT(int t)
6795    {
6796        for (Viewer viewer : Icy.getMainInterface().getViewers())
6797            if (viewer.getSequence() == this)
6798                viewer.setT(t);
6799    }
6800
6801    /**
6802     * Load XML persistent data from file.<br>
6803     * This method should only be called once when the sequence has just be loaded from file.<br>
6804     * Note that it uses {@link #getFilename()} to define the XML filename so be sure that it is correctly filled before
6805     * calling this method.
6806     * 
6807     * @return <code>true</code> if XML data has been correctly loaded, <code>false</code> otherwise.
6808     */
6809    public boolean loadXMLData()
6810    {
6811        return persistent.loadXMLData();
6812    }
6813
6814    /**
6815     * Synchronize XML data with sequence data :<br>
6816     * This function refresh all the meta data and ROIs of the sequence and put it in the current
6817     * XML document.
6818     */
6819    public void refreshXMLData()
6820    {
6821        persistent.refreshXMLData();
6822    }
6823
6824    /**
6825     * Save attached XML data.
6826     */
6827    public boolean saveXMLData()
6828    {
6829        Exception exc = null;
6830        int retry = 0;
6831
6832        // definitely ugly but the XML parser may throw some exception in multi thread environnement
6833        // and we really don't want to lost the sequence metadata !
6834        while (retry < 5)
6835        {
6836            try
6837            {
6838                return persistent.saveXMLData();
6839            }
6840            catch (Exception e)
6841            {
6842                exc = e;
6843            }
6844
6845            retry++;
6846        }
6847
6848        System.err.println("Error while saving Sequence XML persistent data :");
6849        IcyExceptionHandler.showErrorMessage(exc, true);
6850
6851        return false;
6852    }
6853
6854    /**
6855     * Returns true if the specified XML data node exist
6856     * 
6857     * @param name
6858     *        name of node
6859     * @see #getNode(String)
6860     */
6861    public Node isNodeExisting(String name)
6862    {
6863        return persistent.getNode(name);
6864    }
6865
6866    /**
6867     * Get XML data node identified by specified name.<br>
6868     * The node is created if needed.</br>
6869     * Note that the following node names are reserved: <i>image, name, meta, rois, lut</i></br>
6870     * 
6871     * @param name
6872     *        name of wanted node
6873     * @see #isNodeExisting(String)
6874     */
6875    public Node getNode(String name)
6876    {
6877        final Node result = persistent.getNode(name);
6878
6879        if (result == null)
6880            return persistent.setNode(name);
6881
6882        return result;
6883    }
6884
6885    /**
6886     * @deprecated Use {@link #getNode(String)} instead.
6887     */
6888    @Deprecated
6889    public Node setNode(String name)
6890    {
6891        return persistent.setNode(name);
6892    }
6893
6894    @Override
6895    public String toString()
6896    {
6897        return "Sequence: " + getName() + " - " + getSizeX() + " x " + getSizeY() + " x " + getSizeZ() + " x "
6898                + getSizeT() + " - " + getSizeC() + " ch (" + getDataType_() + ")";
6899    }
6900
6901    /**
6902     * Do common job on "image add" here
6903     * 
6904     * @param image
6905     */
6906    public void onImageAdded(IcyBufferedImage image)
6907    {
6908        // colorModel not yet defined ?
6909        if (colorModel == null)
6910            // define it from the image colorModel
6911            setColorModel(IcyColorModel.createInstance(image.getIcyColorModel(), true, true));
6912
6913        // add listener to image
6914        image.addListener(this);
6915
6916        // notify changed
6917        dataChanged(image, SequenceEventType.ADDED);
6918    }
6919
6920    /**
6921     * Do common job on "image replaced" here
6922     */
6923    public void onImageReplaced(IcyBufferedImage oldImage, IcyBufferedImage newImage)
6924    {
6925        // we replaced the only present image
6926        final boolean typeChange = getNumImage() == 1;
6927
6928        beginUpdate();
6929        try
6930        {
6931            if (typeChange)
6932            {
6933                // colorModel not compatible ?
6934                if (!colorModel.isCompatible(newImage.getIcyColorModel()))
6935                    // define it from the new image colorModel
6936                    setColorModel(IcyColorModel.createInstance(newImage.getIcyColorModel(), true, true));
6937                // only inform about a type change if sequence sizeX and sizeY changed
6938                else if ((oldImage.getSizeX() != newImage.getSizeX()) || (oldImage.getSizeY() != newImage.getSizeY()))
6939                    typeChanged();
6940            }
6941
6942            // TODO: improve cleaning here
6943            // need that to avoid memory leak as we manually patch the image colorspace
6944            if (colorModel != null)
6945                colorModel.getIcyColorSpace().removeListener(oldImage.getIcyColorModel());
6946            // remove listener from old image
6947            oldImage.removeListener(this);
6948            // notify about old image remove
6949            dataChanged(oldImage, SequenceEventType.REMOVED);
6950
6951            // add listener to new image
6952            newImage.addListener(this);
6953            // notify about new image added
6954            dataChanged(newImage, SequenceEventType.ADDED);
6955        }
6956        finally
6957        {
6958            endUpdate();
6959        }
6960    }
6961
6962    /**
6963     * Do common job on "image remove" here
6964     * 
6965     * @param image
6966     */
6967    public void onImageRemoved(IcyBufferedImage image)
6968    {
6969        // no more image ? --> releasethe global colorModel
6970        if (isEmpty())
6971            setColorModel(null);
6972
6973        // TODO: improve cleaning here
6974        // need that to avoid memory leak as we manually patch the image colorspace
6975        if (colorModel != null)
6976            colorModel.getIcyColorSpace().removeListener(image.getIcyColorModel());
6977        // remove listener from image
6978        image.removeListener(this);
6979
6980        // notify changed
6981        dataChanged(image, SequenceEventType.REMOVED);
6982    }
6983
6984    /**
6985     * fire change event
6986     */
6987    @SuppressWarnings("deprecation")
6988    protected void fireChangedEvent(SequenceEvent e)
6989    {
6990        final List<SequenceListener> cachedListeners = new ArrayList<SequenceListener>(listeners);
6991
6992        for (SequenceListener listener : cachedListeners)
6993            listener.sequenceChanged(e);
6994
6995        // provide backward compatibility for painter
6996        if (e.getSourceType() == SequenceEventSourceType.SEQUENCE_OVERLAY)
6997        {
6998            final Painter painter;
6999
7000            if (e.getSource() instanceof OverlayWrapper)
7001                painter = ((OverlayWrapper) e.getSource()).getPainter();
7002            else
7003                painter = (Painter) e.getSource();
7004
7005            final SequenceEvent event = new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_PAINTER, painter,
7006                    e.getType(), e.getParam());
7007
7008            for (SequenceListener listener : cachedListeners)
7009                listener.sequenceChanged(event);
7010        }
7011    }
7012
7013    /**
7014     * fire close event
7015     */
7016    protected void fireClosedEvent()
7017    {
7018        for (SequenceListener listener : new ArrayList<SequenceListener>(listeners))
7019            listener.sequenceClosed(this);
7020    }
7021
7022    /**
7023     * fire model image changed event
7024     */
7025    @Override
7026    public void fireModelImageChangedEvent()
7027    {
7028        for (SequenceModelListener listener : new ArrayList<SequenceModelListener>(modelListeners))
7029            listener.imageChanged();
7030    }
7031
7032    /**
7033     * fire model dimension changed event
7034     */
7035    @Override
7036    public void fireModelDimensionChangedEvent()
7037    {
7038        for (SequenceModelListener listener : new ArrayList<SequenceModelListener>(modelListeners))
7039            listener.dimensionChanged();
7040    }
7041
7042    public void beginUpdate()
7043    {
7044        updater.beginUpdate();
7045    }
7046
7047    public void endUpdate()
7048    {
7049        updater.endUpdate();
7050
7051        // no more updating
7052        if (!updater.isUpdating())
7053        {
7054            // lazy channel bounds update
7055            if (channelBoundsInvalid)
7056            {
7057                channelBoundsInvalid = false;
7058                // images channels bounds are valid at this point
7059                internalUpdateChannelsBounds();
7060            }
7061        }
7062    }
7063
7064    public boolean isUpdating()
7065    {
7066        return updater.isUpdating();
7067    }
7068
7069    /**
7070     * sequence meta has changed
7071     */
7072    public void metaChanged(String metaName)
7073    {
7074        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_META, metaName));
7075    }
7076
7077    /**
7078     * sequence meta has changed
7079     */
7080    public void metaChanged(String metaName, int param)
7081    {
7082        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_META, metaName, null, param));
7083    }
7084
7085    /**
7086     * sequence type (colorModel, size) changed
7087     */
7088    protected void typeChanged()
7089    {
7090        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_TYPE));
7091    }
7092
7093    /**
7094     * sequence colorMap changed
7095     */
7096    protected void colormapChanged(IcyColorModel colorModel, int component)
7097    {
7098        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_COLORMAP, colorModel, component));
7099    }
7100
7101    /**
7102     * sequence component bounds changed
7103     */
7104    protected void componentBoundsChanged(IcyColorModel colorModel, int component)
7105    {
7106        updater.changed(
7107                new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_COMPONENTBOUNDS, colorModel, component));
7108    }
7109
7110    // /**
7111    // * @deprecated Use {@link #overlayChanged(Overlay, SequenceEventType)} instead.
7112    // */
7113    // @Deprecated
7114    // private void painterChanged(Painter painter, SequenceEventType type)
7115    // {
7116    // updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_PAINTER, painter,
7117    // type));
7118    // }
7119
7120    /**
7121     * @deprecated Use {@link #overlayChanged(Overlay)} instead.
7122     */
7123    @Deprecated
7124    public void painterChanged(Painter painter)
7125    {
7126        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_OVERLAY, getOverlay(painter),
7127                SequenceEventType.CHANGED));
7128        // painterChanged(painter, SequenceEventType.CHANGED);
7129    }
7130
7131    /**
7132     * overlay painter has changed
7133     */
7134    protected void overlayChanged(Overlay overlay, SequenceEventType type)
7135    {
7136        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_OVERLAY, overlay, type));
7137    }
7138
7139    /**
7140     * Notify specified painter of overlay has changed (the sequence should contains the specified
7141     * Overlay)
7142     */
7143    public void overlayChanged(Overlay overlay)
7144    {
7145        if (contains(overlay))
7146            overlayChanged(overlay, SequenceEventType.CHANGED);
7147    }
7148
7149    /**
7150     * Called when an overlay has changed (internal method).<br>
7151     * Use {@link #overlayChanged(Overlay)} instead.
7152     */
7153    @Override
7154    public void overlayChanged(OverlayEvent event)
7155    {
7156        // only take care about overlay painter change here (need redraw)
7157        if (event.getType() == OverlayEventType.PAINTER_CHANGED)
7158            overlayChanged(event.getSource(), SequenceEventType.CHANGED);
7159    }
7160
7161    /**
7162     * @deprecated Use {@link #roiChanged(ROI)} method instead.
7163     */
7164    @Deprecated
7165    public void roiChanged()
7166    {
7167        final Iterator<ROI> it = rois.iterator();
7168
7169        // send a event for all ROI
7170        while (it.hasNext())
7171            roiChanged(it.next(), SequenceEventType.CHANGED);
7172    }
7173
7174    /**
7175     * Notify specified roi has changed (the sequence should contains the specified ROI)
7176     */
7177    public void roiChanged(ROI roi)
7178    {
7179        if (contains(roi))
7180            roiChanged(roi, SequenceEventType.CHANGED);
7181    }
7182
7183    /**
7184     * Notify specified roi has changed
7185     */
7186    protected void roiChanged(ROI roi, SequenceEventType type)
7187    {
7188        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_ROI, roi, type));
7189    }
7190
7191    /**
7192     * Data has changed (global change)<br>
7193     * Be careful, this implies all component bounds are recalculated, can be heavy !
7194     */
7195    public void dataChanged()
7196    {
7197        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_DATA, null));
7198    }
7199
7200    /**
7201     * data has changed
7202     */
7203    protected void dataChanged(IcyBufferedImage image, SequenceEventType type)
7204    {
7205        updater.changed(new SequenceEvent(this, SequenceEventSourceType.SEQUENCE_DATA, image, type, 0));
7206    }
7207
7208    @Override
7209    public void colorModelChanged(IcyColorModelEvent e)
7210    {
7211        switch (e.getType())
7212        {
7213            case COLORMAP_CHANGED:
7214                colormapChanged(e.getColorModel(), e.getComponent());
7215                break;
7216
7217            case SCALER_CHANGED:
7218                componentBoundsChanged(e.getColorModel(), e.getComponent());
7219                break;
7220        }
7221    }
7222
7223    @Override
7224    public void imageChanged(IcyBufferedImageEvent e)
7225    {
7226        final IcyBufferedImage image = e.getImage();
7227
7228        switch (e.getType())
7229        {
7230            case BOUNDS_CHANGED:
7231                // update sequence channel bounds
7232                if (autoUpdateChannelBounds)
7233                {
7234                    // updating sequence ? delay update
7235                    if (isUpdating())
7236                        channelBoundsInvalid = true;
7237                    else
7238                        // refresh sequence channel bounds from images bounds
7239                        internalUpdateChannelsBounds();
7240                }
7241                break;
7242
7243            case COLORMAP_CHANGED:
7244                // ignore that, we don't care about image colormap
7245                break;
7246
7247            case DATA_CHANGED:
7248                // image data changed
7249                dataChanged(image, SequenceEventType.CHANGED);
7250                break;
7251        }
7252    }
7253
7254    @Override
7255    public void roiChanged(ROIEvent event)
7256    {
7257        // notify the ROI has changed
7258        roiChanged(event.getSource(), SequenceEventType.CHANGED);
7259    }
7260
7261    /**
7262     * process on sequence change
7263     */
7264    @Override
7265    public void onChanged(CollapsibleEvent e)
7266    {
7267        final SequenceEvent event = (SequenceEvent) e;
7268
7269        switch (event.getSourceType())
7270        {
7271            // do here global process on sequence data change
7272            case SEQUENCE_DATA:
7273                // automatic channel bounds update enabled
7274                if (autoUpdateChannelBounds)
7275                {
7276                    // generic CHANGED event
7277                    if (event.getSource() == null)
7278                        // recalculate all images bounds (automatically update sequence bounds in imageChange event)
7279                        recalculateAllImageChannelsBounds();
7280
7281                    // refresh sequence channel bounds from images bounds
7282                    internalUpdateChannelsBounds();
7283                }
7284
7285                // fire SequenceModel event
7286                fireModelImageChangedEvent();
7287                break;
7288
7289            // do here global process on sequence type change
7290            case SEQUENCE_TYPE:
7291                // fire SequenceModel event
7292                fireModelDimensionChangedEvent();
7293                break;
7294
7295            // do here global process on sequence colormap change
7296            case SEQUENCE_COLORMAP:
7297                break;
7298
7299            // do here global process on sequence component bounds change
7300            case SEQUENCE_COMPONENTBOUNDS:
7301                break;
7302
7303            // do here global process on sequence overlay change
7304            case SEQUENCE_OVERLAY:
7305                break;
7306
7307            // do here global process on sequence ROI change
7308            case SEQUENCE_ROI:
7309                break;
7310        }
7311
7312        // notify listener we have changed
7313        fireChangedEvent(event);
7314    }
7315
7316}