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.Point;
022import java.awt.Rectangle;
023import java.awt.geom.Point2D;
024import java.awt.geom.Rectangle2D;
025import java.awt.image.BufferedImage;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.TreeMap;
031
032import javax.swing.SwingConstants;
033
034import icy.common.listener.ProgressListener;
035import icy.image.IcyBufferedImage;
036import icy.image.IcyBufferedImageUtil;
037import icy.image.IcyBufferedImageUtil.FilterType;
038import icy.image.colormap.IcyColorMap;
039import icy.image.colormap.LinearColorMap;
040import icy.image.lut.LUT;
041import icy.math.Scaler;
042import icy.painter.Overlay;
043import icy.roi.BooleanMask2D;
044import icy.roi.ROI;
045import icy.type.DataType;
046import icy.type.collection.array.Array1DUtil;
047import icy.type.point.Point3D;
048import icy.type.rectangle.Rectangle3D;
049import icy.type.rectangle.Rectangle5D;
050import icy.util.OMEUtil;
051import icy.util.StringUtil;
052import ome.xml.meta.OMEXMLMetadata;
053
054/**
055 * {@link Sequence} utilities class.<br>
056 * You can find here tools to manipulate the sequence organization, its data type, its size...
057 * 
058 * @author Stephane
059 */
060public class SequenceUtil
061{
062    public static class AddZHelper
063    {
064        public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition,
065                int numInsert, int copyLast)
066        {
067            if (z < insertPosition)
068                return sequence.getImage(t, z);
069
070            final int pos = z - insertPosition;
071
072            // return new image
073            if (pos < numInsert)
074            {
075                // return copy of previous image(s)
076                if ((insertPosition > 0) && (copyLast > 0))
077                {
078                    // should be < insert position
079                    final int duplicate = Math.min(insertPosition, copyLast);
080                    final int baseReplicate = insertPosition - duplicate;
081
082                    return sequence.getImage(t, baseReplicate + (pos % duplicate));
083                }
084
085                // return new empty image
086                return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
087                        sequence.getDataType_());
088            }
089
090            return sequence.getImage(t, z - numInsert);
091        }
092    }
093
094    public static class AddTHelper
095    {
096        public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition,
097                int numInsert, int copyLast)
098        {
099            if (t < insertPosition)
100                return sequence.getImage(t, z);
101
102            final int pos = t - insertPosition;
103
104            // return new image
105            if (pos < numInsert)
106            {
107                // return copy of previous image(s)
108                if ((insertPosition > 0) && (copyLast > 0))
109                {
110                    // should be < insert position
111                    final int duplicate = Math.min(insertPosition, copyLast);
112                    final int baseReplicate = insertPosition - duplicate;
113
114                    return sequence.getImage(baseReplicate + (pos % duplicate), z);
115                }
116
117                // return new empty image
118                return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
119                        sequence.getDataType_());
120            }
121
122            return sequence.getImage(t - numInsert, z);
123        }
124    }
125
126    public static class MergeCHelper
127    {
128        private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, int c,
129                boolean fillEmpty)
130        {
131            IcyBufferedImage img = seq.getImage(t, z, c);
132
133            if ((img == null) && fillEmpty)
134            {
135                int curZ = z;
136
137                // missing Z slice ?
138                if (z >= seq.getSizeZ())
139                {
140                    // searching in previous slice
141                    while ((img == null) && (curZ > 0))
142                        img = seq.getImage(t, --curZ, c);
143                }
144
145                if (img == null)
146                {
147                    int curT = t;
148
149                    // searching in previous frame
150                    while ((img == null) && (curT > 0))
151                        img = seq.getImage(--curT, z, c);
152                }
153
154                return img;
155            }
156
157            return img;
158        }
159
160        public static IcyBufferedImage getImage(Sequence[] sequences, int[] channels, int sizeX, int sizeY, int t,
161                int z, boolean fillEmpty, boolean rescale) throws IllegalArgumentException
162        {
163            if (sequences.length == 0)
164                return null;
165
166            final List<BufferedImage> images = new ArrayList<BufferedImage>();
167            final List<IcyColorMap> colormaps = new ArrayList<IcyColorMap>();
168
169            for (int i = 0; i < sequences.length; i++)
170            {
171                final Sequence seq = sequences[i];
172                final int c = channels[i];
173
174                // get colormap
175                colormaps.add(seq.getColorMap(c));
176                // get image
177                IcyBufferedImage img = getImageFromSequenceInternal(seq, t, z, c, fillEmpty);
178
179                // create an empty image
180                if (img == null)
181                    img = new IcyBufferedImage(sizeX, sizeY, 1, seq.getDataType_());
182                // resize X and Y dimension if needed
183                else if ((img.getSizeX() != sizeX) || (img.getSizeY() != sizeY))
184                    img = IcyBufferedImageUtil.scale(img, sizeX, sizeY, rescale, SwingConstants.CENTER,
185                            SwingConstants.CENTER, FilterType.BILINEAR);
186
187                images.add(img);
188            }
189
190            final IcyBufferedImage result = IcyBufferedImage.createFrom(images);
191
192            // restore colormap
193            for (int c = 0; c < result.getSizeC(); c++)
194            {
195                final IcyColorMap map = colormaps.get(c);
196
197                if (map != null)
198                    result.setColorMap(c, colormaps.get(c), false);
199            }
200
201            return result;
202        }
203    }
204
205    public static class MergeZHelper
206    {
207        private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty)
208        {
209            IcyBufferedImage img = seq.getImage(t, z);
210
211            if ((img == null) && fillEmpty)
212            {
213                int curZ = z;
214
215                // missing Z slice ?
216                if (z >= seq.getSizeZ())
217                {
218                    // searching in previous slice
219                    while ((img == null) && (curZ > 0))
220                        img = seq.getImage(t, --curZ);
221                }
222
223                if (img == null)
224                {
225                    int curT = t;
226
227                    // searching in previous frame
228                    while ((img == null) && (curT > 0))
229                        img = seq.getImage(--curT, z);
230                }
231
232                return img;
233            }
234
235            return img;
236        }
237
238        private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced,
239                boolean fillEmpty)
240        {
241            int zRemaining = z;
242
243            if (interlaced)
244            {
245                int zInd = 0;
246
247                while (zRemaining >= 0)
248                {
249                    for (Sequence seq : sequences)
250                    {
251                        if (zInd < seq.getSizeZ())
252                        {
253                            if (zRemaining-- == 0)
254                                return getImageFromSequenceInternal(seq, t, zInd, fillEmpty);
255                        }
256                    }
257
258                    zInd++;
259                }
260            }
261            else
262            {
263                for (Sequence seq : sequences)
264                {
265                    final int sizeZ = seq.getSizeZ();
266
267                    // we found the sequence
268                    if (zRemaining < sizeZ)
269                        return getImageFromSequenceInternal(seq, t, zRemaining, fillEmpty);
270
271                    zRemaining -= sizeZ;
272                }
273            }
274
275            return null;
276        }
277
278        public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z,
279                boolean interlaced, boolean fillEmpty, boolean rescale)
280        {
281            IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty);
282
283            if (result != null)
284            {
285                // resize X and Y dimension if needed
286                if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY))
287                    result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER,
288                            SwingConstants.CENTER, FilterType.BILINEAR);
289
290                final int imgSizeC = result.getSizeC();
291
292                // resize C dimension if needed
293                if (imgSizeC < sizeC)
294                    return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC);
295            }
296
297            return result;
298        }
299    }
300
301    public static class MergeTHelper
302    {
303        private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty)
304        {
305            IcyBufferedImage img = seq.getImage(t, z);
306
307            if ((img == null) && fillEmpty)
308            {
309                int curT = t;
310
311                // missing T frame?
312                if (t >= seq.getSizeT())
313                {
314                    // searching in previous frame
315                    while ((img == null) && (curT > 0))
316                        img = seq.getImage(--curT, z);
317                }
318
319                if (img == null)
320                {
321                    int curZ = z;
322
323                    // searching in previous slice
324                    while ((img == null) && (curZ > 0))
325                        img = seq.getImage(t, --curZ);
326                }
327
328                return img;
329            }
330
331            return img;
332        }
333
334        private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced,
335                boolean fillEmpty)
336        {
337            int tRemaining = t;
338
339            if (interlaced)
340            {
341                int tInd = 0;
342
343                while (tRemaining >= 0)
344                {
345                    for (Sequence seq : sequences)
346                    {
347                        if (tInd < seq.getSizeT())
348                        {
349                            if (tRemaining-- == 0)
350                                return getImageFromSequenceInternal(seq, tInd, z, fillEmpty);
351                        }
352                    }
353
354                    tInd++;
355                }
356            }
357            else
358            {
359                for (Sequence seq : sequences)
360                {
361                    final int sizeT = seq.getSizeT();
362
363                    // we found the sequence
364                    if (tRemaining < sizeT)
365                        return getImageFromSequenceInternal(seq, tRemaining, z, fillEmpty);
366
367                    tRemaining -= sizeT;
368                }
369            }
370
371            return null;
372        }
373
374        public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z,
375                boolean interlaced, boolean fillEmpty, boolean rescale)
376        {
377            IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty);
378
379            if (result != null)
380            {
381                // resize X and Y dimension if needed
382                if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY))
383                    result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER,
384                            SwingConstants.CENTER, FilterType.BILINEAR);
385
386                final int imgSizeC = result.getSizeC();
387
388                // resize C dimension if needed
389                if (imgSizeC < sizeC)
390                    return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC);
391            }
392
393            return result;
394        }
395    }
396
397    public static class AdjustZTHelper
398    {
399        public static IcyBufferedImage getImage(Sequence sequence, int t, int z, int newSizeZ, int newSizeT,
400                boolean reverseOrder)
401        {
402            final int sizeZ = sequence.getSizeZ();
403            final int sizeT = sequence.getSizeT();
404
405            // out of range
406            if ((t >= newSizeT) || (z >= newSizeZ))
407                return null;
408
409            final int index;
410
411            // calculate index of wanted image
412            if (reverseOrder)
413                index = (z * newSizeT) + t;
414            else
415                index = (t * newSizeZ) + z;
416
417            final int tOrigin = index / sizeZ;
418            final int zOrigin = index % sizeZ;
419
420            // bounding --> return new image
421            if (tOrigin >= sizeT)
422                return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(),
423                        sequence.getDataType_());
424
425            return sequence.getImage(tOrigin, zOrigin);
426        }
427    }
428
429    /**
430     * Add one or severals frames at position t.
431     * 
432     * @param t
433     *        Position where to add frame(s)
434     * @param num
435     *        Number of frame to add
436     * @param copyLast
437     *        Number of last frame(s) to copy to fill added frames.<br>
438     *        0 means that new frames are empty.<br>
439     *        1 means we duplicate the last frame.<br>
440     *        2 means we duplicate the two last frames.<br>
441     *        and so on...
442     */
443    public static void addT(Sequence sequence, int t, int num, int copyLast)
444    {
445        final int sizeZ = sequence.getSizeZ();
446        final int sizeT = sequence.getSizeT();
447
448        sequence.beginUpdate();
449        try
450        {
451            moveT(sequence, t, sizeT - 1, num);
452
453            for (int i = 0; i < num; i++)
454                for (int z = 0; z < sizeZ; z++)
455                    sequence.setImage(t + i, z, IcyBufferedImageUtil
456                            .getCopy(AddTHelper.getExtendedImage(sequence, t + i, z, t, num, copyLast)));
457        }
458        finally
459        {
460            sequence.endUpdate();
461        }
462    }
463
464    /**
465     * Add one or severals frames at position t.
466     * 
467     * @param t
468     *        Position where to add frame(s)
469     * @param num
470     *        Number of frame to add
471     */
472    public static void addT(Sequence sequence, int t, int num)
473    {
474        addT(sequence, t, num, 0);
475    }
476
477    /**
478     * Add one or severals frames at position t.
479     * 
480     * @param num
481     *        Number of frame to add
482     */
483    public static void addT(Sequence sequence, int num)
484    {
485        addT(sequence, sequence.getSizeT(), num, 0);
486    }
487
488    /**
489     * Add one or severals frames at position t.
490     * 
491     * @param num
492     *        Number of frame to add
493     * @param copyLast
494     *        If true then the last frame is copied in added frames.
495     */
496    public static void addT(Sequence sequence, int num, boolean copyLast)
497    {
498        addT(sequence, sequence.getSizeT(), num, 0);
499    }
500
501    /**
502     * Exchange 2 frames position on the sequence.
503     */
504    public static void swapT(Sequence sequence, int t1, int t2)
505    {
506        final int sizeT = sequence.getSizeT();
507
508        if ((t1 < 0) || (t2 < 0) || (t1 >= sizeT) || (t2 >= sizeT))
509            return;
510
511        // get volume images at position t1 & t2
512        final VolumetricImage vi1 = sequence.getVolumetricImage(t1);
513        final VolumetricImage vi2 = sequence.getVolumetricImage(t2);
514
515        sequence.beginUpdate();
516        try
517        {
518            // start by removing old volume image (if any)
519            sequence.removeAllImages(t1);
520            sequence.removeAllImages(t2);
521
522            // safe volume image copy (TODO : check if we can't direct set volume image internally)
523            if (vi1 != null)
524            {
525                final Map<Integer, IcyBufferedImage> images = vi1.getImages();
526
527                // copy images of volume image 1 at position t2
528                for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
529                    sequence.setImage(t2, entry.getKey().intValue(), entry.getValue());
530            }
531            if (vi2 != null)
532            {
533                final Map<Integer, IcyBufferedImage> images = vi2.getImages();
534
535                // copy images of volume image 2 at position t1
536                for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
537                    sequence.setImage(t1, entry.getKey().intValue(), entry.getValue());
538            }
539        }
540        finally
541        {
542            sequence.endUpdate();
543        }
544    }
545
546    /**
547     * Modify frame position.<br>
548     * The previous frame present at <code>newT</code> position is lost.
549     * 
550     * @param sequence
551     * @param t
552     *        current t position
553     * @param newT
554     *        wanted t position
555     */
556    public static void moveT(Sequence sequence, int t, int newT)
557    {
558        final int sizeT = sequence.getSizeT();
559
560        if ((t < 0) || (t >= sizeT) || (newT < 0) || (t == newT))
561            return;
562
563        // get volume image at position t
564        final VolumetricImage vi = sequence.getVolumetricImage(t);
565
566        sequence.beginUpdate();
567        try
568        {
569            // remove volume image (if any) at position newT
570            sequence.removeAllImages(newT);
571
572            if (vi != null)
573            {
574                final TreeMap<Integer, IcyBufferedImage> images = vi.getImages();
575
576                // copy images of volume image at position newT
577                for (Entry<Integer, IcyBufferedImage> entry : images.entrySet())
578                    sequence.setImage(newT, entry.getKey().intValue(), entry.getValue());
579
580                // remove volume image at position t
581                sequence.removeAllImages(t);
582            }
583        }
584        finally
585        {
586            sequence.endUpdate();
587        }
588    }
589
590    /**
591     * Modify T position of a range of frame by the specified offset
592     * 
593     * @param sequence
594     * @param from
595     *        start of range (t position)
596     * @param to
597     *        end of range (t position)
598     * @param offset
599     *        position shift
600     */
601    public static void moveT(Sequence sequence, int from, int to, int offset)
602    {
603        sequence.beginUpdate();
604        try
605        {
606            if (offset > 0)
607            {
608                for (int t = to; t >= from; t--)
609                    moveT(sequence, t, t + offset);
610            }
611            else
612            {
613                for (int t = from; t <= to; t++)
614                    moveT(sequence, t, t + offset);
615            }
616        }
617        finally
618        {
619            sequence.endUpdate();
620        }
621    }
622
623    /**
624     * Remove a frame at position t.
625     * 
626     * @param sequence
627     * @param t
628     */
629    public static void removeT(Sequence sequence, int t)
630    {
631        final int sizeT = sequence.getSizeT();
632
633        if ((t < 0) || (t >= sizeT))
634            return;
635
636        sequence.removeAllImages(t);
637    }
638
639    /**
640     * Remove a frame at position t and shift all the further t by -1.
641     * 
642     * @param sequence
643     * @param t
644     */
645    public static void removeTAndShift(Sequence sequence, int t)
646    {
647        final int sizeT = sequence.getSizeT();
648
649        if ((t < 0) || (t >= sizeT))
650            return;
651
652        sequence.beginUpdate();
653        try
654        {
655            removeT(sequence, t);
656            moveT(sequence, t + 1, sizeT - 1, -1);
657        }
658        finally
659        {
660            sequence.endUpdate();
661        }
662    }
663
664    /**
665     * Reverse T frames order.
666     */
667    public static void reverseT(Sequence sequence)
668    {
669        final int sizeT = sequence.getSizeT();
670        final int sizeZ = sequence.getSizeZ();
671
672        final Sequence save = new Sequence();
673
674        save.beginUpdate();
675        try
676        {
677            for (int t = 0; t < sizeT; t++)
678                for (int z = 0; z < sizeZ; z++)
679                    save.setImage(t, z, sequence.getImage(t, z));
680        }
681        finally
682        {
683            save.endUpdate();
684        }
685
686        sequence.beginUpdate();
687        try
688        {
689            sequence.removeAllImages();
690
691            for (int t = 0; t < sizeT; t++)
692                for (int z = 0; z < sizeZ; z++)
693                    sequence.setImage(sizeT - (t + 1), z, save.getImage(t, z));
694        }
695        finally
696        {
697            sequence.endUpdate();
698        }
699
700        // to avoid memory leak as images now contained in sequence will retain 'save' sequence forever
701        save.removeAllImages();
702    }
703
704    /**
705     * Add one or severals slices at position z.
706     * 
707     * @param z
708     *        Position where to add slice(s)
709     * @param num
710     *        Number of slice to add
711     * @param copyLast
712     *        Number of last slice(s) to copy to fill added slices.<br>
713     *        0 means that new slices are empty.<br>
714     *        1 means we duplicate the last slice.<br>
715     *        2 means we duplicate the two last slices.<br>
716     *        and so on...
717     */
718    public static void addZ(Sequence sequence, int z, int num, int copyLast)
719    {
720        final int sizeZ = sequence.getSizeZ();
721        final int sizeT = sequence.getSizeT();
722
723        sequence.beginUpdate();
724        try
725        {
726            moveZ(sequence, z, sizeZ - 1, num);
727
728            for (int i = 0; i < num; i++)
729                for (int t = 0; t < sizeT; t++)
730                    sequence.setImage(t, z + i, IcyBufferedImageUtil
731                            .getCopy(AddZHelper.getExtendedImage(sequence, t, z + i, z, num, copyLast)));
732        }
733        finally
734        {
735            sequence.endUpdate();
736        }
737    }
738
739    /**
740     * Add one or severals slices at position z.
741     * 
742     * @param z
743     *        Position where to add slice(s)
744     * @param num
745     *        Number of slice to add
746     */
747    public static void addZ(Sequence sequence, int z, int num)
748    {
749        addZ(sequence, z, num, 0);
750    }
751
752    /**
753     * Add one or severals slices at position z.
754     * 
755     * @param num
756     *        Number of slice to add
757     */
758    public static void addZ(Sequence sequence, int num)
759    {
760        addZ(sequence, sequence.getSizeZ(), num, 0);
761    }
762
763    /**
764     * Add one or severals slices at position z.
765     * 
766     * @param num
767     *        Number of slice to add
768     * @param copyLast
769     *        If true then the last slice is copied in added slices.
770     */
771    public static void addZ(Sequence sequence, int num, boolean copyLast)
772    {
773        addZ(sequence, sequence.getSizeZ(), num, 0);
774    }
775
776    /**
777     * Exchange 2 slices position on the sequence.
778     */
779    public static void swapZ(Sequence sequence, int z1, int z2)
780    {
781        final int sizeZ = sequence.getSizeZ();
782        final int sizeT = sequence.getSizeT();
783
784        if ((z1 < 0) || (z2 < 0) || (z1 >= sizeZ) || (z2 >= sizeZ))
785            return;
786
787        sequence.beginUpdate();
788        try
789        {
790            for (int t = 0; t < sizeT; t++)
791            {
792                final IcyBufferedImage image1 = sequence.getImage(t, z1);
793                final IcyBufferedImage image2 = sequence.getImage(t, z2);
794
795                // set image at new position
796                if (image1 != null)
797                    sequence.setImage(t, z2, image1);
798                else
799                    sequence.removeImage(t, z2);
800                if (image2 != null)
801                    sequence.setImage(t, z1, image2);
802                else
803                    sequence.removeImage(t, z1);
804            }
805        }
806        finally
807        {
808            sequence.endUpdate();
809        }
810    }
811
812    /**
813     * Modify slice position.<br>
814     * The previous slice present at <code>newZ</code> position is lost.
815     * 
816     * @param sequence
817     * @param z
818     *        current z position
819     * @param newZ
820     *        wanted z position
821     */
822    public static void moveZ(Sequence sequence, int z, int newZ)
823    {
824        final int sizeZ = sequence.getSizeZ();
825        final int sizeT = sequence.getSizeT();
826
827        if ((z < 0) || (z >= sizeZ) || (newZ < 0) || (z == newZ))
828            return;
829
830        sequence.beginUpdate();
831        try
832        {
833            for (int t = 0; t < sizeT; t++)
834            {
835                final IcyBufferedImage image = sequence.getImage(t, z);
836
837                if (image != null)
838                {
839                    // set image at new position
840                    sequence.setImage(t, newZ, image);
841                    // and remove image at old position z
842                    sequence.removeImage(t, z);
843                }
844                else
845                    // just set null image at new position (equivalent to no image)
846                    sequence.removeImage(t, newZ);
847            }
848        }
849        finally
850        {
851            sequence.endUpdate();
852        }
853    }
854
855    /**
856     * Modify Z position of a range of slice by the specified offset
857     * 
858     * @param sequence
859     * @param from
860     *        start of range (z position)
861     * @param to
862     *        end of range (z position)
863     * @param offset
864     *        position shift
865     */
866    public static void moveZ(Sequence sequence, int from, int to, int offset)
867    {
868        sequence.beginUpdate();
869        try
870        {
871            if (offset > 0)
872            {
873                for (int z = to; z >= from; z--)
874                    moveZ(sequence, z, z + offset);
875            }
876            else
877            {
878                for (int z = from; z <= to; z++)
879                    moveZ(sequence, z, z + offset);
880            }
881        }
882        finally
883        {
884            sequence.endUpdate();
885        }
886    }
887
888    /**
889     * Remove a slice at position Z.
890     * 
891     * @param sequence
892     * @param z
893     */
894    public static void removeZ(Sequence sequence, int z)
895    {
896        final int sizeZ = sequence.getSizeZ();
897
898        if ((z < 0) || (z >= sizeZ))
899            return;
900
901        sequence.beginUpdate();
902        try
903        {
904            final int maxT = sequence.getSizeT();
905
906            for (int t = 0; t < maxT; t++)
907                sequence.removeImage(t, z);
908        }
909        finally
910        {
911            sequence.endUpdate();
912        }
913    }
914
915    /**
916     * Remove a slice at position t and shift all the further t by -1.
917     * 
918     * @param sequence
919     * @param z
920     */
921    public static void removeZAndShift(Sequence sequence, int z)
922    {
923        final int sizeZ = sequence.getSizeZ();
924
925        if ((z < 0) || (z >= sizeZ))
926            return;
927
928        sequence.beginUpdate();
929        try
930        {
931            removeZ(sequence, z);
932            moveZ(sequence, z + 1, sizeZ - 1, -1);
933        }
934        finally
935        {
936            sequence.endUpdate();
937        }
938    }
939
940    /**
941     * Reverse Z slices order.
942     */
943    public static void reverseZ(Sequence sequence)
944    {
945        final int sizeT = sequence.getSizeT();
946        final int sizeZ = sequence.getSizeZ();
947
948        final Sequence save = new Sequence();
949
950        save.beginUpdate();
951        try
952        {
953            for (int t = 0; t < sizeT; t++)
954                for (int z = 0; z < sizeZ; z++)
955                    save.setImage(t, z, sequence.getImage(t, z));
956        }
957        finally
958        {
959            save.endUpdate();
960        }
961
962        sequence.beginUpdate();
963        try
964        {
965            sequence.removeAllImages();
966
967            for (int t = 0; t < sizeT; t++)
968                for (int z = 0; z < sizeZ; z++)
969                    sequence.setImage(t, sizeZ - (z + 1), save.getImage(t, z));
970        }
971        finally
972        {
973            sequence.endUpdate();
974        }
975
976        // to avoid memory leak as images now contained in sequence will retain 'save' sequence forever
977        save.removeAllImages();
978    }
979
980    /**
981     * Set all images of the sequence in T dimension.
982     */
983    public static void convertToTime(Sequence sequence)
984    {
985        sequence.beginUpdate();
986        try
987        {
988            final List<IcyBufferedImage> images = sequence.getAllImage();
989
990            sequence.removeAllImages();
991            for (int i = 0; i < images.size(); i++)
992                sequence.setImage(i, 0, images.get(i));
993        }
994        finally
995        {
996            sequence.endUpdate();
997        }
998    }
999
1000    /**
1001     * Set all images of the sequence in Z dimension.
1002     */
1003    public static void convertToStack(Sequence sequence)
1004    {
1005        sequence.beginUpdate();
1006        try
1007        {
1008            final List<IcyBufferedImage> images = sequence.getAllImage();
1009
1010            sequence.removeAllImages();
1011            for (int i = 0; i < images.size(); i++)
1012                sequence.setImage(0, i, images.get(i));
1013        }
1014        finally
1015        {
1016            sequence.endUpdate();
1017        }
1018    }
1019
1020    /**
1021     * @deprecated Use {@link #convertToStack(Sequence)} instead.
1022     */
1023    @Deprecated
1024    public static void convertToVolume(Sequence sequence)
1025    {
1026        convertToStack(sequence);
1027    }
1028
1029    /**
1030     * Remove the specified channel from the source sequence.
1031     * 
1032     * @param source
1033     *        Source sequence
1034     * @param channel
1035     *        Channel index to remove
1036     */
1037    public static void removeChannel(Sequence source, int channel)
1038    {
1039        final int sizeC = source.getSizeC();
1040
1041        if (channel >= sizeC)
1042            return;
1043
1044        final int[] keep = new int[sizeC - 1];
1045
1046        int i = 0;
1047        for (int c = 0; c < sizeC; c++)
1048            if (c != channel)
1049                keep[i++] = c;
1050
1051        final Sequence tmp = extractChannels(source, keep);
1052
1053        source.beginUpdate();
1054        try
1055        {
1056            // we need to clear the source sequence to change its type
1057            source.removeAllImages();
1058
1059            // get back all images
1060            for (int t = 0; t < tmp.getSizeT(); t++)
1061                for (int z = 0; z < tmp.getSizeZ(); z++)
1062                    source.setImage(t, z, tmp.getImage(t, z));
1063
1064            // get back modified metadata
1065            source.setMetaData(tmp.getOMEXMLMetadata());
1066            // and colormaps
1067            for (int c = 0; c < tmp.getSizeC(); c++)
1068                source.setDefaultColormap(c, tmp.getDefaultColorMap(c), false);
1069
1070            // to avoid memory leak as images now contained in source will retain this sequence forever
1071            tmp.removeAllImages();
1072        }
1073        finally
1074        {
1075            source.endUpdate();
1076        }
1077    }
1078
1079    /**
1080     * Returns the max size of specified dimension for the given sequences.
1081     */
1082    public static int getMaxDim(Sequence[] sequences, DimensionId dim)
1083    {
1084        int result = 0;
1085
1086        for (Sequence seq : sequences)
1087        {
1088            switch (dim)
1089            {
1090                case X:
1091                    result = Math.max(result, seq.getSizeX());
1092                    break;
1093                case Y:
1094                    result = Math.max(result, seq.getSizeY());
1095                    break;
1096                case C:
1097                    result = Math.max(result, seq.getSizeC());
1098                    break;
1099                case Z:
1100                    result = Math.max(result, seq.getSizeZ());
1101                    break;
1102                case T:
1103                    result = Math.max(result, seq.getSizeT());
1104                    break;
1105            }
1106        }
1107
1108        return result;
1109    }
1110
1111    /**
1112     * Create and returns a new sequence by concatenating all given sequences on C dimension.
1113     * 
1114     * @param sequences
1115     *        Sequences to concatenate (use array order).
1116     * @param channels
1117     *        Selected channel for each sequence (<code>channels.length = sequences.length</code>)<br>
1118     *        If you want to select 2 or more channels from a sequence, just duplicate the sequence
1119     *        entry in the <code>sequences</code> parameter :</code><br>
1120     *        <code>sequences[n] = Sequence1; channels[n] = 0;</code><br>
1121     *        <code>sequences[n+1] = Sequence1; channels[n+1] = 2;</code><br>
1122     *        <code>...</code>
1123     * @param fillEmpty
1124     *        Replace empty image by the previous non empty one.
1125     * @param rescale
1126     *        Images are scaled to all fit in the same XY dimension.
1127     * @param pl
1128     *        ProgressListener to indicate processing progress.
1129     * @throws IllegalArgumentException
1130     *         if sequences contains incompatible sequence for merge operation.
1131     */
1132    public static Sequence concatC(Sequence[] sequences, int[] channels, boolean fillEmpty, boolean rescale,
1133            ProgressListener pl) throws IllegalArgumentException
1134    {
1135        final int sizeX = getMaxDim(sequences, DimensionId.X);
1136        final int sizeY = getMaxDim(sequences, DimensionId.Y);
1137        final int sizeZ = getMaxDim(sequences, DimensionId.Z);
1138        final int sizeT = getMaxDim(sequences, DimensionId.T);
1139
1140        final Sequence result = new Sequence();
1141
1142        if (sequences.length > 0)
1143            result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata()));
1144        result.setName("C Merge");
1145
1146        int ind = 0;
1147        for (int t = 0; t < sizeT; t++)
1148        {
1149            for (int z = 0; z < sizeZ; z++)
1150            {
1151                if (pl != null)
1152                    pl.notifyProgress(ind, sizeT * sizeZ);
1153
1154                result.setImage(t, z,
1155                        MergeCHelper.getImage(sequences, channels, sizeX, sizeY, t, z, fillEmpty, rescale));
1156
1157                ind++;
1158            }
1159        }
1160
1161        for (int i = 0; i < sequences.length; i++)
1162        {
1163            final Sequence seq = sequences[i];
1164            final int c = channels[i];
1165            final String channelName = seq.getChannelName(c);
1166            final IcyColorMap channelColor = seq.getColorMap(c);
1167
1168            // not default channel name --> we keep it
1169            if (!StringUtil.equals(seq.getDefaultChannelName(c), channelName))
1170                result.setChannelName(i, channelName);
1171
1172            // not default white color map --> we keep it
1173            if (!channelColor.equals(LinearColorMap.white_))
1174                result.setColormap(i, channelColor, true);
1175        }
1176
1177        return result;
1178    }
1179
1180    /**
1181     * Create and returns a new sequence by concatenating all given sequences on C dimension.
1182     * 
1183     * @param sequences
1184     *        Sequences to concatenate (use array order).
1185     * @param fillEmpty
1186     *        Replace empty image by the previous non empty one.
1187     * @param rescale
1188     *        Images are scaled to all fit in the same XY dimension.
1189     * @param pl
1190     *        ProgressListener to indicate processing progress.
1191     */
1192    public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale, ProgressListener pl)
1193    {
1194        // compute expanded sequence array and channels array length
1195        int len = 0;
1196        for (Sequence s : sequences)
1197            len += s.getSizeC();
1198
1199        final Sequence[] newSequences = new Sequence[len];
1200        final int[] channels = new int[len];
1201
1202        // fill newSequences and channels arrays
1203        int ind = 0;
1204        for (Sequence s : sequences)
1205        {
1206            for (int c = 0; c < s.getSizeC(); c++)
1207            {
1208                newSequences[ind] = s;
1209                channels[ind] = c;
1210                ind++;
1211
1212            }
1213        }
1214
1215        return concatC(newSequences, channels, fillEmpty, rescale, pl);
1216    }
1217
1218    /**
1219     * Create and returns a new sequence by concatenating all given sequences on C dimension.
1220     * 
1221     * @param sequences
1222     *        Sequences to concatenate (use array order).
1223     * @param fillEmpty
1224     *        Replace empty image by the previous non empty one.
1225     * @param rescale
1226     *        Images are scaled to all fit in the same XY dimension.
1227     */
1228    public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale)
1229    {
1230        return concatC(sequences, fillEmpty, rescale, null);
1231    }
1232
1233    /**
1234     * Create and returns a new sequence by concatenating all given sequences on C dimension.
1235     * 
1236     * @param sequences
1237     *        Sequences to concatenate (use array order).
1238     */
1239    public static Sequence concatC(Sequence[] sequences)
1240    {
1241        return concatC(sequences, true, false, null);
1242    }
1243
1244    /**
1245     * Create and returns a new sequence by concatenating all given sequences on Z dimension.
1246     * 
1247     * @param sequences
1248     *        Sequences to concatenate (use array order).
1249     * @param interlaced
1250     *        Interlace images.<br>
1251     *        normal : 1,1,1,2,2,2,3,3,..<br>
1252     *        interlaced : 1,2,3,1,2,3,..<br>
1253     * @param fillEmpty
1254     *        Replace empty image by the previous non empty one.
1255     * @param rescale
1256     *        Images are scaled to all fit in the same XY dimension.
1257     * @param pl
1258     *        ProgressListener to indicate processing progress.
1259     */
1260    public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale,
1261            ProgressListener pl)
1262    {
1263        final int sizeX = getMaxDim(sequences, DimensionId.X);
1264        final int sizeY = getMaxDim(sequences, DimensionId.Y);
1265        final int sizeC = getMaxDim(sequences, DimensionId.C);
1266        final int sizeT = getMaxDim(sequences, DimensionId.T);
1267        int sizeZ = 0;
1268
1269        for (Sequence seq : sequences)
1270            sizeZ += seq.getSizeZ();
1271
1272        final Sequence result = new Sequence();
1273
1274        if (sequences.length > 0)
1275            result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata()));
1276        result.setName("Z Merge");
1277
1278        int ind = 0;
1279        for (int t = 0; t < sizeT; t++)
1280        {
1281            for (int z = 0; z < sizeZ; z++)
1282            {
1283                if (pl != null)
1284                    pl.notifyProgress(ind, sizeT * sizeZ);
1285
1286                result.setImage(t, z, IcyBufferedImageUtil.getCopy(
1287                        MergeZHelper.getImage(sequences, sizeX, sizeY, sizeC, t, z, interlaced, fillEmpty, rescale)));
1288
1289                ind++;
1290            }
1291        }
1292
1293        return result;
1294    }
1295
1296    /**
1297     * Create and returns a new sequence by concatenating all given sequences on Z dimension.
1298     * 
1299     * @param sequences
1300     *        Sequences to concatenate (use array order).
1301     * @param interlaced
1302     *        Interlace images.<br>
1303     *        normal : 1,1,1,2,2,2,3,3,..<br>
1304     *        interlaced : 1,2,3,1,2,3,..<br>
1305     * @param fillEmpty
1306     *        Replace empty image by the previous non empty one.
1307     * @param rescale
1308     *        Images are scaled to all fit in the same XY dimension.
1309     */
1310    public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale)
1311    {
1312        return concatZ(sequences, interlaced, fillEmpty, rescale, null);
1313
1314    }
1315
1316    /**
1317     * Create and returns a new sequence by concatenating all given sequences on Z dimension.
1318     * 
1319     * @param sequences
1320     *        Sequences to concatenate (use array order).
1321     */
1322    public static Sequence concatZ(Sequence[] sequences)
1323    {
1324        return concatZ(sequences, false, true, false, null);
1325    }
1326
1327    /**
1328     * Create and returns a new sequence by concatenating all given sequences on T dimension.
1329     * 
1330     * @param sequences
1331     *        sequences to concatenate (use array order).
1332     * @param interlaced
1333     *        interlace images.<br>
1334     *        normal : 1,1,1,2,2,2,3,3,..<br>
1335     *        interlaced : 1,2,3,1,2,3,..<br>
1336     * @param fillEmpty
1337     *        replace empty image by the previous non empty one.
1338     * @param rescale
1339     *        Images are scaled to all fit in the same XY dimension.
1340     * @param pl
1341     *        ProgressListener to indicate processing progress.
1342     */
1343    public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale,
1344            ProgressListener pl)
1345    {
1346        final int sizeX = getMaxDim(sequences, DimensionId.X);
1347        final int sizeY = getMaxDim(sequences, DimensionId.Y);
1348        final int sizeC = getMaxDim(sequences, DimensionId.C);
1349        final int sizeZ = getMaxDim(sequences, DimensionId.Z);
1350        int sizeT = 0;
1351
1352        for (Sequence seq : sequences)
1353            sizeT += seq.getSizeT();
1354
1355        final Sequence result = new Sequence();
1356
1357        if (sequences.length > 0)
1358            result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata()));
1359        result.setName("T Merge");
1360
1361        int ind = 0;
1362        for (int t = 0; t < sizeT; t++)
1363        {
1364            for (int z = 0; z < sizeZ; z++)
1365            {
1366                if (pl != null)
1367                    pl.notifyProgress(ind, sizeT * sizeZ);
1368
1369                result.setImage(t, z, IcyBufferedImageUtil.getCopy(
1370                        MergeTHelper.getImage(sequences, sizeX, sizeY, sizeC, t, z, interlaced, fillEmpty, rescale)));
1371
1372                ind++;
1373            }
1374        }
1375
1376        return result;
1377    }
1378
1379    /**
1380     * Create and returns a new sequence by concatenating all given sequences on T dimension.
1381     * 
1382     * @param sequences
1383     *        Sequences to concatenate (use array order).
1384     * @param interlaced
1385     *        Interlace images.<br>
1386     *        normal : 1,1,1,2,2,2,3,3,..<br>
1387     *        interlaced : 1,2,3,1,2,3,..<br>
1388     * @param fillEmpty
1389     *        Replace empty image by the previous non empty one.
1390     * @param rescale
1391     *        Images are scaled to all fit in the same XY dimension.
1392     */
1393    public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale)
1394    {
1395        return concatT(sequences, interlaced, fillEmpty, rescale, null);
1396
1397    }
1398
1399    /**
1400     * Create and returns a new sequence by concatenating all given sequences on T dimension.
1401     * 
1402     * @param sequences
1403     *        Sequences to concatenate (use array order).
1404     */
1405    public static Sequence concatT(Sequence[] sequences)
1406    {
1407        return concatT(sequences, false, true, false, null);
1408    }
1409
1410    /**
1411     * Adjust Z and T dimension of the sequence.
1412     * 
1413     * @param reverseOrder
1414     *        Means that images are T-Z ordered instead of Z-T ordered
1415     * @param newSizeZ
1416     *        New Z size of the sequence
1417     * @param newSizeT
1418     *        New T size of the sequence
1419     */
1420    public static void adjustZT(Sequence sequence, int newSizeZ, int newSizeT, boolean reverseOrder)
1421    {
1422        final int sizeZ = sequence.getSizeZ();
1423        final int sizeT = sequence.getSizeT();
1424
1425        final Sequence tmp = new Sequence();
1426
1427        tmp.beginUpdate();
1428        sequence.beginUpdate();
1429        try
1430        {
1431            try
1432            {
1433                for (int t = 0; t < sizeT; t++)
1434                {
1435                    for (int z = 0; z < sizeZ; z++)
1436                    {
1437                        tmp.setImage(t, z, sequence.getImage(t, z));
1438                        sequence.removeImage(t, z);
1439                    }
1440                }
1441            }
1442            finally
1443            {
1444                tmp.endUpdate();
1445            }
1446
1447            for (int t = 0; t < newSizeT; t++)
1448                for (int z = 0; z < newSizeZ; z++)
1449                    sequence.setImage(t, z, AdjustZTHelper.getImage(tmp, t, z, newSizeZ, newSizeT, reverseOrder));
1450
1451            // to avoid memory leak as images now contained in sequence will 'tmp' sequence forever
1452            tmp.removeAllImages();
1453        }
1454        finally
1455        {
1456            sequence.endUpdate();
1457        }
1458    }
1459
1460    /**
1461     * Build a new single channel sequence (grey) from the specified channel of the source sequence.
1462     * 
1463     * @param source
1464     *        Source sequence
1465     * @param channel
1466     *        Channel index to extract from the source sequence.
1467     * @return Sequence
1468     */
1469    public static Sequence extractChannel(Sequence source, int channel)
1470    {
1471        return extractChannels(source, channel);
1472    }
1473
1474    /**
1475     * @deprecated Use {@link #extractChannels(Sequence, int...)} instead.
1476     */
1477    @Deprecated
1478    public static Sequence extractChannels(Sequence source, List<Integer> channels)
1479    {
1480        final Sequence outSequence = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1481
1482        outSequence.beginUpdate();
1483        try
1484        {
1485            for (int t = 0; t < source.getSizeT(); t++)
1486                for (int z = 0; z < source.getSizeZ(); z++)
1487                    outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels));
1488        }
1489        finally
1490        {
1491            outSequence.endUpdate();
1492        }
1493
1494        // sequence name
1495        if (channels.size() > 1)
1496        {
1497            String s = "";
1498            for (int i = 0; i < channels.size(); i++)
1499                s += " " + channels.get(i).toString();
1500
1501            outSequence.setName(source.getName() + " (channels" + s + ")");
1502        }
1503        else if (channels.size() == 1)
1504            outSequence.setName(source.getName() + " (" + source.getChannelName(channels.get(0).intValue()) + ")");
1505
1506        // channel name
1507        int c = 0;
1508        for (Integer i : channels)
1509        {
1510            outSequence.setChannelName(c, source.getChannelName(i.intValue()));
1511            c++;
1512        }
1513
1514        return outSequence;
1515    }
1516
1517    /**
1518     * Build a new sequence by extracting the specified channels from the source sequence.
1519     * 
1520     * @param source
1521     *        Source sequence
1522     * @param channels
1523     *        Channel indexes to extract from the source sequence.
1524     * @return Sequence
1525     */
1526    public static Sequence extractChannels(Sequence source, int... channels)
1527    {
1528        final Sequence outSequence = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1529        final int sizeT = source.getSizeT();
1530        final int sizeZ = source.getSizeZ();
1531        final int sizeC = source.getSizeC();
1532
1533        outSequence.beginUpdate();
1534        try
1535        {
1536            for (int t = 0; t < sizeT; t++)
1537                for (int z = 0; z < sizeZ; z++)
1538                    outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels));
1539        }
1540        finally
1541        {
1542            outSequence.endUpdate();
1543        }
1544
1545        final OMEXMLMetadata metadata = outSequence.getOMEXMLMetadata();
1546
1547        // remove channel metadata
1548        for (int ch = MetaDataUtil.getNumChannel(metadata, 0) - 1; ch >= 0; ch--)
1549        {
1550            boolean remove = true;
1551
1552            for (int i : channels)
1553            {
1554                if (i == ch)
1555                {
1556                    remove = false;
1557                    break;
1558                }
1559            }
1560
1561            if (remove)
1562                MetaDataUtil.removeChannel(metadata, 0, ch);
1563        }
1564
1565        // sequence name
1566        if (channels.length > 1)
1567        {
1568            String s = "";
1569            for (int i = 0; i < channels.length; i++)
1570                s += " " + channels[i];
1571
1572            outSequence.setName(source.getName() + " (channels" + s + ")");
1573        }
1574        else if (channels.length == 1)
1575            outSequence.setName(source.getName() + " (" + source.getChannelName(channels[0]) + ")");
1576
1577        // copy channel name and colormap
1578        int c = 0;
1579        for (int channel : channels)
1580        {
1581            if (channel < sizeC)
1582            {
1583                outSequence.setChannelName(c, source.getChannelName(channel));
1584                outSequence.setDefaultColormap(c, source.getDefaultColorMap(channel), false);
1585            }
1586
1587            c++;
1588        }
1589
1590        return outSequence;
1591    }
1592
1593    /**
1594     * Build a new sequence by extracting the specified Z slice from the source sequence.
1595     * 
1596     * @param source
1597     *        Source sequence
1598     * @param z
1599     *        Slice index to extract from the source sequence.
1600     * @return Sequence
1601     */
1602    public static Sequence extractSlice(Sequence source, int z)
1603    {
1604        final OMEXMLMetadata metadata = OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata());
1605        final Sequence outSequence = new Sequence(metadata);
1606
1607        // keep only metadata for specified slice
1608        MetaDataUtil.keepPlanes(metadata, 0, -1, z, -1);
1609
1610        outSequence.beginUpdate();
1611        try
1612        {
1613            for (int t = 0; t < source.getSizeT(); t++)
1614                outSequence.setImage(t, 0, IcyBufferedImageUtil.getCopy(source.getImage(t, z)));
1615        }
1616        finally
1617        {
1618            outSequence.endUpdate();
1619        }
1620
1621        outSequence.setName(source.getName() + " (slice " + z + ")");
1622
1623        return outSequence;
1624    }
1625
1626    /**
1627     * Build a new sequence by extracting the specified T frame from the source sequence.
1628     * 
1629     * @param source
1630     *        Source sequence
1631     * @param t
1632     *        Frame index to extract from the source sequence.
1633     * @return Sequence
1634     */
1635    public static Sequence extractFrame(Sequence source, int t)
1636    {
1637        final OMEXMLMetadata metadata = OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata());
1638        final Sequence outSequence = new Sequence(metadata);
1639
1640        // keep only metadata for specified frame
1641        MetaDataUtil.keepPlanes(metadata, 0, t, -1, -1);
1642
1643        outSequence.beginUpdate();
1644        try
1645        {
1646            for (int z = 0; z < source.getSizeZ(); z++)
1647                outSequence.setImage(0, z, IcyBufferedImageUtil.getCopy(source.getImage(t, z)));
1648        }
1649        finally
1650        {
1651            outSequence.endUpdate();
1652        }
1653
1654        outSequence.setName(source.getName() + " (frame " + t + ")");
1655
1656        return outSequence;
1657    }
1658
1659    /**
1660     * Converts the source sequence to the specified data type.<br>
1661     * This method returns a new sequence (the source sequence is not modified).
1662     * 
1663     * @param source
1664     *        Source sequence to convert
1665     * @param dataType
1666     *        Data type wanted
1667     * @param rescale
1668     *        Indicate if we want to scale data value according to data (or data type) range
1669     * @param useDataBounds
1670     *        Only used when <code>rescale</code> parameter is true.<br>
1671     *        Specify if we use the data bounds for rescaling instead of data type bounds.
1672     * @return converted sequence
1673     */
1674    public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale, boolean useDataBounds)
1675    {
1676        if (source == null)
1677            return null;
1678
1679        if (!rescale)
1680            return convertType(source, dataType, null);
1681
1682        // convert with rescale
1683        final double boundsDst[] = dataType.getDefaultBounds();
1684        final int sizeC = source.getSizeC();
1685        final Scaler[] scalers = new Scaler[sizeC];
1686
1687        // build scalers
1688        for (int c = 0; c < sizeC; c++)
1689        {
1690            final double boundsSrc[];
1691
1692            if (useDataBounds)
1693            {
1694                // we need to have data loaded first
1695                source.loadAllData();
1696                boundsSrc = source.getChannelBounds(c);
1697            }
1698            else
1699                boundsSrc = source.getChannelTypeBounds(c);
1700
1701            scalers[c] = new Scaler(boundsSrc[0], boundsSrc[1], boundsDst[0], boundsDst[1], false);
1702        }
1703
1704        // use scaler to scale data
1705        return convertType(source, dataType, scalers);
1706    }
1707
1708    /**
1709     * Converts the source sequence to the specified data type.<br>
1710     * This method returns a new sequence (the source sequence is not modified).
1711     * 
1712     * @param source
1713     *        Source sequence to convert
1714     * @param dataType
1715     *        data type wanted
1716     * @param rescale
1717     *        indicate if we want to scale data value according to data type range
1718     * @return converted sequence
1719     */
1720    public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale)
1721    {
1722        return convertToType(source, dataType, rescale, false);
1723    }
1724
1725    /**
1726     * @deprecated Use {@link #convertType(Sequence, DataType, Scaler[])} instead.
1727     */
1728    @Deprecated
1729    public static Sequence convertToType(Sequence source, DataType dataType, Scaler scaler)
1730    {
1731        final Sequence output = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1732
1733        output.beginUpdate();
1734        try
1735        {
1736            for (int t = 0; t < source.getSizeT(); t++)
1737            {
1738                for (int z = 0; z < source.getSizeZ(); z++)
1739                {
1740                    final IcyBufferedImage converted = IcyBufferedImageUtil.convertToType(source.getImage(t, z),
1741                            dataType, scaler);
1742
1743                    // FIXME : why we did that ??
1744                    // this is not a good idea to force bounds when rescale = false
1745
1746                    // set bounds manually for the converted image
1747                    // for (int c = 0; c < getSizeC(); c++)
1748                    // {
1749                    // converted.setComponentBounds(c, boundsDst);
1750                    // converted.setComponentUserBounds(c, boundsDst);
1751                    // }
1752
1753                    output.setImage(t, z, converted);
1754                }
1755            }
1756
1757            output.setName(source.getName() + " (" + output.getDataType_() + ")");
1758        }
1759        finally
1760        {
1761            output.endUpdate();
1762        }
1763
1764        return output;
1765    }
1766
1767    /**
1768     * Converts the source sequence to the specified data type.<br>
1769     * This method returns a new sequence (the source sequence is not modified).
1770     * 
1771     * @param source
1772     *        Source sequence to convert
1773     * @param dataType
1774     *        data type wanted.
1775     * @param scalers
1776     *        scalers for scaling internal data during conversion (1 scaler per channel).<br>
1777     *        Can be set to <code>null</code> to avoid value conversion.
1778     * @return converted image
1779     */
1780    public static Sequence convertType(Sequence source, DataType dataType, Scaler[] scalers)
1781    {
1782        final Sequence output = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1783
1784        output.beginUpdate();
1785        try
1786        {
1787            for (int t = 0; t < source.getSizeT(); t++)
1788            {
1789                for (int z = 0; z < source.getSizeZ(); z++)
1790                {
1791                    final IcyBufferedImage converted = IcyBufferedImageUtil.convertType(source.getImage(t, z), dataType,
1792                            scalers);
1793
1794                    // FIXME : why we did that ??
1795                    // this is not a good idea to force bounds when rescale = false
1796
1797                    // set bounds manually for the converted image
1798                    // for (int c = 0; c < getSizeC(); c++)
1799                    // {
1800                    // converted.setComponentBounds(c, boundsDst);
1801                    // converted.setComponentUserBounds(c, boundsDst);
1802                    // }
1803
1804                    output.setImage(t, z, converted);
1805                }
1806            }
1807        }
1808        finally
1809        {
1810            output.endUpdate();
1811        }
1812
1813        // preserve channel informations
1814        for (int c = 0; c < source.getSizeC(); c++)
1815        {
1816            output.setChannelName(c, source.getChannelName(c));
1817            output.setDefaultColormap(c, source.getDefaultColorMap(c), true);
1818            // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT'
1819            // based on current channel bounds
1820            output.setColormap(c, source.getColorMap(c));
1821        }
1822
1823        // and finally set name
1824        output.setName(source.getName() + " (" + output.getDataType_() + ")");
1825
1826        return output;
1827    }
1828
1829    /**
1830     * Return a rotated version of the source sequence with specified parameters.
1831     * 
1832     * @param source
1833     *        source image
1834     * @param xOrigin
1835     *        X origin for the rotation
1836     * @param yOrigin
1837     *        Y origin for the rotation
1838     * @param angle
1839     *        rotation angle in radian
1840     * @param filterType
1841     *        filter resampling method used
1842     */
1843    public static Sequence rotate(Sequence source, double xOrigin, double yOrigin, double angle, FilterType filterType)
1844    {
1845        final int sizeT = source.getSizeT();
1846        final int sizeZ = source.getSizeZ();
1847        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1848
1849        result.beginUpdate();
1850        try
1851        {
1852            for (int t = 0; t < sizeT; t++)
1853                for (int z = 0; z < sizeZ; z++)
1854                    result.setImage(t, z,
1855                            IcyBufferedImageUtil.rotate(source.getImage(t, z), xOrigin, yOrigin, angle, filterType));
1856        }
1857        finally
1858        {
1859            result.endUpdate();
1860        }
1861
1862        // preserve channel informations
1863        for (int c = 0; c < source.getSizeC(); c++)
1864        {
1865            result.setChannelName(c, source.getChannelName(c));
1866            result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
1867            // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT'
1868            // based on current channel bounds
1869            result.setColormap(c, source.getColorMap(c));
1870        }
1871
1872        result.setName(source.getName() + " (rotated)");
1873
1874        return result;
1875    }
1876
1877    /**
1878     * Return a rotated version of the source Sequence with specified parameters.
1879     * 
1880     * @param source
1881     *        source image
1882     * @param angle
1883     *        rotation angle in radian
1884     * @param filterType
1885     *        filter resampling method used
1886     */
1887    public static Sequence rotate(Sequence source, double angle, FilterType filterType)
1888    {
1889        if (source == null)
1890            return null;
1891
1892        return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, filterType);
1893    }
1894
1895    /**
1896     * Return a rotated version of the source Sequence with specified parameters.
1897     * 
1898     * @param source
1899     *        source image
1900     * @param angle
1901     *        rotation angle in radian
1902     */
1903    public static Sequence rotate(Sequence source, double angle)
1904    {
1905        if (source == null)
1906            return null;
1907
1908        return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, FilterType.BILINEAR);
1909    }
1910
1911    /**
1912     * Return a copy of the source sequence with specified size, alignment rules and filter type.
1913     * 
1914     * @param source
1915     *        source sequence
1916     * @param resizeContent
1917     *        indicate if content should be resized or not (empty area are 0 filled)
1918     * @param xAlign
1919     *        horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
1920     *        (used only if resizeContent is false)
1921     * @param yAlign
1922     *        vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
1923     *        (used only if resizeContent is false)
1924     * @param filterType
1925     *        filter method used for scale (used only if resizeContent is true)
1926     */
1927    public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign,
1928            FilterType filterType)
1929    {
1930        final int sizeT = source.getSizeT();
1931        final int sizeZ = source.getSizeZ();
1932        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
1933
1934        result.beginUpdate();
1935        try
1936        {
1937            for (int t = 0; t < sizeT; t++)
1938                for (int z = 0; z < sizeZ; z++)
1939                    result.setImage(t, z, IcyBufferedImageUtil.scale(source.getImage(t, z), width, height,
1940                            resizeContent, xAlign, yAlign, filterType));
1941        }
1942        finally
1943        {
1944            result.endUpdate();
1945        }
1946
1947        // preserve channel informations
1948        for (int c = 0; c < source.getSizeC(); c++)
1949        {
1950            result.setChannelName(c, source.getChannelName(c));
1951            result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
1952            // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT'
1953            // based on current channel bounds
1954            result.setColormap(c, source.getColorMap(c));
1955        }
1956
1957        result.setName(source.getName() + " (resized)");
1958
1959        // content was resized ?
1960        if (resizeContent)
1961        {
1962            final double sx = (double) source.getSizeX() / result.getSizeX();
1963            final double sy = (double) source.getSizeY() / result.getSizeY();
1964
1965            // update pixel size
1966            if ((sx != 0d) && !Double.isInfinite(sx))
1967                result.setPixelSizeX(result.getPixelSizeX() * sx);
1968            if ((sy != 0d) && !Double.isInfinite(sy))
1969                result.setPixelSizeY(result.getPixelSizeY() * sy);
1970        }
1971
1972        return result;
1973    }
1974
1975    /**
1976     * Return a copy of the sequence with specified size.<br>
1977     * By default the FilterType.BILINEAR is used as filter method if resizeContent is true
1978     * 
1979     * @param source
1980     *        source sequence
1981     * @param resizeContent
1982     *        indicate if content should be resized or not (empty area are 0 filled)
1983     * @param xAlign
1984     *        horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
1985     *        (used only if resizeContent is false)
1986     * @param yAlign
1987     *        vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
1988     *        (used only if resizeContent is false)
1989     */
1990    public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign)
1991    {
1992        return scale(source, width, height, resizeContent, xAlign, yAlign, FilterType.BILINEAR);
1993    }
1994
1995    /**
1996     * Return a copy of the sequence with specified size.
1997     * 
1998     * @param source
1999     *        source sequence
2000     * @param filterType
2001     *        filter method used for scale (used only if resizeContent is true)
2002     */
2003    public static Sequence scale(Sequence source, int width, int height, FilterType filterType)
2004    {
2005        return scale(source, width, height, true, 0, 0, filterType);
2006    }
2007
2008    /**
2009     * Return a copy of the sequence with specified size.<br>
2010     * By default the FilterType.BILINEAR is used as filter method.
2011     */
2012    public static Sequence scale(Sequence source, int width, int height)
2013    {
2014        return scale(source, width, height, FilterType.BILINEAR);
2015    }
2016
2017    /**
2018     * Creates a new sequence from the specified region of the source sequence.
2019     */
2020    public static Sequence getSubSequence(Sequence source, Rectangle5D.Integer region)
2021    {
2022        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
2023
2024        final Rectangle region2d = region.toRectangle2D().getBounds();
2025        final int startZ;
2026        final int endZ;
2027        final int startT;
2028        final int endT;
2029        final int startC;
2030        final int endC;
2031
2032        if (region.isInfiniteZ())
2033        {
2034            startZ = 0;
2035            endZ = source.getSizeZ();
2036        }
2037        else
2038        {
2039            startZ = Math.max(0, region.z);
2040            endZ = Math.min(source.getSizeZ(), region.z + region.sizeZ);
2041        }
2042        if (region.isInfiniteT())
2043        {
2044            startT = 0;
2045            endT = source.getSizeT();
2046        }
2047        else
2048        {
2049            startT = Math.max(0, region.t);
2050            endT = Math.min(source.getSizeT(), region.t + region.sizeT);
2051        }
2052        if (region.isInfiniteC())
2053        {
2054            startC = 0;
2055            endC = source.getSizeC();
2056        }
2057        else
2058        {
2059            startC = Math.max(0, region.c);
2060            endC = Math.min(source.getSizeC(), region.c + region.sizeC);
2061        }
2062
2063        result.beginUpdate();
2064        try
2065        {
2066            for (int t = startT; t < endT; t++)
2067            {
2068                for (int z = startZ; z < endZ; z++)
2069                {
2070                    IcyBufferedImage img = source.getImage(t, z);
2071
2072                    if (img != null)
2073                        img = IcyBufferedImageUtil.getSubImage(img, region2d, startC, (endC - startC) + 1);
2074
2075                    result.setImage(t - startT, z - startZ, img);
2076                }
2077            }
2078        }
2079        finally
2080        {
2081            result.endUpdate();
2082        }
2083
2084        // preserve channel informations
2085        for (int c = startC; c < endC; c++)
2086        {
2087            result.setChannelName(c - startC, source.getChannelName(c));
2088            result.setDefaultColormap(c - startC, source.getDefaultColorMap(c), true);
2089            // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT'
2090            // based on current channel bounds
2091            result.setColormap(c - startC, source.getColorMap(c));
2092        }
2093
2094        result.setName(source.getName() + " (crop)");
2095
2096        // adjust position X, Y, Z
2097        result.setPositionX(source.getPositionX() + (region2d.x * source.getPixelSizeX()));
2098        result.setPositionY(source.getPositionY() + (region2d.y * source.getPixelSizeY()));
2099        result.setPositionZ(source.getPositionZ() + (startZ * source.getPixelSizeZ()));
2100        // adjust TimeStamp
2101        result.setTimeStamp(source.getTimeStamp() + (long) (source.getPositionTOffset(startT, startZ, startC) * 1000d));
2102
2103        return result;
2104    }
2105
2106    /**
2107     * @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead.
2108     */
2109    @Deprecated
2110    public static Sequence getSubSequence(Sequence source, int startX, int startY, int startC, int startZ, int startT,
2111            int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT)
2112    {
2113        return getSubSequence(source,
2114                new Rectangle5D.Integer(startX, startY, startZ, startT, startC, sizeX, sizeY, sizeZ, sizeT, sizeC));
2115    }
2116
2117    /**
2118     * @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead.
2119     */
2120    @Deprecated
2121    public static Sequence getSubSequence(Sequence source, int startX, int startY, int startZ, int startT, int sizeX,
2122            int sizeY, int sizeZ, int sizeT)
2123    {
2124        return getSubSequence(source, startX, startY, 0, startZ, startT, sizeX, sizeY, source.getSizeC(), sizeZ, sizeT);
2125    }
2126
2127    /**
2128     * Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI}
2129     * bounds.<br>
2130     * 
2131     * @param source
2132     *        the source sequence
2133     * @param roi
2134     *        used to define to region to retain.
2135     * @param nullValue
2136     *        the returned sequence is created by using the ROI rectangular bounds.<br>
2137     *        if <code>nullValue</code> is different of <code>Double.NaN</code> then any pixel
2138     *        outside the ROI region will be set to <code>nullValue</code>
2139     */
2140    public static Sequence getSubSequence(Sequence source, ROI roi, double nullValue)
2141    {
2142        final Rectangle5D.Integer bounds = roi.getBounds5D().toInteger();
2143        final Sequence result = getSubSequence(source, bounds);
2144
2145        // use null value ?
2146        if (!Double.isNaN(nullValue))
2147        {
2148            final int offX = (bounds.x == Integer.MIN_VALUE) ? 0 : (int) bounds.x;
2149            final int offY = (bounds.y == Integer.MIN_VALUE) ? 0 : (int) bounds.y;
2150            final int offZ = (bounds.z == Integer.MIN_VALUE) ? 0 : (int) bounds.z;
2151            final int offT = (bounds.t == Integer.MIN_VALUE) ? 0 : (int) bounds.t;
2152            final int offC = (bounds.c == Integer.MIN_VALUE) ? 0 : (int) bounds.c;
2153            final int sizeX = result.getSizeX();
2154            final int sizeY = result.getSizeY();
2155            final int sizeZ = result.getSizeZ();
2156            final int sizeT = result.getSizeT();
2157            final int sizeC = result.getSizeC();
2158            final DataType dataType = result.getDataType_();
2159
2160            result.beginUpdate();
2161            try
2162            {
2163                for (int t = 0; t < sizeT; t++)
2164                {
2165                    for (int z = 0; z < sizeZ; z++)
2166                    {
2167                        for (int c = 0; c < sizeC; c++)
2168                        {
2169                            final BooleanMask2D mask = roi.getBooleanMask2D(z + offZ, t + offT, c + offC, false);
2170                            final IcyBufferedImage img = result.getImage(t, z);
2171
2172                            img.lockRaster();
2173                            try
2174                            {
2175                                final Object data = img.getDataXY(c);
2176                                int offset = 0;
2177
2178                                for (int y = 0; y < sizeY; y++)
2179                                    for (int x = 0; x < sizeX; x++, offset++)
2180                                        if (!mask.contains(x + offX, y + offY))
2181                                            Array1DUtil.setValue(data, offset, dataType, nullValue);
2182                            }
2183                            finally
2184                            {
2185                                img.releaseRaster(true);
2186                            }
2187
2188                            img.dataChanged();
2189                        }
2190                    }
2191                }
2192            }
2193            finally
2194            {
2195                result.endUpdate();
2196            }
2197        }
2198
2199        return result;
2200    }
2201
2202    /**
2203     * Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI} bounds.
2204     */
2205    public static Sequence getSubSequence(Sequence source, ROI roi)
2206    {
2207        return getSubSequence(source, roi, Double.NaN);
2208    }
2209
2210    /**
2211     * Creates and return a copy of the sequence.
2212     * 
2213     * @param source
2214     *        the source sequence to copy
2215     * @param copyROI
2216     *        Copy the ROI from source sequence.<br>
2217     *        Warning: by doing that the ROI will retain the result sequence as long the source sequence is alive.
2218     * @param copyOverlay
2219     *        Copy the Overlay from source sequence.<br>
2220     *        Warning: by doing that the Overlay will retain the result sequence as long the source sequence is alive.
2221     * @param nameSuffix
2222     *        add the suffix <i>" (copy)"</i> to the new Sequence name to distinguish it
2223     */
2224    public static Sequence getCopy(Sequence source, boolean copyROI, boolean copyOverlay, boolean nameSuffix)
2225    {
2226        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
2227
2228        result.beginUpdate();
2229        try
2230        {
2231            result.copyDataFrom(source);
2232            if (copyROI)
2233            {
2234                for (ROI roi : source.getROIs())
2235                    result.addROI(roi);
2236            }
2237            if (copyOverlay)
2238            {
2239                for (Overlay overlay : source.getOverlays())
2240                    result.addOverlay(overlay);
2241            }
2242        }
2243        finally
2244        {
2245            result.endUpdate();
2246        }
2247
2248        // preserve channel informations
2249        for (int c = 0; c < source.getSizeC(); c++)
2250        {
2251            result.setChannelName(c, source.getChannelName(c));
2252            result.setDefaultColormap(c, source.getDefaultColorMap(c), true);
2253            // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT'
2254            // based on current channel bounds
2255            result.setColormap(c, source.getColorMap(c));
2256        }
2257
2258        if (nameSuffix)
2259            result.setName(source.getName() + " (copy)");
2260
2261        return result;
2262    }
2263
2264    /**
2265     * Creates and return a copy of the sequence.<br>
2266     * Note that only data and metadata are copied, overlays and ROIs are not preserved.
2267     */
2268    public static Sequence getCopy(Sequence source)
2269    {
2270        return getCopy(source, false, false, true);
2271    }
2272
2273    /**
2274     * Convert the specified sequence to gray sequence (single channel)
2275     */
2276    public static Sequence toGray(Sequence source)
2277    {
2278        return convertColor(source, BufferedImage.TYPE_BYTE_GRAY, null);
2279    }
2280
2281    /**
2282     * Convert the specified sequence to RGB sequence (3 channels)
2283     */
2284    public static Sequence toRGB(Sequence source)
2285    {
2286        return convertColor(source, BufferedImage.TYPE_INT_RGB, null);
2287    }
2288
2289    /**
2290     * Convert the specified sequence to ARGB sequence (4 channels)
2291     */
2292    public static Sequence toARGB(Sequence source)
2293    {
2294        return convertColor(source, BufferedImage.TYPE_INT_ARGB, null);
2295    }
2296
2297    /**
2298     * Do color conversion of the specified {@link Sequence} into the specified type.<br>
2299     * The resulting Sequence will have 4, 3 or 1 channel(s) depending the selected type.
2300     * 
2301     * @param source
2302     *        source sequence
2303     * @param imageType
2304     *        wanted image type, only the following is accepted :<br>
2305     *        BufferedImage.TYPE_INT_ARGB (4 channels)<br>
2306     *        BufferedImage.TYPE_INT_RGB (3 channels)<br>
2307     *        BufferedImage.TYPE_BYTE_GRAY (1 channel)<br>
2308     * @param lut
2309     *        lut used for color calculation (source sequence lut is used if null)
2310     */
2311    public static Sequence convertColor(Sequence source, int imageType, LUT lut)
2312    {
2313        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()));
2314        // image receiver
2315        final BufferedImage imgOut = new BufferedImage(source.getSizeX(), source.getSizeY(), imageType);
2316
2317        result.beginUpdate();
2318        try
2319        {
2320            for (int t = 0; t < source.getSizeT(); t++)
2321                for (int z = 0; z < source.getSizeZ(); z++)
2322                    result.setImage(t, z, IcyBufferedImageUtil.toBufferedImage(source.getImage(t, z), imgOut, lut));
2323
2324            // rename channels and set final name
2325            switch (imageType)
2326            {
2327                default:
2328                case BufferedImage.TYPE_INT_ARGB:
2329                    result.setChannelName(0, "red");
2330                    result.setChannelName(1, "green");
2331                    result.setChannelName(2, "blue");
2332                    result.setChannelName(3, "alpha");
2333                    result.setName(source.getName() + " (ARGB rendering)");
2334                    break;
2335
2336                case BufferedImage.TYPE_INT_RGB:
2337                    result.setChannelName(0, "red");
2338                    result.setChannelName(1, "green");
2339                    result.setChannelName(2, "blue");
2340                    result.setName(source.getName() + " (RGB rendering)");
2341                    break;
2342
2343                case BufferedImage.TYPE_BYTE_GRAY:
2344                    result.setChannelName(0, "gray");
2345                    result.setName(source.getName() + " (gray rendering)");
2346                    break;
2347            }
2348        }
2349        finally
2350        {
2351            result.endUpdate();
2352        }
2353
2354        return result;
2355    }
2356
2357    /**
2358     * Convert the given Point2D coordinate from an input resolution and a wanted output resolution level (0/1/2/3/...)
2359     * 
2360     * @see Sequence#getOriginResolution()
2361     */
2362    public static Point2D convertPoint(Point2D pt, int inputResolution, int outputResolution)
2363    {
2364        if (pt == null)
2365            return null;
2366
2367        final double factor = Math.pow(2, inputResolution - outputResolution);
2368
2369        return new Point2D.Double(pt.getX() * factor, pt.getY() * factor);
2370    }
2371
2372    /**
2373     * Convert the given Rectangle2D from an input resolution and a wanted output resolution level (0/1/2/3/...)
2374     * 
2375     * @see Sequence#getOriginResolution()
2376     */
2377    public static Rectangle2D convertRectangle(Rectangle2D rect, int inputResolution, int outputResolution)
2378    {
2379        if (rect == null)
2380            return null;
2381
2382        final double factor = Math.pow(2, inputResolution - outputResolution);
2383
2384        return new Rectangle2D.Double(rect.getX() * factor, rect.getY() * factor, rect.getWidth() * factor,
2385                rect.getHeight() * factor);
2386    }
2387
2388    /**
2389     * Convert the given Point2D coordinate from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br>
2390     * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion.
2391     */
2392    public static Point3D convertPoint(Point3D pt, Sequence source, Sequence destination)
2393    {
2394        if (pt == null)
2395            return new Point3D.Double();
2396
2397        if ((source == null) || (destination == null))
2398            return new Point3D.Double(pt.getX(), pt.getY(), pt.getZ());
2399
2400        // get global position in um
2401        final double posXum = (pt.getX() * source.getPixelSizeX()) + source.getPositionX();
2402        final double posYum = (pt.getY() * source.getPixelSizeY()) + source.getPositionY();
2403        final double posZum = (pt.getZ() * source.getPixelSizeZ()) + source.getPositionZ();
2404
2405        // convert to destination
2406        return new Point3D.Double((posXum - destination.getPositionX()) / destination.getPixelSizeX(),
2407                (posYum - destination.getPositionY()) / destination.getPixelSizeY(),
2408                (posZum - destination.getPositionZ()) / destination.getPixelSizeZ());
2409    }
2410
2411    /**
2412     * Convert the given Point2D coordinate from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br>
2413     * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion.
2414     */
2415    public static Point2D convertPoint(Point2D pt, Sequence source, Sequence destination)
2416    {
2417        if (pt == null)
2418            return new Point2D.Double();
2419
2420        return convertPoint(new Point3D.Double(pt.getX(), pt.getY(), 0d), source, destination).toPoint2D();
2421    }
2422
2423    /**
2424     * Convert the given {@link Rectangle3D} from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br>
2425     * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion.
2426     */
2427    public static Rectangle3D convertRectangle(Rectangle3D rect, Sequence source, Sequence destination)
2428    {
2429        if (rect == null)
2430            return new Rectangle3D.Double();
2431
2432        if ((source == null) || (destination == null))
2433            return new Rectangle3D.Double(rect);
2434
2435        // get global rectangle in um
2436        final double psxs = source.getPixelSizeX();
2437        final double psys = source.getPixelSizeY();
2438        final double pszs = source.getPixelSizeZ();
2439        final double posXum = (rect.getX() * psxs) + source.getPositionX();
2440        final double posYum = (rect.getY() * psys) + source.getPositionY();
2441        final double posZum = (rect.getZ() * pszs) + source.getPositionZ();
2442        final double sizeXum = rect.getX() * psxs;
2443        final double sizeYum = rect.getY() * psys;
2444        final double sizeZum = rect.getZ() * pszs;
2445
2446        // convert to destination
2447        final double psxd = destination.getPixelSizeX();
2448        final double psyd = destination.getPixelSizeY();
2449        final double pszd = destination.getPixelSizeZ();
2450        return new Rectangle3D.Double((posXum - destination.getPositionX()) / psxd,
2451                (posYum - destination.getPositionY()) / psyd, (posZum - destination.getPositionZ()) / pszd,
2452                sizeXum / psxd, sizeYum / psyd, sizeZum / pszd);
2453    }
2454
2455    /**
2456     * Convert the given {@link Rectangle2D} from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br>
2457     * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion.
2458     */
2459    public static Rectangle2D convertRectangle(Rectangle2D rect, Sequence source, Sequence destination)
2460    {
2461        if (rect == null)
2462            return new Rectangle2D.Double();
2463
2464        return convertRectangle(
2465                new Rectangle3D.Double(rect.getX(), rect.getY(), 0d, rect.getWidth(), rect.getHeight(), 0d), source,
2466                destination).toRectangle2D();
2467    }
2468
2469    /**
2470     * Convert the given Point coordinate from the source Sequence into the original image coordinate (pixel)<br>
2471     * This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations
2472     * to compute the original image position.
2473     * 
2474     * @see Sequence#getOriginResolution()
2475     * @see Sequence#getOriginXYRegion()
2476     */
2477    public static Point getOriginPoint(Point pt, Sequence source)
2478    {
2479        if (pt == null)
2480            return null;
2481
2482        final Point2D adjPt = convertPoint(pt, source.getOriginResolution(), 0);
2483        final Point result = new Point((int) adjPt.getX(), (int) adjPt.getY());
2484
2485        final Rectangle region = source.getOriginXYRegion();
2486        if (region != null)
2487            result.setLocation(result.x + region.x, result.y + region.y);
2488
2489        return result;
2490    }
2491
2492    /**
2493     * Convert the given Rectangle region from the source Sequence into the original image region coordinates
2494     * (pixel)<br>
2495     * This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations
2496     * to compute the original image region coordinates.
2497     * 
2498     * @see Sequence#getOriginResolution()
2499     * @see Sequence#getOriginXYRegion()
2500     */
2501    public static Rectangle getOriginRectangle(Rectangle rect, Sequence source)
2502    {
2503        if (rect == null)
2504            return null;
2505
2506        final Rectangle2D adjRect = convertRectangle(rect, source.getOriginResolution(), 0);
2507        final Rectangle result = new Rectangle((int) adjRect.getX(), (int) adjRect.getY(), (int) adjRect.getWidth(),
2508                (int) adjRect.getHeight());
2509
2510        final Rectangle region = source.getOriginXYRegion();
2511        if (region != null)
2512            result.setLocation(result.x + region.x, result.y + region.y);
2513
2514        return result;
2515    }
2516}