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>  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>  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>  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>  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}