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 icy.canvas.IcyCanvas;
022import icy.type.point.Point4D;
023import icy.type.point.Point5D;
024import icy.type.rectangle.Rectangle3D;
025import icy.type.rectangle.Rectangle4D;
026import icy.type.rectangle.Rectangle5D;
027import icy.util.XMLUtil;
028
029import java.awt.Rectangle;
030import java.util.ArrayList;
031import java.util.List;
032
033import org.w3c.dom.Node;
034
035/**
036 * 4D ROI base class.
037 */
038public abstract class ROI4D extends ROI
039{
040    /**
041     * @deprecated Use {@link ROI4D#getROI4DList(List)} instead.
042     */
043    @Deprecated
044    public static ArrayList<ROI4D> getROI4DList(ArrayList<ROI> rois)
045    {
046        final ArrayList<ROI4D> result = new ArrayList<ROI4D>();
047
048        for (ROI roi : rois)
049            if (roi instanceof ROI4D)
050                result.add((ROI4D) roi);
051
052        return result;
053    }
054
055    /**
056     * Return all 4D ROI from the ROI list
057     */
058    public static List<ROI4D> getROI4DList(List<ROI> rois)
059    {
060        final List<ROI4D> result = new ArrayList<ROI4D>();
061
062        for (ROI roi : rois)
063            if (roi instanceof ROI4D)
064                result.add((ROI4D) roi);
065
066        return result;
067    }
068
069    public static final String ID_C = "c";
070
071    /**
072     * c coordinate attachment
073     */
074    protected int c;
075
076    public ROI4D()
077    {
078        super();
079
080        // by default we consider no specific C attachment
081        c = -1;
082    }
083
084    @Override
085    public String getDefaultName()
086    {
087        return "ROI4D";
088    }
089
090    @Override
091    final public int getDimension()
092    {
093        return 4;
094    }
095
096    /**
097     * Returns true if specified ROI is on the same [C] position than current ROI.
098     * 
099     * @param shouldContain
100     *        if <code>true</code> then current ROI should "contains" specified ROI position [C]
101     */
102    protected boolean onSamePos(ROI4D roi, boolean shouldContain)
103    {
104        final int c = getC();
105        final int roiC = roi.getC();
106
107        // same position ?
108        if (shouldContain)
109        {
110            if ((c != -1) && (c != roiC))
111                return false;
112        }
113        else
114        {
115            if ((c != -1) && (roiC != -1) && (c != roiC))
116                return false;
117        }
118
119        return true;
120    }
121
122    /**
123     * Tests if a specified {@link Point4D} is inside the ROI.
124     * 
125     * @param p
126     *        the specified <code>Point4D</code> to be tested
127     * @return <code>true</code> if the specified <code>Point3D</code> is inside the boundary of the <code>ROI</code>;
128     *         <code>false</code> otherwise.
129     */
130    public boolean contains(Point4D p)
131    {
132        return contains(p.getX(), p.getY(), p.getZ(), p.getT());
133    }
134
135    /**
136     * Tests if the interior of the <code>ROI</code> entirely contains the specified <code>Rectangle4D</code>. The
137     * {@code ROI.contains()} method allows a implementation to
138     * conservatively return {@code false} when:
139     * <ul>
140     * <li>the <code>intersect</code> method returns <code>true</code> and
141     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the
142     * <code>Rectangle3D</code> are prohibitively expensive.
143     * </ul>
144     * This means that for some ROIs this method might return {@code false} even though the {@code ROI} contains the
145     * {@code Rectangle4D}.
146     * 
147     * @param r
148     *        The specified <code>Rectangle4D</code>
149     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the <code>Rectangle4D</code>;
150     *         <code>false</code> otherwise or, if the <code>ROI</code> contains the <code>Rectangle4D</code> and the
151     *         <code>intersects</code> method returns <code>true</code> and the containment calculations would be too
152     *         expensive to perform.
153     * @see #contains(double, double, double, double, double, double, double, double)
154     */
155    public boolean contains(Rectangle4D r)
156    {
157        return contains(r.getX(), r.getY(), r.getZ(), r.getT(), r.getSizeX(), r.getSizeY(), r.getSizeZ(), r.getSizeT());
158    }
159
160    /**
161     * Tests if the specified coordinates are inside the <code>ROI</code>.
162     * 
163     * @param x
164     *        the specified X coordinate to be tested
165     * @param y
166     *        the specified Y coordinate to be tested
167     * @param z
168     *        the specified Z coordinate to be tested
169     * @param t
170     *        the specified T coordinate to be tested
171     * @return <code>true</code> if the specified 4D coordinates are inside the <code>ROI</code> boundary;
172     *         <code>false</code> otherwise.
173     */
174    public abstract boolean contains(double x, double y, double z, double t);
175
176    /**
177     * Tests if the <code>ROI</code> entirely contains the specified 4D rectangular area. All
178     * coordinates that lie inside the rectangular area must lie within the <code>ROI</code> for the
179     * entire rectangular area to be considered contained within the <code>ROI</code>.
180     * <p>
181     * The {@code ROI.contains()} method allows a {@code ROI} implementation to conservatively return {@code false}
182     * when:
183     * <ul>
184     * <li>the <code>intersect</code> method returns <code>true</code> and
185     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the rectangular area are
186     * prohibitively expensive.
187     * </ul>
188     * This means that for some {@code ROIs} this method might return {@code false} even though the {@code ROI} contains
189     * the rectangular area.
190     * 
191     * @param x
192     *        the X coordinate of the minimum corner position of the specified rectangular area
193     * @param y
194     *        the Y coordinate of the minimum corner position of the specified rectangular area
195     * @param z
196     *        the Z coordinate of the minimum corner position of the specified rectangular area
197     * @param t
198     *        the T coordinate of the minimum corner position of the specified rectangular area
199     * @param sizeX
200     *        size for X dimension of the specified rectangular area
201     * @param sizeY
202     *        size for Y dimension of the specified rectangular area
203     * @param sizeZ
204     *        size for Z dimension of the specified rectangular area
205     * @param sizeT
206     *        size for T dimension of the specified rectangular area
207     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the
208     *         specified 4D rectangular area; <code>false</code> otherwise or, if the <code>ROI</code> contains the 4D
209     *         rectangular area and the <code>intersects</code> method returns <code>true</code> and the containment
210     *         calculations would be too
211     *         expensive to perform.
212     */
213    public abstract boolean contains(double x, double y, double z, double t, double sizeX, double sizeY, double sizeZ,
214            double sizeT);
215
216    @Override
217    public boolean contains(double x, double y, double z, double t, double c)
218    {
219        final boolean cok;
220
221        if (getC() == -1)
222            cok = true;
223        else
224            cok = (c >= getC()) && (c < (getC() + 1d));
225
226        return contains(x, y, z, t) && cok;
227    }
228
229    @Override
230    public boolean contains(double x, double y, double z, double t, double c, double sizeX, double sizeY, double sizeT,
231            double sizeZ, double sizeC)
232    {
233        final boolean cok;
234
235        if (getC() == -1)
236            cok = true;
237        else
238            cok = (c >= getC()) && ((c + sizeC) <= (getC() + 1d));
239
240        return contains(x, y, z, t, sizeX, sizeY, sizeZ, sizeT) && cok;
241    }
242
243    /*
244     * Generic implementation using the BooleanMask which is not accurate and slow.
245     * Override this for specific ROI type.
246     */
247    @Override
248    public boolean contains(ROI roi)
249    {
250        if (roi instanceof ROI4D)
251        {
252            final ROI4D roi4d = (ROI4D) roi;
253
254            if (onSamePos(roi4d, true))
255            {
256                // special case of ROI Point
257                if (roi4d.isEmpty())
258                    return contains(roi4d.getPosition4D());
259
260                BooleanMask4D mask;
261                BooleanMask4D roiMask;
262
263                // take content first
264                mask = getBooleanMask(false);
265                roiMask = roi4d.getBooleanMask(false);
266
267                // test first only on content
268                if (!mask.contains(roiMask))
269                    return false;
270
271                // take content and edge
272                mask = getBooleanMask(true);
273                roiMask = roi4d.getBooleanMask(true);
274
275                // then test on content and edge
276                if (!mask.contains(roiMask))
277                    return false;
278
279                // contained
280                return true;
281            }
282
283            return false;
284        }
285
286        // use default implementation
287        return super.contains(roi);
288    }
289
290    /**
291     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified <code>Rectangle4D</code>.
292     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
293     * when:
294     * <ul>
295     * <li>there is a high probability that the <code>Rectangle4D</code> and the <code>ROI</code> intersect, but
296     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
297     * </ul>
298     * This means that for some {@code ROIs} this method might return {@code true} even though the {@code Rectangle4D}
299     * does not intersect the {@code ROI}.
300     * 
301     * @param r
302     *        the specified <code>Rectangle4D</code>
303     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
304     *         specified <code>Rectangle4D</code> intersect, or are both highly likely to intersect
305     *         and intersection calculations would be too expensive to perform; <code>false</code> otherwise.
306     * @see #intersects(double, double, double,double, double, double, double, double)
307     */
308    public boolean intersects(Rectangle4D r)
309    {
310        return intersects(r.getX(), r.getY(), r.getZ(), r.getT(), r.getSizeX(), r.getSizeY(), r.getSizeZ(),
311                r.getSizeT());
312    }
313
314    /**
315     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified
316     * 4D rectangular area. The 4D rectangular area is considered to intersect the <code>ROI</code> if any point is
317     * contained in both the interior of the <code>ROI</code> and the specified
318     * rectangular area.
319     * <p>
320     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
321     * when:
322     * <ul>
323     * <li>there is a high probability that the 4D rectangular area and the <code>ROI</code> intersect, but
324     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
325     * </ul>
326     * This means that for some {@code ROIs} this method might return {@code true} even though the 4D rectangular area
327     * does not intersect the {@code ROI}.
328     * 
329     * @param x
330     *        the X coordinate of the minimum corner position of the specified rectangular area
331     * @param y
332     *        the Y coordinate of the minimum corner position of the specified rectangular area
333     * @param z
334     *        the Z coordinate of the minimum corner position of the specified rectangular area
335     * @param t
336     *        the T coordinate of the minimum corner position of the specified rectangular area
337     * @param sizeX
338     *        size for X dimension of the specified rectangular area
339     * @param sizeY
340     *        size for Y dimension of the specified rectangular area
341     * @param sizeZ
342     *        size for Z dimension of the specified rectangular area
343     * @param sizeT
344     *        size for T dimension of the specified rectangular area
345     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
346     *         rectangular area intersect, or are both highly likely to intersect and intersection
347     *         calculations would be too expensive to perform; <code>false</code> otherwise.
348     */
349    public abstract boolean intersects(double x, double y, double z, double t, double sizeX, double sizeY,
350            double sizeZ, double sizeT);
351
352    @Override
353    public boolean intersects(double x, double y, double z, double t, double c, double sizeX, double sizeY,
354            double sizeZ, double sizeT, double sizeC)
355    {
356        // easy discard
357        if ((sizeX == 0d) || (sizeY == 0d) || (sizeZ == 0d) || (sizeT == 0d) || (sizeC == 0d))
358            return false;
359
360        final boolean cok;
361
362        if (getC() == -1)
363            cok = true;
364        else
365            cok = ((c + sizeC) > getC()) && (c < (getC() + 1d));
366
367        return cok && intersects(x, y, z, t, sizeX, sizeY, sizeZ, sizeT);
368    }
369
370    /*
371     * Generic implementation using the BooleanMask which is not accurate and slow.
372     * Override this for specific ROI type.
373     */
374    @Override
375    public boolean intersects(ROI roi)
376    {
377        if (roi instanceof ROI4D)
378        {
379            final ROI4D roi4d = (ROI4D) roi;
380
381            if (onSamePos(roi4d, false))
382                return getBooleanMask(true).intersects(roi4d.getBooleanMask(true));
383        }
384
385        // use default implementation
386        return super.intersects(roi);
387    }
388
389    /**
390     * Calculate and returns the 4D bounding box of the <code>ROI</code>.<br>
391     * This method is used by {@link #getBounds4D()} which should try to cache the result as the
392     * bounding box calculation can take some computation time for complex ROI.
393     */
394    public abstract Rectangle4D computeBounds4D();
395
396    @Override
397    public Rectangle5D computeBounds5D()
398    {
399        final Rectangle4D bounds4D = computeBounds4D();
400        if (bounds4D == null)
401            return new Rectangle5D.Double();
402
403        final Rectangle5D.Double result = new Rectangle5D.Double(bounds4D.getX(), bounds4D.getY(), bounds4D.getZ(),
404                bounds4D.getT(), 0d, bounds4D.getSizeX(), bounds4D.getSizeY(), bounds4D.getSizeZ(),
405                bounds4D.getSizeT(), 0d);
406
407        if (getC() == -1)
408        {
409            result.c = Double.NEGATIVE_INFINITY;
410            result.sizeC = Double.POSITIVE_INFINITY;
411        }
412        else
413        {
414            result.c = getC();
415            result.sizeC = 1d;
416        }
417
418        return result;
419    }
420
421    /**
422     * Returns an integer {@link Rectangle4D} that completely encloses the <code>ROI</code>. Note
423     * that there is no guarantee that the returned <code>Rectangle4D</code> is the smallest
424     * bounding box that encloses the <code>ROI</code>, only that the <code>ROI</code> lies entirely
425     * within the indicated <code>Rectangle4D</code>. The returned <code>Rectangle4D</code> might
426     * also fail to completely enclose the <code>ROI</code> if the <code>ROI</code> overflows the
427     * limited range of the integer data type. The <code>getBounds4D</code> method generally returns
428     * a tighter bounding box due to its greater flexibility in representation.
429     * 
430     * @return an integer <code>Rectangle4D</code> that completely encloses the <code>ROI</code>.
431     */
432    public Rectangle4D.Integer getBounds()
433    {
434        return getBounds4D().toInteger();
435    }
436
437    /**
438     * Returns the bounding box of the <code>ROI</code>. Note that there is no guarantee that the
439     * returned {@link Rectangle4D} is the smallest bounding box that encloses the <code>ROI</code>,
440     * only that the <code>ROI</code> lies entirely within the indicated <code>Rectangle4D</code>.
441     * 
442     * @return an instance of <code>Rectangle4D</code> that is a bounding box of the <code>ROI</code>.
443     */
444    public Rectangle4D getBounds4D()
445    {
446        return getBounds5D().toRectangle4D();
447    }
448
449    /**
450     * Returns the integer ROI position which normally correspond to the <i>minimum</i> point of the
451     * ROI bounds.
452     * 
453     * @see #getBounds()
454     */
455    public Point4D.Integer getPosition()
456    {
457        final Rectangle4D.Integer bounds = getBounds();
458        return new Point4D.Integer(bounds.x, bounds.y, bounds.z, bounds.t);
459    }
460
461    /**
462     * Returns the ROI position which normally correspond to the <i>minimum</i> point of the ROI
463     * bounds.
464     * 
465     * @see #getBounds4D()
466     */
467    public Point4D getPosition4D()
468    {
469        return getBounds4D().getPosition();
470    }
471
472    @Override
473    public boolean canSetBounds()
474    {
475        // default
476        return false;
477    }
478
479    /**
480     * Set the <code>ROI</code> 4D bounds.<br>
481     * Note that not all ROI supports bounds modification and you should call {@link #canSetBounds()} first to test if
482     * the operation is supported.<br>
483     * 
484     * @param bounds
485     *        new ROI 4D bounds
486     */
487    public void setBounds4D(Rectangle4D bounds)
488    {
489        // do nothing by default (not supported)
490    }
491
492    @Override
493    public void setBounds5D(Rectangle5D bounds)
494    {
495        beginUpdate();
496        try
497        {
498            // infinite C dim ?
499            if (bounds.getSizeC() == Double.POSITIVE_INFINITY)
500                setC(-1);
501            else
502                setC((int) bounds.getC());
503
504            setBounds4D(bounds.toRectangle4D());
505        }
506        finally
507        {
508            endUpdate();
509        }
510    }
511
512    @Override
513    public boolean canSetPosition()
514    {
515        // default implementation use translation if available
516        return canTranslate();
517    }
518
519    /**
520     * Set the <code>ROI</code> 4D position.<br>
521     * Note that not all ROI supports position modification and you should call {@link #canSetPosition()} first to test
522     * if the operation is supported.<br>
523     * 
524     * @param position
525     *        new ROI 4D position
526     */
527    public void setPosition4D(Point4D position)
528    {
529        // use translation operation by default if supported
530        if (canTranslate())
531        {
532            final Point4D oldPos = getPosition4D();
533            translate(position.getX() - oldPos.getX(), position.getY() - oldPos.getY(),
534                    position.getZ() - oldPos.getZ(), position.getT() - oldPos.getT());
535        }
536    }
537
538    @Override
539    public void setPosition5D(Point5D position)
540    {
541        beginUpdate();
542        try
543        {
544            setC((int) position.getC());
545            setPosition4D(position.toPoint4D());
546        }
547        finally
548        {
549            endUpdate();
550        }
551    }
552
553    /**
554     * Returns <code>true</code> if the ROI support translate operation.
555     * 
556     * @see #translate(double, double, double, double)
557     */
558    public boolean canTranslate()
559    {
560        // by default
561        return false;
562    }
563
564    /**
565     * Translate the ROI position by the specified delta X/Y/Z/T.<br>
566     * Note that not all ROI support this operation so you should test it by calling {@link #canTranslate()} first.
567     * 
568     * @param dx
569     *        translation value to apply on X dimension
570     * @param dy
571     *        translation value to apply on Y dimension
572     * @param dz
573     *        translation value to apply on Z dimension
574     * @param dt
575     *        translation value to apply on T dimension
576     * @see #canTranslate()
577     * @see #setPosition4D(Point4D)
578     */
579    public void translate(double dx, double dy, double dz, double dt)
580    {
581
582    }
583
584    @Override
585    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, int c, boolean inclusive)
586    {
587        // not on the correct C position --> return empty mask
588        if (!isActiveFor(c))
589            return new boolean[Math.max(0, width) * Math.max(0, height)];
590
591        return getBooleanMask2D(x, y, width, height, z, t, inclusive);
592    }
593
594    /**
595     * Get the boolean bitmap mask for the specified rectangular area of the roi and for the
596     * specified Z,T position.<br>
597     * if the pixel (x,y) is contained in the roi Z,T position then result[(y * width) + x] = true<br>
598     * if the pixel (x,y) is not contained in the roi Z,T position then result[(y * width) + x] =
599     * 
600     * @param x
601     *        the X coordinate of the upper-left corner of the specified rectangular area
602     * @param y
603     *        the Y coordinate of the upper-left corner of the specified rectangular area
604     * @param width
605     *        the width of the specified rectangular area
606     * @param height
607     *        the height of the specified rectangular area
608     * @param z
609     *        Z position we want to retrieve the boolean mask
610     * @param t
611     *        T position we want to retrieve the boolean mask
612     * @param inclusive
613     *        If true then all partially contained (intersected) pixels are included in the mask.
614     * @return the boolean bitmap mask
615     */
616    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, boolean inclusive)
617    {
618        final boolean[] result = new boolean[Math.max(0, width) * Math.max(0, height)];
619
620        // simple and basic implementation, override it to have better performance
621        int offset = 0;
622        for (int j = 0; j < height; j++)
623        {
624            for (int i = 0; i < width; i++)
625            {
626                if (inclusive)
627                    result[offset] = intersects(x + i, y + j, z, t, 1d, 1d, 1d, 1d);
628                else
629                    result[offset] = contains(x + i, y + j, z, t, 1d, 1d, 1d, 1d);
630                offset++;
631            }
632        }
633
634        return result;
635    }
636
637    /**
638     * Get the boolean bitmap mask for the specified rectangular area of the roi and for the
639     * specified Z,T position.<br>
640     * if the pixel (x,y) is contained in the roi Z,T position then result[(y * width) + x] = true<br>
641     * if the pixel (x,y) is not contained in the roi Z,T position then result[(y * width) + x] =
642     * false
643     * 
644     * @param rect
645     *        2D rectangular area we want to retrieve the boolean mask
646     * @param z
647     *        Z position we want to retrieve the boolean mask
648     * @param t
649     *        T position we want to retrieve the boolean mask
650     * @param inclusive
651     *        If true then all partially contained (intersected) pixels are included in the mask.
652     */
653    public boolean[] getBooleanMask2D(Rectangle rect, int z, int t, boolean inclusive)
654    {
655        return getBooleanMask2D(rect.x, rect.y, rect.width, rect.height, z, t, inclusive);
656    }
657
658    @Override
659    public BooleanMask2D getBooleanMask2D(int z, int t, int c, boolean inclusive)
660    {
661        // not on the correct C position --> return empty mask
662        if (!isActiveFor(c))
663            return new BooleanMask2D(new Rectangle(), new boolean[0]);
664
665        return getBooleanMask2D(z, t, inclusive);
666    }
667
668    /**
669     * Get the {@link BooleanMask2D} object representing the roi for the specified Z,T position.<br>
670     * It contains the rectangle mask bounds and the associated boolean array mask.<br>
671     * if the pixel (x,y) is contained in the roi Z,T position then result.mask[(y * w) + x] = true<br>
672     * if the pixel (x,y) is not contained in the roi Z,T position then result.mask[(y * w) + x] =
673     * false
674     * 
675     * @param z
676     *        Z position we want to retrieve the boolean mask
677     * @param t
678     *        T position we want to retrieve the boolean mask
679     * @param inclusive
680     *        If true then all partially contained (intersected) pixels are included in the mask.
681     */
682    public BooleanMask2D getBooleanMask2D(int z, int t, boolean inclusive)
683    {
684        final Rectangle bounds = getBounds4D().toRectangle2D().getBounds();
685
686        // empty ROI --> return empty mask
687        if (bounds.isEmpty())
688            return new BooleanMask2D(new Rectangle(), new boolean[0]);
689
690        final BooleanMask2D result = new BooleanMask2D(bounds, getBooleanMask2D(bounds, z, t, inclusive));
691
692        // optimized bounds to optimize memory usage for this specific Z, T slice mask
693        result.optimizeBounds();
694
695        return result;
696    }
697
698    /**
699     * Returns the {@link BooleanMask3D} object representing the XYZ volume content at specified Z,
700     * T, C position.<br>
701     * It contains the 3D rectangle mask bounds and the associated boolean array mask.
702     * 
703     * @param z
704     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
705     *        dimension
706     * @param t
707     *        T position we want to retrieve the boolean mask.
708     * @param c
709     *        C position we want to retrieve the boolean mask.<br>
710     *        Set it to -1 to retrieve the mask whatever is the C position of this ROI4D.
711     * @param inclusive
712     *        If true then all partially contained (intersected) pixels are included in the mask.
713     */
714    public BooleanMask3D getBooleanMask3D(int z, int t, int c, boolean inclusive)
715    {
716        // not on the correct C position --> return empty mask
717        if (!isActiveFor(c))
718            return new BooleanMask3D();
719
720        // whole Z dimension
721        if (z == -1)
722            return getBooleanMask3D(t, inclusive);
723
724        // define bounds
725        final Rectangle3D.Integer bounds = getBounds4D().toRectangle3D().toInteger();
726        bounds.setZ(z);
727        bounds.setSizeZ(1);
728
729        return new BooleanMask3D(bounds, new BooleanMask2D[] {getBooleanMask2D(z, t, inclusive)});
730    }
731
732    /**
733     * Get the {@link BooleanMask3D} object representing the roi for specified T position.<br>
734     * It contains the 3D rectangle mask bounds and the associated boolean array mask.
735     * 
736     * @param inclusive
737     *        If true then all partially contained (intersected) pixels are included in the mask.
738     */
739    public BooleanMask3D getBooleanMask3D(int t, boolean inclusive)
740    {
741        final Rectangle3D.Integer bounds = getBounds4D().toRectangle3D().toInteger();
742        final BooleanMask2D masks[] = new BooleanMask2D[bounds.sizeZ];
743
744        for (int z = 0; z < masks.length; z++)
745            masks[z] = getBooleanMask2D(bounds.z + z, t, inclusive);
746
747        return new BooleanMask3D(bounds, masks);
748    }
749
750    /**
751     * Returns the {@link BooleanMask4D} object representing the XYZT space content at specified Z,
752     * T, C position.
753     * 
754     * @param z
755     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
756     *        dimension
757     * @param t
758     *        T position we want to retrieve the boolean mask or -1 to retrieve the whole T
759     *        dimension
760     * @param c
761     *        C position we want to retrieve the boolean mask.<br>
762     *        Set it to -1 to retrieve the mask whatever is the C position of this ROI4D.
763     * @param inclusive
764     *        If true then all partially contained (intersected) pixels are included in the mask.
765     */
766    public BooleanMask4D getBooleanMask4D(int z, int t, int c, boolean inclusive)
767    {
768        // not on the correct C position --> return empty mask
769        if (!isActiveFor(c))
770            return new BooleanMask4D();
771
772        // whole Z dimension
773        if (z == -1)
774        {
775            // whole Z and T dimension
776            if (t == -1)
777                return getBooleanMask(inclusive);
778
779            // define bounds
780            final Rectangle4D.Integer bounds = getBounds4D().toInteger();
781            bounds.setT(t);
782            bounds.setSizeT(1);
783
784            // whole Z dimension but specific T
785            return new BooleanMask4D(bounds, new BooleanMask3D[] {getBooleanMask3D(t, inclusive)});
786        }
787
788        final Rectangle4D.Integer bounds4d = getBounds4D().toInteger();
789
790        // specific Z
791        bounds4d.setZ(z);
792        bounds4d.setSizeZ(1);
793        // specific T dimension ?
794        if (t != -1)
795        {
796            bounds4d.setT(t);
797            bounds4d.setSizeT(1);
798        }
799
800        final Rectangle3D.Integer bounds3d = (Rectangle3D.Integer) bounds4d.toRectangle3D();
801        final BooleanMask3D masks[] = new BooleanMask3D[bounds4d.sizeT];
802
803        for (int i = 0; i < bounds4d.sizeT; i++)
804            masks[i] = new BooleanMask3D((Rectangle3D.Integer) bounds3d.clone(), new BooleanMask2D[] {getBooleanMask2D(
805                    z, bounds4d.t + i, inclusive)});
806
807        return new BooleanMask4D(bounds4d, masks);
808    }
809
810    /**
811     * Get the {@link BooleanMask4D} object representing the roi.<br>
812     * It contains the 4D rectangle mask bounds and the associated boolean array mask.<br>
813     * 
814     * @param inclusive
815     *        If true then all partially contained (intersected) pixels are included in the mask.
816     */
817    public BooleanMask4D getBooleanMask(boolean inclusive)
818    {
819        final Rectangle4D.Integer bounds = getBounds();
820        final BooleanMask3D masks[] = new BooleanMask3D[bounds.sizeT];
821
822        for (int t = 0; t < masks.length; t++)
823            masks[t] = getBooleanMask3D(bounds.t + t, inclusive);
824
825        return new BooleanMask4D(bounds, masks);
826    }
827
828    /*
829     * Generic implementation for ROI4D using the BooleanMask object so
830     * the result is just an approximation.
831     * Override to optimize for specific ROI.
832     */
833    @Override
834    public double computeNumberOfContourPoints()
835    {
836        // approximation by using number of point of the edge of boolean mask
837        return getBooleanMask(true).getContourPointsAsIntArray().length / getDimension();
838    }
839
840    /*
841     * Generic implementation for ROI4D using the BooleanMask object so
842     * the result is just an approximation.
843     * Override to optimize for specific ROI.
844     */
845    @Override
846    public double computeNumberOfPoints()
847    {
848        double numPoints = 0;
849
850        // approximation by using number of point of boolean mask with and without border
851        numPoints += getBooleanMask(true).getNumberOfPoints();
852        numPoints += getBooleanMask(false).getNumberOfPoints();
853        numPoints /= 2d;
854
855        return numPoints;
856    }
857
858    /**
859     * Returns the C position.<br>
860     * <code>-1</code> is a special value meaning the ROI is set on all C channels (infinite C
861     * dimension).
862     */
863    public int getC()
864    {
865        return c;
866    }
867
868    /**
869     * Sets C position of this 4D ROI.<br>
870     * You cannot set the ROI on a negative C position as <code>-1</code> is a special value meaning
871     * the ROI is set on all C channels (infinite C dimension).
872     */
873    public void setC(int value)
874    {
875        final int v;
876
877        // special value for infinite dimension --> change to -1
878        if (value == Integer.MIN_VALUE)
879            v = -1;
880        else
881            v = value;
882
883        if (c != v)
884        {
885            c = v;
886            roiChanged(false);
887        }
888    }
889
890    @Override
891    public boolean isActiveFor(IcyCanvas canvas)
892    {
893        return isActiveFor(canvas.getPositionC());
894    }
895
896    /**
897     * Return true if the ROI is active for the specified C coordinate
898     */
899    public boolean isActiveFor(int c)
900    {
901        return (getC() == -1) || (c == -1) || (getC() == c);
902    }
903
904    @Override
905    public boolean loadFromXML(Node node)
906    {
907        beginUpdate();
908        try
909        {
910            if (!super.loadFromXML(node))
911                return false;
912
913            setC(XMLUtil.getElementIntValue(node, ID_C, -1));
914        }
915        finally
916        {
917            endUpdate();
918        }
919
920        return true;
921    }
922
923    @Override
924    public boolean saveToXML(Node node)
925    {
926        if (!super.saveToXML(node))
927            return false;
928
929        XMLUtil.setElementIntValue(node, ID_C, getC());
930
931        return true;
932    }
933}