001/** 002 * 003 */ 004package icy.roi; 005 006import icy.type.collection.array.DynamicArray; 007import icy.type.point.Point4D; 008import icy.type.rectangle.Rectangle3D; 009import icy.type.rectangle.Rectangle4D; 010 011import java.awt.Rectangle; 012import java.util.Map.Entry; 013import java.util.TreeMap; 014 015/** 016 * Class to define a 4D boolean mask region and make basic boolean operation between masks.<br> 017 * The bounds property of this object represents the region defined by the boolean mask. 018 * 019 * @author Stephane 020 */ 021public class BooleanMask4D 022{ 023 // Internal use only 024 private static BooleanMask3D doUnion3D(BooleanMask3D m1, BooleanMask3D m2) 025 { 026 if (m1 == null) 027 { 028 // only use the 3D mask from second mask 029 if (m2 != null) 030 return (BooleanMask3D) m2.clone(); 031 032 return null; 033 } 034 else if (m2 == null) 035 // only use the 3D mask from first mask 036 return (BooleanMask3D) m1.clone(); 037 038 // process union of 3D mask 039 return BooleanMask3D.getUnion(m1, m2); 040 } 041 042 // Internal use only 043 private static BooleanMask3D doIntersection3D(BooleanMask3D m1, BooleanMask3D m2) 044 { 045 if ((m1 == null) || (m2 == null)) 046 return null; 047 048 // process intersection of 3D mask 049 return BooleanMask3D.getIntersection(m1, m2); 050 } 051 052 // Internal use only 053 private static BooleanMask3D doExclusiveUnion3D(BooleanMask3D m1, BooleanMask3D m2) 054 { 055 if (m1 == null) 056 { 057 // only use the 3D mask from second mask 058 if (m2 != null) 059 return (BooleanMask3D) m2.clone(); 060 061 return null; 062 } 063 else if (m2 == null) 064 // only use the 3D mask from first mask 065 return (BooleanMask3D) m1.clone(); 066 067 // process exclusive union of 3D mask 068 return BooleanMask3D.getExclusiveUnion(m1, m2); 069 } 070 071 // Internal use only 072 private static BooleanMask3D doSubtraction3D(BooleanMask3D m1, BooleanMask3D m2) 073 { 074 if (m1 == null) 075 return null; 076 // only use the 3D mask from first mask 077 if (m2 == null) 078 return (BooleanMask3D) m1.clone(); 079 080 // process subtraction of 3D mask 081 return BooleanMask3D.getSubtraction(m1, m2); 082 } 083 084 /** 085 * Build resulting mask from union of the mask1 and mask2: 086 * 087 * <pre> 088 * mask1 + mask2 = result 089 * 090 * ################ ################ ################ 091 * ############## ############## ################ 092 * ############ ############ ################ 093 * ########## ########## ################ 094 * ######## ######## ################ 095 * ###### ###### ###### ###### 096 * #### #### #### #### 097 * ## ## ## ## 098 * </pre> 099 */ 100 public static BooleanMask4D getUnion(BooleanMask4D mask1, BooleanMask4D mask2) 101 { 102 if ((mask1 == null) && (mask2 == null)) 103 return new BooleanMask4D(); 104 105 if ((mask1 == null) || mask1.isEmpty()) 106 return (BooleanMask4D) mask2.clone(); 107 if ((mask2 == null) || mask2.isEmpty()) 108 return (BooleanMask4D) mask1.clone(); 109 110 final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createUnion(mask2.bounds); 111 112 if (!bounds.isEmpty()) 113 { 114 final BooleanMask3D[] mask; 115 116 // special case of infinite T dimension 117 if (bounds.sizeT == Integer.MAX_VALUE) 118 { 119 // we can allow merge ROI only if they both has infinite T dimension 120 if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE)) 121 throw new UnsupportedOperationException( 122 "Cannot merge an infinite T dimension ROI with a finite Z dimension ROI"); 123 124 mask = new BooleanMask3D[1]; 125 126 final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue(); 127 final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue(); 128 129 mask[0] = doUnion3D(m2d1, m2d2); 130 } 131 else 132 { 133 mask = new BooleanMask3D[bounds.sizeT]; 134 135 for (int t = 0; t < bounds.sizeT; t++) 136 { 137 final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t); 138 final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t); 139 140 mask[t] = doUnion3D(m2d1, m2d2); 141 } 142 } 143 144 return new BooleanMask4D(bounds, mask); 145 } 146 147 return new BooleanMask4D(); 148 } 149 150 /** 151 * Build resulting mask from intersection of the mask1 and mask2: 152 * 153 * <pre> 154 * mask1 intersect mask2 = result 155 * 156 * ################ ################ ################ 157 * ############## ############## ############ 158 * ############ ############ ######## 159 * ########## ########## #### 160 * ######## ######## 161 * ###### ###### 162 * #### #### 163 * ## ## 164 * </pre> 165 */ 166 public static BooleanMask4D getIntersection(BooleanMask4D mask1, BooleanMask4D mask2) 167 { 168 if ((mask1 == null) || (mask2 == null)) 169 return new BooleanMask4D(); 170 171 final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createIntersection(mask2.bounds); 172 173 if (!bounds.isEmpty()) 174 { 175 final BooleanMask3D[] mask; 176 177 // special case of infinite T dimension 178 if (bounds.sizeT == Integer.MAX_VALUE) 179 { 180 // we can allow merge ROI only if they both has infinite T dimension 181 if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE)) 182 throw new UnsupportedOperationException( 183 "Cannot merge an infinite T dimension ROI with a finite Z dimension ROI"); 184 185 mask = new BooleanMask3D[1]; 186 187 final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue(); 188 final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue(); 189 190 mask[0] = doIntersection3D(m2d1, m2d2); 191 } 192 else 193 { 194 mask = new BooleanMask3D[bounds.sizeT]; 195 196 for (int t = 0; t < bounds.sizeT; t++) 197 { 198 final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t); 199 final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t); 200 201 mask[t] = doIntersection3D(m2d1, m2d2); 202 } 203 } 204 205 return new BooleanMask4D(bounds, mask); 206 } 207 208 return new BooleanMask4D(); 209 } 210 211 /** 212 * Build resulting mask from exclusive union of the mask1 and mask2: 213 * 214 * <pre> 215 * mask1 xor mask2 = result 216 * 217 * ################ ################ 218 * ############## ############## ## ## 219 * ############ ############ #### #### 220 * ########## ########## ###### ###### 221 * ######## ######## ################ 222 * ###### ###### ###### ###### 223 * #### #### #### #### 224 * ## ## ## ## 225 * </pre> 226 */ 227 public static BooleanMask4D getExclusiveUnion(BooleanMask4D mask1, BooleanMask4D mask2) 228 { 229 if ((mask1 == null) && (mask2 == null)) 230 return new BooleanMask4D(); 231 232 if ((mask1 == null) || mask1.isEmpty()) 233 return (BooleanMask4D) mask2.clone(); 234 if ((mask2 == null) || mask2.isEmpty()) 235 return (BooleanMask4D) mask1.clone(); 236 237 final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createUnion(mask2.bounds); 238 239 if (!bounds.isEmpty()) 240 { 241 final BooleanMask3D[] mask; 242 243 // special case of infinite T dimension 244 if (bounds.sizeT == Integer.MAX_VALUE) 245 { 246 // we can allow merge ROI only if they both has infinite T dimension 247 if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE)) 248 throw new UnsupportedOperationException( 249 "Cannot merge an infinite T dimension ROI with a finite Z dimension ROI"); 250 251 mask = new BooleanMask3D[1]; 252 253 final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue(); 254 final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue(); 255 256 mask[0] = doExclusiveUnion3D(m2d1, m2d2); 257 } 258 else 259 { 260 mask = new BooleanMask3D[bounds.sizeT]; 261 262 for (int t = 0; t < bounds.sizeT; t++) 263 { 264 final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t); 265 final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t); 266 267 mask[t] = doExclusiveUnion3D(m2d1, m2d2); 268 } 269 } 270 271 return new BooleanMask4D(bounds, mask); 272 } 273 274 return new BooleanMask4D(); 275 } 276 277 /** 278 * Build resulting mask from the subtraction of mask2 from mask1: 279 * 280 * <pre> 281 * mask1 - mask2 = result 282 * 283 * ################ ################ 284 * ############## ############## ## 285 * ############ ############ #### 286 * ########## ########## ###### 287 * ######## ######## ######## 288 * ###### ###### ###### 289 * #### #### #### 290 * ## ## ## 291 * </pre> 292 */ 293 public static BooleanMask4D getSubtraction(BooleanMask4D mask1, BooleanMask4D mask2) 294 { 295 if (mask1 == null) 296 return new BooleanMask4D(); 297 if (mask2 == null) 298 return (BooleanMask4D) mask1.clone(); 299 300 final Rectangle4D.Integer bounds = (Rectangle4D.Integer) mask1.bounds.createIntersection(mask2.bounds); 301 302 // need to subtract something ? 303 if (!bounds.isEmpty()) 304 { 305 final BooleanMask3D[] mask; 306 307 // special case of infinite T dimension 308 if (bounds.sizeT == Integer.MAX_VALUE) 309 { 310 // we can allow merge ROI only if they both has infinite T dimension 311 if ((mask1.bounds.sizeT != Integer.MAX_VALUE) || (mask2.bounds.sizeT != Integer.MAX_VALUE)) 312 throw new UnsupportedOperationException( 313 "Cannot merge an infinite T dimension ROI with a finite Z dimension ROI"); 314 315 mask = new BooleanMask3D[1]; 316 317 final BooleanMask3D m2d1 = mask1.mask.firstEntry().getValue(); 318 final BooleanMask3D m2d2 = mask2.mask.firstEntry().getValue(); 319 320 mask[0] = doSubtraction3D(m2d1, m2d2); 321 } 322 else 323 { 324 mask = new BooleanMask3D[bounds.sizeT]; 325 326 for (int t = 0; t < bounds.sizeT; t++) 327 { 328 final BooleanMask3D m2d1 = mask1.getMask3D(t + bounds.t); 329 final BooleanMask3D m2d2 = mask2.getMask3D(t + bounds.t); 330 331 mask[t] = doSubtraction3D(m2d1, m2d2); 332 } 333 } 334 335 return new BooleanMask4D(bounds, mask); 336 } 337 338 return (BooleanMask4D) mask1.clone(); 339 } 340 341 /** 342 * Region represented by the mask. 343 */ 344 public Rectangle4D.Integer bounds; 345 /** 346 * Boolean mask 3D array. 347 */ 348 public final TreeMap<Integer, BooleanMask3D> mask; 349 350 /** 351 * Build a new 4D boolean mask with specified bounds and 3D mask array.<br> 352 * The 3D mask array length should be >= to <code>bounds.getSizeT()</code>. 353 */ 354 public BooleanMask4D(Rectangle4D.Integer bounds, BooleanMask3D[] mask) 355 { 356 super(); 357 358 this.bounds = bounds; 359 this.mask = new TreeMap<Integer, BooleanMask3D>(); 360 361 // special case of infinite T dim 362 if (bounds.sizeT == Integer.MAX_VALUE) 363 this.mask.put(Integer.valueOf(Integer.MIN_VALUE), mask[0]); 364 else 365 { 366 for (int t = 0; t < bounds.sizeT; t++) 367 if (mask[t] != null) 368 this.mask.put(Integer.valueOf(bounds.t + t), mask[t]); 369 } 370 } 371 372 /** 373 * Build a new 4D boolean mask from the specified array of {@link Point4D}.<br> 374 */ 375 public BooleanMask4D(Point4D.Integer[] points) 376 { 377 super(); 378 379 mask = new TreeMap<Integer, BooleanMask3D>(); 380 381 if ((points == null) || (points.length == 0)) 382 bounds = new Rectangle4D.Integer(); 383 else 384 { 385 int minX = Integer.MAX_VALUE; 386 int minY = Integer.MAX_VALUE; 387 int minZ = Integer.MAX_VALUE; 388 int minT = Integer.MAX_VALUE; 389 int maxX = Integer.MIN_VALUE; 390 int maxY = Integer.MIN_VALUE; 391 int maxZ = Integer.MIN_VALUE; 392 int maxT = Integer.MIN_VALUE; 393 394 for (Point4D.Integer pt : points) 395 { 396 final int x = pt.x; 397 final int y = pt.y; 398 final int z = pt.z; 399 final int t = pt.t; 400 401 if (x < minX) 402 minX = x; 403 if (x > maxX) 404 maxX = x; 405 if (y < minY) 406 minY = y; 407 if (y > maxY) 408 maxY = y; 409 if (z < minZ) 410 minZ = z; 411 if (z > maxZ) 412 maxZ = z; 413 if (t < minT) 414 minT = t; 415 if (t > maxT) 416 maxT = t; 417 } 418 419 // define bounds 420 bounds = new Rectangle4D.Integer(minX, minY, minZ, minT, (maxX - minX) + 1, (maxY - minY) + 1, 421 (maxZ - minZ) + 1, (maxT - minT) + 1); 422 423 // set mask 424 for (Point4D.Integer pt : points) 425 { 426 BooleanMask3D m3d = mask.get(Integer.valueOf(pt.t)); 427 428 // allocate 3D boolean mask if needed 429 if (m3d == null) 430 { 431 m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY, 432 bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]); 433 // set 3D mask for position T 434 mask.put(Integer.valueOf(pt.t), m3d); 435 } 436 437 BooleanMask2D m2d = m3d.getMask2D(pt.z); 438 439 // allocate 2D boolean mask if needed 440 if (m2d == null) 441 { 442 m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY), 443 new boolean[bounds.sizeX * bounds.sizeY]); 444 // set 2D mask for position Z 445 m3d.mask.put(Integer.valueOf(pt.z), m2d); 446 } 447 448 // set mask point 449 m2d.mask[((pt.y - minY) * bounds.sizeX) + (pt.x - minX)] = true; 450 } 451 452 // optimize mask 3D bounds 453 for (BooleanMask3D m : mask.values()) 454 m.optimizeBounds(); 455 } 456 } 457 458 /** 459 * Build a new boolean mask from the specified array of {@link Point4D}.<br> 460 */ 461 public BooleanMask4D(Point4D[] points) 462 { 463 super(); 464 465 mask = new TreeMap<Integer, BooleanMask3D>(); 466 467 if ((points == null) || (points.length == 0)) 468 bounds = new Rectangle4D.Integer(); 469 else 470 { 471 int minX = Integer.MAX_VALUE; 472 int minY = Integer.MAX_VALUE; 473 int minZ = Integer.MAX_VALUE; 474 int minT = Integer.MAX_VALUE; 475 int maxX = Integer.MIN_VALUE; 476 int maxY = Integer.MIN_VALUE; 477 int maxZ = Integer.MIN_VALUE; 478 int maxT = Integer.MIN_VALUE; 479 480 for (Point4D pt : points) 481 { 482 final int x = (int) pt.getX(); 483 final int y = (int) pt.getY(); 484 final int z = (int) pt.getZ(); 485 final int t = (int) pt.getT(); 486 487 if (x < minX) 488 minX = x; 489 if (x > maxX) 490 maxX = x; 491 if (y < minY) 492 minY = y; 493 if (y > maxY) 494 maxY = y; 495 if (z < minZ) 496 minZ = z; 497 if (z > maxZ) 498 maxZ = z; 499 if (t < minT) 500 minT = t; 501 if (t > maxT) 502 maxT = t; 503 } 504 505 // define bounds 506 bounds = new Rectangle4D.Integer(minX, minY, minZ, minT, (maxX - minX) + 1, (maxY - minY) + 1, 507 (maxZ - minZ) + 1, (maxT - minT) + 1); 508 509 // set mask 510 for (Point4D pt : points) 511 { 512 BooleanMask3D m3d = mask.get(Integer.valueOf((int) pt.getT())); 513 514 // allocate 3D boolean mask if needed 515 if (m3d == null) 516 { 517 m3d = new BooleanMask3D(new Rectangle3D.Integer(minX, minY, minZ, bounds.sizeX, bounds.sizeY, 518 bounds.sizeZ), new BooleanMask2D[bounds.sizeZ]); 519 // set 3D mask for position T 520 mask.put(Integer.valueOf((int) pt.getT()), m3d); 521 } 522 523 BooleanMask2D m2d = m3d.getMask2D((int) pt.getZ()); 524 525 // allocate 2D boolean mask if needed 526 if (m2d == null) 527 { 528 m2d = new BooleanMask2D(new Rectangle(minX, minY, bounds.sizeX, bounds.sizeY), 529 new boolean[bounds.sizeX * bounds.sizeY]); 530 // set 2D mask for position Z 531 m3d.mask.put(Integer.valueOf((int) pt.getZ()), m2d); 532 } 533 534 // set mask point 535 m2d.mask[(((int) pt.getY() - minY) * bounds.sizeX) + ((int) pt.getX() - minX)] = true; 536 } 537 538 // optimize mask 3D bounds 539 for (BooleanMask3D m : mask.values()) 540 m.optimizeBounds(); 541 } 542 } 543 544 public BooleanMask4D() 545 { 546 this(new Rectangle4D.Integer(), new BooleanMask3D[0]); 547 } 548 549 /** 550 * Returns the 3D boolean mask for the specified T position 551 */ 552 public BooleanMask3D getMask3D(int t) 553 { 554 // special case of infinite T dimension 555 if (bounds.sizeT == Integer.MAX_VALUE) 556 return mask.firstEntry().getValue(); 557 558 return mask.get(Integer.valueOf(t)); 559 } 560 561 /** 562 * Returns the 2D boolean mask for the specified Z, T position 563 */ 564 public BooleanMask2D getMask2D(int z, int t) 565 { 566 final BooleanMask3D m = getMask3D(t); 567 568 if (m != null) 569 return m.getMask2D(z); 570 571 return null; 572 } 573 574 /** 575 * Return <code>true</code> if boolean mask is empty 576 */ 577 public boolean isEmpty() 578 { 579 return bounds.isEmpty(); 580 } 581 582 /** 583 * Return true if mask contains the specified point 584 */ 585 public boolean contains(int x, int y, int z, int t) 586 { 587 if (bounds.contains(x, y, z, t)) 588 { 589 final BooleanMask3D m3d = getMask3D(t); 590 591 if (m3d != null) 592 return m3d.contains(x, y, z); 593 } 594 595 return false; 596 } 597 598 /** 599 * Return true if mask contains the specified 2D mask at position Z, T. 600 */ 601 public boolean contains(BooleanMask2D booleanMask, int z, int t) 602 { 603 if (isEmpty()) 604 return false; 605 606 final BooleanMask2D mask2d = getMask2D(z, t); 607 608 if (mask2d != null) 609 return mask2d.contains(booleanMask); 610 611 return false; 612 } 613 614 /** 615 * Return true if mask contains the specified 3D mask at position t. 616 */ 617 public boolean contains(BooleanMask3D booleanMask, int t) 618 { 619 if (isEmpty()) 620 return false; 621 622 final BooleanMask3D mask3d = getMask3D(t); 623 624 if (mask3d != null) 625 return mask3d.contains(booleanMask); 626 627 return false; 628 } 629 630 /** 631 * Return true if mask contains the specified 4D mask. 632 */ 633 public boolean contains(BooleanMask4D booleanMask) 634 { 635 if (isEmpty()) 636 return false; 637 638 final int sizeT = booleanMask.bounds.sizeT; 639 640 // check for special MAX_INTEGER case (infinite T dim) 641 if (sizeT == Integer.MAX_VALUE) 642 { 643 // we cannot contains it if we are not on infinite T dim too 644 if (bounds.sizeT != Integer.MAX_VALUE) 645 return false; 646 647 return booleanMask.mask.firstEntry().getValue().contains(mask.firstEntry().getValue()); 648 } 649 650 final int offT = booleanMask.bounds.t; 651 652 for (int t = offT; t < offT + sizeT; t++) 653 if (!contains(booleanMask.getMask3D(t), t)) 654 return false; 655 656 return true; 657 } 658 659 /** 660 * Return true if mask intersects (contains at least one point) the specified 2D mask at 661 * position Z, T 662 */ 663 public boolean intersects(BooleanMask2D booleanMask, int z, int t) 664 { 665 if (isEmpty()) 666 return false; 667 668 final BooleanMask2D mask2d = getMask2D(z, t); 669 670 if (mask2d != null) 671 return mask2d.intersects(booleanMask); 672 673 return false; 674 } 675 676 /** 677 * Return true if mask intersects (contains at least one point) the specified 3D mask at 678 * position T 679 */ 680 public boolean intersects(BooleanMask3D booleanMask, int t) 681 { 682 if (isEmpty()) 683 return false; 684 685 final BooleanMask3D mask3d = getMask3D(t); 686 687 if (mask3d != null) 688 return mask3d.intersects(booleanMask); 689 690 return false; 691 } 692 693 /** 694 * Return true if mask intersects (contains at least one point) the specified 4D mask region 695 */ 696 public boolean intersects(BooleanMask4D booleanMask) 697 { 698 if (isEmpty()) 699 return false; 700 701 final int sizeT = booleanMask.bounds.sizeT; 702 703 // check for special MAX_INTEGER case (infinite T dim) 704 if (sizeT == Integer.MAX_VALUE) 705 { 706 // get the single T slice 707 final BooleanMask3D mask3d = booleanMask.mask.firstEntry().getValue(); 708 709 // test with every slice 710 for (BooleanMask3D m : mask.values()) 711 if (m.intersects(mask3d)) 712 return true; 713 714 return false; 715 } 716 717 // check for special MAX_INTEGER case (infinite T dim) 718 if (bounds.sizeT == Integer.MAX_VALUE) 719 { 720 // get the single T slice 721 final BooleanMask3D mask3d = mask.firstEntry().getValue(); 722 723 // test with every slice 724 for (BooleanMask3D m : booleanMask.mask.values()) 725 if (m.intersects(mask3d)) 726 return true; 727 728 return false; 729 } 730 731 final int offT = booleanMask.bounds.t; 732 733 for (int t = offT; t < offT + sizeT; t++) 734 if (intersects(booleanMask.getMask3D(t), t)) 735 return true; 736 737 return false; 738 } 739 740 /** 741 * Optimize mask bounds so it fits mask content. 742 */ 743 public Rectangle4D.Integer getOptimizedBounds(boolean compute3DBounds) 744 { 745 final Rectangle4D.Integer result = new Rectangle4D.Integer(); 746 747 if (mask.isEmpty()) 748 return result; 749 750 Rectangle3D.Integer bounds3D = null;// new Rectangle3D.Integer(); 751 752 for (BooleanMask3D m3d : mask.values()) 753 { 754 // get optimized 3D bounds for each T 755 final Rectangle3D.Integer optB3d; 756 757 if (compute3DBounds) 758 optB3d = m3d.getOptimizedBounds(); 759 else 760 optB3d = new Rectangle3D.Integer(m3d.bounds); 761 762 // only add non empty bounds 763 if (!optB3d.isEmpty()) 764 { 765 if (bounds3D == null) 766 bounds3D = optB3d; 767 else 768 bounds3D.add(optB3d); 769 } 770 } 771 772 // empty ? 773 if ((bounds3D == null) || bounds3D.isEmpty()) 774 return result; 775 776 int minT = mask.firstKey().intValue(); 777 int maxT = mask.lastKey().intValue(); 778 779 // set 3D bounds to start with 780 result.setX(bounds3D.x); 781 result.setY(bounds3D.y); 782 result.setZ(bounds3D.z); 783 result.setSizeX(bounds3D.sizeX); 784 result.setSizeY(bounds3D.sizeY); 785 result.setSizeZ(bounds3D.sizeZ); 786 787 // single T --> check for special MAX_INTEGER case 788 if ((minT == maxT) && (bounds.sizeT == Integer.MAX_VALUE)) 789 { 790 result.setT(Integer.MIN_VALUE); 791 result.setSizeT(Integer.MAX_VALUE); 792 } 793 else 794 { 795 result.setT(minT); 796 result.setSizeT((maxT - minT) + 1); 797 } 798 799 return result; 800 } 801 802 /** 803 * Optimize mask bounds so it fits mask content. 804 */ 805 public Rectangle4D.Integer getOptimizedBounds() 806 { 807 return getOptimizedBounds(true); 808 } 809 810 /** 811 * Optimize mask bounds so it fits mask content. 812 */ 813 public void optimizeBounds() 814 { 815 // start by optimizing 3D bounds 816 for (BooleanMask3D m : mask.values()) 817 m.optimizeBounds(); 818 819 moveBounds(getOptimizedBounds(false)); 820 } 821 822 /** 823 * Change the bounds of BooleanMask.<br> 824 * Keep mask data intersecting from old bounds. 825 */ 826 public void moveBounds(Rectangle4D.Integer value) 827 { 828 // bounds changed ? 829 if (!bounds.equals(value)) 830 { 831 // changed to empty mask 832 if (value.isEmpty()) 833 { 834 // clear bounds and mask 835 bounds = new Rectangle4D.Integer(); 836 mask.clear(); 837 return; 838 } 839 840 final Rectangle3D.Integer bounds3D = new Rectangle3D.Integer(value.x, value.y, value.z, value.sizeX, 841 value.sizeY, value.sizeZ); 842 843 // it was infinite T dim ? 844 if (bounds.sizeT == Integer.MAX_VALUE) 845 { 846 // get the single 3D mask 847 final BooleanMask3D m3d = mask.firstEntry().getValue(); 848 849 // adjust 3D bounds if needed to the single 3D mask 850 m3d.moveBounds(bounds3D); 851 852 // we passed from infinite T to defined T range 853 if (value.sizeT != Integer.MAX_VALUE) 854 { 855 // assign the same 3D mask for all C position 856 mask.clear(); 857 for (int t = 0; t <= value.sizeT; t++) 858 mask.put(Integer.valueOf(t + value.t), (BooleanMask3D) m3d.clone()); 859 } 860 } 861 // we pass to infinite T dim 862 else if (value.sizeT == Integer.MAX_VALUE) 863 { 864 // try to use the 3D mask at T position 865 BooleanMask3D mask3D = getMask3D(value.t); 866 867 // otherwise we use the first found 3D mask 868 if ((mask3D == null) && !mask.isEmpty()) 869 mask3D = mask.firstEntry().getValue(); 870 871 // set new mask 872 mask.clear(); 873 if (mask3D != null) 874 mask.put(Integer.valueOf(Integer.MIN_VALUE), mask3D); 875 } 876 else 877 { 878 // create new mask array 879 final BooleanMask3D[] newMask = new BooleanMask3D[value.sizeT]; 880 881 for (int t = 0; t < value.sizeT; t++) 882 { 883 final BooleanMask3D mask3D = getMask3D(value.t + t); 884 885 if (mask3D != null) 886 // adjust 3D bounds 887 mask3D.moveBounds(bounds3D); 888 889 newMask[t] = mask3D; 890 } 891 892 // set new mask 893 mask.clear(); 894 for (int t = 0; t < value.sizeT; t++) 895 mask.put(Integer.valueOf(value.t + t), newMask[t]); 896 } 897 898 bounds = value; 899 } 900 } 901 902 /** 903 * Transforms the specified 3D coordinates int array [x,y,z] in 4D coordinates int array [x,y,z,t] with the 904 * specified T value. 905 */ 906 public static int[] toInt4D(int[] source3D, int t) 907 { 908 final int[] result = new int[(source3D.length * 4) / 3]; 909 910 int pt = 0; 911 for (int i = 0; i < source3D.length; i += 3) 912 { 913 result[pt++] = source3D[i + 0]; 914 result[pt++] = source3D[i + 1]; 915 result[pt++] = source3D[i + 2]; 916 result[pt++] = t; 917 } 918 919 return result; 920 } 921 922 /** 923 * Return an array of {@link icy.type.point.Point4D.Integer} containing the contour/surface 924 * points 925 * of the 4D mask.<br> 926 * Points are returned in ascending XYZT order. <br> 927 * <br> 928 * WARNING: The basic implementation is not totally accurate.<br> 929 * It returns all points from the first and the last T slices + contour points for intermediate 930 * T 931 * slices. 932 * 933 * @see #getContourPointsAsIntArray() 934 */ 935 public Point4D.Integer[] getContourPoints() 936 { 937 return Point4D.Integer.toPoint4D(getContourPointsAsIntArray()); 938 } 939 940 /** 941 * Return an array of integer containing the contour/surface points of the 4D mask.<br> 942 * <code>result.length</code> = number of point * 4<br> 943 * <code>result[(pt * 4) + 0]</code> = X coordinate for point <i>pt</i>.<br> 944 * <code>result[(pt * 4) + 1]</code> = Y coordinate for point <i>pt</i>.<br> 945 * <code>result[(pt * 4) + 2]</code> = Z coordinate for point <i>pt</i>.<br> 946 * <code>result[(pt * 4) + 3]</code> = T coordinate for point <i>pt</i>.<br> 947 * Points are returned in ascending XYZT order.<br> 948 * <br> 949 * WARNING: The basic implementation is not totally accurate.<br> 950 * It returns all points from the first and the last T slices + contour points for intermediate 951 * T 952 * slices. 953 * 954 * @see #getContourPoints() 955 */ 956 public int[] getContourPointsAsIntArray() 957 { 958 final DynamicArray.Int result = new DynamicArray.Int(8); 959 960 // perimeter = first slice volume + inter slices perimeter + last slice volume 961 // TODO: fix this method and use real 4D contour point 962 if (mask.size() <= 2) 963 { 964 for (Entry<Integer, BooleanMask3D> entry : mask.entrySet()) 965 result.add(toInt4D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue())); 966 } 967 else 968 { 969 final Entry<Integer, BooleanMask3D> firstEntry = mask.firstEntry(); 970 final Entry<Integer, BooleanMask3D> lastEntry = mask.lastEntry(); 971 final Integer firstKey = firstEntry.getKey(); 972 final Integer lastKey = lastEntry.getKey(); 973 974 result.add(toInt4D(firstEntry.getValue().getPointsAsIntArray(), firstKey.intValue())); 975 976 for (Entry<Integer, BooleanMask3D> entry : mask.subMap(firstKey, false, lastKey, false).entrySet()) 977 result.add(toInt4D(entry.getValue().getContourPointsAsIntArray(), entry.getKey().intValue())); 978 979 result.add(toInt4D(lastEntry.getValue().getPointsAsIntArray(), lastKey.intValue())); 980 } 981 982 return result.asArray(); 983 } 984 985 /** 986 * Return the number of points contained in this boolean mask. 987 */ 988 public int getNumberOfPoints() 989 { 990 int result = 0; 991 992 for (BooleanMask3D mask3d : mask.values()) 993 result += mask3d.getNumberOfPoints(); 994 995 return result; 996 } 997 998 /** 999 * Return an array of {@link icy.type.point.Point4D.Integer} representing all points of the 1000 * current 4D mask.<br> 1001 * Points are returned in ascending XYZT order. 1002 */ 1003 public Point4D.Integer[] getPoints() 1004 { 1005 return Point4D.Integer.toPoint4D(getPointsAsIntArray()); 1006 } 1007 1008 /** 1009 * Return an array of integer representing all points of the current 4D mask.<br> 1010 * <code>result.length</code> = number of point * 4<br> 1011 * <code>result[(pt * 4) + 0]</code> = X coordinate for point <i>pt</i>.<br> 1012 * <code>result[(pt * 4) + 1]</code> = Y coordinate for point <i>pt</i>.<br> 1013 * <code>result[(pt * 4) + 2]</code> = Z coordinate for point <i>pt</i>.<br> 1014 * <code>result[(pt * 4) + 3]</code> = T coordinate for point <i>pt</i>.<br> 1015 * Points are returned in ascending XYZT order. 1016 */ 1017 public int[] getPointsAsIntArray() 1018 { 1019 final DynamicArray.Int result = new DynamicArray.Int(8); 1020 1021 for (Entry<Integer, BooleanMask3D> entry : mask.entrySet()) 1022 result.add(toInt4D(entry.getValue().getPointsAsIntArray(), entry.getKey().intValue())); 1023 1024 return result.asArray(); 1025 } 1026 1027 @Override 1028 public Object clone() 1029 { 1030 final BooleanMask4D result = new BooleanMask4D(); 1031 1032 result.bounds = new Rectangle4D.Integer(bounds); 1033 for (Entry<Integer, BooleanMask3D> entry : mask.entrySet()) 1034 result.mask.put(entry.getKey(), (BooleanMask3D) entry.getValue().clone()); 1035 1036 return result; 1037 } 1038}