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