/**
*
*/
package icy.image;
import icy.common.exception.UnsupportedFormatException;
import icy.common.listener.ProgressListener;
import icy.image.IcyBufferedImageUtil.FilterType;
import icy.sequence.MetaDataUtil;
import icy.system.SystemUtil;
import icy.system.thread.Processor;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import loci.formats.ome.OMEXMLMetadataImpl;
/**
* Abstract implementation of the {@link ImageProvider} interface.<br>
* It provide methods wrapper so you only need implement one method the get your importer working.<br>
* But free feel to override more methods to provide better support and/or better performance.
*
* @author Stephane
*/
public abstract class AbstractImageProvider implements ImageProvider
{
/**
* Used for multi thread tile image reading.
*
* @author Stephane
*/
class TileImageReader implements Runnable
{
final int serie;
final int resolution;
final Rectangle region;
final int z;
final int t;
final int c;
final IcyBufferedImage result;
boolean done;
boolean failed;
public TileImageReader(int serie, int resolution, Rectangle region, int z, int t, int c, IcyBufferedImage result)
{
super();
this.serie = serie;
this.resolution = resolution;
this.region = region;
this.z = z;
this.t = t;
this.c = c;
this.result = result;
done = false;
failed = false;
}
public TileImageReader(int serie, int resolution, Rectangle region, int z, int t, IcyBufferedImage result)
{
this(serie, resolution, region, z, t, -1, result);
}
@Override
public void run()
{
if (Thread.interrupted())
{
failed = true;
return;
}
try
{
// get image tile
final IcyBufferedImage img = getImage(serie, resolution, region, z, t, c);
// compute resolution divider
final int divider = (int) Math.pow(2, resolution);
// copy tile to image result
result.copyData(img, null, new Point(region.x / divider, region.y / divider));
}
catch (Exception e)
{
failed = true;
}
done = true;
}
}
public static final int DEFAULT_THUMBNAIL_SIZE = 160;
// default implementation, override it if you need specific value for faster tile access
@Override
public int getTileWidth(int serie) throws UnsupportedFormatException, IOException
{
return MetaDataUtil.getSizeX(getMetaData(), serie);
}
// default implementation, override it if you need specific value for faster tile access
@Override
public int getTileHeight(int serie) throws UnsupportedFormatException, IOException
{
final OMEXMLMetadataImpl meta = getMetaData();
final int sx = MetaDataUtil.getSizeX(meta, serie);
if (sx == 0)
return 0;
// default implementation
final int maxHeight = (1024 * 1024) / sx;
final int sy = MetaDataUtil.getSizeY(meta, serie);
return Math.min(maxHeight, sy);
}
// default implementation which use the getImage(..) method, override it for better support / performance
@Override
public IcyBufferedImage getThumbnail(int serie) throws UnsupportedFormatException, IOException
{
final OMEXMLMetadataImpl meta = getMetaData();
int sx = MetaDataUtil.getSizeX(meta, serie);
int sy = MetaDataUtil.getSizeY(meta, serie);
final int sz = MetaDataUtil.getSizeZ(meta, serie);
final int st = MetaDataUtil.getSizeT(meta, serie);
// empty size --> return null
if ((sx == 0) || (sy == 0) || (sz == 0) || (st == 0))
return null;
final double ratio = Math.min((double) DEFAULT_THUMBNAIL_SIZE / (double) sx, (double) DEFAULT_THUMBNAIL_SIZE
/ (double) sy);
// final thumbnail size
final int tnx = (int) Math.round(sx * ratio);
final int tny = (int) Math.round(sy * ratio);
int resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE);
// take middle image for thumbnail
IcyBufferedImage result = getImage(serie, resolution, sz / 2, st / 2);
sx = result.getSizeX();
sy = result.getSizeY();
// wanted sub resolution of the image (
resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE);
// scale it to desired dimension (fast enough as here we have a small image)
return IcyBufferedImageUtil.scale(result, tnx, tny, FilterType.BILINEAR);
}
// default implementation: use the getImage(..) method then return data.
// It should be the opposite side for performance reason, override this method if possible
@Override
public Object getPixels(int serie, int resolution, Rectangle rectangle, int z, int t, int c)
throws UnsupportedFormatException, IOException
{
return getImage(serie, resolution, rectangle, z, t, c).getDataXY(0);
}
@Override
public IcyBufferedImage getImage(int serie, int resolution, Rectangle rectangle, int z, int t)
throws UnsupportedFormatException, IOException
{
return getImage(serie, resolution, rectangle, z, t, -1);
}
// default implementation using the region getImage(..) method, better to override
@Override
public IcyBufferedImage getImage(int serie, int resolution, int z, int t, int c) throws UnsupportedFormatException,
IOException
{
return getImage(serie, resolution, null, z, t, c);
}
@Override
public IcyBufferedImage getImage(int serie, int resolution, int z, int t) throws UnsupportedFormatException,
IOException
{
return getImage(serie, resolution, null, z, t, -1);
}
@Override
public IcyBufferedImage getImage(int serie, int z, int t) throws UnsupportedFormatException, IOException
{
return getImage(serie, 0, null, z, t, -1);
}
@Override
public IcyBufferedImage getImage(int z, int t) throws UnsupportedFormatException, IOException
{
return getImage(0, 0, null, z, t, -1);
}
/**
* Returns the image located at specified position using tile by tile reading (if supported by the importer).<br>
* This method is useful to read a sub resolution of a very large image which cannot fit in memory and also to take
* advantage of multi threading.
*
* @param serie
* Serie index for multi serie image (use 0 if unsure).
* @param resolution
* Wanted resolution level for the image (use 0 if unsure).<br>
* The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
* So for instance level 0 is the default image resolution while level 1 is base image
* resolution / 2 and so on...
* @param z
* Z position of the image (slice) we want retrieve
* @param t
* T position of the image (frame) we want retrieve
* @param c
* C position of the image (channel) we want retrieve.<br>
* -1 is a special value meaning we want all channel.
* @param tileW
* width of the tile (better to use a multiple of 2)
* @param tileH
* height of the tile (better to use a multiple of 2)
* @param listener
* Progression listener
*/
public IcyBufferedImage getImageByTile(int serie, int resolution, int z, int t, int c, int tileW, int tileH,
ProgressListener listener) throws UnsupportedFormatException, IOException
{
final OMEXMLMetadataImpl meta = getMetaData();
final int sizeX = MetaDataUtil.getSizeX(meta, serie);
final int sizeY = MetaDataUtil.getSizeY(meta, serie);
// resolution divider
final int divider = (int) Math.pow(2, resolution);
// allocate result
final IcyBufferedImage result = new IcyBufferedImage(sizeX / divider, sizeY / divider, MetaDataUtil.getSizeC(
meta, serie), MetaDataUtil.getDataType(meta, serie));
// create processor
final Processor readerProcessor = new Processor(Math.max(1, SystemUtil.getNumberOfCPUs() - 1));
readerProcessor.setThreadName("Image tile reader");
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 TileImageReader(serie, resolution, tile, z, t, c, result));
// 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();
}
return result;
}
/**
* Get the list of tiles to fill the given XY plan size.
*
* @param sizeX
* plan sizeX
* @param sizeY
* plan sizeY
* @param tileW
* tile width
* @param tileH
* tile height
*/
public static List<Rectangle> getTileList(int sizeX, int sizeY, int tileW, int tileH)
{
final List<Rectangle> result = new ArrayList<Rectangle>();
int x, y;
for (y = 0; y < (sizeY - tileH); y += tileH)
{
for (x = 0; x < (sizeX - tileW); x += tileW)
result.add(new Rectangle(x, y, tileW, tileH));
// last tile column
result.add(new Rectangle(x, y, sizeX - x, tileH));
}
// last tiles row
for (x = 0; x < (sizeX - tileW); x += tileW)
result.add(new Rectangle(x, y, tileW, sizeY - y));
// last column/row tile
result.add(new Rectangle(x, y, sizeX - x, sizeY - y));
return result;
}
/**
* Returns the sub image resolution which best suit to the desired size.
*
* @param sizeX
* original image width
* @param sizeY
* original image height
* @param wantedSize
* wanted size (for the maximum dimension)
* @return resolution ratio<br>
* 0 = original resolution<br>
* 1 = (original resolution / 2)<br>
* 2 = (original resolution / 4)
*/
public static int getResolutionFactor(int sizeX, int sizeY, int wantedSize)
{
int sx = sizeX / 2;
int sy = sizeY / 2;
int result = 0;
while ((sx > wantedSize) || (sy > wantedSize))
{
sx /= 2;
sy /= 2;
result++;
}
return result;
}
/**
* Returns the image resolution that best suit to the size resolution.
*
* @param serie
* Serie index for multi serie image (use 0 if unsure).
* @param wantedSize
* wanted size (for the maximum dimension)
* @return resolution ratio<br>
* 0 = original resolution<br>
* 1 = (original resolution / 2)<br>
* 2 = (original resolution / 4)
* @throws IOException
* @throws UnsupportedFormatException
*/
public int getResolutionFactor(int serie, int wantedSize) throws UnsupportedFormatException, IOException
{
final OMEXMLMetadataImpl meta = getMetaData();
return getResolutionFactor(MetaDataUtil.getSizeX(meta, serie), MetaDataUtil.getSizeY(meta, serie), wantedSize);
}
}