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