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