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.canvas.IcyCanvas2D;
023import icy.canvas.IcyCanvas3D;
024import icy.gui.util.FontUtil;
025import icy.preferences.GeneralPreferences;
026import icy.roi.edit.PositionROIEdit;
027import icy.sequence.Sequence;
028import icy.type.point.Point3D;
029import icy.type.point.Point5D;
030import icy.type.rectangle.Rectangle3D;
031import icy.type.rectangle.Rectangle5D;
032import icy.util.EventUtil;
033import icy.util.GraphicsUtil;
034import icy.util.XMLUtil;
035
036import java.awt.Graphics2D;
037import java.awt.Point;
038import java.awt.Rectangle;
039import java.awt.RenderingHints;
040import java.awt.event.InputEvent;
041import java.awt.event.MouseEvent;
042import java.awt.geom.Rectangle2D;
043import java.util.ArrayList;
044import java.util.List;
045
046import org.w3c.dom.Node;
047
048/**
049 * 3D ROI base class
050 */
051public abstract class ROI3D extends ROI
052{
053    /**
054     * @deprecated Use {@link ROI3D#getROI3DList(List)} instead.
055     */
056    @Deprecated
057    public static ArrayList<ROI3D> getROI3DList(ArrayList<ROI> rois)
058    {
059        final ArrayList<ROI3D> result = new ArrayList<ROI3D>();
060
061        for (ROI roi : rois)
062            if (roi instanceof ROI3D)
063                result.add((ROI3D) roi);
064
065        return result;
066    }
067
068    /**
069     * Return all 3D ROI from the ROI list
070     */
071    public static List<ROI3D> getROI3DList(List<ROI> rois)
072    {
073        final List<ROI3D> result = new ArrayList<ROI3D>();
074
075        for (ROI roi : rois)
076            if (roi instanceof ROI3D)
077                result.add((ROI3D) roi);
078
079        return result;
080    }
081
082    public abstract class ROI3DPainter extends ROIPainter
083    {
084        protected Point3D startDragMousePosition;
085        protected Point3D startDragROIPosition;
086
087        public ROI3DPainter()
088        {
089            super();
090
091            startDragMousePosition = null;
092            startDragROIPosition = null;
093        }
094
095        @Override
096        protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
097        {
098            if (imagePoint == null)
099                return false;
100
101            // test on canvas has already be done, don't do it again
102            final boolean focused = isOverEdge(canvas, imagePoint.getX(), imagePoint.getY(), imagePoint.getZ());
103
104            setFocused(focused);
105
106            return focused;
107        }
108
109        @Override
110        protected boolean updateDrag(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
111        {
112            // not dragging --> exit
113            if (startDragMousePosition == null)
114                return false;
115            if (imagePoint == null)
116                return false;
117            if (!canSetPosition())
118                return false;
119
120            double dx = imagePoint.getX() - startDragMousePosition.getX();
121            double dy = imagePoint.getY() - startDragMousePosition.getY();
122            double dz = imagePoint.getZ() - startDragMousePosition.getZ();
123
124            // shift action --> limit to one direction
125            if (EventUtil.isShiftDown(e))
126            {
127                // X or Z drag
128                if (Math.abs(dx) > Math.abs(dy))
129                {
130                    dy = 0d;
131
132                    // Z drag
133                    if (Math.abs(dz) > Math.abs(dx))
134                        dx = 0d;
135                    else
136                        dz = 0d;
137                }
138                // Y or Z drag
139                else
140                {
141                    dx = 0d;
142
143                    // Z drag
144                    if (Math.abs(dz) > Math.abs(dy))
145                        dy = 0d;
146                    else
147                        dz = 0d;
148                }
149            }
150
151            // needed for undo operation
152            final Sequence sequence;
153            final Point5D savePosition;
154
155            // get canvas which modify the ROI --> get the sequence
156            if (canvas != null)
157                sequence = canvas.getSequence();
158            else
159                sequence = null;
160
161            if (sequence != null)
162                savePosition = getPosition5D();
163            else
164                savePosition = null;
165
166            // set new position
167            setPosition3D(new Point3D.Double(startDragROIPosition.getX() + dx, startDragROIPosition.getY() + dy,
168                    startDragROIPosition.getZ() + dz));
169
170            // allow undo as the ROI position has been modified from canvas
171            if ((sequence != null) && (savePosition != null))
172                // add position change to undo manager
173                sequence.addUndoableEdit(new PositionROIEdit(ROI3D.this, savePosition));
174
175            return true;
176        }
177
178        @Override
179        public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
180        {
181            super.mousePressed(e, imagePoint, canvas);
182
183            // ROI editable ? (don't check for consumption as selection can consume event)
184            if (!isReadOnly())
185            {
186                // check we can do the action
187                if (imagePoint != null)
188                {
189                    if (isActiveFor(canvas))
190                    {
191                        // left button action
192                        if (EventUtil.isLeftMouseButton(e))
193                        {
194                            ROI3D.this.beginUpdate();
195                            try
196                            {
197                                // roi focused ?
198                                if (isFocused())
199                                {
200                                    startDragMousePosition = imagePoint.toPoint3D();
201                                    startDragROIPosition = getPosition3D();
202                                }
203                            }
204                            finally
205                            {
206                                ROI3D.this.endUpdate();
207                            }
208                        }
209                    }
210                }
211            }
212        }
213
214        @Override
215        public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
216        {
217            // do parent stuff
218            super.mouseReleased(e, imagePoint, canvas);
219
220            startDragMousePosition = null;
221        }
222
223        @Override
224        public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
225        {
226            // do parent stuff
227            super.mouseDrag(e, imagePoint, canvas);
228
229            // not yet consumed and ROI editable...
230            if (!e.isConsumed() && !isReadOnly())
231            {
232                // check we can do the action
233                if (imagePoint != null)
234                {
235                    if (isActiveFor(canvas))
236                    {
237                        // left button action
238                        if (EventUtil.isLeftMouseButton(e))
239                        {
240                            ROI3D.this.beginUpdate();
241                            try
242                            {
243                                // roi focused ?
244                                if (isFocused())
245                                {
246                                    // store drag start position if not yet done
247                                    if (startDragMousePosition == null)
248                                    {
249                                        startDragMousePosition = imagePoint.toPoint3D();
250                                        startDragROIPosition = getPosition3D();
251                                    }
252
253                                    updateDrag(e, imagePoint, canvas);
254
255                                    // consume event
256                                    e.consume();
257                                }
258                            }
259                            finally
260                            {
261                                ROI3D.this.endUpdate();
262                            }
263                        }
264                    }
265                }
266            }
267        }
268
269        @Override
270        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
271        {
272            super.paint(g, sequence, canvas);
273
274            if (isActiveFor(canvas))
275            {
276                drawROI(g, sequence, canvas);
277                // display name ?
278                if (getShowName())
279                    drawName(g, sequence, canvas);
280            }
281        }
282
283        /**
284         * Draw the ROI
285         */
286        public abstract void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas);
287
288        /**
289         * Draw the ROI name
290         */
291        public void drawName(Graphics2D g, Sequence sequence, IcyCanvas canvas)
292        {
293            if (canvas instanceof IcyCanvas2D)
294            {
295                // not supported
296                if (g == null)
297                    return;
298
299                final Rectangle3D bounds3d = getBounds3D();
300                final int posZ = canvas.getPositionZ();
301
302                // ROI is not visible on this Z position --> nothing to draw
303                if ((posZ != -1) && (bounds3d.getMinZ() > posZ) || (bounds3d.getMaxZ() < posZ))
304                    return;
305
306                final Graphics2D g2 = (Graphics2D) g.create();
307                final IcyCanvas2D cnv2d = (IcyCanvas2D) canvas;
308                final Rectangle2D bounds = bounds3d.toRectangle2D();
309                final Point pos = cnv2d.imageToCanvas(bounds.getCenterX(), bounds.getMinY());
310                final double coef = Math.log(canvas.getScaleX() + 1);
311                final double fontSize = (GeneralPreferences.getGuiFontSize() - 4) + (int) (coef * 10d);
312
313                // go to absolute coordinates
314                g2.transform(cnv2d.getInverseTransform());
315                // set text anti alias
316                g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
317                // set font
318                g2.setFont(FontUtil.setSize(g2.getFont(), (int) fontSize));
319                // set color
320                g2.setColor(getColor());
321
322                // draw ROI name
323                GraphicsUtil.drawHCenteredString(g2, getName(), pos.x, pos.y - (int) (2 * fontSize), true);
324
325                g2.dispose();
326            }
327
328            if (canvas instanceof IcyCanvas3D)
329            {
330                // not yet supported
331
332            }
333        }
334    }
335
336    public static final String ID_T = "t";
337    public static final String ID_C = "c";
338
339    /**
340     * t coordinate attachment
341     */
342    protected int t;
343    /**
344     * c coordinate attachment
345     */
346    protected int c;
347
348    public ROI3D()
349    {
350        super();
351
352        // by default we consider no specific T and C attachment
353        t = -1;
354        c = -1;
355    }
356
357    @Override
358    public String getDefaultName()
359    {
360        return "ROI3D";
361    }
362
363    @Override
364    final public int getDimension()
365    {
366        return 3;
367    }
368
369    /**
370     * Returns true if specified ROI is on the same [T, C] position than current ROI.
371     * 
372     * @param shouldContain
373     *        if <code>true</code> then current ROI should "contains" specified ROI position [T, C]
374     */
375    protected boolean onSamePos(ROI3D roi, boolean shouldContain)
376    {
377        final int t = getT();
378        final int c = getC();
379        final int roiT = roi.getT();
380        final int roiC = roi.getC();
381
382        // same position ?
383        if (shouldContain)
384        {
385            if ((t != -1) && (t != roiT))
386                return false;
387            if ((c != -1) && (c != roiC))
388                return false;
389        }
390        else
391        {
392            if ((t != -1) && (roiT != -1) && (t != roiT))
393                return false;
394            if ((c != -1) && (roiC != -1) && (c != roiC))
395                return false;
396        }
397
398        return true;
399    }
400
401    /**
402     * Tests if a specified {@link Point3D} is inside the ROI.
403     * 
404     * @param p
405     *        the specified <code>Point3D</code> to be tested
406     * @return <code>true</code> if the specified <code>Point3D</code> is inside the boundary of the <code>ROI</code>;
407     *         <code>false</code> otherwise.
408     */
409    public boolean contains(Point3D p)
410    {
411        return contains(p.getX(), p.getY(), p.getZ());
412    }
413
414    /**
415     * Tests if the interior of the <code>ROI</code> entirely contains the specified <code>Rectangle3D</code>. The
416     * {@code ROI.contains()} method allows a implementation to
417     * conservatively return {@code false} when:
418     * <ul>
419     * <li>the <code>intersect</code> method returns <code>true</code> and
420     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the
421     * <code>Rectangle3D</code> are prohibitively expensive.
422     * </ul>
423     * This means that for some ROIs this method might return {@code false} even though the {@code ROI} contains the
424     * {@code Rectangle3D}.
425     * 
426     * @param r
427     *        The specified <code>Rectangle3D</code>
428     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the <code>Rectangle3D</code>;
429     *         <code>false</code> otherwise or, if the <code>ROI</code> contains the <code>Rectangle3D</code> and the
430     *         <code>intersects</code> method returns <code>true</code> and the containment calculations would be too
431     *         expensive to perform.
432     * @see #contains(double, double, double, double, double, double)
433     */
434    public boolean contains(Rectangle3D r)
435    {
436        return contains(r.getX(), r.getY(), r.getZ(), r.getSizeX(), r.getSizeY(), r.getSizeZ());
437    }
438
439    /**
440     * Tests if the specified coordinates are inside the <code>ROI</code>.
441     * 
442     * @param x
443     *        the specified X coordinate to be tested
444     * @param y
445     *        the specified Y coordinate to be tested
446     * @param z
447     *        the specified Z coordinate to be tested
448     * @return <code>true</code> if the specified 3D coordinates are inside the <code>ROI</code> boundary;
449     *         <code>false</code> otherwise.
450     */
451    public abstract boolean contains(double x, double y, double z);
452
453    /**
454     * Tests if the <code>ROI</code> entirely contains the specified 3D rectangular area. All
455     * coordinates that lie inside the rectangular area must lie within the <code>ROI</code> for the
456     * entire rectangular area to be considered contained within the <code>ROI</code>.
457     * <p>
458     * The {@code ROI.contains()} method allows a {@code ROI} implementation to conservatively return {@code false}
459     * when:
460     * <ul>
461     * <li>the <code>intersect</code> method returns <code>true</code> and
462     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the rectangular area are
463     * prohibitively expensive.
464     * </ul>
465     * This means that for some {@code ROIs} this method might return {@code false} even though the {@code ROI} contains
466     * the rectangular area.
467     * 
468     * @param x
469     *        the X coordinate of the minimum corner position of the specified rectangular area
470     * @param y
471     *        the Y coordinate of the minimum corner position of the specified rectangular area
472     * @param z
473     *        the Z coordinate of the minimum corner position of the specified rectangular area
474     * @param sizeX
475     *        size for X dimension of the specified rectangular area
476     * @param sizeY
477     *        size for Y dimension of the specified rectangular area
478     * @param sizeZ
479     *        size for Z dimension of the specified rectangular area
480     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the
481     *         specified 3D rectangular area; <code>false</code> otherwise or, if the <code>ROI</code> contains the 3D
482     *         rectangular area and the <code>intersects</code> method returns <code>true</code> and the containment
483     *         calculations would be too
484     *         expensive to perform.
485     */
486    public abstract boolean contains(double x, double y, double z, double sizeX, double sizeY, double sizeZ);
487
488    @Override
489    public boolean contains(double x, double y, double z, double t, double c)
490    {
491        final boolean tok;
492        final boolean cok;
493
494        if (getT() == -1)
495            tok = true;
496        else
497            tok = (t >= getT()) && (t < (getT() + 1d));
498        if (getC() == -1)
499            cok = true;
500        else
501            cok = (c >= getC()) && (c < (getC() + 1d));
502
503        return contains(x, y, z) && tok && cok;
504    }
505
506    @Override
507    public boolean contains(double x, double y, double z, double t, double c, double sizeX, double sizeY, double sizeZ,
508            double sizeT, double sizeC)
509    {
510        final boolean tok;
511        final boolean cok;
512
513        if (getT() == -1)
514            tok = true;
515        else
516            tok = (t >= getT()) && ((t + sizeT) <= (getT() + 1d));
517        if (getC() == -1)
518            cok = true;
519        else
520            cok = (c >= getC()) && ((c + sizeC) <= (getC() + 1d));
521
522        return contains(x, y, z, sizeX, sizeY, sizeZ) && tok && cok;
523    }
524
525    /*
526     * Generic implementation using the BooleanMask which is not accurate and slow. Override this
527     * for specific ROI type.
528     */
529    @Override
530    public boolean contains(ROI roi)
531    {
532        if (roi instanceof ROI3D)
533        {
534            final ROI3D roi3d = (ROI3D) roi;
535
536            if (onSamePos(roi3d, true))
537            {
538                // special case of ROI Point
539                if (roi3d.isEmpty())
540                    return contains(roi3d.getPosition3D());
541
542                BooleanMask3D mask;
543                BooleanMask3D roiMask;
544
545                // take content first
546                mask = getBooleanMask(false);
547                roiMask = roi3d.getBooleanMask(false);
548
549                // test first only on content
550                if (!mask.contains(roiMask))
551                    return false;
552
553                // take content and edge
554                mask = getBooleanMask(true);
555                roiMask = roi3d.getBooleanMask(true);
556
557                // then test on content and edge
558                if (!mask.contains(roiMask))
559                    return false;
560
561                // contained
562                return true;
563            }
564
565            return false;
566        }
567
568        // use default implementation
569        return super.contains(roi);
570    }
571
572    /**
573     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified <code>Rectangle3D</code>.
574     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
575     * when:
576     * <ul>
577     * <li>there is a high probability that the <code>Rectangle3D</code> and the <code>ROI</code> intersect, but
578     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
579     * </ul>
580     * This means that for some {@code ROIs} this method might return {@code true} even though the {@code Rectangle3D}
581     * does not intersect the {@code ROI}.
582     * 
583     * @param r
584     *        the specified <code>Rectangle3D</code>
585     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
586     *         specified <code>Rectangle3D</code> intersect, or are both highly likely to intersect
587     *         and intersection calculations would be too expensive to perform; <code>false</code> otherwise.
588     * @see #intersects(double, double, double,double, double, double)
589     */
590    public boolean intersects(Rectangle3D r)
591    {
592        return intersects(r.getX(), r.getY(), r.getZ(), r.getSizeX(), r.getSizeY(), r.getSizeZ());
593    }
594
595    /**
596     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified
597     * 3D rectangular area. The 3D rectangular area is considered to intersect the <code>ROI</code> if any point is
598     * contained in both the interior of the <code>ROI</code> and the specified
599     * rectangular area.
600     * <p>
601     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
602     * when:
603     * <ul>
604     * <li>there is a high probability that the 3D rectangular area and the <code>ROI</code> intersect, but
605     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
606     * </ul>
607     * This means that for some {@code ROIs} this method might return {@code true} even though the 3D rectangular area
608     * does not intersect the {@code ROI}.
609     * 
610     * @param x
611     *        the X coordinate of the minimum corner position of the specified rectangular area
612     * @param y
613     *        the Y coordinate of the minimum corner position of the specified rectangular area
614     * @param z
615     *        the Z coordinate of the minimum corner position of the specified rectangular area
616     * @param sizeX
617     *        size for X dimension of the specified rectangular area
618     * @param sizeY
619     *        size for Y dimension of the specified rectangular area
620     * @param sizeZ
621     *        size for Z dimension of the specified rectangular area
622     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
623     *         rectangular area intersect, or are both highly likely to intersect and intersection
624     *         calculations would be too expensive to perform; <code>false</code> otherwise.
625     */
626    public abstract boolean intersects(double x, double y, double z, double sizeX, double sizeY, double sizeZ);
627
628    @Override
629    public boolean intersects(double x, double y, double z, double t, double c, double sizeX, double sizeY,
630            double sizeZ, double sizeT, double sizeC)
631    {
632        // easy discard
633        if ((sizeX == 0d) || (sizeY == 0d) || (sizeZ == 0d) || (sizeT == 0d) || (sizeC == 0d))
634            return false;
635
636        final boolean tok;
637        final boolean cok;
638
639        if (getT() == -1)
640            tok = true;
641        else
642            tok = ((t + sizeT) > getT()) && (t < (getT() + 1d));
643        if (getC() == -1)
644            cok = true;
645        else
646            cok = ((c + sizeC) > getC()) && (c < (getC() + 1d));
647
648        return intersects(x, y, z, sizeX, sizeY, sizeZ) && tok && cok;
649    }
650
651    /*
652     * Generic implementation using the BooleanMask which is not accurate and slow.
653     * Override this for specific ROI type.
654     */
655    @Override
656    public boolean intersects(ROI roi)
657    {
658        if (roi instanceof ROI3D)
659        {
660            final ROI3D roi3d = (ROI3D) roi;
661
662            if (onSamePos(roi3d, false))
663                return getBooleanMask(true).intersects(roi3d.getBooleanMask(true));
664        }
665
666        // use default implementation
667        return super.intersects(roi);
668    }
669
670    /**
671     * Calculate and returns the 3D bounding box of the <code>ROI</code>.<br>
672     * This method is used by {@link #getBounds3D()} which should try to cache the result as the
673     * bounding box calculation can take some computation time for complex ROI.
674     */
675    public abstract Rectangle3D computeBounds3D();
676
677    @Override
678    public Rectangle5D computeBounds5D()
679    {
680        final Rectangle3D bounds3D = computeBounds3D();
681        if (bounds3D == null)
682            return new Rectangle5D.Double();
683
684        final Rectangle5D.Double result = new Rectangle5D.Double(bounds3D.getX(), bounds3D.getY(), bounds3D.getZ(), 0d,
685                0d, bounds3D.getSizeX(), bounds3D.getSizeY(), bounds3D.getSizeZ(), 0d, 0d);
686
687        if (getT() == -1)
688        {
689            result.t = Double.NEGATIVE_INFINITY;
690            result.sizeT = Double.POSITIVE_INFINITY;
691        }
692        else
693        {
694            result.t = getT();
695            result.sizeT = 1d;
696        }
697        if (getC() == -1)
698        {
699            result.c = Double.NEGATIVE_INFINITY;
700            result.sizeC = Double.POSITIVE_INFINITY;
701        }
702        else
703        {
704            result.c = getC();
705            result.sizeC = 1d;
706        }
707
708        return result;
709    }
710
711    /**
712     * Returns an integer {@link Rectangle3D} that completely encloses the <code>ROI</code>. Note
713     * that there is no guarantee that the returned <code>Rectangle3D</code> is the smallest
714     * bounding box that encloses the <code>ROI</code>, only that the <code>ROI</code> lies entirely
715     * within the indicated <code>Rectangle3D</code>. The returned <code>Rectangle3D</code> might
716     * also fail to completely enclose the <code>ROI</code> if the <code>ROI</code> overflows the
717     * limited range of the integer data type. The <code>getBounds3D</code> method generally returns
718     * a tighter bounding box due to its greater flexibility in representation.
719     * 
720     * @return an integer <code>Rectangle3D</code> that completely encloses the <code>ROI</code>.
721     */
722    public Rectangle3D.Integer getBounds()
723    {
724        return getBounds3D().toInteger();
725    }
726
727    /**
728     * Returns the bounding box of the <code>ROI</code>. Note that there is no guarantee that the
729     * returned {@link Rectangle3D} is the smallest bounding box that encloses the <code>ROI</code>,
730     * only that the <code>ROI</code> lies entirely within the indicated <code>Rectangle3D</code>.
731     * 
732     * @return an instance of <code>Rectangle3D</code> that is a bounding box of the <code>ROI</code>.
733     */
734    public Rectangle3D getBounds3D()
735    {
736        return getBounds5D().toRectangle3D();
737    }
738
739    /**
740     * Returns the integer ROI position which normally correspond to the <i>minimum</i> point of the
741     * ROI bounds.
742     * 
743     * @see #getBounds()
744     */
745    public Point3D.Integer getPosition()
746    {
747        final Rectangle3D.Integer bounds = getBounds();
748        return new Point3D.Integer(bounds.x, bounds.y, bounds.z);
749    }
750
751    /**
752     * Returns the high precision ROI position which normally correspond to the <i>minimum</i> point
753     * of the ROI bounds.<br>
754     * 
755     * @see #getBounds3D()
756     */
757    public Point3D getPosition3D()
758    {
759        return getBounds3D().getPosition();
760    }
761
762    @Override
763    public boolean canSetBounds()
764    {
765        // default
766        return false;
767    }
768
769    /**
770     * Set the <code>ROI</code> 3D bounds.<br>
771     * Note that not all ROI supports bounds modification and you should call {@link #canSetBounds()} first to test if
772     * the operation is supported.<br>
773     * 
774     * @param bounds
775     *        new ROI 3D bounds
776     */
777    public void setBounds3D(Rectangle3D bounds)
778    {
779        // do nothing by default (not supported)
780    }
781
782    @Override
783    public void setBounds5D(Rectangle5D bounds)
784    {
785        beginUpdate();
786        try
787        {
788            // infinite T dim ?
789            if (bounds.getSizeT() == Double.POSITIVE_INFINITY)
790                setT(-1);
791            else
792                setT((int) bounds.getT());
793            // infinite C dim ?
794            if (bounds.getSizeC() == Double.POSITIVE_INFINITY)
795                setC(-1);
796            else
797                setC((int) bounds.getC());
798
799            setBounds3D(bounds.toRectangle3D());
800        }
801        finally
802        {
803            endUpdate();
804        }
805    }
806
807    @Override
808    public boolean canSetPosition()
809    {
810        // default implementation use translation if available
811        return canTranslate();
812    }
813
814    /**
815     * Set the <code>ROI</code> 3D position.<br>
816     * Note that not all ROI supports position modification and you should call {@link #canSetPosition()} first to test
817     * if the operation is supported.<br>
818     * 
819     * @param position
820     *        new ROI 3D position
821     */
822    public void setPosition3D(Point3D position)
823    {
824        // use translation operation by default if supported
825        if (canTranslate())
826        {
827            final Point3D oldPos = getPosition3D();
828            translate(position.getX() - oldPos.getX(), position.getY() - oldPos.getY(),
829                    position.getZ() - oldPos.getZ());
830        }
831    }
832
833    @Override
834    public void setPosition5D(Point5D position)
835    {
836        beginUpdate();
837        try
838        {
839            setT((int) position.getT());
840            setC((int) position.getC());
841            setPosition3D(position.toPoint3D());
842        }
843        finally
844        {
845            endUpdate();
846        }
847    }
848
849    /**
850     * Returns <code>true</code> if the ROI support translate operation.
851     * 
852     * @see #translate(double, double, double)
853     */
854    public boolean canTranslate()
855    {
856        // by default
857        return false;
858    }
859
860    /**
861     * Translate the ROI position by the specified delta X/Y/Z.<br>
862     * Note that not all ROI support this operation so you should test it by calling {@link #canTranslate()} first.
863     * 
864     * @param dx
865     *        translation value to apply on X dimension
866     * @param dy
867     *        translation value to apply on Y dimension
868     * @param dz
869     *        translation value to apply on Z dimension
870     * @see #canTranslate()
871     * @see #setPosition3D(Point3D)
872     */
873    public void translate(double dx, double dy, double dz)
874    {
875        // need a default implementation to not break all overriding classes
876    }
877
878    @Override
879    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, int c, boolean inclusive)
880    {
881        // not on the correct T, C position --> return empty mask
882        if (!isActiveFor(t, c))
883            return new boolean[Math.max(0, width) * Math.max(0, height)];
884
885        return getBooleanMask2D(x, y, width, height, z, inclusive);
886    }
887
888    /**
889     * Get the boolean bitmap mask for the specified rectangular area of the roi and for the
890     * specified Z position.<br>
891     * if the pixel (x,y) is contained in the roi Z position then result[(y * width) + x] = true<br>
892     * if the pixel (x,y) is not contained in the roi Z position then result[(y * width) + x] =
893     * false
894     * 
895     * @param x
896     *        the X coordinate of the upper-left corner of the specified rectangular area
897     * @param y
898     *        the Y coordinate of the upper-left corner of the specified rectangular area
899     * @param width
900     *        the width of the specified rectangular area
901     * @param height
902     *        the height of the specified rectangular area
903     * @param z
904     *        Z position we want to retrieve the boolean mask
905     * @param inclusive
906     *        If true then all partially contained (intersected) pixels are included in the mask.
907     * @return the boolean bitmap mask
908     */
909    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
910    {
911        final boolean[] result = new boolean[Math.max(0, width) * Math.max(0, height)];
912
913        // simple and basic implementation, override it to have better performance
914        int offset = 0;
915        for (int j = 0; j < height; j++)
916        {
917            for (int i = 0; i < width; i++)
918            {
919                if (inclusive)
920                    result[offset] = intersects(x + i, y + j, z, 1d, 1d, 1d);
921                else
922                    result[offset] = contains(x + i, y + j, z, 1d, 1d, 1d);
923                offset++;
924            }
925        }
926
927        return result;
928    }
929
930    /**
931     * Get the boolean bitmap mask for the specified rectangular area of the roi and for the
932     * specified Z position.<br>
933     * if the pixel (x,y) is contained in the roi Z position then result[(y * width) + x] = true<br>
934     * if the pixel (x,y) is not contained in the roi Z position then result[(y * width) + x] =
935     * false
936     * 
937     * @param rect
938     *        2D rectangular area we want to retrieve the boolean mask
939     * @param z
940     *        Z position we want to retrieve the boolean mask
941     * @param inclusive
942     *        If true then all partially contained (intersected) pixels are included in the mask.
943     */
944    public boolean[] getBooleanMask2D(Rectangle rect, int z, boolean inclusive)
945    {
946        return getBooleanMask2D(rect.x, rect.y, rect.width, rect.height, z, inclusive);
947    }
948
949    @Override
950    public BooleanMask2D getBooleanMask2D(int z, int t, int c, boolean inclusive)
951    {
952        // not on the correct T, C position --> return empty mask
953        if (!isActiveFor(t, c))
954            return new BooleanMask2D(new Rectangle(), new boolean[0]);
955
956        return getBooleanMask2D(z, inclusive);
957    }
958
959    /**
960     * Get the {@link BooleanMask2D} object representing the roi for the specified Z position.<br>
961     * It contains the rectangle mask bounds and the associated boolean array mask.<br>
962     * if the pixel (x,y) is contained in the roi Z position then result.mask[(y * w) + x] = true<br>
963     * if the pixel (x,y) is not contained in the roi Z position then result.mask[(y * w) + x] =
964     * false
965     * 
966     * @param z
967     *        Z position we want to retrieve the boolean mask
968     * @param inclusive
969     *        If true then all partially contained (intersected) pixels are included in the mask.
970     */
971    public BooleanMask2D getBooleanMask2D(int z, boolean inclusive)
972    {
973        final Rectangle bounds = getBounds3D().toRectangle2D().getBounds();
974
975        // empty ROI --> return empty mask
976        if (bounds.isEmpty())
977            return new BooleanMask2D(new Rectangle(), new boolean[0]);
978
979        final BooleanMask2D result = new BooleanMask2D(bounds, getBooleanMask2D(bounds, z, inclusive));
980
981        // optimized bounds to optimize memory usage for this specific Z slice mask
982        result.optimizeBounds();
983
984        return result;
985    }
986
987    /**
988     * Returns the {@link BooleanMask3D} object representing the XYZ volume content at specified Z,
989     * T, C position.<br>
990     * It contains the 3D rectangle mask bounds and the associated boolean array mask.
991     * 
992     * @param z
993     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
994     *        dimension
995     * @param t
996     *        T position we want to retrieve the boolean mask.<br>
997     *        Set it to -1 to retrieve the mask whatever is the T position of this ROI3D.
998     * @param c
999     *        C position we want to retrieve the boolean mask.<br>
1000     *        Set it to -1 to retrieve the mask whatever is the C position of this ROI3D.
1001     * @param inclusive
1002     *        If true then all partially contained (intersected) pixels are included in the mask.
1003     */
1004    public BooleanMask3D getBooleanMask3D(int z, int t, int c, boolean inclusive)
1005    {
1006        // not on the correct T, C position --> return empty mask
1007        if (!isActiveFor(t, c))
1008            return new BooleanMask3D();
1009
1010        // whole Z dimension
1011        if (z == -1)
1012            return getBooleanMask(inclusive);
1013
1014        // define bounds
1015        final Rectangle3D.Integer bounds = getBounds();
1016        bounds.setZ(z);
1017        bounds.setSizeZ(1);
1018
1019        return new BooleanMask3D(bounds, new BooleanMask2D[] {getBooleanMask2D(z, inclusive)});
1020    }
1021
1022    /**
1023     * Get the {@link BooleanMask3D} object representing the roi.<br>
1024     * It contains the 3D rectangle mask bounds and the associated boolean array mask.<br>
1025     * 
1026     * @param inclusive
1027     *        If true then all partially contained (intersected) pixels are included in the mask.
1028     */
1029    public BooleanMask3D getBooleanMask(boolean inclusive)
1030    {
1031        final Rectangle3D.Integer bounds = getBounds();
1032        final BooleanMask2D masks[] = new BooleanMask2D[bounds.sizeZ];
1033
1034        for (int z = 0; z < masks.length; z++)
1035            masks[z] = getBooleanMask2D(bounds.z + z, inclusive);
1036
1037        return new BooleanMask3D(bounds, masks);
1038    }
1039
1040    /*
1041     * Generic implementation for ROI3D using the BooleanMask object so the result is just an
1042     * approximation. This method should be overridden whenever possible to provide more optimal
1043     * approximations.
1044     */
1045    @Override
1046    public double computeNumberOfContourPoints()
1047    {
1048        return getBooleanMask(true).getContourLength();
1049    }
1050
1051    /*
1052     * Generic implementation for ROI3D using the BooleanMask object so the result is just an
1053     * approximation. Override to optimize for specific ROI.
1054     */
1055    @Override
1056    public double computeNumberOfPoints()
1057    {
1058        double numPoints = 0;
1059
1060        // approximation by using number of point of boolean mask with and without border
1061        numPoints += getBooleanMask(true).getNumberOfPoints();
1062        numPoints += getBooleanMask(false).getNumberOfPoints();
1063        numPoints /= 2d;
1064
1065        return numPoints;
1066    }
1067
1068    /**
1069     * Compute the surface area in um2 given the pixel size informations from the specified Sequence.<br>
1070     * Generic implementation of surface area computation using the number of contour point (approximation).<br>
1071     * This method should be overridden whenever possible to provide faster and accurate calculation.
1072     */
1073    public double computeSurfaceArea(Sequence sequence)
1074    {
1075        return sequence.calculateSize(getNumberOfContourPoints(), 3, 2);
1076    }
1077
1078    /**
1079     * Returns surface area of the 3D ROI in um2 given the pixel size informations from the specified Sequence.
1080     * 
1081     * @see #computeSurfaceArea(Sequence)
1082     * @see #getNumberOfContourPoints()
1083     */
1084    public double getSurfaceArea(Sequence sequence)
1085    {
1086        // we cannot cache surface area as result depends from sequence metadata
1087        return computeSurfaceArea(sequence);
1088    }
1089
1090    @Override
1091    public double getLength(Sequence sequence) throws UnsupportedOperationException
1092    {
1093        // not supported on ROI3D by default
1094        throw new UnsupportedOperationException("getLength() not supported for " + getClassName() + ".");
1095    }
1096
1097    /**
1098     * Return surface area of the 3D ROI in pixels.<br>
1099     * This is basically the number of pixel representing ROI edges.<br>
1100     * 
1101     * @deprecated Use {@link #getNumberOfContourPoints()} instead.
1102     * @see #getNumberOfContourPoints()
1103     * @see #computeNumberOfContourPoints()
1104     */
1105    @Deprecated
1106    public double getSurfaceArea()
1107    {
1108        return getNumberOfContourPoints();
1109    }
1110
1111    /**
1112     * Return volume of the 3D ROI in pixels.<br>
1113     * This is basically the number of pixel contained in the ROI.<br>
1114     * 
1115     * @deprecated Use {@link #getNumberOfPoints()} instead.
1116     * @see #getNumberOfPoints()
1117     * @see #computeNumberOfPoints()
1118     */
1119    @Override
1120    @Deprecated
1121    public double getVolume()
1122    {
1123        return getNumberOfPoints();
1124    }
1125
1126    /**
1127     * Returns the T position.<br>
1128     * <code>-1</code> is a special value meaning the ROI is set on all T frames (infinite T
1129     * dimension).
1130     */
1131    public int getT()
1132    {
1133        return t;
1134    }
1135
1136    /**
1137     * Sets T position of this 3D ROI.<br>
1138     * You cannot set the ROI on a negative T position as <code>-1</code> is a special value meaning
1139     * the ROI is set on all T frames (infinite T dimension).
1140     */
1141    public void setT(int value)
1142    {
1143        final int v;
1144
1145        // special value for infinite dimension --> change to -1
1146        if (value == Integer.MIN_VALUE)
1147            v = -1;
1148        else
1149            v = value;
1150
1151        if (t != v)
1152        {
1153            t = v;
1154            roiChanged(false);
1155        }
1156    }
1157
1158    /**
1159     * Returns the C position.<br>
1160     * <code>-1</code> is a special value meaning the ROI is set on all C channels (infinite C
1161     * dimension).
1162     */
1163    public int getC()
1164    {
1165        return c;
1166    }
1167
1168    /**
1169     * Sets C position of this 3D ROI.<br>
1170     * You cannot set the ROI on a negative C position as <code>-1</code> is a special value meaning
1171     * the ROI is set on all C channels (infinite C dimension).
1172     */
1173    public void setC(int value)
1174    {
1175        final int v;
1176
1177        // special value for infinite dimension --> change to -1
1178        if (value == Integer.MIN_VALUE)
1179            v = -1;
1180        else
1181            v = value;
1182
1183        if (c != v)
1184        {
1185            c = v;
1186            roiChanged(false);
1187        }
1188    }
1189
1190    @Override
1191    public boolean isActiveFor(IcyCanvas canvas)
1192    {
1193        return isActiveFor(canvas.getPositionT(), canvas.getPositionC());
1194    }
1195
1196    /**
1197     * Return true if the ROI is active for the specified T, C coordinates
1198     */
1199    public boolean isActiveFor(int t, int c)
1200    {
1201        return ((getT() == -1) || (t == -1) || (getT() == t)) && ((getC() == -1) || (c == -1) || (getC() == c));
1202    }
1203
1204    // @Override
1205    // public void onChanged(CollapsibleEvent object)
1206    // {
1207    // super.onChanged(object);
1208    //
1209    // final ROIEvent event = (ROIEvent) object;
1210    //
1211    // if (event.getType() == ROIEventType.ROI_CHANGED)
1212    // {
1213    // // need to recompute surface area
1214    // if (StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL))
1215    // surfaceAreaInvalid = true;
1216    // }
1217    // }
1218
1219    /**
1220     * Returns true if specified point coordinates overlap the ROI edge.<br>
1221     * Use {@link #contains(Point3D)} to test for content overlap instead.
1222     */
1223    public boolean isOverEdge(IcyCanvas canvas, Point3D p)
1224    {
1225        return isOverEdge(canvas, p.getX(), p.getY(), p.getZ());
1226    }
1227
1228    /**
1229     * Returns true if specified point coordinates overlap the ROI edge.<br>
1230     * Use {@link #contains(double, double, double)} to test for content overlap instead.</br>
1231     * We provide a default implementation to not break compatibility.
1232     */
1233    public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z)
1234    {
1235        // override it in children classes
1236        return false;
1237    }
1238
1239    /**
1240     * Returns true if specified point coordinates overlap the ROI edge.<br>
1241     * Use {@link #contains(Point5D)} to test for content overlap instead.
1242     */
1243    public boolean isOverEdge(IcyCanvas canvas, Point5D p)
1244    {
1245        return isOverEdge(canvas, p.getX(), p.getY(), p.getZ(), p.getT(), p.getC());
1246    }
1247
1248    /**
1249     * Returns true if specified point coordinates overlap the ROI edge.<br>
1250     * Use {@link #contains(double, double, double, double, double)} to test for content overlap
1251     * instead.
1252     */
1253    public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z, double t, double c)
1254    {
1255        if (isActiveFor((int) t, (int) c))
1256            return isOverEdge(canvas, x, y, z);
1257
1258        return false;
1259    }
1260
1261    @Override
1262    public boolean loadFromXML(Node node)
1263    {
1264        beginUpdate();
1265        try
1266        {
1267            if (!super.loadFromXML(node))
1268                return false;
1269
1270            setT(XMLUtil.getElementIntValue(node, ID_T, -1));
1271            setC(XMLUtil.getElementIntValue(node, ID_C, -1));
1272        }
1273        finally
1274        {
1275            endUpdate();
1276        }
1277
1278        return true;
1279    }
1280
1281    @Override
1282    public boolean saveToXML(Node node)
1283    {
1284        if (!super.saveToXML(node))
1285            return false;
1286
1287        XMLUtil.setElementIntValue(node, ID_T, getT());
1288        XMLUtil.setElementIntValue(node, ID_C, getC());
1289
1290        return true;
1291    }
1292}