/** * */ package plugins.kernel.importer; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.util.ArrayList; import java.util.List; import java.util.Stack; import javax.swing.filechooser.FileFilter; import icy.common.exception.UnsupportedFormatException; import icy.common.listener.ProgressListener; import icy.file.FileUtil; import icy.file.Loader; import icy.gui.dialog.LoaderDialog.AllImagesFileFilter; import icy.image.IcyBufferedImage; import icy.image.IcyBufferedImageUtil; import icy.image.colormap.IcyColorMap; import icy.image.colormap.LinearColorMap; import icy.plugin.abstract_.PluginSequenceFileImporter; import icy.sequence.MetaDataUtil; import icy.system.SystemUtil; import icy.system.thread.Processor; import icy.type.DataType; import icy.type.collection.array.Array1DUtil; import icy.type.collection.array.Array2DUtil; import icy.type.collection.array.ByteArrayConvert; import icy.util.ColorUtil; import icy.util.StringUtil; import jxl.biff.drawing.PNGReader; import loci.formats.FormatException; import loci.formats.IFormatReader; import loci.formats.ImageReader; import loci.formats.MissingLibraryException; import loci.formats.UnknownFormatException; import loci.formats.gui.AWTImageTools; import loci.formats.gui.ExtensionFileFilter; import loci.formats.in.APNGReader; import loci.formats.in.JPEG2000Reader; import loci.formats.ome.OMEXMLMetadataImpl; /** * LOCI Bio-Formats library importer class. * * @author Stephane */ public class LociImporterPlugin extends PluginSequenceFileImporter { protected class LociAllFileFilter extends AllImagesFileFilter { @Override public String getDescription() { return "All image files / Bio-Formats"; } }; /** * Used for multi thread tile image reading. * * @author Stephane */ class LociTileImageReader { class WorkBuffer { final byte[] rawBuffer; final byte[] channelBuffer; final Object[] pixelBuffer; public WorkBuffer(int sizeX, int sizeY, int sizeC, int rgbChannel, DataType dataType) { super(); // allocate arrays rawBuffer = new byte[sizeX * sizeY * rgbChannel * dataType.getSize()]; channelBuffer = new byte[sizeX * sizeY * dataType.getSize()]; pixelBuffer = Array2DUtil.createArray(dataType, sizeC); for (int i = 0; i < sizeC; i++) pixelBuffer[i] = Array1DUtil.createArray(dataType, sizeX * sizeY); } } class TileReaderWorker implements Runnable { final Rectangle region; boolean done; boolean failed; public TileReaderWorker(Rectangle region) { super(); this.region = region; done = false; failed = false; } @SuppressWarnings("resource") @Override public void run() { IcyBufferedImage img; try { // get reader and working buffers final IFormatReader r = getReader(); final WorkBuffer buf = buffers.pop(); try { try { // get image tile if (c == -1) { img = getImageInternal(r, region, z, t, false, buf.rawBuffer, buf.channelBuffer, buf.pixelBuffer); } else { img = getImageInternal(r, region, z, t, c, false, buf.rawBuffer, buf.channelBuffer, buf.pixelBuffer); } } finally { // release reader releaseReader(r); } // downscale image if needed img = downScale(img, downScaleLevel); // copy tile to image result result.copyData(img, null, new Point(region.x / resDivider, region.y / resDivider)); } finally { // release working buffer buffers.push(buf); } } catch (Exception e) { failed = true; } done = true; } } // required image down scaling final int downScaleLevel; // resolution divider final int resDivider; final int z; final int t; final int c; final IcyBufferedImage result; final Stack<WorkBuffer> buffers; public LociTileImageReader(int serie, int resolution, int z, int t, int c, int tileW, int tileH, ProgressListener listener) throws IOException, UnsupportedFormatException { super(); this.z = z; this.t = t; this.c = c; final OMEXMLMetadataImpl meta = getMetaData(); final int sizeX = MetaDataUtil.getSizeX(meta, serie); final int sizeY = MetaDataUtil.getSizeY(meta, serie); final int numThread = Math.max(1, SystemUtil.getNumberOfCPUs() - 1); // prepare main reader and get needed downScale downScaleLevel = prepareReader(serie, resolution); // resolution divider resDivider = (int) Math.pow(2, resolution); // allocate result result = new IcyBufferedImage(sizeX / resDivider, sizeY / resDivider, MetaDataUtil.getSizeC(meta, serie), MetaDataUtil.getDataType(meta, serie)); // allocate working buffers final int sizeC = MetaDataUtil.getSizeC(meta, serie); final int rgbChannelCount = reader.getRGBChannelCount(); final DataType dataType = MetaDataUtil.getDataType(meta, serie); buffers = new Stack<WorkBuffer>(); for (int i = 0; i < numThread; i++) buffers.push(new WorkBuffer(tileW, tileH, sizeC, rgbChannelCount, dataType)); // create processor final Processor readerProcessor = new Processor(numThread); readerProcessor.setThreadName("Image tile reader"); // to avoid multiple update result.beginUpdate(); try { final List<Rectangle> tiles = getTileList(sizeX, sizeY, tileW, tileH); // submit all tasks for (Rectangle tile : tiles) { // wait a bit if the process queue is full while (readerProcessor.isFull()) { try { Thread.sleep(0); } catch (InterruptedException e) { // interrupt all processes readerProcessor.shutdownNow(); break; } } // submit next task readerProcessor.submit(new TileReaderWorker(tile)); // display progression if (listener != null) { // process cancel requested ? if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) { // interrupt processes readerProcessor.shutdownNow(); break; } } } // wait for completion while (readerProcessor.isProcessing()) { try { Thread.sleep(1); } catch (InterruptedException e) { // interrupt all processes readerProcessor.shutdownNow(); break; } // display progression if (listener != null) { // process cancel requested ? if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size())) { // interrupt processes readerProcessor.shutdownNow(); break; } } } // last wait for completion just in case we were interrupted readerProcessor.waitAll(); } finally { result.endUpdate(); } // faster memory release buffers.clear(); } } /** * Main image reader used to retrieve a specific format reader */ protected final ImageReader mainReader; /** * Current format reader */ protected IFormatReader reader; /** * Shared readers for multi threading */ protected final List<IFormatReader> readersPool; /** * Advanced settings */ protected boolean originalMetadata; protected boolean groupFiles; public LociImporterPlugin() { super(); mainReader = new ImageReader(); // just to be sure mainReader.setAllowOpenFiles(true); reader = null; readersPool = new ArrayList<IFormatReader>(); originalMetadata = false; groupFiles = false; } protected void setReader(String path) throws FormatException, IOException { // no reader defined so just get the good one if (reader == null) reader = mainReader.getReader(path); else { // don't check if the file is currently opened if (!isOpen(path)) { // try to check with extension only first then open it if needed if (!reader.isThisType(path, false) && !reader.isThisType(path, true)) reader = mainReader.getReader(path); } } } protected void reportError(final String title, final String message, final String filename) { // TODO: enable that when LOCI will be ready // ThreadUtil.invokeLater(new Runnable() // { // @Override // public void run() // { // final ErrorReportFrame errorFrame = new ErrorReportFrame(null, title, message); // // errorFrame.setReportAction(new ActionListener() // { // @Override // public void actionPerformed(ActionEvent e) // { // try // { // OMEUtil.reportLociError(filename, errorFrame.getReportMessage()); // } // catch (BadLocationException e1) // { // System.err.println("Error while sending report:"); // IcyExceptionHandler.showErrorMessage(e1, false, true); // } // } // }); // } // }); } /** * When set to <code>true</code> the importer will also read original metadata (as * annotations) * * @return the readAllMetadata state<br> * @see #setReadOriginalMetadata(boolean) */ public boolean getReadOriginalMetadata() { return originalMetadata; } /** * When set to <code>true</code> the importer will also read original metadata (as * annotations) */ public void setReadOriginalMetadata(boolean value) { originalMetadata = value; } /** * When set to <code>true</code> the importer will try to group files required for the whole * dataset. * * @return the groupFiles */ public boolean isGroupFiles() { return groupFiles; } /** * When set to <code>true</code> the importer will try to group files required for the whole * dataset. */ public void setGroupFiles(boolean value) { groupFiles = value; } @Override public List<FileFilter> getFileFilters() { final List<FileFilter> result = new ArrayList<FileFilter>(); result.add(new LociAllFileFilter()); result.add(new ExtensionFileFilter(new String[] {"tif", "tiff"}, "TIFF images / Bio-Formats")); result.add(new ExtensionFileFilter(new String[] {"png"}, "PNG images / Bio-Formats")); result.add(new ExtensionFileFilter(new String[] {"jpg", "jpeg"}, "JPEG images / Bio-Formats")); result.add(new ExtensionFileFilter(new String[] {"avi"}, "AVI videos / Bio-Formats")); // final IFormatReader[] readers = mainReader.getReaders(); // for (IFormatReader reader : readers) // result.add(new FormatFileFilter(reader, true)); return result; } @Override public boolean acceptFile(String path) { // easy discard if (Loader.canDiscardImageFile(path)) return false; try { // better for Bio-Formats to have system path format (bug with Bio-Format?) final String adjPath = new File(path).getAbsolutePath(); // this method should not modify the current reader ! // no reader defined or not the same type --> try to obtain the reader for this file if ((reader == null) || (!reader.isThisType(adjPath, false) && !reader.isThisType(adjPath, true))) mainReader.getReader(adjPath); return true; } catch (Exception e) { // assume false on exception (FormatException or IOException) return false; } } public boolean isOpen(String path) { return StringUtil.equals(getOpened(), FileUtil.getGenericPath(path)); } @Override public String getOpened() { if (reader != null) return FileUtil.getGenericPath(reader.getCurrentFile()); return null; } @Override public boolean open(String path, int flags) throws UnsupportedFormatException, IOException { // already opened ? if (isOpen(path)) return true; // close first close(); try { // better for Bio-Formats to have system path format final String adjPath = new File(path).getAbsolutePath(); // ensure we have the correct reader setReader(adjPath); // disable file grouping reader.setGroupFiles(groupFiles); // we want all metadata reader.setOriginalMetadataPopulated(originalMetadata); // prepare meta data store structure reader.setMetadataStore(new OMEXMLMetadataImpl()); // load file with LOCI library reader.setId(adjPath); // set reader in reader pool synchronized (readersPool) { readersPool.add(reader); } return true; } catch (FormatException e) { throw translateException(path, e); } } @Override public void close() throws IOException { // something to close ? if (getOpened() != null) { synchronized (readersPool) { // close all readers for (IFormatReader r : readersPool) r.close(); readersPool.clear(); } } } /** * Clone the current used reader conserving its properties and current path */ protected IFormatReader cloneReader() throws FormatException, IOException, InstantiationException, IllegalAccessException { if (reader == null) return null; // create the new reader instance final IFormatReader result = reader.getClass().newInstance(); // get opened file final String path = getOpened(); if (path != null) { // better for Bio-Formats to have system path format final String adjPath = new File(path).getAbsolutePath(); // disable file grouping result.setGroupFiles(groupFiles); // we want all metadata result.setOriginalMetadataPopulated(originalMetadata); // prepare meta data store structure result.setMetadataStore(new OMEXMLMetadataImpl()); // load file with LOCI library result.setId(adjPath); // preserve serie and resolution info result.setSeries(reader.getSeries()); result.setResolution(reader.getResolution()); } return result; } /** * Returns a reader to use for the current thread (allocate it if needed).<br> * Any obtained reader should be released using {@link #releaseReader(IFormatReader)} * * @see #releaseReader(IFormatReader) */ public IFormatReader getReader() throws FormatException, IOException { try { synchronized (readersPool) { if (readersPool.isEmpty()) readersPool.add(cloneReader()); // allocate last reader (faster) return readersPool.remove(readersPool.size() - 1); } } catch (InstantiationException e) { // better to rethrow as RuntimeException throw new RuntimeException(e.getMessage()); } catch (IllegalAccessException e) { // better to rethrow as RuntimeException throw new RuntimeException(e.getMessage()); } } /** * Release the reader obtained through {@link #getReader()} to the reader pool. * * @see #getReader() */ public void releaseReader(IFormatReader r) { synchronized (readersPool) { readersPool.add(r); } } /** * Prepare the reader to read data from specified serie and at specified resolution.<br> * * @return the image divisor factor to match the wanted resolution if needed */ protected int prepareReader(int serie, int resolution) { final int resCount; final int res; // set wanted serie reader.setSeries(serie); // set wanted resolution resCount = reader.getResolutionCount(); if (resolution >= resCount) res = resCount - 1; else res = resolution; reader.setResolution(res); return resolution - res; } @Override public OMEXMLMetadataImpl getMetaData() throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; // don't need thread safe reader for this return (OMEXMLMetadataImpl) reader.getMetadataStore(); } @Override public int getTileWidth(int serie) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return 0; // prepare reader prepareReader(serie, 0); // don't need thread safe reader for this return reader.getOptimalTileWidth(); } @Override public int getTileHeight(int serie) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return 0; // prepare reader prepareReader(serie, 0); // don't need thread safe reader for this return reader.getOptimalTileHeight(); } @SuppressWarnings("resource") @Override public IcyBufferedImage getThumbnail(int serie) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader (no down scaling here) prepareReader(serie, 0); final IFormatReader r = getReader(); try { // get image return getThumbnail(reader, reader.getSizeZ() / 2, reader.getSizeT() / 2); } finally { releaseReader(r); } } catch (FormatException e) { throw translateException(getOpened(), e); } catch (Throwable t) { // can happen if we don't have enough memory --> try default implementation return super.getThumbnail(serie); } } @SuppressWarnings("resource") @Override public Object getPixels(int serie, int resolution, Rectangle rectangle, int z, int t, int c) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor final int downScaleLevel = prepareReader(serie, resolution); // no need to rescale ? --> directly return the pixels if (downScaleLevel == 0) { final Object result; final IFormatReader r = getReader(); try { // get pixels result = getPixelsInternal(reader, rectangle, z, t, c, false); } finally { releaseReader(r); } return result; } // use classic getImage method when we need rescaling return getImage(serie, resolution, rectangle, z, t, c).getDataXY(0); } catch (FormatException e) { throw translateException(getOpened(), e); } } @SuppressWarnings("resource") @Override public IcyBufferedImage getImage(int serie, int resolution, Rectangle rectangle, int z, int t, int c) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor if wanted resolution is not available final int downScaleLevel = prepareReader(serie, resolution); final IFormatReader r = getReader(); try { // get image final IcyBufferedImage result = getImage(reader, rectangle, z, t, c); // return down scaled version if needed return downScale(result, downScaleLevel); } // not enough memory error ? catch (OutOfMemoryError e) { // need rescaling --> try tiling read if (downScaleLevel > 0) return getImageByTile(serie, resolution, z, t, c, getTileWidth(serie), getTileHeight(serie), null); throw e; } // too large XY plan ? catch (UnsupportedOperationException e) { // need rescaling --> try tiling read if (downScaleLevel > 0) return getImageByTile(serie, resolution, z, t, c, getTileWidth(serie), getTileHeight(serie), null); throw e; } catch (FormatException e) { // we can have here a "Image plane too large. Only 2GB of data can be extracted at // one time." error here --> so can try to use tile loading when we need rescaling if (downScaleLevel > 0) return getImageByTile(serie, resolution, z, t, c, getTileWidth(serie), getTileHeight(serie), null); throw e; } catch (IOException e) { throw e; } finally { releaseReader(r); } } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImageByTile(int serie, int resolution, int z, int t, int c, int tileW, int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException { return new LociTileImageReader(serie, resolution, z, t, c, tileW, tileH, listener).result; } /** * Load a thumbnail version of the image located at (Z, T) position from the specified * {@link IFormatReader} and * returns it as an IcyBufferedImage. * * @param reader * {@link IFormatReader} * @param z * Z position of the image to load * @param t * T position of the image to load * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { return getThumbnail(reader, z, t, -1); } /** * Load a thumbnail version of the image located at (Z, T, C) position from the specified * {@link IFormatReader} and * returns it as an IcyBufferedImage. * * @param reader * {@link IFormatReader} * @param z * Z position of the thumbnail to load * @param t * T position of the thumbnail to load * @param c * Channel index * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t, int c) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { try { // all channel ? if (c == -1) return getImageInternal(reader, null, z, t, true); return getImageInternal(reader, null, z, t, c, true); } catch (ClosedByInterruptException e) { // loading interrupted --> return null return null; } catch (Exception e) { // LOCI do not support thumbnail for all image, try compatible version return getThumbnailCompatible(reader, z, t, c); } } /** * Load a thumbnail version of the image located at (Z, T) position from the specified * {@link IFormatReader} and * returns it as an IcyBufferedImage.<br> * <i>Slow compatible version (load the original image and resize it)</i> * * @param reader * {@link IFormatReader} * @param z * Z position of the image to load * @param t * T position of the image to load * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { return getThumbnailCompatible(reader, z, t, -1); } /** * Load a thumbnail version of the image located at (Z, T, C) position from the specified * {@link IFormatReader} and * returns it as an IcyBufferedImage.<br> * <i>Slow compatible version (load the original image and resize it)</i> * * @param reader * {@link IFormatReader} * @param z * Z position of the thumbnail to load * @param t * T position of the thumbnail to load * @param c * Channel index * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t, int c) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { return IcyBufferedImageUtil.scale(getImage(reader, null, z, t, c), reader.getThumbSizeX(), reader.getThumbSizeY()); } /** * Load a single channel sub image at (Z, T, C) position from the specified * {@link IFormatReader}<br> * and returns it as an IcyBufferedImage. * * @param reader * Reader used to load the image * @param rect * Region we want to retrieve data.<br> * Set to <code>null</code> to retrieve the whole image. * @param z * Z position of the image to load * @param t * T position of the image to load * @param c * Channel index to load * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { // we want all channel ? use method to retrieve whole image if (c == -1) return getImageInternal(reader, rect, z, t, false); return getImageInternal(reader, rect, z, t, c, false); } /** * Load the image located at (Z, T) position from the specified IFormatReader<br> * and return it as an IcyBufferedImage. * * @param reader * {@link IFormatReader} * @param rect * Region we want to retrieve data.<br> * Set to <code>null</code> to retrieve the whole image. * @param z * Z position of the image to load * @param t * T position of the image to load * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { return getImageInternal(reader, rect, z, t, false); } /** * Load the image located at (Z, T) position from the specified IFormatReader<br> * and return it as an IcyBufferedImage (compatible and slower method). * * @param reader * {@link IFormatReader} * @param z * Z position of the image to load * @param t * T position of the image to load * @return {@link IcyBufferedImage} */ public static IcyBufferedImage getImageCompatible(IFormatReader reader, int z, int t) throws FormatException, IOException { final int sizeX = reader.getSizeX(); final int sizeY = reader.getSizeY(); final List<BufferedImage> imageList = new ArrayList<BufferedImage>(); final int sizeC = reader.getEffectiveSizeC(); for (int c = 0; c < sizeC; c++) imageList.add(AWTImageTools.openImage(reader.openBytes(reader.getIndex(z, c, t)), reader, sizeX, sizeY)); // combine channels return IcyBufferedImage.createFrom(imageList); } /** * Load pixels of the specified region of image at (Z, T, C) position and returns them as an * array. * * @param reader * Reader used to load the pixels * @param dataType * pixel data type * @param rect * Define the image rectangular region we want to load.<br> * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> * @param z * Z position of the pixels to load * @param t * T position of the pixels to load * @param c * Channel index to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> * parameter should * contains thumbnail size * @param rawBuffer * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * * Datatype.size]) used to * read the whole RGB raw data (can be <code>null</code>) * @param channelBuffer * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the * channel raw data (can be * <code>null</code>) * @param pixelBuffer * pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel * converted data and to * build the result image (can be <code>null</code>) * @return 1D array containing pixels data.<br> * The type of the array depends from the internal image data type * @throws UnsupportedOperationException * if the XY plane size is >= 2^31 pixels * @throws OutOfMemoryError * if there is not enough memory to open the image */ protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, boolean thumbnail, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { // get pixel data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // check we can open the image // Loader.checkOpening(reader.getResolution(), rect.width, rect.height, 1, 1, 1, dataType, // ""); // prepare informations final int rgbChanCount = reader.getRGBChannelCount(); final boolean interleaved = reader.isInterleaved(); final boolean little = reader.isLittleEndian(); // allocate internal image data array if needed final Object result = Array1DUtil.allocIfNull(pixelBuffer, dataType, rect.width * rect.height); // compute channel offsets final int baseC = c / rgbChanCount; final int subC = c % rgbChanCount; // get image data (whole RGB data for RGB channel) byte[] rawData = getBytesInternal(reader, reader.getIndex(z, baseC, t), rect, thumbnail, rawBuffer); // current final component final int componentByteLen = rawData.length / rgbChanCount; // build data array if (interleaved) { // get channel interleaved data final byte[] channelData = Array1DUtil.getInterleavedData(rawData, subC, rgbChanCount, channelBuffer, 0, componentByteLen); ByteArrayConvert.byteArrayTo(channelData, 0, result, 0, componentByteLen, little); } else ByteArrayConvert.byteArrayTo(rawData, subC * componentByteLen, result, 0, componentByteLen, little); // return raw pixels data return result; } /** * Load pixels of the specified region of image at (Z, T, C) position and returns them as an * array. * * @param reader * Reader used to load the pixels * @param rect * Region we want to retrieve data.<br> * Set to <code>null</code> to retrieve the whole image. * @param z * Z position of the pixels to load * @param t * T position of the pixels to load * @param c * Channel index to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> * parameter is then ignored) * @return 1D array containing pixels data.<br> * The type of the array depends from the internal image data type */ protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, boolean thumbnail) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { final Rectangle r; if (thumbnail) r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); else if (rect == null) r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); else r = rect; return getPixelsInternal(reader, r, z, t, c, thumbnail, null, null, null); } /** * Load a single channel sub image at (Z, T, C) position from the specified * {@link IFormatReader}<br> * and returns it as an IcyBufferedImage. * * @param reader * Reader used to load the image * @param rect * Define the image rectangular region we want to load.<br> * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> * @param z * Z position of the image to load * @param t * T position of the image to load * @param c * Channel index to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> * parameter should * contains thumbnail size * @param rawBuffer * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * * Datatype.size]) used to * read the whole RGB raw data (can be <code>null</code>) * @param channelBuffer * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the * channel raw data (can be * <code>null</code>) * @param pixelBuffer * pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel * converted data and to * build the result image (can be <code>null</code>) * @return {@link IcyBufferedImage} * @throws UnsupportedOperationException * if the XY plane size is >= 2^31 pixels * @throws OutOfMemoryError * if there is not enough memory to open the image */ protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, boolean thumbnail, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { // get pixel data final Object pixelData = getPixelsInternal(reader, rect, z, t, c, thumbnail, rawBuffer, channelBuffer, pixelBuffer); // get pixel data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // create the single channel result image from pixel data final IcyBufferedImage result = new IcyBufferedImage(rect.width, rect.height, pixelData, dataType.isSigned()); // indexed color ? if (reader.isIndexed()) { IcyColorMap map = null; // only 8 bits and 16 bits lookup table supported switch (dataType.getJavaType()) { case BYTE: final byte[][] bmap = reader.get8BitLookupTable(); if (bmap != null) map = new IcyColorMap("Channel " + c, bmap); break; case SHORT: final short[][] smap = reader.get16BitLookupTable(); if (smap != null) map = new IcyColorMap("Channel " + c, smap); break; default: break; } // colormap not set (or black) ? --> try to use metadata if ((map == null) || map.isBlack()) { final OMEXMLMetadataImpl metaData = (OMEXMLMetadataImpl) reader.getMetadataStore(); final Color color = MetaDataUtil.getChannelColor(metaData, reader.getSeries(), c); if ((color != null) && !ColorUtil.isBlack(color)) map = new LinearColorMap("Channel " + c, color); else map = null; } // we were able to retrieve a colormap ? --> set it if (map != null) result.setColorMap(0, map, true); } return result; } /** * Load a single channel sub image at (Z, T, C) position from the specified * {@link IFormatReader}<br> * and returns it as an IcyBufferedImage. * * @param reader * Reader used to load the image * @param rect * Region we want to retrieve data.<br> * Set to <code>null</code> to retrieve the whole image. * @param z * Z position of the image to load * @param t * T position of the image to load * @param c * Channel index to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> * parameter is then ignored) * @return {@link IcyBufferedImage} */ protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c, boolean thumbnail) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { final Rectangle r; if (thumbnail) r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); else if (rect == null) r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); else r = rect; return getImageInternal(reader, r, z, t, c, thumbnail, null, null, null); } /** * Load the image located at (Z, T) position from the specified IFormatReader and return it as * an IcyBufferedImage. * * @param reader * {@link IFormatReader} * @param rect * Define the image rectangular region we want to load.<br> * Should be adjusted if <i>thumbnail</i> parameter is <code>true</code> * @param z * Z position of the image to load * @param t * T position of the image to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> * parameter should * contains thumbnail size * @param rawBuffer * pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * * Datatype.size]) used to * read the whole RGB raw data (can be <code>null</code>) * @param channelBuffer * pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the * channel raw data (can be * <code>null</code>) * @param pixelBuffer * pre allocated 2D array ([SizeC, SizeX*SizeY]) pixel data buffer used to receive the * pixel converted data * and to build the result image (can be <code>null</code>) * @return {@link IcyBufferedImage} * @throws UnsupportedOperationException * if the XY plane size is >= 2^31 pixels * @throws OutOfMemoryError * if there is not enough memory to open the image */ protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, boolean thumbnail, byte[] rawBuffer, byte[] channelBuffer, Object[] pixelBuffer) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException { // get pixel data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // get sizeC final int effSizeC = reader.getEffectiveSizeC(); final int rgbChanCount = reader.getRGBChannelCount(); final int sizeX = rect.width; final int sizeY = rect.height; final int sizeC = effSizeC * rgbChanCount; // check we can open the image // Loader.checkOpening(reader.getResolution(), sizeX, sizeY, sizeC, 1, 1, dataType, ""); final int serie = reader.getSeries(); // prepare informations final boolean indexed = reader.isIndexed(); final boolean little = reader.isLittleEndian(); final OMEXMLMetadataImpl metaData = (OMEXMLMetadataImpl) reader.getMetadataStore(); // prepare internal image data array final Object[] pixelData; if (pixelBuffer == null) { // allocate array pixelData = Array2DUtil.createArray(dataType, sizeC); for (int i = 0; i < sizeC; i++) pixelData[i] = Array1DUtil.createArray(dataType, sizeX * sizeY); } else pixelData = pixelBuffer; // colormap allocation final IcyColorMap[] colormaps = new IcyColorMap[effSizeC]; byte[] rawData = null; for (int effC = 0; effC < effSizeC; effC++) { // get data rawData = getBytesInternal(reader, reader.getIndex(z, effC, t), rect, thumbnail, rawBuffer); // current final component final int c = effC * rgbChanCount; final int componentByteLen = rawData.length / rgbChanCount; // build data array int inOffset = 0; if (reader.isInterleaved()) { final byte[] channelData = (channelBuffer == null) ? new byte[componentByteLen] : channelBuffer; for (int sc = 0; sc < rgbChanCount; sc++) { // get channel interleaved data Array1DUtil.getInterleavedData(rawData, inOffset, rgbChanCount, channelData, 0, componentByteLen); ByteArrayConvert.byteArrayTo(channelData, 0, pixelData[c + sc], 0, componentByteLen, little); inOffset++; } } else { for (int sc = 0; sc < rgbChanCount; sc++) { ByteArrayConvert.byteArrayTo(rawData, inOffset, pixelData[c + sc], 0, componentByteLen, little); inOffset += componentByteLen; } } // indexed color ? if (indexed) { // only 8 bits and 16 bits lookup table supported switch (dataType.getJavaType()) { case BYTE: final byte[][] bmap = reader.get8BitLookupTable(); if (bmap != null) colormaps[effC] = new IcyColorMap("Channel " + effC, bmap); break; case SHORT: final short[][] smap = reader.get16BitLookupTable(); if (smap != null) colormaps[effC] = new IcyColorMap("Channel " + effC, smap); break; default: colormaps[effC] = null; break; } } // colormap not yet set (or black) ? --> try to use metadata if ((colormaps[effC] == null) || colormaps[effC].isBlack()) { final Color color = MetaDataUtil.getChannelColor(metaData, serie, effC); if ((color != null) && !ColorUtil.isBlack(color)) colormaps[effC] = new LinearColorMap("Channel " + effC, color); else colormaps[effC] = null; } } final IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, pixelData, dataType.isSigned()); // affect colormap result.beginUpdate(); try { // set colormaps for (int comp = 0; comp < effSizeC; comp++) { // we were able to retrieve a colormap for that channel ? --> set it if (colormaps[comp] != null) result.setColorMap(comp, colormaps[comp], true); } // special case of 4 channels image, try to restore alpha channel if ((sizeC == 4) && ((colormaps.length < 4) || (colormaps[3] == null))) { // assume real alpha channel depending from the reader we use final boolean alpha = (rgbChanCount == 4) || (reader instanceof PNGReader) || (reader instanceof APNGReader) || (reader instanceof JPEG2000Reader); // restore alpha channel if (alpha) result.setColorMap(3, LinearColorMap.alpha_, true); } } finally { result.endUpdate(); } return result; } /** * Load the image located at (Z, T) position from the specified IFormatReader<br> * and return it as an IcyBufferedImage. * * @param reader * {@link IFormatReader} * @param rect * Region we want to retrieve data.<br> * Set to <code>null</code> to retrieve the whole image. * @param z * Z position of the image to load * @param t * T position of the image to load * @param thumbnail * Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> * parameter is then ignored) * @return {@link IcyBufferedImage} */ protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, boolean thumbnail) throws FormatException, IOException { final Rectangle r; if (thumbnail) r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY()); else if (rect == null) r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); else r = rect; return getImageInternal(reader, r, z, t, thumbnail, null, null, null); } /** * low level byte read from LOCI reader (only used by internal methods) */ protected static byte[] getBytesInternal(IFormatReader reader, int index, Rectangle rect, boolean thumbnail, byte[] buffer) throws FormatException, IOException { if (thumbnail) return reader.openThumbBytes(index); final Rectangle imgRect = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY()); // need to allocate if (buffer == null) { // return whole image if ((rect == null) || rect.equals(imgRect)) return reader.openBytes(index); // return region return reader.openBytes(index, rect.x, rect.y, rect.width, rect.height); } // already allocated / whole image if ((rect == null) || rect.equals(imgRect)) return reader.openBytes(index, buffer); // return region return reader.openBytes(index, buffer, rect.x, rect.y, rect.width, rect.height); } /** * Down scale the specified image with the given down scale factor.<br> * If down scale factor equals <code>0</code> then the input image is directly returned. * * @param source * input image * @param scale * scale factor * @return scaled image or source image is scale factor equals <code>0</code> */ protected static IcyBufferedImage downScale(IcyBufferedImage source, int downScaleLevel) { IcyBufferedImage result = source; int it = downScaleLevel; // process fast down scaling while (it-- > 0) result = IcyBufferedImageUtil.downscaleBy2(result, true); return result; // final double scale = Math.pow(2, downScaleLevel); // if (scale > 1d) // { // final int sizeX = (int) (Math.round(source.getSizeX() / scale)); // final int sizeY = (int) (Math.round(source.getSizeY() / scale)); // // down scale // return IcyBufferedImageUtil.scale(source, sizeX, sizeY, FilterType.BILINEAR); // } // // return source; } protected static UnsupportedFormatException translateException(String path, FormatException exception) { if (exception instanceof UnknownFormatException) return new UnsupportedFormatException(path + ": Unknown image format.", exception); else if (exception instanceof MissingLibraryException) return new UnsupportedFormatException(path + ": Missing library to load the image.", exception); else return new UnsupportedFormatException(path + ": Unsupported image format.", exception); } }