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