001/**
002 * 
003 */
004package icy.image;
005
006import icy.common.exception.UnsupportedFormatException;
007import icy.common.listener.ProgressListener;
008import icy.image.IcyBufferedImageUtil.FilterType;
009import icy.sequence.MetaDataUtil;
010import icy.system.SystemUtil;
011import icy.system.thread.Processor;
012import icy.type.DataType;
013import icy.type.collection.array.Array1DUtil;
014
015import java.awt.Dimension;
016import java.awt.Point;
017import java.awt.Rectangle;
018import java.io.IOException;
019import java.util.List;
020
021import ome.xml.meta.OMEXMLMetadata;
022
023/**
024 * Abstract implementation of the {@link ImageProvider} interface.<br>
025 * It provide methods wrapper so you only need implement one method the get your importer working.<br>
026 * But free feel to override more methods to provide better support and/or better performance.
027 * 
028 * @author Stephane
029 */
030public abstract class AbstractImageProvider implements ImageProvider
031{
032    /**
033     * Used for multi thread tile image reading.
034     * 
035     * @author Stephane
036     */
037    class TilePixelsReader implements Runnable
038    {
039        final int series;
040        final int resolution;
041        final Rectangle region;
042        final int z;
043        final int t;
044        final int c;
045        final Object result;
046        final Dimension resDim;
047        final boolean signed;
048
049        boolean done;
050        boolean failed;
051
052        public TilePixelsReader(int series, int resolution, Rectangle region, int z, int t, int c, Object result,
053                Dimension resDim, boolean signed)
054        {
055            super();
056
057            this.series = series;
058            this.resolution = resolution;
059            this.region = region;
060            this.z = z;
061            this.t = t;
062            this.c = c;
063            this.result = result;
064            this.resDim = resDim;
065            this.signed = signed;
066            done = false;
067            failed = false;
068        }
069
070        @Override
071        public void run()
072        {
073            if (Thread.interrupted())
074            {
075                failed = true;
076                return;
077            }
078
079            try
080            {
081                // get image tile
082                final Object obj = getPixels(series, resolution, region, z, t, c);
083                // compute resolution divider
084                final int divider = (int) Math.pow(2, resolution);
085                // copy tile to image result
086                Array1DUtil.copyRect(obj, region.getSize(), null, result, resDim,
087                        new Point(region.x / divider, region.y / divider), signed);
088            }
089            catch (Exception e)
090            {
091                failed = true;
092            }
093
094            done = true;
095        }
096    }
097
098    public static final int DEFAULT_THUMBNAIL_SIZE = 160;
099
100    // default implementation as ImageProvider interface changed
101    @SuppressWarnings("deprecation")
102    @Override
103    public OMEXMLMetadata getOMEXMLMetaData() throws UnsupportedFormatException, IOException
104    {
105        return getMetaData();
106    }
107
108    // default implementation, override it if you need specific value for faster tile access
109    @Override
110    public int getTileWidth(int series) throws UnsupportedFormatException, IOException
111    {
112        return MetaDataUtil.getSizeX(getOMEXMLMetaData(), series);
113    }
114
115    // default implementation, override it if you need specific value for faster tile access
116    @Override
117    public int getTileHeight(int series) throws UnsupportedFormatException, IOException
118    {
119        final OMEXMLMetadata meta = getOMEXMLMetaData();
120        final int sx = MetaDataUtil.getSizeX(meta, series);
121
122        if (sx == 0)
123            return 0;
124
125        // default implementation
126        final int maxHeight = (1024 * 1024) / sx;
127        final int sy = MetaDataUtil.getSizeY(meta, series);
128
129        return Math.min(maxHeight, sy);
130    }
131
132    // default implementation, override it if sub resolution is supported
133    @Override
134    public boolean isResolutionAvailable(int series, int resolution) throws UnsupportedFormatException, IOException
135    {
136        // by default we have only the original resolution
137        return resolution == 0;
138    }
139
140    // default implementation which use the getImage(..) method, override it for better support / performance
141    @Override
142    public IcyBufferedImage getThumbnail(int series) throws UnsupportedFormatException, IOException
143    {
144        final OMEXMLMetadata meta = getOMEXMLMetaData();
145        int sx = MetaDataUtil.getSizeX(meta, series);
146        int sy = MetaDataUtil.getSizeY(meta, series);
147        final int sz = MetaDataUtil.getSizeZ(meta, series);
148        final int st = MetaDataUtil.getSizeT(meta, series);
149
150        // empty size --> return null
151        if ((sx == 0) || (sy == 0) || (sz == 0) || (st == 0))
152            return null;
153
154        final double ratio = Math.min((double) DEFAULT_THUMBNAIL_SIZE / (double) sx,
155                (double) DEFAULT_THUMBNAIL_SIZE / (double) sy);
156
157        // final thumbnail size
158        final int tnx = (int) Math.round(sx * ratio);
159        final int tny = (int) Math.round(sy * ratio);
160        final int resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE);
161
162        // take middle image for thumbnail
163        final IcyBufferedImage image = getImage(series, resolution, sz / 2, st / 2);
164
165        // sx = result.getSizeX();
166        // sy = result.getSizeY();
167        // // wanted sub resolution of the image
168        // resolution = getResolutionFactor(sx, sy, DEFAULT_THUMBNAIL_SIZE);
169
170        // scale it to desired dimension (fast enough as here we have a small image)
171        final IcyBufferedImage thumbnail = IcyBufferedImageUtil.scale(image, tnx, tny, FilterType.BILINEAR);
172
173        // preserve colormaps
174        thumbnail.setColorMaps(image);
175
176        return thumbnail;
177    }
178
179    // default implementation: use the getImage(..) method then return data.
180    // It should be the opposite side for performance reason, override this method if possible
181    @Override
182    public Object getPixels(int series, int resolution, Rectangle rectangle, int z, int t, int c)
183            throws UnsupportedFormatException, IOException
184    {
185        return getImage(series, resolution, rectangle, z, t, c).getDataXY(0);
186    }
187
188    @Override
189    public IcyBufferedImage getImage(int series, int resolution, Rectangle rectangle, int z, int t)
190            throws UnsupportedFormatException, IOException
191    {
192        return getImage(series, resolution, rectangle, z, t, -1);
193    }
194
195    // default implementation using the region getImage(..) method, better to override
196    @Override
197    public IcyBufferedImage getImage(int series, int resolution, int z, int t, int c)
198            throws UnsupportedFormatException, IOException
199    {
200        return getImage(series, resolution, null, z, t, c);
201    }
202
203    @Override
204    public IcyBufferedImage getImage(int series, int resolution, int z, int t)
205            throws UnsupportedFormatException, IOException
206    {
207        return getImage(series, resolution, null, z, t, -1);
208    }
209
210    @Override
211    public IcyBufferedImage getImage(int series, int z, int t) throws UnsupportedFormatException, IOException
212    {
213        return getImage(series, 0, null, z, t, -1);
214    }
215
216    @Override
217    public IcyBufferedImage getImage(int z, int t) throws UnsupportedFormatException, IOException
218    {
219        return getImage(0, 0, null, z, t, -1);
220    }
221
222    /**
223     * Returns the pixels located at specified position using tile by tile reading (if supported by the importer).<br>
224     * This method is useful to read a sub resolution of a very large image which cannot fit in memory and also to take
225     * advantage of multi threading.
226     * 
227     * @param series
228     *        Series index for multi series image (use 0 if unsure).
229     * @param resolution
230     *        Wanted resolution level for the image (use 0 if unsure).<br>
231     *        The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
232     *        So for instance level 0 is the default image resolution while level 1 is base image
233     *        resolution / 2 and so on...
234     * @param region
235     *        The 2D region we want to retrieve (considering the original image resolution).<br>
236     *        If set to <code>null</code> then the whole image is returned.
237     * @param z
238     *        Z position of the image (slice) we want retrieve
239     * @param t
240     *        T position of the image (frame) we want retrieve
241     * @param c
242     *        C position of the image (channel) we want retrieve (-1 not accepted here).<br>
243     * @param tileW
244     *        width of the tile (better to use a multiple of 2).<br>
245     *        If <= 0 then tile width is automatically determined
246     * @param tileH
247     *        height of the tile (better to use a multiple of 2).<br>
248     *        If <= 0 then tile height is automatically determined
249     * @param listener
250     *        Progression listener
251     * @see #getPixels(int, int, Rectangle, int, int, int)
252     */
253    public Object getPixelsByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
254            int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException
255    {
256        final OMEXMLMetadata meta = getOMEXMLMetaData();
257        final int sizeX = MetaDataUtil.getSizeX(meta, series);
258        final int sizeY = MetaDataUtil.getSizeY(meta, series);
259        final DataType type = MetaDataUtil.getDataType(meta, series);
260        final boolean signed = type.isSigned();
261
262        // define XY region to load
263        Rectangle adjRegion = new Rectangle(sizeX, sizeY);
264        if (region != null)
265            adjRegion = adjRegion.intersection(region);
266
267        // allocate result
268        final Dimension resDim = new Dimension(adjRegion.width >> resolution, adjRegion.height >> resolution);
269        final Object result = Array1DUtil.createArray(type, resDim.width * resDim.height);
270
271        // create processor
272        final Processor readerProcessor = new Processor(Math.max(1, SystemUtil.getNumberOfCPUs() - 1));
273        readerProcessor.setThreadName("Image tile reader");
274
275        int tw = tileW;
276        int th = tileH;
277
278        // adjust tile size if needed
279        if (tw <= 0)
280            tw = getTileWidth(series);
281        if (tw <= 0)
282            tw = 512;
283        if (th <= 0)
284            th = getTileHeight(series);
285        if (th <= 0)
286            th = 512;
287
288        final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th);
289
290        // submit all tasks
291        for (Rectangle tile : tiles)
292        {
293            // wait a bit if the process queue is full
294            while (readerProcessor.isFull())
295            {
296                try
297                {
298                    Thread.sleep(0);
299                }
300                catch (InterruptedException e)
301                {
302                    // interrupt all processes
303                    readerProcessor.shutdownNow();
304                    break;
305                }
306            }
307
308            // submit next task
309            readerProcessor.submit(new TilePixelsReader(series, resolution, tile.intersection(adjRegion), z, t, c,
310                    result, resDim, signed));
311
312            // display progression
313            if (listener != null)
314            {
315                // process cancel requested ?
316                if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
317                {
318                    // interrupt processes
319                    readerProcessor.shutdownNow();
320                    break;
321                }
322            }
323        }
324
325        // wait for completion
326        while (readerProcessor.isProcessing())
327        {
328            try
329            {
330                Thread.sleep(1);
331            }
332            catch (InterruptedException e)
333            {
334                // interrupt all processes
335                readerProcessor.shutdownNow();
336                break;
337            }
338
339            // display progression
340            if (listener != null)
341            {
342                // process cancel requested ?
343                if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
344                {
345                    // interrupt processes
346                    readerProcessor.shutdownNow();
347                    break;
348                }
349            }
350        }
351
352        // last wait for completion just in case we were interrupted
353        readerProcessor.waitAll();
354
355        return result;
356    }
357
358    // /**
359    // * Returns the image located at specified position using tile by tile reading (if supported by the importer).<br>
360    // * This method is useful to read a sub resolution of a very large image which cannot fit in memory and also to take
361    // * advantage of multi threading.
362    // *
363    // * @param series
364    // * Series index for multi series image (use 0 if unsure).
365    // * @param resolution
366    // * Wanted resolution level for the image (use 0 if unsure).<br>
367    // * The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
368    // * So for instance level 0 is the default image resolution while level 1 is base image
369    // * resolution / 2 and so on...
370    // * @param region
371    // * The 2D region we want to retrieve (considering the original image resolution).<br>
372    // * If set to <code>null</code> then the whole image is returned.
373    // * @param z
374    // * Z position of the image (slice) we want retrieve
375    // * @param t
376    // * T position of the image (frame) we want retrieve
377    // * @param c
378    // * C position of the image (channel) we want retrieve.<br>
379    // * -1 is a special value meaning we want all channel.
380    // * @param tileW
381    // * width of the tile (better to use a multiple of 2)
382    // * @param tileH
383    // * height of the tile (better to use a multiple of 2)
384    // * @param listener
385    // * Progression listener
386    // * @see #getImage(int, int, Rectangle, int, int, int)
387    // */
388    // public IcyBufferedImage getImageByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
389    // int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException
390    // {
391    // final OMEXMLMetadata meta = getOMEXMLMetaData();
392    // final int sizeX = MetaDataUtil.getSizeX(meta, series);
393    // final int sizeY = MetaDataUtil.getSizeY(meta, series);
394    //
395    // // TODO: handle rect
396    //
397    // // resolution divider
398    // final int divider = (int) Math.pow(2, resolution);
399    // // allocate result
400    // final IcyBufferedImage result = new IcyBufferedImage(sizeX / divider, sizeY / divider,
401    // MetaDataUtil.getSizeC(meta, series), MetaDataUtil.getDataType(meta, series));
402    // // create processor
403    // final Processor readerProcessor = new Processor(Math.max(1, SystemUtil.getNumberOfCPUs() - 1));
404    //
405    // readerProcessor.setThreadName("Image tile reader");
406    // result.beginUpdate();
407    //
408    // try
409    // {
410    // final List<Rectangle> tiles = ImageUtil.getTileList(sizeX, sizeY, tileW, tileH);
411    //
412    // // submit all tasks
413    // for (Rectangle tile : tiles)
414    // {
415    // // wait a bit if the process queue is full
416    // while (readerProcessor.isFull())
417    // {
418    // try
419    // {
420    // Thread.sleep(0);
421    // }
422    // catch (InterruptedException e)
423    // {
424    // // interrupt all processes
425    // readerProcessor.shutdownNow();
426    // break;
427    // }
428    // }
429    //
430    // // submit next task
431    // readerProcessor.submit(new TileImageReader(series, resolution, tile, z, t, c, result));
432    //
433    // // display progression
434    // if (listener != null)
435    // {
436    // // process cancel requested ?
437    // if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
438    // {
439    // // interrupt processes
440    // readerProcessor.shutdownNow();
441    // break;
442    // }
443    // }
444    // }
445    //
446    // // wait for completion
447    // while (readerProcessor.isProcessing())
448    // {
449    // try
450    // {
451    // Thread.sleep(1);
452    // }
453    // catch (InterruptedException e)
454    // {
455    // // interrupt all processes
456    // readerProcessor.shutdownNow();
457    // break;
458    // }
459    //
460    // // display progression
461    // if (listener != null)
462    // {
463    // // process cancel requested ?
464    // if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
465    // {
466    // // interrupt processes
467    // readerProcessor.shutdownNow();
468    // break;
469    // }
470    // }
471    // }
472    //
473    // // last wait for completion just in case we were interrupted
474    // readerProcessor.waitAll();
475    // }
476    // finally
477    // {
478    // result.endUpdate();
479    // }
480    //
481    // return result;
482    // }
483
484    // /**
485    // * @deprecated Use {@link #getImageByTile(int, int, Rectangle, int, int, int, int, int, ProgressListener)} instead.
486    // */
487    // @Deprecated
488    // public IcyBufferedImage getImageByTile(int series, int resolution, int z, int t, int c, int tileW, int tileH,
489    // ProgressListener listener) throws UnsupportedFormatException, IOException
490    // {
491    // return getImageByTile(series, resolution, null, z, t, c, tileW, tileH, listener);
492    // }
493    //
494    // /**
495    // * @deprecated USe {@link ImageUtil#getTileList(int, int, int, int)} instead
496    // */
497    // @Deprecated
498    // public static List<Rectangle> getTileList(int sizeX, int sizeY, int tileW, int tileH)
499    // {
500    // return ImageUtil.getTileList(sizeX, sizeY, tileW, tileH);
501    // }
502
503    /**
504     * Returns the sub image resolution which best suit to the desired size.
505     * 
506     * @param sizeX
507     *        original image width
508     * @param sizeY
509     *        original image height
510     * @param wantedSize
511     *        wanted size (for the maximum dimension)
512     * @return resolution ratio<br>
513     *         0 = original resolution<br>
514     *         1 = (original resolution / 2)<br>
515     *         2 = (original resolution / 4)
516     */
517    public static int getResolutionFactor(int sizeX, int sizeY, int wantedSize)
518    {
519        int sx = sizeX / 2;
520        int sy = sizeY / 2;
521        int result = 0;
522
523        while ((sx > wantedSize) || (sy > wantedSize))
524        {
525            sx /= 2;
526            sy /= 2;
527            result++;
528        }
529
530        return result;
531    }
532
533    /**
534     * Returns the image resolution that best suit to the size resolution.
535     * 
536     * @param series
537     *        Series index for multi series image (use 0 if unsure).
538     * @param wantedSize
539     *        wanted size (for the maximum dimension)
540     * @return resolution ratio<br>
541     *         0 = original resolution<br>
542     *         1 = (original resolution / 2)<br>
543     *         2 = (original resolution / 4)
544     * @throws IOException
545     * @throws UnsupportedFormatException
546     */
547    public int getResolutionFactor(int series, int wantedSize) throws UnsupportedFormatException, IOException
548    {
549        final OMEXMLMetadata meta = getOMEXMLMetaData();
550        return getResolutionFactor(MetaDataUtil.getSizeX(meta, series), MetaDataUtil.getSizeY(meta, series),
551                wantedSize);
552    }
553}