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