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 plugins.kernel.roi.roi3d; 020 021import java.awt.Color; 022import java.awt.Graphics2D; 023import java.awt.Rectangle; 024import java.awt.event.KeyEvent; 025import java.awt.event.MouseEvent; 026import java.awt.event.MouseWheelEvent; 027import java.awt.geom.Rectangle2D; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.Set; 034import java.util.TreeMap; 035import java.util.concurrent.Semaphore; 036 037import org.w3c.dom.Element; 038import org.w3c.dom.Node; 039 040import icy.canvas.IcyCanvas; 041import icy.painter.OverlayEvent; 042import icy.painter.OverlayListener; 043import icy.roi.BooleanMask2D; 044import icy.roi.ROI; 045import icy.roi.ROI2D; 046import icy.roi.ROI2D.ROI2DPainter; 047import icy.roi.ROI3D; 048import icy.roi.ROIEvent; 049import icy.roi.ROIListener; 050import icy.sequence.Sequence; 051import icy.system.IcyExceptionHandler; 052import icy.type.point.Point5D; 053import icy.type.rectangle.Rectangle3D; 054import icy.util.XMLUtil; 055 056/** 057 * Base class defining a generic 3D ROI as a stack of individual 2D ROI slices. 058 * 059 * @author Alexandre Dufour 060 * @author Stephane Dallongeville 061 * @param <R> 062 * the type of 2D ROI for each slice of this 3D ROI 063 */ 064public class ROI3DStack<R extends ROI2D> extends ROI3D implements ROIListener, OverlayListener, Iterable<R> 065{ 066 /** 067 * @deprecated this property does not exist anymore 068 */ 069 @Deprecated 070 public static final String PROPERTY_USECHILDCOLOR = "useChildColor"; 071 072 protected final TreeMap<Integer, R> slices = new TreeMap<Integer, R>(); 073 074 protected final Class<? extends R> roiClass; 075 protected Semaphore modifyingSlice; 076 protected double translateZ; 077 078 /** 079 * Creates a new 3D ROI based on the given 2D ROI type. 080 */ 081 public ROI3DStack(Class<? extends R> roiClass) 082 { 083 super(); 084 085 this.roiClass = roiClass; 086 modifyingSlice = new Semaphore(1); 087 translateZ = 0d; 088 } 089 090 @Override 091 public String getDefaultName() 092 { 093 return "ROI2D stack"; 094 } 095 096 @Override 097 protected ROIPainter createPainter() 098 { 099 return new ROI3DStackPainter(); 100 } 101 102 /** 103 * Create a new empty 2D ROI slice. 104 */ 105 protected R createSlice() 106 { 107 try 108 { 109 return roiClass.newInstance(); 110 } 111 catch (Exception e) 112 { 113 IcyExceptionHandler.showErrorMessage(e, true, true); 114 return null; 115 } 116 } 117 118 /** 119 * Returns <code>true</code> if the ROI directly uses the 2D slice color draw property and <code>false</code> if it 120 * uses the global 3D ROI color draw property. 121 */ 122 @SuppressWarnings("unchecked") 123 public boolean getUseChildColor() 124 { 125 return ((ROI3DStackPainter) getOverlay()).getUseChildColor(); 126 } 127 128 /** 129 * Set to <code>true</code> if you want to directly use the 2D slice color draw property and <code>false</code> to 130 * keep the global 3D ROI color draw property. 131 * 132 * @see #setColor(int, Color) 133 */ 134 @SuppressWarnings("unchecked") 135 public void setUseChildColor(boolean value) 136 { 137 ((ROI3DStackPainter) getOverlay()).setUseChildColor(value); 138 } 139 140 /** 141 * Set the painter color for the specified ROI slice. 142 * 143 * @see #setUseChildColor(boolean) 144 */ 145 @SuppressWarnings("unchecked") 146 public void setColor(int z, Color value) 147 { 148 ((ROI3DStackPainter) getOverlay()).setColor(z, value); 149 } 150 151 @Override 152 public void setCreating(boolean value) 153 { 154 beginUpdate(); 155 try 156 { 157 super.setCreating(value); 158 159 modifyingSlice.acquireUninterruptibly(); 160 try 161 { 162 synchronized (slices) 163 { 164 for (R slice : slices.values()) 165 slice.setCreating(value); 166 } 167 } 168 finally 169 { 170 modifyingSlice.release(); 171 } 172 } 173 finally 174 { 175 endUpdate(); 176 } 177 } 178 179 @Override 180 public void setReadOnly(boolean value) 181 { 182 beginUpdate(); 183 try 184 { 185 super.setReadOnly(value); 186 187 modifyingSlice.acquireUninterruptibly(); 188 try 189 { 190 synchronized (slices) 191 { 192 for (R slice : slices.values()) 193 slice.setReadOnly(value); 194 } 195 } 196 finally 197 { 198 modifyingSlice.release(); 199 } 200 } 201 finally 202 { 203 endUpdate(); 204 } 205 } 206 207 @Override 208 public void setFocused(boolean value) 209 { 210 beginUpdate(); 211 try 212 { 213 super.setFocused(value); 214 215 modifyingSlice.acquireUninterruptibly(); 216 try 217 { 218 synchronized (slices) 219 { 220 for (R slice : slices.values()) 221 slice.setFocused(value); 222 } 223 } 224 finally 225 { 226 modifyingSlice.release(); 227 } 228 } 229 finally 230 { 231 endUpdate(); 232 } 233 } 234 235 @Override 236 public void setSelected(boolean value) 237 { 238 beginUpdate(); 239 try 240 { 241 super.setSelected(value); 242 243 modifyingSlice.acquireUninterruptibly(); 244 try 245 { 246 synchronized (slices) 247 { 248 for (R slice : slices.values()) 249 slice.setSelected(value); 250 } 251 } 252 finally 253 { 254 modifyingSlice.release(); 255 } 256 } 257 finally 258 { 259 endUpdate(); 260 } 261 } 262 263 @Override 264 public void setName(String value) 265 { 266 beginUpdate(); 267 try 268 { 269 super.setName(value); 270 271 modifyingSlice.acquireUninterruptibly(); 272 try 273 { 274 synchronized (slices) 275 { 276 for (R slice : slices.values()) 277 slice.setName(value); 278 } 279 } 280 finally 281 { 282 modifyingSlice.release(); 283 } 284 } 285 finally 286 { 287 endUpdate(); 288 } 289 } 290 291 @Override 292 public void setT(int value) 293 { 294 beginUpdate(); 295 try 296 { 297 super.setT(value); 298 299 modifyingSlice.acquireUninterruptibly(); 300 try 301 { 302 synchronized (slices) 303 { 304 for (R slice : slices.values()) 305 slice.setT(value); 306 } 307 } 308 finally 309 { 310 modifyingSlice.release(); 311 } 312 } 313 finally 314 { 315 endUpdate(); 316 } 317 } 318 319 @Override 320 public void setC(int value) 321 { 322 beginUpdate(); 323 try 324 { 325 super.setC(value); 326 327 modifyingSlice.acquireUninterruptibly(); 328 try 329 { 330 synchronized (slices) 331 { 332 for (R slice : slices.values()) 333 slice.setC(value); 334 } 335 } 336 finally 337 { 338 modifyingSlice.release(); 339 } 340 } 341 finally 342 { 343 endUpdate(); 344 } 345 } 346 347 /** 348 * Returns <code>true</code> if the ROI stack is empty. 349 */ 350 @Override 351 public boolean isEmpty() 352 { 353 return slices.isEmpty(); 354 } 355 356 /** 357 * @return The size of this ROI stack along Z.<br> 358 * Note that the returned value indicates the difference between upper and lower bounds 359 * of this ROI, but doesn't guarantee that all slices in-between exist ( {@link #getSlice(int)} may still 360 * return <code>null</code>.<br> 361 */ 362 public int getSizeZ() 363 { 364 synchronized (slices) 365 { 366 if (slices.isEmpty()) 367 return 0; 368 369 return (slices.lastKey().intValue() - slices.firstKey().intValue()) + 1; 370 } 371 } 372 373 /** 374 * Returns the ROI slice at given Z position. 375 */ 376 public R getSlice(int z) 377 { 378 return slices.get(Integer.valueOf(z)); 379 } 380 381 /** 382 * Returns the ROI slice at given Z position. 383 */ 384 public R getSlice(int z, boolean createIfNull) 385 { 386 R result = getSlice(z); 387 388 if ((result == null) && createIfNull) 389 { 390 result = createSlice(); 391 if (result != null) 392 setSlice(z, result); 393 } 394 395 return result; 396 } 397 398 /** 399 * Sets the ROI slice for the given Z position. 400 */ 401 public void setSlice(int z, R roi2d) 402 { 403 // nothing to do 404 if (getSlice(z) == roi2d) 405 return; 406 407 // remove previous 408 removeSlice(z); 409 410 if (roi2d != null) 411 { 412 roi2d.beginUpdate(); 413 try 414 { 415 // set Z, T and C position 416 roi2d.setZ(z); 417 roi2d.setT(getT()); 418 roi2d.setC(getC()); 419 } 420 finally 421 { 422 roi2d.endUpdate(); 423 } 424 425 // listen events from this ROI and its overlay 426 roi2d.addListener(this); 427 roi2d.getOverlay().addOverlayListener(this); 428 429 synchronized (slices) 430 { 431 // set new slice 432 slices.put(Integer.valueOf(z), roi2d); 433 } 434 } 435 436 // notify ROI changed 437 roiChanged(true); 438 } 439 440 /** 441 * Removes slice at the given Z position and returns it. 442 */ 443 public R removeSlice(int z) 444 { 445 final R result; 446 447 synchronized (slices) 448 { 449 // remove the current slice (if any) 450 result = slices.remove(Integer.valueOf(z)); 451 } 452 453 // remove listeners 454 if (result != null) 455 { 456 result.removeListener(this); 457 result.getOverlay().removeOverlayListener(this); 458 459 // notify ROI changed 460 roiChanged(true); 461 } 462 463 return result; 464 } 465 466 /** 467 * Removes all slices. 468 */ 469 public void clear() 470 { 471 // nothing to do 472 if (isEmpty()) 473 return; 474 475 synchronized (slices) 476 { 477 for (R slice : slices.values()) 478 { 479 slice.removeListener(this); 480 slice.getOverlay().removeOverlayListener(this); 481 } 482 483 slices.clear(); 484 } 485 486 roiChanged(true); 487 } 488 489 /** 490 * Add the specified {@link ROI3DStack} content to this ROI3DStack 491 */ 492 public void add(ROI3DStack<R> roi) throws UnsupportedOperationException 493 { 494 beginUpdate(); 495 try 496 { 497 synchronized (slices) 498 { 499 for (Entry<Integer, R> entry : roi.slices.entrySet()) 500 add(entry.getKey().intValue(), entry.getValue()); 501 } 502 } 503 finally 504 { 505 endUpdate(); 506 } 507 } 508 509 /** 510 * Exclusively add the specified {@link ROI3DStack} content to this ROI3DStack 511 */ 512 public void exclusiveAdd(ROI3DStack<R> roi) throws UnsupportedOperationException 513 { 514 beginUpdate(); 515 try 516 { 517 synchronized (slices) 518 { 519 for (Entry<Integer, R> entry : roi.slices.entrySet()) 520 exclusiveAdd(entry.getKey().intValue(), entry.getValue()); 521 } 522 } 523 finally 524 { 525 endUpdate(); 526 } 527 } 528 529 /** 530 * Process intersection of the specified {@link ROI3DStack} with this ROI3DStack. 531 */ 532 public void intersect(ROI3DStack<R> roi) throws UnsupportedOperationException 533 { 534 beginUpdate(); 535 try 536 { 537 synchronized (slices) 538 { 539 final Set<Integer> keys = roi.slices.keySet(); 540 final Set<Integer> toRemove = new HashSet<Integer>(); 541 542 // remove slices which are not contained 543 for (Integer key : slices.keySet()) 544 if (!keys.contains(key)) 545 toRemove.add(key); 546 547 // do remove first 548 for (Integer key : toRemove) 549 removeSlice(key.intValue()); 550 551 // then process intersection 552 for (Entry<Integer, R> entry : roi.slices.entrySet()) 553 intersect(entry.getKey().intValue(), entry.getValue()); 554 } 555 } 556 finally 557 { 558 endUpdate(); 559 } 560 } 561 562 /** 563 * Remove the specified {@link ROI3DStack} from this ROI3DStack 564 */ 565 public void subtract(ROI3DStack<R> roi) throws UnsupportedOperationException 566 { 567 beginUpdate(); 568 try 569 { 570 synchronized (slices) 571 { 572 for (Entry<Integer, R> entry : roi.slices.entrySet()) 573 subtract(entry.getKey().intValue(), entry.getValue()); 574 } 575 } 576 finally 577 { 578 endUpdate(); 579 } 580 } 581 582 @Override 583 public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException 584 { 585 if (roi instanceof ROI3D) 586 { 587 final ROI3D roi3d = (ROI3D) roi; 588 589 // only if on same position 590 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 591 { 592 if (this.getClass().isInstance(roi3d)) 593 { 594 add((ROI3DStack) roi3d); 595 return this; 596 } 597 } 598 } 599 else if (roiClass.isInstance(roi)) 600 { 601 final ROI2D roi2d = (ROI2D) roi; 602 603 // only if on same position 604 if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 605 { 606 try 607 { 608 add(roi2d.getZ(), (R) roi2d); 609 return this; 610 } 611 catch (UnsupportedOperationException e) 612 { 613 // not supported, try generic method instead 614 return super.add(roi, allowCreate); 615 } 616 } 617 } 618 619 return super.add(roi, allowCreate); 620 } 621 622 @Override 623 public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException 624 { 625 if (roi instanceof ROI3D) 626 { 627 final ROI3D roi3d = (ROI3D) roi; 628 629 // only if on same position 630 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 631 { 632 if (this.getClass().isInstance(roi3d)) 633 { 634 intersect((ROI3DStack) roi3d); 635 return this; 636 } 637 } 638 else if (roiClass.isInstance(roi)) 639 { 640 final ROI2D roi2d = (ROI2D) roi; 641 642 // only if on same position 643 if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 644 { 645 try 646 { 647 intersect(roi2d.getZ(), (R) roi2d); 648 return this; 649 } 650 catch (UnsupportedOperationException e) 651 { 652 // not supported, try generic method instead 653 return super.intersect(roi, allowCreate); 654 } 655 } 656 } 657 } 658 659 return super.intersect(roi, allowCreate); 660 } 661 662 @Override 663 public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException 664 { 665 if (roi instanceof ROI3D) 666 { 667 final ROI3D roi3d = (ROI3D) roi; 668 669 // only if on same position 670 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 671 { 672 if (this.getClass().isInstance(roi3d)) 673 { 674 exclusiveAdd((ROI3DStack) roi3d); 675 return this; 676 } 677 } 678 else if (roiClass.isInstance(roi)) 679 { 680 final ROI2D roi2d = (ROI2D) roi; 681 682 // only if on same position 683 if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 684 { 685 try 686 { 687 exclusiveAdd(roi2d.getZ(), (R) roi2d); 688 return this; 689 } 690 catch (UnsupportedOperationException e) 691 { 692 // not supported, try generic method instead 693 return super.add(roi, allowCreate); 694 } 695 } 696 } 697 } 698 699 return super.add(roi, allowCreate); 700 } 701 702 @Override 703 public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException 704 { 705 if (roi instanceof ROI3D) 706 { 707 final ROI3D roi3d = (ROI3D) roi; 708 709 // only if on same position 710 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 711 { 712 if (this.getClass().isInstance(roi3d)) 713 { 714 subtract((ROI3DStack<R>) roi3d); 715 return this; 716 } 717 } 718 else if (roiClass.isInstance(roi)) 719 { 720 final ROI2D roi2d = (ROI2D) roi; 721 722 // only if on same position 723 if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) 724 { 725 try 726 { 727 subtract(roi2d.getZ(), (R) roi2d); 728 return this; 729 } 730 catch (UnsupportedOperationException e) 731 { 732 // not supported, try generic method instead 733 return super.subtract(roi, allowCreate); 734 } 735 } 736 } 737 } 738 739 return super.subtract(roi, allowCreate); 740 } 741 742 /** 743 * Adds content of specified <code>ROI</code> slice into the <code>ROI</code> slice at given Z position. 744 * The resulting content of this <code>ROI</code> will include the union of both ROI's contents.<br> 745 * If no slice was present at the specified Z position then the method is equivalent to 746 * {@link #setSlice(int, ROI2D)} 747 * 748 * @param z 749 * the position where the slice must be merged 750 * @param roiSlice 751 * the 2D ROI to merge 752 * @throws UnsupportedOperationException 753 * if the given ROI slice cannot be added to this ROI 754 */ 755 public void add(int z, R roiSlice) 756 { 757 if (roiSlice == null) 758 return; 759 760 final R currentSlice = getSlice(z); 761 final ROI newSlice; 762 763 // merge both slice 764 if (currentSlice != null) 765 { 766 // we need to modify the Z, T and C position so we do the merge correctly 767 roiSlice.setZ(z); 768 roiSlice.setT(getT()); 769 roiSlice.setC(getC()); 770 // do ROI union 771 newSlice = currentSlice.add(roiSlice, true); 772 773 // check the resulting ROI is the same type 774 if (!newSlice.getClass().isInstance(currentSlice)) 775 throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z 776 + ": " + newSlice.getClassName()); 777 } 778 else 779 // get a copy 780 newSlice = roiSlice.getCopy(); 781 782 // set slice 783 setSlice(z, (R) newSlice); 784 } 785 786 /** 787 * Sets the content of the <code>ROI</code> slice at given Z position to be the union of its current content and the 788 * content of the specified <code>ROI</code>, minus their intersection. 789 * The resulting <code>ROI</code> will include only content that were contained in either this <code>ROI</code> or 790 * in the specified <code>ROI</code>, but not in both.<br> 791 * If no slice was present at the specified Z position then the method is equivalent to 792 * {@link #setSlice(int, ROI2D)} 793 * 794 * @param z 795 * the position where the slice must be merged 796 * @param roiSlice 797 * the 2D ROI to merge 798 * @throws UnsupportedOperationException 799 * if the given ROI slice cannot be exclusively added to this ROI 800 */ 801 public void exclusiveAdd(int z, R roiSlice) 802 { 803 if (roiSlice == null) 804 return; 805 806 final R currentSlice = getSlice(z); 807 final ROI newSlice; 808 809 // merge both slice 810 if (currentSlice != null) 811 { 812 // we need to modify the Z, T and C position so we do the merge correctly 813 roiSlice.setZ(z); 814 roiSlice.setT(getT()); 815 roiSlice.setC(getC()); 816 // do ROI exclusive union 817 newSlice = currentSlice.exclusiveAdd(roiSlice, true); 818 819 // check the resulting ROI is same type 820 if (!newSlice.getClass().isInstance(currentSlice)) 821 throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z 822 + ": " + newSlice.getClassName()); 823 } 824 else 825 // get a copy 826 newSlice = roiSlice.getCopy(); 827 828 if (newSlice.isEmpty()) 829 removeSlice(z); 830 else 831 setSlice(z, (R) newSlice); 832 } 833 834 /** 835 * Sets the content of the <code>ROI</code> slice at given Z position to the intersection of 836 * its current content and the content of the specified <code>ROI</code>. 837 * The resulting ROI will include only contents that were contained in both ROI.<br> 838 * If no slice was present at the specified Z position then the method does nothing. 839 * 840 * @param z 841 * the position where the slice must be merged 842 * @param roiSlice 843 * the 2D ROI to merge 844 * @throws UnsupportedOperationException 845 * if the given ROI slice cannot be intersected with this ROI 846 */ 847 public void intersect(int z, R roiSlice) 848 { 849 // better to throw an exception here than removing slice 850 if (roiSlice == null) 851 throw new IllegalArgumentException("Cannot intersect an empty slice in a 3D ROI"); 852 853 final R currentSlice = getSlice(z); 854 855 // merge both slice 856 if (currentSlice != null) 857 { 858 // we need to modify the Z, T and C position so we do the merge correctly 859 roiSlice.setZ(z); 860 roiSlice.setT(getT()); 861 roiSlice.setC(getC()); 862 // do ROI intersection 863 final ROI newSlice = currentSlice.intersect(roiSlice, true); 864 865 // check the resulting ROI is same type 866 if (!newSlice.getClass().isInstance(currentSlice)) 867 throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z 868 + ": " + newSlice.getClassName()); 869 870 if (newSlice.isEmpty()) 871 removeSlice(z); 872 else 873 setSlice(z, (R) newSlice); 874 } 875 } 876 877 /** 878 * Subtract the specified <code>ROI</code> content from the <code>ROI</code> slice at given Z position.<br> 879 * If no slice was present at the specified Z position then the method does nothing. 880 * 881 * @param z 882 * the position where the subtraction should be done 883 * @param roiSlice 884 * the 2D ROI to subtract from Z slice 885 * @throws UnsupportedOperationException 886 * if the given ROI slice cannot be subtracted from this ROI 887 */ 888 public void subtract(int z, R roiSlice) throws UnsupportedOperationException 889 { 890 if (roiSlice == null) 891 return; 892 893 final R currentSlice = getSlice(z); 894 895 // merge both slice 896 if (currentSlice != null) 897 { 898 // we need to modify the Z, T and C position so we do the merge correctly 899 roiSlice.setZ(z); 900 roiSlice.setT(getT()); 901 roiSlice.setC(getC()); 902 // do ROI subtraction 903 final ROI newSlice = currentSlice.subtract(roiSlice, true); 904 905 // check the resulting ROI is same type 906 if (!newSlice.getClass().isInstance(currentSlice)) 907 throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z 908 + ": " + newSlice.getClassName()); 909 910 if (newSlice.isEmpty()) 911 removeSlice(z); 912 else 913 setSlice(z, (R) newSlice); 914 } 915 } 916 917 /** 918 * Called when a ROI slice has changed. 919 */ 920 protected void sliceChanged(ROIEvent event) 921 { 922 if (modifyingSlice.availablePermits() <= 0) 923 return; 924 925 final ROI source = event.getSource(); 926 927 switch (event.getType()) 928 { 929 case ROI_CHANGED: 930 // position change of a slice can change global bounds --> transform to 'content changed' event type 931 roiChanged(true); 932 // roiChanged(StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL)); 933 break; 934 935 case FOCUS_CHANGED: 936 setFocused(source.isFocused()); 937 break; 938 939 case SELECTION_CHANGED: 940 setSelected(source.isSelected()); 941 break; 942 943 case PROPERTY_CHANGED: 944 final String propertyName = event.getPropertyName(); 945 946 if ((propertyName == null) || propertyName.equals(PROPERTY_READONLY)) 947 setReadOnly(source.isReadOnly()); 948 if ((propertyName == null) || propertyName.equals(PROPERTY_CREATING)) 949 setCreating(source.isCreating()); 950 break; 951 } 952 } 953 954 /** 955 * Called when a ROI slice overlay has changed. 956 */ 957 protected void sliceOverlayChanged(OverlayEvent event) 958 { 959 switch (event.getType()) 960 { 961 case PAINTER_CHANGED: 962 // forward the event to ROI stack overlay 963 getOverlay().painterChanged(); 964 break; 965 966 case PROPERTY_CHANGED: 967 // forward the event to ROI stack overlay 968 getOverlay().propertyChanged(event.getPropertyName()); 969 break; 970 } 971 } 972 973 @Override 974 public Rectangle3D computeBounds3D() 975 { 976 Rectangle2D xyBounds = null; 977 978 synchronized (slices) 979 { 980 for (R slice : slices.values()) 981 { 982 final Rectangle2D bnd2d = slice.getBounds2D(); 983 984 // only add non empty bounds 985 if (!bnd2d.isEmpty()) 986 { 987 if (xyBounds == null) 988 xyBounds = (Rectangle2D) bnd2d.clone(); 989 else 990 xyBounds.add(bnd2d); 991 } 992 } 993 } 994 995 // create empty 2D bounds 996 if (xyBounds == null) 997 xyBounds = new Rectangle2D.Double(); 998 999 final int z; 1000 final int sizeZ; 1001 1002 if (!slices.isEmpty()) 1003 { 1004 z = slices.firstKey().intValue(); 1005 sizeZ = getSizeZ(); 1006 } 1007 else 1008 { 1009 z = 0; 1010 sizeZ = 0; 1011 } 1012 1013 return new Rectangle3D.Double(xyBounds.getX(), xyBounds.getY(), z, xyBounds.getWidth(), xyBounds.getHeight(), 1014 sizeZ); 1015 } 1016 1017 @Override 1018 public boolean contains(double x, double y, double z) 1019 { 1020 final R roi2d = getSlice((int) Math.floor(z)); 1021 1022 if (roi2d != null) 1023 return roi2d.contains(x, y); 1024 1025 return false; 1026 } 1027 1028 @Override 1029 public boolean contains(double x, double y, double z, double sizeX, double sizeY, double sizeZ) 1030 { 1031 final Rectangle3D bounds = getBounds3D(); 1032 1033 // easy discard 1034 if (!bounds.contains(x, y, z, sizeX, sizeY, sizeZ)) 1035 return false; 1036 1037 final int lim = (int) Math.floor(z + sizeZ); 1038 for (int zc = (int) Math.floor(z); zc < lim; zc++) 1039 { 1040 final R roi2d = getSlice(zc); 1041 if ((roi2d == null) || !roi2d.contains(x, y, sizeX, sizeY)) 1042 return false; 1043 } 1044 1045 return true; 1046 } 1047 1048 @Override 1049 public boolean intersects(double x, double y, double z, double sizeX, double sizeY, double sizeZ) 1050 { 1051 final Rectangle3D bounds = getBounds3D(); 1052 1053 // easy discard 1054 if (!bounds.intersects(x, y, z, sizeX, sizeY, sizeZ)) 1055 return false; 1056 1057 final int lim = (int) Math.floor(z + sizeZ); 1058 for (int zc = (int) Math.floor(z); zc < lim; zc++) 1059 { 1060 final R roi2d = getSlice(zc); 1061 if ((roi2d != null) && roi2d.intersects(x, y, sizeX, sizeY)) 1062 return true; 1063 } 1064 1065 return false; 1066 } 1067 1068 @Override 1069 public boolean hasSelectedPoint() 1070 { 1071 // default 1072 return false; 1073 } 1074 1075 @Override 1076 public void unselectAllPoints() 1077 { 1078 beginUpdate(); 1079 try 1080 { 1081 modifyingSlice.acquireUninterruptibly(); 1082 try 1083 { 1084 synchronized (slices) 1085 { 1086 for (R slice : slices.values()) 1087 slice.unselectAllPoints(); 1088 } 1089 } 1090 finally 1091 { 1092 modifyingSlice.release(); 1093 } 1094 } 1095 finally 1096 { 1097 endUpdate(); 1098 } 1099 } 1100 1101 // default approximated implementation for ROI3DStack 1102 @Override 1103 public double computeSurfaceArea(Sequence sequence) throws UnsupportedOperationException 1104 { 1105 // 3D contour points = first slice points + all slices perimeter + last slice points 1106 double result = 0; 1107 1108 synchronized (slices) 1109 { 1110 if (!slices.isEmpty()) 1111 { 1112 final double psx = sequence.getPixelSizeX(); 1113 final double psy = sequence.getPixelSizeY(); 1114 final double psz = sequence.getPixelSizeZ(); 1115 1116 result = slices.firstEntry().getValue().getNumberOfPoints() * psx * psy; 1117 result += slices.lastEntry().getValue().getNumberOfPoints() * psx * psy; 1118 1119 for (R slice : slices.values()) 1120 result += slice.getLength(sequence) * psz; 1121 } 1122 } 1123 1124 return result; 1125 } 1126 1127 // default approximated implementation for ROI3DStack 1128 @Override 1129 public double computeNumberOfContourPoints() 1130 { 1131 // 3D contour points = first slice points + inter slices contour points + last slice points 1132 double result = 0; 1133 1134 synchronized (slices) 1135 { 1136 if (slices.size() <= 2) 1137 { 1138 for (R slice : slices.values()) 1139 result += slice.getNumberOfPoints(); 1140 } 1141 else 1142 { 1143 final Entry<Integer, R> firstEntry = slices.firstEntry(); 1144 final Entry<Integer, R> lastEntry = slices.lastEntry(); 1145 final Integer firstKey = firstEntry.getKey(); 1146 final Integer lastKey = lastEntry.getKey(); 1147 1148 result = firstEntry.getValue().getNumberOfPoints(); 1149 1150 for (R slice : slices.subMap(firstKey, false, lastKey, false).values()) 1151 result += slice.getNumberOfContourPoints(); 1152 1153 result += lastEntry.getValue().getNumberOfPoints(); 1154 } 1155 } 1156 1157 return result; 1158 } 1159 1160 @Override 1161 public double computeNumberOfPoints() 1162 { 1163 double volume = 0; 1164 1165 synchronized (slices) 1166 { 1167 for (R slice : slices.values()) 1168 volume += slice.getNumberOfPoints(); 1169 } 1170 1171 return volume; 1172 } 1173 1174 @Override 1175 public boolean canTranslate() 1176 { 1177 synchronized (slices) 1178 { 1179 // only need to test the first entry 1180 if (!slices.isEmpty()) 1181 return slices.firstEntry().getValue().canTranslate(); 1182 } 1183 1184 return false; 1185 } 1186 1187 /** 1188 * Translate the stack of specified Z position. 1189 */ 1190 public void translate(int z) 1191 { 1192 // easy optimizations 1193 if ((z == 0) || isEmpty()) 1194 return; 1195 1196 synchronized (slices) 1197 { 1198 final Map<Integer, R> map = new HashMap<Integer, R>(slices); 1199 1200 slices.clear(); 1201 for (Entry<Integer, R> entry : map.entrySet()) 1202 { 1203 final R roi = entry.getValue(); 1204 final int newZ = roi.getZ() + z; 1205 1206 // // only positive value accepted 1207 // if (newZ >= 0) 1208 // { 1209 roi.setZ(newZ); 1210 slices.put(Integer.valueOf(newZ), roi); 1211 // } 1212 } 1213 } 1214 1215 // notify ROI changed 1216 roiChanged(false); 1217 } 1218 1219 @Override 1220 public void translate(double dx, double dy, double dz) 1221 { 1222 beginUpdate(); 1223 try 1224 { 1225 translateZ += dz; 1226 // convert to integer 1227 final int dzi = (int) translateZ; 1228 // keep trace of not used floating part 1229 translateZ -= dzi; 1230 1231 translate(dzi); 1232 1233 modifyingSlice.acquireUninterruptibly(); 1234 try 1235 { 1236 synchronized (slices) 1237 { 1238 for (R slice : slices.values()) 1239 slice.translate(dx, dy); 1240 } 1241 } 1242 finally 1243 { 1244 modifyingSlice.release(); 1245 } 1246 1247 // notify ROI changed because we modified slice 'internally' 1248 if ((dx != 0d) || (dy != 0d)) 1249 roiChanged(false); 1250 } 1251 finally 1252 { 1253 endUpdate(); 1254 } 1255 } 1256 1257 @Override 1258 public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive) 1259 { 1260 final R roi2d = getSlice(z); 1261 1262 if (roi2d != null) 1263 return roi2d.getBooleanMask(x, y, width, height, inclusive); 1264 1265 return new boolean[width * height]; 1266 } 1267 1268 @Override 1269 public BooleanMask2D getBooleanMask2D(int z, boolean inclusive) 1270 { 1271 final R roi2d = getSlice(z); 1272 1273 if (roi2d != null) 1274 return roi2d.getBooleanMask(inclusive); 1275 1276 return new BooleanMask2D(new Rectangle(), new boolean[0]); 1277 } 1278 1279 // called when one of the slice ROI changed 1280 @Override 1281 public void roiChanged(ROIEvent event) 1282 { 1283 // propagate children change event 1284 sliceChanged(event); 1285 } 1286 1287 // called when one of the slice ROI overlay changed 1288 @Override 1289 public void overlayChanged(OverlayEvent event) 1290 { 1291 // propagate children overlay change event 1292 sliceOverlayChanged(event); 1293 } 1294 1295 @Override 1296 public Iterator<R> iterator() 1297 { 1298 return slices.values().iterator(); 1299 } 1300 1301 @Override 1302 public boolean loadFromXML(Node node) 1303 { 1304 beginUpdate(); 1305 try 1306 { 1307 if (!super.loadFromXML(node)) 1308 return false; 1309 1310 // we don't need to save the 2D ROI class as the parent class already do it 1311 clear(); 1312 1313 for (Element e : XMLUtil.getElements(node, "slice")) 1314 { 1315 // faster than using complete XML serialization 1316 final R slice = createSlice(); 1317 1318 // error while reloading the ROI from XML 1319 if ((slice == null) || !slice.loadFromXML(e)) 1320 return false; 1321 1322 setSlice(slice.getZ(), slice); 1323 } 1324 } 1325 finally 1326 { 1327 endUpdate(); 1328 } 1329 1330 return true; 1331 } 1332 1333 @Override 1334 public boolean saveToXML(Node node) 1335 { 1336 if (!super.saveToXML(node)) 1337 return false; 1338 1339 synchronized (slices) 1340 { 1341 for (R slice : slices.values()) 1342 { 1343 Element sliceNode = XMLUtil.addElement(node, "slice"); 1344 1345 if (!slice.saveToXML(sliceNode)) 1346 return false; 1347 } 1348 } 1349 1350 return true; 1351 } 1352 1353 public class ROI3DStackPainter extends ROI3DPainter 1354 { 1355 protected ROIPainter getSliceOverlayForCanvas(IcyCanvas canvas) 1356 { 1357 final int z = canvas.getPositionZ(); 1358 1359 // canvas position of -1 mean 3D canvas (all Z visible) 1360 if (z >= 0) 1361 return getSliceOverlay(z); 1362 1363 return null; 1364 } 1365 1366 /** 1367 * Returns the ROI overlay at given Z position. 1368 */ 1369 protected ROIPainter getSliceOverlay(int z) 1370 { 1371 R roi = getSlice(z); 1372 1373 if (roi != null) 1374 return roi.getOverlay(); 1375 1376 return null; 1377 } 1378 1379 /** 1380 * @deprecated this property does not exist anymore (always return <code>false</code>) 1381 */ 1382 @Deprecated 1383 public boolean getUseChildColor() 1384 { 1385 return false; 1386 } 1387 1388 /** 1389 * @deprecated this property does not exist anymore 1390 */ 1391 @Deprecated 1392 public void setUseChildColor(boolean value) 1393 { 1394 // 1395 } 1396 1397 /** 1398 * Set the painter color for the specified ROI slice. 1399 * 1400 * @see #setUseChildColor(boolean) 1401 */ 1402 public void setColor(int z, Color value) 1403 { 1404 final ROIPainter sliceOverlay = getSliceOverlay(z); 1405 1406 if (sliceOverlay != null) 1407 { 1408 modifyingSlice.acquireUninterruptibly(); 1409 try 1410 { 1411 sliceOverlay.setColor(value); 1412 } 1413 finally 1414 { 1415 modifyingSlice.release(); 1416 } 1417 } 1418 } 1419 1420 @Override 1421 public void setColor(Color value) 1422 { 1423 beginUpdate(); 1424 try 1425 { 1426 super.setColor(value); 1427 1428 if (!getUseChildColor()) 1429 { 1430 modifyingSlice.acquireUninterruptibly(); 1431 try 1432 { 1433 synchronized (slices) 1434 { 1435 for (R slice : slices.values()) 1436 slice.getOverlay().setColor(value); 1437 } 1438 } 1439 finally 1440 { 1441 modifyingSlice.release(); 1442 } 1443 } 1444 } 1445 finally 1446 { 1447 endUpdate(); 1448 } 1449 } 1450 1451 @Override 1452 public void setOpacity(float value) 1453 { 1454 beginUpdate(); 1455 try 1456 { 1457 super.setOpacity(value); 1458 1459 modifyingSlice.acquireUninterruptibly(); 1460 try 1461 { 1462 synchronized (slices) 1463 { 1464 for (R slice : slices.values()) 1465 slice.getOverlay().setOpacity(value); 1466 } 1467 } 1468 finally 1469 { 1470 modifyingSlice.release(); 1471 } 1472 } 1473 finally 1474 { 1475 endUpdate(); 1476 } 1477 } 1478 1479 @Override 1480 public void setStroke(double value) 1481 { 1482 beginUpdate(); 1483 try 1484 { 1485 super.setStroke(value); 1486 1487 modifyingSlice.acquireUninterruptibly(); 1488 try 1489 { 1490 synchronized (slices) 1491 { 1492 for (R slice : slices.values()) 1493 slice.getOverlay().setStroke(value); 1494 } 1495 } 1496 finally 1497 { 1498 modifyingSlice.release(); 1499 } 1500 } 1501 finally 1502 { 1503 endUpdate(); 1504 } 1505 } 1506 1507 @Override 1508 public void setShowName(boolean value) 1509 { 1510 beginUpdate(); 1511 try 1512 { 1513 super.setShowName(value); 1514 1515 modifyingSlice.acquireUninterruptibly(); 1516 try 1517 { 1518 synchronized (slices) 1519 { 1520 for (R slice : slices.values()) 1521 slice.getOverlay().setShowName(value); 1522 } 1523 } 1524 finally 1525 { 1526 modifyingSlice.release(); 1527 } 1528 } 1529 finally 1530 { 1531 endUpdate(); 1532 } 1533 } 1534 1535 @Override 1536 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 1537 { 1538 // 2D canvas --> use slice implementation 1539 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1540 { 1541 // forward event to current slice 1542 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1543 1544 if (sliceOverlay != null) 1545 sliceOverlay.paint(g, sequence, canvas); 1546 } 1547 // use default parent implementation 1548 else 1549 super.paint(g, sequence, canvas); 1550 } 1551 1552 @Override 1553 public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1554 { 1555 // 2D canvas --> use slice implementation 1556 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1557 { 1558 // forward event to current slice 1559 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1560 1561 if (sliceOverlay != null) 1562 sliceOverlay.keyPressed(e, imagePoint, canvas); 1563 } 1564 // use default parent implementation 1565 else 1566 super.keyPressed(e, imagePoint, canvas); 1567 } 1568 1569 @Override 1570 public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1571 { 1572 // 2D canvas --> use slice implementation 1573 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1574 { 1575 // forward event to current slice 1576 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1577 1578 if (sliceOverlay != null) 1579 sliceOverlay.keyReleased(e, imagePoint, canvas); 1580 } 1581 // use default parent implementation 1582 else 1583 super.keyReleased(e, imagePoint, canvas); 1584 } 1585 1586 @Override 1587 public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1588 { 1589 // 2D canvas --> use slice implementation 1590 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1591 { 1592 // forward event to current slice 1593 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1594 1595 if (sliceOverlay != null) 1596 sliceOverlay.mouseEntered(e, imagePoint, canvas); 1597 } 1598 // use default parent implementation 1599 else 1600 super.mouseEntered(e, imagePoint, canvas); 1601 } 1602 1603 @Override 1604 public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1605 { 1606 // 2D canvas --> use slice implementation 1607 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1608 { 1609 // forward event to current slice 1610 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1611 1612 if (sliceOverlay != null) 1613 sliceOverlay.mouseExited(e, imagePoint, canvas); 1614 } 1615 // use default parent implementation 1616 else 1617 super.mouseExited(e, imagePoint, canvas); 1618 } 1619 1620 @Override 1621 public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1622 { 1623 // 2D canvas --> use slice implementation 1624 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1625 { 1626 // forward event to current slice 1627 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1628 1629 if (sliceOverlay != null) 1630 sliceOverlay.mouseMove(e, imagePoint, canvas); 1631 } 1632 // use default parent implementation 1633 else 1634 super.mouseMove(e, imagePoint, canvas); 1635 } 1636 1637 @Override 1638 public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1639 { 1640 // 2D canvas --> use slice implementation 1641 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1642 { 1643 // forward event to current slice 1644 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1645 1646 if (sliceOverlay != null) 1647 sliceOverlay.mouseDrag(e, imagePoint, canvas); 1648 } 1649 // use default parent implementation 1650 else 1651 super.mouseDrag(e, imagePoint, canvas); 1652 } 1653 1654 @Override 1655 public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1656 { 1657 // 2D canvas --> use slice implementation 1658 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1659 { 1660 // forward event to current slice 1661 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1662 1663 if (sliceOverlay != null) 1664 sliceOverlay.mousePressed(e, imagePoint, canvas); 1665 } 1666 // use default parent implementation 1667 else 1668 super.mousePressed(e, imagePoint, canvas); 1669 } 1670 1671 @Override 1672 public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1673 { 1674 // 2D canvas --> use slice implementation 1675 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1676 { 1677 // forward event to current slice 1678 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1679 1680 if (sliceOverlay != null) 1681 sliceOverlay.mouseReleased(e, imagePoint, canvas); 1682 } 1683 // use default parent implementation 1684 else 1685 super.mouseReleased(e, imagePoint, canvas); 1686 } 1687 1688 @Override 1689 public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1690 { 1691 // 2D canvas --> use slice implementation 1692 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1693 { 1694 // forward event to current slice 1695 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1696 1697 if (sliceOverlay != null) 1698 sliceOverlay.mouseClick(e, imagePoint, canvas); 1699 } 1700 // use default parent implementation 1701 else 1702 super.mouseClick(e, imagePoint, canvas); 1703 } 1704 1705 @Override 1706 public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1707 { 1708 // 2D canvas --> use slice implementation 1709 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1710 { 1711 // forward event to current slice 1712 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1713 1714 if (sliceOverlay != null) 1715 sliceOverlay.mouseWheelMoved(e, imagePoint, canvas); 1716 } 1717 // use default parent implementation 1718 else 1719 super.mouseWheelMoved(e, imagePoint, canvas); 1720 } 1721 1722 @Override 1723 public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) 1724 { 1725 // 2D canvas --> use slice implementation if possible 1726 if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) 1727 { 1728 // forward event to current slice 1729 final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); 1730 1731 if (sliceOverlay instanceof ROI2DPainter) 1732 ((ROI2DPainter) sliceOverlay).drawROI(g, sequence, canvas); 1733 } 1734 1735 // nothing to do... 1736 } 1737 } 1738}