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 icy.canvas.IcyCanvas; 022import icy.common.CollapsibleEvent; 023import icy.gui.inspector.RoisPanel; 024import icy.main.Icy; 025import icy.painter.VtkPainter; 026import icy.roi.BooleanMask2D; 027import icy.roi.BooleanMask3D; 028import icy.roi.ROI; 029import icy.roi.ROI2D; 030import icy.roi.ROI3D; 031import icy.roi.ROIEvent; 032import icy.sequence.Sequence; 033import icy.system.thread.ThreadUtil; 034import icy.type.point.Point3D; 035import icy.type.point.Point5D; 036import icy.type.rectangle.Rectangle3D; 037import icy.util.StringUtil; 038import icy.vtk.IcyVtkPanel; 039import icy.vtk.VtkUtil; 040 041import java.awt.Color; 042import java.awt.Graphics2D; 043import java.awt.event.InputEvent; 044import java.awt.event.MouseEvent; 045import java.awt.geom.Point2D; 046import java.lang.ref.WeakReference; 047import java.util.Arrays; 048import java.util.HashSet; 049import java.util.Map.Entry; 050import java.util.Set; 051 052import plugins.kernel.canvas.VtkCanvas; 053import plugins.kernel.roi.roi2d.ROI2DArea; 054import vtk.vtkActor; 055import vtk.vtkImageData; 056import vtk.vtkInformation; 057import vtk.vtkPolyData; 058import vtk.vtkPolyDataMapper; 059import vtk.vtkProp; 060 061/** 062 * 3D Area ROI. 063 * 064 * @author Stephane 065 */ 066public class ROI3DArea extends ROI3DStack<ROI2DArea> 067{ 068 public class ROI3DAreaPainter extends ROI3DStackPainter implements Runnable 069 { 070 // VTK 3D objects 071 protected vtkPolyData outline; 072 protected vtkPolyDataMapper outlineMapper; 073 protected vtkActor outlineActor; 074 protected vtkInformation vtkInfo; 075 protected vtkPolyData polyData; 076 protected vtkPolyDataMapper polyMapper; 077 protected vtkActor surfaceActor; 078 // 3D internal 079 protected boolean needRebuild; 080 protected double scaling[]; 081 protected WeakReference<VtkCanvas> canvas3d; 082 083 public ROI3DAreaPainter() 084 { 085 super(); 086 087 outline = null; 088 outlineMapper = null; 089 outlineActor = null; 090 vtkInfo = null; 091 polyData = null; 092 polyMapper = null; 093 surfaceActor = null; 094 095 scaling = new double[3]; 096 Arrays.fill(scaling, 1d); 097 098 needRebuild = true; 099 canvas3d = new WeakReference<VtkCanvas>(null); 100 } 101 102 @Override 103 protected void finalize() throws Throwable 104 { 105 super.finalize(); 106 107 // release allocated VTK resources 108 if (surfaceActor != null) 109 surfaceActor.Delete(); 110 if (polyMapper != null) 111 polyMapper.Delete(); 112 if (polyData != null) 113 { 114 polyData.GetPointData().GetScalars().Delete(); 115 polyData.GetPointData().Delete(); 116 polyData.Delete(); 117 } 118 if (outlineActor != null) 119 { 120 outlineActor.SetPropertyKeys(null); 121 outlineActor.Delete(); 122 } 123 if (vtkInfo != null) 124 { 125 vtkInfo.Remove(VtkCanvas.visibilityKey); 126 vtkInfo.Delete(); 127 } 128 if (outlineMapper != null) 129 outlineMapper.Delete(); 130 if (outline != null) 131 { 132 outline.GetPointData().GetScalars().Delete(); 133 outline.GetPointData().Delete(); 134 outline.Delete(); 135 } 136 }; 137 138 protected void initVtkObjects() 139 { 140 outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d); 141 outlineMapper = new vtkPolyDataMapper(); 142 outlineMapper.SetInputData(outline); 143 outlineActor = new vtkActor(); 144 outlineActor.SetMapper(outlineMapper); 145 // disable picking on the outline 146 outlineActor.SetPickable(0); 147 // and set it to wireframe representation 148 outlineActor.GetProperty().SetRepresentationToWireframe(); 149 // use vtkInformations to store outline visibility state (hacky) 150 vtkInfo = new vtkInformation(); 151 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 152 // VtkCanvas use this to restore correctly outline visibility flag 153 outlineActor.SetPropertyKeys(vtkInfo); 154 155 polyMapper = new vtkPolyDataMapper(); 156 surfaceActor = new vtkActor(); 157 surfaceActor.SetMapper(polyMapper); 158 159 final Color col = getColor(); 160 final double r = col.getRed() / 255d; 161 final double g = col.getGreen() / 255d; 162 final double b = col.getBlue() / 255d; 163 164 // set actors color 165 outlineActor.GetProperty().SetColor(r, g, b); 166 surfaceActor.GetProperty().SetColor(r, g, b); 167 } 168 169 /** 170 * rebuild VTK objects (called only when VTK canvas is selected). 171 */ 172 protected void rebuildVtkObjects() 173 { 174 final VtkCanvas canvas = canvas3d.get(); 175 // canvas was closed 176 if (canvas == null) 177 return; 178 179 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 180 // canvas was closed 181 if (vtkPanel == null) 182 return; 183 184 final Sequence seq = canvas.getSequence(); 185 // nothing to update 186 if (seq == null) 187 return; 188 189 // get previous polydata object 190 final vtkPolyData previousPolyData = polyData; 191 192 // get VTK binary image from ROI mask 193 final vtkImageData imageData = VtkUtil.getBinaryImageData(ROI3DArea.this, seq.getSizeZ(), 194 canvas.getPositionT()); 195 // adjust spacing 196 imageData.SetSpacing(scaling[0], scaling[1], scaling[2]); 197 // get VTK polygon data representing the surface of the binary image 198 polyData = VtkUtil.getSurfaceFromImage(imageData, 0.5d); 199 200 // get bounds 201 final Rectangle3D bounds = getBounds3D(); 202 // apply scaling on bounds 203 bounds.setX(bounds.getX() * scaling[0]); 204 bounds.setSizeX(bounds.getSizeX() * scaling[0]); 205 bounds.setY(bounds.getY() * scaling[1]); 206 bounds.setSizeY(bounds.getSizeY() * scaling[1]); 207 if (bounds.isInfiniteZ()) 208 { 209 bounds.setZ(0); 210 bounds.setSizeZ(seq.getSizeZ() * scaling[2]); 211 } 212 else 213 { 214 bounds.setZ(bounds.getZ() * scaling[2]); 215 bounds.setSizeZ(bounds.getSizeZ() * scaling[2]); 216 } 217 218 // actor can be accessed in canvas3d for rendering so we need to synchronize access 219 vtkPanel.lock(); 220 try 221 { 222 // update outline data 223 VtkUtil.setOutlineBounds(outline, bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), 224 bounds.getMaxY(), bounds.getMinZ(), bounds.getMaxZ(), canvas); 225 outlineMapper.Update(); 226 // update surface polygon data 227 polyMapper.SetInputData(polyData); 228 polyMapper.Update(); 229 230 // update actor position 231 surfaceActor.SetPosition(bounds.getX(), bounds.getY(), bounds.getZ()); 232 233 // release image data 234 imageData.GetPointData().GetScalars().Delete(); 235 imageData.GetPointData().Delete(); 236 imageData.Delete(); 237 238 // release previous polydata 239 if (previousPolyData != null) 240 { 241 previousPolyData.GetPointData().GetScalars().Delete(); 242 previousPolyData.GetPointData().Delete(); 243 previousPolyData.Delete(); 244 } 245 } 246 finally 247 { 248 vtkPanel.unlock(); 249 } 250 251 // update color and others properties 252 updateVtkDisplayProperties(); 253 } 254 255 protected void updateVtkDisplayProperties() 256 { 257 if (surfaceActor == null) 258 return; 259 260 final VtkCanvas cnv = canvas3d.get(); 261 final Color col = getDisplayColor(); 262 final double r = col.getRed() / 255d; 263 final double g = col.getGreen() / 255d; 264 final double b = col.getBlue() / 255d; 265 // final double strk = getStroke(); 266 // final float opacity = getOpacity(); 267 268 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 269 270 // we need to lock canvas as actor can be accessed during rendering 271 if (vtkPanel != null) 272 vtkPanel.lock(); 273 try 274 { 275 // set actors color 276 outlineActor.GetProperty().SetColor(r, g, b); 277 if (isSelected()) 278 { 279 outlineActor.GetProperty().SetRepresentationToWireframe(); 280 outlineActor.SetVisibility(1); 281 vtkInfo.Set(VtkCanvas.visibilityKey, 1); 282 } 283 else 284 { 285 outlineActor.GetProperty().SetRepresentationToPoints(); 286 outlineActor.SetVisibility(0); 287 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 288 } 289 surfaceActor.GetProperty().SetColor(r, g, b); 290 // opacity here is about ROI content, global opacity is handled by Layer 291 // surfaceActor.GetProperty().SetOpacity(opacity); 292 setVtkObjectsColor(col); 293 } 294 finally 295 { 296 if (vtkPanel != null) 297 vtkPanel.unlock(); 298 } 299 300 // need to repaint 301 painterChanged(); 302 } 303 304 protected void setVtkObjectsColor(Color color) 305 { 306 if (outline != null) 307 VtkUtil.setPolyDataColor(outline, color, canvas3d.get()); 308 if (polyData != null) 309 VtkUtil.setPolyDataColor(polyData, color, canvas3d.get()); 310 } 311 312 @Override 313 public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 314 { 315 // provide backward compatibility 316 if (imagePoint != null) 317 mouseClick(e, imagePoint.toPoint2D(), canvas); 318 else 319 mouseClick(e, (Point2D) null, canvas); 320 321 // not yet consumed... 322 if (!e.isConsumed()) 323 { 324 // and process ROI stuff now 325 if (isActiveFor(canvas)) 326 { 327 final int clickCount = e.getClickCount(); 328 329 // double click 330 if (clickCount == 2) 331 { 332 // focused ? 333 if (isFocused()) 334 { 335 // show in ROI panel 336 final RoisPanel roiPanel = Icy.getMainInterface().getRoisPanel(); 337 338 if (roiPanel != null) 339 { 340 roiPanel.scrollTo(ROI3DArea.this); 341 // consume event 342 e.consume(); 343 } 344 } 345 } 346 } 347 } 348 } 349 350 @Override 351 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 352 { 353 super.paint(g, sequence, canvas); 354 355 if (isActiveFor(canvas)) 356 { 357 if (canvas instanceof VtkCanvas) 358 { 359 // 3D canvas 360 final VtkCanvas cnv = (VtkCanvas) canvas; 361 // update reference if needed 362 if (canvas3d.get() != cnv) 363 canvas3d = new WeakReference<VtkCanvas>(cnv); 364 365 // FIXME : need a better implementation 366 final double[] s = cnv.getVolumeScale(); 367 368 // scaling changed ? 369 if (!Arrays.equals(scaling, s)) 370 { 371 // update scaling 372 scaling = s; 373 // need rebuild 374 needRebuild = true; 375 } 376 377 // need to rebuild 3D data structures ? 378 if (needRebuild) 379 { 380 // initialize VTK objects if not yet done 381 if (surfaceActor == null) 382 initVtkObjects(); 383 384 // request rebuild 3D objects 385 ThreadUtil.runSingle(this); 386 needRebuild = false; 387 } 388 } 389 } 390 } 391 392 @Override 393 protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas) 394 { 395 // specific VTK canvas processing 396 if (canvas instanceof VtkCanvas) 397 { 398 // mouse is over the ROI actor ? --> focus the ROI 399 final boolean focused = (surfaceActor != null) 400 && (surfaceActor == ((VtkCanvas) canvas).getPickedObject()); 401 402 setFocused(focused); 403 404 return focused; 405 } 406 407 return super.updateFocus(e, imagePoint, canvas); 408 } 409 410 @Override 411 public vtkProp[] getProps() 412 { 413 // initialize VTK objects if not yet done 414 if (surfaceActor == null) 415 initVtkObjects(); 416 417 return new vtkActor[] {surfaceActor, outlineActor}; 418 } 419 420 @Override 421 public void run() 422 { 423 rebuildVtkObjects(); 424 } 425 } 426 427 public ROI3DArea() 428 { 429 super(ROI2DArea.class); 430 } 431 432 public ROI3DArea(Point3D pt) 433 { 434 this(); 435 436 addBrush(pt.toPoint2D(), (int) pt.getZ()); 437 } 438 439 public ROI3DArea(Point5D pt) 440 { 441 this(pt.toPoint3D()); 442 } 443 444 /** 445 * Create a 3D Area ROI type from the specified {@link BooleanMask3D}. 446 */ 447 public ROI3DArea(BooleanMask3D mask) 448 { 449 this(); 450 451 setAsBooleanMask(mask); 452 } 453 454 /** 455 * Create a copy of the specified 3D Area ROI. 456 */ 457 public ROI3DArea(ROI3DArea area) 458 { 459 this(); 460 461 // copy the source 3D area ROI 462 for (Entry<Integer, ROI2DArea> entry : area.slices.entrySet()) 463 slices.put(entry.getKey(), new ROI2DArea(entry.getValue())); 464 465 roiChanged(true); 466 } 467 468 /** 469 * Create a 3D Area ROI type from the specified {@link BooleanMask3D}. 470 */ 471 public ROI3DArea(BooleanMask2D mask2d, int zMin, int zMax) 472 { 473 this(); 474 475 if (zMax < zMin) 476 throw new IllegalArgumentException("ROI3DArea: cannot create the ROI (zMax < zMin)."); 477 478 beginUpdate(); 479 try 480 { 481 for (int z = zMin; z <= zMax; z++) 482 setSlice(z, new ROI2DArea(mask2d)); 483 } 484 finally 485 { 486 endUpdate(); 487 } 488 } 489 490 @Override 491 public String getDefaultName() 492 { 493 return "Area3D"; 494 } 495 496 @Override 497 protected ROIPainter createPainter() 498 { 499 return new ROI3DAreaPainter(); 500 } 501 502 /** 503 * Adds the specified point to this ROI 504 */ 505 public void addPoint(int x, int y, int z) 506 { 507 setPoint(x, y, z, true); 508 } 509 510 /** 511 * Remove a point from the mask.<br> 512 * Don't forget to call optimizeBounds() after consecutive remove operation 513 * to refresh the mask bounds. 514 */ 515 public void removePoint(int x, int y, int z) 516 { 517 setPoint(x, y, z, false); 518 } 519 520 /** 521 * Set the value for the specified point in the mask. 522 * Don't forget to call optimizeBounds() after consecutive remove point operation 523 * to refresh the mask bounds. 524 */ 525 public void setPoint(int x, int y, int z, boolean value) 526 { 527 final ROI2DArea slice = getSlice(z, value); 528 529 if (slice != null) 530 slice.setPoint(x, y, value); 531 } 532 533 /** 534 * Add brush point at specified position and for specified Z slice. 535 */ 536 public void addBrush(Point2D pos, int z) 537 { 538 getSlice(z, true).addBrush(pos); 539 } 540 541 /** 542 * Remove brush point from the mask at specified position and for specified Z slice.<br> 543 * Don't forget to call optimizeBounds() after consecutive remove operation 544 * to refresh the mask bounds. 545 */ 546 public void removeBrush(Point2D pos, int z) 547 { 548 final ROI2DArea slice = getSlice(z, false); 549 550 if (slice != null) 551 slice.removeBrush(pos); 552 } 553 554 /** 555 * Add the specified {@link BooleanMask3D} content to this ROI3DArea 556 */ 557 public void add(BooleanMask3D mask) 558 { 559 beginUpdate(); 560 try 561 { 562 for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet()) 563 add(entry.getKey().intValue(), entry.getValue()); 564 } 565 finally 566 { 567 endUpdate(); 568 } 569 } 570 571 /** 572 * Add the specified BooleanMask2D with the existing slice at given Z position.<br> 573 * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)} with 574 * <code>new ROI2DArea(maskSlice)</code> 575 * 576 * @param z 577 * the position where the slice must be added 578 * @param maskSlice 579 * the 2D boolean mask to merge 580 */ 581 public void add(int z, BooleanMask2D maskSlice) 582 { 583 if (maskSlice == null) 584 return; 585 586 final ROI2DArea currentSlice = getSlice(z); 587 588 if (currentSlice != null) 589 // merge slices 590 currentSlice.add(maskSlice); 591 else 592 // add new slice 593 setSlice(z, new ROI2DArea(maskSlice)); 594 } 595 596 /** 597 * Exclusively add the specified {@link BooleanMask3D} content to this ROI3DArea 598 */ 599 public void exclusiveAdd(BooleanMask3D mask) 600 { 601 beginUpdate(); 602 try 603 { 604 for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet()) 605 exclusiveAdd(entry.getKey().intValue(), entry.getValue()); 606 } 607 finally 608 { 609 endUpdate(); 610 } 611 } 612 613 /** 614 * Exclusively add the specified BooleanMask2D with the existing slice at given Z position.<br> 615 * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)} with 616 * <code>new ROI2DArea(maskSlice)</code> 617 * 618 * @param z 619 * the position where the slice must be exclusively added 620 * @param maskSlice 621 * the 2D boolean mask to merge 622 */ 623 public void exclusiveAdd(int z, BooleanMask2D maskSlice) 624 { 625 if (maskSlice == null) 626 return; 627 628 final ROI2DArea currentSlice = getSlice(z); 629 630 // merge both slice 631 if (currentSlice != null) 632 { 633 // process exclusive add 634 currentSlice.exclusiveAdd(maskSlice); 635 // remove it if empty 636 if (currentSlice.isEmpty()) 637 removeSlice(z); 638 } 639 // add new slice 640 else 641 setSlice(z, new ROI2DArea(maskSlice)); 642 } 643 644 /** 645 * Intersect the specified {@link BooleanMask3D} content with this ROI3DArea 646 */ 647 public void intersect(BooleanMask3D mask) 648 { 649 beginUpdate(); 650 try 651 { 652 final Set<Integer> keys = mask.mask.keySet(); 653 final Set<Integer> toRemove = new HashSet<Integer>(); 654 655 // remove slices which are not contained 656 for (Integer key : slices.keySet()) 657 if (!keys.contains(key)) 658 toRemove.add(key); 659 660 // do remove first 661 for (Integer key : toRemove) 662 removeSlice(key.intValue()); 663 664 // then process intersection 665 for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet()) 666 intersect(entry.getKey().intValue(), entry.getValue()); 667 } 668 finally 669 { 670 endUpdate(); 671 } 672 } 673 674 /** 675 * Intersect the specified BooleanMask2D with the existing slice at given Z position. 676 * 677 * @param z 678 * the position where the slice must be set 679 * @param maskSlice 680 * the 2D boolean mask to merge 681 */ 682 public void intersect(int z, BooleanMask2D maskSlice) 683 { 684 // better to throw an exception here than removing slice 685 if (maskSlice == null) 686 throw new IllegalArgumentException("Cannot intersect an empty slice in a 3D ROI"); 687 688 final ROI2DArea currentSlice = getSlice(z); 689 690 if (currentSlice != null) 691 { 692 // build ROI from mask 693 final ROI2DArea roi = new ROI2DArea(maskSlice); 694 695 // set same position as slice 696 roi.setT(currentSlice.getT()); 697 roi.setZ(currentSlice.getZ()); 698 roi.setC(currentSlice.getC()); 699 // compute intersection 700 currentSlice.intersect(roi, false); 701 702 // remove it if empty 703 if (currentSlice.isEmpty()) 704 removeSlice(z); 705 } 706 } 707 708 /** 709 * Subtract the specified {@link BooleanMask3D} from this ROI3DArea 710 */ 711 public void subtract(BooleanMask3D mask) 712 { 713 beginUpdate(); 714 try 715 { 716 for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet()) 717 subtract(entry.getKey().intValue(), entry.getValue()); 718 } 719 finally 720 { 721 endUpdate(); 722 } 723 } 724 725 /** 726 * Subtract the specified BooleanMask2D from the existing slice at given Z position.<br> 727 * 728 * @param z 729 * the position where the slice must be subtracted 730 * @param maskSlice 731 * the 2D boolean mask to subtract 732 */ 733 public void subtract(int z, BooleanMask2D maskSlice) 734 { 735 if (maskSlice == null) 736 return; 737 738 final ROI2DArea currentSlice = getSlice(z); 739 740 // merge both slice 741 if (currentSlice != null) 742 { 743 // process exclusive add 744 currentSlice.subtract(maskSlice); 745 // remove it if empty 746 if (currentSlice.isEmpty()) 747 removeSlice(z); 748 } 749 } 750 751 @Override 752 public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException 753 { 754 if (roi instanceof ROI3D) 755 { 756 final ROI3D roi3d = (ROI3D) roi; 757 758 // only if on same position 759 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 760 { 761 if (roi3d instanceof ROI3DArea) 762 add((ROI3DArea) roi3d); 763 else 764 add(roi3d.getBooleanMask(true)); 765 766 return this; 767 } 768 } 769 770 return super.add(roi, allowCreate); 771 } 772 773 @Override 774 public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException 775 { 776 if (roi instanceof ROI3D) 777 { 778 final ROI3D roi3d = (ROI3D) roi; 779 780 // only if on same position 781 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 782 { 783 if (roi3d instanceof ROI3DArea) 784 exclusiveAdd((ROI3DArea) roi3d); 785 else 786 exclusiveAdd(roi3d.getBooleanMask(true)); 787 788 return this; 789 } 790 } 791 792 return super.exclusiveAdd(roi, allowCreate); 793 } 794 795 @Override 796 public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException 797 { 798 if (roi instanceof ROI3D) 799 { 800 final ROI3D roi3d = (ROI3D) roi; 801 802 // only if on same position 803 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 804 { 805 if (roi3d instanceof ROI3DArea) 806 intersect((ROI3DArea) roi3d); 807 else 808 intersect(roi3d.getBooleanMask(true)); 809 810 return this; 811 } 812 } 813 814 return super.intersect(roi, allowCreate); 815 } 816 817 @Override 818 public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException 819 { 820 if (roi instanceof ROI3D) 821 { 822 final ROI3D roi3d = (ROI3D) roi; 823 824 // only if on same position 825 if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) 826 { 827 if (roi3d instanceof ROI3DArea) 828 subtract((ROI3DArea) roi3d); 829 else 830 subtract(roi3d.getBooleanMask(true)); 831 832 return this; 833 } 834 } 835 836 return super.subtract(roi, allowCreate); 837 } 838 839 /** 840 * Sets the BooleanMask2D slice at given Z position to this 3D ROI 841 * 842 * @param z 843 * the position where the slice must be set 844 * @param maskSlice 845 * the BooleanMask2D to set 846 */ 847 public void setSlice(int z, BooleanMask2D maskSlice) 848 { 849 // empty mask --> just remove previous 850 if (maskSlice == null) 851 { 852 removeSlice(z); 853 return; 854 } 855 856 setSlice(z, new ROI2DArea(maskSlice)); 857 } 858 859 /** 860 * @deprecated Use one of these methods instead :<br> 861 * {@link #setSlice(int, ROI2DArea)}, {@link #setSlice(int, BooleanMask2D)}, 862 * {@link #add(int, ROI2DArea)} or {@link #add(BooleanMask3D)} 863 */ 864 @Deprecated 865 public void setSlice(int z, ROI2D roiSlice, boolean merge) 866 { 867 if (roiSlice == null) 868 throw new IllegalArgumentException("Cannot add an empty slice in a 3D ROI"); 869 870 final ROI2DArea currentSlice = getSlice(z); 871 final ROI newSlice; 872 873 // merge both slice 874 if ((currentSlice != null) && merge) 875 { 876 // we need to modify the Z, T and C position so we do the merge correctly 877 roiSlice.setZ(z); 878 roiSlice.setT(getT()); 879 roiSlice.setC(getC()); 880 // do ROI union 881 newSlice = currentSlice.getUnion(roiSlice); 882 } 883 else 884 newSlice = roiSlice; 885 886 if (newSlice instanceof ROI2DArea) 887 setSlice(z, (ROI2DArea) newSlice); 888 else if (newSlice instanceof ROI2D) 889 setSlice(z, new ROI2DArea(((ROI2D) newSlice).getBooleanMask(true))); 890 else 891 throw new IllegalArgumentException( 892 "Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName()); 893 } 894 895 // /** 896 // * Merge the specified ROI with the existing slice at given Z position.<br> 897 // * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)} 898 // * 899 // * @param z 900 // * the position where the slice must be set 901 // * @param roiSlice 902 // * the 2D ROI to merge 903 // */ 904 // public void addROI2DSlice(int z, ROI2D roiSlice) 905 // { 906 // if (roiSlice == null) 907 // return; 908 // 909 // final ROI2DArea currentSlice = getSlice(z); 910 // 911 // // merge both slice 912 // if (currentSlice != null) 913 // { 914 // // we need to modify the Z, T and C position so we do the merge correctly 915 // roiSlice.setZ(z); 916 // roiSlice.setT(getT()); 917 // roiSlice.setC(getC()); 918 // // do ROI union 919 // currentSlice.add(roiSlice, true); 920 // } 921 // else 922 // setSlice(z, new ROI2DArea(roiSlice.getBooleanMask(true))); 923 // } 924 925 /** 926 * Returns true if the ROI is empty (the mask does not contains any point). 927 */ 928 @Override 929 public boolean isEmpty() 930 { 931 for (ROI2DArea area : slices.values()) 932 if (!area.isEmpty()) 933 return false; 934 935 return true; 936 } 937 938 /** 939 * @deprecated Use {@link #getBooleanMask(boolean)} and {@link BooleanMask3D#getContourPoints()} instead. 940 */ 941 @Deprecated 942 public Point3D[] getEdgePoints() 943 { 944 return getBooleanMask(true).getContourPoints(); 945 } 946 947 /** 948 * @deprecated Use {@link #getBooleanMask(boolean)} and {@link BooleanMask3D#getPoints()} instead. 949 */ 950 @Deprecated 951 public Point3D[] getPoints() 952 { 953 return getBooleanMask(true).getPoints(); 954 } 955 956 /** 957 * @deprecated Use {@link #translate(double, double, double)} instead. 958 */ 959 @Deprecated 960 public void translate(double dx, double dy) 961 { 962 beginUpdate(); 963 try 964 { 965 for (ROI2DArea slice : slices.values()) 966 slice.translate(dx, dy); 967 } 968 finally 969 { 970 endUpdate(); 971 } 972 } 973 974 @Override 975 public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z) 976 { 977 final ROI2DArea slice = getSlice((int) z); 978 979 if (slice != null) 980 return slice.isOverEdge(canvas, x, y); 981 982 return false; 983 } 984 985 /** 986 * Set all 2D slices ROI to same position.<br> 987 */ 988 public void setPosition2D(Point2D newPosition) 989 { 990 beginUpdate(); 991 try 992 { 993 for (ROI2DArea slice : slices.values()) 994 slice.setPosition2D(newPosition); 995 } 996 finally 997 { 998 endUpdate(); 999 } 1000 } 1001 1002 /** 1003 * Set the mask from a BooleanMask3D object.<br> 1004 * If specified mask is <i>null</i> then ROI is cleared. 1005 */ 1006 public void setAsBooleanMask(BooleanMask3D mask) 1007 { 1008 // mask empty ? --> just clear the ROI 1009 if ((mask == null) || mask.isEmpty()) 1010 clear(); 1011 else 1012 { 1013 final Rectangle3D.Integer bounds3d = mask.bounds; 1014 final int startZ = bounds3d.z; 1015 final int sizeZ = bounds3d.sizeZ; 1016 final BooleanMask2D masks2d[] = new BooleanMask2D[sizeZ]; 1017 1018 for (int z = 0; z < sizeZ; z++) 1019 masks2d[z] = mask.getMask2D(startZ + z); 1020 1021 setAsBooleanMask(bounds3d, masks2d); 1022 } 1023 } 1024 1025 /** 1026 * Set the 3D mask from a 2D boolean mask array 1027 * 1028 * @param rect 1029 * the 3D region defined by 2D boolean mask array 1030 * @param mask 1031 * the 3D mask data (array length should be equals to rect.sizeZ) 1032 */ 1033 public void setAsBooleanMask(Rectangle3D.Integer rect, BooleanMask2D[] mask) 1034 { 1035 if (rect.isInfiniteZ()) 1036 throw new IllegalArgumentException("Cannot set infinite Z dimension on the 3D Area ROI."); 1037 1038 beginUpdate(); 1039 try 1040 { 1041 clear(); 1042 1043 for (int z = 0; z < rect.sizeZ; z++) 1044 setSlice(z + rect.z, new ROI2DArea(mask[z])); 1045 } 1046 finally 1047 { 1048 endUpdate(); 1049 } 1050 } 1051 1052 /** 1053 * Fast up scaling by a factor of 2 (each point become a 2x2x2 block points) 1054 */ 1055 public void upscale() 1056 { 1057 setAsBooleanMask(getBooleanMask(true).upscale()); 1058 } 1059 1060 /** 1061 * Fast 2x down scaling (each 2x2x2 block points become 1 point).<br> 1062 * 1063 * @param nbPointForTrue 1064 * the minimum number of <code>true</code>points from a 2x2x2 block to give a <code>true</code> resulting 1065 * point.<br> 1066 * Accepted value: 1 to 5 (default is 5) 1067 */ 1068 public void downscale(int nbPointForTrue) 1069 { 1070 setAsBooleanMask(getBooleanMask(true).downscale(nbPointForTrue)); 1071 } 1072 1073 /** 1074 * Fast 2x down scaling (each 2x2x2 block points become 1 point).<br> 1075 */ 1076 public void downscale() 1077 { 1078 setAsBooleanMask(getBooleanMask(true).downscale()); 1079 } 1080 1081 /** 1082 * Fast up scaling by a factor of 2 (each point become a 2x2 block points) 1083 * 2D version (down scale is done on XY dimension only).<br> 1084 */ 1085 public void upscale2D() 1086 { 1087 setAsBooleanMask(getBooleanMask(true).upscale2D()); 1088 } 1089 1090 /** 1091 * Fast 2x down scaling (each 2x2 block points become 1 point).<br> 1092 * 2D version (down scale is done on XY dimension only).<br> 1093 * 1094 * @param nbPointForTrue 1095 * the minimum number of <code>true</code>points from a 2x2 block to give a <code>true</code> resulting 1096 * point.<br> 1097 * Accepted value: 1 to 4 1098 */ 1099 public void downscale2D(int nbPointForTrue) 1100 { 1101 setAsBooleanMask(getBooleanMask(true).downscale2D(nbPointForTrue)); 1102 } 1103 1104 /** 1105 * Fast 2x down scaling (each 2x2 block points become 1 point).<br> 1106 * 2D version (down scale is done on XY dimension only).<br> 1107 */ 1108 public void downscale2D() 1109 { 1110 setAsBooleanMask(getBooleanMask(true).downscale2D()); 1111 } 1112 1113 /** 1114 * Optimize the bounds size to the minimum surface which still include all mask.<br> 1115 * You should call it after consecutive remove operations if you directly addressed mask data. 1116 */ 1117 public void optimizeBounds() 1118 { 1119 final Rectangle3D.Integer bounds = getBounds(); 1120 1121 beginUpdate(); 1122 try 1123 { 1124 for (int z = bounds.z; z < bounds.z + bounds.sizeZ; z++) 1125 { 1126 final ROI2DArea roi = getSlice(z); 1127 1128 if (roi != null) 1129 { 1130 if (roi.isEmpty()) 1131 removeSlice(z); 1132 else 1133 { 1134 if (roi.optimizeBounds()) 1135 roi.roiChanged(true); 1136 } 1137 } 1138 } 1139 } 1140 finally 1141 { 1142 endUpdate(); 1143 } 1144 } 1145 1146 /** 1147 * roi changed 1148 */ 1149 @Override 1150 public void onChanged(CollapsibleEvent object) 1151 { 1152 final ROIEvent event = (ROIEvent) object; 1153 1154 // do here global process on ROI change 1155 switch (event.getType()) 1156 { 1157 case ROI_CHANGED: 1158 // the painter need to be rebuild 1159 ((ROI3DAreaPainter) painter).needRebuild = true; 1160 break; 1161 1162 case FOCUS_CHANGED: 1163 case SELECTION_CHANGED: 1164 ((ROI3DAreaPainter) getOverlay()).updateVtkDisplayProperties(); 1165 break; 1166 1167 case PROPERTY_CHANGED: 1168 final String property = event.getPropertyName(); 1169 1170 if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR) 1171 || StringUtil.equals(property, PROPERTY_OPACITY)) 1172 ((ROI3DAreaPainter) getOverlay()).updateVtkDisplayProperties(); 1173 break; 1174 1175 default: 1176 break; 1177 } 1178 1179 super.onChanged(object); 1180 } 1181}