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.roi;
020
021import java.awt.Point;
022import java.awt.Rectangle;
023import java.awt.geom.Line2D;
024import java.awt.geom.Point2D;
025import java.awt.geom.Rectangle2D;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032
033import icy.image.IcyBufferedImage;
034import icy.image.IntensityInfo;
035import icy.math.DataIteratorMath;
036import icy.math.MathUtil;
037import icy.painter.Anchor2D;
038import icy.painter.Anchor3D;
039import icy.plugin.interface_.PluginROIDescriptor;
040import icy.sequence.Sequence;
041import icy.sequence.SequenceDataIterator;
042import icy.sequence.SequenceUtil;
043import icy.type.DataIteratorUtil;
044import icy.type.DataType;
045import icy.type.collection.CollectionUtil;
046import icy.type.dimension.Dimension5D;
047import icy.type.geom.Line2DUtil;
048import icy.type.geom.Polygon2D;
049import icy.type.point.Point3D;
050import icy.type.point.Point4D;
051import icy.type.point.Point5D;
052import icy.type.rectangle.Rectangle2DUtil;
053import icy.type.rectangle.Rectangle3D;
054import icy.type.rectangle.Rectangle4D;
055import icy.type.rectangle.Rectangle5D;
056import icy.util.ShapeUtil.BooleanOperator;
057import plugins.kernel.roi.descriptor.intensity.ROIIntensityDescriptorsPlugin;
058import plugins.kernel.roi.descriptor.intensity.ROIMaxIntensityDescriptor;
059import plugins.kernel.roi.descriptor.intensity.ROIMeanIntensityDescriptor;
060import plugins.kernel.roi.descriptor.intensity.ROIMinIntensityDescriptor;
061import plugins.kernel.roi.descriptor.intensity.ROIStandardDeviationDescriptor;
062import plugins.kernel.roi.descriptor.intensity.ROISumIntensityDescriptor;
063import plugins.kernel.roi.descriptor.measure.ROIAreaDescriptor;
064import plugins.kernel.roi.descriptor.measure.ROIBasicMeasureDescriptorsPlugin;
065import plugins.kernel.roi.descriptor.measure.ROIContourDescriptor;
066import plugins.kernel.roi.descriptor.measure.ROIInteriorDescriptor;
067import plugins.kernel.roi.descriptor.measure.ROIMassCenterDescriptorsPlugin;
068import plugins.kernel.roi.descriptor.measure.ROIPerimeterDescriptor;
069import plugins.kernel.roi.descriptor.measure.ROISurfaceAreaDescriptor;
070import plugins.kernel.roi.descriptor.measure.ROIVolumeDescriptor;
071import plugins.kernel.roi.roi2d.ROI2DArea;
072import plugins.kernel.roi.roi2d.ROI2DEllipse;
073import plugins.kernel.roi.roi2d.ROI2DPoint;
074import plugins.kernel.roi.roi2d.ROI2DPolygon;
075import plugins.kernel.roi.roi2d.ROI2DRectShape;
076import plugins.kernel.roi.roi2d.ROI2DRectangle;
077import plugins.kernel.roi.roi2d.ROI2DShape;
078import plugins.kernel.roi.roi3d.ROI3DArea;
079import plugins.kernel.roi.roi3d.ROI3DPoint;
080import plugins.kernel.roi.roi3d.ROI3DShape;
081import plugins.kernel.roi.roi3d.ROI3DStackEllipse;
082import plugins.kernel.roi.roi3d.ROI3DStackPolygon;
083import plugins.kernel.roi.roi3d.ROI3DStackRectangle;
084import plugins.kernel.roi.roi4d.ROI4DArea;
085import plugins.kernel.roi.roi5d.ROI5DArea;
086
087/**
088 * ROI utilities class.
089 * 
090 * @author Stephane
091 */
092public class ROIUtil
093{
094    final public static String STACK_SUFFIX = " stack";
095    final public static String MASK_SUFFIX = " mask";
096    final public static String SHAPE_SUFFIX = " shape";
097    final public static String OBJECT_SUFFIX = " object";
098    final public static String PART_SUFFIX = " part";
099
100    /**
101     * Returns all available ROI descriptors (see {@link ROIDescriptor}) and their attached plugin
102     * (see {@link PluginROIDescriptor}).<br/>
103     * This list can be extended by installing new plugin(s) implementing the {@link PluginROIDescriptor}
104     * interface.<br/>
105     * This method is an alias of {@link ROIDescriptor#getDescriptors()}
106     * 
107     * @see ROIDescriptor#compute(ROI, Sequence)
108     * @see PluginROIDescriptor#compute(ROI, Sequence)
109     */
110    public static Map<ROIDescriptor, PluginROIDescriptor> getROIDescriptors()
111    {
112        return ROIDescriptor.getDescriptors();
113    }
114
115    /**
116     * Computes the specified descriptor from the input {@link ROIDescriptor} set on given ROI
117     * and returns the result (or <code>null</code> if the descriptor is not found).<br/>
118     * This method is an alias of {@link ROIDescriptor#computeDescriptor(Collection, String, ROI, Sequence)}
119     * 
120     * @param roiDescriptors
121     *        the input {@link ROIDescriptor} set (see {@link #getROIDescriptors()} method)
122     * @param descriptorId
123     *        the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for
124     *        instance)
125     * @param roi
126     *        the ROI on which the descriptor(s) should be computed
127     * @param sequence
128     *        an optional sequence where the pixel size can be retrieved
129     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
130     *         specified set
131     * @throws UnsupportedOperationException
132     *         if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is
133     *         <code>null</code> while the calculation requires it, or if
134     *         the specified Z, T or C position are not supported by the descriptor
135     */
136    public static Object computeDescriptor(Collection<ROIDescriptor> roiDescriptors, String descriptorId, ROI roi,
137            Sequence sequence)
138    {
139        return ROIDescriptor.computeDescriptor(roiDescriptors, descriptorId, roi, sequence);
140    }
141
142    /**
143     * @deprecated Use {@link ROIDescriptor#computeDescriptor(Collection, String, ROI, Sequence)} instead
144     */
145    @Deprecated
146    public static Object computeDescriptor(Set<ROIDescriptor> roiDescriptors, String descriptorId, ROI roi,
147            Sequence sequence)
148    {
149        return ROIDescriptor.computeDescriptor(roiDescriptors, descriptorId, roi, sequence);
150    }
151
152    /**
153     * Computes the specified descriptor on given ROI and returns the result (or <code>null</code> if the descriptor is
154     * not found).<br/>
155     * This method is an alias of {@link ROIDescriptor#computeDescriptor(String, ROI, Sequence)}
156     * 
157     * @param descriptorId
158     *        the id of the descriptor we want to compute ({@link ROIBasicMeasureDescriptorsPlugin#ID_VOLUME} for
159     *        instance)
160     * @param roi
161     *        the ROI on which the descriptor(s) should be computed
162     * @param sequence
163     *        an optional sequence where the pixel size can be retrieved
164     * @return the computed descriptor or <code>null</code> if the descriptor if not found in the
165     *         specified set
166     * @throws UnsupportedOperationException
167     *         if the type of the given ROI is not supported by this descriptor, or if <code>sequence</code> is
168     *         <code>null</code> while the calculation requires it, or if
169     *         the specified Z, T or C position are not supported by the descriptor
170     */
171    public static Object computeDescriptor(String descriptorId, ROI roi, Sequence sequence)
172    {
173        return ROIDescriptor.computeDescriptor(descriptorId, roi, sequence);
174    }
175
176    /**
177     * @deprecated Use {@link ROIStandardDeviationDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)}
178     *             method instead.
179     */
180    @Deprecated
181    public static double getStandardDeviation(Sequence sequence, ROI roi, int z, int t, int c)
182    {
183        try
184        {
185            final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c);
186
187            long numPixels = 0;
188            double sum = 0;
189            double sum2 = 0;
190
191            // faster to do all calculation in a single iteration run
192            while (!it.done())
193            {
194                final double value = it.get();
195
196                sum += value;
197                sum2 += value * value;
198                numPixels++;
199
200                it.next();
201            }
202
203            if (numPixels > 0)
204            {
205                double x1 = (sum2 / numPixels);
206                double x2 = sum / numPixels;
207                x2 *= x2;
208
209                return Math.sqrt(x1 - x2);
210            }
211        }
212        catch (Exception e)
213        {
214            // we can have exception as the process can be really long
215            // and size modified during this period
216        }
217
218        return 0d;
219    }
220
221    /**
222     * @deprecated Use {@link ROIIntensityDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} method
223     *             instead.
224     */
225    @Deprecated
226    public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi, int z, int t, int c)
227    {
228        try
229        {
230            final IntensityInfo result = new IntensityInfo();
231            final SequenceDataIterator it = new SequenceDataIterator(sequence, roi, false, z, t, c);
232
233            long numPixels = 0;
234            double min = Double.MAX_VALUE;
235            double max = -Double.MAX_VALUE;
236            double sum = 0;
237
238            // faster to do all calculation in a single iteration run
239            while (!it.done())
240            {
241                final double value = it.get();
242
243                if (value < min)
244                    min = value;
245                if (value > max)
246                    max = value;
247                sum += value;
248                numPixels++;
249
250                it.next();
251            }
252
253            if (numPixels > 0)
254            {
255                result.minIntensity = min;
256                result.maxIntensity = max;
257                result.meanIntensity = sum / numPixels;
258            }
259            else
260            {
261                result.minIntensity = 0d;
262                result.maxIntensity = 0d;
263                result.meanIntensity = 0d;
264            }
265
266            return result;
267        }
268        catch (Exception e)
269        {
270            // we can have exception as the process can be really long
271            // and size modified during this period
272            return null;
273        }
274    }
275
276    /**
277     * Returns the number of sequence pixels contained in the specified ROI.
278     * 
279     * @param sequence
280     *        The sequence we want to get the number of pixel.
281     * @param roi
282     *        The ROI define the region where we want to compute the number of pixel.
283     * @param z
284     *        The specific Z position (slice) where we want to compute the number of pixel or <code>-1</code> to use the
285     *        ROI Z dimension information.
286     * @param t
287     *        The specific T position (frame) where we want to compute the number of pixel or <code>-1</code> to use the
288     *        ROI T dimension information.
289     * @param c
290     *        The specific C position (channel) where we want to compute the number of pixel or <code>-1</code> to use
291     *        the ROI C dimension information.
292     */
293    public static long getNumPixel(Sequence sequence, ROI roi, int z, int t, int c)
294    {
295        return DataIteratorUtil.count(new SequenceDataIterator(sequence, roi, false, z, t, c));
296    }
297
298    /**
299     * @deprecated Use {@link ROIMinIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
300     *             instead.
301     */
302    @Deprecated
303    public static double getMinIntensity(Sequence sequence, ROI roi, int z, int t, int c)
304    {
305        return DataIteratorMath.min(new SequenceDataIterator(sequence, roi, false, z, t, c));
306    }
307
308    /**
309     * @deprecated Use {@link ROIMaxIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
310     *             instead.
311     */
312    @Deprecated
313    public static double getMaxIntensity(Sequence sequence, ROI roi, int z, int t, int c)
314    {
315        return DataIteratorMath.max(new SequenceDataIterator(sequence, roi, false, z, t, c));
316    }
317
318    /**
319     * @deprecated Use {@link ROIMeanIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
320     *             instead.
321     */
322    @Deprecated
323    public static double getMeanIntensity(Sequence sequence, ROI roi, int z, int t, int c)
324    {
325        return DataIteratorMath.mean(new SequenceDataIterator(sequence, roi, false, z, t, c));
326    }
327
328    /**
329     * @deprecated Use {@link ROISumIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
330     *             instead.
331     */
332    @Deprecated
333    public static double getSumIntensity(Sequence sequence, ROI roi, int z, int t, int c)
334    {
335        return DataIteratorMath.sum(new SequenceDataIterator(sequence, roi, false, z, t, c));
336    }
337
338    /**
339     * @deprecated Use {@link ROIStandardDeviationDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)}
340     *             method instead.
341     */
342    @Deprecated
343    public static double getStandardDeviation(Sequence sequence, ROI roi)
344    {
345        return getStandardDeviation(sequence, roi, -1, -1, -1);
346    }
347
348    /**
349     * @deprecated Use {@link ROIIntensityDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)} method
350     *             instead.
351     */
352    @Deprecated
353    public static IntensityInfo getIntensityInfo(Sequence sequence, ROI roi)
354    {
355        return getIntensityInfo(sequence, roi, -1, -1, -1);
356    }
357
358    /**
359     * Returns the number of sequence pixels contained in the specified ROI.
360     */
361    public static long getNumPixel(Sequence sequence, ROI roi)
362    {
363        return getNumPixel(sequence, roi, -1, -1, -1);
364    }
365
366    /**
367     * @deprecated Use {@link ROIMinIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
368     *             instead.
369     */
370    @Deprecated
371    public static double getMinIntensity(Sequence sequence, ROI roi)
372    {
373        return getMinIntensity(sequence, roi, -1, -1, -1);
374    }
375
376    /**
377     * @deprecated Use {@link ROIMaxIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
378     *             instead.
379     */
380    @Deprecated
381    public static double getMaxIntensity(Sequence sequence, ROI roi)
382    {
383        return getMaxIntensity(sequence, roi, -1, -1, -1);
384    }
385
386    /**
387     * @deprecated Use {@link ROIMeanIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
388     *             instead.
389     */
390    @Deprecated
391    public static double getMeanIntensity(Sequence sequence, ROI roi)
392    {
393        return getMeanIntensity(sequence, roi, -1, -1, -1);
394    }
395
396    /**
397     * @deprecated Use {@link ROISumIntensityDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
398     *             instead.
399     */
400    @Deprecated
401    public static double getSumIntensity(Sequence sequence, ROI roi)
402    {
403        return getSumIntensity(sequence, roi, -1, -1, -1);
404    }
405
406    /**
407     * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)}
408     *             method instead.
409     */
410    @Deprecated
411    public static Point5D getMassCenter(ROI roi)
412    {
413        switch (roi.getDimension())
414        {
415            case 2:
416                final ROI2D roi2d = (ROI2D) roi;
417                final Point2D pt2d = getMassCenter(roi2d);
418                return new Point5D.Double(pt2d.getX(), pt2d.getY(), roi2d.getZ(), roi2d.getT(), roi2d.getC());
419
420            case 3:
421                final ROI3D roi3d = (ROI3D) roi;
422                final Point3D pt3d = getMassCenter(roi3d);
423                return new Point5D.Double(pt3d.getX(), pt3d.getY(), pt3d.getZ(), roi3d.getT(), roi3d.getC());
424
425            case 4:
426                final ROI4D roi4d = (ROI4D) roi;
427                final Point4D pt4d = getMassCenter(roi4d);
428                return new Point5D.Double(pt4d.getX(), pt4d.getY(), pt4d.getZ(), pt4d.getT(), roi4d.getC());
429
430            case 5:
431                return getMassCenter((ROI5D) roi);
432
433            default:
434                return null;
435        }
436    }
437
438    /**
439     * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)}
440     *             method instead.
441     */
442    @Deprecated
443    public static Point2D getMassCenter(ROI2D roi)
444    {
445        double x = 0, y = 0;
446        long len = 0;
447
448        final BooleanMask2D mask = roi.getBooleanMask(true);
449        final boolean m[] = mask.mask;
450        final int h = mask.bounds.height;
451        final int w = mask.bounds.width;
452
453        int off = 0;
454        for (int j = 0; j < h; j++)
455        {
456            for (int i = 0; i < w; i++)
457            {
458                if (m[off++])
459                {
460                    x += i;
461                    y += j;
462                    len++;
463                }
464            }
465        }
466
467        // get bounds
468        final Rectangle2D bounds2D = roi.getBounds2D();
469
470        // empty roi --> use bounds center
471        if (len == 0)
472            return new Point2D.Double(bounds2D.getCenterX(), bounds2D.getCenterY());
473
474        return new Point2D.Double(bounds2D.getX() + (x / len), bounds2D.getY() + (y / len));
475    }
476
477    /**
478     * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)}
479     *             method instead.
480     */
481    @Deprecated
482    public static Point3D getMassCenter(ROI3D roi)
483    {
484        double x = 0, y = 0, z = 0;
485        long len = 0;
486        final BooleanMask3D mask3d = roi.getBooleanMask(true);
487
488        for (Integer zSlice : mask3d.mask.keySet())
489        {
490            final int zi = zSlice.intValue();
491            final double zd = zi;
492            final BooleanMask2D mask = mask3d.getMask2D(zi);
493            final boolean m[] = mask.mask;
494            final double bx = mask.bounds.x;
495            final double by = mask.bounds.y;
496            final int h = mask.bounds.height;
497            final int w = mask.bounds.width;
498
499            int off = 0;
500            for (int j = 0; j < h; j++)
501            {
502                for (int i = 0; i < w; i++)
503                {
504                    if (m[off++])
505                    {
506                        x += bx + i;
507                        y += by + j;
508                        z += zd;
509                        len++;
510                    }
511                }
512            }
513        }
514
515        // get bounds
516        final Rectangle3D bounds3D = roi.getBounds3D();
517
518        // empty roi --> use bounds center
519        if (len == 0)
520            return new Point3D.Double(bounds3D.getCenterX(), bounds3D.getCenterY(), bounds3D.getCenterZ());
521
522        return new Point3D.Double((x / len), (y / len), (z / len));
523    }
524
525    /**
526     * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)}
527     *             method instead.
528     */
529    @Deprecated
530    public static Point4D getMassCenter(ROI4D roi)
531    {
532        final BooleanMask4D mask4d = roi.getBooleanMask(true);
533        double x = 0, y = 0, z = 0, t = 0;
534        long len = 0;
535
536        for (Integer tFrame : mask4d.mask.keySet())
537        {
538            final int ti = tFrame.intValue();
539            final double td = ti;
540            final BooleanMask3D mask3d = mask4d.getMask3D(ti);
541
542            for (Integer zSlice : mask3d.mask.keySet())
543            {
544                final int zi = zSlice.intValue();
545                final double zd = zi;
546                final BooleanMask2D mask = mask3d.getMask2D(zi);
547                final boolean m[] = mask.mask;
548                final double bx = mask.bounds.x;
549                final double by = mask.bounds.y;
550                final int h = mask.bounds.height;
551                final int w = mask.bounds.width;
552
553                int off = 0;
554                for (int j = 0; j < h; j++)
555                {
556                    for (int i = 0; i < w; i++)
557                    {
558                        if (m[off++])
559                        {
560                            x += bx + i;
561                            y += by + j;
562                            z += zd;
563                            t += td;
564                            len++;
565                        }
566                    }
567                }
568            }
569        }
570
571        // get bounds
572        final Rectangle4D bounds4D = roi.getBounds4D();
573
574        // empty roi --> use bounds center
575        if (len == 0)
576            return new Point4D.Double(bounds4D.getCenterX(), bounds4D.getCenterY(), bounds4D.getCenterZ(),
577                    bounds4D.getCenterT());
578
579        return new Point4D.Double((x / len), (y / len), (z / len), (t / len));
580
581    }
582
583    /**
584     * @deprecated Use {@link ROIMassCenterDescriptorsPlugin} or {@link #computeDescriptor(String, ROI, Sequence)}
585     *             method instead.
586     */
587    @Deprecated
588    public static Point5D getMassCenter(ROI5D roi)
589    {
590        final BooleanMask5D mask5d = roi.getBooleanMask(true);
591        double x = 0, y = 0, z = 0, t = 0, c = 0;
592        long len = 0;
593
594        for (Integer cChannel : mask5d.mask.keySet())
595        {
596            final int ci = cChannel.intValue();
597            final double cd = ci;
598            final BooleanMask4D mask4d = mask5d.getMask4D(ci);
599
600            for (Integer tFrame : mask4d.mask.keySet())
601            {
602                final int ti = tFrame.intValue();
603                final double td = ti;
604                final BooleanMask3D mask3d = mask4d.getMask3D(ti);
605
606                for (Integer zSlice : mask3d.mask.keySet())
607                {
608                    final int zi = zSlice.intValue();
609                    final double zd = zi;
610                    final BooleanMask2D mask = mask3d.getMask2D(zi);
611                    final boolean m[] = mask.mask;
612                    final double bx = mask.bounds.x;
613                    final double by = mask.bounds.y;
614                    final int h = mask.bounds.height;
615                    final int w = mask.bounds.width;
616
617                    int off = 0;
618                    for (int j = 0; j < h; j++)
619                    {
620                        for (int i = 0; i < w; i++)
621                        {
622                            if (m[off++])
623                            {
624                                x += bx + i;
625                                y += by + j;
626                                z += zd;
627                                t += td;
628                                c += cd;
629                                len++;
630                            }
631                        }
632                    }
633                }
634            }
635        }
636
637        // get bounds
638        final Rectangle5D bounds5D = roi.getBounds5D();
639
640        // empty roi --> use bounds center
641        if (len == 0)
642            return new Point5D.Double(bounds5D.getCenterX(), bounds5D.getCenterY(), bounds5D.getCenterZ(),
643                    bounds5D.getCenterT(), bounds5D.getCenterC());
644
645        return new Point5D.Double((x / len), (y / len), (z / len), (t / len), (c / len));
646    }
647
648    /**
649     * @deprecated
650     */
651    @Deprecated
652    private static double getMultiplier(Sequence sequence, ROI roi, int dim)
653    {
654        final int dimRoi = roi.getDimension();
655
656        // cannot give this information for this roi
657        if (dimRoi > dim)
658            return 0d;
659
660        final Rectangle5D boundsRoi = roi.getBounds5D();
661        double mul = 1d;
662
663        switch (dim)
664        {
665            case 5:
666                if (dimRoi == 4)
667                {
668                    final int sizeC = sequence.getSizeC();
669
670                    if ((boundsRoi.getSizeC() == Double.POSITIVE_INFINITY) && (sizeC > 1))
671                        mul *= sizeC;
672                    // cannot give this information for this roi
673                    else
674                        mul = 0d;
675                }
676            case 4:
677                if (dimRoi == 3)
678                {
679                    final int sizeT = sequence.getSizeT();
680
681                    if ((boundsRoi.getSizeT() == Double.POSITIVE_INFINITY) && (sizeT > 1))
682                        mul *= sizeT;
683                    // cannot give this information for this roi
684                    else
685                        mul = 0d;
686                }
687            case 3:
688                if (dimRoi == 2)
689                {
690                    final int sizeZ = sequence.getSizeZ();
691
692                    if ((boundsRoi.getSizeZ() == Double.POSITIVE_INFINITY) && (sizeZ > 1))
693                        mul *= sizeZ;
694                    // cannot give this information for this roi
695                    else
696                        mul = 0d;
697                }
698            case 2:
699                if (dimRoi == 1)
700                {
701                    final int sizeY = sequence.getSizeY();
702
703                    if ((boundsRoi.getSizeY() == Double.POSITIVE_INFINITY) && (sizeY > 1))
704                        mul *= sizeY;
705                    // cannot give this information for this roi
706                    else
707                        mul = 0d;
708                }
709        }
710
711        return mul;
712    }
713
714    /**
715     * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
716     */
717    @Deprecated
718    public static String getContourSize(Sequence sequence, double contourPoints, ROI roi, int dim, int roundSignificant)
719    {
720        final double mul = getMultiplier(sequence, roi, dim);
721
722        // 0 means the operation is not supported for this ROI
723        if (mul != 0d)
724            return sequence.calculateSize(MathUtil.roundSignificant(contourPoints, roundSignificant) * mul, dim,
725                    dim - 1, 5);
726
727        return "";
728    }
729
730    /**
731     * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
732     */
733    @Deprecated
734    public static String getContourSize(Sequence sequence, ROI roi, int dim, int roundSignificant)
735    {
736        return getContourSize(sequence, roi.getNumberOfContourPoints(), roi, dim, roundSignificant);
737    }
738
739    /**
740     * @deprecated Use {@link ROIContourDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
741     */
742    @Deprecated
743    public static String getContourSize(Sequence sequence, ROI roi, int dim)
744    {
745        return getContourSize(sequence, roi, dim, 0);
746    }
747
748    /**
749     * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
750     *             instead.
751     */
752    @Deprecated
753    public static String getInteriorSize(Sequence sequence, double interiorPoints, ROI roi, int dim,
754            int roundSignificant)
755    {
756        final double mul = getMultiplier(sequence, roi, dim);
757
758        // 0 means the operation is not supported for this ROI
759        if (mul != 0d)
760            return sequence.calculateSize(MathUtil.roundSignificant(interiorPoints, roundSignificant) * mul, dim, dim,
761                    5);
762
763        return "";
764    }
765
766    /**
767     * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
768     *             instead.
769     */
770    @Deprecated
771    public static String getInteriorSize(Sequence sequence, ROI roi, int dim, int roundSignificant)
772    {
773        return getInteriorSize(sequence, roi.getNumberOfPoints(), roi, dim, roundSignificant);
774    }
775
776    /**
777     * @deprecated Use {@link ROIInteriorDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
778     *             instead.
779     */
780    @Deprecated
781    public static String getInteriorSize(Sequence sequence, ROI roi, int dim)
782    {
783        return getInteriorSize(sequence, roi, dim, 0);
784    }
785
786    /**
787     * @deprecated Use {@link ROIPerimeterDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
788     *             instead.
789     */
790    @Deprecated
791    public static String getPerimeter(Sequence sequence, ROI roi, int roundSignificant)
792    {
793        return getContourSize(sequence, roi, 2, roundSignificant);
794    }
795
796    /**
797     * @deprecated Use {@link ROIPerimeterDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
798     *             instead.
799     */
800    @Deprecated
801    public static String getPerimeter(Sequence sequence, ROI roi)
802    {
803        return getPerimeter(sequence, roi, 0);
804    }
805
806    /**
807     * @deprecated Use {@link ROIAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
808     */
809    @Deprecated
810    public static String getArea(Sequence sequence, ROI roi, int roundSignificant)
811    {
812        return getInteriorSize(sequence, roi, 2, roundSignificant);
813    }
814
815    /**
816     * @deprecated Use {@link ROIAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
817     */
818    @Deprecated
819    public static String getArea(Sequence sequence, ROI roi)
820    {
821        return getArea(sequence, roi, 0);
822    }
823
824    /**
825     * @deprecated Use {@link ROISurfaceAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
826     *             instead.
827     */
828    @Deprecated
829    public static String getSurfaceArea(Sequence sequence, ROI roi, int roundSignificant)
830    {
831        return getContourSize(sequence, roi, 3, roundSignificant);
832    }
833
834    /**
835     * @deprecated Use {@link ROISurfaceAreaDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method
836     *             instead.
837     */
838    @Deprecated
839    public static String getSurfaceArea(Sequence sequence, ROI roi)
840    {
841        return getSurfaceArea(sequence, roi, 0);
842    }
843
844    /**
845     * @deprecated Use {@link ROIVolumeDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
846     */
847    @Deprecated
848    public static String getVolume(Sequence sequence, ROI roi, int roundSignificant)
849    {
850        return getInteriorSize(sequence, roi, 3, roundSignificant);
851    }
852
853    /**
854     * @deprecated Use {@link ROIVolumeDescriptor} or {@link #computeDescriptor(String, ROI, Sequence)} method instead.
855     */
856    @Deprecated
857    public static String getVolume(Sequence sequence, ROI roi)
858    {
859        return getVolume(sequence, roi, 0);
860    }
861
862    /**
863     * Returns the effective ROI number of dimension needed for the specified bounds.
864     */
865    public static int getEffectiveDimension(Rectangle5D bounds)
866    {
867        int result = 5;
868
869        if (bounds.isInfiniteC() || (bounds.getSizeC() <= 1d))
870        {
871            result--;
872            if (bounds.isInfiniteT() || (bounds.getSizeT() <= 1d))
873            {
874                result--;
875                if (bounds.isInfiniteZ() || (bounds.getSizeZ() <= 1d))
876                    result--;
877            }
878        }
879
880        return result;
881    }
882
883    /**
884     * Return 5D dimension for specified operation dimension
885     */
886    private static Dimension5D.Integer getOpDim(int dim, Rectangle5D.Integer bounds)
887    {
888        final Dimension5D.Integer result = new Dimension5D.Integer();
889
890        switch (dim)
891        {
892            case 2: // XY ROI with fixed ZTC
893                result.sizeZ = 1;
894                result.sizeT = 1;
895                result.sizeC = 1;
896                break;
897
898            case 3: // XYZ ROI with fixed TC
899                result.sizeZ = bounds.sizeZ;
900                result.sizeT = 1;
901                result.sizeC = 1;
902                break;
903
904            case 4: // XYZT ROI with fixed C
905                result.sizeZ = bounds.sizeZ;
906                result.sizeT = bounds.sizeT;
907                result.sizeC = 1;
908                break;
909
910            default: // XYZTC ROI
911                result.sizeZ = bounds.sizeZ;
912                result.sizeT = bounds.sizeT;
913                result.sizeC = bounds.sizeC;
914                break;
915        }
916
917        return result;
918    }
919
920    /**
921     * Get ROI result for specified 5D mask and operation dimension.
922     */
923    private static ROI getOpResult(int dim, BooleanMask5D mask, Rectangle5D.Integer bounds)
924    {
925        final ROI result;
926
927        switch (dim)
928        {
929            case 2: // XY ROI with fixed ZTC
930                result = new ROI2DArea(mask.getMask2D(bounds.z, bounds.t, bounds.c));
931
932                // set ZTC position
933                result.beginUpdate();
934                try
935                {
936                    ((ROI2D) result).setZ(bounds.z);
937                    ((ROI2D) result).setT(bounds.t);
938                    ((ROI2D) result).setC(bounds.c);
939                }
940                finally
941                {
942                    result.endUpdate();
943                }
944                break;
945
946            case 3: // XYZ ROI with fixed TC
947                result = new ROI3DArea(mask.getMask3D(bounds.t, bounds.c));
948
949                // set TC position
950                result.beginUpdate();
951                try
952                {
953                    ((ROI3D) result).setT(bounds.t);
954                    ((ROI3D) result).setC(bounds.c);
955                }
956                finally
957                {
958                    result.endUpdate();
959                }
960                break;
961
962            case 4: // XYZT ROI with fixed C
963                result = new ROI4DArea(mask.getMask4D(bounds.c));
964                // set C position
965                ((ROI4D) result).setC(bounds.c);
966                break;
967
968            case 5: // XYZTC ROI
969                result = new ROI5DArea(mask);
970                break;
971
972            default:
973                throw new UnsupportedOperationException(
974                        "Can't process boolean operation on a ROI with unknown dimension.");
975        }
976
977        return result;
978    }
979
980    /**
981     * Compute the resulting bounds for <i>union</i> operation between specified ROIs.<br>
982     * It throws an exception if the <i>union</i> operation cannot be done (incompatible dimension).
983     */
984    public static Rectangle5D getUnionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException
985    {
986        // null checking
987        if (roi1 == null)
988        {
989            if (roi2 == null)
990                return new Rectangle5D.Double();
991            return roi2.getBounds5D();
992        }
993        else if (roi2 == null)
994            return roi1.getBounds5D();
995
996        final Rectangle5D bounds1 = roi1.getBounds5D();
997        final Rectangle5D bounds2 = roi2.getBounds5D();
998
999        // init infinite dim infos
1000        final boolean ic1 = bounds1.isInfiniteC();
1001        final boolean ic2 = bounds2.isInfiniteC();
1002        final boolean it1 = bounds1.isInfiniteT();
1003        final boolean it2 = bounds2.isInfiniteT();
1004        final boolean iz1 = bounds1.isInfiniteZ();
1005        final boolean iz2 = bounds2.isInfiniteZ();
1006
1007        // cannot process union when we have an infinite dimension with a finite one
1008        if ((ic1 ^ ic2) || (it1 ^ it2) || (iz1 ^ iz2))
1009            throw new UnsupportedOperationException("Can't process union on ROI with different infinite dimension");
1010
1011        // do union
1012        Rectangle5D.union(bounds1, bounds2, bounds1);
1013
1014        // init infinite dim infos on result
1015        final boolean ic = bounds1.isInfiniteC(); // || (bounds1.getSizeC() <= 1d);
1016        final boolean it = bounds1.isInfiniteT(); // || (bounds1.getSizeT() <= 1d);
1017        final boolean iz = bounds1.isInfiniteZ(); // || (bounds1.getSizeZ() <= 1d);
1018
1019        // cannot process union if C dimension is finite but T or Z is infinite
1020        if (!ic && (it || iz))
1021            throw new UnsupportedOperationException(
1022                    "Can't process union on ROI with a finite C dimension and infinite T or Z dimension");
1023        // cannot process union if T dimension is finite but Z is infinite
1024        if (!it && iz)
1025            throw new UnsupportedOperationException(
1026                    "Can't process union on ROI with a finite T dimension and infinite Z dimension");
1027
1028        return bounds1;
1029    }
1030
1031    /**
1032     * Compute the resulting bounds for <i>intersection</i> operation between specified ROIs.<br>
1033     * It throws an exception if the <i>intersection</i> operation cannot be done (incompatible dimension).
1034     */
1035    protected static Rectangle5D getIntersectionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException
1036    {
1037        // null checking
1038        if ((roi1 == null) || (roi2 == null))
1039            return new Rectangle5D.Double();
1040
1041        final Rectangle5D bounds1 = roi1.getBounds5D();
1042        final Rectangle5D bounds2 = roi2.getBounds5D();
1043
1044        // do intersection
1045        Rectangle5D.intersect(bounds1, bounds2, bounds1);
1046
1047        // init infinite dim infos
1048        final boolean ic = bounds1.isInfiniteC(); // || (bounds1.getSizeC() <= 1d);
1049        final boolean it = bounds1.isInfiniteT(); // || (bounds1.getSizeT() <= 1d);
1050        final boolean iz = bounds1.isInfiniteZ(); // || (bounds1.getSizeZ() <= 1d);
1051
1052        // cannot process intersection if C dimension is finite but T or Z is infinite
1053        if (!ic && (it || iz))
1054            throw new UnsupportedOperationException(
1055                    "Can't process intersection on ROI with a finite C dimension and infinite T or Z dimension");
1056        // cannot process intersection if T dimension is finite but Z is infinite
1057        if (!it && iz)
1058            throw new UnsupportedOperationException(
1059                    "Can't process intersection on ROI with a finite T dimension and infinite Z dimension");
1060
1061        return bounds1;
1062    }
1063
1064    /**
1065     * Compute the resulting bounds for <i>subtraction</i> of (roi1 - roi2).<br>
1066     * It throws an exception if the <i>subtraction</i> operation cannot be done (incompatible dimension).
1067     */
1068    protected static Rectangle5D getSubtractionBounds(ROI roi1, ROI roi2) throws UnsupportedOperationException
1069    {
1070        // null checking
1071        if (roi1 == null)
1072            return new Rectangle5D.Double();
1073        if (roi2 == null)
1074            return roi1.getBounds5D();
1075
1076        final Rectangle5D bounds1 = roi1.getBounds5D();
1077        final Rectangle5D bounds2 = roi2.getBounds5D();
1078
1079        // init infinite dim infos
1080        final boolean ic1 = bounds1.isInfiniteC();
1081        final boolean ic2 = bounds2.isInfiniteC();
1082        final boolean it1 = bounds1.isInfiniteT();
1083        final boolean it2 = bounds2.isInfiniteT();
1084        final boolean iz1 = bounds1.isInfiniteZ();
1085        final boolean iz2 = bounds2.isInfiniteZ();
1086
1087        // cannot process subtraction when we have an finite dimension on second ROI
1088        // while having a infinite one on the first ROI
1089        if (ic1 && !ic2)
1090            throw new UnsupportedOperationException(
1091                    "Can't process subtraction: ROI 1 has infinite C dimension while ROI 2 has a finite one");
1092        if (it1 && !it2)
1093            throw new UnsupportedOperationException(
1094                    "Can't process subtraction: ROI 1 has infinite T dimension while ROI 2 has a finite one");
1095        if (iz1 && !iz2)
1096            throw new UnsupportedOperationException(
1097                    "Can't process subtraction: ROI 1 has infinite Z dimension while ROI 2 has a finite one");
1098
1099        return bounds1;
1100    }
1101
1102    /**
1103     * Computes union of specified <code>ROI</code> and return result in a new <code>ROI</code>.
1104     */
1105    public static ROI getUnion(ROI roi1, ROI roi2) throws UnsupportedOperationException
1106    {
1107        // null checking
1108        if (roi1 == null)
1109        {
1110            // return empty ROI
1111            if (roi2 == null)
1112                return new ROI2DArea();
1113            // return simple copy
1114            return roi2.getCopy();
1115        }
1116        else if (roi2 == null)
1117            return roi1.getCopy();
1118
1119        final Rectangle5D bounds5D = getUnionBounds(roi1, roi2);
1120        final int dim = getEffectiveDimension(bounds5D);
1121
1122        // we want integer bounds now
1123        final Rectangle5D.Integer bounds = bounds5D.toInteger();
1124        final Dimension5D.Integer roiSize = getOpDim(dim, bounds);
1125        // get 3D and 4D bounds
1126        final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D();
1127        final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D();
1128
1129        final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC];
1130
1131        for (int c = 0; c < roiSize.sizeC; c++)
1132        {
1133            final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT];
1134
1135            for (int t = 0; t < roiSize.sizeT; t++)
1136            {
1137                final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ];
1138
1139                for (int z = 0; z < roiSize.sizeZ; z++)
1140                {
1141                    mask3D[z] = BooleanMask2D.getUnion(
1142                            roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true),
1143                            roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true));
1144                }
1145
1146                mask4D[t] = new BooleanMask3D(new Rectangle3D.Integer(bounds3D), mask3D);
1147            }
1148
1149            mask5D[c] = new BooleanMask4D(new Rectangle4D.Integer(bounds4D), mask4D);
1150        }
1151
1152        // build the 5D result ROI
1153        final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D);
1154        // optimize bounds of the new created mask
1155        mask.optimizeBounds();
1156
1157        // get result
1158        final ROI result = getOpResult(dim, mask, bounds);
1159        // set name
1160        result.setName("Union");
1161
1162        return result;
1163    }
1164
1165    /**
1166     * Computes intersection of specified <code>ROI</code> and return result in a new <code>ROI</code>.
1167     */
1168    public static ROI getIntersection(ROI roi1, ROI roi2) throws UnsupportedOperationException
1169    {
1170        // null checking
1171        if ((roi1 == null) || (roi2 == null))
1172            // return empty ROI
1173            return new ROI2DArea();
1174
1175        final Rectangle5D bounds5D = getIntersectionBounds(roi1, roi2);
1176        final int dim = getEffectiveDimension(bounds5D);
1177
1178        // we want integer bounds now
1179        final Rectangle5D.Integer bounds = bounds5D.toInteger();
1180        final Dimension5D.Integer roiSize = getOpDim(dim, bounds);
1181        // get 2D, 3D and 4D bounds
1182        final Rectangle bounds2D = (Rectangle) bounds.toRectangle2D();
1183        final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D();
1184        final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D();
1185
1186        final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC];
1187
1188        for (int c = 0; c < roiSize.sizeC; c++)
1189        {
1190            final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT];
1191
1192            for (int t = 0; t < roiSize.sizeT; t++)
1193            {
1194                final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ];
1195
1196                for (int z = 0; z < roiSize.sizeZ; z++)
1197                {
1198                    final BooleanMask2D roi1Mask2D = new BooleanMask2D(new Rectangle(bounds2D),
1199                            roi1.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true));
1200                    final BooleanMask2D roi2Mask2D = new BooleanMask2D(new Rectangle(bounds2D),
1201                            roi2.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true));
1202
1203                    mask3D[z] = BooleanMask2D.getIntersection(roi1Mask2D, roi2Mask2D);
1204                }
1205
1206                mask4D[t] = new BooleanMask3D(new Rectangle3D.Integer(bounds3D), mask3D);
1207            }
1208
1209            mask5D[c] = new BooleanMask4D(new Rectangle4D.Integer(bounds4D), mask4D);
1210        }
1211
1212        // build the 5D result ROI
1213        final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D);
1214        // optimize bounds of the new created mask
1215        mask.optimizeBounds();
1216
1217        // get result
1218        final ROI result = getOpResult(dim, mask, bounds);
1219        // set name
1220        result.setName("Intersection");
1221
1222        return result;
1223    }
1224
1225    /**
1226     * Compute exclusive union of specified <code>ROI</code> and return result in a new <code>ROI</code>.
1227     */
1228    public static ROI getExclusiveUnion(ROI roi1, ROI roi2) throws UnsupportedOperationException
1229    {
1230        // null checking
1231        if (roi1 == null)
1232        {
1233            // return empty ROI
1234            if (roi2 == null)
1235                return new ROI2DArea();
1236            // return simple copy
1237            return roi2.getCopy();
1238        }
1239        else if (roi2 == null)
1240            return roi1.getCopy();
1241
1242        final Rectangle5D bounds5D = getUnionBounds(roi1, roi2);
1243        final int dim = getEffectiveDimension(bounds5D);
1244
1245        // we want integer bounds now
1246        final Rectangle5D.Integer bounds = bounds5D.toInteger();
1247        final Dimension5D.Integer roiSize = getOpDim(dim, bounds);
1248        // get 3D and 4D bounds
1249        final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D();
1250        final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D();
1251
1252        final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC];
1253
1254        for (int c = 0; c < roiSize.sizeC; c++)
1255        {
1256            final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT];
1257
1258            for (int t = 0; t < roiSize.sizeT; t++)
1259            {
1260                final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ];
1261
1262                for (int z = 0; z < roiSize.sizeZ; z++)
1263                {
1264                    mask3D[z] = BooleanMask2D.getExclusiveUnion(
1265                            roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true),
1266                            roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true));
1267                }
1268
1269                mask4D[t] = new BooleanMask3D(new Rectangle3D.Integer(bounds3D), mask3D);
1270            }
1271
1272            mask5D[c] = new BooleanMask4D(new Rectangle4D.Integer(bounds4D), mask4D);
1273        }
1274
1275        // build the 5D result ROI
1276        final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D);
1277        // optimize bounds of the new created mask
1278        mask.optimizeBounds();
1279
1280        // get result
1281        final ROI result = getOpResult(dim, mask, bounds);
1282        // set name
1283        result.setName("Exclusive union");
1284
1285        return result;
1286    }
1287
1288    /**
1289     * Computes the subtraction of roi1 - roi2 and returns result in a new <code>ROI</code>.
1290     */
1291    public static ROI getSubtraction(ROI roi1, ROI roi2) throws UnsupportedOperationException
1292    {
1293        // return empty ROI
1294        if (roi1 == null)
1295            return new ROI2DArea();
1296        // return copy of ROI1
1297        if (roi2 == null)
1298            return roi1.getCopy();
1299
1300        final Rectangle5D bounds5D = getSubtractionBounds(roi1, roi2);
1301        final int dim = getEffectiveDimension(bounds5D);
1302
1303        // we want integer bounds now
1304        final Rectangle5D.Integer bounds = bounds5D.toInteger();
1305        final Dimension5D.Integer roiSize = getOpDim(dim, bounds);
1306        // get 3D and 4D bounds
1307        final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D();
1308        final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D();
1309
1310        final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC];
1311
1312        for (int c = 0; c < roiSize.sizeC; c++)
1313        {
1314            final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT];
1315
1316            for (int t = 0; t < roiSize.sizeT; t++)
1317            {
1318                final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ];
1319
1320                for (int z = 0; z < roiSize.sizeZ; z++)
1321                {
1322                    mask3D[z] = BooleanMask2D.getSubtraction(
1323                            roi1.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true),
1324                            roi2.getBooleanMask2D(bounds.z + z, bounds.t + t, bounds.c + c, true));
1325                }
1326
1327                mask4D[t] = new BooleanMask3D(new Rectangle3D.Integer(bounds3D), mask3D);
1328            }
1329
1330            mask5D[c] = new BooleanMask4D(new Rectangle4D.Integer(bounds4D), mask4D);
1331        }
1332
1333        // build the 5D result ROI
1334        final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D);
1335        // optimize bounds of the new created mask
1336        mask.optimizeBounds();
1337
1338        // get result
1339        final ROI result = getOpResult(dim, mask, bounds);
1340        // set name
1341        result.setName("Substraction");
1342
1343        return result;
1344    }
1345
1346    /**
1347     * Merge the specified array of {@link ROI} with the given {@link BooleanOperator}.<br>
1348     * 
1349     * @param rois
1350     *        ROIs we want to merge.
1351     * @param operator
1352     *        {@link BooleanOperator} to apply.
1353     * @return {@link ROI} representing the result of the merge operation.
1354     */
1355    public static ROI merge(List<? extends ROI> rois, BooleanOperator operator) throws UnsupportedOperationException
1356    {
1357        if (rois.size() == 0)
1358            return null;
1359
1360        ROI result = rois.get(0).getCopy();
1361
1362        // copy can fail...
1363        if (result != null)
1364        {
1365            switch (operator)
1366            {
1367                case AND:
1368                    for (int i = 1; i < rois.size(); i++)
1369                        result = result.intersect(rois.get(i), true);
1370                    break;
1371                case OR:
1372                    for (int i = 1; i < rois.size(); i++)
1373                        result = result.add(rois.get(i), true);
1374                    break;
1375                case XOR:
1376                    for (int i = 1; i < rois.size(); i++)
1377                        result = result.exclusiveAdd(rois.get(i), true);
1378                    break;
1379            }
1380        }
1381
1382        // for (int i = 1; i < rois.size(); i++)
1383        // {
1384        // final ROI roi = rois.get(i);
1385        //
1386        // switch (operator)
1387        // {
1388        // case AND:
1389        // result = result.getIntersection(roi);
1390        // break;
1391        // case OR:
1392        // result = result.getUnion(roi);
1393        // break;
1394        // case XOR:
1395        // result = result.getExclusiveUnion(roi);
1396        // break;
1397        // }
1398        // }
1399
1400        return result;
1401    }
1402
1403    /**
1404     * Builds and returns a ROI corresponding to the union of the specified ROI list.
1405     */
1406    public static ROI getUnion(List<? extends ROI> rois) throws UnsupportedOperationException
1407    {
1408        return merge(rois, BooleanOperator.OR);
1409    }
1410
1411    /**
1412     * Builds and returns a ROI corresponding to the exclusive union of the specified ROI list.
1413     */
1414    public static ROI getExclusiveUnion(List<? extends ROI> rois) throws UnsupportedOperationException
1415    {
1416        return merge(rois, BooleanOperator.XOR);
1417    }
1418
1419    /**
1420     * Builds and returns a ROI corresponding to the intersection of the specified ROI list.
1421     */
1422    public static ROI getIntersection(List<? extends ROI> rois) throws UnsupportedOperationException
1423    {
1424        return merge(rois, BooleanOperator.AND);
1425    }
1426
1427    /**
1428     * Subtract the content of the roi2 from the roi1 and return the result as a new {@link ROI}.<br>
1429     * This is equivalent to: <code>roi1.getSubtraction(roi2)</code>
1430     * 
1431     * @return {@link ROI} representing the result of subtraction.
1432     */
1433    public static ROI subtract(ROI roi1, ROI roi2) throws UnsupportedOperationException
1434    {
1435        return roi1.getSubtraction(roi2);
1436    }
1437
1438    /**
1439     * Converts the specified ROI to a ROI Point ({@link ROI2DPoint} or {@link ROI3DPoint}) representing the mass center of the input ROI.
1440     * 
1441     * @return the ROI point representing the mass center of the input ROI.
1442     */
1443    public static ROI convertToPoint(ROI roi)
1444    {
1445        final ROI result;
1446        final Point5D pt = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi);
1447
1448        if (roi instanceof ROI2D)
1449        {
1450            result = new ROI2DPoint(pt.getX(), pt.getY());
1451            ((ROI2DPoint) result).setZ(((ROI2D) roi).getZ());
1452            ((ROI2DPoint) result).setT(((ROI2D) roi).getT());
1453            ((ROI2DPoint) result).setC(((ROI2D) roi).getC());
1454        }
1455        else if (roi instanceof ROI3D)
1456        {
1457            result = new ROI3DPoint(pt.getX(), pt.getY(), pt.getZ());
1458            ((ROI3DPoint) result).setT(((ROI3D) roi).getT());
1459            ((ROI3DPoint) result).setC(((ROI3D) roi).getC());
1460        }
1461        else
1462        {
1463            result = new ROI3DPoint(pt.getX(), pt.getY(), pt.getZ());
1464            ((ROI3DPoint) result).setT((int) pt.getT());
1465            ((ROI3DPoint) result).setC((int) pt.getC());
1466        }
1467
1468        // preserve properties
1469        ROIUtil.copyROIProperties(roi, result, true);
1470
1471        return result;
1472    }
1473
1474    /**
1475     * Converts the specified ROI to a 2D ellipse type ROI centered on the mass center of the input ROI.
1476     * 
1477     * @return the 2D ellipse ROI centered on the mass center of the input ROI.
1478     */
1479    public static ROI2DEllipse convertToEllipse(ROI roi, double radiusX, double radiusY)
1480    {
1481        final Point5D pt = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi);
1482        final double x = pt.getX();
1483        final double y = pt.getY();
1484        final ROI2DEllipse result = new ROI2DEllipse(x - radiusX, y - radiusY, x + radiusX, y + radiusY);
1485
1486        if (roi instanceof ROI2D)
1487        {
1488            result.setZ(((ROI2D) roi).getZ());
1489            result.setT(((ROI2D) roi).getT());
1490            result.setC(((ROI2D) roi).getC());
1491        }
1492        else if (roi instanceof ROI3D)
1493        {
1494            result.setZ((int) pt.getZ());
1495            result.setT(((ROI3D) roi).getT());
1496            result.setC(((ROI3D) roi).getC());
1497        }
1498        else
1499        {
1500            result.setZ((int) pt.getZ());
1501            result.setT((int) pt.getT());
1502            result.setC((int) pt.getC());
1503        }
1504
1505        // preserve properties
1506        ROIUtil.copyROIProperties(roi, result, true);
1507
1508        return result;
1509    }
1510
1511    /**
1512     * Converts the specified ROI to a 2D rectangle type ROI centered on the mass center of the input ROI.
1513     * 
1514     * @return the 2D rectangle ROI centered on the mass center of the input ROI.
1515     */
1516    public static ROI2DRectangle convertToRectangle(ROI roi, double width, double height)
1517    {
1518        final Point5D pt = ROIMassCenterDescriptorsPlugin.computeMassCenter(roi);
1519        final double x = pt.getX();
1520        final double y = pt.getY();
1521        final double rw = width / 2;
1522        final double rh = height / 2;
1523        final ROI2DRectangle result = new ROI2DRectangle(x - rw, y - rh, x + rw, y + rh);
1524
1525        if (roi instanceof ROI2D)
1526        {
1527            result.setZ(((ROI2D) roi).getZ());
1528            result.setT(((ROI2D) roi).getT());
1529            result.setC(((ROI2D) roi).getC());
1530        }
1531        else if (roi instanceof ROI3D)
1532        {
1533            result.setZ((int) pt.getZ());
1534            result.setT(((ROI3D) roi).getT());
1535            result.setC(((ROI3D) roi).getC());
1536        }
1537        else
1538        {
1539            result.setZ((int) pt.getZ());
1540            result.setT((int) pt.getT());
1541            result.setC((int) pt.getC());
1542        }
1543
1544        // preserve properties
1545        ROIUtil.copyROIProperties(roi, result, true);
1546
1547        return result;
1548    }
1549
1550    /**
1551     * Converts the specified 2D ROI to 3D Stack ROI (ROI3DStack) by stacking it along the Z axis given zMin and zMax
1552     * (inclusive) parameters.
1553     * 
1554     * @return the converted 3D stack ROI or <code>null</code> if the input ROI was null
1555     */
1556    public static ROI convertToStack(ROI2D roi, int zMin, int zMax)
1557    {
1558        ROI result = null;
1559
1560        if (roi instanceof ROI2DRectangle)
1561            result = new ROI3DStackRectangle(((ROI2DRectangle) roi).getRectangle(), zMin, zMax);
1562        else if (roi instanceof ROI2DEllipse)
1563            result = new ROI3DStackEllipse(((ROI2DEllipse) roi).getEllipse(), zMin, zMax);
1564        else if (roi instanceof ROI2DPolygon)
1565            result = new ROI3DStackPolygon(((ROI2DPolygon) roi).getPolygon2D(), zMin, zMax);
1566        else if (roi instanceof ROI2DArea)
1567            result = new ROI3DArea(((ROI2DArea) roi).getBooleanMask(true), zMin, zMax);
1568        else if (roi != null)
1569            result = new ROI3DArea(roi.getBooleanMask2D(roi.getZ(), roi.getT(), roi.getC(), true), zMin, zMax);
1570
1571        if ((roi != null) && (result != null))
1572        {
1573            // unselect all control points
1574            result.unselectAllPoints();
1575            // keep original ROI informations
1576            result.setName(roi.getName() + STACK_SUFFIX);
1577            copyROIProperties(roi, result, false);
1578        }
1579
1580        return result;
1581    }
1582
1583    /**
1584     * Converts the specified ROI to a boolean mask type ROI (ROI Area).
1585     * 
1586     * @return the ROI Area corresponding to the input ROI.<br>
1587     *         If the ROI is already of boolean mask type then it's directly returned without any conversion.
1588     */
1589    public static ROI convertToMask(ROI roi)
1590    {
1591        // no conversion needed
1592        if ((roi instanceof ROI2DArea) || (roi instanceof ROI3DArea) || (roi instanceof ROI4DArea)
1593                || (roi instanceof ROI5DArea))
1594            return roi;
1595
1596        final Rectangle5D bounds5D = roi.getBounds5D();
1597        final int dim = getEffectiveDimension(bounds5D);
1598
1599        // we want integer bounds now
1600        final Rectangle5D.Integer bounds = bounds5D.toInteger();
1601        final Dimension5D.Integer roiSize = getOpDim(dim, bounds);
1602        // get 2D, 3D and 4D bounds
1603        final Rectangle bounds2D = (Rectangle) bounds.toRectangle2D();
1604        final Rectangle3D.Integer bounds3D = (Rectangle3D.Integer) bounds.toRectangle3D();
1605        final Rectangle4D.Integer bounds4D = (Rectangle4D.Integer) bounds.toRectangle4D();
1606
1607        // build 5D mask result
1608        final BooleanMask4D mask5D[] = new BooleanMask4D[roiSize.sizeC];
1609
1610        for (int c = 0; c < roiSize.sizeC; c++)
1611        {
1612            final BooleanMask3D mask4D[] = new BooleanMask3D[roiSize.sizeT];
1613
1614            for (int t = 0; t < roiSize.sizeT; t++)
1615            {
1616                final BooleanMask2D mask3D[] = new BooleanMask2D[roiSize.sizeZ];
1617
1618                for (int z = 0; z < roiSize.sizeZ; z++)
1619                    mask3D[z] = new BooleanMask2D(new Rectangle(bounds2D),
1620                            roi.getBooleanMask2D(bounds2D, bounds.z + z, bounds.t + t, bounds.c + c, true));
1621
1622                mask4D[t] = new BooleanMask3D(new Rectangle3D.Integer(bounds3D), mask3D);
1623            }
1624
1625            mask5D[c] = new BooleanMask4D(new Rectangle4D.Integer(bounds4D), mask4D);
1626        }
1627
1628        // build the 5D result ROI
1629        final BooleanMask5D mask = new BooleanMask5D(bounds, mask5D);
1630        // optimize bounds of the new created mask
1631        mask.optimizeBounds();
1632
1633        // get result
1634        final ROI result = getOpResult(dim, mask, bounds);
1635
1636        // keep original ROI informations
1637        String newName = roi.getName() + MASK_SUFFIX;
1638        // check if we can shorter name
1639        final String cancelableSuffix = SHAPE_SUFFIX + MASK_SUFFIX;
1640        if (newName.endsWith(cancelableSuffix))
1641            newName = newName.substring(0, newName.length() - cancelableSuffix.length());
1642        // set name
1643        result.setName(newName);
1644        // copy properties
1645        copyROIProperties(roi, result, false);
1646
1647        return result;
1648    }
1649
1650    /**
1651     * Converts the specified ROI to a shape type ROI (ROI Polygon or ROI Mesh).
1652     * 
1653     * @param roi
1654     *        the roi to convert to shape type ROI
1655     * @param maxDeviation
1656     *        maximum allowed deviation/distance of resulting ROI polygon from the input ROI contour (in pixel).
1657     *        Use <code>-1</code> for automatic maximum deviation calculation.
1658     * @return the ROI Polygon or ROI Mesh corresponding to the input ROI.<br>
1659     *         If the ROI is already of shape type then it's directly returned without any conversion.
1660     */
1661    public static ROI convertToShape(ROI roi, double maxDeviation) throws UnsupportedOperationException
1662    {
1663        if (roi instanceof ROI2DShape)
1664            return roi;
1665
1666        if (roi instanceof ROI2D)
1667        {
1668            final ROI2D roi2d = (ROI2D) roi;
1669
1670            // get contour points in connected order
1671            final List<Point> points = roi2d.getBooleanMask(true).getConnectedContourPoints();
1672
1673            // convert to point2D and center points in observed pixel.
1674            final List<Point2D> points2D = new ArrayList<Point2D>(points.size());
1675            for (Point pt : points)
1676                points2D.add(new Point2D.Double(pt.x + 0.5d, pt.y + 0.5d));
1677
1678            final double dev;
1679
1680            // auto deviation
1681            if (maxDeviation < 0)
1682            {
1683                // compute it from ROI size
1684                final Rectangle2D bnd = roi2d.getBounds2D();
1685                dev = Math.log10(Math.sqrt(bnd.getWidth() * bnd.getHeight())) / Math.log10(3);
1686            }
1687            else
1688                dev = maxDeviation;
1689
1690            // convert to ROI polygon
1691            final ROI2DPolygon result = new ROI2DPolygon(Polygon2D.getPolygon2D(points2D, dev));
1692
1693            // keep original ROI informations
1694            String newName = roi.getName() + SHAPE_SUFFIX;
1695            // check if we can shorter name
1696            final String cancelableSuffix = MASK_SUFFIX + SHAPE_SUFFIX;
1697            if (newName.endsWith(cancelableSuffix))
1698                newName = newName.substring(0, newName.length() - cancelableSuffix.length());
1699            // set name
1700            result.setName(newName);
1701            // copy properties
1702            copyROIProperties(roi, result, false);
1703
1704            return result;
1705        }
1706
1707        if (roi instanceof ROI3D)
1708        {
1709            // not yet supported
1710            throw new UnsupportedOperationException("ROIUtil.convertToShape(ROI): Operation not supported for 3D ROI.");
1711
1712        }
1713
1714        throw new UnsupportedOperationException(
1715                "ROIUtil.convertToShape(ROI): Operation not supported for this ROI: " + roi.getName());
1716    }
1717
1718    /**
1719     * Returns connected component from specified ROI as a list of ROI (Area type).
1720     */
1721    public static List<ROI> getConnectedComponents(ROI roi) throws UnsupportedOperationException
1722    {
1723        final List<ROI> result = new ArrayList<ROI>();
1724
1725        if (roi instanceof ROI2D)
1726        {
1727            final ROI2D roi2d = (ROI2D) roi;
1728            int ind = 0;
1729
1730            for (BooleanMask2D component : roi2d.getBooleanMask(true).getComponents())
1731            {
1732                final ROI2DArea componentRoi = new ROI2DArea(component);
1733
1734                if (!componentRoi.isEmpty())
1735                {
1736                    // keep original ROI informations
1737                    componentRoi.setName(roi.getName() + OBJECT_SUFFIX + " #" + ind++);
1738                    copyROIProperties(roi, componentRoi, false);
1739
1740                    result.add(componentRoi);
1741                }
1742            }
1743
1744            return result;
1745        }
1746
1747        if (roi instanceof ROI3D)
1748        {
1749            // TODO: add label extractor implementation here
1750
1751            // final ROI3D roi3d = (ROI3D) roi;
1752            // int ind = 0;
1753            //
1754            // for (BooleanMask3D component : roi3d.getBooleanMask(true).getComponents())
1755            // {
1756            // final ROI2DArea componentRoi = new ROI2DArea(component);
1757            //
1758            // if (!componentRoi.isEmpty())
1759            // {
1760            // // keep original ROI informations
1761            // componentRoi.setName(roi.getName() + " object #" + ind++);
1762            // copyROIProperties(roi, componentRoi, false);
1763            //
1764            // result.add(componentRoi);
1765            // }
1766            // }
1767
1768            // not yet supported
1769            throw new UnsupportedOperationException(
1770                    "ROIUtil.getConnectedComponents(ROI): Operation not supported yet for 3D ROI.");
1771        }
1772
1773        throw new UnsupportedOperationException(
1774                "ROIUtil.getConnectedComponents(ROI): Operation not supported for this ROI: " + roi.getName());
1775    }
1776
1777    static boolean computePolysFromLine(Line2D line, Point2D edgePt1, Point2D edgePt2, Polygon2D poly1, Polygon2D poly2,
1778            boolean inner)
1779    {
1780        final Line2D edgeLine = new Line2D.Double(edgePt1, edgePt2);
1781
1782        // they intersect ?
1783        if (edgeLine.intersectsLine(line))
1784        {
1785            final Point2D intersection = Line2DUtil.getIntersection(edgeLine, line);
1786
1787            // are we inside poly2 ?
1788            if (inner)
1789            {
1790                // add intersection to poly2
1791                poly2.addPoint(intersection);
1792                // add intersection and pt2 to poly1
1793                poly1.addPoint(intersection);
1794                poly1.addPoint(edgePt2);
1795            }
1796            else
1797            {
1798                // add intersection to poly1
1799                poly1.addPoint(intersection);
1800                // add intersection and pt2 to poly2
1801                poly2.addPoint(intersection);
1802                poly2.addPoint(edgePt2);
1803            }
1804
1805            // we changed region
1806            return !inner;
1807        }
1808
1809        // inside poly2 --> add point to poly2
1810        if (inner)
1811            poly2.addPoint(edgePt2);
1812        else
1813            poly1.addPoint(edgePt2);
1814
1815        // same region
1816        return inner;
1817    }
1818
1819    /**
1820     * Cut the specified ROI with the given Line2D (extended to ROI bounds) and return the 2 resulting ROI in a
1821     * list.<br>
1822     * If the specified ROI cannot be cut by the given Line2D then <code>null</code> is returned.
1823     */
1824    public static List<ROI> split(ROI roi, Line2D line)
1825    {
1826        final Rectangle2D bounds2d = roi.getBounds5D().toRectangle2D();
1827        // need to enlarge bounds a bit to avoid roundness issues on line intersection
1828        final Rectangle2D extendedBounds2d = Rectangle2DUtil.getScaledRectangle(bounds2d, 1.1d, true);
1829        // enlarge line to ROI bounds
1830        final Line2D extendedLine = Rectangle2DUtil.getIntersectionLine(extendedBounds2d, line);
1831
1832        // if the extended line intersects the ROI bounds
1833        if ((extendedLine != null) && bounds2d.intersectsLine(extendedLine))
1834        {
1835            final List<ROI> result = new ArrayList<ROI>();
1836            final Point2D topLeft = new Point2D.Double(bounds2d.getMinX(), bounds2d.getMinY());
1837            final Point2D topRight = new Point2D.Double(bounds2d.getMaxX(), bounds2d.getMinY());
1838            final Point2D bottomRight = new Point2D.Double(bounds2d.getMaxX(), bounds2d.getMaxY());
1839            final Point2D bottomLeft = new Point2D.Double(bounds2d.getMinX(), bounds2d.getMaxY());
1840            final Polygon2D poly1 = new Polygon2D();
1841            final Polygon2D poly2 = new Polygon2D();
1842            boolean inner;
1843
1844            // add first point to poly1
1845            poly1.addPoint(topLeft);
1846            // we are inside poly1 for now
1847            inner = false;
1848
1849            // compute the 2 rectangle part (polygon) from top, right, bottom and left lines
1850            inner = computePolysFromLine(extendedLine, topLeft, topRight, poly1, poly2, inner);
1851            inner = computePolysFromLine(extendedLine, topRight, bottomRight, poly1, poly2, inner);
1852            inner = computePolysFromLine(extendedLine, bottomRight, bottomLeft, poly1, poly2, inner);
1853            inner = computePolysFromLine(extendedLine, bottomLeft, topLeft, poly1, poly2, inner);
1854
1855            // get intersection result from both polygon
1856            final ROI roiPart1 = new ROI2DPolygon(poly1).getIntersection(roi);
1857            final ROI roiPart2 = new ROI2DPolygon(poly2).getIntersection(roi);
1858
1859            // keep original ROI informations
1860            roiPart1.setName(roi.getName() + PART_SUFFIX + " #1");
1861            copyROIProperties(roi, roiPart1, false);
1862            roiPart2.setName(roi.getName() + PART_SUFFIX + " #2");
1863            copyROIProperties(roi, roiPart2, false);
1864
1865            // add to result list
1866            result.add(roiPart1);
1867            result.add(roiPart2);
1868
1869            return result;
1870        }
1871
1872        return null;
1873    }
1874
1875    /**
1876     * Convert a list of ROI into a binary / labeled Sequence.
1877     * 
1878     * @param inputRois
1879     *        list of ROI to convert
1880     * @param sizeX
1881     *        the wanted size X of output Sequence, if set to <code>0</code> then Sequence size X is computed
1882     *        automatically from
1883     *        the global ROI bounds.
1884     * @param sizeY
1885     *        the wanted size Y of output Sequence, if set to <code>0</code> then Sequence size Y is computed
1886     *        automatically from
1887     *        the global ROI bounds.
1888     * @param sizeC
1889     *        the wanted size C of output Sequence, if set to <code>0</code> then Sequence size C is computed
1890     *        automatically from
1891     *        the global ROI bounds.
1892     * @param sizeZ
1893     *        the wanted size Z of output Sequence, if set to <code>0</code> then Sequence size Z is computed
1894     *        automatically from
1895     *        the global ROI bounds.
1896     * @param sizeT
1897     *        the wanted size T of output Sequence, if set to <code>0</code> then Sequence size T is computed
1898     *        automatically from
1899     *        the global ROI bounds.
1900     * @param dataType
1901     *        the wanted dataType of output Sequence
1902     * @param label
1903     *        if set to <code>true</code> then each ROI will be draw as a separate label (value) in the sequence
1904     *        starting from 1.
1905     */
1906    public static Sequence convertToSequence(List<ROI> inputRois, int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT,
1907            DataType dataType, boolean label)
1908    {
1909        final List<ROI> rois = new ArrayList<ROI>();
1910        final Rectangle5D bounds = new Rectangle5D.Double();
1911
1912        try
1913        {
1914            // compute the union of all ROI
1915            final ROI roi = ROIUtil.merge(inputRois, BooleanOperator.OR);
1916            // get bounds of result
1917            bounds.add(roi.getBounds5D());
1918            // add this single ROI to list
1919            rois.add(roi);
1920        }
1921        catch (Exception e)
1922        {
1923            for (ROI roi : inputRois)
1924            {
1925                // compute global bounds
1926                if (roi != null)
1927                {
1928                    bounds.add(roi.getBounds5D());
1929                    rois.add(roi);
1930                }
1931            }
1932        }
1933
1934        int sX = sizeX;
1935        int sY = sizeY;
1936        int sC = sizeC;
1937        int sZ = sizeZ;
1938        int sT = sizeT;
1939
1940        if (sX == 0)
1941            sX = (int) bounds.getSizeX();
1942        if (sY == 0)
1943            sY = (int) bounds.getSizeY();
1944        if (sC == 0)
1945            sC = (bounds.isInfiniteC() ? 1 : (int) bounds.getSizeC());
1946        if (sZ == 0)
1947            sZ = (bounds.isInfiniteZ() ? 1 : (int) bounds.getSizeZ());
1948        if (sT == 0)
1949            sT = (bounds.isInfiniteT() ? 1 : (int) bounds.getSizeT());
1950
1951        // empty base dimension and empty result --> generate a empty 320x240 image
1952        if (sX == 0)
1953            sX = 320;
1954        if (sY == 0)
1955            sY = 240;
1956        if (sC == 0)
1957            sC = 1;
1958        if (sZ == 0)
1959            sZ = 1;
1960        if (sT == 0)
1961            sT = 1;
1962
1963        final Sequence out = new Sequence("ROI conversion");
1964
1965        out.beginUpdate();
1966        try
1967        {
1968            for (int t = 0; t < sT; t++)
1969                for (int z = 0; z < sZ; z++)
1970                    out.setImage(t, z, new IcyBufferedImage(sX, sY, sC, dataType));
1971
1972            double fillValue = 1d;
1973
1974            // set value from ROI(s)
1975            for (ROI roi : rois)
1976            {
1977                if (!roi.getBounds5D().isEmpty())
1978                    DataIteratorUtil.set(new SequenceDataIterator(out, roi), fillValue);
1979
1980                if (label)
1981                    fillValue += 1d;
1982            }
1983
1984            // notify data changed
1985            out.dataChanged();
1986        }
1987        finally
1988        {
1989            out.endUpdate();
1990        }
1991
1992        return out;
1993    }
1994
1995    /**
1996     * Convert a list of ROI into a binary / labeled Sequence.
1997     * 
1998     * @param inputRois
1999     *        list of ROI to convert
2000     * @param sequence
2001     *        the sequence used to define the wanted sequence dimension in return.<br>
2002     *        If this field is <code>null</code> then the global ROI bounds will be used to define the Sequence
2003     *        dimension
2004     * @param label
2005     *        if set to <code>true</code> then each ROI will be draw as a separate label (value) in the sequence
2006     *        starting from 1.
2007     */
2008    public static Sequence convertToSequence(List<ROI> inputRois, Sequence sequence, boolean label)
2009    {
2010        if (sequence == null)
2011            return convertToSequence(inputRois, 0, 0, 0, 0, 0,
2012                    label ? ((inputRois.size() > 255) ? DataType.USHORT : DataType.UBYTE) : DataType.UBYTE, label);
2013
2014        return convertToSequence(inputRois, sequence.getSizeX(), sequence.getSizeY(), 1, sequence.getSizeZ(),
2015                sequence.getSizeT(), sequence.getDataType_(), label);
2016    }
2017
2018    /**
2019     * Convert a single ROI into a binary / labeled Sequence.
2020     * 
2021     * @param inputRoi
2022     *        ROI to convert
2023     * @param sequence
2024     *        the sequence used to define the wanted sequence dimension in return.<br>
2025     *        If this field is <code>null</code> then the global ROI bounds will be used to define the Sequence
2026     *        dimension
2027     */
2028    public static Sequence convertToSequence(ROI inputRoi, Sequence sequence)
2029    {
2030        return convertToSequence(CollectionUtil.createArrayList(inputRoi), sequence, false);
2031    }
2032
2033    /**
2034     * Scale (3D) the given ROI by specified X,Y,Z factors.<br>
2035     * Only {@link ROI2DShape} and {@link ROI3DShape} are supported !
2036     * 
2037     * @param roi
2038     *        input ROI we want to rescale
2039     * @throws UnsupportedOperationException
2040     *         if input ROI is not ROI2DShape or ROI3DShape (scaling supported only for these ROI)
2041     */
2042    public static void scale(ROI roi, double scaleX, double scaleY, double scaleZ) throws UnsupportedOperationException
2043    {
2044        // shape ROI --> can rescale easily
2045        if (roi instanceof ROI2DRectShape)
2046        {
2047            final ROI2DRectShape roi2DRectShape = (ROI2DRectShape) roi;
2048
2049            roi2DRectShape.beginUpdate();
2050            try
2051            {
2052                final Rectangle2D bounds = roi2DRectShape.getBounds2D();
2053
2054                // reshape directly
2055                bounds.setFrame(bounds.getX() * scaleX, bounds.getY() * scaleY, bounds.getWidth() * scaleX,
2056                        bounds.getHeight() * scaleY);
2057                roi2DRectShape.setBounds2D(bounds);
2058
2059                final int z = roi2DRectShape.getZ();
2060
2061                // re scale Z position if needed
2062                if ((z != -1) && (scaleZ != 1d))
2063                    roi2DRectShape.setZ((int) (z * scaleZ));
2064            }
2065            finally
2066            {
2067                roi2DRectShape.endUpdate();
2068            }
2069        }
2070        else if (roi instanceof ROI2DShape)
2071        {
2072            final ROI2DShape roi2DShape = (ROI2DShape) roi;
2073
2074            roi2DShape.beginUpdate();
2075            try
2076            {
2077                // adjust control point position directly
2078                for (Anchor2D pt : roi2DShape.getControlPoints())
2079                {
2080                    final Point2D pos = pt.getPosition();
2081                    // change control point position
2082                    pt.setPosition(pos.getX() * scaleX, pos.getY() * scaleY);
2083                }
2084
2085                final int z = roi2DShape.getZ();
2086
2087                // re scale Z position if needed
2088                if ((z != -1) && (scaleZ != 1d))
2089                    roi2DShape.setZ((int) (z * scaleZ));
2090            }
2091            finally
2092            {
2093                roi2DShape.endUpdate();
2094            }
2095        }
2096        else if (roi instanceof ROI3DShape)
2097        {
2098            final ROI3DShape roi3DShape = (ROI3DShape) roi;
2099
2100            roi3DShape.beginUpdate();
2101            try
2102            {
2103                // adjust control point position directly
2104                for (Anchor3D pt : roi3DShape.getControlPoints())
2105                {
2106                    final Point3D pos = pt.getPosition();
2107                    // change control point position
2108                    pt.setPosition(pos.getX() * scaleX, pos.getY() * scaleY, pos.getZ() * scaleZ);
2109                }
2110            }
2111            finally
2112            {
2113                roi3DShape.endUpdate();
2114            }
2115        }
2116        else
2117            throw new UnsupportedOperationException("ROIUtil.scale: cannot rescale " + roi.getSimpleClassName() + " !");
2118    }
2119
2120    /**
2121     * Scale (2D) the given ROI by specified X/Y factor.<br>
2122     * Only {@link ROI2DShape} and {@link ROI3DShape} are supported !
2123     * 
2124     * @param roi
2125     *        input ROI we want to rescale
2126     * @throws UnsupportedOperationException
2127     *         if input ROI is not ROI2DShape or ROI3DShape (scaling supported only for these ROI)
2128     */
2129    public static void scale(ROI roi, double scaleX, double scaleY) throws UnsupportedOperationException
2130    {
2131        scale(roi, scaleX, scaleY, 1d);
2132    }
2133
2134    /**
2135     * Scale the given ROI by specified scale factor.<br>
2136     * Only {@link ROI2DShape} and {@link ROI3DShape} are supported !
2137     * 
2138     * @param roi
2139     *        input ROI we want to rescale
2140     * @throws UnsupportedOperationException
2141     *         if input ROI is not ROI2DShape or ROI3DShape (scaling supported only for these ROI)
2142     */
2143    public static void scale(ROI roi, double scale) throws UnsupportedOperationException
2144    {
2145        scale(roi, scale, scale, scale);
2146    }
2147
2148    /**
2149     * Create and returns a new ROI which is a 2x up/down scaled version of the input ROI.<br>
2150     * Note that the returned ROI can be ROI2DArea or ROI3DArea if original ROI format doesn't support 2X scale
2151     * operation.
2152     * 
2153     * @param roi
2154     *        input ROI we want to get the up scaled form
2155     * @param scaleOnZ
2156     *        Set to <code>true</code> to scale as well on Z dimension (XY dimension only otherwise)
2157     * @param down
2158     *        Set to <code>true</code> for down scaling and <code>false</code> for up scaling operation
2159     * @throws UnsupportedOperationException
2160     *         if input ROI is ROI4D or ROI5D (up scaling not supported for these ROI)
2161     */
2162    public static ROI get2XScaled(ROI roi, boolean scaleOnZ, boolean down) throws UnsupportedOperationException
2163    {
2164        if (roi == null)
2165            return null;
2166
2167        final double scaling = down ? 0.5d : 2d;
2168        ROI result = roi.getCopy();
2169
2170        // shape ROI --> can rescale easily
2171        if ((result instanceof ROI2DShape) || (result instanceof ROI3DShape))
2172            scale(result, scaling, scaling, scaleOnZ ? scaling : 1d);
2173        else if (result instanceof ROI2D)
2174        {
2175            final ROI2DArea roi2DArea;
2176
2177            if (result instanceof ROI2DArea)
2178            {
2179                roi2DArea = (ROI2DArea) result;
2180
2181                // scale
2182                if (down)
2183                    roi2DArea.downscale();
2184                else
2185                    roi2DArea.upscale();
2186
2187                // scale Z position if wanted
2188                if ((roi2DArea.getZ() != -1) && scaleOnZ)
2189                    roi2DArea.setZ((int) (roi2DArea.getZ() * scaling));
2190            }
2191            else
2192            {
2193                final BooleanMask2D bm = ((ROI2D) result).getBooleanMask(true);
2194
2195                // scale
2196                if (down)
2197                    roi2DArea = new ROI2DArea(bm.downscale());
2198                else
2199                    roi2DArea = new ROI2DArea(bm.upscale());
2200
2201                // get original position
2202                final Point5D pos = result.getPosition5D();
2203
2204                // restore Z,T,C position
2205                if (Double.isInfinite(pos.getZ()))
2206                    roi2DArea.setZ(-1);
2207                else
2208                    roi2DArea.setZ((int) (pos.getZ() * (scaleOnZ ? scaling : 1d)));
2209                if (Double.isInfinite(pos.getT()))
2210                    roi2DArea.setT(-1);
2211                else
2212                    roi2DArea.setT((int) pos.getT());
2213                if (Double.isInfinite(pos.getC()))
2214                    roi2DArea.setC(-1);
2215                else
2216                    roi2DArea.setC((int) pos.getC());
2217
2218                // copy properties
2219                copyROIProperties(result, roi2DArea, true);
2220
2221                result = roi2DArea;
2222            }
2223        }
2224        else if (result instanceof ROI3D)
2225        {
2226            final ROI3DArea roi3DArea;
2227
2228            // we want a ROI2DArea
2229            if (result instanceof ROI3DArea)
2230            {
2231                roi3DArea = (ROI3DArea) result;
2232
2233                // scale
2234                if (down)
2235                {
2236                    if (scaleOnZ)
2237                        roi3DArea.downscale();
2238                    else
2239                        roi3DArea.downscale2D();
2240                }
2241                else
2242                {
2243                    if (scaleOnZ)
2244                        roi3DArea.upscale();
2245                    else
2246                        roi3DArea.upscale2D();
2247                }
2248            }
2249            else
2250            {
2251                final BooleanMask3D bm = ((ROI3D) result).getBooleanMask(true);
2252
2253                // scale
2254                if (down)
2255                {
2256                    if (scaleOnZ)
2257                        roi3DArea = new ROI3DArea(bm.downscale());
2258                    else
2259                        roi3DArea = new ROI3DArea(bm.downscale2D());
2260                }
2261                else
2262                {
2263                    if (scaleOnZ)
2264                        roi3DArea = new ROI3DArea(bm.upscale());
2265                    else
2266                        roi3DArea = new ROI3DArea(bm.upscale2D());
2267                }
2268
2269                // get original position
2270                final Point5D pos = result.getPosition5D();
2271
2272                // restore T,C position
2273                if (Double.isInfinite(pos.getT()))
2274                    roi3DArea.setT(-1);
2275                else
2276                    roi3DArea.setT((int) pos.getT());
2277                if (Double.isInfinite(pos.getC()))
2278                    roi3DArea.setC(-1);
2279                else
2280                    roi3DArea.setC((int) pos.getC());
2281
2282                // copy properties
2283                copyROIProperties(result, roi3DArea, true);
2284
2285                result = roi3DArea;
2286            }
2287        }
2288        // 4D or 5D ROI --> scaling not supported
2289        else
2290            throw new UnsupportedOperationException("ROIUtil.get2XScaled: cannot rescale ROI4D or ROI5D !");
2291
2292        return result;
2293    }
2294
2295    /**
2296     * Create and returns a new ROI which is a 2x up scaled version of the input ROI.<br>
2297     * Note that the returned ROI can be ROI2DArea or ROI3DArea if original ROI format doesn't support up scale
2298     * operation.
2299     * 
2300     * @param roi
2301     *        input ROI we want to get the up scaled form
2302     * @param scaleOnZ
2303     *        Set to <code>true</code> to scale as well on Z dimension (XY dimension only otherwise)
2304     * @throws UnsupportedOperationException
2305     *         if input ROI is ROI4D or ROI5D (up scaling not supported for these ROI)
2306     */
2307    public static ROI getUpscaled(ROI roi, boolean scaleOnZ) throws UnsupportedOperationException
2308    {
2309        return get2XScaled(roi, scaleOnZ, false);
2310    }
2311
2312    /**
2313     * Create and returns a new ROI which is a 2x down scaled version of the input ROI.<br>
2314     * Note that the returned ROI can be ROI2DArea or ROI3DArea if original ROI format doesn't support up scale
2315     * operation.
2316     * 
2317     * @param roi
2318     *        input ROI we want to get the up scaled form
2319     * @param scaleOnZ
2320     *        Set to <code>true</code> to scale as well on Z dimension (XY dimension only otherwise)
2321     * @throws UnsupportedOperationException
2322     *         if input ROI is ROI4D or ROI5D (up scaling not supported for these ROI)
2323     */
2324    public static ROI getDownscaled(ROI roi, boolean scaleOnZ) throws UnsupportedOperationException
2325    {
2326        return get2XScaled(roi, scaleOnZ, true);
2327    }
2328
2329    /**
2330     * Create a copy of the specified ROI coming from <i>source</i> sequence adjusted to the <i>destination</i> sequence.<br>
2331     * The resulting ROI coordinates can be different if the {@link Sequence#getPosition()} are not identical on the 2 sequences.<br>
2332     * The resulting ROI can be scaled if the {@link Sequence#getPixelSize()} are not identical on the 2 sequences<br>
2333     * Note that the returned ROI can have a Boolean Mask format if we can't re-use original ROI format, also the scale operation may not be possible depending
2334     * the original ROI format.
2335     * 
2336     * @param roi
2337     *        input ROI we want to adjust
2338     * @param source
2339     *        the source sequence where the ROI was initially generated (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2340     * @param destination
2341     *        the destination sequence where we want to copy the ROI (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2342     * @param translate
2343     *        if we allow the returned ROI to be translated compared to the original ROI
2344     * @param scale
2345     *        if we allow the returned ROI to be scaled compared to the original ROI
2346     * @param ignoreErrorOnScale
2347     *        ignore unsupported scaling operation, in which case the result ROI won't be correctly scaled
2348     * @return adjusted ROI
2349     * @throws UnsupportedOperationException
2350     *         if input ROI is ROI4D or ROI5D while scaling is required (scaling not supported for these ROI) and <code>ignoreErrorOnScale</code> is set to
2351     *         <code>FALSE</code>
2352     */
2353    public static ROI adjustToSequence(ROI roi, Sequence source, Sequence destination, boolean translate, boolean scale,
2354            boolean ignoreErrorOnScale) throws UnsupportedOperationException
2355    {
2356        if (roi == null)
2357            return null;
2358
2359        // create a copy
2360        ROI result = roi.getCopy();
2361
2362        if (scale)
2363        {
2364            final double scaleX = source.getPixelSizeX() / destination.getPixelSizeX();
2365            final double scaleY = source.getPixelSizeY() / destination.getPixelSizeY();
2366            final double scaleZ = source.getPixelSizeZ() / destination.getPixelSizeZ();
2367
2368            // ROI is a 2D or 3D shape ? --> use easy scaling
2369            if ((result instanceof ROI2DShape) || (result instanceof ROI3DShape))
2370                scale(result, scaleX, scaleY, scaleZ);
2371            else
2372            {
2373                boolean doRescale = true;
2374                boolean doRescaleZ = true;
2375
2376                if (scaleX != scaleY)
2377                {
2378                    doRescale = false;
2379                    if (ignoreErrorOnScale)
2380                        System.out.println(
2381                                "[Warning] ROIUtil.adjustToSequence: cannot rescale ROI with different X/Y scale ratio.");
2382                    else
2383                        throw new UnsupportedOperationException(
2384                                "ROIUtil.adjustToSequence: cannot rescale ROI (different X/Y scale ratio) !");
2385                }
2386
2387                // get log2 of scaleX/Y (round it a bit)
2388                final double resDelta = MathUtil.round(Math.log(scaleX) / Math.log(2), 1);
2389
2390                // too far from 2^x scaling
2391                if (Math.round(resDelta) != resDelta)
2392                {
2393                    doRescale = false;
2394                    if (ignoreErrorOnScale)
2395                        System.out.println(
2396                                "[Warning] ROIUtil.adjustToSequence: cannot rescale ROI with scale XY = " + scaleX);
2397                    else
2398                        throw new UnsupportedOperationException(
2399                                "ROIUtil.adjustToSequence: cannot rescale ROI (scale XY = " + scaleX + ") !");
2400                }
2401
2402                // get log2 of scaleZ (round it a bit)
2403                final double resDeltaZ = MathUtil.round(Math.log(scaleZ) / Math.log(2), 1);
2404
2405                if (Math.round(resDeltaZ) != resDeltaZ)
2406                {
2407                    doRescaleZ = false;
2408                    if (ignoreErrorOnScale)
2409                        System.out.println("[Warning] ROIUtil.adjustToSequence: ignoring ROI Z rescaling (scale Z = "
2410                                + scaleZ + ")");
2411                    else
2412                        throw new UnsupportedOperationException(
2413                                "ROIUtil.adjustToSequence: cannot rescale ROI (scale Z = " + scaleZ + ") !");
2414                }
2415
2416                final boolean zScaling = resDeltaZ != 0d;
2417
2418                // Z rescaling needed ? --> we need to have same XY and Z scale ratio
2419                if (zScaling && (resDeltaZ != resDelta))
2420                {
2421                    doRescaleZ = false;
2422                    if (ignoreErrorOnScale)
2423                        System.out.println("[Warning] ROIUtil.adjustToSequence: ignoring ROI Z rescaling (scale XY = "
2424                                + scaleX + " while scale Z = " + scaleZ + ")");
2425                    else
2426                        throw new UnsupportedOperationException(
2427                                "ROIUtil.adjustToSequence: cannot rescale ROI (scale XY = " + scaleX
2428                                        + " while scale Z = " + scaleZ + ") !");
2429                }
2430
2431                try
2432                {
2433                    if (doRescale)
2434                    {
2435                        int i = (int) resDelta;
2436
2437                        // destination resolution level > source resolution level
2438                        if (resDelta > 0)
2439                        {
2440                            // down scaling
2441                            while (i-- > 0)
2442                                result = getUpscaled(result, zScaling && doRescaleZ);
2443                        }
2444                        else
2445                        {
2446                            // up scaling
2447                            while (i++ < 0)
2448                                result = getDownscaled(result, zScaling && doRescaleZ);
2449                        }
2450                    }
2451                }
2452                catch (UnsupportedOperationException e)
2453                {
2454                    // we should propagate it then
2455                    if (!ignoreErrorOnScale)
2456                        throw e;
2457                }
2458            }
2459        }
2460
2461        // can set position ? --> relocate it
2462        if (translate && result.canSetPosition())
2463        {
2464            // get current position
2465            final Point5D pos = result.getPosition5D();
2466
2467            // can change it Z position ?
2468            if (!Double.isInfinite(pos.getZ()))
2469            {
2470                // we just want to get offset as scaling already taken care of converting relative ROI position
2471                final Point3D offset = SequenceUtil.convertPoint(new Point3D.Double(), source, destination);
2472
2473                // compute position in destination
2474                pos.setX(pos.getX() + offset.getX());
2475                pos.setY(pos.getY() + offset.getY());
2476                pos.setZ(pos.getZ() + offset.getZ());
2477            }
2478            else
2479            {
2480                // we just want to get offset as scaling already taken care of converting relative ROI position
2481                final Point2D offset = SequenceUtil.convertPoint(new Point2D.Double(), source, destination);
2482
2483                // compute position in destination
2484                pos.setX(pos.getX() + offset.getX());
2485                pos.setY(pos.getY() + offset.getY());
2486            }
2487
2488            // can change it ? (we don't scale T dimension)
2489            if (!Double.isInfinite(pos.getT()))
2490            {
2491                // get time interval in ms
2492                final double timeIntervalMs = destination.getTimeInterval() * 1000d;
2493
2494                if (timeIntervalMs > 0d)
2495                {
2496                    // get delta in timestamp (ms)
2497                    final double deltaT = source.getPositionT() - destination.getPositionT();
2498                    // get wanted destination T offset
2499                    final double tOffset = deltaT / timeIntervalMs;
2500
2501                    // assume correct T range is ~1000
2502                    if ((tOffset > -1000d) && (tOffset < 1000d))
2503                        pos.setT(Math.min(999, Math.max(0, pos.getT() + tOffset)));
2504                }
2505            }
2506
2507            // set back position
2508            result.setPosition5D(pos);
2509        }
2510
2511        return result;
2512    }
2513
2514    /**
2515     * Create a copy of the specified ROI coming from <i>source</i> sequence adjusted to the <i>destination</i> sequence.<br>
2516     * The resulting ROI coordinates can be different if the {@link Sequence#getPosition()} are not identical on the 2 sequences.<br>
2517     * The resulting ROI can be scaled if the {@link Sequence#getPixelSize()} are not identical on the 2 sequences<br>
2518     * Note that the returned ROI can have a Boolean Mask format if we can't re-use original ROI format, also the scale operation may not be possible depending
2519     * the original ROI format.
2520     * 
2521     * @param roi
2522     *        input ROI we want to adjust
2523     * @param source
2524     *        the source sequence where the ROI was initially generated (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2525     * @param destination
2526     *        the destination sequence where we want to copy the ROI (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2527     * @param translate
2528     *        if we allow the returned ROI to be translated compared to the original ROI
2529     * @param scale
2530     *        if we allow the returned ROI to be scaled compared to the original ROI
2531     * @return adjusted ROI
2532     * @throws UnsupportedOperationException
2533     *         if input ROI is ROI4D or ROI5D while scaling is required (scaling not supported for these ROI) and <code>ignoreErrorOnScale</code> is set to
2534     *         <code>FALSE</code>
2535     */
2536    public static ROI adjustToSequence(ROI roi, Sequence source, Sequence destination, boolean translate, boolean scale)
2537            throws UnsupportedOperationException
2538    {
2539        return adjustToSequence(roi, source, destination, translate, scale, false);
2540    }
2541
2542    /**
2543     * Create a copy of the specified ROI coming from <i>source</i> sequence adjusted to the <i>destination</i> sequence.<br>
2544     * The resulting ROI coordinates can be different if the {@link Sequence#getPosition()} are not identical on the 2 sequences.<br>
2545     * The resulting ROI can be scaled if the {@link Sequence#getPixelSize()} are not identical on the 2 sequences<br>
2546     * Note that the returned ROI can have a Boolean Mask format if we can't re-use original ROI format, also the scale operation may not be possible depending
2547     * the original ROI format.
2548     * 
2549     * @param roi
2550     *        input ROI we want to adjust
2551     * @param source
2552     *        the source sequence where the ROI was initially generated (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2553     * @param destination
2554     *        the destination sequence where we want to copy the ROI (should contains valid <i>origin</i> information, see Sequence#getOriginXXX(} methods)
2555     * @return adjusted ROI
2556     * @throws UnsupportedOperationException
2557     *         if input ROI is ROI4D or ROI5D while scaling is required (scaling not supported for these ROI)
2558     */
2559    public static ROI adjustToSequence(ROI roi, Sequence source, Sequence destination)
2560            throws UnsupportedOperationException
2561    {
2562        return adjustToSequence(roi, source, destination, true, true);
2563    }
2564
2565    // /**
2566    // * Create and returns a new ROI adjusted to the specified sequence if it represents a sub region of another
2567    // * Sequence.<br>
2568    // * You need to use this function when you generated the ROI on the origin Sequence and want to have it adjusted to
2569    // * the given sub region Sequence coordinates.<br>
2570    // * ROI coordinates can be affected if the {@link Sequence#getOriginXYRegion()} is not <code>null</code>.<br>
2571    // * If {@link Sequence#getOriginResolution()} is not <code>0</code> then the returned ROI will be down scaled to fit
2572    // * the Sequence image resolution.<br>
2573    // * Note that the returned ROI can have a Boolean Mask format if we can't re-use original ROI format..
2574    // *
2575    // * @param roi
2576    // * input ROI we want to get the adjusted form
2577    // * @param subSequence
2578    // * the sequence representing the sub region of the origin Sequence (it should contains <i>origin</i>
2579    // * information, see Sequence#getOriginXXX(} methods)
2580    // * @return adjusted ROI
2581    // * @throws UnsupportedOperationException
2582    // * if input ROI is ROI4D or ROI5D while scaling is required (scaling not supported for these ROI)
2583    // */
2584    // public static ROI adjustToSequence(ROI roi, Sequence subSequence) throws UnsupportedOperationException
2585    // {
2586    // if (roi == null)
2587    // return null;
2588    //
2589    // ROI result = roi.getCopy();
2590    //
2591    // if (subSequence != null)
2592    // {
2593    // int res = subSequence.getOriginResolution();
2594    // final double scaleFactor = 1d / Math.pow(2d, res);
2595    //
2596    // // down scaling
2597    // while (res-- > 0)
2598    // result = getDownscaled(result, false);
2599    //
2600    // // can set position ? --> relocate it
2601    // if (result.canSetPosition())
2602    // {
2603    // // get current position
2604    // final Point5D pos = result.getPosition5D();
2605    //
2606    // final Rectangle originPos = subSequence.getOriginXYRegion();
2607    // // sub region ?
2608    // if (originPos != null)
2609    // {
2610    // pos.setX(pos.getX() - (originPos.getX() * scaleFactor));
2611    // pos.setY(pos.getY() - (originPos.getY() * scaleFactor));
2612    // }
2613    //
2614    // final int zMin = subSequence.getOriginZMin();
2615    // // sub Z stack part ?
2616    // if (zMin != -1)
2617    // {
2618    // // can change it ? (we don't scale Z dimension)
2619    // if (!Double.isInfinite(pos.getZ()))
2620    // pos.setZ(pos.getZ() - zMin);
2621    // }
2622    //
2623    // final int tMin = subSequence.getOriginTMin();
2624    // // sub T sequence part ?
2625    // if (tMin != -1)
2626    // {
2627    // // can change it ? (we don't scale T dimension)
2628    // if (!Double.isInfinite(pos.getT()))
2629    // pos.setT(pos.getT() - tMin);
2630    // }
2631    //
2632    // final int c = subSequence.getOriginChannel();
2633    // // sub channel ?
2634    // if (c != -1)
2635    // {
2636    // // can change it ? (we don't scale C dimension)
2637    // if (!Double.isInfinite(pos.getC()))
2638    // pos.setC(pos.getC() - c);
2639    // }
2640    //
2641    // // set back position
2642    // result.setPosition5D(pos);
2643    // }
2644    // }
2645    //
2646    // return result;
2647    // }
2648    //
2649    // /**
2650    // * Create and returns a new ROI adjusted to the origin sequence given the sub region Sequence.<br>
2651    // * You need to use this function when you generated the ROI on the given sub region Sequence and want to have it
2652    // * back in the origin Sequence coordinates.<br>
2653    // * ROI coordinates can be affected if the {@link Sequence#getOriginXYRegion()} is not <code>null</code>.<br>
2654    // * If {@link Sequence#getOriginResolution()} is not <code>0</code> then the returned ROI will be up scaled to fit
2655    // * the original image resolution.<br>
2656    // * Note that the returned ROI can have a Boolean Mask format if we can't re-use original ROI format.
2657    // *
2658    // * @param roi
2659    // * input ROI we want to get the adjusted form
2660    // * @param subSequence
2661    // * the sequence representing the sub region of the origin Sequence (it should contains <i>origin</i>
2662    // * information, see Sequence#getOriginXXX(} methods)
2663    // * @return adjusted ROI
2664    // * @throws UnsupportedOperationException
2665    // * if input ROI is ROI4D or ROI5D while scaling is required (scaling not supported for these ROI)
2666    // */
2667    // public static ROI adjustToOriginSequence(ROI roi, Sequence subSequence) throws UnsupportedOperationException
2668    // {
2669    // if (roi == null)
2670    // return null;
2671    //
2672    // ROI result = roi.getCopy();
2673    //
2674    // if (subSequence != null)
2675    // {
2676    // int res = subSequence.getOriginResolution();
2677    //
2678    // // up scaling (2D)
2679    // while (res-- > 0)
2680    // result = getUpscaled(result, false);
2681    //
2682    // // can set position ? --> relocate it
2683    // if (result.canSetPosition())
2684    // {
2685    // // get current position
2686    // final Point5D pos = result.getPosition5D();
2687    //
2688    // final Rectangle originPos = subSequence.getOriginXYRegion();
2689    // // sub region ?
2690    // if (originPos != null)
2691    // {
2692    // pos.setX(pos.getX() + originPos.getX());
2693    // pos.setY(pos.getY() + originPos.getY());
2694    // }
2695    //
2696    // final int zMin = subSequence.getOriginZMin();
2697    // // sub Z stack part ?
2698    // if (zMin != -1)
2699    // {
2700    // // can change it ?
2701    // if (!Double.isInfinite(pos.getZ()))
2702    // pos.setZ(pos.getZ() + zMin);
2703    // }
2704    //
2705    // final int tMin = subSequence.getOriginTMin();
2706    // // sub T sequence part ?
2707    // if (tMin != -1)
2708    // {
2709    // // can change it ?
2710    // if (!Double.isInfinite(pos.getT()))
2711    // pos.setT(pos.getT() + tMin);
2712    // }
2713    //
2714    // final int c = subSequence.getOriginChannel();
2715    // // sub channel ?
2716    // if (c != -1)
2717    // {
2718    // // can change it ?
2719    // if (!Double.isInfinite(pos.getC()))
2720    // pos.setC(pos.getC() + c);
2721    // }
2722    //
2723    // // set back position
2724    // result.setPosition5D(pos);
2725    // }
2726    // }
2727    //
2728    // return result;
2729    // }
2730
2731    /**
2732     * Copy properties (name, color...) from <code>source</code> ROI and apply it to <code>destination</code> ROI.
2733     */
2734    public static void copyROIProperties(ROI source, ROI destination, boolean copyName)
2735    {
2736        if ((source == null) || (destination == null))
2737            return;
2738
2739        if (copyName)
2740            destination.setName(source.getName());
2741        destination.setColor(source.getColor());
2742        destination.setOpacity(source.getOpacity());
2743        destination.setStroke(source.getStroke());
2744        destination.setReadOnly(source.isReadOnly());
2745        destination.setSelected(source.isSelected());
2746        destination.setShowName(source.getShowName());
2747        destination.setGroupId(source.getGroupId());
2748
2749        // copy extended properties
2750        for (Entry<String, String> propertyEntry : source.getProperties().entrySet())
2751            destination.setProperty(propertyEntry.getKey(), propertyEntry.getValue());
2752    }
2753}