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