001/**
002 * 
003 */
004package icy.roi;
005
006import icy.type.collection.array.DynamicArray;
007import icy.type.point.Point4D;
008import icy.type.rectangle.Rectangle3D;
009import icy.type.rectangle.Rectangle4D;
010
011import java.awt.Rectangle;
012import java.util.Map.Entry;
013import java.util.TreeMap;
014
015/**
016 * Class to define a 4D boolean mask region and make basic boolean operation between masks.<br>
017 * The bounds property of this object represents the region defined by the boolean mask.
018 * 
019 * @author Stephane
020 */
021public class BooleanMask4D
022{
023    // Internal use only
024    private static BooleanMask3D doUnion3D(BooleanMask3D m1, BooleanMask3D m2)
025    {
026        if (m1 == null)
027        {
028            // only use the 3D mask from second mask
029            if (m2 != null)
030                return (BooleanMask3D) m2.clone();
031
032            return null;
033        }
034        else if (m2 == null)
035            // only use the 3D mask from first mask
036            return (BooleanMask3D) m1.clone();
037
038        // process union of 3D mask
039        return BooleanMask3D.getUnion(m1, m2);
040    }
041
042    // Internal use only
043    private static BooleanMask3D doIntersection3D(BooleanMask3D m1, BooleanMask3D m2)
044    {
045        if ((m1 == null) || (m2 == null))
046            return null;
047
048        // process intersection of 3D mask
049        return BooleanMask3D.getIntersection(m1, m2);
050    }
051
052    // Internal use only
053    private static BooleanMask3D doExclusiveUnion3D(BooleanMask3D m1, BooleanMask3D m2)
054    {
055        if (m1 == null)
056        {
057            // only use the 3D mask from second mask
058            if (m2 != null)
059                return (BooleanMask3D) m2.clone();
060
061            return null;
062        }
063        else if (m2 == null)
064            // only use the 3D mask from first mask
065            return (BooleanMask3D) m1.clone();
066
067        // process exclusive union of 3D mask
068        return BooleanMask3D.getExclusiveUnion(m1, m2);
069    }
070
071    // Internal use only
072    private static BooleanMask3D doSubtraction3D(BooleanMask3D m1, BooleanMask3D m2)
073    {
074        if (m1 == null)
075            return null;
076        // only use the 3D mask from first mask
077        if (m2 == null)
078            return (BooleanMask3D) m1.clone();
079
080        // process subtraction of 3D mask
081        return BooleanMask3D.getSubtraction(m1, m2);
082    }
083
084    /**
085     * Build resulting mask from union of the mask1 and mask2:
086     * 
087     * <pre>
088     *        mask1          +       mask2        =      result
089     * 
090     *     ################     ################     ################
091     *     ##############         ##############     ################
092     *     ############             ############     ################
093     *     ##########                 ##########     ################
094     *     ########                     ########     ################
095     *     ######                         ######     ######    ######
096     *     ####                             ####     ####        ####
097     *     ##                                 ##     ##            ##
098     * </pre>
099     */
100    public static BooleanMask4D getUnion(BooleanMask4D mask1, BooleanMask4D mask2)
101    {
102        if ((mask1 == null) && (mask2 == null))
103            return new BooleanMask4D();
104
105        if ((mask1 == null) || mask1.isEmpty())
106            return (BooleanMask4D) mask2.clone();
107        if ((mask2 == null) || mask2.isEmpty())
108            return (BooleanMask4D) mask1.clone();
109
110        final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createUnion(mask2.bounds);
111
112        if (!bounds.isEmpty())
113        {
114            final BooleanMask3D[] mask;
115
116            // special case of infinite T dimension
117            if (bounds.sizeT == Integer.MAX_VALUE)
118            {
119                // we can allow merge ROI only if they both has infinite T dimension
120                if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE))
121                    throw new UnsupportedOperationException(
122                            "Cannot merge an infinite T dimension ROI with  a finite Z dimension ROI");
123
124                mask = new BooleanMask3D[1];
125
126                final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue();
127                final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue();
128
129                mask[0] = doUnion3D(m2d1, m2d2);
130            }
131            else
132            {
133                mask = new BooleanMask3D[bounds.sizeT];
134
135                for (int t = 0; t < bounds.sizeT; t++)
136                {
137                    final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t);
138                    final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t);
139
140                    mask[t] = doUnion3D(m2d1, m2d2);
141                }
142            }
143
144            return new BooleanMask4D(bounds, mask);
145        }
146
147        return new BooleanMask4D();
148    }
149
150    /**
151     * Build resulting mask from intersection of the mask1 and mask2:
152     * 
153     * <pre>
154     *        mask1     intersect     mask2      =        result
155     * 
156     *     ################     ################     ################
157     *     ##############         ##############       ############
158     *     ############             ############         ########
159     *     ##########                 ##########           ####
160     *     ########                     ########
161     *     ######                         ######
162     *     ####                             ####
163     *     ##                                 ##
164     * </pre>
165     */
166    public static BooleanMask4D getIntersection(BooleanMask4D mask1, BooleanMask4D mask2)
167    {
168        if ((mask1 == null) || (mask2 == null))
169            return new BooleanMask4D();
170
171        final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createIntersection(mask2.bounds);
172
173        if (!bounds.isEmpty())
174        {
175            final BooleanMask3D[] mask;
176
177            // special case of infinite T dimension
178            if (bounds.sizeT == Integer.MAX_VALUE)
179            {
180                // we can allow merge ROI only if they both has infinite T dimension
181                if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE))
182                    throw new UnsupportedOperationException(
183                            "Cannot merge an infinite T dimension ROI with  a finite Z dimension ROI");
184
185                mask = new BooleanMask3D[1];
186
187                final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue();
188                final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue();
189
190                mask[0] = doIntersection3D(m2d1, m2d2);
191            }
192            else
193            {
194                mask = new BooleanMask3D[bounds.sizeT];
195
196                for (int t = 0; t < bounds.sizeT; t++)
197                {
198                    final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t);
199                    final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t);
200
201                    mask[t] = doIntersection3D(m2d1, m2d2);
202                }
203            }
204
205            return new BooleanMask4D(bounds, mask);
206        }
207
208        return new BooleanMask4D();
209    }
210
211    /**
212     * Build resulting mask from exclusive union of the mask1 and mask2:
213     * 
214     * <pre>
215     *          mask1       xor      mask2        =       result
216     * 
217     *     ################     ################
218     *     ##############         ##############     ##            ##
219     *     ############             ############     ####        ####
220     *     ##########                 ##########     ######    ######
221     *     ########                     ########     ################
222     *     ######                         ######     ######    ######
223     *     ####                             ####     ####        ####
224     *     ##                                 ##     ##            ##
225     * </pre>
226     */
227    public static BooleanMask4D getExclusiveUnion(BooleanMask4D mask1, BooleanMask4D mask2)
228    {
229        if ((mask1 == null) && (mask2 == null))
230            return new BooleanMask4D();
231
232        if ((mask1 == null) || mask1.isEmpty())
233            return (BooleanMask4D) mask2.clone();
234        if ((mask2 == null) || mask2.isEmpty())
235            return (BooleanMask4D) mask1.clone();
236
237        final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createUnion(mask2.bounds);
238
239        if (!bounds.isEmpty())
240        {
241            final BooleanMask3D[] mask;
242
243            // special case of infinite T dimension
244            if (bounds.sizeT == Integer.MAX_VALUE)
245            {
246                // we can allow merge ROI only if they both has infinite T dimension
247                if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE))
248                    throw new UnsupportedOperationException(
249                            "Cannot merge an infinite T dimension ROI with  a finite Z dimension ROI");
250
251                mask = new BooleanMask3D[1];
252
253                final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue();
254                final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue();
255
256                mask[0] = doExclusiveUnion3D(m2d1, m2d2);
257            }
258            else
259            {
260                mask = new BooleanMask3D[bounds.sizeT];
261
262                for (int t = 0; t < bounds.sizeT; t++)
263                {
264                    final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t);
265                    final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t);
266
267                    mask[t] = doExclusiveUnion3D(m2d1, m2d2);
268                }
269            }
270
271            return new BooleanMask4D(bounds, mask);
272        }
273
274        return new BooleanMask4D();
275    }
276
277    /**
278     * Build resulting mask from the subtraction of mask2 from mask1:
279     * 
280     * <pre>
281     *        mask1          -        mask2       =  result
282     * 
283     *     ################     ################
284     *     ##############         ##############     ##
285     *     ############             ############     ####
286     *     ##########                 ##########     ######
287     *     ########                     ########     ########
288     *     ######                         ######     ######
289     *     ####                             ####     ####
290     *     ##                                 ##     ##
291     * </pre>
292     */
293    public static BooleanMask4D getSubtraction(BooleanMask4D mask1, BooleanMask4D mask2)
294    {
295        if (mask1 == null)
296            return new BooleanMask4D();
297        if (mask2 == null)
298            return (BooleanMask4D) mask1.clone();
299
300        final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createIntersection(mask2.bounds);
301
302        // need to subtract something ?
303        if (!bounds.isEmpty())
304        {
305            final BooleanMask3D[] mask;
306
307            // special case of infinite T dimension
308            if (bounds.sizeT == Integer.MAX_VALUE)
309            {
310                // we can allow merge ROI only if they both has infinite T dimension
311                if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE))
312                    throw new UnsupportedOperationException(
313                            "Cannot merge an infinite T dimension ROI with  a finite Z dimension ROI");
314
315                mask = new BooleanMask3D[1];
316
317                final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue();
318                final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue();
319
320                mask[0] = doSubtraction3D(m2d1, m2d2);
321            }
322            else
323            {
324                mask = new BooleanMask3D[bounds.sizeT];
325
326                for (int t = 0; t < bounds.sizeT; t++)
327                {
328                    final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t);
329                    final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t);
330
331                    mask[t] = doSubtraction3D(m2d1, m2d2);
332                }
333            }
334
335            return new BooleanMask4D(bounds, mask);
336        }
337
338        return (BooleanMask4D) mask1.clone();
339    }
340
341    /**
342     * Region represented by the mask.
343     */
344    public Rectangle4D.Integer bounds;
345    /**
346     * Boolean mask 3D array.
347     */
348    public final TreeMap<Integer, BooleanMask3D> mask;
349
350    /**
351     * Build a new 4D boolean mask with specified bounds and 3D mask array.<br>
352     * The 3D mask array length should be >= to <code>bounds.getSizeT()</code>.
353     */
354    public BooleanMask4D(Rectangle4D.Integer bounds, BooleanMask3D[] mask)
355    {
356        super();
357
358        this.bounds = bounds;
359        this.mask = new TreeMap<Integer, BooleanMask3D>();
360
361        // special case of infinite T dim
362        if (bounds.sizeT == Integer.MAX_VALUE)
363            this.mask.put(Integer.valueOf(Integer.MIN_VALUE), mask[0]);
364        else
365        {
366            for (int t = 0; t < bounds.sizeT; t++)
367                if (mask[t] != null)
368                    this.mask.put(Integer.valueOf(bounds.t + t), mask[t]);
369        }
370    }
371
372    /**
373     * Build a new 4D boolean mask from the specified array of {@link Point4D}.<br>
374     */
375    public BooleanMask4D(Point4D.Integer[] points)
376    {
377        super();
378
379        mask = new TreeMap<Integer, BooleanMask3D>();
380
381        if ((points == null) || (points.length == 0))
382            bounds = new Rectangle4D.Integer();
383        else
384        {
385            int minX = Integer.MAX_VALUE;
386            int minY = Integer.MAX_VALUE;
387            int minZ = Integer.MAX_VALUE;
388            int minT = Integer.MAX_VALUE;
389            int maxX = Integer.MIN_VALUE;
390            int maxY = Integer.MIN_VALUE;
391            int maxZ = Integer.MIN_VALUE;
392            int maxT = Integer.MIN_VALUE;
393
394            for (Point4D.Integer pt : points)
395            {
396                final int x = pt.x;
397                final int y = pt.y;
398                final int z = pt.z;
399                final int t = pt.t;
400
401                if (x < minX)
402                    minX = x;
403                if (x > maxX)
404                    maxX = x;
405                if (y < minY)
406                    minY = y;
407                if (y > maxY)
408                    maxY = y;
409                if (z < minZ)
410                    minZ = z;
411                if (z > maxZ)
412                    maxZ = z;
413                if (t < minT)
414                    minT = t;
415                if (t > maxT)
416                    maxT = t;
417            }
418
419            // define bounds
420            bounds = new Rectangle4D.Integer(minX, minY, minZ, minT, (maxX - minX) + 1, (maxY - minY) + 1,
421                    (maxZ - minZ) + 1, (maxT - minT) + 1);
422
423            // set mask
424            for (Point4D.Integer pt : points)
425            {
426                BooleanMask3D m3d = mask.get(Integer.valueOf(pt.t));
427
428                // allocate 3D boolean mask if needed
429                if (m3d == null)
430                {
431                    m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY,
432                            bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]);
433                    // set 3D mask for position T
434                    mask.put(Integer.valueOf(pt.t), m3d);
435                }
436
437                BooleanMask2D m2d = m3d.getMask2D(pt.z);
438
439                // allocate 2D boolean mask if needed
440                if (m2d == null)
441                {
442                    m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY),
443                            new boolean[bounds.sizeX * bounds.sizeY]);
444                    // set 2D mask for position Z
445                    m3d.mask.put(Integer.valueOf(pt.z), m2d);
446                }
447
448                // set mask point
449                m2d.mask[((pt.y - minY) * bounds.sizeX) + (pt.x - minX)] = true;
450            }
451
452            // optimize mask 3D bounds
453            for (BooleanMask3D m : mask.values())
454                m.optimizeBounds();
455        }
456    }
457
458    /**
459     * Build a new boolean mask from the specified array of {@link Point4D}.<br>
460     */
461    public BooleanMask4D(Point4D[] points)
462    {
463        super();
464
465        mask = new TreeMap<Integer, BooleanMask3D>();
466
467        if ((points == null) || (points.length == 0))
468            bounds = new Rectangle4D.Integer();
469        else
470        {
471            int minX = Integer.MAX_VALUE;
472            int minY = Integer.MAX_VALUE;
473            int minZ = Integer.MAX_VALUE;
474            int minT = Integer.MAX_VALUE;
475            int maxX = Integer.MIN_VALUE;
476            int maxY = Integer.MIN_VALUE;
477            int maxZ = Integer.MIN_VALUE;
478            int maxT = Integer.MIN_VALUE;
479
480            for (Point4D pt : points)
481            {
482                final int x = (int) pt.getX();
483                final int y = (int) pt.getY();
484                final int z = (int) pt.getZ();
485                final int t = (int) pt.getT();
486
487                if (x < minX)
488                    minX = x;
489                if (x > maxX)
490                    maxX = x;
491                if (y < minY)
492                    minY = y;
493                if (y > maxY)
494                    maxY = y;
495                if (z < minZ)
496                    minZ = z;
497                if (z > maxZ)
498                    maxZ = z;
499                if (t < minT)
500                    minT = t;
501                if (t > maxT)
502                    maxT = t;
503            }
504
505            // define bounds
506            bounds = new Rectangle4D.Integer(minX, minY, minZ, minT, (maxX - minX) + 1, (maxY - minY) + 1,
507                    (maxZ - minZ) + 1, (maxT - minT) + 1);
508
509            // set mask
510            for (Point4D pt : points)
511            {
512                BooleanMask3D m3d = mask.get(Integer.valueOf((int) pt.getT()));
513
514                // allocate 3D boolean mask if needed
515                if (m3d == null)
516                {
517                    m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY,
518                            bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]);
519                    // set 3D mask for position T
520                    mask.put(Integer.valueOf((int) pt.getT()), m3d);
521                }
522
523                BooleanMask2D m2d = m3d.getMask2D((int) pt.getZ());
524
525                // allocate 2D boolean mask if needed
526                if (m2d == null)
527                {
528                    m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY),
529                            new boolean[bounds.sizeX * bounds.sizeY]);
530                    // set 2D mask for position Z
531                    m3d.mask.put(Integer.valueOf((int) pt.getZ()), m2d);
532                }
533
534                // set mask point
535                m2d.mask[(((int) pt.getY() - minY) * bounds.sizeX) + ((int) pt.getX() - minX)] = true;
536            }
537
538            // optimize mask 3D bounds
539            for (BooleanMask3D m : mask.values())
540                m.optimizeBounds();
541        }
542    }
543
544    public BooleanMask4D()
545    {
546        this(new Rectangle4D.Integer(), new BooleanMask3D[0]);
547    }
548
549    /**
550     * Returns the 3D boolean mask for the specified T position
551     */
552    public BooleanMask3D getMask3D(int t)
553    {
554        // special case of infinite T dimension
555        if (bounds.sizeT == Integer.MAX_VALUE)
556            return mask.firstEntry().getValue();
557
558        return mask.get(Integer.valueOf(t));
559    }
560
561    /**
562     * Returns the 2D boolean mask for the specified Z, T position
563     */
564    public BooleanMask2D getMask2D(int z, int t)
565    {
566        final BooleanMask3D m = getMask3D(t);
567
568        if (m != null)
569            return m.getMask2D(z);
570
571        return null;
572    }
573
574    /**
575     * Return <code>true</code> if boolean mask is empty
576     */
577    public boolean isEmpty()
578    {
579        return bounds.isEmpty();
580    }
581
582    /**
583     * Return true if mask contains the specified point
584     */
585    public boolean contains(int x, int y, int z, int t)
586    {
587        if (bounds.contains(x, y, z, t))
588        {
589            final BooleanMask3D m3d = getMask3D(t);
590
591            if (m3d != null)
592                return m3d.contains(x, y, z);
593        }
594
595        return false;
596    }
597
598    /**
599     * Return true if mask contains the specified 2D mask at position Z, T.
600     */
601    public boolean contains(BooleanMask2D booleanMask, int z, int t)
602    {
603        if (isEmpty())
604            return false;
605
606        final BooleanMask2D mask2d = getMask2D(z, t);
607
608        if (mask2d != null)
609            return mask2d.contains(booleanMask);
610
611        return false;
612    }
613
614    /**
615     * Return true if mask contains the specified 3D mask at position t.
616     */
617    public boolean contains(BooleanMask3D booleanMask, int t)
618    {
619        if (isEmpty())
620            return false;
621
622        final BooleanMask3D mask3d = getMask3D(t);
623
624        if (mask3d != null)
625            return mask3d.contains(booleanMask);
626
627        return false;
628    }
629
630    /**
631     * Return true if mask contains the specified 4D mask.
632     */
633    public boolean contains(BooleanMask4D booleanMask)
634    {
635        if (isEmpty())
636            return false;
637
638        final int sizeT = booleanMask.bounds.sizeT;
639
640        // check for special MAX_INTEGER case (infinite T dim)
641        if (sizeT == Integer.MAX_VALUE)
642        {
643            // we cannot contains it if we are not on infinite T dim too
644            if (bounds.sizeT != Integer.MAX_VALUE)
645                return false;
646
647            return booleanMask.mask.firstEntry().getValue().contains(mask.firstEntry().getValue());
648        }
649
650        final int offT = booleanMask.bounds.t;
651
652        for (int t = offT; t < offT + sizeT; t++)
653            if (!contains(booleanMask.getMask3D(t), t))
654                return false;
655
656        return true;
657    }
658
659    /**
660     * Return true if mask intersects (contains at least one point) the specified 2D mask at
661     * position Z, T
662     */
663    public boolean intersects(BooleanMask2D booleanMask, int z, int t)
664    {
665        if (isEmpty())
666            return false;
667
668        final BooleanMask2D mask2d = getMask2D(z, t);
669
670        if (mask2d != null)
671            return mask2d.intersects(booleanMask);
672
673        return false;
674    }
675
676    /**
677     * Return true if mask intersects (contains at least one point) the specified 3D mask at
678     * position T
679     */
680    public boolean intersects(BooleanMask3D booleanMask, int t)
681    {
682        if (isEmpty())
683            return false;
684
685        final BooleanMask3D mask3d = getMask3D(t);
686
687        if (mask3d != null)
688            return mask3d.intersects(booleanMask);
689
690        return false;
691    }
692
693    /**
694     * Return true if mask intersects (contains at least one point) the specified 4D mask region
695     */
696    public boolean intersects(BooleanMask4D booleanMask)
697    {
698        if (isEmpty())
699            return false;
700
701        final int sizeT = booleanMask.bounds.sizeT;
702
703        // check for special MAX_INTEGER case (infinite T dim)
704        if (sizeT == Integer.MAX_VALUE)
705        {
706            // get the single T slice
707            final BooleanMask3D mask3d = booleanMask.mask.firstEntry().getValue();
708
709            // test with every slice
710            for (BooleanMask3D m : mask.values())
711                if (m.intersects(mask3d))
712                    return true;
713
714            return false;
715        }
716
717        // check for special MAX_INTEGER case (infinite T dim)
718        if (bounds.sizeT == Integer.MAX_VALUE)
719        {
720            // get the single T slice
721            final BooleanMask3D mask3d = mask.firstEntry().getValue();
722
723            // test with every slice
724            for (BooleanMask3D m : booleanMask.mask.values())
725                if (m.intersects(mask3d))
726                    return true;
727
728            return false;
729        }
730
731        final int offT = booleanMask.bounds.t;
732
733        for (int t = offT; t < offT + sizeT; t++)
734            if (intersects(booleanMask.getMask3D(t), t))
735                return true;
736
737        return false;
738    }
739
740    /**
741     * Optimize mask bounds so it fits mask content.
742     */
743    public Rectangle4D.Integer getOptimizedBounds(boolean compute3DBounds)
744    {
745        final Rectangle4D.Integer result = new Rectangle4D.Integer();
746
747        if (mask.isEmpty())
748            return result;
749
750        Rectangle3D.Integer bounds3D = null;// new Rectangle3D.Integer();
751
752        for (BooleanMask3D m3d : mask.values())
753        {
754            // get optimized 3D bounds for each T
755            final Rectangle3D.Integer optB3d;
756
757            if (compute3DBounds)
758                optB3d = m3d.getOptimizedBounds();
759            else
760                optB3d = new Rectangle3D.Integer(m3d.bounds);
761
762            // only add non empty bounds
763            if (!optB3d.isEmpty())
764            {
765                if (bounds3D == null)
766                    bounds3D = optB3d;
767                else
768                    bounds3D.add(optB3d);
769            }
770        }
771
772        // empty ?
773        if ((bounds3D == null) || bounds3D.isEmpty())
774            return result;
775
776        int minT = mask.firstKey().intValue();
777        int maxT = mask.lastKey().intValue();
778
779        // set 3D bounds to start with
780        result.setX(bounds3D.x);
781        result.setY(bounds3D.y);
782        result.setZ(bounds3D.z);
783        result.setSizeX(bounds3D.sizeX);
784        result.setSizeY(bounds3D.sizeY);
785        result.setSizeZ(bounds3D.sizeZ);
786
787        // single T --> check for special MAX_INTEGER case
788        if ((minT == maxT) && (bounds.sizeT == Integer.MAX_VALUE))
789        {
790            result.setT(Integer.MIN_VALUE);
791            result.setSizeT(Integer.MAX_VALUE);
792        }
793        else
794        {
795            result.setT(minT);
796            result.setSizeT((maxT - minT) + 1);
797        }
798
799        return result;
800    }
801
802    /**
803     * Optimize mask bounds so it fits mask content.
804     */
805    public Rectangle4D.Integer getOptimizedBounds()
806    {
807        return getOptimizedBounds(true);
808    }
809
810    /**
811     * Optimize mask bounds so it fits mask content.
812     */
813    public void optimizeBounds()
814    {
815        // start by optimizing 3D bounds
816        for (BooleanMask3D m : mask.values())
817            m.optimizeBounds();
818
819        moveBounds(getOptimizedBounds(false));
820    }
821
822    /**
823     * Change the bounds of BooleanMask.<br>
824     * Keep mask data intersecting from old bounds.
825     */
826    public void moveBounds(Rectangle4D.Integer value)
827    {
828        // bounds changed ?
829        if (!bounds.equals(value))
830        {
831            // changed to empty mask
832            if (value.isEmpty())
833            {
834                // clear bounds and mask
835                bounds = new Rectangle4D.Integer();
836                mask.clear();
837                return;
838            }
839
840            final Rectangle3D.Integer bounds3D = new Rectangle3D.Integer(value.x, value.y, value.z, value.sizeX,
841                    value.sizeY, value.sizeZ);
842
843            // it was infinite T dim ?
844            if (bounds.sizeT == Integer.MAX_VALUE)
845            {
846                // get the single 3D mask
847                final BooleanMask3D m3d = mask.firstEntry().getValue();
848
849                // adjust 3D bounds if needed to the single 3D mask
850                m3d.moveBounds(bounds3D);
851
852                // we passed from infinite T to defined T range
853                if (value.sizeT != Integer.MAX_VALUE)
854                {
855                    // assign the same 3D mask for all C position
856                    mask.clear();
857                    for (int t = 0; t <= value.sizeT; t++)
858                        mask.put(Integer.valueOf(t + value.t), (BooleanMask3D) m3d.clone());
859                }
860            }
861            // we pass to infinite T dim
862            else if (value.sizeT == Integer.MAX_VALUE)
863            {
864                // try to use the 3D mask at T position
865                BooleanMask3D mask3D = getMask3D(value.t);
866
867                // otherwise we use the first found 3D mask
868                if ((mask3D == null) && !mask.isEmpty())
869                    mask3D = mask.firstEntry().getValue();
870
871                // set new mask
872                mask.clear();
873                if (mask3D != null)
874                    mask.put(Integer.valueOf(Integer.MIN_VALUE), mask3D);
875            }
876            else
877            {
878                // create new mask array
879                final BooleanMask3D[] newMask = new BooleanMask3D[value.sizeT];
880
881                for (int t = 0; t < value.sizeT; t++)
882                {
883                    final BooleanMask3D mask3D = getMask3D(value.t + t);
884
885                    if (mask3D != null)
886                        // adjust 3D bounds
887                        mask3D.moveBounds(bounds3D);
888
889                    newMask[t] = mask3D;
890                }
891
892                // set new mask
893                mask.clear();
894                for (int t = 0; t < value.sizeT; t++)
895                    mask.put(Integer.valueOf(value.t + t), newMask[t]);
896            }
897
898            bounds = value;
899        }
900    }
901
902    /**
903     * Transforms the specified 3D coordinates int array [x,y,z] in 4D coordinates int array [x,y,z,t] with the
904     * specified T value.
905     */
906    public static int[] toInt4D(int[] source3D, int t)
907    {
908        final int[] result = new int[(source3D.length * 4) / 3];
909
910        int pt = 0;
911        for (int i = 0; i < source3D.length; i += 3)
912        {
913            result[pt++] = source3D[i + 0];
914            result[pt++] = source3D[i + 1];
915            result[pt++] = source3D[i + 2];
916            result[pt++] = t;
917        }
918
919        return result;
920    }
921
922    /**
923     * Return an array of {@link icy.type.point.Point4D.Integer} containing the contour/surface
924     * points
925     * of the 4D mask.<br>
926     * Points are returned in ascending XYZT order. <br>
927     * <br>
928     * WARNING: The basic implementation is not totally accurate.<br>
929     * It returns all points from the first and the last T slices + contour points for intermediate
930     * T
931     * slices.
932     * 
933     * @see #getContourPointsAsIntArray()
934     */
935    public Point4D.Integer[] getContourPoints()
936    {
937        return Point4D.Integer.toPoint4D(getContourPointsAsIntArray());
938    }
939
940    /**
941     * Return an array of integer containing the contour/surface points of the 4D mask.<br>
942     * <code>result.length</code> = number of point * 4<br>
943     * <code>result[(pt * 4) + 0]</code> = X coordinate for point <i>pt</i>.<br>
944     * <code>result[(pt * 4) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
945     * <code>result[(pt * 4) + 2]</code> = Z coordinate for point <i>pt</i>.<br>
946     * <code>result[(pt * 4) + 3]</code> = T coordinate for point <i>pt</i>.<br>
947     * Points are returned in ascending XYZT order.<br>
948     * <br>
949     * WARNING: The basic implementation is not totally accurate.<br>
950     * It returns all points from the first and the last T slices + contour points for intermediate
951     * T
952     * slices.
953     * 
954     * @see #getContourPoints()
955     */
956    public int[] getContourPointsAsIntArray()
957    {
958        final DynamicArray.Int result = new DynamicArray.Int(8);
959
960        // perimeter = first slice volume + inter slices perimeter + last slice volume
961        // TODO: fix this method and use real 4D contour point
962        if (mask.size() <= 2)
963        {
964            for (Entry<Integer, BooleanMask3D> entry : mask.entrySet())
965                result.add(toInt4D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue()));
966        }
967        else
968        {
969            final Entry<Integer, BooleanMask3D> firstEntry = mask.firstEntry();
970            final Entry<Integer, BooleanMask3D> lastEntry = mask.lastEntry();
971            final Integer firstKey = firstEntry.getKey();
972            final Integer lastKey = lastEntry.getKey();
973
974            result.add(toInt4D(firstEntry.getValue().getPointsAsIntArray(), firstKey.intValue()));
975
976            for (Entry<Integer, BooleanMask3D> entry : mask.subMap(firstKey, false, lastKey, false).entrySet())
977                result.add(toInt4D(entry.getValue().getContourPointsAsIntArray(), entry.getKey().intValue()));
978
979            result.add(toInt4D(lastEntry.getValue().getPointsAsIntArray(), lastKey.intValue()));
980        }
981
982        return result.asArray();
983    }
984
985    /**
986     * Return the number of points contained in this boolean mask.
987     */
988    public int getNumberOfPoints()
989    {
990        int result = 0;
991
992        for (BooleanMask3D mask3d : mask.values())
993            result += mask3d.getNumberOfPoints();
994
995        return result;
996    }
997
998    /**
999     * Return an array of {@link icy.type.point.Point4D.Integer} representing all points of the
1000     * current 4D mask.<br>
1001     * Points are returned in ascending XYZT order.
1002     */
1003    public Point4D.Integer[] getPoints()
1004    {
1005        return Point4D.Integer.toPoint4D(getPointsAsIntArray());
1006    }
1007
1008    /**
1009     * Return an array of integer representing all points of the current 4D mask.<br>
1010     * <code>result.length</code> = number of point * 4<br>
1011     * <code>result[(pt * 4) + 0]</code> = X coordinate for point <i>pt</i>.<br>
1012     * <code>result[(pt * 4) + 1]</code> = Y coordinate for point <i>pt</i>.<br>
1013     * <code>result[(pt * 4) + 2]</code> = Z coordinate for point <i>pt</i>.<br>
1014     * <code>result[(pt * 4) + 3]</code> = T coordinate for point <i>pt</i>.<br>
1015     * Points are returned in ascending XYZT order.
1016     */
1017    public int[] getPointsAsIntArray()
1018    {
1019        final DynamicArray.Int result = new DynamicArray.Int(8);
1020
1021        for (Entry<Integer, BooleanMask3D> entry : mask.entrySet())
1022            result.add(toInt4D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue()));
1023
1024        return result.asArray();
1025    }
1026
1027    @Override
1028    public Object clone()
1029    {
1030        final BooleanMask4D result = new BooleanMask4D();
1031
1032        result.bounds = new Rectangle4D.Integer(bounds);
1033        for (Entry<Integer, BooleanMask3D> entry : mask.entrySet())
1034            result.mask.put(entry.getKey(), (BooleanMask3D) entry.getValue().clone());
1035
1036        return result;
1037    }
1038}