/** * */ package plugins.kernel.importer; import icy.common.exception.UnsupportedFormatException; 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.IcyBufferedImageUtil.FilterType; import icy.image.colormap.IcyColorMap; import icy.image.colormap.LinearColorMap; import icy.plugin.abstract_.PluginSequenceFileImporter; import icy.sequence.MetaDataUtil; 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 java.awt.Color; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.filechooser.FileFilter; 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"; } }; protected final ImageReader mainReader; protected IFormatReader reader; /** * Advanced settings */ protected boolean originalMetadata; protected boolean groupFiles; public LociImporterPlugin() { super(); mainReader = new ImageReader(); // just to be sure mainReader.setAllowOpenFiles(true); reader = null; 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); return true; } catch (FormatException e) { throw translateException(path, e); } } @Override public void close() throws IOException { // something to close ? if (getOpened() != null) reader.close(); } /** * 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 double 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 Math.pow(2d, resolution - res); } @Override public OMEXMLMetadataImpl getMetaData() throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; 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); 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); return reader.getOptimalTileHeight(); } @Override public IcyBufferedImage getThumbnail(int serie) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor final double scale = prepareReader(serie, 0); // get image IcyBufferedImage result = getThumbnail(reader, reader.getSizeZ() / 2, reader.getSizeT() / 2); // return down scaled version if needed return downScale(result, scale); } catch (FormatException e) { throw translateException(getOpened(), e); } } @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 double scale = prepareReader(serie, resolution); // no need to rescale ? --> directly return the pixels if (scale == 1d) return getPixels(reader, rectangle, z, t, c); // get the image IcyBufferedImage result = getImage(reader, rectangle, z, t, c); // down scale it result = downScale(result, scale); // and return internal data return result.getDataXY(0); } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImage(int serie, int resolution, Rectangle rectangle, int z, int t, int c) throws icy.common.exception.UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor final double scale = prepareReader(serie, resolution); // get image IcyBufferedImage result = getImage(reader, rectangle, z, t, c); // return down scaled version if needed return downScale(result, scale); } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImage(int serie, int resolution, Rectangle rectangle, int z, int t) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor final double scale = prepareReader(serie, resolution); // get image IcyBufferedImage result = getImage(reader, rectangle, z, t); // return down scaled version if needed return downScale(result, scale); } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImage(int serie, int resolution, 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 double scale = prepareReader(serie, resolution); // get image IcyBufferedImage result = getImage(reader, null, z, t, c); // return down scaled version if needed return downScale(result, scale); } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImage(int serie, int resolution, int z, int t) throws UnsupportedFormatException, IOException { // no image currently opened if (getOpened() == null) return null; try { // prepare reader and get down scale factor final double scale = prepareReader(serie, resolution); // get image IcyBufferedImage result = getImage(reader, null, z, t); // return down scaled version if needed return downScale(result, scale); } catch (FormatException e) { throw translateException(getOpened(), e); } } @Override public IcyBufferedImage getImage(int serie, int z, int t) throws UnsupportedFormatException, IOException { return getImage(serie, 0, z, t); } @Override public IcyBufferedImage getImage(int z, int t) throws UnsupportedFormatException, IOException { return getImage(0, 0, z, t); } /** * 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>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 FormatException, IOException { return IcyBufferedImageUtil.scale(getImage(reader, null, z, t), reader.getThumbSizeX(), reader.getThumbSizeY()); } /** * 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 FormatException, IOException { try { return getImage(reader, null, z, t, true); } catch (Exception e) { // LOCI do not support thumbnail for all image, try compatible version return getThumbnailCompatible(reader, z, t); } } /** * 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>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 FormatException, IOException { return IcyBufferedImageUtil.scale(getImage(reader, null, z, t, c), reader.getThumbSizeX(), reader.getThumbSizeY()); } /** * 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 FormatException, IOException { try { return getImage(reader, null, z, t, c, true); } catch (Exception e) { // LOCI do not support thumbnail for all image, try compatible version return getThumbnailCompatible(reader, z, t); } } /** * 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 FormatException, IOException { return getImage(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 FormatException, IOException { return getImage(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 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 * @return 1D array containing pixels data.<br> * The type of the array depends from the internal image data type */ static Object getPixels(IFormatReader reader, Rectangle rect, int z, int t, int c) throws FormatException, IOException { final int sizeX; final int sizeY; if (rect == null) { sizeX = reader.getSizeX(); sizeY = reader.getSizeY(); } else { sizeX = rect.width; sizeY = rect.height; } // convert in our data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // prepare informations final int rgbChanCount = reader.getRGBChannelCount(); final boolean interleaved = reader.isInterleaved(); final boolean little = reader.isLittleEndian(); // allocate internal image data array final Object result = Array1DUtil.createArray(dataType, sizeX * sizeY); final int baseC = c / rgbChanCount; final int subC = c % rgbChanCount; // get image data final byte[] byteData = getBytes(reader, reader.getIndex(z, baseC, t), rect, false, null); // current final component final int componentByteLen = byteData.length / rgbChanCount; // build data array if (interleaved) ByteArrayConvert.byteArrayTo(byteData, subC, rgbChanCount, result, 0, 1, componentByteLen, little); else ByteArrayConvert.byteArrayTo(byteData, subC * componentByteLen, 1, result, 0, 1, componentByteLen, little); // return raw pixels data 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} */ static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c, boolean thumbnail) throws FormatException, IOException { final int sizeX; final int sizeY; if (thumbnail) { sizeX = reader.getThumbSizeX(); sizeY = reader.getThumbSizeY(); } else if (rect == null) { sizeX = reader.getSizeX(); sizeY = reader.getSizeY(); } else { sizeX = rect.width; sizeY = rect.height; } // convert in our data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // prepare informations final int rgbChanCount = reader.getRGBChannelCount(); final boolean indexed = reader.isIndexed(); final boolean interleaved = reader.isInterleaved(); final boolean little = reader.isLittleEndian(); final OMEXMLMetadataImpl metaData = (OMEXMLMetadataImpl) reader.getMetadataStore(); // allocate internal image data array final Object data = Array1DUtil.createArray(dataType, sizeX * sizeY); final int baseC = c / rgbChanCount; final int subC = c % rgbChanCount; // get image data final byte[] byteData = getBytes(reader, reader.getIndex(z, baseC, t), rect, thumbnail, null); // current final component final int componentByteLen = byteData.length / rgbChanCount; // build data array if (interleaved) ByteArrayConvert.byteArrayTo(byteData, subC, rgbChanCount, data, 0, 1, componentByteLen, little); else ByteArrayConvert.byteArrayTo(byteData, subC * componentByteLen, 1, data, 0, 1, componentByteLen, little); final IcyBufferedImage result = new IcyBufferedImage(rect.width, rect.height, data, dataType.isSigned()); // indexed color ? if (indexed) { 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 Color color = MetaDataUtil.getChannelColor(metaData, reader.getSeries(), c); if ((color != null) && !ColorUtil.isBlack(color)) map = new LinearColorMap("Channel " + c, color); else map = null; } // set colormap... if (map != null) result.setColorMap(0, map, true); } 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} */ static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, boolean thumbnail) throws FormatException, IOException { final int sizeX; final int sizeY; if (thumbnail) { sizeX = reader.getThumbSizeX(); sizeY = reader.getThumbSizeY(); } else if (rect == null) { sizeX = reader.getSizeX(); sizeY = reader.getSizeY(); } else { sizeX = rect.width; sizeY = rect.height; } // convert in our data type final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType()); // prepare informations final int effSizeC = reader.getEffectiveSizeC(); final int rgbChanCount = reader.getRGBChannelCount(); final int sizeC = effSizeC * rgbChanCount; final boolean indexed = reader.isIndexed(); final boolean little = reader.isLittleEndian(); final OMEXMLMetadataImpl metaData = (OMEXMLMetadataImpl) reader.getMetadataStore(); // prepare internal image data array final Object[] data = Array2DUtil.createArray(dataType, sizeC); final IcyColorMap[] colormaps = new IcyColorMap[effSizeC]; // allocate array for (int i = 0; i < sizeC; i++) data[i] = Array1DUtil.createArray(dataType, sizeX * sizeY); byte[] byteData = null; for (int effC = 0; effC < effSizeC; effC++) { // get data byteData = getBytes(reader, reader.getIndex(z, effC, t), rect, thumbnail, byteData); // current final component final int c = effC * rgbChanCount; final int componentByteLen = byteData.length / rgbChanCount; // build data array int inOffset = 0; if (reader.isInterleaved()) { for (int sc = 0; sc < rgbChanCount; sc++) { ByteArrayConvert.byteArrayTo(byteData, inOffset, rgbChanCount, data[c + sc], 0, 1, componentByteLen, little); inOffset++; } } else { for (int sc = 0; sc < rgbChanCount; sc++) { ByteArrayConvert.byteArrayTo(byteData, inOffset, 1, data[c + sc], 0, 1, 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 set (or black) ? --> try to use metadata if ((colormaps[effC] == null) || colormaps[effC].isBlack()) { final Color color = MetaDataUtil.getChannelColor(metaData, reader.getSeries(), 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, data, dataType.isSigned()); // affect colormap result.beginUpdate(); try { if (indexed) { // error ! we should have same number of colormap than component if (colormaps.length != sizeC) { System.err.println("Warning : " + colormaps.length + " colormap for " + sizeC + " components"); System.err.println("Colormap can not be restored"); } else { // set colormaps for (int comp = 0; comp < sizeC; comp++) { // set colormap if (colormaps[comp] != null) result.setColorMap(comp, colormaps[comp], true); } } } // special case of 4 channels image, try to restore alpha channel else if (sizeC == 4) { // assume real alpha channel depending from the reader we use final boolean alpha = (reader instanceof PNGReader) || (reader instanceof APNGReader) || (reader instanceof JPEG2000Reader); // || (reader instanceof TiffJAIReader); // restore alpha channel if (alpha) result.setColorMap(3, LinearColorMap.alpha_, true); } } finally { result.endUpdate(); } return result; } static byte[] getBytes(IFormatReader reader, int index, Rectangle rect, boolean thumbnail, byte[] buffer) throws FormatException, IOException { if (thumbnail) return reader.openThumbBytes(index); // need to allocate if (buffer == null) { if (rect != null) return reader.openBytes(index, rect.x, rect.y, rect.width, rect.height); return reader.openBytes(index); } // already allocated if (rect != null) return reader.openBytes(index, buffer, rect.x, rect.y, rect.width, rect.height); return reader.openBytes(index, buffer); } static IcyBufferedImage downScale(IcyBufferedImage source, double scale) { // need down scale ? 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; } 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); } }