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.Color;
022import java.awt.Graphics2D;
023import java.awt.Image;
024import java.awt.Rectangle;
025import java.awt.event.InputEvent;
026import java.awt.event.KeyEvent;
027import java.awt.event.MouseEvent;
028import java.awt.geom.Point2D;
029import java.lang.reflect.Constructor;
030import java.util.ArrayList;
031import java.util.Comparator;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.Map.Entry;
036import java.util.Set;
037
038import org.w3c.dom.Element;
039import org.w3c.dom.Node;
040
041import icy.canvas.IcyCanvas;
042import icy.common.CollapsibleEvent;
043import icy.common.UpdateEventHandler;
044import icy.common.listener.ChangeListener;
045import icy.file.xml.XMLPersistent;
046import icy.gui.inspector.RoisPanel;
047import icy.main.Icy;
048import icy.painter.Overlay;
049import icy.painter.VtkPainter;
050import icy.plugin.abstract_.Plugin;
051import icy.plugin.interface_.PluginROI;
052import icy.preferences.GeneralPreferences;
053import icy.resource.ResourceUtil;
054import icy.roi.ROIEvent.ROIEventType;
055import icy.roi.ROIEvent.ROIPointEventType;
056import icy.sequence.Sequence;
057import icy.system.IcyExceptionHandler;
058import icy.type.point.Point5D;
059import icy.type.rectangle.Rectangle5D;
060import icy.util.ClassUtil;
061import icy.util.ColorUtil;
062import icy.util.EventUtil;
063import icy.util.ShapeUtil.BooleanOperator;
064import icy.util.StringUtil;
065import icy.util.XMLUtil;
066import plugins.kernel.canvas.VtkCanvas;
067import plugins.kernel.roi.roi2d.ROI2DArea;
068import plugins.kernel.roi.roi3d.ROI3DArea;
069import plugins.kernel.roi.roi4d.ROI4DArea;
070import plugins.kernel.roi.roi5d.ROI5DArea;
071import vtk.vtkProp;
072
073public abstract class ROI implements ChangeListener, XMLPersistent
074{
075    public static class ROIIdComparator implements Comparator<ROI>
076    {
077        @Override
078        public int compare(ROI roi1, ROI roi2)
079        {
080            if (roi1 == roi2)
081                return 0;
082
083            if (roi1 == null)
084                return -1;
085            if (roi2 == null)
086                return 1;
087
088            if (roi1.id < roi2.id)
089                return -1;
090            if (roi1.id > roi2.id)
091                return 1;
092
093            return 0;
094        }
095    }
096
097    public static class ROINameComparator implements Comparator<ROI>
098    {
099        @Override
100        public int compare(ROI roi1, ROI roi2)
101        {
102            if (roi1 == roi2)
103                return 0;
104
105            if (roi1 == null)
106                return -1;
107            if (roi2 == null)
108                return 1;
109
110            return roi1.getName().compareTo(roi2.getName());
111        }
112    }
113
114    /**
115     * Group if for ROI (used to do group type operation)
116     * 
117     * @author Stephane
118     */
119    public static enum ROIGroupId
120    {
121        A, B
122    }
123
124    public static final String ID_ROI = "roi";
125
126    public static final String ID_CLASSNAME = "classname";
127    public static final String ID_ID = "id";
128    public static final String ID_NAME = "name";
129    public static final String ID_GROUPID = "groupid";
130    public static final String ID_COLOR = "color";
131    public static final String ID_STROKE = "stroke";
132    public static final String ID_OPACITY = "opacity";
133    public static final String ID_SELECTED = "selected";
134    public static final String ID_READONLY = "readOnly";
135    public static final String ID_SHOWNAME = "showName";
136    public static final String ID_PROPERTIES = "properties";
137
138    public static final ROIIdComparator idComparator = new ROIIdComparator();
139    public static final ROINameComparator nameComparator = new ROINameComparator();
140
141    public static final double DEFAULT_STROKE = 2;
142    public static final Color DEFAULT_COLOR = Color.GREEN;
143    public static final float DEFAULT_OPACITY = 0.3f;
144
145    // cached value (often used)
146    public static Color defaultColor = null;
147    public static Float defaultOpacity = null;
148    public static Double defaultStroke = null;
149    public static Boolean defaultShowName = null;
150
151    /**
152     * @deprecated Use {@link #DEFAULT_COLOR} instead.
153     */
154    @Deprecated
155    public static final Color DEFAULT_NORMAL_COLOR = DEFAULT_COLOR;
156
157    public static final String PROPERTY_NAME = ID_NAME;
158    public static final String PROPERTY_GROUPID = ID_GROUPID;
159    public static final String PROPERTY_ICON = "icon";
160    public static final String PROPERTY_CREATING = "creating";
161    public static final String PROPERTY_READONLY = ID_READONLY;
162    public static final String PROPERTY_SHOWNAME = ID_SHOWNAME;
163    public static final String PROPERTY_COLOR = ID_COLOR;
164    public static final String PROPERTY_STROKE = ID_STROKE;
165    public static final String PROPERTY_OPACITY = ID_OPACITY;
166
167    // special properties for ROI_CHANGED event
168    public static final String ROI_CHANGED_POSITION = "position";
169    public static final String ROI_CHANGED_ALL = "all";
170
171    /**
172     * Create a ROI from its class name or {@link PluginROI} class name.
173     * 
174     * @param className
175     *        roi class name or {@link PluginROI} class name.
176     * @return ROI (null if command is an incorrect ROI class name)
177     */
178    public static ROI create(String className)
179    {
180        ROI result = null;
181
182        try
183        {
184            // search for the specified className
185            final Class<?> clazz = ClassUtil.findClass(className);
186
187            // class found
188            if (clazz != null)
189            {
190                try
191                {
192                    // we first check if we have a PluginROI class here
193                    final Class<? extends PluginROI> roiClazz = clazz.asSubclass(PluginROI.class);
194                    // create the plugin
195                    final PluginROI plugin = roiClazz.newInstance();
196                    // create ROI
197                    result = plugin.createROI();
198                    // set ROI icon from plugin icon
199                    final Image icon = ((Plugin) plugin).getDescriptor().getIconAsImage();
200                    if (icon != null)
201                        result.setIcon(icon);
202                }
203                catch (ClassCastException e0)
204                {
205                    // check if this is a ROI class
206                    final Class<? extends ROI> roiClazz = clazz.asSubclass(ROI.class);
207
208                    // default constructor
209                    final Constructor<? extends ROI> constructor = roiClazz.getConstructor(new Class[] {});
210                    // build ROI
211                    result = constructor.newInstance();
212                }
213            }
214        }
215        catch (NoSuchMethodException e)
216        {
217            IcyExceptionHandler.handleException(
218                    new NoSuchMethodException(
219                            "Default constructor not found in class '" + className + "', cannot create the ROI."),
220                    true);
221        }
222        catch (ClassNotFoundException e)
223        {
224            IcyExceptionHandler.handleException(
225                    new ClassNotFoundException("Cannot find '" + className + "' class, cannot create the ROI."), true);
226        }
227        catch (Exception e)
228        {
229            IcyExceptionHandler.handleException(e, true);
230        }
231
232        return result;
233    }
234
235    /**
236     * Create a ROI from its class name or {@link PluginROI} class name (interactive mode).
237     * 
238     * @param className
239     *        roi class name or {@link PluginROI} class name.
240     * @param imagePoint
241     *        initial point position in image coordinates (interactive mode).
242     * @return ROI (null if the specified class name is an incorrect ROI class name)
243     */
244    public static ROI create(String className, Point5D imagePoint)
245    {
246        if (imagePoint == null)
247            return create(className);
248
249        ROI result = null;
250
251        try
252        {
253            // search for the specified className
254            final Class<?> clazz = ClassUtil.findClass(className);
255
256            // class found
257            if (clazz != null)
258            {
259                try
260                {
261                    // we first check if we have a PluginROI class here
262                    final Class<? extends PluginROI> roiClazz = clazz.asSubclass(PluginROI.class);
263                    // create the plugin
264                    final PluginROI plugin = roiClazz.newInstance();
265
266                    // then create ROI with the Point5D constructor
267                    result = plugin.createROI(imagePoint);
268                    // not supported --> use default constructor
269                    if (result == null)
270                        result = plugin.createROI();
271
272                    // set ROI icon from plugin icon
273                    final Image icon = ((Plugin) plugin).getDescriptor().getIconAsImage();
274                    if (icon != null)
275                        result.setIcon(icon);
276                }
277                catch (ClassCastException e0)
278                {
279                    // check if this is a ROI class
280                    final Class<? extends ROI> roiClazz = clazz.asSubclass(ROI.class);
281
282                    try
283                    {
284                        // get constructor (Point5D)
285                        final Constructor<? extends ROI> constructor = roiClazz
286                                .getConstructor(new Class[] {Point5D.class});
287                        // build ROI
288                        result = constructor.newInstance(new Object[] {imagePoint});
289                    }
290                    catch (NoSuchMethodException e1)
291                    {
292                        // try default constructor as last chance...
293                        final Constructor<? extends ROI> constructor = roiClazz.getConstructor(new Class[] {});
294                        // build ROI
295                        result = constructor.newInstance();
296                    }
297                }
298            }
299        }
300        catch (Exception e)
301        {
302            IcyExceptionHandler.handleException(
303                    new NoSuchMethodException(
304                            "Default constructor not found in class '" + className + "', cannot create the ROI."),
305                    true);
306        }
307
308        return result;
309    }
310
311    /**
312     * @deprecated Use {@link #create(String, Point5D)} instead
313     */
314    @Deprecated
315    public static ROI create(String className, Point2D imagePoint)
316    {
317        return create(className, new Point5D.Double(imagePoint.getX(), imagePoint.getY(), -1d, -1d, -1d));
318    }
319
320    /**
321     * @deprecated Use {@link ROI#create(String, Point5D)} instead.
322     */
323    @Deprecated
324    public static ROI create(String className, Sequence seq, Point2D imagePoint, boolean creation)
325    {
326        final ROI result = create(className, imagePoint);
327
328        // attach to sequence once ROI is initialized
329        if ((seq != null) && (result != null))
330            seq.addROI(result, true);
331
332        return result;
333    }
334
335    /**
336     * Create a ROI from a xml definition
337     * 
338     * @param node
339     *        xml node defining the roi
340     * @return ROI (null if node is an incorrect ROI definition)
341     */
342    public static ROI createFromXML(Node node)
343    {
344        if (node == null)
345            return null;
346
347        final String className = XMLUtil.getElementValue(node, ID_CLASSNAME, "");
348        if (StringUtil.isEmpty(className))
349            return null;
350
351        final ROI roi = create(className);
352        // load properties from XML
353        if (roi != null)
354        {
355            // error while loading infos --> return null
356            if (!roi.loadFromXML(node))
357                return null;
358
359            roi.setSelected(false);
360        }
361
362        return roi;
363    }
364
365    public static double getAdjustedStroke(IcyCanvas canvas, double stroke)
366    {
367        final double adjStrkX = canvas.canvasToImageLogDeltaX((int) stroke);
368        final double adjStrkY = canvas.canvasToImageLogDeltaY((int) stroke);
369
370        return Math.max(adjStrkX, adjStrkY);
371    }
372
373    /**
374     * Return ROI of specified type from the ROI list
375     */
376    public static List<ROI> getROIList(List<? extends ROI> rois, Class<? extends ROI> clazz)
377    {
378        final List<ROI> result = new ArrayList<ROI>();
379
380        for (ROI roi : rois)
381            if (clazz.isInstance(roi))
382                result.add(roi);
383
384        return result;
385    }
386
387    /**
388     * @deprecated Use {@link #getROIList(List, Class)} instead.
389     */
390    @Deprecated
391    public static ArrayList<ROI> getROIList(ArrayList<? extends ROI> rois, Class<? extends ROI> clazz)
392    {
393        final ArrayList<ROI> result = new ArrayList<ROI>();
394
395        for (ROI roi : rois)
396            if (clazz.isInstance(roi))
397                result.add(roi);
398
399        return result;
400    }
401
402    /**
403     * @deprecated Use {@link #getROIList(List, Class)} instead.
404     */
405    @Deprecated
406    public static List<ROI> getROIList(ROI rois[], Class<? extends ROI> clazz)
407    {
408        final List<ROI> result = new ArrayList<ROI>();
409
410        for (ROI roi : rois)
411            if (clazz.isInstance(roi))
412                result.add(roi);
413
414        return result;
415    }
416
417    /**
418     * Return the number of ROI defined in the specified XML node.
419     * 
420     * @param node
421     *        XML node defining the ROI list
422     * @return the number of ROI defined in the XML node.
423     */
424    public static int getROICount(Node node)
425    {
426        if (node != null)
427        {
428            final List<Node> nodesROI = XMLUtil.getChildren(node, ID_ROI);
429
430            if (nodesROI != null)
431                return nodesROI.size();
432        }
433
434        return 0;
435    }
436
437    /**
438     * Return a list of ROI from a XML node.
439     * 
440     * @param node
441     *        XML node defining the ROI list
442     * @return a list of ROI
443     */
444    public static List<ROI> loadROIsFromXML(Node node)
445    {
446        final List<ROI> result = new ArrayList<ROI>();
447
448        if (node != null)
449        {
450            final List<Node> nodesROI = XMLUtil.getChildren(node, ID_ROI);
451
452            if (nodesROI != null)
453            {
454                for (Node n : nodesROI)
455                {
456                    final ROI roi = createFromXML(n);
457
458                    if (roi != null)
459                        result.add(roi);
460                }
461            }
462        }
463
464        return result;
465    }
466
467    /**
468     * @deprecated Use {@link #loadROIsFromXML(Node)} instead.
469     */
470    @Deprecated
471    public static List<ROI> getROIsFromXML(Node node)
472    {
473        return loadROIsFromXML(node);
474    }
475
476    /**
477     * Set a list of ROI to a XML node.
478     * 
479     * @param node
480     *        XML node which is used to store the list of ROI
481     * @param rois
482     *        the list of ROI to store in the XML node
483     */
484    public static void saveROIsToXML(Node node, List<ROI> rois)
485    {
486        if (node != null)
487        {
488            for (ROI roi : rois)
489            {
490                final Node nodeROI = XMLUtil.addElement(node, ID_ROI);
491
492                if (!roi.saveToXML(nodeROI))
493                {
494                    XMLUtil.removeNode(node, nodeROI);
495                    System.err.println("Error: the roi " + roi.getName() + " was not correctly saved to XML !");
496                }
497            }
498        }
499    }
500
501    /**
502     * @deprecated Use {@link #saveROIsToXML(Node, List)} instead
503     */
504    @Deprecated
505    public static void setROIsFromXML(Node node, List<ROI> rois)
506    {
507        saveROIsToXML(node, rois);
508    }
509
510    public static Color getDefaultColor()
511    {
512        if (defaultColor == null)
513            defaultColor = new Color(
514                    GeneralPreferences.getPreferencesRoiOverlay().getInt(ID_COLOR, DEFAULT_COLOR.getRGB()));
515
516        return defaultColor;
517    }
518
519    public static float getDefaultOpacity()
520    {
521        if (defaultOpacity == null)
522            defaultOpacity = Float
523                    .valueOf(GeneralPreferences.getPreferencesRoiOverlay().getFloat(ID_OPACITY, DEFAULT_OPACITY));
524
525        return defaultOpacity.floatValue();
526    }
527
528    public static double getDefaultStroke()
529    {
530        if (defaultStroke == null)
531            defaultStroke = Double
532                    .valueOf(GeneralPreferences.getPreferencesRoiOverlay().getDouble(ID_STROKE, DEFAULT_STROKE));
533
534        return defaultStroke.doubleValue();
535    }
536
537    public static boolean getDefaultShowName()
538    {
539        if (defaultShowName == null)
540            defaultShowName = Boolean
541                    .valueOf(GeneralPreferences.getPreferencesRoiOverlay().getBoolean(ID_SHOWNAME, false));
542
543        return defaultShowName.booleanValue();
544    }
545
546    public static void setDefaultColor(Color value)
547    {
548        defaultColor = value;
549        GeneralPreferences.getPreferencesRoiOverlay().putInt(ID_COLOR, value.getRGB());
550    }
551
552    public static void setDefaultOpacity(float value)
553    {
554        defaultOpacity = Float.valueOf(value);
555        GeneralPreferences.getPreferencesRoiOverlay().putFloat(ID_OPACITY, value);
556    }
557
558    public static void setDefaultStroke(double value)
559    {
560        defaultStroke = Double.valueOf(value);
561        GeneralPreferences.getPreferencesRoiOverlay().putDouble(ID_STROKE, value);
562    }
563
564    public static void setDefaultShowName(boolean value)
565    {
566        defaultShowName = Boolean.valueOf(value);
567        GeneralPreferences.getPreferencesRoiOverlay().putBoolean(ID_SHOWNAME, value);
568    }
569
570    /**
571     * @deprecated Use {@link IcyCanvas} methods instead
572     */
573    @Deprecated
574    public static double canvasToImageDeltaX(IcyCanvas canvas, int value)
575    {
576        return canvas.canvasToImageDeltaX(value);
577    }
578
579    /**
580     * @deprecated Use {@link IcyCanvas} methods instead
581     */
582    @Deprecated
583    public static double canvasToImageLogDeltaX(IcyCanvas canvas, double value, double logFactor)
584    {
585        return canvas.canvasToImageLogDeltaX((int) value, logFactor);
586    }
587
588    /**
589     * @deprecated Use {@link IcyCanvas} methods instead
590     */
591    @Deprecated
592    public static double canvasToImageLogDeltaX(IcyCanvas canvas, double value)
593    {
594        return canvas.canvasToImageLogDeltaX((int) value);
595    }
596
597    /**
598     * @deprecated Use {@link IcyCanvas} methods instead
599     */
600    @Deprecated
601    public static double canvasToImageLogDeltaX(IcyCanvas canvas, int value, double logFactor)
602    {
603        return canvas.canvasToImageLogDeltaX(value, logFactor);
604    }
605
606    /**
607     * @deprecated Use {@link IcyCanvas} methods instead
608     */
609    @Deprecated
610    public static double canvasToImageLogDeltaX(IcyCanvas canvas, int value)
611    {
612        return canvas.canvasToImageLogDeltaX(value);
613    }
614
615    /**
616     * @deprecated Use {@link IcyCanvas} methods instead
617     */
618    @Deprecated
619    public static double canvasToImageDeltaY(IcyCanvas canvas, int value)
620    {
621        return canvas.canvasToImageDeltaY(value);
622    }
623
624    /**
625     * @deprecated Use {@link IcyCanvas} methods instead
626     */
627    @Deprecated
628    public static double canvasToImageLogDeltaY(IcyCanvas canvas, double value, double logFactor)
629    {
630        return canvas.canvasToImageLogDeltaY((int) value, logFactor);
631    }
632
633    /**
634     * @deprecated Use {@link IcyCanvas} methods instead
635     */
636    @Deprecated
637    public static double canvasToImageLogDeltaY(IcyCanvas canvas, double value)
638    {
639        return canvas.canvasToImageLogDeltaY((int) value);
640    }
641
642    /**
643     * @deprecated Use {@link IcyCanvas} methods instead
644     */
645    @Deprecated
646    public static double canvasToImageLogDeltaY(IcyCanvas canvas, int value, double logFactor)
647    {
648        return canvas.canvasToImageLogDeltaY(value, logFactor);
649    }
650
651    /**
652     * @deprecated Use {@link IcyCanvas} methods instead
653     */
654    @Deprecated
655    public static double canvasToImageLogDeltaY(IcyCanvas canvas, int value)
656    {
657        return canvas.canvasToImageLogDeltaY(value);
658    }
659
660    /**
661     * Abstract basic class for ROI overlay
662     */
663    public abstract class ROIPainter extends Overlay implements VtkPainter
664    {
665        /**
666         * Overlay properties
667         */
668        protected double stroke;
669        protected Color color;
670        protected float opacity;
671        protected boolean showName;
672
673        /**
674         * Last mouse position (image coordinates).
675         * Needed for some internals operation
676         */
677        protected final Point5D.Double mousePos;
678
679        public ROIPainter()
680        {
681            super("ROI painter", OverlayPriority.SHAPE_NORMAL);
682
683            stroke = getDefaultStroke();
684            color = getDefaultColor();
685            opacity = getDefaultOpacity();
686            showName = getDefaultShowName();
687
688            mousePos = new Point5D.Double();
689
690            // we fix the ROI overlay
691            canBeRemoved = false;
692        }
693
694        /**
695         * Return the ROI painter stroke.
696         */
697        public double getStroke()
698        {
699            return painter.stroke;
700        }
701
702        /**
703         * Get adjusted stroke for the current canvas transformation
704         */
705        public double getAdjustedStroke(IcyCanvas canvas)
706        {
707            return ROI.getAdjustedStroke(canvas, getStroke());
708        }
709
710        /**
711         * Set ROI painter stroke.
712         */
713        public void setStroke(double value)
714        {
715            if (stroke != value)
716            {
717                stroke = value;
718                // painter changed event is done on property changed
719                ROI.this.propertyChanged(PROPERTY_STROKE);
720            }
721        }
722
723        /**
724         * Returns the content opacity factor (0 = transparent while 1 means opaque).
725         */
726        public float getOpacity()
727        {
728            return opacity;
729        }
730
731        /**
732         * Sets the content opacity factor (0 = transparent while 1 means opaque).
733         */
734        public void setOpacity(float value)
735        {
736            if (opacity != value)
737            {
738                opacity = value;
739                // painter changed event is done on property changed
740                ROI.this.propertyChanged(PROPERTY_OPACITY);
741            }
742        }
743
744        /**
745         * Returns the color for focused state
746         */
747        public Color getFocusedColor()
748        {
749            final int lum = ColorUtil.getLuminance(getColor());
750
751            if (lum < (256 - 32))
752                return Color.white;
753
754            return Color.gray;
755        }
756
757        /**
758         * @deprecated
759         */
760        @Deprecated
761        public Color getSelectedColor()
762        {
763            return getColor();
764        }
765
766        /**
767         * Returns the color used to display the ROI depending its current state.
768         */
769        public Color getDisplayColor()
770        {
771            if (isFocused())
772                return getFocusedColor();
773
774            return getColor();
775        }
776
777        /**
778         * Return the ROI painter base color.
779         */
780        public Color getColor()
781        {
782            return color;
783        }
784
785        /**
786         * Set the ROI painter base color.
787         */
788        public void setColor(Color value)
789        {
790            if ((color != null) && (color != value))
791            {
792                color = value;
793                // painter changed event is done on property changed
794                ROI.this.propertyChanged(PROPERTY_COLOR);
795            }
796        }
797
798        /**
799         * Return <code>true</code> if ROI painter should display the ROI name at draw time.<br>
800         */
801        public boolean getShowName()
802        {
803            return showName;
804        }
805
806        /**
807         * When set to <code>true</code> the ROI painter display the ROI name at draw time.
808         */
809        public void setShowName(boolean value)
810        {
811            if (showName != value)
812            {
813                showName = value;
814                ROI.this.propertyChanged(PROPERTY_SHOWNAME);
815            }
816        }
817
818        /**
819         * @deprecated Selected color is now automatically calculated
820         */
821        @Deprecated
822        public void setSelectedColor(Color value)
823        {
824            //
825        }
826
827        /**
828         * @deprecated Better to retrieve mouse position from the {@link IcyCanvas} object.
829         */
830        @Deprecated
831        public Point5D.Double getMousePos()
832        {
833            return mousePos;
834        }
835
836        /**
837         * @deprecated Better to retrieve mouse position from the {@link IcyCanvas} object.
838         */
839        @Deprecated
840        public void setMousePos(Point5D pos)
841        {
842            if (!mousePos.equals(pos))
843                mousePos.setLocation(pos);
844        }
845
846        public void computePriority()
847        {
848            if (isFocused())
849                setPriority(OverlayPriority.SHAPE_TOP);
850            else if (isSelected())
851                setPriority(OverlayPriority.SHAPE_HIGH);
852            else
853                setPriority(OverlayPriority.SHAPE_LOW);
854        }
855
856        @Override
857        public boolean isReadOnly()
858        {
859            // use ROI read only property
860            return ROI.this.isReadOnly();
861        }
862
863        @Override
864        public String getName()
865        {
866            // use ROI name property
867            return ROI.this.getName();
868        }
869
870        @Override
871        public void setName(String name)
872        {
873            // modifying layer name modify ROI name
874            ROI.this.setName(name);
875        }
876
877        /**
878         * Update the focus state of the ROI
879         */
880        protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
881        {
882            // empty implementation by default
883            return false;
884        }
885
886        /**
887         * Update the selection state of the ROI (default implementation)
888         */
889        protected boolean updateSelect(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
890        {
891            // nothing to do if the ROI does not have focus
892            if (!isFocused())
893                return false;
894
895            // union selection
896            if (EventUtil.isShiftDown(e))
897            {
898                // not already selected --> add ROI to selection
899                if (!isSelected())
900                {
901                    setSelected(true);
902                    return true;
903                }
904            }
905            else if (EventUtil.isControlDown(e))
906            // switch selection
907            {
908                // inverse state
909                setSelected(!isSelected());
910                return true;
911            }
912            else
913            // exclusive selection
914            {
915                // not selected --> exclusive ROI selection
916                if (!isSelected())
917                {
918                    // exclusive selection can fail if we use embedded ROI (as ROIStack)
919                    if (!canvas.getSequence().setSelectedROI(ROI.this))
920                        ROI.this.setSelected(true);
921
922                    return true;
923                }
924            }
925
926            return false;
927        }
928
929        protected boolean updateDrag(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
930        {
931            // empty implementation by default
932            return false;
933        }
934
935        @Override
936        public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
937        {
938            if (!e.isConsumed())
939            {
940                if (isActiveFor(canvas))
941                {
942                    switch (e.getKeyCode())
943                    {
944                        case KeyEvent.VK_ESCAPE:
945                            // roi selected ? --> global unselect ROI
946                            if (isSelected())
947                            {
948                                canvas.getSequence().setSelectedROI(null);
949                                e.consume();
950                            }
951                            break;
952
953                        case KeyEvent.VK_DELETE:
954                        case KeyEvent.VK_BACK_SPACE:
955                            if (!isReadOnly())
956                            {
957                                // roi selected ?
958                                if (isSelected())
959                                {
960                                    final boolean result;
961
962                                    // if (isFocused())
963                                    // // remove ROI from sequence
964                                    // result = canvas.getSequence().removeROI(ROI.this);
965                                    // else
966                                    // remove all selected ROI from the sequence
967                                    result = canvas.getSequence().removeSelectedROIs(false, true);
968
969                                    if (result)
970                                        e.consume();
971                                }
972                                // roi focused ? --> delete ROI
973                                else if (isFocused())
974                                {
975                                    // remove ROI from sequence
976                                    if (canvas.getSequence().removeROI(ROI.this, true))
977                                        e.consume();
978                                }
979                            }
980                            break;
981                    }
982
983                    // control modifier is used for ROI modification from keyboard
984                    if (EventUtil.isMenuControlDown(e) && isSelected() && !isReadOnly())
985                    {
986                        switch (e.getKeyCode())
987                        {
988                            case KeyEvent.VK_LEFT:
989                                if (EventUtil.isAltDown(e))
990                                {
991                                    // resize
992                                    if (canSetBounds())
993                                    {
994                                        final Rectangle5D bnd = getBounds5D();
995                                        if (EventUtil.isShiftDown(e))
996                                            bnd.setSizeX(Math.max(1, bnd.getSizeX() - 10));
997                                        else
998                                            bnd.setSizeX(Math.max(1, bnd.getSizeX() - 1));
999                                        setBounds5D(bnd);
1000                                        e.consume();
1001                                    }
1002                                }
1003                                else
1004                                {
1005                                    // move
1006                                    if (canSetPosition())
1007                                    {
1008                                        final Point5D pos = getPosition5D();
1009                                        if (EventUtil.isShiftDown(e))
1010                                            pos.setX(pos.getX() - 10);
1011                                        else
1012                                            pos.setX(pos.getX() - 1);
1013                                        setPosition5D(pos);
1014                                        e.consume();
1015                                    }
1016                                }
1017                                break;
1018
1019                            case KeyEvent.VK_RIGHT:
1020                                if (EventUtil.isAltDown(e))
1021                                {
1022                                    // resize
1023                                    if (canSetBounds())
1024                                    {
1025                                        final Rectangle5D bnd = getBounds5D();
1026                                        if (EventUtil.isShiftDown(e))
1027                                            bnd.setSizeX(Math.max(1, bnd.getSizeX() + 10));
1028                                        else
1029                                            bnd.setSizeX(Math.max(1, bnd.getSizeX() + 1));
1030                                        setBounds5D(bnd);
1031                                        e.consume();
1032                                    }
1033                                }
1034                                else
1035                                {
1036                                    // move
1037                                    if (canSetPosition())
1038                                    {
1039                                        final Point5D pos = getPosition5D();
1040                                        if (EventUtil.isShiftDown(e))
1041                                            pos.setX(pos.getX() + 10);
1042                                        else
1043                                            pos.setX(pos.getX() + 1);
1044                                        setPosition5D(pos);
1045                                        e.consume();
1046                                    }
1047                                }
1048                                break;
1049
1050                            case KeyEvent.VK_UP:
1051                                if (EventUtil.isAltDown(e))
1052                                {
1053                                    // resize
1054                                    if (canSetBounds())
1055                                    {
1056                                        final Rectangle5D bnd = getBounds5D();
1057                                        if (EventUtil.isShiftDown(e))
1058                                            bnd.setSizeY(Math.max(1, bnd.getSizeY() - 10));
1059                                        else
1060                                            bnd.setSizeY(Math.max(1, bnd.getSizeY() - 1));
1061                                        setBounds5D(bnd);
1062                                        e.consume();
1063                                    }
1064                                }
1065                                else
1066                                {
1067                                    // move
1068                                    if (canSetPosition())
1069                                    {
1070                                        final Point5D pos = getPosition5D();
1071                                        if (EventUtil.isShiftDown(e))
1072                                            pos.setY(pos.getY() - 10);
1073                                        else
1074                                            pos.setY(pos.getY() - 1);
1075                                        setPosition5D(pos);
1076                                        e.consume();
1077                                    }
1078                                }
1079                                break;
1080
1081                            case KeyEvent.VK_DOWN:
1082                                if (EventUtil.isAltDown(e))
1083                                {
1084                                    // resize
1085                                    if (canSetBounds())
1086                                    {
1087                                        final Rectangle5D bnd = getBounds5D();
1088                                        if (EventUtil.isShiftDown(e))
1089                                            bnd.setSizeY(Math.max(1, bnd.getSizeY() + 10));
1090                                        else
1091                                            bnd.setSizeY(Math.max(1, bnd.getSizeY() + 1));
1092                                        setBounds5D(bnd);
1093                                        e.consume();
1094                                    }
1095                                }
1096                                else
1097                                {
1098                                    // move
1099                                    if (canSetPosition())
1100                                    {
1101                                        final Point5D pos = getPosition5D();
1102                                        if (EventUtil.isShiftDown(e))
1103                                            pos.setY(pos.getY() + 10);
1104                                        else
1105                                            pos.setY(pos.getY() + 1);
1106                                        setPosition5D(pos);
1107                                        e.consume();
1108                                    }
1109                                }
1110                                break;
1111                        }
1112                    }
1113                }
1114            }
1115
1116            // this allow to keep the backward compatibility
1117            super.keyPressed(e, imagePoint, canvas);
1118        }
1119
1120        @Override
1121        public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1122        {
1123            // this allow to keep the backward compatibility
1124            super.keyReleased(e, imagePoint, canvas);
1125
1126            if (isActiveFor(canvas))
1127            {
1128                // just for the shift key state change
1129                if (!isReadOnly())
1130                    updateDrag(e, imagePoint, canvas);
1131            }
1132        }
1133
1134        @Override
1135        public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1136        {
1137            // this allow to keep the backward compatibility
1138            super.mousePressed(e, imagePoint, canvas);
1139
1140            // not yet consumed...
1141            if (!e.isConsumed())
1142            {
1143                if (isActiveFor(canvas))
1144                {
1145                    // left button action
1146                    if (EventUtil.isLeftMouseButton(e))
1147                    {
1148                        ROI.this.beginUpdate();
1149                        try
1150                        {
1151                            // update selection
1152                            if (updateSelect(e, imagePoint, canvas))
1153                                e.consume();
1154                            // always consume when focused to enable dragging
1155                            else if (isFocused())
1156                                e.consume();
1157                        }
1158                        finally
1159                        {
1160                            ROI.this.endUpdate();
1161                        }
1162                    }
1163                }
1164            }
1165        }
1166
1167        @Override
1168        public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1169        {
1170            // this allow to keep the backward compatibility
1171            super.mouseReleased(e, imagePoint, canvas);
1172        }
1173
1174        @Override
1175        public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1176        {
1177            // this allow to keep the backward compatibility
1178            super.mouseClick(e, imagePoint, canvas);
1179
1180            // not yet consumed...
1181            if (!e.isConsumed())
1182            {
1183                // and process ROI stuff now
1184                if (isActiveFor(canvas))
1185                {
1186                    final int clickCount = e.getClickCount();
1187
1188                    // single click
1189                    if (clickCount == 1)
1190                    {
1191                        // right click action
1192                        if (EventUtil.isRightMouseButton(e))
1193                        {
1194                            // unselect (don't consume event)
1195                            if (isSelected())
1196                                ROI.this.setSelected(false);
1197                        }
1198                    }
1199                    // double click
1200                    else if (clickCount == 2)
1201                    {
1202                        // focused ?
1203                        if (isFocused())
1204                        {
1205                            // show in ROI panel
1206                            final RoisPanel roiPanel = Icy.getMainInterface().getRoisPanel();
1207
1208                            if (roiPanel != null)
1209                            {
1210                                roiPanel.scrollTo(ROI.this);
1211                                // consume event
1212                                e.consume();
1213                            }
1214                        }
1215                    }
1216                }
1217            }
1218        }
1219
1220        @Override
1221        public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1222        {
1223            // this allow to keep the backward compatibility
1224            super.mouseDrag(e, imagePoint, canvas);
1225
1226            // nothing here by default, should be implemented in deriving classes...
1227        }
1228
1229        @Override
1230        public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1231        {
1232            // this allow to keep the backward compatibility
1233            super.mouseMove(e, imagePoint, canvas);
1234
1235            // update focus
1236            if (!e.isConsumed())
1237            {
1238                if (isActiveFor(canvas))
1239                {
1240                    if (updateFocus(e, imagePoint, canvas))
1241                        e.consume();
1242                }
1243            }
1244        }
1245
1246        @Override
1247        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
1248        {
1249            // special case of VTK canvas
1250            if (canvas instanceof VtkCanvas)
1251            {
1252                // hide object is not active for canvas
1253                if (!isActiveFor(canvas))
1254                    hideVtkObjects();
1255            }
1256        }
1257
1258        @Override
1259        public boolean loadFromXML(Node node)
1260        {
1261            if (node == null)
1262                return false;
1263
1264            beginUpdate();
1265            try
1266            {
1267                setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, getDefaultColor().getRGB())));
1268                setStroke(XMLUtil.getElementDoubleValue(node, ID_STROKE, getDefaultStroke()));
1269                setOpacity(XMLUtil.getElementFloatValue(node, ID_OPACITY, getDefaultOpacity()));
1270                setShowName(XMLUtil.getElementBooleanValue(node, ID_SHOWNAME, getDefaultShowName()));
1271            }
1272            finally
1273            {
1274                endUpdate();
1275            }
1276
1277            return true;
1278        }
1279
1280        @Override
1281        public boolean saveToXML(Node node)
1282        {
1283            if (node == null)
1284                return false;
1285
1286            XMLUtil.setElementIntValue(node, ID_COLOR, color.getRGB());
1287            XMLUtil.setElementDoubleValue(node, ID_STROKE, stroke);
1288            XMLUtil.setElementFloatValue(node, ID_OPACITY, opacity);
1289            XMLUtil.setElementBooleanValue(node, ID_SHOWNAME, showName);
1290
1291            return true;
1292        }
1293
1294        @Override
1295        public vtkProp[] getProps()
1296        {
1297            // default implementation
1298            return new vtkProp[0];
1299        }
1300
1301        public void hideVtkObjects()
1302        {
1303            for (vtkProp prop : getProps())
1304                prop.SetVisibility(0);
1305        }
1306    }
1307
1308    /**
1309     * id generator
1310     */
1311    private static int id_generator = 1;
1312
1313    /**
1314     * associated ROI painter
1315     */
1316    protected final ROIPainter painter;
1317
1318    protected int id;
1319    protected String name;
1320    protected ROIGroupId groupid;
1321    protected boolean creating;
1322    protected boolean focused;
1323    protected boolean selected;
1324    protected boolean readOnly;
1325    protected final Map<String, String> properties;
1326
1327    // attached ROI icon
1328    protected Image icon;
1329
1330    /**
1331     * cached calculated properties
1332     */
1333    protected Rectangle5D cachedBounds;
1334    protected double cachedNumberOfPoints;
1335    protected double cachedNumberOfContourPoints;
1336    protected boolean boundsInvalid;
1337    protected boolean numberOfContourPointsInvalid;
1338    protected boolean numberOfPointsInvalid;
1339
1340    /**
1341     * listeners
1342     */
1343    protected final List<ROIListener> listeners;
1344    /**
1345     * internal updater
1346     */
1347    protected final UpdateEventHandler updater;
1348
1349    public ROI()
1350    {
1351        super();
1352
1353        // ensure unique id
1354        id = generateId();
1355        painter = createPainter();
1356        name = "";
1357        groupid = null;
1358        readOnly = false;
1359        creating = false;
1360        focused = false;
1361        selected = false;
1362        properties = new HashMap<String, String>();
1363
1364        cachedBounds = new Rectangle5D.Double();
1365        cachedNumberOfPoints = 0d;
1366        cachedNumberOfContourPoints = 0d;
1367        boundsInvalid = true;
1368        numberOfPointsInvalid = true;
1369        numberOfContourPointsInvalid = true;
1370
1371        listeners = new ArrayList<ROIListener>();
1372        updater = new UpdateEventHandler(this, false);
1373
1374        // default icon & name
1375        icon = ResourceUtil.ICON_ROI;
1376        name = getDefaultName();
1377    }
1378
1379    protected abstract ROIPainter createPainter();
1380
1381    /**
1382     * Returns the number of dimension of the ROI:<br>
1383     * 2 for ROI2D<br>
1384     * 3 for ROI3D<br>
1385     * 4 for ROI4D<br>
1386     * 5 for ROI5D<br>
1387     */
1388    public abstract int getDimension();
1389
1390    /**
1391     * generate unique id
1392     */
1393    private static synchronized int generateId()
1394    {
1395        return id_generator++;
1396    }
1397
1398    /**
1399     * @deprecated use {@link Sequence#addROI(ROI)} instead
1400     */
1401    @Deprecated
1402    public void attachTo(Sequence sequence)
1403    {
1404        if (sequence != null)
1405            sequence.addROI(this);
1406    }
1407
1408    /**
1409     * @deprecated use {@link Sequence#removeROI(ROI)} instead
1410     */
1411    @Deprecated
1412    public void detachFrom(Sequence sequence)
1413    {
1414        if (sequence != null)
1415            sequence.removeROI(this);
1416    }
1417
1418    /**
1419     * @deprecated Use {@link #remove(boolean)} instead.
1420     */
1421    @Deprecated
1422    public void detachFromAll(boolean canUndo)
1423    {
1424        remove(canUndo);
1425    }
1426
1427    /**
1428     * @deprecated Use {@link #remove()} instead.
1429     */
1430    @Deprecated
1431    public void detachFromAll()
1432    {
1433        remove(false);
1434    }
1435
1436    /**
1437     * Return true is this ROI is attached to at least one sequence
1438     */
1439    public boolean isAttached(Sequence sequence)
1440    {
1441        if (sequence != null)
1442            return sequence.contains(this);
1443
1444        return false;
1445    }
1446
1447    /**
1448     * Return first sequence where ROI is attached
1449     */
1450    public Sequence getFirstSequence()
1451    {
1452        return Icy.getMainInterface().getFirstSequenceContaining(this);
1453    }
1454
1455    /**
1456     * Return sequences where ROI is attached
1457     */
1458    public ArrayList<Sequence> getSequences()
1459    {
1460        return Icy.getMainInterface().getSequencesContaining(this);
1461    }
1462
1463    /**
1464     * Remove this ROI (detach from all sequence)
1465     */
1466    public void remove(boolean canUndo)
1467    {
1468        final List<Sequence> sequences = Icy.getMainInterface().getSequencesContaining(this);
1469
1470        for (Sequence sequence : sequences)
1471            sequence.removeROI(this, canUndo);
1472    }
1473
1474    /**
1475     * Remove this ROI (detach from all sequence)
1476     */
1477    public void remove()
1478    {
1479        remove(true);
1480    }
1481
1482    /**
1483     * @deprecated Use {@link #remove(boolean)} instead.
1484     */
1485    @Deprecated
1486    public void delete(boolean canUndo)
1487    {
1488        remove(canUndo);
1489    }
1490
1491    /**
1492     * @deprecated Use {@link #remove()} instead.
1493     */
1494    @Deprecated
1495    public void delete()
1496    {
1497        remove(true);
1498    }
1499
1500    public String getClassName()
1501    {
1502        return getClass().getName();
1503    }
1504
1505    public String getSimpleClassName()
1506    {
1507        return ClassUtil.getSimpleClassName(getClassName());
1508    }
1509
1510    /**
1511     * ROI unique id
1512     */
1513    public int getId()
1514    {
1515        return id;
1516    }
1517
1518    /**
1519     * @deprecated Use {@link #getOverlay()} instead.
1520     */
1521    @Deprecated
1522    public ROIPainter getPainter()
1523    {
1524        return getOverlay();
1525    }
1526
1527    /**
1528     * Returns the ROI overlay (used to draw and interact with {@link ROI} on {@link IcyCanvas})
1529     */
1530    public ROIPainter getOverlay()
1531    {
1532        return painter;
1533    }
1534
1535    /**
1536     * Return the ROI painter stroke.
1537     */
1538    public double getStroke()
1539    {
1540        return getOverlay().getStroke();
1541    }
1542
1543    /**
1544     * Get adjusted stroke for the current canvas transformation
1545     */
1546    public double getAdjustedStroke(IcyCanvas canvas)
1547    {
1548        return getOverlay().getAdjustedStroke(canvas);
1549    }
1550
1551    /**
1552     * Set ROI painter stroke.
1553     */
1554    public void setStroke(double value)
1555    {
1556        getOverlay().setStroke(value);
1557    }
1558
1559    /**
1560     * Returns the ROI painter opacity factor (0 = transparent while 1 means opaque).
1561     */
1562    public float getOpacity()
1563    {
1564        return getOverlay().getOpacity();
1565    }
1566
1567    /**
1568     * Sets the ROI painter content opacity factor (0 = transparent while 1 means opaque).
1569     */
1570    public void setOpacity(float value)
1571    {
1572        getOverlay().setOpacity(value);
1573    }
1574
1575    /**
1576     * Return the ROI painter focused color.
1577     */
1578    public Color getFocusedColor()
1579    {
1580        return getOverlay().getFocusedColor();
1581    }
1582
1583    /**
1584     * @deprecated
1585     */
1586    @Deprecated
1587    public Color getSelectedColor()
1588    {
1589        return getOverlay().getSelectedColor();
1590    }
1591
1592    /**
1593     * Returns the color used to display the ROI depending its current state.
1594     */
1595    public Color getDisplayColor()
1596    {
1597        return getOverlay().getDisplayColor();
1598    }
1599
1600    /**
1601     * Return the ROI painter base color.
1602     */
1603    public Color getColor()
1604    {
1605        return getOverlay().getColor();
1606    }
1607
1608    /**
1609     * Set the ROI painter base color.
1610     */
1611    public void setColor(Color value)
1612    {
1613        getOverlay().setColor(value);
1614    }
1615
1616    /**
1617     * @deprecated selected color is automatically calculated.
1618     */
1619    @Deprecated
1620    public void setSelectedColor(Color value)
1621    {
1622        //
1623    }
1624
1625    /**
1626     * @return the icon
1627     */
1628    public Image getIcon()
1629    {
1630        return icon;
1631    }
1632
1633    /**
1634     * @param value
1635     *        the icon to set
1636     */
1637    public void setIcon(Image value)
1638    {
1639        if (icon != value)
1640        {
1641            icon = value;
1642            propertyChanged(PROPERTY_ICON);
1643        }
1644    }
1645
1646    /**
1647     * @return the group id
1648     */
1649    public ROIGroupId getGroupId()
1650    {
1651        return groupid;
1652    }
1653
1654    /**
1655     * @param value
1656     *        the group id to set
1657     */
1658    public void setGroupId(ROIGroupId value)
1659    {
1660        if (groupid != value)
1661        {
1662            groupid = value;
1663            propertyChanged(PROPERTY_GROUPID);
1664        }
1665    }
1666
1667    /**
1668     * @return the default name for this ROI class
1669     */
1670    public String getDefaultName()
1671    {
1672        return "ROI";
1673    }
1674
1675    /**
1676     * Returns <code>true</code> if the ROI has its default name
1677     */
1678    public boolean isDefaultName()
1679    {
1680        return getName().equals(getDefaultName());
1681    }
1682
1683    /**
1684     * @return the name
1685     */
1686    public String getName()
1687    {
1688        return name;
1689    }
1690
1691    /**
1692     * @param value
1693     *        the name to set
1694     */
1695    public void setName(String value)
1696    {
1697        if (name != value)
1698        {
1699            name = value;
1700            propertyChanged(PROPERTY_NAME);
1701            // painter name is ROI name so we notify it
1702            painter.propertyChanged(Overlay.PROPERTY_NAME);
1703        }
1704    }
1705
1706    /**
1707     * Retrieve all custom ROI properties (map of (Key,Value)).
1708     */
1709    public Map<String, String> getProperties()
1710    {
1711        return new HashMap<String, String>(properties);
1712    }
1713
1714    /**
1715     * Retrieve a ROI property value.<br>
1716     * Returns <code>null</code> if the property value is empty.
1717     * 
1718     * @param name
1719     *        Property name.<br>
1720     *        Note that it can be default property name (as {@value #PROPERTY_READONLY}) in which case the value will be
1721     *        returned in String format if possible or launch an {@link IllegalArgumentException} when not possible.
1722     */
1723    public String getProperty(String name)
1724    {
1725        if (name == null)
1726            return null;
1727
1728        // ignore case for property name
1729        final String adjName = name.toLowerCase();
1730
1731        if (StringUtil.equals(adjName, PROPERTY_CREATING))
1732            return Boolean.toString(isCreating());
1733        if (StringUtil.equals(adjName, PROPERTY_NAME))
1734            return getName();
1735        if (StringUtil.equals(adjName, PROPERTY_OPACITY))
1736            return Float.toString(getOpacity());
1737        if (StringUtil.equals(adjName, PROPERTY_READONLY))
1738            return Boolean.toString(isReadOnly());
1739        if (StringUtil.equals(adjName, PROPERTY_SHOWNAME))
1740            return Boolean.toString(getShowName());
1741        if (StringUtil.equals(adjName, PROPERTY_STROKE))
1742            return Double.toString(getStroke());
1743
1744        if (StringUtil.equals(adjName, PROPERTY_COLOR) || StringUtil.equals(adjName, PROPERTY_ICON))
1745            throw new IllegalArgumentException("Cannot return value of property '" + adjName + "' as String");
1746
1747        synchronized (properties)
1748        {
1749            return properties.get(adjName);
1750        }
1751    }
1752
1753    /**
1754     * Generic way to set ROI property value.
1755     * 
1756     * @param name
1757     *        Property name.<br>
1758     *        Note that it can be default property name (as {@value #PROPERTY_READONLY}) in which case the value will be
1759     *        set in String format if possible or launch an {@link IllegalArgumentException} when not possible.
1760     * @param value
1761     *        the value to set in the property (for instance "FALSE" for {@link #PROPERTY_READONLY})
1762     */
1763    public void setProperty(String name, String value)
1764    {
1765        if (name == null)
1766            return;
1767
1768        // ignore case for property name
1769        final String adjName = name.toLowerCase();
1770
1771        if (StringUtil.equals(adjName, PROPERTY_CREATING))
1772            setCreating(Boolean.valueOf(value).booleanValue());
1773        if (StringUtil.equals(adjName, PROPERTY_NAME))
1774            setName(value);
1775        if (StringUtil.equals(adjName, PROPERTY_OPACITY))
1776            setOpacity(Float.valueOf(value).floatValue());
1777        if (StringUtil.equals(adjName, PROPERTY_READONLY))
1778            setReadOnly(Boolean.valueOf(value).booleanValue());
1779        if (StringUtil.equals(adjName, PROPERTY_SHOWNAME))
1780            setShowName(Boolean.valueOf(value).booleanValue());
1781        if (StringUtil.equals(adjName, PROPERTY_STROKE))
1782            setStroke(Double.valueOf(value).doubleValue());
1783
1784        if (StringUtil.equals(adjName, PROPERTY_COLOR) || StringUtil.equals(adjName, PROPERTY_ICON))
1785            throw new IllegalArgumentException("Cannot set value of property '" + adjName + "' as String");
1786
1787        synchronized (properties)
1788        {
1789            properties.put(adjName, value);
1790        }
1791
1792        propertyChanged(adjName);
1793    }
1794
1795    /**
1796     * @deprecated use {@link #getProperty(String)} instead.
1797     */
1798    @Deprecated
1799    public Object getPropertyValue(String propertyName)
1800    {
1801        if (StringUtil.equals(propertyName, PROPERTY_COLOR))
1802            return getColor();
1803        if (StringUtil.equals(propertyName, PROPERTY_CREATING))
1804            return Boolean.valueOf(isCreating());
1805        if (StringUtil.equals(propertyName, PROPERTY_ICON))
1806            return getIcon();
1807        if (StringUtil.equals(propertyName, PROPERTY_NAME))
1808            return getName();
1809        if (StringUtil.equals(propertyName, PROPERTY_OPACITY))
1810            return Float.valueOf(getOpacity());
1811        if (StringUtil.equals(propertyName, PROPERTY_READONLY))
1812            return Boolean.valueOf(isReadOnly());
1813        if (StringUtil.equals(propertyName, PROPERTY_SHOWNAME))
1814            return Boolean.valueOf(getShowName());
1815        if (StringUtil.equals(propertyName, PROPERTY_STROKE))
1816            return Double.valueOf(getStroke());
1817
1818        return null;
1819    }
1820
1821    /**
1822     * @deprecated use {@link #setProperty(String, String)}
1823     */
1824    @Deprecated
1825    public void setPropertyValue(String propertyName, Object value)
1826    {
1827        if (StringUtil.equals(propertyName, PROPERTY_COLOR))
1828            setColor((Color) value);
1829        if (StringUtil.equals(propertyName, PROPERTY_CREATING))
1830            setCreating(((Boolean) value).booleanValue());
1831        if (StringUtil.equals(propertyName, PROPERTY_ICON))
1832            setIcon((Image) value);
1833        if (StringUtil.equals(propertyName, PROPERTY_NAME))
1834            setName((String) value);
1835        if (StringUtil.equals(propertyName, PROPERTY_OPACITY))
1836            setOpacity(((Float) value).floatValue());
1837        if (StringUtil.equals(propertyName, PROPERTY_READONLY))
1838            setReadOnly(((Boolean) value).booleanValue());
1839        if (StringUtil.equals(propertyName, PROPERTY_SHOWNAME))
1840            setShowName(((Boolean) value).booleanValue());
1841        if (StringUtil.equals(propertyName, PROPERTY_STROKE))
1842            setStroke(((Double) value).doubleValue());
1843    }
1844
1845    /**
1846     * @return the creating
1847     */
1848    public boolean isCreating()
1849    {
1850        return creating;
1851    }
1852
1853    /**
1854     * Set the internal <i>creation mode</i> state.<br>
1855     * The ROI interaction behave differently when in <i>creation mode</i>.<br>
1856     * You should not set this state when you create an ROI from the code.
1857     */
1858    public void setCreating(boolean value)
1859    {
1860        if (creating != value)
1861        {
1862            creating = value;
1863            propertyChanged(PROPERTY_CREATING);
1864        }
1865    }
1866
1867    /**
1868     * Returns true if the ROI has a (control) point which is currently focused/selected
1869     */
1870    public abstract boolean hasSelectedPoint();
1871
1872    /**
1873     * Remove focus/selected state on all (control) points.<br>
1874     * Override this method depending implementation
1875     */
1876    public void unselectAllPoints()
1877    {
1878        // do nothing by default
1879    };
1880
1881    /**
1882     * @return the focused
1883     */
1884    public boolean isFocused()
1885    {
1886        return focused;
1887    }
1888
1889    /**
1890     * @param value
1891     *        the focused to set
1892     */
1893    public void setFocused(boolean value)
1894    {
1895        boolean done = false;
1896
1897        if (value)
1898        {
1899            // only one ROI focused per sequence
1900            final List<Sequence> attachedSeqs = Icy.getMainInterface().getSequencesContaining(this);
1901
1902            for (Sequence seq : attachedSeqs)
1903                done |= seq.setFocusedROI(this);
1904        }
1905
1906        if (!done)
1907        {
1908            if (value)
1909                internalFocus();
1910            else
1911                internalUnfocus();
1912        }
1913    }
1914
1915    public void internalFocus()
1916    {
1917        if (focused != true)
1918        {
1919            focused = true;
1920            focusChanged();
1921        }
1922    }
1923
1924    public void internalUnfocus()
1925    {
1926        if (focused != false)
1927        {
1928            focused = false;
1929            focusChanged();
1930        }
1931    }
1932
1933    /**
1934     * @return the selected
1935     */
1936    public boolean isSelected()
1937    {
1938        return selected;
1939    }
1940
1941    /**
1942     * Set the selected state of this ROI.<br>
1943     * Use {@link Sequence#setSelectedROI(ROI)} for exclusive ROI selection.
1944     * 
1945     * @param value
1946     *        the selected to set
1947     */
1948    public void setSelected(boolean value)
1949    {
1950        if (selected != value)
1951        {
1952            selected = value;
1953            // as soon ROI has been unselected, we're not in create mode anymore
1954            if (!value)
1955                setCreating(false);
1956
1957            selectionChanged();
1958        }
1959    }
1960
1961    /**
1962     * @deprecated Use {@link #setSelected(boolean)} or {@link Sequence#setSelectedROI(ROI)} depending you want
1963     *             exclusive selection or not.
1964     */
1965    @Deprecated
1966    public void setSelected(boolean value, boolean exclusive)
1967    {
1968        if (exclusive)
1969        {
1970            // use the sequence for ROI selection with exclusive parameter
1971            final List<Sequence> attachedSeqs = Icy.getMainInterface().getSequencesContaining(this);
1972
1973            for (Sequence seq : attachedSeqs)
1974                seq.setSelectedROI(value ? this : null);
1975        }
1976        else
1977            setSelected(value);
1978    }
1979
1980    /**
1981     * @deprecated Use {@link #setSelected(boolean)} instead.
1982     */
1983    @Deprecated
1984    public void internalUnselect()
1985    {
1986        if (selected != false)
1987        {
1988            selected = false;
1989            // as soon ROI has been unselected, we're not in create mode anymore
1990            setCreating(false);
1991            selectionChanged();
1992        }
1993    }
1994
1995    /**
1996     * @deprecated Use {@link #setSelected(boolean)} instead.
1997     */
1998    @Deprecated
1999    public void internalSelect()
2000    {
2001        if (selected != true)
2002        {
2003            selected = true;
2004            selectionChanged();
2005        }
2006    }
2007
2008    /**
2009     * @deprecated Use {@link #isReadOnly()} instead.
2010     */
2011    @Deprecated
2012    public boolean isEditable()
2013    {
2014        return !isReadOnly();
2015    }
2016
2017    /**
2018     * @deprecated Use {@link #setReadOnly(boolean)} instead.
2019     */
2020    @Deprecated
2021    public void setEditable(boolean value)
2022    {
2023        setReadOnly(!value);
2024    }
2025
2026    /**
2027     * Return true if ROI is in <i>read only</i> state (cannot be modified from GUI).
2028     */
2029    public boolean isReadOnly()
2030    {
2031        return readOnly;
2032    }
2033
2034    /**
2035     * Set the <i>read only</i> state of ROI.
2036     */
2037    public void setReadOnly(boolean value)
2038    {
2039        if (readOnly != value)
2040        {
2041            readOnly = value;
2042
2043            propertyChanged(PROPERTY_READONLY);
2044            if (value)
2045                setSelected(false);
2046        }
2047    }
2048
2049    /**
2050     * Return <code>true</code> if ROI should display its name at draw time.<br>
2051     */
2052    public boolean getShowName()
2053    {
2054        return getOverlay().getShowName();
2055    }
2056
2057    /**
2058     * Set the <i>show name</i> property of ROI.<br>
2059     * When set to <code>true</code> the ROI shows its name at draw time.
2060     */
2061    public void setShowName(boolean value)
2062    {
2063        getOverlay().setShowName(value);
2064    }
2065
2066    /**
2067     * Return true if the ROI is active for the specified canvas.
2068     */
2069    public abstract boolean isActiveFor(IcyCanvas canvas);
2070
2071    /**
2072     * Calculate and returns the bounding box of the <code>ROI</code>.<br>
2073     * This method is used by {@link #getBounds5D()} which should try to cache the result as the
2074     * bounding box calculation can take some computation time for complex ROI.
2075     */
2076    public abstract Rectangle5D computeBounds5D();
2077
2078    /**
2079     * Returns the bounding box of the <code>ROI</code>. Note that there is no guarantee that the
2080     * returned {@link Rectangle5D} is the smallest bounding box that encloses the <code>ROI</code>,
2081     * only that the <code>ROI</code> lies entirely within the indicated <code>Rectangle5D</code>.
2082     * 
2083     * @return an instance of <code>Rectangle5D</code> that is a bounding box of the <code>ROI</code>.
2084     * @see #computeBounds5D()
2085     */
2086    public Rectangle5D getBounds5D()
2087    {
2088        // we need to recompute bounds
2089        if (boundsInvalid)
2090        {
2091            cachedBounds = computeBounds5D();
2092            boundsInvalid = false;
2093        }
2094
2095        return (Rectangle5D) cachedBounds.clone();
2096    }
2097
2098    /**
2099     * Returns the ROI position which normally correspond to the <i>minimum</i> point of the ROI
2100     * bounds.<br>
2101     * 
2102     * @see #getBounds5D()
2103     */
2104    public Point5D getPosition5D()
2105    {
2106        return getBounds5D().getPosition();
2107    }
2108
2109    /**
2110     * Returns <code>true</code> if this ROI accepts bounds change through the {@link #setBounds5D(Rectangle5D)} method.
2111     */
2112    public abstract boolean canSetBounds();
2113
2114    /**
2115     * Returns <code>true</code> if this ROI accepts position change through the {@link #setPosition5D(Point5D)} method.
2116     */
2117    public abstract boolean canSetPosition();
2118
2119    /**
2120     * Set the <code>ROI</code> bounds.<br>
2121     * Note that not all ROI supports bounds modification and you should call {@link #canSetBounds()} first to test if
2122     * the operation is supported.<br>
2123     * 
2124     * @param bounds
2125     *        new ROI bounds
2126     */
2127    public abstract void setBounds5D(Rectangle5D bounds);
2128
2129    /**
2130     * Set the <code>ROI</code> position.<br>
2131     * Note that not all ROI supports position modification and you should call {@link #canSetPosition()} first to test
2132     * if the operation is supported.<br>
2133     * 
2134     * @param position
2135     *        new ROI position
2136     */
2137    public abstract void setPosition5D(Point5D position);
2138
2139    /**
2140     * Returns <code>true</code> if the ROI is empty (does not contains anything).
2141     */
2142    public boolean isEmpty()
2143    {
2144        return getBounds5D().isEmpty();
2145    }
2146
2147    /**
2148     * Tests if a specified 5D point is inside the ROI.
2149     * 
2150     * @return <code>true</code> if the specified <code>Point5D</code> is inside the boundary of the <code>ROI</code>;
2151     *         <code>false</code> otherwise.
2152     */
2153    public abstract boolean contains(double x, double y, double z, double t, double c);
2154
2155    /**
2156     * Tests if a specified {@link Point5D} is inside the ROI.
2157     * 
2158     * @param p
2159     *        the specified <code>Point5D</code> to be tested
2160     * @return <code>true</code> if the specified <code>Point2D</code> is inside the boundary of the <code>ROI</code>;
2161     *         <code>false</code> otherwise.
2162     */
2163    public boolean contains(Point5D p)
2164    {
2165        if (p == null)
2166            return false;
2167
2168        return contains(p.getX(), p.getY(), p.getZ(), p.getT(), p.getC());
2169    }
2170
2171    /**
2172     * Tests if the <code>ROI</code> entirely contains the specified 5D rectangular area. All
2173     * coordinates that lie inside the rectangular area must lie within the <code>ROI</code> for the
2174     * entire rectangular area to be considered contained within the <code>ROI</code>.
2175     * <p>
2176     * The {@code ROI.contains()} method allows a {@code ROI} implementation to conservatively return {@code false}
2177     * when:
2178     * <ul>
2179     * <li>the <code>intersect</code> method returns <code>true</code> and
2180     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the rectangular area are
2181     * prohibitively expensive.
2182     * </ul>
2183     * This means that for some {@code ROIs} this method might return {@code false} even though the {@code ROI} contains
2184     * the rectangular area.
2185     * 
2186     * @param x
2187     *        the X coordinate of the start corner of the specified rectangular area
2188     * @param y
2189     *        the Y coordinate of the start corner of the specified rectangular area
2190     * @param z
2191     *        the Z coordinate of the start corner of the specified rectangular area
2192     * @param t
2193     *        the T coordinate of the start corner of the specified rectangular area
2194     * @param c
2195     *        the C coordinate of the start corner of the specified rectangular area
2196     * @param sizeX
2197     *        the X size of the specified rectangular area
2198     * @param sizeY
2199     *        the Y size of the specified rectangular area
2200     * @param sizeZ
2201     *        the Z size of the specified rectangular area
2202     * @param sizeT
2203     *        the T size of the specified rectangular area
2204     * @param sizeC
2205     *        the C size of the specified rectangular area
2206     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the
2207     *         specified rectangular area; <code>false</code> otherwise or, if the <code>ROI</code> contains the
2208     *         rectangular area and the <code>intersects</code> method returns <code>true</code> and
2209     *         the containment
2210     *         calculations would be too expensive to perform.
2211     */
2212    public abstract boolean contains(double x, double y, double z, double t, double c, double sizeX, double sizeY,
2213            double sizeZ, double sizeT, double sizeC);
2214
2215    /**
2216     * Tests if the <code>ROI</code> entirely contains the specified <code>Rectangle5D</code>. The
2217     * {@code ROI.contains()} method allows a implementation to conservatively return {@code false} when:
2218     * <ul>
2219     * <li>the <code>intersect</code> method returns <code>true</code> and
2220     * <li>the calculations to determine whether or not the <code>ROI</code> entirely contains the
2221     * <code>Rectangle2D</code> are prohibitively expensive.
2222     * </ul>
2223     * This means that for some ROIs this method might return {@code false} even though the {@code ROI} contains the
2224     * {@code Rectangle5D}.
2225     * 
2226     * @param r
2227     *        The specified <code>Rectangle5D</code>
2228     * @return <code>true</code> if the interior of the <code>ROI</code> entirely contains the <code>Rectangle5D</code>;
2229     *         <code>false</code> otherwise or, if the <code>ROI</code> contains the <code>Rectangle5D</code> and the
2230     *         <code>intersects</code> method returns <code>true</code> and the containment
2231     *         calculations would be too
2232     *         expensive to perform.
2233     * @see #contains(double, double, double, double, double, double, double, double, double, double)
2234     */
2235    public boolean contains(Rectangle5D r)
2236    {
2237        if (r == null)
2238            return false;
2239
2240        return contains(r.getX(), r.getY(), r.getZ(), r.getT(), r.getC(), r.getSizeX(), r.getSizeY(), r.getSizeZ(),
2241                r.getSizeT(), r.getSizeC());
2242    }
2243
2244    /**
2245     * Tests if the <code>ROI</code> entirely contains the specified <code>ROI</code>.
2246     * WARNING: this method may be "pixel accurate" only depending the internal implementation.
2247     * 
2248     * @return <code>true</code> if the current <code>ROI</code> entirely contains the
2249     *         specified <code>ROI</code>; <code>false</code> otherwise.
2250     */
2251    public boolean contains(ROI roi)
2252    {
2253        // default implementation using BooleanMask
2254        final Rectangle5D.Integer bounds = getBounds5D().toInteger();
2255        final Rectangle5D.Integer roiBounds = roi.getBounds5D().toInteger();
2256
2257        // trivial optimization
2258        if (bounds.isEmpty())
2259            return false;
2260
2261        // special case of ROI Point --> just test position if contained
2262        if (roiBounds.isEmpty())
2263            return contains(roiBounds.getPosition());
2264
2265        // simple bounds contains test
2266        if (bounds.contains(roiBounds))
2267        {
2268            final Rectangle5D.Integer containedBounds = bounds.createIntersection(roiBounds).toInteger();
2269            int minZ;
2270            int maxZ;
2271            int minT;
2272            int maxT;
2273            int minC;
2274            int maxC;
2275
2276            // special infinite case
2277            if (containedBounds.isInfiniteZ())
2278            {
2279                minZ = -1;
2280                maxZ = -1;
2281            }
2282            else
2283            {
2284                minZ = (int) containedBounds.getMinZ();
2285                maxZ = (int) containedBounds.getMaxZ();
2286            }
2287            if (containedBounds.isInfiniteT())
2288            {
2289                minT = -1;
2290                maxT = -1;
2291            }
2292            else
2293            {
2294                minT = (int) containedBounds.getMinT();
2295                maxT = (int) containedBounds.getMaxT();
2296            }
2297            if (containedBounds.isInfiniteC())
2298            {
2299                minC = -1;
2300                maxC = -1;
2301            }
2302            else
2303            {
2304                minC = (int) containedBounds.getMinC();
2305                maxC = (int) containedBounds.getMaxC();
2306            }
2307
2308            final Rectangle containedBounds2D = (Rectangle) containedBounds.toRectangle2D();
2309
2310            // slow method using the boolean mask
2311            for (int c = minC; c <= maxC; c++)
2312            {
2313                for (int t = minT; t <= maxT; t++)
2314                {
2315                    for (int z = minZ; z <= maxZ; z++)
2316                    {
2317                        BooleanMask2D mask;
2318                        BooleanMask2D roiMask;
2319
2320                        // take content first
2321                        mask = new BooleanMask2D(containedBounds2D,
2322                                getBooleanMask2D(containedBounds2D, z, t, c, false));
2323                        roiMask = new BooleanMask2D(containedBounds2D,
2324                                roi.getBooleanMask2D(containedBounds2D, z, t, c, false));
2325
2326                        // test first only on content
2327                        if (!mask.contains(roiMask))
2328                            return false;
2329
2330                        // take content and edge
2331                        mask = new BooleanMask2D(containedBounds2D, getBooleanMask2D(containedBounds2D, z, t, c, true));
2332                        roiMask = new BooleanMask2D(containedBounds2D,
2333                                roi.getBooleanMask2D(containedBounds2D, z, t, c, true));
2334
2335                        // then test on content and edge
2336                        if (!mask.contains(roiMask))
2337                            return false;
2338                    }
2339                }
2340            }
2341
2342            return true;
2343        }
2344
2345        return false;
2346    }
2347
2348    /**
2349     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified
2350     * rectangular area. The rectangular area is considered to intersect the <code>ROI</code> if any
2351     * point is contained in both the interior of the <code>ROI</code> and the specified rectangular
2352     * area.
2353     * <p>
2354     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
2355     * when:
2356     * <ul>
2357     * <li>there is a high probability that the rectangular area and the <code>ROI</code> intersect, but
2358     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
2359     * </ul>
2360     * This means that for some {@code ROIs} this method might return {@code true} even though the rectangular area does
2361     * not intersect the {@code ROI}.
2362     * 
2363     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
2364     *         rectangular area intersect, or are both highly likely to intersect and intersection
2365     *         calculations would be too expensive to perform; <code>false</code> otherwise.
2366     */
2367    public abstract boolean intersects(double x, double y, double z, double t, double c, double sizeX, double sizeY,
2368            double sizeZ, double sizeT, double sizeC);
2369
2370    /**
2371     * Tests if the interior of the <code>ROI</code> intersects the interior of a specified
2372     * rectangular area. The rectangular area is considered to intersect the <code>ROI</code> if any
2373     * point is contained in both the interior of the <code>ROI</code> and the specified rectangular
2374     * area.
2375     * <p>
2376     * The {@code ROI.intersects()} method allows a {@code ROI} implementation to conservatively return {@code true}
2377     * when:
2378     * <ul>
2379     * <li>there is a high probability that the rectangular area and the <code>ROI</code> intersect, but
2380     * <li>the calculations to accurately determine this intersection are prohibitively expensive.
2381     * </ul>
2382     * This means that for some {@code ROIs} this method might return {@code true} even though the rectangular area does
2383     * not intersect the {@code ROI}.
2384     * 
2385     * @return <code>true</code> if the interior of the <code>ROI</code> and the interior of the
2386     *         rectangular area intersect, or are both highly likely to intersect and intersection
2387     *         calculations would be too expensive to perform; <code>false</code> otherwise.
2388     */
2389    public boolean intersects(Rectangle5D r)
2390    {
2391        if (r == null)
2392            return false;
2393
2394        return intersects(r.getX(), r.getY(), r.getZ(), r.getT(), r.getC(), r.getSizeX(), r.getSizeY(), r.getSizeZ(),
2395                r.getSizeT(), r.getSizeC());
2396    }
2397
2398    /**
2399     * Tests if the current <code>ROI</code> intersects the specified <code>ROI</code>.<br>
2400     * Note that this method may be "pixel accurate" only depending the internal implementation.
2401     * 
2402     * @return <code>true</code> if <code>ROI</code> intersect, <code>false</code> otherwise.
2403     */
2404    public boolean intersects(ROI roi)
2405    {
2406        // default implementation using BooleanMask
2407        final Rectangle5D.Integer bounds = getBounds5D().toInteger();
2408        final Rectangle5D.Integer roiBounds = roi.getBounds5D().toInteger();
2409        final Rectangle5D.Integer intersection = bounds.createIntersection(roiBounds).toInteger();
2410
2411        int minZ;
2412        int maxZ;
2413        int minT;
2414        int maxT;
2415        int minC;
2416        int maxC;
2417
2418        // special infinite case
2419        if (intersection.isInfiniteZ())
2420        {
2421            minZ = -1;
2422            maxZ = -1;
2423        }
2424        else
2425        {
2426            minZ = (int) intersection.getMinZ();
2427            maxZ = (int) intersection.getMaxZ();
2428        }
2429        if (intersection.isInfiniteT())
2430        {
2431            minT = -1;
2432            maxT = -1;
2433        }
2434        else
2435        {
2436            minT = (int) intersection.getMinT();
2437            maxT = (int) intersection.getMaxT();
2438        }
2439        if (intersection.isInfiniteC())
2440        {
2441            minC = -1;
2442            maxC = -1;
2443        }
2444        else
2445        {
2446            minC = (int) intersection.getMinC();
2447            maxC = (int) intersection.getMaxC();
2448        }
2449
2450        // slow method using the boolean mask
2451        for (int c = minC; c <= maxC; c++)
2452        {
2453            for (int t = minT; t <= maxT; t++)
2454            {
2455                for (int z = minZ; z <= maxZ; z++)
2456                {
2457                    if (getBooleanMask2D(z, t, c, true).intersects(roi.getBooleanMask2D(z, t, c, true)))
2458                        return true;
2459                }
2460            }
2461        }
2462
2463        return false;
2464    }
2465
2466    /**
2467     * Returns the boolean array mask for the specified rectangular region at specified C, Z, T
2468     * position.<br>
2469     * <br>
2470     * If pixel (x1, y1, c, z, t) is contained in the roi:<br>
2471     * <code>&nbsp result[((y1 - y) * width) + (x1 - x)] = true</code><br>
2472     * If pixel (x1, y1, c, z, t) is not contained in the roi:<br>
2473     * <code>&nbsp result[((y1 - y) * width) + (x1 - x)] = false</code><br>
2474     * 
2475     * @param x
2476     *        the X coordinate of the upper-left corner of the specified rectangular region
2477     * @param y
2478     *        the Y coordinate of the upper-left corner of the specified rectangular region
2479     * @param width
2480     *        the width of the specified rectangular region
2481     * @param height
2482     *        the height of the specified rectangular region
2483     * @param z
2484     *        Z position we want to retrieve the boolean mask
2485     * @param t
2486     *        T position we want to retrieve the boolean mask
2487     * @param c
2488     *        C position we want to retrieve the boolean mask
2489     * @param inclusive
2490     *        If true then all partially contained (intersected) pixels are included in the mask.
2491     * @return the boolean bitmap mask
2492     */
2493    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, int c, boolean inclusive)
2494    {
2495        final boolean[] result = new boolean[Math.max(0, width) * Math.max(0, height)];
2496
2497        // simple and basic implementation, override it to have better performance
2498        int offset = 0;
2499        for (int j = 0; j < height; j++)
2500        {
2501            for (int i = 0; i < width; i++)
2502            {
2503                if (inclusive)
2504                    result[offset] = intersects(x + i, y + j, z, t, c, 1d, 1d, 1d, 1d, 1d);
2505                else
2506                    result[offset] = contains(x + i, y + j, z, t, c, 1d, 1d, 1d, 1d, 1d);
2507                offset++;
2508            }
2509        }
2510
2511        return result;
2512    }
2513
2514    /**
2515     * Get the boolean bitmap mask for the specified rectangular area of the roi and for the
2516     * specified Z,T position.<br>
2517     * if the pixel (x,y) is contained in the roi Z,T position then result[(y * width) + x] = true <br>
2518     * if the pixel (x,y) is not contained in the roi Z,T position then result[(y * width) + x] =
2519     * false
2520     * 
2521     * @param rect
2522     *        2D rectangular area we want to retrieve the boolean mask
2523     * @param z
2524     *        Z position we want to retrieve the boolean mask
2525     * @param t
2526     *        T position we want to retrieve the boolean mask
2527     * @param c
2528     *        C position we want to retrieve the boolean mask
2529     * @param inclusive
2530     *        If true then all partially contained (intersected) pixels are included in the mask.
2531     */
2532    public boolean[] getBooleanMask2D(Rectangle rect, int z, int t, int c, boolean inclusive)
2533    {
2534        return getBooleanMask2D(rect.x, rect.y, rect.width, rect.height, z, t, c, inclusive);
2535    }
2536
2537    /**
2538     * Returns the {@link BooleanMask2D} object representing the XY plan content at specified Z, T,
2539     * C position.<br>
2540     * <br>
2541     * If pixel (x, y, c, z, t) is contained in the roi:<br>
2542     * <code>&nbsp mask[(y - bounds.y) * bounds.width) + (x - bounds.x)] = true</code> <br>
2543     * If pixel (x, y, c, z, t) is not contained in the roi:<br>
2544     * <code>&nbsp mask[(y - bounds.y) * bounds.width) + (x - bounds.x)] = false</code>
2545     * 
2546     * @param z
2547     *        Z position we want to retrieve the boolean mask.<br>
2548     *        Set it to -1 to retrieve the mask whatever is the Z position of ROI2D.
2549     * @param t
2550     *        T position we want to retrieve the boolean mask.<br>
2551     *        Set it to -1 to retrieve the mask whatever is the T position of ROI2D/ROI3D.
2552     * @param c
2553     *        C position we want to retrieve the boolean mask.<br>
2554     *        Set it to -1 to retrieve the mask whatever is the C position of ROI2D/ROI3D/ROI4D.
2555     * @param inclusive
2556     *        If true then all partially contained (intersected) pixels are included in the mask.
2557     */
2558    public BooleanMask2D getBooleanMask2D(int z, int t, int c, boolean inclusive)
2559    {
2560        final Rectangle bounds2D = getBounds5D().toRectangle2D().getBounds();
2561
2562        // empty ROI --> return empty mask
2563        if (bounds2D.isEmpty())
2564            return new BooleanMask2D(new Rectangle(), new boolean[0]);
2565
2566        return new BooleanMask2D(bounds2D,
2567                getBooleanMask2D(bounds2D.x, bounds2D.y, bounds2D.width, bounds2D.height, z, t, c, inclusive));
2568    }
2569
2570    /**
2571     * @deprecated Override directly these methods:<br>
2572     *             {@link #getUnion(ROI)}<br>
2573     *             {@link #getIntersection(ROI)}<br>
2574     *             {@link #getExclusiveUnion(ROI)}<br>
2575     *             {@link #getSubtraction(ROI)}<br>
2576     *             or use {@link #merge(ROI, BooleanOperator)} method instead.
2577     */
2578    /*
2579     * Generic implementation for ROI using the BooleanMask object so the result is just an
2580     * approximation. Override to optimize for specific ROI.
2581     */
2582    @Deprecated
2583    protected ROI computeOperation(ROI roi, BooleanOperator op) throws UnsupportedOperationException
2584    {
2585        System.out.println("Deprecated method " + getClassName() + ".computeOperation(ROI, BooleanOperator) called !");
2586        return null;
2587    }
2588
2589    /**
2590     * Same as {@link #merge(ROI, BooleanOperator)} except it modifies the current <code>ROI</code> to reflect the
2591     * result of the boolean operation with specified <code>ROI</code>.<br>
2592     * Note that this operation work only if the 2 ROIs are compatible for that type of operation.
2593     * If that is not
2594     * the case a {@link UnsupportedOperationException} is thrown if <code>allowCreate</code> parameter is set to
2595     * <code>false</code>, if the parameter is set to <code>true</code> the result may be returned
2596     * in a new created ROI.
2597     * 
2598     * @param roi
2599     *        the <code>ROI</code> to merge with current <code>ROI</code>
2600     * @param op
2601     *        the boolean operation to process
2602     * @param allowCreate
2603     *        if set to <code>true</code> the method will create a new ROI to return the result of
2604     *        the operation if it
2605     *        cannot be directly processed on the current <code>ROI</code>
2606     * @return the modified ROI or a new created ROI if the operation cannot be directly processed
2607     *         on the current ROI
2608     *         and <code>allowCreate</code> parameter was set to <code>true</code>
2609     * @throws UnsupportedOperationException
2610     *         if the two ROI cannot be merged together.
2611     * @see #merge(ROI, BooleanOperator)
2612     */
2613    public ROI mergeWith(ROI roi, BooleanOperator op, boolean allowCreate) throws UnsupportedOperationException
2614    {
2615        switch (op)
2616        {
2617            case AND:
2618                return intersect(roi, allowCreate);
2619
2620            case OR:
2621                return add(roi, allowCreate);
2622
2623            case XOR:
2624                return exclusiveAdd(roi, allowCreate);
2625        }
2626
2627        return this;
2628    }
2629
2630    /**
2631     * Adds content of specified <code>ROI</code> into this <code>ROI</code>.
2632     * The resulting content of this <code>ROI</code> will include
2633     * the union of both ROI's contents.<br>
2634     * Note that this operation work only if the 2 ROIs are compatible for that type of operation.
2635     * If that is not the case a {@link UnsupportedOperationException} is thrown if <code>allowCreate</code> parameter
2636     * is set to <code>false</code>, if the parameter is set to <code>true</code> the result may be returned in a new
2637     * created ROI.
2638     * 
2639     * <pre>
2640     *     // Example:
2641     *      roi1 (before)     +         roi2       =    roi1 (after)
2642     * 
2643     *     ################     ################     ################
2644     *     ##############         ##############     ################
2645     *     ############             ############     ################
2646     *     ##########                 ##########     ################
2647     *     ########                     ########     ################
2648     *     ######                         ######     ######    ######
2649     *     ####                             ####     ####        ####
2650     *     ##                                 ##     ##            ##
2651     * </pre>
2652     * 
2653     * @param roi
2654     *        the <code>ROI</code> to be added to the current <code>ROI</code>
2655     * @param allowCreate
2656     *        if set to <code>true</code> the method will create a new ROI to return the result of
2657     *        the operation if it
2658     *        cannot be directly processed on the current <code>ROI</code>
2659     * @return the modified ROI or a new created ROI if the operation cannot be directly processed
2660     *         on the current ROI
2661     *         and <code>allowCreate</code> parameter was set to <code>true</code>
2662     * @throws UnsupportedOperationException
2663     *         if the two ROI cannot be added together.
2664     * @see #getUnion(ROI)
2665     */
2666    public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException
2667    {
2668        // nothing to do
2669        if (roi == null)
2670            return this;
2671
2672        if (allowCreate)
2673            return getUnion(roi);
2674
2675        throw new UnsupportedOperationException(getClassName() + " does not support add(ROI) operation !");
2676    }
2677
2678    /**
2679     * Sets the content of this <code>ROI</code> to the intersection of
2680     * its current content and the content of the specified <code>ROI</code>.
2681     * The resulting ROI will include only contents that were contained in both ROI.<br>
2682     * Note that this operation work only if the 2 ROIs are compatible for that type of operation.
2683     * If that is not the case a {@link UnsupportedOperationException} is thrown if <code>allowCreate</code> parameter
2684     * is set to <code>false</code>, if the parameter is set to <code>true</code> the result may be returned in a new
2685     * created ROI.
2686     * 
2687     * <pre>
2688     *     // Example:
2689     *     roi1 (before) intersect    roi2        =   roi1 (after)
2690     * 
2691     *     ################     ################     ################
2692     *     ##############         ##############       ############
2693     *     ############             ############         ########
2694     *     ##########                 ##########           ####
2695     *     ########                     ########
2696     *     ######                         ######
2697     *     ####                             ####
2698     *     ##                                 ##
2699     * </pre>
2700     * 
2701     * @param roi
2702     *        the <code>ROI</code> to be intersected to the current <code>ROI</code>
2703     * @param allowCreate
2704     *        if set to <code>true</code> the method will create a new ROI to return the result of
2705     *        the operation if it
2706     *        cannot be directly processed on the current <code>ROI</code>
2707     * @return the modified ROI or a new created ROI if the operation cannot be directly processed
2708     *         on the current ROI
2709     *         and <code>allowCreate</code> parameter was set to <code>true</code>
2710     * @throws UnsupportedOperationException
2711     *         if the two ROI cannot be intersected together.
2712     * @see #getIntersection(ROI)
2713     */
2714    public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException
2715    {
2716        // nothing to do
2717        if (roi == null)
2718            return this;
2719
2720        if (allowCreate)
2721            return getIntersection(roi);
2722
2723        throw new UnsupportedOperationException(getClassName() + " does not support intersect(ROI) operation !");
2724    }
2725
2726    /**
2727     * Sets the content of this <code>ROI</code> to be the union of its current content and the
2728     * content of the specified <code>ROI</code>, minus their intersection.
2729     * The resulting <code>ROI</code> will include only content that were contained in either this <code>ROI</code> or
2730     * in the specified <code>ROI</code>, but not in both.<br>
2731     * Note that this operation work only if the 2 ROIs are compatible for that type of operation.
2732     * If that is not
2733     * the case a {@link UnsupportedOperationException} is thrown if <code>allowCreate</code> parameter is set to
2734     * <code>false</code>, if the parameter is set to <code>true</code> the result may be returned
2735     * in a new created ROI.
2736     * 
2737     * <pre>
2738     *     // Example:
2739     *      roi1 (before)   xor      roi2         =    roi1 (after)
2740     * 
2741     *     ################     ################
2742     *     ##############         ##############     ##            ##
2743     *     ############             ############     ####        ####
2744     *     ##########                 ##########     ######    ######
2745     *     ########                     ########     ################
2746     *     ######                         ######     ######    ######
2747     *     ####                             ####     ####        ####
2748     *     ##                                 ##     ##            ##
2749     * </pre>
2750     * 
2751     * @param roi
2752     *        the <code>ROI</code> to be exclusively added to the current <code>ROI</code>
2753     * @param allowCreate
2754     *        if set to <code>true</code> the method will create a new ROI to return the result of
2755     *        the operation if it
2756     *        cannot be directly processed on the current <code>ROI</code>
2757     * @return the modified ROI or a new created ROI if the operation cannot be directly processed
2758     *         on the current ROI
2759     *         and <code>allowCreate</code> parameter was set to <code>true</code>
2760     * @throws UnsupportedOperationException
2761     *         if the two ROI cannot be exclusively added together.
2762     * @see #getExclusiveUnion(ROI)
2763     */
2764    public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException
2765    {
2766        // nothing to do
2767        if (roi == null)
2768            return this;
2769
2770        if (allowCreate)
2771            return getExclusiveUnion(roi);
2772
2773        throw new UnsupportedOperationException(getClassName() + " does not support exclusiveAdd(ROI) operation !");
2774    }
2775
2776    /**
2777     * Subtract the specified <code>ROI</code> content from current <code>ROI</code>.<br>
2778     * Note that this operation work only if the 2 ROIs are compatible for that type of operation.
2779     * If that is not the case a {@link UnsupportedOperationException} is thrown if <code>allowCreate</code> parameter
2780     * is set to <code>false</code>, if the parameter is set to <code>true</code> the result may be returned in a new
2781     * created ROI.
2782     * 
2783     * @param roi
2784     *        the <code>ROI</code> to subtract from the current <code>ROI</code>
2785     * @param allowCreate
2786     *        if set to <code>true</code> the method will create a new ROI to return the result of
2787     *        the operation if it
2788     *        cannot be directly processed on the current <code>ROI</code>
2789     * @return the modified ROI or a new created ROI if the operation cannot be directly processed
2790     *         on the current ROI
2791     *         and <code>allowCreate</code> parameter was set to <code>true</code>
2792     * @throws UnsupportedOperationException
2793     *         if we can't subtract the specified <code>ROI</code> from this <code>ROI</code>
2794     * @see #getSubtraction(ROI)
2795     */
2796    public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException
2797    {
2798        // nothing to do
2799        if (roi == null)
2800            return this;
2801
2802        if (allowCreate)
2803            return getSubtraction(roi);
2804
2805        throw new UnsupportedOperationException(getClassName() + " does not support subtract(ROI) operation !");
2806    }
2807
2808    /**
2809     * Compute the boolean operation with specified <code>ROI</code> and return result in a new <code>ROI</code>.
2810     */
2811    public ROI merge(ROI roi, BooleanOperator op) throws UnsupportedOperationException
2812    {
2813        switch (op)
2814        {
2815            case AND:
2816                return getIntersection(roi);
2817
2818            case OR:
2819                return getUnion(roi);
2820
2821            case XOR:
2822                return getExclusiveUnion(roi);
2823        }
2824
2825        return null;
2826    }
2827
2828    /**
2829     * Compute union with specified <code>ROI</code> and return result in a new <code>ROI</code>.<br>
2830     * The default implementation use <code>ROIUtil.getUnion(ROI, ROI)</code> internally but it maybe overridden.
2831     */
2832    public ROI getUnion(ROI roi) throws UnsupportedOperationException
2833    {
2834        return ROIUtil.getUnion(this, roi);
2835    }
2836
2837    /**
2838     * Compute intersection with specified <code>ROI</code> and return result in a new <code>ROI</code>.<br>
2839     * The default implementation use <code>ROIUtil.getIntersection(ROI, ROI)</code> internally but it maybe overridden.
2840     */
2841    public ROI getIntersection(ROI roi) throws UnsupportedOperationException
2842    {
2843        return ROIUtil.getIntersection(this, roi);
2844    }
2845
2846    /**
2847     * Compute exclusive union with specified <code>ROI</code> and return result in a new <code>ROI</code>.<br>
2848     * The default implementation use <code>ROIUtil.getExclusiveUnion(ROI, ROI)</code> internally but it maybe overridden.
2849     */
2850    public ROI getExclusiveUnion(ROI roi) throws UnsupportedOperationException
2851    {
2852        return ROIUtil.getExclusiveUnion(this, roi);
2853    }
2854
2855    /**
2856     * Subtract the specified <code>ROI</code> and return result in a new <code>ROI</code>.<br>
2857     * The default implementation use <code>ROIUtil.getSubtraction(ROI, ROI)</code> internally but it maybe overridden.
2858     */
2859    public ROI getSubtraction(ROI roi) throws UnsupportedOperationException
2860    {
2861        return ROIUtil.getSubtraction(this, roi);
2862    }
2863
2864    /**
2865     * Compute and returns the number of point (pixel) composing the ROI contour.
2866     */
2867    /*
2868     * Override this method to adapt and optimize for a specific ROI.
2869     */
2870    public abstract double computeNumberOfContourPoints();
2871
2872    /**
2873     * Returns the number of point (pixel) composing the ROI contour.<br>
2874     * It is used to calculate the perimeter (2D) or surface area (3D) of the ROI.
2875     * 
2876     * @see #computeNumberOfContourPoints()
2877     */
2878    public double getNumberOfContourPoints()
2879    {
2880        // we need to recompute the number of edge point
2881        if (numberOfContourPointsInvalid)
2882        {
2883            cachedNumberOfContourPoints = computeNumberOfContourPoints();
2884            numberOfContourPointsInvalid = false;
2885        }
2886
2887        return cachedNumberOfContourPoints;
2888    }
2889
2890    /**
2891     * Compute and returns the number of point (pixel) contained in the ROI.
2892     */
2893    /*
2894     * Override this method to adapt and optimize for a specific ROI.
2895     */
2896    public abstract double computeNumberOfPoints();
2897
2898    /**
2899     * Returns the number of point (pixel) contained in the ROI.<br>
2900     * It is used to calculate the area (2D) or volume (3D) of the ROI.
2901     */
2902    public double getNumberOfPoints()
2903    {
2904        // we need to recompute the number of point
2905        if (numberOfPointsInvalid)
2906        {
2907            cachedNumberOfPoints = computeNumberOfPoints();
2908            numberOfPointsInvalid = false;
2909        }
2910
2911        return cachedNumberOfPoints;
2912    }
2913
2914    /**
2915     * Computes and returns the length/perimeter of the ROI in um given the pixel size informations from the specified
2916     * Sequence.<br>
2917     * Generic implementation of length computation uses the number of contour point (approximation).
2918     * This method should be overridden whenever possible to provide faster and accurate calculation.<br>
2919     * Throws a UnsupportedOperationException if the operation is not supported for this ROI.
2920     * 
2921     * @see #getNumberOfContourPoints()
2922     */
2923    public double getLength(Sequence sequence) throws UnsupportedOperationException
2924    {
2925        return sequence.calculateSize(getNumberOfContourPoints(), getDimension(), 1);
2926    }
2927
2928    /**
2929     * @deprecated Use {@link #getLength(Sequence)} or {@link #getNumberOfContourPoints()} instead.
2930     */
2931    @Deprecated
2932    public double getPerimeter()
2933    {
2934        return getNumberOfContourPoints();
2935    }
2936
2937    /**
2938     * @deprecated Only for ROI3D object, use {@link #getNumberOfPoints()} instead for other type of
2939     *             ROI.
2940     */
2941    @Deprecated
2942    public double getVolume()
2943    {
2944        return getNumberOfPoints();
2945    }
2946
2947    /**
2948     * @deprecated Use <code>getOverlay().setMousePos(..)</code> instead.
2949     */
2950    @Deprecated
2951    public void setMousePos(Point2D pos)
2952    {
2953        if (pos != null)
2954            getOverlay().setMousePos(new Point5D.Double(pos.getX(), pos.getY(), -1, -1, -1));
2955    }
2956
2957    /**
2958     * Returns a copy of the ROI or <code>null</code> if the operation failed.
2959     */
2960    public ROI getCopy()
2961    {
2962        // use XML persistence for cloning
2963        final Node node = XMLUtil.createDocument(true).getDocumentElement();
2964        int retry;
2965
2966        // XML methods sometime fails, better to offer retry (hacky)
2967        retry = 3;
2968        // save
2969        while ((retry > 0) && !saveToXML(node))
2970            retry--;
2971
2972        if (retry <= 0)
2973        {
2974            System.err.println("Cannot get a copy of roi " + getName() + ": XML save operation failed.");
2975            // throw new RuntimeException("Cannot get a copy of roi " + getName() + ": XML save
2976            // operation failed !");
2977            return null;
2978        }
2979
2980        ROI result;
2981
2982        // XML methods sometime fails, better to offer retry (hacky)
2983        retry = 3;
2984        result = null;
2985        while ((retry > 0) && (result == null))
2986        {
2987            result = createFromXML(node);
2988            retry--;
2989        }
2990
2991        if (result == null)
2992        {
2993            System.err.println("Cannot get a copy of roi " + getName() + ": creation from XML failed.");
2994            // throw new RuntimeException("Cannot get a copy of roi " + getName() + ": creation from
2995            // XML failed !");
2996            return null;
2997        }
2998
2999        // then generate new id
3000        result.id = generateId();
3001
3002        return result;
3003    }
3004
3005    /**
3006     * Returns the name suffix when we want to obtain only a sub part of the ROI (always in Z,T,C
3007     * order).<br/>
3008     * For instance if we use for z=1, t=5 and c=-1 this method will return <code>[Z=1, T=5]</code>
3009     * 
3010     * @param z
3011     *        the specific Z position (slice) we want to retrieve (<code>-1</code> to retrieve the
3012     *        whole ROI Z dimension)
3013     * @param t
3014     *        the specific T position (frame) we want to retrieve (<code>-1</code> to retrieve the
3015     *        whole ROI T dimension)
3016     * @param c
3017     *        the specific C position (channel) we want to retrieve (<code>-1</code> to retrieve the
3018     *        whole ROI C dimension)
3019     */
3020    static public String getNameSuffix(int z, int t, int c)
3021    {
3022        String result = "";
3023
3024        if (z != -1)
3025        {
3026            if (StringUtil.isEmpty(result))
3027                result = " [";
3028            else
3029                result += ", ";
3030            result += "Z=" + z;
3031        }
3032        if (t != -1)
3033        {
3034            if (StringUtil.isEmpty(result))
3035                result = " [";
3036            else
3037                result += ", ";
3038            result += "T=" + t;
3039        }
3040        if (c != -1)
3041        {
3042            if (StringUtil.isEmpty(result))
3043                result = " [";
3044            else
3045                result += ", ";
3046            result += "C=" + c;
3047        }
3048
3049        if (!StringUtil.isEmpty(result))
3050            result += "]";
3051
3052        return result;
3053    }
3054
3055    /**
3056     * Returns a sub part of the ROI.<br/>
3057     * The default implementation returns result in "area" format: ({@link ROI2DArea}, {@link ROI3DArea},
3058     * {@link ROI4DArea} or {@link ROI5DArea}) where only internals pixels are preserved.</br>
3059     * Note that this function can eventually return <code>null</code> when the result ROI is empty.
3060     * 
3061     * @param z
3062     *        the specific Z position (slice) we want to retrieve (<code>-1</code> to retrieve the
3063     *        whole ROI Z dimension)
3064     * @param t
3065     *        the specific T position (frame) we want to retrieve (<code>-1</code> to retrieve the
3066     *        whole ROI T dimension)
3067     * @param c
3068     *        the specific C position (channel) we want to retrieve (<code>-1</code> to retrieve the
3069     *        whole ROI C dimension)
3070     */
3071    public ROI getSubROI(int z, int t, int c)
3072    {
3073        final ROI result;
3074
3075        switch (getDimension())
3076        {
3077            default:
3078                result = new ROI2DArea(getBooleanMask2D(z, t, c, false));
3079                break;
3080
3081            case 3:
3082                if (z == -1)
3083                    result = new ROI3DArea(((ROI3D) this).getBooleanMask3D(z, t, c, false));
3084                else
3085                    result = new ROI2DArea(((ROI3D) this).getBooleanMask2D(z, t, c, false));
3086                break;
3087
3088            case 4:
3089                if (z == -1)
3090                {
3091                    if (t == -1)
3092                        result = new ROI4DArea(((ROI4D) this).getBooleanMask4D(z, t, c, false));
3093                    else
3094                        result = new ROI3DArea(((ROI4D) this).getBooleanMask3D(z, t, c, false));
3095                }
3096                else
3097                {
3098                    if (t == -1)
3099                        result = new ROI4DArea(((ROI4D) this).getBooleanMask4D(z, t, c, false));
3100                    else
3101                        result = new ROI2DArea(((ROI4D) this).getBooleanMask2D(z, t, c, false));
3102                }
3103                break;
3104
3105            case 5:
3106                if (z == -1)
3107                {
3108                    if (t == -1)
3109                    {
3110                        if (c == -1)
3111                            result = new ROI5DArea(((ROI5D) this).getBooleanMask5D(z, t, c, false));
3112                        else
3113                            result = new ROI4DArea(((ROI5D) this).getBooleanMask4D(z, t, c, false));
3114                    }
3115                    else
3116                    {
3117                        if (c == -1)
3118                            result = new ROI5DArea(((ROI5D) this).getBooleanMask5D(z, t, c, false));
3119                        else
3120                            result = new ROI3DArea(((ROI5D) this).getBooleanMask3D(z, t, c, false));
3121                    }
3122                }
3123                else
3124                {
3125                    if (t == -1)
3126                    {
3127                        if (c == -1)
3128                            result = new ROI5DArea(((ROI5D) this).getBooleanMask5D(z, t, c, false));
3129                        else
3130                            result = new ROI4DArea(((ROI5D) this).getBooleanMask4D(z, t, c, false));
3131                    }
3132                    else
3133                    {
3134                        if (c == -1)
3135                            result = new ROI5DArea(((ROI5D) this).getBooleanMask5D(z, t, c, false));
3136                        else
3137                            result = new ROI2DArea(((ROI5D) this).getBooleanMask2D(z, t, c, false));
3138                    }
3139                }
3140                break;
3141        }
3142
3143        result.beginUpdate();
3144        try
3145        {
3146            if (result.canSetPosition())
3147            {
3148                final Point5D pos = result.getPosition5D();
3149
3150                // set Z, T, C position
3151                if (z != -1)
3152                    pos.setZ(z);
3153                if (t != -1)
3154                    pos.setT(t);
3155                if (c != -1)
3156                    pos.setC(c);
3157
3158                result.setPosition5D(pos);
3159            }
3160
3161            // copy other properties
3162            result.setColor(getColor());
3163            result.setName(getName() + getNameSuffix(z, t, c));
3164            result.setOpacity(getOpacity());
3165            result.setStroke(getStroke());
3166            result.setShowName(getShowName());
3167        }
3168        finally
3169        {
3170            result.endUpdate();
3171        }
3172
3173        return result;
3174    }
3175
3176    /**
3177     * Copy all properties from the given ROI.<br>
3178     * All compatible properties from the source ROI are copied into current ROI except the internal
3179     * id.<br>
3180     * Return <code>false</code> if the operation failed
3181     */
3182    public boolean copyFrom(ROI roi)
3183    {
3184        // use XML persistence for cloning
3185        final Node node = XMLUtil.createDocument(true).getDocumentElement();
3186
3187        // save operation can fails sometime...
3188        if (roi.saveToXML(node))
3189            if (loadFromXML(node, true))
3190                return true;
3191
3192        return false;
3193        // if (tries == 0)
3194        // throw new RuntimeException("Cannot copy roi from " + roi.getName() + ": XML load
3195        // operation failed !");
3196    }
3197
3198    public boolean loadFromXML(Node node, boolean preserveId)
3199    {
3200        if (node == null)
3201            return false;
3202
3203        beginUpdate();
3204        try
3205        {
3206            // FIXME : this can make duplicate id but it is also important to preserve id
3207            if (!preserveId)
3208            {
3209                id = XMLUtil.getElementIntValue(node, ID_ID, 0);
3210                synchronized (ROI.class)
3211                {
3212                    // avoid having same id
3213                    if (id_generator <= id)
3214                        id_generator = id + 1;
3215                }
3216            }
3217            setName(XMLUtil.getElementValue(node, ID_NAME, ""));
3218            setSelected(XMLUtil.getElementBooleanValue(node, ID_SELECTED, false));
3219            setReadOnly(XMLUtil.getElementBooleanValue(node, ID_READONLY, false));
3220
3221            properties.clear();
3222
3223            final Node propertiesNode = XMLUtil.getElement(node, ID_PROPERTIES);
3224            if (propertiesNode != null)
3225            {
3226                synchronized (properties)
3227                {
3228                    for (Element element : XMLUtil.getElements(propertiesNode))
3229                        properties.put(element.getNodeName(), XMLUtil.getValue(element, ""));
3230                }
3231            }
3232
3233            painter.loadFromXML(node);
3234        }
3235        finally
3236        {
3237            endUpdate();
3238        }
3239
3240        return true;
3241    }
3242
3243    @Override
3244    public boolean loadFromXML(Node node)
3245    {
3246        return loadFromXML(node, false);
3247    }
3248
3249    @Override
3250    public boolean saveToXML(Node node)
3251    {
3252        if (node == null)
3253            return false;
3254
3255        XMLUtil.setElementValue(node, ID_CLASSNAME, getClassName());
3256        XMLUtil.setElementIntValue(node, ID_ID, id);
3257        XMLUtil.setElementValue(node, ID_NAME, getName());
3258        XMLUtil.setElementBooleanValue(node, ID_SELECTED, isSelected());
3259        XMLUtil.setElementBooleanValue(node, ID_READONLY, isReadOnly());
3260
3261        final Element propertiesNode = XMLUtil.setElement(node, ID_PROPERTIES);
3262        final Set<Entry<String, String>> entries = properties.entrySet();
3263
3264        synchronized (properties)
3265        {
3266            for (Entry<String, String> entry : entries)
3267                XMLUtil.setElementValue(propertiesNode, entry.getKey(), entry.getValue());
3268        }
3269
3270        painter.saveToXML(node);
3271
3272        return true;
3273    }
3274
3275    /**
3276     * @deprecated Use {@link #roiChanged(boolean)} instead
3277     */
3278    @Deprecated
3279    public void roiChanged(ROIPointEventType pointEventType, Object point)
3280    {
3281        // handle with updater
3282        updater.changed(new ROIEvent(this, ROIEventType.ROI_CHANGED, pointEventType, point));
3283    }
3284
3285    /**
3286     * Called when ROI has changed its content and/or position.<br>
3287     * 
3288     * @param contentChanged
3289     *        mean that ROI content has changed otherwise we consider only a position change
3290     */
3291    public void roiChanged(boolean contentChanged)
3292    {
3293        // handle with updater
3294        if (contentChanged)
3295            updater.changed(new ROIEvent(this, ROIEventType.ROI_CHANGED, ROI_CHANGED_ALL));
3296        else
3297            updater.changed(new ROIEvent(this, ROIEventType.ROI_CHANGED, ROI_CHANGED_POSITION));
3298    }
3299
3300    /**
3301     * @deprecated Use {@link #roiChanged(boolean)} instead.
3302     */
3303    @Deprecated
3304    public void roiChanged()
3305    {
3306        // handle with updater
3307        roiChanged(true);
3308    }
3309
3310    /**
3311     * Called when ROI selected state changed.
3312     */
3313    public void selectionChanged()
3314    {
3315        // handle with updater
3316        updater.changed(new ROIEvent(this, ROIEventType.SELECTION_CHANGED));
3317    }
3318
3319    /**
3320     * Called when ROI focus state changed.
3321     */
3322    public void focusChanged()
3323    {
3324        // handle with updater
3325        updater.changed(new ROIEvent(this, ROIEventType.FOCUS_CHANGED));
3326    }
3327
3328    /**
3329     * Called when ROI painter changed.
3330     * 
3331     * @deprecated
3332     */
3333    @Deprecated
3334    public void painterChanged()
3335    {
3336        // handle with updater
3337        updater.changed(new ROIEvent(this, ROIEventType.PAINTER_CHANGED));
3338    }
3339
3340    /**
3341     * Called when ROI name has changed.
3342     * 
3343     * @deprecated Use {@link #propertyChanged(String)} instead.
3344     */
3345    @Deprecated
3346    public void nameChanged()
3347    {
3348        // handle with updater
3349        updater.changed(new ROIEvent(this, ROIEventType.NAME_CHANGED));
3350    }
3351
3352    /**
3353     * Called when ROI property has changed
3354     */
3355    public void propertyChanged(String propertyName)
3356    {
3357        // handle with updater
3358        updater.changed(new ROIEvent(this, propertyName));
3359
3360        // backward compatibility
3361        if (StringUtil.equals(propertyName, PROPERTY_NAME))
3362            updater.changed(new ROIEvent(this, ROIEventType.NAME_CHANGED));
3363    }
3364
3365    /**
3366     * Add a listener
3367     * 
3368     * @param listener
3369     */
3370    public void addListener(ROIListener listener)
3371    {
3372        if (listener != null)
3373            listeners.add(listener);
3374    }
3375
3376    /**
3377     * Remove a listener
3378     * 
3379     * @param listener
3380     */
3381    public void removeListener(ROIListener listener)
3382    {
3383        if (listener != null)
3384            listeners.remove(listener);
3385    }
3386
3387    private void fireChangedEvent(ROIEvent event)
3388    {
3389        for (ROIListener listener : new ArrayList<ROIListener>(listeners))
3390            listener.roiChanged(event);
3391    }
3392
3393    public void beginUpdate()
3394    {
3395        updater.beginUpdate();
3396        painter.beginUpdate();
3397    }
3398
3399    public void endUpdate()
3400    {
3401        painter.endUpdate();
3402        updater.endUpdate();
3403    }
3404
3405    public boolean isUpdating()
3406    {
3407        return updater.isUpdating();
3408    }
3409
3410    @Override
3411    public void onChanged(CollapsibleEvent object)
3412    {
3413        final ROIEvent event = (ROIEvent) object;
3414
3415        // do here global process on ROI change
3416        switch (event.getType())
3417        {
3418            case ROI_CHANGED:
3419                // cached properties need to be recomputed
3420                boundsInvalid = true;
3421                // need to recompute points
3422                if (StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL))
3423                {
3424                    numberOfContourPointsInvalid = true;
3425                    numberOfPointsInvalid = true;
3426                }
3427                painter.painterChanged();
3428                break;
3429
3430            case SELECTION_CHANGED:
3431            case FOCUS_CHANGED:
3432                // compute painter priority
3433                painter.computePriority();
3434                painter.painterChanged();
3435                break;
3436
3437            case PROPERTY_CHANGED:
3438                final String property = event.getPropertyName();
3439
3440                // painter affecting display
3441                if (StringUtil.isEmpty(property) || StringUtil.equals(property, PROPERTY_NAME)
3442                        || StringUtil.equals(property, PROPERTY_SHOWNAME) || StringUtil.equals(property, PROPERTY_COLOR)
3443                        || StringUtil.equals(property, PROPERTY_OPACITY)
3444                        || StringUtil.equals(property, PROPERTY_SHOWNAME)
3445                        || StringUtil.equals(property, PROPERTY_STROKE))
3446                    painter.painterChanged();
3447                break;
3448
3449            default:
3450                break;
3451        }
3452
3453        // notify listener we have changed
3454        fireChangedEvent(event);
3455    }
3456}