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.sequence; 020 021import java.awt.Point; 022import java.awt.Rectangle; 023import java.awt.geom.Point2D; 024import java.awt.geom.Rectangle2D; 025import java.awt.image.BufferedImage; 026import java.util.ArrayList; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.TreeMap; 031 032import javax.swing.SwingConstants; 033 034import icy.common.listener.ProgressListener; 035import icy.image.IcyBufferedImage; 036import icy.image.IcyBufferedImageUtil; 037import icy.image.IcyBufferedImageUtil.FilterType; 038import icy.image.colormap.IcyColorMap; 039import icy.image.colormap.LinearColorMap; 040import icy.image.lut.LUT; 041import icy.math.Scaler; 042import icy.painter.Overlay; 043import icy.roi.BooleanMask2D; 044import icy.roi.ROI; 045import icy.type.DataType; 046import icy.type.collection.array.Array1DUtil; 047import icy.type.point.Point3D; 048import icy.type.rectangle.Rectangle3D; 049import icy.type.rectangle.Rectangle5D; 050import icy.util.OMEUtil; 051import icy.util.StringUtil; 052import ome.xml.meta.OMEXMLMetadata; 053 054/** 055 * {@link Sequence} utilities class.<br> 056 * You can find here tools to manipulate the sequence organization, its data type, its size... 057 * 058 * @author Stephane 059 */ 060public class SequenceUtil 061{ 062 public static class AddZHelper 063 { 064 public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition, 065 int numInsert, int copyLast) 066 { 067 if (z < insertPosition) 068 return sequence.getImage(t, z); 069 070 final int pos = z - insertPosition; 071 072 // return new image 073 if (pos < numInsert) 074 { 075 // return copy of previous image(s) 076 if ((insertPosition > 0) && (copyLast > 0)) 077 { 078 // should be < insert position 079 final int duplicate = Math.min(insertPosition, copyLast); 080 final int baseReplicate = insertPosition - duplicate; 081 082 return sequence.getImage(t, baseReplicate + (pos % duplicate)); 083 } 084 085 // return new empty image 086 return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(), 087 sequence.getDataType_()); 088 } 089 090 return sequence.getImage(t, z - numInsert); 091 } 092 } 093 094 public static class AddTHelper 095 { 096 public static IcyBufferedImage getExtendedImage(Sequence sequence, int t, int z, int insertPosition, 097 int numInsert, int copyLast) 098 { 099 if (t < insertPosition) 100 return sequence.getImage(t, z); 101 102 final int pos = t - insertPosition; 103 104 // return new image 105 if (pos < numInsert) 106 { 107 // return copy of previous image(s) 108 if ((insertPosition > 0) && (copyLast > 0)) 109 { 110 // should be < insert position 111 final int duplicate = Math.min(insertPosition, copyLast); 112 final int baseReplicate = insertPosition - duplicate; 113 114 return sequence.getImage(baseReplicate + (pos % duplicate), z); 115 } 116 117 // return new empty image 118 return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(), 119 sequence.getDataType_()); 120 } 121 122 return sequence.getImage(t - numInsert, z); 123 } 124 } 125 126 public static class MergeCHelper 127 { 128 private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, int c, 129 boolean fillEmpty) 130 { 131 IcyBufferedImage img = seq.getImage(t, z, c); 132 133 if ((img == null) && fillEmpty) 134 { 135 int curZ = z; 136 137 // missing Z slice ? 138 if (z >= seq.getSizeZ()) 139 { 140 // searching in previous slice 141 while ((img == null) && (curZ > 0)) 142 img = seq.getImage(t, --curZ, c); 143 } 144 145 if (img == null) 146 { 147 int curT = t; 148 149 // searching in previous frame 150 while ((img == null) && (curT > 0)) 151 img = seq.getImage(--curT, z, c); 152 } 153 154 return img; 155 } 156 157 return img; 158 } 159 160 public static IcyBufferedImage getImage(Sequence[] sequences, int[] channels, int sizeX, int sizeY, int t, 161 int z, boolean fillEmpty, boolean rescale) throws IllegalArgumentException 162 { 163 if (sequences.length == 0) 164 return null; 165 166 final List<BufferedImage> images = new ArrayList<BufferedImage>(); 167 final List<IcyColorMap> colormaps = new ArrayList<IcyColorMap>(); 168 169 for (int i = 0; i < sequences.length; i++) 170 { 171 final Sequence seq = sequences[i]; 172 final int c = channels[i]; 173 174 // get colormap 175 colormaps.add(seq.getColorMap(c)); 176 // get image 177 IcyBufferedImage img = getImageFromSequenceInternal(seq, t, z, c, fillEmpty); 178 179 // create an empty image 180 if (img == null) 181 img = new IcyBufferedImage(sizeX, sizeY, 1, seq.getDataType_()); 182 // resize X and Y dimension if needed 183 else if ((img.getSizeX() != sizeX) || (img.getSizeY() != sizeY)) 184 img = IcyBufferedImageUtil.scale(img, sizeX, sizeY, rescale, SwingConstants.CENTER, 185 SwingConstants.CENTER, FilterType.BILINEAR); 186 187 images.add(img); 188 } 189 190 final IcyBufferedImage result = IcyBufferedImage.createFrom(images); 191 192 // restore colormap 193 for (int c = 0; c < result.getSizeC(); c++) 194 { 195 final IcyColorMap map = colormaps.get(c); 196 197 if (map != null) 198 result.setColorMap(c, colormaps.get(c), false); 199 } 200 201 return result; 202 } 203 } 204 205 public static class MergeZHelper 206 { 207 private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty) 208 { 209 IcyBufferedImage img = seq.getImage(t, z); 210 211 if ((img == null) && fillEmpty) 212 { 213 int curZ = z; 214 215 // missing Z slice ? 216 if (z >= seq.getSizeZ()) 217 { 218 // searching in previous slice 219 while ((img == null) && (curZ > 0)) 220 img = seq.getImage(t, --curZ); 221 } 222 223 if (img == null) 224 { 225 int curT = t; 226 227 // searching in previous frame 228 while ((img == null) && (curT > 0)) 229 img = seq.getImage(--curT, z); 230 } 231 232 return img; 233 } 234 235 return img; 236 } 237 238 private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced, 239 boolean fillEmpty) 240 { 241 int zRemaining = z; 242 243 if (interlaced) 244 { 245 int zInd = 0; 246 247 while (zRemaining >= 0) 248 { 249 for (Sequence seq : sequences) 250 { 251 if (zInd < seq.getSizeZ()) 252 { 253 if (zRemaining-- == 0) 254 return getImageFromSequenceInternal(seq, t, zInd, fillEmpty); 255 } 256 } 257 258 zInd++; 259 } 260 } 261 else 262 { 263 for (Sequence seq : sequences) 264 { 265 final int sizeZ = seq.getSizeZ(); 266 267 // we found the sequence 268 if (zRemaining < sizeZ) 269 return getImageFromSequenceInternal(seq, t, zRemaining, fillEmpty); 270 271 zRemaining -= sizeZ; 272 } 273 } 274 275 return null; 276 } 277 278 public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z, 279 boolean interlaced, boolean fillEmpty, boolean rescale) 280 { 281 IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty); 282 283 if (result != null) 284 { 285 // resize X and Y dimension if needed 286 if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY)) 287 result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER, 288 SwingConstants.CENTER, FilterType.BILINEAR); 289 290 final int imgSizeC = result.getSizeC(); 291 292 // resize C dimension if needed 293 if (imgSizeC < sizeC) 294 return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC); 295 } 296 297 return result; 298 } 299 } 300 301 public static class MergeTHelper 302 { 303 private static IcyBufferedImage getImageFromSequenceInternal(Sequence seq, int t, int z, boolean fillEmpty) 304 { 305 IcyBufferedImage img = seq.getImage(t, z); 306 307 if ((img == null) && fillEmpty) 308 { 309 int curT = t; 310 311 // missing T frame? 312 if (t >= seq.getSizeT()) 313 { 314 // searching in previous frame 315 while ((img == null) && (curT > 0)) 316 img = seq.getImage(--curT, z); 317 } 318 319 if (img == null) 320 { 321 int curZ = z; 322 323 // searching in previous slice 324 while ((img == null) && (curZ > 0)) 325 img = seq.getImage(t, --curZ); 326 } 327 328 return img; 329 } 330 331 return img; 332 } 333 334 private static IcyBufferedImage getImageInternal(Sequence[] sequences, int t, int z, boolean interlaced, 335 boolean fillEmpty) 336 { 337 int tRemaining = t; 338 339 if (interlaced) 340 { 341 int tInd = 0; 342 343 while (tRemaining >= 0) 344 { 345 for (Sequence seq : sequences) 346 { 347 if (tInd < seq.getSizeT()) 348 { 349 if (tRemaining-- == 0) 350 return getImageFromSequenceInternal(seq, tInd, z, fillEmpty); 351 } 352 } 353 354 tInd++; 355 } 356 } 357 else 358 { 359 for (Sequence seq : sequences) 360 { 361 final int sizeT = seq.getSizeT(); 362 363 // we found the sequence 364 if (tRemaining < sizeT) 365 return getImageFromSequenceInternal(seq, tRemaining, z, fillEmpty); 366 367 tRemaining -= sizeT; 368 } 369 } 370 371 return null; 372 } 373 374 public static IcyBufferedImage getImage(Sequence[] sequences, int sizeX, int sizeY, int sizeC, int t, int z, 375 boolean interlaced, boolean fillEmpty, boolean rescale) 376 { 377 IcyBufferedImage result = getImageInternal(sequences, t, z, interlaced, fillEmpty); 378 379 if (result != null) 380 { 381 // resize X and Y dimension if needed 382 if ((result.getSizeX() != sizeX) || (result.getSizeY() != sizeY)) 383 result = IcyBufferedImageUtil.scale(result, sizeX, sizeY, rescale, SwingConstants.CENTER, 384 SwingConstants.CENTER, FilterType.BILINEAR); 385 386 final int imgSizeC = result.getSizeC(); 387 388 // resize C dimension if needed 389 if (imgSizeC < sizeC) 390 return IcyBufferedImageUtil.addChannels(result, imgSizeC, sizeC - imgSizeC); 391 } 392 393 return result; 394 } 395 } 396 397 public static class AdjustZTHelper 398 { 399 public static IcyBufferedImage getImage(Sequence sequence, int t, int z, int newSizeZ, int newSizeT, 400 boolean reverseOrder) 401 { 402 final int sizeZ = sequence.getSizeZ(); 403 final int sizeT = sequence.getSizeT(); 404 405 // out of range 406 if ((t >= newSizeT) || (z >= newSizeZ)) 407 return null; 408 409 final int index; 410 411 // calculate index of wanted image 412 if (reverseOrder) 413 index = (z * newSizeT) + t; 414 else 415 index = (t * newSizeZ) + z; 416 417 final int tOrigin = index / sizeZ; 418 final int zOrigin = index % sizeZ; 419 420 // bounding --> return new image 421 if (tOrigin >= sizeT) 422 return new IcyBufferedImage(sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeC(), 423 sequence.getDataType_()); 424 425 return sequence.getImage(tOrigin, zOrigin); 426 } 427 } 428 429 /** 430 * Add one or severals frames at position t. 431 * 432 * @param t 433 * Position where to add frame(s) 434 * @param num 435 * Number of frame to add 436 * @param copyLast 437 * Number of last frame(s) to copy to fill added frames.<br> 438 * 0 means that new frames are empty.<br> 439 * 1 means we duplicate the last frame.<br> 440 * 2 means we duplicate the two last frames.<br> 441 * and so on... 442 */ 443 public static void addT(Sequence sequence, int t, int num, int copyLast) 444 { 445 final int sizeZ = sequence.getSizeZ(); 446 final int sizeT = sequence.getSizeT(); 447 448 sequence.beginUpdate(); 449 try 450 { 451 moveT(sequence, t, sizeT - 1, num); 452 453 for (int i = 0; i < num; i++) 454 for (int z = 0; z < sizeZ; z++) 455 sequence.setImage(t + i, z, IcyBufferedImageUtil 456 .getCopy(AddTHelper.getExtendedImage(sequence, t + i, z, t, num, copyLast))); 457 } 458 finally 459 { 460 sequence.endUpdate(); 461 } 462 } 463 464 /** 465 * Add one or severals frames at position t. 466 * 467 * @param t 468 * Position where to add frame(s) 469 * @param num 470 * Number of frame to add 471 */ 472 public static void addT(Sequence sequence, int t, int num) 473 { 474 addT(sequence, t, num, 0); 475 } 476 477 /** 478 * Add one or severals frames at position t. 479 * 480 * @param num 481 * Number of frame to add 482 */ 483 public static void addT(Sequence sequence, int num) 484 { 485 addT(sequence, sequence.getSizeT(), num, 0); 486 } 487 488 /** 489 * Add one or severals frames at position t. 490 * 491 * @param num 492 * Number of frame to add 493 * @param copyLast 494 * If true then the last frame is copied in added frames. 495 */ 496 public static void addT(Sequence sequence, int num, boolean copyLast) 497 { 498 addT(sequence, sequence.getSizeT(), num, 0); 499 } 500 501 /** 502 * Exchange 2 frames position on the sequence. 503 */ 504 public static void swapT(Sequence sequence, int t1, int t2) 505 { 506 final int sizeT = sequence.getSizeT(); 507 508 if ((t1 < 0) || (t2 < 0) || (t1 >= sizeT) || (t2 >= sizeT)) 509 return; 510 511 // get volume images at position t1 & t2 512 final VolumetricImage vi1 = sequence.getVolumetricImage(t1); 513 final VolumetricImage vi2 = sequence.getVolumetricImage(t2); 514 515 sequence.beginUpdate(); 516 try 517 { 518 // start by removing old volume image (if any) 519 sequence.removeAllImages(t1); 520 sequence.removeAllImages(t2); 521 522 // safe volume image copy (TODO : check if we can't direct set volume image internally) 523 if (vi1 != null) 524 { 525 final Map<Integer, IcyBufferedImage> images = vi1.getImages(); 526 527 // copy images of volume image 1 at position t2 528 for (Entry<Integer, IcyBufferedImage> entry : images.entrySet()) 529 sequence.setImage(t2, entry.getKey().intValue(), entry.getValue()); 530 } 531 if (vi2 != null) 532 { 533 final Map<Integer, IcyBufferedImage> images = vi2.getImages(); 534 535 // copy images of volume image 2 at position t1 536 for (Entry<Integer, IcyBufferedImage> entry : images.entrySet()) 537 sequence.setImage(t1, entry.getKey().intValue(), entry.getValue()); 538 } 539 } 540 finally 541 { 542 sequence.endUpdate(); 543 } 544 } 545 546 /** 547 * Modify frame position.<br> 548 * The previous frame present at <code>newT</code> position is lost. 549 * 550 * @param sequence 551 * @param t 552 * current t position 553 * @param newT 554 * wanted t position 555 */ 556 public static void moveT(Sequence sequence, int t, int newT) 557 { 558 final int sizeT = sequence.getSizeT(); 559 560 if ((t < 0) || (t >= sizeT) || (newT < 0) || (t == newT)) 561 return; 562 563 // get volume image at position t 564 final VolumetricImage vi = sequence.getVolumetricImage(t); 565 566 sequence.beginUpdate(); 567 try 568 { 569 // remove volume image (if any) at position newT 570 sequence.removeAllImages(newT); 571 572 if (vi != null) 573 { 574 final TreeMap<Integer, IcyBufferedImage> images = vi.getImages(); 575 576 // copy images of volume image at position newT 577 for (Entry<Integer, IcyBufferedImage> entry : images.entrySet()) 578 sequence.setImage(newT, entry.getKey().intValue(), entry.getValue()); 579 580 // remove volume image at position t 581 sequence.removeAllImages(t); 582 } 583 } 584 finally 585 { 586 sequence.endUpdate(); 587 } 588 } 589 590 /** 591 * Modify T position of a range of frame by the specified offset 592 * 593 * @param sequence 594 * @param from 595 * start of range (t position) 596 * @param to 597 * end of range (t position) 598 * @param offset 599 * position shift 600 */ 601 public static void moveT(Sequence sequence, int from, int to, int offset) 602 { 603 sequence.beginUpdate(); 604 try 605 { 606 if (offset > 0) 607 { 608 for (int t = to; t >= from; t--) 609 moveT(sequence, t, t + offset); 610 } 611 else 612 { 613 for (int t = from; t <= to; t++) 614 moveT(sequence, t, t + offset); 615 } 616 } 617 finally 618 { 619 sequence.endUpdate(); 620 } 621 } 622 623 /** 624 * Remove a frame at position t. 625 * 626 * @param sequence 627 * @param t 628 */ 629 public static void removeT(Sequence sequence, int t) 630 { 631 final int sizeT = sequence.getSizeT(); 632 633 if ((t < 0) || (t >= sizeT)) 634 return; 635 636 sequence.removeAllImages(t); 637 } 638 639 /** 640 * Remove a frame at position t and shift all the further t by -1. 641 * 642 * @param sequence 643 * @param t 644 */ 645 public static void removeTAndShift(Sequence sequence, int t) 646 { 647 final int sizeT = sequence.getSizeT(); 648 649 if ((t < 0) || (t >= sizeT)) 650 return; 651 652 sequence.beginUpdate(); 653 try 654 { 655 removeT(sequence, t); 656 moveT(sequence, t + 1, sizeT - 1, -1); 657 } 658 finally 659 { 660 sequence.endUpdate(); 661 } 662 } 663 664 /** 665 * Reverse T frames order. 666 */ 667 public static void reverseT(Sequence sequence) 668 { 669 final int sizeT = sequence.getSizeT(); 670 final int sizeZ = sequence.getSizeZ(); 671 672 final Sequence save = new Sequence(); 673 674 save.beginUpdate(); 675 try 676 { 677 for (int t = 0; t < sizeT; t++) 678 for (int z = 0; z < sizeZ; z++) 679 save.setImage(t, z, sequence.getImage(t, z)); 680 } 681 finally 682 { 683 save.endUpdate(); 684 } 685 686 sequence.beginUpdate(); 687 try 688 { 689 sequence.removeAllImages(); 690 691 for (int t = 0; t < sizeT; t++) 692 for (int z = 0; z < sizeZ; z++) 693 sequence.setImage(sizeT - (t + 1), z, save.getImage(t, z)); 694 } 695 finally 696 { 697 sequence.endUpdate(); 698 } 699 700 // to avoid memory leak as images now contained in sequence will retain 'save' sequence forever 701 save.removeAllImages(); 702 } 703 704 /** 705 * Add one or severals slices at position z. 706 * 707 * @param z 708 * Position where to add slice(s) 709 * @param num 710 * Number of slice to add 711 * @param copyLast 712 * Number of last slice(s) to copy to fill added slices.<br> 713 * 0 means that new slices are empty.<br> 714 * 1 means we duplicate the last slice.<br> 715 * 2 means we duplicate the two last slices.<br> 716 * and so on... 717 */ 718 public static void addZ(Sequence sequence, int z, int num, int copyLast) 719 { 720 final int sizeZ = sequence.getSizeZ(); 721 final int sizeT = sequence.getSizeT(); 722 723 sequence.beginUpdate(); 724 try 725 { 726 moveZ(sequence, z, sizeZ - 1, num); 727 728 for (int i = 0; i < num; i++) 729 for (int t = 0; t < sizeT; t++) 730 sequence.setImage(t, z + i, IcyBufferedImageUtil 731 .getCopy(AddZHelper.getExtendedImage(sequence, t, z + i, z, num, copyLast))); 732 } 733 finally 734 { 735 sequence.endUpdate(); 736 } 737 } 738 739 /** 740 * Add one or severals slices at position z. 741 * 742 * @param z 743 * Position where to add slice(s) 744 * @param num 745 * Number of slice to add 746 */ 747 public static void addZ(Sequence sequence, int z, int num) 748 { 749 addZ(sequence, z, num, 0); 750 } 751 752 /** 753 * Add one or severals slices at position z. 754 * 755 * @param num 756 * Number of slice to add 757 */ 758 public static void addZ(Sequence sequence, int num) 759 { 760 addZ(sequence, sequence.getSizeZ(), num, 0); 761 } 762 763 /** 764 * Add one or severals slices at position z. 765 * 766 * @param num 767 * Number of slice to add 768 * @param copyLast 769 * If true then the last slice is copied in added slices. 770 */ 771 public static void addZ(Sequence sequence, int num, boolean copyLast) 772 { 773 addZ(sequence, sequence.getSizeZ(), num, 0); 774 } 775 776 /** 777 * Exchange 2 slices position on the sequence. 778 */ 779 public static void swapZ(Sequence sequence, int z1, int z2) 780 { 781 final int sizeZ = sequence.getSizeZ(); 782 final int sizeT = sequence.getSizeT(); 783 784 if ((z1 < 0) || (z2 < 0) || (z1 >= sizeZ) || (z2 >= sizeZ)) 785 return; 786 787 sequence.beginUpdate(); 788 try 789 { 790 for (int t = 0; t < sizeT; t++) 791 { 792 final IcyBufferedImage image1 = sequence.getImage(t, z1); 793 final IcyBufferedImage image2 = sequence.getImage(t, z2); 794 795 // set image at new position 796 if (image1 != null) 797 sequence.setImage(t, z2, image1); 798 else 799 sequence.removeImage(t, z2); 800 if (image2 != null) 801 sequence.setImage(t, z1, image2); 802 else 803 sequence.removeImage(t, z1); 804 } 805 } 806 finally 807 { 808 sequence.endUpdate(); 809 } 810 } 811 812 /** 813 * Modify slice position.<br> 814 * The previous slice present at <code>newZ</code> position is lost. 815 * 816 * @param sequence 817 * @param z 818 * current z position 819 * @param newZ 820 * wanted z position 821 */ 822 public static void moveZ(Sequence sequence, int z, int newZ) 823 { 824 final int sizeZ = sequence.getSizeZ(); 825 final int sizeT = sequence.getSizeT(); 826 827 if ((z < 0) || (z >= sizeZ) || (newZ < 0) || (z == newZ)) 828 return; 829 830 sequence.beginUpdate(); 831 try 832 { 833 for (int t = 0; t < sizeT; t++) 834 { 835 final IcyBufferedImage image = sequence.getImage(t, z); 836 837 if (image != null) 838 { 839 // set image at new position 840 sequence.setImage(t, newZ, image); 841 // and remove image at old position z 842 sequence.removeImage(t, z); 843 } 844 else 845 // just set null image at new position (equivalent to no image) 846 sequence.removeImage(t, newZ); 847 } 848 } 849 finally 850 { 851 sequence.endUpdate(); 852 } 853 } 854 855 /** 856 * Modify Z position of a range of slice by the specified offset 857 * 858 * @param sequence 859 * @param from 860 * start of range (z position) 861 * @param to 862 * end of range (z position) 863 * @param offset 864 * position shift 865 */ 866 public static void moveZ(Sequence sequence, int from, int to, int offset) 867 { 868 sequence.beginUpdate(); 869 try 870 { 871 if (offset > 0) 872 { 873 for (int z = to; z >= from; z--) 874 moveZ(sequence, z, z + offset); 875 } 876 else 877 { 878 for (int z = from; z <= to; z++) 879 moveZ(sequence, z, z + offset); 880 } 881 } 882 finally 883 { 884 sequence.endUpdate(); 885 } 886 } 887 888 /** 889 * Remove a slice at position Z. 890 * 891 * @param sequence 892 * @param z 893 */ 894 public static void removeZ(Sequence sequence, int z) 895 { 896 final int sizeZ = sequence.getSizeZ(); 897 898 if ((z < 0) || (z >= sizeZ)) 899 return; 900 901 sequence.beginUpdate(); 902 try 903 { 904 final int maxT = sequence.getSizeT(); 905 906 for (int t = 0; t < maxT; t++) 907 sequence.removeImage(t, z); 908 } 909 finally 910 { 911 sequence.endUpdate(); 912 } 913 } 914 915 /** 916 * Remove a slice at position t and shift all the further t by -1. 917 * 918 * @param sequence 919 * @param z 920 */ 921 public static void removeZAndShift(Sequence sequence, int z) 922 { 923 final int sizeZ = sequence.getSizeZ(); 924 925 if ((z < 0) || (z >= sizeZ)) 926 return; 927 928 sequence.beginUpdate(); 929 try 930 { 931 removeZ(sequence, z); 932 moveZ(sequence, z + 1, sizeZ - 1, -1); 933 } 934 finally 935 { 936 sequence.endUpdate(); 937 } 938 } 939 940 /** 941 * Reverse Z slices order. 942 */ 943 public static void reverseZ(Sequence sequence) 944 { 945 final int sizeT = sequence.getSizeT(); 946 final int sizeZ = sequence.getSizeZ(); 947 948 final Sequence save = new Sequence(); 949 950 save.beginUpdate(); 951 try 952 { 953 for (int t = 0; t < sizeT; t++) 954 for (int z = 0; z < sizeZ; z++) 955 save.setImage(t, z, sequence.getImage(t, z)); 956 } 957 finally 958 { 959 save.endUpdate(); 960 } 961 962 sequence.beginUpdate(); 963 try 964 { 965 sequence.removeAllImages(); 966 967 for (int t = 0; t < sizeT; t++) 968 for (int z = 0; z < sizeZ; z++) 969 sequence.setImage(t, sizeZ - (z + 1), save.getImage(t, z)); 970 } 971 finally 972 { 973 sequence.endUpdate(); 974 } 975 976 // to avoid memory leak as images now contained in sequence will retain 'save' sequence forever 977 save.removeAllImages(); 978 } 979 980 /** 981 * Set all images of the sequence in T dimension. 982 */ 983 public static void convertToTime(Sequence sequence) 984 { 985 sequence.beginUpdate(); 986 try 987 { 988 final List<IcyBufferedImage> images = sequence.getAllImage(); 989 990 sequence.removeAllImages(); 991 for (int i = 0; i < images.size(); i++) 992 sequence.setImage(i, 0, images.get(i)); 993 } 994 finally 995 { 996 sequence.endUpdate(); 997 } 998 } 999 1000 /** 1001 * Set all images of the sequence in Z dimension. 1002 */ 1003 public static void convertToStack(Sequence sequence) 1004 { 1005 sequence.beginUpdate(); 1006 try 1007 { 1008 final List<IcyBufferedImage> images = sequence.getAllImage(); 1009 1010 sequence.removeAllImages(); 1011 for (int i = 0; i < images.size(); i++) 1012 sequence.setImage(0, i, images.get(i)); 1013 } 1014 finally 1015 { 1016 sequence.endUpdate(); 1017 } 1018 } 1019 1020 /** 1021 * @deprecated Use {@link #convertToStack(Sequence)} instead. 1022 */ 1023 @Deprecated 1024 public static void convertToVolume(Sequence sequence) 1025 { 1026 convertToStack(sequence); 1027 } 1028 1029 /** 1030 * Remove the specified channel from the source sequence. 1031 * 1032 * @param source 1033 * Source sequence 1034 * @param channel 1035 * Channel index to remove 1036 */ 1037 public static void removeChannel(Sequence source, int channel) 1038 { 1039 final int sizeC = source.getSizeC(); 1040 1041 if (channel >= sizeC) 1042 return; 1043 1044 final int[] keep = new int[sizeC - 1]; 1045 1046 int i = 0; 1047 for (int c = 0; c < sizeC; c++) 1048 if (c != channel) 1049 keep[i++] = c; 1050 1051 final Sequence tmp = extractChannels(source, keep); 1052 1053 source.beginUpdate(); 1054 try 1055 { 1056 // we need to clear the source sequence to change its type 1057 source.removeAllImages(); 1058 1059 // get back all images 1060 for (int t = 0; t < tmp.getSizeT(); t++) 1061 for (int z = 0; z < tmp.getSizeZ(); z++) 1062 source.setImage(t, z, tmp.getImage(t, z)); 1063 1064 // get back modified metadata 1065 source.setMetaData(tmp.getOMEXMLMetadata()); 1066 // and colormaps 1067 for (int c = 0; c < tmp.getSizeC(); c++) 1068 source.setDefaultColormap(c, tmp.getDefaultColorMap(c), false); 1069 1070 // to avoid memory leak as images now contained in source will retain this sequence forever 1071 tmp.removeAllImages(); 1072 } 1073 finally 1074 { 1075 source.endUpdate(); 1076 } 1077 } 1078 1079 /** 1080 * Returns the max size of specified dimension for the given sequences. 1081 */ 1082 public static int getMaxDim(Sequence[] sequences, DimensionId dim) 1083 { 1084 int result = 0; 1085 1086 for (Sequence seq : sequences) 1087 { 1088 switch (dim) 1089 { 1090 case X: 1091 result = Math.max(result, seq.getSizeX()); 1092 break; 1093 case Y: 1094 result = Math.max(result, seq.getSizeY()); 1095 break; 1096 case C: 1097 result = Math.max(result, seq.getSizeC()); 1098 break; 1099 case Z: 1100 result = Math.max(result, seq.getSizeZ()); 1101 break; 1102 case T: 1103 result = Math.max(result, seq.getSizeT()); 1104 break; 1105 } 1106 } 1107 1108 return result; 1109 } 1110 1111 /** 1112 * Create and returns a new sequence by concatenating all given sequences on C dimension. 1113 * 1114 * @param sequences 1115 * Sequences to concatenate (use array order). 1116 * @param channels 1117 * Selected channel for each sequence (<code>channels.length = sequences.length</code>)<br> 1118 * If you want to select 2 or more channels from a sequence, just duplicate the sequence 1119 * entry in the <code>sequences</code> parameter :</code><br> 1120 * <code>sequences[n] = Sequence1; channels[n] = 0;</code><br> 1121 * <code>sequences[n+1] = Sequence1; channels[n+1] = 2;</code><br> 1122 * <code>...</code> 1123 * @param fillEmpty 1124 * Replace empty image by the previous non empty one. 1125 * @param rescale 1126 * Images are scaled to all fit in the same XY dimension. 1127 * @param pl 1128 * ProgressListener to indicate processing progress. 1129 * @throws IllegalArgumentException 1130 * if sequences contains incompatible sequence for merge operation. 1131 */ 1132 public static Sequence concatC(Sequence[] sequences, int[] channels, boolean fillEmpty, boolean rescale, 1133 ProgressListener pl) throws IllegalArgumentException 1134 { 1135 final int sizeX = getMaxDim(sequences, DimensionId.X); 1136 final int sizeY = getMaxDim(sequences, DimensionId.Y); 1137 final int sizeZ = getMaxDim(sequences, DimensionId.Z); 1138 final int sizeT = getMaxDim(sequences, DimensionId.T); 1139 1140 final Sequence result = new Sequence(); 1141 1142 if (sequences.length > 0) 1143 result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata())); 1144 result.setName("C Merge"); 1145 1146 int ind = 0; 1147 for (int t = 0; t < sizeT; t++) 1148 { 1149 for (int z = 0; z < sizeZ; z++) 1150 { 1151 if (pl != null) 1152 pl.notifyProgress(ind, sizeT * sizeZ); 1153 1154 result.setImage(t, z, 1155 MergeCHelper.getImage(sequences, channels, sizeX, sizeY, t, z, fillEmpty, rescale)); 1156 1157 ind++; 1158 } 1159 } 1160 1161 for (int i = 0; i < sequences.length; i++) 1162 { 1163 final Sequence seq = sequences[i]; 1164 final int c = channels[i]; 1165 final String channelName = seq.getChannelName(c); 1166 final IcyColorMap channelColor = seq.getColorMap(c); 1167 1168 // not default channel name --> we keep it 1169 if (!StringUtil.equals(seq.getDefaultChannelName(c), channelName)) 1170 result.setChannelName(i, channelName); 1171 1172 // not default white color map --> we keep it 1173 if (!channelColor.equals(LinearColorMap.white_)) 1174 result.setColormap(i, channelColor, true); 1175 } 1176 1177 return result; 1178 } 1179 1180 /** 1181 * Create and returns a new sequence by concatenating all given sequences on C dimension. 1182 * 1183 * @param sequences 1184 * Sequences to concatenate (use array order). 1185 * @param fillEmpty 1186 * Replace empty image by the previous non empty one. 1187 * @param rescale 1188 * Images are scaled to all fit in the same XY dimension. 1189 * @param pl 1190 * ProgressListener to indicate processing progress. 1191 */ 1192 public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale, ProgressListener pl) 1193 { 1194 // compute expanded sequence array and channels array length 1195 int len = 0; 1196 for (Sequence s : sequences) 1197 len += s.getSizeC(); 1198 1199 final Sequence[] newSequences = new Sequence[len]; 1200 final int[] channels = new int[len]; 1201 1202 // fill newSequences and channels arrays 1203 int ind = 0; 1204 for (Sequence s : sequences) 1205 { 1206 for (int c = 0; c < s.getSizeC(); c++) 1207 { 1208 newSequences[ind] = s; 1209 channels[ind] = c; 1210 ind++; 1211 1212 } 1213 } 1214 1215 return concatC(newSequences, channels, fillEmpty, rescale, pl); 1216 } 1217 1218 /** 1219 * Create and returns a new sequence by concatenating all given sequences on C dimension. 1220 * 1221 * @param sequences 1222 * Sequences to concatenate (use array order). 1223 * @param fillEmpty 1224 * Replace empty image by the previous non empty one. 1225 * @param rescale 1226 * Images are scaled to all fit in the same XY dimension. 1227 */ 1228 public static Sequence concatC(Sequence[] sequences, boolean fillEmpty, boolean rescale) 1229 { 1230 return concatC(sequences, fillEmpty, rescale, null); 1231 } 1232 1233 /** 1234 * Create and returns a new sequence by concatenating all given sequences on C dimension. 1235 * 1236 * @param sequences 1237 * Sequences to concatenate (use array order). 1238 */ 1239 public static Sequence concatC(Sequence[] sequences) 1240 { 1241 return concatC(sequences, true, false, null); 1242 } 1243 1244 /** 1245 * Create and returns a new sequence by concatenating all given sequences on Z dimension. 1246 * 1247 * @param sequences 1248 * Sequences to concatenate (use array order). 1249 * @param interlaced 1250 * Interlace images.<br> 1251 * normal : 1,1,1,2,2,2,3,3,..<br> 1252 * interlaced : 1,2,3,1,2,3,..<br> 1253 * @param fillEmpty 1254 * Replace empty image by the previous non empty one. 1255 * @param rescale 1256 * Images are scaled to all fit in the same XY dimension. 1257 * @param pl 1258 * ProgressListener to indicate processing progress. 1259 */ 1260 public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale, 1261 ProgressListener pl) 1262 { 1263 final int sizeX = getMaxDim(sequences, DimensionId.X); 1264 final int sizeY = getMaxDim(sequences, DimensionId.Y); 1265 final int sizeC = getMaxDim(sequences, DimensionId.C); 1266 final int sizeT = getMaxDim(sequences, DimensionId.T); 1267 int sizeZ = 0; 1268 1269 for (Sequence seq : sequences) 1270 sizeZ += seq.getSizeZ(); 1271 1272 final Sequence result = new Sequence(); 1273 1274 if (sequences.length > 0) 1275 result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata())); 1276 result.setName("Z Merge"); 1277 1278 int ind = 0; 1279 for (int t = 0; t < sizeT; t++) 1280 { 1281 for (int z = 0; z < sizeZ; z++) 1282 { 1283 if (pl != null) 1284 pl.notifyProgress(ind, sizeT * sizeZ); 1285 1286 result.setImage(t, z, IcyBufferedImageUtil.getCopy( 1287 MergeZHelper.getImage(sequences, sizeX, sizeY, sizeC, t, z, interlaced, fillEmpty, rescale))); 1288 1289 ind++; 1290 } 1291 } 1292 1293 return result; 1294 } 1295 1296 /** 1297 * Create and returns a new sequence by concatenating all given sequences on Z dimension. 1298 * 1299 * @param sequences 1300 * Sequences to concatenate (use array order). 1301 * @param interlaced 1302 * Interlace images.<br> 1303 * normal : 1,1,1,2,2,2,3,3,..<br> 1304 * interlaced : 1,2,3,1,2,3,..<br> 1305 * @param fillEmpty 1306 * Replace empty image by the previous non empty one. 1307 * @param rescale 1308 * Images are scaled to all fit in the same XY dimension. 1309 */ 1310 public static Sequence concatZ(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale) 1311 { 1312 return concatZ(sequences, interlaced, fillEmpty, rescale, null); 1313 1314 } 1315 1316 /** 1317 * Create and returns a new sequence by concatenating all given sequences on Z dimension. 1318 * 1319 * @param sequences 1320 * Sequences to concatenate (use array order). 1321 */ 1322 public static Sequence concatZ(Sequence[] sequences) 1323 { 1324 return concatZ(sequences, false, true, false, null); 1325 } 1326 1327 /** 1328 * Create and returns a new sequence by concatenating all given sequences on T dimension. 1329 * 1330 * @param sequences 1331 * sequences to concatenate (use array order). 1332 * @param interlaced 1333 * interlace images.<br> 1334 * normal : 1,1,1,2,2,2,3,3,..<br> 1335 * interlaced : 1,2,3,1,2,3,..<br> 1336 * @param fillEmpty 1337 * replace empty image by the previous non empty one. 1338 * @param rescale 1339 * Images are scaled to all fit in the same XY dimension. 1340 * @param pl 1341 * ProgressListener to indicate processing progress. 1342 */ 1343 public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale, 1344 ProgressListener pl) 1345 { 1346 final int sizeX = getMaxDim(sequences, DimensionId.X); 1347 final int sizeY = getMaxDim(sequences, DimensionId.Y); 1348 final int sizeC = getMaxDim(sequences, DimensionId.C); 1349 final int sizeZ = getMaxDim(sequences, DimensionId.Z); 1350 int sizeT = 0; 1351 1352 for (Sequence seq : sequences) 1353 sizeT += seq.getSizeT(); 1354 1355 final Sequence result = new Sequence(); 1356 1357 if (sequences.length > 0) 1358 result.setMetaData(OMEUtil.createOMEXMLMetadata(sequences[0].getOMEXMLMetadata())); 1359 result.setName("T Merge"); 1360 1361 int ind = 0; 1362 for (int t = 0; t < sizeT; t++) 1363 { 1364 for (int z = 0; z < sizeZ; z++) 1365 { 1366 if (pl != null) 1367 pl.notifyProgress(ind, sizeT * sizeZ); 1368 1369 result.setImage(t, z, IcyBufferedImageUtil.getCopy( 1370 MergeTHelper.getImage(sequences, sizeX, sizeY, sizeC, t, z, interlaced, fillEmpty, rescale))); 1371 1372 ind++; 1373 } 1374 } 1375 1376 return result; 1377 } 1378 1379 /** 1380 * Create and returns a new sequence by concatenating all given sequences on T dimension. 1381 * 1382 * @param sequences 1383 * Sequences to concatenate (use array order). 1384 * @param interlaced 1385 * Interlace images.<br> 1386 * normal : 1,1,1,2,2,2,3,3,..<br> 1387 * interlaced : 1,2,3,1,2,3,..<br> 1388 * @param fillEmpty 1389 * Replace empty image by the previous non empty one. 1390 * @param rescale 1391 * Images are scaled to all fit in the same XY dimension. 1392 */ 1393 public static Sequence concatT(Sequence[] sequences, boolean interlaced, boolean fillEmpty, boolean rescale) 1394 { 1395 return concatT(sequences, interlaced, fillEmpty, rescale, null); 1396 1397 } 1398 1399 /** 1400 * Create and returns a new sequence by concatenating all given sequences on T dimension. 1401 * 1402 * @param sequences 1403 * Sequences to concatenate (use array order). 1404 */ 1405 public static Sequence concatT(Sequence[] sequences) 1406 { 1407 return concatT(sequences, false, true, false, null); 1408 } 1409 1410 /** 1411 * Adjust Z and T dimension of the sequence. 1412 * 1413 * @param reverseOrder 1414 * Means that images are T-Z ordered instead of Z-T ordered 1415 * @param newSizeZ 1416 * New Z size of the sequence 1417 * @param newSizeT 1418 * New T size of the sequence 1419 */ 1420 public static void adjustZT(Sequence sequence, int newSizeZ, int newSizeT, boolean reverseOrder) 1421 { 1422 final int sizeZ = sequence.getSizeZ(); 1423 final int sizeT = sequence.getSizeT(); 1424 1425 final Sequence tmp = new Sequence(); 1426 1427 tmp.beginUpdate(); 1428 sequence.beginUpdate(); 1429 try 1430 { 1431 try 1432 { 1433 for (int t = 0; t < sizeT; t++) 1434 { 1435 for (int z = 0; z < sizeZ; z++) 1436 { 1437 tmp.setImage(t, z, sequence.getImage(t, z)); 1438 sequence.removeImage(t, z); 1439 } 1440 } 1441 } 1442 finally 1443 { 1444 tmp.endUpdate(); 1445 } 1446 1447 for (int t = 0; t < newSizeT; t++) 1448 for (int z = 0; z < newSizeZ; z++) 1449 sequence.setImage(t, z, AdjustZTHelper.getImage(tmp, t, z, newSizeZ, newSizeT, reverseOrder)); 1450 1451 // to avoid memory leak as images now contained in sequence will 'tmp' sequence forever 1452 tmp.removeAllImages(); 1453 } 1454 finally 1455 { 1456 sequence.endUpdate(); 1457 } 1458 } 1459 1460 /** 1461 * Build a new single channel sequence (grey) from the specified channel of the source sequence. 1462 * 1463 * @param source 1464 * Source sequence 1465 * @param channel 1466 * Channel index to extract from the source sequence. 1467 * @return Sequence 1468 */ 1469 public static Sequence extractChannel(Sequence source, int channel) 1470 { 1471 return extractChannels(source, channel); 1472 } 1473 1474 /** 1475 * @deprecated Use {@link #extractChannels(Sequence, int...)} instead. 1476 */ 1477 @Deprecated 1478 public static Sequence extractChannels(Sequence source, List<Integer> channels) 1479 { 1480 final Sequence outSequence = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1481 1482 outSequence.beginUpdate(); 1483 try 1484 { 1485 for (int t = 0; t < source.getSizeT(); t++) 1486 for (int z = 0; z < source.getSizeZ(); z++) 1487 outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels)); 1488 } 1489 finally 1490 { 1491 outSequence.endUpdate(); 1492 } 1493 1494 // sequence name 1495 if (channels.size() > 1) 1496 { 1497 String s = ""; 1498 for (int i = 0; i < channels.size(); i++) 1499 s += " " + channels.get(i).toString(); 1500 1501 outSequence.setName(source.getName() + " (channels" + s + ")"); 1502 } 1503 else if (channels.size() == 1) 1504 outSequence.setName(source.getName() + " (" + source.getChannelName(channels.get(0).intValue()) + ")"); 1505 1506 // channel name 1507 int c = 0; 1508 for (Integer i : channels) 1509 { 1510 outSequence.setChannelName(c, source.getChannelName(i.intValue())); 1511 c++; 1512 } 1513 1514 return outSequence; 1515 } 1516 1517 /** 1518 * Build a new sequence by extracting the specified channels from the source sequence. 1519 * 1520 * @param source 1521 * Source sequence 1522 * @param channels 1523 * Channel indexes to extract from the source sequence. 1524 * @return Sequence 1525 */ 1526 public static Sequence extractChannels(Sequence source, int... channels) 1527 { 1528 final Sequence outSequence = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1529 final int sizeT = source.getSizeT(); 1530 final int sizeZ = source.getSizeZ(); 1531 final int sizeC = source.getSizeC(); 1532 1533 outSequence.beginUpdate(); 1534 try 1535 { 1536 for (int t = 0; t < sizeT; t++) 1537 for (int z = 0; z < sizeZ; z++) 1538 outSequence.setImage(t, z, IcyBufferedImageUtil.extractChannels(source.getImage(t, z), channels)); 1539 } 1540 finally 1541 { 1542 outSequence.endUpdate(); 1543 } 1544 1545 final OMEXMLMetadata metadata = outSequence.getOMEXMLMetadata(); 1546 1547 // remove channel metadata 1548 for (int ch = MetaDataUtil.getNumChannel(metadata, 0) - 1; ch >= 0; ch--) 1549 { 1550 boolean remove = true; 1551 1552 for (int i : channels) 1553 { 1554 if (i == ch) 1555 { 1556 remove = false; 1557 break; 1558 } 1559 } 1560 1561 if (remove) 1562 MetaDataUtil.removeChannel(metadata, 0, ch); 1563 } 1564 1565 // sequence name 1566 if (channels.length > 1) 1567 { 1568 String s = ""; 1569 for (int i = 0; i < channels.length; i++) 1570 s += " " + channels[i]; 1571 1572 outSequence.setName(source.getName() + " (channels" + s + ")"); 1573 } 1574 else if (channels.length == 1) 1575 outSequence.setName(source.getName() + " (" + source.getChannelName(channels[0]) + ")"); 1576 1577 // copy channel name and colormap 1578 int c = 0; 1579 for (int channel : channels) 1580 { 1581 if (channel < sizeC) 1582 { 1583 outSequence.setChannelName(c, source.getChannelName(channel)); 1584 outSequence.setDefaultColormap(c, source.getDefaultColorMap(channel), false); 1585 } 1586 1587 c++; 1588 } 1589 1590 return outSequence; 1591 } 1592 1593 /** 1594 * Build a new sequence by extracting the specified Z slice from the source sequence. 1595 * 1596 * @param source 1597 * Source sequence 1598 * @param z 1599 * Slice index to extract from the source sequence. 1600 * @return Sequence 1601 */ 1602 public static Sequence extractSlice(Sequence source, int z) 1603 { 1604 final OMEXMLMetadata metadata = OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()); 1605 final Sequence outSequence = new Sequence(metadata); 1606 1607 // keep only metadata for specified slice 1608 MetaDataUtil.keepPlanes(metadata, 0, -1, z, -1); 1609 1610 outSequence.beginUpdate(); 1611 try 1612 { 1613 for (int t = 0; t < source.getSizeT(); t++) 1614 outSequence.setImage(t, 0, IcyBufferedImageUtil.getCopy(source.getImage(t, z))); 1615 } 1616 finally 1617 { 1618 outSequence.endUpdate(); 1619 } 1620 1621 outSequence.setName(source.getName() + " (slice " + z + ")"); 1622 1623 return outSequence; 1624 } 1625 1626 /** 1627 * Build a new sequence by extracting the specified T frame from the source sequence. 1628 * 1629 * @param source 1630 * Source sequence 1631 * @param t 1632 * Frame index to extract from the source sequence. 1633 * @return Sequence 1634 */ 1635 public static Sequence extractFrame(Sequence source, int t) 1636 { 1637 final OMEXMLMetadata metadata = OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata()); 1638 final Sequence outSequence = new Sequence(metadata); 1639 1640 // keep only metadata for specified frame 1641 MetaDataUtil.keepPlanes(metadata, 0, t, -1, -1); 1642 1643 outSequence.beginUpdate(); 1644 try 1645 { 1646 for (int z = 0; z < source.getSizeZ(); z++) 1647 outSequence.setImage(0, z, IcyBufferedImageUtil.getCopy(source.getImage(t, z))); 1648 } 1649 finally 1650 { 1651 outSequence.endUpdate(); 1652 } 1653 1654 outSequence.setName(source.getName() + " (frame " + t + ")"); 1655 1656 return outSequence; 1657 } 1658 1659 /** 1660 * Converts the source sequence to the specified data type.<br> 1661 * This method returns a new sequence (the source sequence is not modified). 1662 * 1663 * @param source 1664 * Source sequence to convert 1665 * @param dataType 1666 * Data type wanted 1667 * @param rescale 1668 * Indicate if we want to scale data value according to data (or data type) range 1669 * @param useDataBounds 1670 * Only used when <code>rescale</code> parameter is true.<br> 1671 * Specify if we use the data bounds for rescaling instead of data type bounds. 1672 * @return converted sequence 1673 */ 1674 public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale, boolean useDataBounds) 1675 { 1676 if (source == null) 1677 return null; 1678 1679 if (!rescale) 1680 return convertType(source, dataType, null); 1681 1682 // convert with rescale 1683 final double boundsDst[] = dataType.getDefaultBounds(); 1684 final int sizeC = source.getSizeC(); 1685 final Scaler[] scalers = new Scaler[sizeC]; 1686 1687 // build scalers 1688 for (int c = 0; c < sizeC; c++) 1689 { 1690 final double boundsSrc[]; 1691 1692 if (useDataBounds) 1693 { 1694 // we need to have data loaded first 1695 source.loadAllData(); 1696 boundsSrc = source.getChannelBounds(c); 1697 } 1698 else 1699 boundsSrc = source.getChannelTypeBounds(c); 1700 1701 scalers[c] = new Scaler(boundsSrc[0], boundsSrc[1], boundsDst[0], boundsDst[1], false); 1702 } 1703 1704 // use scaler to scale data 1705 return convertType(source, dataType, scalers); 1706 } 1707 1708 /** 1709 * Converts the source sequence to the specified data type.<br> 1710 * This method returns a new sequence (the source sequence is not modified). 1711 * 1712 * @param source 1713 * Source sequence to convert 1714 * @param dataType 1715 * data type wanted 1716 * @param rescale 1717 * indicate if we want to scale data value according to data type range 1718 * @return converted sequence 1719 */ 1720 public static Sequence convertToType(Sequence source, DataType dataType, boolean rescale) 1721 { 1722 return convertToType(source, dataType, rescale, false); 1723 } 1724 1725 /** 1726 * @deprecated Use {@link #convertType(Sequence, DataType, Scaler[])} instead. 1727 */ 1728 @Deprecated 1729 public static Sequence convertToType(Sequence source, DataType dataType, Scaler scaler) 1730 { 1731 final Sequence output = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1732 1733 output.beginUpdate(); 1734 try 1735 { 1736 for (int t = 0; t < source.getSizeT(); t++) 1737 { 1738 for (int z = 0; z < source.getSizeZ(); z++) 1739 { 1740 final IcyBufferedImage converted = IcyBufferedImageUtil.convertToType(source.getImage(t, z), 1741 dataType, scaler); 1742 1743 // FIXME : why we did that ?? 1744 // this is not a good idea to force bounds when rescale = false 1745 1746 // set bounds manually for the converted image 1747 // for (int c = 0; c < getSizeC(); c++) 1748 // { 1749 // converted.setComponentBounds(c, boundsDst); 1750 // converted.setComponentUserBounds(c, boundsDst); 1751 // } 1752 1753 output.setImage(t, z, converted); 1754 } 1755 } 1756 1757 output.setName(source.getName() + " (" + output.getDataType_() + ")"); 1758 } 1759 finally 1760 { 1761 output.endUpdate(); 1762 } 1763 1764 return output; 1765 } 1766 1767 /** 1768 * Converts the source sequence to the specified data type.<br> 1769 * This method returns a new sequence (the source sequence is not modified). 1770 * 1771 * @param source 1772 * Source sequence to convert 1773 * @param dataType 1774 * data type wanted. 1775 * @param scalers 1776 * scalers for scaling internal data during conversion (1 scaler per channel).<br> 1777 * Can be set to <code>null</code> to avoid value conversion. 1778 * @return converted image 1779 */ 1780 public static Sequence convertType(Sequence source, DataType dataType, Scaler[] scalers) 1781 { 1782 final Sequence output = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1783 1784 output.beginUpdate(); 1785 try 1786 { 1787 for (int t = 0; t < source.getSizeT(); t++) 1788 { 1789 for (int z = 0; z < source.getSizeZ(); z++) 1790 { 1791 final IcyBufferedImage converted = IcyBufferedImageUtil.convertType(source.getImage(t, z), dataType, 1792 scalers); 1793 1794 // FIXME : why we did that ?? 1795 // this is not a good idea to force bounds when rescale = false 1796 1797 // set bounds manually for the converted image 1798 // for (int c = 0; c < getSizeC(); c++) 1799 // { 1800 // converted.setComponentBounds(c, boundsDst); 1801 // converted.setComponentUserBounds(c, boundsDst); 1802 // } 1803 1804 output.setImage(t, z, converted); 1805 } 1806 } 1807 } 1808 finally 1809 { 1810 output.endUpdate(); 1811 } 1812 1813 // preserve channel informations 1814 for (int c = 0; c < source.getSizeC(); c++) 1815 { 1816 output.setChannelName(c, source.getChannelName(c)); 1817 output.setDefaultColormap(c, source.getDefaultColorMap(c), true); 1818 // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT' 1819 // based on current channel bounds 1820 output.setColormap(c, source.getColorMap(c)); 1821 } 1822 1823 // and finally set name 1824 output.setName(source.getName() + " (" + output.getDataType_() + ")"); 1825 1826 return output; 1827 } 1828 1829 /** 1830 * Return a rotated version of the source sequence with specified parameters. 1831 * 1832 * @param source 1833 * source image 1834 * @param xOrigin 1835 * X origin for the rotation 1836 * @param yOrigin 1837 * Y origin for the rotation 1838 * @param angle 1839 * rotation angle in radian 1840 * @param filterType 1841 * filter resampling method used 1842 */ 1843 public static Sequence rotate(Sequence source, double xOrigin, double yOrigin, double angle, FilterType filterType) 1844 { 1845 final int sizeT = source.getSizeT(); 1846 final int sizeZ = source.getSizeZ(); 1847 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1848 1849 result.beginUpdate(); 1850 try 1851 { 1852 for (int t = 0; t < sizeT; t++) 1853 for (int z = 0; z < sizeZ; z++) 1854 result.setImage(t, z, 1855 IcyBufferedImageUtil.rotate(source.getImage(t, z), xOrigin, yOrigin, angle, filterType)); 1856 } 1857 finally 1858 { 1859 result.endUpdate(); 1860 } 1861 1862 // preserve channel informations 1863 for (int c = 0; c < source.getSizeC(); c++) 1864 { 1865 result.setChannelName(c, source.getChannelName(c)); 1866 result.setDefaultColormap(c, source.getDefaultColorMap(c), true); 1867 // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT' 1868 // based on current channel bounds 1869 result.setColormap(c, source.getColorMap(c)); 1870 } 1871 1872 result.setName(source.getName() + " (rotated)"); 1873 1874 return result; 1875 } 1876 1877 /** 1878 * Return a rotated version of the source Sequence with specified parameters. 1879 * 1880 * @param source 1881 * source image 1882 * @param angle 1883 * rotation angle in radian 1884 * @param filterType 1885 * filter resampling method used 1886 */ 1887 public static Sequence rotate(Sequence source, double angle, FilterType filterType) 1888 { 1889 if (source == null) 1890 return null; 1891 1892 return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, filterType); 1893 } 1894 1895 /** 1896 * Return a rotated version of the source Sequence with specified parameters. 1897 * 1898 * @param source 1899 * source image 1900 * @param angle 1901 * rotation angle in radian 1902 */ 1903 public static Sequence rotate(Sequence source, double angle) 1904 { 1905 if (source == null) 1906 return null; 1907 1908 return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, FilterType.BILINEAR); 1909 } 1910 1911 /** 1912 * Return a copy of the source sequence with specified size, alignment rules and filter type. 1913 * 1914 * @param source 1915 * source sequence 1916 * @param resizeContent 1917 * indicate if content should be resized or not (empty area are 0 filled) 1918 * @param xAlign 1919 * horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br> 1920 * (used only if resizeContent is false) 1921 * @param yAlign 1922 * vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br> 1923 * (used only if resizeContent is false) 1924 * @param filterType 1925 * filter method used for scale (used only if resizeContent is true) 1926 */ 1927 public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign, 1928 FilterType filterType) 1929 { 1930 final int sizeT = source.getSizeT(); 1931 final int sizeZ = source.getSizeZ(); 1932 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 1933 1934 result.beginUpdate(); 1935 try 1936 { 1937 for (int t = 0; t < sizeT; t++) 1938 for (int z = 0; z < sizeZ; z++) 1939 result.setImage(t, z, IcyBufferedImageUtil.scale(source.getImage(t, z), width, height, 1940 resizeContent, xAlign, yAlign, filterType)); 1941 } 1942 finally 1943 { 1944 result.endUpdate(); 1945 } 1946 1947 // preserve channel informations 1948 for (int c = 0; c < source.getSizeC(); c++) 1949 { 1950 result.setChannelName(c, source.getChannelName(c)); 1951 result.setDefaultColormap(c, source.getDefaultColorMap(c), true); 1952 // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT' 1953 // based on current channel bounds 1954 result.setColormap(c, source.getColorMap(c)); 1955 } 1956 1957 result.setName(source.getName() + " (resized)"); 1958 1959 // content was resized ? 1960 if (resizeContent) 1961 { 1962 final double sx = (double) source.getSizeX() / result.getSizeX(); 1963 final double sy = (double) source.getSizeY() / result.getSizeY(); 1964 1965 // update pixel size 1966 if ((sx != 0d) && !Double.isInfinite(sx)) 1967 result.setPixelSizeX(result.getPixelSizeX() * sx); 1968 if ((sy != 0d) && !Double.isInfinite(sy)) 1969 result.setPixelSizeY(result.getPixelSizeY() * sy); 1970 } 1971 1972 return result; 1973 } 1974 1975 /** 1976 * Return a copy of the sequence with specified size.<br> 1977 * By default the FilterType.BILINEAR is used as filter method if resizeContent is true 1978 * 1979 * @param source 1980 * source sequence 1981 * @param resizeContent 1982 * indicate if content should be resized or not (empty area are 0 filled) 1983 * @param xAlign 1984 * horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br> 1985 * (used only if resizeContent is false) 1986 * @param yAlign 1987 * vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br> 1988 * (used only if resizeContent is false) 1989 */ 1990 public static Sequence scale(Sequence source, int width, int height, boolean resizeContent, int xAlign, int yAlign) 1991 { 1992 return scale(source, width, height, resizeContent, xAlign, yAlign, FilterType.BILINEAR); 1993 } 1994 1995 /** 1996 * Return a copy of the sequence with specified size. 1997 * 1998 * @param source 1999 * source sequence 2000 * @param filterType 2001 * filter method used for scale (used only if resizeContent is true) 2002 */ 2003 public static Sequence scale(Sequence source, int width, int height, FilterType filterType) 2004 { 2005 return scale(source, width, height, true, 0, 0, filterType); 2006 } 2007 2008 /** 2009 * Return a copy of the sequence with specified size.<br> 2010 * By default the FilterType.BILINEAR is used as filter method. 2011 */ 2012 public static Sequence scale(Sequence source, int width, int height) 2013 { 2014 return scale(source, width, height, FilterType.BILINEAR); 2015 } 2016 2017 /** 2018 * Creates a new sequence from the specified region of the source sequence. 2019 */ 2020 public static Sequence getSubSequence(Sequence source, Rectangle5D.Integer region) 2021 { 2022 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 2023 2024 final Rectangle region2d = region.toRectangle2D().getBounds(); 2025 final int startZ; 2026 final int endZ; 2027 final int startT; 2028 final int endT; 2029 final int startC; 2030 final int endC; 2031 2032 if (region.isInfiniteZ()) 2033 { 2034 startZ = 0; 2035 endZ = source.getSizeZ(); 2036 } 2037 else 2038 { 2039 startZ = Math.max(0, region.z); 2040 endZ = Math.min(source.getSizeZ(), region.z + region.sizeZ); 2041 } 2042 if (region.isInfiniteT()) 2043 { 2044 startT = 0; 2045 endT = source.getSizeT(); 2046 } 2047 else 2048 { 2049 startT = Math.max(0, region.t); 2050 endT = Math.min(source.getSizeT(), region.t + region.sizeT); 2051 } 2052 if (region.isInfiniteC()) 2053 { 2054 startC = 0; 2055 endC = source.getSizeC(); 2056 } 2057 else 2058 { 2059 startC = Math.max(0, region.c); 2060 endC = Math.min(source.getSizeC(), region.c + region.sizeC); 2061 } 2062 2063 result.beginUpdate(); 2064 try 2065 { 2066 for (int t = startT; t < endT; t++) 2067 { 2068 for (int z = startZ; z < endZ; z++) 2069 { 2070 IcyBufferedImage img = source.getImage(t, z); 2071 2072 if (img != null) 2073 img = IcyBufferedImageUtil.getSubImage(img, region2d, startC, (endC - startC) + 1); 2074 2075 result.setImage(t - startT, z - startZ, img); 2076 } 2077 } 2078 } 2079 finally 2080 { 2081 result.endUpdate(); 2082 } 2083 2084 // preserve channel informations 2085 for (int c = startC; c < endC; c++) 2086 { 2087 result.setChannelName(c - startC, source.getChannelName(c)); 2088 result.setDefaultColormap(c - startC, source.getDefaultColorMap(c), true); 2089 // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT' 2090 // based on current channel bounds 2091 result.setColormap(c - startC, source.getColorMap(c)); 2092 } 2093 2094 result.setName(source.getName() + " (crop)"); 2095 2096 // adjust position X, Y, Z 2097 result.setPositionX(source.getPositionX() + (region2d.x * source.getPixelSizeX())); 2098 result.setPositionY(source.getPositionY() + (region2d.y * source.getPixelSizeY())); 2099 result.setPositionZ(source.getPositionZ() + (startZ * source.getPixelSizeZ())); 2100 // adjust TimeStamp 2101 result.setTimeStamp(source.getTimeStamp() + (long) (source.getPositionTOffset(startT, startZ, startC) * 1000d)); 2102 2103 return result; 2104 } 2105 2106 /** 2107 * @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead. 2108 */ 2109 @Deprecated 2110 public static Sequence getSubSequence(Sequence source, int startX, int startY, int startC, int startZ, int startT, 2111 int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT) 2112 { 2113 return getSubSequence(source, 2114 new Rectangle5D.Integer(startX, startY, startZ, startT, startC, sizeX, sizeY, sizeZ, sizeT, sizeC)); 2115 } 2116 2117 /** 2118 * @deprecated Use {@link #getSubSequence(Sequence, icy.type.rectangle.Rectangle5D.Integer)} instead. 2119 */ 2120 @Deprecated 2121 public static Sequence getSubSequence(Sequence source, int startX, int startY, int startZ, int startT, int sizeX, 2122 int sizeY, int sizeZ, int sizeT) 2123 { 2124 return getSubSequence(source, startX, startY, 0, startZ, startT, sizeX, sizeY, source.getSizeC(), sizeZ, sizeT); 2125 } 2126 2127 /** 2128 * Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI} 2129 * bounds.<br> 2130 * 2131 * @param source 2132 * the source sequence 2133 * @param roi 2134 * used to define to region to retain. 2135 * @param nullValue 2136 * the returned sequence is created by using the ROI rectangular bounds.<br> 2137 * if <code>nullValue</code> is different of <code>Double.NaN</code> then any pixel 2138 * outside the ROI region will be set to <code>nullValue</code> 2139 */ 2140 public static Sequence getSubSequence(Sequence source, ROI roi, double nullValue) 2141 { 2142 final Rectangle5D.Integer bounds = roi.getBounds5D().toInteger(); 2143 final Sequence result = getSubSequence(source, bounds); 2144 2145 // use null value ? 2146 if (!Double.isNaN(nullValue)) 2147 { 2148 final int offX = (bounds.x == Integer.MIN_VALUE) ? 0 : (int) bounds.x; 2149 final int offY = (bounds.y == Integer.MIN_VALUE) ? 0 : (int) bounds.y; 2150 final int offZ = (bounds.z == Integer.MIN_VALUE) ? 0 : (int) bounds.z; 2151 final int offT = (bounds.t == Integer.MIN_VALUE) ? 0 : (int) bounds.t; 2152 final int offC = (bounds.c == Integer.MIN_VALUE) ? 0 : (int) bounds.c; 2153 final int sizeX = result.getSizeX(); 2154 final int sizeY = result.getSizeY(); 2155 final int sizeZ = result.getSizeZ(); 2156 final int sizeT = result.getSizeT(); 2157 final int sizeC = result.getSizeC(); 2158 final DataType dataType = result.getDataType_(); 2159 2160 result.beginUpdate(); 2161 try 2162 { 2163 for (int t = 0; t < sizeT; t++) 2164 { 2165 for (int z = 0; z < sizeZ; z++) 2166 { 2167 for (int c = 0; c < sizeC; c++) 2168 { 2169 final BooleanMask2D mask = roi.getBooleanMask2D(z + offZ, t + offT, c + offC, false); 2170 final IcyBufferedImage img = result.getImage(t, z); 2171 2172 img.lockRaster(); 2173 try 2174 { 2175 final Object data = img.getDataXY(c); 2176 int offset = 0; 2177 2178 for (int y = 0; y < sizeY; y++) 2179 for (int x = 0; x < sizeX; x++, offset++) 2180 if (!mask.contains(x + offX, y + offY)) 2181 Array1DUtil.setValue(data, offset, dataType, nullValue); 2182 } 2183 finally 2184 { 2185 img.releaseRaster(true); 2186 } 2187 2188 img.dataChanged(); 2189 } 2190 } 2191 } 2192 } 2193 finally 2194 { 2195 result.endUpdate(); 2196 } 2197 } 2198 2199 return result; 2200 } 2201 2202 /** 2203 * Creates a new sequence which is a sub part of the source sequence defined by the specified {@link ROI} bounds. 2204 */ 2205 public static Sequence getSubSequence(Sequence source, ROI roi) 2206 { 2207 return getSubSequence(source, roi, Double.NaN); 2208 } 2209 2210 /** 2211 * Creates and return a copy of the sequence. 2212 * 2213 * @param source 2214 * the source sequence to copy 2215 * @param copyROI 2216 * Copy the ROI from source sequence.<br> 2217 * Warning: by doing that the ROI will retain the result sequence as long the source sequence is alive. 2218 * @param copyOverlay 2219 * Copy the Overlay from source sequence.<br> 2220 * Warning: by doing that the Overlay will retain the result sequence as long the source sequence is alive. 2221 * @param nameSuffix 2222 * add the suffix <i>" (copy)"</i> to the new Sequence name to distinguish it 2223 */ 2224 public static Sequence getCopy(Sequence source, boolean copyROI, boolean copyOverlay, boolean nameSuffix) 2225 { 2226 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 2227 2228 result.beginUpdate(); 2229 try 2230 { 2231 result.copyDataFrom(source); 2232 if (copyROI) 2233 { 2234 for (ROI roi : source.getROIs()) 2235 result.addROI(roi); 2236 } 2237 if (copyOverlay) 2238 { 2239 for (Overlay overlay : source.getOverlays()) 2240 result.addOverlay(overlay); 2241 } 2242 } 2243 finally 2244 { 2245 result.endUpdate(); 2246 } 2247 2248 // preserve channel informations 2249 for (int c = 0; c < source.getSizeC(); c++) 2250 { 2251 result.setChannelName(c, source.getChannelName(c)); 2252 result.setDefaultColormap(c, source.getDefaultColorMap(c), true); 2253 // it's important to set user colormap after 'endUpdate' as it will internally create a new 'user LUT' 2254 // based on current channel bounds 2255 result.setColormap(c, source.getColorMap(c)); 2256 } 2257 2258 if (nameSuffix) 2259 result.setName(source.getName() + " (copy)"); 2260 2261 return result; 2262 } 2263 2264 /** 2265 * Creates and return a copy of the sequence.<br> 2266 * Note that only data and metadata are copied, overlays and ROIs are not preserved. 2267 */ 2268 public static Sequence getCopy(Sequence source) 2269 { 2270 return getCopy(source, false, false, true); 2271 } 2272 2273 /** 2274 * Convert the specified sequence to gray sequence (single channel) 2275 */ 2276 public static Sequence toGray(Sequence source) 2277 { 2278 return convertColor(source, BufferedImage.TYPE_BYTE_GRAY, null); 2279 } 2280 2281 /** 2282 * Convert the specified sequence to RGB sequence (3 channels) 2283 */ 2284 public static Sequence toRGB(Sequence source) 2285 { 2286 return convertColor(source, BufferedImage.TYPE_INT_RGB, null); 2287 } 2288 2289 /** 2290 * Convert the specified sequence to ARGB sequence (4 channels) 2291 */ 2292 public static Sequence toARGB(Sequence source) 2293 { 2294 return convertColor(source, BufferedImage.TYPE_INT_ARGB, null); 2295 } 2296 2297 /** 2298 * Do color conversion of the specified {@link Sequence} into the specified type.<br> 2299 * The resulting Sequence will have 4, 3 or 1 channel(s) depending the selected type. 2300 * 2301 * @param source 2302 * source sequence 2303 * @param imageType 2304 * wanted image type, only the following is accepted :<br> 2305 * BufferedImage.TYPE_INT_ARGB (4 channels)<br> 2306 * BufferedImage.TYPE_INT_RGB (3 channels)<br> 2307 * BufferedImage.TYPE_BYTE_GRAY (1 channel)<br> 2308 * @param lut 2309 * lut used for color calculation (source sequence lut is used if null) 2310 */ 2311 public static Sequence convertColor(Sequence source, int imageType, LUT lut) 2312 { 2313 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(source.getOMEXMLMetadata())); 2314 // image receiver 2315 final BufferedImage imgOut = new BufferedImage(source.getSizeX(), source.getSizeY(), imageType); 2316 2317 result.beginUpdate(); 2318 try 2319 { 2320 for (int t = 0; t < source.getSizeT(); t++) 2321 for (int z = 0; z < source.getSizeZ(); z++) 2322 result.setImage(t, z, IcyBufferedImageUtil.toBufferedImage(source.getImage(t, z), imgOut, lut)); 2323 2324 // rename channels and set final name 2325 switch (imageType) 2326 { 2327 default: 2328 case BufferedImage.TYPE_INT_ARGB: 2329 result.setChannelName(0, "red"); 2330 result.setChannelName(1, "green"); 2331 result.setChannelName(2, "blue"); 2332 result.setChannelName(3, "alpha"); 2333 result.setName(source.getName() + " (ARGB rendering)"); 2334 break; 2335 2336 case BufferedImage.TYPE_INT_RGB: 2337 result.setChannelName(0, "red"); 2338 result.setChannelName(1, "green"); 2339 result.setChannelName(2, "blue"); 2340 result.setName(source.getName() + " (RGB rendering)"); 2341 break; 2342 2343 case BufferedImage.TYPE_BYTE_GRAY: 2344 result.setChannelName(0, "gray"); 2345 result.setName(source.getName() + " (gray rendering)"); 2346 break; 2347 } 2348 } 2349 finally 2350 { 2351 result.endUpdate(); 2352 } 2353 2354 return result; 2355 } 2356 2357 /** 2358 * Convert the given Point2D coordinate from an input resolution and a wanted output resolution level (0/1/2/3/...) 2359 * 2360 * @see Sequence#getOriginResolution() 2361 */ 2362 public static Point2D convertPoint(Point2D pt, int inputResolution, int outputResolution) 2363 { 2364 if (pt == null) 2365 return null; 2366 2367 final double factor = Math.pow(2, inputResolution - outputResolution); 2368 2369 return new Point2D.Double(pt.getX() * factor, pt.getY() * factor); 2370 } 2371 2372 /** 2373 * Convert the given Rectangle2D from an input resolution and a wanted output resolution level (0/1/2/3/...) 2374 * 2375 * @see Sequence#getOriginResolution() 2376 */ 2377 public static Rectangle2D convertRectangle(Rectangle2D rect, int inputResolution, int outputResolution) 2378 { 2379 if (rect == null) 2380 return null; 2381 2382 final double factor = Math.pow(2, inputResolution - outputResolution); 2383 2384 return new Rectangle2D.Double(rect.getX() * factor, rect.getY() * factor, rect.getWidth() * factor, 2385 rect.getHeight() * factor); 2386 } 2387 2388 /** 2389 * Convert the given Point2D coordinate from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br> 2390 * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion. 2391 */ 2392 public static Point3D convertPoint(Point3D pt, Sequence source, Sequence destination) 2393 { 2394 if (pt == null) 2395 return new Point3D.Double(); 2396 2397 if ((source == null) || (destination == null)) 2398 return new Point3D.Double(pt.getX(), pt.getY(), pt.getZ()); 2399 2400 // get global position in um 2401 final double posXum = (pt.getX() * source.getPixelSizeX()) + source.getPositionX(); 2402 final double posYum = (pt.getY() * source.getPixelSizeY()) + source.getPositionY(); 2403 final double posZum = (pt.getZ() * source.getPixelSizeZ()) + source.getPositionZ(); 2404 2405 // convert to destination 2406 return new Point3D.Double((posXum - destination.getPositionX()) / destination.getPixelSizeX(), 2407 (posYum - destination.getPositionY()) / destination.getPixelSizeY(), 2408 (posZum - destination.getPositionZ()) / destination.getPixelSizeZ()); 2409 } 2410 2411 /** 2412 * Convert the given Point2D coordinate from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br> 2413 * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion. 2414 */ 2415 public static Point2D convertPoint(Point2D pt, Sequence source, Sequence destination) 2416 { 2417 if (pt == null) 2418 return new Point2D.Double(); 2419 2420 return convertPoint(new Point3D.Double(pt.getX(), pt.getY(), 0d), source, destination).toPoint2D(); 2421 } 2422 2423 /** 2424 * Convert the given {@link Rectangle3D} from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br> 2425 * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion. 2426 */ 2427 public static Rectangle3D convertRectangle(Rectangle3D rect, Sequence source, Sequence destination) 2428 { 2429 if (rect == null) 2430 return new Rectangle3D.Double(); 2431 2432 if ((source == null) || (destination == null)) 2433 return new Rectangle3D.Double(rect); 2434 2435 // get global rectangle in um 2436 final double psxs = source.getPixelSizeX(); 2437 final double psys = source.getPixelSizeY(); 2438 final double pszs = source.getPixelSizeZ(); 2439 final double posXum = (rect.getX() * psxs) + source.getPositionX(); 2440 final double posYum = (rect.getY() * psys) + source.getPositionY(); 2441 final double posZum = (rect.getZ() * pszs) + source.getPositionZ(); 2442 final double sizeXum = rect.getX() * psxs; 2443 final double sizeYum = rect.getY() * psys; 2444 final double sizeZum = rect.getZ() * pszs; 2445 2446 // convert to destination 2447 final double psxd = destination.getPixelSizeX(); 2448 final double psyd = destination.getPixelSizeY(); 2449 final double pszd = destination.getPixelSizeZ(); 2450 return new Rectangle3D.Double((posXum - destination.getPositionX()) / psxd, 2451 (posYum - destination.getPositionY()) / psyd, (posZum - destination.getPositionZ()) / pszd, 2452 sizeXum / psxd, sizeYum / psyd, sizeZum / pszd); 2453 } 2454 2455 /** 2456 * Convert the given {@link Rectangle2D} from the <code>source</code> {@link Sequence} to the <code>destination</code> {@link Sequence}.<br> 2457 * It internally uses the {@link Sequence#getPosition()} and {@link Sequence#getPixelSize()} information to do the coordinate conversion. 2458 */ 2459 public static Rectangle2D convertRectangle(Rectangle2D rect, Sequence source, Sequence destination) 2460 { 2461 if (rect == null) 2462 return new Rectangle2D.Double(); 2463 2464 return convertRectangle( 2465 new Rectangle3D.Double(rect.getX(), rect.getY(), 0d, rect.getWidth(), rect.getHeight(), 0d), source, 2466 destination).toRectangle2D(); 2467 } 2468 2469 /** 2470 * Convert the given Point coordinate from the source Sequence into the original image coordinate (pixel)<br> 2471 * This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations 2472 * to compute the original image position. 2473 * 2474 * @see Sequence#getOriginResolution() 2475 * @see Sequence#getOriginXYRegion() 2476 */ 2477 public static Point getOriginPoint(Point pt, Sequence source) 2478 { 2479 if (pt == null) 2480 return null; 2481 2482 final Point2D adjPt = convertPoint(pt, source.getOriginResolution(), 0); 2483 final Point result = new Point((int) adjPt.getX(), (int) adjPt.getY()); 2484 2485 final Rectangle region = source.getOriginXYRegion(); 2486 if (region != null) 2487 result.setLocation(result.x + region.x, result.y + region.y); 2488 2489 return result; 2490 } 2491 2492 /** 2493 * Convert the given Rectangle region from the source Sequence into the original image region coordinates 2494 * (pixel)<br> 2495 * This method use the {@link Sequence#getOriginResolution()} and {@link Sequence#getOriginXYRegion()} informations 2496 * to compute the original image region coordinates. 2497 * 2498 * @see Sequence#getOriginResolution() 2499 * @see Sequence#getOriginXYRegion() 2500 */ 2501 public static Rectangle getOriginRectangle(Rectangle rect, Sequence source) 2502 { 2503 if (rect == null) 2504 return null; 2505 2506 final Rectangle2D adjRect = convertRectangle(rect, source.getOriginResolution(), 0); 2507 final Rectangle result = new Rectangle((int) adjRect.getX(), (int) adjRect.getY(), (int) adjRect.getWidth(), 2508 (int) adjRect.getHeight()); 2509 2510 final Rectangle region = source.getOriginXYRegion(); 2511 if (region != null) 2512 result.setLocation(result.x + region.x, result.y + region.y); 2513 2514 return result; 2515 } 2516}