/**
*
*/
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);
}
}