001/**
002 * 
003 */
004package plugins.kernel.importer;
005
006import java.awt.Color;
007import java.awt.Point;
008import java.awt.Rectangle;
009import java.awt.image.BufferedImage;
010import java.io.File;
011import java.io.IOException;
012import java.nio.channels.ClosedByInterruptException;
013import java.util.ArrayList;
014import java.util.List;
015import java.util.Stack;
016
017import javax.swing.filechooser.FileFilter;
018
019import icy.common.exception.UnsupportedFormatException;
020import icy.common.listener.ProgressListener;
021import icy.file.FileUtil;
022import icy.file.Loader;
023import icy.gui.dialog.LoaderDialog.AllImagesFileFilter;
024import icy.image.IcyBufferedImage;
025import icy.image.IcyBufferedImageUtil;
026import icy.image.IcyBufferedImageUtil.FilterType;
027import icy.image.ImageUtil;
028import icy.image.colormap.IcyColorMap;
029import icy.image.colormap.LinearColorMap;
030import icy.plugin.abstract_.PluginSequenceFileImporter;
031import icy.sequence.MetaDataUtil;
032import icy.system.SystemUtil;
033import icy.system.thread.Processor;
034import icy.type.DataType;
035import icy.type.collection.array.Array1DUtil;
036import icy.type.collection.array.Array2DUtil;
037import icy.type.collection.array.ByteArrayConvert;
038import icy.type.rectangle.Rectangle2DUtil;
039import icy.util.ColorUtil;
040import icy.util.OMEUtil;
041import icy.util.StringUtil;
042import jxl.biff.drawing.PNGReader;
043import loci.formats.FormatException;
044import loci.formats.IFormatReader;
045import loci.formats.ImageReader;
046import loci.formats.MissingLibraryException;
047import loci.formats.TileStitcher;
048import loci.formats.UnknownFormatException;
049import loci.formats.gui.AWTImageTools;
050import loci.formats.gui.ExtensionFileFilter;
051import loci.formats.in.APNGReader;
052import loci.formats.in.DynamicMetadataOptions;
053import loci.formats.in.JPEG2000Reader;
054import loci.formats.in.MetadataLevel;
055import loci.formats.in.NativeND2Reader;
056import loci.formats.meta.MetadataStore;
057import loci.formats.ome.OMEXMLMetadataImpl;
058import ome.xml.meta.OMEXMLMetadata;
059
060/**
061 * LOCI Bio-Formats library importer class.
062 * 
063 * @author Stephane
064 */
065public class LociImporterPlugin extends PluginSequenceFileImporter
066{
067    protected class LociAllFileFilter extends AllImagesFileFilter
068    {
069        @Override
070        public String getDescription()
071        {
072            return "All image files / Bio-Formats";
073        }
074    };
075
076    /**
077     * Used for multi thread tile image reading.
078     * 
079     * @author Stephane
080     */
081    class LociTilePixelsReader
082    {
083        class TilePixelsWorkBuffer
084        {
085            final byte[] rawBuffer;
086            final byte[] channelBuffer;
087            final Object pixelBuffer;
088
089            public TilePixelsWorkBuffer(int sizeX, int sizeY, int rgbChannel, DataType dataType)
090            {
091                super();
092
093                // allocate arrays
094                rawBuffer = new byte[sizeX * sizeY * rgbChannel * dataType.getSize()];
095                channelBuffer = new byte[sizeX * sizeY * dataType.getSize()];
096                pixelBuffer = Array1DUtil.createArray(dataType, sizeX * sizeY);
097            }
098        }
099
100        class TilePixelsReaderWorker implements Runnable
101        {
102            final Rectangle region;
103            boolean done;
104            boolean failed;
105
106            public TilePixelsReaderWorker(Rectangle region)
107            {
108                super();
109
110                this.region = region;
111                done = false;
112                failed = false;
113            }
114
115            @SuppressWarnings("resource")
116            @Override
117            public void run()
118            {
119                Object pixels;
120
121                try
122                {
123                    // get reader and working buffers
124                    final IFormatReader r = getReader();
125                    final TilePixelsWorkBuffer buf = buffers.pop();
126
127                    try
128                    {
129                        try
130                        {
131                            pixels = getPixelsInternal(r, region, z, t, c, false, downScaleLevel, buf.rawBuffer,
132                                    buf.channelBuffer, buf.pixelBuffer);
133                        }
134                        finally
135                        {
136                            // release reader
137                            releaseReader(r);
138                        }
139
140                        // need to adjust input region
141                        final Rectangle adjRegion = new Rectangle(region);
142                        int downScale = downScaleLevel;
143                        while (downScale > 0)
144                        {
145                            // reduce region size by a factor of 2
146                            adjRegion.setBounds(adjRegion.x / 2, adjRegion.y / 2, adjRegion.width / 2,
147                                    adjRegion.height / 2);
148                            downScale--;
149                        }
150
151                        // define destination in destination
152                        final Point pt = adjRegion.getLocation();
153                        pt.translate(-imageRegion.x, -imageRegion.y);
154
155                        // copy tile to result
156                        Array1DUtil.copyRect(pixels, adjRegion.getSize(), null, result, imageRegion.getSize(), pt,
157                                signed);
158                    }
159                    finally
160                    {
161                        // release working buffer
162                        buffers.push(buf);
163                    }
164                }
165                catch (Exception e)
166                {
167                    failed = true;
168                }
169
170                done = true;
171            }
172        }
173
174        // final image region
175        final Rectangle imageRegion;
176        // required image down scaling
177        final int downScaleLevel;
178        // resolution shift divider
179        final int resShift;
180        final int z;
181        final int t;
182        final int c;
183        final boolean signed;
184        final Object result;
185        final Stack<TilePixelsWorkBuffer> buffers;
186
187        public LociTilePixelsReader(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
188                int tileH, ProgressListener listener) throws IOException, UnsupportedFormatException
189        {
190            super();
191
192            this.z = z;
193            this.t = t;
194            this.c = c;
195
196            final OMEXMLMetadata meta = getOMEXMLMetaData();
197            final int sizeX = MetaDataUtil.getSizeX(meta, series);
198            final int sizeY = MetaDataUtil.getSizeY(meta, series);
199            final DataType type = MetaDataUtil.getDataType(meta, series);
200            signed = type.isSigned();
201
202            // define XY region to load
203            Rectangle adjRegion = new Rectangle(sizeX, sizeY);
204            if (region != null)
205                adjRegion = adjRegion.intersection(region);
206
207            // prepare main reader and get needed downScale
208            downScaleLevel = prepareReader(series, resolution);
209            // real resolution shift used by reader
210            resShift = getResolutionShift();
211
212            // adapt region size to final image resolution
213            imageRegion = new Rectangle(adjRegion.x >> resolution, adjRegion.y >> resolution,
214                    adjRegion.width >> resolution, adjRegion.height >> resolution);
215            // adapt region size to reader resolution
216            adjRegion = new Rectangle(adjRegion.x >> resShift, adjRegion.y >> resShift, adjRegion.width >> resShift,
217                    adjRegion.height >> resShift);
218
219            // allocate result (adapted to final wanted resolution)
220            result = Array1DUtil.createArray(type, imageRegion.width * imageRegion.height);
221
222            // allocate working buffers
223            final int rgbChannelCount = reader.getRGBChannelCount();
224
225            int tw = tileW;
226            int th = tileH;
227
228            // adjust tile size if needed
229            if (tw <= 0)
230                tw = getTileWidth(series);
231            if (tw <= 0)
232                tw = 512;
233            if (th <= 0)
234                th = getTileHeight(series);
235            if (th <= 0)
236                th = 512;
237
238            final int numThread = Math.max(1, SystemUtil.getNumberOfCPUs() - 1);
239
240            buffers = new Stack<TilePixelsWorkBuffer>();
241            for (int i = 0; i < numThread; i++)
242                buffers.push(new TilePixelsWorkBuffer(tw, th, rgbChannelCount, type));
243
244            // create processor
245            final Processor readerProcessor = new Processor(numThread);
246            readerProcessor.setThreadName("Pixels tile reader");
247
248            // get all required tiles
249            final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th);
250
251            // submit all tasks
252            for (Rectangle tile : tiles)
253            {
254                // wait a bit if the process queue is full
255                while (readerProcessor.isFull())
256                {
257                    try
258                    {
259                        Thread.sleep(0);
260                    }
261                    catch (InterruptedException e)
262                    {
263                        // interrupt all processes
264                        readerProcessor.shutdownNow();
265                        break;
266                    }
267                }
268
269                // submit next task
270                readerProcessor.submit(new TilePixelsReaderWorker(tile.intersection(adjRegion)));
271
272                // display progression
273                if (listener != null)
274                {
275                    // process cancel requested ?
276                    if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
277                    {
278                        // interrupt processes
279                        readerProcessor.shutdownNow();
280                        break;
281                    }
282                }
283            }
284
285            // wait for completion
286            while (readerProcessor.isProcessing())
287            {
288                try
289                {
290                    Thread.sleep(1);
291                }
292                catch (InterruptedException e)
293                {
294                    // interrupt all processes
295                    readerProcessor.shutdownNow();
296                    break;
297                }
298
299                // display progression
300                if (listener != null)
301                {
302                    // process cancel requested ?
303                    if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
304                    {
305                        // interrupt processes
306                        readerProcessor.shutdownNow();
307                        break;
308                    }
309                }
310            }
311
312            // last wait for completion just in case we were interrupted
313            readerProcessor.waitAll();
314
315            // faster memory release
316            buffers.clear();
317        }
318    }
319
320    /**
321     * Used for multi thread tile image reading.
322     * 
323     * @author Stephane
324     */
325    class LociTileImageReader
326    {
327        class TileImageWorkBuffer
328        {
329            final byte[] rawBuffer;
330            final byte[] channelBuffer;
331            final Object[] pixelBuffer;
332
333            public TileImageWorkBuffer(int sizeX, int sizeY, int sizeC, int rgbChannel, DataType dataType)
334            {
335                super();
336
337                // allocate arrays
338                rawBuffer = new byte[sizeX * sizeY * rgbChannel * dataType.getSize()];
339                channelBuffer = new byte[sizeX * sizeY * dataType.getSize()];
340                pixelBuffer = Array2DUtil.createArray(dataType, sizeC);
341                for (int i = 0; i < sizeC; i++)
342                    pixelBuffer[i] = Array1DUtil.createArray(dataType, sizeX * sizeY);
343            }
344        }
345
346        class TileImageReaderWorker implements Runnable
347        {
348            final Rectangle region;
349            boolean done;
350            boolean failed;
351
352            public TileImageReaderWorker(Rectangle region)
353            {
354                super();
355
356                this.region = region;
357                done = false;
358                failed = false;
359            }
360
361            @SuppressWarnings("resource")
362            @Override
363            public void run()
364            {
365                IcyBufferedImage img;
366
367                try
368                {
369                    // get reader and working buffers
370                    final IFormatReader r = getReader();
371                    final TileImageWorkBuffer buf = buffers.pop();
372
373                    try
374                    {
375                        try
376                        {
377                            // get image tile
378                            if (c == -1)
379                            {
380                                img = getImageInternal(r, region, z, t, false, downScaleLevel, buf.rawBuffer,
381                                        buf.channelBuffer, buf.pixelBuffer);
382                                // colormaps not yet set ?
383                                if (colormaps[0] == null)
384                                {
385                                    for (int c = 0; c < img.getSizeC(); c++)
386                                        colormaps[c] = img.getColorMap(c);
387                                }
388                            }
389                            else
390                            {
391                                img = getImageInternal(r, region, z, t, c, false, downScaleLevel, buf.rawBuffer,
392                                        buf.channelBuffer, buf.pixelBuffer[0]);
393                                // colormap not yet set ?
394                                if (colormaps[0] == null)
395                                    colormaps[0] = img.getColorMap(0);
396                            }
397                        }
398                        finally
399                        {
400                            // release reader
401                            releaseReader(r);
402                        }
403
404                        // define destination point in destination
405                        final Point pt = region.getLocation();
406                        pt.translate(-imageRegion.x, -imageRegion.y);
407
408                        // copy tile to image result
409                        result.copyData(img, null, pt);
410                    }
411                    finally
412                    {
413                        // release working buffer
414                        buffers.push(buf);
415                    }
416                }
417                catch (Exception e)
418                {
419                    failed = true;
420                }
421
422                done = true;
423            }
424        }
425
426        // final image region
427        final Rectangle imageRegion;
428        // required image down scaling
429        final int downScaleLevel;
430        // resolution shift divider
431        final int resShift;
432        final int z;
433        final int t;
434        final int c;
435        final IcyBufferedImage result;
436        final IcyColorMap[] colormaps;
437        final Stack<TileImageWorkBuffer> buffers;
438
439        public LociTileImageReader(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
440                int tileH, ProgressListener listener) throws IOException, UnsupportedFormatException
441        {
442            super();
443
444            this.z = z;
445            this.t = t;
446            this.c = c;
447
448            final OMEXMLMetadata meta = getOMEXMLMetaData();
449            final int sizeX = MetaDataUtil.getSizeX(meta, series);
450            final int sizeY = MetaDataUtil.getSizeY(meta, series);
451            final DataType type = MetaDataUtil.getDataType(meta, series);
452            final int sizeC = (c == -1) ? MetaDataUtil.getSizeC(meta, series) : 1;
453
454            // define XY region to load
455            Rectangle adjRegion = new Rectangle(sizeX, sizeY);
456            if (region != null)
457                adjRegion = adjRegion.intersection(region);
458
459            // prepare main reader and get needed downScale
460            downScaleLevel = prepareReader(series, resolution);
461            // real resolution shift used by reader
462            resShift = getResolutionShift();
463
464            // adapt region size to final image resolution
465            imageRegion = new Rectangle(adjRegion.x >> resolution, adjRegion.y >> resolution,
466                    adjRegion.width >> resolution, adjRegion.height >> resolution);
467            // adapt region size to reader resolution
468            adjRegion = new Rectangle(adjRegion.x >> resShift, adjRegion.y >> resShift, adjRegion.width >> resShift,
469                    adjRegion.height >> resShift);
470
471            // allocate result (adapted to final wanted resolution)
472            result = new IcyBufferedImage(imageRegion.width, imageRegion.height, sizeC, type);
473            // allocate colormaps
474            colormaps = new IcyColorMap[sizeC];
475            // allocate working buffers
476            final int rgbChannelCount = reader.getRGBChannelCount();
477
478            int tw = tileW;
479            int th = tileH;
480
481            // adjust tile size if needed
482            if (tw <= 0)
483                tw = getTileWidth(series);
484            if (tw <= 0)
485                tw = 512;
486            if (th <= 0)
487                th = getTileHeight(series);
488            if (th <= 0)
489                th = 512;
490
491            final int numThread = Math.max(1, SystemUtil.getNumberOfCPUs() - 1);
492
493            buffers = new Stack<TileImageWorkBuffer>();
494            for (int i = 0; i < numThread; i++)
495                buffers.push(new TileImageWorkBuffer(tw, th, sizeC, rgbChannelCount, type));
496
497            // create processor
498            final Processor readerProcessor = new Processor(numThread);
499
500            readerProcessor.setThreadName("Image tile reader");
501            // to avoid multiple update
502            result.beginUpdate();
503
504            try
505            {
506                // get all required tiles
507                final List<Rectangle> tiles = ImageUtil.getTileList(adjRegion, tw, th);
508
509                // submit all tasks
510                for (Rectangle tile : tiles)
511                {
512                    // wait a bit if the process queue is full
513                    while (readerProcessor.isFull())
514                    {
515                        try
516                        {
517                            Thread.sleep(0);
518                        }
519                        catch (InterruptedException e)
520                        {
521                            // interrupt all processes
522                            readerProcessor.shutdownNow();
523                            break;
524                        }
525                    }
526
527                    // submit next task
528                    readerProcessor.submit(new TileImageReaderWorker(tile.intersection(adjRegion)));
529
530                    // display progression
531                    if (listener != null)
532                    {
533                        // process cancel requested ?
534                        if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
535                        {
536                            // interrupt processes
537                            readerProcessor.shutdownNow();
538                            break;
539                        }
540                    }
541                }
542
543                // wait for completion
544                while (readerProcessor.isProcessing())
545                {
546                    try
547                    {
548                        Thread.sleep(1);
549                    }
550                    catch (InterruptedException e)
551                    {
552                        // interrupt all processes
553                        readerProcessor.shutdownNow();
554                        break;
555                    }
556
557                    // display progression
558                    if (listener != null)
559                    {
560                        // process cancel requested ?
561                        if (!listener.notifyProgress(readerProcessor.getCompletedTaskCount(), tiles.size()))
562                        {
563                            // interrupt processes
564                            readerProcessor.shutdownNow();
565                            break;
566                        }
567                    }
568                }
569
570                // last wait for completion just in case we were interrupted
571                readerProcessor.waitAll();
572            }
573            finally
574            {
575                result.endUpdate();
576            }
577
578            // set back colormap
579            for (int i = 0; i < colormaps.length; i++)
580                result.setColorMap(i, colormaps[i], true);
581
582            // faster memory release
583            buffers.clear();
584        }
585    }
586
587    /**
588     * Main image reader used to retrieve a specific format reader
589     */
590    protected final ImageReader mainReader;
591    /**
592     * Current active reader, it can be a {@link TileStitcher} reader wrapper or the <i>internalReader</i> depending <i>groupFiles</i> state
593     */
594    protected IFormatReader reader;
595    /**
596     * Current format reader (just for test and cloning reader operation)O
597     */
598    protected IFormatReader internalReader;
599    /**
600     * Current format reader (just for accept test, it can be changed by accept(..) method)
601     */
602    protected IFormatReader acceptReader;
603
604    /**
605     * Shared readers for multi threading
606     */
607    protected final List<IFormatReader> readersPool;
608
609    /**
610     * Metadata options
611     */
612    protected final DynamicMetadataOptions options;
613    /**
614     * Advanced settings
615     */
616    protected boolean originalMetadata;
617    protected boolean groupFiles;
618
619    /**
620     * internal resolution levels
621     */
622    protected int[] resolutions;
623    /**
624     * Internal opened path (Bio-formats does not always keep track of it)
625     */
626    protected String openedPath;
627    /**
628     * Internal last used open flags
629     */
630    protected int openFlags;
631
632    public LociImporterPlugin()
633    {
634        super();
635
636        mainReader = new ImageReader();
637        // just to be sure
638        mainReader.setAllowOpenFiles(true);
639
640        reader = null;
641        internalReader = null;
642        acceptReader = null;
643        readersPool = new ArrayList<IFormatReader>();
644
645        options = new DynamicMetadataOptions();
646
647        options.setMetadataLevel(MetadataLevel.NO_OVERLAYS);
648        // we don't care about validation
649        options.setValidate(false);
650        // disable ND2 native chunk map to avoid some issues with ND2 file
651        // TODO: remove when fixed in Bio-formats (see images\bio\bug\nd2\3.19_1.nd2 --> should be a timelaps and not a
652        // single image)
653        options.setBoolean(NativeND2Reader.USE_CHUNKMAP_KEY, Boolean.FALSE);
654
655        originalMetadata = false;
656        groupFiles = true;
657        resolutions = null;
658        openedPath = null;
659        openFlags = 0;
660    }
661
662    protected void setReader(String path) throws FormatException, IOException
663    {
664        IFormatReader newReader = internalReader;
665
666        // no reader defined so just get the good one
667        if (internalReader == null)
668            newReader = mainReader.getReader(path);
669        else
670        {
671            // don't check if the file is currently opened
672            if (!isOpen(path))
673            {
674                // try to check only with extension first then open it if needed
675                // Note that OME TIFF can be opened with the classic TIFF reader
676                if (!internalReader.isThisType(path, false) && !internalReader.isThisType(path, true))
677                    newReader = mainReader.getReader(path);
678            }
679        }
680
681        // reader changed ?
682        if (internalReader != newReader)
683        {
684            // update it
685            internalReader = newReader;
686            // update 'accept' reader
687            acceptReader = newReader;
688            // use TileStitcher wrapper when grouping is ON
689            if (groupFiles)
690                reader = TileStitcher.makeTileStitcher(newReader);
691            else
692                reader = newReader;
693        }
694    }
695
696    protected void reportError(final String title, final String message, final String filename)
697    {
698        // TODO: enable that when LOCI will be ready
699        // ThreadUtil.invokeLater(new Runnable()
700        // {
701        // @Override
702        // public void run()
703        // {
704        // final ErrorReportFrame errorFrame = new ErrorReportFrame(null, title, message);
705        //
706        // errorFrame.setReportAction(new ActionListener()
707        // {
708        // @Override
709        // public void actionPerformed(ActionEvent e)
710        // {
711        // try
712        // {
713        // OMEUtil.reportLociError(filename, errorFrame.getReportMessage());
714        // }
715        // catch (BadLocationException e1)
716        // {
717        // System.err.println("Error while sending report:");
718        // IcyExceptionHandler.showErrorMessage(e1, false, true);
719        // }
720        // }
721        // });
722        // }
723        // });
724    }
725
726    /**
727     * When set to <code>true</code> the importer will also read original metadata (as
728     * annotations)
729     * 
730     * @return the readAllMetadata state<br>
731     * @see #setReadOriginalMetadata(boolean)
732     */
733    public boolean getReadOriginalMetadata()
734    {
735        return originalMetadata;
736    }
737
738    /**
739     * When set to <code>true</code> the importer will also read original metadata (as annotations).
740     */
741    public void setReadOriginalMetadata(boolean value)
742    {
743        originalMetadata = value;
744    }
745
746    /**
747     * When set to <code>true</code> the importer will try to group files required for the whole
748     * dataset.
749     * 
750     * @return the groupFiles
751     */
752    public boolean isGroupFiles()
753    {
754        return groupFiles;
755    }
756
757    /**
758     * When set to <code>true</code> the importer will try to group files required for the whole
759     * dataset.
760     */
761    public void setGroupFiles(boolean value)
762    {
763        groupFiles = value;
764    }
765
766    @Override
767    public List<FileFilter> getFileFilters()
768    {
769        final List<FileFilter> result = new ArrayList<FileFilter>();
770
771        result.add(new LociAllFileFilter());
772        result.add(new ExtensionFileFilter(new String[] {"tif", "tiff"}, "TIFF images / Bio-Formats"));
773        result.add(new ExtensionFileFilter(new String[] {"png"}, "PNG images / Bio-Formats"));
774        result.add(new ExtensionFileFilter(new String[] {"jpg", "jpeg"}, "JPEG images / Bio-Formats"));
775        result.add(new ExtensionFileFilter(new String[] {"avi"}, "AVI videos / Bio-Formats"));
776
777        // final IFormatReader[] readers = mainReader.getReaders();
778
779        // for (IFormatReader reader : readers)
780        // result.add(new FormatFileFilter(reader, true));
781
782        return result;
783    }
784
785    @Override
786    public boolean acceptFile(String path)
787    {
788        // easy discard
789        if (Loader.canDiscardImageFile(path))
790            return false;
791
792        // better for Bio-Formats to have system path format (bug with Bio-Format ?)
793        final String adjPath = new File(path).getAbsolutePath();
794
795        while (true)
796        {
797            try
798            {
799                // this method should not modify the current reader so we use a specific reader for that :)
800                if ((acceptReader == null)
801                        || (!acceptReader.isThisType(adjPath, false) && !acceptReader.isThisType(adjPath, true)))
802                    acceptReader = mainReader.getReader(adjPath);
803
804                return true;
805            }
806            catch (ClosedByInterruptException e)
807            {
808                // we don't accept this interrupt here --> remove interrupted state & retry
809                Thread.interrupted();
810            }
811            catch (Exception e)
812            {
813                // assume false on exception (FormatException or IOException)
814                return false;
815            }
816        }
817    }
818
819    public boolean isOpen(String path)
820    {
821        return StringUtil.equals(getOpened(), FileUtil.getGenericPath(path));
822    }
823
824    @Override
825    public String getOpened()
826    {
827        return openedPath;
828
829        // if (reader != null)
830        // return FileUtil.getGenericPath(reader.getCurrentFile());
831        //
832        // return null;
833    }
834
835    @Override
836    public boolean open(String path, int flags) throws UnsupportedFormatException, IOException
837    {
838        // already opened ?
839        if (isOpen(path))
840            return true;
841
842        // close first
843        close();
844
845        try
846        {
847            // better for Bio-Formats to have system path format
848            final String adjPath = new File(path).getAbsolutePath();
849
850            // ensure we have the correct reader
851            setReader(adjPath);
852            // then open it
853            openReader(reader, adjPath, flags);
854
855            // set reader in reader pool
856            synchronized (readersPool)
857            {
858                readersPool.add(reader);
859            }
860
861            // adjust opened path (always in 'generic format')
862            openedPath = FileUtil.getGenericPath(path);
863            // keep trace of last used flags
864            openFlags = flags;
865            // need to update resolution levels
866            resolutions = null;
867
868            return true;
869        }
870        catch (FormatException e)
871        {
872            throw translateException(path, e);
873        }
874    }
875
876    @Override
877    public void close() throws IOException
878    {
879        // something to close ?
880        if (getOpened() != null)
881        {
882            openedPath = null;
883
884            synchronized (readersPool)
885            {
886                // close all readers
887                for (IFormatReader r : readersPool)
888                    r.close();
889
890                readersPool.clear();
891            }
892        }
893    }
894
895    /**
896     * Open reader for given file path
897     */
898    protected void openReader(IFormatReader reader, String path, int flags) throws FormatException, IOException
899    {
900        // for safety
901        reader.close();
902
903        switch (flags & FLAG_METADATA_MASK)
904        {
905            case FLAG_METADATA_MINIMUM:
906                options.setMetadataLevel(MetadataLevel.MINIMUM);
907                // set metadata option
908                reader.setOriginalMetadataPopulated(false);
909                // don't need to filter metadata
910                reader.setMetadataFiltered(false);
911                break;
912
913            case FLAG_METADATA_ALL:
914                options.setMetadataLevel(MetadataLevel.ALL);
915                // set metadata option
916                reader.setOriginalMetadataPopulated(true);
917                // may need metadata filtering
918                reader.setMetadataFiltered(true);
919                break;
920
921            default:
922                options.setMetadataLevel(MetadataLevel.NO_OVERLAYS);
923                // set metadata option
924                reader.setOriginalMetadataPopulated(originalMetadata);
925                // may need metadata filtering
926                reader.setMetadataFiltered(true);
927                break;
928        }
929
930        // disable flattening sub resolution
931        reader.setFlattenedResolutions(false);
932        // set file grouping
933        reader.setGroupFiles(groupFiles);
934        // prepare meta data store structure
935        reader.setMetadataStore((MetadataStore) OMEUtil.createOMEXMLMetadata());
936        reader.setMetadataOptions(options);
937
938        // set path (id)
939        reader.setId(path);
940    }
941
942    /**
943     * Clone the current used reader conserving its properties and current path
944     */
945    protected IFormatReader cloneReader()
946            throws FormatException, IOException, InstantiationException, IllegalAccessException
947    {
948        if (internalReader == null)
949            return null;
950
951        // create the new internal reader instance
952        final IFormatReader newReader = internalReader.getClass().newInstance();
953        final IFormatReader result;
954
955        // use TileStitcher wrapper when grouping is ON
956        if (groupFiles)
957            result = TileStitcher.makeTileStitcher(newReader);
958        else
959            result = newReader;
960
961        // get opened file
962        final String path = getOpened();
963
964        if (path != null)
965            // open reader for path (adjust path format for Bio-Format)
966            openReader(result, new File(path).getAbsolutePath(), openFlags);
967
968        return result;
969    }
970
971    /**
972     * Returns a reader to use for the current thread (allocate it if needed).<br>
973     * Any obtained reader should be released using {@link #releaseReader(IFormatReader)}
974     * 
975     * @see #releaseReader(IFormatReader)
976     */
977    public IFormatReader getReader() throws FormatException, IOException
978    {
979        try
980        {
981            final IFormatReader result;
982
983            synchronized (readersPool)
984            {
985                if (readersPool.isEmpty())
986                    result = cloneReader();
987                // allocate last reader (faster)
988                else
989                    result = readersPool.remove(readersPool.size() - 1);
990            }
991
992            final int s = reader.getSeries();
993            final int r = reader.getResolution();
994
995            // ensure we are working on same series and resolution
996            if (result.getSeries() != s)
997                result.setSeries(s);
998            if (result.getResolution() != r)
999                result.setResolution(r);
1000
1001            return result;
1002        }
1003        catch (InstantiationException e)
1004        {
1005            // better to rethrow as RuntimeException
1006            throw new RuntimeException(e.getMessage());
1007        }
1008        catch (IllegalAccessException e)
1009        {
1010            // better to rethrow as RuntimeException
1011            throw new RuntimeException(e.getMessage());
1012        }
1013    }
1014
1015    /**
1016     * Release the reader obtained through {@link #getReader()} to the reader pool.
1017     * 
1018     * @see #getReader()
1019     */
1020    public void releaseReader(IFormatReader r)
1021    {
1022        synchronized (readersPool)
1023        {
1024            readersPool.add(r);
1025        }
1026    }
1027
1028    /**
1029     * Prepare the reader to read data from specified series but keep the current / default resolution level.<br>
1030     * WARNING: this method should not be called while image reading operation are still occurring (multi threaded
1031     * read).
1032     */
1033    protected void prepareReader(int series)
1034    {
1035        // series changed ?
1036        if (reader.getSeries() != series)
1037        {
1038            // set wanted series
1039            reader.setSeries(series);
1040            // reset resolution level
1041            resolutions = null;
1042        }
1043
1044        // need to update resolution levels ?
1045        if (resolutions == null)
1046        {
1047            // set default resolution
1048            reader.setResolution(0);
1049
1050            // get default sizeX
1051            final double sizeX = reader.getSizeX();
1052            // get resolution count for this series
1053            final int resCount = reader.getResolutionCount();
1054
1055            // init resolution levels
1056            final List<Integer> validResolutions = new ArrayList<Integer>(16);
1057            // full resolution at 0 (always)
1058            validResolutions.add(Integer.valueOf(0));
1059
1060            // check the sub resolution level
1061            for (int r = 1; r < resCount; r++)
1062            {
1063                // set resolution level in reader
1064                reader.setResolution(r);
1065
1066                // get real resolution level
1067                final double level = Math.log(sizeX / reader.getSizeX()) / Math.log(2);
1068                final double levelInt = Math.floor(level);
1069
1070                // we only want 2^x sub resolution
1071                if (Math.abs(level - levelInt) < 0.005d)
1072                    validResolutions.add(Integer.valueOf((int) levelInt));
1073            }
1074
1075            // copy back to resolutions
1076            resolutions = new int[validResolutions.size()];
1077            for (int i = 0; i < resolutions.length; i++)
1078                resolutions[i] = validResolutions.get(i).intValue();
1079        }
1080    }
1081
1082    /**
1083     * Prepare the reader to read data from specified series and at specified resolution.<br>
1084     * WARNING: this method should not be called while image reading operation are still occurring (multi threaded
1085     * read).
1086     * 
1087     * @return the image divisor factor to match the wanted resolution if needed
1088     */
1089    protected int prepareReader(int series, int resolution)
1090    {
1091        prepareReader(series);
1092
1093        if (resolution > 0)
1094        {
1095            // find closest (but strictly <=) available resolution level
1096            int indRes = 1;
1097            while ((indRes < resolutions.length) && (resolutions[indRes] <= resolution))
1098                indRes++;
1099
1100            // get back to correct resolution level index
1101            indRes--;
1102            // set resolution level
1103            reader.setResolution(indRes);
1104
1105            // return difference between selected resolution level and wanted resolution level
1106            return resolution - resolutions[indRes];
1107        }
1108
1109        // just use default full resolution
1110        reader.setResolution(0);
1111
1112        return 0;
1113    }
1114
1115    /**
1116     * Internal use only
1117     */
1118    protected int getResolutionShift()
1119    {
1120        return resolutions[reader.getResolution()];
1121    }
1122
1123    /**
1124     * Internal use only
1125     */
1126    protected double getResolutionDiviserFactor()
1127    {
1128        return 1d / Math.pow(2, getResolutionShift());
1129    }
1130
1131    @Override
1132    public OMEXMLMetadata getOMEXMLMetaData() throws UnsupportedFormatException, IOException
1133    {
1134        // no image currently opened
1135        if (getOpened() == null)
1136            return null;
1137
1138        // retrieve metadata (don't need thread safe reader for this)
1139        final OMEXMLMetadata result = (OMEXMLMetadata) reader.getMetadataStore();
1140
1141        // TileStitcher reduced series number (stitching occurred) ?
1142        if ((reader.getSeriesCount() == 1) && (MetaDataUtil.getNumSeries(result) > 1))
1143        {
1144            // adjust series count in metadata
1145            MetaDataUtil.setNumSeries(result, 1);
1146
1147            final int sx = reader.getSizeX();
1148            final int sy = reader.getSizeY();
1149
1150            // fix metadata regarding reader information if available
1151            if ((sx > 0) && (sy > 0))
1152            {
1153                MetaDataUtil.setSizeX(result, 0, sx);
1154                MetaDataUtil.setSizeY(result, 0, sy);
1155            }
1156        }
1157
1158        return result;
1159    }
1160
1161    @Deprecated
1162    @Override
1163    public OMEXMLMetadataImpl getMetaData() throws UnsupportedFormatException, IOException
1164    {
1165        return (OMEXMLMetadataImpl) getOMEXMLMetaData();
1166    }
1167
1168    @Override
1169    public int getTileWidth(int series) throws UnsupportedFormatException, IOException
1170    {
1171        // no image currently opened
1172        if (getOpened() == null)
1173            return 0;
1174
1175        // prepare reader
1176        prepareReader(series);
1177
1178        // don't need thread safe reader for this
1179        int result = reader.getOptimalTileWidth();
1180
1181        if (result == 0)
1182            return result;
1183
1184        // we want closest power of 2 (smaller or equal)
1185        return (int) Math.pow(2, Math.floor(Math.log(result) / Math.log(2d)));
1186    }
1187
1188    @Override
1189    public int getTileHeight(int series) throws UnsupportedFormatException, IOException
1190    {
1191        // no image currently opened
1192        if (getOpened() == null)
1193            return 0;
1194
1195        // prepare reader
1196        prepareReader(series);
1197
1198        // don't need thread safe reader for this
1199        int result = reader.getOptimalTileHeight();
1200
1201        if (result == 0)
1202            return result;
1203
1204        // we want closest power of 2 (smaller or equal)
1205        return (int) Math.pow(2, Math.floor(Math.log(result) / Math.log(2d)));
1206    }
1207
1208    @Override
1209    public boolean isResolutionAvailable(int series, int resolution) throws UnsupportedFormatException, IOException
1210    {
1211        // no image currently opened
1212        if (getOpened() == null)
1213            return resolution == 0;
1214
1215        // prepare reader
1216        prepareReader(series);
1217
1218        if (resolution > 0)
1219        {
1220            // try to find wanted resolution
1221            for (int r : resolutions)
1222                if (r == resolution)
1223                    return true;
1224        }
1225
1226        return resolution == 0;
1227    }
1228
1229    @SuppressWarnings("resource")
1230    @Override
1231    public IcyBufferedImage getThumbnail(int series) throws UnsupportedFormatException, IOException
1232    {
1233        // no image currently opened
1234        if (getOpened() == null)
1235            return null;
1236
1237        // stitched image ? --> use AbstractImageProvider implementation as getThumbnail(..) from TileSticher doesn't do
1238        // stitching
1239        if ((reader.getSizeX() != internalReader.getSizeX()) || (reader.getSizeY() != internalReader.getSizeY()))
1240            return super.getThumbnail(series);
1241
1242        try
1243        {
1244            // prepare reader (no down scaling here)
1245            prepareReader(series, 0);
1246
1247            final IFormatReader r = getReader();
1248
1249            try
1250            {
1251                // get image
1252                return getThumbnail(r, r.getSizeZ() / 2, r.getSizeT() / 2);
1253            }
1254            finally
1255            {
1256                releaseReader(r);
1257            }
1258        }
1259        catch (FormatException e)
1260        {
1261            throw translateException(getOpened(), e);
1262        }
1263        catch (Throwable t)
1264        {
1265            // can happen if we don't have enough memory --> try default implementation
1266            return super.getThumbnail(series);
1267        }
1268    }
1269
1270    @SuppressWarnings("resource")
1271    @Override
1272    public Object getPixels(int series, int resolution, Rectangle rectangle, int z, int t, int c)
1273            throws UnsupportedFormatException, IOException
1274    {
1275        // no image currently opened
1276        if (getOpened() == null)
1277            return null;
1278
1279        try
1280        {
1281            // prepare reader and get down scale factor
1282            final int downScaleLevel = prepareReader(series, resolution);
1283            final IFormatReader r = getReader();
1284
1285            final Rectangle adjRect;
1286
1287            // adjust rectangle to current reader resolution if needed
1288            if (rectangle != null)
1289                adjRect = Rectangle2DUtil.getScaledRectangle(rectangle, getResolutionDiviserFactor(), false, true)
1290                        .getBounds();
1291            else
1292                adjRect = null;
1293
1294            try
1295            {
1296                // return pixels
1297                return getPixelsInternal(r, adjRect, z, t, c, false, downScaleLevel);
1298            }
1299            finally
1300            {
1301                releaseReader(r);
1302            }
1303        }
1304        catch (FormatException e)
1305        {
1306            throw translateException(getOpened(), e);
1307        }
1308    }
1309
1310    @SuppressWarnings("resource")
1311    @Override
1312    public IcyBufferedImage getImage(int series, int resolution, Rectangle rectangle, int z, int t, int c)
1313            throws UnsupportedFormatException, IOException
1314    {
1315        // no image currently opened
1316        if (getOpened() == null)
1317            return null;
1318
1319        try
1320        {
1321            // prepare reader and get down scale factor if wanted resolution is not available
1322            final int downScaleLevel = prepareReader(series, resolution);
1323            final IFormatReader r = getReader();
1324            final Rectangle adjRect;
1325
1326            // adjust rectangle to current reader resolution if needed
1327            if (rectangle != null)
1328                adjRect = Rectangle2DUtil.getScaledRectangle(rectangle, getResolutionDiviserFactor(), false, true)
1329                        .getBounds();
1330            else
1331                adjRect = null;
1332
1333            try
1334            {
1335                // get image
1336                return getImage(r, adjRect, z, t, c, downScaleLevel);
1337            }
1338            catch (IOException e)
1339            {
1340                throw e;
1341            }
1342            // not enough memory error ?
1343            catch (OutOfMemoryError e)
1344            {
1345                // need rescaling --> try tiling read
1346                if (downScaleLevel > 0)
1347                    return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series),
1348                            getTileHeight(series), null);
1349
1350                throw e;
1351            }
1352            // too large XY plan ?
1353            catch (UnsupportedOperationException e)
1354            {
1355                // need rescaling --> try tiling read
1356                if (downScaleLevel > 0)
1357                    return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series),
1358                            getTileHeight(series), null);
1359
1360                throw e;
1361            }
1362            catch (FormatException e)
1363            {
1364                if (!(e.getCause() instanceof ClosedByInterruptException) && (downScaleLevel > 0))
1365                    // we can have here a "Image plane too large. Only 2GB of data can be extracted at
1366                    // one time." error here --> so can try to use tile loading when we need rescaling
1367                    return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series),
1368                            getTileHeight(series), null);
1369
1370                throw e;
1371            }
1372            catch (Exception e)
1373            {
1374                // we can have NegativeArraySizeException here for instance
1375                // try to use tile loading when we need rescaling
1376                if (downScaleLevel > 0)
1377                    return getImageByTile(series, resolution, rectangle, z, t, c, getTileWidth(series),
1378                            getTileHeight(series), null);
1379
1380                throw new UnsupportedOperationException(e);
1381            }
1382            finally
1383            {
1384                releaseReader(r);
1385            }
1386        }
1387        catch (FormatException e)
1388        {
1389            throw translateException(getOpened(), e);
1390        }
1391    }
1392
1393    public IcyBufferedImage getImageByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
1394            int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException
1395    {
1396        return new LociTileImageReader(series, resolution, region, z, t, c, tileW, tileH, listener).result;
1397    }
1398
1399    @Override
1400    public Object getPixelsByTile(int series, int resolution, Rectangle region, int z, int t, int c, int tileW,
1401            int tileH, ProgressListener listener) throws UnsupportedFormatException, IOException
1402    {
1403        return new LociTilePixelsReader(series, resolution, region, z, t, c, tileW, tileH, listener).result;
1404    }
1405
1406    /**
1407     * Load a thumbnail version of the image located at (Z, T) position from the specified
1408     * {@link IFormatReader} and
1409     * returns it as an IcyBufferedImage.
1410     * 
1411     * @param reader
1412     *        {@link IFormatReader}
1413     * @param z
1414     *        Z position of the image to load
1415     * @param t
1416     *        T position of the image to load
1417     * @return {@link IcyBufferedImage}
1418     */
1419    public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t)
1420            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1421    {
1422        return getThumbnail(reader, z, t, -1);
1423    }
1424
1425    /**
1426     * Load a thumbnail version of the image located at (Z, T, C) position from the specified
1427     * {@link IFormatReader} and returns it as an IcyBufferedImage.
1428     * 
1429     * @param reader
1430     *        {@link IFormatReader}
1431     * @param z
1432     *        Z position of the thumbnail to load
1433     * @param t
1434     *        T position of the thumbnail to load
1435     * @param c
1436     *        Channel index
1437     * @return {@link IcyBufferedImage}
1438     */
1439    public static IcyBufferedImage getThumbnail(IFormatReader reader, int z, int t, int c)
1440            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1441    {
1442        try
1443        {
1444            // all channel ?
1445            if (c == -1)
1446                return getImageInternal(reader, null, z, t, true, 0);
1447
1448            return getImageInternal(reader, null, z, t, c, true, 0);
1449        }
1450        catch (ClosedByInterruptException e)
1451        {
1452            // loading interrupted --> return null
1453            return null;
1454        }
1455        catch (Exception e)
1456        {
1457            // LOCI do not support thumbnail for all image, try compatible version
1458            return getThumbnailCompatible(reader, z, t, c);
1459        }
1460    }
1461
1462    /**
1463     * Load a thumbnail version of the image located at (Z, T) position from the specified
1464     * {@link IFormatReader} and returns it as an IcyBufferedImage.<br>
1465     * <i>Slow compatible version (load the original image and resize it)</i>
1466     * 
1467     * @param reader
1468     *        {@link IFormatReader}
1469     * @param z
1470     *        Z position of the image to load
1471     * @param t
1472     *        T position of the image to load
1473     * @return {@link IcyBufferedImage}
1474     */
1475    public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t)
1476            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1477    {
1478        return getThumbnailCompatible(reader, z, t, -1);
1479    }
1480
1481    /**
1482     * Load a thumbnail version of the image located at (Z, T, C) position from the specified
1483     * {@link IFormatReader} and returns it as an IcyBufferedImage.<br>
1484     * <i>Slow compatible version (load the original image and resize it)</i>
1485     * 
1486     * @param reader
1487     *        {@link IFormatReader}
1488     * @param z
1489     *        Z position of the thumbnail to load
1490     * @param t
1491     *        T position of the thumbnail to load
1492     * @param c
1493     *        Channel index
1494     * @return {@link IcyBufferedImage}
1495     */
1496    public static IcyBufferedImage getThumbnailCompatible(IFormatReader reader, int z, int t, int c)
1497            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1498    {
1499        final IcyBufferedImage image = getImage(reader, null, z, t, c, 0);
1500
1501        // scale it to desired dimension (fast enough as here we have a small image)
1502        final IcyBufferedImage result = IcyBufferedImageUtil.scale(image, reader.getThumbSizeX(),
1503                reader.getThumbSizeY(), FilterType.BILINEAR);
1504
1505        // preserve colormaps
1506        result.setColorMaps(image);
1507
1508        return result;
1509    }
1510
1511    /**
1512     * Load a single channel sub image at (Z, T, C) position from the specified {@link IFormatReader}<br>
1513     * and returns it as an IcyBufferedImage.
1514     * 
1515     * @param reader
1516     *        Reader used to load the image
1517     * @param rect
1518     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1519     *        resolution).<br>
1520     *        Set to <code>null</code> to retrieve the whole image.
1521     * @param z
1522     *        Z position of the image to load
1523     * @param t
1524     *        T position of the image to load
1525     * @param c
1526     *        Channel index to load (-1 = all channels)
1527     * @param downScaleLevel
1528     *        number of downscale to process (scale level = 1/2^downScaleLevel)
1529     * @return {@link IcyBufferedImage}
1530     */
1531    public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c,
1532            int downScaleLevel) throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1533    {
1534        // we want all channel ? use method to retrieve whole image
1535        if (c == -1)
1536            return getImageInternal(reader, rect, z, t, false, downScaleLevel);
1537
1538        return getImageInternal(reader, rect, z, t, c, false, downScaleLevel);
1539    }
1540
1541    /**
1542     * Load a single channel sub image at (Z, T, C) position from the specified {@link IFormatReader}<br>
1543     * and returns it as an IcyBufferedImage.
1544     * 
1545     * @param reader
1546     *        Reader used to load the image
1547     * @param rect
1548     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1549     *        resolution).<br>
1550     *        Set to <code>null</code> to retrieve the whole image.
1551     * @param z
1552     *        Z position of the image to load
1553     * @param t
1554     *        T position of the image to load
1555     * @param c
1556     *        Channel index to load (-1 = all channels)
1557     * @return {@link IcyBufferedImage}
1558     */
1559    public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t, int c)
1560            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1561    {
1562        return getImage(reader, rect, z, t, c, 0);
1563    }
1564
1565    /**
1566     * Load the image located at (Z, T) position from the specified IFormatReader<br>
1567     * and return it as an IcyBufferedImage.
1568     * 
1569     * @param reader
1570     *        {@link IFormatReader}
1571     * @param rect
1572     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1573     *        resolution).<br>
1574     *        Set to <code>null</code> to retrieve the whole image.
1575     * @param z
1576     *        Z position of the image to load
1577     * @param t
1578     *        T position of the image to load
1579     * @return {@link IcyBufferedImage}
1580     */
1581    public static IcyBufferedImage getImage(IFormatReader reader, Rectangle rect, int z, int t)
1582            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1583    {
1584        return getImage(reader, rect, z, t, -1, 0);
1585    }
1586
1587    /**
1588     * Load the image located at (Z, T) position from the specified IFormatReader<br>
1589     * and return it as an IcyBufferedImage (compatible and slower method).
1590     * 
1591     * @param reader
1592     *        {@link IFormatReader}
1593     * @param z
1594     *        Z position of the image to load
1595     * @param t
1596     *        T position of the image to load
1597     * @return {@link IcyBufferedImage}
1598     */
1599    public static IcyBufferedImage getImageCompatible(IFormatReader reader, int z, int t)
1600            throws FormatException, IOException
1601    {
1602        final int sizeX = reader.getSizeX();
1603        final int sizeY = reader.getSizeY();
1604        final List<BufferedImage> imageList = new ArrayList<BufferedImage>();
1605        final int sizeC = reader.getEffectiveSizeC();
1606
1607        for (int c = 0; c < sizeC; c++)
1608            imageList.add(AWTImageTools.openImage(reader.openBytes(reader.getIndex(z, c, t)), reader, sizeX, sizeY));
1609
1610        // combine channels
1611        return IcyBufferedImage.createFrom(imageList);
1612
1613    }
1614
1615    /**
1616     * Load pixels of the specified region of image at (Z, T, C) position and returns them as an
1617     * array.
1618     * 
1619     * @param reader
1620     *        Reader used to load the pixels
1621     * @param dataType
1622     *        pixel data type
1623     * @param rect
1624     *        Define the pixels rectangular region we want to load (considering current selected image resolution).<br>
1625     *        Should be adjusted if <i>thumbnail</i> parameter is <code>true</code>
1626     * @param z
1627     *        Z position of the pixels to load
1628     * @param t
1629     *        T position of the pixels to load
1630     * @param c
1631     *        Channel index to load
1632     * @param thumbnail
1633     *        Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i>
1634     *        parameter should contains thumbnail size
1635     * @param downScaleLevel
1636     *        number of downscale to process (scale level = 1/2^downScaleLevel)
1637     * @param rawBuffer
1638     *        pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY *
1639     *        Datatype.size]) used to read the whole RGB raw data (can be <code>null</code>)
1640     * @param channelBuffer
1641     *        pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the
1642     *        channel raw data (can be <code>null</code>)
1643     * @param pixelBuffer
1644     *        pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel
1645     *        converted data and to build the result image (can be <code>null</code>)
1646     * @return 1D array containing pixels data.<br>
1647     *         The type of the array depends from the internal image data type
1648     * @throws UnsupportedOperationException
1649     *         if the XY plane size is >= 2^31 pixels
1650     * @throws OutOfMemoryError
1651     *         if there is not enough memory to open the image
1652     */
1653    protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c,
1654            boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer)
1655            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1656    {
1657        // get pixel data type
1658        final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType());
1659
1660        // check we can open the image
1661        // Loader.checkOpening(resolutions[reader.getResolution()], rect.width, rect.height, 1, 1, 1, dataType,
1662        // "");
1663
1664        // prepare informations
1665        final int rgbChanCount = reader.getRGBChannelCount();
1666        final boolean interleaved = reader.isInterleaved();
1667        final boolean little = reader.isLittleEndian();
1668
1669        // allocate internal image data array if needed
1670        Object result = Array1DUtil.allocIfNull(pixelBuffer, dataType, rect.width * rect.height);
1671        // compute channel offsets
1672        final int baseC = c / rgbChanCount;
1673        final int subC = c % rgbChanCount;
1674
1675        // get image data (whole RGB data for RGB channel)
1676        byte[] rawData = getBytesInternal(reader, reader.getIndex(z, baseC, t), rect, thumbnail, rawBuffer);
1677
1678        // current final component
1679        final int componentByteLen = rawData.length / rgbChanCount;
1680
1681        // build data array
1682        if (interleaved)
1683        {
1684            // get channel interleaved data
1685            final byte[] channelData = Array1DUtil.getInterleavedData(rawData, subC, rgbChanCount, channelBuffer, 0,
1686                    componentByteLen);
1687            ByteArrayConvert.byteArrayTo(channelData, 0, result, 0, componentByteLen, little);
1688        }
1689        else
1690            ByteArrayConvert.byteArrayTo(rawData, subC * componentByteLen, result, 0, componentByteLen, little);
1691
1692        // don't need downscaling ? --> we can return raw pixels immediately
1693        if (downScaleLevel <= 0)
1694            return result;
1695
1696        // do fast downscaling
1697        int it = downScaleLevel;
1698        int sizeX = rect.width;
1699        int sizeY = rect.height;
1700
1701        while (it-- > 0)
1702        {
1703            result = IcyBufferedImageUtil.downscaleBy2(result, sizeX, sizeY, dataType.isSigned(), true);
1704            sizeX /= 2;
1705            sizeY /= 2;
1706        }
1707
1708        return result;
1709    }
1710
1711    /**
1712     * Load pixels of the specified region of image at (Z, T, C) position and returns them as an
1713     * array.
1714     * 
1715     * @param reader
1716     *        Reader used to load the pixels
1717     * @param rect
1718     *        Define the pixels rectangular region we want to load (considering current selected image resolution).<br>
1719     *        Set to <code>null</code> to retrieve the whole image.
1720     * @param z
1721     *        Z position of the pixels to load
1722     * @param t
1723     *        T position of the pixels to load
1724     * @param c
1725     *        Channel index to load
1726     * @param thumbnail
1727     *        Set to <code>true</code> to request a thumbnail of the image (<code>rect</code>
1728     *        parameter is then ignored)
1729     * @param downScaleLevel
1730     *        number of downscale to process (scale level = 1/2^downScaleLevel)
1731     * @return 1D array containing pixels data.<br>
1732     *         The type of the array depends from the internal image data type
1733     */
1734    protected static Object getPixelsInternal(IFormatReader reader, Rectangle rect, int z, int t, int c,
1735            boolean thumbnail, int downScaleLevel)
1736            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1737    {
1738        final Rectangle r;
1739
1740        if (thumbnail)
1741            r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY());
1742        else if (rect == null)
1743            r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY());
1744        else
1745            r = rect;
1746
1747        return getPixelsInternal(reader, r, z, t, c, thumbnail, downScaleLevel, null, null, null);
1748    }
1749
1750    /**
1751     * Load a single channel sub image at (Z, T, C) position from the specified
1752     * {@link IFormatReader}<br>
1753     * and returns it as an IcyBufferedImage.
1754     * 
1755     * @param reader
1756     *        Reader used to load the image
1757     * @param rect
1758     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1759     *        resolution).<br>
1760     *        Should be adjusted if <i>thumbnail</i> parameter is <code>true</code>
1761     * @param z
1762     *        Z position of the image to load
1763     * @param t
1764     *        T position of the image to load
1765     * @param c
1766     *        Channel index to load
1767     * @param thumbnail
1768     *        Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i>
1769     *        parameter should contains thumbnail size
1770     * @param downScaleLevel
1771     *        number of downscale to process (scale level = 1/2^downScaleLevel)
1772     * @param rawBuffer
1773     *        pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY *
1774     *        Datatype.size]) used to read the whole RGB raw data (can be <code>null</code>)
1775     * @param channelBuffer
1776     *        pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the
1777     *        channel raw data (can be <code>null</code>)
1778     * @param pixelBuffer
1779     *        pre allocated 1D array pixel data buffer ([SizeX * SizeY]) used to receive the pixel
1780     *        converted data and to build the result image (can be <code>null</code>)
1781     * @return {@link IcyBufferedImage}
1782     * @throws UnsupportedOperationException
1783     *         if the XY plane size is >= 2^31 pixels
1784     * @throws OutOfMemoryError
1785     *         if there is not enough memory to open the image
1786     */
1787    protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c,
1788            boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object pixelBuffer)
1789            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1790    {
1791        // get pixel data
1792        final Object pixelData = getPixelsInternal(reader, rect, z, t, c, thumbnail, downScaleLevel, rawBuffer,
1793                channelBuffer, pixelBuffer);
1794        // get pixel data type
1795        final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType());
1796
1797        // get final sizeX and sizeY
1798        int sizeX = rect.width;
1799        int sizeY = rect.height;
1800        int downScale = downScaleLevel;
1801        while (downScale > 0)
1802        {
1803            sizeX /= 2;
1804            sizeY /= 2;
1805            downScale--;
1806        }
1807
1808        // create the single channel result image from pixel data
1809        final IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, pixelData, dataType.isSigned());
1810
1811        IcyColorMap map = null;
1812        final int rgbChannel = reader.getRGBChannelCount();
1813
1814        // indexed color ?
1815        if (reader.isIndexed())
1816        {
1817            // only 8 bits and 16 bits lookup table supported
1818            switch (dataType.getJavaType())
1819            {
1820                case BYTE:
1821                    final byte[][] bmap = reader.get8BitLookupTable();
1822                    if (bmap != null)
1823                        map = new IcyColorMap("Channel " + c, bmap);
1824                    break;
1825
1826                case SHORT:
1827                    final short[][] smap = reader.get16BitLookupTable();
1828                    if (smap != null)
1829                        map = new IcyColorMap("Channel " + c, smap);
1830                    break;
1831
1832                default:
1833                    break;
1834            }
1835        }
1836
1837        // no RGB image ?
1838        if (rgbChannel <= 1)
1839        {
1840            // colormap not set (or black) ? --> try to use metadata
1841            if ((map == null) || map.isBlack())
1842            {
1843                final OMEXMLMetadata metaData = (OMEXMLMetadata) reader.getMetadataStore();
1844                final Color color = MetaDataUtil.getChannelColor(metaData, reader.getSeries(), c);
1845
1846                if ((color != null) && !ColorUtil.isBlack(color))
1847                    map = new LinearColorMap("Channel " + c, color);
1848                else
1849                    map = null;
1850            }
1851        }
1852        else
1853        {
1854            switch (c)
1855            {
1856                case 0:
1857                    map = LinearColorMap.red_;
1858                    break;
1859                case 1:
1860                    map = LinearColorMap.green_;
1861                    break;
1862                case 2:
1863                    map = LinearColorMap.blue_;
1864                    break;
1865                case 3:
1866                    map = LinearColorMap.alpha_;
1867                    break;
1868            }
1869        }
1870
1871        // we were able to retrieve a colormap ? --> set it
1872        if (map != null)
1873            result.setColorMap(0, map, true);
1874
1875        return result;
1876    }
1877
1878    /**
1879     * Load a single channel sub image at (Z, T, C) position from the specified
1880     * {@link IFormatReader}<br>
1881     * and returns it as an IcyBufferedImage.
1882     * 
1883     * @param reader
1884     *        Reader used to load the image
1885     * @param rect
1886     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1887     *        resolution).<br>
1888     *        Set to <code>null</code> to retrieve the whole image.
1889     * @param z
1890     *        Z position of the image to load
1891     * @param t
1892     *        T position of the image to load
1893     * @param c
1894     *        Channel index to load
1895     * @param thumbnail
1896     *        Set to <code>true</code> to request a thumbnail of the image (<code>rect</code>
1897     *        parameter is then ignored)
1898     * @param downScaleLevel
1899     *        number of downscale to process (scale level = 1/2^downScaleLevel)
1900     * @return {@link IcyBufferedImage}
1901     */
1902    protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t, int c,
1903            boolean thumbnail, int downScaleLevel)
1904            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1905    {
1906        final Rectangle r;
1907
1908        if (thumbnail)
1909            r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY());
1910        else if (rect == null)
1911            r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY());
1912        else
1913            r = rect;
1914
1915        return getImageInternal(reader, r, z, t, c, thumbnail, downScaleLevel, null, null, null);
1916    }
1917
1918    /**
1919     * Load the image located at (Z, T) position from the specified IFormatReader and return it as
1920     * an IcyBufferedImage.
1921     * 
1922     * @param reader
1923     *        {@link IFormatReader}
1924     * @param rect
1925     *        Define the image rectangular region we want to retrieve data for (considering current selected image
1926     *        reader resolution).<br>
1927     *        Should be adjusted if <i>thumbnail</i> parameter is <code>true</code>
1928     * @param z
1929     *        Z position of the image to load
1930     * @param t
1931     *        T position of the image to load
1932     * @param thumbnail
1933     *        Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i> parameter should
1934     *        contains thumbnail size
1935     * @param downScaleLevel
1936     *        number of downscale to process after loading the image (scale level = 1/2^downScaleLevel)
1937     * @param rawBuffer
1938     *        pre allocated byte data buffer ([reader.getRGBChannelCount() * SizeX * SizeY * Datatype.size]) used to
1939     *        read the whole RGB raw data (can be <code>null</code>)
1940     * @param channelBuffer
1941     *        pre allocated byte data buffer ([SizeX * SizeY * Datatype.size]) used to read the channel raw data (can be
1942     *        <code>null</code>)
1943     * @param pixelBuffer
1944     *        pre allocated 2D array ([SizeC][SizeX*SizeY]) pixel data buffer used to receive the pixel converted data
1945     *        and to build the result image (can be <code>null</code>)
1946     * @return {@link IcyBufferedImage}
1947     * @throws UnsupportedOperationException
1948     *         if the XY plane size is >= 2^31 pixels
1949     * @throws OutOfMemoryError
1950     *         if there is not enough memory to open the image
1951     */
1952    protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t,
1953            boolean thumbnail, int downScaleLevel, byte[] rawBuffer, byte[] channelBuffer, Object[] pixelBuffer)
1954            throws UnsupportedOperationException, OutOfMemoryError, FormatException, IOException
1955    {
1956        // get pixel data type
1957        final DataType dataType = DataType.getDataTypeFromFormatToolsType(reader.getPixelType());
1958        // get sizeC
1959        final int effSizeC = reader.getEffectiveSizeC();
1960        final int rgbChanCount = reader.getRGBChannelCount();
1961        final int sizeX = rect.width;
1962        final int sizeY = rect.height;
1963        final int sizeC = effSizeC * rgbChanCount;
1964
1965        // check we can open the image
1966        // Loader.checkOpening(resolutions[reader.getResolution()], sizeX, sizeY, sizeC, 1, 1, dataType, "");
1967
1968        final int series = reader.getSeries();
1969        // prepare informations
1970        final boolean indexed = reader.isIndexed();
1971        final boolean little = reader.isLittleEndian();
1972        final OMEXMLMetadata metaData = (OMEXMLMetadata) reader.getMetadataStore();
1973
1974        // prepare internal image data array
1975        final Object[] pixelData;
1976
1977        if (pixelBuffer == null)
1978        {
1979            // allocate array
1980            pixelData = Array2DUtil.createArray(dataType, sizeC);
1981            for (int i = 0; i < sizeC; i++)
1982                pixelData[i] = Array1DUtil.createArray(dataType, sizeX * sizeY);
1983        }
1984        else
1985            pixelData = pixelBuffer;
1986
1987        // colormap allocation
1988        final IcyColorMap[] colormaps = new IcyColorMap[effSizeC];
1989
1990        byte[] rawData = null;
1991        for (int effC = 0; effC < effSizeC; effC++)
1992        {
1993            // get data
1994            rawData = getBytesInternal(reader, reader.getIndex(z, effC, t), rect, thumbnail, rawBuffer);
1995
1996            // current final component
1997            final int c = effC * rgbChanCount;
1998            final int componentByteLen = rawData.length / rgbChanCount;
1999
2000            // build data array
2001            int inOffset = 0;
2002            if (reader.isInterleaved())
2003            {
2004                final byte[] channelData = (channelBuffer == null) ? new byte[componentByteLen] : channelBuffer;
2005
2006                for (int sc = 0; sc < rgbChanCount; sc++)
2007                {
2008                    // get channel interleaved data
2009                    Array1DUtil.getInterleavedData(rawData, inOffset, rgbChanCount, channelData, 0, componentByteLen);
2010                    ByteArrayConvert.byteArrayTo(channelData, 0, pixelData[c + sc], 0, componentByteLen, little);
2011                    inOffset++;
2012                }
2013            }
2014            else
2015            {
2016                for (int sc = 0; sc < rgbChanCount; sc++)
2017                {
2018                    ByteArrayConvert.byteArrayTo(rawData, inOffset, pixelData[c + sc], 0, componentByteLen, little);
2019                    inOffset += componentByteLen;
2020                }
2021            }
2022
2023            // indexed color ?
2024            if (indexed)
2025            {
2026                // only 8 bits and 16 bits lookup table supported
2027                switch (dataType.getJavaType())
2028                {
2029                    case BYTE:
2030                        final byte[][] bmap = reader.get8BitLookupTable();
2031                        if (bmap != null)
2032                            colormaps[effC] = new IcyColorMap("Channel " + effC, bmap);
2033                        break;
2034
2035                    case SHORT:
2036                        final short[][] smap = reader.get16BitLookupTable();
2037                        if (smap != null)
2038                            colormaps[effC] = new IcyColorMap("Channel " + effC, smap);
2039                        break;
2040
2041                    default:
2042                        colormaps[effC] = null;
2043                        break;
2044                }
2045            }
2046
2047            // no RGB image ?
2048            if (rgbChanCount <= 1)
2049            {
2050                // colormap not yet set (or black) ? --> try to use metadata
2051                if ((colormaps[effC] == null) || colormaps[effC].isBlack())
2052                {
2053                    final Color color = MetaDataUtil.getChannelColor(metaData, series, effC);
2054
2055                    if ((color != null) && !ColorUtil.isBlack(color))
2056                        colormaps[effC] = new LinearColorMap("Channel " + effC, color);
2057                    else
2058                        colormaps[effC] = null;
2059                }
2060            }
2061        }
2062
2063        // create result image
2064        IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, pixelData, dataType.isSigned());
2065
2066        // do downscaling if needed
2067        result = IcyBufferedImageUtil.downscaleBy2(result, true, downScaleLevel);
2068
2069        // affect colormap
2070        result.beginUpdate();
2071        try
2072        {
2073            // RGB image (can't use colormaps)
2074            if (rgbChanCount > 1)
2075            {
2076                // RGB at least ? --> set RGB colormap
2077                if ((sizeC >= 3) && (rgbChanCount >= 3))
2078                {
2079                    result.setColorMap(0, LinearColorMap.red_, true);
2080                    result.setColorMap(1, LinearColorMap.green_, true);
2081                    result.setColorMap(2, LinearColorMap.blue_, true);
2082                }
2083                // RGBA ? --> set alpha colormap
2084                if ((sizeC >= 4) && ((rgbChanCount >= 4) || (reader instanceof PNGReader)
2085                        || (reader instanceof APNGReader) || (reader instanceof JPEG2000Reader)))
2086                    result.setColorMap(3, LinearColorMap.alpha_, true);
2087            }
2088            // fluo image
2089            else if (sizeC == effSizeC)
2090            {
2091                // set colormaps
2092                for (int comp = 0; comp < effSizeC; comp++)
2093                {
2094                    // we were able to retrieve a colormap for that channel ? --> set it
2095                    if (colormaps[comp] != null)
2096                        result.setColorMap(comp, colormaps[comp], true);
2097                }
2098            }
2099        }
2100        finally
2101        {
2102            result.endUpdate();
2103        }
2104
2105        return result;
2106    }
2107
2108    /**
2109     * Load the image located at (Z, T) position from the specified IFormatReader<br>
2110     * and return it as an IcyBufferedImage.
2111     * 
2112     * @param reader
2113     *        {@link IFormatReader}
2114     * @param rect
2115     *        Define the image rectangular region we want to retrieve data for (considering current selected image
2116     *        resolution).<br>
2117     *        Set to <code>null</code> to retrieve the whole image.
2118     * @param z
2119     *        Z position of the image to load
2120     * @param t
2121     *        T position of the image to load
2122     * @param thumbnail
2123     *        Set to <code>true</code> to request a thumbnail of the image (<code>rect</code> parameter is then ignored)
2124     * @param downScaleLevel
2125     *        number of downscale to process (scale level = 1/2^downScaleLevel)
2126     * @return {@link IcyBufferedImage}
2127     */
2128    protected static IcyBufferedImage getImageInternal(IFormatReader reader, Rectangle rect, int z, int t,
2129            boolean thumbnail, int downScaleLevel) throws FormatException, IOException
2130    {
2131        final Rectangle r;
2132
2133        if (thumbnail)
2134            r = new Rectangle(0, 0, reader.getThumbSizeX(), reader.getThumbSizeY());
2135        else if (rect == null)
2136            r = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY());
2137        else
2138            r = rect;
2139
2140        return getImageInternal(reader, r, z, t, thumbnail, downScaleLevel, null, null, null);
2141    }
2142
2143    /**
2144     * <b>Internal use only !!</b><br>
2145     * 
2146     * @param reader
2147     *        Reader used to load the pixels
2148     * @param index
2149     *        plane index
2150     * @param rect
2151     *        Define the pixels rectangular region we want to load (considering current selected image resolution).<br>
2152     *        Should be adjusted if <i>thumbnail</i> parameter is <code>true</code>
2153     * @param thumbnail
2154     *        Set to <code>true</code> to request a thumbnail of the image in which case <i>rect</i>
2155     *        parameter should contains thumbnail size
2156     * @param buffer
2157     *        pre allocated output byte data buffer ([reader.getRGBChannelCount() * rect.width * rect.height *
2158     *        Datatype.size]) used to
2159     *        read the whole RGB raw data (can be <code>null</code>)
2160     * @return byte array containing pixels data.<br>
2161     * @throws UnsupportedOperationException
2162     *         if the XY plane size is >= 2^31 pixels
2163     * @throws OutOfMemoryError
2164     *         if there is not enough memory to open the image
2165     */
2166    protected static byte[] getBytesInternal(IFormatReader reader, int index, Rectangle rect, boolean thumbnail,
2167            byte[] buffer) throws FormatException, IOException
2168    {
2169        if (thumbnail)
2170            return reader.openThumbBytes(index);
2171
2172        final Rectangle imgRect = new Rectangle(0, 0, reader.getSizeX(), reader.getSizeY());
2173
2174        // TODO: we should check that we open a big image and then use tile loading instead
2175
2176        // need to allocate
2177        if (buffer == null)
2178        {
2179            // return whole image
2180            if ((rect == null) || rect.contains(imgRect))
2181                return reader.openBytes(index);
2182
2183            // return region
2184            return reader.openBytes(index, rect.x, rect.y, rect.width, rect.height);
2185        }
2186
2187        // already allocated / whole image
2188        if ((rect == null) || rect.equals(imgRect))
2189            return reader.openBytes(index, buffer);
2190
2191        // return region
2192        return reader.openBytes(index, buffer, rect.x, rect.y, rect.width, rect.height);
2193    }
2194
2195    protected static UnsupportedFormatException translateException(String path, FormatException exception)
2196    {
2197        if (exception instanceof UnknownFormatException)
2198            return new UnsupportedFormatException(path + ": Unknown image format.", exception);
2199        else if (exception instanceof MissingLibraryException)
2200            return new UnsupportedFormatException(path + ": Missing library to load the image.", exception);
2201        else if (exception.getCause() instanceof ClosedByInterruptException)
2202            return new UnsupportedFormatException(path + ": loading interrupted.", exception.getCause());
2203        else
2204            return new UnsupportedFormatException(path + ": Unsupported image.", exception);
2205    }
2206}