001/** 002 * 003 */ 004package plugins.kernel.importer; 005 006import java.awt.Color; 007import java.awt.Point; 008import java.awt.Rectangle; 009import java.awt.image.BufferedImage; 010import java.io.File; 011import java.io.IOException; 012import java.nio.channels.ClosedByInterruptException; 013import java.util.ArrayList; 014import java.util.List; 015import java.util.Stack; 016 017import javax.swing.filechooser.FileFilter; 018 019import icy.common.exception.UnsupportedFormatException; 020import icy.common.listener.ProgressListener; 021import icy.file.FileUtil; 022import icy.file.Loader; 023import icy.gui.dialog.LoaderDialog.AllImagesFileFilter; 024import icy.image.IcyBufferedImage; 025import icy.image.IcyBufferedImageUtil; 026import icy.image.IcyBufferedImageUtil.FilterType; 027import icy.image.ImageUtil; 028import icy.image.colormap.IcyColorMap; 029import icy.image.colormap.LinearColorMap; 030import icy.plugin.abstract_.PluginSequenceFileImporter; 031import icy.sequence.MetaDataUtil; 032import icy.system.SystemUtil; 033import icy.system.thread.Processor; 034import icy.type.DataType; 035import icy.type.collection.array.Array1DUtil; 036import icy.type.collection.array.Array2DUtil; 037import icy.type.collection.array.ByteArrayConvert; 038import icy.type.rectangle.Rectangle2DUtil; 039import icy.util.ColorUtil; 040import icy.util.OMEUtil; 041import icy.util.StringUtil; 042import jxl.biff.drawing.PNGReader; 043import loci.formats.FormatException; 044import loci.formats.IFormatReader; 045import loci.formats.ImageReader; 046import loci.formats.MissingLibraryException; 047import loci.formats.TileStitcher; 048import loci.formats.UnknownFormatException; 049import loci.formats.gui.AWTImageTools; 050import loci.formats.gui.ExtensionFileFilter; 051import loci.formats.in.APNGReader; 052import loci.formats.in.DynamicMetadataOptions; 053import loci.formats.in.JPEG2000Reader; 054import loci.formats.in.MetadataLevel; 055import loci.formats.in.NativeND2Reader; 056import loci.formats.meta.MetadataStore; 057import loci.formats.ome.OMEXMLMetadataImpl; 058import ome.xml.meta.OMEXMLMetadata; 059 060/** 061 * LOCI Bio-Formats library importer class. 062 * 063 * @author Stephane 064 */ 065public class LociImporterPlugin extends PluginSequenceFileImporter 066{ 067 protected class LociAllFileFilter extends AllImagesFileFilter 068 { 069 @Override 070 public String getDescription() 071 { 072 return "All image files / Bio-Formats"; 073 } 074 }; 075 076 /** 077 * Used for multi thread tile image reading. 078 * 079 * @author Stephane 080 */ 081 class LociTilePixelsReader 082 { 083 class TilePixelsWorkBuffer 084 { 085 final byte[] rawBuffer; 086 final byte[] channelBuffer; 087 final Object pixelBuffer; 088 089 public TilePixelsWorkBuffer(int sizeX, int sizeY, int rgbChannel, DataType dataType) 090 { 091 super(); 092 093 // allocate arrays 094 rawBuffer = new byte[sizeX * sizeY * rgbChannel * dataType.getSize()]; 095 channelBuffer = new byte[sizeX * sizeY * dataType.getSize()]; 096 pixelBuffer = Array1DUtil.createArray(dataType, sizeX * sizeY); 097 } 098 } 099 100 class TilePixelsReaderWorker implements Runnable 101 { 102 final Rectangle region; 103 boolean done; 104 boolean failed; 105 106 public TilePixelsReaderWorker(Rectangle region) 107 { 108 super(); 109 110 this.region = region; 111 done = false; 112 failed = false; 113 } 114 115 @SuppressWarnings("resource") 116 @Override 117 public void run() 118 { 119 Object pixels; 120 121 try 122 { 123 // get reader and working buffers 124 final IFormatReader r = getReader(); 125 final TilePixelsWorkBuffer buf = buffers.pop(); 126 127 try 128 { 129 try 130 { 131 pixels = getPixelsInternal(r, region, z, t, c, false, downScaleLevel, buf.rawBuffer, 132 buf.channelBuffer, buf.pixelBuffer); 133 } 134 finally 135 { 136 // release reader 137 releaseReader(r); 138 } 139 140 // need to adjust input region 141 final Rectangle adjRegion = new Rectangle(region); 142 int downScale = downScaleLevel; 143 while (downScale > 0) 144 { 145 // reduce region size by a factor of 2 146 adjRegion.setBounds(adjRegion.x / 2, adjRegion.y / 2, adjRegion.width / 2, 147 adjRegion.height / 2); 148 downScale--; 149 } 150 151 // define destination in destination 152 final Point pt = adjRegion.getLocation(); 153 pt.translate(-imageRegion.x, -imageRegion.y); 154 155 // copy tile to result 156 Array1DUtil.copyRect(pixels, adjRegion.getSize(), null, result, imageRegion.getSize(), pt, 157 signed); 158 } 159 finally 160 { 161 // release working buffer 162 buffers.push(buf); 163 } 164 } 165 catch (Exception e) 166 { 167 failed = true; 168 } 169 170 done = true; 171 } 172 } 173 174 // final image region 175 final Rectangle imageRegion; 176 // required image down scaling 177 final int downScaleLevel; 178 // resolution shift divider 179 final int resShift; 180 final int z; 181 final int t; 182 final int c; 183 final boolean signed; 184 final Object result; 185 final Stack<TilePixelsWorkBuffer> buffers; 186 187 public LociTilePixelsReader(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 188 int tileH, ProgressListener listener) throws IOException, UnsupportedFormatException 189 { 190 super(); 191 192 this.z = z; 193 this.t = t; 194 this.c = c; 195 196 final OMEXMLMetadata meta = getOMEXMLMetaData(); 197 final int sizeX = MetaDataUtil.getSizeX(meta, series); 198 final int sizeY = MetaDataUtil.getSizeY(meta, series); 199 final DataType type = MetaDataUtil.getDataType(meta, series); 200 signed = type.isSigned(); 201 202 // define XY region to load 203 Rectangle adjRegion = new Rectangle(sizeX, sizeY); 204 if (region != null) 205 adjRegion = adjRegion.intersection(region); 206 207 // prepare main reader and get needed downScale 208 downScaleLevel = prepareReader(series, resolution); 209 // real resolution shift used by reader 210 resShift = getResolutionShift(); 211 212 // adapt region size to final image resolution 213 imageRegion = new Rectangle(adjRegion.x >> resolution, adjRegion.y >> resolution, 214 adjRegion.width >> resolution, adjRegion.height >> resolution); 215 // adapt region size to reader resolution 216 adjRegion = new Rectangle(adjRegion.x >> resShift, adjRegion.y >> resShift, adjRegion.width >> resShift, 217 adjRegion.height >> resShift); 218 219 // allocate result (adapted to final wanted resolution) 220 result = Array1DUtil.createArray(type, imageRegion.width * imageRegion.height); 221 222 // allocate working buffers 223 final int rgbChannelCount = reader.getRGBChannelCount(); 224 225 int tw = tileW; 226 int th = tileH; 227 228 // adjust tile size if needed 229 if (tw <= 0) 230 tw = getTileWidth(series); 231 if (tw <= 0) 232 tw = 512; 233 if (th <= 0) 234 th = getTileHeight(series); 235 if (th <= 0) 236 th = 512; 237 238 final int numThread = Math.max(1, SystemUtil.getNumberOfCPUs() - 1); 239 240 buffers = new Stack<TilePixelsWorkBuffer>(); 241 for (int i = 0; i < numThread; i++) 242 buffers.push(new TilePixelsWorkBuffer(tw, th, rgbChannelCount, type)); 243 244 // create processor 245 final Processor readerProcessor = new Processor(numThread); 246 readerProcessor.setThreadName("Pixels tile reader"); 247 248 // get all required tiles 249 final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th); 250 251 // submit all tasks 252 for (Rectangle tile : tiles) 253 { 254 // wait a bit if the process queue is full 255 while (readerProcessor.isFull()) 256 { 257 try 258 { 259 Thread.sleep(0); 260 } 261 catch (InterruptedException e) 262 { 263 // interrupt all processes 264 readerProcessor.shutdownNow(); 265 break; 266 } 267 } 268 269 // submit next task 270 readerProcessor.submit(new TilePixelsReaderWorker(tile.intersection(adjRegion))); 271 272 // display progression 273 if (listener != null) 274 { 275 // process cancel requested ? 276 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 277 { 278 // interrupt processes 279 readerProcessor.shutdownNow(); 280 break; 281 } 282 } 283 } 284 285 // wait for completion 286 while (readerProcessor.isProcessing()) 287 { 288 try 289 { 290 Thread.sleep(1); 291 } 292 catch (InterruptedException e) 293 { 294 // interrupt all processes 295 readerProcessor.shutdownNow(); 296 break; 297 } 298 299 // display progression 300 if (listener != null) 301 { 302 // process cancel requested ? 303 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 304 { 305 // interrupt processes 306 readerProcessor.shutdownNow(); 307 break; 308 } 309 } 310 } 311 312 // last wait for completion just in case we were interrupted 313 readerProcessor.waitAll(); 314 315 // faster memory release 316 buffers.clear(); 317 } 318 } 319 320 /** 321 * Used for multi thread tile image reading. 322 * 323 * @author Stephane 324 */ 325 class LociTileImageReader 326 { 327 class TileImageWorkBuffer 328 { 329 final byte[] rawBuffer; 330 final byte[] channelBuffer; 331 final Object[] pixelBuffer; 332 333 public TileImageWorkBuffer(int sizeX, int sizeY, int sizeC, int rgbChannel, DataType dataType) 334 { 335 super(); 336 337 // allocate arrays 338 rawBuffer = new byte[sizeX * sizeY * rgbChannel * dataType.getSize()]; 339 channelBuffer = new byte[sizeX * sizeY * dataType.getSize()]; 340 pixelBuffer = Array2DUtil.createArray(dataType, sizeC); 341 for (int i = 0; i < sizeC; i++) 342 pixelBuffer[i] = Array1DUtil.createArray(dataType, sizeX * sizeY); 343 } 344 } 345 346 class TileImageReaderWorker implements Runnable 347 { 348 final Rectangle region; 349 boolean done; 350 boolean failed; 351 352 public TileImageReaderWorker(Rectangle region) 353 { 354 super(); 355 356 this.region = region; 357 done = false; 358 failed = false; 359 } 360 361 @SuppressWarnings("resource") 362 @Override 363 public void run() 364 { 365 IcyBufferedImage img; 366 367 try 368 { 369 // get reader and working buffers 370 final IFormatReader r = getReader(); 371 final TileImageWorkBuffer buf = buffers.pop(); 372 373 try 374 { 375 try 376 { 377 // get image tile 378 if (c == -1) 379 { 380 img = getImageInternal(r, region, z, t, false, downScaleLevel, buf.rawBuffer, 381 buf.channelBuffer, buf.pixelBuffer); 382 // colormaps not yet set ? 383 if (colormaps[0] == null) 384 { 385 for (int c = 0; c < img.getSizeC(); c++) 386 colormaps[c] = img.getColorMap(c); 387 } 388 } 389 else 390 { 391 img = getImageInternal(r, region, z, t, c, false, downScaleLevel, buf.rawBuffer, 392 buf.channelBuffer, buf.pixelBuffer[0]); 393 // colormap not yet set ? 394 if (colormaps[0] == null) 395 colormaps[0] = img.getColorMap(0); 396 } 397 } 398 finally 399 { 400 // release reader 401 releaseReader(r); 402 } 403 404 // define destination point in destination 405 final Point pt = region.getLocation(); 406 pt.translate(-imageRegion.x, -imageRegion.y); 407 408 // copy tile to image result 409 result.copyData(img, null, pt); 410 } 411 finally 412 { 413 // release working buffer 414 buffers.push(buf); 415 } 416 } 417 catch (Exception e) 418 { 419 failed = true; 420 } 421 422 done = true; 423 } 424 } 425 426 // final image region 427 final Rectangle imageRegion; 428 // required image down scaling 429 final int downScaleLevel; 430 // resolution shift divider 431 final int resShift; 432 final int z; 433 final int t; 434 final int c; 435 final IcyBufferedImage result; 436 final IcyColorMap[] colormaps; 437 final Stack<TileImageWorkBuffer> buffers; 438 439 public LociTileImageReader(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 440 int tileH, ProgressListener listener) throws IOException, UnsupportedFormatException 441 { 442 super(); 443 444 this.z = z; 445 this.t = t; 446 this.c = c; 447 448 final OMEXMLMetadata meta = getOMEXMLMetaData(); 449 final int sizeX = MetaDataUtil.getSizeX(meta, series); 450 final int sizeY = MetaDataUtil.getSizeY(meta, series); 451 final DataType type = MetaDataUtil.getDataType(meta, series); 452 final int sizeC = (c == -1) ? MetaDataUtil.getSizeC(meta, series) : 1; 453 454 // define XY region to load 455 Rectangle adjRegion = new Rectangle(sizeX, sizeY); 456 if (region != null) 457 adjRegion = adjRegion.intersection(region); 458 459 // prepare main reader and get needed downScale 460 downScaleLevel = prepareReader(series, resolution); 461 // real resolution shift used by reader 462 resShift = getResolutionShift(); 463 464 // adapt region size to final image resolution 465 imageRegion = new Rectangle(adjRegion.x >> resolution, adjRegion.y >> resolution, 466 adjRegion.width >> resolution, adjRegion.height >> resolution); 467 // adapt region size to reader resolution 468 adjRegion = new Rectangle(adjRegion.x >> resShift, adjRegion.y >> resShift, adjRegion.width >> resShift, 469 adjRegion.height >> resShift); 470 471 // allocate result (adapted to final wanted resolution) 472 result = new IcyBufferedImage(imageRegion.width, imageRegion.height, sizeC, type); 473 // allocate colormaps 474 colormaps = new IcyColorMap[sizeC]; 475 // allocate working buffers 476 final int rgbChannelCount = reader.getRGBChannelCount(); 477 478 int tw = tileW; 479 int th = tileH; 480 481 // adjust tile size if needed 482 if (tw <= 0) 483 tw = getTileWidth(series); 484 if (tw <= 0) 485 tw = 512; 486 if (th <= 0) 487 th = getTileHeight(series); 488 if (th <= 0) 489 th = 512; 490 491 final int numThread = Math.max(1, SystemUtil.getNumberOfCPUs() - 1); 492 493 buffers = new Stack<TileImageWorkBuffer>(); 494 for (int i = 0; i < numThread; i++) 495 buffers.push(new TileImageWorkBuffer(tw, th, sizeC, rgbChannelCount, type)); 496 497 // create processor 498 final Processor readerProcessor = new Processor(numThread); 499 500 readerProcessor.setThreadName("Image tile reader"); 501 // to avoid multiple update 502 result.beginUpdate(); 503 504 try 505 { 506 // get all required tiles 507 final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th); 508 509 // submit all tasks 510 for (Rectangle tile : tiles) 511 { 512 // wait a bit if the process queue is full 513 while (readerProcessor.isFull()) 514 { 515 try 516 { 517 Thread.sleep(0); 518 } 519 catch (InterruptedException e) 520 { 521 // interrupt all processes 522 readerProcessor.shutdownNow(); 523 break; 524 } 525 } 526 527 // submit next task 528 readerProcessor.submit(new TileImageReaderWorker(tile.intersection(adjRegion))); 529 530 // display progression 531 if (listener != null) 532 { 533 // process cancel requested ? 534 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 535 { 536 // interrupt processes 537 readerProcessor.shutdownNow(); 538 break; 539 } 540 } 541 } 542 543 // wait for completion 544 while (readerProcessor.isProcessing()) 545 { 546 try 547 { 548 Thread.sleep(1); 549 } 550 catch (InterruptedException e) 551 { 552 // interrupt all processes 553 readerProcessor.shutdownNow(); 554 break; 555 } 556 557 // display progression 558 if (listener != null) 559 { 560 // process cancel requested ? 561 if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) 562 { 563 // interrupt processes 564 readerProcessor.shutdownNow(); 565 break; 566 } 567 } 568 } 569 570 // last wait for completion just in case we were interrupted 571 readerProcessor.waitAll(); 572 } 573 finally 574 { 575 result.endUpdate(); 576 } 577 578 // set back colormap 579 for (int i = 0; i < colormaps.length; i++) 580 result.setColorMap(i, colormaps[i], true); 581 582 // faster memory release 583 buffers.clear(); 584 } 585 } 586 587 /** 588 * Main image reader used to retrieve a specific format reader 589 */ 590 protected final ImageReader mainReader; 591 /** 592 * Current active reader, it can be a {@link TileStitcher} reader wrapper or the <i>internalReader</i> depending <i>groupFiles</i> state 593 */ 594 protected IFormatReader reader; 595 /** 596 * Current format reader (just for test and cloning reader operation)O 597 */ 598 protected IFormatReader internalReader; 599 /** 600 * Current format reader (just for accept test, it can be changed by accept(..) method) 601 */ 602 protected IFormatReader acceptReader; 603 604 /** 605 * Shared readers for multi threading 606 */ 607 protected final List<IFormatReader> readersPool; 608 609 /** 610 * Metadata options 611 */ 612 protected final DynamicMetadataOptions options; 613 /** 614 * Advanced settings 615 */ 616 protected boolean originalMetadata; 617 protected boolean groupFiles; 618 619 /** 620 * internal resolution levels 621 */ 622 protected int[] resolutions; 623 /** 624 * Internal opened path (Bio-formats does not always keep track of it) 625 */ 626 protected String openedPath; 627 /** 628 * Internal last used open flags 629 */ 630 protected int openFlags; 631 632 public LociImporterPlugin() 633 { 634 super(); 635 636 mainReader = new ImageReader(); 637 // just to be sure 638 mainReader.setAllowOpenFiles(true); 639 640 reader = null; 641 internalReader = null; 642 acceptReader = null; 643 readersPool = new ArrayList<IFormatReader>(); 644 645 options = new DynamicMetadataOptions(); 646 647 options.setMetadataLevel(MetadataLevel.NO_OVERLAYS); 648 // we don't care about validation 649 options.setValidate(false); 650 // disable ND2 native chunk map to avoid some issues with ND2 file 651 // TODO: remove when fixed in Bio-formats (see images\bio\bug\nd2\3.19_1.nd2 --> should be a timelaps and not a 652 // single image) 653 options.setBoolean(NativeND2Reader.USE_CHUNKMAP_KEY, Boolean.FALSE); 654 655 originalMetadata = false; 656 groupFiles = true; 657 resolutions = null; 658 openedPath = null; 659 openFlags = 0; 660 } 661 662 protected void setReader(String path) throws FormatException, IOException 663 { 664 IFormatReader newReader = internalReader; 665 666 // no reader defined so just get the good one 667 if (internalReader == null) 668 newReader = mainReader.getReader(path); 669 else 670 { 671 // don't check if the file is currently opened 672 if (!isOpen(path)) 673 { 674 // try to check only with extension first then open it if needed 675 // Note that OME TIFF can be opened with the classic TIFF reader 676 if (!internalReader.isThisType(path, false) && !internalReader.isThisType(path, true)) 677 newReader = mainReader.getReader(path); 678 } 679 } 680 681 // reader changed ? 682 if (internalReader != newReader) 683 { 684 // update it 685 internalReader = newReader; 686 // update 'accept' reader 687 acceptReader = newReader; 688 // use TileStitcher wrapper when grouping is ON 689 if (groupFiles) 690 reader = TileStitcher.makeTileStitcher(newReader); 691 else 692 reader = newReader; 693 } 694 } 695 696 protected void reportError(final String title, final String message, final String filename) 697 { 698 // TODO: enable that when LOCI will be ready 699 // ThreadUtil.invokeLater(new Runnable() 700 // { 701 // @Override 702 // public void run() 703 // { 704 // final ErrorReportFrame errorFrame = new ErrorReportFrame(null, title, message); 705 // 706 // errorFrame.setReportAction(new ActionListener() 707 // { 708 // @Override 709 // public void actionPerformed(ActionEvent e) 710 // { 711 // try 712 // { 713 // OMEUtil.reportLociError(filename, errorFrame.getReportMessage()); 714 // } 715 // catch (BadLocationException e1) 716 // { 717 // System.err.println("Error while sending report:"); 718 // IcyExceptionHandler.showErrorMessage(e1, false, true); 719 // } 720 // } 721 // }); 722 // } 723 // }); 724 } 725 726 /** 727 * When set to <code>true</code> the importer will also read original metadata (as 728 * annotations) 729 * 730 * @return the readAllMetadata state<br> 731 * @see #setReadOriginalMetadata(boolean) 732 */ 733 public boolean getReadOriginalMetadata() 734 { 735 return originalMetadata; 736 } 737 738 /** 739 * When set to <code>true</code> the importer will also read original metadata (as annotations). 740 */ 741 public void setReadOriginalMetadata(boolean value) 742 { 743 originalMetadata = value; 744 } 745 746 /** 747 * When set to <code>true</code> the importer will try to group files required for the whole 748 * dataset. 749 * 750 * @return the groupFiles 751 */ 752 public boolean isGroupFiles() 753 { 754 return groupFiles; 755 } 756 757 /** 758 * When set to <code>true</code> the importer will try to group files required for the whole 759 * dataset. 760 */ 761 public void setGroupFiles(boolean value) 762 { 763 groupFiles = value; 764 } 765 766 @Override 767 public List<FileFilter> getFileFilters() 768 { 769 final List<FileFilter> result = new ArrayList<FileFilter>(); 770 771 result.add(new LociAllFileFilter()); 772 result.add(new ExtensionFileFilter(new String[] {"tif", "tiff"}, "TIFF images / Bio-Formats")); 773 result.add(new ExtensionFileFilter(new String[] {"png"}, "PNG images / Bio-Formats")); 774 result.add(new ExtensionFileFilter(new String[] {"jpg", "jpeg"}, "JPEG images / Bio-Formats")); 775 result.add(new ExtensionFileFilter(new String[] {"avi"}, "AVI videos / Bio-Formats")); 776 777 // final IFormatReader[] readers = mainReader.getReaders(); 778 779 // for (IFormatReader reader : readers) 780 // result.add(new FormatFileFilter(reader, true)); 781 782 return result; 783 } 784 785 @Override 786 public boolean acceptFile(String path) 787 { 788 // easy discard 789 if (Loader.canDiscardImageFile(path)) 790 return false; 791 792 // better for Bio-Formats to have system path format (bug with Bio-Format ?) 793 final String adjPath = new File(path).getAbsolutePath(); 794 795 while (true) 796 { 797 try 798 { 799 // this method should not modify the current reader so we use a specific reader for that :) 800 if ((acceptReader == null) 801 || (!acceptReader.isThisType(adjPath, false) && !acceptReader.isThisType(adjPath, true))) 802 acceptReader = mainReader.getReader(adjPath); 803 804 return true; 805 } 806 catch (ClosedByInterruptException e) 807 { 808 // we don't accept this interrupt here --> remove interrupted state & retry 809 Thread.interrupted(); 810 } 811 catch (Exception e) 812 { 813 // assume false on exception (FormatException or IOException) 814 return false; 815 } 816 } 817 } 818 819 public boolean isOpen(String path) 820 { 821 return StringUtil.equals(getOpened(), FileUtil.getGenericPath(path)); 822 } 823 824 @Override 825 public String getOpened() 826 { 827 return openedPath; 828 829 // if (reader != null) 830 // return FileUtil.getGenericPath(reader.getCurrentFile()); 831 // 832 // return null; 833 } 834 835 @Override 836 public boolean open(String path, int flags) throws UnsupportedFormatException, IOException 837 { 838 // already opened ? 839 if (isOpen(path)) 840 return true; 841 842 // close first 843 close(); 844 845 try 846 { 847 // better for Bio-Formats to have system path format 848 final String adjPath = new File(path).getAbsolutePath(); 849 850 // ensure we have the correct reader 851 setReader(adjPath); 852 // then open it 853 openReader(reader, adjPath, flags); 854 855 // set reader in reader pool 856 synchronized (readersPool) 857 { 858 readersPool.add(reader); 859 } 860 861 // adjust opened path (always in 'generic format') 862 openedPath = FileUtil.getGenericPath(path); 863 // keep trace of last used flags 864 openFlags = flags; 865 // need to update resolution levels 866 resolutions = null; 867 868 return true; 869 } 870 catch (FormatException e) 871 { 872 throw translateException(path, e); 873 } 874 } 875 876 @Override 877 public void close() throws IOException 878 { 879 // something to close ? 880 if (getOpened() != null) 881 { 882 openedPath = null; 883 884 synchronized (readersPool) 885 { 886 // close all readers 887 for (IFormatReader r : readersPool) 888 r.close(); 889 890 readersPool.clear(); 891 } 892 } 893 } 894 895 /** 896 * Open reader for given file path 897 */ 898 protected void openReader(IFormatReader reader, String path, int flags) throws FormatException, IOException 899 { 900 // for safety 901 reader.close(); 902 903 switch (flags & FLAG_METADATA_MASK) 904 { 905 case FLAG_METADATA_MINIMUM: 906 options.setMetadataLevel(MetadataLevel.MINIMUM); 907 // set metadata option 908 reader.setOriginalMetadataPopulated(false); 909 // don't need to filter metadata 910 reader.setMetadataFiltered(false); 911 break; 912 913 case FLAG_METADATA_ALL: 914 options.setMetadataLevel(MetadataLevel.ALL); 915 // set metadata option 916 reader.setOriginalMetadataPopulated(true); 917 // may need metadata filtering 918 reader.setMetadataFiltered(true); 919 break; 920 921 default: 922 options.setMetadataLevel(MetadataLevel.NO_OVERLAYS); 923 // set metadata option 924 reader.setOriginalMetadataPopulated(originalMetadata); 925 // may need metadata filtering 926 reader.setMetadataFiltered(true); 927 break; 928 } 929 930 // disable flattening sub resolution 931 reader.setFlattenedResolutions(false); 932 // set file grouping 933 reader.setGroupFiles(groupFiles); 934 // prepare meta data store structure 935 reader.setMetadataStore((MetadataStore) OMEUtil.createOMEXMLMetadata()); 936 reader.setMetadataOptions(options); 937 938 // set path (id) 939 reader.setId(path); 940 } 941 942 /** 943 * Clone the current used reader conserving its properties and current path 944 */ 945 protected IFormatReader cloneReader() 946 throws FormatException, IOException, InstantiationException, IllegalAccessException 947 { 948 if (internalReader == null) 949 return null; 950 951 // create the new internal reader instance 952 final IFormatReader newReader = internalReader.getClass().newInstance(); 953 final IFormatReader result; 954 955 // use TileStitcher wrapper when grouping is ON 956 if (groupFiles) 957 result = TileStitcher.makeTileStitcher(newReader); 958 else 959 result = newReader; 960 961 // get opened file 962 final String path = getOpened(); 963 964 if (path != null) 965 // open reader for path (adjust path format for Bio-Format) 966 openReader(result, new File(path).getAbsolutePath(), openFlags); 967 968 return result; 969 } 970 971 /** 972 * Returns a reader to use for the current thread (allocate it if needed).<br> 973 * Any obtained reader should be released using {@link #releaseReader(IFormatReader)} 974 * 975 * @see #releaseReader(IFormatReader) 976 */ 977 public IFormatReader getReader() throws FormatException, IOException 978 { 979 try 980 { 981 final IFormatReader result; 982 983 synchronized (readersPool) 984 { 985 if (readersPool.isEmpty()) 986 result = cloneReader(); 987 // allocate last reader (faster) 988 else 989 result = readersPool.remove(readersPool.size() - 1); 990 } 991 992 final int s = reader.getSeries(); 993 final int r = reader.getResolution(); 994 995 // ensure we are working on same series and resolution 996 if (result.getSeries() != s) 997 result.setSeries(s); 998 if (result.getResolution() != r) 999 result.setResolution(r); 1000 1001 return result; 1002 } 1003 catch (InstantiationException e) 1004 { 1005 // better to rethrow as RuntimeException 1006 throw new RuntimeException(e.getMessage()); 1007 } 1008 catch (IllegalAccessException e) 1009 { 1010 // better to rethrow as RuntimeException 1011 throw new RuntimeException(e.getMessage()); 1012 } 1013 } 1014 1015 /** 1016 * Release the reader obtained through {@link #getReader()} to the reader pool. 1017 * 1018 * @see #getReader() 1019 */ 1020 public void releaseReader(IFormatReader r) 1021 { 1022 synchronized (readersPool) 1023 { 1024 readersPool.add(r); 1025 } 1026 } 1027 1028 /** 1029 * Prepare the reader to read data from specified series but keep the current / default resolution level.<br> 1030 * WARNING: this method should not be called while image reading operation are still occurring (multi threaded 1031 * read). 1032 */ 1033 protected void prepareReader(int series) 1034 { 1035 // series changed ? 1036 if (reader.getSeries() != series) 1037 { 1038 // set wanted series 1039 reader.setSeries(series); 1040 // reset resolution level 1041 resolutions = null; 1042 } 1043 1044 // need to update resolution levels ? 1045 if (resolutions == null) 1046 { 1047 // set default resolution 1048 reader.setResolution(0); 1049 1050 // get default sizeX 1051 final double sizeX = reader.getSizeX(); 1052 // get resolution count for this series 1053 final int resCount = reader.getResolutionCount(); 1054 1055 // init resolution levels 1056 final List<Integer> validResolutions = new ArrayList<Integer>(16); 1057 // full resolution at 0 (always) 1058 validResolutions.add(Integer.valueOf(0)); 1059 1060 // check the sub resolution level 1061 for (int r = 1; r < resCount; r++) 1062 { 1063 // set resolution level in reader 1064 reader.setResolution(r); 1065 1066 // get real resolution level 1067 final double level = Math.log(sizeX / reader.getSizeX()) / Math.log(2); 1068 final double levelInt = Math.floor(level); 1069 1070 // we only want 2^x sub resolution 1071 if (Math.abs(level - levelInt) < 0.005d) 1072 validResolutions.add(Integer.valueOf((int) levelInt)); 1073 } 1074 1075 // copy back to resolutions 1076 resolutions = new int[validResolutions.size()]; 1077 for (int i = 0; i < resolutions.length; i++) 1078 resolutions[i] = validResolutions.get(i).intValue(); 1079 } 1080 } 1081 1082 /** 1083 * Prepare the reader to read data from specified series and at specified resolution.<br> 1084 * WARNING: this method should not be called while image reading operation are still occurring (multi threaded 1085 * read). 1086 * 1087 * @return the image divisor factor to match the wanted resolution if needed 1088 */ 1089 protected int prepareReader(int series, int resolution) 1090 { 1091 prepareReader(series); 1092 1093 if (resolution > 0) 1094 { 1095 // find closest (but strictly <=) available resolution level 1096 int indRes = 1; 1097 while ((indRes < resolutions.length) && (resolutions[indRes] <= resolution)) 1098 indRes++; 1099 1100 // get back to correct resolution level index 1101 indRes--; 1102 // set resolution level 1103 reader.setResolution(indRes); 1104 1105 // return difference between selected resolution level and wanted resolution level 1106 return resolution - resolutions[indRes]; 1107 } 1108 1109 // just use default full resolution 1110 reader.setResolution(0); 1111 1112 return 0; 1113 } 1114 1115 /** 1116 * Internal use only 1117 */ 1118 protected int getResolutionShift() 1119 { 1120 return resolutions[reader.getResolution()]; 1121 } 1122 1123 /** 1124 * Internal use only 1125 */ 1126 protected double getResolutionDiviserFactor() 1127 { 1128 return 1d / Math.pow(2, getResolutionShift()); 1129 } 1130 1131 @Override 1132 public OMEXMLMetadata getOMEXMLMetaData() throws UnsupportedFormatException, IOException 1133 { 1134 // no image currently opened 1135 if (getOpened() == null) 1136 return null; 1137 1138 // retrieve metadata (don't need thread safe reader for this) 1139 final OMEXMLMetadata result = (OMEXMLMetadata) reader.getMetadataStore(); 1140 1141 // TileStitcher reduced series number (stitching occurred) ? 1142 if ((reader.getSeriesCount() == 1) && (MetaDataUtil.getNumSeries(result) > 1)) 1143 { 1144 // adjust series count in metadata 1145 MetaDataUtil.setNumSeries(result, 1); 1146 1147 final int sx = reader.getSizeX(); 1148 final int sy = reader.getSizeY(); 1149 1150 // fix metadata regarding reader information if available 1151 if ((sx > 0) && (sy > 0)) 1152 { 1153 MetaDataUtil.setSizeX(result, 0, sx); 1154 MetaDataUtil.setSizeY(result, 0, sy); 1155 } 1156 } 1157 1158 return result; 1159 } 1160 1161 @Deprecated 1162 @Override 1163 public OMEXMLMetadataImpl getMetaData() throws UnsupportedFormatException, IOException 1164 { 1165 return (OMEXMLMetadataImpl) getOMEXMLMetaData(); 1166 } 1167 1168 @Override 1169 public int getTileWidth(int series) throws UnsupportedFormatException, IOException 1170 { 1171 // no image currently opened 1172 if (getOpened() == null) 1173 return 0; 1174 1175 // prepare reader 1176 prepareReader(series); 1177 1178 // don't need thread safe reader for this 1179 int result = reader.getOptimalTileWidth(); 1180 1181 if (result == 0) 1182 return result; 1183 1184 // we want closest power of 2 (smaller or equal) 1185 return (int) Math.pow(2, Math.floor(Math.log(result) / Math.log(2d))); 1186 } 1187 1188 @Override 1189 public int getTileHeight(int series) throws UnsupportedFormatException, IOException 1190 { 1191 // no image currently opened 1192 if (getOpened() == null) 1193 return 0; 1194 1195 // prepare reader 1196 prepareReader(series); 1197 1198 // don't need thread safe reader for this 1199 int result = reader.getOptimalTileHeight(); 1200 1201 if (result == 0) 1202 return result; 1203 1204 // we want closest power of 2 (smaller or equal) 1205 return (int) Math.pow(2, Math.floor(Math.log(result) / Math.log(2d))); 1206 } 1207 1208 @Override 1209 public boolean isResolutionAvailable(int series, int resolution) throws UnsupportedFormatException, IOException 1210 { 1211 // no image currently opened 1212 if (getOpened() == null) 1213 return resolution == 0; 1214 1215 // prepare reader 1216 prepareReader(series); 1217 1218 if (resolution > 0) 1219 { 1220 // try to find wanted resolution 1221 for (int r : resolutions) 1222 if (r == resolution) 1223 return true; 1224 } 1225 1226 return resolution == 0; 1227 } 1228 1229 @SuppressWarnings("resource") 1230 @Override 1231 public IcyBufferedImage getThumbnail(int series) throws UnsupportedFormatException, IOException 1232 { 1233 // no image currently opened 1234 if (getOpened() == null) 1235 return null; 1236 1237 // stitched image ? --> use AbstractImageProvider implementation as getThumbnail(..) from TileSticher doesn't do 1238 // stitching 1239 if ((reader.getSizeX() != internalReader.getSizeX()) || (reader.getSizeY() != internalReader.getSizeY())) 1240 return super.getThumbnail(series); 1241 1242 try 1243 { 1244 // prepare reader (no down scaling here) 1245 prepareReader(series, 0); 1246 1247 final IFormatReader r = getReader(); 1248 1249 try 1250 { 1251 // get image 1252 return getThumbnail(r, r.getSizeZ() / 2, r.getSizeT() / 2); 1253 } 1254 finally 1255 { 1256 releaseReader(r); 1257 } 1258 } 1259 catch (FormatException e) 1260 { 1261 throw translateException(getOpened(), e); 1262 } 1263 catch (Throwable t) 1264 { 1265 // can happen if we don't have enough memory --> try default implementation 1266 return super.getThumbnail(series); 1267 } 1268 } 1269 1270 @SuppressWarnings("resource") 1271 @Override 1272 public Object getPixels(int series, int resolution, Rectangle rectangle, int z, int t, int c) 1273 throws UnsupportedFormatException, IOException 1274 { 1275 // no image currently opened 1276 if (getOpened() == null) 1277 return null; 1278 1279 try 1280 { 1281 // prepare reader and get down scale factor 1282 final int downScaleLevel = prepareReader(series, resolution); 1283 final IFormatReader r = getReader(); 1284 1285 final Rectangle adjRect; 1286 1287 // adjust rectangle to current reader resolution if needed 1288 if (rectangle != null) 1289 adjRect = Rectangle2DUtil.getScaledRectangle(rectangle, getResolutionDiviserFactor(), false, true) 1290 .getBounds(); 1291 else 1292 adjRect = null; 1293 1294 try 1295 { 1296 // return pixels 1297 return getPixelsInternal(r, adjRect, z, t, c, false, downScaleLevel); 1298 } 1299 finally 1300 { 1301 releaseReader(r); 1302 } 1303 } 1304 catch (FormatException e) 1305 { 1306 throw translateException(getOpened(), e); 1307 } 1308 } 1309 1310 @SuppressWarnings("resource") 1311 @Override 1312 public IcyBufferedImage getImage(int series, int resolution, Rectangle rectangle, int z, int t, int c) 1313 throws UnsupportedFormatException, IOException 1314 { 1315 // no image currently opened 1316 if (getOpened() == null) 1317 return null; 1318 1319 try 1320 { 1321 // prepare reader and get down scale factor if wanted resolution is not available 1322 final int downScaleLevel = prepareReader(series, resolution); 1323 final IFormatReader r = getReader(); 1324 final Rectangle adjRect; 1325 1326 // adjust rectangle to current reader resolution if needed 1327 if (rectangle != null) 1328 adjRect = Rectangle2DUtil.getScaledRectangle(rectangle, getResolutionDiviserFactor(), false, true) 1329 .getBounds(); 1330 else 1331 adjRect = null; 1332 1333 try 1334 { 1335 // get image 1336 return getImage(r, adjRect, z, t, c, downScaleLevel); 1337 } 1338 catch (IOException e) 1339 { 1340 throw e; 1341 } 1342 // not enough memory error ? 1343 catch (OutOfMemoryError e) 1344 { 1345 // need rescaling --> try tiling read 1346 if (downScaleLevel > 0) 1347 return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series), 1348 getTileHeight(series), null); 1349 1350 throw e; 1351 } 1352 // too large XY plan ? 1353 catch (UnsupportedOperationException e) 1354 { 1355 // need rescaling --> try tiling read 1356 if (downScaleLevel > 0) 1357 return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series), 1358 getTileHeight(series), null); 1359 1360 throw e; 1361 } 1362 catch (FormatException e) 1363 { 1364 if (!(e.getCause() instanceof ClosedByInterruptException) && (downScaleLevel > 0)) 1365 // we can have here a "Image plane too large. Only 2GB of data can be extracted at 1366 // one time." error here --> so can try to use tile loading when we need rescaling 1367 return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series), 1368 getTileHeight(series), null); 1369 1370 throw e; 1371 } 1372 catch (Exception e) 1373 { 1374 // we can have NegativeArraySizeException here for instance 1375 // try to use tile loading when we need rescaling 1376 if (downScaleLevel > 0) 1377 return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series), 1378 getTileHeight(series), null); 1379 1380 throw new UnsupportedOperationException(e); 1381 } 1382 finally 1383 { 1384 releaseReader(r); 1385 } 1386 } 1387 catch (FormatException e) 1388 { 1389 throw translateException(getOpened(), e); 1390 } 1391 } 1392 1393 public IcyBufferedImage getImageByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 1394 int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException 1395 { 1396 return new LociTileImageReader(series, resolution, region, z, t, c, tileW, tileH, listener).result; 1397 } 1398 1399 @Override 1400 public Object getPixelsByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW, 1401 int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException 1402 { 1403 return new LociTilePixelsReader(series, resolution, region, z, t, c, tileW, tileH, listener).result; 1404 } 1405 1406 /** 1407 * Load a thumbnail version of the image located at (Z, T) position from the specified 1408 * {@link IFormatReader} and 1409 * returns it as an IcyBufferedImage. 1410 * 1411 * @param reader 1412 * {@link IFormatReader} 1413 * @param z 1414 * Z position of the image to load 1415 * @param t 1416 * T position of the image to load 1417 * @return {@link IcyBufferedImage} 1418 */ 1419 public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t) 1420 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1421 { 1422 return getThumbnail(reader, z, t, -1); 1423 } 1424 1425 /** 1426 * Load a thumbnail version of the image located at (Z, T, C) position from the specified 1427 * {@link IFormatReader} and returns it as an IcyBufferedImage. 1428 * 1429 * @param reader 1430 * {@link IFormatReader} 1431 * @param z 1432 * Z position of the thumbnail to load 1433 * @param t 1434 * T position of the thumbnail to load 1435 * @param c 1436 * Channel index 1437 * @return {@link IcyBufferedImage} 1438 */ 1439 public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t, int c) 1440 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1441 { 1442 try 1443 { 1444 // all channel ? 1445 if (c == -1) 1446 return getImageInternal(reader, null, z, t, true, 0); 1447 1448 return getImageInternal(reader, null, z, t, c, true, 0); 1449 } 1450 catch (ClosedByInterruptException e) 1451 { 1452 // loading interrupted --> return null 1453 return null; 1454 } 1455 catch (Exception e) 1456 { 1457 // LOCI do not support thumbnail for all image, try compatible version 1458 return getThumbnailCompatible(reader, z, t, c); 1459 } 1460 } 1461 1462 /** 1463 * Load a thumbnail version of the image located at (Z, T) position from the specified 1464 * {@link IFormatReader} and returns it as an IcyBufferedImage.<br> 1465 * <i>Slow compatible version (load the original image and resize it)</i> 1466 * 1467 * @param reader 1468 * {@link IFormatReader} 1469 * @param z 1470 * Z position of the image to load 1471 * @param t 1472 * T position of the image to load 1473 * @return {@link IcyBufferedImage} 1474 */ 1475 public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t) 1476 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1477 { 1478 return getThumbnailCompatible(reader, z, t, -1); 1479 } 1480 1481 /** 1482 * Load a thumbnail version of the image located at (Z, T, C) position from the specified 1483 * {@link IFormatReader} and returns it as an IcyBufferedImage.<br> 1484 * <i>Slow compatible version (load the original image and resize it)</i> 1485 * 1486 * @param reader 1487 * {@link IFormatReader} 1488 * @param z 1489 * Z position of the thumbnail to load 1490 * @param t 1491 * T position of the thumbnail to load 1492 * @param c 1493 * Channel index 1494 * @return {@link IcyBufferedImage} 1495 */ 1496 public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t, int c) 1497 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1498 { 1499 final IcyBufferedImage image = getImage(reader, null, z, t, c, 0); 1500 1501 // scale it to desired dimension (fast enough as here we have a small image) 1502 final IcyBufferedImage result = IcyBufferedImageUtil.scale(image, reader.getThumbSizeX(), 1503 reader.getThumbSizeY(), FilterType.BILINEAR); 1504 1505 // preserve colormaps 1506 result.setColorMaps(image); 1507 1508 return result; 1509 } 1510 1511 /** 1512 * Load a single channel sub image at (Z, T, C) position from the specified {@link IFormatReader}<br> 1513 * and returns it as an IcyBufferedImage. 1514 * 1515 * @param reader 1516 * Reader used to load the image 1517 * @param rect 1518 * Define the image rectangular region we want to retrieve data for (considering current selected image 1519 * resolution).<br> 1520 * Set to <code>null</code> to retrieve the whole image. 1521 * @param z 1522 * Z position of the image to load 1523 * @param t 1524 * T position of the image to load 1525 * @param c 1526 * Channel index to load (-1 = all channels) 1527 * @param downScaleLevel 1528 * number of downscale to process (scale level = 1/2^downScaleLevel) 1529 * @return {@link IcyBufferedImage} 1530 */ 1531 public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c, 1532 int downScaleLevel) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1533 { 1534 // we want all channel ? use method to retrieve whole image 1535 if (c == -1) 1536 return getImageInternal(reader, rect, z, t, false, downScaleLevel); 1537 1538 return getImageInternal(reader, rect, z, t, c, false, downScaleLevel); 1539 } 1540 1541 /** 1542 * Load a single channel sub image at (Z, T, C) position from the specified {@link IFormatReader}<br> 1543 * and returns it as an IcyBufferedImage. 1544 * 1545 * @param reader 1546 * Reader used to load the image 1547 * @param rect 1548 * Define the image rectangular region we want to retrieve data for (considering current selected image 1549 * resolution).<br> 1550 * Set to <code>null</code> to retrieve the whole image. 1551 * @param z 1552 * Z position of the image to load 1553 * @param t 1554 * T position of the image to load 1555 * @param c 1556 * Channel index to load (-1 = all channels) 1557 * @return {@link IcyBufferedImage} 1558 */ 1559 public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c) 1560 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1561 { 1562 return getImage(reader, rect, z, t, c, 0); 1563 } 1564 1565 /** 1566 * Load the image located at (Z, T) position from the specified IFormatReader<br> 1567 * and return it as an IcyBufferedImage. 1568 * 1569 * @param reader 1570 * {@link IFormatReader} 1571 * @param rect 1572 * Define the image rectangular region we want to retrieve data for (considering current selected image 1573 * resolution).<br> 1574 * Set to <code>null</code> to retrieve the whole image. 1575 * @param z 1576 * Z position of the image to load 1577 * @param t 1578 * T position of the image to load 1579 * @return {@link IcyBufferedImage} 1580 */ 1581 public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t) 1582 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1583 { 1584 return getImage(reader, rect, z, t, -1, 0); 1585 } 1586 1587 /** 1588 * Load the image located at (Z, T) position from the specified IFormatReader<br> 1589 * and return it as an IcyBufferedImage (compatible and slower method). 1590 * 1591 * @param reader 1592 * {@link IFormatReader} 1593 * @param z 1594 * Z position of the image to load 1595 * @param t 1596 * T position of the image to load 1597 * @return {@link IcyBufferedImage} 1598 */ 1599 public static IcyBufferedImage getImageCompatible(IFormatReader reader, int z, int t) 1600 throws FormatException, IOException 1601 { 1602 final int sizeX = reader.getSizeX(); 1603 final int sizeY = reader.getSizeY(); 1604 final List<BufferedImage> imageList = new ArrayList<BufferedImage>(); 1605 final int sizeC = reader.getEffectiveSizeC(); 1606 1607 for (int c = 0; c < sizeC; c++) 1608 imageList.add(AWTImageTools.openImage(reader.openBytes(reader.getIndex(z, c, t)), reader, sizeX, sizeY)); 1609 1610 // combine channels 1611 return IcyBufferedImage.createFrom(imageList); 1612 1613 } 1614 1615 /** 1616 * Load pixels of the specified region of image at (Z, T, C) position and returns them as an 1617 * array. 1618 * 1619 * @param reader 1620 * Reader used to load the pixels 1621 * @param dataType 1622 * pixel data type 1623 * @param rect 1624 * Define the pixels rectangular region we want to load (considering current selected image resolution).<br> 1625 * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> 1626 * @param z 1627 * Z position of the pixels to load 1628 * @param t 1629 * T position of the pixels to load 1630 * @param c 1631 * Channel index to load 1632 * @param thumbnail 1633 * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> 1634 * parameter should contains thumbnail size 1635 * @param downScaleLevel 1636 * number of downscale to process (scale level = 1/2^downScaleLevel) 1637 * @param rawBuffer 1638 * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * 1639 * Datatype.size]) used to read the whole RGB raw data (can be <code>null</code>) 1640 * @param channelBuffer 1641 * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the 1642 * channel raw data (can be <code>null</code>) 1643 * @param pixelBuffer 1644 * pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel 1645 * converted data and to build the result image (can be <code>null</code>) 1646 * @return 1D array containing pixels data.<br> 1647 * The type of the array depends from the internal image data type 1648 * @throws UnsupportedOperationException 1649 * if the XY plane size is >= 2^31 pixels 1650 * @throws OutOfMemoryError 1651 * if there is not enough memory to open the image 1652 */ 1653 protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, 1654 boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer) 1655 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1656 { 1657 // get pixel data type 1658 final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); 1659 1660 // check we can open the image 1661 // Loader.checkOpening(resolutions[reader.getResolution()], rect.width, rect.height, 1, 1, 1, dataType, 1662 // ""); 1663 1664 // prepare informations 1665 final int rgbChanCount = reader.getRGBChannelCount(); 1666 final boolean interleaved = reader.isInterleaved(); 1667 final boolean little = reader.isLittleEndian(); 1668 1669 // allocate internal image data array if needed 1670 Object result = Array1DUtil.allocIfNull(pixelBuffer, dataType, rect.width * rect.height); 1671 // compute channel offsets 1672 final int baseC = c / rgbChanCount; 1673 final int subC = c % rgbChanCount; 1674 1675 // get image data (whole RGB data for RGB channel) 1676 byte[] rawData = getBytesInternal(reader, reader.getIndex(z, baseC, t), rect, thumbnail, rawBuffer); 1677 1678 // current final component 1679 final int componentByteLen = rawData.length / rgbChanCount; 1680 1681 // build data array 1682 if (interleaved) 1683 { 1684 // get channel interleaved data 1685 final byte[] channelData = Array1DUtil.getInterleavedData(rawData, subC, rgbChanCount, channelBuffer, 0, 1686 componentByteLen); 1687 ByteArrayConvert.byteArrayTo(channelData, 0, result, 0, componentByteLen, little); 1688 } 1689 else 1690 ByteArrayConvert.byteArrayTo(rawData, subC * componentByteLen, result, 0, componentByteLen, little); 1691 1692 // don't need downscaling ? --> we can return raw pixels immediately 1693 if (downScaleLevel <= 0) 1694 return result; 1695 1696 // do fast downscaling 1697 int it = downScaleLevel; 1698 int sizeX = rect.width; 1699 int sizeY = rect.height; 1700 1701 while (it-- > 0) 1702 { 1703 result = IcyBufferedImageUtil.downscaleBy2(result, sizeX, sizeY, dataType.isSigned(), true); 1704 sizeX /= 2; 1705 sizeY /= 2; 1706 } 1707 1708 return result; 1709 } 1710 1711 /** 1712 * Load pixels of the specified region of image at (Z, T, C) position and returns them as an 1713 * array. 1714 * 1715 * @param reader 1716 * Reader used to load the pixels 1717 * @param rect 1718 * Define the pixels rectangular region we want to load (considering current selected image resolution).<br> 1719 * Set to <code>null</code> to retrieve the whole image. 1720 * @param z 1721 * Z position of the pixels to load 1722 * @param t 1723 * T position of the pixels to load 1724 * @param c 1725 * Channel index to load 1726 * @param thumbnail 1727 * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> 1728 * parameter is then ignored) 1729 * @param downScaleLevel 1730 * number of downscale to process (scale level = 1/2^downScaleLevel) 1731 * @return 1D array containing pixels data.<br> 1732 * The type of the array depends from the internal image data type 1733 */ 1734 protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, 1735 boolean thumbnail, int downScaleLevel) 1736 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1737 { 1738 final Rectangle r; 1739 1740 if (thumbnail) 1741 r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); 1742 else if (rect == null) 1743 r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); 1744 else 1745 r = rect; 1746 1747 return getPixelsInternal(reader, r, z, t, c, thumbnail, downScaleLevel, null, null, null); 1748 } 1749 1750 /** 1751 * Load a single channel sub image at (Z, T, C) position from the specified 1752 * {@link IFormatReader}<br> 1753 * and returns it as an IcyBufferedImage. 1754 * 1755 * @param reader 1756 * Reader used to load the image 1757 * @param rect 1758 * Define the image rectangular region we want to retrieve data for (considering current selected image 1759 * resolution).<br> 1760 * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> 1761 * @param z 1762 * Z position of the image to load 1763 * @param t 1764 * T position of the image to load 1765 * @param c 1766 * Channel index to load 1767 * @param thumbnail 1768 * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> 1769 * parameter should contains thumbnail size 1770 * @param downScaleLevel 1771 * number of downscale to process (scale level = 1/2^downScaleLevel) 1772 * @param rawBuffer 1773 * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * 1774 * Datatype.size]) used to read the whole RGB raw data (can be <code>null</code>) 1775 * @param channelBuffer 1776 * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the 1777 * channel raw data (can be <code>null</code>) 1778 * @param pixelBuffer 1779 * pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel 1780 * converted data and to build the result image (can be <code>null</code>) 1781 * @return {@link IcyBufferedImage} 1782 * @throws UnsupportedOperationException 1783 * if the XY plane size is >= 2^31 pixels 1784 * @throws OutOfMemoryError 1785 * if there is not enough memory to open the image 1786 */ 1787 protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, 1788 boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer) 1789 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1790 { 1791 // get pixel data 1792 final Object pixelData = getPixelsInternal(reader, rect, z, t, c, thumbnail, downScaleLevel, rawBuffer, 1793 channelBuffer, pixelBuffer); 1794 // get pixel data type 1795 final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); 1796 1797 // get final sizeX and sizeY 1798 int sizeX = rect.width; 1799 int sizeY = rect.height; 1800 int downScale = downScaleLevel; 1801 while (downScale > 0) 1802 { 1803 sizeX /= 2; 1804 sizeY /= 2; 1805 downScale--; 1806 } 1807 1808 // create the single channel result image from pixel data 1809 final IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, pixelData, dataType.isSigned()); 1810 1811 IcyColorMap map = null; 1812 final int rgbChannel = reader.getRGBChannelCount(); 1813 1814 // indexed color ? 1815 if (reader.isIndexed()) 1816 { 1817 // only 8 bits and 16 bits lookup table supported 1818 switch (dataType.getJavaType()) 1819 { 1820 case BYTE: 1821 final byte[][] bmap = reader.get8BitLookupTable(); 1822 if (bmap != null) 1823 map = new IcyColorMap("Channel " + c, bmap); 1824 break; 1825 1826 case SHORT: 1827 final short[][] smap = reader.get16BitLookupTable(); 1828 if (smap != null) 1829 map = new IcyColorMap("Channel " + c, smap); 1830 break; 1831 1832 default: 1833 break; 1834 } 1835 } 1836 1837 // no RGB image ? 1838 if (rgbChannel <= 1) 1839 { 1840 // colormap not set (or black) ? --> try to use metadata 1841 if ((map == null) || map.isBlack()) 1842 { 1843 final OMEXMLMetadata metaData = (OMEXMLMetadata) reader.getMetadataStore(); 1844 final Color color = MetaDataUtil.getChannelColor(metaData, reader.getSeries(), c); 1845 1846 if ((color != null) && !ColorUtil.isBlack(color)) 1847 map = new LinearColorMap("Channel " + c, color); 1848 else 1849 map = null; 1850 } 1851 } 1852 else 1853 { 1854 switch (c) 1855 { 1856 case 0: 1857 map = LinearColorMap.red_; 1858 break; 1859 case 1: 1860 map = LinearColorMap.green_; 1861 break; 1862 case 2: 1863 map = LinearColorMap.blue_; 1864 break; 1865 case 3: 1866 map = LinearColorMap.alpha_; 1867 break; 1868 } 1869 } 1870 1871 // we were able to retrieve a colormap ? --> set it 1872 if (map != null) 1873 result.setColorMap(0, map, true); 1874 1875 return result; 1876 } 1877 1878 /** 1879 * Load a single channel sub image at (Z, T, C) position from the specified 1880 * {@link IFormatReader}<br> 1881 * and returns it as an IcyBufferedImage. 1882 * 1883 * @param reader 1884 * Reader used to load the image 1885 * @param rect 1886 * Define the image rectangular region we want to retrieve data for (considering current selected image 1887 * resolution).<br> 1888 * Set to <code>null</code> to retrieve the whole image. 1889 * @param z 1890 * Z position of the image to load 1891 * @param t 1892 * T position of the image to load 1893 * @param c 1894 * Channel index to load 1895 * @param thumbnail 1896 * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> 1897 * parameter is then ignored) 1898 * @param downScaleLevel 1899 * number of downscale to process (scale level = 1/2^downScaleLevel) 1900 * @return {@link IcyBufferedImage} 1901 */ 1902 protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, 1903 boolean thumbnail, int downScaleLevel) 1904 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1905 { 1906 final Rectangle r; 1907 1908 if (thumbnail) 1909 r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); 1910 else if (rect == null) 1911 r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); 1912 else 1913 r = rect; 1914 1915 return getImageInternal(reader, r, z, t, c, thumbnail, downScaleLevel, null, null, null); 1916 } 1917 1918 /** 1919 * Load the image located at (Z, T) position from the specified IFormatReader and return it as 1920 * an IcyBufferedImage. 1921 * 1922 * @param reader 1923 * {@link IFormatReader} 1924 * @param rect 1925 * Define the image rectangular region we want to retrieve data for (considering current selected image 1926 * reader resolution).<br> 1927 * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> 1928 * @param z 1929 * Z position of the image to load 1930 * @param t 1931 * T position of the image to load 1932 * @param thumbnail 1933 * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> parameter should 1934 * contains thumbnail size 1935 * @param downScaleLevel 1936 * number of downscale to process after loading the image (scale level = 1/2^downScaleLevel) 1937 * @param rawBuffer 1938 * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * Datatype.size]) used to 1939 * read the whole RGB raw data (can be <code>null</code>) 1940 * @param channelBuffer 1941 * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the channel raw data (can be 1942 * <code>null</code>) 1943 * @param pixelBuffer 1944 * pre allocated 2D array ([SizeC][SizeX*SizeY]) pixel data buffer used to receive the pixel converted data 1945 * and to build the result image (can be <code>null</code>) 1946 * @return {@link IcyBufferedImage} 1947 * @throws UnsupportedOperationException 1948 * if the XY plane size is >= 2^31 pixels 1949 * @throws OutOfMemoryError 1950 * if there is not enough memory to open the image 1951 */ 1952 protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, 1953 boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object[] pixelBuffer) 1954 throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException 1955 { 1956 // get pixel data type 1957 final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); 1958 // get sizeC 1959 final int effSizeC = reader.getEffectiveSizeC(); 1960 final int rgbChanCount = reader.getRGBChannelCount(); 1961 final int sizeX = rect.width; 1962 final int sizeY = rect.height; 1963 final int sizeC = effSizeC * rgbChanCount; 1964 1965 // check we can open the image 1966 // Loader.checkOpening(resolutions[reader.getResolution()], sizeX, sizeY, sizeC, 1, 1, dataType, ""); 1967 1968 final int series = reader.getSeries(); 1969 // prepare informations 1970 final boolean indexed = reader.isIndexed(); 1971 final boolean little = reader.isLittleEndian(); 1972 final OMEXMLMetadata metaData = (OMEXMLMetadata) reader.getMetadataStore(); 1973 1974 // prepare internal image data array 1975 final Object[] pixelData; 1976 1977 if (pixelBuffer == null) 1978 { 1979 // allocate array 1980 pixelData = Array2DUtil.createArray(dataType, sizeC); 1981 for (int i = 0; i < sizeC; i++) 1982 pixelData[i] = Array1DUtil.createArray(dataType, sizeX * sizeY); 1983 } 1984 else 1985 pixelData = pixelBuffer; 1986 1987 // colormap allocation 1988 final IcyColorMap[] colormaps = new IcyColorMap[effSizeC]; 1989 1990 byte[] rawData = null; 1991 for (int effC = 0; effC < effSizeC; effC++) 1992 { 1993 // get data 1994 rawData = getBytesInternal(reader, reader.getIndex(z, effC, t), rect, thumbnail, rawBuffer); 1995 1996 // current final component 1997 final int c = effC * rgbChanCount; 1998 final int componentByteLen = rawData.length / rgbChanCount; 1999 2000 // build data array 2001 int inOffset = 0; 2002 if (reader.isInterleaved()) 2003 { 2004 final byte[] channelData = (channelBuffer == null) ? new byte[componentByteLen] : channelBuffer; 2005 2006 for (int sc = 0; sc < rgbChanCount; sc++) 2007 { 2008 // get channel interleaved data 2009 Array1DUtil.getInterleavedData(rawData, inOffset, rgbChanCount, channelData, 0, componentByteLen); 2010 ByteArrayConvert.byteArrayTo(channelData, 0, pixelData[c + sc], 0, componentByteLen, little); 2011 inOffset++; 2012 } 2013 } 2014 else 2015 { 2016 for (int sc = 0; sc < rgbChanCount; sc++) 2017 { 2018 ByteArrayConvert.byteArrayTo(rawData, inOffset, pixelData[c + sc], 0, componentByteLen, little); 2019 inOffset += componentByteLen; 2020 } 2021 } 2022 2023 // indexed color ? 2024 if (indexed) 2025 { 2026 // only 8 bits and 16 bits lookup table supported 2027 switch (dataType.getJavaType()) 2028 { 2029 case BYTE: 2030 final byte[][] bmap = reader.get8BitLookupTable(); 2031 if (bmap != null) 2032 colormaps[effC] = new IcyColorMap("Channel " + effC, bmap); 2033 break; 2034 2035 case SHORT: 2036 final short[][] smap = reader.get16BitLookupTable(); 2037 if (smap != null) 2038 colormaps[effC] = new IcyColorMap("Channel " + effC, smap); 2039 break; 2040 2041 default: 2042 colormaps[effC] = null; 2043 break; 2044 } 2045 } 2046 2047 // no RGB image ? 2048 if (rgbChanCount <= 1) 2049 { 2050 // colormap not yet set (or black) ? --> try to use metadata 2051 if ((colormaps[effC] == null) || colormaps[effC].isBlack()) 2052 { 2053 final Color color = MetaDataUtil.getChannelColor(metaData, series, effC); 2054 2055 if ((color != null) && !ColorUtil.isBlack(color)) 2056 colormaps[effC] = new LinearColorMap("Channel " + effC, color); 2057 else 2058 colormaps[effC] = null; 2059 } 2060 } 2061 } 2062 2063 // create result image 2064 IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, pixelData, dataType.isSigned()); 2065 2066 // do downscaling if needed 2067 result = IcyBufferedImageUtil.downscaleBy2(result, true, downScaleLevel); 2068 2069 // affect colormap 2070 result.beginUpdate(); 2071 try 2072 { 2073 // RGB image (can't use colormaps) 2074 if (rgbChanCount > 1) 2075 { 2076 // RGB at least ? --> set RGB colormap 2077 if ((sizeC >= 3) && (rgbChanCount >= 3)) 2078 { 2079 result.setColorMap(0, LinearColorMap.red_, true); 2080 result.setColorMap(1, LinearColorMap.green_, true); 2081 result.setColorMap(2, LinearColorMap.blue_, true); 2082 } 2083 // RGBA ? --> set alpha colormap 2084 if ((sizeC >= 4) && ((rgbChanCount >= 4) || (reader instanceof PNGReader) 2085 || (reader instanceof APNGReader) || (reader instanceof JPEG2000Reader))) 2086 result.setColorMap(3, LinearColorMap.alpha_, true); 2087 } 2088 // fluo image 2089 else if (sizeC == effSizeC) 2090 { 2091 // set colormaps 2092 for (int comp = 0; comp < effSizeC; comp++) 2093 { 2094 // we were able to retrieve a colormap for that channel ? --> set it 2095 if (colormaps[comp] != null) 2096 result.setColorMap(comp, colormaps[comp], true); 2097 } 2098 } 2099 } 2100 finally 2101 { 2102 result.endUpdate(); 2103 } 2104 2105 return result; 2106 } 2107 2108 /** 2109 * Load the image located at (Z, T) position from the specified IFormatReader<br> 2110 * and return it as an IcyBufferedImage. 2111 * 2112 * @param reader 2113 * {@link IFormatReader} 2114 * @param rect 2115 * Define the image rectangular region we want to retrieve data for (considering current selected image 2116 * resolution).<br> 2117 * Set to <code>null</code> to retrieve the whole image. 2118 * @param z 2119 * Z position of the image to load 2120 * @param t 2121 * T position of the image to load 2122 * @param thumbnail 2123 * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> parameter is then ignored) 2124 * @param downScaleLevel 2125 * number of downscale to process (scale level = 1/2^downScaleLevel) 2126 * @return {@link IcyBufferedImage} 2127 */ 2128 protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, 2129 boolean thumbnail, int downScaleLevel) throws FormatException, IOException 2130 { 2131 final Rectangle r; 2132 2133 if (thumbnail) 2134 r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); 2135 else if (rect == null) 2136 r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); 2137 else 2138 r = rect; 2139 2140 return getImageInternal(reader, r, z, t, thumbnail, downScaleLevel, null, null, null); 2141 } 2142 2143 /** 2144 * <b>Internal use only !!</b><br> 2145 * 2146 * @param reader 2147 * Reader used to load the pixels 2148 * @param index 2149 * plane index 2150 * @param rect 2151 * Define the pixels rectangular region we want to load (considering current selected image resolution).<br> 2152 * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> 2153 * @param thumbnail 2154 * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> 2155 * parameter should contains thumbnail size 2156 * @param buffer 2157 * pre allocated output byte data buffer ([reader.getRGBChannelCount() * rect.width * rect.height * 2158 * Datatype.size]) used to 2159 * read the whole RGB raw data (can be <code>null</code>) 2160 * @return byte array containing pixels data.<br> 2161 * @throws UnsupportedOperationException 2162 * if the XY plane size is >= 2^31 pixels 2163 * @throws OutOfMemoryError 2164 * if there is not enough memory to open the image 2165 */ 2166 protected static byte[] getBytesInternal(IFormatReader reader, int index, Rectangle rect, boolean thumbnail, 2167 byte[] buffer) throws FormatException, IOException 2168 { 2169 if (thumbnail) 2170 return reader.openThumbBytes(index); 2171 2172 final Rectangle imgRect = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); 2173 2174 // TODO: we should check that we open a big image and then use tile loading instead 2175 2176 // need to allocate 2177 if (buffer == null) 2178 { 2179 // return whole image 2180 if ((rect == null) || rect.contains(imgRect)) 2181 return reader.openBytes(index); 2182 2183 // return region 2184 return reader.openBytes(index, rect.x, rect.y, rect.width, rect.height); 2185 } 2186 2187 // already allocated / whole image 2188 if ((rect == null) || rect.equals(imgRect)) 2189 return reader.openBytes(index, buffer); 2190 2191 // return region 2192 return reader.openBytes(index, buffer, rect.x, rect.y, rect.width, rect.height); 2193 } 2194 2195 protected static UnsupportedFormatException translateException(String path, FormatException exception) 2196 { 2197 if (exception instanceof UnknownFormatException) 2198 return new UnsupportedFormatException(path + ": Unknown image format.", exception); 2199 else if (exception instanceof MissingLibraryException) 2200 return new UnsupportedFormatException(path + ": Missing library to load the image.", exception); 2201 else if (exception.getCause() instanceof ClosedByInterruptException) 2202 return new UnsupportedFormatException(path + ": loading interrupted.", exception.getCause()); 2203 else 2204 return new UnsupportedFormatException(path + ": Unsupported image.", exception); 2205 } 2206}